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

+153
View File
@@ -396,6 +396,42 @@ export interface paths {
patch?: never;
trace?: never;
};
"/v1/catalog/class-schedules": {
parameters: {
query?: never;
header?: never;
path?: never;
cookie?: never;
};
/**
* 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.
*/
get: operations["listClassSchedules"];
put?: never;
post: operations["createClassSchedule"];
delete?: never;
options?: never;
head?: never;
patch?: never;
trace?: never;
};
"/v1/catalog/class-schedules/{scheduleID}": {
parameters: {
query?: never;
header?: never;
path?: never;
cookie?: never;
};
get?: never;
put?: never;
post?: never;
delete: operations["deleteClassSchedule"];
options?: never;
head?: never;
patch?: never;
trace?: never;
};
"/v1/catalog/class-sessions": {
parameters: {
query?: never;
@@ -1315,6 +1351,23 @@ export interface components {
*/
planCode: "starter" | "pro" | "business";
};
ClassSchedule: {
active: boolean;
/** @description 0=Sunday ... 6=Saturday */
dayOfWeek: number;
/** Format: uuid */
id: string;
/** Format: uuid */
locationId?: string;
locationName?: string;
/** @description Tenant-local start time, HH:MM:SS */
startsLocal: string;
/** Format: uuid */
templateId: string;
templateTitle: string;
/** Format: uuid */
tenantId: string;
};
ClassSession: {
bookedCount: number;
capacity: number;
@@ -1325,6 +1378,11 @@ export interface components {
/** Format: uuid */
locationId?: string;
locationName?: string;
/**
* Format: uuid
* @description Recurring rule that generated this session; absent for manual sessions
*/
scheduleId?: string;
/** Format: date-time */
startsAt: string;
/** Format: uuid */
@@ -1375,6 +1433,16 @@ export interface components {
/** @enum {string} */
status: "confirmed" | "waitlisted";
};
CreateClassScheduleRequest: {
/** @description 0=Sunday ... 6=Saturday */
dayOfWeek: number;
/** Format: uuid */
locationId?: string;
/** @description Tenant-local start time, HH:MM */
startsLocal: string;
/** Format: uuid */
templateId: string;
};
CreateClassSessionRequest: {
capacity?: number;
/** Format: uuid */
@@ -2677,6 +2745,91 @@ export interface operations {
};
};
};
listClassSchedules: {
parameters: {
query?: never;
header?: never;
path?: never;
cookie?: never;
};
requestBody?: never;
responses: {
/** @description Recurring class schedules */
200: {
headers: {
[name: string]: unknown;
};
content: {
"application/json": components["schemas"]["ClassSchedule"][];
};
};
};
};
createClassSchedule: {
parameters: {
query?: never;
header?: never;
path?: never;
cookie?: never;
};
requestBody: {
content: {
"application/json": components["schemas"]["CreateClassScheduleRequest"];
};
};
responses: {
/** @description Class schedule created (sessions materialized immediately) */
201: {
headers: {
[name: string]: unknown;
};
content: {
"application/json": components["schemas"]["ClassSchedule"];
};
};
/** @description Invalid request */
400: {
headers: {
[name: string]: unknown;
};
content?: never;
};
/** @description Template or location not found */
404: {
headers: {
[name: string]: unknown;
};
content?: never;
};
};
};
deleteClassSchedule: {
parameters: {
query?: never;
header?: never;
path: {
scheduleID: string;
};
cookie?: never;
};
requestBody?: never;
responses: {
/** @description Class schedule deleted */
200: {
headers: {
[name: string]: unknown;
};
content?: never;
};
/** @description Class schedule not found */
404: {
headers: {
[name: string]: unknown;
};
content?: never;
};
};
};
listClassSessions: {
parameters: {
query?: never;