Skip to content

Media Module

Location: apps/api/src/modules/media/

Purpose: Upload pipeline and asset addressing for user images/video/audio. Stores files in Cloudflare R2 (zero-egress); the API only persists the public URL + metadata, never the bytes.


Upload flow (presigned)

  1. POST /media/presign — client asks for an upload target for a planned asset (kind, content-type, size). Returns a short-lived signed PUT URL + assetId + the eventual public URL. Size/type are validated server-side.
  2. Client PUTs the bytes directly to R2 (or the dev sink, below).
  3. POST /media/:assetId/commit — owner confirms the upload; the asset row is finalized and becomes referenceable by posts/avatars/etc.
MethodPathPurpose
POST/media/presignIssue a signed upload URL + assetId
POST/media/:assetId/commitFinalize an uploaded asset (owner-only)
POST/media/avatar/generateGenerate a DiceBear avatar (no upload)
PUT/media/local/:assetIdDev sink — accept bytes locally
GET/media/local/:assetIdDev sink — serve a local asset

Avatar generation

POST /media/avatar/generate renders a deterministic DiceBear avatar (lorelei, notionists, personas, micah, avataaars) and stores it like any other asset. Used by the profile-picture picker's "Generate avatar" option.

Dev media sink

When there is no R2 token in development, PUT/GET /media/local/:assetId accept and serve bytes from a local directory so the upload flow works end-to-end offline. Hardened:

  • 404 in production — the route is gated to NODE_ENV !== 'production'.
  • Token-bound — the upload token's assetId must match the path param.
  • Path-safeassetId must be a UUID and is basename()-ed before joining DEV_DIR, so a forged token still can't escape the directory.

Mobile consumption — expo-image

The app renders all remote images through expo-image (not RN Image) with disk + memory caching, so avatars and media don't re-fetch or flicker on scroll. This is a native dependency — it bumped the EAS runtimeVersion to 1.4.0 (OTAs must only reach 1.4.0 binaries).

Invariants

  • The API never proxies media bytes — only signed URLs and metadata.
  • R2 media bucket is public-read; DB backups live in a separate private bucket (see Infrastructure).

See also: /modules/posts, /modules/videos, /deploy/environment

Regulus — invite-only social-knowledge platform