[FEAT] 개인 Todo 기간(endDate) 지원 추가 - #459
Conversation
CreateTodoRequest.date 단일 필드만 지원하던 개인 Todo에 endDate를 추가해 기간 입력을 지원한다. endDate 미지정 시 date와 동일값으로 채워 기존 단일 날짜 Todo와 하위호환을 유지하고, endDate < date는 CAL-002로 거부한다. 월간 캘린더 조회(GET /api/calendar)도 함께 고쳤다 — 기존 findAllByMemberIdAndDateBetween은 Todo의 date(시작일)만 봐서 이전 달에 시작해 이번 달로 넘어오는 Todo를 놓쳤다. findAllByMemberIdOverlappingPeriod로 개명하고 [date, endDate] 구간이 조회 월과 겹치는지로 판정하도록 수정. DB는 V6.5.1(컬럼 추가)~V6.5.3(NOT NULL 전환) 3개 파일로 나눠 진행 (ALTER 하나 = 파일 하나 원칙, V2.6.7~9 선례 따름).
|
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: Repository UI Review profile: CHILL Plan: Pro Plus Run ID: 📒 Files selected for processing (1)
🚧 Files skipped from review as they are similar to previous changes (1)
📝 WalkthroughWalkthrough개인 Todo가 시작일과 종료일을 갖는 기간 Todo로 확장되었습니다. 생성 요청은 종료일을 전달하며, 미지정 시 시작일을 사용합니다. 저장소는 기간 겹침 조건으로 조회하고, 캘린더 응답은 실제 종료일을 반환합니다. Changes개인 Todo 기간 지원
Estimated code review effort: 3 (Moderate) | ~20 minutes Mergeability Score: ⚪ Minimal · up to This change adds Todo end dates and overlap-based calendar queries without any identified actionable merge-blocking risk remaining. Sequence Diagram(s)sequenceDiagram
participant Client
participant TodoController
participant PersonalTodoService
participant PersonalTodoPersistenceAdapter
participant CalendarQueryService
Client->>TodoController: endDate 포함 Todo 생성 요청
TodoController->>PersonalTodoService: CreateTodoCommand 전달
PersonalTodoService->>PersonalTodoPersistenceAdapter: 종료일이 반영된 Todo 저장
PersonalTodoPersistenceAdapter-->>PersonalTodoService: 저장 결과 반환
Client->>CalendarQueryService: 월별 캘린더 조회
CalendarQueryService->>PersonalTodoPersistenceAdapter: 월 기간 겹침 조회
PersonalTodoPersistenceAdapter-->>CalendarQueryService: 겹치는 Todo 반환
CalendarQueryService-->>Client: endDate 포함 캘린더 응답
Possibly related issues
Possibly related PRs
Suggested reviewers: 🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✨ Finishing Touches 💡 1📝 Generate docstrings 💡
🧪 Generate unit tests (beta)
Comment |
There was a problem hiding this comment.
🧹 Nitpick comments (4)
src/main/resources/db/migration/V6.5.3__set_end_date_not_null.sql (1)
1-4: 🗄️ Data Integrity & Integration | 🔵 Trivial구버전 애플리케이션의 쓰기 가능 시점을 확인해 주세요.
롤링 배포에서 구버전 인스턴스가 이 마이그레이션 이후에도 Todo를 생성할 수 있다면,
end_date기본값이 없으므로end_date를 전달하지 않는 INSERT가 실패합니다. 구버전 쓰기가 남을 수 있으면 새 애플리케이션을 먼저 배포해end_date를 기록한 뒤 구버전 인스턴스를 종료하고 이 제약을 적용하세요. 구버전 쓰기가 없는 배포 방식이면 그 전제를 배포 절차에 명시하세요.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@src/main/resources/db/migration/V6.5.3__set_end_date_not_null.sql` around lines 1 - 4, Ensure the deployment sequence prevents legacy application instances from writing after this migration: deploy the version that always populates end_date, wait for older instances to stop, then apply the NOT NULL change in the migration. If the deployment model guarantees no legacy writes, document that prerequisite in the release procedure before applying this migration.src/main/resources/db/migration/V6.5.1__add_end_date_to_personal_todo.sql (1)
4-5: 🚀 Performance & Scalability | 🔵 Trivial새 기간 겹침 조회를 위한 인덱스 계획을 확인해 주세요.
기존
src/main/resources/db/migration/V6.1.1__create_personal_todo.sqlLine 31의 인덱스는(member_id, date)입니다. 새 조회는date <= periodEnd에 하한이 없고end_date >= periodStart를 추가합니다. 따라서 회원의 과거 Todo를 넓게 스캔한 뒤 종료일을 필터링할 수 있습니다. 데이터가 계속 누적된다면 실제 데이터량으로EXPLAIN을 실행하고, 실행 계획이 악화될 때 별도 마이그레이션에서 복합 인덱스를 추가하거나 조정하세요.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@src/main/resources/db/migration/V6.5.1__add_end_date_to_personal_todo.sql` around lines 4 - 5, 기존 `(member_id, date)` 인덱스를 유지한 채 새 기간 겹침 조회의 실행 계획을 실제 데이터 규모로 `EXPLAIN`하여 확인하세요. 회원별 과거 Todo 스캔 후 `end_date` 필터링으로 성능이 악화될 경우에만 별도 마이그레이션에서 적절한 복합 인덱스를 추가하거나 조정하고, 현재 마이그레이션에는 인덱스 변경을 포함하지 마세요.src/test/java/com/module06/backend/calendar/infrastructure/persistence/PersonalTodoPersistenceAdapterTest.java (1)
41-67: 🎯 Functional Correctness | 🔵 Trivial | ⚡ Quick win겹침 조건의 양쪽 방향과 경계값을 추가해 주세요.
현재 테스트는 기간 안의 Todo와
2026년 7월 28일부터2026년 8월 3일까지의 Todo만 확인합니다. 다음 사례를 추가하세요.
2026년 7월 1일부터2026년 7월 31일까지: 제외되어야 합니다.endDate >= periodStart를 검증합니다.2026년 8월 31일부터2026년 9월 5일까지: 포함되어야 합니다. 시작일 조건을 검증합니다.2026년 7월 31일부터2026년 8월 1일까지: 종료일 경계를 포함해야 합니다.- 다기간 Todo 결과의
getEndDate()가2026년 8월 3일인지 확인합니다.현재 사례만으로는 두 겹침 조건 중 하나를 제거한 잘못된 쿼리도 통과할 수 있습니다.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@src/test/java/com/module06/backend/calendar/infrastructure/persistence/PersonalTodoPersistenceAdapterTest.java` around lines 41 - 67, 보완 테스트 메서드인 findsAllByMemberIdOverlappingPeriodOnly와 findsTodoThatStartsBeforeMonthButEndsInsideIt에 양방향 겹침 및 경계 사례를 추가하세요. 2026년 7월 1일~7월 31일 Todo는 제외하고, 8월 31일~9월 5일과 7월 31일~8월 1일 Todo는 포함되는지 검증하며, 다기간 Todo의 getEndDate()가 2026년 8월 3일인지도 확인하세요.src/test/java/com/module06/backend/calendar/application/service/CalendarQueryServiceTest.java (1)
80-95: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win기간 Todo의 종료일 매핑을 구분해서 검증하세요.
현재 fixture는
date와endDate가 같습니다. 따라서CalendarQueryService.toTodoItem()이 다시todo.getDate()를 종료일로 매핑해도 이 테스트는 통과합니다. 종료일을 다른 날짜로 설정하고CalendarItem.endDate()를 검증하세요.테스트 보강 예시
PersonalTodo todo = PersonalTodo.reconstitute( - 10L, COMPANY, MEMBER, "우유 사기", LocalDate.of(2026, 8, 20), LocalDate.of(2026, 8, 20), + 10L, COMPANY, MEMBER, "우유 사기", LocalDate.of(2026, 8, 20), LocalDate.of(2026, 8, 25), false, null, null); ... assertThat(todoItem.id()).isEqualTo(10L); +assertThat(todoItem.startDate()).isEqualTo(LocalDate.of(2026, 8, 20)); +assertThat(todoItem.endDate()).isEqualTo(LocalDate.of(2026, 8, 25)); assertThat(todoItem.isDone()).isFalse();🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@src/test/java/com/module06/backend/calendar/application/service/CalendarQueryServiceTest.java` around lines 80 - 95, Update the period Todo fixture in CalendarQueryServiceTest so its endDate differs from date, then assert the resulting CalendarItem.endDate() matches that distinct end date. Keep the existing type, id, and completion assertions unchanged.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Nitpick comments:
In `@src/main/resources/db/migration/V6.5.1__add_end_date_to_personal_todo.sql`:
- Around line 4-5: 기존 `(member_id, date)` 인덱스를 유지한 채 새 기간 겹침 조회의 실행 계획을 실제 데이터
규모로 `EXPLAIN`하여 확인하세요. 회원별 과거 Todo 스캔 후 `end_date` 필터링으로 성능이 악화될 경우에만 별도
마이그레이션에서 적절한 복합 인덱스를 추가하거나 조정하고, 현재 마이그레이션에는 인덱스 변경을 포함하지 마세요.
In `@src/main/resources/db/migration/V6.5.3__set_end_date_not_null.sql`:
- Around line 1-4: Ensure the deployment sequence prevents legacy application
instances from writing after this migration: deploy the version that always
populates end_date, wait for older instances to stop, then apply the NOT NULL
change in the migration. If the deployment model guarantees no legacy writes,
document that prerequisite in the release procedure before applying this
migration.
In
`@src/test/java/com/module06/backend/calendar/application/service/CalendarQueryServiceTest.java`:
- Around line 80-95: Update the period Todo fixture in CalendarQueryServiceTest
so its endDate differs from date, then assert the resulting
CalendarItem.endDate() matches that distinct end date. Keep the existing type,
id, and completion assertions unchanged.
In
`@src/test/java/com/module06/backend/calendar/infrastructure/persistence/PersonalTodoPersistenceAdapterTest.java`:
- Around line 41-67: 보완 테스트 메서드인 findsAllByMemberIdOverlappingPeriodOnly와
findsTodoThatStartsBeforeMonthButEndsInsideIt에 양방향 겹침 및 경계 사례를 추가하세요. 2026년 7월
1일~7월 31일 Todo는 제외하고, 8월 31일~9월 5일과 7월 31일~8월 1일 Todo는 포함되는지 검증하며, 다기간 Todo의
getEndDate()가 2026년 8월 3일인지도 확인하세요.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Repository UI
Review profile: CHILL
Plan: Pro Plus
Run ID: e3e84b7c-dd1f-4ba3-ae30-4e1b8edfe80c
📒 Files selected for processing (19)
src/main/java/com/module06/backend/calendar/application/command/CreateTodoCommand.javasrc/main/java/com/module06/backend/calendar/application/service/CalendarQueryService.javasrc/main/java/com/module06/backend/calendar/application/service/PersonalTodoService.javasrc/main/java/com/module06/backend/calendar/domain/model/PersonalTodo.javasrc/main/java/com/module06/backend/calendar/domain/repository/PersonalTodoRepository.javasrc/main/java/com/module06/backend/calendar/exception/CalendarErrorCode.javasrc/main/java/com/module06/backend/calendar/infrastructure/persistence/PersonalTodoJpaEntity.javasrc/main/java/com/module06/backend/calendar/infrastructure/persistence/PersonalTodoPersistenceAdapter.javasrc/main/java/com/module06/backend/calendar/infrastructure/persistence/SpringDataPersonalTodoRepository.javasrc/main/java/com/module06/backend/calendar/presentation/api/TodoController.javasrc/main/java/com/module06/backend/calendar/presentation/api/request/CreateTodoRequest.javasrc/main/java/com/module06/backend/calendar/presentation/api/response/TodoResponse.javasrc/main/resources/db/migration/V6.5.1__add_end_date_to_personal_todo.sqlsrc/main/resources/db/migration/V6.5.2__backfill_end_date_from_date.sqlsrc/main/resources/db/migration/V6.5.3__set_end_date_not_null.sqlsrc/test/java/com/module06/backend/calendar/application/service/CalendarQueryServiceTest.javasrc/test/java/com/module06/backend/calendar/application/service/PersonalTodoServiceTest.javasrc/test/java/com/module06/backend/calendar/infrastructure/persistence/PersonalTodoPersistenceAdapterTest.javasrc/test/java/com/module06/backend/calendar/presentation/api/TodoControllerTest.java
findAllByMemberIdAndDateLessThanEqualAndEndDateGreaterThanEqual가 CompanyId 없이 memberId만으로 조회해서 tenant-derived-query-without-company-scope에 걸림. member_id가 전사 유일 PK라 실제 크로스테넌트 위험은 없음(기존 ActionRepository.findAllByAssigneeMemberId와 동일 근거) — nosemgrep 예외 처리, 근거 주석 추가. 로컬에서 baseline-commit 기준 동일 스캔으로 재확인함.
📌 연관 이슈
📝 작업 내용
personal_todo에end_date컬럼 추가(V6.5.1~V6.5.3, 3파일 분리)PersonalTodo·CreateTodoRequest·CreateTodoCommand·TodoResponse에endDate배선endDate미지정 시 서비스 계층에서date와 동일값으로 채움(하위호환),endDate < date는 400(CAL-002)으로 거부GET /api/calendar월별 조회 쿼리를 overlap 조건으로 변경 — 기존엔 Todo의date(시작일)만 봐서 이전 달에 시작해 이번 달로 걸치는 Todo를 놓쳤음(findAllByMemberIdAndDateBetween→findAllByMemberIdOverlappingPeriod)🖥️ 프론트엔드 연동 가이드 (API 명세)
1. 주요 엔드포인트
POST/api/todos: 개인 Todo 생성 —endDate선택 필드 추가됨GET/api/calendar?month=yyyy-MM: TODO 항목의endDate가 이제 실제 종료일2. 요청 파라미터 (Request) — POST /api/todos
3. 정상 응답 예시 (201 Created) — POST /api/todos
응답 JSON 보기 (클릭)
{ "success": true, "message": "Todo를 추가했습니다.", "data": { "id": 10, "title": "여행", "date": "2026-08-20", "endDate": "2026-08-25", "isDone": false } }4.⚠️ 프론트엔드 참고 및 주의사항
endDate생략하면 서버가date와 동일값으로 채워서 응답에 실어 보냄 — 응답의endDate는 항상 값이 있음(null 아님).endDate가date보다 이전이면400 CAL-002— "종료일은 시작일보다 빠를 수 없습니다."GET /api/calendar의 TODO 항목endDate도 이제 실제 종료일로 나감(이전엔date와 항상 같은 값이었음).🚨 주요 에러 코드 및 예외 (Exceptions)
CAL-002(400) :endDate가date보다 이전인 경우💡 백엔드 리뷰 포인트 (Backend Review)
PersonalTodoService(서비스 계층)에 뒀다 —Action.start()류 도메인 메서드는 방어용IllegalStateException만 던지고 실제 비즈니스 검증은 서비스 계층에서BusinessException으로 하는 이 레포 컨벤션을 그대로 따름.PersonalTodo팩토리엔 추가 검증 안 둠(클래스 자체 주석의 YAGNI 원칙 유지).findAllByMemberIdAndDateLessThanEqualAndEndDateGreaterThanEqual)를 신규 인덱스 없이 기존(member_id, date)인덱스로 처리하기로 함 — 인당 Todo 건수가 적어 충분하다고 판단했는데, 이 판단이 맞는지 리뷰 부탁드립니다. 마이그레이션은ALTER 하나 = 파일 하나원칙 때문에 3개로 쪼갰습니다(V2.6.7~9 선례 참고).✅ 체크리스트
Summary by CodeRabbit
새 기능
버그 수정