Skip to content

Commit df2b1a5

Browse files
committed
refactor(db_goals): 拆里程碑域至 db_goals_milestone.rs
1 parent 12eadb4 commit df2b1a5

3 files changed

Lines changed: 149 additions & 124 deletions

File tree

‎app/src-tauri/src/db_goals.rs‎

Lines changed: 7 additions & 123 deletions
Original file line numberDiff line numberDiff line change
@@ -13,9 +13,15 @@ use rusqlite::{params, Connection};
1313
use crate::db::{unix_seconds, Db};
1414
use crate::error::Result;
1515
use crate::goal_schema::{
16-
Goal, GoalMilestone, NewGoal, NewMilestone, CRITERIA_GROUP_SETTLED, MILESTONE_DONE,
16+
Goal, NewGoal, NewMilestone, CRITERIA_GROUP_SETTLED, MILESTONE_DONE,
1717
MILESTONE_IN_PROGRESS, MILESTONE_PENDING,
1818
};
19+
use self::milestone::add_milestone_row;
20+
21+
// 子模块声明(AGENTS.md §3 单文件 ≤300 行;#[path] 使兄弟文件平铺在同目录,各自带 @ai-context)。
22+
/// 里程碑域:里程碑 CRUD + 三写路径共用的插入行 helper。
23+
#[path = "db_goals_milestone.rs"]
24+
mod milestone;
1925

2026
/// 三表 DDL + 索引(幂等:CREATE TABLE IF NOT EXISTS;旧库升级自动补表)。
2127
pub(crate) fn init(conn: &Connection) -> Result<()> {
@@ -177,96 +183,6 @@ impl Db {
177183
})
178184
}
179185

