impl toggle

This commit is contained in:
2023-09-17 17:34:06 +02:00
parent 96523ad870
commit f662f4fb12
3 changed files with 252 additions and 61 deletions

View File

@@ -114,7 +114,7 @@ pub mod view {
pub mod route {
use async_trait::async_trait;
use crate::AppState;
use crate::{models::user::User, AppState};
use axum::{
body::{BoxBody, HttpBody},
extract::{Path, Query, State},
@@ -137,7 +137,7 @@ pub mod route {
const URL: &'static str;
async fn create(
user: Extension<crate::models::user::User>,
user: Extension<User>,
state: State<AppState>,
headers: HeaderMap,
path: Path<Self::UrlParams>,
@@ -153,7 +153,7 @@ pub mod route {
const URL: &'static str;
async fn read(
user: Extension<crate::models::user::User>,
user: Extension<User>,
state: State<AppState>,
headers: HeaderMap,
query: Query<Self::QueryParams>,
@@ -169,14 +169,14 @@ pub mod route {
const URL: &'static str;
async fn start(
user: Extension<crate::models::user::User>,
user: Extension<User>,
state: State<AppState>,
headers: HeaderMap,
path: Path<Self::UrlParams>,
) -> Result<Response<BoxBody>, crate::Error>;
async fn save(
user: Extension<crate::models::user::User>,
user: Extension<User>,
state: State<AppState>,
headers: HeaderMap,
path: Path<Self::UrlParams>,
@@ -184,13 +184,103 @@ pub mod route {
) -> Result<Response<BoxBody>, crate::Error>;
async fn cancel(
user: Extension<crate::models::user::User>,
user: Extension<User>,
state: State<AppState>,
headers: HeaderMap,
path: Path<Self::UrlParams>,
) -> Result<Response<BoxBody>, crate::Error>;
}
#[async_trait]
pub trait ToggleFallback: Send + Sync + Sized + 'static {
type UrlParams: Clone + Copy + Send + Sync + Sized + 'static;
const URL_TRUE: &'static str;
const URL_FALSE: &'static str;
async fn set(
current_user: User,
state: AppState,
headers: HeaderMap,
params: Self::UrlParams,
value: bool,
) -> Result<Response<BoxBody>, crate::Error>;
async fn set_true(
Extension(user): Extension<User>,
State(state): State<AppState>,
headers: HeaderMap,
Path(path): Path<Self::UrlParams>,
) -> Result<Response<BoxBody>, crate::Error> {
Self::set(user, state, headers, path, true).await
}
async fn set_false(
Extension(user): Extension<User>,
State(state): State<AppState>,
headers: HeaderMap,
Path(path): Path<Self::UrlParams>,
) -> Result<Response<BoxBody>, crate::Error> {
Self::set(user, state, headers, path, false).await
}
fn router<B>() -> axum::Router<AppState, B>
where
B: HttpBody + Send + 'static,
<B as HttpBody>::Data: Send,
<B as HttpBody>::Error: std::error::Error + Sync + Send;
}
#[async_trait]
pub trait ToggleHtmx {
type UrlParams: Send + Sync + 'static;
const URL_TRUE: &'static str;
const URL_FALSE: &'static str;
async fn set(
current_user: User,
state: AppState,
params: Self::UrlParams,
value: bool,
) -> Result<Response<BoxBody>, crate::Error>;
async fn set_true(
Extension(user): Extension<User>,
State(state): State<AppState>,
Path(path): Path<Self::UrlParams>,
) -> Result<Response<BoxBody>, crate::Error> {
Self::set(user, state, path, true).await
}
async fn set_false(
Extension(user): Extension<User>,
State(state): State<AppState>,
Path(path): Path<Self::UrlParams>,
) -> Result<Response<BoxBody>, crate::Error> {
Self::set(user, state, path, false).await
}
fn router<B>() -> axum::Router<AppState, B>
where
B: HttpBody + Send + 'static,
<B as HttpBody>::Data: Send,
<B as HttpBody>::Error: std::error::Error + Sync + Send;
}
pub trait Toggle: ToggleHtmx + ToggleFallback {
fn router<B>() -> axum::Router<AppState, B>
where
B: HttpBody + Send + 'static,
<B as HttpBody>::Data: Send,
<B as HttpBody>::Error: std::error::Error + Sync + Send,
{
axum::Router::new()
.merge(<Self as ToggleHtmx>::router())
.merge(<Self as ToggleFallback>::router())
}
}
#[async_trait]
pub trait Delete: super::crud::Delete {
type UrlParams: Send + Sync + 'static;
@@ -198,7 +288,7 @@ pub mod route {
const URL: &'static str;
async fn delete(
user: Extension<crate::models::user::User>,
user: Extension<User>,
state: State<AppState>,
headers: HeaderMap,
path: Path<Self::UrlParams>,

View File

@@ -1,3 +1,5 @@
#![allow(unused_variables)]
pub mod list;
pub use list::List;
@@ -6,6 +8,7 @@ use axum::{
extract::{Form, Path, State as StateExtractor},
http::HeaderMap,
response::{IntoResponse, Redirect, Response},
routing::post,
Extension,
};
use maud::{html, Markup};
@@ -216,10 +219,38 @@ impl crud::Create for Todo {
}
}
#[derive(Debug, PartialEq, Eq)]
pub struct StateUpdate {
new_state: State,
}
impl From<bool> for StateUpdate {
fn from(state: bool) -> Self {
Self {
new_state: state.into(),
}
}
}
impl From<State> for StateUpdate {
fn from(new_state: State) -> Self {
Self { new_state }
}
}
#[derive(Debug, PartialEq, Eq)]
pub struct DescriptionUpdate(String);
impl From<String> for DescriptionUpdate {
fn from(new_description: String) -> Self {
Self(new_description)
}
}
#[derive(Debug)]
pub enum UpdateElement {
State(State),
Description(String),
State(StateUpdate),
Description(DescriptionUpdate),
}
#[async_trait]
@@ -241,7 +272,7 @@ impl crud::Update for Todo {
let todo_id_param = id.to_string();
match update_element {
UpdateElement::State(state) => {
let done = state == State::Done;
let done = state == State::Done.into();
let result = crate::query_one!(
&sqlite::QueryClassification {
@@ -297,7 +328,7 @@ impl crud::Update for Todo {
description,
done
"#,
new_description,
new_description.0,
todo_id_param,
trip_id_param,
trip_id_param,
@@ -468,12 +499,12 @@ impl view::View for Todo {
href={
"/trips/" (input.trip_id)
"/todo/" (self.id)
"/undone"
"/done/false"
}
hx-post={
"/trips/" (input.trip_id)
"/todo/" (self.id)
"/undone"
"/done/htmx/false"
}
hx-target="closest li"
hx-swap="outerHTML"
@@ -494,12 +525,12 @@ impl view::View for Todo {
href={
"/trips/" (input.trip_id)
"/todo/" (self.id)
"/done"
"/done/true"
}
hx-post={
"/trips/" (input.trip_id)
"/todo/" (self.id)
"/done"
"/done/htmx/true"
}
hx-target="closest li"
hx-swap="outerHTML"
@@ -676,36 +707,6 @@ 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>,
@@ -719,7 +720,7 @@ pub async fn trip_todo_done(
&state.database_pool,
Filter { trip_id },
todo_id,
UpdateElement::State(State::Done),
UpdateElement::State(State::Done.into()),
)
.await?;
@@ -738,7 +739,7 @@ pub async fn trip_todo_undone_htmx(
&state.database_pool,
Filter { trip_id },
todo_id,
UpdateElement::State(State::Todo),
UpdateElement::State(State::Todo.into()),
)
.await?;
@@ -769,7 +770,7 @@ pub async fn trip_todo_undone(
&state.database_pool,
Filter { trip_id },
todo_id,
UpdateElement::State(State::Todo),
UpdateElement::State(State::Todo.into()),
)
.await?;
@@ -820,7 +821,7 @@ pub async fn trip_todo_edit_save(
&state.database_pool,
Filter { trip_id },
todo_id,
UpdateElement::Description(form.description),
UpdateElement::Description(form.description.into()),
)
.await?;
@@ -865,3 +866,96 @@ pub async fn trip_todo_edit_cancel(
.into_response()),
}
}
#[async_trait]
impl route::ToggleFallback for StateUpdate {
type UrlParams = (Uuid, Uuid);
const URL_TRUE: &'static str = "/:id/done/true";
const URL_FALSE: &'static str = "/:id/done/false";
async fn set(
current_user: User,
state: AppState,
headers: HeaderMap,
(trip_id, todo_id): (Uuid, Uuid),
value: bool,
) -> Result<Response<BoxBody>, crate::Error> {
let ctx = Context::build(current_user);
Todo::update(
&ctx,
&state.database_pool,
Filter { trip_id },
todo_id,
UpdateElement::State(value.into()),
)
.await?;
Ok(Redirect::to(get_referer(&headers)?).into_response())
}
fn router<B>() -> axum::Router<AppState, B>
where
B: HttpBody + Send + 'static,
<B as HttpBody>::Data: Send,
<B as HttpBody>::Error: std::error::Error + Sync + Send,
{
axum::Router::new()
.route(Self::URL_TRUE, post(Self::set_true))
.route(Self::URL_FALSE, post(Self::set_false))
}
}
#[async_trait]
impl route::ToggleHtmx for StateUpdate {
type UrlParams = (Uuid, Uuid);
const URL_TRUE: &'static str = "/:id/done/htmx/true";
const URL_FALSE: &'static str = "/:id/done/htmx/false";
async fn set(
current_user: User,
state: AppState,
(trip_id, todo_id): (Uuid, Uuid),
value: bool,
) -> Result<Response<BoxBody>, crate::Error> {
let ctx = Context::build(current_user);
Todo::update(
&ctx,
&state.database_pool,
Filter { trip_id },
todo_id,
UpdateElement::State(value.into()),
)
.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,
})
.into_response())
}
fn router<B>() -> axum::Router<AppState, B>
where
B: HttpBody + Send + 'static,
<B as HttpBody>::Data: Send,
<B as HttpBody>::Error: std::error::Error + Sync + Send,
{
axum::Router::new()
.route(Self::URL_TRUE, post(Self::set_true))
.route(Self::URL_FALSE, post(Self::set_false))
}
}
#[async_trait]
impl route::Toggle for StateUpdate {}

View File

@@ -13,7 +13,10 @@ use std::{fmt, time::Duration};
use tower::{timeout::TimeoutLayer, ServiceBuilder};
use crate::{
components::{self, route::Router as _},
components::{
self,
route::{Router as _, Toggle},
},
AppState, Error, RequestError, TopLevelPage,
};
@@ -143,16 +146,16 @@ pub fn router(state: AppState) -> Router {
"/:id/items/:id/unready",
get(trip_item_set_unready).post(trip_item_set_unready_htmx),
)
.route(
"/:id/todo/:id/done",
get(components::trips::todos::trip_todo_done)
.post(components::trips::todos::trip_todo_done_htmx),
)
.route(
"/:id/todo/:id/undone",
get(components::trips::todos::trip_todo_undone)
.post(components::trips::todos::trip_todo_undone_htmx),
)
// .route(
// "/:id/todo/:id/done",
// get(components::trips::todos::trip_todo_done)
// .post(components::trips::todos::trip_todo_done_htmx),
// )
// .route(
// "/:id/todo/:id/undone",
// 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),
@@ -165,7 +168,11 @@ pub fn router(state: AppState) -> Router {
"/:id/todo/:id/edit/cancel",
post(components::trips::todos::trip_todo_edit_cancel),
)
.nest("/:id/todo/", components::trips::todos::Todo::get()),
.nest(
"/:id/todo/",
components::trips::todos::Todo::get()
.merge(<components::trips::todos::StateUpdate as Toggle>::router()),
),
)
.nest(
(&TopLevelPage::Inventory.path()).into(),