Der Forums-Abgleich las von 25 Archivseiten nur zwei. WoltLab verlinkt in der Blätterleiste nur die Nachbarseiten und führt die Gesamtzahl in einem eigenen Element; ausgewertet wurden aber die Links. Alles jenseits von Seite zwei war damit unsichtbar - etwa ein Antrag auf Seite vier, der deshalb nie ankam. Die Rollen verlaufen jetzt zwischen erfassen und gewähren statt zwischen Alltag und Verwaltung: Editoren erfassen Käufe und führen Datei-Import, GExtension- und Forums-Abgleich samt Entscheidung über Anträge durch. Kulanz zu vergeben bleibt Administratoren, ebenso das Auflösen unklarer Pakete, weil dabei Kulanz entstehen kann. Dazu lassen sich frühere Steam- und Forumskonten am Spieler hinterlegen. Abgleich und Import prüfen sie mit, sodass ein Kontowechsel die Zuordnung nicht abreißen lässt. Steam-Kennungen werden dabei auf dieselbe Form gebracht wie die Hauptangabe, und eine Kennung kann nicht zwei Spielern gehören.
76 lines
2.5 KiB
TypeScript
76 lines
2.5 KiB
TypeScript
import type { FastifyInstance } from 'fastify';
|
|
import { z } from 'zod';
|
|
import { requireAdmin, requireAuth, requireEditor } from '../auth/guard.js';
|
|
import { notFound } from '../errors.js';
|
|
import { prisma } from '../db.js';
|
|
import {
|
|
knownSteamIds,
|
|
listUnmatched,
|
|
resolveUnmatched,
|
|
scrapePlayers,
|
|
unmatchedOverview,
|
|
} from '../services/scraper.js';
|
|
import { contextOf, idParam, parseWith } from './helpers.js';
|
|
|
|
export async function scraperRoutes(app: FastifyInstance): Promise<void> {
|
|
/**
|
|
* Gleicht die Profile bereits erfasster Spieler ab. Bewusst nur die
|
|
* bekannten: der Abgleich soll vorhandene Konten ergänzen, nicht fremde
|
|
* Profile auf Vorrat einsammeln.
|
|
*/
|
|
app.post('/api/scrape/run', { preHandler: requireEditor }, async (request) => {
|
|
const body = parseWith(
|
|
z.object({ playerIds: z.array(z.number().int().positive()).max(500).optional() }),
|
|
request.body ?? {},
|
|
);
|
|
|
|
const all = await knownSteamIds();
|
|
const targets = body.playerIds
|
|
? all.filter((entry) => body.playerIds?.includes(entry.playerId))
|
|
: all;
|
|
|
|
return scrapePlayers(targets, contextOf(request));
|
|
});
|
|
|
|
/** Einzelnes Profil abgleichen, etwa direkt von der Spielerseite aus. */
|
|
app.post('/api/scrape/player/:id', { preHandler: requireEditor }, async (request) => {
|
|
const { id } = parseWith(idParam, request.params);
|
|
|
|
const player = await prisma.player.findUnique({
|
|
where: { id },
|
|
select: { id: true, steamId: true },
|
|
});
|
|
if (!player) throw notFound('Spieler');
|
|
if (!player.steamId) {
|
|
throw notFound('SteamID des Spielers');
|
|
}
|
|
|
|
return scrapePlayers([{ playerId: player.id, steamId: player.steamId }], contextOf(request));
|
|
});
|
|
|
|
app.get('/api/scrape/unmatched', { preHandler: requireAuth }, async (request) => {
|
|
const query = parseWith(
|
|
z.object({ all: z.enum(['true', 'false']).default('false') }),
|
|
request.query,
|
|
);
|
|
return { items: await listUnmatched(query.all === 'false') };
|
|
});
|
|
|
|
app.get('/api/scrape/unmatched/overview', { preHandler: requireAuth }, async () => {
|
|
return unmatchedOverview();
|
|
});
|
|
|
|
app.post('/api/scrape/unmatched/:id/resolve', { preHandler: requireAdmin }, async (request) => {
|
|
const { id } = parseWith(idParam, request.params);
|
|
const body = parseWith(
|
|
z.object({
|
|
resolution: z.enum(['CC_PACKAGE', 'REDESIGN_PACKAGE', 'REDESIGN_KULANZ', 'IGNORE']),
|
|
}),
|
|
request.body,
|
|
);
|
|
|
|
await resolveUnmatched(id, body.resolution, contextOf(request));
|
|
return { ok: true };
|
|
});
|
|
}
|