diff --git a/src/main/java/com/stempo/api/domain/application/service/RecordService.java b/src/main/java/com/stempo/api/domain/application/service/RecordService.java index a3adfb9..e12f1dd 100644 --- a/src/main/java/com/stempo/api/domain/application/service/RecordService.java +++ b/src/main/java/com/stempo/api/domain/application/service/RecordService.java @@ -2,6 +2,7 @@ import com.stempo.api.domain.presentation.dto.request.RecordRequestDto; import com.stempo.api.domain.presentation.dto.response.RecordResponseDto; +import com.stempo.api.domain.presentation.dto.response.RecordStatisticsResponseDto; import java.time.LocalDate; import java.util.List; @@ -11,4 +12,6 @@ public interface RecordService { String record(RecordRequestDto requestDto); List getRecordsByDateRange(LocalDate startDate, LocalDate endDate); + + RecordStatisticsResponseDto getRecordStatistics(); } diff --git a/src/main/java/com/stempo/api/domain/application/service/RecordServiceImpl.java b/src/main/java/com/stempo/api/domain/application/service/RecordServiceImpl.java index a584e41..82b091e 100644 --- a/src/main/java/com/stempo/api/domain/application/service/RecordServiceImpl.java +++ b/src/main/java/com/stempo/api/domain/application/service/RecordServiceImpl.java @@ -4,12 +4,15 @@ import com.stempo.api.domain.domain.repository.RecordRepository; import com.stempo.api.domain.presentation.dto.request.RecordRequestDto; import com.stempo.api.domain.presentation.dto.response.RecordResponseDto; +import com.stempo.api.domain.presentation.dto.response.RecordStatisticsResponseDto; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; +import java.time.DayOfWeek; import java.time.LocalDate; import java.time.LocalDateTime; +import java.time.temporal.TemporalAdjusters; import java.util.ArrayList; import java.util.List; @@ -56,4 +59,50 @@ public List getRecordsByDateRange(LocalDate startDate, LocalD } return combinedRecords; } + + @Override + @Transactional(readOnly = true) + public RecordStatisticsResponseDto getRecordStatistics() { + String deviceTag = userService.getCurrentDeviceTag(); + + LocalDateTime todayStartDateTime = LocalDate.now().atStartOfDay(); + LocalDateTime todayEndDateTime = todayStartDateTime.plusDays(1); + LocalDateTime weekStartDateTime = LocalDate.now() + .with(TemporalAdjusters.previousOrSame(DayOfWeek.MONDAY)) + .atStartOfDay(); + + // 오늘의 훈련 횟수 계산 + int todayWalkTrainingCount = recordRepository.countByDeviceTagAndCreatedAtBetween( + deviceTag, todayStartDateTime, todayEndDateTime); + + // 이번 주 훈련 횟수 계산 (월요일부터 오늘까지) + int weeklyWalkTrainingCount = recordRepository.countByDeviceTagAndCreatedAtBetween( + deviceTag, weekStartDateTime, todayEndDateTime); + + // 연속된 훈련 일수 계산 + int consecutiveWalkTrainingDays = calculateConsecutiveTrainingDays(deviceTag); + + return RecordStatisticsResponseDto.of(todayWalkTrainingCount, weeklyWalkTrainingCount, consecutiveWalkTrainingDays); + } + + private int calculateConsecutiveTrainingDays(String deviceTag) { + List createdDates = recordRepository.findCreatedAtByDeviceTagOrderByCreatedAtDesc(deviceTag); + int consecutiveDays = 0; + LocalDate previousDate = null; + + for (LocalDateTime createdAt : createdDates) { + LocalDate recordDate = createdAt.toLocalDate(); + + // 첫 기록이거나, 이전 기록이 하루 전날이면 연속으로 카운트 + if (previousDate == null || previousDate.minusDays(1).isEqual(recordDate)) { + consecutiveDays++; + previousDate = recordDate; + } else { + // 연속된 날짜가 아니면 중단 + break; + } + } + return consecutiveDays; + } + } diff --git a/src/main/java/com/stempo/api/domain/domain/repository/RecordRepository.java b/src/main/java/com/stempo/api/domain/domain/repository/RecordRepository.java index 26bf83c..8ff07c4 100644 --- a/src/main/java/com/stempo/api/domain/domain/repository/RecordRepository.java +++ b/src/main/java/com/stempo/api/domain/domain/repository/RecordRepository.java @@ -16,4 +16,8 @@ public interface RecordRepository { Record findLatestBeforeStartDate(String deviceTag, LocalDateTime startDateTime); List findByDeviceTag(String deviceTag); + + List findCreatedAtByDeviceTagOrderByCreatedAtDesc(String deviceTag); + + int countByDeviceTagAndCreatedAtBetween(String deviceTag, LocalDateTime startDateTime, LocalDateTime endDateTime); } diff --git a/src/main/java/com/stempo/api/domain/persistence/repository/RecordJpaRepository.java b/src/main/java/com/stempo/api/domain/persistence/repository/RecordJpaRepository.java index e858b37..5a4c354 100644 --- a/src/main/java/com/stempo/api/domain/persistence/repository/RecordJpaRepository.java +++ b/src/main/java/com/stempo/api/domain/persistence/repository/RecordJpaRepository.java @@ -34,4 +34,14 @@ Optional findLatestBeforeStartDate( ); List findByDeviceTag(String deviceTag); + + @Query("SELECT r.createdAt " + + "FROM RecordEntity r " + + "WHERE r.deviceTag = :deviceTag " + + "ORDER BY r.createdAt DESC") + List findCreatedAtByDeviceTagOrderByCreatedAtDesc( + @Param("deviceTag") String deviceTag + ); + + int countByDeviceTagAndCreatedAtBetween(String deviceTag, LocalDateTime startDateTime, LocalDateTime endDateTime); } diff --git a/src/main/java/com/stempo/api/domain/persistence/repository/RecordRepositoryImpl.java b/src/main/java/com/stempo/api/domain/persistence/repository/RecordRepositoryImpl.java index 9ff7fbf..3b28d10 100644 --- a/src/main/java/com/stempo/api/domain/persistence/repository/RecordRepositoryImpl.java +++ b/src/main/java/com/stempo/api/domain/persistence/repository/RecordRepositoryImpl.java @@ -55,4 +55,14 @@ public List findByDeviceTag(String deviceTag) { .map(mapper::toDomain) .toList(); } + + @Override + public List findCreatedAtByDeviceTagOrderByCreatedAtDesc(String deviceTag) { + return repository.findCreatedAtByDeviceTagOrderByCreatedAtDesc(deviceTag); + } + + @Override + public int countByDeviceTagAndCreatedAtBetween(String deviceTag, LocalDateTime startDateTime, LocalDateTime endDateTime) { + return repository.countByDeviceTagAndCreatedAtBetween(deviceTag, startDateTime, endDateTime); + } } diff --git a/src/main/java/com/stempo/api/domain/presentation/RecordController.java b/src/main/java/com/stempo/api/domain/presentation/RecordController.java index 476098d..6ef576e 100644 --- a/src/main/java/com/stempo/api/domain/presentation/RecordController.java +++ b/src/main/java/com/stempo/api/domain/presentation/RecordController.java @@ -3,6 +3,7 @@ import com.stempo.api.domain.application.service.RecordService; import com.stempo.api.domain.presentation.dto.request.RecordRequestDto; import com.stempo.api.domain.presentation.dto.response.RecordResponseDto; +import com.stempo.api.domain.presentation.dto.response.RecordStatisticsResponseDto; import com.stempo.api.global.dto.ApiResponse; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.tags.Tag; @@ -48,4 +49,12 @@ public ApiResponse> getRecords( List records = recordService.getRecordsByDateRange(startDate, endDate); return ApiResponse.success(records); } + + @Operation(summary = "[U] 내 재활 운동 기록 통계" , description = "ROLE_USER 이상의 권한이 필요함") + @Secured({ "ROLE_USER", "ROLE_ADMIN" }) + @GetMapping("/api/v1/records/statistics") + public ApiResponse getRecordStatistics() { + RecordStatisticsResponseDto statistics = recordService.getRecordStatistics(); + return ApiResponse.success(statistics); + } } diff --git a/src/main/java/com/stempo/api/domain/presentation/dto/response/RecordStatisticsResponseDto.java b/src/main/java/com/stempo/api/domain/presentation/dto/response/RecordStatisticsResponseDto.java new file mode 100644 index 0000000..2391b85 --- /dev/null +++ b/src/main/java/com/stempo/api/domain/presentation/dto/response/RecordStatisticsResponseDto.java @@ -0,0 +1,21 @@ +package com.stempo.api.domain.presentation.dto.response; + +import lombok.Builder; +import lombok.Getter; + +@Getter +@Builder +public class RecordStatisticsResponseDto { + + private int todayWalkTrainingCount; + private int weeklyWalkTrainingCount; + private int consecutiveWalkTrainingDays; + + public static RecordStatisticsResponseDto of(int todayWalkTrainingCount, int weeklyWalkTrainingCount, int consecutiveWalkTrainingDays) { + return RecordStatisticsResponseDto.builder() + .todayWalkTrainingCount(todayWalkTrainingCount) + .weeklyWalkTrainingCount(weeklyWalkTrainingCount) + .consecutiveWalkTrainingDays(consecutiveWalkTrainingDays) + .build(); + } +}