feat: V2 microservices stack — backend services, gateway, JWT auth
Add full V2 architecture: identity, content, studio (.NET 10) and file, render, notification, gateway (Go) services with vendored deps, plus DB migrations, event/API contracts, and an init-db script. Wire the Next.js frontend to the gateway: server-side JWT auth routes (login/register/refresh/logout/me), gateway fetch helper, and session/ cookie/jwt helpers under src/lib. Containerize the stack via docker-compose.v2.yml and per-service Dockerfiles. Base images resolve through a Nexus mirror (Docker Hub) and MCR directly; npm/NuGet pull from Nexus groups. Self-host fonts via next/font/local to avoid Google Fonts (geo-blocked). Add CI workflow and ignore .env.v2, *.stackdump, and .NET bin/obj. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,335 @@
|
||||
-- =====================================================================
|
||||
-- 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();
|
||||
Reference in New Issue
Block a user