Program templates — code map
API (NestJS)
Base path: /organizations/:orgId/program-templates
| Route | Method | Service method |
|---|---|---|
POST / | create | Validates mode-specific shape, inserts header |
GET / | list | Filters: deliveryMode, q, tags[] |
GET /:id | getById | Returns header + cells |
PATCH /:id | update | Header-only; mode/duration changes |
DELETE /:id | remove | Soft delete |
POST /:id/duplicate | duplicate | Header + cells deep copy with ” (copy)” suffix |
POST /:id/workouts | upsertCells | Replace full cell grid |
POST /:id/preview | preview | Dry-run apply, returns the would-be result |
POST /:id/apply | apply | Materialize into target |
POST /from-history | fromHistory | Save-as-template from existing materialized rows |
Controller: apps/api/src/program-templates/program-templates.controller.ts
Service: apps/api/src/program-templates/program-templates.service.ts (~1119 lines)
Module: apps/api/src/program-templates/program-templates.module.ts
DTOs (apps/api/src/program-templates/dto/)
create-program-template.dto.ts—CreateProgramTemplateDtoupdate-program-template.dto.ts—UpdateProgramTemplateDtoupsert-cells.dto.ts—UpsertProgramTemplateCellsDtoapply-template.dto.ts—ApplyProgramTemplateDto,PreviewProgramTemplateDto,ConflictModeschedule-slot.dto.ts—ScheduleSlotDto,ScheduleOccurrenceDtofrom-history.dto.ts—FromHistoryDto
Key service methods
requireCoach(orgId, clerkId)— role gate (owner/admin/coach).loadTemplateWithCells(orgId, id)— single fetch with cells, used by most read paths.assertModeMatches(template, dtoMode)— apply / preview safety check.applyToCoaching(template, cells, dto, orgId, authorUserId)— bulk insertsworkout_assignments. Conflict modes:skip(default),abort,overwrite.applyToSchedule(template, slots, dto, orgId, clerkId)— expands the slots, drops or replaces anything clashing with the live schedule, and writesclass_sessionsin one transaction. Same conflict modes as coaching;overwriteskips sessions that have bookings.buildPlannedSessions(template, slots, dto, orgId)— the expansion, shared by preview and apply so both see the same set.loadExistingSessions(classTypeIds, planned)— live sessions in the applied window plus their booking counts (two grouped queries, no correlated subquery).normalizeOccurrences(occurrences)— dedupes and (day, time)-sorts a slot on every write.buildCoachingPreview,buildSchedulePreview— mode-specific dry-run.readHistoryCells(orgId, dto)— normalize existing materialized rows back to cells (used byfrom-history).assertClassTypeInOrg,assertLocationInOrg,assertMembershipInOrg,assertLibraryWorkoutInOrg— referential integrity helpers.
Pure modules (no DB, unit-tested)
session-conflicts.ts—findSessionConflicts(planned, existing)andgroupConflicts(...). The single definition of a schedule clash, shared by preview and apply.addDays(date, n),toUtc(date, time, tz)— date math helpers (usesdate-fns-tzfromZonedTime).
Web (Next.js)
| Route | File |
|---|---|
| Templates list (per program) | apps/web/src/app/[lang]/(protected)/dashboard/programs/[id]/templates/page.tsx |
| Template detail | apps/web/src/app/[lang]/(protected)/dashboard/programs/[id]/templates/[templateId]/page.tsx |
| Cell editor | apps/web/src/app/[lang]/(protected)/dashboard/programs/[id]/templates/[templateId]/cells/[week]/[day]/edit/page.tsx |
Components
apps/web/src/components/shared/program-grid/— generic week × day grid used by templates and by workout-assignments coaching view (cell-shell.tsx,cell-menu.tsx,empty-cell-menu.tsx,note-prompt-dialog.tsx).- Apply / preview / save-as-template dialogs: search
components/overview/programming/(TODO: verify exact filenames — appears mixed across overview/programming and dashboard).
DB
libs/db/src/lib/schema/program-templates.ts:
program_templates— header rows.program_template_slots— schedule recurrence: one row per class type,occurrences[]as(day, time)pairs.program_template_workouts— cell rows (week × day × slot).
See data-model.md.
Shared
libs/shared/src/lib/schemas/program-template.schema.ts — Zod response shapes (TODO: verify exact exports). Delivery mode enum lives in workout.schema.ts as deliveryModeValues.
Tests
- TODO: verify presence of
program-templates.service.unit.spec.ts(not present at time of writing). The bulk apply logic is currently covered indirectly via e2e flows inapps/web/e2e/specs/coaching-grid.spec.tsandschedule-setup.spec.ts. - Cross-check
apps/web/e2e/specs/coaching-grid.spec.tsfor template-apply UI flows.