deckhearth/api/admin/users.ts

219 lines
6.9 KiB
TypeScript
Raw Normal View History

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' },
});
}
const body = await req.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();
}
}