From 244bea372ecb2927cbae323d26563e47d25a1c32 Mon Sep 17 00:00:00 2001 From: Thibault Pouch Date: Mon, 2 Mar 2026 09:50:44 +0100 Subject: [PATCH] feat: add user creation endpoint with validation and error handling --- nest-backend/src/routes/users.ts | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/nest-backend/src/routes/users.ts b/nest-backend/src/routes/users.ts index c5c4fbe..d1ca9bc 100644 --- a/nest-backend/src/routes/users.ts +++ b/nest-backend/src/routes/users.ts @@ -25,6 +25,32 @@ router.get('/', authenticate, requireAdmin, async (_req: Request, res: Response) res.json(users.map(safeUser)); }); +// POST /api/users — create a staff account (admin only) +router.post('/', authenticate, requireAdmin, async (req: Request, res: Response): Promise => { + const schema = z.object({ + username: z.string().min(2).max(32), + email: z.string().email(), + password: z.string().min(6), + role: z.enum(['dev', 'com']), + }); + const parsed = schema.safeParse(req.body); + if (!parsed.success) { res.status(400).json({ error: parsed.error.flatten() }); return; } + + const { username, email, password, role } = parsed.data; + + const emailTaken = await prisma.user.findUnique({ where: { email: email.toLowerCase() } }); + if (emailTaken) { res.status(409).json({ error: 'An account with this email already exists.' }); return; } + + const usernameTaken = await prisma.user.findUnique({ where: { username } }); + if (usernameTaken) { res.status(409).json({ error: 'This username is already taken.' }); return; } + + const hashed = await bcrypt.hash(password, 10); + const user = await prisma.user.create({ + data: { username, email: email.toLowerCase(), password: hashed, role }, + }); + res.status(201).json(safeUser(user)); +}); + // GET /api/users/me/profile — current user profile router.get('/me/profile', authenticate, async (req: Request, res: Response): Promise => { const user = await prisma.user.findUnique({ where: { id: req.user!.userId } });