/** * User Management API — Admin only * * GET /api/users List users (search, filter, sort, pagination) * PUT /api/users/:id Update user (role, display_name) * DELETE /api/users/:id Delete user */ const express = require('express'); const router = express.Router(); const userDb = require('../services/user-db'); const { requireAdmin } = require('../middleware/jwt-auth'); // All routes require admin router.use(requireAdmin); /** * GET /api/users * Query: q (search), role, provider, sort (field), order (asc/desc), page, limit */ router.get('/', (req, res) => { try { const db = userDb.getDb(); const { q, role, provider, sort = 'created_at', order = 'desc', page = 1, limit = 50 } = req.query; let where = []; let params = []; if (q) { where.push('(email LIKE ? OR display_name LIKE ? OR github_login LIKE ?)'); params.push(`%${q}%`, `%${q}%`, `%${q}%`); } if (role) { where.push('role = ?'); params.push(role); } if (provider) { where.push('provider = ?'); params.push(provider); } const whereClause = where.length > 0 ? `WHERE ${where.join(' AND ')}` : ''; // Validate sort field const allowedSorts = ['id', 'email', 'display_name', 'role', 'provider', 'created_at', 'last_login']; const sortField = allowedSorts.includes(sort) ? sort : 'created_at'; const sortOrder = order === 'asc' ? 'ASC' : 'DESC'; const offset = (Math.max(1, parseInt(page)) - 1) * parseInt(limit); const lim = Math.min(200, Math.max(1, parseInt(limit))); const total = db.prepare(`SELECT COUNT(*) as count FROM users ${whereClause}`).get(...params).count; const users = db.prepare( `SELECT id, email, display_name, avatar_url, provider, provider_id, github_login, role, api_key, created_at, last_login FROM users ${whereClause} ORDER BY ${sortField} ${sortOrder} LIMIT ? OFFSET ?` ).all(...params, lim, offset); res.json({ users, pagination: { total, page: parseInt(page), limit: lim, pages: Math.ceil(total / lim), } }); } catch (err) { console.error('User list error:', err); res.status(500).json({ error: 'Failed to fetch users' }); } }); /** * PUT /api/users/:id */ router.put('/:id', (req, res) => { try { const db = userDb.getDb(); const userId = parseInt(req.params.id); const user = userDb.getUserById(userId); if (!user) { return res.status(404).json({ error: 'User not found' }); } // Prevent admin from removing their own admin role if (userId === req.user.id && req.body.role && req.body.role !== 'admin') { return res.status(400).json({ error: 'Cannot remove your own admin role' }); } const updates = []; const params = []; if (req.body.role !== undefined) { if (!['user', 'admin'].includes(req.body.role)) { return res.status(400).json({ error: 'Invalid role. Must be "user" or "admin".' }); } updates.push('role = ?'); params.push(req.body.role); } if (req.body.display_name !== undefined) { updates.push('display_name = ?'); params.push(req.body.display_name); } if (updates.length === 0) { return res.status(400).json({ error: 'No fields to update' }); } params.push(userId); db.prepare(`UPDATE users SET ${updates.join(', ')} WHERE id = ?`).run(...params); const updated = userDb.getUserById(userId); res.json({ user: userDb.toPublicUser(updated) }); } catch (err) { console.error('User update error:', err); res.status(500).json({ error: 'Failed to update user' }); } }); /** * DELETE /api/users/:id */ router.delete('/:id', (req, res) => { try { const db = userDb.getDb(); const userId = parseInt(req.params.id); // Prevent self-deletion if (userId === req.user.id) { return res.status(400).json({ error: 'Cannot delete your own account' }); } const user = userDb.getUserById(userId); if (!user) { return res.status(404).json({ error: 'User not found' }); } db.prepare('DELETE FROM users WHERE id = ?').run(userId); res.json({ success: true, message: `User ${user.email} deleted` }); } catch (err) { console.error('User delete error:', err); res.status(500).json({ error: 'Failed to delete user' }); } }); module.exports = router;