list archive
This commit is contained in:
+73
-8
@@ -53,6 +53,8 @@ pub enum AppError {
|
||||
BadRequest(String),
|
||||
#[error("not found")]
|
||||
NotFound,
|
||||
#[error("list is archived")]
|
||||
Archived,
|
||||
}
|
||||
|
||||
impl IntoResponse for AppError {
|
||||
@@ -72,6 +74,10 @@ impl IntoResponse for AppError {
|
||||
StatusCode::NOT_FOUND,
|
||||
views::error_page("404", "That page could not be found."),
|
||||
),
|
||||
AppError::Archived => status_html_response(
|
||||
StatusCode::CONFLICT,
|
||||
views::error_page("409", "This list is archived and cannot be modified."),
|
||||
),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -96,7 +102,10 @@ pub fn build_router(state: AppState) -> Router {
|
||||
)
|
||||
.route("/account/password", post(change_password))
|
||||
.route("/lists", get(lists_page).post(create_list))
|
||||
.route("/archive", get(archive_page))
|
||||
.route("/lists/{list_id}", get(list_page))
|
||||
.route("/lists/{list_id}/archive", post(archive_list))
|
||||
.route("/lists/{list_id}/unarchive", post(unarchive_list))
|
||||
.route("/lists/{list_id}/items", post(add_item))
|
||||
.route("/lists/{list_id}/items/{item_id}/check", post(check_item))
|
||||
.route("/lists/{list_id}/items/{item_id}/edit", post(edit_item))
|
||||
@@ -603,6 +612,17 @@ async fn lists_page(
|
||||
)))
|
||||
}
|
||||
|
||||
async fn archive_page(
|
||||
State(state): State<AppState>,
|
||||
user: CurrentUser,
|
||||
) -> Result<Response, AppError> {
|
||||
let archived_lists = state.lists.list_archived_summaries().await?;
|
||||
Ok(html_response(views::archive_page(
|
||||
&user.session.user,
|
||||
&archived_lists,
|
||||
)))
|
||||
}
|
||||
|
||||
async fn create_list(
|
||||
State(state): State<AppState>,
|
||||
user: CurrentUser,
|
||||
@@ -640,6 +660,30 @@ async fn list_page(
|
||||
)))
|
||||
}
|
||||
|
||||
async fn archive_list(
|
||||
State(state): State<AppState>,
|
||||
user: CurrentUser,
|
||||
Path(list_id): Path<i64>,
|
||||
LoggedForm(form): LoggedForm<CsrfForm>,
|
||||
) -> Result<Response, AppError> {
|
||||
verify_csrf(&user, &form.csrf)?;
|
||||
require_list(&state, list_id).await?;
|
||||
state.lists.archive_list(list_id).await?;
|
||||
Ok(Redirect::to("/lists").into_response())
|
||||
}
|
||||
|
||||
async fn unarchive_list(
|
||||
State(state): State<AppState>,
|
||||
user: CurrentUser,
|
||||
Path(list_id): Path<i64>,
|
||||
LoggedForm(form): LoggedForm<CsrfForm>,
|
||||
) -> Result<Response, AppError> {
|
||||
verify_csrf(&user, &form.csrf)?;
|
||||
require_list(&state, list_id).await?;
|
||||
state.lists.unarchive_list(list_id).await?;
|
||||
Ok(Redirect::to("/lists").into_response())
|
||||
}
|
||||
|
||||
async fn add_item(
|
||||
State(state): State<AppState>,
|
||||
user: CurrentUser,
|
||||
@@ -647,7 +691,7 @@ async fn add_item(
|
||||
LoggedForm(form): LoggedForm<ItemForm>,
|
||||
) -> Result<Response, AppError> {
|
||||
verify_csrf(&user, &form.csrf)?;
|
||||
require_list(&state, list_id).await?;
|
||||
require_mutable_list(&state, list_id).await?;
|
||||
let name = form.name.trim().to_owned();
|
||||
let quantity = form.quantity.trim().to_owned();
|
||||
let note = form.note.trim().to_owned();
|
||||
@@ -671,7 +715,7 @@ async fn check_item(
|
||||
LoggedForm(form): LoggedForm<CheckForm>,
|
||||
) -> Result<Response, AppError> {
|
||||
verify_csrf(&user, &form.csrf)?;
|
||||
require_list(&state, list_id).await?;
|
||||
require_mutable_list(&state, list_id).await?;
|
||||
let checked = match form.checked.as_str() {
|
||||
"1" | "true" => true,
|
||||
"0" | "false" => false,
|
||||
@@ -691,7 +735,7 @@ async fn edit_item(
|
||||
LoggedForm(form): LoggedForm<ItemForm>,
|
||||
) -> Result<Response, AppError> {
|
||||
verify_csrf(&user, &form.csrf)?;
|
||||
require_list(&state, list_id).await?;
|
||||
require_mutable_list(&state, list_id).await?;
|
||||
let name = form.name.trim().to_owned();
|
||||
if name.is_empty() || name.chars().count() > 120 {
|
||||
return Err(AppError::BadRequest(
|
||||
@@ -719,7 +763,7 @@ async fn delete_item(
|
||||
LoggedForm(form): LoggedForm<CsrfForm>,
|
||||
) -> Result<Response, AppError> {
|
||||
verify_csrf(&user, &form.csrf)?;
|
||||
require_list(&state, list_id).await?;
|
||||
require_mutable_list(&state, list_id).await?;
|
||||
state.lists.delete_item(list_id, item_id).await?;
|
||||
list_fragment_response(&state, &user, list_id).await
|
||||
}
|
||||
@@ -957,7 +1001,7 @@ async fn add_meal_to_list(
|
||||
LoggedForm(form): LoggedForm<AddMealForm>,
|
||||
) -> Result<Response, AppError> {
|
||||
verify_csrf(&user, &form.csrf)?;
|
||||
require_list(&state, list_id).await?;
|
||||
require_mutable_list(&state, list_id).await?;
|
||||
state.meals.add_meal_to_list(form.meal_id, list_id).await?;
|
||||
list_fragment_response(&state, &user, list_id).await
|
||||
}
|
||||
@@ -969,7 +1013,7 @@ async fn remove_meal_from_list(
|
||||
LoggedForm(form): LoggedForm<CsrfForm>,
|
||||
) -> Result<Response, AppError> {
|
||||
verify_csrf(&user, &form.csrf)?;
|
||||
require_list(&state, list_id).await?;
|
||||
require_mutable_list(&state, list_id).await?;
|
||||
state
|
||||
.meals
|
||||
.remove_meal_from_list(list_id, list_meal_id)
|
||||
@@ -1181,6 +1225,7 @@ async fn list_fragment_response(
|
||||
let items = state.lists.items(list_id).await?;
|
||||
let categories = state.lists.categories().await?;
|
||||
let list_meals = state.meals.list_meals_on_list(list_id).await?;
|
||||
let editable = access.archived_at.is_none();
|
||||
Ok(html_response(PreEscaped(
|
||||
views::list_items_fragment(
|
||||
&access,
|
||||
@@ -1188,10 +1233,17 @@ async fn list_fragment_response(
|
||||
&categories,
|
||||
&user.session.csrf_token,
|
||||
false,
|
||||
editable,
|
||||
)
|
||||
.into_string()
|
||||
+ &views::list_meals_panel(&list_meals, list_id, &user.session.csrf_token, true)
|
||||
.into_string(),
|
||||
+ &views::list_meals_panel(
|
||||
&list_meals,
|
||||
list_id,
|
||||
&user.session.csrf_token,
|
||||
true,
|
||||
editable,
|
||||
)
|
||||
.into_string(),
|
||||
)))
|
||||
}
|
||||
|
||||
@@ -1206,6 +1258,19 @@ async fn require_list(
|
||||
.ok_or(AppError::NotFound)
|
||||
}
|
||||
|
||||
/// Like [`require_list`], but also rejects archived lists so they stay
|
||||
/// immutable until restored.
|
||||
async fn require_mutable_list(
|
||||
state: &AppState,
|
||||
list_id: i64,
|
||||
) -> Result<crate::domain::GroceryList, AppError> {
|
||||
let list = require_list(state, list_id).await?;
|
||||
if list.archived_at.is_some() {
|
||||
return Err(AppError::Archived);
|
||||
}
|
||||
Ok(list)
|
||||
}
|
||||
|
||||
async fn optional_user(
|
||||
state: &AppState,
|
||||
headers: &HeaderMap,
|
||||
|
||||
Reference in New Issue
Block a user