users.js 4.33 KB
/**
 * 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;