kanban-tui/src/db.rs

236 lines
6.8 KiB
Rust

use crate::{Column, Task};
use anyhow::Error;
use rusqlite::{params, Connection, Result};
use std::ops::{Deref, DerefMut};
pub struct DBConn(pub Connection);
impl DerefMut for DBConn {
fn deref_mut(&mut self) -> &mut Self::Target {
&mut self.0
}
}
impl Deref for DBConn {
type Target = Connection;
fn deref(&self) -> &Self::Target {
&self.0
}
}
impl DBConn {
pub fn new(conn: Connection) -> Self {
DBConn(conn)
}
/// .
///
/// # Errors
///
/// This function will return an error if something is wrong with the SQL
pub fn get_tasks_by_column(&self, column_name: &String) -> Result<Vec<Task>> {
let mut stmt = self.prepare(
r#"
select task.id, title, description from task
join kb_column on column_id = kb_column.id
where kb_column.name = ?1
order by sort_order
"#,
)?;
let mut tasks = Vec::new();
let rows = stmt.query_map([column_name], |row| {
Ok(Task {
id: row.get(0)?,
title: row.get(1)?,
description: row.get(2)?,
})
})?;
for row in rows {
tasks.push(row?);
}
Ok(tasks)
}
/// .
///
/// # Errors
///
/// This function will return an error if there are issues with the SQL
pub fn get_all_columns(&self) -> Result<Vec<Column>> {
let mut stmt = self.prepare("select id, name, selected_task from kb_column")?;
let columns = stmt
.query_map((), |row| {
let name = row.get(1)?;
Ok(Column {
id: row.get(0)?,
tasks: self.get_tasks_by_column(&name)?,
name,
selected_task_idx: row.get(2)?,
})
})?
.filter_map(Result::ok)
.collect();
Ok(columns)
}
/// .
///
/// # Panics
///
/// Panics if something goes wrong with the SQL
pub fn create_new_task(
&self,
title: String,
description: String,
column_id: i64,
) -> Result<Task> {
let mut stmt =
self.prepare(
"insert into task(title, description, column_id, sort_order)
values (?1, ?2, ?3,
(coalesce(1 +
(select sort_order from task
where column_id = ?3 order by sort_order desc limit 1),
0)))")?;
stmt.execute(params![title, description, column_id])?;
let id = self.last_insert_rowid();
Ok(Task {
id,
title,
description,
})
}
/// .
///
/// # Panics
///
/// Panics if something goes wrong with the SQL
pub fn delete_task(&self, task_id: i64) -> Result<()> {
let mut stmt = self.prepare("delete from task where id = ?1")?;
stmt.execute([task_id])?;
Ok(())
}
/// .
///
/// # Panics
///
/// Panics if something goes wrong with the SQL
pub fn update_task_text(&self, task: &Task) -> Result<()> {
let mut stmt =
self.prepare("update task set title = ?2, description = ?3 where id = ?1")?;
stmt.execute((&task.id, &task.title, &task.description))?;
Ok(())
}
/// .
///
/// # Panics
///
/// Panics if something goes wrong with the SQL
pub fn move_task_to_column(&self, task: &Task, target_column: &Column) -> Result<()> {
let mut stmt = self
.prepare(
"update task
set
column_id = ?2,
sort_order = coalesce(1 +
(select sort_order from task
where column_id = ?2 order by sort_order desc limit 1),
0)
where task.id = ?1",
)?;
stmt.execute((&task.id, &target_column.id))?;
self.set_selected_task_for_column(target_column.selected_task_idx, target_column.id)?;
Ok(())
}
/// This is a helper function in case we need to debug sort_order, because I ran into
/// a bug when I forgot to insert the sort_order when creating a task
#[allow(dead_code)]
fn get_sort_order(&self) -> Result<Vec<(i32, String, usize)>> {
let mut stmt = self.prepare(
"select id,title,sort_order from task where column_id = 1")?;
let mut rows = stmt.query(())?;
let mut tasks = Vec::new();
while let Some(row) = rows.next()? {
tasks.push((row.get(0)?, row.get(1)?, row.get(2)?,))
}
Ok(tasks)
}
/// .
///
/// # Panics
///
/// Panics if something goes wrong with the SQL
pub fn swap_task_order(&mut self, task1_id: i64, task2_id: i64) -> Result<()> {
let tx = self.transaction()?;
tx.execute(
"create temp table temp_order as select sort_order from task where id = ?1",
[&task1_id],
)?;
tx.execute(
"update task
set sort_order = (select sort_order from task where id = ?2)
where id = ?1",
(task1_id, task2_id),
)?;
tx.execute(
"update task set sort_order = (select sort_order from temp_order) where id = ?1",
[&task2_id],
)?;
tx.execute("drop table temp_order", ())?;
tx.commit()?;
Ok(())
}
/// # Panics
///
/// Panics if something goes wrong with the SQL
pub fn set_selected_column(&self, column_id: usize) -> Result<(), Error> {
let mut stmt =
self.prepare("insert or replace into app_state(key, value) values (?1, ?2)")?;
stmt.execute((&"selected_column", column_id.to_string()))?;
Ok(())
}
/// # Panics
///
/// Panics if something goes wrong with the SQL
pub fn get_selected_column(&self) -> Result<usize> {
let mut stmt = self.prepare("select value from app_state where key = ?1")?;
stmt.query_row(["selected_column"], |row| {
let value: String = row.get::<usize, String>(0)?;
value.parse::<usize>()
.map_err(|_| rusqlite::Error::InvalidQuery)
})
}
/// # Panics
///
/// Panics if something goes wrong with the SQL
pub fn set_selected_task_for_column(&self, task_idx: usize, column_id: i64) -> Result<()> {
let mut stmt = self.prepare("update kb_column set selected_task = ?2 where id = ?1")?;
stmt.execute((column_id, task_idx))?;
Ok(())
}
/// # Panics
///
/// Panics if something goes wrong with the SQL
pub fn get_selected_task_for_column(&self, column_id: i32) -> Result<usize> {
let mut stmt = self.prepare("select selected_task from kb_column where key = ?1")?;
stmt.query_row([column_id], |row| row.get(0))
}
}