From d9599fe03a9fcc886dd5bb76515786810353f2c3 Mon Sep 17 00:00:00 2001 From: Bennett Date: Thu, 12 Mar 2026 23:06:22 +0300 Subject: [PATCH 1/3] release: Improve login cookie setup --- build.gradle | 1 + .../core/config/RequestLoggingFilter.java | 62 +++++++++++++------ .../config/auth/AuthCookieProperties.java | 2 +- .../flextuma/core/entities/auth/Role.java | 2 +- .../flextuma/core/entities/auth/User.java | 2 +- .../core/interceptors/AuditorAwareImpl.java | 8 ++- .../core/security/SecurityConfig.java | 1 - .../flextuma/core/services/BaseService.java | 4 +- .../auth/controllers/AuthController.java | 31 +++++++++- .../core/services/BaseServiceTest.java | 2 +- .../auth/controllers/AuthControllerTest.java | 1 + .../auth/services/RoleServiceTest.java | 5 ++ 12 files changed, 94 insertions(+), 27 deletions(-) diff --git a/build.gradle b/build.gradle index 3c2f1e3..8b135c8 100644 --- a/build.gradle +++ b/build.gradle @@ -44,6 +44,7 @@ dependencies { annotationProcessor 'org.projectlombok:lombok' providedRuntime 'org.springframework.boot:spring-boot-starter-tomcat-runtime' testImplementation 'org.springframework.boot:spring-boot-starter-test' + testImplementation 'org.springframework.boot:spring-boot-starter-webmvc-test' testImplementation 'org.springframework.security:spring-security-test' testRuntimeOnly 'com.h2database:h2' testRuntimeOnly 'org.junit.platform:junit-platform-launcher' diff --git a/src/main/java/com/flexcodelabs/flextuma/core/config/RequestLoggingFilter.java b/src/main/java/com/flexcodelabs/flextuma/core/config/RequestLoggingFilter.java index 2306435..171cae9 100644 --- a/src/main/java/com/flexcodelabs/flextuma/core/config/RequestLoggingFilter.java +++ b/src/main/java/com/flexcodelabs/flextuma/core/config/RequestLoggingFilter.java @@ -6,6 +6,8 @@ import jakarta.servlet.http.HttpServletResponse; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import org.springframework.core.Ordered; +import org.springframework.core.annotation.Order; import org.springframework.security.core.Authentication; import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.stereotype.Component; @@ -14,10 +16,16 @@ import java.io.IOException; @Component +@Order(Ordered.HIGHEST_PRECEDENCE) public class RequestLoggingFilter extends OncePerRequestFilter { private static final Logger log = LoggerFactory.getLogger("FLEXTUMA"); + @Override + protected boolean shouldNotFilterErrorDispatch() { + return false; + } + @Override protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException { @@ -30,31 +38,47 @@ protected void doFilterInternal(HttpServletRequest request, HttpServletResponse try { filterChain.doFilter(request, response); + } catch (Exception ex) { + logRequest(request, response, fullUri, startTime, 500, ex); + throw ex; } finally { - String username = getUsername(); - long duration = System.currentTimeMillis() - startTime; - int status = response.getStatus(); + // Only log in finally if we haven't already logged via the catch block + // OR we rely on the response status. Best is to extract the logging logic + // into a helper method. + if (request.getAttribute("REQUEST_LOGGED") == null) { + logRequest(request, response, fullUri, startTime, response.getStatus(), null); + } + } + } + + private void logRequest(HttpServletRequest request, HttpServletResponse response, String fullUri, long startTime, + int statusOverride, Exception ex) { + request.setAttribute("REQUEST_LOGGED", true); + String username = getUsername(); + long duration = System.currentTimeMillis() - startTime; - boolean isError = status >= 400; + int status = statusOverride > 0 ? statusOverride : response.getStatus(); + boolean isError = status >= 400 || ex != null; - String logColor = isError ? "\u001B[31m" : "\u001B[32m"; - String reset = "\u001B[0m"; + String logColor = isError ? "\u001B[31m" : "\u001B[32m"; + String reset = "\u001B[0m"; - String statusLog = logColor + (isError ? "ERROR" : "LOG") + reset; - String userInfo = "\u001B[33m[" + username + "]\u001B[0m"; - String coloredMethod = logColor + request.getMethod() + reset; - String coloredUri = logColor + fullUri + reset; + String statusLog = logColor + (isError ? "ERROR" : "LOG") + reset; + String userInfo = "\u001B[33m[" + username + "]\u001B[0m"; + String coloredMethod = logColor + request.getMethod() + reset; + String coloredUri = logColor + fullUri + reset; - org.slf4j.MDC.put("username", username); - try { - if (isError) { - log.error("{} {} {} {} {}ms", statusLog, userInfo, coloredMethod, coloredUri, duration); - } else { - log.info("{} {} {} {} {}ms", statusLog, userInfo, coloredMethod, coloredUri, duration); - } - } finally { - org.slf4j.MDC.remove("username"); + org.slf4j.MDC.put("username", username); + try { + if (isError) { + log.error("{} {} {} {} {}ms - Status: {}", statusLog, userInfo, coloredMethod, coloredUri, duration, + status); + } else { + log.info("{} {} {} {} {}ms - Status: {}", statusLog, userInfo, coloredMethod, coloredUri, duration, + status); } + } finally { + org.slf4j.MDC.remove("username"); } } diff --git a/src/main/java/com/flexcodelabs/flextuma/core/config/auth/AuthCookieProperties.java b/src/main/java/com/flexcodelabs/flextuma/core/config/auth/AuthCookieProperties.java index adfa114..41ca296 100644 --- a/src/main/java/com/flexcodelabs/flextuma/core/config/auth/AuthCookieProperties.java +++ b/src/main/java/com/flexcodelabs/flextuma/core/config/auth/AuthCookieProperties.java @@ -13,7 +13,7 @@ public class AuthCookieProperties { private String name = "SESSION"; private long maxAge = 3600; - private boolean secure = true; + private boolean secure = false; private String sameSite = "Lax"; private String path = "/"; } \ No newline at end of file diff --git a/src/main/java/com/flexcodelabs/flextuma/core/entities/auth/Role.java b/src/main/java/com/flexcodelabs/flextuma/core/entities/auth/Role.java index e70b194..8c928a3 100644 --- a/src/main/java/com/flexcodelabs/flextuma/core/entities/auth/Role.java +++ b/src/main/java/com/flexcodelabs/flextuma/core/entities/auth/Role.java @@ -34,7 +34,7 @@ public class Role extends BaseEntity { @ManyToMany(fetch = FetchType.LAZY) @JoinTable(name = "userprivilege", joinColumns = @JoinColumn(name = "role", referencedColumnName = "id"), inverseJoinColumns = @JoinColumn(name = "privilege", referencedColumnName = "id")) - private Set privileges; + private Set privileges = new java.util.HashSet<>(); @PrePersist public void ensureSystemValue() { diff --git a/src/main/java/com/flexcodelabs/flextuma/core/entities/auth/User.java b/src/main/java/com/flexcodelabs/flextuma/core/entities/auth/User.java index 9358e04..42fc914 100644 --- a/src/main/java/com/flexcodelabs/flextuma/core/entities/auth/User.java +++ b/src/main/java/com/flexcodelabs/flextuma/core/entities/auth/User.java @@ -87,7 +87,7 @@ public class User extends BaseEntity { @ManyToMany(fetch = FetchType.LAZY) @JoinTable(name = "userrole", joinColumns = @JoinColumn(name = "owner", referencedColumnName = "id"), inverseJoinColumns = @JoinColumn(name = "role", referencedColumnName = "id")) - private Set roles; + private Set roles = new java.util.HashSet<>(); @ManyToOne(fetch = FetchType.LAZY) @JoinColumn(name = "organisation", referencedColumnName = "id", nullable = true) diff --git a/src/main/java/com/flexcodelabs/flextuma/core/interceptors/AuditorAwareImpl.java b/src/main/java/com/flexcodelabs/flextuma/core/interceptors/AuditorAwareImpl.java index 321ea56..fd71e50 100644 --- a/src/main/java/com/flexcodelabs/flextuma/core/interceptors/AuditorAwareImpl.java +++ b/src/main/java/com/flexcodelabs/flextuma/core/interceptors/AuditorAwareImpl.java @@ -34,9 +34,15 @@ public Optional getCurrentAuditor() { } Object principal = authentication.getPrincipal(); + String username = null; if (principal instanceof UserDetails userDetails) { - String username = userDetails.getUsername(); + username = userDetails.getUsername(); + } else if (principal instanceof String principalString) { + username = principalString; + } + + if (username != null) { FlushModeType originalFlushMode = entityManager.getFlushMode(); try { entityManager.setFlushMode(FlushModeType.COMMIT); diff --git a/src/main/java/com/flexcodelabs/flextuma/core/security/SecurityConfig.java b/src/main/java/com/flexcodelabs/flextuma/core/security/SecurityConfig.java index 1fb500e..5d04cc2 100644 --- a/src/main/java/com/flexcodelabs/flextuma/core/security/SecurityConfig.java +++ b/src/main/java/com/flexcodelabs/flextuma/core/security/SecurityConfig.java @@ -39,7 +39,6 @@ public PasswordEncoder passwordEncoder() { @Bean public CookieSerializer cookieSerializer() { DefaultCookieSerializer serializer = new DefaultCookieSerializer(); - serializer.setCookieName("SESSION"); serializer.setUseHttpOnlyCookie(true); serializer.setSameSite("Lax"); return serializer; diff --git a/src/main/java/com/flexcodelabs/flextuma/core/services/BaseService.java b/src/main/java/com/flexcodelabs/flextuma/core/services/BaseService.java index 69a9422..9d09019 100644 --- a/src/main/java/com/flexcodelabs/flextuma/core/services/BaseService.java +++ b/src/main/java/com/flexcodelabs/flextuma/core/services/BaseService.java @@ -284,7 +284,9 @@ public Map delete(UUID id) { validateDelete(entity); - getRepository().deleteById(id); + getRepository().delete(entity); + entityManager.flush(); + entityManager.clear(); onPostDelete(id); eventPublisher.publishEvent(new EntityEvent<>(this, entity, EntityEvent.EntityEventType.DELETED)); diff --git a/src/main/java/com/flexcodelabs/flextuma/modules/auth/controllers/AuthController.java b/src/main/java/com/flexcodelabs/flextuma/modules/auth/controllers/AuthController.java index 0610477..0579d41 100644 --- a/src/main/java/com/flexcodelabs/flextuma/modules/auth/controllers/AuthController.java +++ b/src/main/java/com/flexcodelabs/flextuma/modules/auth/controllers/AuthController.java @@ -4,8 +4,12 @@ import org.springframework.http.ResponseCookie; import org.springframework.http.ResponseEntity; import org.springframework.security.authentication.AnonymousAuthenticationToken; +import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; import org.springframework.security.core.Authentication; +import org.springframework.security.core.authority.SimpleGrantedAuthority; +import org.springframework.security.core.context.SecurityContext; import org.springframework.security.core.context.SecurityContextHolder; +import org.springframework.security.web.context.HttpSessionSecurityContextRepository; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestBody; @@ -17,6 +21,8 @@ import com.flexcodelabs.flextuma.core.services.CookieService; import com.flexcodelabs.flextuma.modules.auth.services.UserService; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; import jakarta.validation.Valid; import lombok.RequiredArgsConstructor; @@ -29,8 +35,31 @@ public class AuthController { private final CookieService cookieService; @PostMapping("/login") - public ResponseEntity login(@Valid @RequestBody LoginDto request) { + public ResponseEntity login( + @Valid @RequestBody LoginDto request, + HttpServletRequest httpRequest, + HttpServletResponse httpResponse) { + User user = userService.login(request.getUsername(), request.getPassword()); + + java.util.Set authorities = user.getRoles() + .stream() + .flatMap(role -> role.getPrivileges().stream()) + .map(privilege -> new SimpleGrantedAuthority(privilege.getValue())) + .collect(java.util.stream.Collectors.toSet()); + + UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken( + user.getUsername(), null, authorities); + authentication.setDetails(new org.springframework.security.web.authentication.WebAuthenticationDetailsSource() + .buildDetails(httpRequest)); + + SecurityContext context = SecurityContextHolder.createEmptyContext(); + context.setAuthentication(authentication); + SecurityContextHolder.setContext(context); + + HttpSessionSecurityContextRepository securityContextRepository = new HttpSessionSecurityContextRepository(); + securityContextRepository.saveContext(context, httpRequest, httpResponse); + ResponseCookie cookie = cookieService.createAuthCookie(); return ResponseEntity.ok() .header(HttpHeaders.SET_COOKIE, cookie.toString()) diff --git a/src/test/java/com/flexcodelabs/flextuma/core/services/BaseServiceTest.java b/src/test/java/com/flexcodelabs/flextuma/core/services/BaseServiceTest.java index 226a983..97902e9 100644 --- a/src/test/java/com/flexcodelabs/flextuma/core/services/BaseServiceTest.java +++ b/src/test/java/com/flexcodelabs/flextuma/core/services/BaseServiceTest.java @@ -376,7 +376,7 @@ void delete_shouldDeleteById() { Map response = service.delete(id); assertEquals("test deleted successfully", response.get("message")); - verify(repository).deleteById(id); + verify(repository).delete(entity); } @SuppressWarnings("unchecked") diff --git a/src/test/java/com/flexcodelabs/flextuma/modules/auth/controllers/AuthControllerTest.java b/src/test/java/com/flexcodelabs/flextuma/modules/auth/controllers/AuthControllerTest.java index 47246e8..3e7a9a3 100644 --- a/src/test/java/com/flexcodelabs/flextuma/modules/auth/controllers/AuthControllerTest.java +++ b/src/test/java/com/flexcodelabs/flextuma/modules/auth/controllers/AuthControllerTest.java @@ -60,6 +60,7 @@ void login_shouldReturnUserAndSetCookie_whenCredentialsValid() throws Exception User user = new User(); user.setUsername("user"); + user.setRoles(new java.util.HashSet<>()); ResponseCookie cookie = ResponseCookie.from("SESSION", "token").build(); diff --git a/src/test/java/com/flexcodelabs/flextuma/modules/auth/services/RoleServiceTest.java b/src/test/java/com/flexcodelabs/flextuma/modules/auth/services/RoleServiceTest.java index 01f0094..4841130 100644 --- a/src/test/java/com/flexcodelabs/flextuma/modules/auth/services/RoleServiceTest.java +++ b/src/test/java/com/flexcodelabs/flextuma/modules/auth/services/RoleServiceTest.java @@ -9,6 +9,7 @@ import java.util.UUID; import java.util.List; +import jakarta.persistence.EntityManager; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; @@ -33,6 +34,9 @@ class RoleServiceTest { @Mock private RoleRepository repository; + @Mock + private EntityManager entityManager; + @Mock private SecurityContext securityContext; @@ -52,6 +56,7 @@ class RoleServiceTest { @BeforeEach void setUp() { service = new RoleService(repository); + ReflectionTestUtils.setField(service, "entityManager", entityManager); ReflectionTestUtils.setField(service, "eventPublisher", eventPublisher); ReflectionTestUtils.setField(service, "currentUserResolver", currentUserResolver); From b289d7f91782fc76b43295a6674b4b090aa046a3 Mon Sep 17 00:00:00 2001 From: Bennett Date: Sat, 14 Mar 2026 13:37:48 +0300 Subject: [PATCH 2/3] release: Get campaign content from template --- .../flextuma/FlextumaApplication.java | 2 + .../config/auth/AuthCookieProperties.java | 30 +++++--- .../core/entities/sms/SmsCampaign.java | 4 +- .../core/entities/sms/SmsConnector.java | 4 +- .../exceptions/GlobalExceptionHandler.java | 75 ++++++++++++++++++- .../flextuma/core/services/CookieService.java | 21 +++--- .../services/CampaignDispatchWorker.java | 4 +- .../core/services/CookieServiceTest.java | 22 +++--- .../auth/services/RoleServiceTest.java | 2 +- .../services/CampaignDispatchWorkerTest.java | 16 +++- 10 files changed, 138 insertions(+), 42 deletions(-) diff --git a/src/main/java/com/flexcodelabs/flextuma/FlextumaApplication.java b/src/main/java/com/flexcodelabs/flextuma/FlextumaApplication.java index fdd4949..73489aa 100644 --- a/src/main/java/com/flexcodelabs/flextuma/FlextumaApplication.java +++ b/src/main/java/com/flexcodelabs/flextuma/FlextumaApplication.java @@ -2,9 +2,11 @@ import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.boot.context.properties.ConfigurationPropertiesScan; @SpringBootApplication(excludeName = { "org.springframework.boot.autoconfigure.data.redis.RedisRepositoriesAutoConfiguration" }) +@ConfigurationPropertiesScan @org.springframework.boot.persistence.autoconfigure.EntityScan(basePackages = "com.flexcodelabs.flextuma.core.entities") @org.springframework.data.jpa.repository.config.EnableJpaRepositories(basePackages = "com.flexcodelabs.flextuma.core.repositories") @org.springframework.data.jpa.repository.config.EnableJpaAuditing(auditorAwareRef = "auditorProvider") diff --git a/src/main/java/com/flexcodelabs/flextuma/core/config/auth/AuthCookieProperties.java b/src/main/java/com/flexcodelabs/flextuma/core/config/auth/AuthCookieProperties.java index 41ca296..e7ba186 100644 --- a/src/main/java/com/flexcodelabs/flextuma/core/config/auth/AuthCookieProperties.java +++ b/src/main/java/com/flexcodelabs/flextuma/core/config/auth/AuthCookieProperties.java @@ -1,19 +1,25 @@ package com.flexcodelabs.flextuma.core.config.auth; +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotNull; import org.springframework.boot.context.properties.ConfigurationProperties; -import org.springframework.context.annotation.Configuration; +import org.springframework.boot.context.properties.bind.DefaultValue; +import org.springframework.validation.annotation.Validated; -import lombok.Getter; -import lombok.Setter; +import java.time.Duration; -@Configuration +@Validated @ConfigurationProperties(prefix = "auth.cookie") -@Getter -@Setter -public class AuthCookieProperties { - private String name = "SESSION"; - private long maxAge = 3600; - private boolean secure = false; - private String sameSite = "Lax"; - private String path = "/"; +public record AuthCookieProperties( + @NotBlank @DefaultValue("SESSION") String name, + + @NotNull @DefaultValue("3600s") Duration maxAge, + + @DefaultValue("true") boolean secure, + + @DefaultValue("true") boolean httpOnly, + + @NotBlank @DefaultValue("Strict") String sameSite, + + @NotBlank @DefaultValue("/") String path) { } \ No newline at end of file diff --git a/src/main/java/com/flexcodelabs/flextuma/core/entities/sms/SmsCampaign.java b/src/main/java/com/flexcodelabs/flextuma/core/entities/sms/SmsCampaign.java index 3f6fc22..0c2d870 100644 --- a/src/main/java/com/flexcodelabs/flextuma/core/entities/sms/SmsCampaign.java +++ b/src/main/java/com/flexcodelabs/flextuma/core/entities/sms/SmsCampaign.java @@ -35,8 +35,8 @@ public class SmsCampaign extends Owner { @Column(nullable = false) private String name; - @Column(columnDefinition = "TEXT", nullable = false) - private String content; + @Column(columnDefinition = "TEXT", nullable = true) + private String description; @ManyToOne(fetch = FetchType.LAZY) @JoinColumn(name = "template_id") diff --git a/src/main/java/com/flexcodelabs/flextuma/core/entities/sms/SmsConnector.java b/src/main/java/com/flexcodelabs/flextuma/core/entities/sms/SmsConnector.java index 89c697d..3d73555 100644 --- a/src/main/java/com/flexcodelabs/flextuma/core/entities/sms/SmsConnector.java +++ b/src/main/java/com/flexcodelabs/flextuma/core/entities/sms/SmsConnector.java @@ -18,8 +18,8 @@ public class SmsConnector extends Owner { public static final String PLURAL = "connectors"; - public static final String NAME_PLURAL = "SmsConnectors"; - public static final String NAME_SINGULAR = "SmsConnector"; + public static final String NAME_PLURAL = "SMS Connectors"; + public static final String NAME_SINGULAR = "SMS Connector"; public static final String ALL = "ALL"; public static final String READ = ALL; diff --git a/src/main/java/com/flexcodelabs/flextuma/core/exceptions/GlobalExceptionHandler.java b/src/main/java/com/flexcodelabs/flextuma/core/exceptions/GlobalExceptionHandler.java index cf62300..e0941c7 100644 --- a/src/main/java/com/flexcodelabs/flextuma/core/exceptions/GlobalExceptionHandler.java +++ b/src/main/java/com/flexcodelabs/flextuma/core/exceptions/GlobalExceptionHandler.java @@ -38,13 +38,84 @@ public class GlobalExceptionHandler { @ExceptionHandler(HttpMessageNotReadableException.class) public ResponseEntity handleHttpMessageNotReadable(HttpMessageNotReadableException ex) { - String message = "The request body is missing or the JSON format is invalid."; + String defaultMessage = "The request body is missing or the JSON format is invalid."; if (ex.getMessage() != null && ex.getMessage().contains("Required request body is missing")) { - message = "Required request body is missing. Please provide the required data in the request body."; + defaultMessage = "Required request body is missing. Please provide the required data in the request body."; } + + Throwable mostSpecificCause = ex.getMostSpecificCause(); + String detailedMessage = mostSpecificCause != null ? mostSpecificCause.getMessage() : null; + + String message = defaultMessage; + + if (detailedMessage != null && !detailedMessage.isBlank()) { + String enumMessage = tryBuildEnumErrorMessage(detailedMessage); + if (enumMessage != null) { + message = enumMessage; + } else { + message = defaultMessage + " Details: " + detailedMessage; + } + } + return buildResponse(message, HttpStatus.BAD_REQUEST); } + private String tryBuildEnumErrorMessage(String detailedMessage) { + // Example detailedMessage: + // "JSON parse error: Cannot deserialize value of type + // `com.flexcodelabs.flextuma.core.enums.SmsCampaignStatus` from String \"Running\": + // not one of the values accepted for Enum class: [CANCELLED, COMPLETED, SCHEDULED, + // DRAFT, PROCESSING]" + + if (!detailedMessage.contains("not one of the values accepted for Enum class")) { + return null; + } + + String enumType = null; + String invalidValue = null; + String allowedValues = null; + + int typeStart = detailedMessage.indexOf("`"); + int typeEnd = detailedMessage.indexOf("`", typeStart + 1); + if (typeStart != -1 && typeEnd != -1 && typeEnd > typeStart + 1) { + enumType = detailedMessage.substring(typeStart + 1, typeEnd); + } + + String fromStringToken = "from String \""; + int valueStart = detailedMessage.indexOf(fromStringToken); + if (valueStart != -1) { + valueStart += fromStringToken.length(); + int valueEnd = detailedMessage.indexOf("\"", valueStart); + if (valueEnd != -1 && valueEnd > valueStart) { + invalidValue = detailedMessage.substring(valueStart, valueEnd); + } + } + + int valuesStart = detailedMessage.indexOf('['); + int valuesEnd = detailedMessage.indexOf(']', valuesStart + 1); + if (valuesStart != -1 && valuesEnd != -1 && valuesEnd > valuesStart + 1) { + allowedValues = detailedMessage.substring(valuesStart + 1, valuesEnd); + } + + if (allowedValues == null) { + return null; + } + + StringBuilder builder = new StringBuilder("Invalid value"); + if (invalidValue != null) { + builder.append(" '").append(invalidValue).append("'"); + } + builder.append(" for enum"); + if (enumType != null) { + int lastDot = enumType.lastIndexOf('.'); + String simpleName = lastDot != -1 ? enumType.substring(lastDot + 1) : enumType; + builder.append(" ").append(simpleName); + } + builder.append(". Allowed values are: [").append(allowedValues).append("]."); + + return builder.toString(); + } + @ExceptionHandler(ResponseStatusException.class) public ResponseEntity handleResponseStatusException(ResponseStatusException ex) { HttpStatus status = HttpStatus.resolve(ex.getStatusCode().value()); diff --git a/src/main/java/com/flexcodelabs/flextuma/core/services/CookieService.java b/src/main/java/com/flexcodelabs/flextuma/core/services/CookieService.java index 6e40646..f08fc8b 100644 --- a/src/main/java/com/flexcodelabs/flextuma/core/services/CookieService.java +++ b/src/main/java/com/flexcodelabs/flextuma/core/services/CookieService.java @@ -18,21 +18,22 @@ public ResponseCookie createAuthCookie() { String token = TokenGenerator.generateSecureToken(32); - return ResponseCookie.from(properties.getName(), token) - .httpOnly(true) - .secure(properties.isSecure()) - .path(properties.getPath()) - .maxAge(properties.getMaxAge()) - .sameSite(properties.getSameSite()) + return ResponseCookie.from(properties.name(), token) + .httpOnly(properties.httpOnly()) + .secure(properties.secure()) + .path(properties.path()) + .maxAge(properties.maxAge()) + .sameSite(properties.sameSite()) .build(); } public ResponseCookie deleteAuthCookie() { - return ResponseCookie.from(properties.getName(), "") - .httpOnly(true) - .secure(properties.isSecure()) - .path(properties.getPath()) + return ResponseCookie.from(properties.name(), "") + .httpOnly(properties.httpOnly()) + .secure(properties.secure()) + .path(properties.path()) .maxAge(0) + .sameSite(properties.sameSite()) .build(); } } \ No newline at end of file diff --git a/src/main/java/com/flexcodelabs/flextuma/modules/notification/services/CampaignDispatchWorker.java b/src/main/java/com/flexcodelabs/flextuma/modules/notification/services/CampaignDispatchWorker.java index a5c51d2..2bdb578 100644 --- a/src/main/java/com/flexcodelabs/flextuma/modules/notification/services/CampaignDispatchWorker.java +++ b/src/main/java/com/flexcodelabs/flextuma/modules/notification/services/CampaignDispatchWorker.java @@ -68,7 +68,7 @@ private void processSingleCampaign(SmsCampaign campaign) { String[] recipients = recipientsStr.split(","); log.info("Processing campaign [{}] for {} recipients", campaign.getName(), recipients.length); - SmsSegmentResult segmentResult = segmentCalculator.calculate(campaign.getContent()); + SmsSegmentResult segmentResult = segmentCalculator.calculate(campaign.getTemplate().getContent()); BigDecimal costPerSms = pricePerSegment.multiply(BigDecimal.valueOf(segmentResult.segments())); for (String recipient : recipients) { @@ -90,7 +90,7 @@ private void dispatchToRecipient(SmsCampaign campaign, String recipient, BigDeci SmsLog smsLog = new SmsLog(); smsLog.setRecipient(recipient); - smsLog.setContent(campaign.getContent()); + smsLog.setContent(campaign.getTemplate().getContent()); smsLog.setTemplate(campaign.getTemplate()); smsLog.setConnector(campaign.getConnector()); smsLog.setStatus(SmsLogStatus.PENDING); diff --git a/src/test/java/com/flexcodelabs/flextuma/core/services/CookieServiceTest.java b/src/test/java/com/flexcodelabs/flextuma/core/services/CookieServiceTest.java index 31fd6a0..0f2b443 100644 --- a/src/test/java/com/flexcodelabs/flextuma/core/services/CookieServiceTest.java +++ b/src/test/java/com/flexcodelabs/flextuma/core/services/CookieServiceTest.java @@ -27,11 +27,12 @@ void setUp() { @Test void createAuthCookie_shouldReturnValidCookie() { - when(properties.getName()).thenReturn("SESSION"); - when(properties.getPath()).thenReturn("/"); - when(properties.getMaxAge()).thenReturn(3600L); - when(properties.isSecure()).thenReturn(true); - when(properties.getSameSite()).thenReturn("Lax"); + when(properties.name()).thenReturn("SESSION"); + when(properties.path()).thenReturn("/"); + when(properties.secure()).thenReturn(true); + when(properties.httpOnly()).thenReturn(true); + when(properties.maxAge()).thenReturn(java.time.Duration.ofSeconds(3600)); + when(properties.sameSite()).thenReturn("Strict"); ResponseCookie cookie = service.createAuthCookie(); @@ -41,15 +42,17 @@ void createAuthCookie_shouldReturnValidCookie() { assertEquals("/", cookie.getPath()); assertEquals(3600, cookie.getMaxAge().getSeconds()); assertTrue(cookie.isSecure()); - assertEquals("Lax", cookie.getSameSite()); + assertEquals("Strict", cookie.getSameSite()); assertTrue(cookie.isHttpOnly()); } @Test void deleteAuthCookie_shouldReturnEmptyCookie() { - when(properties.getName()).thenReturn("SESSION"); - when(properties.getPath()).thenReturn("/"); - when(properties.isSecure()).thenReturn(true); + when(properties.name()).thenReturn("SESSION"); + when(properties.path()).thenReturn("/"); + when(properties.secure()).thenReturn(true); + when(properties.httpOnly()).thenReturn(true); + when(properties.sameSite()).thenReturn("Strict"); ResponseCookie cookie = service.deleteAuthCookie(); @@ -59,6 +62,7 @@ void deleteAuthCookie_shouldReturnEmptyCookie() { assertEquals("/", cookie.getPath()); assertEquals(0, cookie.getMaxAge().getSeconds()); assertTrue(cookie.isSecure()); + assertEquals("Strict", cookie.getSameSite()); assertTrue(cookie.isHttpOnly()); } } diff --git a/src/test/java/com/flexcodelabs/flextuma/modules/auth/services/RoleServiceTest.java b/src/test/java/com/flexcodelabs/flextuma/modules/auth/services/RoleServiceTest.java index 4841130..dbed0b2 100644 --- a/src/test/java/com/flexcodelabs/flextuma/modules/auth/services/RoleServiceTest.java +++ b/src/test/java/com/flexcodelabs/flextuma/modules/auth/services/RoleServiceTest.java @@ -96,7 +96,7 @@ void delete_shouldDelete_whenRoleIsNotSystem() { service.delete(id); - verify(repository).deleteById(id); + verify(repository).delete(role); } private void mockPermissions(Set permissions) { diff --git a/src/test/java/com/flexcodelabs/flextuma/modules/notification/services/CampaignDispatchWorkerTest.java b/src/test/java/com/flexcodelabs/flextuma/modules/notification/services/CampaignDispatchWorkerTest.java index 34b6e01..c3d3904 100644 --- a/src/test/java/com/flexcodelabs/flextuma/modules/notification/services/CampaignDispatchWorkerTest.java +++ b/src/test/java/com/flexcodelabs/flextuma/modules/notification/services/CampaignDispatchWorkerTest.java @@ -3,6 +3,8 @@ import com.flexcodelabs.flextuma.core.entities.auth.User; import com.flexcodelabs.flextuma.core.entities.sms.SmsCampaign; import com.flexcodelabs.flextuma.core.entities.sms.SmsLog; +import com.flexcodelabs.flextuma.core.entities.sms.SmsTemplate; +import com.flexcodelabs.flextuma.core.entities.sms.SmsConnector; import com.flexcodelabs.flextuma.core.enums.SmsCampaignStatus; import com.flexcodelabs.flextuma.core.helpers.SmsSegmentCalculator; import com.flexcodelabs.flextuma.core.helpers.SmsSegmentResult; @@ -24,6 +26,7 @@ import java.util.List; import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.*; @@ -66,7 +69,11 @@ void processCampaigns_withDueCampaigns_shouldProcessThem() { SmsCampaign campaign = new SmsCampaign(); campaign.setName("Test Campaign"); campaign.setRecipients("255700112233, 255700445566"); - campaign.setContent("Hello world"); + SmsTemplate template = new SmsTemplate(); + template.setContent("Hello world"); + campaign.setTemplate(template); + SmsConnector connector = new SmsConnector(); + campaign.setConnector(connector); User adminUser = new User(); adminUser.setUsername("admin"); campaign.setCreatedBy(adminUser); @@ -75,7 +82,7 @@ void processCampaigns_withDueCampaigns_shouldProcessThem() { any(Pageable.class))) .thenReturn(List.of(campaign)); - when(segmentCalculator.calculate(anyString())).thenReturn(new SmsSegmentResult(1, true, 11, 149)); + when(segmentCalculator.calculate(any())).thenReturn(new SmsSegmentResult(1, true, 11, 149)); worker.processCampaigns(); @@ -105,6 +112,11 @@ void processCampaigns_withEmptyRecipients_shouldCompleteImmediately() { void processCampaigns_whenDebitFails_shouldContinueProcessing() { SmsCampaign campaign = new SmsCampaign(); campaign.setRecipients("255700112233"); + SmsTemplate template = new SmsTemplate(); + template.setContent("Hello"); + campaign.setTemplate(template); + SmsConnector connector = new SmsConnector(); + campaign.setConnector(connector); User user1 = new User(); user1.setUsername("user1"); campaign.setCreatedBy(user1); From 7297bd92c97a7131b1cf8ccd1a685a841aeb21f2 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Sat, 14 Mar 2026 10:38:25 +0000 Subject: [PATCH 3/3] Release v0.0.4 [skip ci] --- build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index cf43854..f1ce337 100644 --- a/build.gradle +++ b/build.gradle @@ -8,7 +8,7 @@ plugins { } group = 'com.flexcodelabs' -version = '0.0.3' +version = '0.0.4' description = 'Flextuma App' java {