Files
jaengseung-made/docs/superpowers/specs/2026-07-03-phase3a-music-public-design.md

6.8 KiB

Phase 3a 음악 서비스 공개화 — "나의 이야기를 음악으로" 설계

  • 날짜: 2026-07-03
  • 선행: Phase 2(사주/타로)·2.5/2.6(사주 재스킨) main 머지 완료
  • 배경: 운영 비전 2축 3번째 서비스(음악). 숨김 상태의 Suno 스튜디오를 공개·무료화하고, "스토리→음악" 흐름과 회원 저장을 추가. 영상화 유료는 Phase 3b로 분리.

결정 사항 (CEO 확정, 2026-07-03)

결정 내용
영상화 Phase 3b로 분리 — 계좌이체 발주(관리자 수동 제작·납품). 이번 3a 범위 밖
음악 노출·과금 공개+무료 — 페이지 공개(숨김 해제), 생성은 로그인+일일제한(무료). 사주·타로 패턴
스토리→음악 Gemini가 스토리→{가사·스타일·무드} 변환 후 Suno 생성
일일 제한 음악 생성 1회/일(Suno 비용)
callback /api/studio/callback 폴링 전용 확정 — 최소 200 응답 route로 댕글링 해소, 회원 저장은 폴링 완료 후 클라 트리거

확인된 기존 구조

  • POST /api/studio/generate: 무인증, SUNO_API_KEY 미설정 시 503, custom/simple 모드, callBackUrl=${origin}/api/studio/callback(부재), Suno /api/v1/generate 호출 → task 반환
  • GET /api/studio/status?taskId=: 무인증, Suno record-info 폴링
  • lib/ai-usage.ts: AiService='saju'|'tarot', getTodayUsage/recordUsage, KST 일일 집계. ai_usage_log CHECK는 ('saju','tarot')(phase2 마이그, auto-name ai_usage_log_service_check)
  • app/music/: layout(가드 isServiceVisible('music')), page(72), samples(102), studio(543, 다크 테마 Suno UI)
  • 저장·마이페이지 통합 패턴: 타로 interpret→readings save, 마이페이지 'AI 기록' 탭(사주·타로)

워크스트림 5개

WS1. 음악 공개화 + 무료화

  • app/music/layout.tsxisServiceVisible('music')+notFound()+import 제거 → 공개
  • lib/service-visibility.ts HideableService에서 'music' 제거 → 'gyeol'|'lotto'
  • app/api/admin/services/route.ts DEFAULT_SERVICES music 행 제거
  • 마이그레이션에서 service_settings music 행 DELETE
  • Suno 키 미설정 시 503 유지(예시 폴백 없음)

WS2. 스토리 → 음악 (Gemini + Suno)

  • 신규 lib/music/story-prompt.ts: STORY_SYSTEM_PROMPT + buildStoryUserMessage(story) + parseStoryJson/validateStory. Gemini가 스토리 텍스트 → { title, lyrics, style, mood } strict JSON. 타로 prompt.ts 방어(코드블록 스트립·검증·reroll 1회, 45s 가드) 패턴 재사용
  • 신규 POST /api/studio/story: 인증(401) → Gemini 변환(GEMINI_API_KEY, 미설정 503) → { title, lyrics, style, mood } 반환(사용자 편집 가능). story 단계는 일일제한 미집계(값싼 단계)
  • 기존 POST /api/studio/generate(Suno) 수정: 인증(401) 추가 + 일일제한(429, MUSIC_DAILY_LIMIT) + Suno task 생성 성공 시에만 recordUsage('music'). body에 title/lyrics/tags(style)를 story 결과에서 채워 custom 모드로 호출
  • GET /api/studio/status 유지(무인증 폴링 가능 — taskId만 필요, 민감정보 없음)
  • callback 정리: 신규 POST /api/studio/callback이 최소 { ok: true } 200 반환(Suno webhook 404 방지). 회원 저장은 콜백이 아니라 폴링 완료 후 클라가 POST /api/studio/tracks 트리거

