This commit is contained in:
2023-09-17 16:00:42 +02:00
parent 8c0a4c53b8
commit 96523ad870
3 changed files with 216 additions and 226 deletions

View File

@@ -3,7 +3,7 @@ pub use list::List;
use axum::{ use axum::{
body::{BoxBody, HttpBody}, body::{BoxBody, HttpBody},
extract::{Form, Path}, extract::{Form, Path, State as StateExtractor},
http::HeaderMap, http::HeaderMap,
response::{IntoResponse, Redirect, Response}, response::{IntoResponse, Redirect, Response},
Extension, Extension,
@@ -14,11 +14,13 @@ use uuid::Uuid;
use crate::{ use crate::{
components::{ components::{
crud, route, crud::{self, Read, Update},
route,
view::{self, View}, view::{self, View},
}, },
htmx, htmx,
models::Error, models::{user::User, Error},
routing::get_referer,
sqlite, AppState, Context, RequestError, sqlite, AppState, Context, RequestError,
}; };
@@ -572,8 +574,8 @@ impl route::Create for Todo {
#[tracing::instrument] #[tracing::instrument]
async fn create( async fn create(
Extension(current_user): Extension<crate::models::user::User>, Extension(current_user): Extension<User>,
axum::extract::State(state): axum::extract::State<AppState>, StateExtractor(state): StateExtractor<AppState>,
headers: HeaderMap, headers: HeaderMap,
Path((trip_id,)): Path<Self::UrlParams>, Path((trip_id,)): Path<Self::UrlParams>,
Form(form): Form<Self::Form>, Form(form): Form<Self::Form>,
@@ -620,8 +622,8 @@ impl route::Delete for Todo {
#[tracing::instrument] #[tracing::instrument]
async fn delete( async fn delete(
Extension(current_user): Extension<crate::models::user::User>, Extension(current_user): Extension<User>,
axum::extract::State(state): axum::extract::State<AppState>, StateExtractor(state): StateExtractor<AppState>,
_headers: HeaderMap, _headers: HeaderMap,
Path((trip_id, todo_id)): Path<Self::UrlParams>, Path((trip_id, todo_id)): Path<Self::UrlParams>,
) -> Result<Response<BoxBody>, crate::Error> { ) -> Result<Response<BoxBody>, crate::Error> {
@@ -673,3 +675,193 @@ impl route::Router for Todo {
) )
} }
} }
#[tracing::instrument]
pub async fn trip_todo_done_htmx(
Extension(current_user): Extension<User>,
StateExtractor(state): StateExtractor<AppState>,
Path((trip_id, todo_id)): Path<(Uuid, Uuid)>,
) -> Result<impl IntoResponse, crate::Error> {
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<User>,
StateExtractor(state): StateExtractor<AppState>,
Path((trip_id, todo_id)): Path<(Uuid, Uuid)>,
headers: HeaderMap,
) -> Result<impl IntoResponse, crate::Error> {
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<User>,
StateExtractor(state): StateExtractor<AppState>,
Path((trip_id, todo_id)): Path<(Uuid, Uuid)>,
) -> Result<impl IntoResponse, crate::Error> {
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<User>,
StateExtractor(state): StateExtractor<AppState>,
Path((trip_id, todo_id)): Path<(Uuid, Uuid)>,
headers: HeaderMap,
) -> Result<impl IntoResponse, crate::Error> {
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<User>,
StateExtractor(state): StateExtractor<AppState>,
headers: HeaderMap,
Path((trip_id, todo_id)): Path<(Uuid, Uuid)>,
) -> Result<impl IntoResponse, crate::Error> {
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<User>,
StateExtractor(state): StateExtractor<AppState>,
headers: HeaderMap,
Path((trip_id, todo_id)): Path<(Uuid, Uuid)>,
Form(form): Form<TripTodoDescription>,
) -> Result<impl IntoResponse, crate::Error> {
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<User>,
StateExtractor(state): StateExtractor<AppState>,
headers: HeaderMap,
Path((trip_id, todo_id)): Path<(Uuid, Uuid)>,
) -> Result<impl IntoResponse, crate::Error> {
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()),
}
}

View File

@@ -24,7 +24,7 @@ mod routes;
use routes::*; use routes::*;
#[tracing::instrument] #[tracing::instrument]
fn get_referer(headers: &HeaderMap) -> Result<&str, Error> { pub fn get_referer(headers: &HeaderMap) -> Result<&str, Error> {
headers headers
.get("referer") .get("referer")
.ok_or(Error::Request(RequestError::RefererNotFound))? .ok_or(Error::Request(RequestError::RefererNotFound))?
@@ -145,15 +145,26 @@ pub fn router(state: AppState) -> Router {
) )
.route( .route(
"/:id/todo/:id/done", "/: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( .route(
"/:id/todo/:id/undone", "/: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("/:id/todo/", components::trips::todos::Todo::get()),
) )
.nest( .nest(

View File

@@ -8,7 +8,6 @@ use axum::{
use crate::components; use crate::components;
use crate::components::crud::*; use crate::components::crud::*;
use crate::components::trips::todos; use crate::components::trips::todos;
use crate::components::view::*;
use crate::view::Component; use crate::view::Component;
@@ -1268,215 +1267,3 @@ pub async fn trip_item_packagelist_set_unready_htmx(
trip_id, &item, trip_id, &item,
)) ))
} }
#[tracing::instrument]
pub async fn trip_todo_done_htmx(
Extension(current_user): Extension<models::user::User>,
State(state): State<AppState>,
Path((trip_id, todo_id)): Path<(Uuid, Uuid)>,
) -> Result<impl IntoResponse, Error> {
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<models::user::User>,
State(state): State<AppState>,
Path((trip_id, todo_id)): Path<(Uuid, Uuid)>,
headers: HeaderMap,
) -> Result<impl IntoResponse, Error> {
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<models::user::User>,
State(state): State<AppState>,
Path((trip_id, todo_id)): Path<(Uuid, Uuid)>,
) -> Result<impl IntoResponse, Error> {
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<models::user::User>,
State(state): State<AppState>,
Path((trip_id, todo_id)): Path<(Uuid, Uuid)>,
headers: HeaderMap,
) -> Result<impl IntoResponse, Error> {
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<models::user::User>,
State(state): State<AppState>,
headers: HeaderMap,
Path((trip_id, todo_id)): Path<(Uuid, Uuid)>,
) -> Result<impl IntoResponse, Error> {
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<models::user::User>,
State(state): State<AppState>,
headers: HeaderMap,
Path((trip_id, todo_id)): Path<(Uuid, Uuid)>,
Form(form): Form<TripTodoDescription>,
) -> Result<impl IntoResponse, Error> {
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<models::user::User>,
State(state): State<AppState>,
headers: HeaderMap,
Path((trip_id, todo_id)): Path<(Uuid, Uuid)>,
) -> Result<impl IntoResponse, Error> {
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()),
}
}