import express from 'express'; import * as userCtrl from '../controllers/userController.js'; const router = express.Router(); const USERNAME_MIN = 3; const USERNAME_MAX = 30; const DISPLAY_NAME_MAX = 100; const DEFAULT_LIMIT = 50; const MAX_LIMIT = 200; function isValidUsername(username) { if (typeof username !== 'string') { return false; } if (username.length < USERNAME_MIN || username.length > USERNAME_MAX) { return false; } const allowed = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_'; for (let i = 0; i < username.length; i++) { if (allowed.indexOf(username[i]) === -1) { return false; } } return true; } function isValidDisplayName(displayName) { if (displayName === undefined || displayName === null) { return true; } if (typeof displayName !== 'string') { return false; } return displayName.length <= DISPLAY_NAME_MAX; } // Search users (must be before /:id) router.get('/search', async function(req, res) { const q = req.query.q; if (typeof q !== 'string' || q.length < 1) { return res.status(400).json({ error: 'invalid_query' }); } const results = await userCtrl.searchUsers({ q: q, fields: ['username', 'displayName'] }); res.json(results); }); // List users with pagination router.get('/', async function(req, res) { let limit = parseInt(req.query.limit, 10); let offset = parseInt(req.query.offset, 10); if (isNaN(limit) || limit < 1) { limit = DEFAULT_LIMIT; } if (limit > MAX_LIMIT) { limit = MAX_LIMIT; } if (isNaN(offset) || offset < 0) { offset = 0; } const users = await userCtrl.listUsers({ limit: limit, offset: offset }); res.json(users); }); // Get user by id router.get('/:id', async function(req, res) { const user = await userCtrl.getUserById(req.params.id); if (!user) { return res.status(404).json({ error: 'not_found' }); } res.json(user); }); // Create user router.post('/', async function(req, res) { const username = req.body.username; const displayName = req.body.displayName; if (!isValidUsername(username)) { return res.status(400).json({ error: 'invalid_username' }); } if (!isValidDisplayName(displayName)) { return res.status(400).json({ error: 'invalid_display_name' }); } const user = await userCtrl.createUser({ username: username, displayName: displayName }); res.status(201).json(user); }); // Update user router.put('/:id', async function(req, res) { const username = req.body.username; const displayName = req.body.displayName; if (username !== undefined && !isValidUsername(username)) { return res.status(400).json({ error: 'invalid_username' }); } if (!isValidDisplayName(displayName)) { return res.status(400).json({ error: 'invalid_display_name' }); } const updates = {}; if (username !== undefined) { updates.username = username; } if (displayName !== undefined) { updates.displayName = displayName; } const user = await userCtrl.updateUser(req.params.id, updates); if (!user) { return res.status(404).json({ error: 'not_found' }); } res.json(user); }); // Delete user router.delete('/:id', async function(req, res) { const ok = await userCtrl.deleteUser(req.params.id); if (!ok) { return res.status(404).json({ error: 'not_found' }); } res.status(204).send(); }); // Login - get token by username router.post('/login', async function(req, res) { const username = req.body.username; if (!isValidUsername(username)) { return res.status(400).json({ error: 'invalid_username' }); } const user = await userCtrl.getUserByUsername(username); if (!user) { return res.status(404).json({ error: 'user_not_found' }); } res.json({ token: user.token, userId: user._id }); }); export default router;