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();
|
||||
Reference in New Issue
Block a user