feat(education): complete Flyway migration and atomic submit

This commit is contained in:
2026-07-30 12:06:55 +08:00
parent ce02f8acb4
commit 79a5799502
228 changed files with 32551 additions and 1376 deletions

View File

@@ -0,0 +1,95 @@
-- =============================================
-- Education 模块 — 收藏夹 DDL (PostgreSQL)
-- Ticket #10: 学生收藏题目
-- Migration: 007
-- Prerequisites: 000-education-schema.sql (base tables)
-- =============================================
-- =============================================
-- Preconditions
-- =============================================
-- Operator is expected to verify:
-- SELECT COUNT(*) FROM information_schema.tables
-- WHERE table_catalog = current_database()
-- AND table_name = 'education_favorite';
-- Result MUST be 0 before executing this migration.
-- =============================================
-- 收藏表
-- =============================================
-- Purpose: Student favorites for questions with safe snapshots.
-- Each (tenant, user, target_type, target_id) is a unique entry.
-- Logical deletion: setting deleted=true marks as unfavorited.
-- Re-adding after deletion reactivates the row via ON CONFLICT ... DO UPDATE.
--
-- target_type values: 'QUESTION' (extensible enum)
--
-- Snapshot fields (stem, type, difficulty, options, content_version):
-- populated at creation time from the visible question's safe fields.
-- These snapshots preserve the question state as it appeared when favorited,
-- and remain stable even if the source question later changes or becomes unavailable.
--
-- available flag:
-- FALSE when the source question becomes hidden/unpublished after being
-- favorited. Existing favorites with available=FALSE remain listable but
-- display an "unavailable" indicator. New favorites cannot be created for
-- unavailable resources.
--
-- Indexes:
-- uk_tenant_user_target — per (tenant, user, target_type, target_id) uniqueness.
-- INSERT ... ON CONFLICT DO UPDATE is the primary reactivation path.
-- idx_tenant_user — covers listing queries filtered by current tenant+user.
-- idx_tenant_user_target_type — covers target-type-filtered listing.
CREATE TABLE education_favorite (
id BIGINT GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY,
tenant_id BIGINT NOT NULL,
user_id BIGINT NOT NULL,
target_type VARCHAR(32) NOT NULL,
target_id VARCHAR(64) NOT NULL,
-- safe snapshot fields
stem TEXT DEFAULT NULL,
type VARCHAR(32) DEFAULT NULL,
difficulty VARCHAR(32) DEFAULT NULL,
options JSONB DEFAULT NULL,
content_version VARCHAR(64) NOT NULL DEFAULT '',
-- availability
available BOOLEAN NOT NULL DEFAULT true,
-- audit
creator VARCHAR(64) DEFAULT '',
create_time TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
updater VARCHAR(64) DEFAULT '',
update_time TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
deleted BOOLEAN NOT NULL DEFAULT false,
CONSTRAINT uk_tenant_user_target UNIQUE (tenant_id, user_id, target_type, target_id)
);
COMMENT ON TABLE education_favorite IS '教育-收藏夹';
COMMENT ON COLUMN education_favorite.id IS '主键';
COMMENT ON COLUMN education_favorite.tenant_id IS '租户编号';
COMMENT ON COLUMN education_favorite.user_id IS '学生用户编号';
COMMENT ON COLUMN education_favorite.target_type IS '目标类型QUESTION';
COMMENT ON COLUMN education_favorite.target_id IS '目标 ID题目 ID';
COMMENT ON COLUMN education_favorite.stem IS '题干快照';
COMMENT ON COLUMN education_favorite.type IS '题型快照';
COMMENT ON COLUMN education_favorite.difficulty IS '难度快照';
COMMENT ON COLUMN education_favorite.options IS '选项快照 JSON不含 isCorrect';
COMMENT ON COLUMN education_favorite.content_version IS '题目内容版本';
COMMENT ON COLUMN education_favorite.available IS '源资源是否可用';
COMMENT ON COLUMN education_favorite.creator IS '创建者';
COMMENT ON COLUMN education_favorite.create_time IS '创建时间';
COMMENT ON COLUMN education_favorite.updater IS '更新者';
COMMENT ON COLUMN education_favorite.update_time IS '更新时间';
COMMENT ON COLUMN education_favorite.deleted IS '是否删除';
CREATE INDEX idx_tenant_user ON education_favorite (tenant_id, user_id);
CREATE INDEX idx_tenant_user_target_type ON education_favorite (tenant_id, user_id, target_type);
-- =============================================
-- Post-migration verification queries
-- =============================================
-- Verify new table exists:
-- \d education_favorite
-- Verify unique constraint is enforced:
-- SELECT * FROM pg_indexes WHERE tablename = 'education_favorite' AND indexname = 'uk_tenant_user_target';
-- Verify no orphan data (should be 0 after fresh migration):
-- SELECT COUNT(*) FROM education_favorite;