Media

The media module (schema media) is the write side of the media pipeline: image upload -> object store -> CDN. It mints a presigned upload credential, records a pending, tenant-scoped, audited asset row, and — on finalize — confirms the object landed and flips it to ready. The read side (surfaces/storefront/src/lib/media.ts) already resolves the assetRef this module mints to a CDN URL with no round-trip; this module is what finally produces those refs.

The object-store call is behind the vendor-neutral MediaProvider port (provider.ts), injected via deps.provider and otherwise built once by select-provider.ts — S3 when the bucket/CDN/region config is present, the loudly-logged FakeMediaProvider when it is not (never in production). No byte flows through this service: the client uploads straight to the presigned target.

Operator routes

x-operator-token (mirrors payments/commerce’s operator surface), rate-limited.

MethodPathPurpose
POST/v1/tenants/:tenant/media/uploadsMint a presigned upload credential + a pending asset row.
POST/v1/tenants/:tenant/media/:id/finalizeConfirm the object landed and flip the asset to ready.
GET/v1/tenants/:tenant/mediaList a tenant’s assets (operator parity with the merchant list).
GET/v1/tenants/:tenant/media/:idFetch one asset.
GET/v1/tenants/media/auditOperator audit-log read (makeAuditReadRoute).

Merchant routes

fdk_ credential, tenant from the credential per credential-carries-ownership, scope manage:own_media. Mounted at /v1/merchant/media — the portal’s media library/upload. Same repo methods as the operator routes; only the tenant source and audit actor differ.

MethodPathScopePurpose
GET/v1/merchant/media/assetsmanage:own_mediaList the caller’s assets.
GET/v1/merchant/media/assets/:idmanage:own_mediaFetch one asset.
POST/v1/merchant/media/uploadsmanage:own_mediaMint a presigned upload credential.
POST/v1/merchant/media/assets/:id/finalizemanage:own_mediaFinalize an uploaded asset.

Events

DirectionEvent
Publishes(none — see below)
Consumes(none)

No events yet, by design (module.yaml): the read side resolves an assetRef -> CDN URL mechanically with no service round-trip, so a media.asset.finalized.v1 event would have no in-repo consumer today. When a real reactor exists (e.g. a search-reindex on new product imagery), the schema + a consumes: entry get added then — not dead scaffolding now.

Manifest gates

None. Media refs surface through the manifest’s brand.assets (logoRef / faviconRef / ogImageRef) and product imagery, but there is no per-store media on/off toggle.

Seams (honest notes)

  • No tenant-self-serve (unauthenticated) surface — every route is operator- or fdk_-gated today; a shopper-facing seam can be wired in later exactly as the other modules will.
  • The FakeMediaProvider fallback is loudly logged and refused in production — it exists so tests and un-configured dev stages don’t need real S3, never as a silent prod stub.