Decompose create from positioning
create and add_to_backlog now just append. The CLI composes
create + move within a transaction when --before/--after is given.
Assisted-by: Claude Opus 4.6 via pi
diff --git a/src/bin/ranger/commands/task.rs b/src/bin/ranger/commands/task.rs
index f333300..00a8943 100644
--- a/src/bin/ranger/commands/task.rs
+++ b/src/bin/ranger/commands/task.rs
@@ -119,8 +119,6 @@ pub enum TaskCommands {
}
pub async fn run(pool: &SqlitePool, command: TaskCommands, json: bool) -> Result<()> {
- let mut conn = pool.acquire().await?;
-
match command {
TaskCommands::Create {
title,
@@ -131,43 +129,46 @@ pub async fn run(pool: &SqlitePool, command: TaskCommands, json: bool) -> Result
tag,
position,
} => {
- let bl = ops::backlog::get_by_name(&mut conn, &backlog).await?;
+ let mut tx = pool.begin().await?;
+
+ let bl = ops::backlog::get_by_name(&mut tx, &backlog).await?;
let parent_id = if let Some(parent_key) = &parent {
- Some(
- ops::task::get_by_key_prefix(&mut conn, parent_key)
- .await?
- .id,
- )
+ Some(ops::task::get_by_key_prefix(&mut tx, parent_key).await?.id)
} else {
None
};
- let (before_id, after_id) = position.resolve(&mut conn).await?;
+ let (before_id, after_id) = position.resolve(&mut tx).await?;
let state = state.map(|s| s.parse::<State>()).transpose()?;
let task = ops::task::create(
- &mut conn,
+ &mut tx,
ops::task::CreateTask {
title: &title,
backlog_id: bl.id,
state,
parent_id,
description: description.as_deref(),
- before_task_id: before_id,
- after_task_id: after_id,
},
)
.await?;
+ if before_id.is_some() || after_id.is_some() {
+ ops::task::move_task(&mut tx, task.id, bl.id, before_id, after_id).await?;
+ }
+
if let Some(tags) = &tag {
for tag_name in tags.split(',').map(str::trim) {
- let t = ops::tag::get_or_create(&mut conn, tag_name).await?;
- ops::tag::add_to_task(&mut conn, task.id, t.id).await?;
+ let t = ops::tag::get_or_create(&mut tx, tag_name).await?;
+ ops::tag::add_to_task(&mut tx, task.id, t.id).await?;
}
}
+ tx.commit().await?;
+
output::print(&task, json, print_task);
}
TaskCommands::List { backlog, state } => {
+ let mut conn = pool.acquire().await?;
let state = state.map(|s| s.parse::<State>()).transpose()?;
if let Some(backlog_key) = &backlog {
@@ -190,6 +191,7 @@ pub async fn run(pool: &SqlitePool, command: TaskCommands, json: bool) -> Result
}
}
TaskCommands::Show { key } => {
+ let mut conn = pool.acquire().await?;
let task = ops::task::get_by_key_prefix(&mut conn, &key).await?;
let comments = ops::comment::list(&mut conn, task.id).await?;
let tags = ops::tag::list_for_task(&mut conn, task.id).await?;
@@ -233,6 +235,7 @@ pub async fn run(pool: &SqlitePool, command: TaskCommands, json: bool) -> Result
description,
state,
} => {
+ let mut conn = pool.acquire().await?;
let state = state.map(|s| s.parse::<State>()).transpose()?;
let task = ops::task::get_by_key_prefix(&mut conn, &key).await?;
@@ -251,6 +254,7 @@ pub async fn run(pool: &SqlitePool, command: TaskCommands, json: bool) -> Result
backlog,
position,
} => {
+ let mut conn = pool.acquire().await?;
let bl = ops::backlog::get_by_name(&mut conn, &backlog).await?;
let task = ops::task::get_by_key_prefix(&mut conn, &key).await?;
let (before_id, after_id) = position.resolve(&mut conn).await?;
@@ -259,18 +263,21 @@ pub async fn run(pool: &SqlitePool, command: TaskCommands, json: bool) -> Result
println!("Moved {} {}", &task.key[..8], task.title);
}
TaskCommands::Add { task, backlog } => {
+ let mut conn = pool.acquire().await?;
let t = ops::task::get_by_key_prefix(&mut conn, &task).await?;
let bl = ops::backlog::get_by_name(&mut conn, &backlog).await?;
ops::task::add_to_backlog(&mut conn, t.id, bl.id).await?;
println!("Added {} to {}", &t.key[..8], bl.name);
}
TaskCommands::Remove { task, backlog } => {
+ let mut conn = pool.acquire().await?;
let t = ops::task::get_by_key_prefix(&mut conn, &task).await?;
let bl = ops::backlog::get_by_name(&mut conn, &backlog).await?;
ops::task::remove_from_backlog(&mut conn, t.id, bl.id).await?;
println!("Removed {} from {}", &t.key[..8], bl.name);
}
TaskCommands::Delete { key } => {
+ let mut conn = pool.acquire().await?;
let task = ops::task::get_by_key_prefix(&mut conn, &key).await?;
ops::task::delete(&mut conn, task.id).await?;
println!("Deleted {} {}", &task.key[..8], task.title);
diff --git a/src/ops/blocker.rs b/src/ops/blocker.rs
index 5446cc6..e05bc09 100644
--- a/src/ops/blocker.rs
+++ b/src/ops/blocker.rs
@@ -70,8 +70,6 @@ mod tests {
state: None,
parent_id: None,
description: None,
- before_task_id: None,
- after_task_id: None,
},
)
.await
@@ -84,8 +82,6 @@ mod tests {
state: None,
parent_id: None,
description: None,
- before_task_id: None,
- after_task_id: None,
},
)
.await
@@ -111,8 +107,6 @@ mod tests {
state: None,
parent_id: None,
description: None,
- before_task_id: None,
- after_task_id: None,
},
)
.await
@@ -125,8 +119,6 @@ mod tests {
state: None,
parent_id: None,
description: None,
- before_task_id: None,
- after_task_id: None,
},
)
.await
diff --git a/src/ops/comment.rs b/src/ops/comment.rs
index fe3f966..3f9285c 100644
--- a/src/ops/comment.rs
+++ b/src/ops/comment.rs
@@ -54,8 +54,6 @@ mod tests {
state: None,
parent_id: None,
description: None,
- before_task_id: None,
- after_task_id: None,
},
)
.await
diff --git a/src/ops/tag.rs b/src/ops/tag.rs
index e09d842..9c74076 100644
--- a/src/ops/tag.rs
+++ b/src/ops/tag.rs
@@ -100,8 +100,6 @@ mod tests {
state: None,
parent_id: None,
description: None,
- before_task_id: None,
- after_task_id: None,
},
)
.await
@@ -128,8 +126,6 @@ mod tests {
state: None,
parent_id: None,
description: None,
- before_task_id: None,
- after_task_id: None,
},
)
.await
diff --git a/src/ops/task.rs b/src/ops/task.rs
index 1ddd695..69d1394 100644
--- a/src/ops/task.rs
+++ b/src/ops/task.rs
@@ -10,8 +10,6 @@ pub struct CreateTask<'a> {
pub state: Option<State>,
pub parent_id: Option<i64>,
pub description: Option<&'a str>,
- pub before_task_id: Option<i64>,
- pub after_task_id: Option<i64>,
}
pub async fn create(
@@ -34,15 +32,17 @@ pub async fn create(
.fetch_one(&mut *conn)
.await?;
- let new_pos = resolve_position(
- &mut *conn,
- params.backlog_id,
- task.id,
- params.before_task_id,
- params.after_task_id,
+ let last_pos: Option<String> = sqlx::query_scalar(
+ "SELECT position FROM backlog_tasks \
+ WHERE backlog_id = ? \
+ ORDER BY position DESC LIMIT 1",
)
+ .bind(params.backlog_id)
+ .fetch_optional(&mut *conn)
.await?;
+ let new_pos = position::midpoint(last_pos.as_deref(), None);
+
sqlx::query("INSERT INTO backlog_tasks (backlog_id, task_id, position) VALUES (?, ?, ?)")
.bind(params.backlog_id)
.bind(task.id)
@@ -278,20 +278,12 @@ pub async fn add_to_backlog(
task_id: i64,
backlog_id: i64,
) -> Result<(), RangerError> {
- // Get the task's state to find proper position
- let state: String = sqlx::query_scalar("SELECT state FROM tasks WHERE id = ?")
- .bind(task_id)
- .fetch_one(&mut *conn)
- .await?;
-
let last_pos: Option<String> = sqlx::query_scalar(
- "SELECT bt.position FROM backlog_tasks bt \
- JOIN tasks t ON t.id = bt.task_id \
- WHERE bt.backlog_id = ? AND t.state = ? \
- ORDER BY bt.position DESC LIMIT 1",
+ "SELECT position FROM backlog_tasks \
+ WHERE backlog_id = ? \
+ ORDER BY position DESC LIMIT 1",
)
.bind(backlog_id)
- .bind(&state)
.fetch_optional(&mut *conn)
.await?;
@@ -355,8 +347,6 @@ mod tests {
state: None,
parent_id: None,
description: None,
- before_task_id: None,
- after_task_id: None,
},
)
.await
@@ -391,8 +381,6 @@ mod tests {
state: None,
parent_id: None,
description: None,
- before_task_id: None,
- after_task_id: None,
},
)
.await
@@ -405,8 +393,6 @@ mod tests {
state: None,
parent_id: None,
description: None,
- before_task_id: None,
- after_task_id: None,
},
)
.await
@@ -419,8 +405,6 @@ mod tests {
state: None,
parent_id: None,
description: None,
- before_task_id: None,
- after_task_id: None,
},
)
.await
@@ -446,8 +430,6 @@ mod tests {
state: None,
parent_id: None,
description: None,
- before_task_id: None,
- after_task_id: None,
},
)
.await
@@ -460,8 +442,6 @@ mod tests {
state: Some(State::Queued),
parent_id: None,
description: None,
- before_task_id: None,
- after_task_id: None,
},
)
.await
@@ -489,8 +469,6 @@ mod tests {
state: None,
parent_id: None,
description: None,
- before_task_id: None,
- after_task_id: None,
},
)
.await
@@ -513,8 +491,6 @@ mod tests {
state: None,
parent_id: None,
description: None,
- before_task_id: None,
- after_task_id: None,
},
)
.await
@@ -549,8 +525,6 @@ mod tests {
state: None,
parent_id: None,
description: None,
- before_task_id: None,
- after_task_id: None,
},
)
.await
@@ -578,8 +552,6 @@ mod tests {
state: None,
parent_id: None,
description: None,
- before_task_id: None,
- after_task_id: None,
},
)
.await
@@ -606,8 +578,6 @@ mod tests {
state: None,
parent_id: None,
description: None,
- before_task_id: None,
- after_task_id: None,
},
)
.await
@@ -620,8 +590,6 @@ mod tests {
state: None,
parent_id: None,
description: None,
- before_task_id: None,
- after_task_id: None,
},
)
.await
@@ -634,8 +602,6 @@ mod tests {
state: None,
parent_id: None,
description: None,
- before_task_id: None,
- after_task_id: None,
},
)
.await
@@ -665,8 +631,6 @@ mod tests {
state: None,
parent_id: None,
description: None,
- before_task_id: None,
- after_task_id: None,
},
)
.await
@@ -679,8 +643,6 @@ mod tests {
state: None,
parent_id: None,
description: None,
- before_task_id: None,
- after_task_id: None,
},
)
.await
@@ -693,8 +655,6 @@ mod tests {
state: None,
parent_id: None,
description: None,
- before_task_id: None,
- after_task_id: None,
},
)
.await
@@ -724,8 +684,6 @@ mod tests {
state: None,
parent_id: None,
description: None,
- before_task_id: None,
- after_task_id: None,
},
)
.await
@@ -738,8 +696,6 @@ mod tests {
state: None,
parent_id: None,
description: None,
- before_task_id: None,
- after_task_id: None,
},
)
.await
@@ -752,8 +708,6 @@ mod tests {
state: None,
parent_id: None,
description: None,
- before_task_id: None,
- after_task_id: None,
},
)
.await
@@ -783,8 +737,6 @@ mod tests {
state: None,
parent_id: None,
description: None,
- before_task_id: None,
- after_task_id: None,
},
)
.await
@@ -797,8 +749,6 @@ mod tests {
state: None,
parent_id: None,
description: None,
- before_task_id: None,
- after_task_id: None,
},
)
.await
@@ -811,8 +761,6 @@ mod tests {
state: None,
parent_id: None,
description: None,
- before_task_id: None,
- after_task_id: None,
},
)
.await
@@ -842,8 +790,6 @@ mod tests {
state: None,
parent_id: None,
description: None,
- before_task_id: None,
- after_task_id: None,
},
)
.await
@@ -856,8 +802,6 @@ mod tests {
state: None,
parent_id: None,
description: None,
- before_task_id: None,
- after_task_id: None,
},
)
.await
@@ -870,8 +814,6 @@ mod tests {
state: None,
parent_id: None,
description: None,
- before_task_id: None,
- after_task_id: None,
},
)
.await
@@ -901,8 +843,6 @@ mod tests {
state: None,
parent_id: None,
description: None,
- before_task_id: None,
- after_task_id: None,
},
)
.await
@@ -917,172 +857,4 @@ mod tests {
let tasks = list(&mut conn, bl.id, None).await.unwrap();
assert_eq!(tasks.len(), 0);
}
-
- #[tokio::test]
- async fn create_task_before() {
- let pool = test_pool().await;
- let mut conn = pool.acquire().await.unwrap();
- let bl = backlog::create(&mut conn, "Test").await.unwrap();
- let t1 = create(
- &mut conn,
- CreateTask {
- title: "First",
- backlog_id: bl.id,
- state: None,
- parent_id: None,
- description: None,
- before_task_id: None,
- after_task_id: None,
- },
- )
- .await
- .unwrap();
- let t2 = create(
- &mut conn,
- CreateTask {
- title: "Second",
- backlog_id: bl.id,
- state: None,
- parent_id: None,
- description: None,
- before_task_id: None,
- after_task_id: None,
- },
- )
- .await
- .unwrap();
-
- // Create a task before t1 — should produce order: t3, t1, t2
- let t3 = create(
- &mut conn,
- CreateTask {
- title: "Before first",
- backlog_id: bl.id,
- state: None,
- parent_id: None,
- description: None,
- before_task_id: Some(t1.id),
- after_task_id: None,
- },
- )
- .await
- .unwrap();
-
- let tasks = list(&mut conn, bl.id, None).await.unwrap();
- assert_eq!(tasks[0].id, t3.id, "t3 should be first");
- assert_eq!(tasks[1].id, t1.id, "t1 should be second");
- assert_eq!(tasks[2].id, t2.id, "t2 should be third");
- }
-
- #[tokio::test]
- async fn create_task_after() {
- let pool = test_pool().await;
- let mut conn = pool.acquire().await.unwrap();
- let bl = backlog::create(&mut conn, "Test").await.unwrap();
- let t1 = create(
- &mut conn,
- CreateTask {
- title: "First",
- backlog_id: bl.id,
- state: None,
- parent_id: None,
- description: None,
- before_task_id: None,
- after_task_id: None,
- },
- )
- .await
- .unwrap();
- let t2 = create(
- &mut conn,
- CreateTask {
- title: "Second",
- backlog_id: bl.id,
- state: None,
- parent_id: None,
- description: None,
- before_task_id: None,
- after_task_id: None,
- },
- )
- .await
- .unwrap();
-
- // Create a task after t1 — should produce order: t1, t3, t2
- let t3 = create(
- &mut conn,
- CreateTask {
- title: "After first",
- backlog_id: bl.id,
- state: None,
- parent_id: None,
- description: None,
- before_task_id: None,
- after_task_id: Some(t1.id),
- },
- )
- .await
- .unwrap();
-
- let tasks = list(&mut conn, bl.id, None).await.unwrap();
- assert_eq!(tasks[0].id, t1.id, "t1 should be first");
- assert_eq!(tasks[1].id, t3.id, "t3 should be second");
- assert_eq!(tasks[2].id, t2.id, "t2 should be third");
- }
-
- #[tokio::test]
- async fn create_task_between() {
- let pool = test_pool().await;
- let mut conn = pool.acquire().await.unwrap();
- let bl = backlog::create(&mut conn, "Test").await.unwrap();
- let t1 = create(
- &mut conn,
- CreateTask {
- title: "First",
- backlog_id: bl.id,
- state: None,
- parent_id: None,
- description: None,
- before_task_id: None,
- after_task_id: None,
- },
- )
- .await
- .unwrap();
- let t2 = create(
- &mut conn,
- CreateTask {
- title: "Second",
- backlog_id: bl.id,
- state: None,
- parent_id: None,
- description: None,
- before_task_id: None,
- after_task_id: None,
- },
- )
- .await
- .unwrap();
-
- // Create a task after t1 and before t2 — should produce order: t1, t3, t2
- let t3 = create(
- &mut conn,
- CreateTask {
- title: "Between",
- backlog_id: bl.id,
- state: None,
- parent_id: None,
- description: None,
- before_task_id: Some(t2.id),
- after_task_id: Some(t1.id),
- },
- )
- .await
- .unwrap();
-
- let tasks = list(&mut conn, bl.id, None).await.unwrap();
- assert_eq!(tasks[0].id, t1.id, "t1 should be first");
- assert_eq!(tasks[1].id, t3.id, "t3 should be second");
- assert_eq!(tasks[2].id, t2.id, "t2 should be third");
- }
}