242 lines
7.1 KiB
TypeScript
242 lines
7.1 KiB
TypeScript
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' }
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
};
|
|
}
|