board members can delete trips, proper notification + succ message is created #1048

Merged
philipp merged 1 commits from board-can-delete-trips into main 2025-05-21 09:58:23 +02:00
15 changed files with 269 additions and 156 deletions

View File

@ -1,6 +1,7 @@
use std::ops::DerefMut;
use super::{
logbook::{Logbook, LogbookWithBoatAndRowers},
role::Role,
user::{ManageUserUser, User},
};
@ -42,6 +43,7 @@ impl From<Activity> for ActivityWithDetails {
// TODO: add `reason` as additional db field, to be able to query and show this to the users
pub enum Reason<'a> {
Auth(ReasonAuth<'a>),
Logbook(ReasonLogbook<'a>),
// `User` changed the data of `User`, explanation in `String`
UserDataChange(&'a ManageUserUser, &'a User, String),
// New Note for User
@ -55,10 +57,11 @@ impl From<Reason<'_>> for ActivityBuilder {
Reason::UserDataChange(changed_by, changed_user, explanation) => Self::new(&format!(
"{changed_by} hat die Daten von {changed_user} aktualisiert: {explanation}"
))
.relevant_for_user(changed_user),
.user(changed_user),
Reason::NewUserNote(changed_by, user, explanation) => {
Self::new(&format!("({changed_by}) {explanation}")).relevant_for_user(user)
Self::new(&format!("({changed_by}) {explanation}")).user(user)
}
Reason::Logbook(logbook) => logbook.into(),
}
}
}
@ -83,18 +86,42 @@ impl From<ReasonAuth<'_>> for ActivityBuilder {
match value {
ReasonAuth::SuccLogin(user, agent) => {
Self::new(&format!("{user} hat sich eingeloggt (User-Agent: {agent})"))
.relevant_for_user(user)
.user(user)
.keep_until_days(7)
}
ReasonAuth::DeletedUserLogin(user) => Self::new(&format!(
"{user} wollte sich einloggen, klappte jedoch nicht weil der Account gelöscht wurde."
))
.relevant_for_user(user)
.user(user)
.keep_until_days(30),
ReasonAuth::WrongPw(user) => Self::new(&format!(
"User {user} wollte sich einloggen, hat jedoch das falsche Passwort angegeben."
))
.relevant_for_user(user)
.user(user)
.keep_until_days(7),
}
}
}
pub enum ReasonLogbook<'a> {
// `User` tried to login with `String` as UserAgent
BoardOrAdminDeleted(&'a User, &'a LogbookWithBoatAndRowers),
}
impl<'a> From<ReasonLogbook<'a>> for Reason<'a> {
fn from(logbook_reason: ReasonLogbook<'a>) -> Self {
Reason::Logbook(logbook_reason)
}
}
impl From<ReasonLogbook<'_>> for ActivityBuilder {
fn from(value: ReasonLogbook<'_>) -> Self {
match value {
ReasonLogbook::BoardOrAdminDeleted(user, logbook) => Self::new(&format!(
"{user} hat den Logbuch-Eintrag gelöscht: {logbook}"
))
.user(user)
.logbook(&logbook.logbook)
.keep_until_days(7),
}
}
@ -118,7 +145,7 @@ impl ActivityBuilder {
}
#[must_use]
pub fn relevant_for_user(self, user: &User) -> Self {
pub fn user(self, user: &User) -> Self {
Self {
relevant_for: format!("{}user-{};", self.relevant_for, user.id),
..self
@ -126,13 +153,21 @@ impl ActivityBuilder {
}
#[must_use]
pub fn relevant_for_role(self, role: &Role) -> Self {
pub fn role(self, role: &Role) -> Self {
Self {
relevant_for: format!("{}role-{};", self.relevant_for, role.id),
..self
}
}
#[must_use]
pub fn logbook(self, logbook: &Logbook) -> Self {
Self {
relevant_for: format!("{}logbook-{};", self.relevant_for, logbook.id),
..self
}
}
#[must_use]
pub fn keep_until_days(self, days: i64) -> Self {
let now = Utc::now().naive_utc();

View File

@ -1,14 +1,15 @@
use std::ops::DerefMut;
use chrono::NaiveDateTime;
use rocket::FromForm;
use rocket::serde::{Deserialize, Serialize};
use rocket::FromForm;
use sqlx::{FromRow, Sqlite, SqlitePool, Transaction};
use crate::model::boathouse::Boathouse;
use super::location::Location;
use super::user::User;
use std::fmt::Display;
#[derive(FromRow, Debug, Serialize, Deserialize, Eq, Hash, PartialEq, Clone)]
pub struct Boat {
@ -31,6 +32,17 @@ pub struct Boat {
pub deleted: bool,
}
impl Display for Boat {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let private_or_club_boat = if self.owner.is_some() {
"privat"
} else {
"Vereinsboot"
};
write!(f, "{} ({}, {private_or_club_boat})", self.name, self.cat())
}
}
#[derive(Serialize, Deserialize, Debug)]
#[serde(rename_all = "lowercase")]
pub enum BoatDamage {
@ -178,8 +190,10 @@ AND date('now') BETWEEN start_date AND end_date;",
"Vereinsfremde Boote".to_string()
} else if self.default_shipmaster_only_steering {
format!("{}+", self.amount_seats - 1)
} else {
} else if self.skull {
format!("{}x", self.amount_seats)
} else {
format!("{}-", self.amount_seats)
}
}

View File

@ -1,4 +1,4 @@
use std::ops::DerefMut;
use std::{fmt::Display, ops::DerefMut};
use chrono::{Datelike, Duration, Local, NaiveDateTime};
use rocket::FromForm;
@ -6,8 +6,15 @@ use serde::{Deserialize, Serialize};
use sqlx::{FromRow, Sqlite, SqlitePool, Transaction};
use super::{
boat::Boat, log::Log, notification::Notification, role::Role, rower::Rower, user::User,
activity::{ActivityBuilder, ReasonLogbook},
boat::Boat,
log::Log,
notification::Notification,
role::Role,
rower::Rower,
user::User,
};
use crate::model::user::VecUser;
#[derive(FromRow, Serialize, Deserialize, Clone, Debug)]
pub struct Logbook {
@ -115,6 +122,54 @@ pub struct LogbookWithBoatAndRowers {
pub rowers: Vec<User>,
}
impl Display for LogbookWithBoatAndRowers {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
if let Some(arrival) = self.logbook.arrival {
let departure_date = format!("{}", self.logbook.departure.format("%Y-%m-%d"));
let arrival_date = format!("{}", arrival.format("%Y-%m-%d"));
if departure_date == arrival_date {
write!(
f,
"Datum: {}: Start: {}, Ende: {}; ",
&self.logbook.departure.format("%d. %m. %Y"),
&self.logbook.departure.format("%H:%M"),
&arrival.format("%H:%M")
)?;
} else {
write!(
f,
"{} - {}; ",
&self.logbook.departure.format("%d. %m. %Y"),
&arrival.format("%d. %m. %Y"),
)?;
}
} else {
write!(
f,
"Start: {}",
&self.logbook.departure.format("%d. %m. %Y %H:%M")
)?;
}
if let Some(destination) = &self.logbook.destination {
write!(f, "Ziel: {destination}; ")?;
}
write!(f, "Boot: {}; ", self.boat)?;
if let Some(distance) = self.logbook.distance_in_km {
write!(f, "Distanz: {distance} km; ")?;
}
write!(f, "Schiffsführer: {}; ", self.shipmaster_user)?;
write!(f, "Steuerperson: {}; ", self.steering_user)?;
write!(f, "Rudernde: {}; ", VecUser(&self.rowers))?;
if let Some(comments) = &self.logbook.comments {
if !comments.trim().is_empty() {
write!(f, "Kommentar: {comments}; ")?;
}
}
Ok(())
}
}
impl LogbookWithBoatAndRowers {
pub(crate) async fn from(db: &SqlitePool, log: Logbook) -> Self {
let mut tx = db.begin().await.unwrap();
@ -811,43 +866,22 @@ ORDER BY departure DESC
}
pub async fn delete(&self, db: &SqlitePool, user: &User) -> Result<(), LogbookDeleteError> {
Log::create(db, format!("{} deleted trip: {self:?}", user.name)).await;
if self.arrival.is_none() {
if user.has_role(db, "admin").await
|| user.has_role(db, "Vorstand").await
|| user.id == self.shipmaster
{
Log::create(db, format!("{} deleted trip: {self:?}", user.name)).await;
let now = Local::now().naive_local();
let difference = now - self.departure;
if difference > Duration::hours(1) {
let vorstand = Role::find_by_name(db, "Vorstand").await.unwrap();
let logbook = LogbookWithBoatAndRowers::from(db, self.clone()).await;
let mut msg = format!(
"{} hat folgenden Logbuch-Eintrag jetzt gelöscht, welcher bereits vor über einer Stunde begonnen wurde: Schiffsführer: {}, Steuerperson: {}, Abfahrt: {}",
user.name,
logbook.steering_user.name,
logbook.steering_user.name,
logbook.logbook.departure.format("%Y-%m-%d %H:%M")
);
if let Some(destination) = logbook.logbook.destination {
msg.push_str(&format!(", Ziel: {}", destination));
} else {
msg.push_str(", kein Ziel eingegeben");
}
msg.push_str(", Ruderer: ");
let mut it = logbook.rowers.clone().into_iter().peekable();
while let Some(rower) = it.next() {
msg.push_str(&rower.name);
if it.peek().is_some() {
msg.push_str(" + ");
}
}
Notification::create_for_role(
db,
&vorstand,
&msg,
&format!("{user} hat folgenden Logbuch-Eintrag jetzt gelöscht, welcher bereits vor über einer Stunde begonnen wurde: {logbook}"),
"Ungewöhnliches Verhalten",
None,
None,
@ -862,8 +896,24 @@ ORDER BY departure DESC
return Ok(());
}
} else {
// Only admins can delete completed logbook entries
if user.has_role(db, "admin").await {
// Only admins+Vorstand can delete completed logbook entries
if user.has_role(db, "admin").await || user.has_role(db, "Vorstand").await {
let logbookdetails = LogbookWithBoatAndRowers::from(db, self.clone()).await;
ActivityBuilder::from(ReasonLogbook::BoardOrAdminDeleted(user, &logbookdetails))
.save(db)
.await;
let vorstand = Role::find_by_name(db, "Vorstand").await.unwrap();
Notification::create_for_role(
db,
&vorstand,
&format!("{user} hat den Logbuch-Eintrag gelöscht: {logbookdetails}"),
"Logbuch gelöscht",
None,
None,
)
.await;
sqlx::query!("DELETE FROM logbook WHERE id=?", self.id)
.execute(db)
.await

View File

@ -1,9 +1,9 @@
use std::{error::Error, fs};
use lettre::{
Address, Message, SmtpTransport, Transport,
message::{Attachment, MultiPart, SinglePart, header::ContentType},
message::{header::ContentType, Attachment, MultiPart, SinglePart},
transport::smtp::authentication::Credentials,
Address, Message, SmtpTransport, Transport,
};
use sqlx::{Sqlite, SqlitePool, Transaction};
@ -261,7 +261,7 @@ Der Vorstand");
ActivityBuilder::new(&format!(
"{user} hat die Info-Mail bzgl. Gebühren gesendet bekommen."
))
.relevant_for_user(&user)
.user(&user)
.save(db)
.await;
}
@ -388,7 +388,7 @@ Der Vorstand");
ActivityBuilder::new(&format!(
"{user} hat die Mahn-Mail bzgl. Gebühren gesendet bekommen."
))
.relevant_for_user(&user)
.user(&user)
.save(db)
.await;
}

View File

@ -158,7 +158,7 @@ WHERE name like ?
ActivityBuilder::new(&format!(
"{updated_by} hat Rolle {self} von {self:#?} auf FORMATTED_NAME={formatted_name}, DESC={desc} aktualisiert."
)).relevant_for_role(self).save(db).await;
)).role(self).save(db).await;
Ok(())
}

View File

@ -138,10 +138,7 @@ impl User {
None => format!("{updated_by} hat eine Adresse für {self} hinzugefügt: {new_address}"),
};
ActivityBuilder::new(&msg)
.relevant_for_user(self)
.save(db)
.await;
ActivityBuilder::new(&msg).user(self).save(db).await;
}
pub(crate) async fn update_nickname(
@ -174,10 +171,7 @@ impl User {
"{updated_by} hat einen neuen Spitznamen für {self} hinzugefügt: {new_nickname}"
),
};
ActivityBuilder::new(&msg)
.relevant_for_user(self)
.save(db)
.await;
ActivityBuilder::new(&msg).user(self).save(db).await;
Ok(())
}
@ -206,10 +200,7 @@ impl User {
),
};
ActivityBuilder::new(&msg)
.relevant_for_user(self)
.save(db)
.await;
ActivityBuilder::new(&msg).user(self).save(db).await;
}
pub(crate) async fn update_birthdate(
@ -236,10 +227,7 @@ impl User {
}
};
ActivityBuilder::new(&msg)
.relevant_for_user(self)
.save(db)
.await;
ActivityBuilder::new(&msg).user(self).save(db).await;
}
pub(crate) async fn update_family(
@ -261,7 +249,7 @@ impl User {
ActivityBuilder::new(&format!(
"{updated_by} hat {self} zu einer Familie hinzugefügt."
))
.relevant_for_user(self)
.user(self)
.save(db)
.await;
} else {
@ -272,7 +260,7 @@ impl User {
ActivityBuilder::new(&format!(
"{updated_by} hat die Familienzugehörigkeit von {self} gelöscht."
))
.relevant_for_user(self)
.user(self)
.save(db)
.await;
};
@ -318,7 +306,7 @@ impl User {
)
.await;
ActivityBuilder::new(&format!("{updated_by} hat {self} zur Steuerperson gemacht"))
.relevant_for_user(self)
.user(self)
.save(db)
.await;
}
@ -337,7 +325,7 @@ impl User {
)
.await;
ActivityBuilder::new(&format!("{updated_by} hat {self} zum Bootsführer gemacht"))
.relevant_for_user(self)
.user(self)
.save(db)
.await;
}
@ -355,7 +343,7 @@ impl User {
)
.await;
ActivityBuilder::new(&format!("{updated_by} hat {self} zum normalen Mitglied gemacht (keine Steuerperson/Schiffsführer mehr)"))
.relevant_for_user(self)
.user(self)
.save(db)
.await;
}
@ -392,7 +380,7 @@ impl User {
ActivityBuilder::new(&format!(
"{updated_by} hat die Ermäßigung von {self} von {old} auf {new} geändert"
))
.relevant_for_user(self)
.user(self)
.save(db)
.await;
@ -424,7 +412,7 @@ impl User {
ActivityBuilder::new(&format!(
"{updated_by} hat die Rolle {role} von {self} entfernt."
))
.relevant_for_user(self)
.user(self)
.save(db)
.await;
}
@ -451,7 +439,7 @@ impl User {
ActivityBuilder::new(&format!(
"{updated_by} hat den Bezahlstatus von {self} auf 'nicht bezahlt' gesetzt."
))
.relevant_for_user(self)
.user(self)
.save(db)
.await;
}
@ -474,7 +462,7 @@ impl User {
ActivityBuilder::new(&format!(
"{updated_by} hat den Bezahlstatus von {self} auf 'bezahlt' gesetzt."
))
.relevant_for_user(self)
.user(self)
.save(db)
.await;
}
@ -511,7 +499,7 @@ impl User {
ActivityBuilder::new(&format!(
"{updated_by} hat die Rolle '{role}' dem Benutzer {self} hinzugefügt."
))
.relevant_for_user(self)
.user(self)
.save(db)
.await;
}
@ -556,7 +544,7 @@ impl User {
ActivityBuilder::new(&format!(
"{updated_by} hat die Mitgliedserklärung (PDF) für user {self} hinzugefügt."
))
.relevant_for_user(self)
.user(self)
.save(db)
.await;

View File

@ -86,7 +86,7 @@ impl ClubMemberUser {
ActivityBuilder::new(&format!(
"{modified_by} hat {self} zu einem regulären hochgestuft."
))
.relevant_for_user(&self)
.user(&self)
.save(db)
.await;
@ -122,7 +122,7 @@ impl ClubMemberUser {
ActivityBuilder::new(&format!(
"{modified_by} hat {self} zu einem unterstützenden Mitglied gemacht."
))
.relevant_for_user(&self)
.user(&self)
.save(db)
.await;
@ -158,7 +158,7 @@ impl ClubMemberUser {
ActivityBuilder::new(&format!(
"{modified_by} hat {self} zu ein förderndes Mitglied gemacht."
))
.relevant_for_user(&self)
.user(&self)
.save(db)
.await;

View File

@ -1,8 +1,7 @@
use super::{ManageUserUser, User, regular::ClubMember};
use super::{regular::ClubMember, ManageUserUser, User};
use crate::{
NonEmptyString,
model::{activity::ActivityBuilder, mail::Mail, notification::Notification, role::Role},
special_user,
special_user, NonEmptyString,
};
use chrono::NaiveDate;
use rocket::{async_trait, fs::TempFile};
@ -45,7 +44,7 @@ ASKÖ Ruderverein Donau Linz", self.name),
ActivityBuilder::new(&format!(
"User {self} hat die Info-Mail bzgl. neues förderndes Mitglied (Handbuch und WLAN Infos) an {mail} gesendet bekommen"
))
.relevant_for_user(self)
.user(self)
.save(db)
.await;

View File

@ -1,21 +1,20 @@
use std::{fmt::Display, ops::DerefMut};
use argon2::{Argon2, PasswordHasher, password_hash::SaltString};
use argon2::{password_hash::SaltString, Argon2, PasswordHasher};
use chrono::{Datelike, Local, NaiveDate};
use log::info;
use rocket::async_trait;
use rocket::{
Request,
http::{Cookie, Status},
request::{FromRequest, Outcome},
time::{Duration, OffsetDateTime},
Request,
};
use serde::{Deserialize, Serialize};
use sqlx::{FromRow, Sqlite, SqlitePool, Transaction};
use super::activity::{ActivityBuilder, ReasonAuth};
use super::{
Day,
log::Log,
logbook::Logbook,
mail::Mail,
@ -24,6 +23,7 @@ use super::{
role::Role,
stat::Stat,
tripdetails::TripDetails,
Day,
};
use crate::AMOUNT_DAYS_TO_SHOW_TRIPS_AHEAD;
use scheckbuch::ScheckbuchUser;
@ -65,6 +65,21 @@ impl Display for User {
}
}
pub(crate) struct VecUser<'a>(pub &'a Vec<User>);
impl Display for VecUser<'_> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(
f,
"{}",
self.0
.iter()
.map(|user| user.name.as_str())
.collect::<Vec<_>>()
.join(", ")
)
}
}
#[derive(Debug, Serialize, Deserialize)]
pub struct UserWithDetails {
#[serde(flatten)]
@ -457,7 +472,7 @@ ASKÖ Ruderverein Donau Linz", self.name),
smtp_pw,
).await?;
ActivityBuilder::new(&format!("User {self} hat eine Mail bekommen, dass seine 5 Ausfahrten mit der heutigen Ausfahrt aufgebraucht sind, und dass der nächste Schritt eine Vereinsmitgliedschaft wäre (inkl. Links zu Beitrittserklärung + Info, dass sie an info@ geschickt werden soll.")).relevant_for_user(self).save_tx(db).await;
ActivityBuilder::new(&format!("User {self} hat eine Mail bekommen, dass seine 5 Ausfahrten mit der heutigen Ausfahrt aufgebraucht sind, und dass der nächste Schritt eine Vereinsmitgliedschaft wäre (inkl. Links zu Beitrittserklärung + Info, dass sie an info@ geschickt werden soll.")).user(self).save_tx(db).await;
Ok(())
}
@ -515,7 +530,7 @@ ASKÖ Ruderverein Donau Linz", self.name),
ActivityBuilder::new(&format!(
"{changed_by} hat das Passwort von User {self} zurückgesetzt."
))
.relevant_for_user(self)
.user(self)
.save(db)
.await;
}
@ -527,7 +542,7 @@ ASKÖ Ruderverein Donau Linz", self.name),
.await
.unwrap(); //Okay, because we can only create a User of a valid id
ActivityBuilder::new(&format!("{self} hat sein Passwort geändert."))
.relevant_for_user(self)
.user(self)
.save(db)
.await;
}
@ -557,7 +572,7 @@ ASKÖ Ruderverein Donau Linz", self.name),
.await
.unwrap(); //Okay, because we can only create a User of a valid id
ActivityBuilder::new(&format!("User {self} wurde von {deleted_by} gelöscht."))
.relevant_for_user(self)
.user(self)
.save(db)
.await;
}
@ -652,7 +667,7 @@ ASKÖ Ruderverein Donau Linz", self.name),
)
.await;
ActivityBuilder::new(&format!("5 Scheckbuchausfahrten von {self} wurden mit der heutigen Ausfahrt aufgebraucht. Info-Mail wurde an {self} geschickt + alle Steuerberechtigten informiert, dass wir pot. ein neues Mitglied haben"))
.relevant_for_user(self)
.user(self)
.save_tx(db)
.await;
}
@ -670,7 +685,7 @@ ASKÖ Ruderverein Donau Linz", self.name),
)
.await;
ActivityBuilder::new(&format!("{self} hat nun bereits die {amount_trips}. seiner 5 Scheckbuchausfahrten absolviert. Vorstand wurde via Notification informiert."))
.relevant_for_user(self)
.user(self)
.save_tx(db)
.await;
}
@ -695,7 +710,7 @@ ASKÖ Ruderverein Donau Linz", self.name),
ActivityBuilder::new(&format!(
"{self} hat das heurige Fahrtenabzeichen geschafft! Der Vorstand + {self} wurde via Notification informiert."
))
.relevant_for_user(self)
.user(self)
.save_tx(db)
.await;
@ -717,7 +732,7 @@ ASKÖ Ruderverein Donau Linz", self.name),
)
.await;
ActivityBuilder::new(&format!("{self} hat den Äquatorpreis in {level} geschafft! Der Vorstand + {self} wurde via Notification informiert."))
.relevant_for_user(self)
.user(self)
.save_tx(db)
.await;
@ -835,8 +850,8 @@ special_user!(SteeringUser, +"cox", +"Bootsführer");
special_user!(AdminUser, +"admin");
special_user!(AllowedForPlannedTripsUser, +"Donau Linz", +"scheckbuch", +"Förderndes Mitglied");
special_user!(DonauLinzUser, +"Donau Linz", -"Unterstützend", -"Förderndes Mitglied"); // TODO:
// remove ->
// RegularUser
// remove ->
// RegularUser
special_user!(SchnupperBetreuerUser, +"schnupper-betreuer");
special_user!(VorstandUser, +"admin", +"Vorstand");
special_user!(EventUser, +"manage_events");
@ -950,21 +965,17 @@ mod test {
#[sqlx::test]
fn wrong_pw() {
let pool = testdb!();
assert!(
User::login(&pool, "admin".into(), "admi".into())
assert!(User::login(&pool, "admin".into(), "admi".into())
.await
.is_err()
);
.is_err());
}
#[sqlx::test]
fn wrong_username() {
let pool = testdb!();
assert!(
User::login(&pool, "admi".into(), "admin".into())
assert!(User::login(&pool, "admi".into(), "admin".into())
.await
.is_err()
);
.is_err());
}
#[sqlx::test]
@ -984,11 +995,9 @@ mod test {
let pool = testdb!();
let user = User::find_by_id(&pool, 1).await.unwrap();
assert!(
User::login(&pool, "admin".into(), "abc".into())
assert!(User::login(&pool, "admin".into(), "abc".into())
.await
.is_err()
);
.is_err());
user.update_pw(&pool, "abc".into()).await;

View File

@ -1,8 +1,7 @@
use super::{ManageUserUser, User};
use crate::{
NonEmptyString,
model::{activity::ActivityBuilder, mail::Mail, notification::Notification, role::Role},
special_user,
special_user, NonEmptyString,
};
use chrono::NaiveDate;
use rocket::{async_trait, fs::TempFile, tokio::io::AsyncReadExt};
@ -52,7 +51,7 @@ pub trait ClubMember {
ActivityBuilder::new(&format!(
"{created_by} hat Mitglied {user} mit der Rolle {role} angelegt."
))
.relevant_for_user(&user)
.user(&user)
.save(db)
.await;
@ -103,7 +102,7 @@ ASKÖ Ruderverein Donau Linz", self.name),
).await?;
ActivityBuilder::new(&format!("Willkommensmail für {self} wurde an {mail} verschickt (Handbuch, Signal-Gruppe, App-Info, Fingerprint, WLAN)."))
.relevant_for_user(self)
.user(self)
.save(db)
.await;

View File

@ -2,13 +2,12 @@ use super::foerdernd::FoerderndUser;
use super::regular::RegularUser;
use super::unterstuetzend::UnterstuetzendUser;
use super::{ManageUserUser, User};
use crate::NonEmptyString;
use crate::model::activity::ActivityBuilder;
use crate::model::role::Role;
use crate::NonEmptyString;
use crate::{
SCHECKBUCH,
model::{mail::Mail, notification::Notification},
special_user,
special_user, SCHECKBUCH,
};
use chrono::NaiveDate;
use rocket::async_trait;
@ -88,7 +87,7 @@ impl ScheckbuchUser {
ActivityBuilder::new(&format!(
"{changed_by} hat den Scheckbuch-User {self} auf ein reguläres Mitglied upgegraded! Die Steuerpersonen wurden via Notification informiert."
))
.relevant_for_user(&self)
.user(&self)
.save(db)
.await;
@ -144,7 +143,7 @@ impl ScheckbuchUser {
.await;
}
ActivityBuilder::new(&format!("{changed_by} hat den Scheckbuch-User {self} auf ein unterstützendes Mitglied upgegraded!"))
.relevant_for_user(&self)
.user(&self)
.save(db)
.await;
@ -200,7 +199,7 @@ impl ScheckbuchUser {
ActivityBuilder::new(&format!(
"{changed_by} hat den Scheckbuch-User {self} auf ein förderndes Mitglied upgegraded!"
))
.relevant_for_user(&self)
.user(&self)
.save(db)
.await;
@ -215,7 +214,7 @@ impl ScheckbuchUser {
ActivityBuilder::new(&format!(
"{self} hat eine Info-Mail bekommen (Erklärung Scheckbuch, Ruderapp) und alle Steuerberechtigten wurden informiert."
))
.relevant_for_user(self)
.user(self)
.save(db)
.await;
@ -295,7 +294,7 @@ ASKÖ Ruderverein Donau Linz", self.name, SCHECKBUCH/100),
user.notify(db, smtp_pw).await?;
ActivityBuilder::new(&format!("{created_by} hat Scheckbuch {user} angelegt."))
.relevant_for_user(&user)
.user(&user)
.save(db)
.await;

View File

@ -4,9 +4,9 @@ use super::scheckbuch::ScheckbuchUser;
use super::schnupperinterest::SchnupperInterestUser;
use super::unterstuetzend::UnterstuetzendUser;
use super::{ManageUserUser, User};
use crate::NonEmptyString;
use crate::model::activity::ActivityBuilder;
use crate::model::role::Role;
use crate::NonEmptyString;
use crate::{
model::{mail::Mail, notification::Notification},
special_user,
@ -101,7 +101,7 @@ impl SchnupperantUser {
ActivityBuilder::new(&format!(
"{changed_by} hat den Schnupperant {self} auf ein reguläres Mitglied upgegraded!"
))
.relevant_for_user(&self)
.user(&self)
.save(db)
.await;
@ -142,7 +142,7 @@ impl SchnupperantUser {
ActivityBuilder::new(&format!(
"{changed_by} hat dem ehemaligen Schnupperant {self} nun ein Scheckbuch gegeben"
))
.relevant_for_user(&self)
.user(&self)
.save(db)
.await;
@ -184,7 +184,7 @@ impl SchnupperantUser {
ActivityBuilder::new(&format!(
"{changed_by} hat dem eigentlichen Schnupperanten {self} wieder auf die 'Interessierten'-Liste zurückgegeben."
))
.relevant_for_user(&self)
.user(&self)
.save(db)
.await;
@ -253,7 +253,7 @@ impl SchnupperantUser {
ActivityBuilder::new(&format!(
"{changed_by} hat den Schnupperant {self} auf ein unterstützendes Mitglied upgegraded!"
))
.relevant_for_user(&self)
.user(&self)
.save(db)
.await;
@ -320,7 +320,7 @@ impl SchnupperantUser {
ActivityBuilder::new(&format!(
"{changed_by} hat den Schnupperant {self} auf ein förderndes Mitglied upgegraded!"
))
.relevant_for_user(&self)
.user(&self)
.save(db)
.await;
@ -335,7 +335,7 @@ impl SchnupperantUser {
ActivityBuilder::new(&format!(
"{self} hat eine Mail bekommen (Inhalt: wir freuen uns auf ihn + senden detailliertere Infos später zu) und die Schnupperbetreuer wurden via Notification informiert."
))
.relevant_for_user(self)
.user(self)
.save(db)
.await;
@ -423,7 +423,7 @@ ASKÖ Ruderverein Donau Linz",
ActivityBuilder::new(&format!(
"{created_by} hat {user} zur fixen Schnupperkurs-Anmeldung hinzugefügt."
))
.relevant_for_user(&user)
.user(&user)
.save(db)
.await;

View File

@ -1,9 +1,9 @@
use super::scheckbuch::ScheckbuchUser;
use super::schnupperant::SchnupperantUser;
use super::{ManageUserUser, User};
use crate::NonEmptyString;
use crate::model::activity::ActivityBuilder;
use crate::model::role::Role;
use crate::NonEmptyString;
use crate::{model::notification::Notification, special_user};
use rocket::async_trait;
use sqlx::SqlitePool;
@ -44,7 +44,7 @@ impl SchnupperInterestUser {
ActivityBuilder::new(&format!(
"Der Schnupperinteressierte {self} hat sich (ohne Schnupperkurs) doch gleich direkt für ein Scheckbuch entschieden. {changed_by} hat dieses eingerichtet."
))
.relevant_for_user(&self)
.user(&self)
.save(db)
.await;
@ -86,7 +86,7 @@ impl SchnupperInterestUser {
ActivityBuilder::new(&format!(
"Der Schnupperinteressierte {self} hat sich zum Schnupperkurs angemeldet."
))
.relevant_for_user(&self)
.user(&self)
.save(db)
.await;
@ -99,7 +99,7 @@ impl SchnupperInterestUser {
ActivityBuilder::new(&format!(
"Der Schnupperbetreuer hat eine Info via Notification bekommen, dass {self} Interesse an einen Schnupperkurs hat."
))
.relevant_for_user(self)
.user(self)
.save(db)
.await;
@ -153,7 +153,7 @@ impl SchnupperInterestUser {
ActivityBuilder::new(&format!(
"{created_by} hat Schnupper-Interessierten {user} angelegt."
))
.relevant_for_user(&user)
.user(&user)
.save(db)
.await;

View File

@ -1,8 +1,7 @@
use super::{ManageUserUser, User, regular::ClubMember};
use super::{regular::ClubMember, ManageUserUser, User};
use crate::{
NonEmptyString,
model::{activity::ActivityBuilder, mail::Mail, notification::Notification, role::Role},
special_user,
special_user, NonEmptyString,
};
use chrono::NaiveDate;
use rocket::{async_trait, fs::TempFile};
@ -45,7 +44,7 @@ ASKÖ Ruderverein Donau Linz", self.name),
ActivityBuilder::new(&format!(
"{self} hat eine Mail an {mail} bekommen, mit Infos dass er/sie nun ein unterstützendes Mitglied ist (Handbuch, WLAN)."
))
.relevant_for_user(self)
.user(self)
.save(db)
.await;

View File

@ -1,7 +1,6 @@
use std::net::IpAddr;
use rocket::{
Request, Route, State,
form::Form,
get,
http::{Cookie, CookieJar},
@ -10,8 +9,9 @@ use rocket::{
response::{Flash, Redirect},
routes,
time::{Duration, OffsetDateTime},
Request, Route, State,
};
use rocket_dyn_templates::{Template, context};
use rocket_dyn_templates::{context, Template};
use sqlx::SqlitePool;
use tera::Context;
@ -108,26 +108,50 @@ async fn index(
}
#[get("/show", rank = 3)]
async fn show(db: &State<SqlitePool>, user: DonauLinzUser) -> Template {
async fn show(
db: &State<SqlitePool>,
flash: Option<FlashMessage<'_>>,
user: DonauLinzUser,
) -> Template {
let logs = Logbook::completed(db).await;
let boats = Boat::all(db).await;
let users = User::all(db).await;
let logtypes = LogType::all(db).await;
Template::render(
"log.completed",
context!(logs, boats, users, logtypes, loggedin_user: &UserWithDetails::from_user(user.into_inner(), db).await),
)
let mut context = Context::new();
if let Some(msg) = flash {
context.insert("flash", &msg.into_inner());
}
context.insert("logs", &logs);
context.insert("boats", &boats);
context.insert("users", &users);
context.insert("logtypes", &logtypes);
context.insert(
"loggedin_user",
&UserWithDetails::from_user(user.into_inner(), db).await,
);
Template::render("log.completed", context.into_json())
}
#[get("/show?<year>", rank = 2)]
async fn show_for_year(db: &State<SqlitePool>, user: VorstandUser, year: i32) -> Template {
async fn show_for_year(
db: &State<SqlitePool>,
flash: Option<FlashMessage<'_>>,
user: VorstandUser,
year: i32,
) -> Template {
let logs = Logbook::completed_in_year(db, year).await;
Template::render(
"log.completed",
context!(logs, loggedin_user: &UserWithDetails::from_user(user.user, db).await),
)
let mut context = Context::new();
if let Some(msg) = flash {
context.insert("flash", &msg.into_inner());
}
context.insert("logs", &logs);
context.insert(
"loggedin_user",
&UserWithDetails::from_user(user.into_inner(), db).await,
);
Template::render("log.completed", context.into_json())
}
#[get("/show")]
@ -513,10 +537,7 @@ async fn delete(db: &State<SqlitePool>, logbook_id: i64, user: DonauLinzUser) ->
)
.await;
match logbook.delete(db, &user).await {
Ok(_) => Flash::success(
Redirect::to(redirect),
format!("Eintrag {} von {} gelöscht!", logbook_id, user.name),
),
Ok(_) => Flash::success(Redirect::to(redirect), "Erfolgreich gelöscht"),
Err(LogbookDeleteError::NotYourEntry) => Flash::error(
Redirect::to(redirect),
"Du hast nicht die Berechtigung, den Eintrag zu löschen!",
@ -585,7 +606,7 @@ mod test {
use sqlx::SqlitePool;
use crate::model::logbook::Logbook;
use crate::tera::{User, log::Boat};
use crate::tera::{log::Boat, User};
use crate::testdb;
#[sqlx::test]