-- Phase 2 (laugh register): 사용자별 변환된 웃음 wav 캐시. -- -- 사용자가 음성 설정에서 apply_dual 호출 시 백그라운드 작업이 모든 active -- admin 웃음 템플릿을 사용자 ref 음색으로 변환 → 결과 wav 를 storage 에 저장. -- 채팅 chunker (Phase 3) 가 웃음 토큰 감지 시 이 캐시에서 wav url 조회. -- -- 키: (user_id, voice_storage_path, template_id) — 한 사용자의 같은 음성 ref -- + 같은 템플릿 조합당 1행. 같은 음성이면 voice_storage_path 동일하므로 dual -- register (casual/formal) 양쪽이 공유. CREATE TABLE IF NOT EXISTS public.user_laugh_cache ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), user_id UUID NOT NULL REFERENCES auth.users(id) ON DELETE CASCADE, voice_storage_path TEXT NOT NULL, -- 사용자 ref wav 의 storage_path template_id UUID NOT NULL REFERENCES public.admin_laugh_templates(id) ON DELETE CASCADE, laugh_storage_path TEXT NOT NULL, -- 변환된 wav 의 storage_path (voice-clones 버킷) ready BOOLEAN NOT NULL DEFAULT FALSE, -- 변환 성공 = TRUE error TEXT, -- 변환 실패 시 메시지 (~500 chars truncate) duration_sec REAL, -- 변환된 wav 의 재생 시간 created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), UNIQUE (user_id, voice_storage_path, template_id) ); CREATE INDEX IF NOT EXISTS user_laugh_cache_user_voice_idx ON public.user_laugh_cache (user_id, voice_storage_path) WHERE ready = TRUE; ALTER TABLE public.user_laugh_cache ENABLE ROW LEVEL SECURITY; -- 정책 없음: service_role 만 접근. 프론트는 백엔드 API 통해서 조회. COMMENT ON TABLE public.user_laugh_cache IS 'Phase 2 (laugh register): 사용자 ref × admin 템플릿 변환 캐시. apply_dual 직후 백그라운드 VC 작업이 채움.'; COMMENT ON COLUMN public.user_laugh_cache.voice_storage_path IS '사용자 ref wav 의 storage_path (예: {user_id}/{stem}.wav). dual register 의 casual/formal 양쪽이 같은 path 공유.'; COMMENT ON COLUMN public.user_laugh_cache.laugh_storage_path IS '변환된 wav (예: {user_id}/laughs/{stem}_{template_id}.wav).';