ui-improvements #749

Merged
philipp merged 17 commits from ui-improvements into main 2024-09-24 19:43:47 +02:00
5 changed files with 165 additions and 29 deletions
Showing only changes of commit ece64868fe - Show all commits

View File

@ -118,11 +118,20 @@ pub struct LogbookWithBoatAndRowers {
impl LogbookWithBoatAndRowers {
pub(crate) async fn from(db: &SqlitePool, log: Logbook) -> Self {
let mut tx = db.begin().await.unwrap();
let ret = Self::from_tx(&mut tx, log).await;
tx.commit().await.unwrap();
ret
}
pub(crate) async fn from_tx(db: &mut Transaction<'_, Sqlite>, log: Logbook) -> Self {
Self {
rowers: Rower::for_log(db, &log).await,
boat: Boat::find_by_id(db, log.boat_id as i32).await.unwrap(),
shipmaster_user: User::find_by_id(db, log.shipmaster as i32).await.unwrap(),
steering_user: User::find_by_id(db, log.steering_person as i32)
rowers: Rower::for_log_tx(db, &log).await,
boat: Boat::find_by_id_tx(db, log.boat_id as i32).await.unwrap(),
shipmaster_user: User::find_by_id_tx(db, log.shipmaster as i32)
.await
.unwrap(),
steering_user: User::find_by_id_tx(db, log.steering_person as i32)
.await
.unwrap(),
logbook: log,
@ -278,6 +287,16 @@ ORDER BY departure DESC
pub async fn completed_with_user(
db: &SqlitePool,
user: &User,
) -> Vec<LogbookWithBoatAndRowers> {
let mut tx = db.begin().await.unwrap();
let ret = Self::completed_with_user_tx(&mut tx, user).await;
tx.commit().await.unwrap();
ret
}
pub async fn completed_with_user_tx(
db: &mut Transaction<'_, Sqlite>,
user: &User,
) -> Vec<LogbookWithBoatAndRowers> {
let logs = sqlx::query_as(
&format!("
@ -288,13 +307,13 @@ ORDER BY departure DESC
ORDER BY arrival DESC
", user.id)
)
.fetch_all(db)
.fetch_all(db.deref_mut())
.await
.unwrap(); //TODO: fixme
let mut ret = Vec::new();
for log in logs {
ret.push(LogbookWithBoatAndRowers::from(db, log).await);
ret.push(LogbookWithBoatAndRowers::from_tx(db, log).await);
}
ret
}
@ -415,6 +434,7 @@ ORDER BY departure DESC
db: &SqlitePool,
mut log: LogToAdd,
created_by_user: &User,
smtp_pw: &str,
) -> Result<String, LogbookCreateError> {
let Some(boat) = Boat::find_by_id(db, log.boat_id).await else {
return Err(LogbookCreateError::BoatNotFound);
@ -462,7 +482,7 @@ ORDER BY departure DESC
.unwrap(); //ok
return match logbook
.home_with_transaction(&mut tx, created_by_user, log_to_finalize)
.home_with_transaction(&mut tx, created_by_user, log_to_finalize, smtp_pw)
.await
{
Ok(_) => {
@ -613,9 +633,11 @@ ORDER BY departure DESC
db: &SqlitePool,
user: &User,
log: LogToFinalize,
smtp_pw: &str,
) -> Result<(), LogbookUpdateError> {
let mut tx = db.begin().await.unwrap();
self.home_with_transaction(&mut tx, user, log).await?;
self.home_with_transaction(&mut tx, user, log, smtp_pw)
.await?;
tx.commit().await.unwrap();
Ok(())
}
@ -625,6 +647,7 @@ ORDER BY departure DESC
db: &mut Transaction<'_, Sqlite>,
user: &User,
mut log: LogToFinalize,
smtp_pw: &str,
) -> Result<(), LogbookUpdateError> {
//TODO: extract common tests with `create()`
if !user.has_role_tx(db, "Vorstand").await && user.id != self.shipmaster {
@ -778,6 +801,11 @@ ORDER BY departure DESC
).await;
}
for rower in &log.rowers {
let user = User::find_by_id_tx(db, *rower as i32).await.unwrap();
user.received_new_logentry(db, smtp_pw).await;
}
Ok(())
}
@ -914,6 +942,7 @@ mod test {
rowers: vec![4],
},
&User::find_by_id(&pool, 4).await.unwrap(),
"",
)
.await
.unwrap();
@ -945,6 +974,7 @@ mod test {
departure: format!("{}T10:00", start_date),
arrival: format!("{}T12:00", current_date),
},
"",
)
.await
.unwrap();
@ -965,6 +995,7 @@ mod test {
rowers: vec![2],
},
&User::find_by_id(&pool, 1).await.unwrap(),
"",
)
.await
.unwrap();
@ -994,6 +1025,7 @@ mod test {
rowers: vec![5],
},
&User::find_by_id(&pool, 4).await.unwrap(),
"",
)
.await;
@ -1020,6 +1052,7 @@ mod test {
rowers: vec![5],
},
&User::find_by_id(&pool, 4).await.unwrap(),
"",
)
.await;
@ -1046,6 +1079,7 @@ mod test {
rowers: vec![5],
},
&User::find_by_id(&pool, 5).await.unwrap(),
"",
)
.await;
@ -1072,6 +1106,7 @@ mod test {
rowers: vec![5],
},
&User::find_by_id(&pool, 5).await.unwrap(),
"",
)
.await;
@ -1098,6 +1133,7 @@ mod test {
rowers: Vec::new(),
},
&User::find_by_id(&pool, 2).await.unwrap(),
"",
)
.await;
@ -1124,6 +1160,7 @@ mod test {
rowers: vec![5],
},
&User::find_by_id(&pool, 5).await.unwrap(),
"",
)
.await;
@ -1150,6 +1187,7 @@ mod test {
rowers: vec![1, 5],
},
&User::find_by_id(&pool, 5).await.unwrap(),
"",
)
.await;
@ -1181,6 +1219,7 @@ mod test {
departure: format!("{}T10:00", current_date),
arrival: format!("{}T12:00", current_date),
},
"",
)
.await
.unwrap();
@ -1209,6 +1248,7 @@ mod test {
departure: "1990-01-01T10:00".into(),
arrival: "1990-01-01T12:00".into(),
},
"",
)
.await;
@ -1238,6 +1278,7 @@ mod test {
departure: "1990-01-01T10:00".into(),
arrival: "1990-01-01T12:00".into(),
},
"",
)
.await;

