Skip to content

⚙️ [기능추가][Timer][API연동] 백엔드 Timer API 연동 #81

Description

@EM-H20

📝 현재 문제점

  • api-docs.jsonTimer 태그가 신설되며 POST /api/timer-sessions, GET /api/timer-sessions, GET /api/timer-sessions/today-stats 3 개 엔드포인트가 확정되었으나 앱에는 아직 연동되어 있지 않음
  • 현재 lib/features/timer/TimerSessionLocalDataSource (SharedPreferences guest_timer_sessions) 와 local 전용 TimerSessionRepositoryImpl 만 존재하여 세션 기록이 디바이스 로컬에만 보관됨
  • 타이머 종료 시 클라이언트가 직접 ① 연료 충전(fuelNotifier.chargeFuel) ② actualMinutes 누적 ③ 배지 해금 트리거 ④ streak 계산을 수행하고 있어 데이터 정합성·조작 방지·다중 기기 시나리오 모두 한계
  • 서버는 세션 저장 시 시간 5단계 검증 후 연료 자동 충전 + Todo actualMinutes 자동 누적 + streak 자동 계산을 단일 트랜잭션으로 처리하지만, 클라이언트가 이 신뢰 경계를 활용하지 못함
  • 직전 ⚙️ [기능추가][Fuel][API연동] 백엔드 Fuel API 연동 (잔량 조회·거래 내역 페이지네이션) #79 Fuel API 가 동일 server-first + cache 폴백 + 인증/게스트 분기 패턴으로 정리되었으므로 Timer 도 동일 규약으로 정렬되어야 일관성 유지 가능

🛠️ 해결 방안 / 제안 기능

  • TimerSessionRemoteDataSource 신설 후 Timer 태그 3 개 엔드포인트 매핑 (Retrofit)
  • TimerSessionRepositoryImpl 을 서버 우선 + 캐시 폴백 구조로 재구성, 기존 로컬 구현은 LocalTimerSessionRepositoryImpl 로 리네임 (Fuel 패턴 그대로)
  • 인증 상태(isAuthenticatedProvider) 에 따라 server-backed / local-only Repository 를 분기 (게스트는 기존 로컬 동작 그대로 유지)
  • 인증 모드에서 타이머 종료 시 단일 POST 호출로 책임 이양 — 응답의 fuelCharged 신뢰, 클라이언트는 fuel·todo·세션 리스트·오늘 통계 provider 를 invalidate 하여 재조회만 수행
  • 재시도 시 중복 충전 방지를 위해 Idempotency-Key 헤더를 항상 동봉 (세션 1 회당 UUID v4 1 개, 성공 시 폐기)
  • GET /today-stats 응답(totalMinutes / sessionCount / streak) 을 신규 provider 로 노출하여 오늘 통계·연속 일수 표시가 서버 단일 소스로 동작
  • 세션 이력 화면을 서버 페이지네이션으로 전환 (현재 SharedPreferences 전체 로드 → 페이지 단위 lazy 로드 기반)
  • 백그라운드 동기화·기존 게스트 데이터 마이그레이션은 본 이슈 범위 제외 (별도 이슈로 분리)

⚙️ 작업 내용

API 연동 대상 (총 3 개)

  • POST /api/timer-sessions — 세션 저장 + 연료 자동 충전 + actualMinutes 자동 누적 (TimerSessionCreateResponse 응답)
    • Body: todoId?, todoTitle?, startedAt, endedAt, durationMinutes
    • Header: Idempotency-Key (UUID v4)
    • 400 5 종 에러: INVALID_SESSION_TIME / INVALID_DURATION / SESSION_TOO_SHORT / SESSION_TOO_LONG / FUTURE_SESSION
    • 404 TODO_NOT_FOUND — 연결된 Todo 가 본인 소유 아님 / 없음
  • GET /api/timer-sessions — 세션 페이지네이션 조회 (TimerSessionListResponse)
    • Query: startDate, endDate (YYYY-MM-DD), todoId, page (기본 0), size (기본 20, 최대 100)
    • 정렬: startedAt 내림차순 고정
  • GET /api/timer-sessions/today-stats — 오늘 총분/세션수/streak (KST, TodayStatsResponse)

