Moodiary Backend — Roadmap¶
백엔드 작업의 현재 위치 + 다음 경로. PR 머지 시 갱신. 마지막 갱신: 2026-06-15 (AI
/chat실연동 운영 반영 완료 — release #96 으로 운영 활성화(AI_CLIENT_MODE=http+AI_SERVER_URLHF Space +AI_TIMEOUT_MS). 운영에서 일기 작성→POST /chat→emotion/aiText/homeComment저장→폴링 수신 end-to-end 검증(Postman). 남은 건 FE 측 폴링 응답 필드(emotion/homeComment) 처리. dev→main 충돌은-s ours(전략)로 해결 —-X ours(옵션) 함정 회피(T-037).)📚 상세는 다른 문서로 위임: -
api-contracts.md— API 명세 (request/response/예시/외부 AI 계약) -security.md— JWT / 비밀번호 / 시크릿 / 인가 / 약점 -troubleshooting.md— 막힘 + 원인 + 해결 로그 (지속 누적) -learning-notes.md— 모르고 물어봐서 배운 기술 개념 정리 (발표 / Q&A 대비) -ops-runbooks/ec2-cheatsheet.md— EC2 운영 치트시트 - Swagger UI — 구현된 API 의 실시간 진실의 원천
📊 현재 상태¶
| 항목 | 값 |
|---|---|
| 운영 URL | http://15.165.95.129:8080 (Elastic IP, 고정) |
| 운영 반영 | Post CRUD + 인증 (회원가입 / JWT 로그인 / Refresh Token rotation / Google OAuth2 소셜 로그인) + 소유권 + 비동기 AI 응답 (실 /chat 연동, HF Space) + MkDocs 문서 사이트 (/docs/) |
| dev 에만 | Calendar API (emoji=null 임시) |
| 외부 대기 | AI /chat 운영 반영 완료 ✅ — FE 측 폴링 응답 필드(emotion/homeComment) 처리만 남음 |
| 다음 핵심 경로 | AI /chat 운영 반영 ✅ + FE 폴링 필드 처리(외부) ‖ PR 8 통합 검증 (FE Vercel + CORS ✅) → PR 5 후속 (캘린더 emotion JOIN — AI 실연동 완료로 진행 가능) → PR 6 (Flyway baseline) |
| 스택 | Java 25 / Spring Boot 4.0.6 / EC2 + RDS MySQL 9 |
✅ 완료 (운영 반영)¶
| 도메인 | PR | 핵심 결과 |
|---|---|---|
| Post CRUD | #14, #24 | 작성자 자동 채움, 본인 글만 조회/수정/삭제, 403/404 분기 |
| 인증 | #20, #21, #23, #24 | 회원가입 (BCrypt, UNIQUE) + HS512 JWT + 열거 공격 방지 401 |
| JWT 필터 | #23 | Bearer → SecurityContext UUID principal, @AuthenticationPrincipal UUID 패턴 |
| Refresh Token (PR 10) | #63, #66 | access 1h / refresh 2w + rotation + SHA-256 hash 저장 + POST /auth/refresh + POST /auth/logout 의미 변경. release PR #66 으로 운영 반영. |
| 전역 예외 | #14, #20, #23, #24 | 400/401/403/404/409/500 → {"message":"..."} 통일 |
| 인프라 | 다수 | EC2 + RDS + Elastic IP + AWS SSM CD + application.yaml env 플레이스홀더 + JWT_SECRET 주입 |
| CORS (PR 8 BE) | #38 | CorsConfig + APP_CORS_ALLOWED_ORIGINS 외부화 + compose :- default |
| GitHub Pages | #53, #68, #72~#74 | landing + Marp 슬라이드 + MkDocs Material 문서 사이트 (/docs/) 자동 배포. 트리거 main → dev (PR #68) — release 전 반영. PR 9 (#72) 하이브리드 옵션 C 로 learning-notes 가 MkDocs 안으로 흡수됨. T-029/T-030 두 fix iteration (README ↔ index 자동 충돌, outbound 링크 평면화 충돌). |
| PR 9 MkDocs (단독) | #72, #73, #74 | /docs/ 에 Material 테마 문서 사이트. claude-docs/* + README 한 곳 검색. 사이트 홈 + 로드맵 + API 명세 + 보안 + 트러블슈팅 + 학습 노트 + 운영 Runbook + README 모두 nav. strict 빌드 컨벤션 함정 2건 박힘 (T-029 / T-030). |
| AI 응답 골격 | #59 | AiResponse 엔티티 + AiResponseClient Stub + @Async + GET /post/{id}/ai-response |
AI /chat 실연동 (PR 4-final) |
#90, #95, #96 | HTTP 어댑터(RestClient) + ai.client.mode=http 토글 → HF Space /chat 실호출. 폴링 DTO emoji→emotion+homeComment. 운영 AI_* env 주입 + http 전환으로 운영 반영 완료, Postman end-to-end 검증. dev→main 충돌 -s ours 해결 (T-037). |
| 학습 파이프라인 (PKM) | #67, #70 | learning-notes.md 신설 (12 항목) + 3개 항목 (Spring 비동기 / AWS SSM 메커니즘 / CORS) goospel.github.io 공개판 첫 승격 |
| PR 12 Google OAuth2 (풀체인) | #76, #77, #78, #79, #80, #81 | 운영 반영 완료. token-exchange 패턴 — FE 가 Google Sign-In 으로 id_token 받아 BE 에 POST → BE 가 Google tokeninfo 호출 + aud (confused deputy 방어) + email_verified 검증 → DB 의 (provider, providerId) 조회 → 신규/기존 분기 + JWT 발급. oauth2.client.mode=stub\|http 토글로 dev/운영 분리. Kakao 제외 (졸업프로젝트 범위). users 테이블 ALTER 4건 (provider/providerId/password nullable/UNIQUE) 사전 적용. 운영 deploy 직후 T-031 (path enum case-sensitivity) + T-032 (silent 500 진단 가림막) 함정 발견 → hot-fix release PR #81 로 박음. |
| 테스트 | 누적 | 120+ pass — Controller 슬라이스 + Service 단위 + JWT 라운드트립 + CORS preflight + Refresh rotation + OAuth2 Stub + WireMock Google tokeninfo + OAuth2 path case 5종 |
🔄 진행 중 / 외부 대기¶
| PR | 상태 | 대기 사유 |
|---|---|---|
PR 5 — Calendar API (GET /calendar?year=YYYY&month=MM) |
🟡 85% | dev 머지 완료. emoji LEFT JOIN + (user_id, created_at) 인덱스는 PR 4-final 후 후속 PR (AI 실연동 완료로 이제 진행 가능) |
| PR 8 후반 — 프론트 통합 검증 (Vercel) | 🟡 70% | FE 가 S3 대신 Vercel 배포 ✅ + BE CORS origin 추가 ✅ (Mixed Content 해소 + 403 Invalid CORS request 해소). 회원가입 / 로그인 / 일기 CRUD / 캘린더 + OAuth2 전 플로우 통합 검증만 남음. |
📋 백로그 (우선순위 + 의존성)¶
PR 8 후반 (FE S3 배포) ── 졸업 데모 가시성 ⭐⭐⭐, FE 회신 대기
PR 4-final (AI 실어댑터) ── 차별 기능 ⭐⭐⭐, AI 합의 대기
└─► PR 5 후속 (emoji JOIN)
PR 6 (Flyway) ── PR 4-final 머지 시 스키마 베이스라인 같이
PR 11 (OAuth2 Resource Server) ── 선택, 먼 미래 (PR 10 ✅ 정착 후 큰 리팩토링)
PR 7 (ECS 이전) ── 먼 미래, HTTPS/도메인 도입 시
PR 12 Google OAuth2 풀체인 #76 ~ #81 운영 반영 완료 — backlog → 완료. 외부 Console 셋업 (Google Cloud Console) + RDS 사전 ALTER 4건 (users) + env 3-way sync 모두 끝. release PR #79 + hot-fix #81 두 사이클. PR 10/9 도 이미 완료 이동. PR 11 의 "PR 10 권장" 의존도 해소.
각 PR 의 구현 체크리스트 / 위험 / 운영 머지 전 필수 항목은 해당 PR 시작 시점에 PR body 에 작성한다. plan.md 는 "무엇 / 왜 / 의존" 까지만.
PR 4-final — AI 비동기 응답 실어댑터 🤖 ⭐⭐⭐ — ✅ 운영 반영 완료 (release #96)¶
완료: AiResponseClient interface + StubAiResponseClient/HttpAiResponseClient(RestClient) + ai.client.mode 토글 + WireMock. HF Space /chat 실계약: 요청 {user_text, recent_emotions:"", diary_date("M월 d일")} → 응답 {emotion, aiText, homeComment, diaryDate}. 폴링 DTO emoji→emotion+homeComment. invoke(postId, content, diaryDate) 시그니처. timeout 기본 30s (HF cold start). 실패는 상위 RestClientException 으로 catch (T-035).
운영 활성화 (완료): ① EC2 .env 에 AI_CLIENT_MODE=http + AI_SERVER_URL=https://dlqudwn153-moo-diary-ai-prompt.hf.space + AI_TIMEOUT_MS=30000 주입 후 docker-compose up -d ✅ ② 운영 DB ai_response 에 emotion/home_comment 컬럼 ddl-auto:update 자동 add ✅ ③ Postman 으로 일기→AI 감정/코멘트 폴링 end-to-end 검증 ✅. 남은 일(FE): 폴링 응답 필드 emoji→emotion+homeComment 처리.
의존: PR 4-pre ✅. AI 서버 배포 + 계약 확정 + 운영 반영 모두 완료.
PR 5 후속 — Calendar emoji JOIN 📅 ⭐⭐¶
Why: PR 5 가 emoji=null 로 우회됐던 거 채움. AiResponse LEFT JOIN 으로 일자별 이모지 매핑.
의존: PR 4-final 머지. 부가: post(user_id, created_at) 복합 인덱스 수동 DDL.
PR 6 — Flyway 도입 ⭐¶
Why: ddl-auto: update 가 컬럼 삭제 / 타입 변경 / 제약 추가 불가. 스키마 안정화 시점에 잠금.
범위: flyway-mysql + V1__baseline.sql (users + post + ai_response) + ddl-auto: validate 전환 + 운영 RDS 에 flyway baseline 적용.
의존: PR 4-final 머지 (AiResponse 스키마 안착 후 베이스라인 같이).
PR 8 후반 — 프론트 통합 검증 (Vercel) 🌐 ⭐⭐⭐¶
Why: 졸업 발표에 "프론트가 떠서 BE 호출" 까지가 의미 있는 데모.
진행: FE 가 S3 대신 Vercel (https://moo-diary-ten.vercel.app) 에 배포. Mixed Content 해소 + EC2 .env 의 APP_CORS_ALLOWED_ORIGINS 에 Vercel origin 추가로 CORS 연결됨. 남은 건 회원가입 / 로그인 / 일기 CRUD / 캘린더 + OAuth2 전 플로우 통합 검증.
의존: FE 회신 (외부). 주의: Vercel 프리뷰 배포는 production 과 다른 서브도메인 → 검증에 쓰려면 별도 origin 추가 필요 (와일드카드는 allowCredentials=true 와 충돌해 금지).
PR 11 — OAuth2 Resource Server 마이그레이션 🔐 ⭐ (선택)¶
Why: 수동 JwtAuthenticationFilter → Spring Security 표준 Resource Server. 검증 / 클레임 추출 / JWK 도입 자연스러움.
고려 분기점: 자체 발급 유지 vs 외부 IdP, 토큰 호환성 (소프트 전환), JwtAuthenticationConverter 로 UUID principal 추출, 발급 코드만 보존.
의존: PR 10 ✅ — 큰 리팩토링 안전한 시점. 위험: 졸업프로젝트 범위 초과 가능 — "시간 남으면" 카테고리.
PR 7 — ECS 이전 ⭐ (먼 미래)¶
Why: 운영 안정성 + 확장성 + HTTPS / 도메인. 범위: ECR + Fargate + ALB + ACM + CloudWatch. 위험: 졸업프로젝트 범위 초과 가능.
🤝 FE 협업¶
API 명세 + 호출 패턴 + 변경 정책 → api-contracts.md 단일 소스.
프론트 배포 가이드 → ops-runbooks/frontend-s3-cd-setup.md.
공유 자산:
- OpenAPI spec: http://15.165.95.129:8080/v3/api-docs (TS 타입 / 클라이언트 / mock 자동 생성)
- Swagger UI: http://15.165.95.129:8080/swagger-ui/index.html
- 변경 정책: breaking change 시 사전 공유. 응답 포맷 {"message":"..."} 깨면 FE 다 깨짐 — 절대 변경 X.
💡 의사결정 로그¶
왜 이렇게 했는지. 나중에 "왜?" 싶을 때 참고.
| 결정 | 이유 |
|---|---|
| Spring Boot 4.0 + Java 25 | 최신 학습, 궁합 |
| UUID 기본키 | 분산 환경 가정, ID 추측 방지 |
| AWS SSM 배포 (SSH 폐기) | SSH 키 관리 부담 제거 |
| RDS 단일 소스 + Elastic IP | 데이터 보존 + IP 고정 |
| AI = 비동기 + DB 상태 (큐 X) | 졸업프로젝트 범위, 단순성 |
| JWT HS512 + Authorization 헤더 | Stateless, FE 단순성 (localStorage) |
| Refresh Token: DB 저장 + rotation + SHA-256 hash | DB 가 털려도 raw 복원 불가 + 훔친 refresh 단 1회 유효. httpOnly cookie 대신 DB 인 이유 — CORS 의 credentials=true 와 와일드카드 충돌 회피 + cross-domain SPA 호환성. |
| access 1h / refresh 2w | 자주 쓰이는 건 짧게 (탈취 노출 최소), 거의 안 쓰이는 건 길게 (UX 유지) |
@AuthenticationPrincipal UUID 패턴 |
컨트롤러 시그니처 깔끔 |
| GET /post 본인 글만 | 일기 도메인, 캘린더 일관 |
Post.postDate (LocalDate, NOT NULL) + 누락 시 today 폴백 |
일기 앱의 표준 "어느 날의 일기인가" 분리. createdAt (자동 작성 시점) 과 의미 분리 — "지나간 날짜의 일기" 시나리오 지원. 서버 폴백으로 FE 가 명시 안 해도 "오늘 일기" 기본 동작. 미래 날짜 server-side 검증은 박지 않음 (필요해지면 @PastOrPresent 추가). |
application.yaml 추적 + env var 플레이스홀더 |
gitignore 사고 (T-013) 재발 차단 |
ddl-auto: update (안정화 시 validate) |
스키마 미확정 단계 손DDL 부담 제거 |
| 열거 공격 방지 401 | 이메일 존재 노출 X |
이모지 = 유니코드 그대로 (utf8mb4) |
변환 로직 X, 가장 단순 |
| 캘린더 하루 다중 글 = 마지막 글 | 구현 단순 |
캘린더 응답 = 한 달 전체 (빈 날 emoji: null) |
FE 부담 감소 |
| 프론트 = S3 only (CloudFront/도메인 X) | CloudFront 가면 BE 도 HTTPS 작업 연쇄 → 졸업 범위 초과 |
| 프론트 배포 = S3 → Vercel 전환 (2026-06) | FE 담당자가 Vercel 선택 (Git push 자동 배포 + 무료 HTTPS). 위 "S3 only" 가정 대체. BE 는 CORS origin 만 추가하면 됨 — allowlist 가 env 외부화(CorsConfig)라 코드 변경 없이 EC2 .env 의 APP_CORS_ALLOWED_ORIGINS 에 https://moo-diary-ten.vercel.app 추가로 끝. |
| 프론트 CD = BE 담당자 직접 | FE 담당자 CI/CD 학습 부담 흡수 |
| PR 4 → PR 4-pre / PR 4-final 분할 | AI 외부 합의 대기로 막힌 차단 해소 |
| PR 생성 = Claude / 머지 = 사용자 | 분담 명시 (PR #56) |
| Pages trigger main → dev (PR #68) | learning-notes 가 release 전에도 반영되도록. dev 가 main 의 superset 이라 안전. environment allowlist 도 함께 갱신 필요 (T-028). |
| 학습 (PKM) 3-layer 시스템 — project learning-notes → goospel.github.io 공개판 → learning-vault 사적 | 현장 메모 마찰 0 + 의식적 승격 + 사적 / 공개 분리. 묶음 기준 3-5개 / release 직후 / 4문 자격. PR #67/#70 으로 첫 사이클 가동. |
| PR 9 MkDocs 하이브리드 옵션 C (#72) | landing 보존 + learning-notes 를 MkDocs 안으로 흡수. 옵션 A (sub-path 만 추가, 디자인 4종 공존) 와 B (통째 교체, 기존 landing 재작업) 의 균형점. 둘 다 잃을 필요 없음 — landing 디자인 보존 + 검색 기능 + 문서 결속력. |
| 빌드 시점 sed 치환 — 원본 contract / 사이트 contract 분리 점 (#73 T-029 / #74 T-030) | claude-docs/* 원본은 GitHub UI 친화 (project-readme.md, ./claude-docs/X.md), MkDocs 빌드 사본은 평면화된 디렉토리. 둘 다 만족시키려면 빌드 시점에 사본에만 sed. README outbound 는 GitHub blob URL 로 외부화. mkdocs / docusaurus / hugo 어디서나 같은 패턴. |
| PR 12 분할 (12-pre + 12-final) | OAuth2 외부 Console 셋업 (사용자 책임, 1회) 이 BE 진행을 막는 차단점. Stub 으로 외부 의존성 차단 해소 → BE 만으로 OAuth2 흐름 정착 (12-pre) → 외부 셋업 후 실 어댑터 합류 (12-final). PR 4-pre / 4-final 과 같은 패턴 — 외부 의존성 분리는 차단 해소의 1번 도구. |
| Kakao 제외 (PR 12-final) | Kakao Developers 의 "사이트 도메인 localhost 거부" 가 FE S3 배포 선행을 요구 → 졸업프로젝트 범위에서 외부 마찰 큼. 학습 가치 (OAuth2 token-exchange 패턴) 가 Google 과 거의 동일해서 한 provider 로 충분. 부활 비용 작음: enum 값 + adapter 1개. 졸업 후 부활 시 같은 코드 패턴 복제. |
OAuth2 토글 패턴 (oauth2.client.mode=stub\|http) |
provider 추상화의 활성 구현을 부팅 시점에 결정. dev / 로컬 / 단위테스트 = stub, 운영 / 시연 = http. @ConditionalOnProperty + matchIfMissing=true 로 default 가 stub — 외부 키 없어도 부팅됨. 같은 패턴은 PR 4-final 의 ai.client.mode 로 재사용 예정. |
| Path enum case-insensitive 정규화 (#80 T-031 fix) | Spring 기본 String→Enum 변환은 case-sensitive. @PathVariable AuthProvider provider 가 소문자 google path 변환 실패 → MethodArgumentTypeMismatchException → generic 500. Swagger 의 "대소문자 무관" promise 와 어긋남. fix: controller 가 String 으로 받아 .toUpperCase() 명시 정규화 + 미지원 값은 InvalidOAuth2ProviderException (400). 같은 패턴은 다른 enum path 도입 시 재사용. |
GlobalExceptionHandler 의 generic Exception 에 ERROR 로깅 (#80 T-032 fix) |
@ExceptionHandler 가 catch 하면 Spring default exception logging 발동 안 함. 무로깅 silent 500 은 운영 진단 0. 응답 body 는 그대로 (사용자 노출 정보 변경 없음), log.error("...", e) 로 stdout 만 풍부. 향후 silent 500 후보 발견의 1차 단서. |
| Squash release 후 dev → main merge 충돌의 표준 해결 (#79, #81, #96) | main 의 release squash commit 이 dev 의 개별 commit 과 같은 줄 건드려 자동 머지 불가. dev 가 strict semantic superset 임을 명시 검증 후 git merge origin/main -s ours(merge 전략 — 결과 트리를 dev 통째로 채택)로 해결. ⚠️ -X ours(merge 옵션)는 함정 — 충돌 hunk 만 dev 로 풀고 비충돌 hunk 는 양쪽을 병합해서 main 에만 있던(= dev 가 지운) 줄을 부활시킨다 (#96 에서 삭제된 줄이 실제로 부활 → -s ours 로 재해결). git diff --stat origin/dev HEAD 가 비어야 트리 동일 확인. 이후 push 하면 release PR 자동 mergeable. 사용자 OK 필수 — auto classifier 가 처음엔 차단했던 패턴. T-037. |
GET /post 정렬/필터 = QueryDSL 동적 쿼리 (레벨 B, 페이징 분리) |
목록 조회에 정렬(기본 postDate desc) + 선택적 from/to/keyword. 조합이 선택적이라 파생 쿼리로는 메서드 폭발 → BooleanBuilder 로 null 조건만 skip 하는 단일 메서드 (PostSearchRepository, CalendarRepository 와 같은 QueryDSL 패턴). 정렬 필드는 화이트리스트 enum (PostSortField) — 임의 컬럼 정렬 차단. 페이징(Page<>)은 분리 — 응답 모양이 바뀌는 breaking change라 FE 계약 합의가 선행돼야 함. 잘못된 정렬/방향/범위는 InvalidPostSearchException(400). |
| 쿼리 파라미터 타입 변환 실패 글로벌 400 매핑 (T-034) | @RequestParam LocalDate 변환 실패(?from=abc)가 핸들러 공백으로 generic 500 → @ExceptionHandler(MethodArgumentTypeMismatchException) 로 400. T-031(path enum)이 controller .toUpperCase() 국소 우회였을 뿐 핸들러 공백을 안 닫은 게 재노출된 것 — 이번엔 카테고리째 봉합 (path enum / 쿼리 날짜 / @PathVariable UUID 전부 커버). |
| AI 서버 어댑터 = 토글 뒤 실구현 (PR 4-final, OAuth2 패턴 재사용) | AiResponseClient 인터페이스 + StubAiResponseClient(기본) / HttpAiResponseClient(ai.client.mode=http). AI 서버가 아직 미배포 + 형태 미확정이라 우리 측 잠정 계약으로 먼저 구현하고 토글 뒤에 둠 — 운영은 stub default라 안 깨지고, AI 서버 실체화 시 URL/필드명/인증만 맞추면 됨. 요청 {userId, postId, title, content} → 응답 {message, emoji}(message→content 매핑). 인증 보류(헤더 없음). 외부 의존성 분리 = 차단 해소 1번 도구(PR 4-pre / 12-pre 와 동일). |
AI 어댑터 실패 = 성공 외 전부 AiInferenceException (T-035) |
4xx/5xx/타임아웃/네트워크/파싱실패는 호출자엔 다 "AI 못 받음" 한 가지. RestClient read 타임아웃이 본문 추출 도중 터지면 ResourceAccessException 이 아닌 상위 RestClientException 으로 와서, 상위 타입으로 넓게 catch 해야 누락이 없다. cause 보존(T-033). @Async 라 호출자(일기 작성)엔 전파 안 됨 — FAILED 상태로만 표현. |
📝 Changelog¶
| 일자 | 변경 |
|---|---|
| 2026-06-15 | AI /chat 실연동 운영 반영 (release #96) — PR #95(/chat 계약 확정)를 dev→main release(#96)로 운영 반영. EC2 .env 에 AI_CLIENT_MODE=http + AI_SERVER_URL=https://dlqudwn153-moo-diary-ai-prompt.hf.space + AI_TIMEOUT_MS=30000 주입 후 docker-compose up -d. 운영에서 일기 작성→POST /chat→emotion/aiText/homeComment 저장→폴링 수신 end-to-end 검증(Postman). ddl-auto:update 가 ai_response 에 emotion/home_comment 컬럼 자동 add. dev→main 충돌은 -s ours(전략) 로 해결 — -X ours(옵션) 함정 회피(T-037). FE 남은 일: 폴링 응답 필드 emoji→emotion+homeComment 처리. |
| 2026-06-12 | AI 서버 실연동 — /chat 계약 맞춤 (PR 4-final 완성) — AI 배포됨(Hugging Face Space). 어댑터를 실계약으로 재작성: 요청 {user_text, recent_emotions:"", diary_date} → 응답 {emotion, aiText, homeComment, diaryDate}. AiResponse 엔티티 emoji→emotion+homeComment 컬럼 교체, 폴링 DTO/서비스/테스트 전부 갱신. invoke(postId, content, diaryDate) 시그니처 단순화. diary_date 는 "M월 d일" 포맷, recent_emotions 빈 문자열 고정(AI 담당자 합의). timeout 기본 10s→30s. compose.yaml 에 AI_* env 3개 추가. 운영 머지 전 필수: ① EC2 .env + GitHub Secrets 에 AI_CLIENT_MODE=http/AI_SERVER_URL/AI_TIMEOUT_MS ② FE 에 응답 필드 emoji→emotion+homeComment 통보. |
| 2026-06-11 | FE Vercel 배포 → CORS Vercel origin 추가 (운영 설정, 코드/PR 변경 없음) — FE 가 S3 대신 Vercel(https://moo-diary-ten.vercel.app)에 배포. Mixed Content 해소 후 /api/auth/login 이 서버에 도달하지만 403 Invalid CORS request (허용 origin 미등록). EC2 .env 의 APP_CORS_ALLOWED_ORIGINS 에 Vercel origin 추가 + sudo docker-compose up -d 재생성으로 해소. allowlist 는 CorsConfig (src/main/java/hoseo/moodiary/config/CorsConfig.java) 가 env 외부화 → 코드/PR 변경 없음, CD 가 .env 를 안 덮어써서 영속. 함정 2건: ① .env 에 키가 원래 없어 compose :- default(localhost)로 조용히 동작 → 운영 origin 전부 차단, ② 쉘 프롬프트에 KEY=val 입력은 파일 수정이 아니라 세션 변수라 무효 (echo '...' >> .env 로 써넣어야 함). |
| 2026-06-09 | fix: 긴 일기 본문 저장 시 500 (FE 버그 제보) — Post.content 가 length 미지정이라 JPA 기본 VARCHAR(255) 매핑 → 256자+ (특히 여러 줄) 본문이 Data too long → generic 500. 줄바꿈 무관, 길이가 원인. 컬럼 columnDefinition="TEXT" + DTO @Size(title 255 / content 10000)로 binding 단계 400. 회귀 테스트 PostContentLengthTest(@DataJpaTest) + 컨트롤러 400 테스트. T-036. 운영 머지 전 필수: ALTER TABLE post MODIFY COLUMN post_content TEXT; (ddl-auto:update 가 기존 컬럼 타입 변경 안 함). |
| 2026-06-09 | AI 서버 HTTP 어댑터 구현 (PR 4-final) — AiResponseClient 인터페이스화 + StubAiResponseClient(기본)/HttpAiResponseClient + ai.client.mode 토글(OAuth2 패턴). 요청에 userId 추가 ({userId,postId,title,content} → {message,emoji}). application.yaml 에 ai.* 블록(client.mode/server.url/timeout-ms). WireMock 테스트 7케이스. 실패 catch 는 상위 RestClientException 으로 (T-035). AI 서버 미배포 → 운영 기본값 stub 유지, 인증 보류, 계약 잠정. 스키마 변경 없음. |
| 2026-06-09 | GET /post 정렬/필터 추가 (레벨 B) — 정렬(기본 postDate,desc) + 선택적 from/to(postDate 범위) + keyword(제목/내용 부분일치). QueryDSL PostSearchRepository 신설 (BooleanBuilder 동적 조건), 정렬 화이트리스트 PostSortField enum, InvalidPostSearchException(400). 부수: MethodArgumentTypeMismatchException 글로벌 400 핸들러 (T-034) — 잘못된 날짜 형식 500 함정 봉합. dead code findAllByUser_Id 제거. 페이징은 분리 (TODO, Page<> breaking change). 인덱스 권장: post(user_id, post_date) — ddl-auto 가 인덱스 미보장이라 운영 트래픽 증가 시 수동 DDL (PR body 명시). |
| 2026-05-29 | Post 에 postDate (LocalDate) 추가 — 사용자가 "지나간 날짜에 대한 일기" 작성 시 명시. 누락 시 서버가 LocalDate.now() 로 폴백 (기본 = "오늘 일기"). createdAt (자동) 과 별개. Request/Response DTO 모두 yyyy-MM-dd 포맷. update 시 변경 가능. api-contracts.md 의 Post 엔드포인트 4개 + 의사결정 로그 갱신. 운영 머지 전 RDS ALTER 필수 (ddl-auto: update 가 NOT NULL 추가 못 함 — T-019 교훈 5번). |
| 2026-05-29 | release PR #81 — T-031/T-032 hot-fix 운영 반영 — release PR #79 의 deploy 직후 발견된 함정 2건 (path enum case-sensitivity / silent 500 진단 가림막) 의 fix #80 을 main 으로. dev → main merge 충돌은 -X ours 로 해결 (⚠️ 정정: 올바른 방법은 -s ours 전략 — T-037). 운영 검증: /auth/oauth2/google (소문자) → 401 정상 / /auth/oauth2/twitter → 400 정상. |
| 2026-05-29 | fix PR #80 — OAuth2 path case-insensitive + GlobalExceptionHandler 진단 로깅 — release #79 직후 발견한 두 함정 같이. controller 가 String 으로 받아 .toUpperCase() 정규화 + 신규 InvalidOAuth2ProviderException (400). @Slf4j + log.error 추가. 테스트 5 케이스 추가 (소/대/혼합 case + 미지원 + LOCAL service-level reject). troubleshooting T-031 / T-032. |
| 2026-05-29 | release PR #79 — PR 9 MkDocs + PR 12 Google OAuth2 풀체인 + PKM 첫 사이클 (#67~#78) 운영 반영 — dev → main merge 가 main 의 release squash commit 들과 충돌 → -X ours 로 dev (semantic superset) 우선 해결 (⚠️ 정정: 올바른 방법은 -s ours 전략 — T-037) 후 push. 운영 머지 전 체크리스트 4건 모두 ✅ (compose env passthrough / GitHub Secrets / EC2 .env / RDS ALTER 4건). CD 성공 후 운영 검증에서 T-031 발견 → 즉시 hot-fix #80 + release #81 사이클로 fix. |
| 2026-05-28 | PR 12-final — Google OAuth2 실어댑터 + Kakao 제외 (#77) — HttpGoogleOAuth2Provider (RestClient + tokeninfo + aud / email_verified 검증) + WireMock 단위 테스트 8개 + oauth2.client.mode 토글 (@ConditionalOnProperty matchIfMissing=stub). AuthProvider enum 에서 KAKAO 제거. application.yaml 에 ${GOOGLE_OAUTH_CLIENT_ID:dummy} + ${GOOGLE_TOKENINFO_URL:...} + ${OAUTH2_CLIENT_MODE:stub} placeholder 추가. learning-notes 13 추가 (OAuth2 Token-Exchange + audience + email_verified). |
| 2026-05-28 | PR 12-compose (#78) — compose.yaml 에 OAuth2 env 2개 (OAUTH2_CLIENT_MODE + GOOGLE_OAUTH_CLIENT_ID) 컨테이너 주입 + .env.example 보충. PR 12-final 의 운영 머지 전 사전 작업. |
| 2026-05-28 | PR 12-pre 머지 (#76) — OAuth2 Stub 흐름 + User 엔티티 provider/providerId 추가. 외부 Console 셋업 / HTTP 의존 없이 BE 만으로 OAuth2 흐름 정착. PR 4-pre 와 같은 외부 의존성 차단 해소 패턴. |
| 2026-05-28 | PR 9 MkDocs Material 문서 사이트 완료 (#72 + #73 + #74) — /docs/ 에 claude-docs/* + README + 학습 노트 통합 사이트. 하이브리드 옵션 C (landing 보존 + learning-notes 흡수). strict 빌드 두 fix iteration (T-029 README↔index 자동 충돌 / T-030 README outbound 평면화 충돌 + patch incompleteness 경고). 결과 10개 URL 모두 200 OK. |
| 2026-05-28 | PKM 공개판 첫 승격 (PR #70) — learning-notes 항목 10/11/12 (Spring 비동기 / AWS SSM 메커니즘 / CORS) 를 goospel.github.io 로 일반화 승격. 글로벌 CLAUDE.md PKM 파이프라인 (3-5개 묶음 + 4문 자격 + release 직후 타이밍) 첫 실 적용. 각 항목 헤더에 공개판 링크 마커. |
| 2026-05-28 | Pages 워크플로우 확장 + dev trigger 전환 (PR #68) — claude-docs/learning-notes.md 를 pandoc 으로 HTML 빌드 → /learning-notes/ 노출. 트리거 main → dev (학습 노트가 release 전에도 반영). environment allowlist 누락 함정 T-028 발견. |
| 2026-05-27 | release PR #66 — PR 10 Refresh Token 운영 반영 — access 1h / refresh 2w + rotation + SHA-256 hash + POST /auth/refresh / POST /auth/logout. README (#64) + 발표 자료 (#65) 도 같이 최신화. |
| 2026-05-27 | learning-notes.md 신설 (PR #67) — 모르고 물어봐서 배운 기술 개념 9개 정리 (이후 12개로 확장). 발표 / Q&A / 면접 대비 본인 이해 확립. |
| 2026-05-27 | plan.md 압축 (PR #61) — FE 협업 / 운영 치트시트 / 완료 PR 체크리스트 등 중복 / 만료 콘텐츠 정리. 551줄 → ~180줄. 운영 치트시트는 ops-runbooks/ec2-cheatsheet.md 로 분리. |
| 2026-05-27 | PR 4-pre 머지 (#59) + release (#62) — 비동기 AI 응답 골격 + Stub 어댑터. AiResponse 엔티티 + @Async + GET /post/{id}/ai-response 운영 반영. 단순화 결정: interface 제거, 이벤트 패턴 제거, executor 외부화 제거. |
| 2026-05-27 | PR 4 를 두 단계 분할 — PR 4-pre (Stub 골격, 단독 진행) / PR 4-final (HTTP 어댑터, AI 합의 후). AI 담당자 외부 합의 대기 차단 해소. |
| 2026-05-26 | 인증 강화 백로그 추가 (PR 10 Refresh / PR 11 OAuth2 RS / PR 12 소셜 로그인). |
| 2026-05-26 | GitHub Pages 인프라 신설 — landing + Marp 슬라이드 자동 배포. PR 9 (MkDocs) 백로그 추가. |
| 2026-05-26 | 운영 부팅 폭발 대사건 (T-019) — springdoc 2.8.3 ↔ Spring Boot 4 비호환 등 5층 결함. 복구 + CD 신뢰성 강화. |
| 2026-05-26 | PR 8 BE 부분 완료 — CorsConfig + compose env :-default 패턴. FE 셋업 가이드 작성. |
| 2026-05-24 | 인증 라인 완성 + 운영 반영 (release PR #27) — PR 0/⅔ + 인프라 PR 5개 한 번에 main 머지. |
| 2026-05-24 | 이 문서 신설 — 백로그 의존성 그래프 + FE 협업 + 의사결정 로그 섹션. |