EC2, RDS 마이그레이션#19
Conversation
Walkthrough이 PR은 CI/CD 파이프라인 자동화, 인증 및 오류 처리, 달력, 집안일 자동 배정, 가구 관리, 사용자 선호도, 일정 관리 등 여러 도메인을 걸친 새로운 기능과 엔티티를 추가합니다. 총 61개의 새 파일이 도입되었습니다. Changes
Sequence Diagram(s)sequenceDiagram
participant Client as 클라이언트
participant Controller as ChoreController
participant Service as ChoreAssignmentService
participant Repository as 저장소<br/>(User, Chore, 등)
participant FastAPI as FastAPI<br/>배정 서버
participant Database as 데이터베이스
Client->>Controller: POST /api/chores/auto-assign<br/>(startDate, endDate)
Controller->>Controller: 사용자 ID 추출<br/>periodDays 계산
Controller->>Service: assignChores(userId,<br/>startDate, periodDays)
Service->>Repository: 사용자 로드
Service->>Repository: 가구 멤버 조회
Service->>Repository: 가구 집안일 조회
Service->>Repository: 사용자 선호도 조회
Service->>Service: AssignmentRequest 구성<br/>(멤버, 집안일, 선호도)
Service->>FastAPI: POST 요청<br/>(AssignmentRequest)
FastAPI-->>Service: AssignmentResponse<br/>(배정 결과 리스트)
Service->>Repository: 배정 결과 반복<br/>처리 및 저장
Repository->>Database: Chore 엔티티<br/>배치 저장
Service-->>Controller: 완료
Controller-->>Client: HTTP 200<br/>ApiResponse(성공)
Estimated code review effort🎯 4 (Complex) | ⏱️ ~60 minutes Possibly related PRs
Poem
🚥 Pre-merge checks | ❌ 3❌ Failed checks (3 warnings)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
⚔️ Resolve merge conflicts
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Actionable comments posted: 15
🧹 Nitpick comments (16)
.github/workflows/cicd.yml (4)
1-9: 동시 실행 제어(concurrency) 설정 누락동일 브랜치에 빠르게 연속 푸시가 발생하면 여러 워크플로우가 동시에 실행되어 배포 충돌이 발생할 수 있습니다.
♻️ concurrency 설정 추가 제안
name: Partition CI/CD on: push: branches: [ main, develop ] +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + jobs: build-and-push:🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In @.github/workflows/cicd.yml around lines 1 - 9, Add a concurrency policy to the workflow to prevent overlapping runs for the same branch: under the top-level workflow (near "name: Partition CI/CD") add a concurrency stanza that defines a unique group (e.g., use github.ref or "${{ github.workflow }}-${{ github.ref }}" to scope by branch/workflow) and set cancel-in-progress: true so newer pushes cancel earlier runs; this ensures the job "build-and-push" will not run concurrently for the same branch.
51-61: 배포 후 헬스체크 및 오래된 이미지 정리 추가 권장현재 스크립트는 컨테이너 시작 후 정상 동작 여부를 확인하지 않으며, 오래된 Docker 이미지가 계속 쌓여 디스크 공간을 차지할 수 있습니다.
♻️ 헬스체크 및 정리 스크립트 추가
# 최신 이미지 받아서 컨테이너 재시작 sudo docker compose pull sudo docker compose up -d + + # 오래된 이미지 정리 + sudo docker image prune -f + + # 헬스체크 (예: 30초 대기 후 확인) + sleep 30 + curl -f http://localhost:8080/actuator/health || exit 1🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In @.github/workflows/cicd.yml around lines 51 - 61, After pulling and starting containers (the script block that runs `sudo docker compose pull` and `sudo docker compose up -d`), add a health-check loop that queries container/service status (e.g., `docker compose ps` or `docker inspect --format '{{.State.Health.Status}}'` for services with HEALTHCHECK) and waits with timeout/retries until all services are healthy, failing the job if not; then run cleanup commands (`sudo docker image prune -af` and optionally `sudo docker system prune -af`) to remove old unused images and reclaim disk space. Ensure to reference the same script block and the `git checkout ${{ github.ref_name }}` / `git pull` flow so the checks run immediately after `docker compose up -d` and before finishing the CI job.
54-57: GitHub 표현식 사용 시 보안 고려
${{ github.ref_name }}이 셸 스크립트에 직접 삽입됩니다. 현재는branches필터로main과develop만 허용되어 위험이 낮지만, 환경 변수로 전달하는 것이 더 안전한 패턴입니다.♻️ 환경 변수 사용 패턴
deploy: needs: build-and-push runs-on: ubuntu-latest + env: + BRANCH_NAME: ${{ github.ref_name }} steps: - name: Deploy to EC2 via SSH uses: appleboy/ssh-action@v1.1.0 with: host: ${{ secrets.EC2_HOST }} username: ubuntu key: ${{ secrets.EC2_PRIVATE_KEY }} + envs: BRANCH_NAME script: | cd /home/ubuntu/partition # 지금 푸시된 브랜치 기준으로 체크아웃 (main or develop) git fetch origin - git checkout ${{ github.ref_name }} - git pull origin ${{ github.ref_name }} + git checkout "$BRANCH_NAME" + git pull origin "$BRANCH_NAME"🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In @.github/workflows/cicd.yml around lines 54 - 57, Replace direct interpolation of the GitHub expression github.ref_name into the shell commands by assigning it to an environment variable and using that env var in the commands; set e.g. REF_NAME="${{ github.ref_name }}" in the job/step env and then use "$REF_NAME" in the git checkout/git pull commands (and keep the existing branches filter). This prevents untrusted expression content from being injected into the shell while preserving the current behavior that checks out the pushed branch.
15-25: Gradle 의존성 캐싱 추가 권장Gradle 의존성을 캐싱하면 빌드 시간을 크게 단축할 수 있습니다.
♻️ Gradle 캐싱 설정 추가
- name: Set up JDK 21 uses: actions/setup-java@v4 with: java-version: '21' distribution: 'temurin' + cache: gradle - name: Grant execute permission for gradlew run: chmod +x gradlew🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In @.github/workflows/cicd.yml around lines 15 - 25, Add a Gradle dependency cache step to the CI workflow to speed builds: insert a job step named like "Cache Gradle dependencies" before running ./gradlew clean build -x test that uses actions/cache@v3 (or similar) to cache Gradle directories (e.g., ~/.gradle/caches and ~/.gradle/wrapper) with a cache key derived from the OS, Gradle version and relevant files (e.g., gradle-wrapper.properties, build.gradle(.kts), settings.gradle(.kts)) and include a restore-keys fallback; ensure this step sits before the "Build jar" run that invokes ./gradlew so cached files are available for the build.src/main/java/com/partition/domain/schedule/repository/ScheduleRepository.java (1)
15-20:findAllByHouseholdIdAndDateRange에JOIN FETCH누락으로 인한 잠재적 N+1 문제.
findAllByHouseholdIdAndDate(line 23)에서는JOIN FETCH s.user를 사용하지만, 이 메서드에서는 사용하지 않습니다. 만약 호출 측에서 반환된Schedule의user필드에 접근한다면 N+1 쿼리 문제가 발생할 수 있습니다.월간 캘린더 조회 시 사용자 정보에 접근하지 않는다면 현재 구현도 괜찮지만, 일관성을 위해
JOIN FETCH를 추가하는 것을 권장합니다.♻️ 제안하는 수정
- `@Query`("SELECT s FROM Schedule s WHERE s.user.householdId = :householdId AND s.date BETWEEN :startDate AND :endDate") + `@Query`("SELECT s FROM Schedule s JOIN FETCH s.user WHERE s.user.householdId = :householdId AND s.date BETWEEN :startDate AND :endDate") List<Schedule> findAllByHouseholdIdAndDateRange(🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/main/java/com/partition/domain/schedule/repository/ScheduleRepository.java` around lines 15 - 20, findAllByHouseholdIdAndDateRange에서 Schedule.user에 대한 지연로딩으로 N+1 문제가 발생할 수 있으므로 쿼리에 JOIN FETCH를 추가하세요: 수정 대상은 메서드 findAllByHouseholdIdAndDateRange(파라미터 householdId, startDate, endDate)이며 JPQL에서 "SELECT s FROM Schedule s" 부분에 "JOIN FETCH s.user"를 포함해 findAllByHouseholdIdAndDate와 동일한 동작으로 만드세요.src/main/java/com/partition/domain/preference/repository/UserChorePreferenceRepository.java (1)
14-16: Repository 레벨의@Transactional제거를 권장합니다.
UserPreferenceService.savePreferences()가 이미@Transactional로 선언되어 있으므로 (src/main/java/com/partition/domain/preference/service/UserPreferenceService.java:24 참조), repository 메서드에서 별도로 트랜잭션을 선언할 필요가 없습니다. 서비스 계층에서 트랜잭션을 관리하는 것이 일반적인 패턴입니다.♻️ 수정 제안
// 유저의 기존 선호도 데이터를 모두 삭제 (재등록 시 초기화용) `@Modifying` - `@Transactional` + `@Modifying`(clearAutomatically = true) void deleteByUserId(Long userId);참고:
clearAutomatically = true를 추가하면 삭제 후 영속성 컨텍스트가 자동으로 클리어되어 후속 조회에서 최신 데이터를 보장합니다.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/main/java/com/partition/domain/preference/repository/UserChorePreferenceRepository.java` around lines 14 - 16, Remove the repository-level transaction: in UserChorePreferenceRepository keep the `@Modifying` on the deleteByUserId(Long userId) method but remove the `@Transactional` annotation so transactions are managed by the service layer (UserPreferenceService.savePreferences()); additionally, add clearAutomatically = true to the `@Modifying` annotation to ensure the persistence context is cleared after delete and subsequent reads return fresh data.src/main/java/com/partition/domain/chore/repository/ChoreRepository.java (1)
13-18: N+1 문제 가능성 검토 권장일간 조회용 쿼리(Line 21)는
JOIN FETCH를 사용하지만, 월간 조회용 쿼리는 사용하지 않습니다. 만약 반환된Chore목록에서assignee정보에 접근한다면 N+1 쿼리 문제가 발생할 수 있습니다.♻️ 일관성을 위한 수정 제안
CalendarService에서 assignee 정보를 사용하지 않는다면 현재 상태로 유지해도 됩니다. 사용한다면:
- `@Query`("SELECT c FROM Chore c WHERE c.assignee.householdId = :householdId AND c.date BETWEEN :startDate AND :endDate") + `@Query`("SELECT c FROM Chore c JOIN FETCH c.assignee WHERE c.assignee.householdId = :householdId AND c.date BETWEEN :startDate AND :endDate") List<Chore> findAllByHouseholdIdAndDateRange(🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/main/java/com/partition/domain/chore/repository/ChoreRepository.java` around lines 13 - 18, The monthly query method findAllByHouseholdIdAndDateRange can trigger an N+1 when accessing Chore.assignee; update its `@Query` to eagerly fetch the assignee (e.g., use "SELECT c FROM Chore c JOIN FETCH c.assignee WHERE c.assignee.householdId = :householdId AND c.date BETWEEN :startDate AND :endDate") or provide a separate fetch method used only when assignee data is needed, ensuring consistency with the daily query that already uses JOIN FETCH.src/main/java/com/partition/entity/UtilityBill.java (2)
24-24:title필드에 nullable/length 제약 조건 추가 권장다른 엔티티들과 일관되게
title필드에 제약 조건을 명시하면 데이터 무결성이 향상됩니다.♻️ 수정 제안
- private String title; // 월세, 전기세 등 + `@Column`(nullable = false, length = 50) + private String title; // 월세, 전기세 등🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/main/java/com/partition/entity/UtilityBill.java` at line 24, Update the UtilityBill entity's title field to enforce non-null and maximum length constraints by adding appropriate JPA and validation annotations: mark the title field with `@Column`(nullable = false, length = <max>) to enforce DB-level constraints and add a bean validation annotation such as `@NotBlank` and/or `@Size`(max = <max>) to enforce input-level limits; this change should be applied to the title field declaration in the UtilityBill class so it matches other entities' constraints and improves data integrity.
10-14: 다른 엔티티와의 일관성 검토 권장
User엔티티는BaseEntity를 상속하여 audit 필드(createdAt, updatedAt 등)를 갖고 있는데,UtilityBill은 상속하지 않습니다. 공과금 기록에 대한 생성/수정 시간 추적이 필요할 수 있습니다.♻️ 일관성을 위한 수정 제안
`@Entity` `@Getter` `@Table`(name = "utility_bills") `@NoArgsConstructor`(access = AccessLevel.PROTECTED) -public class UtilityBill { +public class UtilityBill extends BaseEntity {또한 엔티티 생성을 위한
@Builder패턴 추가를 고려하세요.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/main/java/com/partition/entity/UtilityBill.java` around lines 10 - 14, UtilityBill currently lacks the audit fields present in User because it doesn't extend BaseEntity; update the UtilityBill class to extend BaseEntity so it inherits createdAt/updatedAt (or alternatively add the equivalent audit fields/annotations directly), and add a `@Builder` annotation (and corresponding all-args constructor or `@Builder-annotated` constructor) to enable consistent construction; refer to the User entity and BaseEntity for the exact field/annotation patterns and to the UtilityBill class name to locate where to apply these changes.src/main/java/com/partition/domain/calender/dto/response/CalendarMonthlyResponse.java (1)
1-1: 패키지명 오타:calender→calendar패키지명에 오타가 있습니다.
calender는calendar의 잘못된 철자입니다. 이 오타는 관련된 모든 파일(controller,service,dto등)에서 일관되게 수정되어야 합니다.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/main/java/com/partition/domain/calender/dto/response/CalendarMonthlyResponse.java` at line 1, The package name is misspelled as "calender"; update the package declaration and any import/usage to "calendar" across the codebase (e.g., change package com.partition.domain.calender.dto.response to com.partition.domain.calendar.dto.response), adjust related files that reference classes under com.partition.domain.calender (controllers, services, dto classes like CalendarMonthlyResponse), and update build/config entries or module references if any so all references consistently use "calendar".src/main/java/com/partition/entity/Chore.java (1)
22-24:assignee가 필수 필드인지 확인이 필요합니다.
@JoinColumn에nullable속성이 지정되지 않았습니다. 담당자가 항상 존재해야 한다면nullable = false를 명시하여 데이터 무결성을 보장하는 것이 좋습니다.💡 nullable 속성 명시 제안
`@ManyToOne`(fetch = FetchType.LAZY) - `@JoinColumn`(name = "user_id") + `@JoinColumn`(name = "user_id", nullable = false) private User assignee; // 담당자🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/main/java/com/partition/entity/Chore.java` around lines 22 - 24, Confirm whether Chore.assignee must always be present; if so, update the mapping on the assignee field (the `@JoinColumn` on the assignee property in class Chore) to include nullable = false to enforce DB-level integrity, and add a validation annotation (e.g., `@NotNull`) on the assignee field to enforce it at the application layer; if assignee can be optional, explicitly set nullable = true to document intent.src/main/java/com/partition/domain/calender/controller/CalendarController.java (1)
29-35:month는 컨트롤러 경계에서 1..12로 막아 주세요.Line 31-35는
month=0이나13도 그대로 서비스로 전달합니다. 월간 조회 API라면 이런 값은 요청 경계에서 바로 400으로 끊는 편이 응답 일관성과 디버깅에 유리합니다.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/main/java/com/partition/domain/calender/controller/CalendarController.java` around lines 29 - 35, 현재 getMonthlyCalendar 컨트롤러가 `@RequestParam` int month 값을 1..12 범위 검사 없이 calendarService.getMonthlyCalendar(userId, year, month)로 넘기고 있으니, 컨트롤러 경계에서 month가 1에서 12 사이인지 검사하여 범위를 벗어나면 400 응답을 반환하도록 수정하세요; 구체적으로 CalendarController#getMonthlyCalendar 내부에서 month 체크를 추가하고(예: if (month < 1 || month > 12) ...) 적절한 ResponseEntity.badRequest() 또는 ApiResponse 오류 바디를 반환해서 잘못된 파라미터가 서비스 레이어로 전달되지 않게 하세요.src/main/java/com/partition/domain/household/controller/HouseholdController.java (1)
30-49: 응답을Map<String, Object>로 두면 계약이 약해집니다.Line 30-49과 Line 57-74가 서로 다른 shape를 같은
Map<String, Object>로 반환하고 있어서, 문서화와 클라이언트 타입 안정성이 같이 약해집니다. 생성 응답/참여 응답 DTO를 따로 두면 이후 필드 변경 때 훨씬 안전합니다.Also applies to: 57-74
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/main/java/com/partition/domain/household/controller/HouseholdController.java` around lines 30 - 49, Replace the loose Map<String,Object> payloads with explicit response DTOs: create a CreateHouseholdResponse (fields: Long householdId, String inviteCode) and a JoinHouseholdResponse (fields matching the join endpoint) and update HouseholdController#createHousehold and the join handler to return ResponseEntity<ApiResponse<CreateHouseholdResponse>> and ResponseEntity<ApiResponse<JoinHouseholdResponse>> respectively, populating the new DTOs from Household (use household.getId(), household.getInviteCode(), etc.) and adjust imports/signatures so the API contract is strongly typed instead of using Map<String,Object>.src/main/java/com/partition/domain/chore/service/ChoreAssignmentService.java (1)
89-105: 배치 저장으로 성능 개선 고려현재 루프 내에서 개별
choreRepository.save(chore)를 호출하고 있습니다. 배정 건수가 많을 경우saveAll()을 사용하면 DB 라운드트립을 줄이고 성능을 개선할 수 있습니다.♻️ saveAll 사용 제안
+ List<Chore> choresToSave = new ArrayList<>(); + for (AssignmentResponse.AssignmentResult result : response.getAssignments()) { User assignee = userMap.get(result.getUserId()); HouseholdChore hhChore = choreMap.get(result.getChoreId()); if (assignee == null || hhChore == null) { log.warn("유효하지 않은 배정 결과 건너뜀: userId={}, choreId={}", result.getUserId(), result.getChoreId()); continue; } Chore chore = Chore.builder() .assignee(assignee) .type(hhChore.getChoreType()) .date(result.getDate()) .build(); - choreRepository.save(chore); + choresToSave.add(chore); } + + choreRepository.saveAll(choresToSave);🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/main/java/com/partition/domain/chore/service/ChoreAssignmentService.java` around lines 89 - 105, The loop in process of AssignmentResponse.AssignmentResult currently calls choreRepository.save(chore) per iteration causing many DB round trips; instead, collect valid Chore instances into a List (using the existing userMap, choreMap lookups and Chore.builder(...) with assignee, type, date) and after the loop call choreRepository.saveAll(collectedChores); ensure you skip null lookups exactly as done now, handle the case of an empty list (no-op), and keep this logic inside the same method that iterates response.getAssignments() so the change is local to that flow.src/main/java/com/partition/domain/schedule/controller/ScheduleController.java (1)
42-55: 빈 업데이트 요청 허용 여부 확인
updateSchedule에서@Valid가 없고ScheduleUpdateRequest에 검증 어노테이션이 없는 것은 부분 업데이트를 위한 의도적인 설계로 보입니다. 다만,content와date모두null인 빈 요청도 허용되어 불필요한 DB 조회와 트랜잭션이 발생할 수 있습니다.최소한 하나의 필드는 제공되어야 한다는 커스텀 검증 추가를 고려해 보세요.
♻️ 커스텀 검증 추가 예시 (ScheduleUpdateRequest)
`@AssertTrue`(message = "수정할 필드가 최소 하나 이상 필요합니다.") public boolean isValid() { return content != null || date != null; }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/main/java/com/partition/domain/schedule/controller/ScheduleController.java` around lines 42 - 55, updateSchedule currently accepts ScheduleUpdateRequest with no validation so empty partial-update requests (both content and date null) trigger unnecessary DB work; add a class-level custom validator to ScheduleUpdateRequest (e.g., an `@AssertTrue` method like isValid()) that returns true only when at least one updatable field (content or date) is non-null, annotate the request object parameter in updateSchedule with `@Valid`, and ensure method signature uses `@Valid` ScheduleUpdateRequest to enforce the new validation so requests with both fields null are rejected before calling scheduleService.updateSchedule.src/main/java/com/partition/domain/calender/service/CalendarService.java (1)
94-103: Schedule의 user 접근 시 null 처리 일관성 고려Lines 84-88에서
chore.getAssignee()는Optional.ofNullable로 방어적으로 처리하고 있으나, Line 100에서schedule.getUser().getName()은 직접 접근하고 있습니다.Schedule엔티티의user필드가nullable = false로 선언되어 있어 현재는 안전하지만, 코드 일관성과 방어적 프로그래밍을 위해 동일한 패턴 적용을 권장합니다.♻️ 일관된 null 처리 제안
List<CalendarDailyResponse> schedules = scheduleRepository.findAllByHouseholdIdAndDateRange(householdId, date, date) .stream() .map(schedule -> CalendarDailyResponse.builder() .category("SCHEDULE") .id(schedule.getId()) .title(schedule.getContent()) - .assigneeName(schedule.getUser().getName()) + .assigneeName( + Optional.ofNullable(schedule.getUser()) + .map(User::getName) + .orElse(null) + ) .isCompleted(false) .build()) .toList();🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/main/java/com/partition/domain/calender/service/CalendarService.java` around lines 94 - 103, The mapping of schedules in CalendarService uses schedule.getUser().getName() directly; make it defensive and consistent with the chore path by null-checking the schedule user before accessing its name (e.g., use Optional.ofNullable(schedule.getUser()).map(User::getName).orElse("") or equivalent) when building CalendarDailyResponse in the stream returned by scheduleRepository.findAllByHouseholdIdAndDateRange so the CalendarDailyResponse.builder receives a safe assigneeName even if schedule.getUser() is null.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In @.github/workflows/cicd.yml:
- Around line 33-38: The workflow currently pushes the Docker image only with
the static tag "latest" using docker/build-push-action@v6; update the tags entry
to include a versioned tag (e.g., commit SHA or timestamp) in addition to latest
so deployments are traceable and rollbacks are possible. Modify the tags input
for the build-push action to emit at least two tags such as ${{
secrets.DOCKERHUB_USERNAME }}/partition-spring:latest and ${{
secrets.DOCKERHUB_USERNAME }}/partition-spring:${{ github.sha }} (or use ${{
github.run_number }} / a timestamp) so each build is uniquely identifiable while
preserving the latest tag.
- Around line 24-25: The CI step named "Build jar" currently skips tests by
using the Gradle flag "./gradlew clean build -x test"; remove the "-x test" and
instead add a distinct workflow job or step that runs Gradle tests (e.g.,
"./gradlew test" or "./gradlew check") before the build step so all test suites
execute in CI; update the "Build jar" step to run the full build without
excluding tests and ensure the new test step fails the pipeline on test
failures.
In `@src/main/java/com/partition/domain/auth/exception/AuthErrorCode.java`:
- Around line 11-18: AuthErrorCode enum is defined but unused; update
AuthService.kakaoLogin() and JwtTokenProvider.validateToken() to throw
CustomException with the appropriate AuthErrorCode values instead of raw
IllegalArgumentException/RuntimeException or only logging; import AuthErrorCode
and replace each generic throw/log path with new
CustomException(AuthErrorCode.EMPTY_ACCESS_TOKEN / INVALID_ACCESS_TOKEN /
EXPIRED_ACCESS_TOKEN / EMPTY_REFRESH_TOKEN / INVALID_REFRESH_TOKEN /
EXPIRED_REFRESH_TOKEN / KAKAO_LOGIN_FAILED) as applicable so token validation
and Kakao-login failures follow the same CustomException(ErrorCode) pattern used
by UserService and ScheduleService.
In `@src/main/java/com/partition/domain/chore/controller/ChoreController.java`:
- Around line 35-43: Validate and cap the requested periodDays in
ChoreController before calling assignmentService.assignChores: compute
periodDays from startDate and endDate, check that it is between 1 and a defined
MAX_PERIOD_DAYS (introduce a constant like MAX_PERIOD_DAYS), and if it exceeds
the max throw an IllegalArgumentException or return a 400-level error with a
clear message; do this validation right after computing periodDays and before
invoking assignmentService.assignChores so overly large ranges are
rejected/limited early.
In `@src/main/java/com/partition/domain/chore/exception/ChoreErrorCode.java`:
- Line 11: Change the HTTP status for the HOUSEHOLD_ID_MISMATCH constant in
ChoreErrorCode from 401 to a more appropriate code; update the enum constant
HOUSEHOLD_ID_MISMATCH(401, ...) to use 403 (Forbidden) if the user is
authenticated but not allowed to access another household, or to 400 (Bad
Request) if the error represents a malformed/invalid household ID—pick the
correct semantics for your domain and update the numeric status and any related
tests/messages accordingly.
In
`@src/main/java/com/partition/domain/chore/repository/HouseholdChoreRepository.java`:
- Around line 8-10: HouseholdChore references a missing Household entity which
breaks the derived query findByHouseholdId; add a JPA entity class named
Household (e.g., class Household with `@Entity`, an id Long primary key, and any
minimal fields) so the `@ManyToOne` relationship in HouseholdChore can resolve,
ensure the class is in the same project package (e.g., com.partition.entity) and
includes proper JPA annotations (`@Entity`, `@Table` if needed, `@Id`,
`@GeneratedValue`) and getters/setters so Spring Data JPA can execute the derived
repository method findByHouseholdId.
In
`@src/main/java/com/partition/domain/chore/service/ChoreAssignmentService.java`:
- Around line 65-74: The RestTemplate bean in AppConfig is created without
timeouts, causing potential indefinite blocking when
ChoreAssignmentService.postForObject() calls FASTAPI_URL; update the
RestTemplate bean (the method that returns a RestTemplate in AppConfig) to use a
ClientHttpRequestFactory (e.g., HttpComponentsClientHttpRequestFactory or
SimpleClientHttpRequestFactory), set sensible connectTimeout and readTimeout
values on that factory, and construct the RestTemplate with that factory so the
restTemplate injected into ChoreAssignmentService will time out instead of
blocking indefinitely.
In
`@src/main/java/com/partition/domain/preference/service/UserPreferenceService.java`:
- Around line 24-46: Concurrent requests can interleave the deleteByUserId +
save loop in savePreferences causing mixed final data; fix by serializing
per-user updates—either acquire a DB row lock on the user before deleting (e.g.,
change the user load in savePreferences to a locked load via userRepository
method annotated with `@Lock`(LockModeType.PESSIMISTIC_WRITE) or a custom
findByIdForUpdate(userId) so the deleteByUserId and subsequent saves occur under
the same DB lock), or implement optimistic locking by adding a `@Version` field to
the relevant entity (User or UserChorePreference) and handling
OptimisticLockException with a retry; update savePreferences to use the
locked/optimistic-aware load and then perform
preferenceRepository.deleteByUserId(...) and the save loop
(UserChorePreference.builder(...) / preferenceRepository.save(...)) under that
locking strategy.
In `@src/main/java/com/partition/domain/schedule/service/ScheduleService.java`:
- Around line 49-50: The Schedule.update call is overwriting the entity's time
with null because Schedule.update assigns time unconditionally; either preserve
existing time or make time explicit: update Schedule.update to only set
this.time = time when time != null (or provide a separate setter), or add a time
field to ScheduleUpdateRequest and pass request.getTime() instead of null from
ScheduleService where schedule.update(request.getContent(), request.getDate(),
null) is called; also update the comment to reflect the true behavior if you
intend null to clear time.
In `@src/main/java/com/partition/domain/user/service/UserService.java`:
- Around line 22-23: The code calls user.updateName(newName) but
User.updateName(String) doesn't exist; either add a simple mutator in the User
entity (add a public void updateName(String name) that sets this.name = name) or
change the service to reuse the existing updateProfile method (call
user.updateProfile(newName, user.getProfileImage() or otherwise supply the
current profileImage) from UserService instead of updateName); update whichever
of User (the entity class) or UserService (where user.updateName is invoked) you
choose so the symbols User.updateName or User.updateProfile are used
consistently.
In
`@src/main/java/com/partition/domain/utilitybill/repository/UtilityBillRepository.java`:
- Around line 11-12: The repository method in UtilityBillRepository uses a
non-existent householdId property on UtilityBill; update the derived query to
target the relationship ID (e.g., rename findAllByHouseholdIdAndDueDateBetween
to findAllByHousehold_IdAndDueDateBetween) or replace it with an explicit `@Query`
on the UtilityBill entity that joins/filters by household.id and dueDate between
the given LocalDate params so Spring Data JPA resolves the relation correctly.
In `@src/main/java/com/partition/entity/Chore.java`:
- Around line 31-39: Add a public mutator on the Chore entity to allow changing
the isCompleted state: implement a method such as markCompleted() (and
optionally markIncomplete() or setCompleted(boolean)) that updates the private
boolean isCompleted and any related invariants; locate the field isCompleted and
the constructor in class Chore and add the new method(s) there so callers can
set the chore as completed.
In `@src/main/java/com/partition/entity/HouseholdChore.java`:
- Around line 28-49: The difficulty range (1-5) is only documented in a comment
but not enforced; add validation inside the HouseholdChore constructor and
inside updateDifficulty(Integer difficulty) to check that difficulty is non-null
and between 1 and 5 and throw an IllegalArgumentException (with a clear message)
if it is out of range so invalid values are rejected at the entity level.
In `@src/main/java/com/partition/entity/Schedule.java`:
- Around line 44-49: The Schedule.time field is always set to null because
ScheduleService calls schedule.update(request.getContent(), request.getDate(),
null); change the service to pass the incoming DTO time value (e.g.,
request.getTime() or the correct getter on the request DTO) into
schedule.update, or if the DTO lacks a time field, add it to the request DTO and
use that getter; keep Schedule.update(String content, LocalDate date, LocalTime
time) behavior unchanged so null can still be used to clear the time when
intended.
In `@src/main/java/com/partition/entity/UserChorePreference.java`:
- Around line 10-32: 엔티티 UserChorePreference에 (user_id, chore_type) 복합 유니크 제약이
없어 동일 유저/동일 집안일 유형의 중복 레코드가 허용됩니다; `@Table` 어노테이션(클래스 UserChorePreference)을 수정해
uniqueConstraints = {`@UniqueConstraint`(columnNames = {"user_id",
"chore_type"})}를 추가하여 DB 레벨에서 유니크 제약을 걸고 마이그레이션/스키마 업데이트를 반영하세요.
---
Nitpick comments:
In @.github/workflows/cicd.yml:
- Around line 1-9: Add a concurrency policy to the workflow to prevent
overlapping runs for the same branch: under the top-level workflow (near "name:
Partition CI/CD") add a concurrency stanza that defines a unique group (e.g.,
use github.ref or "${{ github.workflow }}-${{ github.ref }}" to scope by
branch/workflow) and set cancel-in-progress: true so newer pushes cancel earlier
runs; this ensures the job "build-and-push" will not run concurrently for the
same branch.
- Around line 51-61: After pulling and starting containers (the script block
that runs `sudo docker compose pull` and `sudo docker compose up -d`), add a
health-check loop that queries container/service status (e.g., `docker compose
ps` or `docker inspect --format '{{.State.Health.Status}}'` for services with
HEALTHCHECK) and waits with timeout/retries until all services are healthy,
failing the job if not; then run cleanup commands (`sudo docker image prune -af`
and optionally `sudo docker system prune -af`) to remove old unused images and
reclaim disk space. Ensure to reference the same script block and the `git
checkout ${{ github.ref_name }}` / `git pull` flow so the checks run immediately
after `docker compose up -d` and before finishing the CI job.
- Around line 54-57: Replace direct interpolation of the GitHub expression
github.ref_name into the shell commands by assigning it to an environment
variable and using that env var in the commands; set e.g. REF_NAME="${{
github.ref_name }}" in the job/step env and then use "$REF_NAME" in the git
checkout/git pull commands (and keep the existing branches filter). This
prevents untrusted expression content from being injected into the shell while
preserving the current behavior that checks out the pushed branch.
- Around line 15-25: Add a Gradle dependency cache step to the CI workflow to
speed builds: insert a job step named like "Cache Gradle dependencies" before
running ./gradlew clean build -x test that uses actions/cache@v3 (or similar) to
cache Gradle directories (e.g., ~/.gradle/caches and ~/.gradle/wrapper) with a
cache key derived from the OS, Gradle version and relevant files (e.g.,
gradle-wrapper.properties, build.gradle(.kts), settings.gradle(.kts)) and
include a restore-keys fallback; ensure this step sits before the "Build jar"
run that invokes ./gradlew so cached files are available for the build.
In
`@src/main/java/com/partition/domain/calender/controller/CalendarController.java`:
- Around line 29-35: 현재 getMonthlyCalendar 컨트롤러가 `@RequestParam` int month 값을
1..12 범위 검사 없이 calendarService.getMonthlyCalendar(userId, year, month)로 넘기고 있으니,
컨트롤러 경계에서 month가 1에서 12 사이인지 검사하여 범위를 벗어나면 400 응답을 반환하도록 수정하세요; 구체적으로
CalendarController#getMonthlyCalendar 내부에서 month 체크를 추가하고(예: if (month < 1 ||
month > 12) ...) 적절한 ResponseEntity.badRequest() 또는 ApiResponse 오류 바디를 반환해서 잘못된
파라미터가 서비스 레이어로 전달되지 않게 하세요.
In
`@src/main/java/com/partition/domain/calender/dto/response/CalendarMonthlyResponse.java`:
- Line 1: The package name is misspelled as "calender"; update the package
declaration and any import/usage to "calendar" across the codebase (e.g., change
package com.partition.domain.calender.dto.response to
com.partition.domain.calendar.dto.response), adjust related files that reference
classes under com.partition.domain.calender (controllers, services, dto classes
like CalendarMonthlyResponse), and update build/config entries or module
references if any so all references consistently use "calendar".
In `@src/main/java/com/partition/domain/calender/service/CalendarService.java`:
- Around line 94-103: The mapping of schedules in CalendarService uses
schedule.getUser().getName() directly; make it defensive and consistent with the
chore path by null-checking the schedule user before accessing its name (e.g.,
use Optional.ofNullable(schedule.getUser()).map(User::getName).orElse("") or
equivalent) when building CalendarDailyResponse in the stream returned by
scheduleRepository.findAllByHouseholdIdAndDateRange so the
CalendarDailyResponse.builder receives a safe assigneeName even if
schedule.getUser() is null.
In `@src/main/java/com/partition/domain/chore/repository/ChoreRepository.java`:
- Around line 13-18: The monthly query method findAllByHouseholdIdAndDateRange
can trigger an N+1 when accessing Chore.assignee; update its `@Query` to eagerly
fetch the assignee (e.g., use "SELECT c FROM Chore c JOIN FETCH c.assignee WHERE
c.assignee.householdId = :householdId AND c.date BETWEEN :startDate AND
:endDate") or provide a separate fetch method used only when assignee data is
needed, ensuring consistency with the daily query that already uses JOIN FETCH.
In
`@src/main/java/com/partition/domain/chore/service/ChoreAssignmentService.java`:
- Around line 89-105: The loop in process of AssignmentResponse.AssignmentResult
currently calls choreRepository.save(chore) per iteration causing many DB round
trips; instead, collect valid Chore instances into a List (using the existing
userMap, choreMap lookups and Chore.builder(...) with assignee, type, date) and
after the loop call choreRepository.saveAll(collectedChores); ensure you skip
null lookups exactly as done now, handle the case of an empty list (no-op), and
keep this logic inside the same method that iterates response.getAssignments()
so the change is local to that flow.
In
`@src/main/java/com/partition/domain/household/controller/HouseholdController.java`:
- Around line 30-49: Replace the loose Map<String,Object> payloads with explicit
response DTOs: create a CreateHouseholdResponse (fields: Long householdId,
String inviteCode) and a JoinHouseholdResponse (fields matching the join
endpoint) and update HouseholdController#createHousehold and the join handler to
return ResponseEntity<ApiResponse<CreateHouseholdResponse>> and
ResponseEntity<ApiResponse<JoinHouseholdResponse>> respectively, populating the
new DTOs from Household (use household.getId(), household.getInviteCode(), etc.)
and adjust imports/signatures so the API contract is strongly typed instead of
using Map<String,Object>.
In
`@src/main/java/com/partition/domain/preference/repository/UserChorePreferenceRepository.java`:
- Around line 14-16: Remove the repository-level transaction: in
UserChorePreferenceRepository keep the `@Modifying` on the deleteByUserId(Long
userId) method but remove the `@Transactional` annotation so transactions are
managed by the service layer (UserPreferenceService.savePreferences());
additionally, add clearAutomatically = true to the `@Modifying` annotation to
ensure the persistence context is cleared after delete and subsequent reads
return fresh data.
In
`@src/main/java/com/partition/domain/schedule/controller/ScheduleController.java`:
- Around line 42-55: updateSchedule currently accepts ScheduleUpdateRequest with
no validation so empty partial-update requests (both content and date null)
trigger unnecessary DB work; add a class-level custom validator to
ScheduleUpdateRequest (e.g., an `@AssertTrue` method like isValid()) that returns
true only when at least one updatable field (content or date) is non-null,
annotate the request object parameter in updateSchedule with `@Valid`, and ensure
method signature uses `@Valid` ScheduleUpdateRequest to enforce the new validation
so requests with both fields null are rejected before calling
scheduleService.updateSchedule.
In
`@src/main/java/com/partition/domain/schedule/repository/ScheduleRepository.java`:
- Around line 15-20: findAllByHouseholdIdAndDateRange에서 Schedule.user에 대한 지연로딩으로
N+1 문제가 발생할 수 있으므로 쿼리에 JOIN FETCH를 추가하세요: 수정 대상은 메서드
findAllByHouseholdIdAndDateRange(파라미터 householdId, startDate, endDate)이며 JPQL에서
"SELECT s FROM Schedule s" 부분에 "JOIN FETCH s.user"를 포함해
findAllByHouseholdIdAndDate와 동일한 동작으로 만드세요.
In `@src/main/java/com/partition/entity/Chore.java`:
- Around line 22-24: Confirm whether Chore.assignee must always be present; if
so, update the mapping on the assignee field (the `@JoinColumn` on the assignee
property in class Chore) to include nullable = false to enforce DB-level
integrity, and add a validation annotation (e.g., `@NotNull`) on the assignee
field to enforce it at the application layer; if assignee can be optional,
explicitly set nullable = true to document intent.
In `@src/main/java/com/partition/entity/UtilityBill.java`:
- Line 24: Update the UtilityBill entity's title field to enforce non-null and
maximum length constraints by adding appropriate JPA and validation annotations:
mark the title field with `@Column`(nullable = false, length = <max>) to enforce
DB-level constraints and add a bean validation annotation such as `@NotBlank`
and/or `@Size`(max = <max>) to enforce input-level limits; this change should be
applied to the title field declaration in the UtilityBill class so it matches
other entities' constraints and improves data integrity.
- Around line 10-14: UtilityBill currently lacks the audit fields present in
User because it doesn't extend BaseEntity; update the UtilityBill class to
extend BaseEntity so it inherits createdAt/updatedAt (or alternatively add the
equivalent audit fields/annotations directly), and add a `@Builder` annotation
(and corresponding all-args constructor or `@Builder-annotated` constructor) to
enable consistent construction; refer to the User entity and BaseEntity for the
exact field/annotation patterns and to the UtilityBill class name to locate
where to apply these changes.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
Run ID: 8400ec89-5eaf-40a9-8d83-87a921406f31
📒 Files selected for processing (39)
.github/workflows/cicd.ymlsrc/main/java/com/partition/domain/auth/exception/AuthErrorCode.javasrc/main/java/com/partition/domain/calender/controller/CalendarController.javasrc/main/java/com/partition/domain/calender/dto/response/CalendarDailyResponse.javasrc/main/java/com/partition/domain/calender/dto/response/CalendarMonthlyResponse.javasrc/main/java/com/partition/domain/calender/service/CalendarService.javasrc/main/java/com/partition/domain/chore/controller/ChoreController.javasrc/main/java/com/partition/domain/chore/dto/request/AssignmentRequest.javasrc/main/java/com/partition/domain/chore/dto/response/AssignmentResponse.javasrc/main/java/com/partition/domain/chore/exception/ChoreErrorCode.javasrc/main/java/com/partition/domain/chore/repository/ChoreRepository.javasrc/main/java/com/partition/domain/chore/repository/HouseholdChoreRepository.javasrc/main/java/com/partition/domain/chore/service/ChoreAssignmentService.javasrc/main/java/com/partition/domain/household/controller/HouseholdController.javasrc/main/java/com/partition/domain/household/dto/request/CreateHouseholdRequest.javasrc/main/java/com/partition/domain/household/dto/request/JoinHouseholdRequest.javasrc/main/java/com/partition/domain/household/exception/HouseholdErrorCode.javasrc/main/java/com/partition/domain/preference/dto/request/UserPreferenceRequest.javasrc/main/java/com/partition/domain/preference/repository/UserChorePreferenceRepository.javasrc/main/java/com/partition/domain/preference/service/UserPreferenceService.javasrc/main/java/com/partition/domain/schedule/controller/ScheduleController.javasrc/main/java/com/partition/domain/schedule/dto/request/ScheduleRequest.javasrc/main/java/com/partition/domain/schedule/dto/request/ScheduleUpdateRequest.javasrc/main/java/com/partition/domain/schedule/exception/ScheduleErrorCode.javasrc/main/java/com/partition/domain/schedule/repository/ScheduleRepository.javasrc/main/java/com/partition/domain/schedule/service/ScheduleService.javasrc/main/java/com/partition/domain/user/controller/UserController.javasrc/main/java/com/partition/domain/user/dto/request/UpdateUserRequest.javasrc/main/java/com/partition/domain/user/exception/UserErrorCode.javasrc/main/java/com/partition/domain/user/service/UserService.javasrc/main/java/com/partition/domain/utilitybill/repository/UtilityBillRepository.javasrc/main/java/com/partition/entity/Chore.javasrc/main/java/com/partition/entity/HouseholdChore.javasrc/main/java/com/partition/entity/Schedule.javasrc/main/java/com/partition/entity/UserChorePreference.javasrc/main/java/com/partition/entity/UtilityBill.javasrc/main/java/com/partition/entity/enums/ChoreType.javasrc/main/java/com/partition/global/exception/BaseErrorCode.javasrc/main/java/com/partition/global/exception/GlobalErrorCode.java
| - name: Build jar | ||
| run: ./gradlew clean build -x test |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
# 프로젝트에 테스트 코드가 존재하는지 확인
fd -e java . -p 'src/test' | head -20Repository: Partition-app/Backend
Length of output: 123
🏁 Script executed:
cat -n .github/workflows/cicd.ymlRepository: Partition-app/Backend
Length of output: 2007
🏁 Script executed:
fd -e java . -path '*/src/test/*' | wc -lRepository: Partition-app/Backend
Length of output: 252
🏁 Script executed:
fd -e java . -path '*/src/test/*'Repository: Partition-app/Backend
Length of output: 233
CI/CD 파이프라인에서 테스트가 완전히 건너뛰어지고 있습니다
-x test 플래그로 테스트가 제외되고 있으며, 워크플로우에 테스트를 실행하는 다른 단계가 없습니다. 테스트 코드가 존재함에도 불구하고 검증 없이 코드가 빌드되고 배포되고 있으므로, 별도의 테스트 단계를 추가하여 모든 테스트가 CI 단계에서 실행되도록 수정해야 합니다.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In @.github/workflows/cicd.yml around lines 24 - 25, The CI step named "Build
jar" currently skips tests by using the Gradle flag "./gradlew clean build -x
test"; remove the "-x test" and instead add a distinct workflow job or step that
runs Gradle tests (e.g., "./gradlew test" or "./gradlew check") before the build
step so all test suites execute in CI; update the "Build jar" step to run the
full build without excluding tests and ensure the new test step fails the
pipeline on test failures.
| - name: Build and push Docker image | ||
| uses: docker/build-push-action@v6 | ||
| with: | ||
| context: . | ||
| push: true | ||
| tags: ${{ secrets.DOCKERHUB_USERNAME }}/partition-spring:latest |
There was a problem hiding this comment.
🛠️ Refactor suggestion | 🟠 Major
Docker 이미지에 버전 태그 추가 필요
latest 태그만 사용하면 롤백이 어렵고 어떤 커밋이 배포되었는지 추적할 수 없습니다. 커밋 SHA나 타임스탬프 기반 태그를 추가하는 것이 좋습니다.
♻️ 버전 태그 추가 제안
- name: Build and push Docker image
uses: docker/build-push-action@v6
with:
context: .
push: true
- tags: ${{ secrets.DOCKERHUB_USERNAME }}/partition-spring:latest
+ tags: |
+ ${{ secrets.DOCKERHUB_USERNAME }}/partition-spring:latest
+ ${{ secrets.DOCKERHUB_USERNAME }}/partition-spring:${{ github.sha }}🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In @.github/workflows/cicd.yml around lines 33 - 38, The workflow currently
pushes the Docker image only with the static tag "latest" using
docker/build-push-action@v6; update the tags entry to include a versioned tag
(e.g., commit SHA or timestamp) in addition to latest so deployments are
traceable and rollbacks are possible. Modify the tags input for the build-push
action to emit at least two tags such as ${{ secrets.DOCKERHUB_USERNAME
}}/partition-spring:latest and ${{ secrets.DOCKERHUB_USERNAME
}}/partition-spring:${{ github.sha }} (or use ${{ github.run_number }} / a
timestamp) so each build is uniquely identifiable while preserving the latest
tag.
| // 21xx: Auth 관련 | ||
| EMPTY_ACCESS_TOKEN(400, "액세스 토큰이 없습니다."), | ||
| INVALID_ACCESS_TOKEN(401, "유효하지 않은 액세스 토큰입니다."), | ||
| EXPIRED_ACCESS_TOKEN(401, "액세스 토큰이 만료되었습니다."), | ||
| EMPTY_REFRESH_TOKEN(400, "리프레시 토큰이 없습니다."), | ||
| INVALID_REFRESH_TOKEN(401, "유효하지 않은 리프레시 토큰입니다."), | ||
| EXPIRED_REFRESH_TOKEN(401, "리프레시 토큰이 만료되었습니다."), | ||
| KAKAO_LOGIN_FAILED(502, "카카오 로그인에 실패했습니다."); |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
# Description: AuthErrorCode가 실제로 사용되는지 확인
# AuthErrorCode 사용처 검색
rg -n "AuthErrorCode\." --type javaRepository: Partition-app/Backend
Length of output: 47
🏁 Script executed:
#!/bin/bash
# Check if AuthErrorCode class exists and is defined
find . -name "AuthErrorCode.java" -type f
# Search for AuthErrorCode imports in Java files
rg "import.*AuthErrorCode" --type java
# Search for AuthErrorCode references (any form)
rg "AuthErrorCode" --type java
# Look for how CustomException is actually used
rg "CustomException" --type java -A 2 -B 2 | head -50
# Check AuthService for token-related error handling
find . -name "AuthService.java" -type f -exec cat {} \;Repository: Partition-app/Backend
Length of output: 12985
🏁 Script executed:
#!/bin/bash
# Check JwtTokenProvider to see token validation handling
find . -name "JwtTokenProvider.java" -type f -exec cat {} \;
# Check if there are any exception handlers/controllers that might use AuthErrorCode
rg "UserErrorCode|ScheduleErrorCode" --type java -l | head -20
# Search for token validation in filter/interceptor
find . -name "*Filter.java" -o -name "*Interceptor.java" | xargs cat 2>/dev/null | head -100Repository: Partition-app/Backend
Length of output: 6177
정의된 에러 코드가 실제로 사용되지 않고 있습니다.
코드 전체를 검토한 결과, AuthErrorCode가 정의되었지만 어디에서도 import되거나 참조되지 않습니다. AuthService.kakaoLogin()과 JwtTokenProvider.validateToken()에서는 IllegalArgumentException, RuntimeException 등 일반적인 예외를 직접 throw하거나 로그만 남기고 있으며, CustomException과 함께 AuthErrorCode를 사용하지 않습니다.
반면 UserService, ScheduleService 등 다른 서비스들은 일관되게 CustomException(ErrorCode) 패턴을 따르고 있습니다. 토큰 검증 실패 또는 카카오 로그인 실패 시 정의된 AuthErrorCode를 활용하여 일관된 에러 응답 처리를 구현하시기 바랍니다.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@src/main/java/com/partition/domain/auth/exception/AuthErrorCode.java` around
lines 11 - 18, AuthErrorCode enum is defined but unused; update
AuthService.kakaoLogin() and JwtTokenProvider.validateToken() to throw
CustomException with the appropriate AuthErrorCode values instead of raw
IllegalArgumentException/RuntimeException or only logging; import AuthErrorCode
and replace each generic throw/log path with new
CustomException(AuthErrorCode.EMPTY_ACCESS_TOKEN / INVALID_ACCESS_TOKEN /
EXPIRED_ACCESS_TOKEN / EMPTY_REFRESH_TOKEN / INVALID_REFRESH_TOKEN /
EXPIRED_REFRESH_TOKEN / KAKAO_LOGIN_FAILED) as applicable so token validation
and Kakao-login failures follow the same CustomException(ErrorCode) pattern used
by UserService and ScheduleService.
| // 시작일과 종료일 사이의 기간(일수) 계산 (종료일 포함이므로 +1) | ||
| int periodDays = (int) ChronoUnit.DAYS.between(startDate, endDate) + 1; | ||
|
|
||
| if (periodDays < 1) { | ||
| throw new IllegalArgumentException("종료일은 시작일보다 같거나 이후여야 합니다."); | ||
| } | ||
|
|
||
| // 서비스 호출 (FastAPI 통신 -> DB 저장) | ||
| assignmentService.assignChores(userId, startDate, periodDays); |
There was a problem hiding this comment.
배정 기간 상한이 없어 한 번의 호출이 너무 커질 수 있습니다.
Line 36-43은 역순 날짜만 막고 기간 길이 자체는 제한하지 않습니다. 지금 구현은 곧바로 FastAPI 호출과 결과 저장으로 이어지기 때문에, 수개월/수년 범위 요청 하나가 외부 서비스 부하와 대량 쓰기를 동시에 만들 수 있습니다. 최대 허용 기간을 명시적으로 제한해 두는 편이 안전합니다.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@src/main/java/com/partition/domain/chore/controller/ChoreController.java`
around lines 35 - 43, Validate and cap the requested periodDays in
ChoreController before calling assignmentService.assignChores: compute
periodDays from startDate and endDate, check that it is between 1 and a defined
MAX_PERIOD_DAYS (introduce a constant like MAX_PERIOD_DAYS), and if it exceeds
the max throw an IllegalArgumentException or return a 400-level error with a
clear message; do this validation right after computing periodDays and before
invoking assignmentService.assignChores so overly large ranges are
rejected/limited early.
| @AllArgsConstructor | ||
| public enum ChoreErrorCode implements BaseErrorCode { | ||
| ASSIGNMENT_API_ERROR(502, "집안일 배정 AI 응답 오류입니다."), | ||
| HOUSEHOLD_ID_MISMATCH(401, "집 ID가 일치하지 않습니다."), |
There was a problem hiding this comment.
HOUSEHOLD_ID_MISMATCH의 HTTP 상태 코드가 부적절합니다.
401 Unauthorized는 인증 실패를 나타내는 코드입니다. 사용자가 인증되었지만 다른 household에 접근하려는 경우라면 403 Forbidden이 더 적절합니다. 클라이언트가 잘못된 household ID를 전달한 경우라면 400 Bad Request가 적합할 수 있습니다.
🛠️ 상태 코드 수정 제안
- HOUSEHOLD_ID_MISMATCH(401, "집 ID가 일치하지 않습니다."),
+ HOUSEHOLD_ID_MISMATCH(403, "집 ID가 일치하지 않습니다."),📝 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.
| HOUSEHOLD_ID_MISMATCH(401, "집 ID가 일치하지 않습니다."), | |
| HOUSEHOLD_ID_MISMATCH(403, "집 ID가 일치하지 않습니다."), |
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@src/main/java/com/partition/domain/chore/exception/ChoreErrorCode.java` at
line 11, Change the HTTP status for the HOUSEHOLD_ID_MISMATCH constant in
ChoreErrorCode from 401 to a more appropriate code; update the enum constant
HOUSEHOLD_ID_MISMATCH(401, ...) to use 403 (Forbidden) if the user is
authenticated but not allowed to access another household, or to 400 (Bad
Request) if the error represents a malformed/invalid household ID—pick the
correct semantics for your domain and update the numeric status and any related
tests/messages accordingly.
| // 공과금은 Household와 직접 연관됨 | ||
| List<UtilityBill> findAllByHouseholdIdAndDueDateBetween(Long householdId, LocalDate startDate, LocalDate endDate); |
There was a problem hiding this comment.
런타임 오류: householdId 프로퍼티가 UtilityBill 엔티티에 존재하지 않습니다.
UtilityBill 엔티티는 householdId 필드가 아닌 Household household 관계를 가지고 있습니다 (src/main/java/com/partition/entity/UtilityBill.java:20-22 참조). Spring Data JPA 파생 쿼리에서 관계를 통해 ID에 접근하려면 언더스코어 표기법을 사용해야 합니다.
🐛 수정 제안: 메서드 이름 수정 또는 `@Query` 사용
방법 1: 언더스코어 표기법 사용
- List<UtilityBill> findAllByHouseholdIdAndDueDateBetween(Long householdId, LocalDate startDate, LocalDate endDate);
+ List<UtilityBill> findAllByHousehold_IdAndDueDateBetween(Long householdId, LocalDate startDate, LocalDate endDate);방법 2: @Query 어노테이션 사용 (명시적)
- List<UtilityBill> findAllByHouseholdIdAndDueDateBetween(Long householdId, LocalDate startDate, LocalDate endDate);
+ `@Query`("SELECT u FROM UtilityBill u WHERE u.household.id = :householdId AND u.dueDate BETWEEN :startDate AND :endDate")
+ List<UtilityBill> findAllByHouseholdIdAndDueDateBetween(
+ `@Param`("householdId") Long householdId,
+ `@Param`("startDate") LocalDate startDate,
+ `@Param`("endDate") LocalDate endDate);📝 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.
| // 공과금은 Household와 직접 연관됨 | |
| List<UtilityBill> findAllByHouseholdIdAndDueDateBetween(Long householdId, LocalDate startDate, LocalDate endDate); | |
| // 공과금은 Household와 직접 연관됨 | |
| List<UtilityBill> findAllByHousehold_IdAndDueDateBetween(Long householdId, LocalDate startDate, LocalDate endDate); |
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In
`@src/main/java/com/partition/domain/utilitybill/repository/UtilityBillRepository.java`
around lines 11 - 12, The repository method in UtilityBillRepository uses a
non-existent householdId property on UtilityBill; update the derived query to
target the relationship ID (e.g., rename findAllByHouseholdIdAndDueDateBetween
to findAllByHousehold_IdAndDueDateBetween) or replace it with an explicit `@Query`
on the UtilityBill entity that joins/filters by household.id and dueDate between
the given LocalDate params so Spring Data JPA resolves the relation correctly.
| private boolean isCompleted; // 완료 여부 | ||
|
|
||
| @Builder | ||
| public Chore(User assignee, ChoreType type, LocalDate date) { | ||
| this.assignee = assignee; | ||
| this.type = type; | ||
| this.date = date; | ||
| this.isCompleted = false; | ||
| } |
There was a problem hiding this comment.
isCompleted 상태를 변경할 수 있는 메서드가 필요합니다.
isCompleted 필드가 false로 초기화되지만, 완료 상태로 변경할 수 있는 메서드가 없습니다. 집안일 완료 기능을 구현하려면 상태 변경 메서드가 필요합니다.
🛠️ 완료 상태 변경 메서드 추가 제안
`@Builder`
public Chore(User assignee, ChoreType type, LocalDate date) {
this.assignee = assignee;
this.type = type;
this.date = date;
this.isCompleted = false;
}
+
+ public void complete() {
+ this.isCompleted = true;
+ }
}📝 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.
| private boolean isCompleted; // 완료 여부 | |
| @Builder | |
| public Chore(User assignee, ChoreType type, LocalDate date) { | |
| this.assignee = assignee; | |
| this.type = type; | |
| this.date = date; | |
| this.isCompleted = false; | |
| } | |
| private boolean isCompleted; // 완료 여부 | |
| `@Builder` | |
| public Chore(User assignee, ChoreType type, LocalDate date) { | |
| this.assignee = assignee; | |
| this.type = type; | |
| this.date = date; | |
| this.isCompleted = false; | |
| } | |
| public void complete() { | |
| this.isCompleted = true; | |
| } | |
| } |
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@src/main/java/com/partition/entity/Chore.java` around lines 31 - 39, Add a
public mutator on the Chore entity to allow changing the isCompleted state:
implement a method such as markCompleted() (and optionally markIncomplete() or
setCompleted(boolean)) that updates the private boolean isCompleted and any
related invariants; locate the field isCompleted and the constructor in class
Chore and add the new method(s) there so callers can set the chore as completed.
| @Column(nullable = false) | ||
| private Integer difficulty; // 1-5 (기본값 3) | ||
|
|
||
| @Column(nullable = false) | ||
| private Integer frequency; // 빈도 (주당 횟수) | ||
|
|
||
| @Builder | ||
| public HouseholdChore(Household household, ChoreType choreType, Integer difficulty, Integer frequency) { | ||
| this.household = household; | ||
| this.choreType = choreType; | ||
| this.difficulty = difficulty; | ||
| this.frequency = frequency; | ||
| } | ||
|
|
||
| // 나중에 난이도 수정 API에서 사용 | ||
| public void updateDifficulty(Integer difficulty) { | ||
| this.difficulty = difficulty; | ||
| } | ||
|
|
||
| public void updateFrequency(Integer frequency) { | ||
| this.frequency = frequency; | ||
| } |
There was a problem hiding this comment.
난이도 범위가 주석으로만 남아 있습니다.
Line 29는 difficulty가 1~5라고 적혀 있는데, 생성자와 updateDifficulty는 아무 값이나 그대로 저장합니다. 잘못된 값이 들어오면 이후 배정 로직이 왜곡되므로, 엔티티에서 바로 검증해서 실패시키는 편이 안전합니다.
수정 예시
`@Builder`
public HouseholdChore(Household household, ChoreType choreType, Integer difficulty, Integer frequency) {
+ validateDifficulty(difficulty);
this.household = household;
this.choreType = choreType;
this.difficulty = difficulty;
this.frequency = frequency;
}
@@
public void updateDifficulty(Integer difficulty) {
+ validateDifficulty(difficulty);
this.difficulty = difficulty;
}
+
+ private static void validateDifficulty(Integer difficulty) {
+ if (difficulty == null || difficulty < 1 || difficulty > 5) {
+ throw new IllegalArgumentException("difficulty must be between 1 and 5");
+ }
+ }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@src/main/java/com/partition/entity/HouseholdChore.java` around lines 28 - 49,
The difficulty range (1-5) is only documented in a comment but not enforced; add
validation inside the HouseholdChore constructor and inside
updateDifficulty(Integer difficulty) to check that difficulty is non-null and
between 1 and 5 and throw an IllegalArgumentException (with a clear message) if
it is out of range so invalid values are rejected at the entity level.
| public void update(String content, LocalDate date, LocalTime time) { | ||
| if (content != null) this.content = content; | ||
| if (date != null) this.date = date; | ||
| // 시간은 null로 업데이트(삭제)하고 싶을 수도 있으므로 그대로 할당 | ||
| this.time = time; | ||
| } |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
# Description: ScheduleService에서 update 메서드 호출 방식 확인
rg -n -A 5 'schedule\.update\(' --type=javaRepository: Partition-app/Backend
Length of output: 748
ScheduleService에서 schedule.update() 호출 시 time 파라미터가 항상 null로 전달되어 API를 통해 시간 필드를 수정할 수 없습니다.
ScheduleService.java:50에서 schedule.update(request.getContent(), request.getDate(), null)로 호출하므로, 요청 DTO의 시간 값이 무시되고 시간 필드는 항상 null로 설정됩니다. Schedule.update() 메서드의 주석에서 의도적으로 null 할당을 허용한다고 언급하지만, 실제로는 시간을 설정하거나 변경할 수 없게 됩니다.
ScheduleService 또는 요청 DTO를 수정하여 요청 본문의 시간 값을 schedule.update()에 전달해야 합니다.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@src/main/java/com/partition/entity/Schedule.java` around lines 44 - 49, The
Schedule.time field is always set to null because ScheduleService calls
schedule.update(request.getContent(), request.getDate(), null); change the
service to pass the incoming DTO time value (e.g., request.getTime() or the
correct getter on the request DTO) into schedule.update, or if the DTO lacks a
time field, add it to the request DTO and use that getter; keep
Schedule.update(String content, LocalDate date, LocalTime time) behavior
unchanged so null can still be used to clear the time when intended.
| @Entity | ||
| @Getter | ||
| @Table(name = "user_chore_preferences") | ||
| @NoArgsConstructor(access = AccessLevel.PROTECTED) | ||
| public class UserChorePreference { | ||
|
|
||
| @Id | ||
| @GeneratedValue(strategy = GenerationType.IDENTITY) | ||
| private Long id; | ||
|
|
||
| @ManyToOne(fetch = FetchType.LAZY) | ||
| @JoinColumn(name = "user_id", nullable = false) | ||
| private User user; | ||
|
|
||
| @Enumerated(EnumType.STRING) | ||
| @Column(nullable = false) | ||
| private ChoreType choreType; | ||
|
|
||
| @Column(nullable = false) | ||
| private Integer score; // 1 ~ 5 점 | ||
|
|
||
| @Builder | ||
| public UserChorePreference(User user, ChoreType choreType, Integer score) { |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
cat -n src/main/java/com/partition/entity/UserChorePreference.javaRepository: Partition-app/Backend
Length of output: 1272
🏁 Script executed:
find . -type f \( -name "*.java" -path "*/migration/*" -o -name "*.sql" -o -name "*Flyway*" -o -name "*Liquibase*" \) | head -20Repository: Partition-app/Backend
Length of output: 47
🏁 Script executed:
rg "user_chore_preferences" -A 5 -B 5Repository: Partition-app/Backend
Length of output: 997
🏁 Script executed:
find . -type d -name "migration" -o -name "db" -o -name "database" | head -20Repository: Partition-app/Backend
Length of output: 47
🏁 Script executed:
find . -type f -name "*.sql" | head -30Repository: Partition-app/Backend
Length of output: 47
🏁 Script executed:
find . -path "*/resources/*" -name "*.sql" -o -name "schema*"Repository: Partition-app/Backend
Length of output: 47
🏁 Script executed:
find . -type f \( -name "application*.properties" -o -name "application*.yml" -o -name "application*.yaml" \) | xargs grep -l "hibernate\|jpa\|ddl" 2>/dev/nullRepository: Partition-app/Backend
Length of output: 101
🏁 Script executed:
cat -n src/main/resources/application*.properties src/main/resources/application*.yml 2>/dev/null | head -100Repository: Partition-app/Backend
Length of output: 915
🏁 Script executed:
rg "@UniqueConstraint|@Index" --type javaRepository: Partition-app/Backend
Length of output: 47
(user_id, chore_type) 복합 유니크 제약을 추가해야 합니다.
현재 엔티티 정의에 (user_id, chore_type) 유니크 제약이 없습니다. 이 모델이 "유저당 집안일 유형별 1개 점수"를 의도한 것이라면, 같은 사용자가 같은 choreType에 대해 여러 개의 선호도 행을 생성할 수 있어 데이터 무결성 문제가 발생할 수 있습니다.
@Table 어노테이션에 uniqueConstraints = {@UniqueConstraint(columnNames = {"user_id", "chore_type"})}를 추가하여 데이터베이스 수준에서 이를 보호하세요.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@src/main/java/com/partition/entity/UserChorePreference.java` around lines 10
- 32, 엔티티 UserChorePreference에 (user_id, chore_type) 복합 유니크 제약이 없어 동일 유저/동일 집안일
유형의 중복 레코드가 허용됩니다; `@Table` 어노테이션(클래스 UserChorePreference)을 수정해 uniqueConstraints
= {`@UniqueConstraint`(columnNames = {"user_id", "chore_type"})}를 추가하여 DB 레벨에서 유니크
제약을 걸고 마이그레이션/스키마 업데이트를 반영하세요.
#️⃣연관된 이슈
📝작업 내용
스크린샷 (선택)
💬리뷰 요구사항(선택)
Summary by CodeRabbit
릴리스 노트
새로운 기능
인프라