From 758f1fd8d32ea529ec85e1c0db092ccc05870f3b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=9D=B4=EC=9E=AC=EC=9B=90?= Date: Wed, 25 Sep 2024 21:08:08 +0900 Subject: [PATCH] =?UTF-8?q?Feat:=20running=20record=20id=EB=A1=9C=20?= =?UTF-8?q?=EB=9F=AC=EB=8B=9D=20=EA=B8=B0=EB=A1=9D=20=EC=A1=B0=ED=9A=8C=20?= =?UTF-8?q?api=20=EC=B6=94=EA=B0=80=20(#226)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../running/RunningRecordService.java | 20 +++-- .../v1/running/RunningRecordController.java | 12 +-- .../response/RunningRecordQueryResponse.java | 83 +++++++++++++++++++ .../running/RunningRecordServiceTest.java | 59 +++++++++++++ 4 files changed, 160 insertions(+), 14 deletions(-) create mode 100644 src/main/java/com/dnd/runus/presentation/v1/running/dto/response/RunningRecordQueryResponse.java diff --git a/src/main/java/com/dnd/runus/application/running/RunningRecordService.java b/src/main/java/com/dnd/runus/application/running/RunningRecordService.java index 317926f5..fa79d8ef 100644 --- a/src/main/java/com/dnd/runus/application/running/RunningRecordService.java +++ b/src/main/java/com/dnd/runus/application/running/RunningRecordService.java @@ -26,20 +26,13 @@ import com.dnd.runus.global.exception.NotFoundException; import com.dnd.runus.presentation.v1.running.dto.request.RunningRecordRequest; import com.dnd.runus.presentation.v1.running.dto.request.RunningRecordWeeklySummaryType; -import com.dnd.runus.presentation.v1.running.dto.response.RunningRecordAddResultResponse; -import com.dnd.runus.presentation.v1.running.dto.response.RunningRecordMonthlySummaryResponse; -import com.dnd.runus.presentation.v1.running.dto.response.RunningRecordSummaryResponse; -import com.dnd.runus.presentation.v1.running.dto.response.RunningRecordWeeklySummaryResponse; +import com.dnd.runus.presentation.v1.running.dto.response.*; import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; -import java.time.DayOfWeek; -import java.time.LocalDate; -import java.time.OffsetDateTime; -import java.time.ZoneId; -import java.time.ZoneOffset; import java.time.temporal.ChronoUnit; +import java.time.*; import java.util.List; import static com.dnd.runus.global.constant.MetricsConversionFactor.METERS_IN_A_KILOMETER; @@ -83,6 +76,15 @@ public RunningRecordService( this.defaultZoneOffset = defaultZoneOffset; } + @Transactional(readOnly = true) + public RunningRecordQueryResponse getRunningRecord(long memberId, long runningRecordId) { + RunningRecord record = runningRecordRepository + .findById(runningRecordId) + .filter(r -> r.member().memberId() == memberId) + .orElseThrow(() -> new NotFoundException(RunningRecord.class, runningRecordId)); + return RunningRecordQueryResponse.from(record); + } + @Transactional(readOnly = true) public List getRunningRecordDates(long memberId, int year, int month) { OffsetDateTime from = LocalDate.of(year, month, 1).atStartOfDay().atOffset(defaultZoneOffset); diff --git a/src/main/java/com/dnd/runus/presentation/v1/running/RunningRecordController.java b/src/main/java/com/dnd/runus/presentation/v1/running/RunningRecordController.java index 40b42163..356b28c6 100644 --- a/src/main/java/com/dnd/runus/presentation/v1/running/RunningRecordController.java +++ b/src/main/java/com/dnd/runus/presentation/v1/running/RunningRecordController.java @@ -6,11 +6,7 @@ import com.dnd.runus.presentation.annotation.MemberId; import com.dnd.runus.presentation.v1.running.dto.request.RunningRecordRequest; import com.dnd.runus.presentation.v1.running.dto.request.RunningRecordWeeklySummaryType; -import com.dnd.runus.presentation.v1.running.dto.response.RunningRecordAddResultResponse; -import com.dnd.runus.presentation.v1.running.dto.response.RunningRecordMonthlyDatesResponse; -import com.dnd.runus.presentation.v1.running.dto.response.RunningRecordMonthlySummaryResponse; -import com.dnd.runus.presentation.v1.running.dto.response.RunningRecordSummaryResponse; -import com.dnd.runus.presentation.v1.running.dto.response.RunningRecordWeeklySummaryResponse; +import com.dnd.runus.presentation.v1.running.dto.response.*; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.tags.Tag; import jakarta.validation.Valid; @@ -28,6 +24,12 @@ public class RunningRecordController { private final RunningRecordService runningRecordService; + @GetMapping("{runningRecordId}") + @Operation(summary = "러닝 기록 상세 조회", description = "RunngingRecord id로 러닝 상세 기록을 조회합니다.") + public RunningRecordQueryResponse getRunningRecord(@MemberId long memberId, @PathVariable long runningRecordId) { + return runningRecordService.getRunningRecord(memberId, runningRecordId); + } + @GetMapping("monthly-dates") @Operation(summary = "해당 월의 러닝 기록 조회", description = "해당 월의 러닝 기록을 조회합니다. 해당 월에 러닝 기록이 있는 날짜를 반환합니다.") public RunningRecordMonthlyDatesResponse getRunningRecordDates( diff --git a/src/main/java/com/dnd/runus/presentation/v1/running/dto/response/RunningRecordQueryResponse.java b/src/main/java/com/dnd/runus/presentation/v1/running/dto/response/RunningRecordQueryResponse.java new file mode 100644 index 00000000..35e8078b --- /dev/null +++ b/src/main/java/com/dnd/runus/presentation/v1/running/dto/response/RunningRecordQueryResponse.java @@ -0,0 +1,83 @@ +package com.dnd.runus.presentation.v1.running.dto.response; + +import com.dnd.runus.domain.challenge.achievement.ChallengeAchievement; +import com.dnd.runus.domain.goalAchievement.GoalAchievement; +import com.dnd.runus.domain.running.RunningRecord; +import com.dnd.runus.global.constant.RunningEmoji; +import com.dnd.runus.presentation.v1.running.dto.ChallengeDto; +import com.dnd.runus.presentation.v1.running.dto.GoalResultDto; +import com.dnd.runus.presentation.v1.running.dto.RunningRecordMetricsDto; +import com.dnd.runus.presentation.v1.running.dto.request.RunningAchievementMode; +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotNull; + +import java.time.LocalDateTime; + +public record RunningRecordQueryResponse( + long runningRecordId, + @Schema(description = "러닝 시작 시간") + LocalDateTime startAt, + @Schema(description = "러닝 종료 시간") + LocalDateTime endAt, + @NotNull + @Schema(description = "감정 표현, very-good: 최고, good: 좋음, soso: 보통, bad: 나쁨, very-bad: 최악") + RunningEmoji emotion, + @NotNull + @Schema(description = "달성 모드, normal: 일반(목표 설정 X), challenge: 챌린지, goal: 목표") + RunningAchievementMode achievementMode, + @Schema(description = "챌린지 정보, achievementMode가 challenge인 경우에만 값이 존재합니다.") + ChallengeDto challenge, + @Schema(description = "목표 결과 정보, achievementMode가 goal인 경우에만 값이 존재합니다.") + GoalResultDto goal, + @NotNull + RunningRecordMetricsDto runningData +) { + public static RunningRecordQueryResponse from(RunningRecord runningRecord) { + return buildResponse(runningRecord, null, null, RunningAchievementMode.NORMAL); + } + + public static RunningRecordQueryResponse of(RunningRecord runningRecord, ChallengeAchievement achievement) { + return buildResponse(runningRecord, + new ChallengeDto( + achievement.challenge().challengeId(), + achievement.challenge().name(), + achievement.description(), + achievement.challenge().imageUrl(), + achievement.isSuccess() + ), + null, + RunningAchievementMode.CHALLENGE + ); + } + + public static RunningRecordQueryResponse of(RunningRecord runningRecord, GoalAchievement achievement) { + return buildResponse(runningRecord, + null, + new GoalResultDto( + achievement.getTitle(), + achievement.getDescription(), + achievement.getIconUrl(), + achievement.isAchieved() + ), + RunningAchievementMode.GOAL + ); + } + + private static RunningRecordQueryResponse buildResponse(RunningRecord runningRecord, ChallengeDto challenge, GoalResultDto goal, RunningAchievementMode achievementMode) { + return new RunningRecordQueryResponse( + runningRecord.runningId(), + runningRecord.startAt().toLocalDateTime(), + runningRecord.endAt().toLocalDateTime(), + runningRecord.emoji(), + achievementMode, + challenge, + goal, + new RunningRecordMetricsDto( + runningRecord.averagePace(), + runningRecord.duration(), + runningRecord.distanceMeter(), + runningRecord.calorie() + ) + ); + } +} diff --git a/src/test/java/com/dnd/runus/application/running/RunningRecordServiceTest.java b/src/test/java/com/dnd/runus/application/running/RunningRecordServiceTest.java index d95cd31a..7d0580a8 100644 --- a/src/test/java/com/dnd/runus/application/running/RunningRecordServiceTest.java +++ b/src/test/java/com/dnd/runus/application/running/RunningRecordServiceTest.java @@ -20,12 +20,14 @@ import com.dnd.runus.domain.scale.ScaleRepository; import com.dnd.runus.global.constant.MemberRole; import com.dnd.runus.global.constant.RunningEmoji; +import com.dnd.runus.global.exception.NotFoundException; import com.dnd.runus.presentation.v1.running.dto.RunningRecordMetricsDto; import com.dnd.runus.presentation.v1.running.dto.request.RunningAchievementMode; import com.dnd.runus.presentation.v1.running.dto.request.RunningRecordRequest; import com.dnd.runus.presentation.v1.running.dto.request.RunningRecordWeeklySummaryType; import com.dnd.runus.presentation.v1.running.dto.response.RunningRecordAddResultResponse; import com.dnd.runus.presentation.v1.running.dto.response.RunningRecordMonthlySummaryResponse; +import com.dnd.runus.presentation.v1.running.dto.response.RunningRecordQueryResponse; import com.dnd.runus.presentation.v1.running.dto.response.RunningRecordWeeklySummaryResponse; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.DisplayName; @@ -96,6 +98,63 @@ void setUp() { defaultZoneOffset); } + @Test + @DisplayName("러닝 기록 조회 - 존재하는 러닝 기록 조회") + void getRunningRecord() { + // given + long memberId = 1; + long runningRecordId = 1; + Member member = new Member(memberId, MemberRole.USER, "nickname1", OffsetDateTime.now(), OffsetDateTime.now()); + RunningRecord runningRecord = new RunningRecord( + 1L, + member, + 10_000, + Duration.ofSeconds(10_000), + 500, + new Pace(5, 30), + OffsetDateTime.now(), + OffsetDateTime.now(), + List.of(new Coordinate(0, 0, 0), new Coordinate(0, 0, 0)), + "start location", + "end location", + RunningEmoji.VERY_GOOD); + + given(runningRecordRepository.findById(runningRecordId)).willReturn(Optional.of(runningRecord)); + + // when + RunningRecordQueryResponse result = runningRecordService.getRunningRecord(memberId, runningRecordId); + + // then + assertEquals(runningRecordId, result.runningRecordId()); + assertEquals(runningRecord.emoji(), result.emotion()); + } + + @Test + @DisplayName("러닝 기록 조회 - 존재하지 않는 러닝 기록 조회한다면 NotFoundException을 던진다.") + void getRunningRecord_not_found() { + // given + long memberId = 1; + long runningRecordId = 1; + + given(runningRecordRepository.findById(runningRecordId)).willReturn(Optional.empty()); + + // when & then + assertThrows(NotFoundException.class, () -> runningRecordService.getRunningRecord(memberId, runningRecordId)); + } + + @Test + @DisplayName("러닝 기록 조회 - 존재하지 않는 멤버의 러닝 기록 조회한다면 NotFoundException을 던진다.") + void getRunningRecord_member_not_found() { + // given + long memberId = 1; + long runningRecordId = 1; + + given(runningRecordRepository.findById(runningRecordId)).willReturn(Optional.empty()); + + // when & then + assertThrows(NotFoundException.class, () -> runningRecordService.getRunningRecord(memberId, runningRecordId)); + } + @Test @DisplayName("CHALLENGE 모드의 러닝 기록 추가 요청시, challengeId에 해당하는 챌린지가 있을 경우, 정상적으로 러닝 기록이 추가된다.") void addRunningRecord_challenge() {