-- 스케줄 예약 음성 파일 클라우드 보관. -- -- 정책: 사용자가 스케줄 예약 시 명시적 동의 → 플레이리스트 항목들의 wav 를 -- 병합해 Supabase Storage 에 업로드. 알림 클릭 시 그 단일 파일 재생 → TTS -- 재합성 부하 0, cache miss 0. -- -- 차별화 메시지와 일치: 자유 대화 / Voice Contents 는 비저장 그대로, -- 스케줄 예약은 사용자 명시 동의 + 클라우드 보관 명시. ALTER TABLE public.playlist_schedules ADD COLUMN IF NOT EXISTS audio_file_url TEXT, -- public/signed URL ADD COLUMN IF NOT EXISTS audio_file_path TEXT, -- storage path (삭제용) ADD COLUMN IF NOT EXISTS audio_uploaded_at TIMESTAMPTZ; -- 스케줄 cascade 삭제 시 audio_file_path 가 살아있는 storage 파일 cleanup 은 -- 별도 cron 또는 schedule_service.delete_schedule 에서 처리. -- ───────── Supabase Storage bucket ───────── -- bucket 생성은 Dashboard 에서 직접 하거나, 아래 INSERT 로 가능. INSERT INTO storage.buckets (id, name, public) VALUES ('scheduled-audio', 'scheduled-audio', false) ON CONFLICT (id) DO NOTHING; -- RLS: 본인 폴더(/) 만 읽기/쓰기/삭제. signed URL 은 RLS 우회. DROP POLICY IF EXISTS scheduled_audio_own_select ON storage.objects; CREATE POLICY scheduled_audio_own_select ON storage.objects FOR SELECT TO authenticated USING ( bucket_id = 'scheduled-audio' AND (auth.uid()::text = (storage.foldername(name))[1]) ); DROP POLICY IF EXISTS scheduled_audio_own_insert ON storage.objects; CREATE POLICY scheduled_audio_own_insert ON storage.objects FOR INSERT TO authenticated WITH CHECK ( bucket_id = 'scheduled-audio' AND (auth.uid()::text = (storage.foldername(name))[1]) ); DROP POLICY IF EXISTS scheduled_audio_own_update ON storage.objects; CREATE POLICY scheduled_audio_own_update ON storage.objects FOR UPDATE TO authenticated USING ( bucket_id = 'scheduled-audio' AND (auth.uid()::text = (storage.foldername(name))[1]) ); DROP POLICY IF EXISTS scheduled_audio_own_delete ON storage.objects; CREATE POLICY scheduled_audio_own_delete ON storage.objects FOR DELETE TO authenticated USING ( bucket_id = 'scheduled-audio' AND (auth.uid()::text = (storage.foldername(name))[1]) );