@seifer-webapp-factory/media
v0.1.1
Published
media — vierde capability-module: verticale, pluggable full-stack media-feature (owner-scoped uploads met library, presigned/proxied upload, korte-levensduur download, verwijderen) die de kits samenbindt via één contract. Componeert de nieuwe storage-kit
Readme
media — capability module
The media capability module — a vertical, full-stack self-service feature: owner-scoped file uploads
with a library, two-phase (presigned/proxied) upload, short-lived downloads, and delete. It pairs a
frontend slice with a backend slice through one shared contract, composed from the foundation kits. It is
the first module to compose the new storage kit (EPIC-15) and the second to prove a module→module
dependency (media requires authentication, like account).
Design doc:
../media.md· Tier model + conventions:../README.md.
What it is
| Part | Contents |
|---|---|
| contract/ | the seam — endpoint catalogue, DTOs (zod), error taxonomy, events, config. Both halves derive types from it. |
| backend/src | mechanism (pinned dep): framework-free flow services over the storage façade (requestUpload/uploadContent/confirmUpload/listMedia/getMedia/streamMedia/deleteMedia). |
| backend/templates | surface (materialized): NestJS controller/module, media_assets migration, pg-MediaStore, privacy DataProvider, config fragment, security helpers. |
| frontend/src | mechanism: the typed client + Vue composables (useUpload / useMediaLibrary). |
| frontend/templates | surface: media-library.vue (grid + uploader), i18n, runtime. |
| manifest.ts | facades @seifer-webapp-factory/capability-spec; backendRoutes derived from the contract. |
| scaffolder/ | version-aware materializer + the requires.modules presence-check (refuses without authentication). |
Composition
- Backend kits:
storage(object-store mechanism),persistence,http-kernel,access-control,audit-log,privacy,rate-limit,config,i18n. - Frontend kits:
http-client,forms,data,notifications,auth,i18n,app-kernel. - Requires module:
authentication(owner subject via aSubjectProviderport). - Requires-ports (host provides):
ObjectStore(astorage-kit adapter),SubjectProvider,MediaStore,Database,DesignSystem; optionalAntivirusScanner.
Install & wire (backend)
import { MediaModule } from '@seifer-webapp-factory/media/backend'; // materialized surface in your app
import { s3ObjectStore, awsS3Operations } from '@seifer-webapp-factory/kits/backend/storage';
MediaModule.forRoot({
pool,
objectStore: s3ObjectStore(await awsS3Operations({ bucket, region, endpoint, credentials })),
subjectProvider: { verify: (token) => authTokenService.verify(token) }, // from authentication
config: { policy: { allowedContentTypes: ['image/*', 'application/pdf'], maxSize: 20 * 1024 * 1024 } },
});Run the media_assets migration via the persistence-kit migration runner. Register the module's
DataProvider (MEDIA_DATA_PROVIDER) in your privacy registry so subject-erasure removes blobs + rows.
The two-phase upload
POST /uploads → validate + create pending row + presigned URL (or proxy directive)
PUT browser → store → bytes land directly (presigned) or via /uploads/:id/content (proxied)
POST /uploads/:id/confirm → finalize (HEAD, size/type match) → ready → audited
GET /media, GET /media/:id (download URL), DELETE /media/:idDivergence ladder
- Configure — allowlist, max size, key-prefix, URL TTLs, antivirus toggle (config fragment).
- Extend — add a metadata column + form field; register a domain
DataProvider; add a content-type. - Materialize & edit — eject any
*/templatesfile (e.g.media-library.vue); the mechanism keeps upgrading via semver, template upgrades become a three-way merge. - Fork — replace the
MediaStoreor the two-phase flow services (last resort).
Verification (all green)
| Gate | Result |
|---|---|
| Contract + manifest + backend services + frontend client/composables + scaffolder + pages | 38 unit/component tests |
| Backend e2e (Postgres testcontainer + real local-fs storage + Nest surface) | 6 tests |
| Vertical Playwright e2e (browser → Vite → proxy → Nest → Postgres + storage) | 1 test |
| build · typecheck · typecheck:backend | pass |
Run: npm test (unit) · npm run test:e2e (backend, needs Docker) · npx playwright test (vertical,
needs Docker + browser).
Known debt
The scaffolder core (materialize/presence/ports/errors) is copy-pasted-with-renames per module
(materializeMediaModule ↔ materializeAccountModule …), as flagged in ../README.md.
A generic extraction is a separate breaking refactor. Image processing (thumbnails/variants) is
deliberately out of scope — media_assets.variant_of ships nullable-now for a future image-kit.
