2309e9f43a
Five-service streaming platform: auth, catalogue, streaming, ingest, thumbnailer. Includes React frontend served by nginx, NATS JetStream event bus, aiobotocore async S3, PyAV video metadata + thumbnail extraction, service-to-service JWT auth, and a full unit + e2e test suite.
113 lines
4.1 KiB
Markdown
113 lines
4.1 KiB
Markdown
# StreamStack Architecture
|
|
|
|
## Services
|
|
|
|
| Service | Replicas | Backing stores | Responsibility |
|
|
|---------|----------|----------------|----------------|
|
|
| **auth** | 2 | Postgres, NATS KV | User accounts, JWT issue/refresh/revoke |
|
|
| **catalogue** | 2 | Postgres, NATS pub | Media metadata CRUD, stream token requests |
|
|
| **streaming** | 2 | NATS KV, S3 | Token issuance, byte-range video delivery |
|
|
| **ingest** | 2 | S3, (catalogue HTTP) | Upload video, extract metadata/thumbnail, register in catalogue |
|
|
| **nginx** | 1 | — | Reverse proxy + React SPA |
|
|
|
|
## Infrastructure
|
|
|
|
| Component | Purpose |
|
|
|-----------|---------|
|
|
| **Postgres** | Persistent store for user accounts (auth) and media metadata (catalogue) |
|
|
| **NATS JetStream KV** | Short-lived stream tokens (1h TTL); revoked-token list for JWT blacklisting |
|
|
| **S3 / MinIO** | Binary storage — `media/` bucket for video files, `thumbnails/` bucket for JPEG thumbnails |
|
|
|
|
---
|
|
|
|
## Request flows
|
|
|
|
### Login
|
|
```
|
|
Browser → nginx → auth
|
|
auth reads Postgres (verify credentials)
|
|
auth writes nothing to NATS
|
|
auth returns access_token (JWT, RS256, 30min) + refresh_token (7 days)
|
|
```
|
|
|
|
### Browse catalogue
|
|
```
|
|
Browser → nginx → catalogue
|
|
catalogue reads Postgres (published media items)
|
|
returns list of metadata (title, duration, thumbnail_s3_key, etc.)
|
|
no NATS, no S3
|
|
```
|
|
|
|
### Request a stream token
|
|
```
|
|
Browser → nginx → catalogue POST /catalogue/{id}/stream-token
|
|
catalogue reads Postgres → gets s3_key + size_bytes for the item
|
|
catalogue → streaming POST /stream/token {media_id, s3_key, size_bytes}
|
|
streaming verifies JWT (public key, local)
|
|
streaming writes NATS KV: token → "media_id|user_id|timestamp|s3_key|size_bytes"
|
|
streaming returns {stream_url: "/api/v1/stream/<token>"}
|
|
catalogue returns stream_url to browser
|
|
```
|
|
Token TTL: 1 hour. After that, NATS discards it automatically.
|
|
|
|
### Play video (each range request)
|
|
```
|
|
Browser → nginx → streaming GET /stream/<token> Range: bytes=X-Y
|
|
streaming reads NATS KV (resolve token → s3_key + size_bytes)
|
|
streaming → S3 GET object with byte range (aiobotocore, fully async)
|
|
streams bytes back to browser
|
|
no Postgres, no catalogue HTTP call
|
|
```
|
|
The browser sends many range requests for a single video. Each one costs only a NATS lookup + S3 range-get.
|
|
|
|
### Ingest a video (admin only)
|
|
```
|
|
curl/frontend → nginx → ingest POST /ingest/upload (multipart)
|
|
ingest verifies JWT (admin role required)
|
|
ingest → S3 upload file → media/{uuid}.ext
|
|
ingest → S3 head_object → size_bytes
|
|
ingest runs PyAV (in threadpool):
|
|
- reads S3 via range-gets → extracts duration, codec, width, height, fps
|
|
- decodes first video frame → JPEG → S3 thumbnails/{uuid}.jpg
|
|
ingest → catalogue POST /catalogue/ {s3_key, size_bytes, metadata...}
|
|
catalogue writes Postgres
|
|
catalogue publishes NATS: catalogue.events.media.published
|
|
returns catalogue item JSON
|
|
```
|
|
|
|
---
|
|
|
|
## JWT flow
|
|
|
|
Auth uses **RS256** (asymmetric). The private key signs tokens; all other services hold only the public key and verify locally — no auth HTTP call on every request.
|
|
|
|
Revoked tokens are stored as keys in a NATS KV bucket (`revoked-tokens`). Streaming checks this bucket on token issue, not on every range request.
|
|
|
|
---
|
|
|
|
## Data ownership
|
|
|
|
```
|
|
Postgres auth users, hashed passwords, roles
|
|
catalogue media items, all metadata fields
|
|
|
|
NATS KV streaming stream tokens (s3_key + size_bytes embedded)
|
|
auth revoked JWT list
|
|
|
|
S3 ingest video files → media/
|
|
ingest thumbnails → thumbnails/
|
|
(read) streaming reads media/ for range delivery
|
|
(read) ingest/PyAV reads media/ for metadata extraction
|
|
```
|
|
|
|
---
|
|
|
|
## Inter-service HTTP calls
|
|
|
|
| Caller | Callee | When |
|
|
|--------|--------|------|
|
|
| catalogue | streaming | Stream token request — passes s3_key + size_bytes |
|
|
| ingest | catalogue | After upload — registers the media item |
|
|
|
|
All other cross-service communication is either direct DB access (own service only) or NATS pub/sub. Services do **not** query each other's databases.
|