前端: - fix(app): Isar native 文件直接导入 isar_database_native.dart,消除 5 个条件导出类型错误 - chore(app): build_runner 重新生成 .g.dart 文件 (102 outputs) - fix(app): 移除 secure_token_store_factory 未使用的 kIsWeb import 后端: - refactor(diary): 所有创建端点 POST 返回 201 Created (9 handler, 11 端点) - feat(diary): DiaryApiDoc OpenApi derive — 42 路径 + 32 Schema 汇总到 Swagger - feat(diary): DiaryEvent 枚举添加 event_type/payload/to_domain_event 方法 + 4 测试 测试: 84/84 erp-diary 通过, 509/509 全仓库通过, Flutter analyze 0 error
263 lines
8.2 KiB
Rust
263 lines
8.2 KiB
Rust
// erp-diary 事件定义
|
||
//
|
||
// DiaryEvent 是日记模块的领域事件枚举,提供类型安全的事件构建。
|
||
// 通过 `to_domain_event(tenant_id)` 转换为基座 DomainEvent 后发布到 EventBus。
|
||
//
|
||
// 使用方式(Service 层):
|
||
// use crate::event::DiaryEvent;
|
||
// let evt = DiaryEvent::JournalCreated { journal_id, author_id, class_id };
|
||
// event_bus.publish(evt.to_domain_event(tenant_id), db).await;
|
||
|
||
use serde_json::json;
|
||
use uuid::Uuid;
|
||
|
||
use erp_core::events::DomainEvent;
|
||
|
||
/// 日记模块领域事件
|
||
#[derive(Debug, Clone)]
|
||
pub enum DiaryEvent {
|
||
/// 日记创建
|
||
JournalCreated {
|
||
journal_id: Uuid,
|
||
author_id: Uuid,
|
||
class_id: Option<Uuid>,
|
||
},
|
||
/// 日记更新
|
||
JournalUpdated {
|
||
journal_id: Uuid,
|
||
author_id: Uuid,
|
||
version: i32,
|
||
},
|
||
/// 日记删除
|
||
JournalDeleted {
|
||
journal_id: Uuid,
|
||
author_id: Uuid,
|
||
},
|
||
/// 日记分享到班级
|
||
JournalShared {
|
||
journal_id: Uuid,
|
||
author_id: Uuid,
|
||
class_id: Uuid,
|
||
},
|
||
/// 班级创建
|
||
ClassCreated {
|
||
class_id: Uuid,
|
||
teacher_id: Uuid,
|
||
},
|
||
/// 学生加入班级
|
||
StudentJoinedClass {
|
||
class_id: Uuid,
|
||
student_id: Uuid,
|
||
},
|
||
/// 老师布置主题
|
||
TopicAssigned {
|
||
topic_id: Uuid,
|
||
class_id: Uuid,
|
||
teacher_id: Uuid,
|
||
},
|
||
/// 老师点评
|
||
CommentCreated {
|
||
comment_id: Uuid,
|
||
journal_id: Uuid,
|
||
teacher_id: Uuid,
|
||
student_id: Uuid,
|
||
},
|
||
/// 家长绑定孩子
|
||
ParentBound {
|
||
parent_id: Uuid,
|
||
child_id: Uuid,
|
||
},
|
||
/// 成就解锁
|
||
AchievementUnlocked {
|
||
user_id: Uuid,
|
||
achievement_id: String,
|
||
},
|
||
}
|
||
|
||
impl DiaryEvent {
|
||
/// 返回事件类型字符串(用于 DomainEvent.event_type)
|
||
pub fn event_type(&self) -> &'static str {
|
||
match self {
|
||
Self::JournalCreated { .. } => "diary.created",
|
||
Self::JournalUpdated { .. } => "diary.updated",
|
||
Self::JournalDeleted { .. } => "diary.deleted",
|
||
Self::JournalShared { .. } => "diary.shared",
|
||
Self::ClassCreated { .. } => "diary.class.created",
|
||
Self::StudentJoinedClass { .. } => "diary.class.student_joined",
|
||
Self::TopicAssigned { .. } => "diary.topic.assigned",
|
||
Self::CommentCreated { .. } => "diary.comment.created",
|
||
Self::ParentBound { .. } => "diary.parent.binding_confirmed",
|
||
Self::AchievementUnlocked { .. } => "diary.achievement.unlocked",
|
||
}
|
||
}
|
||
|
||
/// 返回事件 payload(JSON 格式)
|
||
pub fn payload(&self) -> serde_json::Value {
|
||
match self {
|
||
Self::JournalCreated {
|
||
journal_id,
|
||
author_id,
|
||
class_id,
|
||
} => json!({
|
||
"journal_id": journal_id,
|
||
"author_id": author_id,
|
||
"class_id": class_id,
|
||
}),
|
||
Self::JournalUpdated {
|
||
journal_id,
|
||
author_id,
|
||
version,
|
||
} => json!({
|
||
"journal_id": journal_id,
|
||
"author_id": author_id,
|
||
"version": version,
|
||
}),
|
||
Self::JournalDeleted {
|
||
journal_id,
|
||
author_id,
|
||
} => json!({
|
||
"journal_id": journal_id,
|
||
"author_id": author_id,
|
||
}),
|
||
Self::JournalShared {
|
||
journal_id,
|
||
author_id,
|
||
class_id,
|
||
} => json!({
|
||
"journal_id": journal_id,
|
||
"author_id": author_id,
|
||
"class_id": class_id,
|
||
}),
|
||
Self::ClassCreated {
|
||
class_id,
|
||
teacher_id,
|
||
} => json!({
|
||
"class_id": class_id,
|
||
"teacher_id": teacher_id,
|
||
}),
|
||
Self::StudentJoinedClass {
|
||
class_id,
|
||
student_id,
|
||
} => json!({
|
||
"class_id": class_id,
|
||
"student_id": student_id,
|
||
}),
|
||
Self::TopicAssigned {
|
||
topic_id,
|
||
class_id,
|
||
teacher_id,
|
||
} => json!({
|
||
"topic_id": topic_id,
|
||
"class_id": class_id,
|
||
"teacher_id": teacher_id,
|
||
}),
|
||
Self::CommentCreated {
|
||
comment_id,
|
||
journal_id,
|
||
teacher_id,
|
||
student_id,
|
||
} => json!({
|
||
"comment_id": comment_id,
|
||
"journal_id": journal_id,
|
||
"teacher_id": teacher_id,
|
||
"student_id": student_id,
|
||
}),
|
||
Self::ParentBound {
|
||
parent_id,
|
||
child_id,
|
||
} => json!({
|
||
"parent_id": parent_id,
|
||
"child_id": child_id,
|
||
}),
|
||
Self::AchievementUnlocked {
|
||
user_id,
|
||
achievement_id,
|
||
} => json!({
|
||
"user_id": user_id,
|
||
"achievement_id": achievement_id,
|
||
}),
|
||
}
|
||
}
|
||
|
||
/// 转换为基座 DomainEvent,可直接发布到 EventBus
|
||
pub fn to_domain_event(&self, tenant_id: Uuid) -> DomainEvent {
|
||
DomainEvent::new(self.event_type(), tenant_id, self.payload())
|
||
}
|
||
}
|
||
|
||
#[cfg(test)]
|
||
mod tests {
|
||
use super::*;
|
||
|
||
#[test]
|
||
fn journal_created_event_type() {
|
||
let id = Uuid::now_v7();
|
||
let evt = DiaryEvent::JournalCreated {
|
||
journal_id: id,
|
||
author_id: id,
|
||
class_id: None,
|
||
};
|
||
assert_eq!(evt.event_type(), "diary.created");
|
||
assert_eq!(evt.payload()["journal_id"], id.to_string());
|
||
}
|
||
|
||
#[test]
|
||
fn class_created_event_type() {
|
||
let id = Uuid::now_v7();
|
||
let evt = DiaryEvent::ClassCreated {
|
||
class_id: id,
|
||
teacher_id: id,
|
||
};
|
||
assert_eq!(evt.event_type(), "diary.class.created");
|
||
}
|
||
|
||
#[test]
|
||
fn to_domain_event_preserves_fields() {
|
||
let tid = Uuid::now_v7();
|
||
let jid = Uuid::now_v7();
|
||
let aid = Uuid::now_v7();
|
||
|
||
let de = DiaryEvent::JournalCreated {
|
||
journal_id: jid,
|
||
author_id: aid,
|
||
class_id: Some(tid),
|
||
}
|
||
.to_domain_event(tid);
|
||
|
||
assert_eq!(de.event_type, "diary.created");
|
||
assert_eq!(de.tenant_id, tid);
|
||
assert_eq!(de.payload["journal_id"], jid.to_string());
|
||
assert_eq!(de.payload["author_id"], aid.to_string());
|
||
assert_eq!(de.payload["class_id"], tid.to_string());
|
||
}
|
||
|
||
#[test]
|
||
fn all_variants_have_correct_event_type() {
|
||
let id = Uuid::now_v7();
|
||
let variants: Vec<DiaryEvent> = vec![
|
||
DiaryEvent::JournalCreated { journal_id: id, author_id: id, class_id: None },
|
||
DiaryEvent::JournalUpdated { journal_id: id, author_id: id, version: 1 },
|
||
DiaryEvent::JournalDeleted { journal_id: id, author_id: id },
|
||
DiaryEvent::JournalShared { journal_id: id, author_id: id, class_id: id },
|
||
DiaryEvent::ClassCreated { class_id: id, teacher_id: id },
|
||
DiaryEvent::StudentJoinedClass { class_id: id, student_id: id },
|
||
DiaryEvent::TopicAssigned { topic_id: id, class_id: id, teacher_id: id },
|
||
DiaryEvent::CommentCreated { comment_id: id, journal_id: id, teacher_id: id, student_id: id },
|
||
DiaryEvent::ParentBound { parent_id: id, child_id: id },
|
||
DiaryEvent::AchievementUnlocked { user_id: id, achievement_id: "first_diary".into() },
|
||
];
|
||
|
||
let types: Vec<&str> = variants.iter().map(|v| v.event_type()).collect();
|
||
assert!(types.contains(&"diary.created"));
|
||
assert!(types.contains(&"diary.updated"));
|
||
assert!(types.contains(&"diary.deleted"));
|
||
assert!(types.contains(&"diary.shared"));
|
||
assert!(types.contains(&"diary.class.created"));
|
||
assert!(types.contains(&"diary.class.student_joined"));
|
||
assert!(types.contains(&"diary.topic.assigned"));
|
||
assert!(types.contains(&"diary.comment.created"));
|
||
assert!(types.contains(&"diary.parent.binding_confirmed"));
|
||
assert!(types.contains(&"diary.achievement.unlocked"));
|
||
}
|
||
}
|