export interface OpenApiBuildOptions { baseUrl: string; appVersion: string; } export function buildOpenApiDocument(options: OpenApiBuildOptions): unknown { const { baseUrl, appVersion } = options; return { openapi: '3.1.0', info: { title: 'MetoYou Local Desktop API', version: appVersion, description: 'Authenticated local HTTP API exposed by the MetoYou desktop app. ' + 'Authentication is performed against a configured signaling server. ' + 'Bearer tokens issued here are scoped to this device only.' }, servers: [{ url: baseUrl }], components: { securitySchemes: { bearerAuth: { type: 'http', scheme: 'bearer', bearerFormat: 'opaque' } }, schemas: { Error: { type: 'object', required: ['error'], properties: { error: { type: 'string' }, errorCode: { type: 'string' } } }, LoginRequest: { type: 'object', required: [ 'username', 'password', 'serverUrl' ], properties: { username: { type: 'string' }, password: { type: 'string' }, serverUrl: { type: 'string', format: 'uri', description: 'Base URL of the signaling server to authenticate against. Must be in the allowed list configured in the desktop app.' } } }, LoginResponse: { type: 'object', required: [ 'token', 'expiresAt', 'user' ], properties: { token: { type: 'string' }, expiresAt: { type: 'integer', format: 'int64' }, user: { $ref: '#/components/schemas/AuthUser' } } }, AuthUser: { type: 'object', required: [ 'id', 'username', 'displayName' ], properties: { id: { type: 'string' }, username: { type: 'string' }, displayName: { type: 'string' } } }, Profile: { type: 'object', properties: { id: { type: 'string' }, username: { type: 'string' }, displayName: { type: 'string' }, description: { type: 'string' }, avatarUrl: { type: 'string' }, status: { type: 'string' } } }, Room: { type: 'object', properties: { id: { type: 'string' }, name: { type: 'string' } }, additionalProperties: true }, Message: { type: 'object', properties: { id: { type: 'string' }, roomId: { type: 'string' }, channelId: { type: 'string' }, senderId: { type: 'string' }, senderName: { type: 'string' }, content: { type: 'string' }, timestamp: { type: 'integer', format: 'int64' }, editedAt: { type: 'integer', format: 'int64' }, isDeleted: { type: 'boolean' } }, additionalProperties: true } } }, security: [{ bearerAuth: [] }], paths: { '/api/health': { get: { security: [], summary: 'Liveness probe', responses: { '200': { description: 'Service is alive', content: { 'application/json': { schema: { type: 'object', properties: { status: { type: 'string' }, version: { type: 'string' } } } } } } } } }, '/api/openapi.json': { get: { security: [], summary: 'OpenAPI specification', responses: { '200': { description: 'This document' } } } }, '/api/auth/login': { post: { security: [], summary: 'Exchange username/password (validated by a signaling server) for a bearer token', requestBody: { required: true, content: { 'application/json': { schema: { $ref: '#/components/schemas/LoginRequest' } } } }, responses: { '200': { description: 'Token issued', content: { 'application/json': { schema: { $ref: '#/components/schemas/LoginResponse' } } } }, '401': { description: 'Invalid credentials', content: { 'application/json': { schema: { $ref: '#/components/schemas/Error' } } } }, '403': { description: 'Signaling server URL not allowed', content: { 'application/json': { schema: { $ref: '#/components/schemas/Error' } } } } } } }, '/api/auth/logout': { post: { summary: 'Revoke the current bearer token', responses: { '204': { description: 'Token revoked' } } } }, '/api/profile': { get: { summary: 'Get the current user profile', responses: { '200': { description: 'Current user profile', content: { 'application/json': { schema: { $ref: '#/components/schemas/Profile' } } } }, '404': { description: 'No current user is set on this device', content: { 'application/json': { schema: { $ref: '#/components/schemas/Error' } } } } } } }, '/api/rooms': { get: { summary: 'List rooms (servers) known to this device', responses: { '200': { description: 'Rooms array', content: { 'application/json': { schema: { type: 'array', items: { $ref: '#/components/schemas/Room' } } } } } } } }, '/api/rooms/{roomId}/messages': { get: { summary: 'List messages for a room', parameters: [ { name: 'roomId', in: 'path', required: true, schema: { type: 'string' } }, { name: 'limit', in: 'query', required: false, schema: { type: 'integer', minimum: 1, maximum: 500 } }, { name: 'offset', in: 'query', required: false, schema: { type: 'integer', minimum: 0 } } ], responses: { '200': { description: 'Messages array', content: { 'application/json': { schema: { type: 'array', items: { $ref: '#/components/schemas/Message' } } } } } } } } } }; }