docs(phase3b): 음악 영상화 유료 발주 설계 + 향후 로드맵 기록 (WS1~5)

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AAtcmKKtqDUe4NyVgy1aLQ
This commit is contained in:
2026-07-09 12:43:01 +09:00
parent 6a67a9d812
commit bbbc61f402

View File

@@ -0,0 +1,104 @@
# Phase 3b 음악 영상화 유료 발주 설계
- 날짜: 2026-07-09
- 선행: Phase 3a(음악 공개화·스토리→음악) main 머지 완료(677012a), /music 카피 정리(6a67a9d)
- 배경: 음악 서비스 2번째 축 = "영상화"(유료). 회원이 저장한 음악 트랙을 계좌이체 발주하면 관리자가 수동 제작·납품. 자동 영상 생성은 범위 밖.
## 결정 사항 (CEO 확정, 2026-07-09)
| 결정 | 내용 |
|------|------|
| 영상화 방식 | **계좌이체 발주 + 관리자 수동 제작·납품** (Phase 3 스코핑서 확정) |
| 가격 | **고정가 ₩30,000**`music_video` products 1행, admin/products에서 조정 가능 |
| 주문 모델 | **기존 orders 재사용** + `metadata.music_track_id` 트랙 참조. music_tracks에 video 상태 추가 |
| 납품 | **`music_tracks.video_url`** — 관리자가 admin/orders에서 완성 영상 URL 직접 입력(별도 업로더 없음) |
| 진입점 | **마이페이지 AI기록 음악 카드**의 "영상화 신청" 버튼 |
## 확인된 기존 구조
- `products`: `{ id, name, price, is_active, ... }` (`lib/supabase/product-files.ts` ProductRow, `getProductById(admin, id)`)
- `POST /api/orders`: 인증→rate-limit→productId/depositorName 검증→`getProductById`(active)→**중복 pending 가드**(user+product)→insert `{user_id, product_id, amount:product.price, status:'pending', metadata:{method:'bank_transfer', depositor_name}}`→접수 메일
- `admin/orders PATCH`: body `{id, status∈pending|paid|cancelled}` → update; paid 전환 시 `sendOrderPaidEmail`
- `BankTransferModal({ product, isOpen, onClose })`: POST `{ productId: product.id, depositorName }`
- `music_tracks`(Phase 3a): `{ id, user_id, title, story, lyrics, style, audio_url, task_id, created_at }`, RLS `music_select_own`
- 마이페이지 AI기록 탭: 사주·타로·음악 병합, `MusicAiCard`(제목·스토리·`<audio>`)
## 워크스트림
### WS1. DB 마이그레이션 (products 시드 + music_tracks 확장)
- 신규 `supabase/migrations/2026-07-09-phase3b-music-video.sql`:
```sql
-- 의존성: 2026-07-03-phase3a-music.sql(music_tracks) 적용 후
INSERT INTO products (id, name, price, is_active)
VALUES ('music_video', '음악 영상화', 30000, true)
ON CONFLICT (id) DO NOTHING;
ALTER TABLE music_tracks
ADD COLUMN IF NOT EXISTS video_status text NOT NULL DEFAULT 'none'
CHECK (video_status IN ('none','requested','in_progress','delivered')),
ADD COLUMN IF NOT EXISTS video_url text,
ADD COLUMN IF NOT EXISTS video_order_id uuid;
```
(products 실제 컬럼 확인 후 필수 NOT NULL 컬럼 있으면 값 채워 시드. 클라우드+NAS 양쪽)
### WS2. 발주 API 확장 (orders + music_tracks 연결)
- `POST /api/orders` 확장: body에 optional `musicTrackId`(string) 허용
- `productId==='music_video'`이고 `musicTrackId` 있으면: admin으로 `music_tracks`에서 `id=musicTrackId AND user_id=user.id` 조회 → 없으면 404(본인 트랙 아님)
- metadata에 `music_track_id` 포함해 insert
- **중복 가드 확장**: music_video는 `(user_id, product_id='music_video', metadata->>music_track_id = trackId, status='pending')`로 조회(트랙별). 일반 상품은 기존 (user, product) 가드 유지
- 주문 insert 성공 후: `music_tracks` update `video_status='requested', video_order_id=order.id` where `id=musicTrackId AND user_id=user.id`
- 신규 `GET /api/studio/video-product`: 로그인, `music_video` 제품 `{ id, name, price, is_active }` 반환(모달 가격 표시용). 미존재/비활성 시 안내
### WS3. 마이페이지 신청 진입점 + 상태 표시
- `BankTransferModal` 확장: optional prop `musicTrackId?: string` → POST body에 `musicTrackId` 병합(없으면 기존 동작 그대로)
- 마이페이지 `MusicAiCard`(AI기록 탭):
- `video_status` 뱃지 — `none`: "영상화 신청" 버튼(→ `GET /api/studio/video-product`로 제품 로드 후 `BankTransferModal` 오픈, `musicTrackId` 전달) / `requested`: "영상화 접수됨(입금 확인 중)" / `in_progress`: "영상 제작 중" / `delivered`: "영상 완료"
- `delivered` + `video_url` 있으면 `<video controls src={video_url}>` + 다운로드 링크
- `MusicTrackRow` 타입에 `video_status`, `video_url` 추가(GET /api/studio/tracks가 이 컬럼 반환하도록 select 확장)
- 신청 완료 후 목록 갱신(status='requested' 반영)
### WS4. admin 납품
- `app/admin/orders/page.tsx`: `product_id==='music_video'` 주문에
- 연결 트랙 표시(metadata.music_track_id로 music_tracks 조회 — GET 응답에 트랙 title/audio_url 조인 포함)
- **영상 URL 입력 + "납품 완료" 버튼** → `PATCH /api/admin/orders` `{ id, status:'paid', videoUrl }`
- (선택) "제작 중" 버튼 → `{ id, videoStatus:'in_progress' }`
- `admin/orders GET` 확장: music_video 주문에 연결 트랙 정보(title, audio_url) 포함(admin이 metadata.music_track_id로 music_tracks 조인)
- `admin/orders PATCH` 확장:
- optional `videoUrl`, `videoStatus` 허용
- `status='paid'` + music_video 주문이면: 주문 metadata.music_track_id로 `music_tracks` update `video_url=videoUrl, video_status='delivered'`
- `videoStatus='in_progress'`만 오면 status 변경 없이 트랙 video_status만 갱신
- 기존 일반 상품 paid 흐름·메일은 무변경
### WS5. 메일 + 문서 + 검증
- 발주 접수/완료 메일 재사용(`sendOrderReceivedEmails`/`sendOrderPaidEmail`). 영상화 문구는 제품명("음악 영상화")으로 자연 반영 — 별도 템플릿 추가 없음(YAGNI)
- CLAUDE.md: 결제/발주 플로우에 영상화(music_video) 추가, music_tracks video 컬럼·admin 납품 반영
- 검증: `npm run build`·`npm test`·가드레일(신규 UI 라이트) grep, 수동 E2E(신청→admin 납품→마이페이지 완료 재생)
## 향후 방향 기록 (로드맵 — "추후 방향 재설정" 근거)
이 스펙 완료 시점 기준 전체 상태와 다음 후보를 기록한다(메모리 `saas-refactor-2026-07`에도 반영).
**완료(main·양쪽 리모트 푸시)**: Phase 0(정리) · 1(외주 코어) · 2(사주/타로) · 2.5/2.6(사주 재스킨) · 3a(음악 공개화) · /music 카피 · **3b(영상화 발주 — 이 스펙)**
**미결 운영(CEO 수동, 코드 불가)**:
- DB 마이그레이션 **5개** 순차 적용(클라우드+NAS): phase0 → phase1 → phase2 → phase3a → **phase3b**
- 운영 env: `GEMINI_API_KEY`(사주·타로·스토리), `SUNO_API_KEY`(음악), `RESEND_API_KEY`(메일)
- 수동 E2E 전 서비스 스모크
**향후 후보(다음 방향 잡기용)**:
1. **영상 자동 생성 연동** — 수동 제작 → sora/veo/kling 등 API(비용·게이트웨이 설계 필요, NAS video-lab 참고)
2. **NAS 풀 self-host 컷오버** — Vercel/클라우드 Supabase → NAS(진행 중 project [[nas-selfhost-transition]])
3. **운영 대시보드·정산** — 서비스별 매출/사용량 통합(admin/analytics 확장), AI 사용량·Suno 비용 집계
4. **회원 등급/구독 재도입** — Phase 0에서 제거한 subscription 인프라 재설계(SaaS 피벗 시)
5. **사주·타로·음악 크로스 프로모션**, 마케팅 채널(admin 광고 관리) 활용 캠페인
## 범위 밖 (Phase 3b)
- 영상 자동 생성, 영상 편집 UI, 관리자 파일 업로더(URL 문자열로 충분)
- 결제 PG(계좌이체 단일 소스 유지)
## 리스크·주의
- orders POST의 중복 가드를 music_video만 트랙 단위로 분기 — 일반 상품 가드 회귀 없도록 주의
- `metadata->>music_track_id` jsonb 쿼리 문법(Supabase) 정확히
- BankTransferModal은 다른 상품(products)도 쓰므로 musicTrackId는 optional, 미전달 시 기존 동작 불변
- video_url은 관리자 신뢰 입력(공개 재생) — 값 검증은 최소(문자열 길이), XSS는 `<video src>` 특성상 낮으나 URL 형식 권장
- admin/orders에 music_video 조인 추가 시 기존 일반 주문 렌더 회귀 없도록