feat(classes): weekly recurring schedules with lazy session materialization

class_schedules rules generate concrete session rows for a rolling 28-day
horizon — no cron; materialize runs idempotently on dashboard and public
reads via a partial unique index on (schedule_id, starts_at). Rule delete
stops future sessions; existing sessions keep bookings and overrides.

Generated with [Devin](https://devin.ai)

Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
This commit is contained in:
Tomas DvorakandDevin committed 2026-09-18 17:31:17 +02:00
1 parent 7b9080eb1c
commit ad60167a31
15 files changed
+1029 -7

No files matched your search

+114
View File
@@ -1350,6 +1350,68 @@ paths:
$ref: "#/components/schemas/SessionRoster"
"404":
description: Class session not found
/v1/catalog/class-schedules:
get:
tags: [Catalog]
operationId: listClassSchedules
security:
- bearerAuth: []
summary: Weekly recurring rules for class sessions
description: >
Each rule materializes concrete class_sessions for a rolling 28-day
horizon in the tenant timezone. Sessions are written idempotently on
read (list class sessions or public availability), so no background
job is needed. Deleting a rule stops future materialization; sessions
already created stay in place and keep their bookings.
responses:
"200":
description: Recurring class schedules
content:
application/json:
schema:
type: array
items:
$ref: "#/components/schemas/ClassSchedule"
post:
tags: [Catalog]
operationId: createClassSchedule
security:
- bearerAuth: []
requestBody:
required: true
content:
application/json:
schema:
$ref: "#/components/schemas/CreateClassScheduleRequest"
responses:
"201":
description: Class schedule created (sessions materialized immediately)
content:
application/json:
schema:
$ref: "#/components/schemas/ClassSchedule"
"400":
description: Invalid request
"404":
description: Template or location not found
/v1/catalog/class-schedules/{scheduleID}:
delete:
tags: [Catalog]
operationId: deleteClassSchedule
security:
- bearerAuth: []
parameters:
- in: path
name: scheduleID
required: true
schema:
type: string
format: uuid
responses:
"200":
description: Class schedule deleted
"404":
description: Class schedule not found
# ---------------- Calendar ----------------
/v1/calendar/feed:
get:
@@ -2755,6 +2817,10 @@ components:
type: integer
waitlistCount:
type: integer
scheduleId:
type: string
format: uuid
description: Recurring rule that generated this session; absent for manual sessions
SessionRosterEntry:
type: object
required: [bookingId, reference, customerName, customerEmail, status, createdAt]
@@ -2825,6 +2891,54 @@ components:
format: date-time
capacity:
type: integer
ClassSchedule:
type: object
required: [id, tenantId, templateId, templateTitle, dayOfWeek, startsLocal, active]
properties:
id:
type: string
format: uuid
tenantId:
type: string
format: uuid
templateId:
type: string
format: uuid
templateTitle:
type: string
locationId:
type: string
format: uuid
locationName:
type: string
dayOfWeek:
type: integer
minimum: 0
maximum: 6
description: 0=Sunday ... 6=Saturday
startsLocal:
type: string
description: Tenant-local start time, HH:MM:SS
active:
type: boolean
CreateClassScheduleRequest:
type: object
required: [templateId, dayOfWeek, startsLocal]
properties:
templateId:
type: string
format: uuid
locationId:
type: string
format: uuid
dayOfWeek:
type: integer
minimum: 0
maximum: 6
description: 0=Sunday ... 6=Saturday
startsLocal:
type: string
description: Tenant-local start time, HH:MM
PlanEntitlements:
type: object
required: [maxLocations, maxStaff, emailReminders, advancedReporting, widgetEmbedding, umamiTracking]