docs: improve doucmentation
improve doucmentation and fix small store changes
This commit is contained in:
@@ -104,6 +104,37 @@ function clampInt(value: unknown, min: number, max: number, fallback: number): n
|
||||
return Math.max(min, Math.min(max, Math.floor(parsed)));
|
||||
}
|
||||
|
||||
function getTrailingPathParam(pathname: string, pattern: RegExp, name: string): string {
|
||||
const value = pattern.exec(pathname)?.[1];
|
||||
|
||||
if (!value) {
|
||||
throw new HttpError(400, `${name} is required`, 'INVALID_REQUEST');
|
||||
}
|
||||
|
||||
return decodeURIComponent(value);
|
||||
}
|
||||
|
||||
function getRequiredQueryParam(ctx: RouteContext, name: string): string {
|
||||
const value = ctx.request.url.searchParams.get(name)?.trim() ?? '';
|
||||
|
||||
if (!value) {
|
||||
throw new HttpError(400, `${name} is required`, 'INVALID_REQUEST');
|
||||
}
|
||||
|
||||
return value;
|
||||
}
|
||||
|
||||
function getRequiredTimestamp(ctx: RouteContext, name: string): number {
|
||||
const raw = getRequiredQueryParam(ctx, name);
|
||||
const value = Number(raw);
|
||||
|
||||
if (!Number.isFinite(value) || value < 0) {
|
||||
throw new HttpError(400, `${name} must be a non-negative timestamp`, 'INVALID_REQUEST');
|
||||
}
|
||||
|
||||
return Math.floor(value);
|
||||
}
|
||||
|
||||
const ROUTES: RouteDefinition[] = [
|
||||
defineRoute('GET', '/api/health', async (ctx): Promise<RouteResponse> => ({
|
||||
status: 200,
|
||||
@@ -276,20 +307,165 @@ const ROUTES: RouteDefinition[] = [
|
||||
return { status: 200, body: rooms ?? [] };
|
||||
}, true),
|
||||
|
||||
defineRoute('GET', '/api/rooms/{roomId}', async (ctx): Promise<RouteResponse> => {
|
||||
const roomId = getTrailingPathParam(ctx.request.pathname, /\/api\/rooms\/([^/]+)$/u, 'roomId');
|
||||
const room = await runQuery<unknown>(requireDataSource(ctx.dataSource), {
|
||||
type: QueryType.GetRoom,
|
||||
payload: { roomId }
|
||||
});
|
||||
|
||||
if (!room) {
|
||||
throw new HttpError(404, 'Room not found on this device', 'ROOM_NOT_FOUND');
|
||||
}
|
||||
|
||||
return { status: 200, body: room };
|
||||
}, true),
|
||||
|
||||
defineRoute('GET', '/api/rooms/{roomId}/users', async (ctx): Promise<RouteResponse> => {
|
||||
const roomId = getTrailingPathParam(ctx.request.pathname, /\/api\/rooms\/([^/]+)\/users$/u, 'roomId');
|
||||
const users = await runQuery<unknown[]>(requireDataSource(ctx.dataSource), {
|
||||
type: QueryType.GetUsersByRoom,
|
||||
payload: { roomId }
|
||||
});
|
||||
|
||||
return { status: 200, body: users ?? [] };
|
||||
}, true),
|
||||
|
||||
defineRoute('GET', '/api/rooms/{roomId}/messages', async (ctx): Promise<RouteResponse> => {
|
||||
const roomId = ctx.request.url.pathname.match(/\/api\/rooms\/([^/]+)\/messages$/u)?.[1];
|
||||
|
||||
if (!roomId)
|
||||
throw new HttpError(400, 'roomId is required', 'INVALID_REQUEST');
|
||||
|
||||
const roomId = getTrailingPathParam(ctx.request.pathname, /\/api\/rooms\/([^/]+)\/messages$/u, 'roomId');
|
||||
const limit = clampInt(ctx.request.url.searchParams.get('limit'), 1, 500, 100);
|
||||
const offset = clampInt(ctx.request.url.searchParams.get('offset'), 0, Number.MAX_SAFE_INTEGER, 0);
|
||||
const messages = await runQuery<unknown[]>(requireDataSource(ctx.dataSource), {
|
||||
type: QueryType.GetMessages,
|
||||
payload: { roomId: decodeURIComponent(roomId), limit, offset }
|
||||
payload: { roomId, limit, offset }
|
||||
});
|
||||
|
||||
return { status: 200, body: messages ?? [] };
|
||||
}, true),
|
||||
|
||||
defineRoute('GET', '/api/rooms/{roomId}/messages/since', async (ctx): Promise<RouteResponse> => {
|
||||
const roomId = getTrailingPathParam(ctx.request.pathname, /\/api\/rooms\/([^/]+)\/messages\/since$/u, 'roomId');
|
||||
const sinceTimestamp = getRequiredTimestamp(ctx, 'sinceTimestamp');
|
||||
const messages = await runQuery<unknown[]>(requireDataSource(ctx.dataSource), {
|
||||
type: QueryType.GetMessagesSince,
|
||||
payload: { roomId, sinceTimestamp }
|
||||
});
|
||||
|
||||
return { status: 200, body: messages ?? [] };
|
||||
}, true),
|
||||
|
||||
defineRoute('GET', '/api/rooms/{roomId}/bans', async (ctx): Promise<RouteResponse> => {
|
||||
const roomId = getTrailingPathParam(ctx.request.pathname, /\/api\/rooms\/([^/]+)\/bans$/u, 'roomId');
|
||||
const bans = await runQuery<unknown[]>(requireDataSource(ctx.dataSource), {
|
||||
type: QueryType.GetBansForRoom,
|
||||
payload: { roomId }
|
||||
});
|
||||
|
||||
return { status: 200, body: bans ?? [] };
|
||||
}, true),
|
||||
|
||||
defineRoute('GET', '/api/rooms/{roomId}/bans/{userId}', async (ctx): Promise<RouteResponse> => {
|
||||
const match = /\/api\/rooms\/([^/]+)\/bans\/([^/]+)$/u.exec(ctx.request.pathname);
|
||||
|
||||
if (!match) {
|
||||
throw new HttpError(400, 'roomId and userId are required', 'INVALID_REQUEST');
|
||||
}
|
||||
|
||||
const isBanned = await runQuery<boolean>(requireDataSource(ctx.dataSource), {
|
||||
type: QueryType.IsUserBanned,
|
||||
payload: { roomId: decodeURIComponent(match[1]), userId: decodeURIComponent(match[2]) }
|
||||
});
|
||||
|
||||
return { status: 200, body: { isBanned } };
|
||||
}, true),
|
||||
|
||||
defineRoute('GET', '/api/messages/{messageId}', async (ctx): Promise<RouteResponse> => {
|
||||
const messageId = getTrailingPathParam(ctx.request.pathname, /\/api\/messages\/([^/]+)$/u, 'messageId');
|
||||
const message = await runQuery<unknown>(requireDataSource(ctx.dataSource), {
|
||||
type: QueryType.GetMessageById,
|
||||
payload: { messageId }
|
||||
});
|
||||
|
||||
if (!message) {
|
||||
throw new HttpError(404, 'Message not found on this device', 'MESSAGE_NOT_FOUND');
|
||||
}
|
||||
|
||||
return { status: 200, body: message };
|
||||
}, true),
|
||||
|
||||
defineRoute('GET', '/api/messages/{messageId}/reactions', async (ctx): Promise<RouteResponse> => {
|
||||
const messageId = getTrailingPathParam(ctx.request.pathname, /\/api\/messages\/([^/]+)\/reactions$/u, 'messageId');
|
||||
const reactions = await runQuery<unknown[]>(requireDataSource(ctx.dataSource), {
|
||||
type: QueryType.GetReactionsForMessage,
|
||||
payload: { messageId }
|
||||
});
|
||||
|
||||
return { status: 200, body: reactions ?? [] };
|
||||
}, true),
|
||||
|
||||
defineRoute('GET', '/api/messages/{messageId}/attachments', async (ctx): Promise<RouteResponse> => {
|
||||
const messageId = getTrailingPathParam(ctx.request.pathname, /\/api\/messages\/([^/]+)\/attachments$/u, 'messageId');
|
||||
const attachments = await runQuery<unknown[]>(requireDataSource(ctx.dataSource), {
|
||||
type: QueryType.GetAttachmentsForMessage,
|
||||
payload: { messageId }
|
||||
});
|
||||
|
||||
return { status: 200, body: attachments ?? [] };
|
||||
}, true),
|
||||
|
||||
defineRoute('GET', '/api/users/{userId}', async (ctx): Promise<RouteResponse> => {
|
||||
const userId = getTrailingPathParam(ctx.request.pathname, /\/api\/users\/([^/]+)$/u, 'userId');
|
||||
const user = await runQuery<unknown>(requireDataSource(ctx.dataSource), {
|
||||
type: QueryType.GetUser,
|
||||
payload: { userId }
|
||||
});
|
||||
|
||||
if (!user) {
|
||||
throw new HttpError(404, 'User not found on this device', 'USER_NOT_FOUND');
|
||||
}
|
||||
|
||||
return { status: 200, body: user };
|
||||
}, true),
|
||||
|
||||
defineRoute('GET', '/api/attachments', async (ctx): Promise<RouteResponse> => {
|
||||
const attachments = await runQuery<unknown[]>(requireDataSource(ctx.dataSource), {
|
||||
type: QueryType.GetAllAttachments,
|
||||
payload: {}
|
||||
});
|
||||
|
||||
return { status: 200, body: attachments ?? [] };
|
||||
}, true),
|
||||
|
||||
defineRoute('GET', '/api/plugin-data', async (ctx): Promise<RouteResponse> => {
|
||||
const pluginId = getRequiredQueryParam(ctx, 'pluginId');
|
||||
const key = getRequiredQueryParam(ctx, 'key');
|
||||
const scope = getRequiredQueryParam(ctx, 'scope');
|
||||
|
||||
if (scope !== 'local' && scope !== 'server') {
|
||||
throw new HttpError(400, 'scope must be local or server', 'INVALID_REQUEST');
|
||||
}
|
||||
|
||||
const value = await runQuery<unknown>(requireDataSource(ctx.dataSource), {
|
||||
type: QueryType.GetPluginData,
|
||||
payload: {
|
||||
key,
|
||||
pluginId,
|
||||
scope,
|
||||
serverId: ctx.request.url.searchParams.get('serverId') ?? undefined
|
||||
}
|
||||
});
|
||||
|
||||
return { status: 200, body: { value } };
|
||||
}, true),
|
||||
|
||||
defineRoute('GET', '/api/meta/{key}', async (ctx): Promise<RouteResponse> => {
|
||||
const key = getTrailingPathParam(ctx.request.pathname, /\/api\/meta\/([^/]+)$/u, 'key');
|
||||
const value = await runQuery<string | null>(requireDataSource(ctx.dataSource), {
|
||||
type: QueryType.GetMeta,
|
||||
payload: { key }
|
||||
});
|
||||
|
||||
return { status: 200, body: { key, value } };
|
||||
}, true)
|
||||
];
|
||||
|
||||
|
||||
Reference in New Issue
Block a user