View File

@ -5,7 +5,7 @@ use lettre::{
transport::smtp::authentication::Credentials,
Message, SmtpTransport, Transport,
};
use sqlx::SqlitePool;
use sqlx::{Sqlite, SqlitePool, Transaction};
use crate::tera::admin::mail::MailToSend;
@ -20,6 +20,19 @@ impl Mail {
subject: &str,
body: String,
smtp_pw: &str,
) -> Result<(), String> {
let mut tx = db.begin().await.unwrap();
let ret = Self::send_single_tx(&mut tx, to, subject, body, smtp_pw).await;
tx.commit().await.unwrap();
ret
}
pub async fn send_single_tx(
db: &mut Transaction<'_, Sqlite>,
to: &str,
subject: &str,
body: String,
smtp_pw: &str,
) -> Result<(), String> {
let mut email = Message::builder()
.from(
@ -40,7 +53,7 @@ impl Mail {
match single_rec.parse() {
Ok(new_bcc_mail) => email = email.bcc(new_bcc_mail),
Err(_) => {
Log::create(
Log::create_with_tx(
db,
format!("Mail not sent to {single_rec}, because it could not be parsed"),
)

View File

@ -13,6 +13,13 @@ pub struct Rower {
impl Rower {
pub async fn for_log(db: &SqlitePool, log: &Logbook) -> Vec<User> {
let mut tx = db.begin().await.unwrap();
let ret = Self::for_log_tx(&mut tx, log).await;
tx.commit().await.unwrap();
ret
}
pub async fn for_log_tx(db: &mut Transaction<'_, Sqlite>, log: &Logbook) -> Vec<User> {
sqlx::query_as!(
User,
"
@ -22,7 +29,7 @@ WHERE id in (SELECT rower_id FROM rower WHERE logbook_id=?)
",
log.id
)
.fetch_all(db)
.fetch_all(db.deref_mut())
.await
.unwrap()
}

View File

@ -15,8 +15,8 @@ use serde::{Deserialize, Serialize};
use sqlx::{FromRow, Sqlite, SqlitePool, Transaction};
use super::{
family::Family, log::Log, mail::Mail, notification::Notification, role::Role, stat::Stat,
tripdetails::TripDetails, Day,
family::Family, log::Log, logbook::Logbook, mail::Mail, notification::Notification, role::Role,
stat::Stat, tripdetails::TripDetails, Day,
};
use crate::{
tera::admin::user::UserEditForm, AMOUNT_DAYS_TO_SHOW_TRIPS_AHEAD, BOAT_STORAGE,
@ -251,6 +251,51 @@ ASKÖ Ruderverein Donau Linz", self.name, SCHECKBUCH/100),
Ok(())
}
async fn send_end_mail_scheckbuch(
&self,
db: &mut Transaction<'_, Sqlite>,
mail: &str,
smtp_pw: &str,
) -> Result<(), String> {
// 2 things to do:
// 1. Send mail to user
Mail::send_single_tx(
db,
mail,
"ASKÖ Ruderverein Donau Linz | Deine Mitgliedschaft wartet auf Dich",
format!(
"Hallo {0},
herzlichen Glückwunsch---Du hast Deine fünf Scheckbuch-Ausfahrten erfolgreich absolviert! Wir hoffen, dass Du das Rudern bei uns genauso genossen hast wie wir es genossen haben, Dich auf dem Wasser zu begleiten.
Wir würden uns sehr freuen, Dich als festes Mitglied in unserem Verein willkommen zu heißen! Als Mitglied stehen Dir dann alle unsere Ausfahrten offen, die von unseren Steuerleuten organisiert werden. Im Sommer erwarten Dich zusätzlich spannende Events: Wanderfahrten, Sternfahrten, Fetzenfahrt, .... Im Winter bieten wir Indoor-Ergo-Challenges an, bei denen Du Deine Fitness auf dem Ruderergometer unter Beweis stellen kannst. Alle Details zu diesen Aktionen erfährst Du, sobald Du Teil unseres Vereins bist! :-)
Alle Informationen zu den Mitgliedsbeiträgen findest Du unter https://rudernlinz.at/unser-verein/gebuhren/ Falls Du Dich entscheidest, unserem Verein beizutreten, fülle bitte unser Beitrittsformular auf https://rudernlinz.at/unser-verein/downloads/ aus und sende es an info@rudernlinz.at.
Wir freuen uns, Dich bald wieder auf dem Wasser zu sehen.
Riemen- & Dollenbruch,
ASKÖ Ruderverein Donau Linz", self.name),
smtp_pw,
).await?;
// 2. Notify all coxes
let coxes = Role::find_by_name_tx(db, "cox").await.unwrap();
Notification::create_for_role_tx(
db,
&coxes,
&format!(
"Liebe Steuerberechtigte, {} hat alle Ausfahrten des Scheckbuchs absolviert. Hoffentlich können wir uns bald über ein neues Mitglied freuen :-)",
self.name
),
"Scheckbuch fertig",
None,None
)
.await;
Ok(())
}
async fn send_welcome_mail_full_member(
&self,
db: &SqlitePool,
@ -920,6 +965,20 @@ ORDER BY last_access DESC
}
None
}
pub(crate) async fn received_new_logentry(
&self,
db: &mut Transaction<'_, Sqlite>,
smtp_pw: &str,
) {
if self.has_role_tx(db, "scheckbuch").await
&& Logbook::completed_with_user_tx(db, &self).await.len() == 5
{
if let Some(mail) = &self.mail {
let _ = self.send_end_mail_scheckbuch(db, mail, smtp_pw).await;
}
}
}
}
#[async_trait]

View File

@ -15,18 +15,21 @@ use rocket_dyn_templates::{context, Template};
use sqlx::SqlitePool;
use tera::Context;
use crate::model::{
use crate::{
model::{
boat::Boat,
boatreservation::BoatReservation,
distance::Distance,
log::Log,
logbook::{
LogToAdd, LogToFinalize, LogToUpdate, Logbook, LogbookAdminUpdateError, LogbookCreateError,
LogbookDeleteError, LogbookUpdateError,
LogToAdd, LogToFinalize, LogToUpdate, Logbook, LogbookAdminUpdateError,
LogbookCreateError, LogbookDeleteError, LogbookUpdateError,
},
logtype::LogType,
trip::Trip,
user::{AdminUser, DonauLinzUser, User, UserWithDetails, VorstandUser},
},
tera::Config,
};
pub struct KioskCookie(());
@ -210,11 +213,12 @@ async fn create_logbook(
db: &SqlitePool,
data: Form<LogToAdd>,
user: &DonauLinzUser,
smtp_pw: &str,
) -> Flash<Redirect> {
match Logbook::create(
db,
data.into_inner(),
user
user, smtp_pw
)
.await
{
@ -244,6 +248,7 @@ async fn create(
db: &State<SqlitePool>,
data: Form<LogToAdd>,
user: DonauLinzUser,
config: &State<Config>,
) -> Flash<Redirect> {
Log::create(
db,
@ -251,7 +256,7 @@ async fn create(
)
.await;
create_logbook(db, data, &user).await
create_logbook(db, data, &user, &config.smtp_pw).await
}
#[post("/", data = "<data>")]
@ -259,6 +264,7 @@ async fn create_kiosk(
db: &State<SqlitePool>,
data: Form<LogToAdd>,
_kiosk: KioskCookie,
config: &State<Config>,
) -> Flash<Redirect> {
let Some(boat) = Boat::find_by_id(db, data.boat_id).await else {
return Flash::error(Redirect::to("/log"), "Boot gibt's nicht");
@ -287,7 +293,13 @@ async fn create_kiosk(
)
.await;
create_logbook(db, data, &DonauLinzUser::new(db, creator).await.unwrap()).await
create_logbook(
db,
data,
&DonauLinzUser::new(db, creator).await.unwrap(),
&config.smtp_pw,
)
.await
//TODO: fixme
}
@ -331,6 +343,7 @@ async fn home_logbook(
data: Form<LogToFinalize>,
logbook_id: i64,
user: &DonauLinzUser,
smtp_pw: &str,
) -> Flash<Redirect> {
let logbook: Option<Logbook> = Logbook::find_by_id(db, logbook_id).await;
let Some(logbook) = logbook else {
@ -340,7 +353,7 @@ async fn home_logbook(
);
};
match logbook.home(db,user, data.into_inner()).await {
match logbook.home(db,user, data.into_inner(), smtp_pw).await {
Ok(_) => Flash::success(Redirect::to("/log"), "Ausfahrt korrekt eingetragen"),
Err(LogbookUpdateError::TooManyRowers(expected, actual)) => Flash::error(Redirect::to("/log"), format!("Zu viele Ruderer (Boot fasst maximal {expected}, es wurden jedoch {actual} Ruderer ausgewählt)")),
Err(LogbookUpdateError::OnlyAllowedToEndTripsEndingToday) => Flash::error(Redirect::to("/log"), "Nur Ausfahrten, die heute enden dürfen eingetragen werden. Für einen Nachtrag schreibe alle Daten Philipp (Tel. nr. siehe Signal oder it@rudernlinz.at)."),
@ -361,6 +374,7 @@ async fn home_kiosk(
data: Form<LogToFinalize>,
logbook_id: i64,
_kiosk: KioskCookie,
config: &State<Config>,
) -> Flash<Redirect> {
let logbook = Logbook::find_by_id(db, logbook_id).await.unwrap(); //TODO: fixme
@ -382,6 +396,7 @@ async fn home_kiosk(
)
.await
.unwrap(),
&config.smtp_pw,
)
.await
}
@ -392,6 +407,7 @@ async fn home(
data: Form<LogToFinalize>,
logbook_id: i64,
user: DonauLinzUser,
config: &State<Config>,
) -> Flash<Redirect> {
Log::create(
db,
@ -402,7 +418,7 @@ async fn home(
)
.await;
home_logbook(db, data, logbook_id, &user).await
home_logbook(db, data, logbook_id, &user, &config.smtp_pw).await
}
#[get("/<logbook_id>/delete", rank = 2)]