- migrations/019: programs table (slug PK, matching assessment_definitions/cohorts convention), family (init/compile/root) and delivery_mode (self_paced/cohort/hybrid) as real CHECK-constrained enums, versioned like assessment_definitions so an edit to curriculum_path/delivery_mode never silently changes what an existing enrollment means. Seeds exactly one program wrapping the existing RootNode/Kubernetes curriculum and capstone — no new curriculum content invented. - programs.js: list/get/create/update, with automatic version bump only when a snapshot-relevant field actually changes.
46 lines
3.0 KiB
SQL
46 lines
3.0 KiB
SQL
-- Phase 5B §2 — programs: first-class learning products, distinct from curriculum JSON itself
|
|
-- (a program is the commercial/academic unit a student enrolls in; curriculum_path is still
|
|
-- just the content it points at, unchanged since Phase 1). program_id IS the slug — same
|
|
-- convention as assessment_definitions/cohorts (a single TEXT primary key, no separate numeric
|
|
-- id + slug pair).
|
|
--
|
|
-- family is the init_/compile_/root_ youth-engineering ladder (see
|
|
-- docs/PHASE5B_SELF_PACED_ENROLLMENT.md §1) — only 'root' has real curriculum content today;
|
|
-- init/compile rows can be added later without a schema change, they just won't have a working
|
|
-- curriculum_path yet. delivery_mode describes how the program is normally taken; an
|
|
-- individual enrollment can still snapshot a different mode (see 020_enrollments.sql).
|
|
--
|
|
-- version is a plain incrementing integer, matching assessment_definitions' existing
|
|
-- versioning convention — enrollments snapshot the version+curriculum_path they were created
|
|
-- against so a later edit to a program never silently changes what an already-enrolled
|
|
-- student is being held to.
|
|
CREATE TABLE IF NOT EXISTS programs (
|
|
program_id TEXT PRIMARY KEY, -- slug, e.g. 'root-kubernetes-foundations'
|
|
name TEXT NOT NULL,
|
|
description TEXT,
|
|
family TEXT NOT NULL CHECK (family IN ('init', 'compile', 'root')),
|
|
delivery_mode TEXT NOT NULL DEFAULT 'self_paced' CHECK (delivery_mode IN ('self_paced', 'cohort', 'hybrid')),
|
|
curriculum_path TEXT NOT NULL DEFAULT 'rootnode-path',
|
|
completion_assessment_id TEXT REFERENCES assessment_definitions(assessment_definition_id),
|
|
estimated_duration_hours INTEGER,
|
|
version INTEGER NOT NULL DEFAULT 1,
|
|
status TEXT NOT NULL DEFAULT 'active' CHECK (status IN ('active', 'inactive')),
|
|
created_by UUID REFERENCES instructors(instructor_id),
|
|
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
|
|
updated_at TIMESTAMPTZ NOT NULL DEFAULT now()
|
|
);
|
|
|
|
CREATE INDEX IF NOT EXISTS idx_programs_status ON programs(status);
|
|
CREATE INDEX IF NOT EXISTS idx_programs_family ON programs(family);
|
|
|
|
-- Seed: the one legitimate self-paced program this phase ships, wrapping the EXISTING
|
|
-- RootNode/Kubernetes curriculum and capstone — no new curriculum content invented. init_/
|
|
-- compile_ programs are intentionally NOT seeded (no real curriculum for them yet, per this
|
|
-- phase's explicit scope line).
|
|
INSERT INTO programs (program_id, name, description, family, delivery_mode, curriculum_path, completion_assessment_id, estimated_duration_hours)
|
|
VALUES (
|
|
'root-kubernetes-foundations', 'ROOT — Kubernetes Foundations',
|
|
'Self-paced Linux, containers, and Kubernetes operations — the existing RootNode curriculum, from Foundations through the RootNode Capstone.',
|
|
'root', 'self_paced', 'rootnode-path', 'rootnode-capstone', 40
|
|
) ON CONFLICT (program_id) DO NOTHING;
|