diff options
| author | zwlucas <lucas.fariamo08@gmail.com> | 2026-07-25 21:23:29 +0000 |
|---|---|---|
| committer | zwlucas <lucas.fariamo08@gmail.com> | 2026-07-25 21:23:29 +0000 |
| commit | ef55d9bb24430610b60c1b77821307cd72c08c55 (patch) | |
| tree | e6935a810b34370b0a4c821e55f6397aac50c832 /api/src/modules/users | |
| download | helpdesk-master.tar.gz helpdesk-master.zip | |
Signed-off-by: zwlucas <lucas.fariamo08@gmail.com>
Diffstat (limited to 'api/src/modules/users')
| -rw-r--r-- | api/src/modules/users/users.controller.ts | 129 | ||||
| -rw-r--r-- | api/src/modules/users/users.routes.ts | 56 | ||||
| -rw-r--r-- | api/src/modules/users/users.schema.ts | 25 |
3 files changed, 210 insertions, 0 deletions
diff --git a/api/src/modules/users/users.controller.ts b/api/src/modules/users/users.controller.ts new file mode 100644 index 0000000..c3c06fd --- /dev/null +++ b/api/src/modules/users/users.controller.ts @@ -0,0 +1,129 @@ +import type { FastifyRequest, FastifyReply } from 'fastify'; +import bcrypt from 'bcryptjs'; +import { users } from '@/db/schema.js'; +import { eq, and, ne } from 'drizzle-orm'; +import type { CreateUserByAdminBody, UpdateUserBody } from './users.schema.js'; + +export async function listUsersHandler(request: FastifyRequest, reply: FastifyReply) { + const db = request.server.db; + const allUsers = await db.query.users.findMany({ + columns: { + password: false, // Don't return passwords + }, + orderBy: (users, { desc }) => [desc(users.createdAt)], + }); + + return reply.send(allUsers); +} + +export async function createUserHandler( + request: FastifyRequest<{ Body: CreateUserByAdminBody }>, + reply: FastifyReply, +) { + const db = request.server.db; + const { name, email, password, role } = request.body; + + // Verifica se email já existe + const existing = await db.query.users.findFirst({ + where: eq(users.email, email), + }); + + if (existing) { + return reply.conflict('Este e-mail já está cadastrado.'); + } + + const hashedPassword = await bcrypt.hash(password, 12); + + const [newUser] = await db.insert(users).values({ + name, + email, + password: hashedPassword, + role, + isActive: true, + }).returning({ + id: users.id, + name: users.name, + email: users.email, + role: users.role, + isActive: users.isActive, + createdAt: users.createdAt, + }); + + return reply.code(201).send(newUser); +} + +export async function updateUserHandler( + request: FastifyRequest<{ Params: { id: number }; Body: UpdateUserBody }>, + reply: FastifyReply, +) { + const db = request.server.db; + const { id } = request.params; + const { name, email, password, role, isActive } = request.body; + + const existingUser = await db.query.users.findFirst({ + where: eq(users.id, id), + }); + + if (!existingUser) { + return reply.notFound('Usuário não encontrado.'); + } + + if (email && email !== existingUser.email) { + const existingEmail = await db.query.users.findFirst({ + where: and(eq(users.email, email), ne(users.id, id)), + }); + if (existingEmail) { + return reply.conflict('Este e-mail já está em uso por outro usuário.'); + } + } + + const updates: Partial<typeof users.$inferInsert> = { + name, + email, + role, + isActive, + updatedAt: new Date().toISOString(), + }; + + if (password && password.trim() !== '') { + updates.password = await bcrypt.hash(password, 12); + } + + const [updatedUser] = await db.update(users) + .set(updates) + .where(eq(users.id, id)) + .returning({ + id: users.id, + name: users.name, + email: users.email, + role: users.role, + isActive: users.isActive, + createdAt: users.createdAt, + }); + + return reply.send(updatedUser); +} + +export async function deleteUserHandler( + request: FastifyRequest<{ Params: { id: number } }>, + reply: FastifyReply, +) { + const db = request.server.db; + const { id } = request.params; + + const existingUser = await db.query.users.findFirst({ + where: eq(users.id, id), + }); + + if (!existingUser) { + return reply.notFound('Usuário não encontrado.'); + } + + if (id === request.user?.id) { + return reply.badRequest('Você não pode excluir a própria conta.'); + } + + await db.delete(users).where(eq(users.id, id)); + + return reply.code(204).send(); +} diff --git a/api/src/modules/users/users.routes.ts b/api/src/modules/users/users.routes.ts new file mode 100644 index 0000000..c7f1846 --- /dev/null +++ b/api/src/modules/users/users.routes.ts @@ -0,0 +1,56 @@ +import type { FastifyInstance } from 'fastify'; +import { createUserByAdminBodySchema, updateUserBodySchema, userIdParamsSchema } from './users.schema.js'; +import { listUsersHandler, createUserHandler, updateUserHandler, deleteUserHandler } from './users.controller.js'; + +export default async function usersRoutes(fastify: FastifyInstance) { + // Middleware customizado para garantir que apenas admins possam acessar + const requireAdmin = async (request: any, reply: any) => { + if (request.user?.role !== 'admin') { + return reply.forbidden('Apenas administradores podem gerenciar usuários.'); + } + }; + + // ── GET /users ───────────────────────────────────────────────────────────── + fastify.get('/', { + schema: { + tags: ['Users'], + summary: 'Lista todos os usuários (admin)', + }, + preHandler: [fastify.authenticate, requireAdmin], + handler: listUsersHandler, + }); + + // ── POST /users ──────────────────────────────────────────────────────────── + fastify.post('/', { + schema: { + tags: ['Users'], + summary: 'Cria um novo usuário (admin)', + body: createUserByAdminBodySchema, + }, + preHandler: [fastify.authenticate, requireAdmin], + handler: createUserHandler, + }); + + // ── PATCH /users/:id ─────────────────────────────────────────────────────── + fastify.patch('/:id', { + schema: { + tags: ['Users'], + summary: 'Atualiza um usuário (admin)', + params: userIdParamsSchema, + body: updateUserBodySchema, + }, + preHandler: [fastify.authenticate, requireAdmin], + handler: updateUserHandler, + }); + + // ── DELETE /users/:id ────────────────────────────────────────────────────── + fastify.delete('/:id', { + schema: { + tags: ['Users'], + summary: 'Exclui um usuário (admin)', + params: userIdParamsSchema, + }, + preHandler: [fastify.authenticate, requireAdmin], + handler: deleteUserHandler, + }); +} diff --git a/api/src/modules/users/users.schema.ts b/api/src/modules/users/users.schema.ts new file mode 100644 index 0000000..ff3f459 --- /dev/null +++ b/api/src/modules/users/users.schema.ts @@ -0,0 +1,25 @@ +import { z } from 'zod'; +import { USER_ROLES } from '@/db/schema.js'; + +export const createUserByAdminBodySchema = z.object({ + name: z.string().min(2).max(100).trim(), + email: z.string().email().toLowerCase().trim(), + password: z.string().min(8).max(100), + role: z.enum(USER_ROLES), +}); + +export type CreateUserByAdminBody = z.infer<typeof createUserByAdminBodySchema>; + +export const updateUserBodySchema = z.object({ + name: z.string().min(2).max(100).trim().optional(), + email: z.string().email().toLowerCase().trim().optional(), + password: z.string().min(8).max(100).optional().or(z.literal('')), + role: z.enum(USER_ROLES).optional(), + isActive: z.boolean().optional(), +}); + +export type UpdateUserBody = z.infer<typeof updateUserBodySchema>; + +export const userIdParamsSchema = z.object({ + id: z.coerce.number().positive(), +}); |