# backend ## Overview Backend for the video upload prototype providing: - Better Auth email/password authentication - Presigned MinIO uploads/downloads - An authenticated video administration surface at `/admin` ## Requirements - [Bun](https://bun.sh) (tooling used for running scripts & dependency management) - Postgres reachable via `DATABASE_URL` - MinIO-compatible storage reachable via `MINIO_*` env vars - `.env` file populated with secrets and credentials ## Install ```bash bun install ``` ## Configuration Copy the example environment file and adjust the values: ```bash cp .env.example .env ``` Required env vars: | Name | Purpose | | --- | --- | | `DATABASE_URL` | Postgres connection string | | `BETTER_AUTH_SECRET` | Secret used to sign sessions | | `BETTER_AUTH_BASE_URL` | Public base URL for the backend (e.g., `http://localhost:3000`) | | `BETTER_AUTH_TRUSTED_ORIGINS` | Comma-separated list of allowed frontend origins | | `PORT` | HTTP port (default `3000`) | | `DEVICE_ONLINE_STALE_SECONDS` | Presence TTL in seconds before an `online` device is reported as `offline` (default `30`) | | `MEDIA_MODE` | Media runtime mode (`legacy` default, `single_server_sfu` scaffold mode) | | `MEDIA_PROVIDER` | Media backend provider (`mock` by default) | | `TURN_URLS` / `TURN_USERNAME` / `TURN_CREDENTIAL` | TURN/STUN configuration used by single-server SFU mode | | `MEDIA_RECORDINGS_DIR` | Local output directory for server-side recording workers (planned in SFU mode) | | `MEDIA_MAX_PUBLISHERS` / `MEDIA_MAX_SUBSCRIBERS_PER_ROOM` | Soft concurrency limits for single-server media mode (planned) | | `MINIO_*` | Connection settings for the MinIO/S3 endpoint | | `MINIO_CA_CERT_PATH` | Optional path to a PEM CA bundle used to trust a private/self-managed MinIO certificate | | `MINIO_TLS_REJECT_UNAUTHORIZED` | TLS verification toggle for MinIO HTTPS requests (`true` by default) | | `MINIO_INSECURE_SKIP_TLS_VERIFY` | Dev-only escape hatch to skip MinIO TLS certificate verification | | `ADMIN_USERNAME` / `ADMIN_PASSWORD` | Basic auth for `/admin` dashboard | `BETTER_AUTH_URL` is still accepted as a legacy fallback, but `BETTER_AUTH_BASE_URL` is preferred. ## Running - Start the server in development: ```bash bun run dev ``` - Server boots after ensuring the configured MinIO bucket exists. - If MinIO uses a private or incomplete certificate chain, prefer setting `MINIO_CA_CERT_PATH` to a trusted PEM bundle. Only use `MINIO_INSECURE_SKIP_TLS_VERIFY=true` for local development or temporary debugging. ## Database (Drizzle ORM) - Generate a migration: ```bash bun run db:generate ``` - Apply migrations: ```bash bun run db:migrate ``` - Backfill Better Auth credential accounts for existing users: ```bash bun run auth:migrate ``` - Open Drizzle Studio: ```bash bun run db:studio ``` ## API All `/videos` and `/admin` routes require a valid Better Auth session except for the admin dashboard access, which uses HTTP Basic auth with `ADMIN_USERNAME`/`ADMIN_PASSWORD`. ### Authentication Authentication is handled by Better Auth under `/api/auth/*` (for example `/api/auth/sign-in` and `/api/auth/sign-up`). ### Authorization All authenticated endpoints expect a Better Auth session cookie sent by the client. ### Video Management | Endpoint | Purpose | | --- | --- | | `POST /videos/upload-url` | Request a presigned PUT URL for a new video | | `GET /videos/download-url` | Generate a signed GET URL to download a video | | `GET /videos` | List objects in the configured bucket | | `DELETE /videos` | Delete an object by `objectKey` | `POST /videos/upload-url` request body requires `fileName` and `deviceId` (UUID belonging to the authenticated user), with optional `prefix`. ### Device Management (Phase 1) | Endpoint | Purpose | | --- | --- | | `POST /devices/register` | Register a user-owned device as `camera` or `client` and issue bearer device token | | `GET /devices` | List all devices for the authenticated user | | `PATCH /devices/:deviceId` | Update device role/metadata/status | | `POST /devices/:deviceId/heartbeat` | Device-token authenticated presence heartbeat | ### Camera-Client Linking (Phase 1) | Endpoint | Purpose | | --- | --- | | `POST /device-links` | Link one client device to one camera device | | `GET /device-links` | List links for the authenticated user | | `DELETE /device-links/:linkId` | Remove a camera-client link | ### Realtime Commands (Phase 2) | Endpoint | Purpose | | --- | --- | | `POST /commands` | Queue and dispatch command from a linked client device to camera | | `GET /commands` | Inspect command status/history | | `POST /commands/:commandId/ack` | Device-token ack/reject command fallback | Socket.IO channel: - Devices connect with bearer device token (`auth.token` or `Authorization: Bearer ...`). - Camera receives `command:received`. - Camera sends `command:ack` with `acknowledged` or `rejected`. - Source client receives `command:status`. ### Motion Events (Phase 3) | Endpoint | Purpose | | --- | --- | | `POST /events/motion/start` | Camera device creates motion event and fan-outs notification to linked clients | | `POST /events/:eventId/motion/end` | Camera device closes a motion event and broadcasts end state | | `GET /events` | Authenticated user fetches event history | Motion realtime events: - Linked clients receive `motion:detected` as soon as camera starts event. - Linked clients receive `motion:ended` when camera ends event. ### On-Demand Streams + WebRTC Control Plane | Endpoint | Purpose | | --- | --- | | `POST /streams/request` | Client device requests a linked camera to start a live stream | | `POST /streams/:streamSessionId/accept` | Camera device accepts and transitions stream session to `streaming` | | `POST /streams/:streamSessionId/end` | Requester/camera ends an existing stream session | | `GET /streams/me/list` | List stream sessions for the current device | Stream realtime events: - Camera receives `stream:requested` when `SIMPLE_STREAMING=true`. - Client receives `stream:started` when camera accepts. - Both devices receive `stream:ended` when session is closed. - Both participants exchange `webrtc:signal` payloads through Socket.IO for offer/answer/candidate/hangup relay. Legacy compatibility when `SIMPLE_STREAMING=false`: - `start_stream` device commands remain active for camera wake-up. - Media-provider credential endpoints (`publish-credentials`, `subscribe-credentials`, `playback-token`) remain available for older simulator/mobile flows. Experimental SFU scaffolding endpoints (`MEDIA_MODE=single_server_sfu`): - `GET /streams/:streamSessionId/sfu/session` – fetch in-memory SFU session state for participant devices - `POST /streams/:streamSessionId/sfu/publish-transport` – camera creates publish transport descriptor - `POST /streams/:streamSessionId/sfu/subscribe-transport` – participant creates subscribe transport descriptor #### Streaming Scale Tradeoffs (Current Prototype) - The current implementation is **not production-grade at scale**. - The preferred path is direct browser-to-browser WebRTC, with the backend acting as auth/session/signaling control plane. - Native mobile is not yet on the WebRTC path; `SIMPLE_STREAMING` defaults to `false` until a supported RN WebRTC stack is added. - This backend currently acts as a control plane (commands, session state, signaling, events), not a full media plane/SFU. - Running live transport + fan-out + recording on the same web server is possible for small loads but introduces significant CPU, RAM, and network egress pressure under concurrency. - For larger deployments, use a dedicated media plane (managed or self-hosted SFU + recorder) and keep this service focused on auth/session/control APIs. - For a pragmatic prototype path that keeps media on the current server, see `docs/streaming-on-web-server-plan.md`. - `MEDIA_MODE=single_server_sfu` currently enables scaffolding only (interfaces/config/health visibility), not full SFU media routing yet. ### API Docs OpenAPI docs are generated from Zod/OpenAPI definitions: | Endpoint | Purpose | | --- | --- | | `GET /openapi.json` | OpenAPI 3 spec (JSON) | | `GET /docs` | Swagger UI | ### Web Mobile Simulator Use `GET /sim/mobile-sim.html` to run the full single-page browser simulator that behaves like the mobile app. Split-page entrypoints are also available: - `GET /sim/mobile-sim-auth.html` - `GET /sim/mobile-sim-onboarding.html` - `GET /sim/mobile-sim-camera.html` - `GET /sim/mobile-sim-client.html` - `GET /sim/mobile-sim-activity.html` - `GET /sim/mobile-sim-settings.html` Architecture reference page: - `GET /sim/backend-architecture.html` All simulator pages support the same flow: - Register as `camera` or `client` - Connect Socket.IO with bearer device token - Camera: process incoming stream requests, negotiate WebRTC, start/end motion events - Client: create links, request streams, and negotiate WebRTC viewing ### Admin Dashboard Access `/admin` with Basic auth to: - Request presigned upload URLs - Upload files directly via the generated URL - List and delete objects within the MinIO bucket The dashboard UI submits to `/admin/upload-url`, `/admin/objects`, and `/admin/object`. ## Schema - `users` – email/username/password and timestamps - `events` – user-created events with a unique `videoUrl` - `videos` – upload metadata including `objectKey`, bucket, URLs, status, and timestamps ## Notes - MinIO bucket creation happens during startup, so the service must be able to reach the endpoint. - Keep Better Auth and MinIO secrets out of source control.