- Updated README.md to reflect changes in load testing commands and added usage examples for Bash scripts. - Converted PowerShell load testing scripts to Bash equivalents for better cross-platform compatibility. - Added new Bash scripts: run-loadtests.sh, summarize-artillery.sh, and summarize-k6.sh to mirror existing PowerShell functionality. - Updated admin-api.js and ws-chat.yml for consistency and improved performance metrics reporting. - Enhanced error handling and output formatting in summary scripts for better readability.
160 lines
8.8 KiB
Markdown
160 lines
8.8 KiB
Markdown
# Linear Void API Reference
|
|
|
|
Base URLs: Public/admin app `https://linearvoid.tv` · WebSocket `wss://linearvoid.tv/ws` · Local fallback `http://localhost` / `ws://localhost/ws`
|
|
|
|
## Authentication
|
|
|
|
Admin REST endpoints use a cookie session:
|
|
|
|
- Cookie: `linearvoid_admin_session` — `HttpOnly`, `SameSite=Strict`, `Secure` when request is HTTPS (or `X-Forwarded-Proto=https`)
|
|
- Session TTL: `ADMIN_SESSION_TTL_HOURS` (default 12h)
|
|
|
|
## Chat WebSocket API
|
|
|
|
Connect: `GET /ws?username={name}&room={room}` — client/server exchange JSON messages.
|
|
|
|
| Message (client→server) | Field | Notes |
|
|
| ----------------------- | -------------------------------------------------------------------------------- | ----- |
|
|
| `type` | `public` for room chat, `private` for DM routing; `system` for moderation/events | |
|
|
| `room` | `main`; private DMs use `dm:userA-userB` (usernames sorted) | |
|
|
| `username` | sender | |
|
|
| `text` | message body | |
|
|
|
|
Server→client envelope adds `"timestamp"` (ISO-8601, e.g. `2026-08-04T12:34:56Z`).
|
|
|
|
```json
|
|
{ "room": "main", "username": "viewer123", "text": "hello world", "type": "public" }
|
|
```
|
|
|
|
## Admin Session API
|
|
|
|
| Method & Path | Auth | Description |
|
|
| ------------------------ | ----------------------------------- | ----------------------------- |
|
|
| `POST /api/admin/login` | none | Authenticate |
|
|
| `POST /api/admin/logout` | optional (clears cookie either way) | Logout |
|
|
| `GET /api/admin/session` | cookie required | Session check; 401 if invalid |
|
|
|
|
Login request:
|
|
|
|
```json
|
|
{ "username": "admin", "password": "your-password" }
|
|
```
|
|
|
|
Login/logout/session success response (session returns 401 on failure):
|
|
|
|
```json
|
|
{ "authenticated": true, "username": "admin" }
|
|
```
|
|
|
|
Logout response: `{ "authenticated": false }`
|
|
|
|
## Admin Data API (auth required)
|
|
|
|
| Method & Path | Returns |
|
|
| ----------------------------------------- | ---------------------------------------------------------------------------------------------------- |
|
|
| `GET /api/admin/overview` | Aggregate counters + runtime/system snapshot |
|
|
| `GET /api/admin/playlist` | Playlist |
|
|
| `PUT /api/admin/playlist` | Set playlist (body: `{ "content": "file '/app/media/video1.mp4'\nfile '/app/media/video2.mp4'\n" }`) |
|
|
| `GET /api/admin/schedule` | Parsed playlist entries + matched media metadata/runtime |
|
|
| `GET /api/admin/moderation/logs?limit=50` | Moderation logs |
|
|
| `POST /api/admin/moderation/actions` | Perform moderation action |
|
|
| `GET /api/admin/analytics` | Totals (messages, moderation actions, rooms) + top room summary |
|
|
| `GET /api/admin/system` | Health snapshot (chat/playout/redis/database) + config metadata |
|
|
| `GET /api/admin/users` | Presence users (room + username) when Redis available |
|
|
|
|
Moderation action request:
|
|
|
|
```json
|
|
{ "action": "mute", "target": "username-or-id", "moderator": "admin", "reason": "spam", "room": "main" }
|
|
```
|
|
|
|
## Media Library API
|
|
|
|
| Method & Path | Description |
|
|
| ------------------------------ | ------------------------------------------------- |
|
|
| `GET /api/admin/media` | List media |
|
|
| `POST /api/admin/media` | Multipart upload (legacy), form field name `file` |
|
|
| `GET /api/admin/media/{id}` | Get item |
|
|
| `PUT /api/admin/media/{id}` | Update item |
|
|
| `DELETE /api/admin/media/{id}` | Delete item |
|
|
|
|
PUT media body:
|
|
|
|
```json
|
|
{ "status": "ready", "title": "My Clip", "tags": "music,live" }
|
|
```
|
|
|
|
## Resumable Upload API
|
|
|
|
Max file size: **2GB** · Default chunk size: **8MB**
|
|
|
|
| Method & Path | Description |
|
|
| ------------------------------------------------------------------ | ----------------------------------------------------------------- |
|
|
| `POST /api/admin/media/uploads` | Initialize or resume session |
|
|
| `GET /api/admin/media/uploads/{uploadId}` | Check upload status |
|
|
| `PUT /api/admin/media/uploads/{uploadId}/chunk?index={chunkIndex}` | Upload chunk (`Content-Type: application/octet-stream`) |
|
|
| `POST /api/admin/media/uploads/{uploadId}/complete` | Complete upload → returns media listing (`files` + `generatedAt`) |
|
|
|
|
Initialize request:
|
|
|
|
```json
|
|
{
|
|
"uploadId": "optional-existing-id",
|
|
"fileName": "clip.mp4",
|
|
"fileSize": 123456789,
|
|
"mimeType": "video/mp4",
|
|
"chunkSize": 8388608,
|
|
"totalChunks": 15
|
|
}
|
|
```
|
|
|
|
Status envelope (returned by init, status check, and chunk upload):
|
|
|
|
```json
|
|
{
|
|
"uploadId": "hex-token",
|
|
"fileName": "clip.mp4",
|
|
"fileSize": 123456789,
|
|
"chunkSize": 8388608,
|
|
"totalChunks": 15,
|
|
"uploadedChunks": 3,
|
|
"progress": 20
|
|
}
|
|
```
|
|
|
|
## Metrics & Health
|
|
|
|
- `GET /metrics` (chat) — Prometheus: connected clients, stored messages, moderation actions, Redis indicators, active admin sessions, build info
|
|
- `GET /health` — chat service, playout service, and nginx reverse proxy each expose it
|
|
|
|
## HTTP Status Conventions
|
|
|
|
| Code | Meaning | When |
|
|
| ---- | --------------------- | ----------------------------------------------------------- |
|
|
| 200 | OK | Successful GET, PUT, action |
|
|
| 201 | Created | Successful POST (upload init, override created) |
|
|
| 204 | No Content | Successful DELETE, CORS preflight |
|
|
| 400 | Bad Request | Malformed JSON, missing required fields, invalid input |
|
|
| 401 | Unauthorized | Missing/expired admin session |
|
|
| 403 | Forbidden | Valid session, insufficient permissions |
|
|
| 404 | Not Found | Resource doesn't exist |
|
|
| 405 | Method Not Allowed | Wrong method for endpoint |
|
|
| 409 | Conflict | Schedule override overlap, upload ID mismatch |
|
|
| 413 | Payload Too Large | Upload exceeds 2 GB limit |
|
|
| 422 | Unprocessable | File fails normalization check (codec, fps, resolution) |
|
|
| 429 | Too Many Requests | Rate limit exceeded (chat, WS, login) |
|
|
| 500 | Internal Server Error | Unexpected server failure |
|
|
| 503 | Service Unavailable | Redis unavailable for user actions, playout health degraded |
|
|
|
|
## Rate Limiting
|
|
|
|
Per-IP (from `X-Forwarded-For` or `RemoteAddr`) unless noted.
|
|
|
|
| Resource | Limit | Window | Response |
|
|
| --------------------- | ---------------------------------- | ---------------------------------------- | ------------------------------------------------------ |
|
|
| Chat messages | `CHAT_RATE_LIMIT_MAX` (default 10) | `CHAT_RATE_LIMIT_WINDOW_S` (default 10s) | 429 + `{"type":"system","text":"Rate limit exceeded"}` |
|
|
| WebSocket connections | 5 | 60s | 429 + `"too many connections from this IP"` |
|
|
| Admin login | 5 | 5min | 429 + `"too many login attempts, try again later"` |
|
|
|
|
Rate limit headers (applicable responses): `X-RateLimit-Limit` (max requests/window), `Retry-After` (seconds until reset). 413 = payload too large; 500 = internal server error.
|