Couple the household & user

This commit is contained in:
Quentin Boyer 2025-01-01 19:26:44 +01:00
parent eacd89119f
commit b52443f833
5 changed files with 84 additions and 83 deletions

View file

@ -12,11 +12,14 @@ use serde::{Deserialize, Serialize};
use tower_sessions::Session;
use uuid::Uuid;
use crate::entity::{household, household_members, prelude::*};
use crate::entity::{household, household_members, prelude::*, user};
use super::{base_page_with_head, AppState, AuthenticatedUser, RedirectOrError, RouteError};
pub(super) struct CurrentHousehold(pub household::Model);
pub(super) struct CurrentHousehold {
pub(super) user: user::Model,
pub(super) household: household::Model,
}
pub(super) fn routes() -> Router<AppState> {
Router::new()
@ -44,19 +47,30 @@ where
.await
.map_err(|_| RouteError::SessionExtract)?;
let id: Uuid = session
let user_id: Uuid = session
.get("id")
.await
.map_err(RouteError::from)?
.ok_or_else(|| Redirect::to("/login"))?;
let household_id: Uuid = session
.get("household")
.await
.map_err(RouteError::from)?
.ok_or_else(|| Redirect::to("/household/select"))?;
Ok(Self(
Household::find_by_id(id)
let (household, user) = Household::find_by_id(household_id)
.find_also_related(user::Entity)
.filter(user::Column::Id.eq(user_id))
.one(&app_state.db)
.await
.map_err(RouteError::from)?
.unwrap(),
))
.ok_or_else(|| RouteError::RessourceNotFound)?;
Ok(CurrentHousehold {
household,
user: user.ok_or_else(|| RouteError::RessourceNotFound)?,
})
}
}
@ -147,11 +161,11 @@ struct HouseholdName {
}
async fn rename(
household: CurrentHousehold,
current: CurrentHousehold,
state: State<AppState>,
Form(form): Form<HouseholdName>,
) -> Result<Redirect, RouteError> {
let mut household: household::ActiveModel = household.0.into();
let mut household: household::ActiveModel = current.household.into();
household.name = ActiveValue::Set(form.name);
@ -218,8 +232,7 @@ async fn delete_household(
}
async fn leave(
household: CurrentHousehold,
user: AuthenticatedUser,
current: CurrentHousehold,
session: Session,
state: State<AppState>,
) -> Result<Redirect, RouteError> {
@ -227,13 +240,12 @@ async fn leave(
.db
.transaction(|txn| {
Box::pin(async move {
HouseholdMembers::delete_by_id((household.0.id, user.model.id))
HouseholdMembers::delete_by_id((current.household.id, current.user.id))
.exec(txn)
.await?;
let Some(household) = Household::find_by_id(household.0.id)
.one(txn)
.await? else {
let Some(household) = Household::find_by_id(current.household.id).one(txn).await?
else {
return Err(RouteError::InvalidRequest("No such household".into()));
};

View file

@ -14,7 +14,7 @@ use super::{
confirm_danger_modal, error_alert,
household::CurrentHousehold,
sidebar::{sidebar, SidebarLocation},
AppState, AuthenticatedUser, RouteError,
AppState, RouteError,
};
pub(super) fn routes() -> Router<AppState> {
@ -118,12 +118,11 @@ fn render_ingredients(error: Option<String>, ingredient_list: &[ingredient::Mode
async fn ingredients_view(
error: Option<String>,
household: &CurrentHousehold,
user: &AuthenticatedUser,
current: &CurrentHousehold,
db: &DatabaseConnection,
) -> Result<Markup, RouteError> {
let list = household
.0
let list = current
.household
.find_related(Ingredient)
.order_by_asc(ingredient::Column::Id)
.all(db)
@ -131,27 +130,25 @@ async fn ingredients_view(
Ok(sidebar(
SidebarLocation::Ingredients,
household,
user,
current,
render_ingredients(error, &list),
))
}
async fn ingredients(
state: State<AppState>,
user: AuthenticatedUser,
household: CurrentHousehold,
current: CurrentHousehold,
) -> Result<Markup, RouteError> {
ingredients_view(None, &household, &user, &state.db).await
ingredients_view(None, &current, &state.db).await
}
async fn do_ingredient_delete(
household: &CurrentHousehold,
current: &CurrentHousehold,
db: &DatabaseConnection,
id: i64,
) -> Result<Option<String>, RouteError> {
if household
.0
if current
.household
.find_related(Ingredient)
.filter(ingredient::Column::Id.eq(id))
.count(db)
@ -182,14 +179,12 @@ async fn do_ingredient_delete(
async fn delete_ingredient(
state: State<AppState>,
user: AuthenticatedUser,
household: CurrentHousehold,
current: CurrentHousehold,
ig: Path<i64>,
) -> Result<Markup, RouteError> {
ingredients_view(
do_ingredient_delete(&household, &state.db, ig.0).await?,
&household,
&user,
do_ingredient_delete(&current, &state.db, ig.0).await?,
&current,
&state.db,
)
.await
@ -203,12 +198,12 @@ struct IngredientDesc {
async fn edit_ingredient(
state: State<AppState>,
household: CurrentHousehold,
current: CurrentHousehold,
ig: Path<i64>,
form: Form<IngredientDesc>,
) -> Result<Redirect, RouteError> {
let ingredient = household
.0
let ingredient = current
.household
.find_related(Ingredient)
.filter(ingredient::Column::Id.eq(ig.0))
.one(&state.db)
@ -228,7 +223,7 @@ async fn edit_ingredient(
async fn add_ingredient(
state: State<AppState>,
household: CurrentHousehold,
current: CurrentHousehold,
form: Form<IngredientDesc>,
) -> Result<Redirect, RouteError> {
let mut ingredient = ingredient::ActiveModel::new();
@ -237,7 +232,7 @@ async fn add_ingredient(
true => None,
false => Some(form.0.unit),
});
ingredient.household = ActiveValue::Set(household.0.id);
ingredient.household = ActiveValue::Set(current.household.id);
ingredient.insert(&state.db).await?;

View file

@ -345,13 +345,12 @@ fn confirm_danger_modal(id: &str, inner: &str, action: &str, title: &str) -> Mar
}
}
async fn index(user: AuthenticatedUser, household: CurrentHousehold) -> Markup {
async fn index(current: CurrentHousehold) -> Markup {
sidebar::sidebar(
SidebarLocation::Home,
&household,
&user,
&current,
html! {
"Hello world in " (household.0.name) "!"
"Hello world in " (current.household.name) "!"
},
)
}

View file

@ -17,7 +17,7 @@ use super::{
base_page,
household::CurrentHousehold,
sidebar::{sidebar, SidebarLocation},
AppState, AuthenticatedUser, RouteError,
AppState, RouteError,
};
pub(super) fn routes() -> Router<AppState> {
@ -72,11 +72,10 @@ fn recipe_list(recipes: &[recipe::Model], household: Option<Uuid>) -> Markup {
async fn list_recipes(
state: State<AppState>,
user: AuthenticatedUser,
household: CurrentHousehold,
current: CurrentHousehold,
) -> Result<Markup, RouteError> {
let recipes = household
.0
let recipes = current
.household
.find_related(Recipe)
.order_by_asc(recipe::Column::Name)
.all(&state.db)
@ -84,8 +83,7 @@ async fn list_recipes(
Ok(sidebar(
SidebarLocation::RecipeList,
&household,
&user,
&current,
recipe_list(&recipes, None),
))
}
@ -222,12 +220,11 @@ async fn recipe_view(
async fn view_recipe(
state: State<AppState>,
user: AuthenticatedUser,
household: CurrentHousehold,
current: CurrentHousehold,
id: Path<i32>,
) -> Result<Markup, RouteError> {
let recipe = household
.0
let recipe = current
.household
.find_related(Recipe)
.filter(recipe::Column::Id.eq(id.0))
.one(&state.db)
@ -236,9 +233,8 @@ async fn view_recipe(
Ok(sidebar(
SidebarLocation::RecipeList,
&household,
&user,
recipe_view(&recipe, household.0.id, true, &state.db).await?,
&current,
recipe_view(&recipe, current.household.id, true, &state.db).await?,
))
}
@ -290,12 +286,11 @@ struct CreateAddIngredient {
async fn create_recipe_ingredient(
state: State<AppState>,
_user: AuthenticatedUser,
household: CurrentHousehold,
current: CurrentHousehold,
Form(form): Form<CreateAddIngredient>,
) -> Result<Markup, RouteError> {
let ingredient = ingredient::ActiveModel {
household: sea_orm::ActiveValue::Set(household.0.id),
household: sea_orm::ActiveValue::Set(current.household.id),
name: sea_orm::ActiveValue::Set(form.name),
unit: sea_orm::ActiveValue::Set(form.unit),
id: sea_orm::ActiveValue::NotSet,
@ -363,9 +358,13 @@ fn create_ingredient_modal() -> Markup {
async fn ingredient_list(
state: &State<AppState>,
household: &CurrentHousehold,
current: &CurrentHousehold,
) -> Result<Markup, RouteError> {
let list = household.0.find_related(Ingredient).all(&state.db).await?;
let list = current
.household
.find_related(Ingredient)
.all(&state.db)
.await?;
Ok(html! {
@for ig in list {
@ -376,10 +375,10 @@ async fn ingredient_list(
async fn select_ingredient(
state: &State<AppState>,
household: &CurrentHousehold,
current: &CurrentHousehold,
extra_controls: Markup,
) -> Result<Markup, RouteError> {
let ingredients = ingredient_list(state, household).await?;
let ingredients = ingredient_list(state, current).await?;
Ok(html! {
.d-flex.flex-column.align-items-start {
@ -469,8 +468,7 @@ igAdd.addEventListener("click", function(event) {
async fn do_create_recipe(
state: State<AppState>,
_user: AuthenticatedUser,
household: CurrentHousehold,
current: CurrentHousehold,
Form(form): Form<Vec<(String, String)>>,
) -> Result<Redirect, RouteError> {
let mut name = None;
@ -527,7 +525,7 @@ async fn do_create_recipe(
person_count: extract("person_count", person_count)?,
ranking: extract("rating", rating)?,
steps: extract("steps", steps)?,
household: sea_orm::ActiveValue::Set(household.0.id),
household: sea_orm::ActiveValue::Set(current.household.id),
}
.insert(tx)
.await?;
@ -552,8 +550,7 @@ async fn do_create_recipe(
async fn create_recipe(
state: State<AppState>,
user: AuthenticatedUser,
household: CurrentHousehold,
current: CurrentHousehold,
) -> Result<Markup, RouteError> {
let create_ig = html! {
button .btn.btn-primary
@ -564,8 +561,7 @@ async fn create_recipe(
Ok(sidebar(
SidebarLocation::RecipeCreator,
&household,
&user,
&current,
html! {
script { (PreEscaped(r#"
function removeIngredient (self) {
@ -603,7 +599,7 @@ function removeIngredient (self) {
.d-flex.flex-column.justify-content-start {
h2 { "Ingredients" }
(select_ingredient(&state, &household, create_ig).await?)
(select_ingredient(&state, &current, create_ig).await?)
ul .list-group.list-group-flush.text-start #recipeIngredients {}
}

View file

@ -1,6 +1,6 @@
use maud::{html, Markup};
use super::{base_page, confirm_danger_modal, household::CurrentHousehold, AuthenticatedUser};
use super::{base_page, confirm_danger_modal, household::CurrentHousehold};
#[derive(Clone, Copy, PartialEq)]
pub(super) enum SidebarLocation {
@ -80,9 +80,8 @@ fn rename_hs_modal(current: &str) -> Markup {
}
pub(super) fn sidebar(
current: SidebarLocation,
household: &CurrentHousehold,
user: &AuthenticatedUser,
current_page: SidebarLocation,
current: &CurrentHousehold,
inner: Markup,
) -> Markup {
let entries = &[
@ -118,7 +117,7 @@ pub(super) fn sidebar(
li .nav-item."w-100" {
a href=(entry.location.to())
class={"nav-link text-white" (
(entry.location == current).then_some(" active").unwrap_or("")
(entry.location == current_page).then_some(" active").unwrap_or("")
)} {
i class={"fs-4 " (entry.icon)} {}
span ."ms-2".d-none.d-sm-inline { (entry.label) }
@ -129,17 +128,17 @@ pub(super) fn sidebar(
hr ."w-100";
(confirm_danger_modal(
"leaveModal",
&format!("Are you sure you want to leave the household {}", household.0.name),
&format!("Are you sure you want to leave the household {}", current.household.name),
"/household/leave",
"Leave Household",
))
(rename_hs_modal(&household.0.name))
(rename_hs_modal(&current.household.name))
.dropdown {
a href="#" "data-bs-toggle"="dropdown" "aria-expanded"="false"
.d-flex.align-items-center.text-white.text-decoration-none.dropdown-toggle {
i ."fs-4".bi-house-door-fill {}
strong ."ms-2".d-none.d-sm-inline {
(household.0.name) " (" (user.model.name) ")"
(current.household.name) " (" (current.user.name) ")"
}
}
ul .dropdown-menu {