90ac0b81d1
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>
316 lines
14 KiB
SQL
316 lines
14 KiB
SQL
-- =====================================================================
|
|
-- RENDER SCHEMA — Part 2: Jobs, Frames, Snapshots, Exports
|
|
-- =====================================================================
|
|
|
|
SET search_path TO render, public;
|
|
|
|
CREATE TYPE render_step AS ENUM (
|
|
'Queued','Preparing','TemplateCache','JsxGen','Music',
|
|
'Rendering','Validating','Repairing','Optimisation','Video',
|
|
'Mixing','Final','Uploading','Done','Failed','Cancelled'
|
|
);
|
|
|
|
CREATE TYPE price_kind AS ENUM ('Free','Preview','Cash','Plan','Snapshot','Reseller');
|
|
|
|
CREATE TYPE render_quality AS ENUM ('Low','Medium','High','Full','Lossless');
|
|
|
|
CREATE TYPE frame_job_status AS ENUM (
|
|
'Pending','Rendering','Validated','Repairing','Converting','Done','Failed'
|
|
);
|
|
|
|
CREATE TYPE render_priority_queue AS ENUM (
|
|
'snapshot','vip','paid','preview','mockup','voiceover'
|
|
);
|
|
|
|
-- ---------------------------------------------------------------------
|
|
-- render_jobs — top-level render task
|
|
-- ---------------------------------------------------------------------
|
|
CREATE TABLE render_jobs (
|
|
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
tenant_id UUID NOT NULL,
|
|
user_id UUID NOT NULL,
|
|
|
|
-- References (loose cross-schema)
|
|
saved_project_id UUID NOT NULL,
|
|
original_project_id UUID NOT NULL,
|
|
project_name TEXT,
|
|
|
|
-- Job identity
|
|
title TEXT,
|
|
name TEXT,
|
|
external_job_id TEXT, -- RabbitMQ message ID
|
|
priority_queue render_priority_queue NOT NULL,
|
|
priority_score INT NOT NULL DEFAULT 50, -- 0-100, higher = sooner
|
|
|
|
-- Pipeline state
|
|
step render_step NOT NULL DEFAULT 'Queued',
|
|
render_progress INT NOT NULL DEFAULT 0 CHECK (render_progress BETWEEN 0 AND 100),
|
|
convert_progress INT NOT NULL DEFAULT 0,
|
|
image_preview_b64 TEXT, -- last frame thumbnail
|
|
|
|
-- Pricing
|
|
price_type price_kind NOT NULL,
|
|
paid_price_minor BIGINT NOT NULL DEFAULT 0,
|
|
discount_code TEXT,
|
|
support_flatrender BOOLEAN NOT NULL DEFAULT FALSE,
|
|
|
|
-- Output config
|
|
mode TEXT NOT NULL, -- FIX/FLEXIBLE/MockUp/MusicVisualizer
|
|
quality render_quality NOT NULL DEFAULT 'High',
|
|
resolution TEXT NOT NULL, -- FullHD/FourK
|
|
r_height INT NOT NULL,
|
|
frame_rate INT NOT NULL DEFAULT 30,
|
|
is_60_fps BOOLEAN NOT NULL DEFAULT FALSE,
|
|
duration_sec NUMERIC(8,2) NOT NULL,
|
|
export_duration_sec NUMERIC(8,2),
|
|
|
|
-- Audio (NEW)
|
|
has_music BOOLEAN NOT NULL DEFAULT FALSE,
|
|
has_sfx BOOLEAN NOT NULL DEFAULT FALSE,
|
|
has_voiceover BOOLEAN NOT NULL DEFAULT FALSE,
|
|
music_volume NUMERIC(4,3),
|
|
sfx_volume NUMERIC(4,3),
|
|
voiceover_volume NUMERIC(4,3),
|
|
|
|
-- Resource allocation
|
|
render_node_count INT NOT NULL DEFAULT 1,
|
|
current_active_nodes INT NOT NULL DEFAULT 0,
|
|
region TEXT, -- preferred region
|
|
tell_me_when_done BOOLEAN NOT NULL DEFAULT TRUE,
|
|
|
|
-- Retry / recovery
|
|
retry_count INT NOT NULL DEFAULT 0,
|
|
max_retries INT NOT NULL DEFAULT 3,
|
|
repair_attempts INT NOT NULL DEFAULT 0,
|
|
failed_message TEXT,
|
|
failed_at_step render_step,
|
|
|
|
-- File outputs (paths in MinIO; final URL goes in exports.path)
|
|
render_folder TEXT, -- temp working dir
|
|
output_folder TEXT, -- frames dir
|
|
physical_render_folder TEXT, -- absolute path for nodes
|
|
physical_output_folder TEXT,
|
|
target_replica_name TEXT,
|
|
|
|
-- Reference to result
|
|
export_id UUID,
|
|
|
|
-- Timing
|
|
task_start_date TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
|
start_render_date TIMESTAMPTZ,
|
|
done_task_date TIMESTAMPTZ,
|
|
queued_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
|
started_at TIMESTAMPTZ,
|
|
completed_at TIMESTAMPTZ,
|
|
|
|
metadata JSONB NOT NULL DEFAULT '{}'::jsonb,
|
|
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
|
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
|
|
);
|
|
|
|
CREATE INDEX idx_render_jobs_user ON render_jobs(user_id, created_at DESC);
|
|
CREATE INDEX idx_render_jobs_tenant ON render_jobs(tenant_id, created_at DESC);
|
|
CREATE INDEX idx_render_jobs_step ON render_jobs(step) WHERE step NOT IN ('Done','Failed','Cancelled');
|
|
CREATE INDEX idx_render_jobs_queue ON render_jobs(priority_queue, priority_score DESC, queued_at)
|
|
WHERE step = 'Queued';
|
|
CREATE INDEX idx_render_jobs_in_flight ON render_jobs(started_at) WHERE step NOT IN ('Done','Failed','Cancelled','Queued');
|
|
|
|
CREATE TRIGGER tg_render_jobs_updated_at
|
|
BEFORE UPDATE ON render_jobs FOR EACH ROW EXECUTE FUNCTION public.tg_set_updated_at();
|
|
|
|
-- ---------------------------------------------------------------------
|
|
-- frame_jobs — per-node frame-range assignments
|
|
-- ---------------------------------------------------------------------
|
|
CREATE TABLE frame_jobs (
|
|
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
render_job_id UUID NOT NULL REFERENCES render_jobs(id) ON DELETE CASCADE,
|
|
node_id UUID NOT NULL REFERENCES render_nodes(id),
|
|
|
|
-- Range
|
|
start_frame INT NOT NULL,
|
|
end_frame INT NOT NULL,
|
|
collect_frame_count INT NOT NULL,
|
|
order_value INT NOT NULL DEFAULT 0,
|
|
folder_name TEXT NOT NULL, -- "O0", "O1"...
|
|
convert_url TEXT,
|
|
|
|
-- Status
|
|
status frame_job_status NOT NULL DEFAULT 'Pending',
|
|
frames_rendered INT NOT NULL DEFAULT 0,
|
|
frames_validated INT NOT NULL DEFAULT 0,
|
|
|
|
-- Errors
|
|
attempt INT NOT NULL DEFAULT 1,
|
|
last_error TEXT,
|
|
|
|
-- Outputs
|
|
output_mp4_url TEXT, -- chunk MP4 after ffmpeg
|
|
|
|
-- Timing
|
|
assigned_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
|
started_at TIMESTAMPTZ,
|
|
last_progress_at TIMESTAMPTZ,
|
|
completed_at TIMESTAMPTZ,
|
|
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
|
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
|
|
);
|
|
|
|
CREATE INDEX idx_frame_jobs_render ON frame_jobs(render_job_id, order_value);
|
|
CREATE INDEX idx_frame_jobs_node ON frame_jobs(node_id, status);
|
|
CREATE INDEX idx_frame_jobs_stalled ON frame_jobs(last_progress_at) WHERE status = 'Rendering';
|
|
|
|
CREATE TRIGGER tg_frame_jobs_updated_at
|
|
BEFORE UPDATE ON frame_jobs FOR EACH ROW EXECUTE FUNCTION public.tg_set_updated_at();
|
|
|
|
-- ---------------------------------------------------------------------
|
|
-- frame_repair_jobs — missing/corrupt frame repair tracking
|
|
-- ---------------------------------------------------------------------
|
|
CREATE TABLE frame_repair_jobs (
|
|
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
render_job_id UUID NOT NULL REFERENCES render_jobs(id) ON DELETE CASCADE,
|
|
node_id UUID REFERENCES render_nodes(id),
|
|
|
|
-- Range to repair
|
|
start_frame INT NOT NULL,
|
|
end_frame INT NOT NULL,
|
|
missing_frames INT[] NOT NULL DEFAULT '{}',
|
|
corrupt_frames INT[] NOT NULL DEFAULT '{}',
|
|
|
|
attempt INT NOT NULL DEFAULT 1,
|
|
status frame_job_status NOT NULL DEFAULT 'Pending',
|
|
error TEXT,
|
|
|
|
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
|
completed_at TIMESTAMPTZ
|
|
);
|
|
|
|
CREATE INDEX idx_frame_repair_job ON frame_repair_jobs(render_job_id, attempt);
|
|
|
|
-- ---------------------------------------------------------------------
|
|
-- snapshots — single-frame scene previews (new feature)
|
|
-- ---------------------------------------------------------------------
|
|
CREATE TABLE snapshots (
|
|
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
tenant_id UUID NOT NULL,
|
|
user_id UUID NOT NULL,
|
|
saved_project_id UUID NOT NULL,
|
|
scene_key TEXT NOT NULL,
|
|
frame_number INT NOT NULL,
|
|
|
|
-- Cache key — same inputs = same output
|
|
inputs_hash TEXT NOT NULL,
|
|
|
|
-- Status
|
|
status TEXT NOT NULL DEFAULT 'Pending', -- Pending/Rendering/Done/Failed
|
|
render_node_id UUID REFERENCES render_nodes(id),
|
|
|
|
-- Output
|
|
image_url TEXT,
|
|
thumbnail_url TEXT,
|
|
width INT,
|
|
height INT,
|
|
size_bytes BIGINT,
|
|
|
|
-- Timing
|
|
requested_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
|
completed_at TIMESTAMPTZ,
|
|
duration_ms INT,
|
|
expires_at TIMESTAMPTZ NOT NULL DEFAULT NOW() + INTERVAL '7 days',
|
|
|
|
error_message TEXT,
|
|
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
|
|
);
|
|
|
|
CREATE UNIQUE INDEX uq_snapshots_cache ON snapshots(saved_project_id, scene_key, frame_number, inputs_hash)
|
|
WHERE status = 'Done';
|
|
CREATE INDEX idx_snapshots_user ON snapshots(user_id, requested_at DESC);
|
|
CREATE INDEX idx_snapshots_expire ON snapshots(expires_at) WHERE status = 'Done';
|
|
|
|
-- ---------------------------------------------------------------------
|
|
-- exports — final rendered output records
|
|
-- ---------------------------------------------------------------------
|
|
CREATE TYPE export_create_type AS ENUM ('Render','Upload','Snapshot','Reupload');
|
|
CREATE TYPE export_file_type AS ENUM ('Video','Image','Audio','GIF','PDF');
|
|
|
|
CREATE TABLE exports (
|
|
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
tenant_id UUID NOT NULL,
|
|
user_id UUID NOT NULL,
|
|
|
|
saved_project_id UUID NOT NULL,
|
|
project_id UUID NOT NULL,
|
|
render_job_id UUID REFERENCES render_jobs(id) ON DELETE SET NULL,
|
|
|
|
-- Output
|
|
image TEXT, -- thumbnail
|
|
path TEXT NOT NULL, -- main file URL
|
|
file_extension TEXT NOT NULL DEFAULT 'mp4',
|
|
file_type export_file_type NOT NULL DEFAULT 'Video',
|
|
|
|
render_quality render_quality NOT NULL,
|
|
create_type export_create_type NOT NULL DEFAULT 'Render',
|
|
size_bytes BIGINT NOT NULL,
|
|
duration_sec NUMERIC(8,2),
|
|
width INT,
|
|
height INT,
|
|
|
|
-- Lifecycle
|
|
produce_date TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
|
auto_delete_date TIMESTAMPTZ NOT NULL DEFAULT NOW() + INTERVAL '30 days',
|
|
delete_notified BOOLEAN NOT NULL DEFAULT FALSE,
|
|
|
|
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
|
deleted_at TIMESTAMPTZ
|
|
);
|
|
|
|
CREATE INDEX idx_exports_user ON exports(user_id, produce_date DESC) WHERE deleted_at IS NULL;
|
|
CREATE INDEX idx_exports_tenant ON exports(tenant_id) WHERE deleted_at IS NULL;
|
|
CREATE INDEX idx_exports_saved_project ON exports(saved_project_id);
|
|
CREATE INDEX idx_exports_auto_delete ON exports(auto_delete_date) WHERE deleted_at IS NULL;
|
|
|
|
-- Now wire FK back to render_jobs
|
|
ALTER TABLE render_jobs
|
|
ADD CONSTRAINT fk_render_jobs_export
|
|
FOREIGN KEY (export_id) REFERENCES exports(id) ON DELETE SET NULL;
|
|
|
|
-- ---------------------------------------------------------------------
|
|
-- export_files — mockup mode produces multiple images per export
|
|
-- ---------------------------------------------------------------------
|
|
CREATE TABLE export_files (
|
|
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
export_id UUID NOT NULL REFERENCES exports(id) ON DELETE CASCADE,
|
|
user_id UUID NOT NULL,
|
|
|
|
name TEXT,
|
|
thumbnail TEXT,
|
|
path TEXT NOT NULL,
|
|
size_bytes BIGINT NOT NULL,
|
|
file_type export_file_type NOT NULL DEFAULT 'Image',
|
|
width INT,
|
|
height INT,
|
|
sort INT NOT NULL DEFAULT 0,
|
|
|
|
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
|
|
);
|
|
|
|
CREATE INDEX idx_export_files_export ON export_files(export_id, sort);
|
|
|
|
-- ---------------------------------------------------------------------
|
|
-- render_progress_events — WebSocket fan-out source (short-lived)
|
|
-- ---------------------------------------------------------------------
|
|
CREATE TABLE render_progress_events (
|
|
id BIGSERIAL PRIMARY KEY,
|
|
render_job_id UUID NOT NULL REFERENCES render_jobs(id) ON DELETE CASCADE,
|
|
step render_step NOT NULL,
|
|
progress INT NOT NULL,
|
|
current_frame INT,
|
|
total_frames INT,
|
|
eta_seconds INT,
|
|
preview_b64 TEXT,
|
|
message TEXT,
|
|
emitted_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
|
|
);
|
|
|
|
CREATE INDEX idx_progress_events_job ON render_progress_events(render_job_id, emitted_at DESC);
|
|
-- Cleanup: keep last N per job via cron, drop > 7 days
|