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
28
apps/api/migrations/038_media_assets.sql
Normal file
28
apps/api/migrations/038_media_assets.sql
Normal file
|
|
@ -0,0 +1,28 @@
|
|||
-- 038: media assets for publishing (media attachments backend).
|
||||
--
|
||||
-- Owner need: posts need images/video, not text alone. This is the asset
|
||||
-- spine the draft's mediaAssetIds point at — one row per uploaded file, the
|
||||
-- bytes themselves in object storage (envelope-encrypted, content-addressed)
|
||||
-- keyed by storage_key. Everything is per-workspace; the sha256 is the
|
||||
-- plaintext content hash the storage service computes, so a re-upload
|
||||
-- deduplicates the OBJECT but still gets its own row (different alt text,
|
||||
-- different post usage). created_by is SET NULL on user deletion: the asset
|
||||
-- outlives the account that uploaded it.
|
||||
|
||||
CREATE TABLE media_assets (
|
||||
id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
workspace_id uuid NOT NULL REFERENCES workspaces (id) ON DELETE CASCADE,
|
||||
storage_key text NOT NULL,
|
||||
mime text NOT NULL,
|
||||
size_bytes bigint NOT NULL CHECK (size_bytes > 0),
|
||||
sha256 text NOT NULL,
|
||||
alt_text text,
|
||||
created_by uuid REFERENCES users (id) ON DELETE SET NULL,
|
||||
created_at timestamptz NOT NULL DEFAULT now(),
|
||||
UNIQUE (id, workspace_id)
|
||||
);
|
||||
|
||||
CREATE POLICY tenant_isolation ON media_assets
|
||||
USING (workspace_id = NULLIF(current_setting('app.workspace_id', true), '')::uuid);
|
||||
|
||||
CREATE INDEX media_assets_workspace_created ON media_assets (workspace_id, created_at);
|
||||
Loading…
Add table
Add a link
Reference in a new issue