f0397740d7
Usernames are auto-assigned at signup (shipped earlier this session) but there was still no way to change one. Adds a Username field to Settings, validated client- and server-side against the same 3-20-char lowercase/digits/underscore pattern used for generation, with a 409 on collision (excluding the user's own current username). Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
45 lines
1.5 KiB
TypeScript
45 lines
1.5 KiB
TypeScript
import { db, users, eq } from "@epicure/db";
|
|
|
|
export const USERNAME_PATTERN = /^[a-z0-9_]{3,20}$/;
|
|
|
|
/** Lowercase alnum-and-underscore slug, at least 3 chars, at most 20. */
|
|
function slugify(seed: string): string {
|
|
const base = seed
|
|
.split("@")[0]! // if seed is an email, drop the domain
|
|
.toLowerCase()
|
|
.replace(/[^a-z0-9_]/g, "")
|
|
.slice(0, 20);
|
|
return base.length >= 3 ? base : `${base}user`.slice(0, 20);
|
|
}
|
|
|
|
/**
|
|
* Every user needs a username — it's the only thing profile pages, follows,
|
|
* and people-search key on, but there's no signup-time or settings UI to set
|
|
* one. Called from the user.create.before hook (lib/auth/server.ts) so every
|
|
* account gets one automatically, generated from their name or email.
|
|
*/
|
|
export async function generateUniqueUsername(seed: string): Promise<string> {
|
|
const base = slugify(seed);
|
|
let candidate = base;
|
|
let suffix = 0;
|
|
|
|
while (true) {
|
|
const existing = await db.query.users.findFirst({
|
|
where: eq(users.username, candidate),
|
|
columns: { id: true },
|
|
});
|
|
if (!existing) return candidate;
|
|
suffix += 1;
|
|
candidate = `${base}${suffix}`.slice(0, 20);
|
|
}
|
|
}
|
|
|
|
/** True if `candidate` belongs to some other user — a user re-saving their own current username is not a conflict. */
|
|
export async function isUsernameTaken(candidate: string, excludeUserId: string): Promise<boolean> {
|
|
const existing = await db.query.users.findFirst({
|
|
where: eq(users.username, candidate),
|
|
columns: { id: true },
|
|
});
|
|
return !!existing && existing.id !== excludeUserId;
|
|
}
|