From d2914f9287fe4ba961f21a60d3e89e360ad88fcb Mon Sep 17 00:00:00 2001 From: Philipp Hofer Date: Wed, 30 Apr 2025 13:38:45 +0200 Subject: [PATCH] be able to update data individually; Fixes #952 --- src/model/family.rs | 16 ++- src/model/trip.rs | 2 +- src/model/user/basic.rs | 140 ++++++++++++++++++++- src/model/user/mod.rs | 4 +- src/tera/admin/user.rs | 181 ++++++++++++++++++++++++++++ templates/admin/user/view.html.tera | 60 ++++++++- 6 files changed, 392 insertions(+), 11 deletions(-) diff --git a/src/model/family.rs b/src/model/family.rs index 55aa5eb..46bc95e 100644 --- a/src/model/family.rs +++ b/src/model/family.rs @@ -7,7 +7,7 @@ use super::user::User; #[derive(FromRow, Serialize, Clone)] pub struct Family { - id: i64, + pub(crate) id: i64, } #[derive(Serialize, Clone)] @@ -91,4 +91,18 @@ GROUP BY family.id;" .await .unwrap() } + + pub async fn clean_families_without_members(db: &SqlitePool) { + sqlx::query( + "DELETE FROM family +WHERE id NOT IN ( + SELECT DISTINCT family_id + FROM user + WHERE family_id IS NOT NULL +);", + ) + .execute(db) + .await + .unwrap(); + } } diff --git a/src/model/trip.rs b/src/model/trip.rs index 36c0773..e94614a 100644 --- a/src/model/trip.rs +++ b/src/model/trip.rs @@ -47,7 +47,7 @@ pub struct TripUpdate<'a> { pub is_locked: bool, } -impl<'a> TripUpdate<'a> { +impl TripUpdate<'_> { fn cancelled(&self) -> bool { self.max_people == -1 } diff --git a/src/model/user/basic.rs b/src/model/user/basic.rs index e4db2b2..60a3cd7 100644 --- a/src/model/user/basic.rs +++ b/src/model/user/basic.rs @@ -1,7 +1,9 @@ // TODO: put back in `src/model/user/mod.rs` once that is cleaned up use super::{AllowedToEditPaymentStatusUser, ManageUserUser, User}; -use crate::model::{log::Log, mail::valid_mails, role::Role}; +use crate::model::{family::Family, log::Log, mail::valid_mails, role::Role}; +use chrono::NaiveDate; +use rocket::{fs::TempFile, tokio::io::AsyncReadExt}; use sqlx::SqlitePool; impl User { @@ -60,6 +62,35 @@ impl User { Ok(()) } + pub(crate) async fn update_address( + &self, + db: &SqlitePool, + updated_by: &ManageUserUser, + new_address: &str, + ) -> Result<(), String> { + let new_address = new_address.trim(); + + let query = if new_address.is_empty() { + sqlx::query!("UPDATE user SET address = NULL where id = ?", self.id) + } else { + sqlx::query!( + "UPDATE user SET address = ? where id = ?", + new_address, + self.id + ) + }; + query.execute(db).await.unwrap(); //Okay, because we can only create a User of a valid id + + let msg = match &self.address { + Some(old_address) if new_address.is_empty() => format!("{updated_by} has removed the address of {self} (old address: {old_address})"), + Some(old_address) => format!("{updated_by} has changed the address of {self} from {old_address} to {new_address}"), + None => format!("{updated_by} has added an address for {self}: {new_address}") + }; + Log::create(db, msg).await; + + Ok(()) + } + pub(crate) async fn update_nickname( &self, db: &SqlitePool, @@ -89,6 +120,82 @@ impl User { Ok(()) } + pub(crate) async fn update_member_since( + &self, + db: &SqlitePool, + updated_by: &ManageUserUser, + new_member_since_date: &NaiveDate, + ) { + sqlx::query!( + "UPDATE user SET member_since_date = ? where id = ?", + new_member_since_date, + self.id + ) + .execute(db) + .await + .unwrap(); //Okay, because we can only create a User of a valid id + + let msg = match &self.member_since_date { + Some(old_member_since_date) => format!("{updated_by} has changed the member_since date of {self} from {old_member_since_date} to {new_member_since_date}"), + None => format!("{updated_by} has added a member_since_date for {self}: {new_member_since_date}") + }; + Log::create(db, msg).await; + } + + pub(crate) async fn update_birthdate( + &self, + db: &SqlitePool, + updated_by: &ManageUserUser, + new_birthdate: &NaiveDate, + ) { + sqlx::query!( + "UPDATE user SET birthdate = ? where id = ?", + new_birthdate, + self.id + ) + .execute(db) + .await + .unwrap(); //Okay, because we can only create a User of a valid id + + let msg = match &self.birthdate{ + Some(old_birthdate) => format!("{updated_by} has changed the birthdate of {self} from {old_birthdate} to {new_birthdate}"), + None => format!("{updated_by} has added a birthdate for {self}: {new_birthdate}") + }; + Log::create(db, msg).await; + } + + pub(crate) async fn update_family( + &self, + db: &SqlitePool, + updated_by: &ManageUserUser, + family: Option, + ) { + if let Some(family) = family { + let family_id = family.id; + sqlx::query!( + "UPDATE user SET family_id = ? where id = ?", + family_id, + self.id + ) + .execute(db) + .await + .unwrap(); + } else { + sqlx::query!("UPDATE user SET family_id = NULL where id = ?", self.id) + .execute(db) + .await + .unwrap(); + }; + + Family::clean_families_without_members(db).await; + + Log::create( + db, + format!("{updated_by} hat die Familie von {self} aktualisiert."), + ) + .await; + } + pub(crate) async fn remove_role( &self, db: &SqlitePool, @@ -196,4 +303,35 @@ impl User { Ok(()) } + + pub(crate) async fn add_membership_pdf( + &self, + db: &SqlitePool, + updated_by: &ManageUserUser, + membership_pdf: &TempFile<'_>, + ) -> Result<(), String> { + if self.has_membership_pdf(db).await { + return Err(format!("User {self} hat bereits eine Beitrittserklärung.")); + } + + let mut stream = membership_pdf.open().await.unwrap(); + let mut buffer = Vec::new(); + stream.read_to_end(&mut buffer).await.unwrap(); + sqlx::query!( + "UPDATE user SET membership_pdf = ? where id = ?", + buffer, + self.id + ) + .execute(db) + .await + .unwrap(); //Okay, because we can only create a User of a valid id + + Log::create( + db, + format!("{updated_by} has added the membership pdf for user {self}"), + ) + .await; + + Ok(()) + } } diff --git a/src/model/user/mod.rs b/src/model/user/mod.rs index 1d0be00..f04b779 100644 --- a/src/model/user/mod.rs +++ b/src/model/user/mod.rs @@ -123,7 +123,7 @@ impl User { .await?; } else if self.has_role(db, "schnupperant").await { self.send_welcome_mail_schnupper(db, mail, smtp_pw).await?; - } else if let Some(scheckbuch) = ScheckbuchUser::new(db, &self).await { + } else if let Some(scheckbuch) = ScheckbuchUser::new(db, self).await { scheckbuch.notify(db, mail, smtp_pw).await?; } else { return Err(format!( @@ -272,7 +272,7 @@ ASKÖ Ruderverein Donau Linz", self.name), } pub async fn allowed_to_update_always_show_trip(&self, db: &SqlitePool) -> bool { - AllowedToUpdateTripToAlwaysBeShownUser::new(db, &self) + AllowedToUpdateTripToAlwaysBeShownUser::new(db, self) .await .is_some() } diff --git a/src/tera/admin/user.rs b/src/tera/admin/user.rs index e3424c0..b1d2531 100644 --- a/src/tera/admin/user.rs +++ b/src/tera/admin/user.rs @@ -13,6 +13,7 @@ use crate::{ }, tera::Config, }; +use chrono::NaiveDate; use futures::future::join_all; use rocket::{ form::Form, @@ -389,6 +390,110 @@ async fn update_phone( } } +#[derive(FromForm, Debug)] +pub struct AddressUpdateForm { + address: String, +} + +#[post("/user//change-address", data = "")] +async fn update_address( + db: &State, + data: Form, + admin: ManageUserUser, + id: i32, +) -> Flash { + let Some(user) = User::find_by_id(db, id).await else { + return Flash::error( + Redirect::to("/admin/user"), + format!("User with ID {} does not exist!", id), + ); + }; + + match user.update_address(db, &admin, &data.address).await { + Ok(_) => Flash::success( + Redirect::to(format!("/admin/user/{}", user.id)), + "Adresse erfolgreich geändert", + ), + Err(e) => Flash::error(Redirect::to(format!("/admin/user/{}", user.id)), e), + } +} + +#[derive(FromForm, Debug)] +pub struct FamilyUpdateForm { + family_id: Option, +} + +#[post("/user//change-family", data = "")] +async fn update_family( + db: &State, + data: Form, + admin: ManageUserUser, + id: i32, +) -> Flash { + let Some(user) = User::find_by_id(db, id).await else { + return Flash::error( + Redirect::to("/admin/user"), + format!("User with ID {} does not exist!", id), + ); + }; + + let family = match data.family_id { + Some(-1) => Some( + Family::find_by_id(db, Family::insert(db).await) + .await + .unwrap(), + ), + Some(id) => match Family::find_by_id(db, id).await { + Some(f) => Some(f), + None => { + return Flash::error( + Redirect::to("/admin/user/{id}"), + format!("Family with ID {} does not exist!", id), + ); + } + }, + None => None, + }; + + user.update_family(db, &admin, family).await; + + Flash::success( + Redirect::to(format!("/admin/user/{}", user.id)), + "Familie erfolgreich geändert", + ) +} + +#[derive(FromForm, Debug)] +pub struct AddMembershipPDFForm<'a> { + membership_pdf: TempFile<'a>, +} + +#[post("/user//add-membership-pdf", data = "")] +async fn add_membership_pdf( + db: &State, + data: Form>, + admin: ManageUserUser, + id: i32, +) -> Flash { + let Some(user) = User::find_by_id(db, id).await else { + return Flash::error( + Redirect::to("/admin/user"), + format!("User with ID {} does not exist!", id), + ); + }; + + match user + .add_membership_pdf(db, &admin, &data.membership_pdf) + .await + { + Ok(_) => Flash::success( + Redirect::to(format!("/admin/user/{}", user.id)), + "Beitrittserklärung erfolgreich hinzugefügt", + ), + Err(e) => Flash::error(Redirect::to(format!("/admin/user/{}", user.id)), e), + } +} + #[derive(FromForm, Debug)] pub struct NicknameUpdateForm { nickname: String, @@ -417,6 +522,77 @@ async fn update_nickname( } } +#[derive(FromForm, Debug)] +pub struct MemberSinceUpdateForm { + member_since: String, +} + +#[post("/user//change-member-since", data = "")] +async fn update_member_since( + db: &State, + data: Form, + admin: ManageUserUser, + id: i32, +) -> Flash { + let Some(user) = User::find_by_id(db, id).await else { + return Flash::error( + Redirect::to("/admin/user"), + format!("User with ID {} does not exist!", id), + ); + }; + let Ok(new_member_since_date) = NaiveDate::parse_from_str(&data.member_since, "%Y-%m-%d") + else { + return Flash::error( + Redirect::to("/admin/user/{id}"), + format!( + "Datum {} ist nicht im YYYY-MM-DD Format", + &data.member_since + ), + ); + }; + + user.update_member_since(db, &admin, &new_member_since_date) + .await; + + Flash::success( + Redirect::to(format!("/admin/user/{}", user.id)), + "Beitrittsdatum erfolgreich geändert", + ) +} + +#[derive(FromForm, Debug)] +pub struct BirthdateUpdateForm { + birthdate: String, +} + +#[post("/user//change-birthdate", data = "")] +async fn update_birthdate( + db: &State, + data: Form, + admin: ManageUserUser, + id: i32, +) -> Flash { + let Some(user) = User::find_by_id(db, id).await else { + return Flash::error( + Redirect::to("/admin/user"), + format!("User with ID {} does not exist!", id), + ); + }; + let Ok(new_birthdate) = NaiveDate::parse_from_str(&data.birthdate, "%Y-%m-%d") else { + return Flash::error( + Redirect::to("/admin/user/{id}"), + format!("Datum {} ist nicht im YYYY-MM-DD Format", &data.birthdate), + ); + }; + + user.update_birthdate(db, &admin, &new_birthdate).await; + + Flash::success( + Redirect::to(format!("/admin/user/{}", user.id)), + "Geburtstag erfolgreich geändert", + ) +} + #[derive(FromForm, Debug)] pub struct AddRoleForm { role_id: i32, @@ -649,6 +825,11 @@ pub fn routes() -> Vec { update_mail, update_phone, update_nickname, + update_member_since, + update_birthdate, + update_address, + update_family, + add_membership_pdf, add_role, remove_role, ] diff --git a/templates/admin/user/view.html.tera b/templates/admin/user/view.html.tera index 4a0dc3b..67bd9fc 100644 --- a/templates/admin/user/view.html.tera +++ b/templates/admin/user/view.html.tera @@ -41,7 +41,8 @@ {% endif %} -
  • Spitzname: {{ user.nickname }} +
  • + Spitzname: {{ user.nickname }} {% if allowed_to_edit %}
    ✏️ @@ -76,7 +77,9 @@
    @@ -93,11 +96,56 @@
      -
    • Mitglied seit: {{ user.member_since_date }}
    • -
    • Geburtsdatum: {{ user.birthdate }}
    • -
    • Adresse: {{ user.address }}
    • - Familie: {{ macros::select(label="Familie", data=families, name='family_id', selected_id=user.family_id, display=['names'], default="Keine Familie", new_last_entry='Neue Familie anlegen') }} + Mitglied seit: {{ user.member_since_date }} + {% if allowed_to_edit %} +
      + ✏️ +
      + {{ macros::input(label='Mitglied seit', name='member_since', type="date", value=user.member_since_date) }} + +
      +
      + {% endif %} +
    • +
    • + Geburtsdatum: {{ user.birthdate }} + {% if allowed_to_edit %} +
      + ✏️ +
      + {{ macros::input(label='Geburtstag', name='birthdate', type="date", value=user.birthdate) }} + +
      +
      + {% endif %} +
    • +
    • + Adresse: {{ user.address }} + {% if allowed_to_edit %} +
      + ✏️ +
      + {{ macros::input(label='Neue Adresse', name='address', type="text", value=user.address) }} + +
      +
      + {% endif %} +
    • +
    • + Familie: + {% for family in families %} + {% if user.family_id == family.id %}{{ family.names }}{% endif %} + {% endfor %} + {% if allowed_to_edit %} +
      + ✏️ +
      + {{ macros::select(label="Familie", data=families, name='family_id', selected_id=user.family_id, display=['names'], default="Keine Familie", new_last_entry='Neue Familie anlegen') }} + +
      +
      + {% endif %}