Files

198 lines
9.2 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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_PUBLIC_ORIGIN` | Optional browser-facing MinIO origin used for presigned URLs and CSP (for example `https://storage.example.com`) |
| `MINIO_PUBLIC_ENDPOINT` / `MINIO_PUBLIC_PORT` / `MINIO_PUBLIC_USE_SSL` | Optional browser-facing MinIO host settings if you prefer host/port flags instead of `MINIO_PUBLIC_ORIGIN` |
| `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 the backend reaches MinIO on an internal host (for example `minio:9000`) but browsers must upload/download through a different public host, set `MINIO_PUBLIC_ORIGIN` so presigned URLs target the browser-reachable origin instead of the internal one.
- 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 |
### 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.