import { NextResponse } from "next/server"; import { randomUUID } from "crypto"; import { query, queryOne } from "@/lib/db-postgres"; import { ensureWorkspaceForUser } from "@/lib/workspaces"; import { hashPassword, createDbSession, SESSION_COOKIE_NAME, sessionCookieOptions, } from "@/lib/auth/password"; // POST /api/auth/register { email, password, name? } // Creates an email/password account: a NextAuth `users` row, the custom // `fs_users` row (with the scrypt password hash + workspace metadata, mirroring // the Google signIn callback), a Vibn workspace, and a database session — then // sets the session cookie so the user is signed in immediately. export async function POST(request: Request) { let body: { email?: unknown; password?: unknown; name?: unknown }; try { body = await request.json(); } catch { return NextResponse.json({ error: "Invalid request body." }, { status: 400 }); } const email = String(body.email ?? "").trim().toLowerCase(); const password = String(body.password ?? ""); const name = body.name ? String(body.name).trim() : null; if (!/^\S+@\S+\.\S+$/.test(email)) { return NextResponse.json( { error: "Enter a valid email address." }, { status: 400 }, ); } if (password.length < 8) { return NextResponse.json( { error: "Password must be at least 8 characters." }, { status: 400 }, ); } try { const existing = await queryOne<{ id: string }>( `SELECT id FROM users WHERE lower(email) = $1 LIMIT 1`, [email], ); if (existing) { return NextResponse.json( { error: "An account with this email already exists. Try signing in." }, { status: 409 }, ); } // 1. NextAuth user row (id is normally a cuid; any unique string works). const userId = randomUUID(); await query( `INSERT INTO users (id, name, email, email_verified) VALUES ($1, $2, $3, NOW())`, [userId, name, email], ); // 2. Custom fs_users row — same shape the Google signIn callback writes, // plus the password hash. const workspace = email.split("@")[0].replace(/[^a-z0-9]+/g, "-") + "-account"; const passwordHash = await hashPassword(password); const data = JSON.stringify({ email, name, image: null, workspace, passwordHash }); const fsRows = await query<{ id: string }>( `INSERT INTO fs_users (id, user_id, data) VALUES (gen_random_uuid()::text, $1, $2::jsonb) RETURNING id`, [userId, data], ); const fsUserId = fsRows[0].id; // 3. Ensure a Vibn workspace (no Coolify/Gitea provisioning yet — happens // lazily on first project create, same as the OAuth path). try { await ensureWorkspaceForUser({ userId: fsUserId, email, displayName: name, }); } catch (wsErr) { console.error("[register] ensureWorkspaceForUser failed:", wsErr); } // 4. Sign them in: create a DB session + set the cookie. const { token, expires } = await createDbSession(userId); const res = NextResponse.json({ ok: true }); res.cookies.set(SESSION_COOKIE_NAME, token, sessionCookieOptions(expires)); return res; } catch (err) { console.error("[register] exception:", err); return NextResponse.json( { error: "Could not create your account. Please try again." }, { status: 500 }, ); } }