--- id: ADR-0002 title: Media Manager backend contract and mock storage adapter status: active date: 2026-07-15 tags: ["architecture", "media", "backend-gap", "repository-pattern"] --- ## Context Sprint 4 (Media Manager) needs a media library: upload, browse, delete, and pick images/files for use across Product Editor, Static Pages (CMS), and Branding. No media backend exists yet — `/media` currently routes to a "coming soon" placeholder (`BackofficeComingSoonPageComponent`), and `docs/BACKEND.md` does not document any upload/storage endpoint. This mirrors the already-documented draft-publish-flow gap in the Project Editor (see `PE-20260713T010000Z-0003`): build the real contract, then implement a client-side mock adapter behind the same interface so the UI never needs to change when the backend ships. ## Decision - **Domain model** `MediaAsset`: `{ id, url, thumbnailUrl?, filename, mimeType, size, width?, height?, altText?: Record, tags?: string[], createdAt }`. `altText` follows the platform's `translations.{lang}` rule (ADR-0001) — never a flat string. - **Repository contract** (future backend, to be implemented server-side): - `GET /media?page=&pageSize=&search=` → paginated `MediaAsset[]` - `POST /media/upload` (multipart) → `MediaAsset` - `DELETE /media/:id` → 204 - `PATCH /media/:id` (altText/tags only) → `MediaAsset` - **Frontend abstraction**: a `MediaRepository` interface (Repository pattern, per `docs/context/features/*` conventions) with two implementations selected via DI token: - `MockMediaRepository` — stores assets in IndexedDB (not localStorage: binary blobs need it) as an interim store until the backend exists. Data URLs are generated for rendering; the shape returned matches `MediaAsset` exactly. - `HttpMediaRepository` — thin wrapper over the endpoints above, added when the backend ships. Swapping providers is the only change required. - **Media never enters the Bootstrap model.** Like products/orders/users, media assets are runtime admin data, not tenant configuration — consistent with ADR-0001's rule that Bootstrap contains only what's needed before the app starts. - **Media Picker** is a standalone, reusable dialog (built on the existing `app-dialog` Design System primitive) so Product Editor and CMS editors consume the same selection UI instead of each building their own. ## Consequences - Any feature needing to reference an image (product gallery, static page hero, branding logo) does so via `MediaAsset.url`/`id`, obtained through the shared Media Picker — never a raw file input duplicated per feature. - When the backend ships, only `MediaRepository`'s DI provider changes; no component or facade code should need to change.