traits for responses

This commit is contained in:
2023-09-16 00:45:51 +02:00
parent 7e62acf91a
commit 2221ee0412
6 changed files with 160 additions and 95 deletions

View File

@@ -75,7 +75,6 @@ pub mod crud {
T: sqlx::Acquire<'c, Database = sqlx::Sqlite> + Send + std::fmt::Debug;
async fn delete_all<'c>(
// &self,
ctx: &Context,
pool: &'c sqlite::Pool,
filter: Self::Filter,
@@ -109,3 +108,37 @@ pub mod view {
fn build(&self, input: Self::Input) -> Markup;
}
}
pub mod route {
use async_trait::async_trait;
use crate::AppState;
use axum::{
body::BoxBody,
extract::{Path, State},
http::HeaderMap,
response::Response,
Extension, Form,
};
pub enum Method {
Get,
Post,
}
#[async_trait]
pub trait Create: super::crud::Create {
type FormX: Send + Sync + 'static;
type UrlParams: Send + Sync + 'static;
const URL: &'static str;
async fn create(
user: Extension<crate::models::user::User>,
state: State<AppState>,
headers: HeaderMap,
path: Path<Self::UrlParams>,
form: Form<Self::FormX>,
) -> Result<Response<BoxBody>, crate::Error>;
}
}

View File

@@ -1,13 +1,26 @@
use axum::{
body::BoxBody,
extract::{Form, Path},
http::HeaderMap,
response::{IntoResponse, Redirect, Response},
Extension,
};
use maud::{html, Markup};
use serde::Deserialize;
use uuid::Uuid;
use crate::components::crud;
use crate::components::view::{self, *};
use crate::{
components::{
crud, route,
view::{self, *},
},
htmx,
models::Error,
sqlite, AppState, Context, RequestError,
};
use async_trait::async_trait;
use crate::{models::Error, sqlite, Context};
use super::Trip;
#[derive(Debug, PartialEq, Eq)]
@@ -545,6 +558,62 @@ impl view::View for Todo {
}
}
#[derive(Deserialize, Debug)]
#[serde(deny_unknown_fields)]
pub struct TripTodoNew {
#[serde(rename = "new-todo-description")]
description: String,
}
#[async_trait]
impl route::Create for Todo {
type FormX = TripTodoNew;
type UrlParams = (Uuid,);
const URL: &'static str = "/:id/todo/new";
#[tracing::instrument]
async fn create(
Extension(current_user): Extension<crate::models::user::User>,
axum::extract::State(state): axum::extract::State<AppState>,
headers: HeaderMap,
Path((trip_id,)): Path<(Uuid,)>,
Form(form): Form<TripTodoNew>,
) -> Result<Response<BoxBody>, crate::Error> {
let ctx = Context::build(current_user);
// method output is not required as we reload the whole trip todos anyway
let _todo_item = <Self as crud::Create>::create(
&ctx,
&state.database_pool,
TodoFilter { trip_id },
TodoNew {
description: form.description,
},
)
.await?;
if htmx::is_htmx(&headers) {
let trip = Trip::find(&ctx, &state.database_pool, trip_id).await?;
match trip {
None => Err(crate::Error::Request(RequestError::NotFound {
message: format!("trip with id {trip_id} not found"),
})),
Some(mut trip) => {
trip.load_todos(&ctx, &state.database_pool).await?;
Ok(crate::models::trips::todos::TodoList {
trip: &trip,
todos: &trip.todos(),
}
.build(None)
.into_response())
}
}
} else {
Ok(Redirect::to(&format!("/trips/{trip_id}/")).into_response())
}
}
}
pub struct NewTodo;
impl NewTodo {

View File

@@ -12,7 +12,7 @@ use uuid::Uuid;
use std::{fmt, time::Duration};
use tower::{timeout::TimeoutLayer, ServiceBuilder};
use crate::{AppState, Error, RequestError, TopLevelPage};
use crate::{components::route, AppState, Error, RequestError, TopLevelPage};
use super::auth;
@@ -151,7 +151,10 @@ pub fn router(state: AppState) -> Router {
.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))
.route("/:id/todo/new", post(trip_todo_new))
.route(
"/:id/todo/new",
post(<crate::models::trips::todos::Todo as route::Create>::create),
)
.route("/:id/todo/:id/delete", post(trip_todo_delete)),
)
.nest(

View File

@@ -7,7 +7,7 @@ use axum::{
use crate::components::crud::*;
use crate::components::view::*;
use crate::models::trips::todos::{TodoBuildInput, TodoFilter, TodoNew, TodoUpdate};
use crate::models::trips::todos::{TodoBuildInput, TodoFilter, TodoUpdate};
use crate::view::Component;
@@ -1480,54 +1480,6 @@ pub async fn trip_todo_edit_cancel(
}
}
#[derive(Deserialize, Debug)]
#[serde(deny_unknown_fields)]
pub struct TripTodoNew {
#[serde(rename = "new-todo-description")]
description: String,
}
#[tracing::instrument]
pub async fn trip_todo_new(
Extension(current_user): Extension<models::user::User>,
State(state): State<AppState>,
headers: HeaderMap,
Path(trip_id): Path<Uuid>,
Form(form): Form<TripTodoNew>,
) -> Result<impl IntoResponse, Error> {
let ctx = Context::build(current_user);
// method output is not required as we reload the whole trip todos anyway
let _todo_item = models::trips::todos::Todo::create(
&ctx,
&state.database_pool,
TodoFilter { trip_id },
TodoNew {
description: form.description,
},
)
.await?;
if htmx::is_htmx(&headers) {
let trip = models::trips::Trip::find(&ctx, &state.database_pool, trip_id).await?;
match trip {
None => Err(Error::Request(RequestError::NotFound {
message: format!("trip with id {trip_id} not found"),
})),
Some(mut trip) => {
trip.load_todos(&ctx, &state.database_pool).await?;
Ok(models::trips::todos::TodoList {
trip: &trip,
todos: &trip.todos(),
}
.build(None)
.into_response())
}
}
} else {
Ok(Redirect::to(&format!("/trips/{trip_id}/")).into_response())
}
}
#[tracing::instrument]
pub async fn trip_todo_delete(
Extension(current_user): Extension<models::user::User>,