import { NextRequest, NextResponse } from 'next/server'; import { z } from 'zod'; import bcrypt from 'bcryptjs'; import { randomBytes } from 'crypto'; import { eq, count } from 'drizzle-orm'; import { db } from '@/lib/db'; import { users, verificationTokens } from '@/lib/db/schema'; import { sendEmail } from '@/lib/email'; import { verificationEmail } from '@/lib/email/templates'; import { isSignupsEnabled } from '@/lib/site-config'; const RegisterSchema = z.object({ email: z.string().email(), name: z.string().min(1).max(255), password: z.string().min(8).max(128), }); export async function POST(req: NextRequest): Promise { let body: unknown; try { body = await req.json(); } catch { return NextResponse.json({ error: 'Invalid JSON' }, { status: 400 }); } const signupsEnabled = await isSignupsEnabled(); if (!signupsEnabled) { return NextResponse.json({ error: 'Signups are currently disabled' }, { status: 403 }); } const parsed = RegisterSchema.safeParse(body); if (!parsed.success) { return NextResponse.json( { error: parsed.error.issues.map((i) => i.message).join('; ') }, { status: 400 }, ); } const { email, name, password } = parsed.data; const normalizedEmail = email.toLowerCase(); const [existing] = await db.select({ id: users.id }).from(users).where(eq(users.email, normalizedEmail)).limit(1); if (existing) { return NextResponse.json({ error: 'Email already registered' }, { status: 409 }); } const [{ total }] = await db.select({ total: count() }).from(users); const role = total === 0 ? 'admin' : 'learner'; const passwordHash = await bcrypt.hash(password, 12); const [user] = await db .insert(users) .values({ email: normalizedEmail, name, passwordHash, role }) .returning({ id: users.id }); const token = randomBytes(32).toString('hex'); const expires = new Date(Date.now() + 86_400_000); // 24 hours await db.insert(verificationTokens).values({ identifier: normalizedEmail, token, expires }); const { subject, html, text } = verificationEmail(token); void sendEmail({ to: normalizedEmail, subject, html, text }).catch(() => {}); return NextResponse.json({ userId: user.id }, { status: 201 }); }