import { Injectable, NotFoundException, ConflictException, BadRequestException, } from '@nestjs/common'; import { InjectRepository } from '@nestjs/typeorm'; import { Repository } from 'typeorm'; import * as bcrypt from 'bcrypt'; import { User, UserRole } from '../entities'; import { CreateUserDto, UpdateUserDto, UserResponseDto, ChangePasswordDto, } from './user.dto'; @Injectable() export class UserService { constructor( @InjectRepository(User) private userRepository: Repository, ) {} async create(createUserDto: CreateUserDto): Promise { // Check if email already exists const existingEmail = await this.userRepository.findOne({ where: { email: createUserDto.email }, }); if (existingEmail) { throw new ConflictException('Email already exists'); } // Check if username already exists const existingUsername = await this.userRepository.findOne({ where: { username: createUserDto.username }, }); if (existingUsername) { throw new ConflictException('Username already exists'); } // Hash password const saltRounds = 10; const passwordHash = await bcrypt.hash(createUserDto.password, saltRounds); // Create user const user = this.userRepository.create({ ...createUserDto, passwordHash, }); const savedUser = await this.userRepository.save(user); return new UserResponseDto(savedUser); } async findAll(): Promise { const users = await this.userRepository.find({ order: { createdAt: 'DESC' }, }); return users.map((user) => new UserResponseDto(user)); } async findOne(id: string): Promise { const user = await this.userRepository.findOne({ where: { id } }); if (!user) { throw new NotFoundException('User not found'); } return new UserResponseDto(user); } async findByUsername(username: string): Promise { return this.userRepository.findOne({ where: { username } }); } async findByEmail(email: string): Promise { return this.userRepository.findOne({ where: { email } }); } async update( id: string, updateUserDto: UpdateUserDto, ): Promise { const user = await this.userRepository.findOne({ where: { id } }); if (!user) { throw new NotFoundException('User not found'); } // Check if email is being changed and if it already exists if (updateUserDto.email && updateUserDto.email !== user.email) { const existingEmail = await this.userRepository.findOne({ where: { email: updateUserDto.email }, }); if (existingEmail) { throw new ConflictException('Email already exists'); } } // Check if username is being changed and if it already exists if (updateUserDto.username && updateUserDto.username !== user.username) { const existingUsername = await this.userRepository.findOne({ where: { username: updateUserDto.username }, }); if (existingUsername) { throw new ConflictException('Username already exists'); } } // Hash new password if provided if (updateUserDto.password) { const saltRounds = 10; updateUserDto.password = await bcrypt.hash( updateUserDto.password, saltRounds, ); } // Update user Object.assign(user, updateUserDto); if (updateUserDto.password) { user.passwordHash = updateUserDto.password; delete updateUserDto.password; } const updatedUser = await this.userRepository.save(user); return new UserResponseDto(updatedUser); } async remove(id: string): Promise { const user = await this.userRepository.findOne({ where: { id } }); if (!user) { throw new NotFoundException('User not found'); } // Prevent deleting the last admin if (user.role === UserRole.ADMIN) { const adminCount = await this.userRepository.count({ where: { role: UserRole.ADMIN }, }); if (adminCount <= 1) { throw new BadRequestException('Cannot delete the last admin user'); } } await this.userRepository.remove(user); } async changePassword( id: string, changePasswordDto: ChangePasswordDto, ): Promise { const user = await this.userRepository.findOne({ where: { id } }); if (!user) { throw new NotFoundException('User not found'); } // Verify current password const isPasswordValid = await bcrypt.compare( changePasswordDto.currentPassword, user.passwordHash, ); if (!isPasswordValid) { throw new BadRequestException('Current password is incorrect'); } // Hash new password const saltRounds = 10; user.passwordHash = await bcrypt.hash( changePasswordDto.newPassword, saltRounds, ); await this.userRepository.save(user); } async validateUser(username: string, password: string): Promise { const user = await this.userRepository.findOne({ where: { username, isActive: true }, }); if (!user) { return null; } const isPasswordValid = await bcrypt.compare(password, user.passwordHash); if (!isPasswordValid) { return null; } return user; } async createAdminIfNotExists(): Promise { const adminExists = await this.userRepository.findOne({ where: { role: UserRole.ADMIN }, }); if (!adminExists) { const saltRounds = 10; const passwordHash = await bcrypt.hash('admin123', saltRounds); const adminUser = this.userRepository.create({ email: 'admin@placebo.mk', username: 'admin', passwordHash, role: UserRole.ADMIN, isActive: true, }); await this.userRepository.save(adminUser); console.log('Default admin user created: admin / admin123'); } } }