import type { FastifyInstance } from 'fastify'; import { z } from 'zod'; import { CreditKind } from '@prisma/client'; import { AliasType } from '@prisma/client'; import { requireAdmin, requireAuth, requireEditor } from '../auth/guard.js'; import { listLedger } from '../services/credits.js'; import { addAlias, createPlayer, deletePlayer, getPlayerDetail, markProblem, removeAlias, 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(), isProblem: z.boolean().optional(), problemNote: z.string().trim().max(1000).nullish(), }); export async function playerRoutes(app: FastifyInstance): Promise { 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), filter: z .enum(['all', 'problems', 'group', 'active', 'onlyInactive', 'none']) .default('all'), sort: z.enum(['name', 'characters', 'newest']).default('name'), /// Alte Schreibweise, damit gespeicherte Adressen weiter greifen. problems: z.enum(['true', 'false']).optional(), }), request.query, ); const filter = query.problems === 'true' ? 'problems' : query.filter; return searchPlayers(query.q, query.take, query.skip, filter, query.sort); }); 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); // Die Problem-Markierung läuft über die eigene Route, damit ein Klären // oder Markieren als solches im Protokoll steht und nicht als "geändert". const body = parseWith(playerBody.omit({ isProblem: true, problemNote: true }).partial(), request.body); return updatePlayer(id, body, contextOf(request)); }); /** * Einen Spieler als Problemfall markieren oder die Markierung aufheben. * Reine Merkhilfe - an Guthaben und CCs ändert sich dadurch nichts. */ app.post('/api/players/:id/problem', { preHandler: requireEditor }, async (request) => { const { id } = parseWith(idParam, request.params); const body = parseWith( z.object({ isProblem: z.boolean(), note: z.string().trim().max(1000).nullish(), }), request.body, ); return markProblem(id, body, contextOf(request)); }); // Frühere Steam- und Forumskonten eines Spielers. app.post('/api/players/:id/aliases', { preHandler: requireEditor }, async (request, reply) => { const { id } = parseWith(idParam, request.params); const body = parseWith( z.object({ type: z.nativeEnum(AliasType), value: z.string().trim().min(1).max(120), note: z.string().trim().max(200).optional(), }), request.body, ); const created = await addAlias(id, body.type, body.value, body.note, contextOf(request)); reply.code(201); return created; }); app.delete('/api/players/aliases/:id', { preHandler: requireEditor }, async (request) => { const { id } = parseWith(idParam, request.params); await removeAlias(id, contextOf(request)); return { ok: true }; }); app.delete('/api/players/:id', { preHandler: requireAdmin }, async (request) => { const { id } = parseWith(idParam, request.params); await deletePlayer(id, contextOf(request)); return { ok: true }; }); }