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.
96 lines
3.4 KiB
TypeScript
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));
|
|
});
|
|
}
|