WS3. 회원 저장 + 일일제한

  • 마이그레이션 supabase/migrations/2026-07-03-phase3a-music.sql:
    CREATE TABLE IF NOT EXISTS music_tracks (
      id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
      user_id uuid NOT NULL REFERENCES auth.users(id) ON DELETE CASCADE,
      title text,
      story text,
      lyrics text,
      style text,
      audio_url text,
      task_id text,
      created_at timestamptz NOT NULL DEFAULT now()
    );
    ALTER TABLE music_tracks ENABLE ROW LEVEL SECURITY;
    CREATE POLICY music_select_own ON music_tracks FOR SELECT USING (auth.uid() = user_id);
    
    -- ai_usage_log CHECK에 'music' 추가 (phase2 마이그 적용 후 실행 전제)
    ALTER TABLE ai_usage_log DROP CONSTRAINT IF EXISTS ai_usage_log_service_check;
    ALTER TABLE ai_usage_log ADD CONSTRAINT ai_usage_log_service_check CHECK (service IN ('saju','tarot','music'));
    
    DELETE FROM service_settings WHERE id = 'music';
    
    의존성: phase2-saju-tarot 마이그(ai_usage_log 생성)가 먼저 적용돼야 함 — 스펙·플랜·CEO 안내에 명시
  • lib/ai-usage.ts: AiService'music' 추가, export const MUSIC_DAILY_LIMIT = 1;
  • 신규 POST/GET /api/studio/tracks: 저장(admin insert, user_id 세션)·본인 조회(세션 client RLS). 타로 readings 패턴

WS4. 라이트 재스킨 + 스토리 UI

  • app/music/{page,samples,studio}.tsx 다크 → --jsm 라이트 재스킨(사주 2.5/2.6 패턴, gradient/blur/보라/이모지 0건). navy 밴드 무테두리 flat 관용구
  • 스토리 UI 흐름(studio 재구성): ①스토리 textarea → POST /studio/story → ②가사·스타일 미리보기(편집 가능) → ③"음악 만들기" → POST /studio/generate → ④status 폴링 → ⑤플레이어 + (로그인 시) 자동 저장(POST /studio/tracks). 비로그인 생성 시도 → 로그인 CTA(/login?next=/music/studio)
  • 셔플류 클라 이슈 없음(폼 기반)

WS5. 진입점 + AI기록 통합 + 문서

  • TopNav 음악 링크 추가(외주/소프트웨어/제작사례/사주/타로/음악 = 6링크)
  • 마이페이지 'AI 기록' 탭에 음악 트랙 통합(GET /api/studio/tracks) — 사주·타로·음악 3종 병합 리스트, 음악은 제목·스토리 요약·오디오 링크
  • CLAUDE.md: 음악 공개 서비스·스토리→음악·music_tracks·studio API 반영, 숨김 서비스 표에서 music 제거
  • 최종 검증: grep -rnE "gradient|violet|purple|blur" app/music/**/*.tsx 0건, build+test 30, sajumusic 라우트 존재

범위 밖 (Phase 3b)

  • 영상화 유료 발주(계좌이체 order로 video 신청·관리자 제작·납품·다운로드)
  • 음악 자동 영상 생성 API 연동

리스크·주의

  • Suno 실동작SUNO_API_KEY 운영 설정 의존 — 로컬 미설정 시 503(사주 Gemini와 동일 정책). 수동 E2E는 운영 키 있는 환경에서
  • ai_usage_log CHECK ALTER는 phase2 마이그 DB 적용 후 실행돼야 함(미적용 시 ALTER 실패) — CEO 안내
  • studio 페이지가 큼(543줄) — 재스킨 + 스토리 UI 재구성은 태스크 분할(라이트 재스킨과 스토리 흐름 분리 가능)
  • Suno 응답 스키마(task/record-info)는 기존 status route가 이미 다룸 — 저장 시 audio_url 추출 지점은 구현 시 record-info 응답 구조 확인
  • 생성은 비동기(task) — recordUsage는 task 생성 성공(Suno 202/200) 시점 집계(완료 아님). 완료 실패해도 1회 소진되나 개인 서비스 규모에서 허용(사주·타로와 동일 기조)