d26df78c70
Build & Push / Pipeline Tests (push) Successful in 1m52s
Test / Type Check (all packages) (push) Successful in 52s
Build & Push / Build & Push Docker Image (push) Successful in 25s
Test / API Unit Tests (push) Successful in 1m6s
Test / Homepage Unit Tests (push) Successful in 45s
Test / Carplace Unit Tests (push) Successful in 44s
Test / Admin Unit Tests (push) Successful in 42s
Test / Dashboard Unit Tests (push) Successful in 42s
Test / API Integration Tests (push) Successful in 1m7s
117 lines
3.4 KiB
JavaScript
117 lines
3.4 KiB
JavaScript
#!/usr/bin/env node
|
|
|
|
const bcrypt = require('bcryptjs')
|
|
const path = require('node:path')
|
|
const {
|
|
ensureDatabaseUrl,
|
|
loadDefaultEnvFiles,
|
|
normalizeDatabaseUrlForExecution,
|
|
} = require('../packages/database/src/runtime-config')
|
|
|
|
const ADMIN_ROLES = new Set(['SUPER_ADMIN', 'ADMIN', 'SUPPORT', 'FINANCE', 'VIEWER'])
|
|
|
|
function readArg(name) {
|
|
const prefix = `--${name}=`
|
|
const match = process.argv.slice(2).find((arg) => arg.startsWith(prefix))
|
|
return match ? match.slice(prefix.length) : undefined
|
|
}
|
|
|
|
function readValue(argName, envName, fallback) {
|
|
return readArg(argName) ?? process.env[envName] ?? fallback
|
|
}
|
|
|
|
function requireValue(argName, envName) {
|
|
const value = readValue(argName, envName)
|
|
if (!value || !value.trim()) {
|
|
throw new Error(`Missing required value. Provide --${argName}=... or ${envName}=...`)
|
|
}
|
|
return value.trim()
|
|
}
|
|
|
|
function readBoolean(argName, envName, fallback = false) {
|
|
const value = readValue(argName, envName)
|
|
if (value === undefined) return fallback
|
|
return ['1', 'true', 'yes', 'y'].includes(String(value).toLowerCase())
|
|
}
|
|
|
|
async function main() {
|
|
loadDefaultEnvFiles(path.resolve(__dirname, '..'))
|
|
ensureDatabaseUrl()
|
|
normalizeDatabaseUrlForExecution()
|
|
|
|
const email = requireValue('email', 'ADMIN_EMAIL').toLowerCase()
|
|
const checkOnly = process.argv.includes('--check')
|
|
|
|
const { PrismaClient } = require('../packages/database/generated')
|
|
const prisma = new PrismaClient()
|
|
try {
|
|
const existing = await prisma.adminUser.findUnique({ where: { email } })
|
|
|
|
if (checkOnly) {
|
|
console.log(existing ? 'EXISTS' : 'MISSING')
|
|
process.exitCode = existing ? 0 : 1
|
|
return
|
|
}
|
|
|
|
const password = requireValue('password', 'ADMIN_PASSWORD')
|
|
const firstName = readValue('first-name', 'ADMIN_FIRST_NAME', 'Admin').trim()
|
|
const lastName = readValue('last-name', 'ADMIN_LAST_NAME', 'User').trim()
|
|
const role = readValue('role', 'ADMIN_ROLE', 'SUPER_ADMIN').trim().toUpperCase()
|
|
const updateExisting = readBoolean('update-existing', 'ADMIN_UPDATE_EXISTING', false)
|
|
|
|
if (!ADMIN_ROLES.has(role)) {
|
|
throw new Error(`Invalid ADMIN_ROLE "${role}". Use one of: ${Array.from(ADMIN_ROLES).join(', ')}`)
|
|
}
|
|
|
|
if (password.length < 8) {
|
|
throw new Error('Admin password must be at least 8 characters long.')
|
|
}
|
|
|
|
const passwordHash = await bcrypt.hash(password, 12)
|
|
|
|
if (existing) {
|
|
if (!updateExisting) {
|
|
console.log(`Admin user already exists: ${email}`)
|
|
console.log('Set ADMIN_UPDATE_EXISTING=true or pass --update-existing=true to update the name, role, password, and active status.')
|
|
return
|
|
}
|
|
|
|
const admin = await prisma.adminUser.update({
|
|
where: { email },
|
|
data: {
|
|
firstName,
|
|
lastName,
|
|
role,
|
|
passwordHash,
|
|
isActive: true,
|
|
passwordResetToken: null,
|
|
passwordResetExpiresAt: null,
|
|
},
|
|
})
|
|
|
|
console.log(`Updated ${admin.role}: ${admin.email} (id: ${admin.id})`)
|
|
return
|
|
}
|
|
|
|
const admin = await prisma.adminUser.create({
|
|
data: {
|
|
email,
|
|
firstName,
|
|
lastName,
|
|
passwordHash,
|
|
role,
|
|
isActive: true,
|
|
},
|
|
})
|
|
|
|
console.log(`Created ${admin.role}: ${admin.email} (id: ${admin.id})`)
|
|
} finally {
|
|
await prisma.$disconnect()
|
|
}
|
|
}
|
|
|
|
main().catch((error) => {
|
|
console.error(error.message || error)
|
|
process.exit(1)
|
|
})
|