Files
nj/crates/erp-server/migration/src/m20260531_000184_diary_seed_data.rs
iven 3d9896a676 feat(diary): 添加 15 个 SeaORM 实体和数据库迁移 (Phase B1)
实体:
- journal_entry: 日记核心表 (心情/天气/标签/版本)
- journal_element: 日记元素 (文字/图片/贴纸/手写/胶带)
- handwriting_stroke: 手写笔画 (独立大字段表)
- school_class: 班级 (6位码/过期控制)
- class_member: 班级成员 (复合PK)
- topic_assignment: 主题布置
- comment: 老师点评
- sticker_pack + sticker: 贴纸包和贴纸
- template: 日记模板
- achievement + user_achievement: 成就系统
- parent_child_binding: 家长-孩子绑定 (PIPL)
- teacher_profile: 老师档案
- user_settings: 用户设置

迁移 (000170-000184):
- 15 个建表迁移 + 索引 + RLS 策略 + 种子数据
- 所有表含 tenant_id 多租户隔离
- 软删除 + 乐观锁版本号
- 外键级联删除
- 暖记权限注册到基座 permissions 表

验证: cargo check 通过, 425 个测试全通过
2026-05-31 22:29:56 +08:00

100 lines
5.7 KiB
Rust
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// 暖记种子数据 — 默认成就 + 基础贴纸包 + 暖记权限
use sea_orm_migration::prelude::*;
#[derive(DeriveMigrationName)]
pub struct Migration;
#[async_trait::async_trait]
impl MigrationTrait for Migration {
async fn up(&self, manager: &SchemaManager) -> Result<(), DbErr> {
let conn = manager.get_connection();
// 默认租户和系统用户 UUID内嵌 SQL避免类型转换问题
let tid = "'00000000-0000-0000-0000-000000000000'::uuid";
// 插入默认成就
let achievements = [
("first_diary", "初出茅庐", "写下第一篇日记", "📝", "writing", r#"{"type":"diary_count","threshold":1}"#, 10),
("diary_10", "小有成就", "累计写 10 篇日记", "✏️", "writing", r#"{"type":"diary_count","threshold":10}"#, 20),
("diary_50", "笔下生花", "累计写 50 篇日记", "🌸", "writing", r#"{"type":"diary_count","threshold":50}"#, 30),
("diary_100", "日记达人", "累计写 100 篇日记", "🏆", "writing", r#"{"type":"diary_count","threshold":100}"#, 40),
("streak_3", "三日不间断", "连续 3 天写日记", "🔥", "writing", r#"{"type":"streak","threshold":3}"#, 50),
("streak_7", "一周坚持", "连续 7 天写日记", "", "writing", r#"{"type":"streak","threshold":7}"#, 60),
("streak_30", "月度冠军", "连续 30 天写日记", "👑", "writing", r#"{"type":"streak","threshold":30}"#, 70),
("first_share", "分享快乐", "第一次分享日记到班级", "💝", "social", r#"{"type":"share_count","threshold":1}"#, 80),
("comment_received", "获得鼓励", "第一次收到老师点评", "💌", "social", r#"{"type":"comment_received","threshold":1}"#, 90),
("all_moods", "情绪彩虹", "使用过所有 5 种心情", "🌈", "collection", r#"{"type":"mood_variety","threshold":5}"#, 100),
];
for (code, name, desc, icon, category, condition, sort) in &achievements {
let sql = format!(
r#"INSERT INTO achievements (id, tenant_id, code, name, description, icon, category, condition, sort_order, created_at, updated_at, created_by, updated_by, version)
VALUES (gen_random_uuid(), {tid}, '{code}', '{name}', '{desc}', '{icon}', '{category}', '{condition}'::jsonb, {sort}, now(), now(), {tid}, {tid}, 1)"#,
);
conn.execute(sea_orm::Statement::from_string(
sea_orm::DatabaseBackend::Postgres,
sql,
)).await.map_err(|e| DbErr::Custom(e.to_string()))?;
}
// 插入基础贴纸包
conn.execute(sea_orm::Statement::from_string(
sea_orm::DatabaseBackend::Postgres,
format!(
r#"INSERT INTO sticker_packs (id, tenant_id, name, description, is_free, price, category, created_at, updated_at, created_by, updated_by, version)
VALUES (gen_random_uuid(), {tid}, '基础贴纸', '暖记默认贴纸包,包含常用表情和装饰', true, 0, 'basic', now(), now(), {tid}, {tid}, 1)"#,
),
)).await.map_err(|e| DbErr::Custom(e.to_string()))?;
// 插入暖记权限到 permissions 表 (resource + action 模式)
let diary_permissions = [
("diary.journal.create", "创建日记", "journal", "create", "允许创建日记条目"),
("diary.journal.read", "查看日记", "journal", "read", "允许查看日记条目"),
("diary.journal.update", "编辑日记", "journal", "update", "允许编辑日记条目"),
("diary.journal.delete", "删除日记", "journal", "delete", "允许删除日记条目"),
("diary.class.manage", "管理班级", "class", "manage", "允许创建和管理班级"),
("diary.topic.assign", "布置主题", "topic", "assign", "允许老师布置日记主题"),
("diary.comment.write", "写评语", "comment", "write", "允许老师点评日记"),
("diary.parent.bind", "家长绑定", "parent", "bind", "允许家长绑定孩子账号"),
];
for (code, name, resource, action, desc) in &diary_permissions {
let sql = format!(
r#"INSERT INTO permissions (id, tenant_id, code, name, resource, action, description, created_at, updated_at, created_by, updated_by, version)
VALUES (gen_random_uuid(), {tid}, '{code}', '{name}', '{resource}', '{action}', '{desc}', now(), now(), {tid}, {tid}, 1)
ON CONFLICT (tenant_id, code) WHERE deleted_at IS NULL DO NOTHING"#,
);
conn.execute(sea_orm::Statement::from_string(
sea_orm::DatabaseBackend::Postgres,
sql,
)).await.map_err(|e| DbErr::Custom(e.to_string()))?;
}
Ok(())
}
async fn down(&self, manager: &SchemaManager) -> Result<(), DbErr> {
let conn = manager.get_connection();
let tid_str = "WHERE tenant_id = '00000000-0000-0000-0000-000000000000'";
conn.execute(sea_orm::Statement::from_string(
sea_orm::DatabaseBackend::Postgres,
format!("DELETE FROM achievements {tid_str}"),
)).await.map_err(|e| DbErr::Custom(e.to_string()))?;
conn.execute(sea_orm::Statement::from_string(
sea_orm::DatabaseBackend::Postgres,
format!("DELETE FROM sticker_packs {tid_str}"),
)).await.map_err(|e| DbErr::Custom(e.to_string()))?;
conn.execute(sea_orm::Statement::from_string(
sea_orm::DatabaseBackend::Postgres,
format!("DELETE FROM permissions WHERE code LIKE 'diary.%' AND tenant_id = '00000000-0000-0000-0000-000000000000'"),
)).await.map_err(|e| DbErr::Custom(e.to_string()))?;
Ok(())
}
}