541 lines
16 KiB
TypeScript
541 lines
16 KiB
TypeScript
export interface OpenApiBuildOptions {
|
|
baseUrl: string;
|
|
appVersion: string;
|
|
}
|
|
|
|
export function buildOpenApiDocument(options: OpenApiBuildOptions): unknown {
|
|
const { baseUrl, appVersion } = options;
|
|
const roomIdPathParameter = { name: 'roomId', in: 'path', required: true, schema: { type: 'string' } };
|
|
const userIdPathParameter = { name: 'userId', in: 'path', required: true, schema: { type: 'string' } };
|
|
const messageIdPathParameter = { name: 'messageId', in: 'path', required: true, schema: { type: 'string' } };
|
|
const sinceTimestampQueryParameter = {
|
|
name: 'sinceTimestamp',
|
|
in: 'query',
|
|
required: true,
|
|
schema: { type: 'integer', minimum: 0, format: 'int64' }
|
|
};
|
|
|
|
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
|
|
},
|
|
User: {
|
|
type: 'object',
|
|
properties: {
|
|
id: { type: 'string' },
|
|
oderId: { type: 'string' },
|
|
username: { type: 'string' },
|
|
displayName: { type: 'string' },
|
|
status: { type: 'string' },
|
|
role: { type: 'string' },
|
|
isOnline: { type: 'boolean' }
|
|
},
|
|
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
|
|
},
|
|
Reaction: {
|
|
type: 'object',
|
|
properties: {
|
|
id: { type: 'string' },
|
|
messageId: { type: 'string' },
|
|
userId: { type: 'string' },
|
|
oderId: { type: 'string' },
|
|
emoji: { type: 'string' },
|
|
timestamp: { type: 'integer', format: 'int64' }
|
|
},
|
|
additionalProperties: true
|
|
},
|
|
Attachment: {
|
|
type: 'object',
|
|
properties: {
|
|
id: { type: 'string' },
|
|
messageId: { type: 'string' },
|
|
filename: { type: 'string' },
|
|
size: { type: 'integer' },
|
|
mime: { type: 'string' },
|
|
isImage: { type: 'boolean' },
|
|
filePath: { type: 'string' },
|
|
savedPath: { type: 'string' }
|
|
},
|
|
additionalProperties: true
|
|
},
|
|
Ban: {
|
|
type: 'object',
|
|
properties: {
|
|
oderId: { type: 'string' },
|
|
roomId: { type: 'string' },
|
|
userId: { type: 'string' },
|
|
bannedBy: { type: 'string' },
|
|
displayName: { type: 'string' },
|
|
reason: { type: 'string' },
|
|
expiresAt: { type: 'integer', format: 'int64' },
|
|
timestamp: { type: 'integer', format: 'int64' }
|
|
},
|
|
additionalProperties: true
|
|
},
|
|
PluginDataValue: {
|
|
type: 'object',
|
|
properties: {
|
|
value: {}
|
|
}
|
|
},
|
|
MetaValue: {
|
|
type: 'object',
|
|
properties: {
|
|
key: { type: 'string' },
|
|
value: { type: ['string', 'null'] }
|
|
}
|
|
}
|
|
}
|
|
},
|
|
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}': {
|
|
get: {
|
|
summary: 'Get a room by id',
|
|
parameters: [roomIdPathParameter],
|
|
responses: {
|
|
'200': {
|
|
description: 'Room details',
|
|
content: {
|
|
'application/json': {
|
|
schema: { $ref: '#/components/schemas/Room' }
|
|
}
|
|
}
|
|
},
|
|
'404': {
|
|
description: 'Room not found',
|
|
content: { 'application/json': { schema: { $ref: '#/components/schemas/Error' } } }
|
|
}
|
|
}
|
|
}
|
|
},
|
|
'/api/rooms/{roomId}/users': {
|
|
get: {
|
|
summary: 'List users known for a room',
|
|
parameters: [roomIdPathParameter],
|
|
responses: {
|
|
'200': {
|
|
description: 'Users array',
|
|
content: {
|
|
'application/json': {
|
|
schema: { type: 'array', items: { $ref: '#/components/schemas/User' } }
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
},
|
|
'/api/rooms/{roomId}/messages': {
|
|
get: {
|
|
summary: 'List messages for a room',
|
|
parameters: [
|
|
roomIdPathParameter,
|
|
{ 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' }
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
},
|
|
'/api/rooms/{roomId}/messages/since': {
|
|
get: {
|
|
summary: 'List room messages after a timestamp',
|
|
parameters: [roomIdPathParameter, sinceTimestampQueryParameter],
|
|
responses: {
|
|
'200': {
|
|
description: 'Messages array',
|
|
content: {
|
|
'application/json': {
|
|
schema: { type: 'array', items: { $ref: '#/components/schemas/Message' } }
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
},
|
|
'/api/rooms/{roomId}/bans': {
|
|
get: {
|
|
summary: 'List active bans for a room',
|
|
parameters: [roomIdPathParameter],
|
|
responses: {
|
|
'200': {
|
|
description: 'Bans array',
|
|
content: {
|
|
'application/json': {
|
|
schema: { type: 'array', items: { $ref: '#/components/schemas/Ban' } }
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
},
|
|
'/api/rooms/{roomId}/bans/{userId}': {
|
|
get: {
|
|
summary: 'Check whether a user is banned in a room',
|
|
parameters: [roomIdPathParameter, userIdPathParameter],
|
|
responses: {
|
|
'200': {
|
|
description: 'Ban status',
|
|
content: {
|
|
'application/json': {
|
|
schema: {
|
|
type: 'object',
|
|
required: ['isBanned'],
|
|
properties: { isBanned: { type: 'boolean' } }
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
},
|
|
'/api/messages/{messageId}': {
|
|
get: {
|
|
summary: 'Get a message by id',
|
|
parameters: [messageIdPathParameter],
|
|
responses: {
|
|
'200': {
|
|
description: 'Message details',
|
|
content: {
|
|
'application/json': {
|
|
schema: { $ref: '#/components/schemas/Message' }
|
|
}
|
|
}
|
|
},
|
|
'404': {
|
|
description: 'Message not found',
|
|
content: { 'application/json': { schema: { $ref: '#/components/schemas/Error' } } }
|
|
}
|
|
}
|
|
}
|
|
},
|
|
'/api/messages/{messageId}/reactions': {
|
|
get: {
|
|
summary: 'List reactions for a message',
|
|
parameters: [messageIdPathParameter],
|
|
responses: {
|
|
'200': {
|
|
description: 'Reactions array',
|
|
content: {
|
|
'application/json': {
|
|
schema: { type: 'array', items: { $ref: '#/components/schemas/Reaction' } }
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
},
|
|
'/api/messages/{messageId}/attachments': {
|
|
get: {
|
|
summary: 'List attachments for a message',
|
|
parameters: [messageIdPathParameter],
|
|
responses: {
|
|
'200': {
|
|
description: 'Attachments array',
|
|
content: {
|
|
'application/json': {
|
|
schema: { type: 'array', items: { $ref: '#/components/schemas/Attachment' } }
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
},
|
|
'/api/users/{userId}': {
|
|
get: {
|
|
summary: 'Get a user by id',
|
|
parameters: [userIdPathParameter],
|
|
responses: {
|
|
'200': {
|
|
description: 'User details',
|
|
content: {
|
|
'application/json': {
|
|
schema: { $ref: '#/components/schemas/User' }
|
|
}
|
|
}
|
|
},
|
|
'404': {
|
|
description: 'User not found',
|
|
content: { 'application/json': { schema: { $ref: '#/components/schemas/Error' } } }
|
|
}
|
|
}
|
|
}
|
|
},
|
|
'/api/attachments': {
|
|
get: {
|
|
summary: 'List all attachments stored on this device',
|
|
responses: {
|
|
'200': {
|
|
description: 'Attachments array',
|
|
content: {
|
|
'application/json': {
|
|
schema: { type: 'array', items: { $ref: '#/components/schemas/Attachment' } }
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
},
|
|
'/api/plugin-data': {
|
|
get: {
|
|
summary: 'Read a plugin data value',
|
|
parameters: [
|
|
{ name: 'pluginId', in: 'query', required: true, schema: { type: 'string' } },
|
|
{ name: 'key', in: 'query', required: true, schema: { type: 'string' } },
|
|
{ name: 'scope', in: 'query', required: true, schema: { type: 'string', enum: ['local', 'server'] } },
|
|
{ name: 'serverId', in: 'query', required: false, schema: { type: 'string' } }
|
|
],
|
|
responses: {
|
|
'200': {
|
|
description: 'Plugin data value',
|
|
content: {
|
|
'application/json': {
|
|
schema: { $ref: '#/components/schemas/PluginDataValue' }
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
},
|
|
'/api/meta/{key}': {
|
|
get: {
|
|
summary: 'Read a desktop metadata value',
|
|
parameters: [{ name: 'key', in: 'path', required: true, schema: { type: 'string' } }],
|
|
responses: {
|
|
'200': {
|
|
description: 'Metadata value',
|
|
content: {
|
|
'application/json': {
|
|
schema: { $ref: '#/components/schemas/MetaValue' }
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
};
|
|
}
|