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.
56 lines
2.6 KiB
PL/PgSQL
56 lines
2.6 KiB
PL/PgSQL
-- 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;
|