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).
This commit is contained in:
parent
abb343284a
commit
71db0a08b2
15 changed files with 1276 additions and 9 deletions
43
apps/api/migrations/039_producer.sql
Normal file
43
apps/api/migrations/039_producer.sql
Normal file
|
|
@ -0,0 +1,43 @@
|
|||
-- 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;
|
||||
Loading…
Add table
Add a link
Reference in a new issue