refactor: stricter domain: server-directory

This commit is contained in:
2026-04-11 14:50:38 +02:00
parent 3fb5515c3a
commit c8bb82feb5
16 changed files with 51 additions and 43 deletions

View File

@@ -14,14 +14,22 @@ server-directory/
│ └── 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
│ ├── constants/
│ └── server-directory.constants.ts CLIENT_UPDATE_REQUIRED_MESSAGE
── logic/
│ │ ├── room-signal-source.logic.ts Room → signal-source selector resolution
│ │ ├── room-signal-source.logic.spec.ts Unit tests
│ │ └── server-endpoint-defaults.logic.ts Default endpoint templates, URL sanitisation, reconciliation helpers
│ └── models/
│ └── server-directory.model.ts ServerEndpoint, ServerInfo, ServerJoinAccessResponse, invite/ban/kick types
├── infrastructure/
│ ├── constants/
│ │ └── server-directory.infrastructure.constants.ts Health-check timeout, localStorage keys
│ └── services/
│ ├── 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-health.service.ts Health probe (GET /api/health with 5 s timeout, fallback to /api/servers)
│ └── server-endpoint-storage.service.ts localStorage read/write for endpoint list and removed-default tracking
├── feature/
@@ -45,8 +53,8 @@ graph TD
Health[ServerEndpointHealthService]
Compat[ServerEndpointCompatibilityService]
Storage[ServerEndpointStorageService]
Defaults[server-endpoint-defaults]
Models[server-directory.models]
Defaults[server-endpoint-defaults.logic]
Models[server-directory.model]
Facade --> Service
Service --> API
@@ -61,12 +69,12 @@ graph TD
click Facade "application/facades/server-directory.facade.ts" "Thin domain boundary" _blank
click Service "application/services/server-directory.service.ts" "Orchestrator" _blank
click State "application/services/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
click API "infrastructure/services/server-directory-api.service.ts" "HTTP client for server API" _blank
click Health "infrastructure/services/server-endpoint-health.service.ts" "Health probe" _blank
click Compat "infrastructure/services/server-endpoint-compatibility.service.ts" "Version compatibility" _blank
click Storage "infrastructure/services/server-endpoint-storage.service.ts" "localStorage persistence" _blank
click Defaults "domain/logic/server-endpoint-defaults.logic.ts" "Default endpoint templates" _blank
click Models "domain/models/server-directory.model.ts" "Domain types" _blank
```
## Endpoint lifecycle

View File

@@ -6,7 +6,7 @@ import {
import { Observable } from 'rxjs';
import { STORAGE_KEY_CONNECTION_SETTINGS } from '../../../../core/constants';
import { User } from '../../../../shared-kernel';
import { ServerDirectoryApiService } from '../../infrastructure/server-directory-api.service';
import { ServerDirectoryApiService } from '../../infrastructure/services/server-directory-api.service';
import type {
BanServerMemberRequest,
CreateServerInviteRequest,
@@ -19,15 +19,15 @@ import type {
ServerJoinAccessResponse,
ServerSourceSelector,
UnbanServerMemberRequest
} from '../../domain/server-directory.models';
} from '../../domain/models/server-directory.model';
import {
buildRoomSignalSelector,
buildRoomSignalSource,
type RoomSignalSource,
type RoomSignalSourceInput
} from '../../domain/room-signal-source';
import { ServerEndpointCompatibilityService } from '../../infrastructure/server-endpoint-compatibility.service';
import { ServerEndpointHealthService } from '../../infrastructure/server-endpoint-health.service';
} from '../../domain/logic/room-signal-source.logic';
import { ServerEndpointCompatibilityService } from '../../infrastructure/services/server-endpoint-compatibility.service';
import { ServerEndpointHealthService } from '../../infrastructure/services/server-endpoint-health.service';
import { ServerEndpointStateService } from './server-endpoint-state.service';
@Injectable({ providedIn: 'root' })

View File

@@ -16,14 +16,14 @@ import {
hasEndpointForDefault,
matchDefaultEndpointTemplate,
sanitiseServerBaseUrl
} from '../../domain/server-endpoint-defaults';
import { ServerEndpointStorageService } from '../../infrastructure/server-endpoint-storage.service';
} from '../../domain/logic/server-endpoint-defaults.logic';
import { ServerEndpointStorageService } from '../../infrastructure/services/server-endpoint-storage.service';
import type {
ConfiguredDefaultServerDefinition,
DefaultEndpointTemplate,
ServerEndpoint,
ServerEndpointVersions
} from '../../domain/server-directory.models';
} from '../../domain/models/server-directory.model';
function resolveDefaultHttpProtocol(): 'http' | 'https' {
return typeof window !== 'undefined' && window.location?.protocol === 'https:'

View File

@@ -3,7 +3,7 @@ import {
buildRoomSignalSelector,
buildRoomSignalSource,
getSourceUrlFromSignalingUrl
} from './room-signal-source';
} from './room-signal-source.logic';
describe('room-signal-source helpers', () => {
it('converts signaling urls back to normalized source urls', () => {

View File

@@ -1,5 +1,5 @@
import type { ServerEndpoint, ServerSourceSelector } from './server-directory.models';
import { normaliseConfiguredServerUrl, sanitiseServerBaseUrl } from './server-endpoint-defaults';
import type { ServerEndpoint, ServerSourceSelector } from '../models/server-directory.model';
import { normaliseConfiguredServerUrl, sanitiseServerBaseUrl } from './server-endpoint-defaults.logic';
export interface RoomSignalSource {
sourceId?: string;

View File

@@ -3,7 +3,7 @@ import type {
DefaultEndpointTemplate,
DefaultServerDefinition,
ServerEndpoint
} from './server-directory.models';
} from '../models/server-directory.model';
export function sanitiseServerBaseUrl(rawUrl: string): string {
let cleaned = rawUrl.trim().replace(/\/+$/, '');

View File

@@ -3,7 +3,7 @@ import type {
ChannelPermissionOverride,
RoomRole,
RoomRoleAssignment
} from '../../../shared-kernel';
} from '../../../../shared-kernel';
export type ServerEndpointStatus = 'online' | 'offline' | 'checking' | 'unknown' | 'incompatible';

View File

@@ -11,7 +11,7 @@ import { Store } from '@ngrx/store';
import { RoomsActions } from '../../../../store/rooms/rooms.actions';
import { UsersActions } from '../../../../store/users/users.actions';
import { selectCurrentUser } from '../../../../store/users/users.selectors';
import type { ServerInviteInfo } from '../../domain/server-directory.models';
import type { ServerInviteInfo } from '../../domain/models/server-directory.model';
import { STORAGE_KEY_CURRENT_USER_ID } from '../../../../core/constants';
import { DatabaseService } from '../../../../infrastructure/persistence';
import { ServerDirectoryFacade } from '../../application/facades/server-directory.facade';

View File

@@ -36,7 +36,7 @@ import {
import { Room, User } from '../../../../shared-kernel';
import { SettingsModalService } from '../../../../core/services/settings-modal.service';
import { DatabaseService } from '../../../../infrastructure/persistence';
import { type ServerInfo } from '../../domain/server-directory.models';
import { type ServerInfo } from '../../domain/models/server-directory.model';
import { ServerDirectoryFacade } from '../../application/facades/server-directory.facade';
import { selectCurrentUser } from '../../../../store/users/users.selectors';
import { ConfirmDialogComponent } from '../../../../shared';

View File

@@ -1,4 +1,4 @@
export * from './application/facades/server-directory.facade';
export * from './domain/server-directory.constants';
export * from './domain/server-directory.models';
export * from './domain/room-signal-source';
export * from './domain/constants/server-directory.constants';
export * from './domain/models/server-directory.model';
export * from './domain/logic/room-signal-source.logic';

View File

@@ -15,8 +15,8 @@ import {
RoomRole,
RoomRoleAssignment,
User
} from '../../../shared-kernel';
import { ServerEndpointStateService } from '../application/services/server-endpoint-state.service';
} from '../../../../shared-kernel';
import { ServerEndpointStateService } from '../../application/services/server-endpoint-state.service';
import type {
BanServerMemberRequest,
CreateServerInviteRequest,
@@ -28,8 +28,8 @@ import type {
ServerJoinAccessResponse,
ServerSourceSelector,
UnbanServerMemberRequest
} from '../domain/server-directory.models';
import type { RoomSignalSourceInput } from '../domain/room-signal-source';
} from '../../domain/models/server-directory.model';
import type { RoomSignalSourceInput } from '../../domain/logic/room-signal-source.logic';
@Injectable({ providedIn: 'root' })
export class ServerDirectoryApiService {

View File

@@ -1,6 +1,6 @@
import { Injectable, inject } from '@angular/core';
import { ElectronBridgeService } from '../../../core/platform/electron/electron-bridge.service';
import type { ServerVersionCompatibilityResult } from '../domain/server-directory.models';
import { ElectronBridgeService } from '../../../../core/platform/electron/electron-bridge.service';
import type { ServerVersionCompatibilityResult } from '../../domain/models/server-directory.model';
@Injectable({ providedIn: 'root' })
export class ServerEndpointCompatibilityService {

View File

@@ -1,10 +1,10 @@
import { Injectable, inject } from '@angular/core';
import { SERVER_HEALTH_CHECK_TIMEOUT_MS } from './server-directory.infrastructure.constants';
import { SERVER_HEALTH_CHECK_TIMEOUT_MS } from '../constants/server-directory.infrastructure.constants';
import type {
ServerEndpoint,
ServerEndpointHealthResult,
ServerHealthCheckPayload
} from '../domain/server-directory.models';
} from '../../domain/models/server-directory.model';
import { ServerEndpointCompatibilityService } from './server-endpoint-compatibility.service';
@Injectable({ providedIn: 'root' })

View File

@@ -1,6 +1,6 @@
import { Injectable } from '@angular/core';
import { REMOVED_DEFAULT_SERVER_KEYS_STORAGE_KEY, SERVER_ENDPOINTS_STORAGE_KEY } from './server-directory.infrastructure.constants';
import type { ServerEndpoint } from '../domain/server-directory.models';
import { REMOVED_DEFAULT_SERVER_KEYS_STORAGE_KEY, SERVER_ENDPOINTS_STORAGE_KEY } from '../constants/server-directory.infrastructure.constants';
import type { ServerEndpoint } from '../../domain/models/server-directory.model';
@Injectable({ providedIn: 'root' })
export class ServerEndpointStorageService {