Skip to Content
Living documentation — last reviewed 2026-05-28
FeaturesClass TypesClass Types — Behavior

Class Types — Behavior

State

Class types do not have a status machine. They have a single boolean is_active:

StateMeaning
is_active=trueSurfaces in findAll, allowed as class_type_id for new sessions.
is_active=falseHidden from findAll. Existing class sessions still reference it — soft-deactivate, not cascade.

DELETE /class-types/:id performs update set is_active=false, never DELETE FROM. The row stays so historical sessions, bookings, and analytics keep their referential integrity.

Invariants

  • Program must belong to the same org as the caller’s membership. Create + update both verify program.organizationId === orgId before mutating (class-types.service.ts:33-42, :115-121).
  • Class type cannot be reassigned to another program — there is no programId field on UpdateClassTypeDto.
  • No hard delete — deactivation only. This is load-bearing for the schedule history.
  • Org isolation via the program — class types have no direct organization_id column. findAll joins class_types to programs and filters programs.organization_id = orgId in SQL — it used to fetch every active class type across every tenant and filter in JS, which meant any consumer holding the (unfiltered) intermediate result could see another org’s rows before the org check ran.
  • findAll accepts an optional programId filter, pushed into the same SQL WHERE clause — GET /class-types?programId= returns only that program’s class types, never another program’s (in-org or not).
  • Default space must belong to the org — defaultLocationId on create/update must be a location of the same org; otherwise BadRequestException('Location not found in this organization'). null clears it. The FK is ON DELETE SET NULL, so a hard delete of the location (DB only; the API only deactivates) clears it.
  • Default space pre-fills, it does not bind — on POST /sessions with locationId omitted (undefined), the session gets defaultLocationId if that space is active. An explicit locationId (including null) wins. Not flag-gated. Space checks then apply to the result when the spaces flag is on.

Golden paths

Owner: add a new class

  1. Go to /dashboard/schedule → “Tracks” / class types tab.
  2. Click “Add class type”, pick parent program from a dropdown, name it, set defaults (60 min, capacity 20, default space, color), toggle hasWorkout if it’s a no-program class.
  3. POST /organizations/:orgId/class-types returns the new row + program metadata.
  4. The class type appears in the create-session form.

Owner: deactivate

  1. Open class type; click “Delete”.
  2. DELETE /organizations/:orgId/class-types/:id.
  3. The class type disappears from findAll. Historical sessions still resolve classType (relational query returns the row regardless of is_active).

Edge cases & error states

ScenarioBehavior
Create with programId belonging to a different orgBadRequestException('Program not found in this organization')
Create/update with defaultLocationId not in this orgBadRequestException('Location not found in this organization')
Default space later deactivateddefaultLocationId stays set; new sessions with locationId omitted get no space (the prefill skips inactive spaces).
Member or coach tries to create/update/deleteForbiddenException('Only owners and admins can …')
Get class type by id from another orgNotFoundException('Class type not found') (post-fetch org check)
Update with empty bodyReturns the row with updated_at bumped; all field updates are conditional on !== undefined.
Deactivate a class type with active future sessionsSessions remain. UI calendar still shows them. Booking still works. (TODO: confirm whether dashboard surfaces a warning.)

Side effects

None outside the row mutation itself. No events emitted, no notifications, no cascades.

Permissions

Actionowneradmincoachmember
List✓✓✓✓
Get by id✓✓✓✓
Create✓✓✗✗
Update✓✓✗✗
Deactivate✓✓✗✗

All endpoints require an active membership in the org; cross-org access fails at requireMembership (403) or the program.organizationId post-check (404).