Skip to content

[FEAT] 캘린더 개인 Todo — 생성·완료토글 (#234) - #236

Merged
mosungjin merged 1 commit into
developfrom
feat/mnppi-calendar-todo
Aug 8, 2026
Merged

[FEAT] 캘린더 개인 Todo — 생성·완료토글 (#234)#236
mosungjin merged 1 commit into
developfrom
feat/mnppi-calendar-todo

Conversation

@MNPPI223

@MNPPI223 MNPPI223 commented Aug 8, 2026

Copy link
Copy Markdown
Contributor

📌 연관 이슈


📝 작업 내용

  • 캘린더 개인 Todo — 생성·완료토글 구현
  • V6.1.1__create_personal_todo.sql 신규 테이블(title·date만, Figma 모달 필드 그대로)
  • POST /api/todos, PATCH /api/todos/{todoId}/complete(토글)
  • 조작 범위는 생성·조회·완료토글만(홍근 확인) — 수정·삭제는 스코프 밖

✅ 마이그레이션 버전 확인 완료

V6.1.1은 안전하다. DBA 확인 결과 아직 운영 환경 자체가 구성되지 않았음 — 첫 배포 시 빈 DB에 V1부터 순서대로 전부 적용되므로 FLYWAY_OUT_OF_ORDER 관련 스킵 위험이 애초에 없다. (이전에 "머지 전 확인 필요" 블로커로 표시했던 것은 운영 DB가 이미 존재한다는 잘못된 전제에서 시작한 조사였음 — 정정.)


🖥️ 프론트엔드 연동 가이드 (API 명세)

1. 주요 엔드포인트

  • POST /api/todos : Todo 생성
  • PATCH /api/todos/{todoId}/complete : 완료 체크박스 토글

2. 요청 파라미터 (Request)

파라미터명 위치 필수 여부 설명
title Body 필수 최대 200자
date Body 필수 LocalDate, 단일 날짜
todoId Path 필수(토글)

3. 정상 응답 예시 (200 OK)

응답 JSON 보기 (클릭)
{
  "httpStatus": 201,
  "message": "Todo를 추가했습니다.",
  "data": { "id": 1, "title": "우유 사기", "date": "2026-08-20", "isDone": false }
}

4. ⚠️ 프론트엔드 참고 및 주의사항

  • 완료토글은 토글 방식이다 — 같은 API를 다시 호출하면 완료 취소된다. 별도의 완료취소 엔드포인트는 없다.
  • 수정·삭제 API는 없다(스코프 밖).
  • GET /api/calendar 통합조회는 이 PR에 없다 — 다음 라운드에서 별도 구현.

🚨 주요 에러 코드 및 예외 (Exceptions)

  • CAL-001(TODO_NOT_FOUND) : 존재하지 않거나 다른 회사·다른 사람 소유의 Todo id로 토글 시도

💡 백엔드 리뷰 포인트 (Backend Review)

  • 아키텍처 및 도메인: 완료토글 실패를 전부 TODO_NOT_FOUND 하나로 뭉갠 판단(존재 자체를 노출하지 않는 방향) 괜찮은지

✅ 체크리스트

  • 코드 컨벤션을 준수했습니다.
  • 불필요한 주석 및 콘솔 로그를 제거했습니다.
  • API 기능이 정상 동작하는지 테스트했습니다.
  • 예외(잘못된 값) 상황에 대한 검증 및 테스트를 통과했습니다.

2026-08-06 배분된 캘린더 작업의 첫 조각. Figma 확인 결과 read-only
집계가 아니라 신규 CRUD 엔티티(개인 Todo)가 필요함이 드러났다 —
회의·AI 파생이 아닌 순수 개인용 할 일.

- V6.1.1 신규 테이블(title·date만, 홍근 확인 모달 필드 그대로)
- POST /api/todos, PATCH /api/todos/{id}/complete(토글)
- 조작 범위는 생성·조회·완료토글만, 수정·삭제는 스코프 밖
- 테스트 12건, 전체 스위트 그린

⚠️ V6.1.1 버전 번호는 운영 flyway_schema_history 확인 전까지
잠정치 — PO/DBA 확인 필요(머지 전).

이슈 #234
@MNPPI223 MNPPI223 added the blocked 머지 전 외부 확인/승인 대기 label Aug 8, 2026
@coderabbitai

coderabbitai Bot commented Aug 8, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

개인 Todo 도메인과 저장소를 추가했습니다. Todo 생성 및 완료 상태 토글 유스케이스를 구현했습니다. 소유권 검증, 데이터베이스 마이그레이션, HTTP API와 계층별 테스트를 추가했습니다.

Changes

개인 Todo 기능

Layer / File(s) Summary
도메인 및 애플리케이션 흐름
src/main/java/com/module06/backend/calendar/domain/model/PersonalTodo.java, src/main/java/com/module06/backend/calendar/application/..., src/main/java/com/module06/backend/calendar/exception/CalendarErrorCode.java, src/test/java/com/module06/backend/calendar/application/service/PersonalTodoServiceTest.java
PersonalTodo 생성, 복원, 완료 상태 토글을 구현했습니다. 생성 시 미완료 상태를 사용합니다. 완료 토글 시 회사와 회원 소유권을 검증하고 실패하면 TODO_NOT_FOUND를 반환합니다.
Todo 영속화 구조
src/main/resources/db/migration/V6.1.1__create_personal_todo.sql, src/main/java/com/module06/backend/calendar/infrastructure/persistence/..., src/test/java/com/module06/backend/calendar/infrastructure/persistence/PersonalTodoPersistenceAdapterTest.java
personal_todo 테이블과 회원·날짜 복합 인덱스를 추가했습니다. JPA 저장소와 도메인 변환을 구현했습니다. 저장, 날짜 범위 조회, 완료 상태 재조회 테스트를 추가했습니다.
Todo HTTP API
src/main/java/com/module06/backend/calendar/presentation/api/..., src/test/java/com/module06/backend/calendar/presentation/api/TodoControllerTest.java
POST /api/todosPATCH /api/todos/{todoId}/complete를 추가했습니다. 생성 요청의 제목과 날짜를 검증하고 TodoResponse로 반환합니다. API 배선과 잘못된 제목 응답을 테스트합니다.

Estimated code review effort: 3 (Moderate) | ~25 minutes

Suggested labels: enhancement

🚥 Pre-merge checks | ✅ 3 | ❌ 2

❌ Failed checks (1 warning, 1 inconclusive)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 6.90% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
Linked Issues check ❓ Inconclusive 구현은 이슈 #234의 Todo 생성·완료 토글·소유권 검증 요구를 충족하지만 마이그레이션 버전 확인 여부는 제공되지 않았습니다. 운영 flyway_schema_history의 최고 버전을 확인하고 필요하면 V6.1.1을 조정한 후 머지하십시오.
✅ Passed checks (3 passed)
Check name Status Explanation
Out of Scope Changes check ✅ Passed 변경 사항은 개인 Todo의 도메인, API, 영속화, 예외 처리 및 테스트 범위에 포함되며 수정·삭제나 캘린더 통합 조회는 추가하지 않았습니다.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed 제목은 캘린더 개인 Todo의 생성 및 완료 상태 토글이라는 주요 변경 사항을 명확하고 간결하게 설명합니다.
✨ Finishing Touches 💡 1
📝 Generate docstrings 💡
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/mnppi-calendar-todo

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🤖 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.

Inline comments:
In
`@src/main/java/com/module06/backend/calendar/application/service/PersonalTodoService.java`:
- Around line 40-50: Update PersonalTodoService.toggleComplete to prevent lost
updates during concurrent completion toggles by applying one supported strategy:
use a pessimistic-locking repository query, add optimistic versioning with retry
handling, or perform an atomic toggle update query. Ensure concurrent toggles
are serialized or both state changes are preserved, and add an integration test
covering simultaneous toggles of the same todo.

In
`@src/test/java/com/module06/backend/calendar/application/service/PersonalTodoServiceTest.java`:
- Around line 104-114: Update toggleThrowsWhenTodoBelongsToAnotherCompany to
also verify that personalTodoRepository.save is never called when ownership
validation fails, while preserving the existing BusinessException and
TODO_NOT_FOUND assertions.
🪄 Autofix

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: Repository UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 927e34fd-a615-4be2-8128-bb1a6580d913

📥 Commits

Reviewing files that changed from the base of the PR and between f26746a and ce1d623.

📒 Files selected for processing (17)
  • src/main/java/com/module06/backend/calendar/application/command/CreateTodoCommand.java
  • src/main/java/com/module06/backend/calendar/application/service/PersonalTodoService.java
  • src/main/java/com/module06/backend/calendar/application/usecase/CreateTodoUseCase.java
  • src/main/java/com/module06/backend/calendar/application/usecase/ToggleTodoCompleteUseCase.java
  • src/main/java/com/module06/backend/calendar/domain/model/PersonalTodo.java
  • src/main/java/com/module06/backend/calendar/domain/repository/PersonalTodoRepository.java
  • src/main/java/com/module06/backend/calendar/exception/CalendarErrorCode.java
  • src/main/java/com/module06/backend/calendar/infrastructure/persistence/PersonalTodoJpaEntity.java
  • src/main/java/com/module06/backend/calendar/infrastructure/persistence/PersonalTodoPersistenceAdapter.java
  • src/main/java/com/module06/backend/calendar/infrastructure/persistence/SpringDataPersonalTodoRepository.java
  • src/main/java/com/module06/backend/calendar/presentation/api/TodoController.java
  • src/main/java/com/module06/backend/calendar/presentation/api/request/CreateTodoRequest.java
  • src/main/java/com/module06/backend/calendar/presentation/api/response/TodoResponse.java
  • src/main/resources/db/migration/V6.1.1__create_personal_todo.sql
  • src/test/java/com/module06/backend/calendar/application/service/PersonalTodoServiceTest.java
  • src/test/java/com/module06/backend/calendar/infrastructure/persistence/PersonalTodoPersistenceAdapterTest.java
  • src/test/java/com/module06/backend/calendar/presentation/api/TodoControllerTest.java

Comment on lines +40 to +50
public PersonalTodo toggleComplete(Long companyId, Long memberId, Long todoId) {
PersonalTodo todo = personalTodoRepository.findById(todoId)
.orElseThrow(() -> new BusinessException(CalendarErrorCode.TODO_NOT_FOUND));

// 다른 회사·다른 사람의 Todo는 "없다"와 동일하게 취급한다 — 존재 자체를 노출하지 않는다.
if (!companyId.equals(todo.getCompanyId()) || !memberId.equals(todo.getMemberId())) {
throw new BusinessException(CalendarErrorCode.TODO_NOT_FOUND);
}

todo.toggleDone();
return personalTodoRepository.save(todo);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🗄️ Data Integrity & Integration | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

ast-grep outline src/main/java/com/module06/backend/calendar/infrastructure/persistence/SpringDataPersonalTodoRepository.java --items all
rg -n -C 3 '`@Lock`|PESSIMISTIC|`@Version`|version|findById' \
  src/main/java/com/module06/backend/calendar/infrastructure/persistence \
  src/main/java/com/module06/backend/calendar

Repository: Z-Groupware/BACKEND

Length of output: 4544


🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '%s\n' '--- PersonalTodoJpaEntity ---'
fd -i 'PersonalTodoJpaEntity' .
printf '%s\n' '--- Persistence adapter ---'
cat -n src/main/java/com/module06/backend/calendar/infrastructure/persistence/PersonalTodoPersistenceAdapter.java
printf '%s\n' '--- Todo entity and migrations ---'
rg -n -C 4 'class PersonalTodoJpaEntity|`@Entity`|isDone|done|CREATE TABLE|personal_todo|todo' \
  src/main/java src/main/resources
printf '%s\n' '--- Toggle tests and repository tests ---'
rg -n -C 5 'toggleComplete|toggleDone|TODO_NOT_FOUND|PersonalTodo' \
  src/test

Repository: Z-Groupware/BACKEND

Length of output: 50376


🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '%s\n' '--- PersonalTodoJpaEntity ---'
cat -n src/main/java/com/module06/backend/calendar/infrastructure/persistence/PersonalTodoJpaEntity.java

printf '%s\n' '--- Personal todo migration ---'
cat -n src/main/resources/db/migration/V6.1.1__create_personal_todo.sql

printf '%s\n' '--- Relevant test files ---'
fd -t f . src/test | rg 'calendar|todo|PersonalTodo'
while IFS= read -r file; do
  printf '\n--- %s ---\n' "$file"
  rg -n -C 4 'toggleComplete|toggleDone|PersonalTodo|TODO_NOT_FOUND' "$file" || true
done < <(fd -t f . src/test | rg 'calendar|todo|PersonalTodo')

Repository: Z-Groupware/BACKEND

Length of output: 4885


완료 토글에 동시성 제어를 추가하십시오.

findById에는 비관 잠금이 없고 PersonalTodoJpaEntitypersonal_todo 테이블에도 @Version 또는 버전 컬럼이 없습니다. 두 요청이 같은 isDone 값을 읽으면 한 토글이 유실됩니다. 잠금이 적용된 조회, 낙관적 잠금과 재시도, 또는 원자적 토글 쿼리 중 하나를 적용하고 동시 토글 통합 테스트를 추가하십시오.

🤖 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/java/com/module06/backend/calendar/application/service/PersonalTodoService.java`
around lines 40 - 50, Update PersonalTodoService.toggleComplete to prevent lost
updates during concurrent completion toggles by applying one supported strategy:
use a pessimistic-locking repository query, add optimistic versioning with retry
handling, or perform an atomic toggle update query. Ensure concurrent toggles
are serialized or both state changes are preserved, and add an integration test
covering simultaneous toggles of the same todo.

Comment on lines +104 to +114
@Test
void toggleThrowsWhenTodoBelongsToAnotherCompany() {
PersonalTodoService service = service();
PersonalTodo otherCompanyTodo = PersonalTodo.reconstitute(
TODO_ID, 999L, MEMBER, "다른 회사 Todo", LocalDate.of(2026, 8, 20), false, null, null);
when(personalTodoRepository.findById(TODO_ID)).thenReturn(Optional.of(otherCompanyTodo));

assertThatThrownBy(() -> service.toggleComplete(COMPANY, MEMBER, TODO_ID))
.isInstanceOf(BusinessException.class)
.hasFieldOrPropertyWithValue("errorCode", CalendarErrorCode.TODO_NOT_FOUND);
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🗄️ Data Integrity & Integration | 🟡 Minor | ⚡ Quick win

다른 회사 Todo의 저장 금지 검증을 추가하십시오.

현재 테스트는 예외만 확인합니다. 소유권 검사 전에 save가 호출되고 이후 예외가 발생해도 테스트가 통과할 수 있습니다. 다른 회사의 Todo 상태 변경을 막도록 save 미호출도 검증하십시오.

수정 예시
         assertThatThrownBy(() -> service.toggleComplete(COMPANY, MEMBER, TODO_ID))
                 .isInstanceOf(BusinessException.class)
                 .hasFieldOrPropertyWithValue("errorCode", CalendarErrorCode.TODO_NOT_FOUND);
+
+        verify(personalTodoRepository, never()).save(any());
📝 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.

Suggested change
@Test
void toggleThrowsWhenTodoBelongsToAnotherCompany() {
PersonalTodoService service = service();
PersonalTodo otherCompanyTodo = PersonalTodo.reconstitute(
TODO_ID, 999L, MEMBER, "다른 회사 Todo", LocalDate.of(2026, 8, 20), false, null, null);
when(personalTodoRepository.findById(TODO_ID)).thenReturn(Optional.of(otherCompanyTodo));
assertThatThrownBy(() -> service.toggleComplete(COMPANY, MEMBER, TODO_ID))
.isInstanceOf(BusinessException.class)
.hasFieldOrPropertyWithValue("errorCode", CalendarErrorCode.TODO_NOT_FOUND);
}
`@Test`
void toggleThrowsWhenTodoBelongsToAnotherCompany() {
PersonalTodoService service = service();
PersonalTodo otherCompanyTodo = PersonalTodo.reconstitute(
TODO_ID, 999L, MEMBER, "다른 회사 Todo", LocalDate.of(2026, 8, 20), false, null, null);
when(personalTodoRepository.findById(TODO_ID)).thenReturn(Optional.of(otherCompanyTodo));
assertThatThrownBy(() -> service.toggleComplete(COMPANY, MEMBER, TODO_ID))
.isInstanceOf(BusinessException.class)
.hasFieldOrPropertyWithValue("errorCode", CalendarErrorCode.TODO_NOT_FOUND);
verify(personalTodoRepository, never()).save(any());
}
🤖 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/PersonalTodoServiceTest.java`
around lines 104 - 114, Update toggleThrowsWhenTodoBelongsToAnotherCompany to
also verify that personalTodoRepository.save is never called when ownership
validation fails, while preserving the existing BusinessException and
TODO_NOT_FOUND assertions.

@MNPPI223 MNPPI223 self-assigned this Aug 8, 2026
@MNPPI223 MNPPI223 added this to Z Aug 8, 2026
@github-project-automation github-project-automation Bot moved this to Todo in Z Aug 8, 2026
@MNPPI223 MNPPI223 added this to the [M1] BE 기능 개발 milestone Aug 8, 2026
@MNPPI223 MNPPI223 removed the blocked 머지 전 외부 확인/승인 대기 label Aug 8, 2026

@mosungjin mosungjin left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

확인했습니다.

@mosungjin
mosungjin merged commit a8986dd into develop Aug 8, 2026
9 checks passed
@github-project-automation github-project-automation Bot moved this from Todo to Done in Z Aug 8, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

Status: Done

Development

Successfully merging this pull request may close these issues.

[FEAT] 캘린더 개인 Todo — 생성·완료토글 (CR + 완료처리)

2 participants