mirror of
https://github.com/Ark0N/Codeman.git
synced 2026-10-07 16:09:43 +02:00
51 lines
2.3 KiB
TypeScript
51 lines
2.3 KiB
TypeScript
/** @fileoverview Authenticated owner-scoped tab-layout read/write API. */
|
|
import type { FastifyInstance } from 'fastify';
|
|
import { ownerLayoutKey } from '../../tab-layout-persistence.js';
|
|
import { TabLayoutValidationError } from '../../tab-layout.js';
|
|
import { ApiErrorCode, createErrorResponse } from '../../types.js';
|
|
import { ownerFor } from '../route-helpers.js';
|
|
import type { TabLayoutPort } from '../ports/index.js';
|
|
|
|
export const TAB_LAYOUT_BODY_LIMIT = 128 * 1024;
|
|
|
|
function parseWriteBody(body: unknown): { baseVersion: number; layout: unknown } {
|
|
if (body === null || typeof body !== 'object' || Array.isArray(body)) {
|
|
throw new TabLayoutValidationError('body must be an object');
|
|
}
|
|
const keys = Object.keys(body);
|
|
if (keys.length !== 2 || !Object.hasOwn(body, 'baseVersion') || !Object.hasOwn(body, 'layout')) {
|
|
throw new TabLayoutValidationError('body must contain exactly baseVersion and layout');
|
|
}
|
|
const input = body as { baseVersion?: unknown; layout?: unknown };
|
|
if (!Number.isSafeInteger(input.baseVersion) || (input.baseVersion as number) < 0 || input.layout === undefined) {
|
|
throw new TabLayoutValidationError('baseVersion must be a non-negative safe integer and layout is required');
|
|
}
|
|
return { baseVersion: input.baseVersion as number, layout: input.layout };
|
|
}
|
|
|
|
export function registerTabLayoutRoutes(app: FastifyInstance, ctx: TabLayoutPort): void {
|
|
app.get('/api/tab-layout', async (req) => ({
|
|
success: true,
|
|
data: { layout: await ctx.tabLayouts.get(ownerLayoutKey(ownerFor(req))) },
|
|
}));
|
|
|
|
app.put('/api/tab-layout', { bodyLimit: TAB_LAYOUT_BODY_LIMIT }, async (req, reply) => {
|
|
try {
|
|
const { baseVersion, layout } = parseWriteBody(req.body);
|
|
const result = await ctx.tabLayouts.put(ownerLayoutKey(ownerFor(req)), layout, baseVersion);
|
|
if (result.status === 'conflict') {
|
|
return reply.code(409).send({
|
|
...createErrorResponse(ApiErrorCode.CONFLICT, 'Tab layout version conflict'),
|
|
data: { layout: result.layout },
|
|
});
|
|
}
|
|
return { success: true, data: { layout: result.layout } };
|
|
} catch (error) {
|
|
if (error instanceof TabLayoutValidationError) {
|
|
return reply.code(400).send(createErrorResponse(ApiErrorCode.INVALID_INPUT, error.message));
|
|
}
|
|
throw error;
|
|
}
|
|
});
|
|
}
|