Files
got-gaming/services/challenges/challenge.service.ts

501 lines
11 KiB
TypeScript

import { prisma } from "../database";
import type {
Challenge,
ChallengeStatus,
Prisma,
} from "@/prisma/generated/prisma/client";
import { ValidationError, NotFoundError, ConflictError } 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<Challenge> {
// 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<Challenge> {
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<Challenge> {
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<Challenge> {
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<Challenge> {
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<Challenge> {
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.adminId = data.adminId;
}
if (data.adminComment !== undefined) {
updateData.adminComment = data.adminComment;
}
if (data.winnerId !== undefined) {
updateData.winnerId = data.winnerId;
}
return prisma.challenge.update({
where: { id: challengeId },
data: updateData,
});
}
/**
* Supprime un défi (admin seulement)
*/
async deleteChallenge(challengeId: string): Promise<void> {
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<ChallengeWithUsers | null> {
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<ChallengeWithUsers[]> {
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<ChallengeWithUsers[]>;
}
/**
* Récupère les défis en attente de validation admin
*/
async getPendingValidationChallenges(): Promise<ChallengeWithUsers[]> {
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<ChallengeWithUsers[]>;
}
/**
* Récupère tous les défis (pour admin)
*/
async getAllChallenges(options?: {
status?: ChallengeStatus;
take?: number;
}): Promise<ChallengeWithUsers[]> {
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<ChallengeWithUsers[]>;
}
}
export const challengeService = new ChallengeService();