96 lines
4.7 KiB
SQL
96 lines
4.7 KiB
SQL
-- =============================================
|
||
-- 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;
|