180-
/// 目标里程碑清单(order_idx 升序——计划顺序即创建语义)。
181-
pub fn list_milestones(&self, goal_id: i64) -> Result<Vec<GoalMilestone>> {
182-
self.with_conn(|conn| {
183-
let mut stmt = conn.prepare(
184-
"SELECT * FROM goal_milestones WHERE goal_id = ?1 ORDER BY order_idx ASC, id ASC",
185-
)?;
186-
let rows = stmt.query_map(params![goal_id], row_to_milestone)?;
187-
rows.collect::<rusqlite::Result<Vec<_>>>().map_err(Into::into)
188-
})
189-
}
190-
191-
/// 新增里程碑(order_idx ≤0 时自动追加在末尾;criteria_type 白名单在命令层)。
192-
pub fn add_milestone(&self, goal_id: i64, new: &NewMilestone) -> Result<GoalMilestone> {
193-
let now = unix_seconds();
194-
self.with_conn(|conn| {
195-
let idx = conn.query_row(
196-
"SELECT COALESCE(MAX(order_idx), -1) + 1 FROM goal_milestones WHERE goal_id = ?1",
197-
params![goal_id],
198-
|r| r.get::<_, i64>(0),
199-
)?;
200-
let order = if new.order_idx > 0 { new.order_idx } else { idx };
201-
conn.execute(
202-
"INSERT INTO goal_milestones (goal_id, title, due_at, order_idx, status, criteria_type, ref_group_id, created_at)
203-
VALUES (?1, ?2, ?3, ?4, 'pending', ?5, ?6, ?7)",
204-
params![
205-
goal_id, new.title, new.due_at, order, new.criteria_type,
206-
new.ref_group_id, now
207-
],
208-
)?;
209-
Ok(GoalMilestone {
210-
id: conn.last_insert_rowid(),
211-
goal_id,
212-
title: new.title.clone(),
213-
due_at: new.due_at,
214-
order_idx: order,
215-
status: MILESTONE_PENDING.to_string(),
216-
criteria_type: new.criteria_type.clone(),
217-
ref_group_id: new.ref_group_id,
218-
completed_at: None,
219-
created_at: now,
220-
})
221-
})
222-
}
223-
224-
/// 更新里程碑(整段覆盖:标题/期限;顺序不在此改——增删即重排语义)。
225-
pub fn update_milestone(&self, id: i64, title: &str, due_at: Option<i64>) -> Result<bool> {
226-
self.with_conn(|conn| {
227-
let affected = conn.execute(
228-
"UPDATE goal_milestones SET title = ?2, due_at = ?3 WHERE id = ?1",
229-
params![id, title, due_at],
230-
)?;
231-
Ok(affected > 0)
232-
})
233-
}
234-
235-
/// 按 id 读取里程碑(旧状态读取——状态流转前判据,不存在 → None)。
236-
pub fn get_milestone(&self, id: i64) -> Result<Option<GoalMilestone>> {
237-
self.with_conn(|conn| {
238-
let mut stmt = conn.prepare("SELECT * FROM goal_milestones WHERE id = ?1")?;
239-
let mut rows = stmt.query_map(params![id], row_to_milestone)?;
240-
match rows.next() {
241-
Some(Ok(m)) => Ok(Some(m)),
242-
Some(Err(e)) => Err(e.into()),
243-
None => Ok(None),
244-
}
245-
})
246-
}
247-
248-
/// 删除里程碑(不存在 → false)。
249-
pub fn delete_milestone(&self, id: i64) -> Result<bool> {
250-
self.with_conn(|conn| {
251-
let affected = conn.execute("DELETE FROM goal_milestones WHERE id = ?1", params![id])?;
252-
Ok(affected > 0)
253-
})
254-
}
255-
256-
/// 里程碑状态流转(done 写 completed_at;非完成态清空——状态机白名单在命令层)。
257-
pub fn set_milestone_status(&self, id: i64, status: &str) -> Result<bool> {
258-
let now = unix_seconds();
259-
self.with_conn(|conn| {
260-
let affected = conn.execute(
261-
"UPDATE goal_milestones SET status = ?2,
262-
completed_at = CASE WHEN ?3 THEN ?4 ELSE NULL END
263-
WHERE id = ?1",
264-
params![id, status, status == MILESTONE_DONE, now],
265-
)?;
266-
Ok(affected > 0)
267-
})
268-
}
269-
270186
/// 绑定组到目标(UNIQUE 幂等:重复绑定返回 false;组不存在靠外键报错,
271187
/// 命令层先行校验)。
272188
pub fn bind_group(&self, goal_id: i64, group_id: i64) -> Result<bool> {
@@ -654,22 +570,6 @@ impl Db {
654570
}
655571
}
656572

