feat: editor-v2 S1 — EDL schema, strict validation, /video/edl API, edl_projects store
apps/api/src/media-control/edl.ts: the EdlProject schema (closed enums, integer ms, 3dB steps, <=50 items, <=15 min, filtergraph-metacharacter blocklist on card text) + validateEdlProject — the mirror-assert owner for the worker's edl-plan port. 043_edl_projects: RLS tenant table. EdlController: POST /video/edl, PATCH autosave, GET (project + latest assemble job), POST :id/render (queues kind 'assemble' with the validated project). Tests 130 passing across media-control.
This commit is contained in:
parent
10dfda3b7c
commit
d8de9f5d82
6 changed files with 643 additions and 1 deletions
56
apps/api/migrations/043_edl_projects.sql
Normal file
56
apps/api/migrations/043_edl_projects.sql
Normal file
|
|
@ -0,0 +1,56 @@
|
|||
-- 043: EDL projects — the intake half of the editor-v2 assemble feature.
|
||||
--
|
||||
-- edl_projects stores a validated editor timeline (the EdlProject shape from
|
||||
-- apps/api/src/media-control/edl.ts) as jsonb. The api validates strictly
|
||||
-- (closed enums, integer ms, unknown keys rejected, filtergraph metacharacters
|
||||
-- blocked in card text) before anything is written; the media-worker
|
||||
-- re-validates the job's input_json on the assemble lane (044).
|
||||
--
|
||||
-- video_processing_jobs gains kind 'assemble' (044's claim already admits it)
|
||||
-- and video_asset_id becomes nullable: an assemble job is queued from an EDL
|
||||
-- project, not a single source asset, so there is no asset to point at yet —
|
||||
-- the produced output is a derived video_assets row like compose/mix.
|
||||
--
|
||||
-- RLS / NULLIF / reject_workspace_change / sp_app grants follow the
|
||||
-- 001/008/021/026/029 conventions: no app.workspace_id => NULL => no rows;
|
||||
-- FORCE binds sp_app too. The jsonb column is content, not a schema facet:
|
||||
-- the authoritative typed validation is app-side (edl.ts).
|
||||
|
||||
BEGIN;
|
||||
|
||||
CREATE TABLE edl_projects (
|
||||
id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
workspace_id uuid NOT NULL REFERENCES workspaces(id) ON DELETE CASCADE ON UPDATE RESTRICT,
|
||||
group_id uuid NULL,
|
||||
project jsonb NOT NULL,
|
||||
created_by uuid NULL,
|
||||
created_at timestamptz NOT NULL DEFAULT now(),
|
||||
updated_at timestamptz NOT NULL DEFAULT now(),
|
||||
UNIQUE (workspace_id, id)
|
||||
);
|
||||
|
||||
-- Tenant-pinned reference target (029 style): the composite UNIQUE lets child
|
||||
-- rows pin (id, workspace_id) so an EDL project can never point across tenants.
|
||||
CREATE INDEX edl_projects_ws_updated_idx ON edl_projects (workspace_id, updated_at DESC);
|
||||
|
||||
CREATE TRIGGER trg_edl_projects_ws_immutable BEFORE UPDATE ON edl_projects
|
||||
FOR EACH ROW EXECUTE FUNCTION reject_workspace_change();
|
||||
|
||||
ALTER TABLE edl_projects ENABLE ROW LEVEL SECURITY;
|
||||
ALTER TABLE edl_projects FORCE ROW LEVEL SECURITY;
|
||||
|
||||
CREATE POLICY tenant_isolation ON edl_projects
|
||||
USING (workspace_id = NULLIF(current_setting('app.workspace_id', true), '')::uuid)
|
||||
WITH CHECK (workspace_id = NULLIF(current_setting('app.workspace_id', true), '')::uuid);
|
||||
|
||||
GRANT SELECT, INSERT, UPDATE, DELETE ON edl_projects TO sp_app;
|
||||
|
||||
-- The assemble lane: widen the 039 kind CHECK so an undecodable row cannot
|
||||
-- exist, and let an assemble job carry no source asset yet.
|
||||
ALTER TABLE video_processing_jobs DROP CONSTRAINT video_processing_jobs_kind_check;
|
||||
ALTER TABLE video_processing_jobs
|
||||
ADD CONSTRAINT video_processing_jobs_kind_check
|
||||
CHECK (kind IN ('probe', 'compose', 'mix', 'assemble'));
|
||||
ALTER TABLE video_processing_jobs ALTER COLUMN video_asset_id DROP NOT NULL;
|
||||
|
||||
COMMIT;
|
||||
Loading…
Add table
Add a link
Reference in a new issue