Description 📝 현재 문제점
api-docs.json 에 Timer 태그가 신설되며 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.dart — isAuthenticatedProvider 기반 분기 (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
ApiEndpoints 에 timerSessions, 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 계산을 단일 트랜잭션으로 처리하므로, 본 이슈 이후 클라이언트의 중복 계산 코드는 인증 모드 경로에서 모두 제거됨
🙋♂️ 담당자
백엔드: 이름
프론트엔드: 이름
디자인: 이름
Reactions are currently unavailable
You can’t perform that action at this time.
📝 현재 문제점
api-docs.json에Timer태그가 신설되며POST /api/timer-sessions,GET /api/timer-sessions,GET /api/timer-sessions/today-stats3 개 엔드포인트가 확정되었으나 앱에는 아직 연동되어 있지 않음lib/features/timer/는TimerSessionLocalDataSource(SharedPreferencesguest_timer_sessions) 와 local 전용TimerSessionRepositoryImpl만 존재하여 세션 기록이 디바이스 로컬에만 보관됨fuelNotifier.chargeFuel) ②actualMinutes누적 ③ 배지 해금 트리거 ④ streak 계산을 수행하고 있어 데이터 정합성·조작 방지·다중 기기 시나리오 모두 한계actualMinutes자동 누적 + streak 자동 계산을 단일 트랜잭션으로 처리하지만, 클라이언트가 이 신뢰 경계를 활용하지 못함server-first + cache 폴백 + 인증/게스트 분기패턴으로 정리되었으므로 Timer 도 동일 규약으로 정렬되어야 일관성 유지 가능🛠️ 해결 방안 / 제안 기능
TimerSessionRemoteDataSource신설 후Timer태그 3 개 엔드포인트 매핑 (Retrofit)TimerSessionRepositoryImpl을 서버 우선 + 캐시 폴백 구조로 재구성, 기존 로컬 구현은LocalTimerSessionRepositoryImpl로 리네임 (Fuel 패턴 그대로)isAuthenticatedProvider) 에 따라 server-backed / local-only Repository 를 분기 (게스트는 기존 로컬 동작 그대로 유지)fuelCharged신뢰, 클라이언트는 fuel·todo·세션 리스트·오늘 통계 provider 를invalidate하여 재조회만 수행Idempotency-Key헤더를 항상 동봉 (세션 1 회당 UUID v4 1 개, 성공 시 폐기)GET /today-stats응답(totalMinutes/sessionCount/streak) 을 신규 provider 로 노출하여 오늘 통계·연속 일수 표시가 서버 단일 소스로 동작⚙️ 작업 내용
API 연동 대상 (총 3 개)
POST /api/timer-sessions— 세션 저장 + 연료 자동 충전 +actualMinutes자동 누적 (TimerSessionCreateResponse응답)todoId?,todoTitle?,startedAt,endedAt,durationMinutesIdempotency-Key(UUID v4)INVALID_SESSION_TIME/INVALID_DURATION/SESSION_TOO_SHORT/SESSION_TOO_LONG/FUTURE_SESSIONTODO_NOT_FOUND— 연결된 Todo 가 본인 소유 아님 / 없음GET /api/timer-sessions— 세션 페이지네이션 조회 (TimerSessionListResponse)startDate,endDate(YYYY-MM-DD),todoId,page(기본 0),size(기본 20, 최대 100)startedAt내림차순 고정GET /api/timer-sessions/today-stats— 오늘 총분/세션수/streak (KST,TodayStatsResponse)레이어별 작업
timer_session_remote_datasource.dart(Retrofit) — 3 개 엔드포인트 정의 +Idempotency-Key헤더 지원TimerSessionCreateRequestModel,TimerSessionCreateResponseModel({ session, fuelCharged })TimerSessionPageResponseModel({ content[], page, size, totalElements, totalPages })TodayStatsResponseModelTimerSessionRepositoryImpl재작성 — 서버 우선 호출 → 응답 캐시 갱신, 401 / 네트워크 실패 시 캐시 폴백LocalTimerSessionRepositoryImpl로 리네임 (게스트·오프라인 전용, 동작 보존)TimerSessionEntity기존 유지 (필드 동일)TimerSessionCreateResult({ session, fuelCharged }),TimerSessionPage,TimerSessionFilter,TodayStatsTimerSessionExceptionInvalidSessionTime,InvalidDuration,SessionTooLong,FutureSession,LinkedTodoNotFoundcreateSession,getSessionsPage,getTodayStatstimer_session_provider.dart—isAuthenticatedProvider기반 분기 (Fuel 패턴 복제), 페이지네이션 화today_stats_provider.dart신규 — 인증 모드 = 서버 호출, 게스트 = 기존 로컬 계산timer_provider.dart(TimerNotifier) 수정:idempotencyKey(UUID v4) 발급, 진행 중 보관repo.createSession()1 회 → 성공 시fuelNotifier·todoNotifier·timerSessionListNotifier·todayStatsNotifierinvalidateApiEndpoints에timerSessions,timerSessionsTodayStats상수 추가AuthInterceptor의 재발급 큐 재사용테스트
TimerSessionRemoteDataSource직렬화/역직렬화 +Idempotency-Key헤더 전달 단위 테스트TimerSessionRepositoryImpl서버 우선 / 캐시 폴백 / 400 5 종 → 커스텀 예외 매핑 / 404TODO_NOT_FOUND처리LocalTimerSessionRepositoryImpl회귀 가드 (기존 게스트 동작 보존)isAuthenticatedProvider모킹) + 응답 후 fuel/todo invalidate 호출 검증TimerNotifierstop 흐름 — 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>형식비고
guest_timer_sessionsSharedPreferences 데이터는 게스트 전용으로 유지, 로그인 시 서버로 업로드하지 않음 (정책 합의 시 후속 이슈로 분리)actualMinutes누적·streak 계산을 단일 트랜잭션으로 처리하므로, 본 이슈 이후 클라이언트의 중복 계산 코드는 인증 모드 경로에서 모두 제거됨🙋♂️ 담당자