regalade/src/routes/recipe.rs

211 lines
6.2 KiB
Rust
Raw Normal View History

2023-06-25 15:13:15 +02:00
use api::{
2023-06-25 16:11:26 +02:00
CreateRecipeRequest, CreateRecipeResponse, EmptyResponse, IngredientInfo, ListRecipesResponse,
RecipeInfo, RecipeIngredientEditRequest, RecipeRenameRequest,
2023-06-25 15:13:15 +02:00
};
use axum::{
2023-06-25 16:11:26 +02:00
async_trait,
extract::{FromRef, FromRequestParts, Path, State},
http::request::Parts,
2023-06-25 15:13:15 +02:00
Json,
};
2023-06-22 22:29:53 +02:00
use sea_orm::{prelude::*, ActiveValue, TransactionTrait};
use crate::entity::{ingredient, prelude::*, recipe, recipe_ingerdients, recipe_steps};
use super::{
household::AuthorizedHousehold, ingredients::IngredientExtractor, AppState, JsonResult,
RouteError,
};
2023-06-22 22:29:53 +02:00
pub(super) async fn create_recipe(
AuthorizedHousehold(household): AuthorizedHousehold,
State(state): State<AppState>,
Json(request): Json<CreateRecipeRequest>,
) -> JsonResult<CreateRecipeResponse> {
let id = state
.db
.transaction(|txn| {
Box::pin(async move {
let model = recipe::ActiveModel {
name: ActiveValue::Set(request.name),
ranking: ActiveValue::Set(request.rating as i32),
2023-06-22 23:03:50 +02:00
household: ActiveValue::Set(household.id),
2023-06-22 22:29:53 +02:00
..Default::default()
};
let recipe = model.insert(txn).await?;
for (num, text) in request.steps.into_iter().enumerate() {
let model = recipe_steps::ActiveModel {
num: ActiveValue::Set(num as _),
recipe_id: ActiveValue::Set(recipe.id),
text: ActiveValue::Set(text),
};
model.insert(txn).await?;
}
for (ig, amount) in request.ingredients {
if 0 == household
.find_related(Ingredient)
.filter(ingredient::Column::Id.eq(ig))
.count(txn)
.await?
{
Err(RouteError::InvalidRequest(format!(
"No such ingredient {ig}"
)))?;
}
let model = recipe_ingerdients::ActiveModel {
recipe_id: ActiveValue::Set(recipe.id),
ingredient_id: ActiveValue::Set(ig),
amount: ActiveValue::Set(amount),
};
model.insert(txn).await?;
}
Ok(recipe.id)
})
})
.await?;
Ok(CreateRecipeResponse { id }.into())
}
2023-06-22 23:04:46 +02:00
pub(super) async fn list_recipes(
AuthorizedHousehold(household): AuthorizedHousehold,
State(state): State<AppState>,
) -> JsonResult<ListRecipesResponse> {
Ok(ListRecipesResponse {
recipes: household
.find_related(Recipe)
.all(&state.db)
.await?
.into_iter()
.map(|r| (r.id, r.name))
.collect(),
}
.into())
}
2023-06-25 15:13:15 +02:00
2023-06-25 16:11:26 +02:00
pub(super) struct RecipeExtractor(recipe::Model);
#[async_trait]
impl<S> FromRequestParts<S> for RecipeExtractor
where
S: Send + Sync,
AppState: FromRef<S>,
{
type Rejection = RouteError;
async fn from_request_parts(parts: &mut Parts, state: &S) -> Result<Self, Self::Rejection> {
let State(app_state): State<AppState> = State::from_request_parts(parts, state)
.await
.expect("No state");
#[derive(serde::Deserialize)]
pub(super) struct RecipeId {
recipe_id: i64,
}
let household = AuthorizedHousehold::from_request_parts(parts, state).await?;
let Path(recipe): Path<RecipeId> = Path::from_request_parts(parts, state).await?;
match household
.0
.find_related(Recipe)
.filter(recipe::Column::Id.eq(recipe.recipe_id))
.one(&app_state.db)
.await?
{
None => Err(RouteError::RessourceNotFound),
Some(r) => Ok(Self(r)),
}
}
2023-06-25 15:13:15 +02:00
}
pub(super) async fn fetch_recipe(
State(state): State<AppState>,
2023-06-25 16:11:26 +02:00
RecipeExtractor(recipe): RecipeExtractor,
2023-06-25 15:13:15 +02:00
) -> JsonResult<RecipeInfo> {
let steps = recipe
.find_related(RecipeSteps)
.all(&state.db)
.await?
.into_iter()
.map(|m| m.text)
.collect();
let recipe_ingredients = recipe.find_related(Ingredient).all(&state.db).await?;
let mut ingredients = Vec::new();
for ingredient in recipe_ingredients {
ingredients.push((
ingredient.id,
IngredientInfo {
name: ingredient.name,
unit: ingredient.unit,
},
RecipeIngerdients::find_by_id((recipe.id, ingredient.id))
.one(&state.db)
.await?
.expect("Ingredient should exist as it was fetched")
.amount,
));
}
Ok(RecipeInfo {
name: recipe.name,
steps,
ingredients,
}
.into())
}
2023-06-25 16:11:26 +02:00
pub(super) async fn edit_name(
State(state): State<AppState>,
RecipeExtractor(recipe): RecipeExtractor,
Json(req): Json<RecipeRenameRequest>,
) -> JsonResult<EmptyResponse> {
let active_model = recipe::ActiveModel {
name: ActiveValue::Set(req.name),
id: ActiveValue::Set(recipe.id),
..Default::default()
};
active_model.update(&state.db).await?;
Ok(EmptyResponse {}.into())
}
pub(super) async fn edit_ig_amount(
State(state): State<AppState>,
RecipeExtractor(recipe): RecipeExtractor,
IngredientExtractor(ingredient): IngredientExtractor,
Json(req): Json<RecipeIngredientEditRequest>,
) -> JsonResult<EmptyResponse> {
let active_model = recipe_ingerdients::ActiveModel {
recipe_id: ActiveValue::Set(recipe.id),
ingredient_id: ActiveValue::Set(ingredient.id),
amount: ActiveValue::Set(req.amount),
};
active_model.update(&state.db).await?;
Ok(EmptyResponse {}.into())
}
pub(super) async fn delete_ig(
State(state): State<AppState>,
RecipeExtractor(recipe): RecipeExtractor,
IngredientExtractor(ingredient): IngredientExtractor,
) -> JsonResult<EmptyResponse> {
RecipeIngerdients::delete_by_id((recipe.id, ingredient.id))
.exec(&state.db)
.await?;
Ok(EmptyResponse {}.into())
}