import { Injectable } from '@nestjs/common'; import * as argon2 from 'argon2'; import { PrismaService } from '../prisma/prisma.service'; @Injectable() export class UserService { constructor(private prisma: PrismaService) {} /** * Find user by username. Uses UNSCOPED Prisma (not tenant-scoped) * because login must work across all tenants. * Usernames are stored lowercase (case-insensitive login) -- normalize * the lookup input to match regardless of how it was typed. */ async findByUsername(username: string) { return this.prisma.user.findUnique({ where: { username: username.toLowerCase() }, }); } /** * Find user by ID. */ async findById(id: string) { return this.prisma.user.findUnique({ where: { id } }); } /** * Create a new user with hashed password. * Username is normalized to lowercase so login is case-insensitive. */ async create(data: { username: string; email: string; password?: string; displayName?: string; role?: 'SUPER_ADMIN' | 'ADMIN' | 'USER'; tenantId: string; mustChangePassword?: boolean; ldapDn?: string; }) { const { password, ...rest } = data; return this.prisma.user.create({ data: { ...rest, username: rest.username.toLowerCase(), passwordHash: password ? await argon2.hash(password) : null, }, }); } /** * Update user. If password is provided, hash it. */ async update( id: string, data: { username?: string; email?: string; password?: string; displayName?: string; role?: 'SUPER_ADMIN' | 'ADMIN' | 'USER'; isActive?: boolean; mustChangePassword?: boolean; }, ) { const { password, ...rest } = data; const updateData: any = { ...rest }; if (updateData.username) { updateData.username = updateData.username.toLowerCase(); } if (password) { updateData.passwordHash = await argon2.hash(password); } return this.prisma.user.update({ where: { id }, data: updateData, }); } /** * Deactivate a user (soft delete). */ async deactivate(id: string) { return this.prisma.user.update({ where: { id }, data: { isActive: false }, }); } /** * Hard delete a user. */ async delete(id: string) { return this.prisma.user.delete({ where: { id } }); } }