73 lines
6.3 KiB
Markdown
73 lines
6.3 KiB
Markdown
# 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/` 디렉토리가 있다고 되어 있으나 실제로는 없음).
|
|
|
|
```powershell
|
|
# 앱 실행 (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`, `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`가 있습니다.
|
|
|
|
**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` 순으로 진행시킵니다. 단계:
|
|
|
|
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 컬럼으로 저장).
|
|
|
|
`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**과 연동합니다.
|
|
|
|
### 컨벤션
|
|
|
|
- **영속성**: 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 허용).
|
|
|
|
## 보안 참고
|
|
|
|
`src/main/resources/application.yml`에 PostgreSQL 비밀번호가 하드코딩되어 있고, `credentials.json` / `tokens/StoredCredential`에 Google OAuth 시크릿이 저장되어 저장소에 커밋되어 있습니다. 이 값들을 로그나 외부 서비스에 노출하지 말고, 추가 시크릿을 커밋하라는 요청이 있으면 경고하세요.
|