import { BadRequestException, ForbiddenException, Injectable, NotFoundException } from "@nestjs/common"; import { InjectRepository } from "@nestjs/typeorm"; import { RoleType, StatutUtilisateurType, Users } from "src/entities/users.entity"; import { In, Repository } from "typeorm"; import { CreateUserDto } from "./dto/create_user.dto"; import { UpdateUserDto } from "./dto/update_user.dto"; import * as bcrypt from 'bcrypt'; import { StatutValidationType, Validation } from "src/entities/validations.entity"; import { Parents } from "src/entities/parents.entity"; import { AssistanteMaternelle } from "src/entities/assistantes_maternelles.entity"; @Injectable() export class UserService { constructor( @InjectRepository(Users) private readonly usersRepository: Repository, @InjectRepository(Validation) private readonly validationRepository: Repository, @InjectRepository(Parents) private readonly parentsRepository: Repository, @InjectRepository(AssistanteMaternelle) private readonly assistantesRepository: Repository ) { } async createUser(dto: CreateUserDto, currentUser?: Users): Promise { if (!dto.cguAccepted) { throw new BadRequestException( 'Vous devez accepter les CGU et la Politique de confidentialité pour créer un compte.', ); } const exist = await this.usersRepository.findOneBy({ email: dto.email }); if (exist) throw new BadRequestException('Email déjà utilisé'); const isSuperAdmin = currentUser?.role === RoleType.SUPER_ADMIN; const isAdmin = currentUser?.role === RoleType.ADMINISTRATEUR; let role: RoleType; if (dto.role === RoleType.GESTIONNAIRE) { if (!isAdmin && !isSuperAdmin) { throw new ForbiddenException('Seuls les administrateurs peuvent créer un gestionnaire'); } role = RoleType.GESTIONNAIRE; } else if (dto.role === RoleType.ADMINISTRATEUR) { if (!isAdmin && !isSuperAdmin) { throw new ForbiddenException('Seuls les administrateurs peuvent créer un administrateur'); } role = RoleType.ADMINISTRATEUR; } else if (dto.role === RoleType.ASSISTANTE_MATERNELLE) { role = RoleType.ASSISTANTE_MATERNELLE; if (!dto.photo_url) { throw new BadRequestException( 'La photo de profil est obligatoire pour les assistantes maternelles.', ); } } else { role = RoleType.PARENT; } const statut = isSuperAdmin ? dto.statut ?? StatutUtilisateurType.EN_ATTENTE : StatutUtilisateurType.EN_ATTENTE; if (!dto.nom?.trim()) throw new BadRequestException('Nom est obligatoire.'); if (!dto.prenom?.trim()) throw new BadRequestException('Prénom est obligatoire.'); if (!dto.adresse?.trim()) throw new BadRequestException('Adresse est obligatoire.'); if (!dto.telephone?.trim()) throw new BadRequestException('Téléphone est obligatoire.'); let consentDate: Date | undefined; if (dto.consentement_photo && dto.date_consentement_photo) { const parsed = new Date(dto.date_consentement_photo); if (!isNaN(parsed.getTime())) { consentDate = parsed; } } const salt = await bcrypt.genSalt(); const hashedPassword = await bcrypt.hash(dto.password, salt); const entity = this.usersRepository.create({ email: dto.email, password: hashedPassword, prenom: dto.prenom, nom: dto.nom, role, statut, genre: dto.genre, telephone: dto.telephone, ville: dto.ville, code_postal: dto.code_postal, adresse: dto.adresse, photo_url: dto.photo_url, consentement_photo: dto.consentement_photo ?? false, date_consentement_photo: consentDate, changement_mdp_obligatoire: role === RoleType.ADMINISTRATEUR || role === RoleType.GESTIONNAIRE ? true : dto.changement_mdp_obligatoire ?? false, }); const saved = await this.usersRepository.save(entity); return this.findOne(saved.id); } async findAll(): Promise { return this.usersRepository.find(); } async findOneBy(where: Partial): Promise { return this.usersRepository.findOne({ where }); } async findOne(id: string): Promise { const user = await this.usersRepository.findOne({ where: { id } }); if (!user) { throw new NotFoundException('Utilisateur introuvable'); } return user; } async findByEmailOrNull(email: string): Promise { return this.usersRepository.findOne({ where: { email } }); } async updateUser(id: string, dto: UpdateUserDto, currentUser: Users): Promise { const user = await this.findOne(id); // Interdire changement de rôle si pas super admin if (dto.role && currentUser.role !== RoleType.SUPER_ADMIN) { throw new ForbiddenException('Accès réservé aux super admins'); } // Empêcher de modifier le flag changement_mdp_obligatoire pour admin/gestionnaire if ( (user.role === RoleType.ADMINISTRATEUR || user.role === RoleType.GESTIONNAIRE) && dto.changement_mdp_obligatoire === false ) { throw new ForbiddenException( 'Impossible de désactiver l’obligation de changement de mot de passe pour ce rôle', ); } // Gestion du mot de passe if (dto.password) { const salt = await bcrypt.genSalt(); user.password = await bcrypt.hash(dto.password, salt); delete (dto as any).password; // Une fois le mot de passe changé, on peut lever l’obligation user.changement_mdp_obligatoire = false; } // Conversion de la date de consentement if (dto.date_consentement_photo !== undefined) { user.date_consentement_photo = dto.date_consentement_photo ? new Date(dto.date_consentement_photo) : undefined; delete (dto as any).date_consentement_photo; } Object.assign(user, dto); return this.usersRepository.save(user); } // Valider un compte utilisateur async validateUser(user_id: string, currentUser: Users, comment?: string): Promise { if (![RoleType.SUPER_ADMIN, RoleType.ADMINISTRATEUR, RoleType.GESTIONNAIRE].includes(currentUser.role)) { throw new ForbiddenException('Accès réservé aux super admins, administrateurs et gestionnaires'); } const user = await this.usersRepository.findOne({ where: { id: user_id } }); if (!user) throw new NotFoundException('Utilisateur introuvable'); user.statut = StatutUtilisateurType.ACTIF; const savedUser = await this.usersRepository.save(user); if (user.role === RoleType.PARENT) { const existParent = await this.parentsRepository.findOneBy({ user_id: user.id }); if (!existParent) { const parentEntity = this.parentsRepository.create({ user_id: user.id, user }); await this.parentsRepository.save(parentEntity); } } else if (user.role === RoleType.ASSISTANTE_MATERNELLE) { const existAssistante = await this.assistantesRepository.findOneBy({ user_id: user.id }); if (!existAssistante) { const assistanteEntity = this.assistantesRepository.create({ user_id: user.id, user }); await this.assistantesRepository.save(assistanteEntity); } } const validation = this.validationRepository.create({ user: savedUser, type: 'validation_compte', status: StatutValidationType.VALIDE, validated_by: currentUser, comment, }); await this.validationRepository.save(validation); return savedUser; } // Mettre un compte en statut suspendu async suspendUser(user_id: string, currentUser: Users, comment?: string): Promise { if (![RoleType.SUPER_ADMIN, RoleType.ADMINISTRATEUR, RoleType.GESTIONNAIRE].includes(currentUser.role)) { throw new ForbiddenException('Accès réservé aux super admins, administrateurs et gestionnaires'); } const user = await this.usersRepository.findOne({ where: { id: user_id } }); if (!user) throw new NotFoundException('Utilisateur introuvable'); user.statut = StatutUtilisateurType.SUSPENDU; const savedUser = await this.usersRepository.save(user); const suspend = this.validationRepository.create({ user: savedUser, type: 'suspension_compte', status: StatutValidationType.VALIDE, validated_by: currentUser, comment, }) await this.validationRepository.save(suspend); return savedUser; } async remove(id: string, currentUser: Users): Promise { if (currentUser.role !== RoleType.SUPER_ADMIN) { throw new ForbiddenException('Accès réservé aux super admins'); } const result = await this.usersRepository.delete(id); if (result.affected === 0) { throw new NotFoundException('Utilisateur introuvable'); } } }