Files
chat-server/ARCHITECTURE.md
T

163 lines
5.4 KiB
Markdown

# Reliquary Server Architecture
## 1. Overview
Reliquary is a self-hosted chat server with:
- JWT auth
- ObjectBox persistence
- WebSocket gateway fanout
- Component-based messages
- Decoupled upload pipeline (upload first, message commit anytime)
## 2. Identity and Roles (Current)
- `userId` is an immutable opaque identifier (host-independent).
- `handle` is derived metadata: `@username@home`.
- `ChatUser.roles` controls authorization (`admin`, `member`, etc.).
- `GET /api/me` returns `roles` so the client can gate admin UI.
### Runtime Config Split
- `server.bind_host` + `server.bind_port`: where Relic listens locally.
- `server.public_base_url`: canonical external URL used for identity/handles/cross-server metadata.
- This avoids conflating local bind settings with public identity/routing.
### First Admin Bootstrap
- Config key: `server.first_setup` in `config.yaml`.
- When `first_setup: true`, the first registered local user gets `admin`.
- Later registrations remain `member`.
## 3. Storage Model
### 3.1 Entities
- `ChatUser`: identity/profile/auth data.
- `ChatChannel`: channel metadata.
- `ChatMessage`: channel message, search text, and `componentsJson`.
- `ChatFile`: canonical media object for:
- message attachments
- user avatar media
- server icon/banner media
- `ServerSettings`: singleton row for mutable server presentation settings.
### 3.2 ChatFile Fields (Important)
- `attachmentId` (unique external ID; API-facing)
- `originalFileName` (preserved filename metadata)
- `storedFileName` (physical disk key)
- `sha256`
- `mimeType`, `sizeBytes`, `width`, `height`
- `usageType`: `message | avatar | server_icon | server_banner`
- `status`: `pending | ready | failed`
## 4. Upload and Message Flow
### 4.1 Recommended Client Flow (Implemented Server Contract)
1. Client stages attachment in composer/component editor.
2. Client calls `POST /api/uploads/init` to create pending upload.
3. Client uploads bytes to `PUT /api/uploads/:uploadId`.
4. Client sends `POST /api/messages` with components referencing `uploadId`/`attachmentId`.
5. If upload completes after message create, server emits `message.updated`.
### 4.2 Why This Flow
- No chronological jumping for text.
- Immediate local optimistic UI.
- Progressive media readiness via gateway updates.
- Server keeps authoritative metadata and storage consistency.
## 5. API Surface
### 5.0 Identity Contract
- Registration returns:
- `userId` (opaque stable ID)
- `username`
- `handle` (computed, mutable as home host changes)
- Cross-server join request now expects:
- `userId`
- `username`
- `homeServer`
- `timestamp`
- `signature` over `userId:timestamp`
### 5.1 Uploads and Attachments
- `POST /api/uploads/init` (auth)
- `PUT /api/uploads/:uploadId` (auth)
- `POST /api/uploads` (auth, legacy single-step compatibility)
- `GET /attachments/:attachmentId` (public immutable media URL)
- `GET /api/channels/:channelId/media` (auth, gallery index feed)
### 5.2 Profile and Server Media
- `PATCH /api/me` (auth): update profile text fields.
- `PUT /api/me/avatar` (auth): bind uploaded media as avatar.
- `GET /avatars/:userId` (public alias for current avatar).
- `GET /icons/:serverId` (public alias for current server icon).
- `GET /banners/:serverId` (public alias for current server banner).
### 5.3 Server Settings
- `GET /api/server/settings` (auth)
- `PATCH /api/server/settings` (admin)
- `PUT /api/server/icon` (admin)
- `PUT /api/server/banner` (admin)
## 6. Gateway Behavior
- Existing events:
- `message.created`
- `message.updated`
- `message.deleted`
- For upload-driven updates:
- A message can be created with media items in `uploading`.
- When upload becomes ready, server rewrites affected components and emits `message.updated`.
## 7. Component Media Item Contract (Client)
Each media item in a component should carry stable IDs and upload state:
```json
{
"itemId": "local_or_stable_id",
"source": "local_upload|external_url",
"uploadId": "pending_or_ready_attachment_id",
"attachmentId": "same_as_uploadId_when_known",
"status": "uploading|ready|failed",
"url": "/attachments/<attachmentId>",
"originalFileName": "image.png",
"mimeType": "image/webp",
"sizeBytes": 12345,
"width": 1280,
"height": 720,
"thumbHash": "optional"
}
```
### 7.1 Validation Rules (Server)
- Every component node must include:
- `id` (string)
- `type` (string)
- `typeVersion` (positive int)
- `data` (object)
- Nested components are supported under `data.children` with depth/size limits.
- Media item rules:
- `source=local_upload` => `attachmentId` required.
- `source=external_url` => valid `http/https` `url` required.
- Invalid component payloads are rejected server-side.
## 8. Client Implementation Notes
### 8.1 Staging and Transform Choice
On file pick/drop, choose transform mode before upload starts:
- `Compressed WebP` (recommended default)
- `WebP`
- `Original`
If user changes mode for a staged file:
- cancel in-flight upload
- reprocess
- re-upload
### 8.2 Metadata Trust
Client may send metadata hints, but server metadata is canonical.
Client should refresh with upload response values.
### 8.3 Caching
- `/attachments/:attachmentId`: immutable and long-cache.
- `/avatars`, `/icons`, `/banners`: mutable aliases; revalidate frequently.
## 9. AVIF and JPEG XL Policy
- MVP default: WebP (best compatibility/perf tradeoff).
- AVIF: optional client mode if target platform decode support is verified.
- JPEG XL: defer for now due to uneven ecosystem support.