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.
| Method | Path | Purpose |
|---|---|---|
POST | /v1/tenants/:tenant/media/uploads | Mint a presigned upload credential + a pending asset row. |
POST | /v1/tenants/:tenant/media/:id/finalize | Confirm the object landed and flip the asset to ready. |
GET | /v1/tenants/:tenant/media | List a tenant’s assets (operator parity with the merchant list). |
GET | /v1/tenants/:tenant/media/:id | Fetch one asset. |
GET | /v1/tenants/media/audit | Operator 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.
| Method | Path | Scope | Purpose |
|---|---|---|---|
GET | /v1/merchant/media/assets | manage:own_media | List the caller’s assets. |
GET | /v1/merchant/media/assets/:id | manage:own_media | Fetch one asset. |
POST | /v1/merchant/media/uploads | manage:own_media | Mint a presigned upload credential. |
POST | /v1/merchant/media/assets/:id/finalize | manage:own_media | Finalize an uploaded asset. |
Events
| Direction | Event |
|---|---|
| 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
FakeMediaProviderfallback 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.