feat(scheduling): dezky Scheduling — Calendly-style booking on Stalwart calendars
First-party booking system on top of Stalwart calendars (no third-party scheduling dependency). Hosts expose public booking pages; visitors pick a slot computed from the host's live Stalwart free/busy, and confirming writes the event to the host's calendar and sends a dezky-branded confirmation with an .ics. platform-api (services/platform-api/src/scheduling): - Schemas: Host, StalwartCredential (AES-256-GCM at rest), AvailabilitySchedule, EventType, Booking, SlotLock (unique (hostId,startUtc) + TTL). - StalwartCalendarModule: JMAP gateway (free/busy via Principal/getAvailability, event create/delete, scheduleAgent=client) + on-behalf app-password provisioning. CredentialCipher for at-rest encryption. - DST-correct slot engine (Luxon) with unit tests; two-layer double-booking guard (atomic SlotLock + live free/busy re-check). - Booking confirm/cancel/reschedule, branded email + .ics via JMAP submission, self-service manage tokens. /api/v1 public + tenant-gated admin routes, per-IP rate limiting. apps/booking: standalone public, whitelabel booking app (booking.dezky.eu) — path-based tenant resolution, per-tenant brand colour, booking + manage flows. apps/portal: admin scheduling page (hosts, event types, availability, bookings with edit/delete + admin cancel/reschedule) and proxy routes. infra: booking dev service in docker-compose; scheduling env vars.
This commit is contained in:
@@ -0,0 +1,57 @@
|
||||
import { Module } from '@nestjs/common'
|
||||
import { MongooseModule } from '@nestjs/mongoose'
|
||||
import { ThrottlerModule } from '@nestjs/throttler'
|
||||
import { AuthModule } from '../auth/auth.module.js'
|
||||
import { IntegrationsModule } from '../integrations/integrations.module.js'
|
||||
import { AvailabilitySchedule, AvailabilityScheduleSchema } from '../schemas/availability-schedule.schema.js'
|
||||
import { Booking, BookingSchema } from '../schemas/booking.schema.js'
|
||||
import { EventType, EventTypeSchema } from '../schemas/event-type.schema.js'
|
||||
import { Host, HostSchema } from '../schemas/scheduling-host.schema.js'
|
||||
import { SlotLock, SlotLockSchema } from '../schemas/slot-lock.schema.js'
|
||||
import { User, UserSchema } from '../schemas/user.schema.js'
|
||||
import { TenantsModule } from '../tenants/tenants.module.js'
|
||||
import { AvailabilityService } from './availability/availability.service.js'
|
||||
import { BookingsService } from './bookings/bookings.service.js'
|
||||
import { JmapMailer } from './email/jmap-mailer.service.js'
|
||||
import { EventTypesService } from './event-types/event-types.service.js'
|
||||
import { HostsService } from './hosts/hosts.service.js'
|
||||
import { PublicSchedulingController } from './public/public-scheduling.controller.js'
|
||||
import { PublicSchedulingService } from './public/public-scheduling.service.js'
|
||||
import { SchedulingAdminController } from './scheduling-admin.controller.js'
|
||||
import { SlotService } from './slots/slot.service.js'
|
||||
import { StalwartCalendarModule } from './stalwart-calendar/stalwart-calendar.module.js'
|
||||
|
||||
// dezky Scheduling — Calendly-style booking on top of Stalwart calendars. Public
|
||||
// pages (booking.dezky.eu) hit the unauthenticated /api/v1/public routes; host
|
||||
// config sits behind the workspace-portal OIDC. The Stalwart integration is
|
||||
// isolated in StalwartCalendarModule so it can be extracted later.
|
||||
@Module({
|
||||
imports: [
|
||||
MongooseModule.forFeature([
|
||||
{ name: Host.name, schema: HostSchema },
|
||||
{ name: AvailabilitySchedule.name, schema: AvailabilityScheduleSchema },
|
||||
{ name: EventType.name, schema: EventTypeSchema },
|
||||
{ name: Booking.name, schema: BookingSchema },
|
||||
{ name: SlotLock.name, schema: SlotLockSchema },
|
||||
{ name: User.name, schema: UserSchema },
|
||||
]),
|
||||
// Per-IP rate limiting for the public booking endpoints (default read limit;
|
||||
// write endpoints tighten it via @Throttle).
|
||||
ThrottlerModule.forRoot({ throttlers: [{ name: 'default', ttl: 60_000, limit: 60 }] }),
|
||||
AuthModule,
|
||||
TenantsModule,
|
||||
IntegrationsModule, // StalwartClient — host→account lookup during onboarding
|
||||
StalwartCalendarModule,
|
||||
],
|
||||
controllers: [SchedulingAdminController, PublicSchedulingController],
|
||||
providers: [
|
||||
HostsService,
|
||||
AvailabilityService,
|
||||
EventTypesService,
|
||||
SlotService,
|
||||
BookingsService,
|
||||
PublicSchedulingService,
|
||||
JmapMailer,
|
||||
],
|
||||
})
|
||||
export class SchedulingModule {}
|
||||
Reference in New Issue
Block a user