import { NextRequest, NextResponse } from 'next/server'; import { Pool } from 'pg'; import jwt from 'jsonwebtoken'; const pool = new Pool({ connectionString: process.env.DATABASE_URL, ssl: process.env.NODE_ENV === 'production' ? { rejectUnauthorized: false } : false, }); // Middleware to verify admin access async function verifyAdmin(req: NextRequest) { const authHeader = req.headers.get('authorization'); if (!authHeader?.startsWith('Bearer ')) { throw new Error('No token provided'); } const token = authHeader.substring(7); const jwtSecret = process.env.JWT_SECRET || 'fallback-secret-change-in-production'; try { const decoded = jwt.verify(token, jwtSecret) as any; // Check if user has admin role if (!decoded.roles?.includes('admin')) { throw new Error('Admin access required'); } return decoded; } catch (error) { throw new Error('Invalid token or insufficient permissions'); } } export default async function handler(req: NextRequest) { const client = await pool.connect(); try { // Verify admin access await verifyAdmin(req); if (req.method === 'GET') { // Get all users with their roles const { searchParams } = new URL(req.url); const page = parseInt(searchParams.get('page') || '1'); const limit = parseInt(searchParams.get('limit') || '20'); const search = searchParams.get('search') || ''; const offset = (page - 1) * limit; let whereClause = ''; let queryParams: any[] = [limit, offset]; if (search) { whereClause = 'WHERE u.username ILIKE $3 OR u.email ILIKE $3 OR u.first_name ILIKE $3 OR u.last_name ILIKE $3'; queryParams.push(`%${search}%`); } const usersResult = await client.query(` SELECT u.id, u.username, u.email, u.first_name, u.last_name, u.is_active, u.email_verified, u.created_at, u.last_login, array_agg(r.name) as roles FROM users u LEFT JOIN user_roles ur ON u.id = ur.user_id LEFT JOIN roles r ON ur.role_id = r.id ${whereClause} GROUP BY u.id, u.username, u.email, u.first_name, u.last_name, u.is_active, u.email_verified, u.created_at, u.last_login ORDER BY u.created_at DESC LIMIT $1 OFFSET $2 `, queryParams); // Get total count for pagination const countResult = await client.query(` SELECT COUNT(DISTINCT u.id) as total FROM users u ${whereClause.replace('$3', search ? '$1' : '')} `, search ? [`%${search}%`] : []); return new NextResponse(JSON.stringify({ success: true, users: usersResult.rows.map(user => ({ ...user, roles: user.roles.filter(Boolean) // Remove null values })), pagination: { page, limit, total: parseInt(countResult.rows[0].total), totalPages: Math.ceil(countResult.rows[0].total / limit) } }), { status: 200, headers: { 'Content-Type': 'application/json' }, }); } else if (req.method === 'PUT') { // Update user (activate/deactivate, change roles) const { searchParams } = new URL(req.url); const userId = searchParams.get('id'); if (!userId) { return new NextResponse(JSON.stringify({ error: 'User ID required' }), { status: 400, headers: { 'Content-Type': 'application/json' }, }); } // Parse request body let body: { isActive?: boolean; roles?: string[] }; 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 { isActive, roles } = body; // Update user status if (typeof isActive === 'boolean') { await client.query( 'UPDATE users SET is_active = $1, updated_at = CURRENT_TIMESTAMP WHERE id = $2', [isActive, userId] ); } // Update user roles if (roles && Array.isArray(roles)) { // Remove existing roles await client.query('DELETE FROM user_roles WHERE user_id = $1', [userId]); // Add new roles for (const roleName of roles) { const roleResult = await client.query('SELECT id FROM roles WHERE name = $1', [roleName]); if (roleResult.rows.length > 0) { await client.query( 'INSERT INTO user_roles (user_id, role_id) VALUES ($1, $2)', [userId, roleResult.rows[0].id] ); } } } // Get updated user data const updatedUser = await client.query(` SELECT u.id, u.username, u.email, u.first_name, u.last_name, u.is_active, u.email_verified, u.created_at, u.last_login, array_agg(r.name) as roles FROM users u LEFT JOIN user_roles ur ON u.id = ur.user_id LEFT JOIN roles r ON ur.role_id = r.id WHERE u.id = $1 GROUP BY u.id, u.username, u.email, u.first_name, u.last_name, u.is_active, u.email_verified, u.created_at, u.last_login `, [userId]); return new NextResponse(JSON.stringify({ success: true, message: 'User updated successfully', user: { ...updatedUser.rows[0], roles: updatedUser.rows[0].roles.filter(Boolean) } }), { status: 200, headers: { 'Content-Type': 'application/json' }, }); } else if (req.method === 'DELETE') { // Delete user (soft delete by deactivating) const { searchParams } = new URL(req.url); const userId = searchParams.get('id'); if (!userId) { return new NextResponse(JSON.stringify({ error: 'User ID required' }), { status: 400, headers: { 'Content-Type': 'application/json' }, }); } await client.query( 'UPDATE users SET is_active = false, updated_at = CURRENT_TIMESTAMP WHERE id = $1', [userId] ); return new NextResponse(JSON.stringify({ success: true, message: 'User deactivated successfully' }), { status: 200, headers: { 'Content-Type': 'application/json' }, }); } else { return new NextResponse(JSON.stringify({ error: 'Method not allowed' }), { status: 405, headers: { 'Content-Type': 'application/json' }, }); } } catch (error) { console.error('Admin users API error:', error); if ((error as Error).message.includes('Admin access required') || (error as Error).message.includes('No token provided') || (error as Error).message.includes('Invalid token')) { return new NextResponse(JSON.stringify({ error: 'Unauthorized', message: (error as Error).message }), { status: 401, headers: { 'Content-Type': 'application/json' }, }); } return new NextResponse(JSON.stringify({ error: 'Internal server error', details: process.env.NODE_ENV === 'development' ? (error as Error).message : undefined }), { status: 500, headers: { 'Content-Type': 'application/json' }, }); } finally { client.release(); } }