From ea47cccdb158f3b598168132a523cc0917537b5e Mon Sep 17 00:00:00 2001 From: Marcus Fihlon Date: Wed, 29 Apr 2026 11:01:21 +0200 Subject: [PATCH] refactor(komoot): align DTOs with Lombok class pattern Signed-off-by: Marcus Fihlon --- .../controller/KomootImportController.java | 4 +- .../model/dto/KomootActivitiesResponse.java | 19 +++++-- .../dto/KomootActivityImportRequest.java | 32 ++++++----- .../model/dto/KomootActivitySummaryDTO.java | 39 ++++++++------ .../dto/KomootImportExecutionResponse.java | 21 +++++--- .../fitpub/model/dto/KomootImportRequest.java | 53 ++++++++++++++----- .../fitpub/service/KomootImportService.java | 36 ++++++------- .../service/KomootImportServiceTest.java | 44 +++++++-------- 8 files changed, 155 insertions(+), 93 deletions(-) diff --git a/src/main/java/net/javahippie/fitpub/controller/KomootImportController.java b/src/main/java/net/javahippie/fitpub/controller/KomootImportController.java index 99c1f9e..146ebe3 100644 --- a/src/main/java/net/javahippie/fitpub/controller/KomootImportController.java +++ b/src/main/java/net/javahippie/fitpub/controller/KomootImportController.java @@ -43,7 +43,7 @@ public class KomootImportController { .getId(); log.info("User {} requested Komoot activity preview for Komoot ID {}", - authentication.getName(), request.userId()); + authentication.getName(), request.getUserId()); KomootActivitiesResponse response = komootImportService.fetchCompletedActivities(request, fitPubUserId); return ResponseEntity.ok(response); } @@ -58,7 +58,7 @@ public class KomootImportController { .getId(); log.info("User {} requested Komoot import for activity {}", - authentication.getName(), request.activityId()); + authentication.getName(), request.getActivityId()); KomootImportExecutionResponse response = komootImportService.importActivity( request, diff --git a/src/main/java/net/javahippie/fitpub/model/dto/KomootActivitiesResponse.java b/src/main/java/net/javahippie/fitpub/model/dto/KomootActivitiesResponse.java index c9223d7..296acb3 100644 --- a/src/main/java/net/javahippie/fitpub/model/dto/KomootActivitiesResponse.java +++ b/src/main/java/net/javahippie/fitpub/model/dto/KomootActivitiesResponse.java @@ -1,13 +1,22 @@ package net.javahippie.fitpub.model.dto; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + import java.util.List; /** * Response payload for the Komoot import preview. */ -public record KomootActivitiesResponse( - String userId, - int totalCount, - List activities -) { +@Data +@Builder +@NoArgsConstructor +@AllArgsConstructor +public class KomootActivitiesResponse { + + private String userId; + private int totalCount; + private List activities; } diff --git a/src/main/java/net/javahippie/fitpub/model/dto/KomootActivityImportRequest.java b/src/main/java/net/javahippie/fitpub/model/dto/KomootActivityImportRequest.java index 1c67621..6d9b7eb 100644 --- a/src/main/java/net/javahippie/fitpub/model/dto/KomootActivityImportRequest.java +++ b/src/main/java/net/javahippie/fitpub/model/dto/KomootActivityImportRequest.java @@ -4,25 +4,33 @@ import jakarta.validation.constraints.Email; import jakarta.validation.constraints.NotBlank; import jakarta.validation.constraints.NotNull; import jakarta.validation.constraints.Pattern; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; /** * Request payload for importing one specific Komoot activity. * *

The password is only used for the current request and is never persisted.

*/ -public record KomootActivityImportRequest( - @NotBlank - @Email - String email, +@Data +@Builder +@NoArgsConstructor +@AllArgsConstructor +public class KomootActivityImportRequest { - @NotBlank - String password, + @NotBlank + @Email + private String email; - @NotBlank - @Pattern(regexp = "\\d+", message = "Komoot user ID must contain digits only") - String userId, + @NotBlank + private String password; - @NotNull - Long activityId -) { + @NotBlank + @Pattern(regexp = "\\d+", message = "Komoot user ID must contain digits only") + private String userId; + + @NotNull + private Long activityId; } diff --git a/src/main/java/net/javahippie/fitpub/model/dto/KomootActivitySummaryDTO.java b/src/main/java/net/javahippie/fitpub/model/dto/KomootActivitySummaryDTO.java index a75ae5b..3bdf613 100644 --- a/src/main/java/net/javahippie/fitpub/model/dto/KomootActivitySummaryDTO.java +++ b/src/main/java/net/javahippie/fitpub/model/dto/KomootActivitySummaryDTO.java @@ -1,24 +1,33 @@ package net.javahippie.fitpub.model.dto; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + import java.time.OffsetDateTime; import java.util.UUID; /** * Reduced activity representation returned by the Komoot import preview. */ -public record KomootActivitySummaryDTO( - long id, - String name, - String sport, - String mappedActivityType, - String status, - String type, - OffsetDateTime date, - Double distanceMeters, - Integer durationSeconds, - Integer timeInMotionSeconds, - Double elevationUp, - boolean imported, - UUID fitPubActivityId -) { +@Data +@Builder +@NoArgsConstructor +@AllArgsConstructor +public class KomootActivitySummaryDTO { + + private long id; + private String name; + private String sport; + private String mappedActivityType; + private String status; + private String type; + private OffsetDateTime date; + private Double distanceMeters; + private Integer durationSeconds; + private Integer timeInMotionSeconds; + private Double elevationUp; + private boolean imported; + private UUID fitPubActivityId; } diff --git a/src/main/java/net/javahippie/fitpub/model/dto/KomootImportExecutionResponse.java b/src/main/java/net/javahippie/fitpub/model/dto/KomootImportExecutionResponse.java index dc80fe3..abb31e8 100644 --- a/src/main/java/net/javahippie/fitpub/model/dto/KomootImportExecutionResponse.java +++ b/src/main/java/net/javahippie/fitpub/model/dto/KomootImportExecutionResponse.java @@ -1,14 +1,23 @@ package net.javahippie.fitpub.model.dto; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + import java.util.UUID; /** * Response for importing exactly one Komoot activity into FitPub. */ -public record KomootImportExecutionResponse( - UUID importedActivityId, - Long importedKomootActivityId, - String status, - String message -) { +@Data +@Builder +@NoArgsConstructor +@AllArgsConstructor +public class KomootImportExecutionResponse { + + private UUID importedActivityId; + private Long importedKomootActivityId; + private String status; + private String message; } diff --git a/src/main/java/net/javahippie/fitpub/model/dto/KomootImportRequest.java b/src/main/java/net/javahippie/fitpub/model/dto/KomootImportRequest.java index 345f1b9..5c58345 100644 --- a/src/main/java/net/javahippie/fitpub/model/dto/KomootImportRequest.java +++ b/src/main/java/net/javahippie/fitpub/model/dto/KomootImportRequest.java @@ -1,8 +1,12 @@ package net.javahippie.fitpub.model.dto; +import jakarta.validation.constraints.AssertTrue; import jakarta.validation.constraints.Email; import jakarta.validation.constraints.NotBlank; import jakarta.validation.constraints.Pattern; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; import java.time.LocalDate; @@ -11,23 +15,46 @@ import java.time.LocalDate; * *

The password is only used for the current request and is never persisted.

*/ -public record KomootImportRequest( - @NotBlank - @Email - String email, +@Data +@Builder +@NoArgsConstructor +public class KomootImportRequest { - @NotBlank - String password, + @NotBlank + @Email + private String email; - @NotBlank - @Pattern(regexp = "\\d+", message = "Komoot user ID must contain digits only") - String userId, + @NotBlank + private String password; - LocalDate startDate, + @NotBlank + @Pattern(regexp = "\\d+", message = "Komoot user ID must contain digits only") + private String userId; - LocalDate endDate -) { - public KomootImportRequest { + private LocalDate startDate; + + private LocalDate endDate; + + public KomootImportRequest(String email, String password, String userId, LocalDate startDate, LocalDate endDate) { + this.email = email; + this.password = password; + this.userId = userId; + this.startDate = startDate; + this.endDate = endDate; + validateDateRange(); + } + + @AssertTrue(message = "Start date and end date must either both be set or both be empty, and start date must be before or equal to end date.") + public boolean isDateRangeConsistent() { + try { + validateDateRange(); + return true; + } catch (IllegalArgumentException e) { + return false; + } + } + + private void validateDateRange() { boolean onlyOneDateProvided = (startDate == null) != (endDate == null); if (onlyOneDateProvided) { throw new IllegalArgumentException("Start date and end date must either both be set or both be empty."); diff --git a/src/main/java/net/javahippie/fitpub/service/KomootImportService.java b/src/main/java/net/javahippie/fitpub/service/KomootImportService.java index 7f92073..645838f 100644 --- a/src/main/java/net/javahippie/fitpub/service/KomootImportService.java +++ b/src/main/java/net/javahippie/fitpub/service/KomootImportService.java @@ -86,7 +86,7 @@ public class KomootImportService { } URI nextUri = buildInitialUri(request); - HttpEntity httpEntity = new HttpEntity<>(buildHeaders(request.email(), request.password())); + HttpEntity httpEntity = new HttpEntity<>(buildHeaders(request.getEmail(), request.getPassword())); try { while (nextUri != null) { @@ -113,8 +113,8 @@ public class KomootImportService { throw new IllegalStateException("Failed to parse Komoot activity list.", e); } - log.info("Fetched {} completed Komoot activities for user ID {}", activities.size(), request.userId()); - return new KomootActivitiesResponse(request.userId(), activities.size(), activities); + log.info("Fetched {} completed Komoot activities for user ID {}", activities.size(), request.getUserId()); + return new KomootActivitiesResponse(request.getUserId(), activities.size(), activities); } void pauseBeforeNextPageRequest() { @@ -122,29 +122,29 @@ public class KomootImportService { } public KomootImportExecutionResponse importActivity(KomootActivityImportRequest request, UUID fitPubUserId) { - Activity existingActivity = activityRepository.findByUserIdAndKomootActivityId(fitPubUserId, request.activityId()).orElse(null); + Activity existingActivity = activityRepository.findByUserIdAndKomootActivityId(fitPubUserId, request.getActivityId()).orElse(null); if (existingActivity != null) { return new KomootImportExecutionResponse( existingActivity.getId(), - request.activityId(), + request.getActivityId(), "SKIPPED_ALREADY_IMPORTED", - "Komoot activity " + request.activityId() + " was already imported." + "Komoot activity " + request.getActivityId() + " was already imported." ); } - JsonNode details = fetchActivityDetails(request.email(), request.password(), request.activityId()); + JsonNode details = fetchActivityDetails(request.getEmail(), request.getPassword(), request.getActivityId()); pauseBetweenDetailAndGpxRequest(); - byte[] gpxData = fetchActivityGpx(request.email(), request.password(), request.activityId()); + byte[] gpxData = fetchActivityGpx(request.getEmail(), request.getPassword(), request.getActivityId()); ByteArrayMultipartFile gpxFile = new ByteArrayMultipartFile( "file", - "komoot-" + request.activityId() + ".gpx", + "komoot-" + request.getActivityId() + ".gpx", "application/gpx+xml", gpxData ); Activity.Visibility mappedVisibility = mapVisibility(nullableText(details, "status")); - String mappedTitle = firstNonBlank(nullableText(details, "name"), null, "Komoot Activity " + request.activityId()); + String mappedTitle = firstNonBlank(nullableText(details, "name"), null, "Komoot Activity " + request.getActivityId()); String mappedDescription = nullableText(details, "description"); Activity.ActivityType mappedActivityType = mapKomootSportToActivityType(nullableText(details, "sport")); @@ -156,7 +156,7 @@ public class KomootImportService { mappedVisibility ); - importedActivity.setKomootActivityId(request.activityId()); + importedActivity.setKomootActivityId(request.getActivityId()); importedActivity.setTitle(mappedTitle); importedActivity.setDescription(mappedDescription); importedActivity.setVisibility(mappedVisibility); @@ -167,7 +167,7 @@ public class KomootImportService { log.info( "Imported Komoot activity {} into FitPub activity {} with visibility {} and type {}", - request.activityId(), + request.getActivityId(), importedActivity.getId(), importedActivity.getVisibility(), importedActivity.getActivityType() @@ -177,9 +177,9 @@ public class KomootImportService { return new KomootImportExecutionResponse( importedActivity.getId(), - request.activityId(), + request.getActivityId(), "IMPORTED", - "Imported Komoot activity " + request.activityId() + " into FitPub activity " + importedActivity.getId() + "Imported Komoot activity " + request.getActivityId() + " into FitPub activity " + importedActivity.getId() ); } @@ -193,15 +193,15 @@ public class KomootImportService { private URI buildInitialUri(KomootImportRequest request) { String normalizedBaseUrl = komootBaseUrl.endsWith("/") ? komootBaseUrl.substring(0, komootBaseUrl.length() - 1) : komootBaseUrl; - UriComponentsBuilder builder = UriComponentsBuilder.fromUriString(normalizedBaseUrl + "/api/v007/users/" + request.userId() + "/tours/") + UriComponentsBuilder builder = UriComponentsBuilder.fromUriString(normalizedBaseUrl + "/api/v007/users/" + request.getUserId() + "/tours/") .queryParam("type", "tour_recorded") .queryParam("sort_field", "date") .queryParam("sort_direction", "desc") .queryParam("limit", PAGE_SIZE); - if (request.startDate() != null && request.endDate() != null) { - builder.queryParam("start_date", formatKomootStartDate(request.startDate())) - .queryParam("end_date", formatKomootEndDate(request.endDate())); + if (request.getStartDate() != null && request.getEndDate() != null) { + builder.queryParam("start_date", formatKomootStartDate(request.getStartDate())) + .queryParam("end_date", formatKomootEndDate(request.getEndDate())); } else { builder.queryParam("status", "private") .queryParam("name", "") diff --git a/src/test/java/net/javahippie/fitpub/service/KomootImportServiceTest.java b/src/test/java/net/javahippie/fitpub/service/KomootImportServiceTest.java index 37d43d7..0a0803d 100644 --- a/src/test/java/net/javahippie/fitpub/service/KomootImportServiceTest.java +++ b/src/test/java/net/javahippie/fitpub/service/KomootImportServiceTest.java @@ -154,15 +154,15 @@ class KomootImportServiceTest { new KomootImportRequest("user@example.com", "secret", "123456", null, null), userId); - assertThat(response.totalCount()).isEqualTo(2); - assertThat(response.activities()).hasSize(2); - assertThat(response.activities().get(0).id()).isEqualTo(1001L); - assertThat(response.activities().get(0).imported()).isFalse(); - assertThat(response.activities().get(0).fitPubActivityId()).isNull(); - assertThat(response.activities().get(0).timeInMotionSeconds()).isEqualTo(7800); - assertThat(response.activities().get(1).name()).isEqualTo("Lunch Walk"); - assertThat(response.activities().get(1).imported()).isTrue(); - assertThat(response.activities().get(1).fitPubActivityId()).isEqualTo(existingActivityId); + assertThat(response.getTotalCount()).isEqualTo(2); + assertThat(response.getActivities()).hasSize(2); + assertThat(response.getActivities().get(0).getId()).isEqualTo(1001L); + assertThat(response.getActivities().get(0).isImported()).isFalse(); + assertThat(response.getActivities().get(0).getFitPubActivityId()).isNull(); + assertThat(response.getActivities().get(0).getTimeInMotionSeconds()).isEqualTo(7800); + assertThat(response.getActivities().get(1).getName()).isEqualTo("Lunch Walk"); + assertThat(response.getActivities().get(1).isImported()).isTrue(); + assertThat(response.getActivities().get(1).getFitPubActivityId()).isEqualTo(existingActivityId); verify(throttledService).pauseBeforeNextPageRequest(); server.verify(); @@ -219,11 +219,11 @@ class KomootImportServiceTest { ), userId); - assertThat(response.totalCount()).isEqualTo(2); - assertThat(response.activities()).extracting("id").containsExactly(1002L, 1003L); - assertThat(response.activities().get(0).imported()).isFalse(); - assertThat(response.activities().get(1).imported()).isTrue(); - assertThat(response.activities().get(1).fitPubActivityId()).isEqualTo(existingActivityId); + assertThat(response.getTotalCount()).isEqualTo(2); + assertThat(response.getActivities()).extracting("id").containsExactly(1002L, 1003L); + assertThat(response.getActivities().get(0).isImported()).isFalse(); + assertThat(response.getActivities().get(1).isImported()).isTrue(); + assertThat(response.getActivities().get(1).getFitPubActivityId()).isEqualTo(existingActivityId); server.verify(); } @@ -296,9 +296,9 @@ class KomootImportServiceTest { userId ); - assertThat(response.importedActivityId()).isEqualTo(importedActivityId); - assertThat(response.importedKomootActivityId()).isEqualTo(2880957035L); - assertThat(response.status()).isEqualTo("IMPORTED"); + assertThat(response.getImportedActivityId()).isEqualTo(importedActivityId); + assertThat(response.getImportedKomootActivityId()).isEqualTo(2880957035L); + assertThat(response.getStatus()).isEqualTo("IMPORTED"); assertThat(importedActivity.getKomootActivityId()).isEqualTo(2880957035L); assertThat(importedActivity.getTitle()).isEqualTo("Latest Ride"); assertThat(importedActivity.getDescription()).isEqualTo("Imported from Komoot"); @@ -326,9 +326,9 @@ class KomootImportServiceTest { userId ); - assertThat(response.importedActivityId()).isEqualTo(existingActivityId); - assertThat(response.importedKomootActivityId()).isEqualTo(3002L); - assertThat(response.status()).isEqualTo("SKIPPED_ALREADY_IMPORTED"); + assertThat(response.getImportedActivityId()).isEqualTo(existingActivityId); + assertThat(response.getImportedKomootActivityId()).isEqualTo(3002L); + assertThat(response.getStatus()).isEqualTo("SKIPPED_ALREADY_IMPORTED"); } @Test @@ -386,8 +386,8 @@ class KomootImportServiceTest { userId ); - assertThat(response.importedActivityId()).isEqualTo(importedActivityId); - assertThat(response.status()).isEqualTo("IMPORTED"); + assertThat(response.getImportedActivityId()).isEqualTo(importedActivityId); + assertThat(response.getStatus()).isEqualTo("IMPORTED"); assertThat(importedActivity.getActivityType()).isEqualTo(Activity.ActivityType.OTHER); verify(throttledService).pauseBetweenDetailAndGpxRequest();