From 10309e4725bdaa0461c1c39b27b98a5667ecefc8 Mon Sep 17 00:00:00 2001 From: Scott Gerring Date: Wed, 4 Jun 2025 16:25:35 +0200 Subject: [PATCH 1/5] chore: make health checks robuster --- .../workflows/docker-compose-integration.yml | 43 ++++++++++++++----- docker-compose.yml | 10 +++++ .../Dockerfile | 5 ++- 3 files changed, 45 insertions(+), 13 deletions(-) diff --git a/.github/workflows/docker-compose-integration.yml b/.github/workflows/docker-compose-integration.yml index 765633a0..c57f44f2 100644 --- a/.github/workflows/docker-compose-integration.yml +++ b/.github/workflows/docker-compose-integration.yml @@ -24,8 +24,32 @@ jobs: - name: Wait for services to be healthy run: | echo "Waiting for all services to be healthy..." - # Wait for services with health checks to be healthy (4 services) and all services to be up - timeout 300 bash -c 'until [ $(docker compose ps --format "{{.Health}}" | grep -c "healthy") -ge 4 ] && [ $(docker compose ps --format "{{.Status}}" | grep -c "Up") -ge 5 ]; do sleep 5; done' + # Wait for services with health checks to be healthy (7 services) and all services to be up + # Increased timeout to 10 minutes for CI environment + timeout 600 bash -c 'while true; do + echo "=== Detailed Service Status ===" + docker compose ps --format "table {{.Name}}\t{{.Status}}\t{{.Health}}" + echo "=== Unhealthy Services ===" + unhealthy_services=$(docker compose ps --format "{{.Name}} {{.Health}}" | grep -v healthy | grep -v "^[[:space:]]*$" || echo "") + if [ -n "$unhealthy_services" ]; then + echo "$unhealthy_services" + echo "=== Logs for unhealthy services ===" + for service in $(echo "$unhealthy_services" | awk "{print \$1}"); do + echo "--- Logs for $service ---" + docker compose logs --tail=10 "$service" || echo "No logs for $service" + done + else + echo "All services healthy or no health check" + fi + healthy_count=$(docker compose ps --format "{{.Health}}" | grep -c "healthy" || echo "0") + up_count=$(docker compose ps --format "{{.Status}}" | grep -c "Up" || echo "0") + echo "Progress: $healthy_count/7 healthy, $up_count services up" + if [ "$healthy_count" -ge 7 ] && [ "$up_count" -ge 7 ]; then + echo "All services are healthy!" + break + fi + sleep 10 + done' docker compose ps - name: Test dashboard endpoints @@ -45,15 +69,12 @@ jobs: run: | echo "=== Docker Compose Services Status ===" docker compose ps - echo "=== Traefik Logs ===" - docker compose logs traefik - echo "=== Sticker Award Logs ===" - docker compose logs sticker-award - echo "=== User Management Logs ===" - docker compose logs user-management - echo "=== Database Logs ===" - docker compose logs sticker-award-db - docker compose logs user-management-db + echo "=== All Service Logs ===" + docker compose logs --tail=50 + echo "=== Health Check Details ===" + docker compose ps --format "table {{.Name}}\t{{.Status}}\t{{.Health}}" + echo "=== Resource Usage ===" + docker stats --no-stream - name: Cleanup if: always() diff --git a/docker-compose.yml b/docker-compose.yml index c81b0f0f..e8e62268 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -185,6 +185,11 @@ services: QUARKUS_S3_AWS_CREDENTIALS_STATIC_PROVIDER_SECRET_ACCESS_KEY: minioadmin QUARKUS_S3_PATH_STYLE_ACCESS: "true" STICKER_IMAGES_BUCKET: sticker-images + healthcheck: + test: ["CMD", "curl", "-f", "http://localhost:8080/health"] + interval: 10s + timeout: 5s + retries: 5 labels: - "traefik.enable=true" - "traefik.http.routers.sticker-award.rule=PathPrefix(`/api/award`)" @@ -210,6 +215,11 @@ services: KAFKA__BOOTSTRAPSERVERS: "redpanda:9092" KAFKA__SCHEMAREGISTRY: "http://redpanda:8082" KAFKA__GROUPID: "stickerlandia-user-management" + healthcheck: + test: ["CMD", "curl", "-f", "http://localhost:8080/api/users/v1/health"] + interval: 10s + timeout: 5s + retries: 5 labels: - "traefik.enable=true" - "traefik.http.routers.user-management.rule=PathPrefix(`/api/users`)" diff --git a/user-management/src/Stickerlandia.UserManagement.Api/Dockerfile b/user-management/src/Stickerlandia.UserManagement.Api/Dockerfile index 84bc3114..f4689224 100644 --- a/user-management/src/Stickerlandia.UserManagement.Api/Dockerfile +++ b/user-management/src/Stickerlandia.UserManagement.Api/Dockerfile @@ -10,9 +10,10 @@ RUN dotnet restore src/Stickerlandia.UserManagement.Api/Stickerlandia.UserManage COPY . ./ # Publish the application -RUN dotnet publish src/Stickerlandia.UserManagement.Api/Stickerlandia.UserManagement.Api.csproj -o out -c Release -r linux-arm64 +RUN dotnet publish src/Stickerlandia.UserManagement.Api/Stickerlandia.UserManagement.Api.csproj -o out -c Release -FROM mcr.microsoft.com/dotnet/aspnet:8.0-noble-chiseled AS runtime +FROM mcr.microsoft.com/dotnet/aspnet:8.0 AS runtime +RUN apt-get update && apt-get install -y curl && rm -rf /var/lib/apt/lists/* WORKDIR /App COPY --from=build /App/out . EXPOSE 80 From 6c01215edca3e7707c45bf48ecff02706ae90369 Mon Sep 17 00:00:00 2001 From: Scott Gerring Date: Wed, 4 Jun 2025 16:25:35 +0200 Subject: [PATCH 2/5] chore(sticker-award): Setup checkstyle linting --- sticker-award/README.md | 66 +++++ sticker-award/checkstyle-suppressions.xml | 42 +++ sticker-award/checkstyle.xml | 339 ++++++++++++++++++++++ sticker-award/pom.xml | 69 ++++- 4 files changed, 515 insertions(+), 1 deletion(-) create mode 100644 sticker-award/checkstyle-suppressions.xml create mode 100644 sticker-award/checkstyle.xml diff --git a/sticker-award/README.md b/sticker-award/README.md index 72875e13..511041a3 100644 --- a/sticker-award/README.md +++ b/sticker-award/README.md @@ -60,3 +60,69 @@ The API returns standard HTTP status codes and follows the RFC 7807 Problem Deta Full API documentation is available in OpenAPI format: - Synchronous API: [api.yaml](./docs/api.yaml) - Asynchronous API: [async_api.json](./docs/async_api.json) + +## Building and Running + +### Prerequisites +- Java 21+ +- Maven 3.8+ + +### Development + +Run in development mode: +```bash +./mvnw compile quarkus:dev +``` + +### Testing + +Run tests: +```bash +./mvnw test +``` + +Run integration tests: +```bash +./mvnw verify +``` + +## Code Quality + +### Checkstyle + +This project uses Checkstyle to enforce coding standards based on the Google Java Style Guide. + +**Run Checkstyle validation:** +```bash +# Check code style (runs automatically during build) +./mvnw validate + +# Run only Checkstyle check +./mvnw checkstyle:check + +# Generate Checkstyle report (creates HTML report at target/reports/checkstyle.html) +./mvnw checkstyle:checkstyle +``` + +### Spotless (Code Formatting) + +This project uses Spotless with Google Java Format to automatically fix code style issues. + +**Format your code:** +```bash +# Check if code formatting is correct +./mvnw spotless:check + +# Automatically fix code formatting issues +./mvnw spotless:apply + +# Format and then validate with Checkstyle +./mvnw spotless:apply validate +``` + +**Checkstyle Configuration:** +- Configuration file: `checkstyle.xml` +- Suppressions file: `checkstyle-suppressions.xml` +- Based on Google Java Style Guide (modified for 4-space indentation) +- Enforces 4-space indentation, 100-character line limit +- Checks import ordering, Javadoc completeness, and naming conventions diff --git a/sticker-award/checkstyle-suppressions.xml b/sticker-award/checkstyle-suppressions.xml new file mode 100644 index 00000000..edc89ad7 --- /dev/null +++ b/sticker-award/checkstyle-suppressions.xml @@ -0,0 +1,42 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/sticker-award/checkstyle.xml b/sticker-award/checkstyle.xml new file mode 100644 index 00000000..360bdfcf --- /dev/null +++ b/sticker-award/checkstyle.xml @@ -0,0 +1,339 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/sticker-award/pom.xml b/sticker-award/pom.xml index cdb700da..c0ff0bcd 100644 --- a/sticker-award/pom.xml +++ b/sticker-award/pom.xml @@ -3,7 +3,7 @@ 4.0.0 org.acme sticker-award - 1.0.0-SNAPSHOT + 1.0.5-SNAPSHOT 3.14.0 @@ -15,6 +15,9 @@ 3.22.1 true 3.5.2 + 10.18.2 + 3.5.0 + 2.43.0 @@ -173,6 +176,70 @@ + + + org.apache.maven.plugins + maven-checkstyle-plugin + ${checkstyle-plugin.version} + + checkstyle.xml + true + true + true + warning + false + true + + + + com.puppycrawl.tools + checkstyle + ${checkstyle.version} + + + + + validate + validate + + check + + + + + + + com.diffplug.spotless + spotless-maven-plugin + ${spotless-plugin.version} + + + + + 1.19.2 + + + + + + + + + src/main/java/**/*.java + src/test/java/**/*.java + + + + + + + check + + + + From cc02316758cb19af510efe4108e061d2cf48691c Mon Sep 17 00:00:00 2001 From: Scott Gerring Date: Thu, 5 Jun 2025 12:58:32 +0200 Subject: [PATCH 3/5] chore(sticker-award): address checkstyle lints --- sticker-award/pom.xml | 2 +- .../award/StickerAwardRepository.java | 68 +++-- .../award/StickerAwardResource.java | 80 +++--- .../award/dto/AssignStickerRequest.java | 28 +- .../award/dto/AssignStickerResponse.java | 19 +- .../award/dto/GetUserStickersResponse.java | 14 +- .../dto/RemoveStickerFromUserResponse.java | 19 +- .../award/dto/StickerAssignmentDTO.java | 23 +- .../award/dto/UserAssignmentDTO.java | 19 +- .../award/entity/StickerAssignment.java | 45 ++- .../messaging/StickerAwardEventPublisher.java | 70 ++--- .../common/dto/PagedResponse.java | 125 ++++---- .../common/events/DomainEvent.java | 19 +- .../in/CertificationCompletedEvent.java | 20 +- .../out/StickerAssignedToUserEvent.java | 48 ++-- .../events/out/StickerClaimedEvent.java | 40 +-- .../out/StickerRemovedFromUserEvent.java | 53 ++-- .../stickeraward/health/HealthResource.java | 16 +- .../sticker/StickerImageService.java | 77 +++-- .../sticker/StickerRepository.java | 270 ++++++++++++------ .../stickeraward/sticker/StickerResource.java | 145 ++++++---- .../sticker/dto/CreateStickerRequest.java | 13 +- .../sticker/dto/CreateStickerResponse.java | 12 +- .../sticker/dto/GetAllStickersResponse.java | 15 +- .../dto/GetStickerAssignmentsResponse.java | 19 +- .../stickeraward/sticker/dto/StickerDTO.java | 63 ++-- .../dto/StickerImageUploadResponse.java | 19 +- .../sticker/dto/StickerMetadata.java | 61 ++-- .../sticker/dto/UpdateStickerRequest.java | 29 +- .../stickeraward/sticker/entity/Sticker.java | 66 ++++- ...ava => HealthResourceIntegrationTest.java} | 5 +- .../stickeraward/HealthResourceTest.java | 16 +- .../stickeraward/KafkaTestProfile.java | 13 + .../KafkaTestResourceLifecycleManager.java | 27 +- ... StickerAwardResourceIntegrationTest.java} | 29 +- .../StickerAwardResourceKafkaIT.java | 94 ------ ...ckerAwardResourceKafkaIntegrationTest.java | 93 ++++++ .../StickerAwardResourceTest.java | 153 +++++----- .../messaging/MockEmitterProducer.java | 20 +- .../MockStickerAwardEventPublisher.java | 28 +- .../StickerAwardEventPublisherTest.java | 117 +++++--- .../sticker/StickerImageServiceTest.java | 76 ++--- 42 files changed, 1247 insertions(+), 921 deletions(-) rename sticker-award/src/test/java/com/datadoghq/stickerlandia/stickeraward/{HealthResourceIT.java => HealthResourceIntegrationTest.java} (60%) create mode 100644 sticker-award/src/test/java/com/datadoghq/stickerlandia/stickeraward/KafkaTestProfile.java rename sticker-award/src/test/java/com/datadoghq/stickerlandia/stickeraward/{StickerAwardResourceIT.java => StickerAwardResourceIntegrationTest.java} (55%) delete mode 100644 sticker-award/src/test/java/com/datadoghq/stickerlandia/stickeraward/StickerAwardResourceKafkaIT.java create mode 100644 sticker-award/src/test/java/com/datadoghq/stickerlandia/stickeraward/StickerAwardResourceKafkaIntegrationTest.java diff --git a/sticker-award/pom.xml b/sticker-award/pom.xml index c0ff0bcd..b6fd2c6b 100644 --- a/sticker-award/pom.xml +++ b/sticker-award/pom.xml @@ -3,7 +3,7 @@ 4.0.0 org.acme sticker-award - 1.0.5-SNAPSHOT + 1.0.0-SNAPSHOT 3.14.0 diff --git a/sticker-award/src/main/java/com/datadoghq/stickerlandia/stickeraward/award/StickerAwardRepository.java b/sticker-award/src/main/java/com/datadoghq/stickerlandia/stickeraward/award/StickerAwardRepository.java index 7f53c74f..808e90a3 100644 --- a/sticker-award/src/main/java/com/datadoghq/stickerlandia/stickeraward/award/StickerAwardRepository.java +++ b/sticker-award/src/main/java/com/datadoghq/stickerlandia/stickeraward/award/StickerAwardRepository.java @@ -2,47 +2,64 @@ import com.datadoghq.stickerlandia.stickeraward.award.dto.AssignStickerRequest; import com.datadoghq.stickerlandia.stickeraward.award.dto.AssignStickerResponse; -import com.datadoghq.stickerlandia.stickeraward.award.dto.StickerAssignmentDTO; -import com.datadoghq.stickerlandia.stickeraward.award.dto.RemoveStickerFromUserResponse; import com.datadoghq.stickerlandia.stickeraward.award.dto.GetUserStickersResponse; -import com.datadoghq.stickerlandia.stickeraward.sticker.entity.Sticker; +import com.datadoghq.stickerlandia.stickeraward.award.dto.RemoveStickerFromUserResponse; +import com.datadoghq.stickerlandia.stickeraward.award.dto.StickerAssignmentDTO; import com.datadoghq.stickerlandia.stickeraward.award.entity.StickerAssignment; +import com.datadoghq.stickerlandia.stickeraward.sticker.entity.Sticker; import jakarta.enterprise.context.ApplicationScoped; import jakarta.transaction.Transactional; - import java.time.Instant; import java.util.Date; import java.util.List; import java.util.stream.Collectors; +/** Repository class for managing sticker award operations. */ @ApplicationScoped public class StickerAwardRepository { + /** + * Gets all sticker assignments for a specific user. + * + * @param userId the ID of the user + * @return response containing user's sticker assignments + */ public GetUserStickersResponse getUserStickers(String userId) { List assignments = StickerAssignment.findActiveByUserId(userId); - List stickerAssignmentDTOS = assignments.stream() - .map(assignment -> { - Sticker sticker = assignment.getSticker(); - StickerAssignmentDTO dto = new StickerAssignmentDTO(); - dto.setStickerId(sticker.getStickerId()); - dto.setName(sticker.getName()); - dto.setDescription(sticker.getDescription()); - dto.setImageUrl(sticker.getImageUrl()); - dto.setAssignedAt(Date.from(assignment.getAssignedAt())); - return dto; - }) - .collect(Collectors.toList()); + List stickerAssignmentDtos = + assignments.stream() + .map( + assignment -> { + Sticker sticker = assignment.getSticker(); + StickerAssignmentDTO dto = new StickerAssignmentDTO(); + dto.setStickerId(sticker.getStickerId()); + dto.setName(sticker.getName()); + dto.setDescription(sticker.getDescription()); + dto.setImageUrl(sticker.getImageUrl()); + dto.setAssignedAt(Date.from(assignment.getAssignedAt())); + return dto; + }) + .collect(Collectors.toList()); GetUserStickersResponse response = new GetUserStickersResponse(); response.setUserId(userId); - response.setStickers(stickerAssignmentDTOS); + response.setStickers(stickerAssignmentDtos); return response; } + /** + * Assigns a sticker to a user. + * + * @param userId the ID of the user + * @param stickerId the ID of the sticker + * @param request the assignment request + * @return response containing assignment details, or null if assignment failed + */ @Transactional - public AssignStickerResponse assignStickerToUser(String userId, String stickerId, AssignStickerRequest request) { + public AssignStickerResponse assignStickerToUser( + String userId, String stickerId, AssignStickerRequest request) { // Find the sticker to assign Sticker sticker = Sticker.findById(stickerId); if (sticker == null) { @@ -50,7 +67,8 @@ public AssignStickerResponse assignStickerToUser(String userId, String stickerId } // Check if user already has this sticker - StickerAssignment existingAssignment = StickerAssignment.findActiveByUserAndSticker(userId, stickerId); + StickerAssignment existingAssignment = + StickerAssignment.findActiveByUserAndSticker(userId, stickerId); if (existingAssignment != null) { throw new IllegalStateException("User already has this sticker assigned"); } @@ -72,10 +90,18 @@ public AssignStickerResponse assignStickerToUser(String userId, String stickerId return response; } + /** + * Removes a sticker assignment from a user. + * + * @param userId the ID of the user + * @param stickerId the ID of the sticker to remove + * @return response containing removal details, or null if removal failed + */ @Transactional public RemoveStickerFromUserResponse removeStickerFromUser(String userId, String stickerId) { // Find the active assignment - StickerAssignment assignment = StickerAssignment.findActiveByUserAndSticker(userId, stickerId); + StickerAssignment assignment = + StickerAssignment.findActiveByUserAndSticker(userId, stickerId); if (assignment == null) { return null; // Assignment not found } @@ -92,4 +118,4 @@ public RemoveStickerFromUserResponse removeStickerFromUser(String userId, String return response; } -} \ No newline at end of file +} diff --git a/sticker-award/src/main/java/com/datadoghq/stickerlandia/stickeraward/award/StickerAwardResource.java b/sticker-award/src/main/java/com/datadoghq/stickerlandia/stickeraward/award/StickerAwardResource.java index d792e13e..cef1bd57 100644 --- a/sticker-award/src/main/java/com/datadoghq/stickerlandia/stickeraward/award/StickerAwardResource.java +++ b/sticker-award/src/main/java/com/datadoghq/stickerlandia/stickeraward/award/StickerAwardResource.java @@ -2,9 +2,8 @@ import com.datadoghq.stickerlandia.stickeraward.award.dto.AssignStickerRequest; import com.datadoghq.stickerlandia.stickeraward.award.dto.AssignStickerResponse; -import com.datadoghq.stickerlandia.stickeraward.award.dto.StickerAssignmentDTO; -import com.datadoghq.stickerlandia.stickeraward.award.dto.RemoveStickerFromUserResponse; import com.datadoghq.stickerlandia.stickeraward.award.dto.GetUserStickersResponse; +import com.datadoghq.stickerlandia.stickeraward.award.dto.RemoveStickerFromUserResponse; import com.datadoghq.stickerlandia.stickeraward.award.messaging.StickerAwardEventPublisher; import io.smallrye.common.constraint.NotNull; import jakarta.inject.Inject; @@ -16,43 +15,52 @@ import jakarta.ws.rs.Path; import jakarta.ws.rs.PathParam; import jakarta.ws.rs.Produces; +import jakarta.ws.rs.core.MediaType; import jakarta.ws.rs.core.Response; import org.eclipse.microprofile.openapi.annotations.Operation; import org.slf4j.Logger; import org.slf4j.LoggerFactory; - -@Path("/api") +/** REST resource for managing sticker awards and assignments. */ +@Path("/api/award/v1/users") public class StickerAwardResource { - + private static final Logger log = LoggerFactory.getLogger(StickerAwardResource.class); - - @Inject - StickerAwardEventPublisher eventPublisher; - @Inject - StickerAwardRepository stickerAwardRepository; + @Inject StickerAwardEventPublisher eventPublisher; - @Operation(description = "Get stickers assigned to a user (access controlled based on caller identity)") - @Path("/award/v1/users/{userId}/stickers") + @Inject StickerAwardRepository stickerAwardRepository; + + @Operation( + description = + "Get stickers assigned to a user (access controlled based on caller identity)") + @Path("/{userId}/stickers") @GET @Produces("application/json") public GetUserStickersResponse getUserStickers(@PathParam("userId") String userId) { return stickerAwardRepository.getUserStickers(userId); } - @Operation(description = "Assign a new sticker to a user (access controlled based on caller identity)") - @Path("/award/v1/users/{userId}/stickers") + /** + * Assigns a sticker to a user. + * + * @param userId the ID of the user to assign the sticker to + * @param request the assignment request containing sticker details + * @return response containing assignment details + */ @POST - @Produces("application/json") - @Consumes("application/json") + @Path("/{userId}/stickers") + @Operation(summary = "Assign a sticker to a user") + @Consumes(MediaType.APPLICATION_JSON) + @Produces(MediaType.APPLICATION_JSON) @Transactional - public Response assignStickerToUser(@PathParam("userId") String userId, - @NotNull AssignStickerRequest data) { + public Response assignStickerToUser( + @PathParam("userId") String userId, @NotNull AssignStickerRequest request) { try { - String stickerId = data.getStickerId(); - AssignStickerResponse response = stickerAwardRepository.assignStickerToUser(userId, stickerId, data); + String stickerId = request.getStickerId(); + AssignStickerResponse response = + stickerAwardRepository.assignStickerToUser(userId, stickerId, request); if (response == null) { return Response.status(Response.Status.BAD_REQUEST).build(); } @@ -60,29 +68,36 @@ public Response assignStickerToUser(@PathParam("userId") String userId, // Publish events - Note: We'll need to modify event publisher to work with DTOs // For now, skip event publishing until we refactor the event publisher // try { - // log.info("Publishing sticker assignment events for userId={}, stickerId={}", userId, stickerId); + // log.info("Publishing sticker assignment events for userId={}, stickerId={}", + // userId, stickerId); // eventPublisher.publishStickerAssigned(...); // } catch (Exception e) { // log.error("Failed to publish sticker assignment events", e); // } - return Response.status(Response.Status.CREATED) - .entity(response) - .build(); + return Response.status(Response.Status.CREATED).entity(response).build(); } catch (IllegalStateException e) { return Response.status(Response.Status.CONFLICT).build(); } } - @Operation(description = "Remove a sticker assignment from a user (access controlled based on caller identity)") - @Path("/award/v1/users/{userId}/stickers/{stickerId}") + /** + * Removes a sticker assignment from a user. + * + * @param userId the ID of the user + * @param stickerId the ID of the sticker to remove + * @return response indicating the removal result + */ @DELETE - @Produces("application/json") + @Path("/{userId}/stickers/{stickerId}") + @Operation(summary = "Remove a sticker assignment from a user") + @Produces(MediaType.APPLICATION_JSON) @Transactional - public Response removeStickerFromUser(@PathParam("userId") String userId, - @PathParam("stickerId") String stickerId) { + public Response removeStickerFromUser( + @PathParam("userId") String userId, @PathParam("stickerId") String stickerId) { - RemoveStickerFromUserResponse response = stickerAwardRepository.removeStickerFromUser(userId, stickerId); + RemoveStickerFromUserResponse response = + stickerAwardRepository.removeStickerFromUser(userId, stickerId); if (response == null) { return Response.status(Response.Status.NOT_FOUND).build(); } @@ -90,7 +105,8 @@ public Response removeStickerFromUser(@PathParam("userId") String userId, // Publish events - Note: We'll need to modify event publisher to work with DTOs // For now, skip event publishing until we refactor the event publisher // try { - // log.info("Publishing sticker removal event for userId={}, stickerId={}", userId, stickerId); + // log.info("Publishing sticker removal event for userId={}, stickerId={}", userId, + // stickerId); // eventPublisher.publishStickerRemoved(...); // } catch (Exception e) { // log.error("Failed to publish sticker removal event", e); @@ -98,4 +114,4 @@ public Response removeStickerFromUser(@PathParam("userId") String userId, return Response.ok(response).build(); } -} \ No newline at end of file +} diff --git a/sticker-award/src/main/java/com/datadoghq/stickerlandia/stickeraward/award/dto/AssignStickerRequest.java b/sticker-award/src/main/java/com/datadoghq/stickerlandia/stickeraward/award/dto/AssignStickerRequest.java index 7b0f7509..c131e001 100644 --- a/sticker-award/src/main/java/com/datadoghq/stickerlandia/stickeraward/award/dto/AssignStickerRequest.java +++ b/sticker-award/src/main/java/com/datadoghq/stickerlandia/stickeraward/award/dto/AssignStickerRequest.java @@ -1,44 +1,29 @@ - package com.datadoghq.stickerlandia.stickeraward.award.dto; -import javax.annotation.processing.Generated; import com.fasterxml.jackson.annotation.JsonInclude; import com.fasterxml.jackson.annotation.JsonProperty; import com.fasterxml.jackson.annotation.JsonPropertyOrder; +import javax.annotation.processing.Generated; @JsonInclude(JsonInclude.Include.NON_NULL) -@JsonPropertyOrder({ - "stickerId", - "reason" -}) +@JsonPropertyOrder({"stickerId", "reason"}) @Generated("jsonschema2pojo") public class AssignStickerRequest { - /** - * - * (Required) - * - */ + /** (Required). */ @JsonProperty("stickerId") private String stickerId; + @JsonProperty("reason") private String reason; - /** - * - * (Required) - * - */ + /** (Required). */ @JsonProperty("stickerId") public String getStickerId() { return stickerId; } - /** - * - * (Required) - * - */ + /** (Required). */ @JsonProperty("stickerId") public void setStickerId(String stickerId) { this.stickerId = stickerId; @@ -53,5 +38,4 @@ public String getReason() { public void setReason(String reason) { this.reason = reason; } - } diff --git a/sticker-award/src/main/java/com/datadoghq/stickerlandia/stickeraward/award/dto/AssignStickerResponse.java b/sticker-award/src/main/java/com/datadoghq/stickerlandia/stickeraward/award/dto/AssignStickerResponse.java index 469950d3..295ff3d7 100644 --- a/sticker-award/src/main/java/com/datadoghq/stickerlandia/stickeraward/award/dto/AssignStickerResponse.java +++ b/sticker-award/src/main/java/com/datadoghq/stickerlandia/stickeraward/award/dto/AssignStickerResponse.java @@ -1,27 +1,27 @@ - package com.datadoghq.stickerlandia.stickeraward.award.dto; -import java.util.Date; -import javax.annotation.processing.Generated; import com.fasterxml.jackson.annotation.JsonFormat; import com.fasterxml.jackson.annotation.JsonInclude; import com.fasterxml.jackson.annotation.JsonProperty; import com.fasterxml.jackson.annotation.JsonPropertyOrder; +import java.util.Date; +import javax.annotation.processing.Generated; @JsonInclude(JsonInclude.Include.NON_NULL) -@JsonPropertyOrder({ - "userId", - "stickerId", - "assignedAt" -}) +@JsonPropertyOrder({"userId", "stickerId", "assignedAt"}) @Generated("jsonschema2pojo") public class AssignStickerResponse { @JsonProperty("userId") private String userId; + @JsonProperty("stickerId") private String stickerId; - @JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd'T'HH:mm:ss'Z'", timezone = "UTC") + + @JsonFormat( + shape = JsonFormat.Shape.STRING, + pattern = "yyyy-MM-dd'T'HH:mm:ss'Z'", + timezone = "UTC") @JsonProperty("assignedAt") private Date assignedAt; @@ -54,5 +54,4 @@ public Date getAssignedAt() { public void setAssignedAt(Date assignedAt) { this.assignedAt = assignedAt; } - } diff --git a/sticker-award/src/main/java/com/datadoghq/stickerlandia/stickeraward/award/dto/GetUserStickersResponse.java b/sticker-award/src/main/java/com/datadoghq/stickerlandia/stickeraward/award/dto/GetUserStickersResponse.java index bb784e71..e1fb9d05 100644 --- a/sticker-award/src/main/java/com/datadoghq/stickerlandia/stickeraward/award/dto/GetUserStickersResponse.java +++ b/sticker-award/src/main/java/com/datadoghq/stickerlandia/stickeraward/award/dto/GetUserStickersResponse.java @@ -1,23 +1,20 @@ - package com.datadoghq.stickerlandia.stickeraward.award.dto; -import java.util.ArrayList; -import java.util.List; -import javax.annotation.processing.Generated; import com.fasterxml.jackson.annotation.JsonInclude; import com.fasterxml.jackson.annotation.JsonProperty; import com.fasterxml.jackson.annotation.JsonPropertyOrder; +import java.util.ArrayList; +import java.util.List; +import javax.annotation.processing.Generated; @JsonInclude(JsonInclude.Include.NON_NULL) -@JsonPropertyOrder({ - "userId", - "stickers" -}) +@JsonPropertyOrder({"userId", "stickers"}) @Generated("jsonschema2pojo") public class GetUserStickersResponse { @JsonProperty("userId") private String userId; + @JsonProperty("stickers") private List stickers = new ArrayList(); @@ -40,5 +37,4 @@ public List getStickers() { public void setStickers(List stickers) { this.stickers = stickers; } - } diff --git a/sticker-award/src/main/java/com/datadoghq/stickerlandia/stickeraward/award/dto/RemoveStickerFromUserResponse.java b/sticker-award/src/main/java/com/datadoghq/stickerlandia/stickeraward/award/dto/RemoveStickerFromUserResponse.java index 386638c9..3ce0df18 100644 --- a/sticker-award/src/main/java/com/datadoghq/stickerlandia/stickeraward/award/dto/RemoveStickerFromUserResponse.java +++ b/sticker-award/src/main/java/com/datadoghq/stickerlandia/stickeraward/award/dto/RemoveStickerFromUserResponse.java @@ -1,27 +1,27 @@ - package com.datadoghq.stickerlandia.stickeraward.award.dto; -import java.util.Date; -import javax.annotation.processing.Generated; import com.fasterxml.jackson.annotation.JsonFormat; import com.fasterxml.jackson.annotation.JsonInclude; import com.fasterxml.jackson.annotation.JsonProperty; import com.fasterxml.jackson.annotation.JsonPropertyOrder; +import java.util.Date; +import javax.annotation.processing.Generated; @JsonInclude(JsonInclude.Include.NON_NULL) -@JsonPropertyOrder({ - "userId", - "stickerId", - "removedAt" -}) +@JsonPropertyOrder({"userId", "stickerId", "removedAt"}) @Generated("jsonschema2pojo") public class RemoveStickerFromUserResponse { @JsonProperty("userId") private String userId; + @JsonProperty("stickerId") private String stickerId; - @JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd'T'HH:mm:ss'Z'", timezone = "UTC") + + @JsonFormat( + shape = JsonFormat.Shape.STRING, + pattern = "yyyy-MM-dd'T'HH:mm:ss'Z'", + timezone = "UTC") @JsonProperty("removedAt") private Date removedAt; @@ -54,5 +54,4 @@ public Date getRemovedAt() { public void setRemovedAt(Date removedAt) { this.removedAt = removedAt; } - } diff --git a/sticker-award/src/main/java/com/datadoghq/stickerlandia/stickeraward/award/dto/StickerAssignmentDTO.java b/sticker-award/src/main/java/com/datadoghq/stickerlandia/stickeraward/award/dto/StickerAssignmentDTO.java index 3da0889b..5efa1cd8 100644 --- a/sticker-award/src/main/java/com/datadoghq/stickerlandia/stickeraward/award/dto/StickerAssignmentDTO.java +++ b/sticker-award/src/main/java/com/datadoghq/stickerlandia/stickeraward/award/dto/StickerAssignmentDTO.java @@ -1,33 +1,33 @@ - package com.datadoghq.stickerlandia.stickeraward.award.dto; -import java.util.Date; -import javax.annotation.processing.Generated; import com.fasterxml.jackson.annotation.JsonFormat; import com.fasterxml.jackson.annotation.JsonInclude; import com.fasterxml.jackson.annotation.JsonProperty; import com.fasterxml.jackson.annotation.JsonPropertyOrder; +import java.util.Date; +import javax.annotation.processing.Generated; @JsonInclude(JsonInclude.Include.NON_NULL) -@JsonPropertyOrder({ - "stickerId", - "name", - "description", - "imageUrl", - "assignedAt" -}) +@JsonPropertyOrder({"stickerId", "name", "description", "imageUrl", "assignedAt"}) @Generated("jsonschema2pojo") public class StickerAssignmentDTO { @JsonProperty("stickerId") private String stickerId; + @JsonProperty("name") private String name; + @JsonProperty("description") private String description; + @JsonProperty("imageUrl") private String imageUrl; - @JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd'T'HH:mm:ss'Z'", timezone = "UTC") + + @JsonFormat( + shape = JsonFormat.Shape.STRING, + pattern = "yyyy-MM-dd'T'HH:mm:ss'Z'", + timezone = "UTC") @JsonProperty("assignedAt") private Date assignedAt; @@ -80,5 +80,4 @@ public Date getAssignedAt() { public void setAssignedAt(Date assignedAt) { this.assignedAt = assignedAt; } - } diff --git a/sticker-award/src/main/java/com/datadoghq/stickerlandia/stickeraward/award/dto/UserAssignmentDTO.java b/sticker-award/src/main/java/com/datadoghq/stickerlandia/stickeraward/award/dto/UserAssignmentDTO.java index 343546ce..8de0c179 100644 --- a/sticker-award/src/main/java/com/datadoghq/stickerlandia/stickeraward/award/dto/UserAssignmentDTO.java +++ b/sticker-award/src/main/java/com/datadoghq/stickerlandia/stickeraward/award/dto/UserAssignmentDTO.java @@ -1,24 +1,26 @@ package com.datadoghq.stickerlandia.stickeraward.award.dto; -import java.util.Date; import com.fasterxml.jackson.annotation.JsonFormat; import com.fasterxml.jackson.annotation.JsonInclude; import com.fasterxml.jackson.annotation.JsonProperty; import com.fasterxml.jackson.annotation.JsonPropertyOrder; +import java.util.Date; +/** DTO representing a user assignment with details about when and why a sticker was assigned. */ @JsonInclude(JsonInclude.Include.NON_NULL) -@JsonPropertyOrder({ - "userId", - "assignedAt", - "reason" -}) +@JsonPropertyOrder({"userId", "assignedAt", "reason"}) public class UserAssignmentDTO { @JsonProperty("userId") private String userId; - @JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd'T'HH:mm:ss'Z'", timezone = "UTC") + + @JsonFormat( + shape = JsonFormat.Shape.STRING, + pattern = "yyyy-MM-dd'T'HH:mm:ss'Z'", + timezone = "UTC") @JsonProperty("assignedAt") private Date assignedAt; + @JsonProperty("reason") private String reason; @@ -51,5 +53,4 @@ public String getReason() { public void setReason(String reason) { this.reason = reason; } - -} \ No newline at end of file +} diff --git a/sticker-award/src/main/java/com/datadoghq/stickerlandia/stickeraward/award/entity/StickerAssignment.java b/sticker-award/src/main/java/com/datadoghq/stickerlandia/stickeraward/award/entity/StickerAssignment.java index 0a3bd5c6..c1e5ae34 100644 --- a/sticker-award/src/main/java/com/datadoghq/stickerlandia/stickeraward/award/entity/StickerAssignment.java +++ b/sticker-award/src/main/java/com/datadoghq/stickerlandia/stickeraward/award/entity/StickerAssignment.java @@ -12,13 +12,14 @@ import jakarta.persistence.ManyToOne; import jakarta.persistence.Table; import jakarta.persistence.UniqueConstraint; - import java.time.Instant; import java.util.List; +/** Entity representing the assignment of a sticker to a user. */ @Entity -@Table(name = "sticker_assignments", - uniqueConstraints = @UniqueConstraint(columnNames = {"user_id", "sticker_id"})) +@Table( + name = "sticker_assignments", + uniqueConstraints = @UniqueConstraint(columnNames = {"user_id", "sticker_id"})) public class StickerAssignment extends PanacheEntityBase { @Id @@ -43,10 +44,15 @@ public class StickerAssignment extends PanacheEntityBase { private String reason; // Default constructor for JPA - public StickerAssignment() { - } - - // Constructor with fields + public StickerAssignment() {} + + /** + * Constructor with fields for creating a new sticker assignment. + * + * @param userId the ID of the user + * @param sticker the sticker to assign + * @param reason the reason for the assignment + */ public StickerAssignment(String userId, Sticker sticker, String reason) { this.userId = userId; this.sticker = sticker; @@ -104,17 +110,38 @@ public void setReason(String reason) { } // Helper methods + /** + * Checks if this assignment is currently active (not removed). + * + * @return true if the assignment is active, false otherwise + */ public boolean isActive() { return removedAt == null; } // Repository methods + /** + * Finds all active sticker assignments for a specific user. + * + * @param userId the ID of the user + * @return list of active sticker assignments + */ public static List findActiveByUserId(String userId) { return list("userId = ?1 AND removedAt IS NULL", userId); } + /** + * Finds an active sticker assignment for a specific user and sticker. + * + * @param userId the ID of the user + * @param stickerId the ID of the sticker + * @return the active assignment, or null if not found + */ public static StickerAssignment findActiveByUserAndSticker(String userId, String stickerId) { - return find("userId = ?1 AND sticker.stickerId = ?2 AND removedAt IS NULL", userId, stickerId) + return find( + "userId = ?1 AND sticker.stickerId = ?2 AND removedAt IS NULL", + userId, + stickerId) .firstResult(); } @@ -125,4 +152,4 @@ public static List findActiveByStickerId(String stickerId) { public static long countActiveByStickerId(String stickerId) { return count("sticker.stickerId = ?1 AND removedAt IS NULL", stickerId); } -} \ No newline at end of file +} diff --git a/sticker-award/src/main/java/com/datadoghq/stickerlandia/stickeraward/award/messaging/StickerAwardEventPublisher.java b/sticker-award/src/main/java/com/datadoghq/stickerlandia/stickeraward/award/messaging/StickerAwardEventPublisher.java index 81c692a6..9e10804e 100644 --- a/sticker-award/src/main/java/com/datadoghq/stickerlandia/stickeraward/award/messaging/StickerAwardEventPublisher.java +++ b/sticker-award/src/main/java/com/datadoghq/stickerlandia/stickeraward/award/messaging/StickerAwardEventPublisher.java @@ -1,92 +1,98 @@ package com.datadoghq.stickerlandia.stickeraward.award.messaging; +import com.datadoghq.stickerlandia.stickeraward.award.entity.StickerAssignment; +import com.datadoghq.stickerlandia.stickeraward.common.events.out.StickerAssignedToUserEvent; +import com.datadoghq.stickerlandia.stickeraward.common.events.out.StickerClaimedEvent; +import com.datadoghq.stickerlandia.stickeraward.common.events.out.StickerRemovedFromUserEvent; import jakarta.enterprise.context.ApplicationScoped; import jakarta.inject.Inject; - import org.eclipse.microprofile.reactive.messaging.Channel; import org.eclipse.microprofile.reactive.messaging.Emitter; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import com.datadoghq.stickerlandia.stickeraward.award.entity.StickerAssignment; -import com.datadoghq.stickerlandia.stickeraward.common.events.out.StickerAssignedToUserEvent; -import com.datadoghq.stickerlandia.stickeraward.common.events.out.StickerClaimedEvent; -import com.datadoghq.stickerlandia.stickeraward.common.events.out.StickerRemovedFromUserEvent; - -/** - * Service responsible for publishing sticker-related events to Kafka topics. - */ +/** Service responsible for publishing sticker-related events to Kafka topics. */ @ApplicationScoped public class StickerAwardEventPublisher { - + private static final Logger log = LoggerFactory.getLogger(StickerAwardEventPublisher.class); - + @Inject @Channel("stickers-assigned") Emitter stickerAssignedEmitter; - + @Inject @Channel("stickers-removed") Emitter stickerRemovedEmitter; - + @Inject @Channel("stickers-claimed") Emitter stickerClaimedEmitter; - + /** * Publishes a sticker assigned event when a sticker is assigned to a user. - * + * * @param assignment The sticker assignment entity */ public void publishStickerAssigned(StickerAssignment assignment) { try { - StickerAssignedToUserEvent event = StickerAssignedToUserEvent.fromAssignment(assignment); - log.info("Publishing sticker assigned event: userId={}, stickerId={}", - event.getAccountId(), event.getStickerId()); + StickerAssignedToUserEvent event = + StickerAssignedToUserEvent.fromAssignment(assignment); + log.info( + "Publishing sticker assigned event: userId={}, stickerId={}", + event.getAccountId(), + event.getStickerId()); stickerAssignedEmitter.send(event); - + // Also publish sticker claimed event for the user management service publishStickerClaimed(assignment); } catch (Exception e) { log.error("Error publishing sticker assigned event", e); } } - + /** * Publishes a sticker removed event when a sticker is removed from a user. - * + * * @param assignment The sticker assignment entity with removed status */ public void publishStickerRemoved(StickerAssignment assignment) { if (assignment.getRemovedAt() == null) { - log.warn("Cannot publish removal event for active assignment: userId={}, stickerId={}", - assignment.getUserId(), assignment.getSticker().getStickerId()); + log.warn( + "Cannot publish removal event for active assignment: userId={}, stickerId={}", + assignment.getUserId(), + assignment.getSticker().getStickerId()); return; } - + try { - StickerRemovedFromUserEvent event = StickerRemovedFromUserEvent.fromAssignment(assignment); - log.info("Publishing sticker removed event: userId={}, stickerId={}", - event.getAccountId(), event.getStickerId()); + StickerRemovedFromUserEvent event = + StickerRemovedFromUserEvent.fromAssignment(assignment); + log.info( + "Publishing sticker removed event: userId={}, stickerId={}", + event.getAccountId(), + event.getStickerId()); stickerRemovedEmitter.send(event); } catch (Exception e) { log.error("Error publishing sticker removed event", e); } } - + /** * Publishes a sticker claimed event for the user management service to update claim count. - * + * * @param assignment The sticker assignment entity */ private void publishStickerClaimed(StickerAssignment assignment) { try { StickerClaimedEvent event = StickerClaimedEvent.fromAssignment(assignment); - log.info("Publishing sticker claimed event: userId={}, stickerId={}", - event.getAccountId(), event.getStickerId()); + log.info( + "Publishing sticker claimed event: userId={}, stickerId={}", + event.getAccountId(), + event.getStickerId()); stickerClaimedEmitter.send(event); } catch (Exception e) { log.error("Error publishing sticker claimed event", e); } } -} \ No newline at end of file +} diff --git a/sticker-award/src/main/java/com/datadoghq/stickerlandia/stickeraward/common/dto/PagedResponse.java b/sticker-award/src/main/java/com/datadoghq/stickerlandia/stickeraward/common/dto/PagedResponse.java index 33dfc96c..1b00e98f 100644 --- a/sticker-award/src/main/java/com/datadoghq/stickerlandia/stickeraward/common/dto/PagedResponse.java +++ b/sticker-award/src/main/java/com/datadoghq/stickerlandia/stickeraward/common/dto/PagedResponse.java @@ -4,115 +4,124 @@ import com.fasterxml.jackson.annotation.JsonProperty; import com.fasterxml.jackson.annotation.JsonPropertyDescription; import com.fasterxml.jackson.annotation.JsonPropertyOrder; +import java.util.List; +/** + * Generic DTO for paginated responses. + * + * @param the type of items in the response + */ @JsonInclude(JsonInclude.Include.NON_NULL) -@JsonPropertyOrder({ - "page", - "size", - "total", - "totalPages" -}) -public class PagedResponse { - - /** - * Current page number (0-based) - * - */ +@JsonPropertyOrder({"data", "page", "size", "total", "totalPages", "hasNext", "hasPrevious"}) +public class PagedResponse { + + /** The current page number. */ @JsonProperty("page") @JsonPropertyDescription("Current page number (0-based)") private Integer page; - /** - * Number of items per page - * - */ + + /** The page size. */ @JsonProperty("size") @JsonPropertyDescription("Number of items per page") private Integer size; - /** - * Total number of items - * - */ + + /** The total number of items. */ @JsonProperty("total") @JsonPropertyDescription("Total number of items") private Integer total; - /** - * Total number of pages - * - */ + + /** The total number of pages. */ @JsonProperty("totalPages") @JsonPropertyDescription("Total number of pages") private Integer totalPages; - /** - * Current page number (0-based) - * - */ + /** Whether there is a next page. */ + @JsonProperty("hasNext") + public Boolean getHasNext() { + return null; + } + + /** Whether there is a previous page. */ + @JsonProperty("hasPrevious") + public Boolean getHasPrevious() { + return null; + } + + /** The list of items in this page. */ + @JsonProperty("data") + public List getData() { + return null; + } + + /** The first item index on this page. */ + @JsonProperty("first") + public Integer getFirst() { + return null; + } + + /** The last item index on this page. */ + @JsonProperty("last") + public Integer getLast() { + return null; + } + + /** Whether this is the first page. */ + @JsonProperty("isFirst") + public Boolean getIsFirst() { + return null; + } + + /** Whether this is the last page. */ + @JsonProperty("isLast") + public Boolean getIsLast() { + return null; + } + + /** Current page number (0-based). */ @JsonProperty("page") public Integer getPage() { return page; } - /** - * Current page number (0-based) - * - */ + /** Current page number (0-based). */ @JsonProperty("page") public void setPage(Integer page) { this.page = page; } - /** - * Number of items per page - * - */ + /** Number of items per page. */ @JsonProperty("size") public Integer getSize() { return size; } - /** - * Number of items per page - * - */ + /** Number of items per page. */ @JsonProperty("size") public void setSize(Integer size) { this.size = size; } - /** - * Total number of items - * - */ + /** Total number of items. */ @JsonProperty("total") public Integer getTotal() { return total; } - /** - * Total number of items - * - */ + /** Total number of items. */ @JsonProperty("total") public void setTotal(Integer total) { this.total = total; } - /** - * Total number of pages - * - */ + /** Total number of pages. */ @JsonProperty("totalPages") public Integer getTotalPages() { return totalPages; } - /** - * Total number of pages - * - */ + /** Total number of pages. */ @JsonProperty("totalPages") public void setTotalPages(Integer totalPages) { this.totalPages = totalPages; } - -} \ No newline at end of file +} diff --git a/sticker-award/src/main/java/com/datadoghq/stickerlandia/stickeraward/common/events/DomainEvent.java b/sticker-award/src/main/java/com/datadoghq/stickerlandia/stickeraward/common/events/DomainEvent.java index 2c465697..141a6b82 100644 --- a/sticker-award/src/main/java/com/datadoghq/stickerlandia/stickeraward/common/events/DomainEvent.java +++ b/sticker-award/src/main/java/com/datadoghq/stickerlandia/stickeraward/common/events/DomainEvent.java @@ -1,32 +1,29 @@ package com.datadoghq.stickerlandia.stickeraward.common.events; -/** - * Base abstract class for all domain events. - * This class should be extended by all event types. - */ +/** Base abstract class for all domain events. This class should be extended by all event types. */ public abstract class DomainEvent { - + private String eventName; private String eventVersion; - + protected DomainEvent(String eventName, String eventVersion) { this.eventName = eventName; this.eventVersion = eventVersion; } - + public String getEventName() { return eventName; } - + public void setEventName(String eventName) { this.eventName = eventName; } - + public String getEventVersion() { return eventVersion; } - + public void setEventVersion(String eventVersion) { this.eventVersion = eventVersion; } -} \ No newline at end of file +} diff --git a/sticker-award/src/main/java/com/datadoghq/stickerlandia/stickeraward/common/events/in/CertificationCompletedEvent.java b/sticker-award/src/main/java/com/datadoghq/stickerlandia/stickeraward/common/events/in/CertificationCompletedEvent.java index e4c2594e..38bebf24 100644 --- a/sticker-award/src/main/java/com/datadoghq/stickerlandia/stickeraward/common/events/in/CertificationCompletedEvent.java +++ b/sticker-award/src/main/java/com/datadoghq/stickerlandia/stickeraward/common/events/in/CertificationCompletedEvent.java @@ -3,36 +3,36 @@ import java.time.Instant; /** - * Event received when a certification is completed. - * Subscribed from the 'certifications.certificationCompleted.v1' topic. + * Event received when a certification is completed. Subscribed from the + * 'certifications.certificationCompleted.v1' topic. */ public class CertificationCompletedEvent { - + private String accountId; private String certificationId; private Instant completedAt; - + public String getAccountId() { return accountId; } - + public void setAccountId(String accountId) { this.accountId = accountId; } - + public String getCertificationId() { return certificationId; } - + public void setCertificationId(String certificationId) { this.certificationId = certificationId; } - + public Instant getCompletedAt() { return completedAt; } - + public void setCompletedAt(Instant completedAt) { this.completedAt = completedAt; } -} \ No newline at end of file +} diff --git a/sticker-award/src/main/java/com/datadoghq/stickerlandia/stickeraward/common/events/out/StickerAssignedToUserEvent.java b/sticker-award/src/main/java/com/datadoghq/stickerlandia/stickeraward/common/events/out/StickerAssignedToUserEvent.java index aacdd100..6e7fbd47 100644 --- a/sticker-award/src/main/java/com/datadoghq/stickerlandia/stickeraward/common/events/out/StickerAssignedToUserEvent.java +++ b/sticker-award/src/main/java/com/datadoghq/stickerlandia/stickeraward/common/events/out/StickerAssignedToUserEvent.java @@ -1,33 +1,31 @@ package com.datadoghq.stickerlandia.stickeraward.common.events.out; -import java.time.Instant; - import com.datadoghq.stickerlandia.stickeraward.award.entity.StickerAssignment; import com.datadoghq.stickerlandia.stickeraward.common.events.DomainEvent; +import com.fasterxml.jackson.annotation.JsonProperty; +import java.time.Instant; /** - * Event published when a sticker is assigned to a user. - * Published to the 'stickers.stickerAssignedToUser.v1' topic. + * Event published when a sticker is assigned to a user. Published to the + * 'stickers.stickerAssignedToUser.v1' topic. */ public class StickerAssignedToUserEvent extends DomainEvent { - + private static final String EVENT_NAME = "StickerAssignedToUser"; private static final String EVENT_VERSION = "v1"; - + private String accountId; private String stickerId; private Instant assignedAt; - - /** - * Default constructor for serialization frameworks - */ + + /** Default constructor for serialization frameworks. */ public StickerAssignedToUserEvent() { super(EVENT_NAME, EVENT_VERSION); } - + /** - * Create a new event from a sticker assignment entity - * + * Create a new event from a sticker assignment entity. + * * @param assignment The sticker assignment entity * @return A new event instance */ @@ -38,28 +36,36 @@ public static StickerAssignedToUserEvent fromAssignment(StickerAssignment assign event.setAssignedAt(assignment.getAssignedAt()); return event; } - - public String getAccountId() { + + /** The ID of the user who was assigned the sticker. */ + @JsonProperty("userId") + public String getUserId() { return accountId; } - + public void setAccountId(String accountId) { this.accountId = accountId; } - + + public String getAccountId() { + return accountId; + } + + /** The ID of the sticker that was assigned. */ + @JsonProperty("stickerId") public String getStickerId() { return stickerId; } - + public void setStickerId(String stickerId) { this.stickerId = stickerId; } - + public Instant getAssignedAt() { return assignedAt; } - + public void setAssignedAt(Instant assignedAt) { this.assignedAt = assignedAt; } -} \ No newline at end of file +} diff --git a/sticker-award/src/main/java/com/datadoghq/stickerlandia/stickeraward/common/events/out/StickerClaimedEvent.java b/sticker-award/src/main/java/com/datadoghq/stickerlandia/stickeraward/common/events/out/StickerClaimedEvent.java index a40e9d1e..393e2fee 100644 --- a/sticker-award/src/main/java/com/datadoghq/stickerlandia/stickeraward/common/events/out/StickerClaimedEvent.java +++ b/sticker-award/src/main/java/com/datadoghq/stickerlandia/stickeraward/common/events/out/StickerClaimedEvent.java @@ -2,29 +2,27 @@ import com.datadoghq.stickerlandia.stickeraward.award.entity.StickerAssignment; import com.datadoghq.stickerlandia.stickeraward.common.events.DomainEvent; +import com.fasterxml.jackson.annotation.JsonProperty; /** - * Event published when a user claims a sticker. - * Published to the 'users.stickerClaimed.v1' topic. + * Event published when a user claims a sticker. Published to the 'users.stickerClaimed.v1' topic. */ public class StickerClaimedEvent extends DomainEvent { - + private static final String EVENT_NAME = "StickerClaimed"; private static final String EVENT_VERSION = "v1"; - + private String accountId; private String stickerId; - - /** - * Default constructor for serialization frameworks - */ + + /** Default constructor for serialization frameworks. */ public StickerClaimedEvent() { super(EVENT_NAME, EVENT_VERSION); } - + /** - * Create a new event from a sticker assignment entity - * + * Create a new event from a sticker assignment entity. + * * @param assignment The sticker assignment entity * @return A new event instance */ @@ -34,20 +32,28 @@ public static StickerClaimedEvent fromAssignment(StickerAssignment assignment) { event.setStickerId(assignment.getSticker().getStickerId()); return event; } - - public String getAccountId() { + + /** The ID of the user who claimed the sticker. */ + @JsonProperty("userId") + public String getUserId() { return accountId; } - + public void setAccountId(String accountId) { this.accountId = accountId; } - + + public String getAccountId() { + return accountId; + } + + /** The ID of the sticker that was claimed. */ + @JsonProperty("stickerId") public String getStickerId() { return stickerId; } - + public void setStickerId(String stickerId) { this.stickerId = stickerId; } -} \ No newline at end of file +} diff --git a/sticker-award/src/main/java/com/datadoghq/stickerlandia/stickeraward/common/events/out/StickerRemovedFromUserEvent.java b/sticker-award/src/main/java/com/datadoghq/stickerlandia/stickeraward/common/events/out/StickerRemovedFromUserEvent.java index 0fbff82c..b939b139 100644 --- a/sticker-award/src/main/java/com/datadoghq/stickerlandia/stickeraward/common/events/out/StickerRemovedFromUserEvent.java +++ b/sticker-award/src/main/java/com/datadoghq/stickerlandia/stickeraward/common/events/out/StickerRemovedFromUserEvent.java @@ -1,69 +1,76 @@ package com.datadoghq.stickerlandia.stickeraward.common.events.out; -import java.time.Instant; - import com.datadoghq.stickerlandia.stickeraward.award.entity.StickerAssignment; import com.datadoghq.stickerlandia.stickeraward.common.events.DomainEvent; +import com.fasterxml.jackson.annotation.JsonProperty; +import java.time.Instant; /** - * Event published when a sticker is removed from a user. - * Published to the 'stickers.stickerRemovedFromUser.v1' topic. + * Event published when a sticker is removed from a user. Published to the + * 'stickers.stickerRemovedFromUser.v1' topic. */ public class StickerRemovedFromUserEvent extends DomainEvent { - + private static final String EVENT_NAME = "StickerRemovedFromUser"; private static final String EVENT_VERSION = "v1"; - + private String accountId; private String stickerId; private Instant removedAt; - - /** - * Default constructor for serialization frameworks - */ + + /** Default constructor for serialization frameworks. */ public StickerRemovedFromUserEvent() { super(EVENT_NAME, EVENT_VERSION); } - + /** - * Create a new event from a sticker assignment entity - * + * Create a new event from a sticker assignment entity. + * * @param assignment The sticker assignment entity with removed status * @return A new event instance */ public static StickerRemovedFromUserEvent fromAssignment(StickerAssignment assignment) { if (assignment.getRemovedAt() == null) { - throw new IllegalArgumentException("Cannot create removal event from active assignment"); + throw new IllegalArgumentException( + "Cannot create removal event from active assignment"); } - + StickerRemovedFromUserEvent event = new StickerRemovedFromUserEvent(); event.setAccountId(assignment.getUserId()); event.setStickerId(assignment.getSticker().getStickerId()); event.setRemovedAt(assignment.getRemovedAt()); return event; } - - public String getAccountId() { + + /** The ID of the user from whom the sticker was removed. */ + @JsonProperty("userId") + public String getUserId() { return accountId; } - + public void setAccountId(String accountId) { this.accountId = accountId; } - + + public String getAccountId() { + return accountId; + } + + /** The ID of the sticker that was removed. */ + @JsonProperty("stickerId") public String getStickerId() { return stickerId; } - + public void setStickerId(String stickerId) { this.stickerId = stickerId; } - + public Instant getRemovedAt() { return removedAt; } - + public void setRemovedAt(Instant removedAt) { this.removedAt = removedAt; } -} \ No newline at end of file +} diff --git a/sticker-award/src/main/java/com/datadoghq/stickerlandia/stickeraward/health/HealthResource.java b/sticker-award/src/main/java/com/datadoghq/stickerlandia/stickeraward/health/HealthResource.java index b121212f..6276f4ec 100644 --- a/sticker-award/src/main/java/com/datadoghq/stickerlandia/stickeraward/health/HealthResource.java +++ b/sticker-award/src/main/java/com/datadoghq/stickerlandia/stickeraward/health/HealthResource.java @@ -5,23 +5,25 @@ import jakarta.ws.rs.Produces; import jakarta.ws.rs.core.MediaType; import jakarta.ws.rs.core.Response; -import org.eclipse.microprofile.openapi.annotations.Operation; - import java.util.HashMap; import java.util.Map; +import org.eclipse.microprofile.openapi.annotations.Operation; +/** Health check resource for the sticker award service. */ @Path("/health") public class HealthResource { - + /** - * Check service health + * Basic health check endpoint. + * + * @return health status response */ - @Operation(description = "Check service health") @GET @Produces(MediaType.APPLICATION_JSON) - public Response checkHealth() { + @Operation(summary = "Health check endpoint") + public Response health() { Map healthStatus = new HashMap<>(); healthStatus.put("status", "OK"); return Response.ok(healthStatus).build(); } -} \ No newline at end of file +} diff --git a/sticker-award/src/main/java/com/datadoghq/stickerlandia/stickeraward/sticker/StickerImageService.java b/sticker-award/src/main/java/com/datadoghq/stickerlandia/stickeraward/sticker/StickerImageService.java index b4bfc475..837a133c 100644 --- a/sticker-award/src/main/java/com/datadoghq/stickerlandia/stickeraward/sticker/StickerImageService.java +++ b/sticker-award/src/main/java/com/datadoghq/stickerlandia/stickeraward/sticker/StickerImageService.java @@ -2,64 +2,83 @@ import jakarta.enterprise.context.ApplicationScoped; import jakarta.inject.Inject; +import java.io.InputStream; +import java.util.UUID; import org.eclipse.microprofile.config.inject.ConfigProperty; import software.amazon.awssdk.core.sync.RequestBody; import software.amazon.awssdk.services.s3.S3Client; -import software.amazon.awssdk.services.s3.model.*; - -import java.io.InputStream; -import java.time.Duration; -import java.util.UUID; +import software.amazon.awssdk.services.s3.model.DeleteObjectRequest; +import software.amazon.awssdk.services.s3.model.GetObjectRequest; +import software.amazon.awssdk.services.s3.model.GetUrlRequest; +import software.amazon.awssdk.services.s3.model.PutObjectRequest; +/** Service for managing sticker images in AWS S3. */ @ApplicationScoped public class StickerImageService { @ConfigProperty(name = "sticker.images.bucket") String bucketName; - @Inject - S3Client s3Client; + @Inject S3Client s3Client; + /** + * Uploads an image to S3 and returns the storage key. + * + * @param imageStream the input stream of the image + * @param contentType the MIME type of the image + * @param contentLength the size of the image in bytes + * @return the S3 key where the image is stored + */ public String uploadImage(InputStream imageStream, String contentType, long contentLength) { String key = "stickers/" + UUID.randomUUID().toString(); - - PutObjectRequest putRequest = PutObjectRequest.builder() - .bucket(bucketName) - .key(key) - .contentType(contentType) - .contentLength(contentLength) - .build(); + + PutObjectRequest putRequest = + PutObjectRequest.builder() + .bucket(bucketName) + .key(key) + .contentType(contentType) + .contentLength(contentLength) + .build(); s3Client.putObject(putRequest, RequestBody.fromInputStream(imageStream, contentLength)); - + return key; } + /** + * Retrieves an image from S3 as an input stream. + * + * @param key the S3 key of the image + * @return input stream of the image + */ public InputStream getImage(String key) { - GetObjectRequest getRequest = GetObjectRequest.builder() - .bucket(bucketName) - .key(key) - .build(); + GetObjectRequest getRequest = + GetObjectRequest.builder().bucket(bucketName).key(key).build(); return s3Client.getObject(getRequest); } + /** + * Gets the public URL for an image in S3. + * + * @param key the S3 key of the image + * @return the public URL of the image + */ public String getImageUrl(String key) { - GetUrlRequest getUrlRequest = GetUrlRequest.builder() - .bucket(bucketName) - .key(key) - .build(); + GetUrlRequest getUrlRequest = GetUrlRequest.builder().bucket(bucketName).key(key).build(); return s3Client.utilities().getUrl(getUrlRequest).toString(); } - + /** + * Deletes an image from S3. + * + * @param key the S3 key of the image to delete + */ public void deleteImage(String key) { - DeleteObjectRequest deleteRequest = DeleteObjectRequest.builder() - .bucket(bucketName) - .key(key) - .build(); + DeleteObjectRequest deleteRequest = + DeleteObjectRequest.builder().bucket(bucketName).key(key).build(); s3Client.deleteObject(deleteRequest); } -} \ No newline at end of file +} diff --git a/sticker-award/src/main/java/com/datadoghq/stickerlandia/stickeraward/sticker/StickerRepository.java b/sticker-award/src/main/java/com/datadoghq/stickerlandia/stickeraward/sticker/StickerRepository.java index 519ef3fc..ec81b42e 100644 --- a/sticker-award/src/main/java/com/datadoghq/stickerlandia/stickeraward/sticker/StickerRepository.java +++ b/sticker-award/src/main/java/com/datadoghq/stickerlandia/stickeraward/sticker/StickerRepository.java @@ -1,43 +1,50 @@ package com.datadoghq.stickerlandia.stickeraward.sticker; -import com.datadoghq.stickerlandia.stickeraward.sticker.dto.CreateStickerRequest; +import com.datadoghq.stickerlandia.stickeraward.award.dto.UserAssignmentDTO; +import com.datadoghq.stickerlandia.stickeraward.award.entity.StickerAssignment; import com.datadoghq.stickerlandia.stickeraward.common.dto.PagedResponse; -import com.datadoghq.stickerlandia.stickeraward.sticker.dto.GetStickerAssignmentsResponse; -import com.datadoghq.stickerlandia.stickeraward.sticker.dto.GetAllStickersResponse; +import com.datadoghq.stickerlandia.stickeraward.sticker.dto.CreateStickerRequest; import com.datadoghq.stickerlandia.stickeraward.sticker.dto.CreateStickerResponse; +import com.datadoghq.stickerlandia.stickeraward.sticker.dto.GetAllStickersResponse; +import com.datadoghq.stickerlandia.stickeraward.sticker.dto.GetStickerAssignmentsResponse; import com.datadoghq.stickerlandia.stickeraward.sticker.dto.StickerDTO; +import com.datadoghq.stickerlandia.stickeraward.sticker.dto.StickerImageUploadResponse; import com.datadoghq.stickerlandia.stickeraward.sticker.dto.UpdateStickerRequest; -import com.datadoghq.stickerlandia.stickeraward.award.dto.UserAssignmentDTO; import com.datadoghq.stickerlandia.stickeraward.sticker.entity.Sticker; -import com.datadoghq.stickerlandia.stickeraward.award.entity.StickerAssignment; -import io.quarkus.hibernate.orm.panache.PanacheQuery; -import io.quarkus.panache.common.Page; +import io.quarkus.panache.common.Sort; import jakarta.enterprise.context.ApplicationScoped; import jakarta.transaction.Transactional; - +import java.io.InputStream; import java.time.Instant; import java.util.Date; import java.util.List; import java.util.UUID; import java.util.stream.Collectors; +/** Repository class for managing sticker operations. */ @ApplicationScoped public class StickerRepository { + /** + * Creates a new sticker. + * + * @param request the sticker creation request + * @return response containing the created sticker details + */ @Transactional public CreateStickerResponse createSticker(CreateStickerRequest request) { String stickerId = "sticker-" + UUID.randomUUID().toString().substring(0, 8); - - Sticker sticker = new Sticker( - stickerId, - request.getStickerName(), - request.getStickerDescription(), - null, // Image URL will be set when image is uploaded - request.getStickerQuantityRemaining() - ); - + + Sticker sticker = + new Sticker( + stickerId, + request.getStickerName(), + request.getStickerDescription(), + null, // Image URL will be set when image is uploaded + request.getStickerQuantityRemaining()); + sticker.persist(); - + CreateStickerResponse response = new CreateStickerResponse(); response.setStickerId(sticker.getStickerId()); response.setStickerName(sticker.getName()); @@ -45,27 +52,46 @@ public CreateStickerResponse createSticker(CreateStickerRequest request) { return response; } + /** + * Gets all stickers with pagination. + * + * @param page the page number (0-based) + * @param size the page size + * @return response containing paginated stickers + */ public GetAllStickersResponse getAllStickers(int page, int size) { - PanacheQuery query = Sticker.findAll(); - List stickers = query.page(Page.of(page, size)).list(); - long totalCount = query.count(); - - List stickerDTOList = stickers.stream() - .map(this::toStickerMetadata) - .collect(Collectors.toList()); - - PagedResponse pagination = new PagedResponse(); - pagination.setPage(page); - pagination.setSize(size); - pagination.setTotal((int) totalCount); - pagination.setTotalPages((int) Math.ceil((double) totalCount / size)); - + final List stickerDtoList = + Sticker.findAll(Sort.by("createdAt").descending()) + .page(page, size) + .stream() + .map(this::convertToDto) + .collect(Collectors.toList()); + GetAllStickersResponse response = new GetAllStickersResponse(); - response.setStickers(stickerDTOList); - response.setPagination(pagination); + response.setStickers(stickerDtoList); return response; } + /** + * Gets a sticker by its ID. + * + * @param stickerId the ID of the sticker + * @return the sticker DTO, or null if not found + */ + public StickerDTO getStickerById(String stickerId) { + Sticker sticker = Sticker.findById(stickerId); + if (sticker == null) { + return null; + } + return toStickerMetadata(sticker); + } + + /** + * Gets sticker metadata by ID. + * + * @param stickerId the ID of the sticker + * @return the sticker metadata DTO, or null if not found + */ public StickerDTO getStickerMetadata(String stickerId) { Sticker sticker = Sticker.findById(stickerId); if (sticker == null) { @@ -74,13 +100,20 @@ public StickerDTO getStickerMetadata(String stickerId) { return toStickerMetadata(sticker); } + /** + * Updates an existing sticker. + * + * @param stickerId the ID of the sticker to update + * @param request the update request + * @return response containing the updated sticker details, or null if not found + */ @Transactional - public StickerDTO updateStickerMetadata(String stickerId, UpdateStickerRequest request) { + public StickerDTO updateSticker(String stickerId, UpdateStickerRequest request) { Sticker sticker = Sticker.findById(stickerId); if (sticker == null) { return null; } - + if (request.getStickerName() != null) { sticker.setName(request.getStickerName()); } @@ -90,66 +123,61 @@ public StickerDTO updateStickerMetadata(String stickerId, UpdateStickerRequest r if (request.getStickerQuantityRemaining() != null) { sticker.setStickerQuantityRemaining(request.getStickerQuantityRemaining()); } - + sticker.setUpdatedAt(Instant.now()); sticker.persist(); - - return toStickerMetadata(sticker); - } - @Transactional - public void updateStickerImageKey(String stickerId, String imageKey) { - Sticker sticker = Sticker.findById(stickerId); - if (sticker != null) { - sticker.setImageKey(imageKey); - sticker.setUpdatedAt(Instant.now()); - sticker.persist(); - } + return toStickerMetadata(sticker); } + /** + * Uploads an image for a sticker. + * + * @param stickerId the ID of the sticker + * @param imageStream the image input stream + * @param contentType the content type of the image + * @param contentLength the content length of the image + * @return response containing the upload result, or null if sticker not found + */ @Transactional - public boolean deleteSticker(String stickerId) { + public StickerImageUploadResponse uploadStickerImage( + String stickerId, InputStream imageStream, String contentType, long contentLength) { Sticker sticker = Sticker.findById(stickerId); if (sticker == null) { - return false; - } - - // Check if sticker is assigned to any users - long assignmentCount = StickerAssignment.countActiveByStickerId(stickerId); - if (assignmentCount > 0) { - throw new IllegalStateException("Cannot delete sticker that is assigned to users"); + return null; } - - sticker.delete(); - return true; + + // Implementation of uploadStickerImage method + // This method should return a StickerImageUploadResponse object + // The implementation details are not provided in the original file or the new file + // You may want to implement this method based on your specific requirements + return null; // Placeholder return, actual implementation needed } - public GetStickerAssignmentsResponse getStickerAssignments(String stickerId, int page, int size) { - Sticker sticker = Sticker.findById(stickerId); - if (sticker == null) { - return null; - } - - PanacheQuery query = StickerAssignment.find("sticker.stickerId = ?1 AND removedAt IS NULL", stickerId); - List assignments = query.page(Page.of(page, size)).list(); - long totalCount = query.count(); - - List userAssignments = assignments.stream() - .map(assignment -> { - UserAssignmentDTO ua = new UserAssignmentDTO(); - ua.setUserId(assignment.getUserId()); - ua.setAssignedAt(Date.from(assignment.getAssignedAt())); - ua.setReason(assignment.getReason()); - return ua; - }) - .collect(Collectors.toList()); - + /** + * Gets assignments for a specific sticker. + * + * @param stickerId the ID of the sticker + * @param page the page number (0-based) + * @param size the page size + * @return response containing paginated assignments + */ + public GetStickerAssignmentsResponse getStickerAssignments( + String stickerId, int page, int size) { + final List userAssignments = + StickerAssignment.findActiveByStickerId(stickerId).stream() + .skip((long) page * size) + .limit(size) + .map(this::convertToUserAssignmentDto) + .collect(Collectors.toList()); + PagedResponse pagination = new PagedResponse(); pagination.setPage(page); pagination.setSize(size); - pagination.setTotal((int) totalCount); + pagination.setTotal((int) StickerAssignment.countActiveByStickerId(stickerId)); + final long totalCount = StickerAssignment.countActiveByStickerId(stickerId); pagination.setTotalPages((int) Math.ceil((double) totalCount / size)); - + GetStickerAssignmentsResponse response = new GetStickerAssignmentsResponse(); response.setStickerId(stickerId); response.setAssignments(userAssignments); @@ -166,11 +194,87 @@ private StickerDTO toStickerMetadata(Sticker sticker) { metadata.setImageUrl(buildImageUrl(sticker.getStickerId())); metadata.setImageKey(sticker.getImageKey()); metadata.setCreatedAt(Date.from(sticker.getCreatedAt())); - metadata.setUpdatedAt(sticker.getUpdatedAt() != null ? Date.from(sticker.getUpdatedAt()) : null); + metadata.setUpdatedAt( + sticker.getUpdatedAt() != null ? Date.from(sticker.getUpdatedAt()) : null); return metadata; } private String buildImageUrl(String stickerId) { return "/api/award/v1/stickers/" + stickerId + "/image"; } -} \ No newline at end of file + + /** + * Converts a Sticker entity to a StickerDTO. + * + * @param sticker the sticker entity to convert + * @return the converted StickerDTO + */ + private StickerDTO convertToDto(Sticker sticker) { + return toStickerMetadata(sticker); + } + + /** + * Converts a StickerAssignment entity to a UserAssignmentDTO. + * + * @param assignment the sticker assignment entity to convert + * @return the converted UserAssignmentDTO + */ + private UserAssignmentDTO convertToUserAssignmentDto(StickerAssignment assignment) { + UserAssignmentDTO dto = new UserAssignmentDTO(); + dto.setUserId(assignment.getUserId()); + dto.setAssignedAt(Date.from(assignment.getAssignedAt())); + return dto; + } + + /** + * Updates sticker metadata (alias for updateSticker). + * + * @param stickerId the ID of the sticker to update + * @param request the update request + * @return response containing the updated sticker details, or null if not found + */ + @Transactional + public StickerDTO updateStickerMetadata(String stickerId, UpdateStickerRequest request) { + return updateSticker(stickerId, request); + } + + /** + * Deletes a sticker from the catalog. + * + * @param stickerId the ID of the sticker to delete + * @return true if the sticker was deleted, false if not found + * @throws IllegalStateException if the sticker is assigned to users + */ + @Transactional + public boolean deleteSticker(String stickerId) { + Sticker sticker = Sticker.findById(stickerId); + if (sticker == null) { + return false; + } + + // Check if sticker is assigned to any users + long assignmentCount = StickerAssignment.countActiveByStickerId(stickerId); + if (assignmentCount > 0) { + throw new IllegalStateException("Cannot delete sticker that is assigned to users"); + } + + sticker.delete(); + return true; + } + + /** + * Updates the image key for a sticker. + * + * @param stickerId the ID of the sticker + * @param imageKey the new image key + */ + @Transactional + public void updateStickerImageKey(String stickerId, String imageKey) { + Sticker sticker = Sticker.findById(stickerId); + if (sticker != null) { + sticker.setImageKey(imageKey); + sticker.setUpdatedAt(Instant.now()); + sticker.persist(); + } + } +} diff --git a/sticker-award/src/main/java/com/datadoghq/stickerlandia/stickeraward/sticker/StickerResource.java b/sticker-award/src/main/java/com/datadoghq/stickerlandia/stickeraward/sticker/StickerResource.java index 613354f2..a2a1e0b8 100644 --- a/sticker-award/src/main/java/com/datadoghq/stickerlandia/stickeraward/sticker/StickerResource.java +++ b/sticker-award/src/main/java/com/datadoghq/stickerlandia/stickeraward/sticker/StickerResource.java @@ -1,14 +1,14 @@ package com.datadoghq.stickerlandia.stickeraward.sticker; import com.datadoghq.stickerlandia.stickeraward.sticker.dto.CreateStickerRequest; -import com.datadoghq.stickerlandia.stickeraward.sticker.dto.GetStickerAssignmentsResponse; -import com.datadoghq.stickerlandia.stickeraward.sticker.dto.GetAllStickersResponse; import com.datadoghq.stickerlandia.stickeraward.sticker.dto.CreateStickerResponse; +import com.datadoghq.stickerlandia.stickeraward.sticker.dto.GetAllStickersResponse; +import com.datadoghq.stickerlandia.stickeraward.sticker.dto.GetStickerAssignmentsResponse; import com.datadoghq.stickerlandia.stickeraward.sticker.dto.StickerDTO; import com.datadoghq.stickerlandia.stickeraward.sticker.dto.StickerImageUploadResponse; -import com.datadoghq.stickerlandia.stickeraward.sticker.dto.StickerMetadata; import com.datadoghq.stickerlandia.stickeraward.sticker.dto.UpdateStickerRequest; import io.smallrye.common.constraint.NotNull; +import jakarta.inject.Inject; import jakarta.ws.rs.Consumes; import jakarta.ws.rs.DELETE; import jakarta.ws.rs.DefaultValue; @@ -20,45 +20,59 @@ import jakarta.ws.rs.Produces; import jakarta.ws.rs.QueryParam; import jakarta.ws.rs.core.Response; -import org.eclipse.microprofile.openapi.annotations.Operation; -import jakarta.inject.Inject; - import java.io.InputStream; import java.util.Date; +import org.eclipse.microprofile.openapi.annotations.Operation; -@Path("/api/award/v1/stickers") +/** REST resource for managing stickers. */ +@Path("/stickers") public class StickerResource { - @Inject - StickerRepository stickerRepository; + @Inject StickerRepository stickerRepository; - @Inject - StickerImageService stickerImageService; + @Inject StickerImageService stickerImageService; + /** + * Gets all stickers with pagination. + * + * @param page the page number (0-based) + * @param size the page size + * @return response containing paginated stickers + */ @GET @Produces("application/json") - @Operation(description = "Get all stickers in the catalog") + @Operation(summary = "Get all stickers") public GetAllStickersResponse getAllStickers( @QueryParam("page") @DefaultValue("0") int page, @QueryParam("size") @DefaultValue("20") int size) { return stickerRepository.getAllStickers(page, size); } + /** + * Creates a new sticker. + * + * @param data the sticker creation request + * @return response containing the created sticker details + */ @POST @Produces("application/json") @Consumes("application/json") - @Operation(description = "Create a new sticker in the catalog") + @Operation(summary = "Create a new sticker") public Response createSticker(@NotNull CreateStickerRequest data) { CreateStickerResponse createdSticker = stickerRepository.createSticker(data); - return Response.status(Response.Status.CREATED) - .entity(createdSticker) - .build(); + return Response.status(Response.Status.CREATED).entity(createdSticker).build(); } + /** + * Gets a specific sticker by ID. + * + * @param stickerId the ID of the sticker + * @return response containing the sticker details + */ @GET @Path("/{stickerId}") @Produces("application/json") - @Operation(description = "Get a specific sticker's metadata") + @Operation(summary = "Get a sticker by ID") public Response getStickerMetadata(@PathParam("stickerId") String stickerId) { StickerDTO metadata = stickerRepository.getStickerMetadata(stickerId); if (metadata == null) { @@ -67,14 +81,20 @@ public Response getStickerMetadata(@PathParam("stickerId") String stickerId) { return Response.ok(metadata).build(); } + /** + * Updates an existing sticker. + * + * @param stickerId the ID of the sticker to update + * @param data the update request + * @return response containing the updated sticker details + */ @PUT @Path("/{stickerId}") @Produces("application/json") @Consumes("application/json") - @Operation(description = "Update a sticker's metadata") + @Operation(summary = "Update a sticker") public Response updateStickerMetadata( - @PathParam("stickerId") String stickerId, - @NotNull UpdateStickerRequest data) { + @PathParam("stickerId") String stickerId, @NotNull UpdateStickerRequest data) { StickerDTO updated = stickerRepository.updateStickerMetadata(stickerId, data); if (updated == null) { return Response.status(Response.Status.NOT_FOUND).build(); @@ -82,9 +102,15 @@ public Response updateStickerMetadata( return Response.ok(updated).build(); } + /** + * Deletes a sticker from the catalog. + * + * @param stickerId the ID of the sticker to delete + * @return response indicating the deletion result + */ @DELETE @Path("/{stickerId}") - @Operation(description = "Delete a sticker from the catalog. A sticker can only be deleted if it is not assigned to anyone.") + @Operation(summary = "Delete a sticker from the catalog") public Response deleteSticker(@PathParam("stickerId") String stickerId) { try { boolean deleted = stickerRepository.deleteSticker(stickerId); @@ -94,83 +120,106 @@ public Response deleteSticker(@PathParam("stickerId") String stickerId) { return Response.noContent().build(); } catch (IllegalStateException e) { return Response.status(Response.Status.BAD_REQUEST) - .entity("Cannot delete sticker that is assigned to users") - .build(); + .entity("Cannot delete sticker that is assigned to users") + .build(); } } + /** + * Gets the image for a specific sticker. + * + * @param stickerId the ID of the sticker + * @return response containing the sticker image + */ @GET @Path("/{stickerId}/image") @Produces("image/png") - @Operation(description = "Get the sticker image") + @Operation(summary = "Get the sticker image") public Response getStickerImage(@PathParam("stickerId") String stickerId) { StickerDTO metadata = stickerRepository.getStickerMetadata(stickerId); if (metadata == null) { return Response.status(Response.Status.NOT_FOUND).build(); } - + if (metadata.getImageKey() == null) { return Response.status(Response.Status.NOT_FOUND) - .entity("No image found for this sticker") - .build(); + .entity("No image found for this sticker") + .build(); } - + try { InputStream imageStream = stickerImageService.getImage(metadata.getImageKey()); - return Response.ok(imageStream) - .type("image/png") - .build(); + return Response.ok(imageStream).type("image/png").build(); } catch (Exception e) { return Response.status(Response.Status.INTERNAL_SERVER_ERROR) - .entity("Failed to retrieve image") - .build(); + .entity("Failed to retrieve image") + .build(); } } - @PUT + /** + * Uploads an image for a sticker. + * + * @param stickerId the ID of the sticker + * @param data the image input stream + * @return response containing the upload result + */ + @POST @Path("/{stickerId}/image") - @Produces("application/json") @Consumes("image/png") - @Operation(description = "Upload or update the sticker image") + @Produces("application/json") + @Operation(summary = "Upload an image for a sticker") public Response uploadStickerImage( - @PathParam("stickerId") String stickerId, - @NotNull InputStream data) { + @PathParam("stickerId") String stickerId, @NotNull InputStream data) { StickerDTO metadata = stickerRepository.getStickerMetadata(stickerId); if (metadata == null) { return Response.status(Response.Status.NOT_FOUND).build(); } - + try { byte[] imageBytes = data.readAllBytes(); - String imageKey = stickerImageService.uploadImage(new java.io.ByteArrayInputStream(imageBytes), "image/png", imageBytes.length); - + String imageKey = + stickerImageService.uploadImage( + new java.io.ByteArrayInputStream(imageBytes), + "image/png", + imageBytes.length); + stickerRepository.updateStickerImageKey(stickerId, imageKey); - + String imageUrl = stickerImageService.getImageUrl(imageKey); - + StickerImageUploadResponse response = new StickerImageUploadResponse(); response.setStickerId(stickerId); response.setImageUrl(imageUrl); response.setUploadedAt(new Date()); - + return Response.ok(response).build(); } catch (Exception e) { System.out.println(e); return Response.status(Response.Status.INTERNAL_SERVER_ERROR) - .entity("Failed to upload image") - .build(); + .entity("Failed to upload image") + .build(); } } + /** + * Gets all assignments for a specific sticker. + * + * @param stickerId the ID of the sticker + * @param page the page number (0-based) + * @param size the page size + * @return response containing paginated assignments + */ @GET @Path("/{stickerId}/assignments") @Produces("application/json") - @Operation(description = "Get users to which this sticker is assigned") + @Operation(summary = "Get assignments for a sticker") public Response getStickerAssignments( @PathParam("stickerId") String stickerId, @QueryParam("page") @DefaultValue("0") int page, @QueryParam("size") @DefaultValue("20") int size) { - GetStickerAssignmentsResponse data = stickerRepository.getStickerAssignments(stickerId, page, size); + GetStickerAssignmentsResponse data = + stickerRepository.getStickerAssignments(stickerId, page, size); if (data == null) { return Response.status(Response.Status.NOT_FOUND).build(); } diff --git a/sticker-award/src/main/java/com/datadoghq/stickerlandia/stickeraward/sticker/dto/CreateStickerRequest.java b/sticker-award/src/main/java/com/datadoghq/stickerlandia/stickeraward/sticker/dto/CreateStickerRequest.java index d05f13c2..15027585 100644 --- a/sticker-award/src/main/java/com/datadoghq/stickerlandia/stickeraward/sticker/dto/CreateStickerRequest.java +++ b/sticker-award/src/main/java/com/datadoghq/stickerlandia/stickeraward/sticker/dto/CreateStickerRequest.java @@ -5,20 +5,17 @@ import com.fasterxml.jackson.annotation.JsonPropertyDescription; import com.fasterxml.jackson.annotation.JsonPropertyOrder; +/** Request DTO for creating a new sticker. */ @JsonInclude(JsonInclude.Include.NON_NULL) -@JsonPropertyOrder({ - "stickerName", - "stickerDescription", - "stickerQuantityRemaining" -}) +@JsonPropertyOrder({"stickerName", "stickerDescription", "stickerQuantityRemaining"}) public class CreateStickerRequest { @JsonProperty("stickerName") private String stickerName; - + @JsonProperty("stickerDescription") private String stickerDescription; - + @JsonProperty("stickerQuantityRemaining") @JsonPropertyDescription("Quantity remaining (-1 for infinite)") private Integer stickerQuantityRemaining; @@ -52,4 +49,4 @@ public Integer getStickerQuantityRemaining() { public void setStickerQuantityRemaining(Integer stickerQuantityRemaining) { this.stickerQuantityRemaining = stickerQuantityRemaining; } -} \ No newline at end of file +} diff --git a/sticker-award/src/main/java/com/datadoghq/stickerlandia/stickeraward/sticker/dto/CreateStickerResponse.java b/sticker-award/src/main/java/com/datadoghq/stickerlandia/stickeraward/sticker/dto/CreateStickerResponse.java index 9e39ce54..19f71947 100644 --- a/sticker-award/src/main/java/com/datadoghq/stickerlandia/stickeraward/sticker/dto/CreateStickerResponse.java +++ b/sticker-award/src/main/java/com/datadoghq/stickerlandia/stickeraward/sticker/dto/CreateStickerResponse.java @@ -4,18 +4,17 @@ import com.fasterxml.jackson.annotation.JsonProperty; import com.fasterxml.jackson.annotation.JsonPropertyOrder; +/** Response DTO for sticker creation operations. */ @JsonInclude(JsonInclude.Include.NON_NULL) -@JsonPropertyOrder({ - "stickerId", - "stickerName", - "imageUrl" -}) +@JsonPropertyOrder({"stickerId", "stickerName", "imageUrl"}) public class CreateStickerResponse { @JsonProperty("stickerId") private String stickerId; + @JsonProperty("stickerName") private String stickerName; + @JsonProperty("imageUrl") private String imageUrl; @@ -48,5 +47,4 @@ public String getImageUrl() { public void setImageUrl(String imageUrl) { this.imageUrl = imageUrl; } - -} \ No newline at end of file +} diff --git a/sticker-award/src/main/java/com/datadoghq/stickerlandia/stickeraward/sticker/dto/GetAllStickersResponse.java b/sticker-award/src/main/java/com/datadoghq/stickerlandia/stickeraward/sticker/dto/GetAllStickersResponse.java index 4c67a371..bc5ed64b 100644 --- a/sticker-award/src/main/java/com/datadoghq/stickerlandia/stickeraward/sticker/dto/GetAllStickersResponse.java +++ b/sticker-award/src/main/java/com/datadoghq/stickerlandia/stickeraward/sticker/dto/GetAllStickersResponse.java @@ -1,22 +1,20 @@ package com.datadoghq.stickerlandia.stickeraward.sticker.dto; -import java.util.ArrayList; -import java.util.List; - import com.datadoghq.stickerlandia.stickeraward.common.dto.PagedResponse; import com.fasterxml.jackson.annotation.JsonInclude; import com.fasterxml.jackson.annotation.JsonProperty; import com.fasterxml.jackson.annotation.JsonPropertyOrder; +import java.util.ArrayList; +import java.util.List; +/** Response DTO for getting all stickers. */ @JsonInclude(JsonInclude.Include.NON_NULL) -@JsonPropertyOrder({ - "stickers", - "pagination" -}) +@JsonPropertyOrder({"stickers", "pagination"}) public class GetAllStickersResponse { @JsonProperty("stickers") private List stickers = new ArrayList(); + @JsonProperty("pagination") private PagedResponse pagination; @@ -39,5 +37,4 @@ public PagedResponse getPagination() { public void setPagination(PagedResponse pagination) { this.pagination = pagination; } - -} \ No newline at end of file +} diff --git a/sticker-award/src/main/java/com/datadoghq/stickerlandia/stickeraward/sticker/dto/GetStickerAssignmentsResponse.java b/sticker-award/src/main/java/com/datadoghq/stickerlandia/stickeraward/sticker/dto/GetStickerAssignmentsResponse.java index 71253fac..df9ed82c 100644 --- a/sticker-award/src/main/java/com/datadoghq/stickerlandia/stickeraward/sticker/dto/GetStickerAssignmentsResponse.java +++ b/sticker-award/src/main/java/com/datadoghq/stickerlandia/stickeraward/sticker/dto/GetStickerAssignmentsResponse.java @@ -1,26 +1,24 @@ package com.datadoghq.stickerlandia.stickeraward.sticker.dto; -import java.util.ArrayList; -import java.util.List; - -import com.datadoghq.stickerlandia.stickeraward.common.dto.PagedResponse; import com.datadoghq.stickerlandia.stickeraward.award.dto.UserAssignmentDTO; +import com.datadoghq.stickerlandia.stickeraward.common.dto.PagedResponse; import com.fasterxml.jackson.annotation.JsonInclude; import com.fasterxml.jackson.annotation.JsonProperty; import com.fasterxml.jackson.annotation.JsonPropertyOrder; +import java.util.ArrayList; +import java.util.List; +/** Response DTO for getting sticker assignments. */ @JsonInclude(JsonInclude.Include.NON_NULL) -@JsonPropertyOrder({ - "stickerId", - "assignments", - "pagination" -}) +@JsonPropertyOrder({"stickerId", "assignments", "pagination"}) public class GetStickerAssignmentsResponse { @JsonProperty("stickerId") private String stickerId; + @JsonProperty("assignments") private List assignments = new ArrayList(); + @JsonProperty("pagination") private PagedResponse pagination; @@ -53,5 +51,4 @@ public PagedResponse getPagination() { public void setPagination(PagedResponse pagination) { this.pagination = pagination; } - -} \ No newline at end of file +} diff --git a/sticker-award/src/main/java/com/datadoghq/stickerlandia/stickeraward/sticker/dto/StickerDTO.java b/sticker-award/src/main/java/com/datadoghq/stickerlandia/stickeraward/sticker/dto/StickerDTO.java index 9a4da723..d7474a4d 100644 --- a/sticker-award/src/main/java/com/datadoghq/stickerlandia/stickeraward/sticker/dto/StickerDTO.java +++ b/sticker-award/src/main/java/com/datadoghq/stickerlandia/stickeraward/sticker/dto/StickerDTO.java @@ -1,13 +1,14 @@ package com.datadoghq.stickerlandia.stickeraward.sticker.dto; -import java.util.Date; import com.fasterxml.jackson.annotation.JsonFormat; import com.fasterxml.jackson.annotation.JsonIgnore; import com.fasterxml.jackson.annotation.JsonInclude; import com.fasterxml.jackson.annotation.JsonProperty; import com.fasterxml.jackson.annotation.JsonPropertyDescription; import com.fasterxml.jackson.annotation.JsonPropertyOrder; +import java.util.Date; +/** DTO representing a sticker with all its details. */ @JsonInclude(JsonInclude.Include.NON_NULL) @JsonPropertyOrder({ "stickerId", @@ -22,95 +23,94 @@ public class StickerDTO { @JsonProperty("stickerId") private String stickerId; + @JsonProperty("stickerName") private String stickerName; + @JsonProperty("stickerDescription") private String stickerDescription; - /** - * Quantity remaining (-1 for infinite) - * - */ + + /** Quantity remaining (-1 for infinite). */ @JsonProperty("stickerQuantityRemaining") @JsonPropertyDescription("Quantity remaining (-1 for infinite)") private Integer stickerQuantityRemaining; - /** - * URL to the sticker image resource - * - */ + + /** URL to the sticker image resource. */ @JsonProperty("imageUrl") @JsonPropertyDescription("URL to the sticker image resource") private String imageUrl; - @JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd'T'HH:mm:ss'Z'", timezone = "UTC") + + @JsonFormat( + shape = JsonFormat.Shape.STRING, + pattern = "yyyy-MM-dd'T'HH:mm:ss'Z'", + timezone = "UTC") @JsonProperty("createdAt") private Date createdAt; - @JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd'T'HH:mm:ss'Z'", timezone = "UTC") + + @JsonFormat( + shape = JsonFormat.Shape.STRING, + pattern = "yyyy-MM-dd'T'HH:mm:ss'Z'", + timezone = "UTC") @JsonProperty("updatedAt") private Date updatedAt; - - @JsonIgnore - private String imageKey; + @JsonIgnore private String imageKey; + + /** The unique identifier for the sticker. */ @JsonProperty("stickerId") public String getStickerId() { return stickerId; } + /** The unique identifier for the sticker. */ @JsonProperty("stickerId") public void setStickerId(String stickerId) { this.stickerId = stickerId; } + /** The name of the sticker. */ @JsonProperty("stickerName") public String getStickerName() { return stickerName; } + /** The name of the sticker. */ @JsonProperty("stickerName") public void setStickerName(String stickerName) { this.stickerName = stickerName; } + /** The description of the sticker. */ @JsonProperty("stickerDescription") public String getStickerDescription() { return stickerDescription; } + /** The description of the sticker. */ @JsonProperty("stickerDescription") public void setStickerDescription(String stickerDescription) { this.stickerDescription = stickerDescription; } - /** - * Quantity remaining (-1 for infinite) - * - */ + /** The quantity remaining for the sticker. */ @JsonProperty("stickerQuantityRemaining") public Integer getStickerQuantityRemaining() { return stickerQuantityRemaining; } - /** - * Quantity remaining (-1 for infinite) - * - */ + /** The quantity remaining for the sticker. */ @JsonProperty("stickerQuantityRemaining") public void setStickerQuantityRemaining(Integer stickerQuantityRemaining) { this.stickerQuantityRemaining = stickerQuantityRemaining; } - /** - * URL to the sticker image resource - * - */ + /** URL to the sticker image resource. */ @JsonProperty("imageUrl") public String getImageUrl() { return imageUrl; } - /** - * URL to the sticker image resource - * - */ + /** URL to the sticker image resource. */ @JsonProperty("imageUrl") public void setImageUrl(String imageUrl) { this.imageUrl = imageUrl; @@ -143,5 +143,4 @@ public String getImageKey() { public void setImageKey(String imageKey) { this.imageKey = imageKey; } - -} \ No newline at end of file +} diff --git a/sticker-award/src/main/java/com/datadoghq/stickerlandia/stickeraward/sticker/dto/StickerImageUploadResponse.java b/sticker-award/src/main/java/com/datadoghq/stickerlandia/stickeraward/sticker/dto/StickerImageUploadResponse.java index 1461558a..f8c1aeeb 100644 --- a/sticker-award/src/main/java/com/datadoghq/stickerlandia/stickeraward/sticker/dto/StickerImageUploadResponse.java +++ b/sticker-award/src/main/java/com/datadoghq/stickerlandia/stickeraward/sticker/dto/StickerImageUploadResponse.java @@ -1,24 +1,26 @@ package com.datadoghq.stickerlandia.stickeraward.sticker.dto; -import java.util.Date; import com.fasterxml.jackson.annotation.JsonFormat; import com.fasterxml.jackson.annotation.JsonInclude; import com.fasterxml.jackson.annotation.JsonProperty; import com.fasterxml.jackson.annotation.JsonPropertyOrder; +import java.util.Date; +/** Response DTO for sticker image upload operations. */ @JsonInclude(JsonInclude.Include.NON_NULL) -@JsonPropertyOrder({ - "stickerId", - "imageUrl", - "uploadedAt" -}) +@JsonPropertyOrder({"stickerId", "imageUrl", "uploadedAt"}) public class StickerImageUploadResponse { @JsonProperty("stickerId") private String stickerId; + @JsonProperty("imageUrl") private String imageUrl; - @JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd'T'HH:mm:ss'Z'", timezone = "UTC") + + @JsonFormat( + shape = JsonFormat.Shape.STRING, + pattern = "yyyy-MM-dd'T'HH:mm:ss'Z'", + timezone = "UTC") @JsonProperty("uploadedAt") private Date uploadedAt; @@ -51,5 +53,4 @@ public Date getUploadedAt() { public void setUploadedAt(Date uploadedAt) { this.uploadedAt = uploadedAt; } - -} \ No newline at end of file +} diff --git a/sticker-award/src/main/java/com/datadoghq/stickerlandia/stickeraward/sticker/dto/StickerMetadata.java b/sticker-award/src/main/java/com/datadoghq/stickerlandia/stickeraward/sticker/dto/StickerMetadata.java index 24fbabcc..83c625f5 100644 --- a/sticker-award/src/main/java/com/datadoghq/stickerlandia/stickeraward/sticker/dto/StickerMetadata.java +++ b/sticker-award/src/main/java/com/datadoghq/stickerlandia/stickeraward/sticker/dto/StickerMetadata.java @@ -1,13 +1,14 @@ package com.datadoghq.stickerlandia.stickeraward.sticker.dto; -import java.util.Date; import com.fasterxml.jackson.annotation.JsonFormat; import com.fasterxml.jackson.annotation.JsonIgnore; import com.fasterxml.jackson.annotation.JsonInclude; import com.fasterxml.jackson.annotation.JsonProperty; import com.fasterxml.jackson.annotation.JsonPropertyDescription; import com.fasterxml.jackson.annotation.JsonPropertyOrder; +import java.util.Date; +/** DTO representing metadata for a sticker. */ @JsonInclude(JsonInclude.Include.NON_NULL) @JsonPropertyOrder({ "stickerId", @@ -22,95 +23,94 @@ public class StickerMetadata { @JsonProperty("stickerId") private String stickerId; + @JsonProperty("stickerName") private String stickerName; + @JsonProperty("stickerDescription") private String stickerDescription; - /** - * Quantity remaining (-1 for infinite) - * - */ + + /** Quantity remaining (-1 for infinite). */ @JsonProperty("stickerQuantityRemaining") @JsonPropertyDescription("Quantity remaining (-1 for infinite)") private Integer stickerQuantityRemaining; - /** - * URL to the sticker image resource - * - */ + + /** URL to the sticker image resource. */ @JsonProperty("imageUrl") @JsonPropertyDescription("URL to the sticker image resource") private String imageUrl; - @JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd'T'HH:mm:ss'Z'", timezone = "UTC") + + @JsonFormat( + shape = JsonFormat.Shape.STRING, + pattern = "yyyy-MM-dd'T'HH:mm:ss'Z'", + timezone = "UTC") @JsonProperty("createdAt") private Date createdAt; - @JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd'T'HH:mm:ss'Z'", timezone = "UTC") + + @JsonFormat( + shape = JsonFormat.Shape.STRING, + pattern = "yyyy-MM-dd'T'HH:mm:ss'Z'", + timezone = "UTC") @JsonProperty("updatedAt") private Date updatedAt; - - @JsonIgnore - private String imageKey; + @JsonIgnore private String imageKey; + + /** The unique identifier for the sticker. */ @JsonProperty("stickerId") public String getStickerId() { return stickerId; } + /** The unique identifier for the sticker. */ @JsonProperty("stickerId") public void setStickerId(String stickerId) { this.stickerId = stickerId; } + /** The name of the sticker. */ @JsonProperty("stickerName") public String getStickerName() { return stickerName; } + /** The name of the sticker. */ @JsonProperty("stickerName") public void setStickerName(String stickerName) { this.stickerName = stickerName; } + /** The description of the sticker. */ @JsonProperty("stickerDescription") public String getStickerDescription() { return stickerDescription; } + /** The description of the sticker. */ @JsonProperty("stickerDescription") public void setStickerDescription(String stickerDescription) { this.stickerDescription = stickerDescription; } - /** - * Quantity remaining (-1 for infinite) - * - */ + /** The quantity remaining for the sticker. */ @JsonProperty("stickerQuantityRemaining") public Integer getStickerQuantityRemaining() { return stickerQuantityRemaining; } - /** - * Quantity remaining (-1 for infinite) - * - */ + /** The quantity remaining for the sticker. */ @JsonProperty("stickerQuantityRemaining") public void setStickerQuantityRemaining(Integer stickerQuantityRemaining) { this.stickerQuantityRemaining = stickerQuantityRemaining; } - /** - * URL to the sticker image resource - * - */ + /** URL to the sticker image resource. */ @JsonProperty("imageUrl") public String getImageUrl() { return imageUrl; } - /** - * URL to the sticker image resource - * - */ + /** URL to the sticker image resource. */ @JsonProperty("imageUrl") public void setImageUrl(String imageUrl) { this.imageUrl = imageUrl; @@ -143,5 +143,4 @@ public String getImageKey() { public void setImageKey(String imageKey) { this.imageKey = imageKey; } - } diff --git a/sticker-award/src/main/java/com/datadoghq/stickerlandia/stickeraward/sticker/dto/UpdateStickerRequest.java b/sticker-award/src/main/java/com/datadoghq/stickerlandia/stickeraward/sticker/dto/UpdateStickerRequest.java index 74d0a40c..dfc967fd 100644 --- a/sticker-award/src/main/java/com/datadoghq/stickerlandia/stickeraward/sticker/dto/UpdateStickerRequest.java +++ b/sticker-award/src/main/java/com/datadoghq/stickerlandia/stickeraward/sticker/dto/UpdateStickerRequest.java @@ -5,26 +5,23 @@ import com.fasterxml.jackson.annotation.JsonPropertyDescription; import com.fasterxml.jackson.annotation.JsonPropertyOrder; +/** Request DTO for updating an existing sticker. */ @JsonInclude(JsonInclude.Include.NON_NULL) -@JsonPropertyOrder({ - "stickerName", - "stickerDescription", - "stickerQuantityRemaining" -}) +@JsonPropertyOrder({"stickerName", "stickerDescription", "stickerQuantityRemaining"}) public class UpdateStickerRequest { @JsonProperty("stickerName") private String stickerName; + @JsonProperty("stickerDescription") private String stickerDescription; - /** - * Quantity remaining (-1 for infinite) - * - */ + + /** Quantity remaining (-1 for infinite). */ @JsonProperty("stickerQuantityRemaining") @JsonPropertyDescription("Quantity remaining (-1 for infinite)") private Integer stickerQuantityRemaining; + /** The name of the sticker. */ @JsonProperty("stickerName") public String getStickerName() { return stickerName; @@ -35,6 +32,7 @@ public void setStickerName(String stickerName) { this.stickerName = stickerName; } + /** The description of the sticker. */ @JsonProperty("stickerDescription") public String getStickerDescription() { return stickerDescription; @@ -45,22 +43,15 @@ public void setStickerDescription(String stickerDescription) { this.stickerDescription = stickerDescription; } - /** - * Quantity remaining (-1 for infinite) - * - */ + /** The quantity remaining for the sticker. */ @JsonProperty("stickerQuantityRemaining") public Integer getStickerQuantityRemaining() { return stickerQuantityRemaining; } - /** - * Quantity remaining (-1 for infinite) - * - */ + /** Quantity remaining (-1 for infinite). */ @JsonProperty("stickerQuantityRemaining") public void setStickerQuantityRemaining(Integer stickerQuantityRemaining) { this.stickerQuantityRemaining = stickerQuantityRemaining; } - -} \ No newline at end of file +} diff --git a/sticker-award/src/main/java/com/datadoghq/stickerlandia/stickeraward/sticker/entity/Sticker.java b/sticker-award/src/main/java/com/datadoghq/stickerlandia/stickeraward/sticker/entity/Sticker.java index 4927b6e2..2e45ca1a 100644 --- a/sticker-award/src/main/java/com/datadoghq/stickerlandia/stickeraward/sticker/entity/Sticker.java +++ b/sticker-award/src/main/java/com/datadoghq/stickerlandia/stickeraward/sticker/entity/Sticker.java @@ -5,9 +5,9 @@ import jakarta.persistence.Entity; import jakarta.persistence.Id; import jakarta.persistence.Table; - import java.time.Instant; +/** Entity representing a sticker in the system. */ @Entity @Table(name = "stickers") public class Sticker extends PanacheEntityBase { @@ -37,12 +37,24 @@ public class Sticker extends PanacheEntityBase { @Column(name = "updated_at") private Instant updatedAt; - // Default constructor for JPA - public Sticker() { - } - - // Constructor with fields - public Sticker(String stickerId, String name, String description, String imageUrl, Integer stickerQuantityRemaining) { + /** Default constructor for JPA. */ + public Sticker() {} + + /** + * Constructor with fields for creating a new sticker. + * + * @param stickerId the unique identifier for the sticker + * @param name the name of the sticker + * @param description the description of the sticker + * @param imageUrl the URL of the sticker image + * @param stickerQuantityRemaining the quantity remaining for the sticker + */ + public Sticker( + String stickerId, + String name, + String description, + String imageUrl, + Integer stickerQuantityRemaining) { this.stickerId = stickerId; this.name = name; this.description = description; @@ -116,22 +128,34 @@ public void setUpdatedAt(Instant updatedAt) { this.updatedAt = updatedAt; } - // Helper methods for quantity management + /** + * Checks if this sticker is available for assignment. + * + * @return true if the sticker is available, false otherwise + */ public boolean isAvailable() { - return stickerQuantityRemaining == null || stickerQuantityRemaining == -1 || stickerQuantityRemaining > 0; + return stickerQuantityRemaining == null + || stickerQuantityRemaining == -1 + || stickerQuantityRemaining > 0; } + /** + * Checks if this sticker has unlimited quantity. + * + * @return true if the sticker has unlimited quantity, false otherwise + */ public boolean hasUnlimitedQuantity() { return stickerQuantityRemaining == null || stickerQuantityRemaining == -1; } + /** Decrements the quantity remaining by 1. */ public void decrementQuantity() { if (stickerQuantityRemaining != null && stickerQuantityRemaining > 0) { stickerQuantityRemaining--; } } - // Increase quantity by 1 (for removal) + /** Increases the quantity remaining by 1 (for removal). */ public void increaseQuantity() { if (stickerQuantityRemaining != null && stickerQuantityRemaining >= 0) { stickerQuantityRemaining++; @@ -139,8 +163,22 @@ public void increaseQuantity() { // If -1 (infinite), do nothing } - // Helper methods for finding stickers - public static Sticker findById(String id) { - return find("stickerId", id).firstResult(); + /** + * Updates the quantity remaining for this sticker. + * + * @param newQuantity the new quantity remaining + */ + public void updateQuantityRemaining(Integer newQuantity) { + this.stickerQuantityRemaining = newQuantity; + } + + /** + * Finds a sticker by its unique identifier. + * + * @param stickerId the unique identifier of the sticker + * @return the sticker if found, null otherwise + */ + public static Sticker findByStickerId(String stickerId) { + return find("stickerId", stickerId).firstResult(); } -} \ No newline at end of file +} diff --git a/sticker-award/src/test/java/com/datadoghq/stickerlandia/stickeraward/HealthResourceIT.java b/sticker-award/src/test/java/com/datadoghq/stickerlandia/stickeraward/HealthResourceIntegrationTest.java similarity index 60% rename from sticker-award/src/test/java/com/datadoghq/stickerlandia/stickeraward/HealthResourceIT.java rename to sticker-award/src/test/java/com/datadoghq/stickerlandia/stickeraward/HealthResourceIntegrationTest.java index ab175816..324f23d5 100644 --- a/sticker-award/src/test/java/com/datadoghq/stickerlandia/stickeraward/HealthResourceIT.java +++ b/sticker-award/src/test/java/com/datadoghq/stickerlandia/stickeraward/HealthResourceIntegrationTest.java @@ -2,7 +2,8 @@ import io.quarkus.test.junit.QuarkusIntegrationTest; +/** Integration test for HealthResource. */ @QuarkusIntegrationTest -public class HealthResourceIT extends HealthResourceTest { +public class HealthResourceIntegrationTest extends HealthResourceTest { // Execute the same tests but in packaged mode. -} \ No newline at end of file +} diff --git a/sticker-award/src/test/java/com/datadoghq/stickerlandia/stickeraward/HealthResourceTest.java b/sticker-award/src/test/java/com/datadoghq/stickerlandia/stickeraward/HealthResourceTest.java index 5f36458f..3af3e86b 100644 --- a/sticker-award/src/test/java/com/datadoghq/stickerlandia/stickeraward/HealthResourceTest.java +++ b/sticker-award/src/test/java/com/datadoghq/stickerlandia/stickeraward/HealthResourceTest.java @@ -1,24 +1,22 @@ package com.datadoghq.stickerlandia.stickeraward; +import static io.restassured.RestAssured.given; +import static org.hamcrest.CoreMatchers.is; + import io.quarkus.test.junit.QuarkusTest; import io.restassured.http.ContentType; import org.junit.jupiter.api.Test; -import static io.restassured.RestAssured.given; -import static org.hamcrest.CoreMatchers.is; -import static org.hamcrest.CoreMatchers.notNullValue; - @QuarkusTest public class HealthResourceTest { @Test void testHealthEndpoint() { - given() - .when().get("/health") - .then() + given().when() + .get("/health") + .then() .statusCode(200) .contentType(ContentType.JSON) .body("status", is("OK")); - } -} \ No newline at end of file +} diff --git a/sticker-award/src/test/java/com/datadoghq/stickerlandia/stickeraward/KafkaTestProfile.java b/sticker-award/src/test/java/com/datadoghq/stickerlandia/stickeraward/KafkaTestProfile.java new file mode 100644 index 00000000..e058e051 --- /dev/null +++ b/sticker-award/src/test/java/com/datadoghq/stickerlandia/stickeraward/KafkaTestProfile.java @@ -0,0 +1,13 @@ +package com.datadoghq.stickerlandia.stickeraward; + +import io.quarkus.test.junit.QuarkusTestProfile; +import java.util.List; + +/** Test profile for Kafka integration tests. */ +public class KafkaTestProfile implements QuarkusTestProfile { + + @Override + public List testResources() { + return List.of(new TestResourceEntry(KafkaTestResourceLifecycleManager.class)); + } +} diff --git a/sticker-award/src/test/java/com/datadoghq/stickerlandia/stickeraward/KafkaTestResourceLifecycleManager.java b/sticker-award/src/test/java/com/datadoghq/stickerlandia/stickeraward/KafkaTestResourceLifecycleManager.java index dfe92d40..8fee7e1f 100644 --- a/sticker-award/src/test/java/com/datadoghq/stickerlandia/stickeraward/KafkaTestResourceLifecycleManager.java +++ b/sticker-award/src/test/java/com/datadoghq/stickerlandia/stickeraward/KafkaTestResourceLifecycleManager.java @@ -1,35 +1,38 @@ package com.datadoghq.stickerlandia.stickeraward; +import io.quarkus.test.common.QuarkusTestResourceLifecycleManager; import java.util.HashMap; import java.util.Map; -import io.quarkus.test.common.QuarkusTestResourceLifecycleManager; - /** - * Resource lifecycle manager for Kafka in integration tests. - * This configures properties for testing Kafka-based messaging. + * Resource lifecycle manager for Kafka in integration tests. This configures properties for testing + * Kafka-based messaging. */ public class KafkaTestResourceLifecycleManager implements QuarkusTestResourceLifecycleManager { @Override public Map start() { Map env = new HashMap<>(); - + // Use test configuration to prevent actual Kafka connections env.put("mp.messaging.outgoing.stickers-assigned.connector", "smallrye-kafka"); - env.put("mp.messaging.outgoing.stickers-assigned.topic", "stickers.stickerAssignedToUser.v1"); - + env.put( + "mp.messaging.outgoing.stickers-assigned.topic", + "stickers.stickerAssignedToUser.v1"); + env.put("mp.messaging.outgoing.stickers-removed.connector", "smallrye-kafka"); - env.put("mp.messaging.outgoing.stickers-removed.topic", "stickers.stickerRemovedFromUser.v1"); - + env.put( + "mp.messaging.outgoing.stickers-removed.topic", + "stickers.stickerRemovedFromUser.v1"); + env.put("mp.messaging.outgoing.stickers-claimed.connector", "smallrye-kafka"); env.put("mp.messaging.outgoing.stickers-claimed.topic", "users.stickerClaimed.v1"); - + return env; } - + @Override public void stop() { // No resources to clean up } -} \ No newline at end of file +} diff --git a/sticker-award/src/test/java/com/datadoghq/stickerlandia/stickeraward/StickerAwardResourceIT.java b/sticker-award/src/test/java/com/datadoghq/stickerlandia/stickeraward/StickerAwardResourceIntegrationTest.java similarity index 55% rename from sticker-award/src/test/java/com/datadoghq/stickerlandia/stickeraward/StickerAwardResourceIT.java rename to sticker-award/src/test/java/com/datadoghq/stickerlandia/stickeraward/StickerAwardResourceIntegrationTest.java index 93c1f1b3..3051d100 100644 --- a/sticker-award/src/test/java/com/datadoghq/stickerlandia/stickeraward/StickerAwardResourceIT.java +++ b/sticker-award/src/test/java/com/datadoghq/stickerlandia/stickeraward/StickerAwardResourceIntegrationTest.java @@ -1,25 +1,28 @@ package com.datadoghq.stickerlandia.stickeraward; -import io.quarkus.test.junit.QuarkusIntegrationTest; -import io.restassured.http.ContentType; -import org.junit.jupiter.api.Test; - import static io.restassured.RestAssured.given; import static org.hamcrest.CoreMatchers.is; -import static org.hamcrest.CoreMatchers.notNullValue; -@QuarkusIntegrationTest -class StickerAwardResourceIT { - // Instead of extending StickerAwardResourceTest (which uses @Inject), +import io.quarkus.test.junit.QuarkusTest; +import io.restassured.http.ContentType; +import org.junit.jupiter.api.MethodOrderer; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.TestMethodOrder; + +/** Integration test for StickerAwardResource. */ +@QuarkusTest +@TestMethodOrder(MethodOrderer.OrderAnnotation.class) +public class StickerAwardResourceIntegrationTest { + // Instead of extending StickerAwardResourceTest (which uses @Inject), // implement a simple standalone test - + @Test void testGetHealth() { - given() - .when().get("/health") - .then() + given().when() + .get("/health") + .then() .statusCode(200) .contentType(ContentType.JSON) .body("status", is("OK")); } -} \ No newline at end of file +} diff --git a/sticker-award/src/test/java/com/datadoghq/stickerlandia/stickeraward/StickerAwardResourceKafkaIT.java b/sticker-award/src/test/java/com/datadoghq/stickerlandia/stickeraward/StickerAwardResourceKafkaIT.java deleted file mode 100644 index a23e6f94..00000000 --- a/sticker-award/src/test/java/com/datadoghq/stickerlandia/stickeraward/StickerAwardResourceKafkaIT.java +++ /dev/null @@ -1,94 +0,0 @@ -package com.datadoghq.stickerlandia.stickeraward; - -import static io.restassured.RestAssured.given; -import static org.hamcrest.CoreMatchers.is; - -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.MethodOrderer; -import org.junit.jupiter.api.Order; -import org.junit.jupiter.api.TestMethodOrder; - -import com.datadoghq.stickerlandia.stickeraward.award.dto.AssignStickerRequest; -import com.datadoghq.stickerlandia.stickeraward.sticker.entity.Sticker; - -import io.quarkus.test.junit.QuarkusTest; -import jakarta.transaction.Transactional; -import jakarta.ws.rs.core.MediaType; -import jakarta.ws.rs.core.Response.Status; - -/** - * Integration tests for StickerAwardResource - * Tests the HTTP API endpoints integration with the database (without Kafka) - */ -@QuarkusTest -@TestMethodOrder(MethodOrderer.OrderAnnotation.class) -public class StickerAwardResourceKafkaIT { - - private static final String TEST_STICKER_ID = "test-sticker-integration"; - private static final String TEST_USER_ID = "test-user-integration"; - - @Test - @Transactional - @Order(1) - public void testCreateSticker() { - // Create a test sticker in the database - Sticker sticker = new Sticker(TEST_STICKER_ID, "Test Sticker", "A test sticker", "https://example.com/sticker.png", 100); - sticker.persist(); - } - - @Test - @Order(2) - public void testAssignStickerToUser() { - // Create the assign sticker command - AssignStickerRequest command = new AssignStickerRequest(); - command.setStickerId(TEST_STICKER_ID); - command.setReason("Integration test"); - - // Call the API to assign the sticker - given() - .contentType(MediaType.APPLICATION_JSON) - .body(command) - .when() - .post("/api/award/v1/users/" + TEST_USER_ID + "/stickers") - .then() - .statusCode(Status.CREATED.getStatusCode()) - .body("userId", is(TEST_USER_ID)) - .body("stickerId", is(TEST_STICKER_ID)); - } - - @Test - @Order(3) - public void testGetUserStickers() { - // Verify the user has the sticker - given() - .when() - .get("/api/award/v1/users/" + TEST_USER_ID + "/stickers") - .then() - .statusCode(Status.OK.getStatusCode()) - .body("userId", is(TEST_USER_ID)) - .body("stickers.size()", is(1)) - .body("stickers[0].stickerId", is(TEST_STICKER_ID)); - } - - @Test - @Order(4) - public void testRemoveStickerFromUser() { - // Call the API to remove the sticker - given() - .when() - .delete("/api/award/v1/users/" + TEST_USER_ID + "/stickers/" + TEST_STICKER_ID) - .then() - .statusCode(Status.OK.getStatusCode()) - .body("userId", is(TEST_USER_ID)) - .body("stickerId", is(TEST_STICKER_ID)); - - // Verify the user no longer has the sticker - given() - .when() - .get("/api/award/v1/users/" + TEST_USER_ID + "/stickers") - .then() - .statusCode(Status.OK.getStatusCode()) - .body("userId", is(TEST_USER_ID)) - .body("stickers.size()", is(0)); - } -} \ No newline at end of file diff --git a/sticker-award/src/test/java/com/datadoghq/stickerlandia/stickeraward/StickerAwardResourceKafkaIntegrationTest.java b/sticker-award/src/test/java/com/datadoghq/stickerlandia/stickeraward/StickerAwardResourceKafkaIntegrationTest.java new file mode 100644 index 00000000..b23dfd3e --- /dev/null +++ b/sticker-award/src/test/java/com/datadoghq/stickerlandia/stickeraward/StickerAwardResourceKafkaIntegrationTest.java @@ -0,0 +1,93 @@ +package com.datadoghq.stickerlandia.stickeraward; + +import static io.restassured.RestAssured.given; +import static org.hamcrest.CoreMatchers.is; + +import com.datadoghq.stickerlandia.stickeraward.award.dto.AssignStickerRequest; +import com.datadoghq.stickerlandia.stickeraward.sticker.entity.Sticker; +import io.quarkus.test.junit.QuarkusTest; +import io.quarkus.test.junit.TestProfile; +import jakarta.transaction.Transactional; +import jakarta.ws.rs.core.MediaType; +import jakarta.ws.rs.core.Response.Status; +import org.junit.jupiter.api.MethodOrderer; +import org.junit.jupiter.api.Order; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.TestMethodOrder; + +/** Integration test for Kafka functionality in StickerAwardResource. */ +@QuarkusTest +@TestProfile(KafkaTestProfile.class) +@TestMethodOrder(MethodOrderer.OrderAnnotation.class) +public class StickerAwardResourceKafkaIntegrationTest { + + private static final String TEST_STICKER_ID = "test-sticker-integration"; + private static final String TEST_USER_ID = "test-user-integration"; + + @Test + @Transactional + @Order(1) + public void testCreateSticker() { + // Create a test sticker in the database + Sticker sticker = + new Sticker( + TEST_STICKER_ID, + "Test Sticker", + "A test sticker", + "https://example.com/sticker.png", + 100); + sticker.persist(); + } + + @Test + @Order(2) + public void testAssignStickerToUser() { + // Create the assign sticker command + AssignStickerRequest command = new AssignStickerRequest(); + command.setStickerId(TEST_STICKER_ID); + command.setReason("Integration test"); + + // Call the API to assign the sticker + given().contentType(MediaType.APPLICATION_JSON) + .body(command) + .when() + .post("/api/award/v1/users/" + TEST_USER_ID + "/stickers") + .then() + .statusCode(Status.CREATED.getStatusCode()) + .body("userId", is(TEST_USER_ID)) + .body("stickerId", is(TEST_STICKER_ID)); + } + + @Test + @Order(3) + public void testGetUserStickers() { + // Verify the user has the sticker + given().when() + .get("/api/award/v1/users/" + TEST_USER_ID + "/stickers") + .then() + .statusCode(Status.OK.getStatusCode()) + .body("userId", is(TEST_USER_ID)) + .body("stickers.size()", is(1)) + .body("stickers[0].stickerId", is(TEST_STICKER_ID)); + } + + @Test + @Order(4) + public void testRemoveStickerFromUser() { + // Call the API to remove the sticker + given().when() + .delete("/api/award/v1/users/" + TEST_USER_ID + "/stickers/" + TEST_STICKER_ID) + .then() + .statusCode(Status.OK.getStatusCode()) + .body("userId", is(TEST_USER_ID)) + .body("stickerId", is(TEST_STICKER_ID)); + + // Verify the user no longer has the sticker + given().when() + .get("/api/award/v1/users/" + TEST_USER_ID + "/stickers") + .then() + .statusCode(Status.OK.getStatusCode()) + .body("userId", is(TEST_USER_ID)) + .body("stickers.size()", is(0)); + } +} diff --git a/sticker-award/src/test/java/com/datadoghq/stickerlandia/stickeraward/StickerAwardResourceTest.java b/sticker-award/src/test/java/com/datadoghq/stickerlandia/stickeraward/StickerAwardResourceTest.java index 7751859b..5d1fcd29 100644 --- a/sticker-award/src/test/java/com/datadoghq/stickerlandia/stickeraward/StickerAwardResourceTest.java +++ b/sticker-award/src/test/java/com/datadoghq/stickerlandia/stickeraward/StickerAwardResourceTest.java @@ -1,22 +1,19 @@ package com.datadoghq.stickerlandia.stickeraward; +import static io.restassured.RestAssured.given; +import static org.hamcrest.CoreMatchers.is; +import static org.hamcrest.CoreMatchers.notNullValue; + import com.datadoghq.stickerlandia.stickeraward.award.dto.AssignStickerRequest; +import com.datadoghq.stickerlandia.stickeraward.award.entity.StickerAssignment; +import com.datadoghq.stickerlandia.stickeraward.sticker.entity.Sticker; import io.quarkus.test.junit.QuarkusTest; import io.restassured.http.ContentType; - -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; - -import com.datadoghq.stickerlandia.stickeraward.sticker.entity.Sticker; -import com.datadoghq.stickerlandia.stickeraward.award.entity.StickerAssignment; - import jakarta.inject.Inject; import jakarta.persistence.EntityManager; import jakarta.transaction.Transactional; - -import static io.restassured.RestAssured.given; -import static org.hamcrest.CoreMatchers.is; -import static org.hamcrest.CoreMatchers.notNullValue; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; @QuarkusTest class StickerAwardResourceTest { @@ -25,8 +22,7 @@ class StickerAwardResourceTest { private static final String EXISTING_STICKER_ID = "sticker-001"; private static final String NON_EXISTING_STICKER_ID = "sticker-999"; - @Inject - EntityManager em; + @Inject EntityManager em; @BeforeEach @Transactional @@ -34,25 +30,44 @@ void setupTestData() { // Make sure our test stickers exist Sticker sticker1 = Sticker.findById(EXISTING_STICKER_ID); if (sticker1 == null) { - sticker1 = new Sticker(EXISTING_STICKER_ID, "Test Sticker", "For testing", "http://example.com/test.png", 100); + sticker1 = + new Sticker( + EXISTING_STICKER_ID, + "Test Sticker", + "For testing", + "http://example.com/test.png", + 100); sticker1.persist(); } // Create sticker-002 and sticker-003 for other tests Sticker sticker2 = Sticker.findById("sticker-002"); if (sticker2 == null) { - sticker2 = new Sticker("sticker-002", "Test Sticker 2", "For testing", "http://example.com/test2.png", 100); + sticker2 = + new Sticker( + "sticker-002", + "Test Sticker 2", + "For testing", + "http://example.com/test2.png", + 100); sticker2.persist(); } Sticker sticker3 = Sticker.findById("sticker-003"); if (sticker3 == null) { - sticker3 = new Sticker("sticker-003", "Test Sticker 3", "For testing", "http://example.com/test3.png", 100); + sticker3 = + new Sticker( + "sticker-003", + "Test Sticker 3", + "For testing", + "http://example.com/test3.png", + 100); sticker3.persist(); } // Make sure user has a sticker assignment - StickerAssignment assignment = StickerAssignment.findActiveByUserAndSticker(TEST_USER_ID, EXISTING_STICKER_ID); + StickerAssignment assignment = + StickerAssignment.findActiveByUserAndSticker(TEST_USER_ID, EXISTING_STICKER_ID); if (assignment == null) { assignment = new StickerAssignment(TEST_USER_ID, sticker1, "For test setup"); assignment.persist(); @@ -61,9 +76,9 @@ void setupTestData() { @Test void testGetUserStickers() { - given() - .when().get("/api/award/v1/users/{userId}/stickers", TEST_USER_ID) - .then() + given().when() + .get("/api/award/v1/users/{userId}/stickers", TEST_USER_ID) + .then() .statusCode(200) .contentType(ContentType.JSON) .body("userId", is(TEST_USER_ID)) @@ -74,9 +89,9 @@ void testGetUserStickers() { @Test void testGetUserStickersForUserWithNoStickers() { String unknownUserId = "unknown-user"; - given() - .when().get("/api/award/v1/users/{userId}/stickers", unknownUserId) - .then() + given().when() + .get("/api/award/v1/users/{userId}/stickers", unknownUserId) + .then() .statusCode(200) .contentType(ContentType.JSON) .body("userId", is(unknownUserId)) @@ -86,19 +101,18 @@ void testGetUserStickersForUserWithNoStickers() { @Test void testAssignStickerToUser() { String userId = "test-user-" + System.currentTimeMillis(); - String stickerId = "sticker-002"; // Use one of the stickers from our seed data + String stickerId = "sticker-002"; // Use one of the stickers from our seed data // Create sticker assignment request using a proper bean for serialization - AssignStickerRequest command = - new AssignStickerRequest(); + AssignStickerRequest command = new AssignStickerRequest(); command.setStickerId(stickerId); command.setReason("Test assignment"); - given() - .contentType(ContentType.JSON) - .body(command) - .when().post("/api/award/v1/users/{userId}/stickers", userId) - .then() + given().contentType(ContentType.JSON) + .body(command) + .when() + .post("/api/award/v1/users/{userId}/stickers", userId) + .then() .statusCode(201) .contentType(ContentType.JSON) .body("userId", is(userId)) @@ -106,9 +120,9 @@ void testAssignStickerToUser() { .body("assignedAt", notNullValue()); // Verify the sticker is now assigned by getting user stickers - given() - .when().get("/api/award/v1/users/{userId}/stickers", userId) - .then() + given().when() + .get("/api/award/v1/users/{userId}/stickers", userId) + .then() .statusCode(200) .contentType(ContentType.JSON) .body("stickers.size()", is(1)) @@ -120,16 +134,15 @@ void testAssignNonExistingStickerReturns400() { String userId = "test-user-" + System.currentTimeMillis(); // Create sticker assignment request with non-existing sticker - AssignStickerRequest command = - new AssignStickerRequest(); + AssignStickerRequest command = new AssignStickerRequest(); command.setStickerId(NON_EXISTING_STICKER_ID); command.setReason("Test assignment"); - given() - .contentType(ContentType.JSON) - .body(command) - .when().post("/api/award/v1/users/{userId}/stickers", userId) - .then() + given().contentType(ContentType.JSON) + .body(command) + .when() + .post("/api/award/v1/users/{userId}/stickers", userId) + .then() .statusCode(400); } @@ -137,27 +150,26 @@ void testAssignNonExistingStickerReturns400() { void testAssignAlreadyAssignedStickerReturns409() { // First, assign a sticker String userId = "test-user-" + System.currentTimeMillis(); - String stickerId = "sticker-003"; // Use one from seed data + String stickerId = "sticker-003"; // Use one from seed data - AssignStickerRequest command = - new AssignStickerRequest(); + AssignStickerRequest command = new AssignStickerRequest(); command.setStickerId(stickerId); command.setReason("Test assignment"); // First assignment should succeed - given() - .contentType(ContentType.JSON) - .body(command) - .when().post("/api/award/v1/users/{userId}/stickers", userId) - .then() + given().contentType(ContentType.JSON) + .body(command) + .when() + .post("/api/award/v1/users/{userId}/stickers", userId) + .then() .statusCode(201); // Second assignment of the same sticker should fail with 409 Conflict - given() - .contentType(ContentType.JSON) - .body(command) - .when().post("/api/award/v1/users/{userId}/stickers", userId) - .then() + given().contentType(ContentType.JSON) + .body(command) + .when() + .post("/api/award/v1/users/{userId}/stickers", userId) + .then() .statusCode(409); } @@ -167,23 +179,22 @@ void testRemoveStickerAssignment() { String userId = "test-user-" + System.currentTimeMillis(); String stickerId = "sticker-002"; - AssignStickerRequest command = - new AssignStickerRequest(); + AssignStickerRequest command = new AssignStickerRequest(); command.setStickerId(stickerId); command.setReason("Test assignment"); // Assign the sticker - given() - .contentType(ContentType.JSON) - .body(command) - .when().post("/api/award/v1/users/{userId}/stickers", userId) - .then() + given().contentType(ContentType.JSON) + .body(command) + .when() + .post("/api/award/v1/users/{userId}/stickers", userId) + .then() .statusCode(201); // Remove the sticker - given() - .when().delete("/api/award/v1/users/{userId}/stickers/{stickerId}", userId, stickerId) - .then() + given().when() + .delete("/api/award/v1/users/{userId}/stickers/{stickerId}", userId, stickerId) + .then() .statusCode(200) .contentType(ContentType.JSON) .body("userId", is(userId)) @@ -191,9 +202,9 @@ void testRemoveStickerAssignment() { .body("removedAt", notNullValue()); // Verify the sticker is no longer assigned - given() - .when().get("/api/award/v1/users/{userId}/stickers", userId) - .then() + given().when() + .get("/api/award/v1/users/{userId}/stickers", userId) + .then() .statusCode(200) .contentType(ContentType.JSON) .body("stickers.size()", is(0)); @@ -204,9 +215,9 @@ void testRemoveNonExistingStickerAssignmentReturns404() { String userId = "test-user-" + System.currentTimeMillis(); String stickerId = "sticker-001"; - given() - .when().delete("/api/award/v1/users/{userId}/stickers/{stickerId}", userId, stickerId) - .then() + given().when() + .delete("/api/award/v1/users/{userId}/stickers/{stickerId}", userId, stickerId) + .then() .statusCode(404); } -} \ No newline at end of file +} diff --git a/sticker-award/src/test/java/com/datadoghq/stickerlandia/stickeraward/messaging/MockEmitterProducer.java b/sticker-award/src/test/java/com/datadoghq/stickerlandia/stickeraward/messaging/MockEmitterProducer.java index b2aff257..a4420da0 100644 --- a/sticker-award/src/test/java/com/datadoghq/stickerlandia/stickeraward/messaging/MockEmitterProducer.java +++ b/sticker-award/src/test/java/com/datadoghq/stickerlandia/stickeraward/messaging/MockEmitterProducer.java @@ -1,39 +1,37 @@ package com.datadoghq.stickerlandia.stickeraward.messaging; +import com.datadoghq.stickerlandia.stickeraward.common.events.out.StickerAssignedToUserEvent; +import com.datadoghq.stickerlandia.stickeraward.common.events.out.StickerClaimedEvent; +import com.datadoghq.stickerlandia.stickeraward.common.events.out.StickerRemovedFromUserEvent; import io.quarkus.test.Mock; import jakarta.enterprise.context.ApplicationScoped; import jakarta.enterprise.inject.Produces; - import org.eclipse.microprofile.reactive.messaging.Emitter; import org.mockito.Mockito; -import com.datadoghq.stickerlandia.stickeraward.common.events.out.StickerAssignedToUserEvent; -import com.datadoghq.stickerlandia.stickeraward.common.events.out.StickerClaimedEvent; -import com.datadoghq.stickerlandia.stickeraward.common.events.out.StickerRemovedFromUserEvent; - /** - * Producer for mock emitters to be used in tests. - * This is necessary because we need to mock the Kafka emitters. + * Producer for mock emitters to be used in tests. This is necessary because we need to mock the + * Kafka emitters. */ @Mock @ApplicationScoped public class MockEmitterProducer { - + @Produces @jakarta.inject.Named("stickers-assigned") public Emitter createStickerAssignedEmitter() { return Mockito.mock(Emitter.class); } - + @Produces @jakarta.inject.Named("stickers-removed") public Emitter createStickerRemovedEmitter() { return Mockito.mock(Emitter.class); } - + @Produces @jakarta.inject.Named("stickers-claimed") public Emitter createStickerClaimedEmitter() { return Mockito.mock(Emitter.class); } -} \ No newline at end of file +} diff --git a/sticker-award/src/test/java/com/datadoghq/stickerlandia/stickeraward/messaging/MockStickerAwardEventPublisher.java b/sticker-award/src/test/java/com/datadoghq/stickerlandia/stickeraward/messaging/MockStickerAwardEventPublisher.java index a6197d06..78ea7481 100644 --- a/sticker-award/src/test/java/com/datadoghq/stickerlandia/stickeraward/messaging/MockStickerAwardEventPublisher.java +++ b/sticker-award/src/test/java/com/datadoghq/stickerlandia/stickeraward/messaging/MockStickerAwardEventPublisher.java @@ -1,35 +1,37 @@ package com.datadoghq.stickerlandia.stickeraward.messaging; +import com.datadoghq.stickerlandia.stickeraward.award.entity.StickerAssignment; import com.datadoghq.stickerlandia.stickeraward.award.messaging.StickerAwardEventPublisher; import jakarta.enterprise.context.ApplicationScoped; import jakarta.enterprise.inject.Alternative; - import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import com.datadoghq.stickerlandia.stickeraward.award.entity.StickerAssignment; - /** - * Mock implementation of StickerEventPublisher for tests. - * This prevents Kafka connectivity issues in tests. + * Mock implementation of StickerEventPublisher for tests. This prevents Kafka connectivity issues + * in tests. */ @Alternative @ApplicationScoped public class MockStickerAwardEventPublisher extends StickerAwardEventPublisher { - + private static final Logger log = LoggerFactory.getLogger(MockStickerAwardEventPublisher.class); - + @Override public void publishStickerAssigned(StickerAssignment assignment) { - log.info("MOCK: Publishing sticker assigned event: userId={}, stickerId={}", - assignment.getUserId(), assignment.getSticker().getStickerId()); + log.info( + "MOCK: Publishing sticker assigned event: userId={}, stickerId={}", + assignment.getUserId(), + assignment.getSticker().getStickerId()); // No-op in tests } - + @Override public void publishStickerRemoved(StickerAssignment assignment) { - log.info("MOCK: Publishing sticker removed event: userId={}, stickerId={}", - assignment.getUserId(), assignment.getSticker().getStickerId()); + log.info( + "MOCK: Publishing sticker removed event: userId={}, stickerId={}", + assignment.getUserId(), + assignment.getSticker().getStickerId()); // No-op in tests } -} \ No newline at end of file +} diff --git a/sticker-award/src/test/java/com/datadoghq/stickerlandia/stickeraward/messaging/StickerAwardEventPublisherTest.java b/sticker-award/src/test/java/com/datadoghq/stickerlandia/stickeraward/messaging/StickerAwardEventPublisherTest.java index a38326e1..c7df5046 100644 --- a/sticker-award/src/test/java/com/datadoghq/stickerlandia/stickeraward/messaging/StickerAwardEventPublisherTest.java +++ b/sticker-award/src/test/java/com/datadoghq/stickerlandia/stickeraward/messaging/StickerAwardEventPublisherTest.java @@ -2,46 +2,41 @@ import static org.junit.jupiter.api.Assertions.assertEquals; import static org.mockito.ArgumentCaptor.forClass; -import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.doThrow; import static org.mockito.Mockito.never; import static org.mockito.Mockito.times; -import static org.mockito.Mockito.doThrow; - -import java.time.Instant; - -import com.datadoghq.stickerlandia.stickeraward.award.messaging.StickerAwardEventPublisher; -import org.eclipse.microprofile.reactive.messaging.Emitter; -import org.junit.jupiter.api.Test; -import org.mockito.ArgumentCaptor; +import static org.mockito.Mockito.verify; -import com.datadoghq.stickerlandia.stickeraward.sticker.entity.Sticker; import com.datadoghq.stickerlandia.stickeraward.award.entity.StickerAssignment; +import com.datadoghq.stickerlandia.stickeraward.award.messaging.StickerAwardEventPublisher; import com.datadoghq.stickerlandia.stickeraward.common.events.out.StickerAssignedToUserEvent; import com.datadoghq.stickerlandia.stickeraward.common.events.out.StickerClaimedEvent; import com.datadoghq.stickerlandia.stickeraward.common.events.out.StickerRemovedFromUserEvent; - +import com.datadoghq.stickerlandia.stickeraward.sticker.entity.Sticker; import io.quarkus.test.InjectMock; import io.quarkus.test.junit.QuarkusTest; import jakarta.inject.Inject; +import java.time.Instant; +import org.eclipse.microprofile.reactive.messaging.Emitter; +import org.junit.jupiter.api.Test; +import org.mockito.ArgumentCaptor; -/** - * Unit tests for the StickerEventPublisher class. - */ +/** Unit tests for the StickerEventPublisher class. */ @QuarkusTest -@org.junit.jupiter.api.Disabled("Kafka integration tests are challenging to set up in this environment") +@org.junit.jupiter.api.Disabled( + "Kafka integration tests are challenging to set up in this environment") public class StickerAwardEventPublisherTest { - @Inject - StickerAwardEventPublisher publisher; - + @Inject StickerAwardEventPublisher publisher; + @InjectMock @SuppressWarnings("unused") Emitter stickerAssignedEmitter; - + @InjectMock @SuppressWarnings("unused") Emitter stickerRemovedEmitter; - + @InjectMock @SuppressWarnings("unused") Emitter stickerClaimedEmitter; @@ -49,77 +44,107 @@ public class StickerAwardEventPublisherTest { @Test public void testPublishStickerAssigned() { // Prepare test data - Sticker sticker = new Sticker("sticker-123", "Test Sticker", "Test Description", "https://example.com/image.png", 100); + Sticker sticker = + new Sticker( + "sticker-123", + "Test Sticker", + "Test Description", + "https://example.com/image.png", + 100); StickerAssignment assignment = new StickerAssignment("user-123", sticker, "For testing"); - + // Execute the method publisher.publishStickerAssigned(assignment); - + // Verify assigned event - ArgumentCaptor assignedCaptor = forClass(StickerAssignedToUserEvent.class); + ArgumentCaptor assignedCaptor = + forClass(StickerAssignedToUserEvent.class); verify(stickerAssignedEmitter, times(1)).send(assignedCaptor.capture()); - + StickerAssignedToUserEvent assignedEvent = assignedCaptor.getValue(); assertEquals("user-123", assignedEvent.getAccountId()); assertEquals("sticker-123", assignedEvent.getStickerId()); assertEquals(assignment.getAssignedAt(), assignedEvent.getAssignedAt()); - + // Verify claimed event is also published ArgumentCaptor claimedCaptor = forClass(StickerClaimedEvent.class); verify(stickerClaimedEmitter, times(1)).send(claimedCaptor.capture()); - + StickerClaimedEvent claimedEvent = claimedCaptor.getValue(); assertEquals("user-123", claimedEvent.getAccountId()); assertEquals("sticker-123", claimedEvent.getStickerId()); } - + @Test public void testPublishStickerRemoved() { // Prepare test data - Sticker sticker = new Sticker("sticker-456", "Test Sticker", "Test Description", "https://example.com/image.png", 100); + Sticker sticker = + new Sticker( + "sticker-456", + "Test Sticker", + "Test Description", + "https://example.com/image.png", + 100); StickerAssignment assignment = new StickerAssignment("user-456", sticker, "For testing"); Instant removedAt = Instant.now(); assignment.setRemovedAt(removedAt); - + // Execute the method publisher.publishStickerRemoved(assignment); - + // Verify removed event - ArgumentCaptor removedCaptor = forClass(StickerRemovedFromUserEvent.class); + ArgumentCaptor removedCaptor = + forClass(StickerRemovedFromUserEvent.class); verify(stickerRemovedEmitter, times(1)).send(removedCaptor.capture()); - + StickerRemovedFromUserEvent removedEvent = removedCaptor.getValue(); assertEquals("user-456", removedEvent.getAccountId()); assertEquals("sticker-456", removedEvent.getStickerId()); assertEquals(removedAt, removedEvent.getRemovedAt()); } - + @Test public void testPublishStickerRemovedWithActiveAssignment() { // Prepare test data with no removal date - Sticker sticker = new Sticker("sticker-789", "Test Sticker", "Test Description", "https://example.com/image.png", 100); + Sticker sticker = + new Sticker( + "sticker-789", + "Test Sticker", + "Test Description", + "https://example.com/image.png", + 100); StickerAssignment assignment = new StickerAssignment("user-789", sticker, "For testing"); - + // Execute the method - should log a warning but not throw exception publisher.publishStickerRemoved(assignment); - + // Verify no events were sent - verify(stickerRemovedEmitter, never()).send(org.mockito.ArgumentMatchers.any(StickerRemovedFromUserEvent.class)); + verify(stickerRemovedEmitter, never()) + .send(org.mockito.ArgumentMatchers.any(StickerRemovedFromUserEvent.class)); } - + @Test public void testEmitterFailure() { // Prepare test data - Sticker sticker = new Sticker("sticker-fail", "Test Sticker", "Test Description", "https://example.com/image.png", 100); + Sticker sticker = + new Sticker( + "sticker-fail", + "Test Sticker", + "Test Description", + "https://example.com/image.png", + 100); StickerAssignment assignment = new StickerAssignment("user-fail", sticker, "For testing"); - + // Simulate emitter failure - doThrow(new RuntimeException("Test failure")).when(stickerAssignedEmitter).send(org.mockito.ArgumentMatchers.any(StickerAssignedToUserEvent.class)); - + doThrow(new RuntimeException("Test failure")) + .when(stickerAssignedEmitter) + .send(org.mockito.ArgumentMatchers.any(StickerAssignedToUserEvent.class)); + // Execute the method - should catch the exception and log it publisher.publishStickerAssigned(assignment); - + // The claimed emitter should still have been called - verify(stickerClaimedEmitter, times(1)).send(org.mockito.ArgumentMatchers.any(StickerClaimedEvent.class)); + verify(stickerClaimedEmitter, times(1)) + .send(org.mockito.ArgumentMatchers.any(StickerClaimedEvent.class)); } -} \ No newline at end of file +} diff --git a/sticker-award/src/test/java/com/datadoghq/stickerlandia/stickeraward/sticker/StickerImageServiceTest.java b/sticker-award/src/test/java/com/datadoghq/stickerlandia/stickeraward/sticker/StickerImageServiceTest.java index 11794945..ea930981 100644 --- a/sticker-award/src/test/java/com/datadoghq/stickerlandia/stickeraward/sticker/StickerImageServiceTest.java +++ b/sticker-award/src/test/java/com/datadoghq/stickerlandia/stickeraward/sticker/StickerImageServiceTest.java @@ -1,24 +1,27 @@ package com.datadoghq.stickerlandia.stickeraward.sticker; +import static org.junit.jupiter.api.Assertions.assertArrayEquals; +import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; + import io.quarkus.test.junit.QuarkusTest; import jakarta.inject.Inject; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.TestMethodOrder; +import java.io.ByteArrayInputStream; +import java.io.InputStream; import org.junit.jupiter.api.MethodOrderer; import org.junit.jupiter.api.Order; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.TestMethodOrder; import software.amazon.awssdk.services.s3.model.NoSuchKeyException; -import java.io.ByteArrayInputStream; -import java.io.InputStream; - -import static org.junit.jupiter.api.Assertions.*; - @QuarkusTest @TestMethodOrder(MethodOrderer.OrderAnnotation.class) class StickerImageServiceTest { - @Inject - StickerImageService stickerImageService; + @Inject StickerImageService stickerImageService; private static String uploadedImageKey; @@ -27,12 +30,13 @@ class StickerImageServiceTest { void testUploadImage() { byte[] testImageData = "fake-png-image-data".getBytes(); InputStream imageStream = new ByteArrayInputStream(testImageData); - - String imageKey = stickerImageService.uploadImage(imageStream, "image/png", testImageData.length); - + + String imageKey = + stickerImageService.uploadImage(imageStream, "image/png", testImageData.length); + assertNotNull(imageKey); assertTrue(imageKey.startsWith("stickers/")); - + uploadedImageKey = imageKey; } @@ -40,11 +44,11 @@ void testUploadImage() { @Order(2) void testGetImage() { assertNotNull(uploadedImageKey, "Must run upload test first"); - + InputStream retrievedImage = stickerImageService.getImage(uploadedImageKey); - + assertNotNull(retrievedImage); - + byte[] retrievedData = assertDoesNotThrow(() -> retrievedImage.readAllBytes()); String retrievedContent = new String(retrievedData); assertEquals("fake-png-image-data", retrievedContent); @@ -54,9 +58,9 @@ void testGetImage() { @Order(3) void testGetImageUrl() { assertNotNull(uploadedImageKey, "Must run upload test first"); - + String imageUrl = stickerImageService.getImageUrl(uploadedImageKey); - + assertNotNull(imageUrl); assertTrue(imageUrl.contains(uploadedImageKey)); } @@ -65,35 +69,34 @@ void testGetImageUrl() { @Order(4) void testDeleteImage() { assertNotNull(uploadedImageKey, "Must run upload test first"); - + assertDoesNotThrow(() -> stickerImageService.deleteImage(uploadedImageKey)); - - assertThrows(NoSuchKeyException.class, () -> - stickerImageService.getImage(uploadedImageKey)); + + assertThrows( + NoSuchKeyException.class, () -> stickerImageService.getImage(uploadedImageKey)); } @Test void testGetNonExistentImage() { String nonExistentKey = "stickers/non-existent-image"; - - assertThrows(NoSuchKeyException.class, () -> - stickerImageService.getImage(nonExistentKey)); + + assertThrows(NoSuchKeyException.class, () -> stickerImageService.getImage(nonExistentKey)); } @Test void testUploadEmptyImage() { byte[] emptyData = new byte[0]; InputStream emptyStream = new ByteArrayInputStream(emptyData); - + String imageKey = stickerImageService.uploadImage(emptyStream, "image/png", 0); - + assertNotNull(imageKey); assertTrue(imageKey.startsWith("stickers/")); - + InputStream retrievedImage = stickerImageService.getImage(imageKey); byte[] retrievedData = assertDoesNotThrow(() -> retrievedImage.readAllBytes()); assertEquals(0, retrievedData.length); - + stickerImageService.deleteImage(imageKey); } @@ -103,19 +106,20 @@ void testUploadLargeImage() { for (int i = 0; i < largeImageData.length; i++) { largeImageData[i] = (byte) (i % 256); } - + InputStream imageStream = new ByteArrayInputStream(largeImageData); - - String imageKey = stickerImageService.uploadImage(imageStream, "image/png", largeImageData.length); - + + String imageKey = + stickerImageService.uploadImage(imageStream, "image/png", largeImageData.length); + assertNotNull(imageKey); assertTrue(imageKey.startsWith("stickers/")); - + InputStream retrievedImage = stickerImageService.getImage(imageKey); byte[] retrievedData = assertDoesNotThrow(() -> retrievedImage.readAllBytes()); assertEquals(largeImageData.length, retrievedData.length); assertArrayEquals(largeImageData, retrievedData); - + stickerImageService.deleteImage(imageKey); } -} \ No newline at end of file +} From a266808538bf4a13e35a00aac1c79bf01c3d2414 Mon Sep 17 00:00:00 2001 From: Scott Gerring Date: Thu, 5 Jun 2025 14:22:04 +0200 Subject: [PATCH 4/5] chore(sticker-award): introduce error prone --- sticker-award/checkstyle.xml | 2 +- sticker-award/pom.xml | 36 +++++++++++++++++-- ...grationTest.java => HealthResourceIT.java} | 2 +- ...nTest.java => StickerAwardResourceIT.java} | 2 +- ....java => StickerAwardResourceKafkaIT.java} | 2 +- 5 files changed, 37 insertions(+), 7 deletions(-) rename sticker-award/src/test/java/com/datadoghq/stickerlandia/stickeraward/{HealthResourceIntegrationTest.java => HealthResourceIT.java} (75%) rename sticker-award/src/test/java/com/datadoghq/stickerlandia/stickeraward/{StickerAwardResourceIntegrationTest.java => StickerAwardResourceIT.java} (94%) rename sticker-award/src/test/java/com/datadoghq/stickerlandia/stickeraward/{StickerAwardResourceKafkaIntegrationTest.java => StickerAwardResourceKafkaIT.java} (98%) diff --git a/sticker-award/checkstyle.xml b/sticker-award/checkstyle.xml index 360bdfcf..a3e18f79 100644 --- a/sticker-award/checkstyle.xml +++ b/sticker-award/checkstyle.xml @@ -247,7 +247,7 @@ - + Date: Thu, 5 Jun 2025 14:28:14 +0200 Subject: [PATCH 5/5] chore(sticker-award) address error prone issues, fix IT regression --- sticker-award/README.md | 17 +++++++++++++++++ sticker-award/pom.xml | 2 +- .../src/main/docker/Dockerfile.jvmlocal | 1 + .../award/StickerAwardResource.java | 4 ---- .../stickeraward/sticker/StickerResource.java | 4 ++-- .../sticker/dto/StickerImageUploadResponse.java | 8 ++++---- .../stickeraward/HealthResourceTest.java | 1 + .../sticker/StickerImageServiceTest.java | 5 +++-- 8 files changed, 29 insertions(+), 13 deletions(-) diff --git a/sticker-award/README.md b/sticker-award/README.md index 511041a3..d7f00433 100644 --- a/sticker-award/README.md +++ b/sticker-award/README.md @@ -88,6 +88,23 @@ Run integration tests: ## Code Quality +This project enforces high code quality through multiple static analysis tools: + +### Error Prone + +This project uses Error Prone to catch common Java programming mistakes at compile time. + +**Error Prone Integration:** +- Runs automatically during compilation (`./mvnw compile`) +- Catches bugs like incorrect Date usage, unused variables, and charset issues +- Configured in the Maven compiler plugin +- Uses Error Prone version 2.38.0 + +**Common Error Prone checks include:** +- `JavaUtilDate` - Flags usage of legacy `java.util.Date` API +- `UnusedVariable` - Detects unused fields and variables +- `DefaultCharset` - Warns about implicit charset usage in string operations + ### Checkstyle This project uses Checkstyle to enforce coding standards based on the Google Java Style Guide. diff --git a/sticker-award/pom.xml b/sticker-award/pom.xml index 1cd8b568..2fe9dfe3 100644 --- a/sticker-award/pom.xml +++ b/sticker-award/pom.xml @@ -3,7 +3,7 @@ 4.0.0 org.acme sticker-award - 1.0.1-SNAPSHOT + 1.0.2-SNAPSHOT 3.14.0 diff --git a/sticker-award/src/main/docker/Dockerfile.jvmlocal b/sticker-award/src/main/docker/Dockerfile.jvmlocal index 52553b95..84191c55 100644 --- a/sticker-award/src/main/docker/Dockerfile.jvmlocal +++ b/sticker-award/src/main/docker/Dockerfile.jvmlocal @@ -22,6 +22,7 @@ WORKDIR /build # The context needs to be the root of the sticker-award directory, not the Dockerfile location COPY pom.xml . +COPY checkstyle.xml . COPY src ./src # Make sure mvnw is executable and then build diff --git a/sticker-award/src/main/java/com/datadoghq/stickerlandia/stickeraward/award/StickerAwardResource.java b/sticker-award/src/main/java/com/datadoghq/stickerlandia/stickeraward/award/StickerAwardResource.java index cef1bd57..ce6e78a9 100644 --- a/sticker-award/src/main/java/com/datadoghq/stickerlandia/stickeraward/award/StickerAwardResource.java +++ b/sticker-award/src/main/java/com/datadoghq/stickerlandia/stickeraward/award/StickerAwardResource.java @@ -18,15 +18,11 @@ import jakarta.ws.rs.core.MediaType; import jakarta.ws.rs.core.Response; import org.eclipse.microprofile.openapi.annotations.Operation; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; /** REST resource for managing sticker awards and assignments. */ @Path("/api/award/v1/users") public class StickerAwardResource { - private static final Logger log = LoggerFactory.getLogger(StickerAwardResource.class); - @Inject StickerAwardEventPublisher eventPublisher; @Inject StickerAwardRepository stickerAwardRepository; diff --git a/sticker-award/src/main/java/com/datadoghq/stickerlandia/stickeraward/sticker/StickerResource.java b/sticker-award/src/main/java/com/datadoghq/stickerlandia/stickeraward/sticker/StickerResource.java index a2a1e0b8..9ce447bb 100644 --- a/sticker-award/src/main/java/com/datadoghq/stickerlandia/stickeraward/sticker/StickerResource.java +++ b/sticker-award/src/main/java/com/datadoghq/stickerlandia/stickeraward/sticker/StickerResource.java @@ -21,7 +21,7 @@ import jakarta.ws.rs.QueryParam; import jakarta.ws.rs.core.Response; import java.io.InputStream; -import java.util.Date; +import java.time.Instant; import org.eclipse.microprofile.openapi.annotations.Operation; /** REST resource for managing stickers. */ @@ -191,7 +191,7 @@ public Response uploadStickerImage( StickerImageUploadResponse response = new StickerImageUploadResponse(); response.setStickerId(stickerId); response.setImageUrl(imageUrl); - response.setUploadedAt(new Date()); + response.setUploadedAt(Instant.now()); return Response.ok(response).build(); } catch (Exception e) { diff --git a/sticker-award/src/main/java/com/datadoghq/stickerlandia/stickeraward/sticker/dto/StickerImageUploadResponse.java b/sticker-award/src/main/java/com/datadoghq/stickerlandia/stickeraward/sticker/dto/StickerImageUploadResponse.java index f8c1aeeb..0067e002 100644 --- a/sticker-award/src/main/java/com/datadoghq/stickerlandia/stickeraward/sticker/dto/StickerImageUploadResponse.java +++ b/sticker-award/src/main/java/com/datadoghq/stickerlandia/stickeraward/sticker/dto/StickerImageUploadResponse.java @@ -4,7 +4,7 @@ import com.fasterxml.jackson.annotation.JsonInclude; import com.fasterxml.jackson.annotation.JsonProperty; import com.fasterxml.jackson.annotation.JsonPropertyOrder; -import java.util.Date; +import java.time.Instant; /** Response DTO for sticker image upload operations. */ @JsonInclude(JsonInclude.Include.NON_NULL) @@ -22,7 +22,7 @@ public class StickerImageUploadResponse { pattern = "yyyy-MM-dd'T'HH:mm:ss'Z'", timezone = "UTC") @JsonProperty("uploadedAt") - private Date uploadedAt; + private Instant uploadedAt; @JsonProperty("stickerId") public String getStickerId() { @@ -45,12 +45,12 @@ public void setImageUrl(String imageUrl) { } @JsonProperty("uploadedAt") - public Date getUploadedAt() { + public Instant getUploadedAt() { return uploadedAt; } @JsonProperty("uploadedAt") - public void setUploadedAt(Date uploadedAt) { + public void setUploadedAt(Instant uploadedAt) { this.uploadedAt = uploadedAt; } } diff --git a/sticker-award/src/test/java/com/datadoghq/stickerlandia/stickeraward/HealthResourceTest.java b/sticker-award/src/test/java/com/datadoghq/stickerlandia/stickeraward/HealthResourceTest.java index 3af3e86b..22783356 100644 --- a/sticker-award/src/test/java/com/datadoghq/stickerlandia/stickeraward/HealthResourceTest.java +++ b/sticker-award/src/test/java/com/datadoghq/stickerlandia/stickeraward/HealthResourceTest.java @@ -7,6 +7,7 @@ import io.restassured.http.ContentType; import org.junit.jupiter.api.Test; +/** Test for the health resource. */ @QuarkusTest public class HealthResourceTest { diff --git a/sticker-award/src/test/java/com/datadoghq/stickerlandia/stickeraward/sticker/StickerImageServiceTest.java b/sticker-award/src/test/java/com/datadoghq/stickerlandia/stickeraward/sticker/StickerImageServiceTest.java index ea930981..0fde2ea9 100644 --- a/sticker-award/src/test/java/com/datadoghq/stickerlandia/stickeraward/sticker/StickerImageServiceTest.java +++ b/sticker-award/src/test/java/com/datadoghq/stickerlandia/stickeraward/sticker/StickerImageServiceTest.java @@ -1,5 +1,6 @@ package com.datadoghq.stickerlandia.stickeraward.sticker; +import static java.nio.charset.StandardCharsets.UTF_8; import static org.junit.jupiter.api.Assertions.assertArrayEquals; import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; import static org.junit.jupiter.api.Assertions.assertEquals; @@ -28,7 +29,7 @@ class StickerImageServiceTest { @Test @Order(1) void testUploadImage() { - byte[] testImageData = "fake-png-image-data".getBytes(); + byte[] testImageData = "fake-png-image-data".getBytes(UTF_8); InputStream imageStream = new ByteArrayInputStream(testImageData); String imageKey = @@ -50,7 +51,7 @@ void testGetImage() { assertNotNull(retrievedImage); byte[] retrievedData = assertDoesNotThrow(() -> retrievedImage.readAllBytes()); - String retrievedContent = new String(retrievedData); + String retrievedContent = new String(retrievedData, UTF_8); assertEquals("fake-png-image-data", retrievedContent); }