T-HUB/apps/api/migrations/039_producer.sql
glass beadian 71db0a08b2 feat: media attachments backend + producer job intake (t1+t3)
Rich media (t1): media_assets (migration 038) + /media module — multipart
upload with magic-byte sniffing, size caps, auth-scoped serve with
ETag/same-origin; the publishing 'text only' gate lifted to accept
0..4 validated mediaAssetIds with optional positional altTexts.

Producer intake (t3): video_processing_jobs gains kind compose|mix +
input_json + produced_asset_id (migration 039), video_assets.derived_from;
POST /video/:id/produce validates overlays/voiceover strictly and queues
the job for the media-worker runner (landing separately).
2026-10-14 20:00:31 +00:00

43 lines
2.3 KiB
PL/PgSQL

-- 039: producer job intake — a producer can ask for a NEW asset derived from an
-- existing one (burned-in lower thirds/logos/clock, an optional voiceover mix).
--
-- WHY COLUMNS AND NOT A NEW TABLE: video_processing_jobs (009) already models the
-- per-asset work queue (state machine, progress, output_key, RLS, tenant-immutability
-- triggers, workspace-scoped grants). Intake only needs to distinguish WHAT a queued
-- row asks the media-worker to do and carry the request payload; that is two columns
-- on the existing queue plus a nullable pointer to the row it produces. A second
-- table would duplicate the state machine and the isolation policy for no gain.
--
-- kind 'probe' — the 009 behaviour: ingest + ffprobe + no-op transcode.
-- 'compose' — burn overlays (and optionally a logo PNG) over the source.
-- 'mix' — compose AND replace/duck the audio bed with a voiceover.
-- Default 'probe' keeps every existing row and INSERT (009 code path)
-- meaning exactly what it meant before this migration.
-- input_json the validated producer request ({...request, requestedBy}), only
-- ever written by the API after strict allowlist parsing; the worker
-- re-validates. NULL for probe rows.
-- produced_asset_id the video_assets row the job writes when it finishes, so a
-- finished job points forward to its output.
-- video_assets.derived_from the SOURCE asset id of a produced row; NULL for ingested
-- uploads. Produced rows are ordinary video_assets rows, so they appear
-- in the library listing and inherit its tenant isolation unchanged.
--
-- The kind enum is CHECK-constrained so an undecodable row cannot exist.
BEGIN;
ALTER TABLE video_processing_jobs
ADD COLUMN kind text NOT NULL DEFAULT 'probe'
CHECK (kind IN ('probe', 'compose', 'mix')),
ADD COLUMN input_json jsonb,
ADD COLUMN produced_asset_id uuid;
ALTER TABLE video_assets
ADD COLUMN derived_from uuid REFERENCES video_assets(id);
-- Jobs that produce a derived asset are claimed by the media-worker by kind.
CREATE INDEX video_processing_jobs_kind_queued
ON video_processing_jobs (kind, created_at DESC)
WHERE state = 'queued';
COMMIT;