# 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/", "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.