import { getUsers, getUserByEmail, getUserById, createUser, deleteUser } from 'officerdb'; import { createRouter } from '@@/create-router'; import { sign } from '@@/jwt'; import { USER_ROLES } from 'definitions'; import { sendMail } from 'emailer'; import * as errors from '@@/custom-errors'; import { originMiddleware } from '@@/_middlewares'; import { updateUserHandler } from './update-user'; export const usersRouter = createRouter(); usersRouter.use(originMiddleware); // List all users (Super Admin only) usersRouter.get('/', async (ctx) => { const user = ctx.get('user'); if (user.role !== 'Super Admin') throw errors.FORBIDDEN(); const users = await getUsers(); const sanitized = users.map(({ password, ...rest }) => rest); return ctx.json(sanitized); }); // Self-update (any authenticated user) usersRouter.put('/', updateUserHandler); // Invite a new user (Super Admin only) usersRouter.post('/invite', async (ctx) => { const reqUser = ctx.get('user'); if (reqUser.role !== 'Super Admin') throw errors.FORBIDDEN(); const { email, role } = ctx.get('body'); if (!email || typeof email !== 'string' || !/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email)) { throw errors.BAD_REQUEST('Invalid email address'); } const validRoles = USER_ROLES.filter((r) => r !== 'Super Admin'); const assignedRole = (typeof role === 'string' && validRoles.includes(role as (typeof validRoles)[number])) ? (role as (typeof USER_ROLES)[number]) : ('Member' as const); const existing = await getUserByEmail(email); if (existing) throw errors.CONFLICT('A user with this email already exists'); const dbUser = await createUser({ email, role: assignedRole, status: 'Invited', }); const origin = ctx.get('origin'); const verificationCode = await sign({ id: dbUser.id, email: dbUser.email }, '24h'); const url = `${origin}/auth/verify?verificationCode=${verificationCode}`; await sendMail({ template: 'UserInvite', subject: 'You have been invited to officer.dev', to: email, data: { invitedBy: reqUser.name ?? reqUser.email, url }, }); const { password, ...safeUser } = dbUser; return ctx.json(safeUser); }); // Resend invite (Super Admin only, status must be Invited) usersRouter.post('/:id/resend-invite', async (ctx) => { const reqUser = ctx.get('user'); if (reqUser.role !== 'Super Admin') throw errors.FORBIDDEN(); const id = Number(ctx.req.param('id')); if (!id || isNaN(id)) throw errors.BAD_REQUEST('Invalid user ID'); const target = await getUserById(id); if (!target) throw errors.NOT_FOUND('User not found'); if (target.status !== 'Invited') throw errors.BAD_REQUEST('User is not in Invited status'); const origin = ctx.get('origin'); const verificationCode = await sign({ id: target.id, email: target.email }, '24h'); const url = `${origin}/auth/verify?verificationCode=${verificationCode}`; await sendMail({ template: 'UserInvite', subject: 'You have been invited to officer.dev', to: target.email, data: { invitedBy: reqUser.name ?? reqUser.email, url }, }); return ctx.json({ ok: true }); }); // Delete a user (Super Admin only, cannot delete self) usersRouter.delete('/:id', async (ctx) => { const reqUser = ctx.get('user'); if (reqUser.role !== 'Super Admin') throw errors.FORBIDDEN(); const id = Number(ctx.req.param('id')); if (!id || isNaN(id)) throw errors.BAD_REQUEST('Invalid user ID'); if (id === reqUser.id) throw errors.BAD_REQUEST('Cannot delete yourself'); const target = await getUserById(id); if (!target) throw errors.NOT_FOUND('User not found'); await deleteUser(id); return ctx.json({ ok: true }); });