import { prisma } from "../database"; import type { Event, Prisma } from "@/prisma/generated/prisma/client"; import { EventType } from "@/prisma/generated/prisma/client"; import { ValidationError, NotFoundError } from "../errors"; import { calculateEventStatus } from "@/lib/eventStatus"; export interface CreateEventInput { date: Date; name: string; description: string; type: EventType; room?: string | null; time?: string | null; maxPlaces?: number | null; } export interface UpdateEventInput { date?: Date; name?: string; description?: string; type?: EventType; room?: string | null; time?: string | null; maxPlaces?: number | null; } export interface EventWithRegistrationsCount extends Event { registrationsCount: number; } /** * Service de gestion des événements */ export class EventService { /** * Récupère tous les événements */ async getAllEvents(options?: { orderBy?: Prisma.EventOrderByWithRelationInput; take?: number; }): Promise { return prisma.event.findMany({ orderBy: options?.orderBy || { date: "asc" }, ...(options?.take && { take: options.take }), }); } /** * Récupère un événement par son ID */ async getEventById(id: string): Promise { return prisma.event.findUnique({ where: { id }, }); } /** * Récupère les événements à venir */ async getUpcomingEvents(limit: number = 3): Promise { const now = new Date(); return prisma.event.findMany({ where: { date: { gte: now, }, }, orderBy: { date: "asc", }, take: limit, }); } /** * Crée un nouvel événement */ async createEvent(data: CreateEventInput): Promise { return prisma.event.create({ data: { date: data.date, name: data.name, description: data.description, type: data.type, room: data.room || null, time: data.time || null, maxPlaces: data.maxPlaces ? parseInt(String(data.maxPlaces)) : null, }, }); } /** * Met à jour un événement */ async updateEvent(id: string, data: UpdateEventInput): Promise { const updateData: Prisma.EventUpdateInput = {}; if (data.date !== undefined) { updateData.date = data.date; } if (data.name !== undefined) { updateData.name = data.name; } if (data.description !== undefined) { updateData.description = data.description; } if (data.type !== undefined) { updateData.type = data.type; } if (data.room !== undefined) { updateData.room = data.room || null; } if (data.time !== undefined) { updateData.time = data.time || null; } if (data.maxPlaces !== undefined) { updateData.maxPlaces = data.maxPlaces ? parseInt(String(data.maxPlaces)) : null; } return prisma.event.update({ where: { id }, data: updateData, }); } /** * Supprime un événement */ async deleteEvent(id: string): Promise { await prisma.event.delete({ where: { id }, }); } /** * Récupère les événements avec le nombre d'inscriptions (pour admin) */ async getEventsWithRegistrationsCount(): Promise< EventWithRegistrationsCount[] > { const events = await prisma.event.findMany({ orderBy: { date: "desc", }, include: { _count: { select: { registrations: true, }, }, }, }); return events.map((event) => ({ ...event, registrationsCount: event._count.registrations, })); } /** * Récupère les événements avec leur statut calculé (pour admin) */ async getEventsWithStatus(): Promise< Array< EventWithRegistrationsCount & { status: "UPCOMING" | "LIVE" | "PAST" } > > { const events = await this.getEventsWithRegistrationsCount(); return events.map((event) => ({ ...event, status: calculateEventStatus(event.date), })); } /** * Valide et crée un événement avec toutes les règles métier */ async validateAndCreateEvent(data: { date: string | Date; name: string; description: string; type: string; room?: string | null; time?: string | null; maxPlaces?: string | number | null; }): Promise { // Validation des champs requis if (!data.date || !data.name || !data.description || !data.type) { throw new ValidationError("Tous les champs sont requis"); } // Convertir et valider la date const eventDate = typeof data.date === "string" ? new Date(data.date) : data.date; if (isNaN(eventDate.getTime())) { throw new ValidationError("Format de date invalide", "date"); } // Valider le type d'événement if (!Object.values(EventType).includes(data.type as EventType)) { throw new ValidationError("Type d'événement invalide", "type"); } // Créer l'événement return this.createEvent({ date: eventDate, name: data.name, description: data.description, type: data.type as EventType, room: data.room || null, time: data.time || null, maxPlaces: data.maxPlaces ? parseInt(String(data.maxPlaces)) : null, }); } /** * Valide et met à jour un événement avec toutes les règles métier */ async validateAndUpdateEvent( id: string, data: { date?: string | Date; name?: string; description?: string; type?: string; room?: string | null; time?: string | null; maxPlaces?: string | number | null; } ): Promise { // Vérifier que l'événement existe const existingEvent = await this.getEventById(id); if (!existingEvent) { throw new NotFoundError("Événement"); } const updateData: UpdateEventInput = {}; // Valider et convertir la date si fournie if (data.date !== undefined) { const eventDate = typeof data.date === "string" ? new Date(data.date) : data.date; if (isNaN(eventDate.getTime())) { throw new ValidationError("Format de date invalide", "date"); } updateData.date = eventDate; } // Valider le type si fourni if (data.type !== undefined) { if (!Object.values(EventType).includes(data.type as EventType)) { throw new ValidationError("Type d'événement invalide", "type"); } updateData.type = data.type as EventType; } // Autres champs if (data.name !== undefined) updateData.name = data.name; if (data.description !== undefined) updateData.description = data.description; if (data.room !== undefined) updateData.room = data.room || null; if (data.time !== undefined) updateData.time = data.time || null; if (data.maxPlaces !== undefined) { updateData.maxPlaces = data.maxPlaces ? parseInt(String(data.maxPlaces)) : null; } return this.updateEvent(id, updateData); } } export const eventService = new EventService();