import { prisma } from "../database"; import type { Challenge, ChallengeStatus, Prisma, } from "@/prisma/generated/prisma/client"; import { ValidationError, NotFoundError } from "../errors"; export interface CreateChallengeInput { challengerId: string; challengedId: string; title: string; description: string; pointsReward?: number; } export interface UpdateChallengeInput { status?: ChallengeStatus; adminId?: string; adminComment?: string; winnerId?: string; title?: string; description?: string; pointsReward?: number; } export interface ChallengeWithUsers extends Challenge { challenger: { id: string; username: string; avatar: string | null; }; challenged: { id: string; username: string; avatar: string | null; }; admin?: { id: string; username: string; } | null; winner?: { id: string; username: string; } | null; } /** * Service de gestion des défis entre joueurs */ export class ChallengeService { /** * Crée un nouveau défi */ async createChallenge( data: CreateChallengeInput ): Promise { // Vérifier que les deux joueurs existent const [challenger, challenged] = await Promise.all([ prisma.user.findUnique({ where: { id: data.challengerId } }), prisma.user.findUnique({ where: { id: data.challengedId } }), ]); if (!challenger) { throw new NotFoundError("Joueur qui lance le défi"); } if (!challenged) { throw new NotFoundError("Joueur qui reçoit le défi"); } // Vérifier qu'on ne se défie pas soi-même if (data.challengerId === data.challengedId) { throw new ValidationError("Vous ne pouvez pas vous défier vous-même"); } return prisma.challenge.create({ data: { challengerId: data.challengerId, challengedId: data.challengedId, title: data.title, description: data.description, pointsReward: data.pointsReward || 100, status: "PENDING", }, }); } /** * Accepte un défi */ async acceptChallenge( challengeId: string, userId: string ): Promise { const challenge = await prisma.challenge.findUnique({ where: { id: challengeId }, }); if (!challenge) { throw new NotFoundError("Défi"); } // Vérifier que l'utilisateur est bien celui qui reçoit le défi if (challenge.challengedId !== userId) { throw new ValidationError( "Vous n'êtes pas autorisé à accepter ce défi" ); } // Vérifier que le défi est en attente if (challenge.status !== "PENDING") { throw new ValidationError( "Ce défi ne peut plus être accepté (statut: " + challenge.status + ")" ); } return prisma.challenge.update({ where: { id: challengeId }, data: { status: "ACCEPTED", acceptedAt: new Date(), }, }); } /** * Annule un défi (par le challenger ou le challenged) */ async cancelChallenge( challengeId: string, userId: string ): Promise { const challenge = await prisma.challenge.findUnique({ where: { id: challengeId }, }); if (!challenge) { throw new NotFoundError("Défi"); } // Vérifier que l'utilisateur est bien impliqué dans le défi if ( challenge.challengerId !== userId && challenge.challengedId !== userId ) { throw new ValidationError( "Vous n'êtes pas autorisé à annuler ce défi" ); } // Vérifier que le défi peut être annulé if (challenge.status === "COMPLETED") { throw new ValidationError("Un défi complété ne peut pas être annulé"); } return prisma.challenge.update({ where: { id: challengeId }, data: { status: "CANCELLED", }, }); } /** * Valide un défi (admin seulement) */ async validateChallenge( challengeId: string, adminId: string, winnerId: string, adminComment?: string ): Promise { const challenge = await prisma.challenge.findUnique({ where: { id: challengeId }, include: { challenger: true, challenged: true, }, }); if (!challenge) { throw new NotFoundError("Défi"); } // Vérifier que le défi est accepté if (challenge.status !== "ACCEPTED") { throw new ValidationError( "Seuls les défis acceptés peuvent être validés" ); } // Vérifier que le winner est bien l'un des deux joueurs if ( winnerId !== challenge.challengerId && winnerId !== challenge.challengedId ) { throw new ValidationError( "Le gagnant doit être l'un des deux joueurs du défi" ); } // Mettre à jour le défi const updatedChallenge = await prisma.challenge.update({ where: { id: challengeId }, data: { status: "COMPLETED", adminId, adminComment: adminComment || null, winnerId, completedAt: new Date(), }, include: { challenger: true, challenged: true, winner: true, }, }); // Attribuer les points au gagnant await prisma.user.update({ where: { id: winnerId }, data: { score: { increment: challenge.pointsReward, }, }, }); return updatedChallenge; } /** * Rejette un défi (admin seulement) */ async rejectChallenge( challengeId: string, adminId: string, adminComment?: string ): Promise { const challenge = await prisma.challenge.findUnique({ where: { id: challengeId }, }); if (!challenge) { throw new NotFoundError("Défi"); } // Vérifier que le défi est accepté if (challenge.status !== "ACCEPTED") { throw new ValidationError( "Seuls les défis acceptés peuvent être rejetés" ); } return prisma.challenge.update({ where: { id: challengeId }, data: { status: "REJECTED", adminId, adminComment: adminComment || null, }, }); } /** * Met à jour un défi (admin seulement) */ async updateChallenge( challengeId: string, data: UpdateChallengeInput ): Promise { const challenge = await prisma.challenge.findUnique({ where: { id: challengeId }, }); if (!challenge) { throw new NotFoundError("Défi"); } const updateData: Prisma.ChallengeUpdateInput = {}; if (data.title !== undefined) { updateData.title = data.title; } if (data.description !== undefined) { updateData.description = data.description; } if (data.pointsReward !== undefined) { updateData.pointsReward = data.pointsReward; } if (data.status !== undefined) { updateData.status = data.status; } if (data.adminId !== undefined) { updateData.admin = data.adminId ? { connect: { id: data.adminId } } : { disconnect: true }; } if (data.adminComment !== undefined) { updateData.adminComment = data.adminComment; } if (data.winnerId !== undefined) { updateData.winner = data.winnerId ? { connect: { id: data.winnerId } } : { disconnect: true }; } return prisma.challenge.update({ where: { id: challengeId }, data: updateData, }); } /** * Supprime un défi (admin seulement) */ async deleteChallenge(challengeId: string): Promise { const challenge = await prisma.challenge.findUnique({ where: { id: challengeId }, }); if (!challenge) { throw new NotFoundError("Défi"); } await prisma.challenge.delete({ where: { id: challengeId }, }); } /** * Récupère un défi par son ID avec les utilisateurs */ async getChallengeById(id: string): Promise { const challenge = await prisma.challenge.findUnique({ where: { id }, include: { challenger: { select: { id: true, username: true, avatar: true, }, }, challenged: { select: { id: true, username: true, avatar: true, }, }, admin: { select: { id: true, username: true, }, }, winner: { select: { id: true, username: true, }, }, }, }); return challenge as ChallengeWithUsers | null; } /** * Récupère tous les défis d'un utilisateur */ async getUserChallenges(userId: string): Promise { return prisma.challenge.findMany({ where: { OR: [ { challengerId: userId }, { challengedId: userId }, ], }, include: { challenger: { select: { id: true, username: true, avatar: true, }, }, challenged: { select: { id: true, username: true, avatar: true, }, }, admin: { select: { id: true, username: true, }, }, winner: { select: { id: true, username: true, }, }, }, orderBy: { createdAt: "desc", }, }) as Promise; } /** * Récupère les défis en attente de validation admin */ async getPendingValidationChallenges(): Promise { return prisma.challenge.findMany({ where: { status: "ACCEPTED", }, include: { challenger: { select: { id: true, username: true, avatar: true, }, }, challenged: { select: { id: true, username: true, avatar: true, }, }, admin: { select: { id: true, username: true, }, }, winner: { select: { id: true, username: true, }, }, }, orderBy: { acceptedAt: "asc", }, }) as Promise; } /** * Récupère tous les défis (pour admin) */ async getAllChallenges(options?: { status?: ChallengeStatus; take?: number; }): Promise { return prisma.challenge.findMany({ where: options?.status ? { status: options.status } : undefined, include: { challenger: { select: { id: true, username: true, avatar: true, }, }, challenged: { select: { id: true, username: true, avatar: true, }, }, admin: { select: { id: true, username: true, }, }, winner: { select: { id: true, username: true, }, }, }, orderBy: { createdAt: "desc", }, take: options?.take, }) as Promise; } } export const challengeService = new ChallengeService();