refactor crud

This commit is contained in:
2023-09-15 13:13:56 +02:00
committed by Hannes Körber
parent 838263b091
commit bcb20c3db8
16 changed files with 466 additions and 239 deletions

View File

@@ -1,32 +0,0 @@
{
"db_name": "SQLite",
"query": "UPDATE trip_todos\n SET description = ?\n WHERE \n id = ? \n AND trip_id = ?\n AND EXISTS(SELECT 1 FROM trips WHERE trip_id = ? AND user_id = ?)\n RETURNING\n id,\n description,\n done\n ",
"describe": {
"columns": [
{
"name": "id",
"ordinal": 0,
"type_info": "Text"
},
{
"name": "description",
"ordinal": 1,
"type_info": "Text"
},
{
"name": "done",
"ordinal": 2,
"type_info": "Bool"
}
],
"parameters": {
"Right": 5
},
"nullable": [
false,
false,
false
]
},
"hash": "33cba5d1fbcfb492f8f8443782c45f9326e3fa966b3f8e864b3e01d4fe7b25b8"
}

View File

@@ -0,0 +1,12 @@
{
"db_name": "SQLite",
"query": "\n INSERT INTO trip_todos\n (id, description, done, trip_id)\n SELECT ?, ?, false, id as trip_id\n FROM trips\n WHERE trip_id = ? AND EXISTS(SELECT 1 FROM trips WHERE id = ? and user_id = ?)\n LIMIT 1\n ",
"describe": {
"columns": [],
"parameters": {
"Right": 5
},
"nullable": []
},
"hash": "4b087f9afbc94acbdf09a04c934b158668704d4497c2f7fef327d56559c5ee8a"
}

View File

@@ -0,0 +1,32 @@
{
"db_name": "SQLite",
"query": "\n UPDATE trip_todos\n SET description = ?\n WHERE \n id = ? \n AND trip_id = ?\n AND EXISTS(SELECT 1 FROM trips WHERE trip_id = ? AND user_id = ?)\n RETURNING\n id,\n description,\n done\n ",
"describe": {
"columns": [
{
"name": "id",
"ordinal": 0,
"type_info": "Text"
},
{
"name": "description",
"ordinal": 1,
"type_info": "Text"
},
{
"name": "done",
"ordinal": 2,
"type_info": "Bool"
}
],
"parameters": {
"Right": 5
},
"nullable": [
false,
false,
false
]
},
"hash": "4f9e7f676f42a548c8993a0182313fb3beebb547e658ea80302490953205a4c5"
}

View File

@@ -1,12 +0,0 @@
{
"db_name": "SQLite",
"query": "DELETE FROM trip_todos\n WHERE \n id = ?\n AND EXISTS (SELECT 1 FROM trips WHERE trip_id = ? AND user_id = ?)",
"describe": {
"columns": [],
"parameters": {
"Right": 3
},
"nullable": []
},
"hash": "5ac0e60ed79f626300f0dfde880f92d4eae3aa4281eafc2ac29fdb83525e0536"
}

View File

@@ -1,12 +0,0 @@
{
"db_name": "SQLite",
"query": "INSERT INTO trip_todos\n (id, description, done, trip_id)\n SELECT ?, ?, false, id as trip_id\n FROM trips\n WHERE trip_id = ? AND EXISTS(SELECT 1 FROM trips WHERE id = ? and user_id = ?)\n LIMIT 1",
"describe": {
"columns": [],
"parameters": {
"Right": 5
},
"nullable": []
},
"hash": "9784595191d25448b2a24c856288d8fa3ba73c423cafcaa555c6f0a588b622a3"
}

View File

@@ -0,0 +1,32 @@
{
"db_name": "SQLite",
"query": "\n UPDATE trip_todos\n SET done = ?\n WHERE trip_id = ?\n AND id = ?\n AND EXISTS(SELECT 1 FROM trips WHERE id = ? AND user_id = ?)\n RETURNING\n id,\n description,\n done\n ",
"describe": {
"columns": [
{
"name": "id",
"ordinal": 0,
"type_info": "Text"
},
{
"name": "description",
"ordinal": 1,
"type_info": "Text"
},
{
"name": "done",
"ordinal": 2,
"type_info": "Bool"
}
],
"parameters": {
"Right": 5
},
"nullable": [
false,
false,
false
]
},
"hash": "d20992191e5fd34a3b03a117abf5e95f04eedf2875afa5b2d687b893fee5fbbb"
}