657-
/// 里程碑插入行(create_goal 事务与 add_milestone 共用;criteria_type 默认 manual)。
658-
fn add_milestone_row(
659-
conn: &Connection,
660-
goal_id: i64,
661-
m: &NewMilestone,
662-
order_idx: i64,
663-
now: i64,
664-
) -> Result<()> {
665-
conn.execute(
666-
"INSERT INTO goal_milestones (goal_id, title, due_at, order_idx, status, criteria_type, ref_group_id, created_at)
667-
VALUES (?1, ?2, ?3, ?4, 'pending', ?5, ?6, ?7)",
668-
params![goal_id, m.title, m.due_at, order_idx, m.criteria_type, m.ref_group_id, now],
669-
)?;
670-
Ok(())
671-
}
672-
673573
/// goals 行 → Goal。
674574
fn row_to_goal(row: &rusqlite::Row<'_>) -> rusqlite::Result<Goal> {
675575
Ok(Goal {
@@ -686,22 +586,6 @@ fn row_to_goal(row: &rusqlite::Row<'_>) -> rusqlite::Result<Goal> {
686586
})
687587
}
688588

689-
/// goal_milestones 行 → GoalMilestone。
690-
fn row_to_milestone(row: &rusqlite::Row<'_>) -> rusqlite::Result<GoalMilestone> {
691-
Ok(GoalMilestone {
692-
id: row.get(0)?,
693-
goal_id: row.get(1)?,
694-
title: row.get(2)?,
695-
due_at: row.get(3)?,
696-
order_idx: row.get(4)?,
697-
status: row.get(5)?,
698-
criteria_type: row.get(6)?,
699-
ref_group_id: row.get(7)?,
700-
completed_at: row.get(8)?,
701-
created_at: row.get(9)?,
702-
})
703-
}
704-
705589
#[cfg(test)]
706590
#[path = "db_goals_tests.rs"]
707591
mod tests;
Lines changed: 141 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,141 @@
1+
//! 里程碑域:`goal_milestones` 实体 CRUD + 里程碑插入行 helper + 行映射。
2+
//!
3+
//! @ai-context: 由 db_goals.rs 按域拆出(AGENTS.md §3 单文件 ≤300 行);主文件
4+
//! db_goals.rs 只留模块文档 + schema init + 子模块声明 + 测试接线。
5+
//! @ai-context: `add_milestone_row` 是 goal_milestones **三条插入路径中两条**的
6+
//! 共用实现——建目标事务在 db_goals_goal.rs(create_goal)、AI 规划
7+
//! 事务在 db_goals_plan.rs(apply_plan_core);第三条 add_milestone
8+
//! 内联 INSERT 保持原样。**只此一份,禁止在别处复制**。
9+
//! @ai-context: 副作用/边界:方法都走 Db::with_conn 单锁(std::sync::Mutex
10+
//! **不可重入**);调用方若已持锁(事务闭包内)只能调
11+
//! `add_milestone_row(&Connection, …)`,不得再进 self.*。
12+
13+
use rusqlite::{params, Connection};
14+
15+
use crate::db::{unix_seconds, Db};
16+
use crate::error::Result;
17+
use crate::goal_schema::{GoalMilestone, NewMilestone, MILESTONE_DONE, MILESTONE_PENDING};
18+
19+
impl Db {
20+
/// 目标里程碑清单(order_idx 升序——计划顺序即创建语义)。
21+
pub fn list_milestones(&self, goal_id: i64) -> Result<Vec<GoalMilestone>> {
22+
self.with_conn(|conn| {
23+
let mut stmt = conn.prepare(
24+
"SELECT * FROM goal_milestones WHERE goal_id = ?1 ORDER BY order_idx ASC, id ASC",
25+
)?;
26+
let rows = stmt.query_map(params![goal_id], row_to_milestone)?;
27+
rows.collect::<rusqlite::Result<Vec<_>>>().map_err(Into::into)
28+
})
29+
}
30+
31+
/// 新增里程碑(order_idx ≤0 时自动追加在末尾;criteria_type 白名单在命令层)。
32+
pub fn add_milestone(&self, goal_id: i64, new: &NewMilestone) -> Result<GoalMilestone> {
33+
let now = unix_seconds();
34+
self.with_conn(|conn| {
35+
let idx = conn.query_row(
36+
"SELECT COALESCE(MAX(order_idx), -1) + 1 FROM goal_milestones WHERE goal_id = ?1",
37+
params![goal_id],
38+
|r| r.get::<_, i64>(0),
39+
)?;
40+
let order = if new.order_idx > 0 { new.order_idx } else { idx };
41+
conn.execute(
42+
"INSERT INTO goal_milestones (goal_id, title, due_at, order_idx, status, criteria_type, ref_group_id, created_at)
43+
VALUES (?1, ?2, ?3, ?4, 'pending', ?5, ?6, ?7)",
44+
params![
45+
goal_id, new.title, new.due_at, order, new.criteria_type,
46+
new.ref_group_id, now
47+
],
48+
)?;
49+
Ok(GoalMilestone {
50+
id: conn.last_insert_rowid(),
51+
goal_id,
52+
title: new.title.clone(),
53+
due_at: new.due_at,
54+
order_idx: order,
55+
status: MILESTONE_PENDING.to_string(),
56+
criteria_type: new.criteria_type.clone(),
57+
ref_group_id: new.ref_group_id,
58+
completed_at: None,
59+
created_at: now,
60+
})
61+
})
62+
}
63+
64+
/// 更新里程碑(整段覆盖:标题/期限;顺序不在此改——增删即重排语义)。
65+
pub fn update_milestone(&self, id: i64, title: &str, due_at: Option<i64>) -> Result<bool> {
66+
self.with_conn(|conn| {
67+
let affected = conn.execute(
68+
"UPDATE goal_milestones SET title = ?2, due_at = ?3 WHERE id = ?1",
69+
params![id, title, due_at],
70+
)?;
71+
Ok(affected > 0)
72+
})
73+
}
74+
75+
/// 按 id 读取里程碑(旧状态读取——状态流转前判据,不存在 → None)。
76+
pub fn get_milestone(&self, id: i64) -> Result<Option<GoalMilestone>> {
77+
self.with_conn(|conn| {
78+
let mut stmt = conn.prepare("SELECT * FROM goal_milestones WHERE id = ?1")?;
79+
let mut rows = stmt.query_map(params![id], row_to_milestone)?;
80+
match rows.next() {
81+
Some(Ok(m)) => Ok(Some(m)),
82+
Some(Err(e)) => Err(e.into()),
83+
None => Ok(None),
84+
}
85+
})
86+
}
87+
88+
/// 删除里程碑(不存在 → false)。
89+
pub fn delete_milestone(&self, id: i64) -> Result<bool> {
90+
self.with_conn(|conn| {
91+
let affected = conn.execute("DELETE FROM goal_milestones WHERE id = ?1", params![id])?;
92+
Ok(affected > 0)
93+
})
94+
}
95+
96+
/// 里程碑状态流转(done 写 completed_at;非完成态清空——状态机白名单在命令层)。
97+
pub fn set_milestone_status(&self, id: i64, status: &str) -> Result<bool> {
98+
let now = unix_seconds();
99+
self.with_conn(|conn| {
100+
let affected = conn.execute(
101+
"UPDATE goal_milestones SET status = ?2,
102+
completed_at = CASE WHEN ?3 THEN ?4 ELSE NULL END
103+
WHERE id = ?1",
104+
params![id, status, status == MILESTONE_DONE, now],
105+
)?;
106+
Ok(affected > 0)
107+
})
108+
}
109+
}
110+
111+
/// 里程碑插入行(create_goal 事务与 add_milestone 共用;criteria_type 默认 manual)。
112+
pub(super) fn add_milestone_row(
113+
conn: &Connection,
114+
goal_id: i64,
115+
m: &NewMilestone,
116+
order_idx: i64,
117+
now: i64,
118+
) -> Result<()> {
119+
conn.execute(
120+
"INSERT INTO goal_milestones (goal_id, title, due_at, order_idx, status, criteria_type, ref_group_id, created_at)
121+
VALUES (?1, ?2, ?3, ?4, 'pending', ?5, ?6, ?7)",
122+
params![goal_id, m.title, m.due_at, order_idx, m.criteria_type, m.ref_group_id, now],
123+
)?;
124+
Ok(())
125+
}
126+
127+
/// goal_milestones 行 → GoalMilestone。
128+
fn row_to_milestone(row: &rusqlite::Row<'_>) -> rusqlite::Result<GoalMilestone> {
129+
Ok(GoalMilestone {
130+
id: row.get(0)?,
131+
goal_id: row.get(1)?,
132+
title: row.get(2)?,
133+
due_at: row.get(3)?,
134+
order_idx: row.get(4)?,
135+
status: row.get(5)?,
136+
criteria_type: row.get(6)?,
137+
ref_group_id: row.get(7)?,
138+
completed_at: row.get(8)?,
139+
created_at: row.get(9)?,
140+
})
141+
}

‎docs/standards/line-limit-exemptions.md‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -103,7 +103,7 @@
103103
| app/src/pages/SessionsPage.tsx | 352 | 本层为状态宿主与数据编排:会话列表/详情状态、事件驱动刷新(自动摘取,待细化) | 若再增长:按职责拆分 |
104104
| app/src/components/NoteLinkToSystem.tsx | 351 | v0.19.7(REQ-286)重构:挂体系选择器(体系下拉 + 三 tab + LinkEntityPicker 搜索树列表 + 三类内联轻建编排 + 既有反查/撤链/钳制语义保持)——实体选择/创建交互已下沉 LinkEntityPicker.tsx(134 行),本文件保留编排与数据装载内聚 | 若再增长:树行构建(flattenNodeRows)与实体装载拆至 useSystemEntities.ts hook |
105105
| app/src-tauri/src/commands_ai_settings.rs | 349 | AI 设置命令域(视图/密钥/授权/目标 AI) + v0.19.1 ai_set_kb_qa 最小面命令(+28)——read-modify-write 同域先例(ai_set_goal_plan)内聚 | 若再增长:kb/goal 最小面命令拆至 commands_ai_settings_extra.rs |
106-
| app/src-tauri/src/commands_goals.rs | 344 | 超硬限(>600 行),不允许豁免 —— v0.18.0(REQ-248~250):学习目标命令域(15 命令 + inner 纯编排 + 访谈校验/埋点/宣言组装/进度收集)——命令薄壳与 inner 同域(commands_knowledge_core 先例);列表/详情/进度三视图共用 collect_signals/goal_card_metrics;v0.18.1 生命周期命令已拆至 commands_goals_lifecycle.rs | 若再增长:里程碑命令组拆至 commands_goals_milestones.rs |
106+
| app/src-tauri/src/commands_goals.rs | 344 | v0.18.0(REQ-248~250)学习目标命令域——批 0-C3 Task 6 分 3 步拆出 `commands_goals_views.rs`(读侧视图)/ `commands_goals_milestones.rs`(里程碑+绑组写入域)/ `commands_goals_intent.rs`(访谈输入契约)三个平铺子模块,`#[path]` 由父模块自己声明 ⇒ `lib.rs` 零改动;本文件保留目标生命周期核心(5 条命令 + 5 个 inner)+ 跨模块 seam(`require_goal`/`collect_signals`/`parse_criteria`,故 `commands_goals_lifecycle.rs`/`commands_goals_plan.rs` 零改动)+ `goal_created` 埋点与访谈组装;11 处 `emit_changed(DataDomain::Goals)` 中 5 处留本文件、6 处随里程碑/绑组命令迁出;15 条 IPC 名逐字不变(注册路径两段→三段共 10 条,已在 `app_commands.rs` 同批改写) | 若再增长:目标 CRUD 核心之外的共享 seam(`require_goal`/`collect_signals`/`parse_criteria`,18 行)可拆至 `commands_goals_seam.rs`(内容地板 ≈213 行) |
107107
| app/src-tauri/src/structure_models.rs | 343 | v0.5.0 模型版:模型清单/独立状态机下载器(进度事件/.part 原子写/按需启用三分类)+ 磁盘就绪判定(disk_done)内聚 | 若再增长:download_one 拆至 structure_download.rs |
108108
| app/src-tauri/src/db_ai_tasks.rs | 340 | 任务注册表在 AppState 内存(HashMap)——重启即失、未采纳(自动摘取,待细化) | 若再增长:按职责拆分 |
109109
| app/src-tauri/src/screen_merge_tests.rs | 339 | 屏级聚合/净化纯函数测试域(聚类/行合并/零跨度/图去重回归)单模块 #[path] 挂载 | 若再增长:净化组拆至 screen_merge_purify_tests.rs |

0 commit comments

Comments
 (0)