You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 

86 lines
2.3 KiB

import "./env";
import { hash } from "bcryptjs";
import { eq, sql } from "drizzle-orm";
import { dbGlobal } from "./lib/db";
import { users } from "./lib/schema/auth";
/** Match `server/service/auth/index.ts` */
const USERNAME_REGEX = /^[a-zA-Z0-9_]{3,20}$/;
const MIN_PASSWORD_LENGTH = 6;
/**
* Public slug is optional: use lowercased username only if it satisfies a URL-style slug
* (lowercase alphanumeric only, 3–20 chars). Usernames with underscores or mixed rules
* that do not fit stay null; the admin can set `publicSlug` later via profile APIs.
*/
const PUBLIC_SLUG_REGEX = /^[a-z0-9]{3,20}$/;
function derivePublicSlug(username: string): string | null {
const lower = username.toLowerCase();
return PUBLIC_SLUG_REGEX.test(lower) ? lower : null;
}
async function getNextUserId() {
const [row] = await dbGlobal
.select({
maxId: sql<number>`COALESCE(MAX(${users.id}), 0)`,
})
.from(users);
return (row?.maxId ?? 0) + 1;
}
async function main() {
const [existingAdmin] = await dbGlobal
.select({ id: users.id })
.from(users)
.where(eq(users.role, "admin"))
.limit(1);
if (existingAdmin) {
console.log("Bootstrap skipped: admin exists");
process.exit(0);
}
const username = process.env.BOOTSTRAP_ADMIN_USERNAME;
const password = process.env.BOOTSTRAP_ADMIN_PASSWORD;
if (!username || !password) {
console.warn(
"Bootstrap skipped: set BOOTSTRAP_ADMIN_USERNAME and BOOTSTRAP_ADMIN_PASSWORD",
);
process.exit(0);
}
if (!USERNAME_REGEX.test(username) || password.length < MIN_PASSWORD_LENGTH) {
console.warn(
"Bootstrap skipped: invalid BOOTSTRAP_ADMIN_USERNAME or BOOTSTRAP_ADMIN_PASSWORD",
);
process.exit(0);
}
const passwordHash = await hash(password, 10);
const userId = await getNextUserId();
const publicSlug = derivePublicSlug(username);
try {
await dbGlobal.insert(users).values({
id: userId,
username,
password: passwordHash,
role: "admin",
status: "active",
publicSlug,
});
console.log("Bootstrap complete: admin user created");
} catch (err) {
console.warn("Bootstrap skipped: could not insert admin (unique conflict or DB error)", err);
process.exit(0);
}
process.exit(0);
}
main().catch((e) => {
console.error(e);
process.exit(1);
});