Decouple item (class) form from model

This makes it possible to introduce form-only fields without affecting
the model.
This commit is contained in:
Simon Bruder 2024-07-16 11:50:44 +02:00
parent e3cb717087
commit 7defae7931
Signed by: simon
GPG key ID: 347FF8699CDA0776
3 changed files with 62 additions and 34 deletions

View file

@ -7,6 +7,7 @@ use std::collections::HashMap;
use actix_identity::Identity; use actix_identity::Identity;
use actix_web::{error, get, post, web, Responder}; use actix_web::{error, get, post, web, Responder};
use maud::html; use maud::html;
use serde::Deserialize;
use sqlx::PgPool; use sqlx::PgPool;
use uuid::Uuid; use uuid::Uuid;
@ -263,10 +264,28 @@ async fn list(pool: web::Data<PgPool>, user: Identity) -> actix_web::Result<impl
)) ))
} }
#[derive(Debug, Deserialize)]
pub struct NewItemForm {
pub name: Option<String>,
pub parent: Option<Uuid>,
pub class: Uuid,
pub original_packaging: Option<Uuid>,
pub description: String,
}
#[derive(Debug, Deserialize)]
pub struct NewItemFormPrefilled {
pub name: Option<String>,
pub parent: Option<Uuid>,
pub class: Option<Uuid>,
pub original_packaging: Option<Uuid>,
pub description: Option<String>,
}
#[get("/items/add")] #[get("/items/add")]
async fn add_form( async fn add_form(
pool: web::Data<PgPool>, pool: web::Data<PgPool>,
form: web::Query<NewItemForm>, form: web::Query<NewItemFormPrefilled>,
user: Identity, user: Identity,
) -> actix_web::Result<impl Responder> { ) -> actix_web::Result<impl Responder> {
let datalist_items = datalist::items(&pool) let datalist_items = datalist::items(&pool)
@ -340,13 +359,23 @@ async fn add_form(
#[post("/items/add")] #[post("/items/add")]
async fn add_post( async fn add_post(
data: web::Form<NewItem>, data: web::Form<NewItemForm>,
pool: web::Data<PgPool>, pool: web::Data<PgPool>,
_user: Identity, _user: Identity,
) -> actix_web::Result<impl Responder> { ) -> actix_web::Result<impl Responder> {
let item = manage::item::add(&pool, data.into_inner()) let data = data.into_inner();
.await let item = manage::item::add(
.map_err(error::ErrorInternalServerError)?; &pool,
NewItem {
name: data.name,
class: data.class,
parent: data.parent,
original_packaging: data.original_packaging,
description: data.description,
},
)
.await
.map_err(error::ErrorInternalServerError)?;
Ok(web::Redirect::to("/item/".to_owned() + &item.id.to_string()).see_other()) Ok(web::Redirect::to("/item/".to_owned() + &item.id.to_string()).see_other())
} }

View file

@ -5,6 +5,7 @@
use actix_identity::Identity; use actix_identity::Identity;
use actix_web::{error, get, post, web, Responder}; use actix_web::{error, get, post, web, Responder};
use maud::html; use maud::html;
use serde::Deserialize;
use sqlx::PgPool; use sqlx::PgPool;
use uuid::Uuid; use uuid::Uuid;
@ -230,10 +231,24 @@ async fn list(pool: web::Data<PgPool>, user: Identity) -> actix_web::Result<impl
)) ))
} }
#[derive(Debug, Deserialize)]
pub struct NewItemClassForm {
pub name: String,
pub parent: Option<Uuid>,
pub description: String,
}
#[derive(Debug, Deserialize)]
pub struct NewItemClassFormPrefilled {
pub name: Option<String>,
pub parent: Option<Uuid>,
pub description: Option<String>,
}
#[get("/item-classes/add")] #[get("/item-classes/add")]
async fn add_form( async fn add_form(
pool: web::Data<PgPool>, pool: web::Data<PgPool>,
form: web::Query<NewItemClassForm>, form: web::Query<NewItemClassFormPrefilled>,
user: Identity, user: Identity,
) -> actix_web::Result<impl Responder> { ) -> actix_web::Result<impl Responder> {
let datalist_item_classes = datalist::item_classes(&pool) let datalist_item_classes = datalist::item_classes(&pool)
@ -285,13 +300,21 @@ async fn add_form(
#[post("/item-classes/add")] #[post("/item-classes/add")]
async fn add( async fn add(
data: web::Form<NewItemClass>, data: web::Form<NewItemClassForm>,
pool: web::Data<PgPool>, pool: web::Data<PgPool>,
_user: Identity, _user: Identity,
) -> actix_web::Result<impl Responder> { ) -> actix_web::Result<impl Responder> {
let item = manage::item_class::add(&pool, data.into_inner()) let data = data.into_inner();
.await let item = manage::item_class::add(
.map_err(error::ErrorInternalServerError)?; &pool,
NewItemClass {
name: data.name,
parent: data.parent,
description: data.description,
},
)
.await
.map_err(error::ErrorInternalServerError)?;
Ok(web::Redirect::to("/item-class/".to_owned() + &item.id.to_string()).see_other()) Ok(web::Redirect::to("/item-class/".to_owned() + &item.id.to_string()).see_other())
} }

View file

@ -36,21 +36,6 @@ pub struct NewItem {
pub description: String, pub description: String,
} }
// Its structure is not how it could ideally be
// (doubly nested Options where the original struct already has an Option>)
// because the intended usage (in a GET request parameter) does not allow such fine-grained types.
// TODO: this can be automated from NewItem with derive macro
#[derive(Debug, Deserialize)]
pub struct NewItemForm {
#[serde(default)]
pub name: Option<String>,
#[serde(default)]
pub parent: Option<Uuid>,
pub class: Option<Uuid>,
pub original_packaging: Option<Uuid>,
pub description: Option<String>,
}
#[derive(Clone, Debug, Serialize)] #[derive(Clone, Debug, Serialize)]
pub struct ItemClass { pub struct ItemClass {
pub id: Uuid, pub id: Uuid,
@ -69,12 +54,3 @@ pub struct NewItemClass {
pub parent: Option<Uuid>, pub parent: Option<Uuid>,
pub description: String, pub description: String,
} }
// see NewItemForm
#[derive(Debug, Deserialize)]
pub struct NewItemClassForm {
pub name: Option<String>,
#[serde(default)]
pub parent: Option<Uuid>,
pub description: Option<String>,
}