import { ConflictException, Injectable, NotFoundException, } from '@nestjs/common'; import * as bcrypt from 'bcryptjs'; import { PrismaService } from '../prisma/prisma.service'; import { AuditLogService } from '../audit/audit-log.service'; import { User } from '../../generated/prisma'; import { CreateUserDto } from './dto/create-user.dto'; import { UpdateUserDto } from './dto/update-user.dto'; const BCRYPT_SALT_ROUNDS = 12; export interface UserSummary { id: string; email: string; name: string; role: string; isActive: boolean; createdAt: Date; deactivatedAt: Date | null; } @Injectable() export class UsersService { constructor( private readonly prisma: PrismaService, private readonly auditLog: AuditLogService, ) {} private toSummary(user: User): UserSummary { const { id, email, name, role, isActive, createdAt, deactivatedAt } = user; return { id, email, name, role, isActive, createdAt, deactivatedAt }; } async findAll(): Promise { const users = await this.prisma.user.findMany({ orderBy: { createdAt: 'asc' }, }); return users.map((user) => this.toSummary(user)); } async findById(id: string): Promise { const user = await this.prisma.user.findUnique({ where: { id } }); if (!user) { throw new NotFoundException('User not found'); } return user; } async findSummaryById(id: string): Promise { return this.toSummary(await this.findById(id)); } async findByEmail(email: string): Promise { return this.prisma.user.findUnique({ where: { email } }); } async create( dto: CreateUserDto, actingUserId?: string, ): Promise { const existing = await this.findByEmail(dto.email); if (existing) { throw new ConflictException('Email already in use'); } const passwordHash = await bcrypt.hash(dto.password, BCRYPT_SALT_ROUNDS); const user = await this.prisma.user.create({ data: { email: dto.email, passwordHash, name: dto.name, role: dto.role }, }); await this.auditLog.record({ entityType: 'User', entityId: user.id, action: 'CREATE', userId: actingUserId, afterData: this.toSummary(user), }); return this.toSummary(user); } async update( id: string, dto: UpdateUserDto, actingUserId?: string, ): Promise { const before = await this.findById(id); const user = await this.prisma.user.update({ where: { id }, data: { ...dto }, }); await this.auditLog.record({ entityType: 'User', entityId: id, action: 'UPDATE', userId: actingUserId, beforeData: this.toSummary(before), afterData: this.toSummary(user), }); return this.toSummary(user); } async deactivate(id: string, actingUserId?: string): Promise { const before = await this.findById(id); const user = await this.prisma.user.update({ where: { id }, data: { isActive: false, deactivatedAt: new Date() }, }); await this.auditLog.record({ entityType: 'User', entityId: id, action: 'DEACTIVATE', userId: actingUserId, beforeData: this.toSummary(before), afterData: this.toSummary(user), }); return this.toSummary(user); } }