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,PreviewProgramTemplateDtofrom-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.applyToFeed(template, cells, dto, orgId, authorUserId)— bulk insertsworkout_feed_posts. Skips non-workout cells.applyToSchedule(template, cells, _dto, orgId, authorUserId)— bulk insertsclass_sessions+daily_programmingin one transaction.buildCoachingPreview,buildFeedPreview,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.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_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.