h-lab/CLAUDE.md
hehih 487dfc6d0f docs: update CLAUDE.md for ChannelVideo single-master architecture
Reflect removal of Video/YtVideo/Opal: ChannelVideo is now the single
video master with the collect->curate->rework->publish pipeline. Update
external-integration section (Python transcript via ChannelService,
YouTube API via YoutubeSearchService; Google Docs/Opal gone) and the
security note (OAuth creds now unused, gitignored).
2026-05-30 19:59:38 +09:00

6.5 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/ 아래에 추가하세요.

아키텍처

두 가지 패키지 컨벤션이 공존 (중요)

코드베이스에 두 가지 구조 스타일이 섞여 있습니다. 코드를 추가하기 전에 어느 쪽인지 파악하세요:

  1. domain/<aggregate>/ — DDD 스타일 패키지 (channel, production, category, publish). 각 패키지가 Entity + Repository + Service + @RestController(Swagger 문서화, /api/v1/... 경로) + dto/를 함께 묶습니다.
  2. web/ + service/ — 이후에 추가된 두 번째 레이어. web/에는 Thymeleaf 페이지 컨트롤러(WebController, ChannelDetailController)와 추가 JSON @RestController들(/api/... 경로, v1 없음)이 있습니다. service/에는 횡단 관심사인 YoutubeSearchService(YouTube Data API 검색)가 있습니다.

영상 모델은 domain/channel/ChannelVideo(테이블 channel_videos)가 단일 마스터입니다. 수집(채널 동기화 + 조회수 검색)→큐레이션→재가공→발행 전 과정이 이 엔티티 하나를 중심으로 돕니다. (과거 Video/YtVideo 및 Opal 파이프라인은 제거됨 — DB에는 videos/yt_video/scriptgen/opal_* 테이블이 ddl-auto:update 특성상 고아 상태로 남아있을 수 있으나 더 이상 매핑되지 않음.) domain/production/ProductionVideo(테이블 production_video)는 별개 개념으로, n8n 크롤 랭킹 스냅샷을 담습니다 — 수집 영상과 혼동하지 마세요.

ChannelVideo 콘텐츠 파이프라인 (앱의 핵심)

ChannelVideo를 "수집→분류→재가공→유통" 흐름으로 진행시키는 것이 앱의 핵심입니다.

  1. 수집 → 등록 채널 동기화(source=CHANNEL) 또는 조회수 검색 수집(source=SEARCH, ChannelVideo.fromSearch). 검색은 service/YoutubeSearchService(POST /api/youtube/search), 일괄 저장은 domain/channel/SearchCollectionService(POST /api/youtube/collect, 중복 자동 스킵). 정기 자동 수집은 global/schedule/ScheduledCollectionService.
  2. 큐레이션domain/channel/ChannelVideoCurationService. interestStatus(NEW/REVIEWING/TARGET/DONE/EXCLUDED), 카테고리(domain/category), 북마크, 떡상 지표(viewsPerSubRatio 등). 수집함 /collection, 칸반 보드 /board.
  3. 재가공 → 재가공 작업공간 /rework/{id}. 원본 자막 추출(ChannelService.extractScript → Python transcript 서비스 호출, channel_video_scripts 저장) + 재작성 에디터(ChannelVideo.reworkText, 저장 시 status TARGET 승격).
  4. 발행(유통)domain/publish/PublishService + PublishPackage(ChannelVideo와 1:1). 발행 큐 /publish, 재가공 화면 하단 "발행 준비" 섹션. 실제 업로드는 수동(URL 기록) — 플랫폼 API 미연동.

외부 연동

  • Python transcript 마이크로서비스 (http://h-python.tolag.shop/transcript) — domain/channel/ChannelService.extractScriptRestTemplate로 직접 호출해 자막을 가져와 channel_video_scripts에 저장합니다.
  • YouTube Data APIservice/YoutubeSearchService(검색)와 domain/channel/ChannelService(채널/영상 메타 수집)가 youtube.api.key(env YOUTUBE_API_KEY)로 호출. 일일 쿼터는 global/schedule/YoutubeQuotaGuard로 가드.
  • n8n webhookproduction/ 도메인이 랭킹/크롤 데이터를 위해 연동.

컨벤션

  • 영속성: PostgreSQL(원격, application.yml에 설정), JPA ddl-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 허용).

보안 참고

application.yml은 시크릿을 환경변수로 받되 fallback 값이 하드코딩되어 있습니다(DB_URL/DB_USERNAME/DB_PASSWORD, YOUTUBE_API_KEY). 또한 credentials.json / tokens/StoredCredential에 Google OAuth 시크릿이 남아있습니다 — 이는 제거된 Opal(Google Docs 연동) 전용이라 현재 코드에서는 미사용이지만 파일은 여전히 저장소에 존재할 수 있습니다(.gitignore에 추가되어 git 추적에서는 제외). 이 값들을 로그나 외부 서비스에 노출하지 말고, 추가 시크릿을 커밋하라는 요청이 있으면 경고하세요.