//! 项目事件流相关命令(知识图谱 Phase 2 V30,对标设计 §2.4 + §五 get_project_timeline) //! //! 统一事件流 project_events 的查询入口。供 AI / Dashboard 项目时间线视图使用 //! (设计 §10.3.4 模式 B 事件流 + §五 get_project_timeline)。 //! //! 筛选策略:Repo::get_by_project 取最近 N 条(上限 200,对标设计 §10.5 单用户桌面应用 //! 全量查询无压力),应用层按 event_type / 时间范围过滤。复杂多维筛选不进 SQL(数据量小, //! 应用层过滤足够 + 保持 Repo 专用方法精简,对标 TaskLinkRepo 全专用路径)。 use serde::{Deserialize, Serialize}; use tauri::State; use df_storage::models::ProjectEventRecord; use crate::state::AppState; use super::err_str; /// get_project_timeline 查询入参(对标设计 §五 get_project_timeline:time 范围/event_type 筛选)。 /// /// 所有字段可选: /// - `project_id`:必填(查询某项目的事件流)。 /// - `event_type`:可选,按事件类型精确过滤(如 "task_advanced");空/None = 不过滤。 /// - `limit`:可选,返回上限(默认 50,Repo 内部钳制最大 200)。 #[derive(Debug, Deserialize)] pub struct GetTimelineInput { pub project_id: String, /// 事件类型过滤(精确匹配,如 "task_created" / "idea_promoted")。None/空串 = 不过滤。 pub event_type: Option, /// 返回条数上限(默认 50)。Repo 内部钳制 ≤ 200。 #[serde(default = "default_limit")] pub limit: u32, } fn default_limit() -> u32 { 50 } /// 时间线条目(对标设计 §10.3.4 模式 B 事件流)。直接回 ProjectEventRecord(Serialize 已派生), /// 外层补 total 便于前端分页/计数。 #[derive(Debug, Serialize)] pub struct TimelineResult { /// 过滤后的事件列表(时间倒序,Repo 已排) pub items: Vec, /// 过滤后总数(≤ limit) pub total: usize, /// 查询的项目 ID(回显) pub project_id: String, } /// 查询项目事件流(对标设计 §五 get_project_timeline + §2.4)。 /// /// 支持按 event_type 过滤 + limit 上限。时间范围过滤暂未实现(设计 §2.4 列出 time 范围筛选, /// 但当前 get_by_project 仅返回最近 N 条倒序,时间范围筛选用例可后续按需加 Repo 方法; /// 单用户桌面应用事件量小,最近 N 条 + event_type 过滤已覆盖主要场景)。 #[tauri::command] pub async fn get_project_timeline( state: State<'_, AppState>, input: GetTimelineInput, ) -> Result { let project_id = input.project_id.trim().to_string(); if project_id.is_empty() { return Err("project_id 不能为空".to_string()); } // event_type 过滤值规范化(None/空串 = 不过滤) let event_filter = input .event_type .as_deref() .map(str::trim) .filter(|s| !s.is_empty()) .map(|s| s.to_string()); // Repo 取最近 limit 条(内部钳制 ≤ 200),时间倒序。 let mut events = state .project_events .get_by_project(&project_id, input.limit) .await .map_err(err_str)?; // 应用层按 event_type 过滤(SQL 下推不值得——单用户事件量小,保持 Repo 方法精简)。 if let Some(et) = &event_filter { events.retain(|e| e.event_type == *et); } let total = events.len(); Ok(TimelineResult { items: events, total, project_id, }) }