Class Types — Data Model
Table: class_types in libs/db/src/lib/schema/scheduling.ts.
Columns
| Column | Type | Notes |
|---|---|---|
id | uuid PK | defaultRandom() |
program_id | uuid NOT NULL | FK → programs.id. Org boundary — programs.organization_id is the org. |
name | varchar(255) NOT NULL | Display name. |
description | text | Optional long-form. |
default_duration_min | int | Used to suggest endsAt in the session creator. |
default_capacity | int | Default capacity for new sessions (overridable). |
color | varchar(7) | Hex; renders calendar pill. |
has_workout | bool NOT NULL default true | When false, schedule UX suppresses workout-attachment affordances (“Open Gym” pattern). |
is_active | bool NOT NULL default true | Soft-deactivate flag. |
created_at / updated_at | timestamptz | |
deleted_at | timestamptz | Defined on the column but never written by the service — remove sets is_active=false instead. TODO: verify whether any consumer reads deleted_at. |
Indexes / constraints
No additional indexes declared in the schema file (only the PK). No UNIQUE constraints (multiple class types may share a name within an org).
Relations
program— one-to-one (mandatory parent).classSessions— one-to-many.dailyProgramming— one-to-many (daily_programming.class_type_id).
Lifecycle of a row
- Create —
INSERTwithis_active=true,has_workout=truedefault. Service validates the program belongs to the caller’s org. - Update — partial PATCH; no field except
program_idcan change (program_id is intentionally omitted fromUpdateClassTypeDto). - Deactivate —
UPDATE … SET is_active=false. Historical references remain valid.
No row is ever hard-deleted by the service.
Multi-org isolation pattern
class_types has no direct organization_id column. Org-scoping pivots through the parent programs.organization_id. Every service method:
- Calls
MembershipsService.requireMembership(orgId, clerkId)(403 if not a member). - Joins / fetches the parent
programand verifiesprogram.organizationId === orgId(404 if mismatch).
Soft-delete vs hard-delete
Soft only, via is_active. The deleted_at column exists but is unused — kept for parity with sibling tables.