Refactor event handling and user management: Replace direct database calls with service layer methods for events, user profiles, and preferences, enhancing code organization and maintainability. Update API routes to utilize new services for event registration, feedback, and user statistics, ensuring a consistent approach across the application.
This commit is contained in:
251
services/users/user-stats.service.ts
Normal file
251
services/users/user-stats.service.ts
Normal file
@@ -0,0 +1,251 @@
|
||||
import { prisma } from "../database";
|
||||
import type { User, Role, Prisma } from "@/prisma/generated/prisma/client";
|
||||
import { NotFoundError } from "../errors";
|
||||
import { userService } from "./user.service";
|
||||
|
||||
export interface UpdateUserStatsInput {
|
||||
hpDelta?: number;
|
||||
xpDelta?: number;
|
||||
score?: number;
|
||||
level?: number;
|
||||
role?: Role;
|
||||
}
|
||||
|
||||
export interface LeaderboardEntry {
|
||||
rank: number;
|
||||
username: string;
|
||||
email: string;
|
||||
score: number;
|
||||
level: number;
|
||||
avatar: string | null;
|
||||
bio: string | null;
|
||||
characterClass: string | null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Service de gestion des statistiques utilisateur
|
||||
*/
|
||||
export class UserStatsService {
|
||||
/**
|
||||
* Récupère le leaderboard
|
||||
*/
|
||||
async getLeaderboard(limit: number = 10): Promise<LeaderboardEntry[]> {
|
||||
const users = await prisma.user.findMany({
|
||||
orderBy: {
|
||||
score: "desc",
|
||||
},
|
||||
take: limit,
|
||||
select: {
|
||||
id: true,
|
||||
username: true,
|
||||
email: true,
|
||||
score: true,
|
||||
level: true,
|
||||
avatar: true,
|
||||
bio: true,
|
||||
characterClass: true,
|
||||
},
|
||||
});
|
||||
|
||||
return users.map((user, index) => ({
|
||||
rank: index + 1,
|
||||
username: user.username,
|
||||
email: user.email,
|
||||
score: user.score,
|
||||
level: user.level,
|
||||
avatar: user.avatar,
|
||||
bio: user.bio,
|
||||
characterClass: user.characterClass,
|
||||
}));
|
||||
}
|
||||
|
||||
/**
|
||||
* Met à jour les statistiques d'un utilisateur
|
||||
*/
|
||||
async updateUserStats(
|
||||
id: string,
|
||||
stats: UpdateUserStatsInput,
|
||||
select?: Prisma.UserSelect
|
||||
): Promise<User> {
|
||||
// Récupérer l'utilisateur actuel
|
||||
const user = await prisma.user.findUnique({
|
||||
where: { id },
|
||||
});
|
||||
|
||||
if (!user) {
|
||||
throw new Error("Utilisateur non trouvé");
|
||||
}
|
||||
|
||||
// Calculer les nouvelles valeurs
|
||||
let newHp = user.hp;
|
||||
let newXp = user.xp;
|
||||
let newLevel = user.level;
|
||||
let newMaxXp = user.maxXp;
|
||||
|
||||
// Appliquer les changements de HP
|
||||
if (stats.hpDelta !== undefined) {
|
||||
newHp = Math.max(0, Math.min(user.maxHp, user.hp + stats.hpDelta));
|
||||
}
|
||||
|
||||
// Appliquer les changements de XP
|
||||
if (stats.xpDelta !== undefined) {
|
||||
newXp = user.xp + stats.xpDelta;
|
||||
newLevel = user.level;
|
||||
newMaxXp = user.maxXp;
|
||||
|
||||
// Gérer le niveau up si nécessaire (quand on ajoute de l'XP)
|
||||
if (newXp >= newMaxXp && newXp > 0) {
|
||||
while (newXp >= newMaxXp) {
|
||||
newXp -= newMaxXp;
|
||||
newLevel += 1;
|
||||
// Augmenter le maxXp pour le prochain niveau (formule simple)
|
||||
newMaxXp = Math.floor(newMaxXp * 1.2);
|
||||
}
|
||||
}
|
||||
|
||||
// Gérer le niveau down si nécessaire (quand on enlève de l'XP)
|
||||
if (newXp < 0 && newLevel > 1) {
|
||||
while (newXp < 0 && newLevel > 1) {
|
||||
newLevel -= 1;
|
||||
// Calculer le maxXp du niveau précédent
|
||||
newMaxXp = Math.floor(newMaxXp / 1.2);
|
||||
newXp += newMaxXp;
|
||||
}
|
||||
// S'assurer que l'XP ne peut pas être négative
|
||||
newXp = Math.max(0, newXp);
|
||||
}
|
||||
|
||||
// S'assurer que le niveau minimum est 1
|
||||
if (newLevel < 1) {
|
||||
newLevel = 1;
|
||||
newXp = 0;
|
||||
}
|
||||
}
|
||||
|
||||
// Construire les données de mise à jour
|
||||
const updateData: Prisma.UserUpdateInput = {
|
||||
hp: newHp,
|
||||
xp: newXp,
|
||||
level: newLevel,
|
||||
maxXp: newMaxXp,
|
||||
};
|
||||
|
||||
// Appliquer les changements directs
|
||||
if (stats.score !== undefined) {
|
||||
updateData.score = Math.max(0, stats.score);
|
||||
}
|
||||
|
||||
if (stats.level !== undefined) {
|
||||
// Si le niveau est modifié directement, utiliser cette valeur
|
||||
const targetLevel = Math.max(1, stats.level);
|
||||
updateData.level = targetLevel;
|
||||
|
||||
// Recalculer le maxXp pour le nouveau niveau
|
||||
// Formule: maxXp = 5000 * (1.2 ^ (level - 1))
|
||||
let calculatedMaxXp = 5000;
|
||||
for (let i = 1; i < targetLevel; i++) {
|
||||
calculatedMaxXp = Math.floor(calculatedMaxXp * 1.2);
|
||||
}
|
||||
updateData.maxXp = calculatedMaxXp;
|
||||
|
||||
// Réinitialiser l'XP si le niveau change directement (sauf si on modifie aussi l'XP)
|
||||
if (targetLevel !== user.level && stats.xpDelta === undefined) {
|
||||
updateData.xp = 0;
|
||||
}
|
||||
}
|
||||
|
||||
if (stats.role !== undefined) {
|
||||
if (stats.role === "ADMIN" || stats.role === "USER") {
|
||||
updateData.role = stats.role;
|
||||
}
|
||||
}
|
||||
|
||||
return prisma.user.update({
|
||||
where: { id },
|
||||
data: updateData,
|
||||
select,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Met à jour les stats ET le profil d'un utilisateur (pour admin)
|
||||
*/
|
||||
async updateUserStatsAndProfile(
|
||||
id: string,
|
||||
data: {
|
||||
username?: string;
|
||||
avatar?: string | null;
|
||||
hpDelta?: number;
|
||||
xpDelta?: number;
|
||||
score?: number;
|
||||
level?: number;
|
||||
role?: Role;
|
||||
},
|
||||
select?: Prisma.UserSelect
|
||||
): Promise<User> {
|
||||
// Vérifier que l'utilisateur existe
|
||||
const user = await userService.getUserById(id);
|
||||
if (!user) {
|
||||
throw new NotFoundError("Utilisateur");
|
||||
}
|
||||
|
||||
const selectFields = select || {
|
||||
id: true,
|
||||
username: true,
|
||||
email: true,
|
||||
role: true,
|
||||
score: true,
|
||||
level: true,
|
||||
hp: true,
|
||||
maxHp: true,
|
||||
xp: true,
|
||||
maxXp: true,
|
||||
avatar: true,
|
||||
};
|
||||
|
||||
// Mettre à jour les stats si nécessaire
|
||||
const hasStatsChanges =
|
||||
data.hpDelta !== undefined ||
|
||||
data.xpDelta !== undefined ||
|
||||
data.score !== undefined ||
|
||||
data.level !== undefined ||
|
||||
data.role !== undefined;
|
||||
|
||||
let updatedUser: User;
|
||||
if (hasStatsChanges) {
|
||||
updatedUser = await this.updateUserStats(
|
||||
id,
|
||||
{
|
||||
hpDelta: data.hpDelta,
|
||||
xpDelta: data.xpDelta,
|
||||
score: data.score,
|
||||
level: data.level,
|
||||
role: data.role,
|
||||
},
|
||||
selectFields
|
||||
);
|
||||
} else {
|
||||
updatedUser = await userService.getUserById(id, selectFields);
|
||||
if (!updatedUser) {
|
||||
throw new NotFoundError("Utilisateur");
|
||||
}
|
||||
}
|
||||
|
||||
// Mettre à jour username/avatar si nécessaire
|
||||
if (data.username !== undefined || data.avatar !== undefined) {
|
||||
updatedUser = await userService.updateUser(
|
||||
id,
|
||||
{
|
||||
username:
|
||||
data.username !== undefined ? data.username.trim() : undefined,
|
||||
avatar: data.avatar,
|
||||
},
|
||||
selectFields
|
||||
);
|
||||
}
|
||||
|
||||
return updatedUser;
|
||||
}
|
||||
}
|
||||
|
||||
export const userStatsService = new UserStatsService();
|
||||
515
services/users/user.service.ts
Normal file
515
services/users/user.service.ts
Normal file
@@ -0,0 +1,515 @@
|
||||
import { prisma } from "../database";
|
||||
import bcrypt from "bcryptjs";
|
||||
import type {
|
||||
User,
|
||||
CharacterClass,
|
||||
Role,
|
||||
Prisma,
|
||||
} from "@/prisma/generated/prisma/client";
|
||||
import { ValidationError, NotFoundError, ConflictError } from "../errors";
|
||||
|
||||
// Constantes de validation
|
||||
const VALID_CHARACTER_CLASSES = [
|
||||
"WARRIOR",
|
||||
"MAGE",
|
||||
"ROGUE",
|
||||
"RANGER",
|
||||
"PALADIN",
|
||||
"ENGINEER",
|
||||
"MERCHANT",
|
||||
"SCHOLAR",
|
||||
"BERSERKER",
|
||||
"NECROMANCER",
|
||||
] as const;
|
||||
|
||||
const USERNAME_MIN_LENGTH = 3;
|
||||
const USERNAME_MAX_LENGTH = 20;
|
||||
const BIO_MAX_LENGTH = 500;
|
||||
const PASSWORD_MIN_LENGTH = 6;
|
||||
|
||||
export interface CreateUserInput {
|
||||
email: string;
|
||||
username: string;
|
||||
password: string;
|
||||
bio?: string | null;
|
||||
characterClass?: CharacterClass | null;
|
||||
avatar?: string | null;
|
||||
}
|
||||
|
||||
export interface UpdateUserInput {
|
||||
username?: string;
|
||||
avatar?: string | null;
|
||||
bio?: string | null;
|
||||
characterClass?: CharacterClass | null;
|
||||
}
|
||||
|
||||
export interface UserSelect {
|
||||
id?: boolean;
|
||||
email?: boolean;
|
||||
username?: boolean;
|
||||
avatar?: boolean;
|
||||
bio?: boolean;
|
||||
characterClass?: boolean;
|
||||
hp?: boolean;
|
||||
maxHp?: boolean;
|
||||
xp?: boolean;
|
||||
maxXp?: boolean;
|
||||
level?: boolean;
|
||||
score?: boolean;
|
||||
role?: boolean;
|
||||
createdAt?: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
* Service de gestion des utilisateurs
|
||||
*/
|
||||
export class UserService {
|
||||
/**
|
||||
* Récupère un utilisateur par son ID
|
||||
*/
|
||||
async getUserById(
|
||||
id: string,
|
||||
select?: Prisma.UserSelect
|
||||
): Promise<User | null> {
|
||||
return prisma.user.findUnique({
|
||||
where: { id },
|
||||
select,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Récupère un utilisateur par son email
|
||||
*/
|
||||
async getUserByEmail(email: string): Promise<User | null> {
|
||||
return prisma.user.findUnique({
|
||||
where: { email },
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Récupère un utilisateur par son username
|
||||
*/
|
||||
async getUserByUsername(username: string): Promise<User | null> {
|
||||
return prisma.user.findUnique({
|
||||
where: { username },
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Vérifie si un username est disponible
|
||||
*/
|
||||
async checkUsernameAvailability(
|
||||
username: string,
|
||||
excludeUserId?: string
|
||||
): Promise<boolean> {
|
||||
const existingUser = await prisma.user.findFirst({
|
||||
where: {
|
||||
username: username.trim(),
|
||||
...(excludeUserId && { NOT: { id: excludeUserId } }),
|
||||
},
|
||||
});
|
||||
return !existingUser;
|
||||
}
|
||||
|
||||
/**
|
||||
* Vérifie si un email ou username existe déjà
|
||||
*/
|
||||
async checkEmailOrUsernameExists(
|
||||
email: string,
|
||||
username: string
|
||||
): Promise<boolean> {
|
||||
const existingUser = await prisma.user.findFirst({
|
||||
where: {
|
||||
OR: [{ email }, { username }],
|
||||
},
|
||||
});
|
||||
return !!existingUser;
|
||||
}
|
||||
|
||||
/**
|
||||
* Crée un nouvel utilisateur
|
||||
*/
|
||||
async createUser(data: CreateUserInput): Promise<User> {
|
||||
// Hasher le mot de passe
|
||||
const hashedPassword = await bcrypt.hash(data.password, 10);
|
||||
|
||||
return prisma.user.create({
|
||||
data: {
|
||||
email: data.email,
|
||||
username: data.username,
|
||||
password: hashedPassword,
|
||||
bio: data.bio || null,
|
||||
characterClass: data.characterClass || null,
|
||||
avatar: data.avatar || null,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Met à jour un utilisateur
|
||||
*/
|
||||
async updateUser(
|
||||
id: string,
|
||||
data: UpdateUserInput,
|
||||
select?: Prisma.UserSelect
|
||||
): Promise<User> {
|
||||
const updateData: Prisma.UserUpdateInput = {};
|
||||
|
||||
if (data.username !== undefined) {
|
||||
updateData.username = data.username.trim();
|
||||
}
|
||||
if (data.avatar !== undefined) {
|
||||
updateData.avatar = data.avatar || null;
|
||||
}
|
||||
if (data.bio !== undefined) {
|
||||
updateData.bio = data.bio === null ? null : data.bio.trim() || null;
|
||||
}
|
||||
if (data.characterClass !== undefined) {
|
||||
updateData.characterClass = data.characterClass || null;
|
||||
}
|
||||
|
||||
return prisma.user.update({
|
||||
where: { id },
|
||||
data: updateData,
|
||||
select,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Met à jour le mot de passe d'un utilisateur
|
||||
*/
|
||||
async updateUserPassword(
|
||||
id: string,
|
||||
currentPassword: string,
|
||||
newPassword: string
|
||||
): Promise<void> {
|
||||
// Récupérer l'utilisateur avec le mot de passe
|
||||
const user = await prisma.user.findUnique({
|
||||
where: { id },
|
||||
select: { password: true },
|
||||
});
|
||||
|
||||
if (!user) {
|
||||
throw new Error("Utilisateur non trouvé");
|
||||
}
|
||||
|
||||
// Vérifier l'ancien mot de passe
|
||||
const isPasswordValid = await bcrypt.compare(
|
||||
currentPassword,
|
||||
user.password
|
||||
);
|
||||
|
||||
if (!isPasswordValid) {
|
||||
throw new Error("Mot de passe actuel incorrect");
|
||||
}
|
||||
|
||||
// Hasher le nouveau mot de passe
|
||||
const hashedPassword = await bcrypt.hash(newPassword, 10);
|
||||
|
||||
// Mettre à jour le mot de passe
|
||||
await prisma.user.update({
|
||||
where: { id },
|
||||
data: { password: hashedPassword },
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Supprime un utilisateur
|
||||
*/
|
||||
async deleteUser(id: string): Promise<void> {
|
||||
await prisma.user.delete({
|
||||
where: { id },
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Récupère tous les utilisateurs (pour admin)
|
||||
*/
|
||||
async getAllUsers(options?: {
|
||||
orderBy?: Prisma.UserOrderByWithRelationInput;
|
||||
select?: Prisma.UserSelect;
|
||||
}): Promise<User[]> {
|
||||
return prisma.user.findMany({
|
||||
orderBy: options?.orderBy || { score: "desc" },
|
||||
select: options?.select,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Vérifie les credentials pour l'authentification
|
||||
*/
|
||||
async verifyCredentials(
|
||||
email: string,
|
||||
password: string
|
||||
): Promise<User | null> {
|
||||
const user = await prisma.user.findUnique({
|
||||
where: { email },
|
||||
});
|
||||
|
||||
if (!user) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const isPasswordValid = await bcrypt.compare(password, user.password);
|
||||
|
||||
if (!isPasswordValid) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return user;
|
||||
}
|
||||
|
||||
/**
|
||||
* Vérifie si un compte a été créé récemment (dans les X minutes)
|
||||
*/
|
||||
async isAccountRecentlyCreated(
|
||||
userId: string,
|
||||
minutesAgo: number = 10
|
||||
): Promise<boolean> {
|
||||
const user = await prisma.user.findUnique({
|
||||
where: { id: userId },
|
||||
select: { createdAt: true },
|
||||
});
|
||||
|
||||
if (!user) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const timeLimit = new Date(Date.now() - minutesAgo * 60 * 1000);
|
||||
return user.createdAt >= timeLimit;
|
||||
}
|
||||
|
||||
/**
|
||||
* Valide et crée un utilisateur avec toutes les règles métier
|
||||
*/
|
||||
async validateAndCreateUser(data: {
|
||||
email: string;
|
||||
username: string;
|
||||
password: string;
|
||||
bio?: string | null;
|
||||
characterClass?: string | null;
|
||||
avatar?: string | null;
|
||||
}): Promise<User> {
|
||||
// Validation des champs requis
|
||||
if (!data.email || !data.username || !data.password) {
|
||||
throw new ValidationError(
|
||||
"Email, nom d'utilisateur et mot de passe sont requis"
|
||||
);
|
||||
}
|
||||
|
||||
// Validation du mot de passe
|
||||
if (data.password.length < PASSWORD_MIN_LENGTH) {
|
||||
throw new ValidationError(
|
||||
`Le mot de passe doit contenir au moins ${PASSWORD_MIN_LENGTH} caractères`,
|
||||
"password"
|
||||
);
|
||||
}
|
||||
|
||||
// Validation du characterClass
|
||||
if (
|
||||
data.characterClass &&
|
||||
!VALID_CHARACTER_CLASSES.includes(data.characterClass as CharacterClass)
|
||||
) {
|
||||
throw new ValidationError(
|
||||
"Classe de personnage invalide",
|
||||
"characterClass"
|
||||
);
|
||||
}
|
||||
|
||||
// Vérifier si l'email ou username existe déjà
|
||||
const exists = await this.checkEmailOrUsernameExists(
|
||||
data.email,
|
||||
data.username
|
||||
);
|
||||
if (exists) {
|
||||
throw new ConflictError(
|
||||
"Cet email ou nom d'utilisateur est déjà utilisé"
|
||||
);
|
||||
}
|
||||
|
||||
// Créer l'utilisateur
|
||||
return this.createUser({
|
||||
email: data.email,
|
||||
username: data.username,
|
||||
password: data.password,
|
||||
bio: data.bio || null,
|
||||
characterClass: (data.characterClass as CharacterClass) || null,
|
||||
avatar: data.avatar || null,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Valide et met à jour le profil utilisateur avec toutes les règles métier
|
||||
*/
|
||||
async validateAndUpdateUserProfile(
|
||||
userId: string,
|
||||
data: {
|
||||
username?: string;
|
||||
avatar?: string | null;
|
||||
bio?: string | null;
|
||||
characterClass?: string | null;
|
||||
},
|
||||
select?: Prisma.UserSelect
|
||||
): Promise<User> {
|
||||
// Validation username
|
||||
if (data.username !== undefined) {
|
||||
if (
|
||||
typeof data.username !== "string" ||
|
||||
data.username.trim().length === 0
|
||||
) {
|
||||
throw new ValidationError(
|
||||
"Le nom d'utilisateur ne peut pas être vide",
|
||||
"username"
|
||||
);
|
||||
}
|
||||
|
||||
if (
|
||||
data.username.length < USERNAME_MIN_LENGTH ||
|
||||
data.username.length > USERNAME_MAX_LENGTH
|
||||
) {
|
||||
throw new ValidationError(
|
||||
`Le nom d'utilisateur doit contenir entre ${USERNAME_MIN_LENGTH} et ${USERNAME_MAX_LENGTH} caractères`,
|
||||
"username"
|
||||
);
|
||||
}
|
||||
|
||||
// Vérifier si le username est déjà pris
|
||||
const isAvailable = await this.checkUsernameAvailability(
|
||||
data.username.trim(),
|
||||
userId
|
||||
);
|
||||
if (!isAvailable) {
|
||||
throw new ConflictError("Ce nom d'utilisateur est déjà pris");
|
||||
}
|
||||
}
|
||||
|
||||
// Validation bio
|
||||
if (data.bio !== undefined && data.bio !== null) {
|
||||
if (typeof data.bio !== "string") {
|
||||
throw new ValidationError(
|
||||
"La bio doit être une chaîne de caractères",
|
||||
"bio"
|
||||
);
|
||||
}
|
||||
if (data.bio.length > BIO_MAX_LENGTH) {
|
||||
throw new ValidationError(
|
||||
`La bio ne peut pas dépasser ${BIO_MAX_LENGTH} caractères`,
|
||||
"bio"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// Validation characterClass
|
||||
if (
|
||||
data.characterClass !== undefined &&
|
||||
data.characterClass !== null &&
|
||||
!VALID_CHARACTER_CLASSES.includes(data.characterClass as CharacterClass)
|
||||
) {
|
||||
throw new ValidationError(
|
||||
"Classe de personnage invalide",
|
||||
"characterClass"
|
||||
);
|
||||
}
|
||||
|
||||
// Mettre à jour l'utilisateur
|
||||
return this.updateUser(
|
||||
userId,
|
||||
{
|
||||
username:
|
||||
data.username !== undefined ? data.username.trim() : undefined,
|
||||
avatar: data.avatar,
|
||||
bio: data.bio,
|
||||
characterClass: data.characterClass as CharacterClass | null,
|
||||
},
|
||||
select
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Valide et finalise l'inscription d'un utilisateur (avec vérification du temps)
|
||||
*/
|
||||
async validateAndCompleteRegistration(
|
||||
userId: string,
|
||||
data: {
|
||||
username?: string;
|
||||
avatar?: string | null;
|
||||
bio?: string | null;
|
||||
characterClass?: string | null;
|
||||
}
|
||||
): Promise<User> {
|
||||
// Vérifier que l'utilisateur existe
|
||||
const user = await this.getUserById(userId);
|
||||
if (!user) {
|
||||
throw new NotFoundError("Utilisateur");
|
||||
}
|
||||
|
||||
// Vérifier que le compte a été créé récemment (dans les 10 dernières minutes)
|
||||
const isRecent = await this.isAccountRecentlyCreated(userId, 10);
|
||||
if (!isRecent) {
|
||||
throw new ValidationError("Temps écoulé pour finaliser l'inscription");
|
||||
}
|
||||
|
||||
// Valider et mettre à jour
|
||||
return this.validateAndUpdateUserProfile(userId, data);
|
||||
}
|
||||
|
||||
/**
|
||||
* Valide et met à jour le mot de passe avec toutes les règles métier
|
||||
*/
|
||||
async validateAndUpdatePassword(
|
||||
userId: string,
|
||||
currentPassword: string,
|
||||
newPassword: string,
|
||||
confirmPassword: string
|
||||
): Promise<void> {
|
||||
// Validation des champs requis
|
||||
if (!currentPassword || !newPassword || !confirmPassword) {
|
||||
throw new ValidationError("Tous les champs sont requis");
|
||||
}
|
||||
|
||||
// Validation du nouveau mot de passe
|
||||
if (newPassword.length < PASSWORD_MIN_LENGTH) {
|
||||
throw new ValidationError(
|
||||
`Le nouveau mot de passe doit contenir au moins ${PASSWORD_MIN_LENGTH} caractères`,
|
||||
"newPassword"
|
||||
);
|
||||
}
|
||||
|
||||
// Vérifier que les mots de passe correspondent
|
||||
if (newPassword !== confirmPassword) {
|
||||
throw new ValidationError(
|
||||
"Les mots de passe ne correspondent pas",
|
||||
"confirmPassword"
|
||||
);
|
||||
}
|
||||
|
||||
// Mettre à jour le mot de passe (la méthode updateUserPassword gère déjà la vérification de l'ancien mot de passe)
|
||||
await this.updateUserPassword(userId, currentPassword, newPassword);
|
||||
}
|
||||
|
||||
/**
|
||||
* Valide et supprime un utilisateur avec vérification des règles métier
|
||||
*/
|
||||
async validateAndDeleteUser(
|
||||
userId: string,
|
||||
currentUserId: string
|
||||
): Promise<void> {
|
||||
// Vérifier que l'utilisateur existe
|
||||
const user = await this.getUserById(userId);
|
||||
if (!user) {
|
||||
throw new NotFoundError("Utilisateur");
|
||||
}
|
||||
|
||||
// Empêcher la suppression de soi-même
|
||||
if (user.id === currentUserId) {
|
||||
throw new ValidationError(
|
||||
"Vous ne pouvez pas supprimer votre propre compte"
|
||||
);
|
||||
}
|
||||
|
||||
// Supprimer l'utilisateur
|
||||
await this.deleteUser(userId);
|
||||
}
|
||||
}
|
||||
|
||||
export const userService = new UserService();
|
||||
Reference in New Issue
Block a user