diff --git a/src/components/trips/todos/mod.rs b/src/components/trips/todos/mod.rs index 8c48406..665309d 100644 --- a/src/components/trips/todos/mod.rs +++ b/src/components/trips/todos/mod.rs @@ -3,7 +3,7 @@ pub use list::List; use axum::{ body::{BoxBody, HttpBody}, - extract::{Form, Path}, + extract::{Form, Path, State as StateExtractor}, http::HeaderMap, response::{IntoResponse, Redirect, Response}, Extension, @@ -14,11 +14,13 @@ use uuid::Uuid; use crate::{ components::{ - crud, route, + crud::{self, Read, Update}, + route, view::{self, View}, }, htmx, - models::Error, + models::{user::User, Error}, + routing::get_referer, sqlite, AppState, Context, RequestError, }; @@ -572,8 +574,8 @@ impl route::Create for Todo { #[tracing::instrument] async fn create( - Extension(current_user): Extension, - axum::extract::State(state): axum::extract::State, + Extension(current_user): Extension, + StateExtractor(state): StateExtractor, headers: HeaderMap, Path((trip_id,)): Path, Form(form): Form, @@ -620,8 +622,8 @@ impl route::Delete for Todo { #[tracing::instrument] async fn delete( - Extension(current_user): Extension, - axum::extract::State(state): axum::extract::State, + Extension(current_user): Extension, + StateExtractor(state): StateExtractor, _headers: HeaderMap, Path((trip_id, todo_id)): Path, ) -> Result, crate::Error> { @@ -673,3 +675,193 @@ impl route::Router for Todo { ) } } + +#[tracing::instrument] +pub async fn trip_todo_done_htmx( + Extension(current_user): Extension, + StateExtractor(state): StateExtractor, + Path((trip_id, todo_id)): Path<(Uuid, Uuid)>, +) -> Result { + let ctx = Context::build(current_user); + Todo::update( + &ctx, + &state.database_pool, + Filter { trip_id }, + todo_id, + UpdateElement::State(State::Done), + ) + .await?; + + let todo_item = Todo::find(&ctx, &state.database_pool, Filter { trip_id }, todo_id) + .await? + .ok_or_else(|| { + crate::Error::Request(RequestError::NotFound { + message: format!("todo with id {todo_id} not found"), + }) + })?; + + Ok(todo_item.build(BuildInput { + trip_id, + state: UiState::Default, + })) +} + +#[tracing::instrument] +pub async fn trip_todo_done( + Extension(current_user): Extension, + StateExtractor(state): StateExtractor, + Path((trip_id, todo_id)): Path<(Uuid, Uuid)>, + headers: HeaderMap, +) -> Result { + let ctx = Context::build(current_user); + Todo::update( + &ctx, + &state.database_pool, + Filter { trip_id }, + todo_id, + UpdateElement::State(State::Done), + ) + .await?; + + Ok(Redirect::to(get_referer(&headers)?)) +} + +#[tracing::instrument] +pub async fn trip_todo_undone_htmx( + Extension(current_user): Extension, + StateExtractor(state): StateExtractor, + Path((trip_id, todo_id)): Path<(Uuid, Uuid)>, +) -> Result { + let ctx = Context::build(current_user); + Todo::update( + &ctx, + &state.database_pool, + Filter { trip_id }, + todo_id, + UpdateElement::State(State::Todo), + ) + .await?; + + let todo_item = Todo::find(&ctx, &state.database_pool, Filter { trip_id }, todo_id) + .await? + .ok_or_else(|| { + crate::Error::Request(RequestError::NotFound { + message: format!("todo with id {todo_id} not found"), + }) + })?; + + Ok(todo_item.build(BuildInput { + trip_id, + state: UiState::Default, + })) +} + +#[tracing::instrument] +pub async fn trip_todo_undone( + Extension(current_user): Extension, + StateExtractor(state): StateExtractor, + Path((trip_id, todo_id)): Path<(Uuid, Uuid)>, + headers: HeaderMap, +) -> Result { + let ctx = Context::build(current_user); + Todo::update( + &ctx, + &state.database_pool, + Filter { trip_id }, + todo_id, + UpdateElement::State(State::Todo), + ) + .await?; + + Ok(Redirect::to(get_referer(&headers)?)) +} + +#[derive(Deserialize, Debug)] +#[serde(deny_unknown_fields)] +pub struct TripTodoDescription { + #[serde(rename = "todo-description")] + description: String, +} + +#[tracing::instrument] +pub async fn trip_todo_edit( + Extension(current_user): Extension, + StateExtractor(state): StateExtractor, + headers: HeaderMap, + Path((trip_id, todo_id)): Path<(Uuid, Uuid)>, +) -> Result { + let ctx = Context::build(current_user); + let todo_item = Todo::find(&ctx, &state.database_pool, Filter { trip_id }, todo_id).await?; + + match todo_item { + None => Err(crate::Error::Request(RequestError::NotFound { + message: format!("todo with id {todo_id} not found"), + })), + Some(todo_item) => Ok(todo_item + .build(BuildInput { + trip_id, + state: UiState::Edit, + }) + .into_response()), + } +} + +#[tracing::instrument] +pub async fn trip_todo_edit_save( + Extension(current_user): Extension, + StateExtractor(state): StateExtractor, + headers: HeaderMap, + Path((trip_id, todo_id)): Path<(Uuid, Uuid)>, + Form(form): Form, +) -> Result { + let ctx = Context::build(current_user); + let todo_item = Todo::update( + &ctx, + &state.database_pool, + Filter { trip_id }, + todo_id, + UpdateElement::Description(form.description), + ) + .await?; + + match todo_item { + None => Err(crate::Error::Request(RequestError::NotFound { + message: format!("todo with id {todo_id} not found"), + })), + Some(todo_item) => { + if htmx::is_htmx(&headers) { + Ok(todo_item + .build(BuildInput { + trip_id, + state: UiState::Default, + }) + .into_response()) + } else { + Ok(Redirect::to(&format!("/trips/{trip_id}/")).into_response()) + } + } + } +} + +#[tracing::instrument] +pub async fn trip_todo_edit_cancel( + Extension(current_user): Extension, + StateExtractor(state): StateExtractor, + headers: HeaderMap, + Path((trip_id, todo_id)): Path<(Uuid, Uuid)>, +) -> Result { + let ctx = Context::build(current_user); + let todo_item = Todo::find(&ctx, &state.database_pool, Filter { trip_id }, todo_id).await?; + + match todo_item { + None => Err(crate::Error::Request(RequestError::NotFound { + message: format!("todo with id {todo_id} not found"), + })), + Some(todo_item) => Ok(todo_item + .build(BuildInput { + trip_id, + state: UiState::Default, + }) + .into_response()), + } +} diff --git a/src/routing/mod.rs b/src/routing/mod.rs index ffccdc0..73ce6b0 100644 --- a/src/routing/mod.rs +++ b/src/routing/mod.rs @@ -24,7 +24,7 @@ mod routes; use routes::*; #[tracing::instrument] -fn get_referer(headers: &HeaderMap) -> Result<&str, Error> { +pub fn get_referer(headers: &HeaderMap) -> Result<&str, Error> { headers .get("referer") .ok_or(Error::Request(RequestError::RefererNotFound))? @@ -145,15 +145,26 @@ pub fn router(state: AppState) -> Router { ) .route( "/:id/todo/:id/done", - get(trip_todo_done).post(trip_todo_done_htmx), + get(components::trips::todos::trip_todo_done) + .post(components::trips::todos::trip_todo_done_htmx), ) .route( "/:id/todo/:id/undone", - get(trip_todo_undone).post(trip_todo_undone_htmx), + get(components::trips::todos::trip_todo_undone) + .post(components::trips::todos::trip_todo_undone_htmx), + ) + .route( + "/:id/todo/:id/edit", + post(components::trips::todos::trip_todo_edit), + ) + .route( + "/:id/todo/:id/edit/save", + post(components::trips::todos::trip_todo_edit_save), + ) + .route( + "/:id/todo/:id/edit/cancel", + post(components::trips::todos::trip_todo_edit_cancel), ) - .route("/:id/todo/:id/edit", post(trip_todo_edit)) - .route("/:id/todo/:id/edit/save", post(trip_todo_edit_save)) - .route("/:id/todo/:id/edit/cancel", post(trip_todo_edit_cancel)) .nest("/:id/todo/", components::trips::todos::Todo::get()), ) .nest( diff --git a/src/routing/routes.rs b/src/routing/routes.rs index 3ac977a..a97715e 100644 --- a/src/routing/routes.rs +++ b/src/routing/routes.rs @@ -8,7 +8,6 @@ use axum::{ use crate::components; use crate::components::crud::*; use crate::components::trips::todos; -use crate::components::view::*; use crate::view::Component; @@ -1268,215 +1267,3 @@ pub async fn trip_item_packagelist_set_unready_htmx( trip_id, &item, )) } - -#[tracing::instrument] -pub async fn trip_todo_done_htmx( - Extension(current_user): Extension, - State(state): State, - Path((trip_id, todo_id)): Path<(Uuid, Uuid)>, -) -> Result { - let ctx = Context::build(current_user); - components::trips::todos::Todo::update( - &ctx, - &state.database_pool, - todos::Filter { trip_id }, - todo_id, - todos::UpdateElement::State(components::trips::todos::State::Done), - ) - .await?; - - let todo_item = components::trips::todos::Todo::find( - &ctx, - &state.database_pool, - todos::Filter { trip_id }, - todo_id, - ) - .await? - .ok_or_else(|| { - Error::Request(RequestError::NotFound { - message: format!("todo with id {todo_id} not found"), - }) - })?; - - Ok(todo_item.build(todos::BuildInput { - trip_id, - state: components::trips::todos::UiState::Default, - })) -} - -#[tracing::instrument] -pub async fn trip_todo_done( - Extension(current_user): Extension, - State(state): State, - Path((trip_id, todo_id)): Path<(Uuid, Uuid)>, - headers: HeaderMap, -) -> Result { - let ctx = Context::build(current_user); - components::trips::todos::Todo::update( - &ctx, - &state.database_pool, - todos::Filter { trip_id }, - todo_id, - todos::UpdateElement::State(components::trips::todos::State::Done), - ) - .await?; - - Ok(Redirect::to(get_referer(&headers)?)) -} - -#[tracing::instrument] -pub async fn trip_todo_undone_htmx( - Extension(current_user): Extension, - State(state): State, - Path((trip_id, todo_id)): Path<(Uuid, Uuid)>, -) -> Result { - let ctx = Context::build(current_user); - components::trips::todos::Todo::update( - &ctx, - &state.database_pool, - todos::Filter { trip_id }, - todo_id, - todos::UpdateElement::State(components::trips::todos::State::Todo), - ) - .await?; - - let todo_item = components::trips::todos::Todo::find( - &ctx, - &state.database_pool, - todos::Filter { trip_id }, - todo_id, - ) - .await? - .ok_or_else(|| { - Error::Request(RequestError::NotFound { - message: format!("todo with id {todo_id} not found"), - }) - })?; - - Ok(todo_item.build(todos::BuildInput { - trip_id, - state: components::trips::todos::UiState::Default, - })) -} - -#[tracing::instrument] -pub async fn trip_todo_undone( - Extension(current_user): Extension, - State(state): State, - Path((trip_id, todo_id)): Path<(Uuid, Uuid)>, - headers: HeaderMap, -) -> Result { - let ctx = Context::build(current_user); - components::trips::todos::Todo::update( - &ctx, - &state.database_pool, - todos::Filter { trip_id }, - todo_id, - todos::UpdateElement::State(components::trips::todos::State::Todo), - ) - .await?; - - Ok(Redirect::to(get_referer(&headers)?)) -} - -#[derive(Deserialize, Debug)] -#[serde(deny_unknown_fields)] -pub struct TripTodoDescription { - #[serde(rename = "todo-description")] - description: String, -} - -#[tracing::instrument] -pub async fn trip_todo_edit( - Extension(current_user): Extension, - State(state): State, - headers: HeaderMap, - Path((trip_id, todo_id)): Path<(Uuid, Uuid)>, -) -> Result { - let ctx = Context::build(current_user); - let todo_item = components::trips::todos::Todo::find( - &ctx, - &state.database_pool, - todos::Filter { trip_id }, - todo_id, - ) - .await?; - - match todo_item { - None => Err(Error::Request(RequestError::NotFound { - message: format!("todo with id {todo_id} not found"), - })), - Some(todo_item) => Ok(todo_item - .build(todos::BuildInput { - trip_id, - state: components::trips::todos::UiState::Edit, - }) - .into_response()), - } -} - -#[tracing::instrument] -pub async fn trip_todo_edit_save( - Extension(current_user): Extension, - State(state): State, - headers: HeaderMap, - Path((trip_id, todo_id)): Path<(Uuid, Uuid)>, - Form(form): Form, -) -> Result { - let ctx = Context::build(current_user); - let todo_item = components::trips::todos::Todo::update( - &ctx, - &state.database_pool, - todos::Filter { trip_id }, - todo_id, - todos::UpdateElement::Description(form.description), - ) - .await?; - - match todo_item { - None => Err(Error::Request(RequestError::NotFound { - message: format!("todo with id {todo_id} not found"), - })), - Some(todo_item) => { - if htmx::is_htmx(&headers) { - Ok(todo_item - .build(todos::BuildInput { - trip_id, - state: components::trips::todos::UiState::Default, - }) - .into_response()) - } else { - Ok(Redirect::to(&format!("/trips/{trip_id}/")).into_response()) - } - } - } -} - -#[tracing::instrument] -pub async fn trip_todo_edit_cancel( - Extension(current_user): Extension, - State(state): State, - headers: HeaderMap, - Path((trip_id, todo_id)): Path<(Uuid, Uuid)>, -) -> Result { - let ctx = Context::build(current_user); - let todo_item = components::trips::todos::Todo::find( - &ctx, - &state.database_pool, - todos::Filter { trip_id }, - todo_id, - ) - .await?; - - match todo_item { - None => Err(Error::Request(RequestError::NotFound { - message: format!("todo with id {todo_id} not found"), - })), - Some(todo_item) => Ok(todo_item - .build(todos::BuildInput { - trip_id, - state: components::trips::todos::UiState::Default, - }) - .into_response()), - } -}