Files
soroush.asadi 90ac0b81d1 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>
2026-05-29 23:29:31 +03:30

369 lines
16 KiB
SQL

-- =====================================================================
-- CONTENT SCHEMA — Part 3: Scenes & Editable Elements
-- =====================================================================
SET search_path TO content, public;
CREATE TYPE scene_kind AS ENUM ('Normal','Config','DesignStart','DesignEnd');
CREATE TYPE content_element_type AS ENUM (
'Text','TextArea','Media','Audio','Voiceover',
'CheckBox','DropDown','Fill','Color','Number',
'Date','Toggle','Slider','Counter','Hidden'
);
CREATE TYPE justify_kind AS ENUM ('LEFT_JUSTIFY','CENTER_JUSTIFY','RIGHT_JUSTIFY','FULL_JUSTIFY');
CREATE TYPE ai_input_type AS ENUM ('None','TitleSuggest','BodySuggest','TranslateRtl','TranslateLtr','RemoveBG','UpscaleImage','TTS');
CREATE TYPE repeat_sort_strategy AS ENUM ('Manual','Alphabetical','Numerical','InsertOrder');
CREATE TYPE attr_value_kind AS ENUM ('fill','stroke','tracking','dropshadow');
-- ---------------------------------------------------------------------
-- scenes
-- ---------------------------------------------------------------------
CREATE TABLE scenes (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
project_id UUID NOT NULL REFERENCES projects(id) ON DELETE CASCADE,
-- Identity (maps to AE comp name)
key TEXT NOT NULL, -- matches AE comp name
title TEXT NOT NULL,
localized_title JSONB, -- {"fa":"...","en":"..."}
-- Type
scene_type scene_kind NOT NULL DEFAULT 'Normal',
-- Media
image TEXT,
demo TEXT,
scene_color_svg TEXT, -- SVG color preview (legacy)
snapshot_url TEXT, -- pre-rendered representative frame
-- Animation flags
generate_kf BOOLEAN NOT NULL DEFAULT FALSE,
-- Timing
default_duration_sec NUMERIC(8,2),
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,
sort INT NOT NULL DEFAULT 0,
is_active BOOLEAN NOT NULL DEFAULT TRUE,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
deleted_at TIMESTAMPTZ,
UNIQUE (project_id, key)
);
CREATE INDEX idx_scenes_project ON scenes(project_id, sort) WHERE deleted_at IS NULL;
CREATE INDEX idx_scenes_type ON scenes(project_id, scene_type);
CREATE TRIGGER tg_scenes_updated_at
BEFORE UPDATE ON scenes FOR EACH ROW EXECUTE FUNCTION public.tg_set_updated_at();
-- ---------------------------------------------------------------------
-- M2M: scenes ↔ categories
-- ---------------------------------------------------------------------
CREATE TABLE scene_categories (
scene_id UUID NOT NULL REFERENCES scenes(id) ON DELETE CASCADE,
category_id UUID NOT NULL REFERENCES categories(id) ON DELETE CASCADE,
PRIMARY KEY (scene_id, category_id)
);
-- ---------------------------------------------------------------------
-- repeater_items — repeating sub-blocks within a scene
-- ---------------------------------------------------------------------
CREATE TABLE repeater_items (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
scene_id UUID NOT NULL REFERENCES scenes(id) ON DELETE CASCADE,
title TEXT NOT NULL,
repeat_box_key TEXT NOT NULL, -- AE layer name of container
repeat_item_key TEXT NOT NULL, -- AE layer name of item template
max_repeat_count INT NOT NULL DEFAULT 10,
user_can_change_sort BOOLEAN NOT NULL DEFAULT TRUE,
repeat_sort_strategy repeat_sort_strategy NOT NULL DEFAULT 'Manual',
sort INT NOT NULL DEFAULT 0,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);
CREATE INDEX idx_repeaters_scene ON repeater_items(scene_id);
CREATE TRIGGER tg_repeaters_updated_at
BEFORE UPDATE ON repeater_items FOR EACH ROW EXECUTE FUNCTION public.tg_set_updated_at();
-- ---------------------------------------------------------------------
-- scene_content_elements — every editable field in a scene
-- ---------------------------------------------------------------------
CREATE TABLE scene_content_elements (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
scene_id UUID NOT NULL REFERENCES scenes(id) ON DELETE CASCADE,
repeater_item_id UUID REFERENCES repeater_items(id) ON DELETE CASCADE,
-- Identity (maps to AE frl_/frd_ layer name)
key TEXT NOT NULL,
title TEXT NOT NULL,
localized_title JSONB,
hint TEXT,
type content_element_type NOT NULL,
default_value TEXT,
-- Text-specific
font_id UUID REFERENCES fonts(id),
font_face TEXT,
font_face_name TEXT,
font_size INT,
default_font_size INT,
default_font_face TEXT,
is_font_changeable BOOLEAN NOT NULL DEFAULT TRUE,
is_font_size_changeable BOOLEAN NOT NULL DEFAULT TRUE,
justify justify_kind NOT NULL DEFAULT 'CENTER_JUSTIFY',
can_justify BOOLEAN NOT NULL DEFAULT TRUE,
position_in_container INT NOT NULL DEFAULT 0, -- 0-8 (see JSX positionMode)
is_text_box BOOLEAN NOT NULL DEFAULT FALSE,
max_size INT, -- max char count
direction_layer_key TEXT, -- companion frd_ for RTL
direction_layer_value INT NOT NULL DEFAULT 0, -- 0=LTR, 1=RTL
-- Media-specific
video_support BOOLEAN NOT NULL DEFAULT FALSE,
min_duration_sec NUMERIC(6,2),
max_duration_sec NUMERIC(6,2),
width INT,
height INT,
thumbnail TEXT,
-- Dropdown / mapped list
mapped_list JSONB, -- [{"label","value"}, ...]
counter_mode TEXT,
-- AI
ai_input_type ai_input_type NOT NULL DEFAULT 'None',
-- Visibility / linking
is_hidden BOOLEAN NOT NULL DEFAULT FALSE,
is_focused BOOLEAN NOT NULL DEFAULT FALSE,
opacity_controller_key TEXT, -- ref another element's key
-- Design pattern variants (legacy DP1-4 system)
dp1_image TEXT, dp1_title TEXT,
dp2_image TEXT, dp2_title TEXT,
dp3_image TEXT, dp3_title TEXT,
dp4_image TEXT, dp4_title TEXT,
-- Repeater virtualization
virtual_count INT NOT NULL DEFAULT 1,
sort INT NOT NULL DEFAULT 0,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
UNIQUE (scene_id, key)
);
CREATE INDEX idx_sce_scene ON scene_content_elements(scene_id, sort);
CREATE INDEX idx_sce_repeater ON scene_content_elements(repeater_item_id) WHERE repeater_item_id IS NOT NULL;
CREATE TRIGGER tg_sce_updated_at
BEFORE UPDATE ON scene_content_elements FOR EACH ROW EXECUTE FUNCTION public.tg_set_updated_at();
-- ---------------------------------------------------------------------
-- scene_color_elements — color zones (frd_ data layers per scene)
-- ---------------------------------------------------------------------
CREATE TABLE scene_color_elements (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
scene_id UUID NOT NULL REFERENCES scenes(id) ON DELETE CASCADE,
element_key TEXT NOT NULL, -- matches frd_ layer name
title TEXT NOT NULL,
icon TEXT,
attr_value attr_value_kind NOT NULL DEFAULT 'fill',
default_color TEXT NOT NULL, -- '#RRGGBB' or 'r,g,b'
sort INT NOT NULL DEFAULT 0,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
UNIQUE (scene_id, element_key)
);
CREATE INDEX idx_sce_color_scene ON scene_color_elements(scene_id, sort);
CREATE TRIGGER tg_sce_color_updated_at
BEFORE UPDATE ON scene_color_elements FOR EACH ROW EXECUTE FUNCTION public.tg_set_updated_at();
-- ---------------------------------------------------------------------
-- scene_color_presets — theme presets per scene
-- ---------------------------------------------------------------------
CREATE TABLE scene_color_presets (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
scene_id UUID NOT NULL REFERENCES scenes(id) ON DELETE CASCADE,
name TEXT,
sort INT NOT NULL DEFAULT 0,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);
CREATE TABLE scene_color_preset_items (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
preset_id UUID NOT NULL REFERENCES 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_sc_preset_items_preset ON scene_color_preset_items(preset_id);
-- ---------------------------------------------------------------------
-- shared_colors — global colors across project (frshare comp)
-- ---------------------------------------------------------------------
CREATE TABLE shared_colors (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
project_id UUID NOT NULL REFERENCES projects(id) ON DELETE CASCADE,
element_key TEXT NOT NULL,
title TEXT NOT NULL,
icon TEXT,
attr_value attr_value_kind NOT NULL DEFAULT 'fill',
default_color TEXT NOT NULL,
sort INT NOT NULL DEFAULT 0,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
UNIQUE (project_id, element_key)
);
CREATE INDEX idx_shared_colors_project ON shared_colors(project_id, sort);
CREATE TRIGGER tg_shared_colors_updated_at
BEFORE UPDATE ON shared_colors FOR EACH ROW EXECUTE FUNCTION public.tg_set_updated_at();
-- ---------------------------------------------------------------------
-- shared_color_presets — project-wide palette themes
-- ---------------------------------------------------------------------
CREATE TABLE shared_color_presets (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
project_id UUID NOT NULL REFERENCES projects(id) ON DELETE CASCADE,
name TEXT,
sort INT NOT NULL DEFAULT 0,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);
CREATE TABLE shared_color_preset_items (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
preset_id UUID NOT NULL REFERENCES 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_shared_preset_items_preset ON shared_color_preset_items(preset_id);
-- ---------------------------------------------------------------------
-- shared_layers — global text/media layers across all scenes
-- ---------------------------------------------------------------------
CREATE TABLE shared_layers (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
project_id UUID NOT NULL REFERENCES projects(id) ON DELETE CASCADE,
key TEXT NOT NULL,
title TEXT NOT NULL,
localized_title JSONB,
hint TEXT,
type content_element_type NOT NULL,
default_value TEXT,
font_id UUID REFERENCES fonts(id),
font_face TEXT,
font_face_name TEXT,
font_size INT,
default_font_size INT,
default_font_face TEXT,
is_font_changeable BOOLEAN NOT NULL DEFAULT TRUE,
is_font_size_changeable BOOLEAN NOT NULL DEFAULT TRUE,
justify justify_kind NOT NULL DEFAULT 'CENTER_JUSTIFY',
can_justify BOOLEAN NOT NULL DEFAULT TRUE,
position_in_container INT NOT NULL DEFAULT 0,
is_text_box BOOLEAN NOT NULL DEFAULT FALSE,
max_size INT,
direction_layer_key TEXT,
direction_layer_value INT NOT NULL DEFAULT 0,
video_support BOOLEAN NOT NULL DEFAULT FALSE,
min_duration_sec NUMERIC(6,2),
max_duration_sec NUMERIC(6,2),
width INT,
height INT,
thumbnail TEXT,
mapped_list JSONB,
counter_mode TEXT,
ai_input_type ai_input_type NOT NULL DEFAULT 'None',
is_hidden BOOLEAN NOT NULL DEFAULT FALSE,
is_focused BOOLEAN NOT NULL DEFAULT FALSE,
dp1_image TEXT, dp1_title TEXT,
dp2_image TEXT, dp2_title TEXT,
dp3_image TEXT, dp3_title TEXT,
dp4_image TEXT, dp4_title TEXT,
virtual_count INT NOT NULL DEFAULT 1,
sort INT NOT NULL DEFAULT 0,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
UNIQUE (project_id, key)
);
CREATE INDEX idx_shared_layers_project ON shared_layers(project_id, sort);
CREATE TRIGGER tg_shared_layers_updated_at
BEFORE UPDATE ON shared_layers FOR EACH ROW EXECUTE FUNCTION public.tg_set_updated_at();
-- ---------------------------------------------------------------------
-- template_svg_previews — NEW: drop-an-image → traced SVG for live color preview
-- ---------------------------------------------------------------------
CREATE TABLE template_svg_previews (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
project_id UUID REFERENCES projects(id) ON DELETE CASCADE,
scene_id UUID REFERENCES scenes(id) ON DELETE CASCADE,
-- Exactly one of project_id or scene_id should be set
source_image_url TEXT, -- the dropped image
svg_url TEXT NOT NULL, -- in MinIO
thumbnail_url TEXT,
-- Maps each SVG <path data-color-key="frd_bg"> to a color element
color_zones JSONB NOT NULL,
-- Example: [{"element_key":"frd_bg","detected_color":"#1A1A2E","bbox":[x,y,w,h]}]
width INT,
height INT,
generation_method TEXT, -- 'auto','manual','ai-assisted'
generated_by_ai BOOLEAN NOT NULL DEFAULT FALSE,
quality_score NUMERIC(3,2), -- 0-1 confidence
created_by_user_id UUID,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
CHECK ((project_id IS NULL) <> (scene_id IS NULL)) -- exactly one
);
CREATE INDEX idx_svg_previews_project ON template_svg_previews(project_id);
CREATE INDEX idx_svg_previews_scene ON template_svg_previews(scene_id);
CREATE TRIGGER tg_svg_previews_updated_at
BEFORE UPDATE ON template_svg_previews FOR EACH ROW EXECUTE FUNCTION public.tg_set_updated_at();