add todos

This commit is contained in:
2023-09-13 00:44:59 +02:00
parent 4c850f6c0b
commit 6a6c62d736
11 changed files with 495 additions and 5 deletions

View File

@@ -1,7 +1,6 @@
use super::Error;
use crate::{sqlite, Context};
use futures::{TryFutureExt, TryStreamExt};
use uuid::Uuid;
pub struct Inventory {

View File

@@ -8,11 +8,12 @@ use super::{
use crate::{sqlite, Context};
use futures::{TryFutureExt, TryStreamExt};
use serde::{Deserialize, Serialize};
use time;
use uuid::Uuid;
pub mod todos;
// #[macro_use]
// mod macros {
// macro_rules! build_state_query {
@@ -491,6 +492,7 @@ impl TryFrom<DbTripRow> for Trip {
temp_min: row.temp_min,
temp_max: row.temp_max,
comment: row.comment,
todos: None,
types: None,
categories: None,
})
@@ -508,8 +510,9 @@ pub struct Trip {
pub temp_min: Option<i64>,
pub temp_max: Option<i64>,
pub comment: Option<String>,
pub(crate) types: Option<Vec<TripType>>,
pub(crate) categories: Option<Vec<TripCategory>>,
pub todos: Option<Vec<todos::Todo>>,
pub types: Option<Vec<TripType>>,
pub categories: Option<Vec<TripCategory>>,
}
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
@@ -990,7 +993,12 @@ impl Trip {
pub fn categories(&self) -> &Vec<TripCategory> {
self.categories
.as_ref()
.expect("you need to call load_trips_types()")
.expect("you need to call load_categories()")
}
#[tracing::instrument]
pub fn todos(&self) -> &Vec<todos::Todo> {
self.todos.as_ref().expect("you need to call load_todos()")
}
#[tracing::instrument]
@@ -1009,6 +1017,12 @@ impl Trip {
.sum::<i64>()
}
#[tracing::instrument]
pub async fn load_todos(&mut self, ctx: &Context, pool: &sqlite::Pool) -> Result<(), Error> {
self.todos = Some(todos::Todo::load(ctx, pool, self.id).await?);
Ok(())
}
#[tracing::instrument]
pub async fn load_trips_types(
&mut self,

176
src/models/trips/todos.rs Normal file
View File

@@ -0,0 +1,176 @@
use uuid::Uuid;
use crate::{
models::{Error, QueryError},
sqlite, Context,
};
#[derive(Debug, PartialEq, Eq)]
pub enum State {
Todo,
Done,
}
impl From<bool> for State {
fn from(done: bool) -> Self {
if done {
Self::Done
} else {
Self::Todo
}
}
}
impl From<State> 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<TodoRow> for Todo {
type Error = Error;
fn try_from(row: TodoRow) -> Result<Self, Self::Error> {
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<Vec<Self>, Error> {
let trip_id_param = trip_id.to_string();
let user_id = ctx.user.id.to_string();
let todos: Vec<Todo> = 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<Option<Self>, 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}"),
})
})
}
}