276 lines
7.0 KiB
TypeScript
276 lines
7.0 KiB
TypeScript
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<Event[]> {
|
|
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<Event | null> {
|
|
return prisma.event.findUnique({
|
|
where: { id },
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Récupère les événements à venir
|
|
*/
|
|
async getUpcomingEvents(limit: number = 3): Promise<Event[]> {
|
|
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<Event> {
|
|
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<Event> {
|
|
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<void> {
|
|
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<Event> {
|
|
// 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<Event> {
|
|
// 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();
|