ptitspas-ynov-back/src/routes/user/user.service.ts
2025-09-23 13:02:14 +02:00

233 lines
9.6 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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<Users>,
@InjectRepository(Validation)
private readonly validationRepository: Repository<Validation>,
@InjectRepository(Parents)
private readonly parentsRepository: Repository<Parents>,
@InjectRepository(AssistanteMaternelle)
private readonly assistantesRepository: Repository<AssistanteMaternelle>
) { }
async createUser(dto: CreateUserDto, currentUser?: Users): Promise<Users> {
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<Users[]> {
return this.usersRepository.find();
}
async findOneBy(where: Partial<Users>): Promise<Users | null> {
return this.usersRepository.findOne({ where });
}
async findOne(id: string): Promise<Users> {
const user = await this.usersRepository.findOne({ where: { id } });
if (!user) {
throw new NotFoundException('Utilisateur introuvable');
}
return user;
}
async findByEmailOrNull(email: string): Promise<Users | null> {
return this.usersRepository.findOne({ where: { email } });
}
async updateUser(id: string, dto: UpdateUserDto, currentUser: Users): Promise<Users> {
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 lobligation 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 lobligation
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<Users> {
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<Users> {
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<void> {
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');
}
}
}