Public Access
118 lines
3.0 KiB
TypeScript
118 lines
3.0 KiB
TypeScript
import { Router } from 'express'
|
|
import bcrypt from 'bcrypt'
|
|
import { validateBody } from '../middleware/validate.js'
|
|
import { asyncHandler } from '../utils/asyncHandler.js'
|
|
import { AppError } from '../middleware/error.js'
|
|
import { getDb } from '../db/database.js'
|
|
import * as userRepo from '../repositories/userRepo.js'
|
|
import * as settingsRepo from '../repositories/settingsRepo.js'
|
|
import { setupSchema } from '../validators/setupSchemas.js'
|
|
import { signToken } from '../utils/tokens.js'
|
|
|
|
const router = Router()
|
|
|
|
const SALT_ROUNDS = 10
|
|
|
|
function getSiteTitle(): string | null {
|
|
return settingsRepo.getSetting('site_name') ?? null
|
|
}
|
|
|
|
// GET /api/setup/status
|
|
router.get(
|
|
'/status',
|
|
asyncHandler(async (_req, res) => {
|
|
const title = getSiteTitle()
|
|
res.json({
|
|
completed: !!title,
|
|
siteTitle: title ?? undefined,
|
|
})
|
|
})
|
|
)
|
|
|
|
// POST /api/setup
|
|
router.post(
|
|
'/',
|
|
validateBody(setupSchema),
|
|
asyncHandler(async (req, res) => {
|
|
const db = getDb()
|
|
|
|
// Atomic transaction prevents race conditions between concurrent setup requests
|
|
const tx = db.transaction(() => {
|
|
const title = settingsRepo.getSetting('site_name')
|
|
if (title) {
|
|
throw new AppError(400, 'Setup already completed')
|
|
}
|
|
|
|
const userCount = userRepo.countUsers()
|
|
if (userCount > 0) {
|
|
throw new AppError(400, 'Users already exist')
|
|
}
|
|
|
|
const {
|
|
email,
|
|
password,
|
|
displayName,
|
|
siteTitle,
|
|
language,
|
|
smtpHost,
|
|
smtpPort,
|
|
smtpUser,
|
|
smtpPass,
|
|
smtpFrom,
|
|
smtpSecure,
|
|
appUrl,
|
|
} = req.body
|
|
|
|
const passwordHash = bcrypt.hashSync(password, SALT_ROUNDS)
|
|
|
|
const { id } = userRepo.createUser({
|
|
email,
|
|
passwordHash,
|
|
displayName,
|
|
role: 'admin',
|
|
emailVerified: 1,
|
|
})
|
|
|
|
const settings: Array<[string, string]> = [
|
|
['site_name', siteTitle?.trim() || 'BrainWave'],
|
|
['site_language', language || 'en'],
|
|
['maintenance_mode', 'false'],
|
|
]
|
|
|
|
if (smtpHost !== undefined) settings.push(['smtp_host', smtpHost])
|
|
if (smtpPort !== undefined) settings.push(['smtp_port', smtpPort])
|
|
if (smtpUser !== undefined) settings.push(['smtp_user', smtpUser])
|
|
if (smtpPass !== undefined) settings.push(['smtp_pass', smtpPass])
|
|
if (smtpFrom !== undefined) settings.push(['smtp_from', smtpFrom])
|
|
if (smtpSecure !== undefined) settings.push(['smtp_secure', smtpSecure])
|
|
if (appUrl !== undefined) settings.push(['app_url', appUrl])
|
|
|
|
settingsRepo.setSettingsBatch(settings)
|
|
|
|
return { id, email, displayName }
|
|
})
|
|
|
|
const { id, email, displayName } = tx()
|
|
|
|
const token = signToken({
|
|
userId: id,
|
|
email,
|
|
displayName,
|
|
role: 'admin',
|
|
})
|
|
|
|
res.status(201).json({
|
|
message: 'Setup completed successfully',
|
|
token,
|
|
user: {
|
|
id,
|
|
email,
|
|
displayName,
|
|
role: 'admin',
|
|
},
|
|
})
|
|
})
|
|
)
|
|
|
|
export default router
|