Rollen neu geschnitten, Seitenzahl korrigiert, frühere Konten
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.
This commit is contained in:
@@ -4,6 +4,7 @@ import { conflict, notFound } from '../errors.js';
|
||||
import { getBalances, getBalancesForMany, listLedger, type Balances } from './credits.js';
|
||||
import { listCharacters } from './characters.js';
|
||||
import { recordAudit, type ActionContext } from './audit.js';
|
||||
import { AliasType } from '@prisma/client';
|
||||
import { looksLikeSteamId, normalizeSteamId } from '../lib/steamid.js';
|
||||
|
||||
export interface PlayerInput {
|
||||
@@ -67,7 +68,7 @@ export async function getPlayerDetail(playerId: number) {
|
||||
const player = await prisma.player.findUnique({ where: { id: playerId } });
|
||||
if (!player) throw notFound('Spieler');
|
||||
|
||||
const [balances, characters, ledger, unmatched] = await Promise.all([
|
||||
const [balances, characters, ledger, unmatched, aliases] = await Promise.all([
|
||||
getBalances(playerId),
|
||||
listCharacters(playerId),
|
||||
listLedger({ playerId, take: 200 }),
|
||||
@@ -76,6 +77,10 @@ export async function getPlayerDetail(playerId: number) {
|
||||
orderBy: { scrapedAt: 'desc' },
|
||||
include: { player: { select: { id: true, displayName: true } } },
|
||||
}),
|
||||
prisma.playerAlias.findMany({
|
||||
where: { playerId },
|
||||
orderBy: [{ type: 'asc' }, { value: 'asc' }],
|
||||
}),
|
||||
]);
|
||||
|
||||
return {
|
||||
@@ -85,6 +90,7 @@ export async function getPlayerDetail(playerId: number) {
|
||||
ledger: ledger.items,
|
||||
ledgerTotal: ledger.total,
|
||||
unmatched,
|
||||
aliases,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -219,3 +225,76 @@ export async function deletePlayer(playerId: number, ctx: ActionContext): Promis
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Trägt ein früheres Konto nach. Steam-Kennungen werden dabei auf dieselbe
|
||||
* Form gebracht wie die Hauptangabe, sonst greift der Abgleich nicht.
|
||||
*/
|
||||
export async function addAlias(
|
||||
playerId: number,
|
||||
type: AliasType,
|
||||
rawValue: string,
|
||||
note: string | undefined,
|
||||
ctx: ActionContext,
|
||||
) {
|
||||
const player = await prisma.player.findUnique({ where: { id: playerId } });
|
||||
if (!player) throw notFound('Spieler');
|
||||
|
||||
let value = rawValue.trim();
|
||||
if (type === AliasType.STEAM_ID) {
|
||||
const normalized = normalizeSteamId(value);
|
||||
if (!normalized) {
|
||||
throw conflict('INVALID_STEAMID', `"${value}" sieht nach keiner SteamID aus`);
|
||||
}
|
||||
value = normalized;
|
||||
|
||||
if (value === player.steamId) {
|
||||
throw conflict('IS_PRIMARY', 'Das ist bereits die hinterlegte SteamID dieses Spielers');
|
||||
}
|
||||
} else if (value.toLowerCase() === player.forumName?.toLowerCase()) {
|
||||
throw conflict('IS_PRIMARY', 'Das ist bereits der hinterlegte Forum-Name dieses Spielers');
|
||||
}
|
||||
|
||||
if (value.length === 0) throw conflict('EMPTY', 'Der Wert darf nicht leer sein');
|
||||
|
||||
const vorhanden = await prisma.playerAlias.findUnique({
|
||||
where: { type_value: { type, value } },
|
||||
include: { player: { select: { displayName: true } } },
|
||||
});
|
||||
if (vorhanden) {
|
||||
throw conflict(
|
||||
'ALIAS_TAKEN',
|
||||
vorhanden.playerId === playerId
|
||||
? 'Dieses Konto ist hier schon eingetragen'
|
||||
: `Dieses Konto gehört bereits zu "${vorhanden.player.displayName}"`,
|
||||
);
|
||||
}
|
||||
|
||||
const created = await prisma.playerAlias.create({
|
||||
data: { playerId, type, value, note: note?.trim() || null },
|
||||
});
|
||||
|
||||
await recordAudit(prisma, ctx, {
|
||||
action: 'player.alias_added',
|
||||
targetType: 'player',
|
||||
targetId: playerId,
|
||||
details: { type, value, note: note ?? null },
|
||||
});
|
||||
|
||||
return created;
|
||||
}
|
||||
|
||||
export async function removeAlias(aliasId: number, ctx: ActionContext): Promise<void> {
|
||||
const alias = await prisma.playerAlias.findUnique({ where: { id: aliasId } });
|
||||
if (!alias) throw notFound('Eintrag');
|
||||
|
||||
await prisma.playerAlias.delete({ where: { id: aliasId } });
|
||||
|
||||
await recordAudit(prisma, ctx, {
|
||||
action: 'player.alias_removed',
|
||||
targetType: 'player',
|
||||
targetId: alias.playerId,
|
||||
details: { type: alias.type, value: alias.value },
|
||||
});
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user