From 6371366a96958413a0266f7fad2f5c3fb08ad3ab Mon Sep 17 00:00:00 2001 From: philipp Date: Wed, 15 May 2024 14:41:18 +0200 Subject: [PATCH 1/2] send welcome mail to new members --- src/model/mail.rs | 52 +++++++++++++++++++++ src/model/notification.rs | 12 +++++ src/model/user.rs | 68 +++++++++++++++++++++++++++- src/tera/admin/user.rs | 42 +++++++++++++---- templates/admin/user/index.html.tera | 4 ++ 5 files changed, 168 insertions(+), 10 deletions(-) diff --git a/src/model/mail.rs b/src/model/mail.rs index 1a82ce1..dee2bfa 100644 --- a/src/model/mail.rs +++ b/src/model/mail.rs @@ -14,6 +14,58 @@ use super::{family::Family, log::Log, role::Role, user::User}; pub struct Mail {} impl Mail { + pub async fn send_single( + db: &SqlitePool, + to: &str, + subject: &str, + body: String, + smtp_pw: &str, + ) { + let mut email = Message::builder() + .from( + "ASKÖ Ruderverein Donau Linz " + .parse() + .unwrap(), + ) + .reply_to( + "ASKÖ Ruderverein Donau Linz " + .parse() + .unwrap(), + ) + .to("ASKÖ Ruderverein Donau Linz " + .parse() + .unwrap()); + let splitted = to.split(','); + for single_rec in splitted { + match single_rec.parse() { + Ok(new_bcc_mail) => email = email.bcc(new_bcc_mail), + Err(_) => { + Log::create( + db, + format!("Mail not sent to {single_rec}, because it could not be parsed"), + ) + .await; + } + } + } + + let email = email + .subject(subject) + .header(ContentType::TEXT_PLAIN) + .body(body) + .unwrap(); + + let creds = Credentials::new("no-reply@rudernlinz.at".to_owned(), smtp_pw.into()); + + let mailer = SmtpTransport::relay("mail.your-server.de") + .unwrap() + .credentials(creds) + .build(); + + // Send the email + mailer.send(&email).unwrap(); + } + pub async fn send(db: &SqlitePool, data: MailToSend<'_>, smtp_pw: String) -> bool { let mut email = Message::builder() .from( diff --git a/src/model/notification.rs b/src/model/notification.rs index a7747b7..3a29e49 100644 --- a/src/model/notification.rs +++ b/src/model/notification.rs @@ -69,6 +69,18 @@ impl Notification { } } + pub async fn create_for_role( + db: &SqlitePool, + role: &Role, + message: &str, + category: &str, + link: Option<&str>, + ) { + let mut tx = db.begin().await.unwrap(); + Self::create_for_role_tx(&mut tx, role, message, category, link).await; + tx.commit().await.unwrap(); + } + pub async fn for_user(db: &SqlitePool, user: &User) -> Vec { let rows = sqlx::query!( " diff --git a/src/model/user.rs b/src/model/user.rs index 4340ab9..4b36757 100644 --- a/src/model/user.rs +++ b/src/model/user.rs @@ -14,7 +14,10 @@ use rocket::{ use serde::{Deserialize, Serialize}; use sqlx::{FromRow, Sqlite, SqlitePool, Transaction}; -use super::{family::Family, log::Log, role::Role, tripdetails::TripDetails, Day}; +use super::{ + family::Family, log::Log, mail::Mail, notification::Notification, role::Role, + tripdetails::TripDetails, Day, +}; use crate::tera::admin::user::UserEditForm; const RENNRUDERBEITRAG: i32 = 11000; @@ -137,6 +140,69 @@ impl Fee { } impl User { + pub async fn send_welcome_email(&self, db: &SqlitePool, smtp_pw: &str) -> Result<(), String> { + if !self.has_role(db, "Donau Linz").await { + return Err(format!( + "Could not send welcome mail, because user {} is not in Donau Linz group", + self.name + )); + } + + let Some(mail) = &self.mail else { + return Err(format!( + "Could not send welcome mail, because user {} has no email address", + self.name + )); + }; + + Mail::send_single( + db, + mail, + "Willkommen im ASKÖ Ruderverein Donau Linz!", + format!( +"Hallo {0}, + +herzlich willkommen im ASKÖ Ruderverein Donau Linz! Wir freuen uns sehr, dich als neues Mitglied in unserem Verein begrüßen zu dürfen. + +Um dir den Einstieg zu erleichtern, findest du in unserem Handbuch alle wichtigen Informationen über unseren Verein: https://rudernlinz.at/book. Bei weiteren Fragen stehen dir die Adressen info@rudernlinz.at und it@rudernlinz.at jederzeit zur Verfügung. + +Du kannst auch gerne unserer Signal-Gruppe beitreten, um auf dem Laufenden zu bleiben und dich mit anderen Mitgliedern auszutauschen: https://signal.group/#CjQKICFrq6zSsRHxrucS3jEcQn6lknEXacAykwwLV3vNLKxPEhA17jxz7cpjfu3JZokLq1TH + +Für die Organisation unserer Ausfahrten nutzen wir app.rudernlinz.at. Logge dich einfach mit deinem Namen ('{0}' ohne Anführungszeichen) ein, beim ersten Mal kannst du das Passwortfeld leer lassen. Unter 'Geplante Ausfahrten' kannst du dich jederzeit zu den Ausfahrten anmelden. + +Beim nächsten Treffen im Verein, erinnere mich (Philipp Hofer) bitte daran, deinen Fingerabdruck zu registrieren, damit du eigenständig Zugang zum Bootshaus erhältst. + +Wir freuen uns darauf, dich bald am Wasser zu sehen und gemeinsam tolle Erfahrungen zu sammeln! + +Riemen- & Dollenbruch +ASKÖ Ruderverein Donau Linz + ", self.name), + smtp_pw, + ).await; + + Log::create( + db, + format!("Willkommensemail wurde an {} versandt", self.name), + ) + .await; + + let coxes = Role::find_by_name(db, "cox").await.unwrap(); + Notification::create_for_role( + db, + &coxes, + &format!( + "Liebe Steuerberechtigte, seit {} gibt es ein neues Mitglied: {}", + self.member_since_date.clone().unwrap(), + self.name + ), + "Neues Vereinsmitglied", + None, + ) + .await; + + Ok(()) + } + pub async fn fee(&self, db: &SqlitePool) -> Option { if !self.has_role(db, "Donau Linz").await { return None; diff --git a/src/tera/admin/user.rs b/src/tera/admin/user.rs index 919d06f..27109ce 100644 --- a/src/tera/admin/user.rs +++ b/src/tera/admin/user.rs @@ -1,14 +1,17 @@ use std::collections::HashMap; -use crate::model::{ - family::Family, - log::Log, - logbook::Logbook, - role::Role, - user::{ - AdminUser, User, UserWithDetails, UserWithMembershipPdf, UserWithRolesAndMembershipPdf, - VorstandUser, +use crate::{ + model::{ + family::Family, + log::Log, + logbook::Logbook, + role::Role, + user::{ + AdminUser, User, UserWithDetails, UserWithMembershipPdf, UserWithRolesAndMembershipPdf, + VorstandUser, + }, }, + tera::Config, }; use futures::future::join_all; use rocket::{ @@ -202,6 +205,26 @@ async fn fees_paid( ) } +#[get("/user//send-welcome-mail")] +async fn send_welcome_mail( + db: &State, + _admin: AdminUser, + config: &State, + user: i32, +) -> Flash { + let Some(user) = User::find_by_id(db, user).await else { + return Flash::error(Redirect::to("/admin/user"), "User does not exist"); + }; + + match user.send_welcome_email(db, &config.smtp_pw).await { + Ok(()) => Flash::success( + Redirect::to("/admin/user"), + format!("Willkommens-Email wurde an {} versandt.", user.name), + ), + Err(e) => Flash::error(Redirect::to("/admin/user"), e), + } +} + #[get("/user//reset-pw")] async fn resetpw(db: &State, _admin: AdminUser, user: i32) -> Flash { let user = User::find_by_id(db, user).await; @@ -332,6 +355,7 @@ pub fn routes() -> Vec { fees, fees_paid, scheckbuch, - download_membership_pdf + download_membership_pdf, + send_welcome_mail ] } diff --git a/templates/admin/user/index.html.tera b/templates/admin/user/index.html.tera index 985ce62..558dcf0 100644 --- a/templates/admin/user/index.html.tera +++ b/templates/admin/user/index.html.tera @@ -58,6 +58,10 @@ Passwort zurücksetzen {% endif %} + {% if not user.last_access and "Donau Linz" in user.roles and "admin" in loggedin_user.roles %} + Willkommensmail verschicken + {% endif %}
{% for role in roles %} -- 2.45.2 From a465dfcce57798610226fbefedb7ff93e7b16323 Mon Sep 17 00:00:00 2001 From: philipp Date: Wed, 15 May 2024 14:42:58 +0200 Subject: [PATCH 2/2] reformat tera --- templates/admin/user/index.html.tera | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/templates/admin/user/index.html.tera b/templates/admin/user/index.html.tera index 558dcf0..24c712a 100644 --- a/templates/admin/user/index.html.tera +++ b/templates/admin/user/index.html.tera @@ -58,10 +58,10 @@ Passwort zurücksetzen {% endif %} - {% if not user.last_access and "Donau Linz" in user.roles and "admin" in loggedin_user.roles %} + {% if not user.last_access and "Donau Linz" in user.roles and "admin" in loggedin_user.roles %} Willkommensmail verschicken - {% endif %} + {% endif %}
{% for role in roles %} -- 2.45.2