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:
Julien Froidefond
2025-12-12 16:19:13 +01:00
parent fd095246a3
commit 494ac3f503
34 changed files with 1795 additions and 1096 deletions

View 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();