Locations — Data Model
Table: locations in libs/db/src/lib/schema/scheduling.ts.
Columns
| Column | Type | Notes |
|---|---|---|
id | uuid PK | defaultRandom() |
organization_id | uuid NOT NULL | FK → organizations.id. Direct org boundary. |
name | varchar(255) NOT NULL | Display name. |
address | text | Free text; not normalized. |
city | varchar(255) | |
country | varchar(100) | |
latitude | double precision | Decimal degrees. Nullable. |
longitude | double precision | Decimal degrees. Nullable. |
capacity | int | Informational; not used in booking logic. |
is_active | bool NOT NULL default true | Soft-deactivate flag. |
created_at / updated_at | timestamptz | |
deleted_at | timestamptz | Column exists; not written by the service — soft-delete is via is_active=false. |
Indexes / constraints
Only the PK. No UNIQUE; multiple locations can share a name within an org.
Relations
organization— one mandatory.classSessions— one-to-many (nullable on the session side).
Lifecycle
- Create —
INSERTwithis_active=true. Tier check runs againstcount(active locations in org). Address fields and coords are optional. - Update — partial PATCH; all fields conditional on
!== undefined. Cannot changeorganization_id. - Deactivate —
UPDATE … SET is_active=false. Historical sessions keep their FK.
Multi-org isolation pattern
locations has a direct organization_id FK. Every read/write filters on eq(locations.organizationId, orgId). No FK indirection like class_types.
Soft-delete vs hard-delete
Soft only, via is_active. The deleted_at column is unused but present for schema parity.
GPS check-in usage
ClassSessionsService.selfCheckin reads:
session.location?.latitude
session.location?.longitudeBoth must be non-null. If either is missing or the session has no location, selfCheckin with method='gps' returns 400 'Session location coordinates are not configured'. The distance check is Haversine (haversineMeters) compared against GPS_MAX_METERS (see class-sessions/utils/checkin.utils.ts).