From 9b0603427e45422e278d5ba40e4ee93729bbf8e2 Mon Sep 17 00:00:00 2001 From: admin Date: Thu, 21 May 2026 14:29:49 +0200 Subject: [PATCH] Add users management route (GET, PATCH role/is_active, DELETE) Co-Authored-By: Claude Sonnet 4.6 --- src/index.js | 1 + src/routes/users.js | 62 +++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 63 insertions(+) create mode 100644 src/routes/users.js diff --git a/src/index.js b/src/index.js index 0197ae4..73afeb6 100644 --- a/src/index.js +++ b/src/index.js @@ -38,6 +38,7 @@ app.use('/api/blocklist', auth, require('./routes/blocklist')); app.use('/api/languages', auth, require('./routes/languages')); app.use('/api/user-names', auth, require('./routes/user-names')); app.use('/api/users-public', auth, require('./routes/users-public')); +app.use('/api/users', auth, require('./routes/users')); // 404 app.use((req, res) => { diff --git a/src/routes/users.js b/src/routes/users.js new file mode 100644 index 0000000..5f6f257 --- /dev/null +++ b/src/routes/users.js @@ -0,0 +1,62 @@ +const router = require('express').Router(); +const { query } = require('../db'); + +const ROLES = ['end-user', 'admin']; + +// GET /api/users +router.get('/', async (req, res, next) => { + try { + const { limit = 50, offset = 0 } = req.query; + const result = await query( + `SELECT id, email, role, is_active, created_at, updated_at + FROM users ORDER BY created_at DESC LIMIT $1 OFFSET $2`, + [Math.min(parseInt(limit), 500), parseInt(offset)] + ); + res.json(result.rows); + } catch (err) { next(err); } +}); + +// GET /api/users/:id +router.get('/:id', async (req, res, next) => { + try { + const result = await query( + `SELECT id, email, role, is_active, created_at, updated_at FROM users WHERE id = $1`, + [req.params.id] + ); + if (!result.rows.length) return res.status(404).json({ error: 'Not found' }); + res.json(result.rows[0]); + } catch (err) { next(err); } +}); + +// PATCH /api/users/:id +router.patch('/:id', async (req, res, next) => { + try { + const allowed = ['role', 'is_active']; + const fields = Object.keys(req.body).filter(k => allowed.includes(k)); + if (!fields.length) return res.status(400).json({ error: 'No valid fields provided' }); + + if (req.body.role && !ROLES.includes(req.body.role)) + return res.status(400).json({ error: `role must be one of: ${ROLES.join(', ')}` }); + + const setClauses = fields.map((f, i) => `${f} = $${i + 1}`).join(', '); + const values = [...fields.map(f => req.body[f]), req.params.id]; + const result = await query( + `UPDATE users SET ${setClauses} WHERE id = $${fields.length + 1} + RETURNING id, email, role, is_active, created_at, updated_at`, + values + ); + if (!result.rows.length) return res.status(404).json({ error: 'Not found' }); + res.json(result.rows[0]); + } catch (err) { next(err); } +}); + +// DELETE /api/users/:id +router.delete('/:id', async (req, res, next) => { + try { + const result = await query('DELETE FROM users WHERE id = $1 RETURNING id', [req.params.id]); + if (!result.rows.length) return res.status(404).json({ error: 'Not found' }); + res.status(204).end(); + } catch (err) { next(err); } +}); + +module.exports = router;