import { BadRequestException, ConflictException, Injectable, NotFoundException, } from '@nestjs/common'; import { AdminRole, Prisma } from '@prisma/client'; import * as bcrypt from 'bcryptjs'; import { PrismaService } from '../prisma/prisma.service'; import { CreateAdminUserDto, UpdateAdminUserDto } from './dto/admin-user.dto'; import { isValidPhone, normalizePhone, toEnglishDigits, } from '../common/utils/phone'; const DEFAULT_PASSWORD = 'admin123'; @Injectable() export class AdminUsersService { constructor(private readonly prisma: PrismaService) {} async findAll() { const users = await this.prisma.adminUser.findMany({ orderBy: { createdAt: 'desc' }, }); return users.map((u) => this.map(u)); } async findOne(id: string) { const user = await this.prisma.adminUser.findUnique({ where: { id } }); if (!user) throw new NotFoundException('کاربر ادمین یافت نشد'); return this.map(user); } async create(dto: CreateAdminUserDto) { const phone = normalizePhone(toEnglishDigits(dto.phone)); if (!isValidPhone(phone)) { throw new BadRequestException('شماره موبایل نامعتبر است'); } const passwordHash = await bcrypt.hash( dto.password?.trim() || DEFAULT_PASSWORD, 10, ); try { const user = await this.prisma.adminUser.create({ data: { fullName: dto.fullName.trim(), phone, email: dto.email.trim().toLowerCase(), passwordHash, role: dto.role ?? AdminRole.admin, isActive: dto.isActive ?? true, }, }); return this.map(user); } catch (e) { if ( e instanceof Prisma.PrismaClientKnownRequestError && e.code === 'P2002' ) { throw new ConflictException('شماره یا ایمیل تکراری است'); } throw e; } } async update(id: string, dto: UpdateAdminUserDto) { await this.ensure(id); const phone = dto.phone !== undefined ? normalizePhone(toEnglishDigits(dto.phone)) : undefined; if (phone !== undefined && !isValidPhone(phone)) { throw new BadRequestException('شماره موبایل نامعتبر است'); } const data: Prisma.AdminUserUpdateInput = { fullName: dto.fullName?.trim(), phone, email: dto.email?.trim().toLowerCase(), role: dto.role, isActive: dto.isActive, }; if (dto.password?.trim()) { data.passwordHash = await bcrypt.hash(dto.password.trim(), 10); } try { const user = await this.prisma.adminUser.update({ where: { id }, data, }); return this.map(user); } catch (e) { if ( e instanceof Prisma.PrismaClientKnownRequestError && e.code === 'P2002' ) { throw new ConflictException('شماره یا ایمیل تکراری است'); } throw e; } } async setActive(id: string, isActive: boolean) { await this.ensure(id); const user = await this.prisma.adminUser.update({ where: { id }, data: { isActive }, }); return this.map(user); } private async ensure(id: string) { const user = await this.prisma.adminUser.findUnique({ where: { id } }); if (!user) throw new NotFoundException('کاربر ادمین یافت نشد'); return user; } private map(user: { id: string; fullName: string; phone: string; email: string; role: AdminRole; isActive: boolean; lastLoginAt: Date | null; createdAt: Date; updatedAt: Date; }) { return { id: user.id, fullName: user.fullName, phone: user.phone, email: user.email, role: user.role, isActive: user.isActive, lastLoginAt: user.lastLoginAt?.toISOString() ?? null, createdAt: user.createdAt.toISOString(), updatedAt: user.updatedAt.toISOString(), }; } }