feat: 건강 기록(통증부위, 운동시간) 작성, 조회 로직 구현 & 온기 조회 API 리팩토링 #22
Conversation
fix: Temperature 엔티티에 불필요한 칼럼 제거
feat: 온도 조회 controller 테스트코드 추가
Walkthrough이번 변경사항은 건강 기록(통증 및 운동) 관련 신규 API와 엔티티, DTO, 서비스, 레포지토리 계층을 도입하고, 온도(Temperature) 기능의 API 및 서비스 구조를 가족 단위 집계 중심으로 대폭 리팩토링했습니다. 또한, IDE 및 Gradle 관련 설정 파일들이 새로 추가되었으며, 기존 온도 DTO와 테스트 코드도 새로운 구조에 맞게 수정 또는 삭제되었습니다. Changes
Sequence Diagram(s)sequenceDiagram
actor User
participant HealthRecordController
participant HealthRecordService
participant PainRecordRepository
participant ExerciseRecordRepository
User->>HealthRecordController: POST /health/pain (date, area, level)
HealthRecordController->>HealthRecordService: addPainRecord(parentId, date, area, level)
HealthRecordService->>PainRecordRepository: save()
PainRecordRepository-->>HealthRecordService: PainRecord
HealthRecordService-->>HealthRecordController: PainRecord
HealthRecordController-->>User: PainRecordResponse
User->>HealthRecordController: POST /health/exercise (date, duration)
HealthRecordController->>HealthRecordService: addExerciseRecord(parentId, date, duration)
HealthRecordService->>ExerciseRecordRepository: save()
ExerciseRecordRepository-->>HealthRecordService: ExerciseRecord
HealthRecordService-->>HealthRecordController: ExerciseRecord
HealthRecordController-->>User: ExerciseRecordWithDiffResponse
User->>HealthRecordController: GET /health/parents/pain (parentId, date)
HealthRecordController->>HealthRecordService: getPainRecords(parentId, date)
HealthRecordService->>PainRecordRepository: findByParentIdAndDate()
PainRecordRepository-->>HealthRecordService: List<PainRecord>
HealthRecordService-->>HealthRecordController: List<PainRecord>
HealthRecordController-->>User: List<PainRecordResponse>
sequenceDiagram
actor User
participant TemperatureController
participant TemperatureService
participant TemperatureRepository
participant FamilyRepository
participant UserRepository
User->>TemperatureController: GET /temperature/summary?familyId=...
TemperatureController->>TemperatureService: getFamilyTemperatureSummary(familyId)
TemperatureService->>FamilyRepository: findById()
FamilyRepository-->>TemperatureService: Family
TemperatureService->>TemperatureRepository: findByFamilyId()
TemperatureRepository-->>TemperatureService: List<Temperature>
TemperatureService->>UserRepository: findAllById()
UserRepository-->>TemperatureService: List<User>
TemperatureService-->>TemperatureController: FamilyTemperatureResponse
TemperatureController-->>User: FamilyTemperatureResponse
User->>TemperatureController: GET /temperature/daily?familyId=...
TemperatureController->>TemperatureService: getFamilyTemperatureDaily(familyId)
TemperatureService->>FamilyRepository: findById()
FamilyRepository-->>TemperatureService: Family
TemperatureService->>TemperatureRepository: getFamilyTemperatureDaily()
TemperatureRepository-->>TemperatureService: List<DailyTemperature>
TemperatureService-->>TemperatureController: FamilyTemperatureDailyResponse
TemperatureController-->>User: FamilyTemperatureDailyResponse
Poem
✨ Finishing Touches
🪧 TipsChatThere are 3 ways to chat with CodeRabbit:
SupportNeed help? Create a ticket on our support page for assistance with any issues or questions. Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments. CodeRabbit Commands (Invoked using PR comments)
Other keywords and placeholders
CodeRabbit Configuration File (
|
There was a problem hiding this comment.
Actionable comments posted: 14
♻️ Duplicate comments (2)
backend/ongi/src/main/java/ongi/temperature/controller/TemperatureController.java (2)
24-27: familyId 파라미터 검증 누락위와 동일한 검증 문제가 있습니다.
30-33: familyId 파라미터 검증 누락위와 동일한 검증 문제가 있습니다.
🧹 Nitpick comments (21)
.idea/vcs.xml (1)
1-6: IDE 설정 파일은 저장소에서 제외하는 것을 권장합니다
.idea디렉터리는 개발자 또는 CI 환경마다 달라지는 사용자·환경 의존 설정을 포함하므로 버전 관리 대상이 아니어도 됩니다. 팀 간 충돌·불필요한 diff 발생을 막기 위해.gitignore에 추가하여 커밋을 피하는 방안을 고려해주세요.+# IntelliJ IDEA +.idea/.idea/jarRepositories.xml (1)
1-20: IDE 전용jarRepositories.xml커밋 여부 재검토
원격 Maven repository 설정은 Gradlerepositories {}블록으로 통일 관리하는 편이 IDE 의존성을 줄이고, 빌드 도구 및 CLI 환경과의 일관성을 보장합니다. IDE 설정 파일을 커밋하지 않도록.idea/전체를 ignore 하는 것을 다시 한 번 고려해 주세요..idea/uiDesigner.xml (1)
1-124: UI Designer 팔레트 설정 파일도 공유 필요성이 낮습니다
Swing GUI 빌드용 팔레트 정의는 일반적으로 개인 개발 환경에 국한됩니다.
공유가 불가피한 합의가 없다면, 이 파일 역시 저장소에서 제외하여 PR 변경량을 최소화하는 것이 좋습니다..idea/misc.xml (1)
1-10: 프로젝트 출력 디렉터리 등 IDE 내부 설정은.idea커밋 시 발생할 수 있는 충돌 주의
특히FrameworkDetectionExcludesConfiguration등은 사용자 별로 달라질 가능성이 높아 merge conflict 원인이 됩니다. 팀 규칙에 따라 IDE 설정을 버전 관리할지 재확인해 주세요..idea/modules.xml (1)
1-9:modules.xml커밋 시 모듈 경로 충돌 가능성
개발자마다 경로 구조가 다를 수 있어, 이 파일을 공유하면 인텔리제이에서 모듈 경로 오류가 발생할 수 있습니다. Gradlesettings.gradle(orsettings.gradle.kts) 기준으로 모듈을 관리하면 IDE가 자동 동기화할 수 있으니, 저장소 포함 여부를 재검토하세요..idea/PNUSW-2025-OnGi-10.iml (1)
1-8: IDE 전용 설정 파일은 VCS에 포함하지 않는 것을 권장합니다
.iml파일은 개발자 로컬 IDE 환경에 강하게 결합되어 있어 팀 간 불필요한 충돌을 유발하거나 CI 서버에 불필요한 메타데이터를 노출할 수 있습니다. 가능하면 루트.gitignore에 추가해 VCS 추적 대상에서 제외해주세요..idea/compiler.xml (1)
7-9: 사용자 절대 경로가 하드코딩되어 있습니다
$USER_HOME$/.gradle/.../lombok-1.18.38.jar경로는 각 개발자 PC마다 달라질 수 있습니다. IDE가 자동 생성하므로 VCS에 굳이 올릴 필요가 없으며, 커밋할 경우 다른 개발자 환경에서 경로 오류가 발생할 수 있습니다..idea/gradle.xml (1)
7-15: Gradle JVM 설정 값이 고정-경로 및 개인 설정에 의존합니다
gradleJvm="temurin-17"과 같은 IDE-특화 설정은 팀원마다 상이할 수 있으며, VCS에 포함되면 충돌과 불필요한 변경 내역이 지속적으로 발생합니다..idea디렉터리 전체를 무시하거나, 공유가 꼭 필요하다면gradle.properties등 표준 Gradle 설정으로 옮기는 것을 권장합니다.backend/ongi/src/main/java/ongi/temperature/entity/Temperature.java (1)
21-24: 클래스 선언 포맷팅 개선 필요클래스 선언 부분의 포맷팅이 비정상적입니다. 가독성을 위해 표준적인 형태로 수정하는 것이 좋겠습니다.
다음과 같이 수정하세요:
-public class - - -Temperature { +public class Temperature {backend/ongi/src/main/java/ongi/temperature/dto/FamilyTemperatureDailyResponse.java (1)
8-19: DTO 일관성을 위한 어노테이션 추가 권장다른 DTO 클래스들(FamilyTemperatureResponse)과 일관성을 위해
@Builder와@NoArgsConstructor어노테이션 추가를 고려해보세요. 이는 향후 유연성을 제공할 수 있습니다.다음과 같이 수정할 수 있습니다:
@Getter +@Builder +@NoArgsConstructor @AllArgsConstructor public class FamilyTemperatureDailyResponse { private List<DailyTemperature> dailyTemperatures; @Getter + @Builder + @NoArgsConstructor @AllArgsConstructor public static class DailyTemperature { private LocalDate date; private Double totalTemperature; } }backend/ongi/src/main/java/ongi/temperature/dto/FamilyTemperatureContributionResponse.java (1)
9-21: DTO 일관성을 위한 어노테이션 추가 권장FamilyTemperatureResponse와 일관성을 위해
@Builder와@NoArgsConstructor어노테이션 추가를 고려해보세요.다음과 같이 수정할 수 있습니다:
@Getter +@Builder +@NoArgsConstructor @AllArgsConstructor public class FamilyTemperatureContributionResponse { private List<Contribution> contributions; @Getter + @Builder + @NoArgsConstructor @AllArgsConstructor public static class Contribution { private LocalDate date; private UUID userId; private Double contributed; } }backend/ongi/src/test/java/ongi/TemperatureControllerUnitTest.java (1)
23-23: 불필요한 @activeprofiles 어노테이션순수 단위 테스트에서는
@ActiveProfiles가 필요하지 않습니다. 이는 주로 Spring 컨텍스트가 필요한 통합 테스트에서 사용됩니다.Mock을 사용하는 단위 테스트이므로 이 어노테이션을 제거하세요:
-@ActiveProfiles("test") class TemperatureControllerUnitTest {backend/ongi/src/main/java/ongi/temperature/service/TemperatureService.java (3)
54-55: 불필요한 ArrayList 생성
userTemperatures.keySet()을 새로운 ArrayList로 변환할 필요가 없습니다.다음과 같이 최적화하세요:
-List<UUID> userIds = new ArrayList<>(userTemperatures.keySet()); -Map<UUID, User> users = userRepository.findAllById(userIds).stream() +Map<UUID, User> users = userRepository.findAllById(userTemperatures.keySet()).stream()
87-87: 매직 넘버 사용"최근 5일"을 계산하기 위해 매직 넘버 4를 사용하고 있습니다.
상수로 정의하여 가독성과 유지보수성을 향상시키세요:
+private static final int DAYS_TO_RETRIEVE = 5; -java.time.LocalDateTime fromDate = java.time.LocalDate.now().minusDays(4).atStartOfDay(); // 최근 5일 +java.time.LocalDateTime fromDate = java.time.LocalDate.now().minusDays(DAYS_TO_RETRIEVE - 1).atStartOfDay();Also applies to: 96-96
105-145: 다수의 미구현 TODO 메서드온도 증가/감소 관련 여러 메서드가 TODO로 남아있습니다. 이러한 메서드들은 시스템의 핵심 기능으로 보입니다.
이러한 TODO 항목들을 추적하기 위한 이슈를 생성하시겠습니까? 각 메서드의 구현 우선순위와 요구사항을 정리할 수 있습니다.
backend/ongi/src/main/java/ongi/health/dto/PainRecordResponse.java (1)
16-17: 열거형 문자열 표현 방식을 검토해주세요.
enum.name()메서드는 코드에서 정의된 열거형 이름을 그대로 반환합니다. 사용자에게 표시되는 응답이라면 국제화나 사용자 친화적인 문자열을 고려해보세요.열거형에 사용자 친화적인 문자열을 반환하는 메서드를 추가하거나, 별도의 매핑 로직을 사용하는 것을 고려해보세요.
backend/ongi/src/main/java/ongi/health/entity/ExerciseRecord.java (1)
25-26: 운동 시간 필드에 대한 유효성 검증을 고려해주세요.주석에서 duration 값이 0, 30, 60 등의 특정 값만 허용됨을 시사하고 있습니다. 잘못된 값이 입력되지 않도록 유효성 검증을 추가하는 것을 고려해보세요.
Bean Validation을 사용하여 허용 가능한 값들을 제한할 수 있습니다:
@Column(nullable = false) +@Min(0) +@Max(1440) // 24시간 * 60분 private int duration; // 0, 30, 60, ...또는 enum이나 커스텀 유효성 검증을 고려해보세요.
backend/ongi/src/main/java/ongi/health/entity/PainRecord.java (2)
19-23: 데이터 무결성을 위한 추가 검증 고려필드에
nullable = false설정은 좋지만, 추가적인 유효성 검증 어노테이션(@NotNull, @SiZe 등)과 데이터베이스 제약 조건을 고려해보세요.+import jakarta.validation.constraints.NotNull; +import jakarta.validation.constraints.PastOrPresent; + @Column(nullable = false) +@NotNull private UUID parentId; @Column(nullable = false) +@NotNull +@PastOrPresent private LocalDate date;
33-39: Enum 값의 명명 규칙 개선 제안Enum 값들이 의미가 명확하지만, 일부 값의 명명을 더 직관적으로 개선할 수 있습니다.
public enum PainLevel { - STRONG, MID_STRONG, MID_WEAK, WEAK + SEVERE, MODERATE_HIGH, MODERATE_LOW, MILD }또한 각 enum에 한글 설명을 추가하면 더 유용합니다:
public enum PainArea { - HEAD, NECK, SHOULDER, CHEST, BACK, ARM, HAND, ABDOMEN, WAIST, LEG, KNEE, FOOT, NONE + HEAD("머리"), NECK("목"), SHOULDER("어깨"), CHEST("가슴"), BACK("등"), + ARM("팔"), HAND("손"), ABDOMEN("복부"), WAIST("허리"), LEG("다리"), + KNEE("무릎"), FOOT("발"), NONE("없음"); + + private final String description; + + PainArea(String description) { + this.description = description; + } }backend/ongi/src/main/java/ongi/health/service/HealthRecordService.java (1)
57-64: 데이터베이스 쿼리 최적화 고려현재 구현에서는 동일한 parentId로 두 번의 개별 쿼리를 실행합니다. 성능 최적화를 위해 배치 조회를 고려해보세요.
public ExerciseRecordWithDiffResponse getExerciseRecordWithDiff(UUID parentId, LocalDate date) { - ExerciseRecord today = exerciseRecordRepository.findByParentIdAndDate(parentId, date).orElse(null); - ExerciseRecord prev = exerciseRecordRepository.findByParentIdAndDate(parentId, date.minusDays(1)).orElse(null); + // 리포지토리에 배치 조회 메서드 추가를 고려 + List<ExerciseRecord> records = exerciseRecordRepository.findByParentIdAndDateBetween( + parentId, date.minusDays(1), date); + + ExerciseRecord today = records.stream() + .filter(r -> r.getDate().equals(date)) + .findFirst().orElse(null); + ExerciseRecord prev = records.stream() + .filter(r -> r.getDate().equals(date.minusDays(1))) + .findFirst().orElse(null); + int prevDuration = prev != null ? prev.getDuration() : 0; int todayDuration = today != null ? today.getDuration() : 0; int diff = todayDuration - prevDuration; return new ExerciseRecordWithDiffResponse(today, prevDuration, diff); }backend/ongi/src/main/java/ongi/health/controller/HealthRecordController.java (1)
39-50: API 응답 일관성 개선운동 기록 추가 시 diff 정보를 0으로 설정하는 것보다는 별도의 응답 DTO를 사용하는 것이 더 명확합니다.
운동 기록 추가용 별도 응답 DTO를 만들거나, 기존 DTO에 플래그를 추가하여 diff 정보의 유효성을 나타내는 것을 고려해보세요:
ExerciseRecordWithDiffResponse response = new ExerciseRecordWithDiffResponse( - record.getId(), record.getDate(), record.getDuration(), 0, 0); + record.getId(), record.getDate(), record.getDuration(), null, null);또는 별도의
ExerciseRecordResponseDTO를 생성하여 사용하는 것이 더 명확할 수 있습니다.
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
⛔ Files ignored due to path filters (1)
backend/ongi/gradle/wrapper/gradle-wrapper.jaris excluded by!**/*.jar
📒 Files selected for processing (29)
.idea/.gitignore(1 hunks).idea/PNUSW-2025-OnGi-10.iml(1 hunks).idea/compiler.xml(1 hunks).idea/gradle.xml(1 hunks).idea/jarRepositories.xml(1 hunks).idea/misc.xml(1 hunks).idea/modules.xml(1 hunks).idea/uiDesigner.xml(1 hunks).idea/vcs.xml(1 hunks)backend/ongi/gradle/wrapper/gradle-wrapper.properties(1 hunks)backend/ongi/gradlew(4 hunks)backend/ongi/gradlew.bat(2 hunks)backend/ongi/src/main/java/ongi/health/controller/HealthRecordController.java(1 hunks)backend/ongi/src/main/java/ongi/health/dto/ExerciseRecordWithDiffResponse.java(1 hunks)backend/ongi/src/main/java/ongi/health/dto/PainRecordResponse.java(1 hunks)backend/ongi/src/main/java/ongi/health/entity/ExerciseRecord.java(1 hunks)backend/ongi/src/main/java/ongi/health/entity/PainRecord.java(1 hunks)backend/ongi/src/main/java/ongi/health/repository/ExerciseRecordRepository.java(1 hunks)backend/ongi/src/main/java/ongi/health/repository/PainRecordRepository.java(1 hunks)backend/ongi/src/main/java/ongi/health/service/HealthRecordService.java(1 hunks)backend/ongi/src/main/java/ongi/temperature/controller/TemperatureController.java(1 hunks)backend/ongi/src/main/java/ongi/temperature/dto/FamilyTemperatureContributionResponse.java(1 hunks)backend/ongi/src/main/java/ongi/temperature/dto/FamilyTemperatureDailyResponse.java(1 hunks)backend/ongi/src/main/java/ongi/temperature/dto/FamilyTemperatureResponse.java(1 hunks)backend/ongi/src/main/java/ongi/temperature/dto/MemberTemperatureResponse.java(0 hunks)backend/ongi/src/main/java/ongi/temperature/entity/Temperature.java(1 hunks)backend/ongi/src/main/java/ongi/temperature/repository/TemperatureRepository.java(1 hunks)backend/ongi/src/main/java/ongi/temperature/service/TemperatureService.java(3 hunks)backend/ongi/src/test/java/ongi/TemperatureControllerUnitTest.java(3 hunks)
💤 Files with no reviewable changes (1)
- backend/ongi/src/main/java/ongi/temperature/dto/MemberTemperatureResponse.java
🧰 Additional context used
🧬 Code Graph Analysis (3)
backend/ongi/src/main/java/ongi/temperature/dto/FamilyTemperatureDailyResponse.java (2)
backend/ongi/src/main/java/ongi/temperature/dto/FamilyTemperatureContributionResponse.java (2)
Getter(9-21)Getter(14-20)backend/ongi/src/main/java/ongi/temperature/dto/FamilyTemperatureResponse.java (2)
Getter(11-30)Getter(20-29)
backend/ongi/src/main/java/ongi/health/entity/ExerciseRecord.java (1)
backend/ongi/src/main/java/ongi/health/entity/PainRecord.java (1)
Entity(8-40)
backend/ongi/src/main/java/ongi/temperature/dto/FamilyTemperatureContributionResponse.java (2)
backend/ongi/src/main/java/ongi/temperature/dto/FamilyTemperatureDailyResponse.java (2)
Getter(8-19)Getter(13-18)backend/ongi/src/main/java/ongi/temperature/dto/FamilyTemperatureResponse.java (2)
Getter(11-30)Getter(20-29)
🔇 Additional comments (21)
.idea/.gitignore (1)
1-8: 루트.gitignore와 중복 여부 확인 필요이 규칙들은 유용하지만, 동일한 패턴이 이미 루트
.gitignore에 존재한다면 중복 관리가 발생할 수 있습니다. 한 곳에서만 관리하도록 정리하면 유지보수가 수월해집니다..idea/gradle.xml (1)
18-22:#JAVA_HOME토큰 사용 시 CI 환경과 일관성 확인 필요두 번째 프로젝트 설정에서
gradleJvm에 환경변수 토큰이 사용되고 있습니다. CI/CD 파이프라인에서도 동일한 환경변수가 정의돼 있지 않으면 빌드 실패가 발생할 수 있으니 확인 부탁드립니다.backend/ongi/gradle/wrapper/gradle-wrapper.properties (1)
3-3: Gradle 릴리즈 노트 보안 이슈 검토 필요기존 스크립트로 8.5→8.14.2 사이의 보안 키워드를 검색했으나, HTML 전체가 출력되어 신뢰할 수 있는 결과를 얻기 어렵습니다.
아래 스크립트로 “security|vulnerabilit|CVE” 키워드를 재검증하시고, 필요 시 Gradle 보안 고지 문서도 함께 확인해주세요.#!/bin/bash for version in 8.5 8.6 8.7 8.8 8.9 8.10 8.11 8.12 8.13 8.14 8.14.1 8.14.2; do echo "=== Gradle $version ===" curl -s "https://docs.gradle.org/$version/release-notes.html" \ | grep -E -i "security|vulnerability|CVE" \ || echo "No security entries" done위 검토 후에도 다운그레이드가 의도된 변경인지, 보안·호환성 리스크가 없는지 최종 확인 부탁드립니다.
backend/ongi/gradlew.bat (4)
46-50: 에러 메시지 포맷팅 개선에러 메시지를 여러 줄로 분리하고 명시적인 stderr 리다이렉션을 제거하여 가독성을 향상시켰습니다.
60-64: 일관된 에러 메시지 포맷팅JAVA_HOME 관련 에러 메시지도 동일한 형식으로 개선되어 일관성이 향상되었습니다.
71-71: 명시적 CLASSPATH 설정CLASSPATH를 명시적으로 gradle-wrapper.jar로 설정하여 실행 환경을 더욱 안정적으로 만들었습니다.
75-75: Java 실행 방식 개선
-jar옵션 대신 메인 클래스를 직접 호출하는 방식으로 변경하여 더 명확한 실행 구조를 제공합니다.backend/ongi/gradlew (5)
58-58: Gradle 템플릿 소스 URL 업데이트Gradle 레포지토리 내 템플릿 소스 경로가 새로운 위치로 업데이트되었습니다.
87-87: 경로 해결 방식 개선
cd -P ... && printf '%s\n' "$PWD"에서cd ... && pwd -P로 변경하여 더 간결하고 표준적인 방식으로 절대 경로를 해결합니다.
115-115: 명시적 CLASSPATH 설정CLASSPATH를 명시적으로 gradle-wrapper.jar로 설정하여 실행 환경의 안정성을 향상시켰습니다.
206-206: 주석 내용 수정환경 변수 목록에서 중복된
JAVA_OPTS항목을 수정하여 문서의 정확성을 개선했습니다.
214-214: Java 실행 방식 개선
-jar옵션 대신 메인 클래스org.gradle.wrapper.GradleWrapperMain을 직접 호출하는 방식으로 변경하여 더 명확한 실행 구조를 제공합니다.backend/ongi/src/main/java/ongi/temperature/dto/FamilyTemperatureResponse.java (1)
19-19: 코드 정리 완료불필요한 공백 제거로 코드가 깔끔해졌습니다.
backend/ongi/src/main/java/ongi/temperature/repository/TemperatureRepository.java (1)
4-5: Import 문 추가 완료새로운 DTO 클래스들을 사용하기 위한 import 문이 적절히 추가되었습니다.
backend/ongi/src/test/java/ongi/TemperatureControllerUnitTest.java (1)
39-161: 테스트 구현이 우수합니다테스트가 잘 구조화되어 있고, given-when-then 패턴을 따르며, 성공 및 실패 케이스를 모두 다루고 있습니다.
backend/ongi/src/main/java/ongi/health/repository/ExerciseRecordRepository.java (1)
12-16: 코드 구조가 올바르게 구현되었습니다.Spring Data JPA 리포지토리 인터페이스가 표준 규칙에 따라 잘 구현되었습니다. 쿼리 메서드 명명 규칙과 반환 타입이 적절합니다.
backend/ongi/src/main/java/ongi/health/repository/PainRecordRepository.java (2)
13-13: 통증 기록의 날짜별 다중성 설계가 일관성 있게 구현되었습니다.
findByParentIdAndDate메서드가List<PainRecord>를 반환하는 것은 하루에 여러 통증 기록이 가능함을 의미합니다. 이는ExerciseRecordRepository의Optional<ExerciseRecord>반환과 대조적인데, 비즈니스 로직상 합리적인 설계로 보입니다.
11-15: 리포지토리 인터페이스가 올바르게 구현되었습니다.Spring Data JPA 표준을 따르고 있으며, 쿼리 메서드 명명 규칙과 반환 타입이 적절합니다.
backend/ongi/src/main/java/ongi/health/dto/ExerciseRecordWithDiffResponse.java (1)
13-21: null 안전성이 잘 구현되었습니다.편의 생성자에서
entity != null체크를 통해 NPE를 방지하고 있으며, 적절한 기본값(null 또는 0)을 제공하고 있습니다. 운동 기록 간 차이 계산을 위한 설계가 잘 되어 있습니다.backend/ongi/src/main/java/ongi/health/entity/ExerciseRecord.java (1)
8-27: 엔티티 설계가 일관성 있게 구현되었습니다.JPA 엔티티 구조가
PainRecord엔티티와 일관성 있게 설계되었고, Lombok 애노테이션 사용과 필드 정의가 적절합니다.backend/ongi/src/main/java/ongi/health/entity/PainRecord.java (1)
8-14: 엔티티 설계가 잘 구성되어 있습니다.JPA 엔티티 어노테이션과 Lombok을 적절히 활용하여 보일러플레이트 코드를 줄였습니다. Builder 패턴 지원으로 객체 생성이 편리합니다.
| // 최근 5일간 가족 구성원별 온도 기여 내역 | ||
| @Query("SELECT new ongi.temperature.dto.FamilyTemperatureContributionResponse.Contribution(CAST(t.createdAt AS date), t.userId, SUM(t.temperature)) " + | ||
| "FROM Temperature t WHERE t.familyId = :familyId AND t.createdAt >= :fromDate " + | ||
| "GROUP BY CAST(t.createdAt AS date), t.userId ORDER BY CAST(t.createdAt AS date) DESC") | ||
| List<FamilyTemperatureContributionResponse.Contribution> getFamilyTemperatureContributions(@Param("familyId") String familyId, @Param("fromDate") java.time.LocalDateTime fromDate); |
There was a problem hiding this comment.
🛠️ Refactor suggestion
집계 쿼리 최적화 권장
대용량 데이터에서 성능 문제가 발생할 수 있습니다. 다음 사항을 고려해보세요:
(familyId, createdAt)복합 인덱스 생성- 페이징 처리 고려 (현재는 5일로 제한되어 있지만)
- 쿼리 실행 계획 검토
인덱스 생성 예시:
CREATE INDEX idx_temperature_family_created
ON temperatures(family_id, created_at DESC);🤖 Prompt for AI Agents
In
backend/ongi/src/main/java/ongi/temperature/repository/TemperatureRepository.java
around lines 28 to 32, the query for recent 5-day family temperature
contributions may cause performance issues on large datasets. To fix this,
create a composite index on the temperature table for (familyId, createdAt) with
createdAt in descending order to optimize filtering and ordering. Additionally,
consider implementing pagination in the query method to limit result size and
review the query execution plan to ensure efficient execution.
backend/ongi/src/main/java/ongi/temperature/repository/TemperatureRepository.java
Show resolved
Hide resolved
|
|
||
| @RestController | ||
| @RequestMapping("/api/temperatures") | ||
| @RequestMapping("/temperature") |
There was a problem hiding this comment.
API 경로 변경으로 인한 하위 호환성 문제
기존 /api/temperatures에서 /temperature로 변경되었습니다. 이는 기존 클라이언트에 대한 breaking change입니다.
하위 호환성을 위해 다음 중 하나를 고려하세요:
-@RequestMapping("/temperature")
+@RequestMapping("/api/temperature")또는 기존 경로도 지원하도록 설정을 추가하세요.
🤖 Prompt for AI Agents
In
backend/ongi/src/main/java/ongi/temperature/controller/TemperatureController.java
at line 12, the API path was changed from /api/temperatures to /temperature,
causing a breaking change for existing clients. To fix this, either revert to
the original path or configure the controller to support both paths by adding an
additional @RequestMapping annotation or adjusting the mapping to include both
endpoints, ensuring backward compatibility.
| @GetMapping("/summary") | ||
| public ResponseEntity<FamilyTemperatureResponse> getFamilyTemperatureSummary(@RequestParam String familyId) { | ||
| return ResponseEntity.ok(temperatureService.getFamilyTemperatureSummary(familyId)); | ||
| } |
There was a problem hiding this comment.
🛠️ Refactor suggestion
familyId 파라미터 검증 누락
familyId 파라미터에 대한 입력 검증이 없습니다. 빈 문자열이나 잘못된 형식의 ID가 전달될 수 있습니다.
다음과 같이 검증을 추가하세요:
-public ResponseEntity<FamilyTemperatureResponse> getFamilyTemperatureSummary(@RequestParam String familyId) {
+public ResponseEntity<FamilyTemperatureResponse> getFamilyTemperatureSummary(
+ @RequestParam @NotBlank(message = "가족 ID는 필수입니다") String familyId) {컨트롤러 클래스에 @Validated 어노테이션도 추가해야 합니다.
📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| @GetMapping("/summary") | |
| public ResponseEntity<FamilyTemperatureResponse> getFamilyTemperatureSummary(@RequestParam String familyId) { | |
| return ResponseEntity.ok(temperatureService.getFamilyTemperatureSummary(familyId)); | |
| } | |
| @GetMapping("/summary") | |
| public ResponseEntity<FamilyTemperatureResponse> getFamilyTemperatureSummary( | |
| @RequestParam @NotBlank(message = "가족 ID는 필수입니다") String familyId) { | |
| return ResponseEntity.ok(temperatureService.getFamilyTemperatureSummary(familyId)); | |
| } |
🤖 Prompt for AI Agents
In
backend/ongi/src/main/java/ongi/temperature/controller/TemperatureController.java
around lines 18 to 21, the familyId parameter lacks input validation, allowing
empty or invalid IDs. Add validation annotations such as @NotBlank to the
familyId parameter to ensure it is not empty, and annotate the controller class
with @Validated to enable validation processing. This will enforce proper input
checks before proceeding with the service call.
| @@ -1,13 +1,9 @@ | |||
| package ongi; | |||
There was a problem hiding this comment.
테스트 클래스 패키지 위치 오류
테스트 클래스가 잘못된 패키지에 위치해 있습니다. 컨트롤러 클래스와 동일한 패키지 구조를 따라야 합니다.
다음과 같이 수정하세요:
-package ongi;
+package ongi.temperature.controller;📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| package ongi; | |
| -package ongi; | |
| +package ongi.temperature.controller; |
🤖 Prompt for AI Agents
In backend/ongi/src/test/java/ongi/TemperatureControllerUnitTest.java at line 1,
the test class is placed in the wrong package. Change the package declaration to
match the controller class's package structure so that the test class resides in
the correct package hierarchy aligned with the controller.
| @Transactional | ||
| public ExerciseRecord addExerciseRecord(UUID parentId, LocalDate date, int duration) { | ||
| ExerciseRecord record = ExerciseRecord.builder() | ||
| .parentId(parentId) | ||
| .date(date) | ||
| .duration(duration) | ||
| .build(); | ||
| return exerciseRecordRepository.save(record); | ||
| } |
There was a problem hiding this comment.
🛠️ Refactor suggestion
운동 시간 유효성 검증 추가
운동 시간(duration)에 대한 범위 검증이 필요합니다.
@Transactional
public ExerciseRecord addExerciseRecord(UUID parentId, LocalDate date, int duration) {
+ Assert.notNull(parentId, "Parent ID는 필수입니다");
+ Assert.notNull(date, "날짜는 필수입니다");
+ Assert.isTrue(duration >= 0, "운동 시간은 0 이상이어야 합니다");
+ Assert.isTrue(duration <= 1440, "운동 시간은 1440분(24시간)을 초과할 수 없습니다");
+
ExerciseRecord record = ExerciseRecord.builder()
.parentId(parentId)
.date(date)
.duration(duration)
.build();
return exerciseRecordRepository.save(record);
}📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| @Transactional | |
| public ExerciseRecord addExerciseRecord(UUID parentId, LocalDate date, int duration) { | |
| ExerciseRecord record = ExerciseRecord.builder() | |
| .parentId(parentId) | |
| .date(date) | |
| .duration(duration) | |
| .build(); | |
| return exerciseRecordRepository.save(record); | |
| } | |
| @Transactional | |
| public ExerciseRecord addExerciseRecord(UUID parentId, LocalDate date, int duration) { | |
| Assert.notNull(parentId, "Parent ID는 필수입니다"); | |
| Assert.notNull(date, "날짜는 필수입니다"); | |
| Assert.isTrue(duration >= 0, "운동 시간은 0 이상이어야 합니다"); | |
| Assert.isTrue(duration <= 1440, "운동 시간은 1440분(24시간)을 초과할 수 없습니다"); | |
| ExerciseRecord record = ExerciseRecord.builder() | |
| .parentId(parentId) | |
| .date(date) | |
| .duration(duration) | |
| .build(); | |
| return exerciseRecordRepository.save(record); | |
| } |
🤖 Prompt for AI Agents
In backend/ongi/src/main/java/ongi/health/service/HealthRecordService.java
around lines 41 to 49, the addExerciseRecord method lacks validation for the
duration parameter. Add a validation step to check that duration is within a
valid range (e.g., positive and reasonable upper limit). If the duration is
invalid, throw an appropriate exception before creating and saving the
ExerciseRecord.
| @Transactional | ||
| public PainRecord addPainRecord(UUID parentId, LocalDate date, PainRecord.PainArea area, PainRecord.PainLevel level) { | ||
| PainRecord record = PainRecord.builder() | ||
| .parentId(parentId) | ||
| .date(date) | ||
| .painArea(area) | ||
| .painLevel(level) | ||
| .build(); | ||
| return painRecordRepository.save(record); | ||
| } |
There was a problem hiding this comment.
🛠️ Refactor suggestion
입력 유효성 검증 추가 필요
서비스 메서드에서 입력 파라미터에 대한 검증이 없습니다. null 값이나 잘못된 값이 전달될 수 있습니다.
+import org.springframework.util.Assert;
+
@Transactional
public PainRecord addPainRecord(UUID parentId, LocalDate date, PainRecord.PainArea area, PainRecord.PainLevel level) {
+ Assert.notNull(parentId, "Parent ID는 필수입니다");
+ Assert.notNull(date, "날짜는 필수입니다");
+ Assert.notNull(area, "통증 부위는 필수입니다");
+ Assert.notNull(level, "통증 정도는 필수입니다");
+
PainRecord record = PainRecord.builder()
.parentId(parentId)
.date(date)
.painArea(area)
.painLevel(level)
.build();
return painRecordRepository.save(record);
}📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| @Transactional | |
| public PainRecord addPainRecord(UUID parentId, LocalDate date, PainRecord.PainArea area, PainRecord.PainLevel level) { | |
| PainRecord record = PainRecord.builder() | |
| .parentId(parentId) | |
| .date(date) | |
| .painArea(area) | |
| .painLevel(level) | |
| .build(); | |
| return painRecordRepository.save(record); | |
| } | |
| import org.springframework.util.Assert; | |
| @Transactional | |
| public PainRecord addPainRecord(UUID parentId, LocalDate date, PainRecord.PainArea area, PainRecord.PainLevel level) { | |
| Assert.notNull(parentId, "Parent ID는 필수입니다"); | |
| Assert.notNull(date, "날짜는 필수입니다"); | |
| Assert.notNull(area, "통증 부위는 필수입니다"); | |
| Assert.notNull(level, "통증 정도는 필수입니다"); | |
| PainRecord record = PainRecord.builder() | |
| .parentId(parentId) | |
| .date(date) | |
| .painArea(area) | |
| .painLevel(level) | |
| .build(); | |
| return painRecordRepository.save(record); | |
| } |
🤖 Prompt for AI Agents
In backend/ongi/src/main/java/ongi/health/service/HealthRecordService.java
around lines 24 to 33, the addPainRecord method lacks input validation for its
parameters. Add checks to ensure none of the parameters (parentId, date, area,
level) are null or invalid before proceeding. If any parameter is invalid, throw
an appropriate exception to prevent saving incorrect data.
| // 자녀용: 부모 운동 기록 + 전날 대비 증감 조회 | ||
| @GetMapping("/parents/exercise") | ||
| public ResponseEntity<ExerciseRecordWithDiffResponse> getParentExerciseRecordForChild( | ||
| @RequestParam UUID parentId, | ||
| @RequestParam @DateTimeFormat(iso = DateTimeFormat.ISO.DATE) LocalDate date | ||
| ) { | ||
| ExerciseRecordWithDiffResponse resp = healthRecordService.getExerciseRecordWithDiff(parentId, date); | ||
| return ResponseEntity.ok(resp); | ||
| } | ||
| } No newline at end of file |
There was a problem hiding this comment.
동일한 보안 이슈 존재
운동 기록 조회 엔드포인트에서도 동일한 권한 검증 문제가 있습니다.
@GetMapping("/parents/exercise")
public ResponseEntity<ExerciseRecordWithDiffResponse> getParentExerciseRecordForChild(
+ @AuthenticationPrincipal CustomUserDetails userDetails,
@RequestParam UUID parentId,
@RequestParam @DateTimeFormat(iso = DateTimeFormat.ISO.DATE) LocalDate date
) {
+ if (!hasAccessToParent(userDetails.getUser(), parentId)) {
+ return ResponseEntity.status(HttpStatus.FORBIDDEN).build();
+ }
+
ExerciseRecordWithDiffResponse resp = healthRecordService.getExerciseRecordWithDiff(parentId, date);
return ResponseEntity.ok(resp);
}🤖 Prompt for AI Agents
In backend/ongi/src/main/java/ongi/health/controller/HealthRecordController.java
around lines 65 to 74, the getParentExerciseRecordForChild method lacks proper
authorization checks, leading to a security issue. Add appropriate security
annotations or explicit permission checks to verify that the requesting user has
the right to access the parent's exercise records before returning the response.
| @PostMapping("/pain") | ||
| public ResponseEntity<PainRecordResponse> addPainRecord( | ||
| @AuthenticationPrincipal CustomUserDetails userDetails, | ||
| @RequestParam @DateTimeFormat(iso = DateTimeFormat.ISO.DATE) LocalDate date, | ||
| @RequestParam PainRecord.PainArea area, | ||
| @RequestParam PainRecord.PainLevel level | ||
| ) { | ||
| PainRecord record = healthRecordService.addPainRecord(userDetails.getUser().getUuid(), date, area, level); | ||
| PainRecordResponse response = new PainRecordResponse(record); | ||
| return ResponseEntity.ok(response); | ||
| } |
There was a problem hiding this comment.
🛠️ Refactor suggestion
입력 검증 및 예외 처리 추가 필요
컨트롤러에서 입력 파라미터에 대한 검증이 부족하고 예외 처리가 없습니다.
+import org.springframework.validation.annotation.Validated;
+import jakarta.validation.Valid;
+import jakarta.validation.constraints.NotNull;
+import org.springframework.web.bind.annotation.ExceptionHandler;
+import org.springframework.http.HttpStatus;
+
+@Validated
@RestController
@RequestMapping("/health")
@RequiredArgsConstructor
public class HealthRecordController {
@PostMapping("/pain")
public ResponseEntity<PainRecordResponse> addPainRecord(
@AuthenticationPrincipal CustomUserDetails userDetails,
- @RequestParam @DateTimeFormat(iso = DateTimeFormat.ISO.DATE) LocalDate date,
- @RequestParam PainRecord.PainArea area,
- @RequestParam PainRecord.PainLevel level
+ @RequestParam @DateTimeFormat(iso = DateTimeFormat.ISO.DATE) @NotNull LocalDate date,
+ @RequestParam @NotNull PainRecord.PainArea area,
+ @RequestParam @NotNull PainRecord.PainLevel level
) {
+ try {
PainRecord record = healthRecordService.addPainRecord(userDetails.getUser().getUuid(), date, area, level);
PainRecordResponse response = new PainRecordResponse(record);
return ResponseEntity.ok(response);
+ } catch (Exception e) {
+ // 적절한 예외 처리 로직 추가
+ return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).build();
+ }
}📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| @PostMapping("/pain") | |
| public ResponseEntity<PainRecordResponse> addPainRecord( | |
| @AuthenticationPrincipal CustomUserDetails userDetails, | |
| @RequestParam @DateTimeFormat(iso = DateTimeFormat.ISO.DATE) LocalDate date, | |
| @RequestParam PainRecord.PainArea area, | |
| @RequestParam PainRecord.PainLevel level | |
| ) { | |
| PainRecord record = healthRecordService.addPainRecord(userDetails.getUser().getUuid(), date, area, level); | |
| PainRecordResponse response = new PainRecordResponse(record); | |
| return ResponseEntity.ok(response); | |
| } | |
| import org.springframework.validation.annotation.Validated; | |
| import jakarta.validation.Valid; | |
| import jakarta.validation.constraints.NotNull; | |
| import org.springframework.web.bind.annotation.ExceptionHandler; | |
| import org.springframework.http.HttpStatus; | |
| @Validated | |
| @RestController | |
| @RequestMapping("/health") | |
| @RequiredArgsConstructor | |
| public class HealthRecordController { | |
| @PostMapping("/pain") | |
| public ResponseEntity<PainRecordResponse> addPainRecord( | |
| @AuthenticationPrincipal CustomUserDetails userDetails, | |
| @RequestParam @DateTimeFormat(iso = DateTimeFormat.ISO.DATE) @NotNull LocalDate date, | |
| @RequestParam @NotNull PainRecord.PainArea area, | |
| @RequestParam @NotNull PainRecord.PainLevel level | |
| ) { | |
| try { | |
| PainRecord record = healthRecordService.addPainRecord( | |
| userDetails.getUser().getUuid(), date, area, level | |
| ); | |
| PainRecordResponse response = new PainRecordResponse(record); | |
| return ResponseEntity.ok(response); | |
| } catch (Exception e) { | |
| // 적절한 예외 처리 로직 추가 | |
| return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).build(); | |
| } | |
| } | |
| } |
🤖 Prompt for AI Agents
In backend/ongi/src/main/java/ongi/health/controller/HealthRecordController.java
around lines 26 to 36, the addPainRecord method lacks input validation and
exception handling for the request parameters. Add validation annotations or
manual checks to ensure the parameters are valid and handle potential exceptions
gracefully by returning appropriate HTTP error responses. This will improve
robustness and user feedback on invalid inputs.
| // 자녀용: 부모 통증 기록 조회 | ||
| @GetMapping("/parents/pain") | ||
| public ResponseEntity<List<PainRecordResponse>> getParentPainRecordsForChild( | ||
| @RequestParam UUID parentId, | ||
| @RequestParam @DateTimeFormat(iso = DateTimeFormat.ISO.DATE) LocalDate date | ||
| ) { | ||
| List<PainRecord> records = healthRecordService.getPainRecords(parentId, date); | ||
| List<PainRecordResponse> response = records.stream() | ||
| .map(PainRecordResponse::new) | ||
| .toList(); | ||
| return ResponseEntity.ok(response); | ||
| } |
There was a problem hiding this comment.
보안 권한 검증 필요
자녀가 부모의 기록을 조회하는 엔드포인트에서 권한 검증이 없습니다. 임의의 parentId로 다른 사용자의 데이터에 접근할 수 있습니다.
@GetMapping("/parents/pain")
public ResponseEntity<List<PainRecordResponse>> getParentPainRecordsForChild(
+ @AuthenticationPrincipal CustomUserDetails userDetails,
@RequestParam UUID parentId,
@RequestParam @DateTimeFormat(iso = DateTimeFormat.ISO.DATE) LocalDate date
) {
+ // 현재 사용자가 해당 부모에 접근할 권한이 있는지 확인
+ if (!hasAccessToParent(userDetails.getUser(), parentId)) {
+ return ResponseEntity.status(HttpStatus.FORBIDDEN).build();
+ }
+
List<PainRecord> records = healthRecordService.getPainRecords(parentId, date);
List<PainRecordResponse> response = records.stream()
.map(PainRecordResponse::new)
.toList();
return ResponseEntity.ok(response);
}
+
+private boolean hasAccessToParent(User user, UUID parentId) {
+ // 부모-자녀 관계 검증 로직 구현 필요
+ return true; // 임시 구현
+}🤖 Prompt for AI Agents
In backend/ongi/src/main/java/ongi/health/controller/HealthRecordController.java
around lines 52 to 63, the getParentPainRecordsForChild method lacks
authorization checks, allowing access to any parent's pain records by specifying
parentId. Add security validation to verify that the requesting user is
authorized to access the specified parent's data, such as checking the
authenticated user's identity or roles against the parentId before fetching
records, and return an appropriate error response if unauthorized.
* feat: 건강 기록(통증부위, 운동시간) 작성/조회 로직 구현 fix: Temperature 엔티티에 불필요한 칼럼 제거 * refactor: 온기 조회 시스템 리팩토링 및 엔드포인트 추가 * fix: 쿼리문 오류 수정 feat: 온도 조회 controller 테스트코드 추가 * fix: 쿼리문 오류 수정 2차
건강 기록 조회 로직 : 최근 일주일간의 기록을 조회하도록 수정 예정