import { NextRequest, NextResponse } from 'next/server'; import { Pool } from 'pg'; import bcrypt from 'bcryptjs'; import jwt from 'jsonwebtoken'; const pool = new Pool({ connectionString: process.env.DATABASE_URL, ssl: process.env.NODE_ENV === 'production' ? { rejectUnauthorized: false } : false, }); interface RegisterRequest { username: string; email: string; password: string; firstName?: string; lastName?: string; } export default async function handler(req: NextRequest) { if (req.method !== 'POST') { return new NextResponse(JSON.stringify({ error: 'Method not allowed' }), { status: 405, headers: { 'Content-Type': 'application/json' }, }); } // Check environment variables if (!process.env.DATABASE_URL) { return new NextResponse(JSON.stringify({ error: 'Database configuration missing', details: 'DATABASE_URL environment variable not set' }), { status: 500, headers: { 'Content-Type': 'application/json' }, }); } if (!process.env.JWT_SECRET) { console.warn('JWT_SECRET not set, using fallback'); } const client = await pool.connect(); try { // Parse request body let body: RegisterRequest; try { const bodyText = await req.text(); body = JSON.parse(bodyText); } catch (parseError) { return new NextResponse(JSON.stringify({ error: 'Invalid JSON in request body' }), { status: 400, headers: { 'Content-Type': 'application/json' }, }); } const { username, email, password, firstName, lastName } = body; // Validate input if (!username || !email || !password) { return new NextResponse(JSON.stringify({ error: 'Username, email, and password are required' }), { status: 400, headers: { 'Content-Type': 'application/json' }, }); } if (password.length < 6) { return new NextResponse(JSON.stringify({ error: 'Password must be at least 6 characters long' }), { status: 400, headers: { 'Content-Type': 'application/json' }, }); } // Check if users table exists const tableCheck = await client.query(` SELECT EXISTS ( SELECT FROM information_schema.tables WHERE table_schema = 'public' AND table_name = 'users' ); `); if (!tableCheck.rows[0].exists) { return new NextResponse(JSON.stringify({ error: 'Database not initialized', details: 'Please run the setup-auth endpoint first' }), { status: 500, headers: { 'Content-Type': 'application/json' }, }); } // Check if user already exists const existingUser = await client.query( 'SELECT id FROM users WHERE username = $1 OR email = $2', [username, email] ); if (existingUser.rows.length > 0) { return new NextResponse(JSON.stringify({ error: 'Username or email already exists' }), { status: 409, headers: { 'Content-Type': 'application/json' }, }); } // Hash password const saltRounds = 12; const passwordHash = await bcrypt.hash(password, saltRounds); // Create user const userResult = await client.query(` INSERT INTO users (username, email, password_hash, first_name, last_name) VALUES ($1, $2, $3, $4, $5) RETURNING id, username, email, first_name, last_name, created_at `, [username, email, passwordHash, firstName || null, lastName || null]); const user = userResult.rows[0]; // Assign default 'user' role const roleResult = await client.query( 'SELECT id FROM roles WHERE name = $1', ['user'] ); if (roleResult.rows.length > 0) { await client.query( 'INSERT INTO user_roles (user_id, role_id) VALUES ($1, $2)', [user.id, roleResult.rows[0].id] ); } // Generate JWT token const jwtSecret = process.env.JWT_SECRET || 'fallback-secret-change-in-production'; const token = jwt.sign( { userId: user.id, username: user.username, email: user.email }, jwtSecret, { expiresIn: '7d' } ); // Store session const tokenHash = await bcrypt.hash(token, 10); const expiresAt = new Date(Date.now() + 7 * 24 * 60 * 60 * 1000); // 7 days await client.query(` INSERT INTO user_sessions (user_id, token_hash, expires_at, user_agent, ip_address) VALUES ($1, $2, $3, $4, $5) `, [ user.id, tokenHash, expiresAt, req.headers.get('user-agent') || null, req.headers.get('x-forwarded-for') || req.headers.get('x-real-ip') || null ]); // Get user roles for response const userRoles = await client.query(` SELECT r.name, r.description FROM roles r JOIN user_roles ur ON r.id = ur.role_id WHERE ur.user_id = $1 `, [user.id]); return new NextResponse(JSON.stringify({ success: true, message: 'User registered successfully', user: { id: user.id, username: user.username, email: user.email, firstName: user.first_name, lastName: user.last_name, roles: userRoles.rows.map(r => r.name), createdAt: user.created_at }, token }), { status: 201, headers: { 'Content-Type': 'application/json' }, }); } catch (error) { console.error('Registration error:', error); return new NextResponse(JSON.stringify({ error: 'Registration failed', details: process.env.NODE_ENV === 'development' ? (error as Error).message : undefined }), { status: 500, headers: { 'Content-Type': 'application/json' }, }); } finally { client.release(); } }