6.3 KiB
CLAUDE.md
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
개요
h-lab(아티팩트명 yanalyst)은 유튜브 데이터를 수집·분석하고, 이를 바탕으로 다단계 콘텐츠 제작 파이프라인을 구동하는 개인용 웹 서비스입니다. Spring Boot 3.4.0 / Java 21 백엔드에 서버 사이드 렌더링(Thymeleaf) UI를 사용합니다 (README.md에 언급된 React/Vite 프론트엔드는 SSR로 대체되어 제거됨). 모든 UI는 동일한 Spring Boot 앱에서 제공됩니다.
명령어
Windows에서는 gradlew.bat을 사용합니다. Gradle wrapper는 저장소 루트에 있습니다 (README.md에는 backend/ 디렉토리가 있다고 되어 있으나 실제로는 없음).
# 앱 실행 (UI + API를 http://localhost:8088 에서 제공)
.\gradlew.bat bootRun
# 빌드
.\gradlew.bat build
# 테스트 실행
.\gradlew.bat test
# 단일 테스트 클래스 / 메서드 실행
.\gradlew.bat test --tests "com.hlab.yanalyst.SomeTest"
.\gradlew.bat test --tests "com.hlab.yanalyst.SomeTest.someMethod"
- 서버 포트는 8088 (
application.yml)입니다. README의 8080이 아닙니다. Swagger UI:http://localhost:8088/swagger-ui.html. - 현재
src/test디렉토리가 없습니다 —.\gradlew.bat test는 사실상 빈 상태로 통과합니다. 테스트는src/test/java/com/hlab/yanalyst/아래에 추가하세요.
아키텍처
두 가지 패키지 컨벤션이 공존 (중요)
코드베이스에 두 가지 구조 스타일이 섞여 있습니다. 코드를 추가하기 전에 어느 쪽인지 파악하세요:
domain/<aggregate>/— DDD 스타일 패키지 (channel,video,opal,production,script). 각 패키지가 Entity + Repository + Service +@RestController(Swagger 문서화,/api/v1/...경로) +dto/를 함께 묶습니다.web/+service/— 이후에 추가된 두 번째 레이어.web/에는 Thymeleaf 페이지 컨트롤러(WebController,ChannelDetailController)와 추가 JSON@RestController들(/api/...경로,v1없음)이 있습니다.service/에는 횡단 관심사인AnalysisWorkflowService와YtVideoService가 있습니다.
video 엔티티가 두 개라는 점에 유의: domain/video/Video.java(/api/v1/videos) vs domain/video/YtVideo.java(테이블 yt_video, web/YtVideoController + web/VideoActionController를 통해 /api/videos에서 구동). 실제 콘텐츠 제작 워크플로우는 YtVideo 기반으로 동작하며, Video는 구버전 읽기 모델입니다. 기능을 수정하기 전에 어느 쪽을 대상으로 하는지 확인하세요.
Opal 콘텐츠 제작 워크플로우
service/AnalysisWorkflowService가 오케스트레이터이며 앱의 핵심입니다. 이 파이프라인은 YtVideo.status 필드를 CRAWLED → SCRIPT_READY → DRAFTING → FINALIZED 순으로 진행시킵니다. 단계:
- generateScript → 트랜스크립트를 가져와
ScriptGen저장 (비디오당 1개, PK = videoId). - generateDraft → 버전이 매겨진
OpalDraft행 생성 (비디오별versionNo자동 증가), 사용자 피드백을 반영 가능. - acceptDraft → draft를 활성
OpalFinal로 승격 (비디오당isActive=true는 하나만 유지, 이전 것은 비활성화). - generateFinalAsset → 최종 스크립트를 가져와
OpalFinalAsset생성 (title/summary/timeline/video_prompt/image_urls를 JSON 컬럼으로 저장).
web/VideoActionController를 통해 구동됩니다 (POST /api/videos/{id}/script|drafts|final-asset 등).
외부 연동 (service/external/)
ExternalApiService는 인터페이스이며 구현체가 두 개입니다:
ExternalApiServiceImpl(@Primary) — 실제 구현. Python 트랜스크립트 마이크로서비스(http://h-python.tolag.shop/transcript)를 호출하고, Google Docs API로 Google Docs를 읽고 비웁니다. Google Doc ID는 모드별(TRUE_STORYvsSTRUCTURE_CHANGE) 및 워크플로우 단계별로 하드코딩되어 있습니다.generateScript는 실패 시 예외를 던지지 않고 fallback 플레이스홀더 트랜스크립트로 degrade하며,generateFinalAsset은 아직 플레이스홀더 데이터를 반환하는 스텁입니다.ExternalApiServiceStub— fallback/테스트용 더미.
Google Docs 인증은 OAuth installed-app 플로우를 사용합니다: 클라이언트 시크릿은 src/main/resources/credentials.json, 리프레시 토큰은 tokens/ 디렉토리(StoredCredential)에 캐싱됩니다. 최초 실행 시 포트 8888에서 브라우저를 열어 인증을 진행합니다. 이 구현체는 읽은 후 원본 Google Doc의 내용을 삭제합니다(clearGoogleDoc) — 테스트 시 파괴적이므로 주의하세요.
production/ 도메인은 랭킹/크롤 데이터를 위해 외부 n8n webhook과 연동합니다.
컨벤션
- 영속성: PostgreSQL(원격,
application.yml에 설정), JPAddl-auto: update— 스키마가 엔티티에서 자동 관리되며 마이그레이션 파일은 없습니다. Lombok 전면 사용,@EnableJpaAuditing과@CreationTimestamp/@UpdateTimestamp. p6spy가global/config/P6SpyFormatter로 포맷된 SQL을 로깅합니다. - API 응답: JSON 결과는
global/common/ApiResponse<T>로 감쌉니다 (ApiResponse.ok(...)/.created(...)/.error(...)). 에러는global/error/GlobalExceptionHandler에서 중앙 처리됩니다. - Thymeleaf: 템플릿은
src/main/resources/templates/에 있고, 공유layout/base.html+layout/sidebar.html을 사용합니다(thymeleaf-layout-dialect). 페이지 컨트롤러는 사이드바 하이라이트를 위해currentPage모델 속성을 설정합니다. 정적 CSS/JS는static/에 있으며 다크모드 디자인은variables.css기반입니다. - CORS는
global/config/WebMvcConfig에서 전면 개방되어 있습니다 (allowedOriginPatterns("*"), credentials 허용).
보안 참고
src/main/resources/application.yml에 PostgreSQL 비밀번호가 하드코딩되어 있고, credentials.json / tokens/StoredCredential에 Google OAuth 시크릿이 저장되어 저장소에 커밋되어 있습니다. 이 값들을 로그나 외부 서비스에 노출하지 말고, 추가 시크릿을 커밋하라는 요청이 있으면 경고하세요.