Files
chat-server/ARCHITECTURE.md
T

5.4 KiB

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

  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:

{
  "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.