-- ===================================================================== -- STUDIO SCHEMA — Saved Projects (User's Project Instances) -- ===================================================================== -- This is where user input lives: their text values, color choices, -- media uploads, music, voiceover, etc. The render service reads from -- here to build the JSX. -- ===================================================================== SET search_path TO studio, public; CREATE TYPE saved_project_type AS ENUM ('Draft','Active','Archived','Trash'); -- --------------------------------------------------------------------- -- saved_projects — root of user's project instance -- --------------------------------------------------------------------- CREATE TABLE saved_projects ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), tenant_id UUID NOT NULL, -- references identity.tenants user_id UUID NOT NULL, -- references identity.users -- Source template snapshot (so deleting template doesn't break this) original_project_id UUID NOT NULL, -- references content.projects original_project_name TEXT NOT NULL, original_container_id UUID, original_container_slug CITEXT, -- Identity name TEXT NOT NULL, image TEXT, type saved_project_type NOT NULL DEFAULT 'Draft', -- Snapshot of project metadata frame_rate INT NOT NULL DEFAULT 30, project_duration_sec NUMERIC(8,2) NOT NULL, resolution TEXT NOT NULL, -- HD/FullHD/TwoK/FourK choose_mode TEXT NOT NULL, -- FIX/FLEXIBLE/MockUp/MusicVisualizer vip_factor NUMERIC(4,2) NOT NULL DEFAULT 1.0, -- ===================================== -- Audio (NEW — voiceover + volumes) -- ===================================== music_file_id UUID, -- references file_mgr.user_files music_track_id UUID, -- references content.music_tracks (library) music_volume NUMERIC(4,3) NOT NULL DEFAULT 0.7 CHECK (music_volume BETWEEN 0 AND 1), voiceover_file_id UUID, -- references file_mgr.user_files voiceover_volume NUMERIC(4,3) NOT NULL DEFAULT 1.0 CHECK (voiceover_volume BETWEEN 0 AND 1), voiceover_recorded_in_browser BOOLEAN NOT NULL DEFAULT FALSE, sfx_volume NUMERIC(4,3) NOT NULL DEFAULT 1.0 CHECK (sfx_volume BETWEEN 0 AND 1), sfx_enabled BOOLEAN NOT NULL DEFAULT TRUE, -- Music visualizer mode audio_visualizer_music_url TEXT, audio_visualizer_duration_sec NUMERIC(8,2), -- ===================================== -- Customization options -- ===================================== manual_color_picker BOOLEAN NOT NULL DEFAULT FALSE, selected_preset_story_id UUID, -- references content.preset_stories -- Auto-save / state last_edit_step TEXT, -- track wizard step edit_state JSONB NOT NULL DEFAULT '{}'::jsonb, create_date TIMESTAMPTZ NOT NULL DEFAULT NOW(), last_edit_date TIMESTAMPTZ NOT NULL DEFAULT NOW(), created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), deleted_at TIMESTAMPTZ ); CREATE INDEX idx_saved_proj_user ON saved_projects(user_id, last_edit_date DESC) WHERE deleted_at IS NULL; CREATE INDEX idx_saved_proj_tenant ON saved_projects(tenant_id) WHERE deleted_at IS NULL; CREATE INDEX idx_saved_proj_original ON saved_projects(original_project_id); CREATE INDEX idx_saved_proj_name_trgm ON saved_projects USING gin (name gin_trgm_ops); CREATE TRIGGER tg_saved_projects_updated_at BEFORE UPDATE ON saved_projects FOR EACH ROW EXECUTE FUNCTION public.tg_set_updated_at(); -- --------------------------------------------------------------------- -- saved_scenes — user's chosen scenes (FLEXIBLE mode = multiple per project) -- --------------------------------------------------------------------- CREATE TABLE saved_scenes ( id BIGSERIAL PRIMARY KEY, saved_project_id UUID NOT NULL REFERENCES saved_projects(id) ON DELETE CASCADE, -- Snapshot from original scene original_scene_id UUID, -- references content.scenes key TEXT NOT NULL, -- AE comp name title TEXT, image TEXT, demo TEXT, scene_color_svg TEXT, scene_type TEXT NOT NULL, -- Normal/Config/DesignStart/DesignEnd -- Timing sort INT NOT NULL, scene_length_sec NUMERIC(8,2) NOT NULL, min_duration_sec NUMERIC(8,2), max_duration_sec NUMERIC(8,2), overlap_at_end_sec NUMERIC(6,2) NOT NULL DEFAULT 0, can_handle_duration BOOLEAN NOT NULL DEFAULT TRUE, -- Customization manual_color_selection BOOLEAN NOT NULL DEFAULT FALSE, selected_color_preset_id UUID, -- ref to saved_scene_color_presets created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW() ); CREATE INDEX idx_saved_scenes_proj ON saved_scenes(saved_project_id, sort); CREATE TRIGGER tg_saved_scenes_updated_at BEFORE UPDATE ON saved_scenes FOR EACH ROW EXECUTE FUNCTION public.tg_set_updated_at(); -- --------------------------------------------------------------------- -- saved_scene_contents — user-filled content values per scene -- --------------------------------------------------------------------- CREATE TABLE saved_scene_contents ( id BIGSERIAL PRIMARY KEY, saved_scene_id BIGINT NOT NULL REFERENCES saved_scenes(id) ON DELETE CASCADE, -- Element identity key TEXT NOT NULL, title TEXT, localized_title JSONB, hint TEXT, type TEXT NOT NULL, -- Text/Media/Audio/Voiceover/... -- User value value TEXT, -- text or file UUID value_file_id UUID, -- if file: references file_mgr.user_files inserted_file_type TEXT, -- Image/Video/Audio file_url_cached TEXT, -- resolved CDN url at last save file_url_cached_at TIMESTAMPTZ, -- Text styling font_face TEXT, font_face_name TEXT, font_size INT, default_font_size INT, default_font_face TEXT, justify TEXT, position_in_container INT NOT NULL DEFAULT 0, direction_layer_value INT NOT NULL DEFAULT 0, is_text_box BOOLEAN NOT NULL DEFAULT FALSE, -- AI assistance ai_input_type TEXT, -- Design pattern choice selected_dp INT, -- 1-4 -- Repeater repeater_item_key TEXT, repeater_index INT, -- Selection state is_focused BOOLEAN NOT NULL DEFAULT FALSE, status TEXT, mapped_list JSONB, thumbnail TEXT, sort INT NOT NULL DEFAULT 0, created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW() ); CREATE INDEX idx_saved_contents_scene ON saved_scene_contents(saved_scene_id, sort); CREATE INDEX idx_saved_contents_filerefs ON saved_scene_contents(value_file_id) WHERE value_file_id IS NOT NULL; CREATE TRIGGER tg_saved_contents_updated_at BEFORE UPDATE ON saved_scene_contents FOR EACH ROW EXECUTE FUNCTION public.tg_set_updated_at(); -- --------------------------------------------------------------------- -- saved_scene_colors — color choices per scene -- --------------------------------------------------------------------- CREATE TABLE saved_scene_colors ( id BIGSERIAL PRIMARY KEY, saved_scene_id BIGINT NOT NULL REFERENCES saved_scenes(id) ON DELETE CASCADE, element_key TEXT NOT NULL, title TEXT, icon TEXT, attr_value TEXT NOT NULL DEFAULT 'fill', value TEXT NOT NULL, -- hex or rgb is_selected BOOLEAN NOT NULL DEFAULT TRUE, sort INT NOT NULL DEFAULT 0, UNIQUE (saved_scene_id, element_key) ); CREATE INDEX idx_saved_colors_scene ON saved_scene_colors(saved_scene_id); -- --------------------------------------------------------------------- -- saved_scene_color_presets + items -- --------------------------------------------------------------------- CREATE TABLE saved_scene_color_presets ( id BIGSERIAL PRIMARY KEY, saved_scene_id BIGINT NOT NULL REFERENCES saved_scenes(id) ON DELETE CASCADE, is_selected BOOLEAN NOT NULL DEFAULT FALSE, sort INT NOT NULL DEFAULT 0 ); CREATE TABLE saved_scene_color_preset_items ( id BIGSERIAL PRIMARY KEY, preset_id BIGINT NOT NULL REFERENCES saved_scene_color_presets(id) ON DELETE CASCADE, element_key TEXT NOT NULL, value TEXT NOT NULL, sort INT NOT NULL DEFAULT 0 ); CREATE INDEX idx_saved_scp_items_preset ON saved_scene_color_preset_items(preset_id); -- --------------------------------------------------------------------- -- saved_scene_characters -- --------------------------------------------------------------------- CREATE TABLE saved_scene_characters ( id BIGSERIAL PRIMARY KEY, saved_scene_id BIGINT NOT NULL REFERENCES saved_scenes(id) ON DELETE CASCADE, key UUID NOT NULL, name TEXT, icon TEXT ); CREATE INDEX idx_saved_chars_scene ON saved_scene_characters(saved_scene_id); -- --------------------------------------------------------------------- -- saved_scene_character_controllers -- --------------------------------------------------------------------- CREATE TABLE saved_scene_character_controllers ( id BIGSERIAL PRIMARY KEY, saved_scene_character_id BIGINT NOT NULL REFERENCES saved_scene_characters(id) ON DELETE CASCADE, name TEXT, key TEXT NOT NULL, value TEXT NOT NULL, sort INT NOT NULL DEFAULT 0 ); CREATE INDEX idx_saved_char_ctrl_char ON saved_scene_character_controllers(saved_scene_character_id); -- --------------------------------------------------------------------- -- saved_shared_colors — project-level color choices -- --------------------------------------------------------------------- CREATE TABLE saved_shared_colors ( id BIGSERIAL PRIMARY KEY, saved_project_id UUID NOT NULL REFERENCES saved_projects(id) ON DELETE CASCADE, element_key TEXT NOT NULL, title TEXT, icon TEXT, attr_value TEXT NOT NULL DEFAULT 'fill', value TEXT NOT NULL, is_selected BOOLEAN NOT NULL DEFAULT TRUE, sort INT NOT NULL DEFAULT 0, UNIQUE (saved_project_id, element_key) ); CREATE INDEX idx_saved_shared_colors_proj ON saved_shared_colors(saved_project_id); -- --------------------------------------------------------------------- -- saved_shared_color_presets -- --------------------------------------------------------------------- CREATE TABLE saved_shared_color_presets ( id BIGSERIAL PRIMARY KEY, saved_project_id UUID NOT NULL REFERENCES saved_projects(id) ON DELETE CASCADE, name TEXT, is_selected BOOLEAN NOT NULL DEFAULT FALSE, sort INT NOT NULL DEFAULT 0 ); CREATE TABLE saved_shared_color_preset_items ( id BIGSERIAL PRIMARY KEY, preset_id BIGINT NOT NULL REFERENCES saved_shared_color_presets(id) ON DELETE CASCADE, element_key TEXT NOT NULL, value TEXT NOT NULL, sort INT NOT NULL DEFAULT 0 ); CREATE INDEX idx_saved_sscp_items_preset ON saved_shared_color_preset_items(preset_id); -- --------------------------------------------------------------------- -- saved_shared_layers — project-level layer values -- --------------------------------------------------------------------- CREATE TABLE saved_shared_layers ( id BIGSERIAL PRIMARY KEY, saved_project_id UUID NOT NULL REFERENCES saved_projects(id) ON DELETE CASCADE, key TEXT NOT NULL, title TEXT, localized_title JSONB, hint TEXT, type TEXT NOT NULL, value TEXT, value_file_id UUID, file_url_cached TEXT, file_url_cached_at TIMESTAMPTZ, font_face TEXT, font_face_name TEXT, font_size INT, default_font_size INT, default_font_face TEXT, justify TEXT, position_in_container INT NOT NULL DEFAULT 0, direction_layer_value INT NOT NULL DEFAULT 0, is_text_box BOOLEAN NOT NULL DEFAULT FALSE, ai_input_type TEXT, mapped_list JSONB, thumbnail TEXT, width INT, height INT, min_duration_sec NUMERIC(6,2), max_duration_sec NUMERIC(6,2), is_focused BOOLEAN NOT NULL DEFAULT FALSE, is_font_changeable BOOLEAN NOT NULL DEFAULT TRUE, is_font_size_changeable BOOLEAN NOT NULL DEFAULT TRUE, status TEXT, sort INT NOT NULL DEFAULT 0, created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), UNIQUE (saved_project_id, key) ); CREATE INDEX idx_saved_shared_layers_proj ON saved_shared_layers(saved_project_id); CREATE TRIGGER tg_saved_shared_layers_updated_at BEFORE UPDATE ON saved_shared_layers FOR EACH ROW EXECUTE FUNCTION public.tg_set_updated_at();