Move toju-app into own its folder
This commit is contained in:
176
toju-app/src/app/domains/server-directory/README.md
Normal file
176
toju-app/src/app/domains/server-directory/README.md
Normal file
@@ -0,0 +1,176 @@
|
||||
# Server Directory Domain
|
||||
|
||||
Manages the list of server endpoints the client can connect to, health-checking them, resolving API URLs, and providing server CRUD, search, invites, and moderation. This is the central domain that other domains (auth, chat, attachment) depend on for knowing where the backend is.
|
||||
|
||||
## Module map
|
||||
|
||||
```
|
||||
server-directory/
|
||||
├── application/
|
||||
│ ├── server-directory.facade.ts High-level API: server CRUD, search, health, invites, moderation
|
||||
│ └── server-endpoint-state.service.ts Signal-based endpoint list, reconciliation with defaults, localStorage persistence
|
||||
│
|
||||
├── domain/
|
||||
│ ├── server-directory.models.ts ServerEndpoint, ServerInfo, ServerJoinAccessResponse, invite/ban/kick types
|
||||
│ ├── server-directory.constants.ts CLIENT_UPDATE_REQUIRED_MESSAGE
|
||||
│ └── server-endpoint-defaults.ts Default endpoint templates, URL sanitisation, reconciliation helpers
|
||||
│
|
||||
├── infrastructure/
|
||||
│ ├── server-directory-api.service.ts HTTP client for all server API calls
|
||||
│ ├── server-endpoint-health.service.ts Health probe (GET /api/health with 5 s timeout, fallback to /api/servers)
|
||||
│ ├── server-endpoint-compatibility.service.ts Semantic version comparison for client/server compatibility
|
||||
│ └── server-endpoint-storage.service.ts localStorage read/write for endpoint list and removed-default tracking
|
||||
│
|
||||
├── feature/
|
||||
│ ├── invite/ Invite creation and resolution UI
|
||||
│ ├── server-search/ Server search/browse panel
|
||||
│ └── settings/ Server endpoint management settings
|
||||
│
|
||||
└── index.ts Barrel exports
|
||||
```
|
||||
|
||||
## Layer composition
|
||||
|
||||
The facade delegates HTTP work to the API service and endpoint state to the state service. Health probing combines the health service and compatibility service. Storage is accessed only through the state service.
|
||||
|
||||
```mermaid
|
||||
graph TD
|
||||
Facade[ServerDirectoryFacade]
|
||||
State[ServerEndpointStateService]
|
||||
API[ServerDirectoryApiService]
|
||||
Health[ServerEndpointHealthService]
|
||||
Compat[ServerEndpointCompatibilityService]
|
||||
Storage[ServerEndpointStorageService]
|
||||
Defaults[server-endpoint-defaults]
|
||||
Models[server-directory.models]
|
||||
|
||||
Facade --> API
|
||||
Facade --> State
|
||||
Facade --> Health
|
||||
Facade --> Compat
|
||||
API --> State
|
||||
State --> Storage
|
||||
State --> Defaults
|
||||
Health --> Compat
|
||||
|
||||
click Facade "application/server-directory.facade.ts" "High-level API" _blank
|
||||
click State "application/server-endpoint-state.service.ts" "Signal-based endpoint state" _blank
|
||||
click API "infrastructure/server-directory-api.service.ts" "HTTP client for server API" _blank
|
||||
click Health "infrastructure/server-endpoint-health.service.ts" "Health probe" _blank
|
||||
click Compat "infrastructure/server-endpoint-compatibility.service.ts" "Version compatibility" _blank
|
||||
click Storage "infrastructure/server-endpoint-storage.service.ts" "localStorage persistence" _blank
|
||||
click Defaults "domain/server-endpoint-defaults.ts" "Default endpoint templates" _blank
|
||||
click Models "domain/server-directory.models.ts" "Domain types" _blank
|
||||
```
|
||||
|
||||
## Endpoint lifecycle
|
||||
|
||||
On startup, `ServerEndpointStateService` loads endpoints from localStorage, reconciles them with the configured defaults from the environment, and ensures at least one endpoint is active.
|
||||
|
||||
```mermaid
|
||||
stateDiagram-v2
|
||||
[*] --> Load: constructor
|
||||
Load --> HasStored: localStorage has endpoints
|
||||
Load --> InitDefaults: no stored endpoints
|
||||
InitDefaults --> Ready: save default endpoints
|
||||
HasStored --> Reconcile: compare stored vs defaults
|
||||
Reconcile --> Ready: merge, ensure active
|
||||
Ready --> HealthCheck: facade.testAllServers()
|
||||
|
||||
state HealthCheck {
|
||||
[*] --> Probing
|
||||
Probing --> Online: /api/health 200 OK
|
||||
Probing --> Incompatible: version mismatch
|
||||
Probing --> Offline: request failed
|
||||
}
|
||||
```
|
||||
|
||||
## Health probing
|
||||
|
||||
The facade exposes `testServer(endpointId)` and `testAllServers()`. Both delegate to `ServerEndpointHealthService.probeEndpoint()`, which:
|
||||
|
||||
1. Sends `GET /api/health` with a 5-second timeout
|
||||
2. On success, checks the response's `serverVersion` against the client version via `ServerEndpointCompatibilityService`
|
||||
3. If versions are incompatible, the endpoint is marked `incompatible` and deactivated
|
||||
4. If `/api/health` fails, falls back to `GET /api/servers` as a basic liveness check
|
||||
5. Updates the endpoint's status, latency, and version info in the state service
|
||||
|
||||
```mermaid
|
||||
sequenceDiagram
|
||||
participant Facade
|
||||
participant Health as HealthService
|
||||
participant Compat as CompatibilityService
|
||||
participant API as Server
|
||||
|
||||
Facade->>Health: probeEndpoint(endpoint, clientVersion)
|
||||
Health->>API: GET /api/health (5s timeout)
|
||||
|
||||
alt 200 OK
|
||||
API-->>Health: { serverVersion }
|
||||
Health->>Compat: evaluateServerVersion(serverVersion, clientVersion)
|
||||
Compat-->>Health: { isCompatible, serverVersion }
|
||||
Health-->>Facade: online / incompatible + latency + versions
|
||||
else Request failed
|
||||
Health->>API: GET /api/servers (fallback)
|
||||
alt 200 OK
|
||||
API-->>Health: servers list
|
||||
Health-->>Facade: online + latency
|
||||
else Also failed
|
||||
Health-->>Facade: offline
|
||||
end
|
||||
end
|
||||
|
||||
Facade->>Facade: updateServerStatus(id, status, latency, versions)
|
||||
```
|
||||
|
||||
## Server search
|
||||
|
||||
The facade's `searchServers(query)` method supports two modes controlled by a `searchAllServers` flag:
|
||||
|
||||
- **Single endpoint**: searches only the active server's API
|
||||
- **All endpoints**: fans out the query to every online active endpoint via `forkJoin`, then deduplicates results by server ID
|
||||
|
||||
The API service normalises every `ServerInfo` response, filling in `sourceId`, `sourceName`, and `sourceUrl` so the UI knows which endpoint each server came from.
|
||||
|
||||
## Default endpoint management
|
||||
|
||||
Default servers are configured in the environment file. The state service builds `DefaultEndpointTemplate` objects from the configuration and uses them during reconciliation:
|
||||
|
||||
- Stored endpoints are matched to defaults by `defaultKey` or URL
|
||||
- Missing defaults are added unless the user explicitly removed them (tracked in a separate localStorage key)
|
||||
- `restoreDefaultServers()` re-adds any removed defaults and clears the removal tracking
|
||||
- The primary default URL is used as a fallback when no endpoint is resolved
|
||||
|
||||
URL sanitisation strips trailing slashes and `/api` suffixes. Protocol-less URLs get `http` or `https` based on the current page protocol.
|
||||
|
||||
## Server administration
|
||||
|
||||
The facade provides methods for server registration, updates, and unregistration. These map directly to the API service's HTTP calls:
|
||||
|
||||
| Method | HTTP | Endpoint |
|
||||
|---|---|---|
|
||||
| `registerServer` | POST | `/api/servers` |
|
||||
| `updateServer` | PUT | `/api/servers/:id` |
|
||||
| `unregisterServer` | DELETE | `/api/servers/:id` |
|
||||
|
||||
## Invites and moderation
|
||||
|
||||
| Method | Purpose |
|
||||
|---|---|
|
||||
| `createInvite(serverId, request)` | Creates a time-limited invite link |
|
||||
| `getInvite(inviteId)` | Resolves invite metadata |
|
||||
| `requestServerAccess(request)` | Joins a server (via membership, password, invite, or public access) |
|
||||
| `kickServerMember(serverId, request)` | Removes a user from the server |
|
||||
| `banServerMember(serverId, request)` | Bans a user with optional reason and expiry |
|
||||
| `unbanServerMember(serverId, request)` | Lifts a ban |
|
||||
|
||||
## Persistence
|
||||
|
||||
All endpoint state is persisted to localStorage under two keys:
|
||||
|
||||
| Key | Contents |
|
||||
|---|---|
|
||||
| `metoyou_server_endpoints` | Full `ServerEndpoint[]` array |
|
||||
| `metoyou_removed_default_server_keys` | Set of default endpoint keys the user explicitly removed |
|
||||
|
||||
The storage service handles JSON serialisation and defensive parsing. Invalid data falls back to empty state rather than throwing.
|
||||
Reference in New Issue
Block a user