Files
got-gaming/services/events/event.service.ts

279 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();