Phase 0: 환경 구축 ████████████████████ 100%
Phase 1: 인증 & 사용자 ████████████████████ 100%
Phase 2: 운동 기록 ████████████████████ 100%
Phase 3: 식단 기록 ████████████████████ 100%
Phase 4: 신체 측정 & 진행 사진 ████████████████████ 100%
Phase 5: 목표 & 인사이트 ████████████████████ 100%
Phase 6: MVP 출시 준비 ████████████████████ 100%
https://api.gainsy.site)RateLimitingFilter 추가: /api/v1/auth/** 분당 20회 IP 제한, 초과 시 429 응답application-prod.yml — Tomcat threads(max 200, min-spare 20, accept-count 100), HikariCP leak-detection-threshold: 60000, JSON 로그 포맷 추가/actuator/health/readiness, /actuator/health/liveness 활성화 및 permitAll 추가RegisterRequest에 @Past 검증, AuthService에 ChronoUnit.YEARS 나이 계산 추가 (L3 완료)/healthcare/prod/app) + RDS storage/connections 알람 추가deletion_protection = true, skip_final_snapshot = falseSignUpView — 이용약관·개인정보처리방침 필수 동의 체크박스 추가, 두 항목 모두 동의 시 가입 버튼 활성화, 링크 탭 시 Safari 오픈 (L3 완료)MyPageView — 앱 정보 섹션에 이용약관·개인정보처리방침 링크 추가PUT /api/v1/diet/meals/{mealId}/logs/{logId} — 식사 기록 수정 API 신규 추가AiNutritionEstimationService 예외 시 폴백 응답 반환 (AI API 오류가 클라이언트로 전파되지 않도록 안정화)application-prod.yml — Nginx 리버스 프록시 뒤에서 X-Forwarded-* 헤더 신뢰 설정 (forward-headers-strategy: native)api.gainsy.site DNS A 레코드 (EIP 15.165.250.185) 추가api_domain 변수 전달, Nginx 리버스 프록시 prod 설정 완성ensureBucketExists 빈 제거, prod S3 설정 환경변수로 전환host.docker.internal 사용으로 S3 presigned URL 수정ci-backend.yml, ci-ios.yml, dev-to-prod.yml)deploy-dev.yml 삭제 (dev EC2 없음), deploy-prod.yml → dev-to-prod.yml 리네임backgroundPrimary, backgroundPage, backgroundCard, textPrimary 등 다크/라이트 완전 지원preferredColorScheme 적용 — AppContainer 기반 테마 전환 동작Dynamic Type 지원 — Typography 스케일 재구성, 전체 폰트 사이즈 유연화PrivacyInfo.xcprivacy — App Store Privacy Manifest 추가 (API 사용 이유 선언)Info.plist 앱 메타데이터 및 권한 설명 업데이트 (카메라, 사진 라이브러리, 건강 등)EditMealLogView/EditMealLogViewModel + PUT API 연결APIClient 401 수신 시 AuthState 초기화 후 온보딩 전환DiaryView 완성 — 날짜별 운동/식단/신체 카드, 캘린더 연동, 신체 변화 측정 버튼 조건부 숨김api.gainsy.site) — defaultBaseURL 교체, Info.plist ATS 예외 블록 제거DEVELOPMENT_TEAM = HVVJG5AF82 설정 (project.yml)MainActor 격리 경고 수정Vitae → gainsy (CFBundleDisplayName), App Store 등록명 GainsyViewModel 의존성 프로토콜 추출 — 테스트 주입 가능 구조로 전환docs/operations/DOMAIN_MIGRATION_GAINSY_SITE.md — api.gainsy.site 도메인 전환 운영 가이드 추가docs/legal/privacy.html, terms.html — GitHub Pages 기반 개인정보 처리방침/이용약관 페이지 추가FcmConfig — FCM_CREDENTIALS_PATH 기반 FirebaseApp 조건부 초기화 (mock/real 분기)FcmService — FCM 메시지 발송 래퍼. FirebaseApp 없으면 MOCKED 반환 (ObjectProvider 패턴)NotificationService — 주간 요약 알림 비즈니스 로직, 6일 이내 중복 발송 방지WeeklyNotificationScheduler — 매주 월요일 KST 09:00 자동 발송 (@ConditionalOnProperty로 로컬 비활성화)V14__notification_logs.sql — 알림 발송 이력 테이블 (type, status, fcm_token, error_message)UserRepository.findAllWithFcmToken() — FCM 토큰 보유 사용자 조회 쿼리 추가@EnableScheduling — HealthCareApplication에 스케줄링 활성화FcmServiceTest 3개 + NotificationServiceTest 4개 추가 (7개 통과)FcmTokenUploader — fcmTokenRefreshed 수신 → PATCH /api/v1/users/me fcmToken 업로드AppContainer — FcmTokenUploader 소유, 앱 생명주기 동안 토큰 갱신 자동 처리AppDelegate.userNotificationCenter(_:didReceive:) — 알림 탭 시 pushNotificationTapped 브로드캐스트MainTabView — pushNotificationTapped 수신, WEEKLY_SUMMARY 타입 → 탐색 탭으로 자동 이동ProgressPhotoService.deletePhoto() — 소유권 검증 후 soft-delete, ResourceNotFoundException / UnauthorizedException 분기DELETE /api/v1/body-measurements/photos/{photoId} 엔드포인트 추가ProgressPhotoServiceTest — deletePhoto 성공·notFound·타인 소유 3개 케이스 추가db/migration/V13__food_catalog_usage_count.sql — food_catalog 테이블에 usage_count BIGINT NOT NULL DEFAULT 0 컬럼 추가FoodCatalog 엔티티 — usageCount 필드 추가FoodCatalogRepository — searchAll() 공개 검색, incrementUsageCount(), decrementUsageCount() 메서드 추가FoodCatalogResponse — usageCount, createdByUserId 필드 포함CreateCustomFoodRequest — Bean Validation 강화 (이름 100자 이내, HTML 특수문자 차단, 칼로리 0~9999)FoodCatalogService — 같은 이름+카테고리 중복 검사, NFC 정규화, 연속 공백 축약DietLogService — 식단 기록 생성/삭제 시 사용 식품별(distinct) usage_count +1/-1FoodCatalogController — POST /api/v1/diet/catalog 누구나 등록 가능, 검색 공개화(GET /api/v1/diet/catalog Authorization required=false)AiNutritionController — ObjectProvider 패턴으로 OPENAI_API_KEY 미설정 시 안전 처리Date+Formatting.swift — Array.uniqued(by:) 확장 메서드 추가APIEndpoint.swift — getExerciseCatalog muscleGroup 파라미터 추가DietModels.swift — FoodCatalogItem usageCount, createdByUserId 필드 추가AddDietLogViewModel — submitCustomFood(), showCustomFoodForm 상태 추가AddDietLogView — 빈 검색 결과 시 “직접 등록하기” 버튼, AddCustomFoodView 추가AddCustomFoodView — 식품명, 카테고리(Picker), 칼로리(필수), 단백질/탄수/지방 입력 폼 + 검색어 자동 채움 + 성공 시 결과 prependcatalogResults, externalResults — displayName 기준 uniqued(by:) 처리로 중복 제거MainTabView — 탭 전환 시 이전 탭의 NavigationStack을 루트로 리셋APIEndpoint — deleteProgressPhoto(id:) case 추가ProgressPhotoViewModel — deletePhoto() 삭제 후 로컬 상태 즉시 반영, 비교 모드(compareMode 토글·선택·isSelected) 추가ProgressPhotoView — 그리드 셀 context menu 삭제, 상세 화면 하단 삭제 버튼, 툴바 ‘비교’ 토글 버튼compareBar — 선택 안내 + ‘비교 보기’ 버튼 (2장 선택 시 활성)PhotoCompareView — 두 사진 좌우 분할 비교 화면 (날짜·체중 오버레이)AddExerciseSessionViewModel / AddExerciseSessionView — 부위별 운동 탐색 그리드(12개 근육군 이모지 카드), 선택 시 해당 근육군 결과 필터AiNutritionEstimationService — 한국어 음식명 → 100g 기준 영양성분 AI 추정 (OpenAI Responses API 재사용)POST /api/v1/diet/ai-estimate — 공공 API 검색 결과 없을 때 클라이언트 폴백용 엔드포인트AiExerciseEstimationService — 한국어 운동명 → muscleGroup, exerciseType, MET값 AI 추정POST /api/v1/exercise/ai-estimate — 카탈로그 검색 결과 없을 때 클라이언트 폴백용 엔드포인트V11__exercise_catalog_seed.sql — 110개 운동 시드 데이터 (근육군 14종, 한/영 이름, MET값 포함)@ConditionalOnExpression으로 OPENAI_API_KEY 미설정 시 자동 비활성화APIEndpoint — .aiEstimateFood, .aiEstimateExercise, .createCustomFood, .createCustomExercise 4개 case 추가DietModels.swift — AiNutritionEstimateResponse, AiNutritionEstimateRequest 모델 추가ExerciseModels.swift — AiExerciseEstimateResponse, AiExerciseEstimateRequest 모델 추가AddDietLogViewModel — estimateWithAI(), addAiEstimatedFood() 메서드 + aiEstimateResult, isAiEstimating 상태 추가AddDietLogViewModel — 식품 검색 500ms 디바운스, 진행 중 검색 취소, 느린 이전 응답 덮어쓰기 방지 로직 추가AddDietLogView / FoodSearchSheet — onChange 즉시 호출 제거, return 즉시 검색 유지, 검색어 삭제 시 결과 초기화 경로 통일AddExerciseSessionViewModel — estimateWithAI(), addAiEstimatedExercise() 메서드 + aiEstimateResult, isAiEstimating 상태 추가AddDietLogViewModelTests — 디바운스, 즉시 검색, 검색어 삭제, 느린 응답 역전 방지 시나리오 단위 테스트 추가isAiEstimated: true + disclaimer 필드 포함, 사용자 수정 후 저장 플로우 설계InsightsControllerTest 10개 추가 — 주간 회고 weekOffset, 빈 데이터, 401 인증 오류, 날짜 유효성 검증 등InsightsServiceTest 11개 추가 — 델타 반올림(2자리), ENDURANCE 목표 스킵, WEIGHT_LOSS 달성률 계산 등ProgressPhotoResponse.isBaseline @JsonProperty 누락 버그 수정 (직렬화 시 is prefix 탈락 방지)ExploreView)에서 WeeklyRetrospectiveView, ChangeAnalysisView 진입점 연결ProgressPhotoView onChange iOS 16 호환 시그니처 수정DiaryView 중복 HistoryCalendarView/HistoryCalendarViewModel 파일 삭제InsightsController / InsightsService 신규 구현 — GET /api/v1/insights/weekly-summary, GET /api/v1/insights/change-analysisGoalService: ENDURANCE 진행률을 운동 세션 기간 합산으로 계산하는 loadExercisePoints() 추가GoalSummary.percentComplete 목록 조회 시 읽기 전용 경량 계산(calculatePercentCompleteReadOnly) 적용GoalProgressResponse weeklyRateTarget 필드 추가SecurityConfig: RestAuthenticationEntryPoint, RestAccessDeniedHandler JSON 응답 적용JwtSecurityIntegrationTest 추가 — 인증 없음/무효 토큰/유효 토큰 시나리오 검증InsightsModels.swift — WeeklySummaryResponse, ChangeAnalysisResponse 모델 정의APIEndpoint — .getWeeklySummary, .getChangeAnalysis case 추가WeeklyRetrospectiveView/ViewModel — 주간 네비게이션 + 실데이터 연동 완성ChangeAnalysisView/ViewModel — 기간 선택 프리셋 + 실데이터 연동 완성EditGoalView/EditGoalViewModel — GoalProgressView에서 목표 수정 진입점 추가GoalModels: GoalProgressResponse.weeklyRateTarget 필드 반영AuthController: @AuthenticationPrincipal 대신 Bearer 헤더 직접 해석으로 로그아웃 경로 정리UserController: GET/PATCH/DELETE /api/v1/users/me에서 Bearer 헤더 직접 검증AuthControllerTest 추가 — 회원가입/로그인/토큰 갱신/로그아웃 MockMvc 단위 테스트UserControllerTest 추가 — 프로필 조회/수정/삭제 MockMvc 단위 테스트JwtSecurityIntegrationTest 추가 — 실제 SecurityFilterChain + JwtAuthenticationFilter 기준 인증 없음/무효 토큰/유효 토큰 시나리오 검증SecurityConfig: RestAuthenticationEntryPoint, RestAccessDeniedHandler 연결로 보안 실패 응답을 JSON 형식으로 통일ProgressPhotoController: 경로를 /api/v1/body-measurements/photos로 통일FoodCatalogRepository: 공백 무시 검색 + prefix 우선 정렬로 식품 검색 품질 개선FoodCatalogService: 검색어 trim 정규화 및 테스트 추가ExternalFoodResult: @Jacksonized 추가, 외부 DTO 역직렬화 안정화V10__normalize_endurance_goal_units_to_minutes.sql: endurance 목표/체크포인트의 초 단위를 분 단위로 정규화docs/design-docs/DB_SCHEMA.md: endurance 목표 단위를 minutes 기준으로 업데이트ProgressPhotoModels, ProgressPhotoViewModel, ProgressPhotoView, AddProgressPhotoView 추가GET /api/v1/body-measurements/range, GET /api/v1/body-measurements/at-or-before 기반으로 연동APIEndpoint: 진행 사진 업로드 URL 발급/등록/목록 조회 계약 추가GoalModels: endurance 단위를 분 기준으로 표시하고 목표 타입별 주간 변화량 규칙 반영AddGoalViewModel, AddGoalView: 목표 단위/주간 변화량 입력 UX 보정HomeViewModel: 활성 목표 진행률 API 연동으로 홈 대시보드 정확도 개선AddExerciseSessionViewModel, AddExerciseSessionView: 운동 시간 입력 및 ISO-8601 시작/종료 시각 전송 지원MyPageViewModel, MyPageView: 프로필 조회/수정/삭제 실데이터 연결HomeView, RecordHubView, MyPageView: 디자인 시스템을 활용한 UI 개편ios/Configs/Debug.xcconfig, Release.xcconfig: 환경별 iOS 설정 파일 추가docs/design-docs/EXERCISE_EXTERNAL_INTEGRATION.md: 운동 외부 데이터 연동 원칙과 채택안 정리docs/references/EXERCISE_API_SURVEY_2026-04-22.md: 운동 종목·칼로리 API 조사 문서 추가gan-harness/spec.md, gan-harness/eval-rubric.md: 평가용 스펙과 루브릭 추가CLAUDE.md: 저장소 작업 규칙 정리POST /api/v1/diet/ai-estimate) — 한국어 음식명 → 영양성분POST /api/v1/exercise/ai-estimate) — 한국어 운동명 → MET/분류POST /api/v1/diet/catalog) — 누구나 공용 DB에 등록 가능, 중복 방지usage_count) — 식단 기록 추가/삭제 시 자동 카운팅, 검색 시 정렬DELETE /api/v1/body-measurements/photos/{photoId}, soft-delete, 소유권 검증)estimateWithAI, addAiEstimatedExercise)PhotoCompareView — 같은 부위 before/after 좌우 분할 비교 (날짜·체중 오버레이)APIClient 토큰 refresh/401 재시도, 핵심 ViewModel, 온보딩/로그인/메인 탭 UI smoke 테스트 포함 전체 xcodebuild test 통과api.gainsy.site)DEVELOPMENT_TEAM, Bundle ID com.kingloo.gainsy.ios)PrivacyInfo.xcprivacy Privacy ManifestPOST /api/v1/diet/ai-estimate) + iOS ViewModel 연동 완료POST /api/v1/diet/catalog) + iOS AddCustomFoodView 완료GET /api/v1/diet/catalog Authorization required=false) 완료HomeViewModel, GoalProgressViewModel, ProgressPhotoViewModel, MyPageViewModel, APIClient refresh/401 재시도, 온보딩/로그인/메인 탭 smoke 검증 완료GoogleService-Info.plist 미설정/Firebase dev skip 로그와 APNs entitlement 경고가 출력되지만 테스트 실패와 무관하다.gh pr create 실행 (본문 작성 완료)docs/exec-plans/MVP_ROADMAP.mddocs/exec-plans/BACKEND_TODO.md에서 관리docs/exec-plans/BACKEND_IOS_SYNC_WORKFLOW.md를 기준으로 관리마지막 업데이트: 2026-05-16 (심사 제출 후 백엔드 보안·운영 강화 + iOS 필수 동의 + prod SSL 재발급 완료)