Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01AAtcmKKtqDUe4NyVgy1aLQ
7.8 KiB
7.8 KiB
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.tsProductRow,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 전환 시sendOrderPaidEmailBankTransferModal({ 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 }, RLSmusic_select_own- 마이페이지 AI기록 탭: 사주·타로·음악 병합,
MusicAiCard(제목·스토리·<audio>)
워크스트림
WS1. DB 마이그레이션 (products 시드 + music_tracks 확장)
- 신규
supabase/migrations/2026-07-09-phase3b-music-video.sql:(products 실제 컬럼 확인 후 필수 NOT NULL 컬럼 있으면 값 채워 시드. 클라우드+NAS 양쪽)-- 의존성: 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;
WS2. 발주 API 확장 (orders + music_tracks 연결)
POST /api/orders확장: body에 optionalmusicTrackId(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_tracksupdatevideo_status='requested', video_order_id=order.idwhereid=musicTrackId AND user_id=user.id
- 신규
GET /api/studio/video-product: 로그인,music_video제품{ id, name, price, is_active }반환(모달 가격 표시용). 미존재/비활성 시 안내
WS3. 마이페이지 신청 진입점 + 상태 표시
BankTransferModal확장: optional propmusicTrackId?: 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_tracksupdatevideo_url=videoUrl, video_status='delivered'videoStatus='in_progress'만 오면 status 변경 없이 트랙 video_status만 갱신- 기존 일반 상품 paid 흐름·메일은 무변경
- optional
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 전 서비스 스모크
향후 후보(다음 방향 잡기용):
- 영상 자동 생성 연동 — 수동 제작 → sora/veo/kling 등 API(비용·게이트웨이 설계 필요, NAS video-lab 참고)
- NAS 풀 self-host 컷오버 — Vercel/클라우드 Supabase → NAS(진행 중 project nas-selfhost-transition)
- 운영 대시보드·정산 — 서비스별 매출/사용량 통합(admin/analytics 확장), AI 사용량·Suno 비용 집계
- 회원 등급/구독 재도입 — Phase 0에서 제거한 subscription 인프라 재설계(SaaS 피벗 시)
- 사주·타로·음악 크로스 프로모션, 마케팅 채널(admin 광고 관리) 활용 캠페인
범위 밖 (Phase 3b)
- 영상 자동 생성, 영상 편집 UI, 관리자 파일 업로더(URL 문자열로 충분)
- 결제 PG(계좌이체 단일 소스 유지)
리스크·주의
- orders POST의 중복 가드를 music_video만 트랙 단위로 분기 — 일반 상품 가드 회귀 없도록 주의
metadata->>music_track_idjsonb 쿼리 문법(Supabase) 정확히- BankTransferModal은 다른 상품(products)도 쓰므로 musicTrackId는 optional, 미전달 시 기존 동작 불변
- video_url은 관리자 신뢰 입력(공개 재생) — 값 검증은 최소(문자열 길이), XSS는
<video src>특성상 낮으나 URL 형식 권장 - admin/orders에 music_video 조인 추가 시 기존 일반 주문 렌더 회귀 없도록