레이어별 작업

  • Data Layer
    • timer_session_remote_datasource.dart (Retrofit) — 3 개 엔드포인트 정의 + Idempotency-Key 헤더 지원
    • Request/Response DTO (Freezed + JsonSerializable):
      • TimerSessionCreateRequestModel, TimerSessionCreateResponseModel ({ session, fuelCharged })
      • TimerSessionPageResponseModel ({ content[], page, size, totalElements, totalPages })
      • TodayStatsResponseModel
    • TimerSessionRepositoryImpl 재작성 — 서버 우선 호출 → 응답 캐시 갱신, 401 / 네트워크 실패 시 캐시 폴백
    • 기존 로컬 구현 → LocalTimerSessionRepositoryImpl 로 리네임 (게스트·오프라인 전용, 동작 보존)
  • Domain Layer
    • TimerSessionEntity 기존 유지 (필드 동일)
    • 신규 도메인 모델: TimerSessionCreateResult ({ session, fuelCharged }), TimerSessionPage, TimerSessionFilter, TodayStats
    • 신규 예외 sealed class: TimerSessionException
      • InvalidSessionTime, InvalidDuration, SessionTooLong, FutureSession, LinkedTodoNotFound
    • Repository 인터페이스 확장: createSession, getSessionsPage, getTodayStats
  • Presentation Layer
    • timer_session_provider.dartisAuthenticatedProvider 기반 분기 (Fuel 패턴 복제), 페이지네이션 화
    • today_stats_provider.dart 신규 — 인증 모드 = 서버 호출, 게스트 = 기존 로컬 계산
    • timer_provider.dart (TimerNotifier) 수정:
      • start 시 세션용 idempotencyKey (UUID v4) 발급, 진행 중 보관
      • stop 시 분기:
        • 게스트: 기존 로컬 로직 그대로 (fuel·actualMinutes·streak 직접 계산)
        • 인증: repo.createSession() 1 회 → 성공 시 fuelNotifier·todoNotifier·timerSessionListNotifier·todayStatsNotifier invalidate
      • 성공 시 idempotencyKey 폐기, 실패 시 유지하여 재시도 가능
    • 에러 메시지 UX (한국어, Toss 라이팅) — 4xx 별 안내 + 폴백 정책
  • Infrastructure
    • ApiEndpointstimerSessions, timerSessionsTodayStats 상수 추가
    • 401 → 기존 AuthInterceptor 의 재발급 큐 재사용
    • 로그아웃 / 탈퇴 시 timer 캐시 invalidate 훅 등록 (Fuel 과 동일 위치)

테스트

  • TimerSessionRemoteDataSource 직렬화/역직렬화 + Idempotency-Key 헤더 전달 단위 테스트
  • TimerSessionRepositoryImpl 서버 우선 / 캐시 폴백 / 400 5 종 → 커스텀 예외 매핑 / 404 TODO_NOT_FOUND 처리
  • LocalTimerSessionRepositoryImpl 회귀 가드 (기존 게스트 동작 보존)
  • Provider 인증·게스트 분기 (isAuthenticatedProvider 모킹) + 응답 후 fuel/todo invalidate 호출 검증
  • TimerNotifier stop 흐름 — Idempotency-Key 발급/유지/폐기, 성공/실패 시 UI 반영
  • today_stats_provider 인증/게스트 분기 + 페이지네이션 경계 케이스 (page=0, totalPages-1)

문서/체크리스트

  • 09_PIPELINE.md 의 spec → plan → execute → TDD → verification 순서 준수
  • flutter analyze / flutter test 통과 확인 후 커밋
  • 커밋 메시지 feat : Timer Remote DataSource·Repository 구현(server-first + Idempotency-Key) #<issue> 형식

비고

  • 백그라운드 동기화 부재 — 현재 타이머는 백그라운드 ↔ 포그라운드 재계산 보정이 없으며, 본 이슈에서는 다루지 않음. 서버 5단계 시간 검증으로 인해 부정확 세션이 거절될 가능성이 있음 → 별도 이슈로 분리
  • 기존 게스트 세션 마이그레이션 미수행guest_timer_sessions SharedPreferences 데이터는 게스트 전용으로 유지, 로그인 시 서버로 업로드하지 않음 (정책 합의 시 후속 이슈로 분리)
  • 백엔드는 시간 검증·연료 충전·actualMinutes 누적·streak 계산을 단일 트랜잭션으로 처리하므로, 본 이슈 이후 클라이언트의 중복 계산 코드는 인증 모드 경로에서 모두 제거됨

🙋‍♂️ 담당자

  • 백엔드: 이름
  • 프론트엔드: 이름
  • 디자인: 이름

Metadata

Metadata

Assignees

Labels

작업완료작업 완료 상태인 경우 (이슈 폐쇄)

Type

No type
No fields configured for issues without a type.

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions