export function normalizeEmail(value: unknown): string | null { if (typeof value !== "string") { return null; } const email = value.trim().toLowerCase(); if (!email.includes("@") || email.length > 254) { return null; } return email; } export function normalizePassword(value: unknown): string | null { if (typeof value !== "string") { return null; } if (value.length < 10 || value.length > 128) { return null; } return value; } export function normalizeDisplayName(value: unknown): string | null { if (value === null || value === undefined || value === "") { return null; } if (typeof value !== "string") { return null; } const displayName = value.trim(); if (displayName.length < 1 || displayName.length > 80) { return null; } return displayName; } export function normalizeUserRole(value: unknown): "ADMIN" | "USER" | null { if (value === "ADMIN" || value === "USER") { return value; } return null; } export function normalizeDashboardTitle(value: unknown): string | null { if (typeof value !== "string") { return null; } const title = value.trim(); if (title.length < 1 || title.length > 80) { return null; } return title; } export function normalizeOptionalDashboardSubtitle(value: unknown): string | null { if (value === null || value === undefined || value === "") { return null; } if (typeof value !== "string") { return null; } const subtitle = value.trim(); if (subtitle.length > 120) { return null; } return subtitle.length > 0 ? subtitle : null; } export function normalizeThemeColor(value: unknown): string | null { if (typeof value !== "string") { return null; } const color = value.trim(); const shortHexMatch = /^#([0-9a-fA-F]{3})$/.exec(color); if (shortHexMatch) { const [r, g, b] = shortHexMatch[1].split(""); return `#${r}${r}${g}${g}${b}${b}`.toLowerCase(); } if (/^#[0-9a-fA-F]{6}$/.test(color)) { return color.toLowerCase(); } return null; } export function normalizeOptionalLogoUrl(value: unknown): string | null { if (value === null || value === undefined || value === "") { return null; } if (typeof value !== "string") { return null; } const trimmed = value.trim(); if (trimmed.length < 1 || trimmed.length > 2048) { return null; } if (trimmed.startsWith("/") && !trimmed.startsWith("//")) { return trimmed; } try { const url = new URL(trimmed); if (url.protocol !== "http:" && url.protocol !== "https:") { return null; } return url.toString(); } catch { return null; } } export function normalizeOptionalImageUrl(value: unknown): string | null { if (value === null || value === undefined || value === "") { return null; } if (typeof value !== "string") { return null; } const trimmed = value.trim(); if (trimmed.length < 1 || trimmed.length > 2048) { return null; } if (trimmed.startsWith("/") && !trimmed.startsWith("//")) { return trimmed; } try { const url = new URL(trimmed); if (url.protocol !== "http:" && url.protocol !== "https:") { return null; } return url.toString(); } catch { return null; } } export function normalizeTitle(value: unknown): string | null { if (typeof value !== "string") { return null; } const title = value.trim(); if (title.length < 1 || title.length > 80) { return null; } return title; } export function normalizeUrl(value: unknown): string | null { if (typeof value !== "string") { return null; } const trimmed = value.trim(); if (trimmed.length < 3 || trimmed.length > 2048) { return null; } const withProtocol = trimmed.startsWith("http://") || trimmed.startsWith("https://") ? trimmed : `https://${trimmed}`; try { const url = new URL(withProtocol); if (url.protocol !== "http:" && url.protocol !== "https:") { return null; } return url.toString(); } catch { return null; } } export function normalizeOptionalUrl(value: unknown): string | null { if (value === null || value === undefined || value === "") { return null; } return normalizeUrl(value); } export function normalizePositiveInteger(value: unknown, fallback: number, min: number, max: number): number { const parsed = typeof value === "number" ? value : Number.parseInt(String(value), 10); if (!Number.isFinite(parsed) || parsed < min || parsed > max) { return fallback; } return parsed; }