use uuid::Uuid; use crate::{ models::{Error, QueryError}, sqlite, Context, }; #[derive(Debug, PartialEq, Eq)] pub enum State { Todo, Done, } impl From for State { fn from(done: bool) -> Self { if done { Self::Done } else { Self::Todo } } } impl From for bool { fn from(value: State) -> Self { match value { State::Todo => false, State::Done => true, } } } #[derive(Debug)] pub struct Todo { pub id: Uuid, pub description: String, pub state: State, } struct TodoRow { id: String, description: String, done: bool, } impl TryFrom for Todo { type Error = Error; fn try_from(row: TodoRow) -> Result { Ok(Todo { id: Uuid::try_parse(&row.id)?, description: row.description, state: row.done.into(), }) } } impl Todo { pub fn is_done(&self) -> bool { self.state == State::Done } pub async fn load( ctx: &Context, pool: &sqlite::Pool, trip_id: Uuid, ) -> Result, Error> { let trip_id_param = trip_id.to_string(); let user_id = ctx.user.id.to_string(); let todos: Vec = crate::query_all!( &sqlite::QueryClassification { query_type: sqlite::QueryType::Select, component: sqlite::Component::Todo, }, pool, TodoRow, Todo, r#" SELECT todo.id AS id, todo.description AS description, todo.done AS done FROM trip_todos AS todo INNER JOIN trips ON trips.id = todo.trip_id WHERE trips.id = $1 AND trips.user_id = $2 "#, trip_id_param, user_id, ) .await?; Ok(todos) } #[tracing::instrument] pub async fn find( ctx: &Context, pool: &sqlite::Pool, trip_id: Uuid, todo_id: Uuid, ) -> Result, Error> { let trip_id_param = trip_id.to_string(); let todo_id_param = todo_id.to_string(); let user_id = ctx.user.id.to_string(); crate::query_one!( &sqlite::QueryClassification { query_type: sqlite::QueryType::Select, component: sqlite::Component::Todo, }, pool, TodoRow, Self, r#" SELECT todo.id AS id, todo.description AS description, todo.done AS done FROM trip_todos AS todo INNER JOIN trips ON trips.id = todo.trip_id WHERE trips.id = $1 AND todo.id = $2 AND trips.user_id = $3 "#, trip_id_param, todo_id_param, user_id, ) .await } #[tracing::instrument] pub async fn set_state( ctx: &Context, pool: &sqlite::Pool, trip_id: Uuid, todo_id: Uuid, state: State, ) -> Result<(), Error> { let user_id = ctx.user.id.to_string(); let trip_id_param = trip_id.to_string(); let todo_id_param = todo_id.to_string(); let done = state == State::Done; let result = crate::execute!( &sqlite::QueryClassification { query_type: sqlite::QueryType::Update, component: sqlite::Component::Trips, }, pool, r#" UPDATE trip_todos SET done = ? WHERE trip_id = ? AND id = ? AND EXISTS(SELECT 1 FROM trips WHERE id = ? AND user_id = ?)"#, done, trip_id_param, todo_id_param, trip_id_param, user_id ) .await?; (result.rows_affected() != 0).then_some(()).ok_or_else(|| { Error::Query(QueryError::NotFound { description: format!("todo {todo_id} not found for trip {trip_id}"), }) }) } }