501 lines
11 KiB
TypeScript
501 lines
11 KiB
TypeScript
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<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.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<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();
|
|
|