# 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.
