import bcrypt from 'bcrypt'; import { Injectable, NotFoundException } from '@nestjs/common'; import { plainToInstance } from 'class-transformer'; import { Prisma, Usuario } from 'generated/prisma'; import { PrismaService } from '@prisma/prisma.service'; import { PaginatedResponse } from '@shared/dto/paginated'; import { UsuariosResponseDto } from './dto/usuarios.response'; import { CreateUsuarioDto } from './dto/create-usuario-dto'; type ListUsuariosFilters = { name?: string; email?: string; }; type ListUsuariosPaginatedParams = ListUsuariosFilters & { page?: number; perPage?: number; direction?: 'asc' | 'desc'; }; const SELECT = { id: true, uuid: true, email: true, email_verificado: true, nome: true, sobrenome: true, papel: true, status: true, ultimo_login_em: true, ultimo_login_ip: true, tentativas_login_falhadas: true, bloqueado_ate: true, criado_em: true, atualizado_em: true, } as const; @Injectable() export class UsuariosService { constructor(private readonly prisma: PrismaService) {} private buildWhere({ name, email, }: ListUsuariosFilters): Prisma.UsuarioWhereInput { const andConditions: Prisma.UsuarioWhereInput[] = [ { email: { not: 'admin@clipperia.com' } }, ]; if (name) { andConditions.push({ OR: [ { nome: { contains: name, mode: 'insensitive' } }, { sobrenome: { contains: name, mode: 'insensitive' } }, ], }); } if (email) { andConditions.push({ email: { contains: email, mode: 'insensitive' } }); } return { AND: andConditions }; } async list( filters: ListUsuariosFilters = {}, ): Promise { const where = this.buildWhere(filters); const data = await this.prisma.usuario.findMany({ where, orderBy: { id: 'desc' }, select: SELECT, }); return plainToInstance(UsuariosResponseDto, data, { excludeExtraneousValues: true, }); } async listPaginated( params: ListUsuariosPaginatedParams, ): Promise> { const parsedPage = Number(params.page); const safePage = Number.isFinite(parsedPage) && parsedPage > 0 ? Math.floor(parsedPage) : 1; const parsedPerPage = Number(params.perPage); const safePerPage = Number.isFinite(parsedPerPage) && parsedPerPage > 0 ? Math.floor(parsedPerPage) : 10; const safeDirection: 'asc' | 'desc' = params.direction === 'asc' ? 'asc' : 'desc'; const where = this.buildWhere(params); const skip = (safePage - 1) * safePerPage; const [rows, total] = await Promise.all([ this.prisma.usuario.findMany({ where, orderBy: { id: safeDirection }, skip, take: safePerPage, select: SELECT, }), this.prisma.usuario.count({ where }), ]); const content: UsuariosResponseDto[] = plainToInstance( UsuariosResponseDto, rows, { excludeExtraneousValues: true }, ); const totalPages = Math.max(1, Math.ceil(total / safePerPage)); return { content, pagination: { page: safePage, direction: safeDirection, perPage: safePerPage, total, totalPages, hasNext: safePage < totalPages, hasPrev: safePage > 1, }, }; } async get(uuid: string): Promise { const row = await this.prisma.usuario.findUnique({ where: { uuid }, select: SELECT, }); if (!row) { throw new NotFoundException('Usuário não encontrado'); } return plainToInstance(UsuariosResponseDto, row, { excludeExtraneousValues: true, }); } async update(id: number, data: Prisma.UsuarioUpdateInput): Promise { return this.prisma.usuario.update({ where: { id }, data, }); } async create(dto: CreateUsuarioDto): Promise { const { email, password, nome, sobrenome } = dto; const senhaCriptografada = await bcrypt.hash(password, 10); const usuario = await this.prisma.usuario.create({ data: { email, password: senhaCriptografada, nome, sobrenome, }, }); return plainToInstance(UsuariosResponseDto, usuario, { excludeExtraneousValues: true, }); } async delete(id: number): Promise { return this.prisma.usuario.delete({ where: { id }, }); } async emailVerificado(uuid: string): Promise { const usuario = await this.prisma.usuario.update({ where: { uuid }, data: { email_verificado: 'V' }, }); return plainToInstance(UsuariosResponseDto, usuario, { excludeExtraneousValues: true, }); } }