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.
This commit is contained in:
@@ -0,0 +1,71 @@
|
||||
import type { FastifyInstance } from 'fastify';
|
||||
import { z } from 'zod';
|
||||
import { CreditKind } from '@prisma/client';
|
||||
import { requireAdmin, requireAuth, requireEditor } from '../auth/guard.js';
|
||||
import { listLedger } from '../services/credits.js';
|
||||
import {
|
||||
createPlayer,
|
||||
deletePlayer,
|
||||
getPlayerDetail,
|
||||
searchPlayers,
|
||||
updatePlayer,
|
||||
} from '../services/players.js';
|
||||
import { contextOf, idParam, parseWith } from './helpers.js';
|
||||
|
||||
const playerBody = z.object({
|
||||
displayName: z.string().trim().min(1).max(120),
|
||||
steamId: z.string().trim().max(64).nullish(),
|
||||
forumName: z.string().trim().max(120).nullish(),
|
||||
notes: z.string().trim().max(2000).nullish(),
|
||||
});
|
||||
|
||||
export async function playerRoutes(app: FastifyInstance): Promise<void> {
|
||||
app.get('/api/players', { preHandler: requireAuth }, async (request) => {
|
||||
const query = parseWith(
|
||||
z.object({
|
||||
q: z.string().trim().max(120).default(''),
|
||||
take: z.coerce.number().int().min(1).max(200).default(50),
|
||||
skip: z.coerce.number().int().min(0).default(0),
|
||||
}),
|
||||
request.query,
|
||||
);
|
||||
return searchPlayers(query.q, query.take, query.skip);
|
||||
});
|
||||
|
||||
app.get('/api/players/:id', { preHandler: requireAuth }, async (request) => {
|
||||
const { id } = parseWith(idParam, request.params);
|
||||
return getPlayerDetail(id);
|
||||
});
|
||||
|
||||
app.get('/api/players/:id/ledger', { preHandler: requireAuth }, async (request) => {
|
||||
const { id } = parseWith(idParam, request.params);
|
||||
const query = parseWith(
|
||||
z.object({
|
||||
kind: z.nativeEnum(CreditKind).optional(),
|
||||
take: z.coerce.number().int().min(1).max(500).default(100),
|
||||
skip: z.coerce.number().int().min(0).default(0),
|
||||
}),
|
||||
request.query,
|
||||
);
|
||||
return listLedger({ playerId: id, ...query });
|
||||
});
|
||||
|
||||
app.post('/api/players', { preHandler: requireEditor }, async (request, reply) => {
|
||||
const body = parseWith(playerBody, request.body);
|
||||
const created = await createPlayer(body, contextOf(request));
|
||||
reply.code(201);
|
||||
return created;
|
||||
});
|
||||
|
||||
app.patch('/api/players/:id', { preHandler: requireEditor }, async (request) => {
|
||||
const { id } = parseWith(idParam, request.params);
|
||||
const body = parseWith(playerBody.partial(), request.body);
|
||||
return updatePlayer(id, body, contextOf(request));
|
||||
});
|
||||
|
||||
app.delete('/api/players/:id', { preHandler: requireAdmin }, async (request) => {
|
||||
const { id } = parseWith(idParam, request.params);
|
||||
await deletePlayer(id, contextOf(request));
|
||||
return { ok: true };
|
||||
});
|
||||
}
|
||||
Reference in New Issue
Block a user