View File

@@ -1,12 +0,0 @@
{
"db_name": "SQLite",
"query": "\n UPDATE trip_todos\n SET done = ?\n WHERE trip_id = ?\n AND id = ?\n AND EXISTS(SELECT 1 FROM trips WHERE id = ? AND user_id = ?)",
"describe": {
"columns": [],
"parameters": {
"Right": 5
},
"nullable": []
},
"hash": "d29d72b5c9dbf34d672aa271823b3b87f29e85ccdf67dc40f9ce372d5db9727d"
}

View File

@@ -0,0 +1,12 @@
{
"db_name": "SQLite",
"query": "\n DELETE FROM trip_todos\n WHERE \n id = ?\n AND EXISTS (SELECT 1 FROM trips WHERE trip_id = ? AND user_id = ?)\n ",
"describe": {
"columns": [],
"parameters": {
"Right": 3
},
"nullable": []
},
"hash": "d47bf74e8aaecdeb3730edab4d267f61ab03356497e777e55493c4f188e14a25"
}

1
Cargo.lock generated
View File

@@ -1428,6 +1428,7 @@ checksum = "b15813163c1d831bf4a13c3610c05c0d03b39feb07f7e09fa234dac9b15aaf39"
name = "packager" name = "packager"
version = "0.1.0" version = "0.1.0"
dependencies = [ dependencies = [
"async-trait",
"axum", "axum",
"axum-prometheus", "axum-prometheus",
"base64 0.21.4", "base64 0.21.4",

View File

@@ -23,6 +23,9 @@ default = ["jaeger", "prometheus", "tokio-console"]
opt-level = 0 opt-level = 0
lto = "off" lto = "off"
[dependencies.async-trait]
version = "0.1"
[dependencies.opentelemetry] [dependencies.opentelemetry]
version = "0.20" version = "0.20"
optional = true optional = true

89
src/components/mod.rs Normal file
View File

@@ -0,0 +1,89 @@
pub mod crud {
use async_trait::async_trait;
use crate::{models::Error, sqlite, Context};
#[async_trait]
pub trait Create: Sized {
type Id;
type Filter;
type Info;
async fn create(
ctx: &Context,
pool: &sqlite::Pool,
filter: Self::Filter,
info: Self::Info,
) -> Result<Self::Id, Error>;
}
#[async_trait]
pub trait Read: Sized {
type Filter;
type Id;
async fn findall(
ctx: &Context,
pool: &sqlite::Pool,
filter: Self::Filter,
) -> Result<Vec<Self>, Error>;
async fn find(
ctx: &Context,
pool: &sqlite::Pool,
filter: Self::Filter,
id: Self::Id,
) -> Result<Option<Self>, Error>;
}
#[async_trait]
pub trait Update: Sized {
type Id;
type Filter;
type Update;
async fn update(
ctx: &Context,
pool: &sqlite::Pool,
filter: Self::Filter,
id: Self::Id,
update: Self::Update,
) -> Result<Option<Self>, Error>;
}
#[async_trait]
pub trait Delete: Sized {
type Id;
type Filter;
async fn delete(
ctx: &Context,
pool: impl sqlx::Acquire,
filter: Self::Filter,
id: Self::Id,
) -> Result<bool, Error>;
async fn delete_all(
ctx: &Context,
pool: &sqlite::Pool,
filter: Self::Filter,
ids: Vec<Self::Id>,
) -> Result<bool, Error> {
let mut transaction = pool.begin().await?;
for id in ids {
Self::delete(ctx, &mut transaction, filter, id).await?;
}
}
}
}
pub mod view {
use maud::Markup;
pub trait View {
type Input;
fn build(&self, input: Self::Input) -> Markup;
}
}

View File

@@ -4,6 +4,7 @@ use std::fmt;
pub mod auth; pub mod auth;
pub mod cli; pub mod cli;
pub mod components;
pub mod error; pub mod error;
pub mod htmx; pub mod htmx;
pub mod models; pub mod models;

View File

@@ -1,5 +1,7 @@
use std::fmt; use std::fmt;
use crate::components::crud::*;
use super::{ use super::{
consts, consts,
error::{DatabaseError, Error, QueryError}, error::{DatabaseError, Error, QueryError},
@@ -1019,7 +1021,8 @@ impl Trip {
#[tracing::instrument] #[tracing::instrument]
pub async fn load_todos(&mut self, ctx: &Context, pool: &sqlite::Pool) -> Result<(), Error> { 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?); self.todos =
Some(todos::Todo::findall(ctx, pool, todos::TodoFilter { trip_id: self.id }).await?);
Ok(()) Ok(())
} }

View File

@@ -1,10 +1,12 @@
use maud::{html, Markup}; use maud::{html, Markup};
use uuid::Uuid; use uuid::Uuid;
use crate::{ use crate::components::crud;
models::{Error, QueryError}, use crate::components::view::{self, *};
sqlite, Context,
}; use async_trait::async_trait;
use crate::{models::Error, sqlite, Context};
use super::Trip; use super::Trip;
@@ -58,17 +60,28 @@ impl TryFrom<TodoRow> for Todo {
} }
} }
#[derive(Debug)]
pub struct TodoFilter {
pub trip_id: Uuid,
}
impl Todo { impl Todo {
pub fn is_done(&self) -> bool { pub fn is_done(&self) -> bool {
self.state == State::Done self.state == State::Done
} }
}
pub async fn load( #[async_trait]
impl crud::Read for Todo {
type Filter = TodoFilter;
type Id = Uuid;
async fn findall(
ctx: &Context, ctx: &Context,
pool: &sqlite::Pool, pool: &sqlite::Pool,
trip_id: Uuid, filter: TodoFilter,
) -> Result<Vec<Self>, Error> { ) -> Result<Vec<Self>, Error> {
let trip_id_param = trip_id.to_string(); let trip_id_param = filter.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!(
@@ -100,13 +113,13 @@ impl Todo {
} }
#[tracing::instrument] #[tracing::instrument]
pub async fn find( async fn find(
ctx: &Context, ctx: &Context,
pool: &sqlite::Pool, pool: &sqlite::Pool,
trip_id: Uuid, filter: TodoFilter,
todo_id: Uuid, todo_id: Uuid,
) -> Result<Option<Self>, Error> { ) -> Result<Option<Self>, Error> {
let trip_id_param = trip_id.to_string(); let trip_id_param = filter.trip_id.to_string();
let todo_id_param = todo_id.to_string(); let todo_id_param = todo_id.to_string();
let user_id = ctx.user.id.to_string(); let user_id = ctx.user.id.to_string();
crate::query_one!( crate::query_one!(
@@ -136,72 +149,45 @@ impl Todo {
) )
.await .await
} }
}
#[tracing::instrument] pub struct TodoNew {
pub async fn set_state( pub description: String,
}
#[async_trait]
impl crud::Create for Todo {
type Id = Uuid;
type Filter = TodoFilter;
type Info = TodoNew;
async fn create(
ctx: &Context, ctx: &Context,
pool: &sqlite::Pool, pool: &sqlite::Pool,
trip_id: Uuid, filter: Self::Filter,
todo_id: Uuid, info: Self::Info,
state: State, ) -> Result<Self::Id, Error> {
) -> 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}"),
})
})
}
pub async fn new(
ctx: &Context,
pool: &sqlite::Pool,
trip_id: Uuid,
description: String,
) -> Result<Uuid, 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 = trip_id.to_string(); let trip_id_param = filter.trip_id.to_string();
crate::execute!( crate::execute!(
&sqlite::QueryClassification { &sqlite::QueryClassification {
query_type: sqlite::QueryType::Insert, query_type: sqlite::QueryType::Insert,
component: sqlite::Component::Todo, component: sqlite::Component::Todo,
}, },
pool, pool,
"INSERT INTO trip_todos r#"
(id, description, done, trip_id) INSERT INTO trip_todos
SELECT ?, ?, false, id as trip_id (id, description, done, trip_id)
FROM trips SELECT ?, ?, false, id as trip_id
WHERE trip_id = ? AND EXISTS(SELECT 1 FROM trips WHERE id = ? and user_id = ?) FROM trips
LIMIT 1", WHERE trip_id = ? AND EXISTS(SELECT 1 FROM trips WHERE id = ? and user_id = ?)
LIMIT 1
"#,
id_param, id_param,
description, info.description,
trip_id_param, trip_id_param,
trip_id_param, trip_id_param,
user_id, user_id,
@@ -210,27 +196,130 @@ impl Todo {
Ok(id) Ok(id)
} }
}
#[derive(Debug)]
pub enum TodoUpdate {
State(State),
Description(String),
}
#[async_trait]
impl crud::Update for Todo {
type Id = Uuid;
type Filter = TodoFilter;
type Update = TodoUpdate;
#[tracing::instrument] #[tracing::instrument]
pub async fn delete( async fn update(
ctx: &Context, ctx: &Context,
pool: &sqlite::Pool, pool: &sqlite::Pool,
trip_id: Uuid, filter: Self::Filter,
id: Self::Id,
update: Self::Update,
) -> Result<Option<Self>, Error> {
let user_id = ctx.user.id.to_string();
let trip_id_param = filter.trip_id.to_string();
let todo_id_param = id.to_string();
match update {
TodoUpdate::State(state) => {
let done = state == State::Done;
let result = crate::query_one!(
&sqlite::QueryClassification {
query_type: sqlite::QueryType::Update,
component: sqlite::Component::Trips,
},
pool,
TodoRow,
Todo,
r#"
UPDATE trip_todos
SET done = ?
WHERE trip_id = ?
AND id = ?
AND EXISTS(SELECT 1 FROM trips WHERE id = ? AND user_id = ?)
RETURNING
id,
description,
done
"#,
done,
trip_id_param,
todo_id_param,
trip_id_param,
user_id
)
.await?;
Ok(result)
}
TodoUpdate::Description(new_description) => {
let user_id = ctx.user.id.to_string();
let trip_id_param = filter.trip_id.to_string();
let todo_id_param = id.to_string();
let result = crate::query_one!(
&sqlite::QueryClassification {
query_type: sqlite::QueryType::Update,
component: sqlite::Component::Todo,
},
pool,
TodoRow,
Todo,
r#"
UPDATE trip_todos
SET description = ?
WHERE
id = ?
AND trip_id = ?
AND EXISTS(SELECT 1 FROM trips WHERE trip_id = ? AND user_id = ?)
RETURNING
id,
description,
done
"#,
new_description,
todo_id_param,
trip_id_param,
trip_id_param,
user_id,
)
.await?;
Ok(result)
}
}
}
}
#[async_trait]
impl crud::Delete for Todo {
type Id = Uuid;
type Filter = TodoFilter;
#[tracing::instrument]
async fn delete(
ctx: &Context,
pool: &sqlite::Pool,
filter: TodoFilter,
id: Uuid, id: Uuid,
) -> Result<bool, Error> { ) -> Result<bool, Error> {
let id_param = id.to_string(); let id_param = id.to_string();
let user_id = ctx.user.id.to_string(); let user_id = ctx.user.id.to_string();
let trip_id_param = trip_id.to_string(); let trip_id_param = filter.trip_id.to_string();
let results = crate::execute!( let results = crate::execute!(
&sqlite::QueryClassification { &sqlite::QueryClassification {
query_type: sqlite::QueryType::Delete, query_type: sqlite::QueryType::Delete,
component: sqlite::Component::Todo, component: sqlite::Component::Todo,
}, },
pool, pool,
"DELETE FROM trip_todos r#"
WHERE DELETE FROM trip_todos
id = ? WHERE
AND EXISTS (SELECT 1 FROM trips WHERE trip_id = ? AND user_id = ?)", id = ?
AND EXISTS (SELECT 1 FROM trips WHERE trip_id = ? AND user_id = ?)
"#,
id_param, id_param,
trip_id_param, trip_id_param,
user_id, user_id,
@@ -247,9 +336,17 @@ pub enum TodoUiState {
Edit, Edit,
} }
impl Todo { #[derive(Debug)]
pub struct TodoBuildInput {
pub trip_id: Uuid,
pub state: TodoUiState,
}
impl view::View for Todo {
type Input = TodoBuildInput;
#[tracing::instrument] #[tracing::instrument]
pub fn build(&self, trip_id: &Uuid, state: TodoUiState) -> Markup { fn build(&self, input: Self::Input) -> Markup {
let done = self.is_done(); let done = self.is_done();
html!( html!(
li li
@@ -261,19 +358,19 @@ impl Todo {
."bg-red-50"[!done] ."bg-red-50"[!done]
."h-full" ."h-full"
{ {
@if state == TodoUiState::Edit { @if input.state == TodoUiState::Edit {
form form
name="edit-todo" name="edit-todo"
id="edit-todo" id="edit-todo"
action={ action={
"/trips/" (trip_id) "/trips/" (input.trip_id)
"/todo/" (self.id) "/todo/" (self.id)
"/edit/save" "/edit/save"
} }
target="_self" target="_self"
method="post" method="post"
hx-post={ hx-post={
"/trips/" (trip_id) "/trips/" (input.trip_id)
"/todo/" (self.id) "/todo/" (self.id)
"/edit/save" "/edit/save"
} }
@@ -325,7 +422,7 @@ impl Todo {
a a
href="." href="."
hx-post={ hx-post={
"/trips/" (trip_id) "/trips/" (input.trip_id)
"/todo/" (self.id) "/todo/" (self.id)
"/edit/cancel" "/edit/cancel"
} }
@@ -352,12 +449,12 @@ impl Todo {
."aspect-square" ."aspect-square"
."hover:bg-red-50" ."hover:bg-red-50"
href={ href={
"/trips/" (trip_id) "/trips/" (input.trip_id)
"/todo/" (self.id) "/todo/" (self.id)
"/undone" "/undone"
} }
hx-post={ hx-post={
"/trips/" (trip_id) "/trips/" (input.trip_id)
"/todo/" (self.id) "/todo/" (self.id)
"/undone" "/undone"
} }
@@ -378,12 +475,12 @@ impl Todo {
."aspect-square" ."aspect-square"
."hover:bg-green-50" ."hover:bg-green-50"
href={ href={
"/trips/" (trip_id) "/trips/" (input.trip_id)
"/todo/" (self.id) "/todo/" (self.id)
"/done" "/done"
} }
hx-post={ hx-post={
"/trips/" (trip_id) "/trips/" (input.trip_id)
"/todo/" (self.id) "/todo/" (self.id)
"/done" "/done"
} }
@@ -412,7 +509,7 @@ impl Todo {
."hover:bg-blue-400" ."hover:bg-blue-400"
href=(format!("?edit_todo={id}", id = self.id)) href=(format!("?edit_todo={id}", id = self.id))
hx-post={ hx-post={
"/trips/" (trip_id) "/trips/" (input.trip_id)
"/todo/" (self.id) "/todo/" (self.id)
"/edit" "/edit"
} }
@@ -429,7 +526,7 @@ impl Todo {
."hover:bg-red-200" ."hover:bg-red-200"
href=(format!("?delete_todo={id}", id = self.id)) href=(format!("?delete_todo={id}", id = self.id))
hx-post={ hx-post={
"/trips/" (trip_id) "/trips/" (input.trip_id)
"/todo/" (self.id) "/todo/" (self.id)
"/delete" "/delete"
} }
@@ -442,48 +539,6 @@ impl Todo {
} }
) )
} }
#[tracing::instrument]
pub async fn set_description(
ctx: &Context,
pool: &sqlite::Pool,
trip_id: Uuid,
todo_id: Uuid,
new_description: String,
) -> Result<Option<Self>, 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 result = crate::query_one!(
&sqlite::QueryClassification {
query_type: sqlite::QueryType::Update,
component: sqlite::Component::Todo,
},
pool,
TodoRow,
Todo,
"UPDATE trip_todos
SET description = ?
WHERE
id = ?
AND trip_id = ?
AND EXISTS(SELECT 1 FROM trips WHERE trip_id = ? AND user_id = ?)
RETURNING
id,
description,
done
",
new_description,
todo_id_param,
trip_id_param,
trip_id_param,
user_id,
)
.await?;
Ok(result)
}
} }
pub struct NewTodo; pub struct NewTodo;
@@ -573,7 +628,7 @@ impl<'a> TodoList<'a> {
} else { } else {
TodoUiState::Default TodoUiState::Default
}).unwrap_or(TodoUiState::Default); }).unwrap_or(TodoUiState::Default);
(todo.build(&self.trip.id, state)) (todo.build(TodoBuildInput{trip_id:self.trip.id, state}))
} }
(NewTodo::build(&self.trip.id)) (NewTodo::build(&self.trip.id))
} }

View File

@@ -5,6 +5,10 @@ use axum::{
Form, Form,
}; };
use crate::components::crud::*;
use crate::components::view::*;
use crate::models::trips::todos::{TodoBuildInput, TodoFilter, TodoNew, TodoUpdate};
use crate::view::Component; use crate::view::Component;
use serde::Deserialize; use serde::Deserialize;
@@ -433,8 +437,13 @@ pub async fn trip(
state.client_state.active_category_id = trip_query.category; state.client_state.active_category_id = trip_query.category;
if let Some(delete_todo) = trip_query.delete_todo { if let Some(delete_todo) = trip_query.delete_todo {
let deleted = let deleted = models::trips::todos::Todo::delete(
models::trips::todos::Todo::delete(&ctx, &state.database_pool, id, delete_todo).await?; &ctx,
&state.database_pool,
TodoFilter { trip_id: id },
delete_todo,
)
.await?;
return if deleted { return if deleted {
Ok(Redirect::to(get_referer(&headers)?).into_response()) Ok(Redirect::to(get_referer(&headers)?).into_response())
@@ -1266,24 +1275,32 @@ pub async fn trip_todo_done_htmx(
Path((trip_id, todo_id)): Path<(Uuid, Uuid)>, Path((trip_id, todo_id)): Path<(Uuid, Uuid)>,
) -> Result<impl IntoResponse, Error> { ) -> Result<impl IntoResponse, Error> {
let ctx = Context::build(current_user); let ctx = Context::build(current_user);
models::trips::todos::Todo::set_state( models::trips::todos::Todo::update(
&ctx, &ctx,
&state.database_pool, &state.database_pool,
trip_id, TodoFilter { trip_id },
todo_id, todo_id,
models::trips::todos::State::Done, TodoUpdate::State(models::trips::todos::State::Done),
) )
.await?; .await?;
let todo_item = models::trips::todos::Todo::find(&ctx, &state.database_pool, trip_id, todo_id) let todo_item = models::trips::todos::Todo::find(
.await? &ctx,
.ok_or_else(|| { &state.database_pool,
Error::Request(RequestError::NotFound { TodoFilter { trip_id },
message: format!("todo with id {todo_id} not found"), todo_id,
}) )
})?; .await?
.ok_or_else(|| {
Error::Request(RequestError::NotFound {
message: format!("todo with id {todo_id} not found"),
})
})?;
Ok(todo_item.build(&trip_id, models::trips::todos::TodoUiState::Default)) Ok(todo_item.build(TodoBuildInput {
trip_id,
state: models::trips::todos::TodoUiState::Default,
}))
} }
#[tracing::instrument] #[tracing::instrument]
@@ -1294,12 +1311,12 @@ pub async fn trip_todo_done(
headers: HeaderMap, headers: HeaderMap,
) -> Result<impl IntoResponse, Error> { ) -> Result<impl IntoResponse, Error> {
let ctx = Context::build(current_user); let ctx = Context::build(current_user);
models::trips::todos::Todo::set_state( models::trips::todos::Todo::update(
&ctx, &ctx,
&state.database_pool, &state.database_pool,
trip_id, TodoFilter { trip_id },
todo_id, todo_id,
models::trips::todos::State::Done, TodoUpdate::State(models::trips::todos::State::Done),
) )
.await?; .await?;
@@ -1313,24 +1330,32 @@ pub async fn trip_todo_undone_htmx(
Path((trip_id, todo_id)): Path<(Uuid, Uuid)>, Path((trip_id, todo_id)): Path<(Uuid, Uuid)>,
) -> Result<impl IntoResponse, Error> { ) -> Result<impl IntoResponse, Error> {
let ctx = Context::build(current_user); let ctx = Context::build(current_user);
models::trips::todos::Todo::set_state( models::trips::todos::Todo::update(
&ctx, &ctx,
&state.database_pool, &state.database_pool,
trip_id, TodoFilter { trip_id },
todo_id, todo_id,
models::trips::todos::State::Todo, TodoUpdate::State(models::trips::todos::State::Todo),
) )
.await?; .await?;
let todo_item = models::trips::todos::Todo::find(&ctx, &state.database_pool, trip_id, todo_id) let todo_item = models::trips::todos::Todo::find(
.await? &ctx,
.ok_or_else(|| { &state.database_pool,
Error::Request(RequestError::NotFound { TodoFilter { trip_id },
message: format!("todo with id {todo_id} not found"), todo_id,
}) )
})?; .await?
.ok_or_else(|| {
Error::Request(RequestError::NotFound {
message: format!("todo with id {todo_id} not found"),
})
})?;
Ok(todo_item.build(&trip_id, models::trips::todos::TodoUiState::Default)) Ok(todo_item.build(TodoBuildInput {
trip_id,
state: models::trips::todos::TodoUiState::Default,
}))
} }
#[tracing::instrument] #[tracing::instrument]
@@ -1341,12 +1366,12 @@ pub async fn trip_todo_undone(
headers: HeaderMap, headers: HeaderMap,
) -> Result<impl IntoResponse, Error> { ) -> Result<impl IntoResponse, Error> {
let ctx = Context::build(current_user); let ctx = Context::build(current_user);
models::trips::todos::Todo::set_state( models::trips::todos::Todo::update(
&ctx, &ctx,
&state.database_pool, &state.database_pool,
trip_id, TodoFilter { trip_id },
todo_id, todo_id,
models::trips::todos::State::Todo, TodoUpdate::State(models::trips::todos::State::Todo),
) )
.await?; .await?;
@@ -1368,15 +1393,23 @@ 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, Error> { ) -> Result<impl IntoResponse, Error> {
let ctx = Context::build(current_user); let ctx = Context::build(current_user);
let todo_item = let todo_item = models::trips::todos::Todo::find(
models::trips::todos::Todo::find(&ctx, &state.database_pool, trip_id, todo_id).await?; &ctx,
&state.database_pool,
TodoFilter { trip_id },
todo_id,
)
.await?;
match todo_item { match todo_item {
None => Err(Error::Request(RequestError::NotFound { None => Err(Error::Request(RequestError::NotFound {
message: format!("todo with id {todo_id} not found"), message: format!("todo with id {todo_id} not found"),
})), })),
Some(todo_item) => Ok(todo_item Some(todo_item) => Ok(todo_item
.build(&trip_id, models::trips::todos::TodoUiState::Edit) .build(TodoBuildInput {
trip_id,
state: models::trips::todos::TodoUiState::Edit,
})
.into_response()), .into_response()),
} }
} }
@@ -1390,12 +1423,12 @@ pub async fn trip_todo_edit_save(
Form(form): Form<TripTodoDescription>, Form(form): Form<TripTodoDescription>,
) -> Result<impl IntoResponse, Error> { ) -> Result<impl IntoResponse, Error> {
let ctx = Context::build(current_user); let ctx = Context::build(current_user);
let todo_item = models::trips::todos::Todo::set_description( let todo_item = models::trips::todos::Todo::update(
&ctx, &ctx,
&state.database_pool, &state.database_pool,
trip_id, TodoFilter { trip_id },
todo_id, todo_id,
form.description, TodoUpdate::Description(form.description),
) )
.await?; .await?;
@@ -1406,7 +1439,10 @@ pub async fn trip_todo_edit_save(
Some(todo_item) => { Some(todo_item) => {
if htmx::is_htmx(&headers) { if htmx::is_htmx(&headers) {
Ok(todo_item Ok(todo_item
.build(&trip_id, models::trips::todos::TodoUiState::Default) .build(TodoBuildInput {
trip_id,
state: models::trips::todos::TodoUiState::Default,
})
.into_response()) .into_response())
} else { } else {
Ok(Redirect::to(&format!("/trips/{trip_id}/")).into_response()) Ok(Redirect::to(&format!("/trips/{trip_id}/")).into_response())
@@ -1423,15 +1459,23 @@ 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, Error> { ) -> Result<impl IntoResponse, Error> {
let ctx = Context::build(current_user); let ctx = Context::build(current_user);
let todo_item = let todo_item = models::trips::todos::Todo::find(
models::trips::todos::Todo::find(&ctx, &state.database_pool, trip_id, todo_id).await?; &ctx,
&state.database_pool,
TodoFilter { trip_id },
todo_id,
)
.await?;
match todo_item { match todo_item {
None => Err(Error::Request(RequestError::NotFound { None => Err(Error::Request(RequestError::NotFound {
message: format!("todo with id {todo_id} not found"), message: format!("todo with id {todo_id} not found"),
})), })),
Some(todo_item) => Ok(todo_item Some(todo_item) => Ok(todo_item
.build(&trip_id, models::trips::todos::TodoUiState::Default) .build(TodoBuildInput {
trip_id,
state: models::trips::todos::TodoUiState::Default,
})
.into_response()), .into_response()),
} }
} }
@@ -1453,9 +1497,15 @@ pub async fn trip_todo_new(
) -> Result<impl IntoResponse, Error> { ) -> Result<impl IntoResponse, Error> {
let ctx = Context::build(current_user); let ctx = Context::build(current_user);
// method output is not required as we reload the whole trip todos anyway // method output is not required as we reload the whole trip todos anyway
let _todo_item = let _todo_item = models::trips::todos::Todo::create(
models::trips::todos::Todo::new(&ctx, &state.database_pool, trip_id, form.description) &ctx,
.await?; &state.database_pool,
TodoFilter { trip_id },
TodoNew {
description: form.description,
},
)
.await?;
if htmx::is_htmx(&headers) { if htmx::is_htmx(&headers) {
let trip = models::trips::Trip::find(&ctx, &state.database_pool, trip_id).await?; let trip = models::trips::Trip::find(&ctx, &state.database_pool, trip_id).await?;
@@ -1486,8 +1536,13 @@ pub async fn trip_todo_delete(
Path((trip_id, todo_id)): Path<(Uuid, Uuid)>, Path((trip_id, todo_id)): Path<(Uuid, Uuid)>,
) -> Result<impl IntoResponse, Error> { ) -> Result<impl IntoResponse, Error> {
let ctx = Context::build(current_user); let ctx = Context::build(current_user);
let deleted = let deleted = models::trips::todos::Todo::delete(
models::trips::todos::Todo::delete(&ctx, &state.database_pool, trip_id, todo_id).await?; &ctx,
&state.database_pool,
TodoFilter { trip_id },
todo_id,
)
.await?;
if !deleted { if !deleted {
return Err(Error::Request(RequestError::NotFound { return Err(Error::Request(RequestError::NotFound {

View File

@@ -88,7 +88,7 @@ fn get_jaeger_layer<
// mentioned earlier. // mentioned earlier.
let tracer = opentelemetry_jaeger::new_agent_pipeline() let tracer = opentelemetry_jaeger::new_agent_pipeline()
.with_service_name(env!("CARGO_PKG_NAME")) .with_service_name(env!("CARGO_PKG_NAME"))
.with_max_packet_size(20_000) .with_max_packet_size(50_000)
.with_auto_split_batch(true) .with_auto_split_batch(true)
.install_batch(Tokio) .install_batch(Tokio)
.unwrap(); .unwrap();