Skip to content

Commit b974a19

Browse files
committed
refactor(db_goals): 拆目标域至 db_goals_goal.rs
1 parent d012c21 commit b974a19

3 files changed

Lines changed: 153 additions & 128 deletions

File tree

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

Lines changed: 4 additions & 128 deletions
Original file line numberDiff line numberDiff line change
@@ -12,11 +12,7 @@ use rusqlite::{params, Connection};
1212

1313
use crate::db::{unix_seconds, Db};
1414
use crate::error::Result;
15-
use crate::goal_schema::{
16-
Goal, NewGoal, CRITERIA_GROUP_SETTLED, MILESTONE_DONE,
17-
MILESTONE_IN_PROGRESS, MILESTONE_PENDING,
18-
};
19-
use self::milestone::add_milestone_row;
15+
use crate::goal_schema::{CRITERIA_GROUP_SETTLED, MILESTONE_DONE, MILESTONE_IN_PROGRESS, MILESTONE_PENDING};
2016

2117
// 子模块声明(AGENTS.md §3 单文件 ≤300 行;#[path] 使兄弟文件平铺在同目录,各自带 @ai-context)。
2218
/// 里程碑域:里程碑 CRUD + 三写路径共用的插入行 helper。
@@ -25,6 +21,9 @@ mod milestone;
2521
/// AI 规划域:规划落库事务 / 体系链接 / 概念活动 / 规划上下文。
2622
#[path = "db_goals_plan.rs"]
2723
mod plan;
24+
/// 目标域:goals 实体 CRUD(含事务建目标)+ goals 行映射。
25+
#[path = "db_goals_goal.rs"]
26+
mod goal;
2827

