import { usePrisma } from "../../utils/prisma"; /** * GET /api/profile/check-nickname?nickname=foo * * Validiert ob ein Nickname für den User OK ist: * - Min 3 Zeichen * - Max 32 Zeichen * - Nicht in Profanity-Blocklist (kleines hartcodiertes Set) * - Nicht von einem anderen User belegt (case-insensitive) * * Returns: { available: boolean, reason?: 'too_short' | 'too_long' | 'profanity' | 'taken' } * * Genutzt von der Nickname-Slide im Onboarding mit ~500ms Debounce um * Live-Feedback zu geben. Idempotent + günstig (1 SELECT auf einen Index). */ const PROFANITY_BLOCKLIST: ReadonlySet = new Set([ // Minimal-Set DE/EN — slurs + bot-impersonation. Erweiterbar; lib-frei // damit Bundle-Size klein bleibt. "admin", "administrator", "rebreak", "lyra", "support", "moderator", "mod", "system", "root", "nigger", "nazi", "fuck", "shit", "fotze", "hure", "schwuchtel", "fag", "bitch", "cunt", "arsch", "wichser", ]); function isProfanity(nickname: string): boolean { const lower = nickname.toLowerCase().trim(); if (PROFANITY_BLOCKLIST.has(lower)) return true; for (const word of PROFANITY_BLOCKLIST) { if (lower.includes(word)) return true; } return false; } export default defineEventHandler(async (event) => { const user = await requireUser(event); const query = getQuery(event); const raw = String(query.nickname ?? "").trim(); if (raw.length < 3) { return { success: true, data: { available: false, reason: "too_short" } }; } if (raw.length > 32) { return { success: true, data: { available: false, reason: "too_long" } }; } if (isProfanity(raw)) { return { success: true, data: { available: false, reason: "profanity" } }; } // Case-insensitive lookup. Eigener Nickname (= aktueller User) ist OK // — sonst kann User seinen eigenen Namen nicht "behalten". const db = usePrisma(); const existing = await db.profile.findFirst({ where: { nickname: { equals: raw, mode: "insensitive" }, id: { not: user.id }, deletedAt: null, }, select: { id: true }, }); if (existing) { return { success: true, data: { available: false, reason: "taken" } }; } return { success: true, data: { available: true } }; });