Files
saqel/backend/migrations/20260909_curriculum_publication_identity.sql
T

75 lines
4.2 KiB
SQL

-- Curriculum publication identity. Apply after taking a schema backup.
-- This migration is additive: it does not rewrite `lessons` or local assets.
CREATE TABLE IF NOT EXISTS `curriculum_lessons` (
`id` BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
`uuid` CHAR(36) NOT NULL,
`grade_key` VARCHAR(64) NOT NULL,
`subject_key` VARCHAR(96) NOT NULL,
`semester_key` VARCHAR(64) NOT NULL,
`unit_key` VARCHAR(96) NOT NULL,
`lesson_key` VARCHAR(128) NOT NULL,
`curriculum_version` VARCHAR(64) NOT NULL,
`title` VARCHAR(500) NOT NULL,
`source_manifest_path` VARCHAR(500) NOT NULL,
`source_status` ENUM('unverified','reviewed','approved','withdrawn') NOT NULL DEFAULT 'unverified',
`reviewed_at` TIMESTAMP NULL DEFAULT NULL,
`reviewed_by` BIGINT UNSIGNED NULL,
`created_at` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
`updated_at` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
PRIMARY KEY (`id`),
UNIQUE KEY `uq_curriculum_lesson_uuid` (`uuid`),
UNIQUE KEY `uq_curriculum_lesson_identity` (`grade_key`,`subject_key`,`semester_key`,`unit_key`,`lesson_key`,`curriculum_version`),
KEY `idx_curriculum_lesson_manifest` (`source_manifest_path`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
CREATE TABLE IF NOT EXISTS `publication_bundles` (
`id` BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
`uuid` CHAR(36) NOT NULL,
`curriculum_lesson_id` BIGINT UNSIGNED NOT NULL,
`bundle_version` VARCHAR(64) NOT NULL,
`status` ENUM('draft','review','published','withdrawn') NOT NULL DEFAULT 'draft',
`published_at` TIMESTAMP NULL DEFAULT NULL,
`withdrawn_at` TIMESTAMP NULL DEFAULT NULL,
`created_at` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (`id`),
UNIQUE KEY `uq_bundle_uuid` (`uuid`),
UNIQUE KEY `uq_bundle_lesson_version` (`curriculum_lesson_id`,`bundle_version`),
KEY `idx_bundle_publication` (`curriculum_lesson_id`,`status`,`published_at`),
CONSTRAINT `fk_bundle_curriculum_lesson` FOREIGN KEY (`curriculum_lesson_id`) REFERENCES `curriculum_lessons` (`id`) ON DELETE RESTRICT
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
CREATE TABLE IF NOT EXISTS `content_assets` (
`id` BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
`uuid` CHAR(36) NOT NULL,
`asset_type` ENUM('lesson_markdown','worksheet_markdown','textbook_pdf','lesson_mindmap_image','unit_mindmap_image','audio','transcript','other') NOT NULL,
`storage_driver` ENUM('local','r2') NOT NULL DEFAULT 'local',
`storage_key` VARCHAR(700) NOT NULL,
`mime_type` VARCHAR(127) NOT NULL,
`byte_size` BIGINT UNSIGNED NOT NULL,
`sha256` CHAR(64) NOT NULL,
`source_reference` VARCHAR(700) NULL,
`rights_status` ENUM('unknown','review_required','cleared','restricted') NOT NULL DEFAULT 'unknown',
`review_status` ENUM('draft','review','approved','withdrawn') NOT NULL DEFAULT 'draft',
`created_at` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (`id`),
UNIQUE KEY `uq_content_asset_uuid` (`uuid`),
UNIQUE KEY `uq_content_asset_integrity` (`storage_driver`,`storage_key`,`sha256`),
KEY `idx_content_asset_status` (`review_status`,`asset_type`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
CREATE TABLE IF NOT EXISTS `publication_bundle_assets` (
`publication_bundle_id` BIGINT UNSIGNED NOT NULL,
`content_asset_id` BIGINT UNSIGNED NOT NULL,
`role` ENUM('primary_lesson','worksheet','textbook','lesson_mindmap','unit_mindmap','audio','transcript','supporting') NOT NULL,
`sort_order` SMALLINT UNSIGNED NOT NULL DEFAULT 0,
`created_at` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (`publication_bundle_id`,`content_asset_id`,`role`),
KEY `idx_bundle_assets_role` (`publication_bundle_id`,`role`,`sort_order`),
CONSTRAINT `fk_bundle_asset_bundle` FOREIGN KEY (`publication_bundle_id`) REFERENCES `publication_bundles` (`id`) ON DELETE CASCADE,
CONSTRAINT `fk_bundle_asset_asset` FOREIGN KEY (`content_asset_id`) REFERENCES `content_assets` (`id`) ON DELETE RESTRICT
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
-- Rollback: do not drop published records. Mark a bundle withdrawn and preserve
-- its assets, then restore the preceding published bundle if one exists.