114 lines
4.6 KiB
JavaScript
114 lines
4.6 KiB
JavaScript
/**
|
|
* Group management routes (admin-only when AUTH_ENABLED=true)
|
|
*
|
|
* GET /api/v1/groups — list all groups
|
|
* POST /api/v1/groups — create group
|
|
* PATCH /api/v1/groups/:id — update group
|
|
* DELETE /api/v1/groups/:id — delete group
|
|
* GET /api/v1/groups/:id/members — list members
|
|
* POST /api/v1/groups/:id/members — add member { user_id }
|
|
* DELETE /api/v1/groups/:id/members/:uid — remove member
|
|
*/
|
|
import express from 'express';
|
|
import pool from '../db/pool.js';
|
|
import { requireAuth, requireAdmin } from '../middleware/auth.js';
|
|
|
|
const router = express.Router();
|
|
router.use(requireAuth, requireAdmin);
|
|
|
|
// ── List ──────────────────────────────────────────────────────
|
|
router.get('/', async (_req, res, next) => {
|
|
try {
|
|
const { rows } = await pool.query(
|
|
`SELECT g.id, g.name, g.description, g.created_at,
|
|
COUNT(ug.user_id)::int AS member_count
|
|
FROM groups g
|
|
LEFT JOIN user_groups ug ON ug.group_id = g.id
|
|
GROUP BY g.id
|
|
ORDER BY g.name`
|
|
);
|
|
res.json(rows);
|
|
} catch (err) { next(err); }
|
|
});
|
|
|
|
// ── Create ────────────────────────────────────────────────────
|
|
router.post('/', async (req, res, next) => {
|
|
try {
|
|
const { name, description } = req.body;
|
|
if (!name) return res.status(400).json({ error: 'name required' });
|
|
const { rows } = await pool.query(
|
|
`INSERT INTO groups (name, description) VALUES ($1, $2) RETURNING *`,
|
|
[name.trim(), description || null]
|
|
);
|
|
res.status(201).json(rows[0]);
|
|
} catch (err) {
|
|
if (err.code === '23505') return res.status(409).json({ error: 'Group name already exists' });
|
|
next(err);
|
|
}
|
|
});
|
|
|
|
// ── Update ────────────────────────────────────────────────────
|
|
router.patch('/:id', async (req, res, next) => {
|
|
try {
|
|
const { name, description } = req.body;
|
|
const sets = []; const vals = [];
|
|
if (name !== undefined) { sets.push(`name = $${sets.length + 1}`); vals.push(name); }
|
|
if (description !== undefined) { sets.push(`description = $${sets.length + 1}`); vals.push(description); }
|
|
if (!sets.length) return res.status(400).json({ error: 'Nothing to update' });
|
|
vals.push(req.params.id);
|
|
const { rows } = await pool.query(
|
|
`UPDATE groups SET ${sets.join(', ')} WHERE id = $${vals.length} RETURNING *`,
|
|
vals
|
|
);
|
|
if (!rows.length) return res.status(404).json({ error: 'Group not found' });
|
|
res.json(rows[0]);
|
|
} catch (err) { next(err); }
|
|
});
|
|
|
|
// ── Delete ────────────────────────────────────────────────────
|
|
router.delete('/:id', async (req, res, next) => {
|
|
try {
|
|
const { rowCount } = await pool.query('DELETE FROM groups WHERE id = $1', [req.params.id]);
|
|
if (!rowCount) return res.status(404).json({ error: 'Group not found' });
|
|
res.json({ message: 'Group deleted' });
|
|
} catch (err) { next(err); }
|
|
});
|
|
|
|
// ── Members ───────────────────────────────────────────────────
|
|
router.get('/:id/members', async (req, res, next) => {
|
|
try {
|
|
const { rows } = await pool.query(
|
|
`SELECT u.id, u.username, u.display_name, u.role
|
|
FROM user_groups ug
|
|
JOIN users u ON u.id = ug.user_id
|
|
WHERE ug.group_id = $1
|
|
ORDER BY u.username`,
|
|
[req.params.id]
|
|
);
|
|
res.json(rows);
|
|
} catch (err) { next(err); }
|
|
});
|
|
|
|
router.post('/:id/members', async (req, res, next) => {
|
|
try {
|
|
const { user_id } = req.body;
|
|
if (!user_id) return res.status(400).json({ error: 'user_id required' });
|
|
await pool.query(
|
|
`INSERT INTO user_groups (user_id, group_id) VALUES ($1, $2) ON CONFLICT DO NOTHING`,
|
|
[user_id, req.params.id]
|
|
);
|
|
res.status(201).json({ message: 'Member added' });
|
|
} catch (err) { next(err); }
|
|
});
|
|
|
|
router.delete('/:id/members/:uid', async (req, res, next) => {
|
|
try {
|
|
await pool.query(
|
|
`DELETE FROM user_groups WHERE group_id = $1 AND user_id = $2`,
|
|
[req.params.id, req.params.uid]
|
|
);
|
|
res.json({ message: 'Member removed' });
|
|
} catch (err) { next(err); }
|
|
});
|
|
|
|
export default router;
|