add meal carry over feature
This commit is contained in:
+101
@@ -133,6 +133,11 @@ pub fn build_router(state: AppState) -> Router {
|
||||
"/lists/{list_id}/meals/{list_meal_id}/remove",
|
||||
post(remove_meal_from_list),
|
||||
)
|
||||
.route(
|
||||
"/lists/{list_id}/carry",
|
||||
get(carry_over_modal).post(carry_meals),
|
||||
)
|
||||
.route("/lists/{list_id}/carry/meals", get(carry_source_meals))
|
||||
.route("/rewards", get(rewards_page).post(create_rewards_card))
|
||||
.route("/rewards/{card_id}", get(rewards_scan_page))
|
||||
.route("/rewards/{card_id}/delete", post(delete_rewards_card))
|
||||
@@ -338,6 +343,21 @@ struct AddMealForm {
|
||||
csrf: String,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
struct CarrySourceQuery {
|
||||
source: i64,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
struct CarryMealsForm {
|
||||
source_list_id: i64,
|
||||
/// Selected list_meal ids as a comma-separated string, since the form
|
||||
/// extractor does not coalesce repeated keys into a Vec.
|
||||
#[serde(default)]
|
||||
list_meal_ids: String,
|
||||
csrf: String,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
struct MealPickerQuery {
|
||||
picker: Option<i64>,
|
||||
@@ -1157,6 +1177,87 @@ async fn remove_meal_from_list(
|
||||
list_fragment_response(&state, &user, list_id).await
|
||||
}
|
||||
|
||||
/// Renders the carry-over modal: a picker of active source lists (newest first,
|
||||
/// excluding the current list) that the user can import meals from.
|
||||
async fn carry_over_modal(
|
||||
State(state): State<AppState>,
|
||||
_user: CurrentUser,
|
||||
Path(dest_list_id): Path<i64>,
|
||||
) -> Result<Response, AppError> {
|
||||
require_mutable_list(&state, dest_list_id).await?;
|
||||
let lists = state.lists.list_summaries().await?;
|
||||
let sources: Vec<_> = lists
|
||||
.into_iter()
|
||||
.filter(|list| list.id != dest_list_id)
|
||||
.collect();
|
||||
Ok(html_response(views::carry_over_modal(
|
||||
&sources,
|
||||
dest_list_id,
|
||||
)))
|
||||
}
|
||||
|
||||
/// Renders the selectable meal rows for a chosen source list inside the carry
|
||||
/// modal.
|
||||
async fn carry_source_meals(
|
||||
State(state): State<AppState>,
|
||||
user: CurrentUser,
|
||||
Path(dest_list_id): Path<i64>,
|
||||
Query(query): Query<CarrySourceQuery>,
|
||||
) -> Result<Response, AppError> {
|
||||
require_mutable_list(&state, dest_list_id).await?;
|
||||
let source = require_list(&state, query.source).await?;
|
||||
if source.id == dest_list_id {
|
||||
return Err(AppError::BadRequest(
|
||||
"Cannot carry meals from a list into itself.".into(),
|
||||
));
|
||||
}
|
||||
let list_meals = state.meals.list_meals_on_list(source.id).await?;
|
||||
Ok(html_response(views::carry_source_meals(
|
||||
&list_meals,
|
||||
source.id,
|
||||
dest_list_id,
|
||||
&user.session.csrf_token,
|
||||
)))
|
||||
}
|
||||
|
||||
/// Copies the selected meals from a source list into the current list without
|
||||
/// re-adding their ingredients.
|
||||
async fn carry_meals(
|
||||
State(state): State<AppState>,
|
||||
user: CurrentUser,
|
||||
Path(dest_list_id): Path<i64>,
|
||||
LoggedForm(form): LoggedForm<CarryMealsForm>,
|
||||
) -> Result<Response, AppError> {
|
||||
verify_csrf(&user, &form.csrf)?;
|
||||
require_mutable_list(&state, dest_list_id).await?;
|
||||
let source = require_list(&state, form.source_list_id).await?;
|
||||
if source.id == dest_list_id {
|
||||
return Err(AppError::BadRequest(
|
||||
"Cannot carry meals from a list into itself.".into(),
|
||||
));
|
||||
}
|
||||
if form.list_meal_ids.is_empty() {
|
||||
return Err(AppError::BadRequest(
|
||||
"Select at least one meal to carry over.".into(),
|
||||
));
|
||||
}
|
||||
let list_meal_ids: Vec<i64> = form
|
||||
.list_meal_ids
|
||||
.split(',')
|
||||
.filter_map(|id| id.trim().parse().ok())
|
||||
.collect();
|
||||
if list_meal_ids.is_empty() {
|
||||
return Err(AppError::BadRequest(
|
||||
"Select at least one meal to carry over.".into(),
|
||||
));
|
||||
}
|
||||
state
|
||||
.meals
|
||||
.carry_meals_to_list(source.id, dest_list_id, &list_meal_ids)
|
||||
.await?;
|
||||
list_fragment_response(&state, &user, dest_list_id).await
|
||||
}
|
||||
|
||||
async fn create_invitation(
|
||||
State(state): State<AppState>,
|
||||
user: CurrentUser,
|
||||
|
||||
Reference in New Issue
Block a user