From 487dfc6d0f836454f2f93736149f4ec70a18c34f Mon Sep 17 00:00:00 2001 From: hehih Date: Sat, 30 May 2026 19:59:38 +0900 Subject: [PATCH] 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). --- .claude/settings.local.json | 3 ++- CLAUDE.md | 34 ++++++++++++++-------------------- 2 files changed, 16 insertions(+), 21 deletions(-) diff --git a/.claude/settings.local.json b/.claude/settings.local.json index 67269d6..605cee3 100644 --- a/.claude/settings.local.json +++ b/.claude/settings.local.json @@ -5,7 +5,8 @@ "PowerShell($env:JAVA_HOME=\"D:\\\\Development\\\\app\\\\JDK\\\\jdk-21.0.5\"; .\\\\gradlew.bat clean compileJava --console=plain 2>&1 | Select-Object -Last 20)", "Bash(git rm *)", "Bash(git add *)", - "Bash(git -c user.name=hehih -c user.email=hehihoho86@gmail.com commit -q -m 'Retire Opal/YtVideo pipeline; ChannelVideo is the single video master *)" + "Bash(git -c user.name=hehih -c user.email=hehihoho86@gmail.com commit -q -m 'Retire Opal/YtVideo pipeline; ChannelVideo is the single video master *)", + "Bash(git -c user.name=hehih -c user.email=hehihoho86@gmail.com commit -q -m 'docs: update CLAUDE.md for ChannelVideo single-master architecture *)" ] } } diff --git a/CLAUDE.md b/CLAUDE.md index a8ab4ea..5d84bf3 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -34,31 +34,25 @@ Windows에서는 `gradlew.bat`을 사용합니다. Gradle wrapper는 저장소 코드베이스에 두 가지 구조 스타일이 섞여 있습니다. 코드를 추가하기 전에 어느 쪽인지 파악하세요: -1. **`domain//`** — DDD 스타일 패키지 (`channel`, `video`, `opal`, `production`, `script`). 각 패키지가 Entity + Repository + Service + `@RestController`(Swagger 문서화, `/api/v1/...` 경로) + `dto/`를 함께 묶습니다. -2. **`web/`** + **`service/`** — 이후에 추가된 두 번째 레이어. `web/`에는 Thymeleaf 페이지 컨트롤러(`WebController`, `ChannelDetailController`)와 추가 JSON `@RestController`들(`/api/...` 경로, `v1` 없음)이 있습니다. `service/`에는 횡단 관심사인 `AnalysisWorkflowService`와 `YtVideoService`가 있습니다. +1. **`domain//`** — 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 검색)가 있습니다. -**video 엔티티가 두 개**라는 점에 유의: `domain/video/Video.java`(`/api/v1/videos`) vs `domain/video/YtVideo.java`(테이블 `yt_video`, `web/YtVideoController` + `web/VideoActionController`를 통해 `/api/videos`에서 구동). 실제 콘텐츠 제작 워크플로우는 **`YtVideo`** 기반으로 동작하며, `Video`는 구버전 읽기 모델입니다. 기능을 수정하기 전에 어느 쪽을 대상으로 하는지 확인하세요. +**영상 모델은 `domain/channel/ChannelVideo`(테이블 `channel_videos`)가 단일 마스터**입니다. 수집(채널 동기화 + 조회수 검색)→큐레이션→재가공→발행 전 과정이 이 엔티티 하나를 중심으로 돕니다. (과거 `Video`/`YtVideo` 및 Opal 파이프라인은 제거됨 — DB에는 `videos`/`yt_video`/`scriptgen`/`opal_*` 테이블이 ddl-auto:update 특성상 고아 상태로 남아있을 수 있으나 더 이상 매핑되지 않음.) `domain/production/ProductionVideo`(테이블 `production_video`)는 별개 개념으로, n8n 크롤 랭킹 스냅샷을 담습니다 — 수집 영상과 혼동하지 마세요. -### Opal 콘텐츠 제작 워크플로우 +### ChannelVideo 콘텐츠 파이프라인 (앱의 핵심) -`service/AnalysisWorkflowService`가 오케스트레이터이며 앱의 핵심입니다. 이 파이프라인은 `YtVideo.status` 필드를 `CRAWLED → SCRIPT_READY → DRAFTING → FINALIZED` 순으로 진행시킵니다. 단계: +`ChannelVideo`를 "수집→분류→재가공→유통" 흐름으로 진행시키는 것이 앱의 핵심입니다. -1. **generateScript** → 트랜스크립트를 가져와 `ScriptGen` 저장 (비디오당 1개, PK = videoId). -2. **generateDraft** → 버전이 매겨진 `OpalDraft` 행 생성 (비디오별 `versionNo` 자동 증가), 사용자 피드백을 반영 가능. -3. **acceptDraft** → draft를 활성 `OpalFinal`로 승격 (비디오당 `isActive=true`는 하나만 유지, 이전 것은 비활성화). -4. **generateFinalAsset** → 최종 스크립트를 가져와 `OpalFinalAsset` 생성 (title/summary/timeline/video_prompt/image_urls를 JSON 컬럼으로 저장). +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 미연동. -`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_STORY` vs `STRUCTURE_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**과 연동합니다. +- **Python transcript 마이크로서비스** (`http://h-python.tolag.shop/transcript`) — `domain/channel/ChannelService.extractScript`가 `RestTemplate`로 직접 호출해 자막을 가져와 `channel_video_scripts`에 저장합니다. +- **YouTube Data API** — `service/YoutubeSearchService`(검색)와 `domain/channel/ChannelService`(채널/영상 메타 수집)가 `youtube.api.key`(env `YOUTUBE_API_KEY`)로 호출. 일일 쿼터는 `global/schedule/YoutubeQuotaGuard`로 가드. +- **n8n webhook** — `production/` 도메인이 랭킹/크롤 데이터를 위해 연동. ### 컨벤션 @@ -69,4 +63,4 @@ Google Docs 인증은 OAuth installed-app 플로우를 사용합니다: 클라 ## 보안 참고 -`src/main/resources/application.yml`에 PostgreSQL 비밀번호가 하드코딩되어 있고, `credentials.json` / `tokens/StoredCredential`에 Google OAuth 시크릿이 저장되어 저장소에 커밋되어 있습니다. 이 값들을 로그나 외부 서비스에 노출하지 말고, 추가 시크릿을 커밋하라는 요청이 있으면 경고하세요. +`application.yml`은 시크릿을 환경변수로 받되 fallback 값이 하드코딩되어 있습니다(`DB_URL/DB_USERNAME/DB_PASSWORD`, `YOUTUBE_API_KEY`). 또한 `credentials.json` / `tokens/StoredCredential`에 Google OAuth 시크릿이 남아있습니다 — 이는 제거된 Opal(Google Docs 연동) 전용이라 **현재 코드에서는 미사용**이지만 파일은 여전히 저장소에 존재할 수 있습니다(`.gitignore`에 추가되어 git 추적에서는 제외). 이 값들을 로그나 외부 서비스에 노출하지 말고, 추가 시크릿을 커밋하라는 요청이 있으면 경고하세요.