Phase1
This commit is contained in:
158
packages/database/src/cli/bootstrap-admin.ts
Normal file
158
packages/database/src/cli/bootstrap-admin.ts
Normal file
@@ -0,0 +1,158 @@
|
||||
import { PrismaClient } from '@prisma/client';
|
||||
|
||||
import {
|
||||
generateUsernameFromEmail,
|
||||
hashPassword,
|
||||
validateUsername,
|
||||
} from '@hexahost/auth';
|
||||
|
||||
const PLATFORM_ROLES = [
|
||||
'USER',
|
||||
'SUPPORT',
|
||||
'MODERATOR',
|
||||
'ADMIN',
|
||||
'SUPER_ADMIN',
|
||||
] as const;
|
||||
|
||||
interface CliArgs {
|
||||
email: string;
|
||||
password: string;
|
||||
username?: string;
|
||||
displayName?: string;
|
||||
}
|
||||
|
||||
function parseArgs(argv: string[]): CliArgs {
|
||||
const args: Record<string, string> = {};
|
||||
|
||||
for (let i = 0; i < argv.length; i += 1) {
|
||||
const key = argv[i];
|
||||
const value = argv[i + 1];
|
||||
if (key?.startsWith('--') && value) {
|
||||
args[key.slice(2)] = value;
|
||||
i += 1;
|
||||
}
|
||||
}
|
||||
|
||||
if (!args['email'] || !args['password']) {
|
||||
console.error(
|
||||
'Usage: bootstrap-admin --email <email> --password <password> [--username <name>] [--display-name <name>]',
|
||||
);
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
if (args['password'].length < 12) {
|
||||
console.error('Password must be at least 12 characters.');
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
return {
|
||||
email: args['email'].trim().toLowerCase(),
|
||||
password: args['password'],
|
||||
username: args['username'],
|
||||
displayName: args['display-name'],
|
||||
};
|
||||
}
|
||||
|
||||
async function ensurePlatformRoles(prisma: PrismaClient): Promise<void> {
|
||||
for (const name of PLATFORM_ROLES) {
|
||||
await prisma.platformRole.upsert({
|
||||
where: { name },
|
||||
create: { name, description: `${name} platform role` },
|
||||
update: {},
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
async function main(): Promise<void> {
|
||||
const input = parseArgs(process.argv.slice(2));
|
||||
const prisma = new PrismaClient();
|
||||
|
||||
try {
|
||||
await ensurePlatformRoles(prisma);
|
||||
|
||||
const existing = await prisma.user.findUnique({
|
||||
where: { email: input.email },
|
||||
});
|
||||
|
||||
if (existing) {
|
||||
const superAdminRole = await prisma.platformRole.findUnique({
|
||||
where: { name: 'SUPER_ADMIN' },
|
||||
});
|
||||
|
||||
if (!superAdminRole) {
|
||||
throw new Error('SUPER_ADMIN role missing after seed');
|
||||
}
|
||||
|
||||
await prisma.userPlatformRole.upsert({
|
||||
where: {
|
||||
userId_roleId: {
|
||||
userId: existing.id,
|
||||
roleId: superAdminRole.id,
|
||||
},
|
||||
},
|
||||
create: {
|
||||
userId: existing.id,
|
||||
roleId: superAdminRole.id,
|
||||
},
|
||||
update: {},
|
||||
});
|
||||
|
||||
console.log(`User ${input.email} already exists — SUPER_ADMIN role ensured.`);
|
||||
return;
|
||||
}
|
||||
|
||||
let username = input.username?.trim().toLowerCase();
|
||||
if (username) {
|
||||
const validation = validateUsername(username);
|
||||
if (!validation.valid) {
|
||||
throw new Error(validation.error ?? 'Invalid username');
|
||||
}
|
||||
} else {
|
||||
username = generateUsernameFromEmail(input.email);
|
||||
}
|
||||
|
||||
const passwordHash = await hashPassword(input.password);
|
||||
|
||||
const superAdminRole = await prisma.platformRole.findUniqueOrThrow({
|
||||
where: { name: 'SUPER_ADMIN' },
|
||||
});
|
||||
|
||||
const user = await prisma.$transaction(async (tx) => {
|
||||
const created = await tx.user.create({
|
||||
data: {
|
||||
email: input.email,
|
||||
username,
|
||||
displayName: input.displayName ?? 'Administrator',
|
||||
emailVerifiedAt: new Date(),
|
||||
credentials: {
|
||||
create: { passwordHash },
|
||||
},
|
||||
platformRoles: {
|
||||
create: { roleId: superAdminRole.id },
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
await tx.auditEvent.create({
|
||||
data: {
|
||||
userId: created.id,
|
||||
action: 'admin.bootstrap',
|
||||
entityType: 'user',
|
||||
entityId: created.id,
|
||||
metadata: { source: 'bootstrap-cli' },
|
||||
},
|
||||
});
|
||||
|
||||
return created;
|
||||
});
|
||||
|
||||
console.log(`Admin user created: ${user.email} (${user.id})`);
|
||||
} finally {
|
||||
await prisma.$disconnect();
|
||||
}
|
||||
}
|
||||
|
||||
void main().catch((error: unknown) => {
|
||||
console.error(error instanceof Error ? error.message : error);
|
||||
process.exit(1);
|
||||
});
|
||||
@@ -3,6 +3,10 @@ export type {
|
||||
User,
|
||||
UserCredential,
|
||||
UserSession,
|
||||
EmailVerificationToken,
|
||||
PasswordResetToken,
|
||||
TotpCredential,
|
||||
RecoveryCode,
|
||||
PlatformRole,
|
||||
UserPlatformRole,
|
||||
GameServer,
|
||||
|
||||
Reference in New Issue
Block a user