Productionize EventSphere platform

This commit is contained in:
Austin A
2026-04-25 21:02:19 +01:00
commit 1f1d30a9f5
171 changed files with 18682 additions and 0 deletions

View File

@@ -0,0 +1,51 @@
import { Body, Controller, Get, Post, Query, Req, UseGuards } from "@nestjs/common";
import type { Request } from "express";
import { JwtAuthGuard, PermissionsGuard, RequirePermissions, TenantGuard } from "../auth/guards";
import { CalendarService } from "./calendar.service";
@Controller("calendar")
export class CalendarController {
constructor(private readonly service: CalendarService) {}
@UseGuards(JwtAuthGuard, TenantGuard, PermissionsGuard)
@RequirePermissions("calendar.read")
@Get("routing-forms")
routingForms(@Req() req: Request) {
return this.service.routingForms((req.user as any).tenantId as string);
}
@UseGuards(JwtAuthGuard, TenantGuard, PermissionsGuard)
@RequirePermissions("calendar.write")
@Post("routing-forms")
createRoutingForm(@Req() req: Request, @Body() payload: Record<string, unknown>) {
return this.service.createRoutingForm((req.user as any).tenantId as string, payload);
}
@UseGuards(JwtAuthGuard, TenantGuard, PermissionsGuard)
@RequirePermissions("calendar.read")
@Get("slots")
slots(@Req() req: Request, @Query("routingFormId") routingFormId?: string) {
return this.service.slots((req.user as any).tenantId as string, routingFormId);
}
@UseGuards(JwtAuthGuard, TenantGuard, PermissionsGuard)
@RequirePermissions("calendar.write")
@Post("slots")
createSlot(@Req() req: Request, @Body() payload: Record<string, unknown>) {
return this.service.createSlot((req.user as any).tenantId as string, payload);
}
@UseGuards(JwtAuthGuard, TenantGuard, PermissionsGuard)
@RequirePermissions("calendar.read")
@Get("bookings")
bookings(@Req() req: Request) {
return this.service.bookings((req.user as any).tenantId as string);
}
@UseGuards(JwtAuthGuard, TenantGuard, PermissionsGuard)
@RequirePermissions("calendar.write")
@Post("bookings")
createBooking(@Req() req: Request, @Body() payload: Record<string, unknown>) {
return this.service.createBooking((req.user as any).tenantId as string, payload);
}
}

View File

@@ -0,0 +1,9 @@
import { Module } from "@nestjs/common";
import { CalendarController } from "./calendar.controller";
import { CalendarService } from "./calendar.service";
@Module({
controllers: [CalendarController],
providers: [CalendarService]
})
export class CalendarModule {}

View File

@@ -0,0 +1,92 @@
import { BadRequestException, Injectable, NotFoundException } from "@nestjs/common";
import { PrismaService } from "../../prisma/prisma.service";
@Injectable()
export class CalendarService {
constructor(private readonly prisma: PrismaService) {}
routingForms(tenantId: string) {
return this.prisma.calendarRoutingForm.findMany({
where: { tenantId },
orderBy: { createdAt: "desc" },
include: { event: true, _count: { select: { slots: true, bookings: true } } }
});
}
createRoutingForm(tenantId: string, payload: any) {
return this.prisma.calendarRoutingForm.create({
data: {
tenantId,
eventId: payload.eventId || null,
name: String(payload.name ?? ""),
slug: String(payload.slug ?? "").toLowerCase().trim(),
description: payload.description ? String(payload.description) : null,
durationMinutes: Number(payload.durationMinutes ?? 30),
active: payload.active === undefined ? true : Boolean(payload.active)
}
});
}
slots(tenantId: string, routingFormId?: string) {
return this.prisma.calendarSlot.findMany({
where: { tenantId, ...(routingFormId ? { routingFormId } : {}) },
orderBy: { startsAt: "asc" },
include: { routingForm: true }
});
}
async createSlot(tenantId: string, payload: any) {
const routingForm = await this.prisma.calendarRoutingForm.findFirst({ where: { tenantId, id: String(payload.routingFormId ?? "") } });
if (!routingForm) throw new NotFoundException("Routing form not found");
const startsAt = new Date(String(payload.startsAt ?? ""));
const endsAt = new Date(String(payload.endsAt ?? ""));
if (!Number.isFinite(startsAt.getTime()) || !Number.isFinite(endsAt.getTime()) || endsAt <= startsAt) {
throw new BadRequestException("Invalid slot time");
}
return this.prisma.calendarSlot.create({
data: {
tenantId,
routingFormId: routingForm.id,
startsAt,
endsAt,
capacity: Number(payload.capacity ?? 1),
active: payload.active === undefined ? true : Boolean(payload.active)
},
include: { routingForm: true }
});
}
bookings(tenantId: string) {
return this.prisma.booking.findMany({
where: { tenantId },
orderBy: { startsAt: "desc" },
include: { routingForm: true, event: true, attendee: true }
});
}
async createBooking(tenantId: string, payload: any) {
const routingForm = await this.prisma.calendarRoutingForm.findFirst({ where: { tenantId, id: String(payload.routingFormId ?? "") } });
if (!routingForm) throw new NotFoundException("Routing form not found");
const startsAt = new Date(String(payload.startsAt ?? ""));
const endsAt = new Date(String(payload.endsAt ?? ""));
if (!Number.isFinite(startsAt.getTime()) || !Number.isFinite(endsAt.getTime()) || endsAt <= startsAt) {
throw new BadRequestException("Invalid booking time");
}
return this.prisma.booking.create({
data: {
tenantId,
routingFormId: routingForm.id,
eventId: payload.eventId || routingForm.eventId || null,
attendeeId: payload.attendeeId || null,
fullName: String(payload.fullName ?? ""),
email: String(payload.email ?? "").toLowerCase().trim(),
phone: payload.phone ? String(payload.phone) : null,
startsAt,
endsAt,
status: payload.status ? String(payload.status) : "booked",
notes: payload.notes ? String(payload.notes) : null
},
include: { routingForm: true, event: true, attendee: true }
});
}
}