regalade/src/routes/recipe.rs

264 lines
7.8 KiB
Rust
Raw Normal View History

2023-06-25 15:13:15 +02:00
use api::{
AddRecipeIngredientRequest, CreateRecipeRequest, CreateRecipeResponse, EmptyResponse,
2023-06-26 12:47:06 +02:00
IngredientInfo, ListRecipesResponse, RecipeEditRating, RecipeEditStepsRequest, RecipeInfo,
RecipeIngredientEditRequest, RecipeRenameRequest, RecipeEditPersonCount,
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_ingredients};
2023-06-22 22:29:53 +02:00
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),
steps: ActiveValue::Set(request.steps),
2023-06-30 20:27:19 +02:00
person_count: ActiveValue::Set(request.person_count.max(1) as i32),
2023-06-22 22:29:53 +02:00
..Default::default()
};
let recipe = 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_ingredients::ActiveModel {
2023-06-22 22:29:53 +02:00
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()
2023-06-26 12:06:08 +02:00
.map(|r| (r.id, r.name, r.ranking as _))
2023-06-22 23:04:46 +02:00
.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 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,
},
RecipeIngredients::find_by_id((recipe.id, ingredient.id))
2023-06-25 15:13:15 +02:00
.one(&state.db)
.await?
.expect("Ingredient should exist as it was fetched")
.amount,
));
}
Ok(RecipeInfo {
person_count: recipe.person_count as _,
2023-06-25 15:13:15 +02:00
name: recipe.name,
steps: recipe.steps,
2023-06-26 12:06:08 +02:00
rating: recipe.ranking as _,
2023-06-25 15:13:15 +02:00
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_ingredients::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> {
RecipeIngredients::delete_by_id((recipe.id, ingredient.id))
.exec(&state.db)
.await?;
Ok(EmptyResponse {}.into())
}
pub(super) async fn add_ig_request(
State(state): State<AppState>,
RecipeExtractor(recipe): RecipeExtractor,
IngredientExtractor(ingredient): IngredientExtractor,
Json(req): Json<AddRecipeIngredientRequest>,
) -> JsonResult<EmptyResponse> {
let model = recipe_ingredients::ActiveModel {
recipe_id: ActiveValue::Set(recipe.id),
ingredient_id: ActiveValue::Set(ingredient.id),
amount: ActiveValue::Set(req.amount),
};
model.insert(&state.db).await?;
Ok(EmptyResponse {}.into())
}
2023-06-25 22:02:58 +02:00
pub(super) async fn edit_step(
State(state): State<AppState>,
RecipeExtractor(recipe): RecipeExtractor,
Json(req): Json<RecipeEditStepsRequest>,
) -> JsonResult<EmptyResponse> {
let model = recipe::ActiveModel {
id: ActiveValue::Set(recipe.id),
steps: ActiveValue::Set(req.steps),
..Default::default()
};
model.update(&state.db).await?;
Ok(EmptyResponse {}.into())
}
2023-06-26 12:47:06 +02:00
pub(super) async fn edit_rating(
State(state): State<AppState>,
RecipeExtractor(recipe): RecipeExtractor,
Json(req): Json<RecipeEditRating>,
) -> JsonResult<EmptyResponse> {
let model = recipe::ActiveModel {
id: ActiveValue::Set(recipe.id),
ranking: ActiveValue::Set(req.rating as _),
..Default::default()
};
model.update(&state.db).await?;
Ok(EmptyResponse {}.into())
}
pub(super) async fn edit_person_count(
State(state): State<AppState>,
RecipeExtractor(recipe): RecipeExtractor,
Json(req): Json<RecipeEditPersonCount>,
) -> JsonResult<EmptyResponse> {
let model = recipe::ActiveModel {
id: ActiveValue::Set(recipe.id),
2023-06-30 13:13:20 +02:00
person_count: ActiveValue::Set(req.person_count.max(1) as _),
..Default::default()
};
model.update(&state.db).await?;
Ok(EmptyResponse {}.into())
}