206 lines
5.2 KiB
TypeScript
206 lines
5.2 KiB
TypeScript
import { prisma } from "../database";
|
|
import type { EventFeedback, Prisma } from "@/prisma/generated/prisma/client";
|
|
import { ValidationError, NotFoundError } from "../errors";
|
|
import { eventService } from "./event.service";
|
|
import { sitePreferencesService } from "../preferences/site-preferences.service";
|
|
|
|
export interface CreateOrUpdateFeedbackInput {
|
|
rating: number;
|
|
comment?: string | null;
|
|
}
|
|
|
|
export interface FeedbackStatistics {
|
|
eventId: string;
|
|
eventName: string;
|
|
eventDate: Date | null;
|
|
eventType: string | null;
|
|
averageRating: number;
|
|
feedbackCount: number;
|
|
}
|
|
|
|
/**
|
|
* Service de gestion des feedbacks sur les événements
|
|
*/
|
|
export class EventFeedbackService {
|
|
/**
|
|
* Crée ou met à jour un feedback
|
|
*/
|
|
async createOrUpdateFeedback(
|
|
userId: string,
|
|
eventId: string,
|
|
data: CreateOrUpdateFeedbackInput
|
|
): Promise<EventFeedback> {
|
|
return prisma.eventFeedback.upsert({
|
|
where: {
|
|
userId_eventId: {
|
|
userId,
|
|
eventId,
|
|
},
|
|
},
|
|
update: {
|
|
rating: data.rating,
|
|
comment: data.comment || null,
|
|
},
|
|
create: {
|
|
userId,
|
|
eventId,
|
|
rating: data.rating,
|
|
comment: data.comment || null,
|
|
},
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Récupère le feedback d'un utilisateur pour un événement
|
|
*/
|
|
async getUserFeedback(
|
|
userId: string,
|
|
eventId: string
|
|
): Promise<EventFeedback | null> {
|
|
return prisma.eventFeedback.findUnique({
|
|
where: {
|
|
userId_eventId: {
|
|
userId,
|
|
eventId,
|
|
},
|
|
},
|
|
include: {
|
|
event: {
|
|
select: {
|
|
id: true,
|
|
name: true,
|
|
date: true,
|
|
},
|
|
},
|
|
},
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Récupère tous les feedbacks (pour admin)
|
|
*/
|
|
async getAllFeedbacks(options?: {
|
|
include?: Prisma.EventFeedbackInclude;
|
|
orderBy?: Prisma.EventFeedbackOrderByWithRelationInput;
|
|
}): Promise<EventFeedback[]> {
|
|
return prisma.eventFeedback.findMany({
|
|
include: options?.include || {
|
|
event: {
|
|
select: {
|
|
id: true,
|
|
name: true,
|
|
date: true,
|
|
type: true,
|
|
},
|
|
},
|
|
user: {
|
|
select: {
|
|
id: true,
|
|
username: true,
|
|
email: true,
|
|
avatar: true,
|
|
score: true,
|
|
},
|
|
},
|
|
},
|
|
orderBy: options?.orderBy || { createdAt: "desc" },
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Récupère les statistiques de feedback par événement
|
|
*/
|
|
async getFeedbackStatistics(): Promise<FeedbackStatistics[]> {
|
|
// Calculer les statistiques par événement
|
|
const eventStats = await prisma.eventFeedback.groupBy({
|
|
by: ["eventId"],
|
|
_avg: {
|
|
rating: true,
|
|
},
|
|
_count: {
|
|
id: true,
|
|
},
|
|
});
|
|
|
|
// Récupérer les détails des événements pour les stats
|
|
const eventIds = eventStats.map((stat) => stat.eventId);
|
|
const events = await prisma.event.findMany({
|
|
where: {
|
|
id: {
|
|
in: eventIds,
|
|
},
|
|
},
|
|
select: {
|
|
id: true,
|
|
name: true,
|
|
date: true,
|
|
type: true,
|
|
},
|
|
});
|
|
|
|
// Combiner les stats avec les détails des événements
|
|
return eventStats.map((stat) => {
|
|
const event = events.find((e) => e.id === stat.eventId);
|
|
return {
|
|
eventId: stat.eventId,
|
|
eventName: event?.name || "Événement supprimé",
|
|
eventDate: event?.date || null,
|
|
eventType: event?.type || null,
|
|
averageRating: stat._avg.rating || 0,
|
|
feedbackCount: stat._count.id,
|
|
};
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Valide et crée/met à jour un feedback avec toutes les règles métier
|
|
*/
|
|
async validateAndCreateFeedback(
|
|
userId: string,
|
|
eventId: string,
|
|
data: { rating: number; comment?: string | null }
|
|
): Promise<EventFeedback> {
|
|
// Valider la note (1-5)
|
|
if (!data.rating || data.rating < 1 || data.rating > 5) {
|
|
throw new ValidationError("La note doit être entre 1 et 5", "rating");
|
|
}
|
|
|
|
// Vérifier que l'événement existe
|
|
const event = await eventService.getEventById(eventId);
|
|
if (!event) {
|
|
throw new NotFoundError("Événement");
|
|
}
|
|
|
|
// Vérifier si c'est un nouveau feedback ou une mise à jour
|
|
const existingFeedback = await this.getUserFeedback(userId, eventId);
|
|
const isNewFeedback = !existingFeedback;
|
|
|
|
// Récupérer les points à attribuer depuis les préférences du site
|
|
const sitePreferences = await sitePreferencesService.getOrCreateSitePreferences();
|
|
const pointsToAward = sitePreferences.eventFeedbackPoints || 100;
|
|
|
|
// Créer ou mettre à jour le feedback et attribuer les points (seulement pour nouveau feedback)
|
|
const [feedback] = await Promise.all([
|
|
this.createOrUpdateFeedback(userId, eventId, {
|
|
rating: data.rating,
|
|
comment: data.comment || null,
|
|
}),
|
|
// Attribuer les points seulement si c'est un nouveau feedback
|
|
isNewFeedback
|
|
? prisma.user.update({
|
|
where: { id: userId },
|
|
data: {
|
|
score: {
|
|
increment: pointsToAward,
|
|
},
|
|
},
|
|
})
|
|
: Promise.resolve(null),
|
|
]);
|
|
|
|
return feedback;
|
|
}
|
|
}
|
|
|
|
export const eventFeedbackService = new EventFeedbackService();
|