Files
CC-Manager/server/src/routes/characters.routes.ts
T
Harlekin a7228e63d1 HTTP-Endpunkte und Anwendungsaufbau
Eingaben werden mit zod validiert, jede aendernde Aktion schreibt ins
Audit-Log. Beim ersten Start entsteht ein Administrator samt Startwerten fuer
die Produkterkennung; solange ein Erstpasswort gilt, bleiben schreibende Wege
bis auf den Passwortwechsel gesperrt.

Nicht-API-Pfade fallen auf die SPA zurueck, damit Deep-Links funktionieren.
2026-09-18 11:37:31 +02:00

96 lines
3.4 KiB
TypeScript

import type { FastifyInstance } from 'fastify';
import { z } from 'zod';
import { CreditKind, InactiveReason } from '@prisma/client';
import { requireEditor } from '../auth/guard.js';
import {
createCharacter,
deactivateCharacter,
reactivateFree,
reactivatePaid,
redesignCharacter,
revertCharacter,
updateCharacter,
} from '../services/characters.js';
import { contextOf, idParam, optionalNote, parseWith } from './helpers.js';
const redesignKind = z.enum([CreditKind.REDESIGN_PAID, CreditKind.REDESIGN_KULANZ]);
const reviveKind = z.enum([CreditKind.CC, CreditKind.REDESIGN_PAID, CreditKind.REDESIGN_KULANZ]);
export async function characterRoutes(app: FastifyInstance): Promise<void> {
app.post('/api/players/:id/characters', { preHandler: requireEditor }, async (request, reply) => {
const { id } = parseWith(idParam, request.params);
const body = parseWith(
z.object({
name: z.string().trim().min(1).max(160),
loreText: z.string().trim().max(5000).optional(),
note: optionalNote,
}),
request.body,
);
const result = await createCharacter(id, body, contextOf(request));
reply.code(201);
return result;
});
app.patch('/api/characters/:id', { preHandler: requireEditor }, async (request) => {
const { id } = parseWith(idParam, request.params);
const body = parseWith(
z.object({
name: z.string().trim().min(1).max(160).optional(),
loreText: z.string().trim().max(5000).nullable().optional(),
}),
request.body,
);
return updateCharacter(id, body, contextOf(request));
});
app.post('/api/characters/:id/redesign', { preHandler: requireEditor }, async (request) => {
const { id } = parseWith(idParam, request.params);
const body = parseWith(
z.object({
name: z.string().trim().min(1).max(160),
loreText: z.string().trim().max(5000).optional(),
useKind: redesignKind,
note: optionalNote,
}),
request.body,
);
return redesignCharacter(id, body, contextOf(request));
});
app.post('/api/characters/:id/deactivate', { preHandler: requireEditor }, async (request) => {
const { id } = parseWith(idParam, request.params);
const body = parseWith(
z.object({
reason: z.enum([InactiveReason.PLAYER_INACTIVITY, InactiveReason.OTHER]),
note: optionalNote,
}),
request.body,
);
return deactivateCharacter(id, body.reason, contextOf(request), body.note);
});
app.post('/api/characters/:id/reactivate-free', { preHandler: requireEditor }, async (request) => {
const { id } = parseWith(idParam, request.params);
return reactivateFree(id, contextOf(request));
});
app.post('/api/characters/:id/reactivate-paid', { preHandler: requireEditor }, async (request) => {
const { id } = parseWith(idParam, request.params);
const body = parseWith(
z.object({ useKind: reviveKind, note: optionalNote }),
request.body,
);
return reactivatePaid(id, body.useKind, contextOf(request), body.note);
});
app.post('/api/characters/:id/revert', { preHandler: requireEditor }, async (request) => {
const { id } = parseWith(idParam, request.params);
const body = parseWith(
z.object({ reason: z.string().trim().min(1, 'Eine Begruendung ist Pflicht').max(500) }),
request.body,
);
return revertCharacter(id, body.reason, contextOf(request));
});
}