import { z } from 'zod'; /** * Canonical username shape. * * - 2..30 characters (yes, 2 — initials like "dm" are real and the * director uses them) * - lowercase letters, digits, `.`, `_`, `-` * - case-insensitive uniqueness is enforced by a partial unique index on * LOWER(username) in migration 0054. * * The same regex lives on the DB CHECK constraint, so any insert that * slips past the API still gets rejected at the DB layer. */ export const USERNAME_REGEX = /^[a-z0-9._-]{2,30}$/; export const usernameSchema = z .string() .transform((s) => s.trim().toLowerCase()) .refine( (s) => USERNAME_REGEX.test(s), 'Use 2–30 lowercase letters, digits, dot, underscore, or hyphen.', ); /** Reserved names that the API rejects even if the regex would accept them. * Keeps obvious confusables out of customer-facing URLs / mentions. */ export const RESERVED_USERNAMES = new Set([ 'admin', 'administrator', 'root', 'system', 'support', 'noreply', 'no-reply', 'help', 'security', 'me', 'self', 'api', 'auth', 'login', 'logout', 'signin', 'signup', 'register', 'undefined', 'null', ]); export function isReservedUsername(username: string): boolean { return RESERVED_USERNAMES.has(username.trim().toLowerCase()); }