feat: YouTube music download system with admin dashboard
Sidecar architecture: edge functions (auth + DB) → youtube-worker container (youtubei.js for playlist metadata, yt-dlp for audio downloads). - Edge functions: youtube-playlist, youtube-process, youtube-status (CRUD) - youtube-worker sidecar: Node.js + yt-dlp, downloads M4A to Supabase Storage - Admin music page: import playlists, process queue, inline genre editing - 12 music genres with per-track assignment and import-time defaults - DB migrations: download_jobs, download_items tables with genre column - Deploy script and CI workflow for edge functions + sidecar
This commit is contained in:
96
supabase/migrations/002_download_jobs.sql
Normal file
96
supabase/migrations/002_download_jobs.sql
Normal file
@@ -0,0 +1,96 @@
|
||||
-- ============================================================
|
||||
-- YouTube Playlist Download Jobs
|
||||
-- ============================================================
|
||||
-- Tracks playlist-level download jobs and per-video items
|
||||
-- Used by the youtube-playlist, youtube-process, youtube-status edge functions
|
||||
|
||||
-- Download jobs table (one row per playlist import)
|
||||
CREATE TABLE IF NOT EXISTS public.download_jobs (
|
||||
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
|
||||
playlist_url TEXT NOT NULL,
|
||||
playlist_title TEXT,
|
||||
status TEXT NOT NULL DEFAULT 'pending'
|
||||
CHECK (status IN ('pending', 'processing', 'completed', 'failed')),
|
||||
total_items INTEGER NOT NULL DEFAULT 0,
|
||||
completed_items INTEGER NOT NULL DEFAULT 0,
|
||||
failed_items INTEGER NOT NULL DEFAULT 0,
|
||||
created_by UUID NOT NULL REFERENCES auth.users(id) ON DELETE CASCADE,
|
||||
created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
|
||||
updated_at TIMESTAMP WITH TIME ZONE DEFAULT NOW()
|
||||
);
|
||||
|
||||
-- Download items table (one row per video in the playlist)
|
||||
CREATE TABLE IF NOT EXISTS public.download_items (
|
||||
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
|
||||
job_id UUID NOT NULL REFERENCES public.download_jobs(id) ON DELETE CASCADE,
|
||||
video_id TEXT NOT NULL,
|
||||
title TEXT,
|
||||
duration_seconds INTEGER,
|
||||
thumbnail_url TEXT,
|
||||
status TEXT NOT NULL DEFAULT 'pending'
|
||||
CHECK (status IN ('pending', 'downloading', 'completed', 'failed')),
|
||||
storage_path TEXT,
|
||||
public_url TEXT,
|
||||
error_message TEXT,
|
||||
created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW()
|
||||
);
|
||||
|
||||
-- ============================================================
|
||||
-- INDEXES
|
||||
-- ============================================================
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_download_jobs_created_by ON public.download_jobs(created_by);
|
||||
CREATE INDEX IF NOT EXISTS idx_download_jobs_status ON public.download_jobs(status);
|
||||
CREATE INDEX IF NOT EXISTS idx_download_jobs_created_at ON public.download_jobs(created_at DESC);
|
||||
CREATE INDEX IF NOT EXISTS idx_download_items_job_id ON public.download_items(job_id);
|
||||
CREATE INDEX IF NOT EXISTS idx_download_items_status ON public.download_items(status);
|
||||
CREATE INDEX IF NOT EXISTS idx_download_items_created_at ON public.download_items(created_at);
|
||||
|
||||
-- ============================================================
|
||||
-- TRIGGERS
|
||||
-- ============================================================
|
||||
|
||||
CREATE TRIGGER update_download_jobs_updated_at
|
||||
BEFORE UPDATE ON public.download_jobs
|
||||
FOR EACH ROW EXECUTE FUNCTION public.update_updated_at_column();
|
||||
|
||||
-- ============================================================
|
||||
-- ROW LEVEL SECURITY
|
||||
-- ============================================================
|
||||
|
||||
ALTER TABLE public.download_jobs ENABLE ROW LEVEL SECURITY;
|
||||
ALTER TABLE public.download_items ENABLE ROW LEVEL SECURITY;
|
||||
|
||||
-- Admin-only read/write (same pattern as existing tables)
|
||||
CREATE POLICY "Allow admin read access" ON public.download_jobs
|
||||
FOR SELECT USING (
|
||||
EXISTS (SELECT 1 FROM public.admin_users WHERE id = auth.uid())
|
||||
);
|
||||
|
||||
CREATE POLICY "Allow admin write access" ON public.download_jobs
|
||||
FOR ALL USING (
|
||||
EXISTS (SELECT 1 FROM public.admin_users WHERE id = auth.uid())
|
||||
);
|
||||
|
||||
CREATE POLICY "Allow admin read access" ON public.download_items
|
||||
FOR SELECT USING (
|
||||
EXISTS (SELECT 1 FROM public.admin_users WHERE id = auth.uid())
|
||||
);
|
||||
|
||||
CREATE POLICY "Allow admin write access" ON public.download_items
|
||||
FOR ALL USING (
|
||||
EXISTS (SELECT 1 FROM public.admin_users WHERE id = auth.uid())
|
||||
);
|
||||
|
||||
-- ============================================================
|
||||
-- STORAGE BUCKET: workout-audio
|
||||
-- ============================================================
|
||||
-- Create via Supabase Dashboard or CLI:
|
||||
-- supabase storage create workout-audio --public
|
||||
--
|
||||
-- Configuration:
|
||||
-- Bucket name: workout-audio
|
||||
-- Public: true
|
||||
-- Allowed MIME types: audio/mp4, audio/webm, audio/mpeg
|
||||
-- Max file size: 100MB
|
||||
-- Path pattern: {job_id}/{video_id}.m4a
|
||||
18
supabase/migrations/003_music_genre.sql
Normal file
18
supabase/migrations/003_music_genre.sql
Normal file
@@ -0,0 +1,18 @@
|
||||
-- ============================================================
|
||||
-- Music Genre Classification
|
||||
-- ============================================================
|
||||
-- Adds per-track genre to download_items so users can filter
|
||||
-- workout music by genre preference.
|
||||
|
||||
-- Genre enum values: edm, hip-hop, pop, rock, latin, house,
|
||||
-- drum-and-bass, dubstep, r-and-b, country, metal, ambient
|
||||
|
||||
ALTER TABLE public.download_items
|
||||
ADD COLUMN IF NOT EXISTS genre TEXT
|
||||
CHECK (genre IS NULL OR genre IN (
|
||||
'edm', 'hip-hop', 'pop', 'rock', 'latin', 'house',
|
||||
'drum-and-bass', 'dubstep', 'r-and-b', 'country', 'metal', 'ambient'
|
||||
));
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_download_items_genre
|
||||
ON public.download_items(genre);
|
||||
Reference in New Issue
Block a user