server: Add routes to manage ingredients
This commit is contained in:
parent
14fbde812f
commit
c23da789a1
3 changed files with 146 additions and 1 deletions
|
|
@ -48,3 +48,33 @@ pub struct UserInfo {
|
||||||
pub name: String,
|
pub name: String,
|
||||||
pub id: Uuid,
|
pub id: Uuid,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Serialize, Deserialize)]
|
||||||
|
pub struct CreateIngredientRequest {
|
||||||
|
pub name: String,
|
||||||
|
pub unit: Option<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Serialize, Deserialize)]
|
||||||
|
pub struct CreateIngredientResponse {
|
||||||
|
pub id: i64,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Serialize, Deserialize)]
|
||||||
|
pub struct IngredientInfo {
|
||||||
|
pub name: String,
|
||||||
|
pub unit: Option<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Serialize, Deserialize)]
|
||||||
|
pub struct IngredientList {
|
||||||
|
pub ingredients: HashMap<i64, IngredientInfo>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Serialize, Deserialize)]
|
||||||
|
pub struct EditIngredientRequest {
|
||||||
|
pub name: Option<String>,
|
||||||
|
pub unit: Option<String>,
|
||||||
|
#[serde(default)]
|
||||||
|
pub has_unit: bool,
|
||||||
|
}
|
||||||
|
|
|
||||||
99
src/routes/ingredients.rs
Normal file
99
src/routes/ingredients.rs
Normal file
|
|
@ -0,0 +1,99 @@
|
||||||
|
use axum::{
|
||||||
|
extract::{Path, State},
|
||||||
|
Json,
|
||||||
|
};
|
||||||
|
|
||||||
|
use api::{
|
||||||
|
CreateIngredientRequest, CreateIngredientResponse, EditIngredientRequest, EmptyResponse,
|
||||||
|
IngredientInfo, IngredientList,
|
||||||
|
};
|
||||||
|
use sea_orm::{prelude::*, ActiveValue};
|
||||||
|
use serde::Deserialize;
|
||||||
|
|
||||||
|
use super::{household::AuthorizedHousehold, AppState, JsonResult};
|
||||||
|
use crate::entity::{ingredient, prelude::*};
|
||||||
|
|
||||||
|
pub(super) async fn create_ingredient(
|
||||||
|
AuthorizedHousehold(household): AuthorizedHousehold,
|
||||||
|
State(state): State<AppState>,
|
||||||
|
Json(request): Json<CreateIngredientRequest>,
|
||||||
|
) -> JsonResult<CreateIngredientResponse> {
|
||||||
|
let model = ingredient::ActiveModel {
|
||||||
|
household: ActiveValue::Set(household.id),
|
||||||
|
name: ActiveValue::Set(request.name),
|
||||||
|
unit: ActiveValue::Set(request.unit),
|
||||||
|
..Default::default()
|
||||||
|
};
|
||||||
|
|
||||||
|
let ingredient = model.insert(&state.db).await?;
|
||||||
|
|
||||||
|
Ok(Json(CreateIngredientResponse { id: ingredient.id }))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(super) async fn list_ingredients(
|
||||||
|
AuthorizedHousehold(household): AuthorizedHousehold,
|
||||||
|
State(state): State<AppState>,
|
||||||
|
) -> JsonResult<IngredientList> {
|
||||||
|
let ingredients = household.find_related(Ingredient).all(&state.db).await?;
|
||||||
|
|
||||||
|
Ok(Json(IngredientList {
|
||||||
|
ingredients: ingredients
|
||||||
|
.into_iter()
|
||||||
|
.map(|m| {
|
||||||
|
(
|
||||||
|
m.id,
|
||||||
|
IngredientInfo {
|
||||||
|
name: m.name,
|
||||||
|
unit: m.unit,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
})
|
||||||
|
.collect(),
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Deserialize)]
|
||||||
|
pub struct IngredientId {
|
||||||
|
iid: i64,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(super) async fn remove_ingredient(
|
||||||
|
AuthorizedHousehold(household): AuthorizedHousehold,
|
||||||
|
State(state): State<AppState>,
|
||||||
|
Path(IngredientId { iid }): Path<IngredientId>,
|
||||||
|
) -> JsonResult<EmptyResponse> {
|
||||||
|
Ingredient::delete_by_id(iid)
|
||||||
|
.filter(ingredient::Column::Household.eq(household.id))
|
||||||
|
.exec(&state.db)
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
Ok(Json(EmptyResponse {}))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(super) async fn edit_ingredient(
|
||||||
|
AuthorizedHousehold(household): AuthorizedHousehold,
|
||||||
|
State(state): State<AppState>,
|
||||||
|
Path(IngredientId { iid }): Path<IngredientId>,
|
||||||
|
Json(request): Json<EditIngredientRequest>,
|
||||||
|
) -> JsonResult<EmptyResponse> {
|
||||||
|
let Some(ingredient) = Ingredient::find_by_id(iid)
|
||||||
|
.filter(ingredient::Column::Household.eq(household.id))
|
||||||
|
.one(&state.db)
|
||||||
|
.await? else {
|
||||||
|
return Err(super::RouteError::RessourceNotFound);
|
||||||
|
};
|
||||||
|
|
||||||
|
let mut ingredient: ingredient::ActiveModel = ingredient.into();
|
||||||
|
|
||||||
|
if let Some(name) = request.name {
|
||||||
|
ingredient.name = ActiveValue::Set(name);
|
||||||
|
}
|
||||||
|
|
||||||
|
if request.has_unit || request.unit.is_some() {
|
||||||
|
ingredient.unit = ActiveValue::Set(request.unit);
|
||||||
|
}
|
||||||
|
|
||||||
|
ingredient.update(&state.db).await?;
|
||||||
|
|
||||||
|
Ok(Json(EmptyResponse {}))
|
||||||
|
}
|
||||||
|
|
@ -11,7 +11,7 @@ use axum::{
|
||||||
HeaderValue, Method, StatusCode,
|
HeaderValue, Method, StatusCode,
|
||||||
},
|
},
|
||||||
response::IntoResponse,
|
response::IntoResponse,
|
||||||
routing::{get, post, put},
|
routing::{delete, get, post, put},
|
||||||
Json, Router, TypedHeader,
|
Json, Router, TypedHeader,
|
||||||
};
|
};
|
||||||
use jwt_simple::prelude::*;
|
use jwt_simple::prelude::*;
|
||||||
|
|
@ -22,6 +22,7 @@ use tower_http::cors::{self, AllowOrigin, CorsLayer};
|
||||||
use crate::entity::{prelude::*, user};
|
use crate::entity::{prelude::*, user};
|
||||||
|
|
||||||
mod household;
|
mod household;
|
||||||
|
mod ingredients;
|
||||||
|
|
||||||
#[derive(thiserror::Error, Debug)]
|
#[derive(thiserror::Error, Debug)]
|
||||||
enum RouteError {
|
enum RouteError {
|
||||||
|
|
@ -39,6 +40,8 @@ enum RouteError {
|
||||||
Unauthorized,
|
Unauthorized,
|
||||||
#[error("Could not fetch required value from path")]
|
#[error("Could not fetch required value from path")]
|
||||||
PathRejection(#[from] axum::extract::rejection::PathRejection),
|
PathRejection(#[from] axum::extract::rejection::PathRejection),
|
||||||
|
#[error("The supplied ressource does not exist")]
|
||||||
|
RessourceNotFound,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl IntoResponse for RouteError {
|
impl IntoResponse for RouteError {
|
||||||
|
|
@ -60,6 +63,7 @@ impl IntoResponse for RouteError {
|
||||||
"Unauthorized to access this ressource",
|
"Unauthorized to access this ressource",
|
||||||
)
|
)
|
||||||
.into_response(),
|
.into_response(),
|
||||||
|
RouteError::RessourceNotFound => StatusCode::NOT_FOUND.into_response(),
|
||||||
e => {
|
e => {
|
||||||
tracing::error!("Internal error: {e:?}");
|
tracing::error!("Internal error: {e:?}");
|
||||||
StatusCode::INTERNAL_SERVER_ERROR.into_response()
|
StatusCode::INTERNAL_SERVER_ERROR.into_response()
|
||||||
|
|
@ -186,4 +190,16 @@ pub(crate) fn router(api_allowed: Option<HeaderValue>) -> Router<AppState> {
|
||||||
.delete(household::leave)
|
.delete(household::leave)
|
||||||
.layer(mk_service(vec![Method::PUT, Method::DELETE])),
|
.layer(mk_service(vec![Method::PUT, Method::DELETE])),
|
||||||
)
|
)
|
||||||
|
.route(
|
||||||
|
"/household/:house_id/ingredients/:iid",
|
||||||
|
delete(ingredients::remove_ingredient)
|
||||||
|
.patch(ingredients::edit_ingredient)
|
||||||
|
.layer(mk_service(vec![Method::DELETE, Method::PATCH])),
|
||||||
|
)
|
||||||
|
.route(
|
||||||
|
"/household/:house_id/ingredients",
|
||||||
|
post(ingredients::create_ingredient)
|
||||||
|
.get(ingredients::list_ingredients)
|
||||||
|
.layer(mk_service(vec![Method::GET, Method::POST])),
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue