From 4dda3a1be9d1f6ab5c9c03ea484619bc6ca98f6a Mon Sep 17 00:00:00 2001 From: FranInfanti Date: Mon, 18 May 2026 17:36:41 -0300 Subject: [PATCH 1/7] feat: add user metrics dashboard endpoint --- .../user/controller/AdminController.java | 22 +++++++++++++++++++ .../user/dto/admin/UserStatsResponseDTO.java | 9 ++++++++ .../user/repository/UserRepository.java | 10 +++++++++ .../com/bazaar/user/service/AdminService.java | 17 ++++++++++++++ 4 files changed, 58 insertions(+) create mode 100644 src/main/java/com/bazaar/user/dto/admin/UserStatsResponseDTO.java diff --git a/src/main/java/com/bazaar/user/controller/AdminController.java b/src/main/java/com/bazaar/user/controller/AdminController.java index f4c9e29..8e26183 100644 --- a/src/main/java/com/bazaar/user/controller/AdminController.java +++ b/src/main/java/com/bazaar/user/controller/AdminController.java @@ -2,6 +2,7 @@ import com.bazaar.user.common.Role; import com.bazaar.user.dto.admin.UserDetailsPageResponseDTO; +import com.bazaar.user.dto.admin.UserStatsResponseDTO; import com.bazaar.user.service.AdminService; import io.swagger.v3.oas.annotations.Operation; @@ -96,4 +97,25 @@ public void unblockUser(@RequestHeader("X-User-Role") Role role, @PathVariable L adminService.unblockUser(role, unblockUserId); } + /** + * Handles HTTP GET requests to fetch historical user registration rates. + * @param role Role of the user making the request, needs to be administrator. + * @return A {@link UserStatsResponseDTO} containing the computed registration values. + */ + @Operation(summary = "Obtains the users stats", + description = "Obtains the users registration rates as a system administrator") + @ApiResponses(value = { + @ApiResponse(responseCode = "200", description = "Users stats obtained successfully", + content = @Content(mediaType = MediaType.APPLICATION_JSON_VALUE, + schema = @Schema(implementation = UserDetailsPageResponseDTO.class))), + @ApiResponse(responseCode = "403", description = "User Forbidden", + content = @Content(mediaType = MediaType.APPLICATION_PROBLEM_JSON_VALUE, + schema = @Schema(implementation = ErrorResponse.class))) }) + @GetMapping(value = "/users/stats", produces = MediaType.APPLICATION_JSON_VALUE) + @ResponseStatus(HttpStatus.OK) + public UserStatsResponseDTO getUsersStats(@RequestHeader("X-User-Role") Role role, + @RequestParam(defaultValue = "30") Integer period) { + return adminService.getUsersStats(role, period); + } + } diff --git a/src/main/java/com/bazaar/user/dto/admin/UserStatsResponseDTO.java b/src/main/java/com/bazaar/user/dto/admin/UserStatsResponseDTO.java new file mode 100644 index 0000000..13a7d65 --- /dev/null +++ b/src/main/java/com/bazaar/user/dto/admin/UserStatsResponseDTO.java @@ -0,0 +1,9 @@ +package com.bazaar.user.dto.admin; + +/** + * Data Transfer Object (DTO) representing user registration metrics. + * + * @param totalUsers The total number of registered users in the system. + */ +public record UserStatsResponseDTO(Long totalUsers) { +} \ No newline at end of file diff --git a/src/main/java/com/bazaar/user/repository/UserRepository.java b/src/main/java/com/bazaar/user/repository/UserRepository.java index 1a83d31..17a8f98 100644 --- a/src/main/java/com/bazaar/user/repository/UserRepository.java +++ b/src/main/java/com/bazaar/user/repository/UserRepository.java @@ -7,6 +7,7 @@ import org.springframework.data.jpa.repository.Query; import org.springframework.data.repository.query.Param; +import java.time.LocalDateTime; import java.util.Optional; /** @@ -45,4 +46,13 @@ public interface UserRepository extends JpaRepository { """) Page findByNameAndEmail(@Param("name") String name, @Param("email") String email, Pageable pageable); + /** + * Counts the total number of users registered after a specific timestamp. This + * execution maps to a database-level count operation avoiding entity memory + * allocation. + * @param startDate The lower boundary timestamp to filter registrations (exclusive). + * @return The count of users registered within the designated timeframe. + */ + long countByCreatedAtAfter(LocalDateTime startDate); + } diff --git a/src/main/java/com/bazaar/user/service/AdminService.java b/src/main/java/com/bazaar/user/service/AdminService.java index c81aeb2..e84396d 100644 --- a/src/main/java/com/bazaar/user/service/AdminService.java +++ b/src/main/java/com/bazaar/user/service/AdminService.java @@ -4,6 +4,7 @@ import com.bazaar.user.common.Role; import com.bazaar.user.common.Status; import com.bazaar.user.dto.admin.UserDetailsPageResponseDTO; +import com.bazaar.user.dto.admin.UserStatsResponseDTO; import com.bazaar.user.dto.catalog.ProductUpdateStatusRequestDTO; import com.bazaar.user.exception.UserBlockedException; import com.bazaar.user.exception.UserForbiddenException; @@ -21,6 +22,7 @@ import org.springframework.data.domain.Pageable; import org.springframework.stereotype.Service; +import java.time.LocalDateTime; import java.util.Locale; import java.util.Objects; @@ -119,4 +121,19 @@ public void unblockUser(Role role, Long unblockUserId) { catalogClient.modifyProductStatus(unblockUserId, new ProductUpdateStatusRequestDTO(true)); } + /** + * Retrieves a consolidated view of user metrics covering absolute historical + * registrations alongside 7, 30, and 90-day intervals. + * @param role The security role executing the request to enforce privilege + * boundaries. + * @return A {@link UserStatsResponseDTO} containing the computed registration values. + */ + public UserStatsResponseDTO getUsersStats(Role role, Integer days) { + checkPrivileges(role); + + LocalDateTime startDate = LocalDateTime.now().minusDays(days); + + return new UserStatsResponseDTO(userRepository.countByCreatedAtAfter(startDate)); + } + } From 047bac59faf2de8f30333bc4f34c46e535651b0a Mon Sep 17 00:00:00 2001 From: FranInfanti Date: Mon, 18 May 2026 23:31:09 -0300 Subject: [PATCH 2/7] feat: add growth rate to total users --- .../user/dto/admin/UserStatsResponseDTO.java | 5 ++- .../user/repository/UserRepository.java | 22 ++++++++---- .../com/bazaar/user/service/AdminService.java | 34 +++++++++++++++---- 3 files changed, 47 insertions(+), 14 deletions(-) diff --git a/src/main/java/com/bazaar/user/dto/admin/UserStatsResponseDTO.java b/src/main/java/com/bazaar/user/dto/admin/UserStatsResponseDTO.java index 13a7d65..acec6dd 100644 --- a/src/main/java/com/bazaar/user/dto/admin/UserStatsResponseDTO.java +++ b/src/main/java/com/bazaar/user/dto/admin/UserStatsResponseDTO.java @@ -4,6 +4,9 @@ * Data Transfer Object (DTO) representing user registration metrics. * * @param totalUsers The total number of registered users in the system. + * @param activeUsers The total number of active users in the system. + * @param blockedUsers The total number of blocked users in the system. + * @param growthRate The rate growth or decline compared to the previous identical period. */ -public record UserStatsResponseDTO(Long totalUsers) { +public record UserStatsResponseDTO(Long totalUsers, Long activeUsers, Long blockedUsers, double growthRate) { } \ No newline at end of file diff --git a/src/main/java/com/bazaar/user/repository/UserRepository.java b/src/main/java/com/bazaar/user/repository/UserRepository.java index 17a8f98..88d1b5c 100644 --- a/src/main/java/com/bazaar/user/repository/UserRepository.java +++ b/src/main/java/com/bazaar/user/repository/UserRepository.java @@ -1,5 +1,6 @@ package com.bazaar.user.repository; +import com.bazaar.user.common.Status; import com.bazaar.user.model.User; import org.springframework.data.domain.Page; import org.springframework.data.domain.Pageable; @@ -47,12 +48,21 @@ public interface UserRepository extends JpaRepository { Page findByNameAndEmail(@Param("name") String name, @Param("email") String email, Pageable pageable); /** - * Counts the total number of users registered after a specific timestamp. This - * execution maps to a database-level count operation avoiding entity memory - * allocation. - * @param startDate The lower boundary timestamp to filter registrations (exclusive). - * @return The count of users registered within the designated timeframe. + * Counts users registered within a specific time range. + * @param start The start timestamp (exclusive). + * @param end The end timestamp (inclusive). + * @return The total count of matching users. */ - long countByCreatedAtAfter(LocalDateTime startDate); + long countByCreatedAtAfterAndCreatedAtBefore(LocalDateTime start, LocalDateTime end); + + /** + * Counts users registered within a specific time range filtered by their current + * status. + * @param start The start timestamp (exclusive). + * @param status The user status filter. + * @param end The end timestamp (inclusive). + * @return The count of matching users by status. + */ + long countByCreatedAtAfterAndCreatedAtBeforeAndStatus(LocalDateTime start, LocalDateTime end, Status status); } diff --git a/src/main/java/com/bazaar/user/service/AdminService.java b/src/main/java/com/bazaar/user/service/AdminService.java index e84396d..9fb75d5 100644 --- a/src/main/java/com/bazaar/user/service/AdminService.java +++ b/src/main/java/com/bazaar/user/service/AdminService.java @@ -122,18 +122,38 @@ public void unblockUser(Role role, Long unblockUserId) { } /** - * Retrieves a consolidated view of user metrics covering absolute historical - * registrations alongside 7, 30, and 90-day intervals. - * @param role The security role executing the request to enforce privilege - * boundaries. - * @return A {@link UserStatsResponseDTO} containing the computed registration values. + * Retrieves user metrics filtered by status for a given period, including + * period-over-period growth. + * @param role The role context of the request issuer. + * @param days The number of days defining the current analytical period. + * @return A {@link UserStatsResponseDTO} containing absolute stats and percentage + * trends. */ public UserStatsResponseDTO getUsersStats(Role role, Integer days) { checkPrivileges(role); - LocalDateTime startDate = LocalDateTime.now().minusDays(days); + LocalDateTime now = LocalDateTime.now(); + LocalDateTime startDate = now.minusDays(days); + LocalDateTime previousStartDate = startDate.minusDays(days); - return new UserStatsResponseDTO(userRepository.countByCreatedAtAfter(startDate)); + long totalUsers = userRepository.countByCreatedAtAfterAndCreatedAtBefore(startDate, now); + long activeUsers = userRepository.countByCreatedAtAfterAndCreatedAtBeforeAndStatus(startDate, now, + Status.ACTIVE); + long blockedUsers = userRepository.countByCreatedAtAfterAndCreatedAtBeforeAndStatus(startDate, now, + Status.BLOCKED); + + long previousTotalUsers = userRepository.countByCreatedAtAfterAndCreatedAtBefore(previousStartDate, startDate); + + double growthRate = 0.0; + if (previousTotalUsers > 0) { + growthRate = ((double) (totalUsers - previousTotalUsers) / previousTotalUsers) * 100.0; + growthRate = Math.round(growthRate * 100.0) / 100.0; + } + else if (totalUsers > 0) { + growthRate = 100.0; + } + + return new UserStatsResponseDTO(totalUsers, activeUsers, blockedUsers, growthRate); } } From 38170173a5078e19b5da2716470150b660ae384f Mon Sep 17 00:00:00 2001 From: FranInfanti Date: Thu, 21 May 2026 12:27:20 -0300 Subject: [PATCH 3/7] refactor: refactor code --- .../com/bazaar/user/client/CatalogClient.java | 1 - .../config/global/GlobalExceptionHandler.java | 2 -- .../user/controller/AuthController.java | 1 - .../com/bazaar/user/service/AdminService.java | 1 - .../user/service/CloudinaryService.java | 4 +-- .../user/controller/AdminControllerTest.java | 2 +- .../user/controller/AuthControllerTest.java | 5 --- .../user/controller/UserControllerTest.java | 32 +++++++++---------- .../bazaar/user/testConfig/AbstractTest.java | 6 ++-- 9 files changed, 22 insertions(+), 32 deletions(-) diff --git a/src/main/java/com/bazaar/user/client/CatalogClient.java b/src/main/java/com/bazaar/user/client/CatalogClient.java index d25009c..1b4f673 100644 --- a/src/main/java/com/bazaar/user/client/CatalogClient.java +++ b/src/main/java/com/bazaar/user/client/CatalogClient.java @@ -1,7 +1,6 @@ package com.bazaar.user.client; import com.bazaar.user.dto.catalog.ProductUpdateStatusRequestDTO; -import org.springframework.web.bind.annotation.PatchMapping; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestHeader; import org.springframework.web.service.annotation.HttpExchange; diff --git a/src/main/java/com/bazaar/user/config/global/GlobalExceptionHandler.java b/src/main/java/com/bazaar/user/config/global/GlobalExceptionHandler.java index 33430aa..29e241e 100644 --- a/src/main/java/com/bazaar/user/config/global/GlobalExceptionHandler.java +++ b/src/main/java/com/bazaar/user/config/global/GlobalExceptionHandler.java @@ -24,8 +24,6 @@ import org.springframework.web.bind.MissingRequestHeaderException; import org.springframework.web.bind.annotation.ExceptionHandler; import org.springframework.web.bind.annotation.RestControllerAdvice; -import org.springframework.web.client.ResourceAccessException; -import org.springframework.web.client.RestClientResponseException; import org.springframework.web.method.annotation.MethodArgumentTypeMismatchException; import org.springframework.web.multipart.MultipartException; import org.springframework.web.multipart.support.MissingServletRequestPartException; diff --git a/src/main/java/com/bazaar/user/controller/AuthController.java b/src/main/java/com/bazaar/user/controller/AuthController.java index b8b942a..6d4350e 100644 --- a/src/main/java/com/bazaar/user/controller/AuthController.java +++ b/src/main/java/com/bazaar/user/controller/AuthController.java @@ -2,7 +2,6 @@ import com.bazaar.user.apiResponse.ErrorResponse; import com.bazaar.user.apiResponse.swaggerResponses.AuthResponse; -import com.bazaar.user.common.Role; import com.bazaar.user.common.Source; import com.bazaar.user.dto.auth.AuthResponseDTO; import com.bazaar.user.dto.auth.ForgotPasswordRequestDTO; diff --git a/src/main/java/com/bazaar/user/service/AdminService.java b/src/main/java/com/bazaar/user/service/AdminService.java index 9fb75d5..6ddd1c0 100644 --- a/src/main/java/com/bazaar/user/service/AdminService.java +++ b/src/main/java/com/bazaar/user/service/AdminService.java @@ -6,7 +6,6 @@ import com.bazaar.user.dto.admin.UserDetailsPageResponseDTO; import com.bazaar.user.dto.admin.UserStatsResponseDTO; import com.bazaar.user.dto.catalog.ProductUpdateStatusRequestDTO; -import com.bazaar.user.exception.UserBlockedException; import com.bazaar.user.exception.UserForbiddenException; import com.bazaar.user.exception.UserNotFoundException; import com.bazaar.user.exception.UserUnblockedException; diff --git a/src/main/java/com/bazaar/user/service/CloudinaryService.java b/src/main/java/com/bazaar/user/service/CloudinaryService.java index 0b1b024..2946831 100644 --- a/src/main/java/com/bazaar/user/service/CloudinaryService.java +++ b/src/main/java/com/bazaar/user/service/CloudinaryService.java @@ -44,8 +44,8 @@ public String upload(MultipartFile file) { messageSource.getMessage("exception.cloudinary.NOT_ALLOWED_TYPE", null, Locale.getDefault())); try { - Map result = cloudinary.uploader().upload(file.getBytes(), ObjectUtils.emptyMap()); - return (String) result.get("secure_url"); + Map result = cloudinary.uploader().upload(file.getBytes(), ObjectUtils.emptyMap()); + return result.get("secure_url").toString(); } catch (Exception e) { throw new ExternalServiceException( diff --git a/src/test/java/com/bazaar/user/controller/AdminControllerTest.java b/src/test/java/com/bazaar/user/controller/AdminControllerTest.java index 65dc9a5..d2f0d8e 100644 --- a/src/test/java/com/bazaar/user/controller/AdminControllerTest.java +++ b/src/test/java/com/bazaar/user/controller/AdminControllerTest.java @@ -545,7 +545,7 @@ void testBlockedUserCannotUpdateHisPrivateProfile() { var blockResponse = blockUser(2L, Role.ADMIN, blockUserId.longValue()); assertEquals(HttpStatus.NO_CONTENT, blockResponse.getStatusCode()); - MultiValueMap body = userUpdate("Johnny", "Phillip Doe", + MultiValueMap body = userUpdate("Johnny", "Phillip Doe", "Hi! I'm Johnny Phillip Doe, my email is JohnDoe@gmail.com", "static/images/image.jpg", MediaType.IMAGE_JPEG); diff --git a/src/test/java/com/bazaar/user/controller/AuthControllerTest.java b/src/test/java/com/bazaar/user/controller/AuthControllerTest.java index 5f1c56b..82f27e9 100644 --- a/src/test/java/com/bazaar/user/controller/AuthControllerTest.java +++ b/src/test/java/com/bazaar/user/controller/AuthControllerTest.java @@ -12,10 +12,7 @@ import com.bazaar.user.testConfig.AbstractTest; import com.jayway.jsonpath.JsonPath; -import jakarta.mail.Session; -import jakarta.mail.internet.MimeMessage; import org.junit.jupiter.api.Assertions; -import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.mockito.ArgumentCaptor; import org.springframework.beans.factory.annotation.Autowired; @@ -25,8 +22,6 @@ import org.springframework.http.HttpStatus; import org.springframework.http.MediaType; import org.springframework.http.ResponseEntity; -import org.springframework.mail.MailSender; -import org.springframework.mail.javamail.JavaMailSender; import org.springframework.test.context.ActiveProfiles; import org.springframework.test.context.bean.override.mockito.MockitoBean; import org.springframework.test.web.reactive.server.WebTestClient; diff --git a/src/test/java/com/bazaar/user/controller/UserControllerTest.java b/src/test/java/com/bazaar/user/controller/UserControllerTest.java index 88bfe4e..2e96534 100644 --- a/src/test/java/com/bazaar/user/controller/UserControllerTest.java +++ b/src/test/java/com/bazaar/user/controller/UserControllerTest.java @@ -40,7 +40,7 @@ void testUpdateUserSuccess() { String refreshToken = JsonPath.read(registerResponse.getBody(), "$.data.refreshToken"); Long header = jwtService.validateAndRotate(refreshToken).getId(); - MultiValueMap body = userUpdate("Johnny", "Phillip Doe", + MultiValueMap body = userUpdate("Johnny", "Phillip Doe", "Hi! I'm Johnny Phillip Doe, my email is JohnDoe@gmail.com", "static/images/image.jpg", MediaType.IMAGE_JPEG); @@ -69,7 +69,7 @@ void testUpdateUserSuccess() { @Test void testCannotUpdateNonExistentUser() { Long header = 1L; - MultiValueMap body = userUpdate("Johnny", "Phillip Doe", + MultiValueMap body = userUpdate("Johnny", "Phillip Doe", "Hi! I'm Johnny Phillip Doe, my email is JohnDoe@gmail.com", "static/images/image.jpg", MediaType.IMAGE_JPEG); @@ -88,7 +88,7 @@ void testUpdateUserWithNoImageSuccess() { String refreshToken = JsonPath.read(registerResponse.getBody(), "$.data.refreshToken"); Long header = jwtService.validateAndRotate(refreshToken).getId(); - MultiValueMap body = userUpdate("Johnny", "Phillip Doe", + MultiValueMap body = userUpdate("Johnny", "Phillip Doe", "Hi! I'm Johnny Phillip Doe, my email is JohnDoe@gmail.com", null, null); var userResponse = putUser(header.toString(), body); @@ -115,7 +115,7 @@ void testCannotUpdateWithInvalidImageType() { String refreshToken = JsonPath.read(registerResponse.getBody(), "$.data.refreshToken"); Long header = jwtService.validateAndRotate(refreshToken).getId(); - MultiValueMap body = userUpdate("Johnny", "Phillip Doe", + MultiValueMap body = userUpdate("Johnny", "Phillip Doe", "Hi! I'm Johnny Phillip Doe, my email is JohnDoe@gmail.com", "static/images/gif.gif", MediaType.IMAGE_GIF); @@ -131,7 +131,7 @@ void testCannotUpdateWithMissingHeader() { var registerResponse = postRegister(registerJson("John", "Doe", "JohnDoe@gmail.com", "JohnDoe7")); assertEquals(HttpStatus.CREATED, registerResponse.getStatusCode()); - MultiValueMap body = userUpdate("Johnny", "Phillip Doe", + MultiValueMap body = userUpdate("Johnny", "Phillip Doe", "Hi! I'm Johnny Phillip Doe, my email is JohnDoe@gmail.com", "static/images/gif.gif", MediaType.IMAGE_GIF); @@ -150,8 +150,6 @@ void testCannotUpdateWithMissingBody() { Long header = jwtService.validateAndRotate(refreshToken).getId(); var response = putUser(header.toString(), null); - String detail = JsonPath.read(response.getBody(), "$.detail"); - assertEquals(HttpStatus.BAD_REQUEST, response.getStatusCode()); } @@ -163,7 +161,7 @@ void testUpdateTwiceWithAndWithoutAnImage() { String refreshToken = JsonPath.read(registerResponse.getBody(), "$.data.refreshToken"); Long header = jwtService.validateAndRotate(refreshToken).getId(); - MultiValueMap body = userUpdate("Johnny", "Phillip Doe", + MultiValueMap body = userUpdate("Johnny", "Phillip Doe", "Hi! I'm Johnny Phillip Doe, my email is JohnDoe@gmail.com", "static/images/image.jpg", MediaType.IMAGE_JPEG); @@ -199,8 +197,9 @@ void testCannotUpdateWithBlankName() { String refreshToken = JsonPath.read(registerResponse.getBody(), "$.data.refreshToken"); Long header = jwtService.validateAndRotate(refreshToken).getId(); - MultiValueMap body = userUpdate("", "Phillip Doe", "Hi! I'm Johnny Phillip Doe, my email is JohnDoe@gmail.com", - "static/images/image.jpg", MediaType.IMAGE_JPEG); + MultiValueMap body = userUpdate("", "Phillip Doe", + "Hi! I'm Johnny Phillip Doe, my email is JohnDoe@gmail.com", "static/images/image.jpg", + MediaType.IMAGE_JPEG); var response = putUser(header.toString(), body); @@ -215,7 +214,7 @@ void testCannotUpdateWithBlankSurname() { String refreshToken = JsonPath.read(registerResponse.getBody(), "$.data.refreshToken"); Long header = jwtService.validateAndRotate(refreshToken).getId(); - MultiValueMap body = userUpdate("Johnny", "", "Hi! I'm Johnny Phillip Doe, my email is JohnDoe@gmail.com", + MultiValueMap body = userUpdate("Johnny", "", "Hi! I'm Johnny Phillip Doe, my email is JohnDoe@gmail.com", "static/images/image.jpg", MediaType.IMAGE_JPEG); var response = putUser(header.toString(), body); @@ -231,7 +230,8 @@ void testCannotUpdateWithBlankDescription() { String refreshToken = JsonPath.read(registerResponse.getBody(), "$.data.refreshToken"); Long header = jwtService.validateAndRotate(refreshToken).getId(); - MultiValueMap body = userUpdate("Johnny", "Phillip Doe", "", "static/images/image.jpg", MediaType.IMAGE_JPEG); + MultiValueMap body = userUpdate("Johnny", "Phillip Doe", "", "static/images/image.jpg", + MediaType.IMAGE_JPEG); var response = putUser(header.toString(), body); @@ -246,7 +246,7 @@ void testCannotUpdateWithVeryLongName() { String refreshToken = JsonPath.read(registerResponse.getBody(), "$.data.refreshToken"); Long header = jwtService.validateAndRotate(refreshToken).getId(); - MultiValueMap body = userUpdate("A".repeat(51), "Phillip Doe", + MultiValueMap body = userUpdate("A".repeat(51), "Phillip Doe", "Hi! I'm Johnny Phillip Doe, my email is JohnDoe@gmail.com", "static/images/image.jpg", MediaType.IMAGE_JPEG); @@ -263,7 +263,7 @@ void testCannotUpdateWithVeryLongSurname() { String refreshToken = JsonPath.read(registerResponse.getBody(), "$.data.refreshToken"); Long header = jwtService.validateAndRotate(refreshToken).getId(); - MultiValueMap body = userUpdate("Johnny", "A".repeat(51), + MultiValueMap body = userUpdate("Johnny", "A".repeat(51), "Hi! I'm Johnny Phillip Doe, my email is JohnDoe@gmail.com", "static/images/image.jpg", MediaType.IMAGE_JPEG); @@ -280,7 +280,7 @@ void testCannotUpdateWithVeryLongDescription() { String refreshToken = JsonPath.read(registerResponse.getBody(), "$.data.refreshToken"); Long header = jwtService.validateAndRotate(refreshToken).getId(); - MultiValueMap body = userUpdate("Johnny", "Phillip Doe", "Hi".repeat(256), "static/images/image.jpg", + MultiValueMap body = userUpdate("Johnny", "Phillip Doe", "Hi".repeat(256), "static/images/image.jpg", MediaType.IMAGE_JPEG); var response = putUser(header.toString(), body); @@ -296,7 +296,7 @@ void testCannotUpdateWithBrokenJSON() { String refreshToken = JsonPath.read(registerResponse.getBody(), "$.data.refreshToken"); Long header = jwtService.validateAndRotate(refreshToken).getId(); - MultiValueMap body = userUpdateBroken("static/images/image.jpg", MediaType.IMAGE_JPEG); + MultiValueMap body = userUpdateBroken("static/images/image.jpg", MediaType.IMAGE_JPEG); var response = putUser(header.toString(), body); diff --git a/src/test/java/com/bazaar/user/testConfig/AbstractTest.java b/src/test/java/com/bazaar/user/testConfig/AbstractTest.java index 9753fbb..c960402 100644 --- a/src/test/java/com/bazaar/user/testConfig/AbstractTest.java +++ b/src/test/java/com/bazaar/user/testConfig/AbstractTest.java @@ -185,7 +185,7 @@ public static String resetPasswordJson(String token, String newPassword) { """.formatted(token, newPassword); } - public static MultiValueMap userUpdate(String name, String surname, String description, String file, + public static MultiValueMap userUpdate(String name, String surname, String description, String file, MediaType fileType) { String request = """ { @@ -205,7 +205,7 @@ public static MultiValueMap userUpdate(String name, String surname, String descr return builder.build(); } - public static MultiValueMap userUpdateBroken(String file, MediaType fileType) { + public static MultiValueMap userUpdateBroken(String file, MediaType fileType) { String request = """ { "name": "John", @@ -308,7 +308,7 @@ public ResponseEntity postResetPassword(String body) { .body(result.getResponseBody()); } - public ResponseEntity putUser(String header, MultiValueMap body) { + public ResponseEntity putUser(String header, MultiValueMap body) { var requestSpec = webTestClient.put().uri(PROFILE_URL).contentType(MediaType.MULTIPART_FORM_DATA); if (header != null) From 1cba5094d28162c3c0fe18f68cf59d79f4e93c53 Mon Sep 17 00:00:00 2001 From: FranInfanti Date: Thu, 21 May 2026 13:00:20 -0300 Subject: [PATCH 4/7] test: add new tests for user metrics dashboard endpoint --- .../user/controller/AdminControllerTest.java | 83 +++++++- .../bazaar/user/testConfig/AbstractTest.java | 183 ++++++++++-------- 2 files changed, 179 insertions(+), 87 deletions(-) diff --git a/src/test/java/com/bazaar/user/controller/AdminControllerTest.java b/src/test/java/com/bazaar/user/controller/AdminControllerTest.java index d2f0d8e..d00cd5a 100644 --- a/src/test/java/com/bazaar/user/controller/AdminControllerTest.java +++ b/src/test/java/com/bazaar/user/controller/AdminControllerTest.java @@ -3,7 +3,6 @@ import com.bazaar.user.UserApplication; import com.bazaar.user.common.Role; import com.bazaar.user.common.Source; -import com.bazaar.user.model.User; import com.bazaar.user.testConfig.AbstractTest; import com.jayway.jsonpath.JsonPath; @@ -17,6 +16,7 @@ import org.springframework.test.context.ActiveProfiles; import org.springframework.util.MultiValueMap; +import java.time.LocalDateTime; import java.util.List; import java.util.Locale; @@ -24,9 +24,9 @@ import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.junit.jupiter.api.Assertions.assertTrue; -@SpringBootTest(classes = UserApplication.class, webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT) -@AutoConfigureWebTestClient @ActiveProfiles("test") +@AutoConfigureWebTestClient +@SpringBootTest(classes = UserApplication.class, webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT) public class AdminControllerTest extends AbstractTest { @Autowired @@ -590,13 +590,80 @@ void testUserCannotLoginIntoBackoffice() { @Test void testAdminCanLoginIntoBackoffice() { - User user = new User("John", "Doe", "JohnDoe@gmail.com", passwordEncoder.encode("JohnDoe7")); - user.setRole(Role.ADMIN); - userRepository.saveAndFlush(user); + assertEquals(HttpStatus.CREATED, + postRegister(registerJson("John", "Doe", "JohnDoe@gmail.com", "JohnDoe7")).getStatusCode()); - var loginResponse = postLogin(loginJson("JohnDoe@gmail.com", "JohnDoe7"), Source.BACKOFFICE); + jdbcTemplate.update("UPDATE users SET role = ? WHERE email = ?", Role.ADMIN.name(), "JohnDoe@gmail.com"); + + assertEquals(HttpStatus.OK, + postLogin(loginJson("JohnDoe@gmail.com", "JohnDoe7"), Source.BACKOFFICE).getStatusCode()); + } + + @Test + void testGetUsersStatsSuccess() { + assertEquals(HttpStatus.CREATED, + postRegister(registerJson("Lewis", "Hamilton", "lh44@gmail.com", "Password123!")).getStatusCode()); + jdbcTemplate.update("UPDATE users SET created_at = ? WHERE email = ?", LocalDateTime.now().minusDays(45), + "lh44@gmail.com"); + + assertEquals(HttpStatus.CREATED, + postRegister(registerJson("Charles", "Leclerc", "cleclerc@gmail.com", "Password123!")).getStatusCode()); + + assertEquals(HttpStatus.CREATED, + postRegister(registerJson("Max", "Verstappen", "mverstappen@gmail.com", "Password123!")) + .getStatusCode()); + + var userToBlock = userRepository.findByEmailIgnoreCase("mverstappen@gmail.com").orElseThrow(); + assertEquals(HttpStatus.NO_CONTENT, blockUser(999L, Role.ADMIN, userToBlock.getId()).getStatusCode()); + + var response = getUsersStats(Role.ADMIN, null); + + assertEquals(HttpStatus.OK, response.getStatusCode()); + assertEquals(2, (int) JsonPath.read(response.getBody(), "$.data.totalUsers")); + assertEquals(1, (int) JsonPath.read(response.getBody(), "$.data.activeUsers")); + assertEquals(1, (int) JsonPath.read(response.getBody(), "$.data.blockedUsers")); + assertEquals(100.0, (double) JsonPath.read(response.getBody(), "$.data.growthRate")); + } + + @Test + void testGetUsersStatsWithDifferentPeriodSuccess() { + assertEquals(HttpStatus.CREATED, + postRegister(registerJson("Lewis", "Hamilton", "lh44@gmail.com", "Password123!")).getStatusCode()); + jdbcTemplate.update("UPDATE users SET created_at = ? WHERE email = ?", LocalDateTime.now().minusDays(25), + "lh44@gmail.com"); + + assertEquals(HttpStatus.CREATED, + postRegister(registerJson("Charles", "Leclerc", "cleclerc@gmail.com", "Password123!")).getStatusCode()); + jdbcTemplate.update("UPDATE users SET created_at = ? WHERE email = ?", LocalDateTime.now().minusDays(5), + "cleclerc@gmail.com"); + + assertEquals(HttpStatus.CREATED, + postRegister(registerJson("Max", "Verstappen", "mverstappen@gmail.com", "Password123!")) + .getStatusCode()); + jdbcTemplate.update("UPDATE users SET created_at = ? WHERE email = ?", LocalDateTime.now().minusDays(10), + "mverstappen@gmail.com"); + + assertEquals(HttpStatus.CREATED, + postRegister(registerJson("Lando", "Norris", "lnorris@gmail.com", "Password123!")).getStatusCode()); + jdbcTemplate.update("UPDATE users SET created_at = ? WHERE email = ?", LocalDateTime.now().minusDays(2), + "lnorris@gmail.com"); - assertEquals(HttpStatus.OK, loginResponse.getStatusCode()); + var response = getUsersStats(Role.ADMIN, 15); + + assertEquals(HttpStatus.OK, response.getStatusCode()); + assertEquals(3, (int) JsonPath.read(response.getBody(), "$.data.totalUsers")); + assertEquals(3, (int) JsonPath.read(response.getBody(), "$.data.activeUsers")); + assertEquals(0, (int) JsonPath.read(response.getBody(), "$.data.blockedUsers")); + assertEquals(200.0, (double) JsonPath.read(response.getBody(), "$.data.growthRate")); + } + + @Test + void testCannotGetUsersStatsWithoutAdminPrivileges() { + var response = getUsersStats(Role.USER, null); + String detail = JsonPath.read(response.getBody(), "$.detail"); + + assertEquals(HttpStatus.FORBIDDEN, response.getStatusCode()); + assertEquals(messageSource.getMessage("exception.USER_FORBIDDEN", null, Locale.ENGLISH), detail); } } diff --git a/src/test/java/com/bazaar/user/testConfig/AbstractTest.java b/src/test/java/com/bazaar/user/testConfig/AbstractTest.java index c960402..e3ea76f 100644 --- a/src/test/java/com/bazaar/user/testConfig/AbstractTest.java +++ b/src/test/java/com/bazaar/user/testConfig/AbstractTest.java @@ -16,6 +16,7 @@ import org.springframework.http.MediaType; import org.springframework.http.ResponseEntity; import org.springframework.http.client.MultipartBodyBuilder; +import org.springframework.jdbc.core.JdbcTemplate; import org.springframework.security.crypto.password.PasswordEncoder; import org.springframework.test.context.DynamicPropertyRegistry; import org.springframework.test.context.DynamicPropertySource; @@ -45,6 +46,9 @@ public abstract class AbstractTest { @Autowired private WebTestClient webTestClient; + @Autowired + protected JdbcTemplate jdbcTemplate; + @Autowired protected UserRepository userRepository; @@ -80,13 +84,15 @@ public abstract class AbstractTest { public static final String DETAILS_URL = "/admin/users"; + public static final String STATS_URL = "/admin/users/stats"; + public static final String BLOCK_URL = "admin/users/block/{blockUserId}"; public static final String UNBLOCK_URL = "admin/users/unblock/{unblockUserId}"; static PostgreSQLContainer postgres = new PostgreSQLContainer<>("postgres:15-alpine").withDatabaseName("testdb") - .withUsername("test") - .withPassword("test"); + .withUsername("test") + .withPassword("test"); static { postgres.start(); @@ -225,87 +231,87 @@ public static String resetPasswordJson(String token, String newPassword) { public ResponseEntity postRegister(String body) { var result = webTestClient.post() - .uri(REGISTER_URL) - .contentType(MediaType.APPLICATION_JSON) - .bodyValue(body) - .exchange() - .expectBody(String.class) - .returnResult(); + .uri(REGISTER_URL) + .contentType(MediaType.APPLICATION_JSON) + .bodyValue(body) + .exchange() + .expectBody(String.class) + .returnResult(); return ResponseEntity.status(result.getStatus()) - .headers(result.getResponseHeaders()) - .body(result.getResponseBody()); + .headers(result.getResponseHeaders()) + .body(result.getResponseBody()); } public ResponseEntity postRefresh(String body) { var result = webTestClient.post() - .uri(REFRESH_URL) - .contentType(MediaType.APPLICATION_JSON) - .bodyValue(body) - .exchange() - .expectBody(String.class) - .returnResult(); + .uri(REFRESH_URL) + .contentType(MediaType.APPLICATION_JSON) + .bodyValue(body) + .exchange() + .expectBody(String.class) + .returnResult(); return ResponseEntity.status(result.getStatus()) - .headers(result.getResponseHeaders()) - .body(result.getResponseBody()); + .headers(result.getResponseHeaders()) + .body(result.getResponseBody()); } public ResponseEntity postLogin(String body, Source source) { var result = webTestClient.post() - .uri(LOGIN_URL) - .contentType(MediaType.APPLICATION_JSON) - .header("X-Login-Source", source.toString()) - .bodyValue(body) - .exchange() - .expectBody(String.class) - .returnResult(); + .uri(LOGIN_URL) + .contentType(MediaType.APPLICATION_JSON) + .header("X-Login-Source", source.toString()) + .bodyValue(body) + .exchange() + .expectBody(String.class) + .returnResult(); return ResponseEntity.status(result.getStatus()) - .headers(result.getResponseHeaders()) - .body(result.getResponseBody()); + .headers(result.getResponseHeaders()) + .body(result.getResponseBody()); } public ResponseEntity postGoogleLogin(String body) { var result = webTestClient.post() - .uri(GOOGLE_LOGIN_URL) - .contentType(MediaType.APPLICATION_JSON) - .bodyValue(body) - .exchange() - .expectBody(String.class) - .returnResult(); + .uri(GOOGLE_LOGIN_URL) + .contentType(MediaType.APPLICATION_JSON) + .bodyValue(body) + .exchange() + .expectBody(String.class) + .returnResult(); return ResponseEntity.status(result.getStatus()) - .headers(result.getResponseHeaders()) - .body(result.getResponseBody()); + .headers(result.getResponseHeaders()) + .body(result.getResponseBody()); } public ResponseEntity postForgotPassword(String body) { var result = webTestClient.post() - .uri(FORGOT_PASSWORD_URL) - .contentType(MediaType.APPLICATION_JSON) - .bodyValue(body) - .exchange() - .expectBody(String.class) - .returnResult(); + .uri(FORGOT_PASSWORD_URL) + .contentType(MediaType.APPLICATION_JSON) + .bodyValue(body) + .exchange() + .expectBody(String.class) + .returnResult(); return ResponseEntity.status(result.getStatus()) - .headers(result.getResponseHeaders()) - .body(result.getResponseBody()); + .headers(result.getResponseHeaders()) + .body(result.getResponseBody()); } public ResponseEntity postResetPassword(String body) { var result = webTestClient.post() - .uri(RESET_PASSWORD_URL) - .contentType(MediaType.APPLICATION_JSON) - .bodyValue(body) - .exchange() - .expectBody(String.class) - .returnResult(); + .uri(RESET_PASSWORD_URL) + .contentType(MediaType.APPLICATION_JSON) + .bodyValue(body) + .exchange() + .expectBody(String.class) + .returnResult(); return ResponseEntity.status(result.getStatus()) - .headers(result.getResponseHeaders()) - .body(result.getResponseBody()); + .headers(result.getResponseHeaders()) + .body(result.getResponseBody()); } public ResponseEntity putUser(String header, MultiValueMap body) { @@ -319,46 +325,46 @@ public ResponseEntity putUser(String header, MultiValueMap body) { var result = responseSpec.expectBody(String.class).returnResult(); return ResponseEntity.status(result.getStatus()) - .headers(result.getResponseHeaders()) - .body(result.getResponseBody()); + .headers(result.getResponseHeaders()) + .body(result.getResponseBody()); } public ResponseEntity getPrivateUser(String header) { var result = webTestClient.get() - .uri(PROFILE_URL) - .header("X-User-Id", header) - .exchange() - .expectBody(String.class) - .returnResult(); + .uri(PROFILE_URL) + .header("X-User-Id", header) + .exchange() + .expectBody(String.class) + .returnResult(); return ResponseEntity.status(result.getStatus()) - .headers(result.getResponseHeaders()) - .body(result.getResponseBody()); + .headers(result.getResponseHeaders()) + .body(result.getResponseBody()); } public ResponseEntity getPublicUser(String pathVariable) { var result = webTestClient.get() - .uri(PROFILE_URL + "/" + pathVariable) - .exchange() - .expectBody(String.class) - .returnResult(); + .uri(PROFILE_URL + "/" + pathVariable) + .exchange() + .expectBody(String.class) + .returnResult(); return ResponseEntity.status(result.getStatus()) - .headers(result.getResponseHeaders()) - .body(result.getResponseBody()); + .headers(result.getResponseHeaders()) + .body(result.getResponseBody()); } public ResponseEntity getUserStatus(String header) { var result = webTestClient.get() - .uri(STATUS_URL) - .header("X-User-Id", header) - .exchange() - .expectBody(String.class) - .returnResult(); + .uri(STATUS_URL) + .header("X-User-Id", header) + .exchange() + .expectBody(String.class) + .returnResult(); return ResponseEntity.status(result.getStatus()) - .headers(result.getResponseHeaders()) - .body(result.getResponseBody()); + .headers(result.getResponseHeaders()) + .body(result.getResponseBody()); } public ResponseEntity getUsersDetails(int page, int size, Role role, String name, String email) { @@ -375,8 +381,8 @@ public ResponseEntity getUsersDetails(int page, int size, Role role, Str }).header("X-User-Role", role.name()).exchange().expectBody(String.class).returnResult(); return ResponseEntity.status(result.getStatus()) - .headers(result.getResponseHeaders()) - .body(result.getResponseBody()); + .headers(result.getResponseHeaders()) + .body(result.getResponseBody()); } public ResponseEntity blockUser(Long userId, Role role, Long blockUserId) { @@ -391,8 +397,8 @@ public ResponseEntity blockUser(Long userId, Role role, Long blockUserId var result = requestSpec.exchange().expectBody(String.class).returnResult(); return ResponseEntity.status(result.getStatus()) - .headers(result.getResponseHeaders()) - .body(result.getResponseBody()); + .headers(result.getResponseHeaders()) + .body(result.getResponseBody()); } public ResponseEntity unblockUser(Long userId, Role role, Long unblockUserId) { @@ -407,8 +413,27 @@ public ResponseEntity unblockUser(Long userId, Role role, Long unblockUs var result = requestSpec.exchange().expectBody(String.class).returnResult(); return ResponseEntity.status(result.getStatus()) - .headers(result.getResponseHeaders()) - .body(result.getResponseBody()); + .headers(result.getResponseHeaders()) + .body(result.getResponseBody()); + } + + public ResponseEntity getUsersStats(Role role, Integer period) { + var result = webTestClient.get().uri(uriBuilder -> { + uriBuilder.path(STATS_URL); + if (period != null) + uriBuilder.queryParam("period", period); + return uriBuilder.build(); + }); + + if (role != null) { + result.header("X-User-Role", role.name()); + } + + var response = result.exchange().expectBody(String.class).returnResult(); + + return ResponseEntity.status(response.getStatus()) + .headers(response.getResponseHeaders()) + .body(response.getResponseBody()); } } From 66d91017b5d6424f73b60271e3560a43d20274b0 Mon Sep 17 00:00:00 2001 From: FranInfanti Date: Thu, 21 May 2026 14:28:45 -0300 Subject: [PATCH 5/7] docs: update README --- README.md | 72 ++++++++++++++++++++++++++++--------------------------- 1 file changed, 37 insertions(+), 35 deletions(-) diff --git a/README.md b/README.md index dfda6e1..2d7adf5 100644 --- a/README.md +++ b/README.md @@ -4,59 +4,61 @@ ## Overview -This module handles the core security flow of the **Bazaar** platform, managing user identity, session persistence, and secure access via JWT (JSON Web Tokens). +This module handles the core users flow of the **Bazaar** platform, managing user identity, session persistence, and secure access via JWT (JSON Web Tokens). --- ## Environment Variables -Create a `.env` file based on [.env.example](/auth/.env.example). +Create a `.env` file based on the [.env.example](/api-user/.env.example): ```bash cp .env.example .env ``` +* `PORT` - Backend running port. +* `HOST` - Backend running host address. +* `ENVIORMENT` - Running environment mode. +* `DATABASE_HOST` - Database running host address. +* `DATABASE_NAME` - Database name. +* `DATABASE_PORT` - Database running port. +* `DATABASE_USER` - Database user. +* `DATABASE_PASSWORD` - Database very hard to guess password. +* `EXPOSE_PORT` - Docker expose port for the database. +* `JWT_SECRET` - Your JWT secret key which should be kept in secret. +* `JWT_ISSUER` - Your JWT issuer. +* `CLOUDINARY_CLOUD_NAME` - Your Cloudinary cloud name. +* `CLOUDINARY_API_KEY` - Your Cloudinary API key. +* `CLOUDINARY_API_SECRET` - Your Cloudinary API secret. +* `API_CATALOG_URL` - Your API catalog domain name. +* `GMAIL_NAME` - Your Gmail name. +* `GMAIL_KEY` - Your Gmail API key. +* `GMAIL_ADDRESS` - Your Gmail address. +* `APP_BASE_URL` - Your service domain name. +* `APP_SHA256_CERT_FINGERPRINT` - Your SHA256 cert fingerprint. +* `SENDERGRID_KEY` - Your very secure sendergrid key. + --- ## Running the Project 1. **Clone the repository** - ```bash - git clone https://github.com/Baza-ar/api-auth.git api-user - cd api-user - ``` -2. **Configure the environment variables** Create a `.env` file based on [.env.example](/auth/.env.example): - * `PORT` - Backend running port. - * `HOST` - Backend running host address. - * `ENVIORMENT` - Running environment mode. - * `DATABASE_HOST` - Database running host address. - * `DATABASE_NAME` - Database name. - * `DATABASE_PORT` - Database running port. - * `DATABASE_USER` - Database user. - * `DATABASE_PASSWORD` - Database very hard to guess password. - * `EXPOSE_PORT` - Docker expose port for the database. - * `JWT_SECRET` - Your JWT secret key which should be kept in secret. - * `JWT_ISSUER` - Your JWT issuer. - * `CLOUDINARY_CLOUD_NAME` - Your Cloudinary cloud name. - * `CLOUDINARY_API_KEY` - Your Cloudinary API key. - * `CLOUDINARY_API_SECRET` - Your Cloudinary API secret. - * `API_CATALOG_URL` - Your API catalog domain name. - * `GMAIL_NAME` - Your Gmail name. - * `GMAIL_KEY` - Your Gmail API key. - * `GMAIL_ADDRESS` - Your Gmail address. - * `APP_BASE_URL` - Your service domain name. - * `APP_SHA256_CERT_FINGERPRINT` - Your SHA256 cert fingerprint. - * `SENDERGRID_KEY` - Your very secure sendergrid key. -3. **Running the whole project** ```bash + git clone https://github.com/Baza-ar/api-user.git api-user + cd api-user + ``` +2. **Run the whole project** + ```bash + docker create network -d bridge bazaar-network + docker-compose up --build - ``` - > [!NOTE] - > You should only create the **Docker Network** once, check with. - > ```bash - > docker network ls - > ``` + ``` +> [!NOTE] +> You should only create the **Docker Network** once, check with. +> ```bash +> docker network ls +> ``` --- ## Stopping From bbb84b0ae5f4fe3521e6a634435cd0e3cdb78e43 Mon Sep 17 00:00:00 2001 From: FranInfanti Date: Thu, 21 May 2026 14:36:18 -0300 Subject: [PATCH 6/7] style: apply format --- .../user/controller/AdminControllerTest.java | 4 +- .../bazaar/user/testConfig/AbstractTest.java | 162 +++++++++--------- 2 files changed, 83 insertions(+), 83 deletions(-) diff --git a/src/test/java/com/bazaar/user/controller/AdminControllerTest.java b/src/test/java/com/bazaar/user/controller/AdminControllerTest.java index d00cd5a..8f93066 100644 --- a/src/test/java/com/bazaar/user/controller/AdminControllerTest.java +++ b/src/test/java/com/bazaar/user/controller/AdminControllerTest.java @@ -611,7 +611,7 @@ void testGetUsersStatsSuccess() { assertEquals(HttpStatus.CREATED, postRegister(registerJson("Max", "Verstappen", "mverstappen@gmail.com", "Password123!")) - .getStatusCode()); + .getStatusCode()); var userToBlock = userRepository.findByEmailIgnoreCase("mverstappen@gmail.com").orElseThrow(); assertEquals(HttpStatus.NO_CONTENT, blockUser(999L, Role.ADMIN, userToBlock.getId()).getStatusCode()); @@ -639,7 +639,7 @@ void testGetUsersStatsWithDifferentPeriodSuccess() { assertEquals(HttpStatus.CREATED, postRegister(registerJson("Max", "Verstappen", "mverstappen@gmail.com", "Password123!")) - .getStatusCode()); + .getStatusCode()); jdbcTemplate.update("UPDATE users SET created_at = ? WHERE email = ?", LocalDateTime.now().minusDays(10), "mverstappen@gmail.com"); diff --git a/src/test/java/com/bazaar/user/testConfig/AbstractTest.java b/src/test/java/com/bazaar/user/testConfig/AbstractTest.java index e3ea76f..670cf0d 100644 --- a/src/test/java/com/bazaar/user/testConfig/AbstractTest.java +++ b/src/test/java/com/bazaar/user/testConfig/AbstractTest.java @@ -91,8 +91,8 @@ public abstract class AbstractTest { public static final String UNBLOCK_URL = "admin/users/unblock/{unblockUserId}"; static PostgreSQLContainer postgres = new PostgreSQLContainer<>("postgres:15-alpine").withDatabaseName("testdb") - .withUsername("test") - .withPassword("test"); + .withUsername("test") + .withPassword("test"); static { postgres.start(); @@ -231,87 +231,87 @@ public static String resetPasswordJson(String token, String newPassword) { public ResponseEntity postRegister(String body) { var result = webTestClient.post() - .uri(REGISTER_URL) - .contentType(MediaType.APPLICATION_JSON) - .bodyValue(body) - .exchange() - .expectBody(String.class) - .returnResult(); + .uri(REGISTER_URL) + .contentType(MediaType.APPLICATION_JSON) + .bodyValue(body) + .exchange() + .expectBody(String.class) + .returnResult(); return ResponseEntity.status(result.getStatus()) - .headers(result.getResponseHeaders()) - .body(result.getResponseBody()); + .headers(result.getResponseHeaders()) + .body(result.getResponseBody()); } public ResponseEntity postRefresh(String body) { var result = webTestClient.post() - .uri(REFRESH_URL) - .contentType(MediaType.APPLICATION_JSON) - .bodyValue(body) - .exchange() - .expectBody(String.class) - .returnResult(); + .uri(REFRESH_URL) + .contentType(MediaType.APPLICATION_JSON) + .bodyValue(body) + .exchange() + .expectBody(String.class) + .returnResult(); return ResponseEntity.status(result.getStatus()) - .headers(result.getResponseHeaders()) - .body(result.getResponseBody()); + .headers(result.getResponseHeaders()) + .body(result.getResponseBody()); } public ResponseEntity postLogin(String body, Source source) { var result = webTestClient.post() - .uri(LOGIN_URL) - .contentType(MediaType.APPLICATION_JSON) - .header("X-Login-Source", source.toString()) - .bodyValue(body) - .exchange() - .expectBody(String.class) - .returnResult(); + .uri(LOGIN_URL) + .contentType(MediaType.APPLICATION_JSON) + .header("X-Login-Source", source.toString()) + .bodyValue(body) + .exchange() + .expectBody(String.class) + .returnResult(); return ResponseEntity.status(result.getStatus()) - .headers(result.getResponseHeaders()) - .body(result.getResponseBody()); + .headers(result.getResponseHeaders()) + .body(result.getResponseBody()); } public ResponseEntity postGoogleLogin(String body) { var result = webTestClient.post() - .uri(GOOGLE_LOGIN_URL) - .contentType(MediaType.APPLICATION_JSON) - .bodyValue(body) - .exchange() - .expectBody(String.class) - .returnResult(); + .uri(GOOGLE_LOGIN_URL) + .contentType(MediaType.APPLICATION_JSON) + .bodyValue(body) + .exchange() + .expectBody(String.class) + .returnResult(); return ResponseEntity.status(result.getStatus()) - .headers(result.getResponseHeaders()) - .body(result.getResponseBody()); + .headers(result.getResponseHeaders()) + .body(result.getResponseBody()); } public ResponseEntity postForgotPassword(String body) { var result = webTestClient.post() - .uri(FORGOT_PASSWORD_URL) - .contentType(MediaType.APPLICATION_JSON) - .bodyValue(body) - .exchange() - .expectBody(String.class) - .returnResult(); + .uri(FORGOT_PASSWORD_URL) + .contentType(MediaType.APPLICATION_JSON) + .bodyValue(body) + .exchange() + .expectBody(String.class) + .returnResult(); return ResponseEntity.status(result.getStatus()) - .headers(result.getResponseHeaders()) - .body(result.getResponseBody()); + .headers(result.getResponseHeaders()) + .body(result.getResponseBody()); } public ResponseEntity postResetPassword(String body) { var result = webTestClient.post() - .uri(RESET_PASSWORD_URL) - .contentType(MediaType.APPLICATION_JSON) - .bodyValue(body) - .exchange() - .expectBody(String.class) - .returnResult(); + .uri(RESET_PASSWORD_URL) + .contentType(MediaType.APPLICATION_JSON) + .bodyValue(body) + .exchange() + .expectBody(String.class) + .returnResult(); return ResponseEntity.status(result.getStatus()) - .headers(result.getResponseHeaders()) - .body(result.getResponseBody()); + .headers(result.getResponseHeaders()) + .body(result.getResponseBody()); } public ResponseEntity putUser(String header, MultiValueMap body) { @@ -325,46 +325,46 @@ public ResponseEntity putUser(String header, MultiValueMap body) { var result = responseSpec.expectBody(String.class).returnResult(); return ResponseEntity.status(result.getStatus()) - .headers(result.getResponseHeaders()) - .body(result.getResponseBody()); + .headers(result.getResponseHeaders()) + .body(result.getResponseBody()); } public ResponseEntity getPrivateUser(String header) { var result = webTestClient.get() - .uri(PROFILE_URL) - .header("X-User-Id", header) - .exchange() - .expectBody(String.class) - .returnResult(); + .uri(PROFILE_URL) + .header("X-User-Id", header) + .exchange() + .expectBody(String.class) + .returnResult(); return ResponseEntity.status(result.getStatus()) - .headers(result.getResponseHeaders()) - .body(result.getResponseBody()); + .headers(result.getResponseHeaders()) + .body(result.getResponseBody()); } public ResponseEntity getPublicUser(String pathVariable) { var result = webTestClient.get() - .uri(PROFILE_URL + "/" + pathVariable) - .exchange() - .expectBody(String.class) - .returnResult(); + .uri(PROFILE_URL + "/" + pathVariable) + .exchange() + .expectBody(String.class) + .returnResult(); return ResponseEntity.status(result.getStatus()) - .headers(result.getResponseHeaders()) - .body(result.getResponseBody()); + .headers(result.getResponseHeaders()) + .body(result.getResponseBody()); } public ResponseEntity getUserStatus(String header) { var result = webTestClient.get() - .uri(STATUS_URL) - .header("X-User-Id", header) - .exchange() - .expectBody(String.class) - .returnResult(); + .uri(STATUS_URL) + .header("X-User-Id", header) + .exchange() + .expectBody(String.class) + .returnResult(); return ResponseEntity.status(result.getStatus()) - .headers(result.getResponseHeaders()) - .body(result.getResponseBody()); + .headers(result.getResponseHeaders()) + .body(result.getResponseBody()); } public ResponseEntity getUsersDetails(int page, int size, Role role, String name, String email) { @@ -381,8 +381,8 @@ public ResponseEntity getUsersDetails(int page, int size, Role role, Str }).header("X-User-Role", role.name()).exchange().expectBody(String.class).returnResult(); return ResponseEntity.status(result.getStatus()) - .headers(result.getResponseHeaders()) - .body(result.getResponseBody()); + .headers(result.getResponseHeaders()) + .body(result.getResponseBody()); } public ResponseEntity blockUser(Long userId, Role role, Long blockUserId) { @@ -397,8 +397,8 @@ public ResponseEntity blockUser(Long userId, Role role, Long blockUserId var result = requestSpec.exchange().expectBody(String.class).returnResult(); return ResponseEntity.status(result.getStatus()) - .headers(result.getResponseHeaders()) - .body(result.getResponseBody()); + .headers(result.getResponseHeaders()) + .body(result.getResponseBody()); } public ResponseEntity unblockUser(Long userId, Role role, Long unblockUserId) { @@ -413,8 +413,8 @@ public ResponseEntity unblockUser(Long userId, Role role, Long unblockUs var result = requestSpec.exchange().expectBody(String.class).returnResult(); return ResponseEntity.status(result.getStatus()) - .headers(result.getResponseHeaders()) - .body(result.getResponseBody()); + .headers(result.getResponseHeaders()) + .body(result.getResponseBody()); } public ResponseEntity getUsersStats(Role role, Integer period) { @@ -432,8 +432,8 @@ public ResponseEntity getUsersStats(Role role, Integer period) { var response = result.exchange().expectBody(String.class).returnResult(); return ResponseEntity.status(response.getStatus()) - .headers(response.getResponseHeaders()) - .body(response.getResponseBody()); + .headers(response.getResponseHeaders()) + .body(response.getResponseBody()); } } From 30e17a7b7975b2d1c4662acbf0246844c16535c6 Mon Sep 17 00:00:00 2001 From: FranInfanti Date: Fri, 22 May 2026 09:32:45 -0300 Subject: [PATCH 7/7] feat: add integration with Grafana --- .env.example | 8 +++++++- Dockerfile | 6 ++++++ 2 files changed, 13 insertions(+), 1 deletion(-) diff --git a/.env.example b/.env.example index 366f122..edc2b0c 100644 --- a/.env.example +++ b/.env.example @@ -36,4 +36,10 @@ APP_SHA256_CERT_FINGERPRINT=dummy-sha256-fingerprint-for-app API_CATALOG_URL=your-api-catalog-url # GOOGLE -GOOGLE_CLIENT_ID=your-google-client-id.apps.googleusercontent.com \ No newline at end of file +GOOGLE_CLIENT_ID=your-google-client-id.apps.googleusercontent.com + +# GRAFANA +OTEL_SERVICE_NAME=your-otel-serivce-name +OTEL_EXPORTER_OTLP_PROTOCOL=your-exporter-otlp-protocol +OTEL_EXPORTER_OTLP_ENDPOINT=your-otlp-endpoint +OTEL_EXPORTER_OTLP_HEADERS=your-otel-headers \ No newline at end of file diff --git a/Dockerfile b/Dockerfile index 1339677..36c478b 100644 --- a/Dockerfile +++ b/Dockerfile @@ -11,10 +11,16 @@ FROM eclipse-temurin:21-jdk-alpine RUN apk add --no-cache curl +RUN mkdir -p /otel && \ + curl -L -o /otel/opentelemetry-javaagent.jar \ + https://github.com/open-telemetry/opentelemetry-java-instrumentation/releases/latest/download/opentelemetry-javaagent.jar + WORKDIR /app COPY --from=build /app/target/*.jar app.jar EXPOSE 8080 +ENV JAVA_TOOL_OPTIONS="-javaagent:/otel/opentelemetry-javaagent.jar" + ENTRYPOINT ["java", "-jar", "app.jar"] \ No newline at end of file