// TODO: put back in `src/model/user/mod.rs` once that is cleaned up use super::{AllowedToEditPaymentStatusUser, ManageUserUser, User}; use crate::model::{ activity::{self, ActivityBuilder}, family::Family, mail::valid_mails, notification::Notification, role::Role, }; use chrono::NaiveDate; use rocket::{fs::TempFile, tokio::io::AsyncReadExt}; use sqlx::SqlitePool; impl User { pub(crate) async fn add_note( &self, db: &SqlitePool, updated_by: &ManageUserUser, note: &str, ) -> Result<(), String> { let note = note.trim(); ActivityBuilder::from(activity::Reason::UserDataChange( updated_by, self, note.to_string(), )) .save(db) .await; Ok(()) } pub(crate) async fn update_mail( &self, db: &SqlitePool, updated_by: &ManageUserUser, new_mail: &str, ) -> Result<(), String> { let new_mail = new_mail.trim(); if !valid_mails(new_mail) { return Err(format!( "{new_mail} ist kein gültiges Format für eine Mailadresse" )); } sqlx::query!("UPDATE user SET mail = ? where id = ?", new_mail, self.id) .execute(db) .await .unwrap(); //Okay, because we can only create a User of a valid id let msg = match &self.mail { Some(old_mail) => format!("Mail-Adresse von {old_mail} auf {new_mail} geändert."), None => format!("Neue Mail-Adresse für: {new_mail}"), }; ActivityBuilder::from(activity::Reason::UserDataChange(updated_by, self, msg)) .save(db) .await; Ok(()) } pub(crate) async fn update_phone( &self, db: &SqlitePool, updated_by: &ManageUserUser, new_phone: &str, ) { let new_phone = new_phone.trim(); let query = if new_phone.is_empty() { if self.phone.is_none() { return; // nothing to do } sqlx::query!("UPDATE user SET phone = NULL where id = ?", self.id) } else { if let Some(old_phone) = &self.phone { if old_phone == new_phone { return; //nothing to do } } sqlx::query!("UPDATE user SET phone = ? where id = ?", new_phone, self.id) }; query.execute(db).await.unwrap(); //Okay, because we can only create a User of a valid id let msg = match &self.phone { Some(old_phone) if new_phone.is_empty() => { format!("Telefonnummer wurde entfernt (alte Nummer: {old_phone})") } Some(old_phone) => { format!("Telefonnummer wurde von {old_phone} auf {new_phone} geändert.") } None => format!("Neue Telefonnummer hinzugefügt: {new_phone}"), }; ActivityBuilder::from(activity::Reason::UserDataChange(updated_by, self, msg)) .save(db) .await; } pub(crate) async fn update_address( &self, db: &SqlitePool, updated_by: &ManageUserUser, new_address: &str, ) { let new_address = new_address.trim(); let query = if new_address.is_empty() { if self.address.is_none() { return; // nothing to do } sqlx::query!("UPDATE user SET address = NULL where id = ?", self.id) } else { if let Some(old_address) = &self.address { if old_address == new_address { return; //nothing to do } } 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} hat die Adresse von {self} entfernt (alte Adresse: {old_address})" ), Some(old_address) => format!( "{updated_by} hat die Adresse von {self} von {old_address} auf {new_address} geändert." ), None => format!("{updated_by} hat eine Adresse für {self} hinzugefügt: {new_address}"), }; ActivityBuilder::new(&msg) .relevant_for_user(self) .save(db) .await; } pub(crate) async fn update_nickname( &self, db: &SqlitePool, updated_by: &ManageUserUser, new_nickname: &str, ) -> Result<(), String> { let new_nickname = new_nickname.trim(); let query = if new_nickname.is_empty() { sqlx::query!("UPDATE user SET nickname = NULL where id = ?", self.id) } else { sqlx::query!( "UPDATE user SET nickname = ? where id = ?", new_nickname, self.id ) }; query.execute(db).await.unwrap(); //Okay, because we can only create a User of a valid id let msg = match &self.nickname { Some(old_nickname) if new_nickname.is_empty() => format!( "{updated_by} hat den Sitznamen von {self} entfernt (alter Spitzname: {old_nickname})" ), Some(old_nickname) => format!( "{updated_by} hat den Spitznamen von {self} von {old_nickname} auf {new_nickname} geändert." ), None => format!( "{updated_by} hat einen neuen Spitznamen für {self} hinzugefügt: {new_nickname}" ), }; ActivityBuilder::new(&msg) .relevant_for_user(self) .save(db) .await; 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} hat das Beitrittsdatum von {self} von {old_member_since_date} auf {new_member_since_date} geändert." ), None => format!( "{updated_by} hat ein neues Beitrittsdatum für {self} hinzugefügt: {new_member_since_date}" ), }; ActivityBuilder::new(&msg) .relevant_for_user(self) .save(db) .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} hat das Geburtsdatum von {self} von {old_birthdate} auf {new_birthdate} geändert." ), None => { format!("{updated_by} hat ein Geburtsdatum für {self} hinzugefügt: {new_birthdate}") } }; ActivityBuilder::new(&msg) .relevant_for_user(self) .save(db) .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(); ActivityBuilder::new(&format!( "{updated_by} hat {self} zu einer Familie hinzugefügt." )) .relevant_for_user(self) .save(db) .await; } else { sqlx::query!("UPDATE user SET family_id = NULL where id = ?", self.id) .execute(db) .await .unwrap(); ActivityBuilder::new(&format!( "{updated_by} hat die Familienzugehörigkeit von {self} gelöscht." )) .relevant_for_user(self) .save(db) .await; }; Family::clean_families_without_members(db).await; } pub(crate) async fn change_skill( &self, db: &SqlitePool, updated_by: &ManageUserUser, skill: Option, ) -> Result<(), String> { let old_skill = self.skill(db).await; let member = Role::find_by_name(db, "Donau Linz").await.unwrap(); let cox = Role::find_by_name(db, "cox").await.unwrap(); let bootsfuehrer = Role::find_by_name(db, "Bootsführer").await.unwrap(); match (old_skill, skill) { (None, new) if new == Some(cox.clone()) => { self.add_role(db, updated_by, &cox).await?; Notification::create_for_role( db, &member, &format!( "Liebes Vereinsmitglied, {self} ist ab sofort Steuerperson 🎉 Hip hip ...!" ), "Neue Steuerperson", None, None, ) .await; Notification::create( db, self, &format!( "Liebe neue Steuerperson, gratuliere zur geschafften Steuerprüfung 💪. Du kannst ab sofort selber Ausfahrten ausschreiben und der Steuerpersonen Signal-Gruppe beitreten: https://signal.group/#CjQKIHJInNb3zSVW7ipLo7_ygIqVxhxUaaNYx4sy2jdklLsIEhBHJNM2KZM1UnBdQxWy_Gdp" ), "Gratulation", None, None, ) .await; ActivityBuilder::new(&format!("{updated_by} hat {self} zur Steuerperson gemacht")) .relevant_for_user(self) .save(db) .await; } (old, new) if old == Some(cox.clone()) && new == Some(bootsfuehrer.clone()) => { self.remove_role(db, updated_by, &cox).await?; self.add_role(db, updated_by, &bootsfuehrer).await?; Notification::create_for_role( db, &member, &format!( "Liebes Vereinsmitglied, {self} ist ab sofort Bootsführer:in 🎉 Hip hip ...!" ), "Neue:r Bootsführer:in", None, None, ) .await; ActivityBuilder::new(&format!("{updated_by} hat {self} zum Bootsführer gemacht")) .relevant_for_user(self) .save(db) .await; } (old, None) => { if let Some(old) = old { self.remove_role(db, updated_by, &old).await?; let vorstand = Role::find_by_name(db, "Vorstand").await.unwrap(); Notification::create_for_role( db, &vorstand, &format!("Lieber Vorstand, {self} ist ab sofort kein {old} mehr."), "Steuerperson--;", None, None, ) .await; ActivityBuilder::new(&format!("{updated_by} hat {self} zum normalen Mitglied gemacht (keine Steuerperson/Schiffsführer mehr)")) .relevant_for_user(self) .save(db) .await; } } (old, new) => return Err(format!("Not allowed to change from {old:?} to {new:?}")), }; Ok(()) } pub(crate) async fn change_financial( &self, db: &SqlitePool, updated_by: &ManageUserUser, financial: Option, ) -> Result<(), String> { let mut new = String::new(); let mut old = String::new(); if let Some(old_financial) = self.financial(db).await { self.remove_role(db, updated_by, &old_financial).await?; old.push_str(&old_financial.to_string()); } else { old.push_str("Keine Ermäßigung"); } if let Some(new_financial) = financial { self.add_role(db, updated_by, &new_financial).await?; new.push_str(&new_financial.to_string()); } else { new.push_str("Keine Ermäßigung"); } ActivityBuilder::new(&format!( "{updated_by} hat die Ermäßigung von {self} von {old} auf {new} geändert" )) .relevant_for_user(self) .save(db) .await; Ok(()) } pub(crate) async fn remove_role( &self, db: &SqlitePool, updated_by: &ManageUserUser, role: &Role, ) -> Result<(), String> { if !self.has_role(db, &role.name).await { return Err(format!( "Kann Rolle {role} von User {self} nicht entfernen, da der User die Rolle gar nicht hat" )); } sqlx::query!( "DELETE FROM user_role WHERE user_id = ? and role_id = ?", self.id, role.id ) .execute(db) .await .unwrap(); if !role.hide_in_lists && role.cluster.is_none() { ActivityBuilder::new(&format!( "{updated_by} hat die Rolle {role} von {self} entfernt." )) .relevant_for_user(self) .save(db) .await; } Ok(()) } pub(crate) async fn has_not_paid( &self, db: &SqlitePool, updated_by: &AllowedToEditPaymentStatusUser, ) { let paid = Role::find_by_name(db, "paid").await.unwrap(); sqlx::query!( "DELETE FROM user_role WHERE user_id = ? and role_id = ?", self.id, paid.id ) .execute(db) .await .unwrap(); ActivityBuilder::new(&format!( "{updated_by} hat den Bezahlstatus von {self} auf 'nicht bezahlt' gesetzt." )) .relevant_for_user(self) .save(db) .await; } pub(crate) async fn has_paid( &self, db: &SqlitePool, updated_by: &AllowedToEditPaymentStatusUser, ) { let paid = Role::find_by_name(db, "paid").await.unwrap(); sqlx::query!( "INSERT INTO user_role(user_id, role_id) VALUES (?, ?)", self.id, paid.id ) .execute(db) .await .expect("paid role has no group"); ActivityBuilder::new(&format!( "{updated_by} hat den Bezahlstatus von {self} auf 'bezahlt' gesetzt." )) .relevant_for_user(self) .save(db) .await; } pub(crate) async fn add_role( &self, db: &SqlitePool, updated_by: &ManageUserUser, role: &Role, ) -> Result<(), String> { if self.has_role(db, &role.name).await { return Err(format!( "Kann Rolle {role} von User {self} nicht hinzufügen, da der User die Rolle schon hat" )); } sqlx::query!( "INSERT INTO user_role(user_id, role_id) VALUES (?, ?)", self.id, role.id ) .execute(db) .await .map_err(|_| { format!( "User already has a role in the cluster '{}'", role.cluster .clone() .expect("db trigger can't activate on empty string") ) })?; if !role.hide_in_lists && role.cluster.is_none() { ActivityBuilder::new(&format!( "{updated_by} hat die Rolle '{role}' dem Benutzer {self} hinzugefügt." )) .relevant_for_user(self) .save(db) .await; } Ok(()) } pub(crate) async fn remove_membership_pdf(&self, db: &SqlitePool, updated_by: &ManageUserUser) { sqlx::query!( "UPDATE user SET membership_pdf = null where id = ?", self.id ) .execute(db) .await .unwrap(); //Okay, because we can only create a User of a valid id } 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.")); } if membership_pdf.len() == 0 { return Err("Keine Beitrittserklärung mitgeschickt.".to_string()); } 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 ActivityBuilder::new(&format!( "{updated_by} hat die Mitgliedserklärung (PDF) für user {self} hinzugefügt." )) .relevant_for_user(self) .save(db) .await; Ok(()) } }