This commit is contained in:
2023-09-17 23:25:02 +02:00
parent e0f14f0b9e
commit 2c092d1fc9
4 changed files with 150 additions and 98 deletions

View File

@@ -7,62 +7,66 @@ pub mod crud {
#[async_trait] #[async_trait]
pub trait Create: Sized { pub trait Create: Sized {
type Id: Sized + Send + Sync + Copy + 'static; type Id: Sized + Send + Sync + 'static;
type Filter: Sized + Send + Sync + 'static; type Higher: Sized + Send + Sync + 'static;
type Info: Sized + Send + Sync + 'static; type Info: Sized + Send + Sync + 'static;
async fn create( async fn create(
ctx: &Context, ctx: &Context,
pool: &sqlite::Pool, pool: &sqlite::Pool,
filter: Self::Filter, higher: Self::Higher,
info: Self::Info, info: Self::Info,
) -> Result<Self::Id, Error>; ) -> Result<Self::Id, Error>;
} }
#[async_trait] #[async_trait]
pub trait Read: Sized { pub trait Read: Sized {
type Filter; type Reference;
type Id: Copy; type Higher;
async fn findall( async fn findall(
ctx: &Context, ctx: &Context,
pool: &sqlite::Pool, pool: &sqlite::Pool,
filter: Self::Filter, higher: Self::Higher,
) -> Result<Vec<Self>, Error>; ) -> Result<Vec<Self>, Error>;
async fn find( async fn find(
ctx: &Context, ctx: &Context,
pool: &sqlite::Pool, pool: &sqlite::Pool,
filter: Self::Filter, reference: Self::Reference,
id: Self::Id,
) -> Result<Option<Self>, Error>; ) -> Result<Option<Self>, Error>;
} }
#[async_trait] #[async_trait]
pub trait Update: Sized { pub trait Update: Sized {
type Id: Copy; type Reference;
type Filter;
type UpdateElement; type UpdateElement;
async fn update( async fn update(
ctx: &Context, ctx: &Context,
pool: &sqlite::Pool, pool: &sqlite::Pool,
filter: Self::Filter, reference: Self::Reference,
id: Self::Id,
update: Self::UpdateElement, update: Self::UpdateElement,
) -> Result<Option<Self>, Error>; ) -> Result<Option<Self>, Error>;
} }
pub trait Higher {
type Id: Copy;
type Reference;
fn with_id(&self, id: Self::Id) -> Self::Reference;
}
#[async_trait] #[async_trait]
pub trait Delete: Sized { pub trait Delete: Sized {
type Id: Send + Copy; type Id: Send + Copy;
type Filter: Send + Sync; type Higher: Send + Sync + Higher<Reference = Self::Reference, Id = Self::Id>;
type Reference: Send + Sync;
async fn delete<'c, T>( async fn delete<'c, T>(
ctx: &Context, ctx: &Context,
db: T, db: T,
filter: &Self::Filter, reference: &Self::Reference,
id: Self::Id,
) -> Result<bool, Error> ) -> Result<bool, Error>
where where
// we require something that allows us to get something that implements // we require something that allows us to get something that implements
@@ -79,7 +83,7 @@ pub mod crud {
async fn delete_all<'c>( async fn delete_all<'c>(
ctx: &Context, ctx: &Context,
pool: &'c sqlite::Pool, pool: &'c sqlite::Pool,
filter: Self::Filter, higher: Self::Higher,
ids: Vec<Self::Id>, ids: Vec<Self::Id>,
) -> Result<bool, Error> { ) -> Result<bool, Error> {
use sqlx::Acquire as _; use sqlx::Acquire as _;
@@ -88,7 +92,7 @@ pub mod crud {
let conn = transaction.acquire().await?; let conn = transaction.acquire().await?;
for id in ids { for id in ids {
if !Self::delete(ctx, &mut *conn, &filter, id).await? { if !Self::delete(ctx, &mut *conn, &higher.with_id(id)).await? {
// transaction will rollback on drop // transaction will rollback on drop
return Ok(false); return Ok(false);
} }
@@ -102,14 +106,12 @@ pub mod crud {
#[async_trait] #[async_trait]
pub trait Toggle: Sized { pub trait Toggle: Sized {
type Id: Sized + Send + Sync + Copy + 'static; type Reference: Sized + Send + Sync + 'static;
type Filter: Sized + Send + Sync + 'static;
async fn set( async fn set(
ctx: &Context, ctx: &Context,
pool: &sqlite::Pool, pool: &sqlite::Pool,
filter: Self::Filter, reference: Self::Reference,
id: Self::Id,
value: bool, value: bool,
) -> Result<(), crate::Error>; ) -> Result<(), crate::Error>;
} }
@@ -247,8 +249,6 @@ pub mod route {
#[async_trait] #[async_trait]
pub trait ToggleHtmx: super::crud::Toggle { pub trait ToggleHtmx: super::crud::Toggle {
type Id: Send + Sync + Copy + 'static + From<Self::UrlParams>;
type Filter: Send + Sync + 'static + From<Self::UrlParams>;
type UrlParams: Send + Sync + 'static; type UrlParams: Send + Sync + 'static;
const URL_TRUE: &'static str; const URL_TRUE: &'static str;

View File

@@ -82,14 +82,35 @@ impl TryFrom<TodoRow> for Todo {
} }
} }
#[derive(Debug, Clone)] #[derive(Debug, Copy, Clone)]
pub struct Filter { pub struct Higher {
pub trip_id: Uuid, pub trip_id: Uuid,
} }
impl From<(Uuid, Uuid)> for Filter { impl crud::Higher for Higher {
fn from((trip_id, _todo_id): (Uuid, Uuid)) -> Self { type Id = Id;
Self { trip_id } type Reference = Reference;
fn with_id(&self, id: Self::Id) -> Self::Reference {
Reference {
id,
higher: self.clone(),
}
}
}
#[derive(Debug)]
pub struct Reference {
pub id: Id,
pub higher: Higher,
}
impl From<(Uuid, Uuid)> for Reference {
fn from((trip_id, todo_id): (Uuid, Uuid)) -> Self {
Self {
id: Id::new(todo_id),
higher: Higher { trip_id },
}
} }
} }
@@ -122,15 +143,15 @@ impl Todo {
#[async_trait] #[async_trait]
impl crud::Read for Todo { impl crud::Read for Todo {
type Filter = Filter; type Reference = Reference;
type Id = Id; type Higher = Higher;
async fn findall( async fn findall(
ctx: &Context, ctx: &Context,
pool: &sqlite::Pool, pool: &sqlite::Pool,
filter: Filter, higher: Higher,
) -> Result<Vec<Self>, Error> { ) -> Result<Vec<Self>, Error> {
let trip_id_param = filter.trip_id.to_string(); let trip_id_param = higher.trip_id.to_string();
let user_id = ctx.user.id.to_string(); let user_id = ctx.user.id.to_string();
let todos: Vec<Todo> = crate::query_all!( let todos: Vec<Todo> = crate::query_all!(
@@ -165,11 +186,10 @@ impl crud::Read for Todo {
async fn find( async fn find(
ctx: &Context, ctx: &Context,
pool: &sqlite::Pool, pool: &sqlite::Pool,
filter: Filter, reference: Reference,
todo_id: Id,
) -> Result<Option<Self>, Error> { ) -> Result<Option<Self>, Error> {
let trip_id_param = filter.trip_id.to_string(); let trip_id_param = reference.higher.trip_id.to_string();
let todo_id_param = todo_id.0.to_string(); let todo_id_param = reference.id.0.to_string();
let user_id = ctx.user.id.to_string(); let user_id = ctx.user.id.to_string();
crate::query_one!( crate::query_one!(
&sqlite::QueryClassification { &sqlite::QueryClassification {
@@ -207,20 +227,20 @@ pub struct TodoNew {
#[async_trait] #[async_trait]
impl crud::Create for Todo { impl crud::Create for Todo {
type Id = Id; type Id = Id;
type Filter = Filter; type Higher = Higher;
type Info = TodoNew; type Info = TodoNew;
async fn create( async fn create(
ctx: &Context, ctx: &Context,
pool: &sqlite::Pool, pool: &sqlite::Pool,
filter: Self::Filter, higher: Self::Higher,
info: Self::Info, info: Self::Info,
) -> Result<Self::Id, Error> { ) -> Result<Self::Id, Error> {
let user_id = ctx.user.id.to_string(); let user_id = ctx.user.id.to_string();
let id = Uuid::new_v4(); let id = Uuid::new_v4();
tracing::info!("adding new todo with id {id}"); tracing::info!("adding new todo with id {id}");
let id_param = id.to_string(); let id_param = id.to_string();
let trip_id_param = filter.trip_id.to_string(); let trip_id_param = higher.trip_id.to_string();
crate::execute!( crate::execute!(
&sqlite::QueryClassification { &sqlite::QueryClassification {
query_type: sqlite::QueryType::Insert, query_type: sqlite::QueryType::Insert,
@@ -283,21 +303,19 @@ pub enum UpdateElement {
#[async_trait] #[async_trait]
impl crud::Update for Todo { impl crud::Update for Todo {
type Id = Id; type Reference = Reference;
type Filter = Filter;
type UpdateElement = UpdateElement; type UpdateElement = UpdateElement;
#[tracing::instrument] #[tracing::instrument]
async fn update( async fn update(
ctx: &Context, ctx: &Context,
pool: &sqlite::Pool, pool: &sqlite::Pool,
filter: Self::Filter, reference: Self::Reference,
id: Self::Id,
update_element: Self::UpdateElement, update_element: Self::UpdateElement,
) -> Result<Option<Self>, Error> { ) -> Result<Option<Self>, Error> {
let user_id = ctx.user.id.to_string(); let user_id = ctx.user.id.to_string();
let trip_id_param = filter.trip_id.to_string(); let trip_id_param = reference.higher.trip_id.to_string();
let todo_id_param = id.to_string(); let todo_id_param = reference.id.to_string();
match update_element { match update_element {
UpdateElement::State(state) => { UpdateElement::State(state) => {
let done = state == State::Done.into(); let done = state == State::Done.into();
@@ -333,8 +351,8 @@ impl crud::Update for Todo {
} }
UpdateElement::Description(new_description) => { UpdateElement::Description(new_description) => {
let user_id = ctx.user.id.to_string(); let user_id = ctx.user.id.to_string();
let trip_id_param = filter.trip_id.to_string(); let trip_id_param = reference.higher.trip_id.to_string();
let todo_id_param = id.to_string(); let todo_id_param = reference.id.to_string();
let result = crate::query_one!( let result = crate::query_one!(
&sqlite::QueryClassification { &sqlite::QueryClassification {
@@ -373,16 +391,17 @@ impl crud::Update for Todo {
#[async_trait] #[async_trait]
impl crud::Delete for Todo { impl crud::Delete for Todo {
type Id = Id; type Id = Id;
type Filter = Filter; type Higher = Higher;
type Reference = Reference;
#[tracing::instrument] #[tracing::instrument]
async fn delete<'c, T>(ctx: &Context, db: T, filter: &Filter, id: Id) -> Result<bool, Error> async fn delete<'c, T>(ctx: &Context, db: T, reference: &Reference) -> Result<bool, Error>
where where
T: sqlx::Acquire<'c, Database = sqlx::Sqlite> + Send + std::fmt::Debug, T: sqlx::Acquire<'c, Database = sqlx::Sqlite> + Send + std::fmt::Debug,
{ {
let id_param = id.0.to_string(); let id_param = reference.id.0.to_string();
let user_id = ctx.user.id.to_string(); let user_id = ctx.user.id.to_string();
let trip_id_param = filter.trip_id.to_string(); let trip_id_param = reference.higher.trip_id.to_string();
let results = crate::execute!( let results = crate::execute!(
&sqlite::QueryClassification { &sqlite::QueryClassification {
@@ -644,7 +663,7 @@ impl route::Create for Todo {
let _todo_item = <Self as crud::Create>::create( let _todo_item = <Self as crud::Create>::create(
&ctx, &ctx,
&state.database_pool, &state.database_pool,
Filter { trip_id }, Higher { trip_id },
TodoNew { TodoNew {
description: form.description, description: form.description,
}, },
@@ -690,8 +709,10 @@ impl route::Delete for Todo {
let deleted = <Self as crud::Delete>::delete( let deleted = <Self as crud::Delete>::delete(
&ctx, &ctx,
&state.database_pool, &state.database_pool,
&Filter { trip_id }, &Reference {
components::trips::todos::Id(todo_id), higher: Higher { trip_id },
id: components::trips::todos::Id(todo_id),
},
) )
.await?; .await?;
@@ -747,8 +768,10 @@ pub async fn trip_todo_done(
Todo::update( Todo::update(
&ctx, &ctx,
&state.database_pool, &state.database_pool,
Filter { trip_id }, Reference {
Id(todo_id), id: Id(todo_id),
higher: Higher { trip_id },
},
UpdateElement::State(State::Done.into()), UpdateElement::State(State::Done.into()),
) )
.await?; .await?;
@@ -766,19 +789,28 @@ pub async fn trip_todo_undone_htmx(
Todo::update( Todo::update(
&ctx, &ctx,
&state.database_pool, &state.database_pool,
Filter { trip_id }, Reference {
Id(todo_id), id: Id(todo_id),
higher: Higher { trip_id },
},
UpdateElement::State(State::Todo.into()), UpdateElement::State(State::Todo.into()),
) )
.await?; .await?;
let todo_item = Todo::find(&ctx, &state.database_pool, Filter { trip_id }, Id(todo_id)) let todo_item = Todo::find(
.await? &ctx,
.ok_or_else(|| { &state.database_pool,
crate::Error::Request(RequestError::NotFound { Reference {
message: format!("todo with id {todo_id} not found"), id: Id(todo_id),
}) higher: Higher { trip_id },
})?; },
)
.await?
.ok_or_else(|| {
crate::Error::Request(RequestError::NotFound {
message: format!("todo with id {todo_id} not found"),
})
})?;
Ok(todo_item.build(BuildInput { Ok(todo_item.build(BuildInput {
trip_id, trip_id,
@@ -797,8 +829,10 @@ pub async fn trip_todo_undone(
Todo::update( Todo::update(
&ctx, &ctx,
&state.database_pool, &state.database_pool,
Filter { trip_id }, Reference {
Id(todo_id), id: Id(todo_id),
higher: Higher { trip_id },
},
UpdateElement::State(State::Todo.into()), UpdateElement::State(State::Todo.into()),
) )
.await?; .await?;
@@ -821,7 +855,15 @@ pub async fn trip_todo_edit(
Path((trip_id, todo_id)): Path<(Uuid, Uuid)>, Path((trip_id, todo_id)): Path<(Uuid, Uuid)>,
) -> Result<impl IntoResponse, crate::Error> { ) -> Result<impl IntoResponse, crate::Error> {
let ctx = Context::build(current_user); let ctx = Context::build(current_user);
let todo_item = Todo::find(&ctx, &state.database_pool, Filter { trip_id }, Id(todo_id)).await?; let todo_item = Todo::find(
&ctx,
&state.database_pool,
Reference {
id: Id(todo_id),
higher: Higher { trip_id },
},
)
.await?;
match todo_item { match todo_item {
None => Err(crate::Error::Request(RequestError::NotFound { None => Err(crate::Error::Request(RequestError::NotFound {
@@ -848,8 +890,10 @@ pub async fn trip_todo_edit_save(
let todo_item = Todo::update( let todo_item = Todo::update(
&ctx, &ctx,
&state.database_pool, &state.database_pool,
Filter { trip_id }, Reference {
Id(todo_id), id: Id(todo_id),
higher: Higher { trip_id },
},
UpdateElement::Description(form.description.into()), UpdateElement::Description(form.description.into()),
) )
.await?; .await?;
@@ -881,7 +925,15 @@ pub async fn trip_todo_edit_cancel(
Path((trip_id, todo_id)): Path<(Uuid, Uuid)>, Path((trip_id, todo_id)): Path<(Uuid, Uuid)>,
) -> Result<impl IntoResponse, crate::Error> { ) -> Result<impl IntoResponse, crate::Error> {
let ctx = Context::build(current_user); let ctx = Context::build(current_user);
let todo_item = Todo::find(&ctx, &state.database_pool, Filter { trip_id }, Id(todo_id)).await?; let todo_item = Todo::find(
&ctx,
&state.database_pool,
Reference {
id: Id(todo_id),
higher: Higher { trip_id },
},
)
.await?;
match todo_item { match todo_item {
None => Err(crate::Error::Request(RequestError::NotFound { None => Err(crate::Error::Request(RequestError::NotFound {
@@ -898,17 +950,15 @@ pub async fn trip_todo_edit_cancel(
#[async_trait] #[async_trait]
impl crud::Toggle for StateUpdate { impl crud::Toggle for StateUpdate {
type Id = Id; type Reference = Reference;
type Filter = Filter;
async fn set( async fn set(
ctx: &Context, ctx: &Context,
pool: &sqlite::Pool, pool: &sqlite::Pool,
filter: Self::Filter, reference: Self::Reference,
id: Self::Id,
value: bool, value: bool,
) -> Result<(), crate::Error> { ) -> Result<(), crate::Error> {
Todo::update(&ctx, &pool, filter, id, UpdateElement::State(value.into())).await?; Todo::update(&ctx, &pool, reference, UpdateElement::State(value.into())).await?;
Ok(()) Ok(())
} }
} }
@@ -931,8 +981,10 @@ impl route::ToggleFallback for StateUpdate {
<Self as crud::Toggle>::set( <Self as crud::Toggle>::set(
&ctx, &ctx,
&state.database_pool, &state.database_pool,
Filter { trip_id }, Reference {
Id(todo_id), id: Id(todo_id),
higher: Higher { trip_id },
},
value, value,
) )
.await?; .await?;
@@ -954,8 +1006,6 @@ impl route::ToggleFallback for StateUpdate {
#[async_trait] #[async_trait]
impl route::ToggleHtmx for StateUpdate { impl route::ToggleHtmx for StateUpdate {
type Id = Id;
type Filter = Filter;
type UrlParams = (Uuid, Uuid); type UrlParams = (Uuid, Uuid);
const URL_TRUE: &'static str = "/:id/done/htmx/true"; const URL_TRUE: &'static str = "/:id/done/htmx/true";
@@ -968,14 +1018,7 @@ impl route::ToggleHtmx for StateUpdate {
value: bool, value: bool,
) -> Result<(crate::Context, AppState, Self::UrlParams, bool), crate::Error> { ) -> Result<(crate::Context, AppState, Self::UrlParams, bool), crate::Error> {
let ctx = Context::build(current_user); let ctx = Context::build(current_user);
<Self as crud::Toggle>::set( <Self as crud::Toggle>::set(&ctx, &state.database_pool, params.into(), value).await?;
&ctx,
&state.database_pool,
params.into(),
params.into(),
value,
)
.await?;
Ok((ctx, state, params, value)) Ok((ctx, state, params, value))
} }
@@ -986,13 +1029,20 @@ impl route::ToggleHtmx for StateUpdate {
(trip_id, todo_id): Self::UrlParams, (trip_id, todo_id): Self::UrlParams,
value: bool, value: bool,
) -> Result<Response<BoxBody>, crate::Error> { ) -> Result<Response<BoxBody>, crate::Error> {
let todo_item = Todo::find(&ctx, &state.database_pool, Filter { trip_id }, Id(todo_id)) let todo_item = Todo::find(
.await? &ctx,
.ok_or_else(|| { &state.database_pool,
crate::Error::Request(RequestError::NotFound { Reference {
message: format!("todo with id {todo_id} not found"), id: Id(todo_id),
}) higher: Higher { trip_id },
})?; },
)
.await?
.ok_or_else(|| {
crate::Error::Request(RequestError::NotFound {
message: format!("todo with id {todo_id} not found"),
})
})?;
Ok(todo_item Ok(todo_item
.build(BuildInput { .build(BuildInput {

View File

@@ -1023,7 +1023,7 @@ impl Trip {
crate::components::trips::todos::Todo::findall( crate::components::trips::todos::Todo::findall(
ctx, ctx,
pool, pool,
crate::components::trips::todos::Filter { trip_id: self.id }, crate::components::trips::todos::Higher { trip_id: self.id },
) )
.await?, .await?,
); );

View File

@@ -440,8 +440,10 @@ pub async fn trip(
let deleted = components::trips::todos::Todo::delete( let deleted = components::trips::todos::Todo::delete(
&ctx, &ctx,
&state.database_pool, &state.database_pool,
&todos::Filter { trip_id: id }, &todos::Reference {
components::trips::todos::Id::new(delete_todo), id: components::trips::todos::Id::new(delete_todo),
higher: todos::Higher { trip_id: id },
},
) )
.await?; .await?;