2928
/// 三表 DDL + 索引(幂等:CREATE TABLE IF NOT EXISTS;旧库升级自动补表)。
3029
pub(crate) fn init(conn: &Connection) -> Result<()> {
@@ -78,113 +77,6 @@ pub(crate) fn init(conn: &Connection) -> Result<()> {
7877
}
7978

8079
impl Db {
81-
/// 新建目标(事务:goal + 里程碑草案 + 初始绑定组;status=active 一步到位)。
82-
pub fn create_goal(&self, new: &NewGoal) -> Result<Goal> {
83-
let now = unix_seconds();
84-
self.with_conn(|conn| {
85-
let tx = conn.unchecked_transaction()?;
86-
tx.execute(
87-
"INSERT INTO goals (name, domain_tag, status, horizon_end, success_criteria_json, intent_json, created_at, updated_at)
88-
VALUES (?1, ?2, 'active', ?3, ?4, ?5, ?6, ?6)",
89-
params![
90-
new.name,
91-
new.domain_tag,
92-
new.horizon_end,
93-
new.success_criteria_json,
94-
new.intent_json,
95-
now
96-
],
97-
)?;
98-
let goal_id = tx.last_insert_rowid();
99-
for (idx, m) in new.milestones.iter().enumerate() {
100-
add_milestone_row(&tx, goal_id, m, idx as i64, now)?;
101-
}
102-
for gid in &new.group_ids {
103-
tx.execute(
104-
"INSERT OR IGNORE INTO goal_groups (goal_id, group_id, added_at) VALUES (?1, ?2, ?3)",
105-
params![goal_id, gid, now],
106-
)?;
107-
}
108-
tx.commit()?;
109-
Ok(Goal {
110-
id: goal_id,
111-
name: new.name.clone(),
112-
domain_tag: new.domain_tag.clone(),
113-
status: "active".to_string(),
114-
horizon_end: new.horizon_end,
115-
success_criteria_json: new.success_criteria_json.clone(),
116-
intent_json: new.intent_json.clone(),
117-
created_at: now,
118-
completed_at: None,
119-
updated_at: now,
120-
})
121-
})
122-
}
123-
124-
/// 全部目标(列表页;按创建时间倒序——同秒并列时按 id 倒序,顺序确定)。
125-
pub fn list_goals(&self) -> Result<Vec<Goal>> {
126-
self.with_conn(|conn| {
127-
let mut stmt =
128-
conn.prepare("SELECT * FROM goals ORDER BY created_at DESC, id DESC")?;
129-
let rows = stmt.query_map([], row_to_goal)?;
130-
rows.collect::<rusqlite::Result<Vec<_>>>().map_err(Into::into)
131-
})
132-
}
133-
134-
/// 按 id 读取目标(不存在 → None)。
135-
pub fn get_goal(&self, id: i64) -> Result<Option<Goal>> {
136-
self.with_conn(|conn| {
137-
let mut stmt = conn.prepare("SELECT * FROM goals WHERE id = ?1")?;
138-
let mut rows = stmt.query_map(params![id], row_to_goal)?;
139-
match rows.next() {
140-
Some(Ok(g)) => Ok(Some(g)),
141-
Some(Err(e)) => Err(e.into()),
142-
None => Ok(None),
143-
}
144-
})
145-
}
146-
147-
/// 更新目标元数据(名称/领域/时限/判据配方/访谈答案——重访谈配方重推入口)。
148-
pub fn update_goal_core(
149-
&self,
150-
id: i64,
151-
name: &str,
152-
domain_tag: Option<&str>,
153-
horizon_end: Option<i64>,
154-
success_criteria_json: &str,
155-
intent_json: &str,
156-
) -> Result<bool> {
157-
self.with_conn(|conn| {
158-
let affected = conn.execute(
159-
"UPDATE goals SET name = ?2, domain_tag = ?3, horizon_end = ?4,
160-
success_criteria_json = ?5, intent_json = ?6, updated_at = ?7
161-
WHERE id = ?1",
162-
params![id, name, domain_tag, horizon_end, success_criteria_json, intent_json, unix_seconds()],
163-
)?;
164-
Ok(affected > 0)
165-
})
166-
}
167-
168-
/// 状态转移(graduated/abandoned 时写 completed_at;恢复后清空)。
169-
pub fn set_goal_status(&self, id: i64, status: &str) -> Result<bool> {
170-
let now = unix_seconds();
171-
self.with_conn(|conn| {
172-
let affected = conn.execute(
173-
"UPDATE goals SET status = ?2, completed_at = CASE WHEN ?3 THEN ?4 ELSE NULL END, updated_at = ?4
174-
WHERE id = ?1",
175-
params![id, status, status == "graduated" || status == "abandoned", now],
176-
)?;
177-
Ok(affected > 0)
178-
})
179-
}
180-
181-
/// 删除目标(里程碑/绑定随 FK CASCADE;毕业快照保留属 M2——M1 无快照表)。
182-
pub fn delete_goal(&self, id: i64) -> Result<bool> {
183-
self.with_conn(|conn| {
184-
let affected = conn.execute("DELETE FROM goals WHERE id = ?1", params![id])?;
185-
Ok(affected > 0)
186-
})
187-
}
18880

18981
/// 绑定组到目标(UNIQUE 幂等:重复绑定返回 false;组不存在靠外键报错,
19082
/// 命令层先行校验)。
@@ -363,22 +255,6 @@ impl Db {
363255
}
364256
}
365257

366-
/// goals 行 → Goal。
367-
fn row_to_goal(row: &rusqlite::Row<'_>) -> rusqlite::Result<Goal> {
368-
Ok(Goal {
369-
id: row.get(0)?,
370-
name: row.get(1)?,
371-
domain_tag: row.get(2)?,
372-
status: row.get(3)?,
373-
horizon_end: row.get(4)?,
374-
success_criteria_json: row.get(5)?,
375-
intent_json: row.get(6)?,
376-
created_at: row.get(7)?,
377-
completed_at: row.get(8)?,
378-
updated_at: row.get(9)?,
379-
})
380-
}
381-
382258
#[cfg(test)]
383259
#[path = "db_goals_tests.rs"]
384260
mod tests;

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

Lines changed: 144 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,144 @@
1+
//! 目标域:goals 实体 CRUD(含事务建目标)+ goals 行映射。
2+
//!
3+
//! @ai-context: 由 db_goals.rs 按域拆出(AGENTS.md §3 单文件 ≤300 行)。create_goal
4+
//! 是**唯一**跨表显式事务写(goals + goal_milestones 草案 + goal_groups
5+
//! 初始绑定);里程碑插入复用 db_goals_milestone.rs 的 add_milestone_row
6+
//! (与 db_goals_plan.rs 的 apply_plan_core 共用同一份实现)。
7+
//! @ai-context: 副作用/边界:with_conn 单锁不可重入——事务闭包内不得再调 self.*;
8+
//! 返回值在事务内组装、**不重读库**(既有语义,勿改成 get_goal 重读)。
9+
//! @ai-context: `SELECT *` + 位置索引(row_to_goal)与主文件 init 的列顺序是隐性
10+
//! 契约——给 goals 加列必须两处同步。
11+
12+
use rusqlite::params;
13+
14+
use crate::db::{unix_seconds, Db};
15+
use crate::error::Result;
16+
use crate::goal_schema::{Goal, NewGoal};
17+
18+
use super::milestone::add_milestone_row;
19+
20+
impl Db {
21+
/// 新建目标(事务:goal + 里程碑草案 + 初始绑定组;status=active 一步到位)。
22+
pub fn create_goal(&self, new: &NewGoal) -> Result<Goal> {
23+
let now = unix_seconds();
24+
self.with_conn(|conn| {
25+
let tx = conn.unchecked_transaction()?;
26+
tx.execute(
27+
"INSERT INTO goals (name, domain_tag, status, horizon_end, success_criteria_json, intent_json, created_at, updated_at)
28+
VALUES (?1, ?2, 'active', ?3, ?4, ?5, ?6, ?6)",
29+
params![
30+
new.name,
31+
new.domain_tag,
32+
new.horizon_end,
33+
new.success_criteria_json,
34+
new.intent_json,
35+
now
36+
],
37+
)?;
38+
let goal_id = tx.last_insert_rowid();
39+
for (idx, m) in new.milestones.iter().enumerate() {
40+
add_milestone_row(&tx, goal_id, m, idx as i64, now)?;
41+
}
42+
for gid in &new.group_ids {
43+
tx.execute(
44+
"INSERT OR IGNORE INTO goal_groups (goal_id, group_id, added_at) VALUES (?1, ?2, ?3)",
45+
params![goal_id, gid, now],
46+
)?;
47+
}
48+
tx.commit()?;
49+
Ok(Goal {
50+
id: goal_id,
51+
name: new.name.clone(),
52+
domain_tag: new.domain_tag.clone(),
53+
status: "active".to_string(),
54+
horizon_end: new.horizon_end,
55+
success_criteria_json: new.success_criteria_json.clone(),
56+
intent_json: new.intent_json.clone(),
57+
created_at: now,
58+
completed_at: None,
59+
updated_at: now,
60+
})
61+
})
62+
}
63+
64+
/// 全部目标(列表页;按创建时间倒序——同秒并列时按 id 倒序,顺序确定)。
65+
pub fn list_goals(&self) -> Result<Vec<Goal>> {
66+
self.with_conn(|conn| {
67+
let mut stmt =
68+
conn.prepare("SELECT * FROM goals ORDER BY created_at DESC, id DESC")?;
69+
let rows = stmt.query_map([], row_to_goal)?;
70+
rows.collect::<rusqlite::Result<Vec<_>>>().map_err(Into::into)
71+
})
72+
}
73+
74+
/// 按 id 读取目标(不存在 → None)。
75+
pub fn get_goal(&self, id: i64) -> Result<Option<Goal>> {
76+
self.with_conn(|conn| {
77+
let mut stmt = conn.prepare("SELECT * FROM goals WHERE id = ?1")?;
78+
let mut rows = stmt.query_map(params![id], row_to_goal)?;
79+
match rows.next() {
80+
Some(Ok(g)) => Ok(Some(g)),
81+
Some(Err(e)) => Err(e.into()),
82+
None => Ok(None),
83+
}
84+
})
85+
}
86+
87+
/// 更新目标元数据(名称/领域/时限/判据配方/访谈答案——重访谈配方重推入口)。
88+
pub fn update_goal_core(
89+
&self,
90+
id: i64,
91+
name: &str,
92+
domain_tag: Option<&str>,
93+
horizon_end: Option<i64>,
94+
success_criteria_json: &str,
95+
intent_json: &str,
96+
) -> Result<bool> {
97+
self.with_conn(|conn| {
98+
let affected = conn.execute(
99+
"UPDATE goals SET name = ?2, domain_tag = ?3, horizon_end = ?4,
100+
success_criteria_json = ?5, intent_json = ?6, updated_at = ?7
101+
WHERE id = ?1",
102+
params![id, name, domain_tag, horizon_end, success_criteria_json, intent_json, unix_seconds()],
103+
)?;
104+
Ok(affected > 0)
105+
})
106+
}
107+
108+
/// 状态转移(graduated/abandoned 时写 completed_at;恢复后清空)。
109+
pub fn set_goal_status(&self, id: i64, status: &str) -> Result<bool> {
110+
let now = unix_seconds();
111+
self.with_conn(|conn| {
112+
let affected = conn.execute(
113+
"UPDATE goals SET status = ?2, completed_at = CASE WHEN ?3 THEN ?4 ELSE NULL END, updated_at = ?4
114+
WHERE id = ?1",
115+
params![id, status, status == "graduated" || status == "abandoned", now],
116+
)?;
117+
Ok(affected > 0)
118+
})
119+
}
120+
121+
/// 删除目标(里程碑/绑定随 FK CASCADE;毕业快照保留属 M2——M1 无快照表)。
122+
pub fn delete_goal(&self, id: i64) -> Result<bool> {
123+
self.with_conn(|conn| {
124+
let affected = conn.execute("DELETE FROM goals WHERE id = ?1", params![id])?;
125+
Ok(affected > 0)
126+
})
127+
}
128+
}
129+
130+
/// goals 行 → Goal。
131+
fn row_to_goal(row: &rusqlite::Row<'_>) -> rusqlite::Result<Goal> {
132+
Ok(Goal {
133+
id: row.get(0)?,
134+
name: row.get(1)?,
135+
domain_tag: row.get(2)?,
136+
status: row.get(3)?,
137+
horizon_end: row.get(4)?,
138+
success_criteria_json: row.get(5)?,
139+
intent_json: row.get(6)?,
140+
created_at: row.get(7)?,
141+
completed_at: row.get(8)?,
142+
updated_at: row.get(9)?,
143+
})
144+
}

0 commit comments

Comments
 (0)