-- 040_streaming_core.sql -- Core Streaming Entities: Tracks, Playlists (Aligned with ORIGIN) -- === TRACKS === CREATE TABLE public.tracks ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), creator_id UUID NOT NULL REFERENCES public.users(id) ON DELETE CASCADE, file_id UUID REFERENCES public.files(id) ON DELETE RESTRICT, -- NULL temporairement avant création fichier -- Track Info title VARCHAR(255) NOT NULL, description TEXT, artist VARCHAR(255), album VARCHAR(255), genre VARCHAR(100), year INTEGER DEFAULT 0, -- Release year (for Go model compatibility) -- Audio Properties duration INTEGER NOT NULL, -- seconds bpm INTEGER, musical_key VARCHAR(10), -- Visibility visibility public.visibility NOT NULL DEFAULT 'public', is_downloadable BOOLEAN NOT NULL DEFAULT false, -- Media cover_art_file_id UUID REFERENCES public.files(id) ON DELETE SET NULL, waveform_data JSONB, -- Counts (denormalized) play_count INTEGER NOT NULL DEFAULT 0, like_count INTEGER NOT NULL DEFAULT 0, comment_count INTEGER NOT NULL DEFAULT 0, download_count INTEGER NOT NULL DEFAULT 0, -- Legacy/Go Compatibility fields (Denormalized or Mapped) user_id UUID, -- Maps to creator_id file_path VARCHAR(500), -- Maps to files.url or storage_path file_size BIGINT, -- Maps to files.file_size format VARCHAR(10), bitrate INTEGER, sample_rate INTEGER, waveform_path VARCHAR(500), -- Legacy cover_art_path VARCHAR(500), -- Legacy status VARCHAR(20) DEFAULT 'uploading', -- Legacy status status_message TEXT, stream_status VARCHAR(20) DEFAULT 'pending', -- Legacy stream status (pending, processing, ready, error) stream_manifest_url VARCHAR(500), -- Legacy stream manifest URL is_public BOOLEAN DEFAULT true, -- Maps to visibility='public' -- Timestamps published_at TIMESTAMPTZ, created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), deleted_at TIMESTAMPTZ, CONSTRAINT chk_tracks_duration_positive CHECK (duration >= 0) -- Permet 0 temporairement avant extraction métadonnées ); -- Indexes CREATE INDEX idx_tracks_creator_id ON public.tracks(creator_id); CREATE INDEX idx_tracks_genre ON public.tracks(genre); CREATE INDEX idx_tracks_visibility ON public.tracks(visibility); CREATE INDEX idx_tracks_published_at_desc ON public.tracks(published_at DESC) WHERE published_at IS NOT NULL; CREATE INDEX idx_tracks_play_count_desc ON public.tracks(play_count DESC); CREATE INDEX idx_tracks_created_at_desc ON public.tracks(created_at DESC); CREATE INDEX idx_tracks_search_gin ON public.tracks USING GIN(to_tsvector('english', title || ' ' || COALESCE(artist, '') || ' ' || COALESCE(album, ''))); -- === TRACK VERSIONS (Legacy/Go Support) === -- Origin doesn't strictly specify this, but code implies it. Keeping minimal. CREATE TABLE public.track_versions ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), track_id UUID NOT NULL REFERENCES public.tracks(id) ON DELETE CASCADE, version_number INTEGER NOT NULL, file_path VARCHAR(500) NOT NULL, file_size BIGINT NOT NULL, changelog TEXT, created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), deleted_at TIMESTAMPTZ ); CREATE INDEX idx_track_versions_track_id ON public.track_versions(track_id); CREATE INDEX idx_track_versions_created_at ON public.track_versions(created_at DESC); -- === PLAYLISTS === CREATE TABLE public.playlists ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), user_id UUID NOT NULL REFERENCES public.users(id) ON DELETE CASCADE, -- Playlist Info name VARCHAR(255) NOT NULL, description TEXT, -- Media cover_url TEXT, -- Properties visibility public.visibility NOT NULL DEFAULT 'public', is_collaborative BOOLEAN NOT NULL DEFAULT false, -- Counts track_count INTEGER NOT NULL DEFAULT 0, duration_seconds INTEGER NOT NULL DEFAULT 0, follower_count INTEGER NOT NULL DEFAULT 0, -- Legacy title VARCHAR(200), -- Maps to name is_public BOOLEAN DEFAULT true, -- Maps to visibility -- Timestamps created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), deleted_at TIMESTAMPTZ ); -- Indexes CREATE INDEX idx_playlists_user_id ON public.playlists(user_id); CREATE INDEX idx_playlists_visibility ON public.playlists(visibility); CREATE INDEX idx_playlists_created_at_desc ON public.playlists(created_at DESC); -- === PLAYLIST TRACKS (Junction) === CREATE TABLE public.playlist_tracks ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), playlist_id UUID NOT NULL REFERENCES public.playlists(id) ON DELETE CASCADE, track_id UUID NOT NULL REFERENCES public.tracks(id) ON DELETE CASCADE, -- Order position INTEGER NOT NULL, -- Metadata added_by UUID NOT NULL REFERENCES public.users(id) ON DELETE CASCADE, added_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), CONSTRAINT uq_playlist_tracks_playlist_track UNIQUE (playlist_id, track_id) ); CREATE INDEX idx_playlist_tracks_playlist_id_position ON public.playlist_tracks(playlist_id, position); CREATE INDEX idx_playlist_tracks_track_id ON public.playlist_tracks(track_id); CREATE INDEX idx_playlist_tracks_added_by ON public.playlist_tracks(added_by); -- === PLAYLIST COLLABORATORS (Legacy/Lab) === CREATE TABLE public.playlist_collaborators ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), playlist_id UUID NOT NULL REFERENCES public.playlists(id) ON DELETE CASCADE, user_id UUID NOT NULL REFERENCES public.users(id) ON DELETE CASCADE, permission public.playlist_permission NOT NULL DEFAULT 'read', created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), deleted_at TIMESTAMPTZ ); CREATE INDEX idx_playlist_collaborators_playlist_id ON public.playlist_collaborators(playlist_id); CREATE INDEX idx_playlist_collaborators_user_id ON public.playlist_collaborators(user_id); -- === PLAYLIST FOLLOWS (Legacy/Lab - likely covered by 'follows' or custom logic) === CREATE TABLE public.playlist_follows ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), playlist_id UUID NOT NULL REFERENCES public.playlists(id) ON DELETE CASCADE, user_id UUID NOT NULL REFERENCES public.users(id) ON DELETE CASCADE, created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), deleted_at TIMESTAMPTZ ); CREATE INDEX idx_playlist_follows_playlist_id ON public.playlist_follows(playlist_id); CREATE INDEX idx_playlist_follows_user_id ON public.playlist_follows(user_id); -- === QUEUES === CREATE TABLE public.queues ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), user_id UUID NOT NULL REFERENCES public.users(id) ON DELETE CASCADE UNIQUE, -- Current Track current_track_id UUID REFERENCES public.tracks(id) ON DELETE SET NULL, current_position INTEGER NOT NULL DEFAULT 0, -- Playback State is_playing BOOLEAN NOT NULL DEFAULT false, shuffle BOOLEAN NOT NULL DEFAULT false, repeat_mode VARCHAR(20) NOT NULL DEFAULT 'off', volume INTEGER NOT NULL DEFAULT 100, updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW() ); CREATE UNIQUE INDEX idx_queues_user_id ON public.queues(user_id); -- === QUEUE ITEMS === CREATE TABLE public.queue_items ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), queue_id UUID NOT NULL REFERENCES public.queues(id) ON DELETE CASCADE, track_id UUID NOT NULL REFERENCES public.tracks(id) ON DELETE CASCADE, position INTEGER NOT NULL, added_at TIMESTAMPTZ NOT NULL DEFAULT NOW() ); CREATE INDEX idx_queue_items_queue_id_position ON public.queue_items(queue_id, position);