2024-03-20 21:16:55 +01:00
use std ::ops ::{ Deref , DerefMut } ;
2023-04-04 15:16:21 +02:00
2023-04-03 17:32:41 +02:00
use argon2 ::{ password_hash ::SaltString , Argon2 , PasswordHasher } ;
2023-07-31 16:33:44 +02:00
use chrono ::{ Datelike , Local , NaiveDate } ;
2023-07-11 09:16:13 +02:00
use log ::info ;
2023-04-03 22:03:45 +02:00
use rocket ::{
async_trait ,
2024-03-20 21:16:55 +01:00
http ::{ Cookie , Status } ,
2023-04-03 22:03:45 +02:00
request ::{ self , FromRequest , Outcome } ,
2023-06-07 00:07:11 +02:00
time ::{ Duration , OffsetDateTime } ,
2024-03-20 20:59:41 +01:00
tokio ::io ::AsyncReadExt ,
2023-07-31 16:33:44 +02:00
Request ,
2023-04-03 22:03:45 +02:00
} ;
use serde ::{ Deserialize , Serialize } ;
2023-10-29 20:41:30 +01:00
use sqlx ::{ FromRow , Sqlite , SqlitePool , Transaction } ;
2023-04-03 16:11:26 +02:00
2024-05-15 14:41:18 +02:00
use super ::{
2024-09-11 23:52:16 +02:00
family ::Family , log ::Log , logbook ::Logbook , mail ::Mail , notification ::Notification , role ::Role ,
stat ::Stat , tripdetails ::TripDetails , Day ,
2024-05-15 14:41:18 +02:00
} ;
2024-09-03 21:35:43 +03:00
use crate ::{
tera ::admin ::user ::UserEditForm , AMOUNT_DAYS_TO_SHOW_TRIPS_AHEAD , BOAT_STORAGE ,
EINSCHREIBGEBUEHR , FAMILY_THREE_OR_MORE , FAMILY_TWO , FOERDERND , REGULAR , RENNRUDERBEITRAG ,
SCHECKBUCH , STUDENT_OR_PUPIL , UNTERSTUETZEND ,
} ;
2024-01-19 00:39:15 +01:00
2024-04-30 21:35:14 +02:00
#[ derive(FromRow, Serialize, Deserialize, Clone, Debug, Eq, Hash, PartialEq) ]
2023-04-03 16:11:26 +02:00
pub struct User {
2023-04-04 10:44:14 +02:00
pub id : i64 ,
pub name : String ,
2023-07-24 13:01:39 +02:00
pub pw : Option < String > ,
2023-11-02 12:15:10 +01:00
pub dob : Option < String > ,
pub weight : Option < String > ,
pub sex : Option < String > ,
2023-07-24 13:01:39 +02:00
pub deleted : bool ,
2023-07-31 16:33:44 +02:00
pub last_access : Option < chrono ::NaiveDateTime > ,
2023-12-30 21:21:30 +01:00
pub member_since_date : Option < String > ,
pub birthdate : Option < String > ,
pub mail : Option < String > ,
pub nickname : Option < String > ,
pub notes : Option < String > ,
pub phone : Option < String > ,
pub address : Option < String > ,
2024-01-18 16:37:54 +01:00
pub family_id : Option < i64 > ,
2024-09-10 23:25:26 +02:00
pub user_token : String ,
2024-03-20 23:48:42 +01:00
}
2023-12-23 21:27:52 +01:00
#[ derive(Debug, Serialize, Deserialize) ]
2024-05-06 12:17:03 +02:00
pub struct UserWithDetails {
2023-12-23 21:27:52 +01:00
#[ serde(flatten) ]
pub user : User ,
2024-04-17 13:51:47 +02:00
pub amount_unread_notifications : i32 ,
2024-10-25 18:29:50 +02:00
pub allowed_to_steer : bool ,
2023-09-23 22:13:48 +02:00
pub on_water : bool ,
2023-12-23 21:27:52 +01:00
pub roles : Vec < String > ,
2023-09-23 22:13:48 +02:00
}
2024-05-06 12:17:03 +02:00
impl UserWithDetails {
2023-09-23 22:13:48 +02:00
pub async fn from_user ( user : User , db : & SqlitePool ) -> Self {
2024-10-25 18:55:08 +02:00
let allowed_to_steer = user . allowed_to_steer ( db ) . await ;
2023-09-23 22:13:48 +02:00
Self {
on_water : user . on_water ( db ) . await ,
2023-12-23 21:27:52 +01:00
roles : user . roles ( db ) . await ,
2024-05-06 12:17:03 +02:00
amount_unread_notifications : user . amount_unread_notifications ( db ) . await ,
2024-10-25 18:29:50 +02:00
allowed_to_steer ,
2023-09-23 22:13:48 +02:00
user ,
}
}
}
2023-04-03 17:21:34 +02:00
#[ derive(Debug) ]
2023-04-03 16:11:26 +02:00
pub enum LoginError {
InvalidAuthenticationCombo ,
2023-07-27 22:16:12 +02:00
UserNotFound ,
2023-07-28 11:50:11 +02:00
UserDeleted ,
2023-04-03 22:03:45 +02:00
NotLoggedIn ,
2023-04-04 10:44:14 +02:00
NotAnAdmin ,
2023-04-04 15:16:21 +02:00
NotACox ,
2023-08-02 14:29:19 +02:00
NotATech ,
2023-10-24 10:16:26 +02:00
GuestNotAllowed ,
2024-05-22 00:13:23 +02:00
NoPasswordSet ( Box < User > ) ,
2023-05-03 15:59:28 +02:00
DeserializationError ,
2023-04-03 16:11:26 +02:00
}
2024-01-19 00:39:15 +01:00
#[ derive(Debug, Serialize) ]
2024-01-22 22:08:05 +01:00
pub struct Fee {
pub sum_in_cents : i32 ,
pub parts : Vec < ( String , i32 ) > ,
pub name : String ,
pub user_ids : String ,
pub paid : bool ,
2024-03-15 11:41:03 +01:00
pub users : Vec < User > ,
2024-01-19 00:39:15 +01:00
}
2024-03-06 13:27:03 +01:00
impl Default for Fee {
fn default ( ) -> Self {
Self ::new ( )
}
}
2024-01-19 00:39:15 +01:00
impl Fee {
pub fn new ( ) -> Self {
Self {
sum_in_cents : 0 ,
name : " " . into ( ) ,
parts : Vec ::new ( ) ,
2024-01-22 19:05:18 +01:00
user_ids : " " . into ( ) ,
2024-03-15 11:41:03 +01:00
users : Vec ::new ( ) ,
2024-01-22 19:05:18 +01:00
paid : false ,
2024-01-19 00:39:15 +01:00
}
}
pub fn add ( & mut self , desc : String , price_in_cents : i32 ) {
self . sum_in_cents + = price_in_cents ;
self . parts . push ( ( desc , price_in_cents ) ) ;
}
2024-01-22 19:05:18 +01:00
pub fn add_person ( & mut self , user : & User ) {
if ! self . name . is_empty ( ) {
self . name . push_str ( " + " ) ;
2024-02-21 14:46:17 +01:00
self . user_ids . push ( '&' ) ;
2024-01-22 19:05:18 +01:00
}
self . name . push_str ( & user . name ) ;
self . user_ids . push_str ( & format! ( " user_ids[]= {} " , user . id ) ) ;
2024-03-15 11:41:03 +01:00
self . users . push ( user . clone ( ) ) ;
2024-01-22 19:05:18 +01:00
}
pub fn paid ( & mut self ) {
self . paid = true ;
2024-01-19 00:39:15 +01:00
}
pub fn merge ( & mut self , fee : Fee ) {
for ( desc , price_in_cents ) in fee . parts {
self . add ( desc , price_in_cents ) ;
}
}
}
2023-04-03 16:11:26 +02:00
impl User {
2024-10-25 18:55:08 +02:00
pub async fn allowed_to_steer ( & self , db : & SqlitePool ) -> bool {
self . has_role ( db , " cox " ) . await | | self . has_role ( db , " Bootsführer " ) . await
}
pub async fn allowed_to_steer_tx ( & self , db : & mut Transaction < '_ , Sqlite > ) -> bool {
self . has_role_tx ( db , " cox " ) . await | | self . has_role_tx ( db , " Bootsführer " ) . await
}
2024-05-15 14:41:18 +02:00
pub async fn send_welcome_email ( & self , db : & SqlitePool , smtp_pw : & str ) -> Result < ( ) , String > {
2024-05-15 16:01:01 +02:00
let Some ( mail ) = & self . mail else {
2024-05-15 14:41:18 +02:00
return Err ( format! (
2024-05-15 16:01:01 +02:00
" Could not send welcome mail, because user {} has no email address " ,
2024-05-15 14:41:18 +02:00
self . name
) ) ;
2024-05-15 16:01:01 +02:00
} ;
2024-05-15 14:41:18 +02:00
2024-05-15 16:01:01 +02:00
if self . has_role ( db , " Donau Linz " ) . await {
2024-05-22 08:30:38 +02:00
self . send_welcome_mail_full_member ( db , mail , smtp_pw )
. await ? ;
2024-05-15 16:01:01 +02:00
} else if self . has_role ( db , " scheckbuch " ) . await {
2024-05-22 08:30:38 +02:00
self . send_welcome_mail_scheckbuch ( db , mail , smtp_pw ) . await ? ;
} else if self . has_role ( db , " schnupperant " ) . await {
self . send_welcome_mail_schnupper ( db , mail , smtp_pw ) . await ? ;
2024-05-15 16:01:01 +02:00
} else {
2024-05-15 14:41:18 +02:00
return Err ( format! (
2024-05-22 08:30:38 +02:00
" Could not send welcome mail, because user {} is not in Donau Linz or scheckbuch or schnupperant group " ,
2024-05-15 14:41:18 +02:00
self . name
) ) ;
2024-05-15 16:01:01 +02:00
}
Log ::create (
db ,
format! ( " Willkommensemail wurde an {} versandt " , self . name ) ,
)
. await ;
2024-05-15 14:41:18 +02:00
2024-05-15 16:01:01 +02:00
Ok ( ( ) )
}
2024-05-22 08:30:38 +02:00
async fn send_welcome_mail_schnupper (
& self ,
db : & SqlitePool ,
mail : & str ,
smtp_pw : & str ,
) -> Result < ( ) , String > {
// 2 things to do:
// 1. Send mail to user
Mail ::send_single (
db ,
mail ,
" Schnupperrudern beim ASKÖ Ruderverein Donau Linz " ,
format! (
" Hallo {0},
2024-06-02 14:52:07 +02:00
es freut uns sehr , dich bei unserem Schnupperkurs willkommen heißen zu dürfen . Detaillierte Informationen folgen noch , ich werde sie dir ein paar Tage vor dem Termin zusenden .
2024-05-22 08:30:38 +02:00
Liebe Grüße , Philipp " , self.name),
smtp_pw ,
) . await ? ;
// 2. Notify all coxes
let coxes = Role ::find_by_name ( db , " schnupper-betreuer " ) . await . unwrap ( ) ;
Notification ::create_for_role (
db ,
& coxes ,
& format! (
" Liebe Schnupper-Betreuer, {} nimmt am Schnupperkurs teil. " ,
self . name
) ,
" Neue(r) Schnupperteilnehmer:in " ,
None ,
None ,
)
. await ;
Ok ( ( ) )
}
async fn send_welcome_mail_scheckbuch (
& self ,
db : & SqlitePool ,
mail : & str ,
smtp_pw : & str ,
) -> Result < ( ) , String > {
2024-05-15 16:01:01 +02:00
// 2 things to do:
// 1. Send mail to user
Mail ::send_single (
db ,
mail ,
" ASKÖ Ruderverein Donau Linz | Dein Scheckbuch wartet auf Dich " ,
format! (
" Hallo {0},
herzlich willkommen beim ASKÖ Ruderverein Donau Linz ! Wir freuen uns sehr , dass Du Dich entschieden hast , das Rudern bei uns auszuprobieren . Mit Deinem Scheckbuch kannst Du jetzt an fünf Ausfahrten teilnehmen und so diesen Sport in seiner vollen Vielfalt erleben . Falls du die { 1 } € noch nicht bezahlt hast , nimm diese bitte zur nächsten Ausfahrt mit ( oder ü berweise sie auf unser Bankkonto [ dieses findest du auf https ://rudernlinz.at]).
2024-10-28 16:13:37 +01:00
Für die Organisation unserer Ausfahrten nutzen wir rudi . rudernlinz . at . Logge Dich bitte mit Deinem Namen ( ' { 0 } ' , ohne Anführungszeichen ) ein . Beim ersten Mal kannst Du das Passwortfeld leer lassen . Unter ' Geplante Ausfahrten ' kannst Du Dich jederzeit für eine Ausfahrt anmelden . Wir bieten mindestens einmal pro Woche Ausfahrten an , sowohl für Anfänger als auch für Fortgeschrittene ( A + F Rudern ) . Zusätzliche Ausfahrten werden von unseren Steuerleuten ausgeschrieben , ö fters reinschauen kann sich also lohnen :- )
2024-05-15 16:01:01 +02:00
Nach deinen 5 Ausfahrten würden wir uns freuen , dich als Mitglied in unserem Verein begrüßen zu dürfen .
Wir freuen uns darauf , Dich bald am Wasser zu sehen und gemeinsam tolle Erfahrungen zu sammeln !
Riemen - & Dollenbruch ,
ASKÖ Ruderverein Donau Linz " , self.name, SCHECKBUCH/100),
smtp_pw ,
2024-05-22 08:30:38 +02:00
) . await ? ;
2024-05-15 16:01:01 +02:00
// 2. Notify all coxes
2024-10-25 18:55:08 +02:00
Notification ::create_for_steering_people (
2024-05-15 16:01:01 +02:00
db ,
& format! (
" Liebe Steuerberechtigte, {} hat nun ein Scheckbuch. Wie immer, freuen wir uns wenn du uns beim A+F Rudern unterstützt oder selber Ausfahrten ausschreibst. Bitte beachte, dass Scheckbuch-Personen nur Ausfahrten sehen, bei denen 'Scheckbuch-Anmeldungen erlauben' ausgewählt wurde. " ,
self . name
) ,
" Neues Scheckbuch " ,
2024-05-21 19:41:00 +02:00
None , None
2024-05-15 16:01:01 +02:00
)
. await ;
2024-05-22 08:30:38 +02:00
Ok ( ( ) )
2024-05-15 16:01:01 +02:00
}
2024-09-11 23:52:16 +02:00
async fn send_end_mail_scheckbuch (
& self ,
db : & mut Transaction < '_ , Sqlite > ,
mail : & str ,
smtp_pw : & str ,
) -> Result < ( ) , String > {
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 ? ;
Ok ( ( ) )
}
2024-05-22 08:30:38 +02:00
async fn send_welcome_mail_full_member (
& self ,
db : & SqlitePool ,
mail : & str ,
smtp_pw : & str ,
) -> Result < ( ) , String > {
2024-05-15 16:01:01 +02:00
// 2 things to do:
// 1. Send mail to user
2024-05-15 14:41:18 +02:00
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 .
2024-06-10 22:17:41 +02:00
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 (für allgemeine Fragen) und it@rudernlinz.at (bei technischen Fragen) jederzeit zur Verfügung.
2024-05-15 14:41:18 +02:00
Du kannst auch gerne unserer Signal - Gruppe beitreten , um auf dem Laufenden zu bleiben und dich mit anderen Mitgliedern auszutauschen : https ://signal.group/#CjQKICFrq6zSsRHxrucS3jEcQn6lknEXacAykwwLV3vNLKxPEhA17jxz7cpjfu3JZokLq1TH
2024-10-28 16:13:37 +01:00
Für die Organisation unserer Ausfahrten nutzen wir rudi . 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 .
2024-05-15 14:41:18 +02:00
2024-07-30 23:28:52 +02:00
Beim nächsten Treffen im Verein , erinnere jemand vom Vorstand ( https ://rudernlinz.at/unser-verein/vorstand/) bitte daran, deinen Fingerabdruck zu registrieren, damit du Zugang zum Bootshaus erhältst.
2024-05-15 14:41:18 +02:00
2024-06-10 22:23:26 +02:00
Damit du dich noch mehr verbunden fühlst ( :- ) ) , haben wir im Bootshaus ein WLAN für Vereinsmitglieder ' ASKÖ Ruderverein Donau Linz ' eingerichtet . Das Passwort dafür lautet ' donau1921 ' ( ohne Anführungszeichen ) . Bitte gib das Passwort an keine vereinsfremden Personen weiter .
2024-06-10 22:07:14 +02:00
2024-05-15 14:41:18 +02:00
Wir freuen uns darauf , dich bald am Wasser zu sehen und gemeinsam tolle Erfahrungen zu sammeln !
Riemen - & Dollenbruch
2024-05-15 16:01:01 +02:00
ASKÖ Ruderverein Donau Linz " , self.name),
2024-05-15 14:41:18 +02:00
smtp_pw ,
2024-05-22 08:30:38 +02:00
) . await ? ;
2024-05-15 14:41:18 +02:00
2024-05-15 16:01:01 +02:00
// 2. Notify all coxes
2024-10-25 18:55:08 +02:00
Notification ::create_for_steering_people (
2024-05-15 14:41:18 +02:00
db ,
& format! (
" Liebe Steuerberechtigte, seit {} gibt es ein neues Mitglied: {} " ,
self . member_since_date . clone ( ) . unwrap ( ) ,
self . name
) ,
" Neues Vereinsmitglied " ,
None ,
2024-05-21 19:41:00 +02:00
None ,
2024-05-15 14:41:18 +02:00
)
. await ;
2024-05-22 08:30:38 +02:00
Ok ( ( ) )
2024-05-15 14:41:18 +02:00
}
2024-01-19 00:39:15 +01:00
pub async fn fee ( & self , db : & SqlitePool ) -> Option < Fee > {
if ! self . has_role ( db , " Donau Linz " ) . await {
return None ;
}
2024-02-16 13:13:57 +01:00
if self . deleted {
return None ;
}
2024-01-19 00:39:15 +01:00
let mut fee = Fee ::new ( ) ;
if let Some ( family ) = Family ::find_by_opt_id ( db , self . family_id ) . await {
for member in family . members ( db ) . await {
2024-01-22 19:05:18 +01:00
fee . add_person ( & member ) ;
if member . has_role ( db , " paid " ) . await {
fee . paid ( ) ;
2024-01-19 08:02:09 +01:00
}
2024-01-19 00:39:15 +01:00
fee . merge ( member . fee_without_families ( db ) . await ) ;
}
if family . amount_family_members ( db ) . await > 2 {
fee . add ( " Familie 3+ Personen " . into ( ) , FAMILY_THREE_OR_MORE ) ;
} else {
fee . add ( " Familie 2 Personen " . into ( ) , FAMILY_TWO ) ;
}
} else {
2024-02-21 14:46:17 +01:00
fee . add_person ( self ) ;
2024-01-22 19:05:18 +01:00
if self . has_role ( db , " paid " ) . await {
fee . paid ( ) ;
}
2024-01-19 00:39:15 +01:00
fee . merge ( self . fee_without_families ( db ) . await ) ;
}
Some ( fee )
}
async fn fee_without_families ( & self , db : & SqlitePool ) -> Fee {
let mut fee = Fee ::new ( ) ;
if ! self . has_role ( db , " Donau Linz " ) . await {
return fee ;
}
if self . has_role ( db , " Rennrudern " ) . await {
2024-10-25 18:31:43 +02:00
if self . has_role ( db , " half-rennrudern " ) . await {
fee . add ( " Rennruderbeitrag (1/2 Preis) " . into ( ) , RENNRUDERBEITRAG / 2 ) ;
} else {
fee . add ( " Rennruderbeitrag " . into ( ) , RENNRUDERBEITRAG ) ;
}
2024-01-19 00:39:15 +01:00
}
let amount_boats = self . amount_boats ( db ) . await ;
if amount_boats > 0 {
fee . add (
format! ( " {} x Bootsplatz " , amount_boats ) ,
amount_boats * BOAT_STORAGE ,
) ;
}
2024-08-21 16:14:54 +02:00
if let Some ( member_since_date ) = & self . member_since_date {
if let Ok ( member_since_date ) = NaiveDate ::parse_from_str ( member_since_date , " %Y-%m-%d " )
{
if member_since_date . year ( ) = = Local ::now ( ) . year ( )
& & ! self . has_role ( db , " no-einschreibgebuehr " ) . await
{
fee . add ( " Einschreibgebühr " . into ( ) , EINSCHREIBGEBUEHR ) ;
}
}
}
2024-01-19 00:39:15 +01:00
2024-07-22 21:35:07 +02:00
let halfprice = if let Some ( member_since_date ) = & self . member_since_date {
2024-07-22 21:56:47 +02:00
if let Ok ( member_since_date ) = NaiveDate ::parse_from_str ( member_since_date , " %Y-%m-%d " )
{
let halfprice_startdate =
NaiveDate ::from_ymd_opt ( Local ::now ( ) . year ( ) , 7 , 1 ) . unwrap ( ) ;
member_since_date > = halfprice_startdate
} else {
false
}
2024-07-22 21:35:07 +02:00
} else {
false
} ;
2024-01-19 00:44:53 +01:00
if self . has_role ( db , " Unterstützend " ) . await {
fee . add ( " Unterstützendes Mitglied " . into ( ) , UNTERSTUETZEND ) ;
} else if self . has_role ( db , " Förderndes Mitglied " ) . await {
fee . add ( " Förderndes Mitglied " . into ( ) , FOERDERND ) ;
} else if Family ::find_by_opt_id ( db , self . family_id ) . await . is_none ( ) {
2024-01-19 00:39:15 +01:00
if self . has_role ( db , " Student " ) . await | | self . has_role ( db , " Schüler " ) . await {
2024-07-22 21:35:07 +02:00
if halfprice {
fee . add ( " Schüler/Student (Halbpreis) " . into ( ) , STUDENT_OR_PUPIL / 2 ) ;
} else {
fee . add ( " Schüler/Student " . into ( ) , STUDENT_OR_PUPIL ) ;
}
2024-03-08 13:57:45 +01:00
} else if self . has_role ( db , " Ehrenmitglied " ) . await {
fee . add ( " Ehrenmitglied " . into ( ) , 0 ) ;
2024-01-19 00:39:15 +01:00
} else {
2024-07-22 21:35:07 +02:00
if halfprice {
fee . add ( " Mitgliedsbeitrag (Halbpreis) " . into ( ) , REGULAR / 2 ) ;
} else {
fee . add ( " Mitgliedsbeitrag " . into ( ) , REGULAR ) ;
}
2024-01-19 00:39:15 +01:00
}
}
fee
}
pub async fn amount_boats ( & self , db : & SqlitePool ) -> i32 {
sqlx ::query! (
" SELECT COUNT(*) as count FROM boat WHERE owner = ? " ,
self . id
)
. fetch_one ( db )
. await
. unwrap ( )
. count
}
2024-04-17 13:51:47 +02:00
pub async fn amount_unread_notifications ( & self , db : & SqlitePool ) -> i32 {
sqlx ::query! (
" SELECT COUNT(*) as count FROM notification WHERE user_id = ? AND read_at IS NULL " ,
self . id
)
. fetch_one ( db )
. await
. unwrap ( )
. count
}
2023-12-23 21:27:52 +01:00
pub async fn has_role ( & self , db : & SqlitePool , role : & str ) -> bool {
if sqlx ::query! (
" SELECT * FROM user_role WHERE user_id=? AND role_id = (SELECT id FROM role WHERE name = ?) " ,
self . id ,
role
)
. fetch_optional ( db )
. await
. unwrap ( )
. is_some ( )
{
return true ;
}
false
}
2024-09-02 12:18:23 +03:00
pub async fn allowed_to_update_always_show_trip ( & self , db : & SqlitePool ) -> bool {
AllowedToUpdateTripToAlwaysBeShownUser ::new ( db , self . clone ( ) )
. await
. is_some ( )
}
2024-05-06 13:35:42 +02:00
pub async fn has_membership_pdf ( & self , db : & SqlitePool ) -> bool {
match sqlx ::query_scalar! ( " SELECT membership_pdf FROM user WHERE id = ? " , self . id )
. fetch_one ( db )
. await
. unwrap ( )
{
Some ( a ) if a . is_empty ( ) = > false ,
None = > false ,
_ = > true ,
}
}
2024-10-11 12:39:23 +02:00
pub async fn has_membership_pdf_tx ( & self , db : & mut Transaction < '_ , Sqlite > ) -> bool {
match sqlx ::query_scalar! ( " SELECT membership_pdf FROM user WHERE id = ? " , self . id )
. fetch_one ( db . deref_mut ( ) )
. await
. unwrap ( )
{
Some ( a ) if a . is_empty ( ) = > false ,
None = > false ,
_ = > true ,
}
}
2024-05-06 13:35:42 +02:00
2023-12-23 21:27:52 +01:00
pub async fn roles ( & self , db : & SqlitePool ) -> Vec < String > {
sqlx ::query! (
2024-03-04 16:59:44 +01:00
" SELECT r.name FROM role r JOIN user_role ur ON r.id = ur.role_id JOIN user u ON u.id = ur.user_id WHERE ur.user_id = ? AND u.deleted = 0; " ,
2023-12-23 21:27:52 +01:00
self . id
)
. fetch_all ( db )
. await
. unwrap ( )
. into_iter ( ) . map ( | r | r . name ) . collect ( )
}
2024-10-11 12:39:23 +02:00
pub async fn real_roles ( & self , db : & SqlitePool ) -> Vec < Role > {
sqlx ::query_as! (
Role ,
" SELECT r.id, r.name, r.cluster
FROM role r
JOIN user_role ur ON r . id = ur . role_id
JOIN user u ON u . id = ur . user_id
WHERE ur . user_id = ? AND u . deleted = 0 ; " ,
self . id
)
. fetch_all ( db )
. await
. unwrap ( )
}
2023-12-23 21:27:52 +01:00
pub async fn has_role_tx ( & self , db : & mut Transaction < '_ , Sqlite > , role : & str ) -> bool {
if sqlx ::query! (
" SELECT * FROM user_role WHERE user_id=? AND role_id = (SELECT id FROM role WHERE name = ?) " ,
self . id ,
role
)
. fetch_optional ( db . deref_mut ( ) )
. await
. unwrap ( )
. is_some ( )
{
return true ;
}
false
}
2023-04-10 14:25:31 +02:00
pub async fn find_by_id ( db : & SqlitePool , id : i32 ) -> Option < Self > {
2023-07-31 16:33:44 +02:00
sqlx ::query_as! (
Self ,
2023-04-24 14:34:06 +02:00
"
2024-09-10 23:25:26 +02:00
SELECT id , name , pw , deleted , last_access , dob , weight , sex , member_since_date , birthdate , mail , nickname , notes , phone , address , family_id , user_token
2023-04-04 10:44:14 +02:00
FROM user
WHERE id like ?
" ,
2023-04-24 14:34:06 +02:00
id
2023-04-04 10:44:14 +02:00
)
2023-04-24 14:34:06 +02:00
. fetch_one ( db )
. await
2023-07-31 16:33:44 +02:00
. ok ( )
2023-04-04 10:44:14 +02:00
}
2023-10-29 20:41:30 +01:00
pub async fn find_by_id_tx ( db : & mut Transaction < '_ , Sqlite > , id : i32 ) -> Option < Self > {
sqlx ::query_as! (
Self ,
"
2024-09-10 23:25:26 +02:00
SELECT id , name , pw , deleted , last_access , dob , weight , sex , member_since_date , birthdate , mail , nickname , notes , phone , address , family_id , user_token
2023-10-29 20:41:30 +01:00
FROM user
WHERE id like ?
" ,
id
)
2023-11-22 13:19:31 +01:00
. fetch_one ( db . deref_mut ( ) )
2023-10-29 20:41:30 +01:00
. await
. ok ( )
}
2023-05-24 12:11:55 +02:00
pub async fn find_by_name ( db : & SqlitePool , name : & str ) -> Option < Self > {
2024-05-28 15:04:06 +02:00
let name = name . trim ( ) . to_lowercase ( ) ;
2023-07-31 16:33:44 +02:00
sqlx ::query_as! (
Self ,
2023-04-24 14:34:06 +02:00
"
2024-09-10 23:25:26 +02:00
SELECT id , name , pw , deleted , last_access , dob , weight , sex , member_since_date , birthdate , mail , nickname , notes , phone , address , family_id , user_token
2023-04-03 16:11:26 +02:00
FROM user
2024-05-27 08:32:00 +02:00
WHERE lower ( name ) = ?
2023-04-03 16:11:26 +02:00
" ,
2023-04-24 14:34:06 +02:00
name
2023-04-03 16:11:26 +02:00
)
2023-04-24 14:34:06 +02:00
. fetch_one ( db )
. await
2023-07-31 16:33:44 +02:00
. ok ( )
2023-04-03 16:11:26 +02:00
}
2023-07-27 15:00:52 +02:00
pub async fn on_water ( & self , db : & SqlitePool ) -> bool {
2023-07-30 22:59:47 +02:00
if sqlx ::query! (
2023-07-27 15:00:52 +02:00
" SELECT * FROM logbook WHERE shipmaster=? AND arrival is null " ,
self . id
)
. fetch_optional ( db )
. await
. unwrap ( )
. is_some ( )
2023-07-30 22:59:47 +02:00
{
return true ;
}
if sqlx ::query! (
" SELECT * FROM logbook JOIN rower ON rower.logbook_id=logbook.id WHERE rower_id=? AND arrival is null " ,
self . id
)
. fetch_optional ( db )
. await
. unwrap ( )
. is_some ( )
{
return true ;
}
false
2023-07-27 15:00:52 +02:00
}
2023-04-26 11:22:22 +02:00
pub async fn all ( db : & SqlitePool ) -> Vec < Self > {
2023-07-31 16:33:44 +02:00
sqlx ::query_as! (
Self ,
2023-04-26 11:22:22 +02:00
"
2024-09-10 23:25:26 +02:00
SELECT id , name , pw , deleted , last_access , dob , weight , sex , member_since_date , birthdate , mail , nickname , notes , phone , address , family_id , user_token
2023-04-26 11:22:22 +02:00
FROM user
2023-04-28 19:29:20 +02:00
WHERE deleted = 0
2023-06-06 10:08:54 +02:00
ORDER BY last_access DESC
2023-04-26 11:22:22 +02:00
"
)
. fetch_all ( db )
. await
2023-07-31 16:25:07 +02:00
. unwrap ( )
2023-04-26 11:22:22 +02:00
}
2024-03-04 23:11:44 +01:00
pub async fn all_with_role ( db : & SqlitePool , role : & Role ) -> Vec < Self > {
2024-04-15 22:03:20 +02:00
let mut tx = db . begin ( ) . await . unwrap ( ) ;
let ret = Self ::all_with_role_tx ( & mut tx , role ) . await ;
tx . commit ( ) . await . unwrap ( ) ;
ret
}
pub async fn all_with_role_tx ( db : & mut Transaction < '_ , Sqlite > , role : & Role ) -> Vec < Self > {
2024-03-04 23:11:44 +01:00
sqlx ::query_as! (
Self ,
"
2024-09-10 23:25:26 +02:00
SELECT id , name , pw , deleted , last_access , dob , weight , sex , member_since_date , birthdate , mail , nickname , notes , phone , address , family_id , user_token
2024-03-04 23:11:44 +01:00
FROM user u
JOIN user_role ur ON u . id = ur . user_id
WHERE ur . role_id = ? AND deleted = 0
ORDER BY name ;
" , role.id
)
2024-04-15 22:03:20 +02:00
. fetch_all ( db . deref_mut ( ) )
2024-03-04 23:11:44 +01:00
. await
. unwrap ( )
}
2024-01-19 08:02:09 +01:00
pub async fn all_payer_groups ( db : & SqlitePool ) -> Vec < Self > {
sqlx ::query_as! (
Self ,
"
2024-09-10 23:25:26 +02:00
SELECT id , name , pw , deleted , last_access , dob , weight , sex , member_since_date , birthdate , mail , nickname , notes , phone , address , family_id , user_token FROM user
2024-01-19 08:02:09 +01:00
WHERE family_id IS NOT NULL
GROUP BY family_id
UNION
- - Select users with a null family_id , without grouping
2024-09-10 23:25:26 +02:00
SELECT id , name , pw , deleted , last_access , dob , weight , sex , member_since_date , birthdate , mail , nickname , notes , phone , address , family_id , user_token FROM user
2024-01-19 08:02:09 +01:00
WHERE family_id IS NULL ;
"
)
. fetch_all ( db )
. await
. unwrap ( )
}
2023-11-02 12:15:10 +01:00
pub async fn ergo ( db : & SqlitePool ) -> Vec < Self > {
2024-10-19 22:02:44 +02:00
let ergo = Role ::find_by_name ( db , " ergo " ) . await . unwrap ( ) ;
Self ::all_with_role ( db , & ergo ) . await
2023-11-02 12:15:10 +01:00
}
2023-07-23 12:17:57 +02:00
pub async fn cox ( db : & SqlitePool ) -> Vec < Self > {
2023-07-31 16:33:44 +02:00
sqlx ::query_as! (
Self ,
2023-07-23 12:17:57 +02:00
"
2024-09-10 23:25:26 +02:00
SELECT id , name , pw , deleted , last_access , dob , weight , sex , member_since_date , birthdate , mail , nickname , notes , phone , address , family_id , user_token
2023-07-23 12:17:57 +02:00
FROM user
2023-12-23 21:27:52 +01:00
WHERE deleted = 0 AND ( SELECT COUNT ( * ) FROM user_role WHERE user_id = user . id AND role_id = ( SELECT id FROM role WHERE name = ' cox ' ) ) > 0
2023-07-23 12:17:57 +02:00
ORDER BY last_access DESC
"
)
. fetch_all ( db )
. await
2023-07-31 16:25:07 +02:00
. unwrap ( )
2023-07-23 12:17:57 +02:00
}
2023-12-23 21:27:52 +01:00
pub async fn create ( db : & SqlitePool , name : & str ) -> bool {
2024-05-04 18:19:07 +02:00
let name = name . trim ( ) ;
2023-12-23 21:27:52 +01:00
sqlx ::query! ( " INSERT INTO USER(name) VALUES (?) " , name )
. execute ( db )
. await
. is_ok ( )
2023-04-26 11:22:22 +02:00
}
2024-08-19 11:27:10 +02:00
pub async fn create_with_mail ( db : & SqlitePool , name : & str , mail : & str ) -> bool {
let name = name . trim ( ) ;
sqlx ::query! ( " INSERT INTO USER(name, mail) VALUES (?, ?) " , name , mail )
. execute ( db )
. await
. is_ok ( )
}
2024-10-19 22:02:44 +02:00
pub async fn update_ergo ( & self , db : & SqlitePool , dob : i32 , weight : i64 , sex : & str ) {
sqlx ::query! (
" UPDATE user SET dob = ?, weight = ?, sex = ? where id = ? " ,
dob ,
weight ,
sex ,
self . id
)
. execute ( db )
. await
. unwrap ( ) ; //Okay, because we can only create a User of a valid id
}
2024-10-11 12:39:23 +02:00
pub async fn update ( & self , db : & SqlitePool , data : UserEditForm < '_ > ) -> Result < ( ) , String > {
let mut db = db . begin ( ) . await . map_err ( | e | e . to_string ( ) ) ? ;
2024-01-18 16:37:54 +01:00
let mut family_id = data . family_id ;
if family_id . is_some_and ( | x | x = = - 1 ) {
2024-10-11 12:39:23 +02:00
family_id = Some ( Family ::insert_tx ( & mut db ) . await )
2024-01-18 16:37:54 +01:00
}
2024-10-11 12:39:23 +02:00
if ! self . has_membership_pdf_tx ( & mut db ) . await {
2024-03-20 20:59:41 +01:00
if let Some ( membership_pdf ) = data . membership_pdf {
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
)
2024-10-11 12:39:23 +02:00
. execute ( db . deref_mut ( ) )
2024-03-20 20:59:41 +01:00
. await
. unwrap ( ) ; //Okay, because we can only create a User of a valid id
}
}
2023-04-26 11:22:22 +02:00
sqlx ::query! (
2024-01-18 16:37:54 +01:00
" UPDATE user SET dob = ?, weight = ?, sex = ?, member_since_date=?, birthdate=?, mail=?, nickname=?, notes=?, phone=?, address=?, family_id = ? where id = ? " ,
2023-11-02 12:15:10 +01:00
data . dob ,
data . weight ,
data . sex ,
2023-12-30 21:21:30 +01:00
data . member_since_date ,
data . birthdate ,
data . mail ,
data . nickname ,
data . notes ,
data . phone ,
data . address ,
2024-01-18 16:37:54 +01:00
family_id ,
2023-04-26 11:22:22 +02:00
self . id
)
2024-10-11 12:39:23 +02:00
. execute ( db . deref_mut ( ) )
2023-04-26 11:22:22 +02:00
. await
. unwrap ( ) ; //Okay, because we can only create a User of a valid id
2023-12-23 21:27:52 +01:00
// handle roles
sqlx ::query! ( " DELETE FROM user_role WHERE user_id = ? " , self . id )
2024-10-11 12:39:23 +02:00
. execute ( db . deref_mut ( ) )
2023-12-23 21:27:52 +01:00
. await
. unwrap ( ) ;
for role_id in data . roles . into_keys ( ) {
2024-10-11 12:39:23 +02:00
let role = Role ::find_by_id_tx ( & mut db , role_id . parse ::< i32 > ( ) . unwrap ( ) )
. await
. unwrap ( ) ;
self . add_role_tx ( & mut db , & role ) . await ? ;
2023-12-23 21:27:52 +01:00
}
2024-10-11 12:39:23 +02:00
db . commit ( ) . await . map_err ( | e | e . to_string ( ) ) ? ;
Ok ( ( ) )
2023-04-04 10:44:14 +02:00
}
2024-10-11 12:39:23 +02:00
pub async fn add_role ( & self , db : & SqlitePool , role : & Role ) -> Result < ( ) , String > {
2024-01-22 19:05:18 +01:00
sqlx ::query! (
" INSERT INTO user_role(user_id, role_id) VALUES (?, ?) " ,
self . id ,
role . id
)
. execute ( db )
. await
2024-10-11 12:39:23 +02:00
. map_err ( | _ | {
format! (
" User already has a role in the cluster '{}' " ,
role . cluster
. clone ( )
. expect ( " db trigger can't activate on empty string " )
)
} ) ? ;
Ok ( ( ) )
}
pub async fn add_role_tx (
& self ,
db : & mut Transaction < '_ , Sqlite > ,
role : & Role ,
) -> Result < ( ) , String > {
sqlx ::query! (
" INSERT INTO user_role(user_id, role_id) VALUES (?, ?) " ,
self . id ,
role . id
)
. execute ( db . deref_mut ( ) )
. await
. map_err ( | _ | {
format! (
" User already has a role in the cluster '{}' " ,
role . cluster
. clone ( )
. expect ( " db trigger can't activate on empty string " )
)
} ) ? ;
Ok ( ( ) )
2024-01-22 19:05:18 +01:00
}
pub async fn remove_role ( & self , db : & SqlitePool , role : & Role ) {
sqlx ::query! (
" DELETE FROM user_role WHERE user_id = ? and role_id = ? " ,
self . id ,
role . id
)
. execute ( db )
. await
. unwrap ( ) ;
}
2023-05-24 12:11:55 +02:00
pub async fn login ( db : & SqlitePool , name : & str , pw : & str ) -> Result < Self , LoginError > {
2024-05-27 08:32:00 +02:00
let name = name . trim ( ) . to_lowercase ( ) ; // just to make sure...
let Some ( user ) = User ::find_by_name ( db , & name ) . await else {
2024-01-24 13:09:12 +01:00
if ! [
" n-sageder " ,
" p-hofer " ,
2024-07-29 14:26:26 +02:00
" marie-birner " ,
2024-03-20 00:48:27 +01:00
" daniel-kortschak " ,
2024-03-20 00:11:11 +01:00
" rudernlinz " ,
2024-01-24 13:09:12 +01:00
" m-birner " ,
" s-sollberger " ,
" d-kortschak " ,
" wwwadmin " ,
" wadminw " ,
" admin " ,
2024-01-31 10:23:33 +01:00
" m sageder " ,
" d kortschak " ,
" a almousa " ,
" p hofer " ,
" s sollberger " ,
" n sageder " ,
" wp-system " ,
" s.sollberger " ,
" m.birner " ,
2024-02-07 12:36:27 +01:00
" m-sageder " ,
" a-almousa " ,
2024-03-05 13:45:19 +01:00
" m.sageder " ,
2024-03-08 10:16:36 +01:00
" n.sageder " ,
2024-03-05 13:45:19 +01:00
" a.almousa " ,
" p.hofer " ,
2024-05-22 23:41:24 +02:00
" philipp-hofer " ,
2024-03-05 13:45:19 +01:00
" d.kortschak " ,
" [login] " ,
2024-01-24 13:09:12 +01:00
]
2024-05-27 08:32:00 +02:00
. contains ( & name . as_str ( ) )
2024-01-24 13:09:12 +01:00
{
Log ::create ( db , format! ( " Username ( {name} ) not found (tried to login) " ) ) . await ;
}
2023-04-26 12:52:19 +02:00
return Err ( LoginError ::InvalidAuthenticationCombo ) ; // Username not found
2023-04-10 14:25:31 +02:00
} ;
2023-04-03 16:11:26 +02:00
2023-04-28 19:29:20 +02:00
if user . deleted {
2023-07-28 11:50:11 +02:00
Log ::create (
db ,
format! ( " User ( {name} ) already deleted (tried to login). " ) ,
)
. await ;
2023-04-28 19:29:20 +02:00
return Err ( LoginError ::InvalidAuthenticationCombo ) ; //User existed sometime ago; has
//been deleted
}
2023-07-25 13:32:20 +02:00
if let Some ( user_pw ) = user . pw . as_ref ( ) {
let password_hash = & Self ::get_hashed_pw ( pw ) ;
if password_hash = = user_pw {
return Ok ( user ) ;
2023-07-11 09:16:13 +02:00
}
2023-07-25 13:32:20 +02:00
Log ::create ( db , format! ( " User {name} supplied the wrong PW " ) ) . await ;
Err ( LoginError ::InvalidAuthenticationCombo )
} else {
info! ( " User {name} has no PW set " ) ;
2024-05-22 00:13:23 +02:00
Err ( LoginError ::NoPasswordSet ( Box ::new ( user ) ) )
2023-04-03 16:11:26 +02:00
}
2023-04-04 10:44:14 +02:00
}
2023-04-03 16:11:26 +02:00
2023-04-04 10:44:14 +02:00
pub async fn reset_pw ( & self , db : & SqlitePool ) {
sqlx ::query! ( " UPDATE user SET pw = null where id = ? " , self . id )
. execute ( db )
. await
2023-04-26 11:22:22 +02:00
. unwrap ( ) ; //Okay, because we can only create a User of a valid id
2023-04-04 10:44:14 +02:00
}
2023-05-24 12:11:55 +02:00
pub async fn update_pw ( & self , db : & SqlitePool , pw : & str ) {
2023-05-30 14:36:23 +02:00
let pw = Self ::get_hashed_pw ( pw ) ;
2023-04-04 10:44:14 +02:00
sqlx ::query! ( " UPDATE user SET pw = ? where id = ? " , pw , self . id )
. execute ( db )
. await
2023-04-26 11:22:22 +02:00
. unwrap ( ) ; //Okay, because we can only create a User of a valid id
}
fn get_hashed_pw ( pw : & str ) -> String {
let salt = SaltString ::from_b64 ( " dS/X5/sPEKTj4Rzs/CuvzQ " ) . unwrap ( ) ;
let argon2 = Argon2 ::default ( ) ;
argon2
. hash_password ( pw . as_bytes ( ) , & salt )
. unwrap ( )
. to_string ( )
2023-04-03 16:11:26 +02:00
}
2023-04-28 19:29:20 +02:00
2023-05-10 08:57:20 +02:00
pub async fn logged_in ( & self , db : & SqlitePool ) {
sqlx ::query! (
" UPDATE user SET last_access = CURRENT_TIMESTAMP where id = ? " ,
self . id
)
. execute ( db )
. await
. unwrap ( ) ; //Okay, because we can only create a User of a valid id
}
2023-04-28 19:29:20 +02:00
pub async fn delete ( & self , db : & SqlitePool ) {
sqlx ::query! ( " UPDATE user SET deleted=1 WHERE id=? " , self . id )
. execute ( db )
. await
. unwrap ( ) ; //Okay, because we can only create a User of a valid id
}
2023-06-08 17:23:23 +02:00
pub async fn get_days ( & self , db : & SqlitePool ) -> Vec < Day > {
let mut days = Vec ::new ( ) ;
2023-12-23 21:27:52 +01:00
for i in 0 .. self . amount_days_to_show ( db ) . await {
2023-06-08 17:23:23 +02:00
let date = ( Local ::now ( ) + chrono ::Duration ::days ( i ) ) . date_naive ( ) ;
2023-12-23 21:27:52 +01:00
if self . has_role ( db , " scheckbuch " ) . await {
2023-06-08 17:23:23 +02:00
days . push ( Day ::new_guest ( db , date , false ) . await ) ;
} else {
days . push ( Day ::new ( db , date , false ) . await ) ;
}
}
2023-12-23 21:27:52 +01:00
for date in TripDetails ::pinned_days ( db , self . amount_days_to_show ( db ) . await - 1 ) . await {
if self . has_role ( db , " scheckbuch " ) . await {
2023-06-08 17:23:23 +02:00
let day = Day ::new_guest ( db , date , true ) . await ;
2024-05-28 09:08:48 +02:00
if ! day . events . is_empty ( ) {
2023-06-08 17:23:23 +02:00
days . push ( day ) ;
}
} else {
days . push ( Day ::new ( db , date , true ) . await ) ;
}
}
days
}
2024-09-02 12:18:23 +03:00
pub ( crate ) async fn amount_days_to_show ( & self , db : & SqlitePool ) -> i64 {
2024-10-25 18:55:08 +02:00
if self . allowed_to_steer ( db ) . await {
2023-06-08 17:23:23 +02:00
let end_of_year = NaiveDate ::from_ymd_opt ( Local ::now ( ) . year ( ) , 12 , 31 ) . unwrap ( ) ; //Ok,
//december
//has 31
//days
2024-09-02 09:23:09 +03:00
let days_left_in_year = end_of_year
2023-06-08 17:23:23 +02:00
. signed_duration_since ( Local ::now ( ) . date_naive ( ) )
. num_days ( )
2024-09-02 09:23:09 +03:00
+ 1 ;
2024-09-02 12:18:23 +03:00
if days_left_in_year < = 31 {
2024-09-02 09:23:09 +03:00
let end_of_next_year =
NaiveDate ::from_ymd_opt ( Local ::now ( ) . year ( ) + 1 , 12 , 31 ) . unwrap ( ) ; //Ok,
//december
//has 31
//days
end_of_next_year
. signed_duration_since ( Local ::now ( ) . date_naive ( ) )
. num_days ( )
+ 1
} else {
days_left_in_year
}
2023-06-08 17:23:23 +02:00
} else {
2024-09-03 21:35:43 +03:00
AMOUNT_DAYS_TO_SHOW_TRIPS_AHEAD
2023-06-08 17:23:23 +02:00
}
}
2024-06-10 14:55:01 +02:00
pub ( crate ) async fn close_thousands_trip ( & self , db : & SqlitePool ) -> Option < String > {
let rowed_km = Stat ::person ( db , None , self ) . await . rowed_km ;
if rowed_km % 1000 > 970 {
return Some ( format! (
" {} braucht nur mehr {} km bis die {} km voll sind 🤑 " ,
self . name ,
1000 - rowed_km % 1000 ,
rowed_km + 1000 - ( rowed_km % 1000 )
) ) ;
}
None
}
2024-09-11 23:52:16 +02:00
pub ( crate ) async fn received_new_logentry (
& self ,
db : & mut Transaction < '_ , Sqlite > ,
smtp_pw : & str ,
) {
2024-09-12 08:35:10 +02:00
if self . has_role_tx ( db , " scheckbuch " ) . await {
let amount_trips = Logbook ::completed_with_user_tx ( db , & self ) . await . len ( ) ;
if amount_trips = = 5 {
if let Some ( mail ) = & self . mail {
let _ = self . send_end_mail_scheckbuch ( db , mail , smtp_pw ) . await ;
}
2024-10-25 18:55:08 +02:00
Notification ::create_for_steering_people_tx (
2024-09-12 08:35:10 +02:00
db ,
& 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 ;
} else if amount_trips > 5 {
let board = Role ::find_by_name_tx ( db , " Vorstand " ) . await . unwrap ( ) ;
Notification ::create_for_role_tx (
db ,
& board ,
& format! (
" Lieber Vorstand, {} hat nun bereits die {}. seiner 5 Scheckbuchausfahrten absolviert. " ,
self . name , amount_trips
) ,
" Scheckbuch überfertig " ,
None , None
)
. await ;
2024-09-11 23:52:16 +02:00
}
}
2024-09-12 08:35:10 +02:00
// TODO: check fahrtenabzeichen fertig?
// TODO: check äquatorpreis geschafft?
2024-09-11 23:52:16 +02:00
}
2023-04-03 16:11:26 +02:00
}
2023-04-03 22:03:45 +02:00
#[ async_trait ]
impl < ' r > FromRequest < ' r > for User {
type Error = LoginError ;
async fn from_request ( req : & ' r Request < '_ > ) -> request ::Outcome < Self , Self ::Error > {
match req . cookies ( ) . get_private ( " loggedin_user " ) {
2023-07-27 22:16:12 +02:00
Some ( user_id ) = > match user_id . value ( ) . parse ::< i32 > ( ) {
Ok ( user_id ) = > {
2023-05-10 08:57:20 +02:00
let db = req . rocket ( ) . state ::< SqlitePool > ( ) . unwrap ( ) ;
2023-07-27 22:16:12 +02:00
let Some ( user ) = User ::find_by_id ( db , user_id ) . await else {
2024-01-10 14:08:15 +01:00
return Outcome ::Error ( ( Status ::Forbidden , LoginError ::UserNotFound ) ) ;
2023-07-27 22:16:12 +02:00
} ;
2023-07-28 11:50:11 +02:00
if user . deleted {
2024-01-10 14:08:15 +01:00
return Outcome ::Error ( ( Status ::Forbidden , LoginError ::UserDeleted ) ) ;
2023-07-28 11:50:11 +02:00
}
2023-05-10 08:57:20 +02:00
user . logged_in ( db ) . await ;
2023-06-07 00:07:11 +02:00
2023-07-27 22:16:12 +02:00
let mut cookie = Cookie ::new ( " loggedin_user " , format! ( " {} " , user . id ) ) ;
2024-01-10 14:08:15 +01:00
cookie . set_expires ( OffsetDateTime ::now_utc ( ) + Duration ::weeks ( 2 ) ) ;
2023-06-07 00:07:11 +02:00
req . cookies ( ) . add_private ( cookie ) ;
2023-05-10 08:57:20 +02:00
Outcome ::Success ( user )
}
2024-01-10 14:08:15 +01:00
Err ( _ ) = > Outcome ::Error ( ( Status ::Unauthorized , LoginError ::DeserializationError ) ) ,
2023-05-03 15:59:28 +02:00
} ,
2023-11-08 17:39:39 +01:00
None = > Outcome ::Error ( ( Status ::Unauthorized , LoginError ::NotLoggedIn ) ) ,
2023-04-03 22:03:45 +02:00
}
}
}
2024-08-19 10:34:37 +02:00
/// Creates a struct named $name. Allows to be created from a user, if one of the specified $roles are active for the user.
macro_rules ! special_user {
( $name :ident , $( $role :tt ) * ) = > {
#[ derive(Debug) ]
pub struct $name {
pub ( crate ) user : User ,
2023-08-02 14:29:19 +02:00
}
2024-08-19 10:34:37 +02:00
impl Deref for $name {
type Target = User ;
fn deref ( & self ) -> & Self ::Target {
& self . user
2023-12-23 21:27:52 +01:00
}
2023-10-24 10:16:26 +02:00
}
2024-01-10 14:08:15 +01:00
2024-08-19 10:34:37 +02:00
impl $name {
pub fn into_inner ( self ) -> User {
self . user
2024-01-10 14:08:15 +01:00
}
}
2024-08-19 10:34:37 +02:00
#[ async_trait ]
impl < ' r > FromRequest < ' r > for $name {
type Error = LoginError ;
async fn from_request ( req : & ' r Request < '_ > ) -> request ::Outcome < Self , Self ::Error > {
let db = req . rocket ( ) . state ::< SqlitePool > ( ) . unwrap ( ) ;
match User ::from_request ( req ) . await {
Outcome ::Success ( user ) = > {
if special_user! ( @ check_roles user , db , $( $role ) * ) {
Outcome ::Success ( $name { user } )
} else {
Outcome ::Forward ( Status ::Forbidden )
}
}
Outcome ::Error ( f ) = > Outcome ::Error ( f ) ,
Outcome ::Forward ( f ) = > Outcome ::Forward ( f ) ,
2023-12-23 21:27:52 +01:00
}
}
2023-04-04 10:44:14 +02:00
}
2024-03-06 15:55:13 +01:00
2024-08-19 10:34:37 +02:00
impl $name {
pub async fn new ( db : & SqlitePool , user : User ) -> Option < Self > {
if special_user! ( @ check_roles user , db , $( $role ) * ) {
Some ( $name { user } )
2024-03-06 15:55:13 +01:00
} else {
2024-08-19 10:34:37 +02:00
None
2024-03-06 15:55:13 +01:00
}
}
}
2024-08-19 10:34:37 +02:00
} ;
( @ check_roles $user :ident , $db :ident , $( + $role :expr ) , * $(, - $neg_role :expr ) * ) = > {
{
2024-08-19 10:51:50 +02:00
let mut has_positive_role = false ;
2024-08-19 10:34:37 +02:00
$(
2024-08-19 10:51:50 +02:00
if $user . has_role ( $db , $role ) . await {
has_positive_role = true ;
2024-01-19 00:39:15 +01:00
}
2024-08-19 10:34:37 +02:00
) *
2024-08-19 10:51:50 +02:00
has_positive_role
2024-08-19 10:34:37 +02:00
$(
2024-08-19 10:51:50 +02:00
& & ! $user . has_role ( $db , $neg_role ) . await
2024-08-19 10:34:37 +02:00
) *
2024-01-19 00:39:15 +01:00
}
2024-08-19 10:34:37 +02:00
} ;
2024-01-22 19:27:22 +01:00
}
2024-08-19 10:34:37 +02:00
special_user! ( TechUser , + " tech " ) ;
2024-11-25 12:12:36 +01:00
special_user! ( ErgoUser , + " ergo " ) ;
2024-10-25 18:29:50 +02:00
special_user! ( SteeringUser , + " cox " , + " Bootsführer " ) ;
2024-08-19 10:34:37 +02:00
special_user! ( AdminUser , + " admin " ) ;
special_user! ( AllowedForPlannedTripsUser , + " Donau Linz " , + " scheckbuch " ) ;
special_user! ( DonauLinzUser , + " Donau Linz " , - " Unterstützend " , - " Förderndes Mitglied " ) ;
special_user! ( SchnupperBetreuerUser , + " schnupper-betreuer " ) ;
special_user! ( VorstandUser , + " Vorstand " ) ;
special_user! ( EventUser , + " manage_events " ) ;
2024-08-19 10:51:50 +02:00
special_user! ( AllowedToEditPaymentStatusUser , + " kassier " , + " admin " ) ;
2024-08-19 13:23:08 +02:00
special_user! ( ManageUserUser , + " admin " , + " schriftfuehrer " ) ;
2024-09-02 12:18:23 +03:00
special_user! ( AllowedToUpdateTripToAlwaysBeShownUser , + " admin " ) ;
2024-01-22 19:27:22 +01:00
2024-04-08 19:04:57 +02:00
#[ derive(FromRow, Serialize, Deserialize, Clone, Debug) ]
pub struct UserWithRolesAndMembershipPdf {
#[ serde(flatten) ]
pub user : User ,
pub membership_pdf : bool ,
pub roles : Vec < String > ,
}
impl UserWithRolesAndMembershipPdf {
pub ( crate ) async fn from_user ( db : & SqlitePool , user : User ) -> Self {
2024-05-06 13:35:42 +02:00
let membership_pdf = user . has_membership_pdf ( db ) . await ;
2024-04-08 19:04:57 +02:00
Self {
roles : user . roles ( db ) . await ,
user ,
membership_pdf ,
}
}
}
2024-01-22 19:27:22 +01:00
2024-03-26 12:34:19 +01:00
#[ derive(FromRow, Serialize, Deserialize, Clone, Debug) ]
pub struct UserWithMembershipPdf {
#[ serde(flatten) ]
pub user : User ,
pub membership_pdf : Option < Vec < u8 > > ,
}
impl UserWithMembershipPdf {
pub ( crate ) async fn from ( db : & SqlitePool , user : User ) -> Self {
let membership_pdf : Option < Vec < u8 > > =
sqlx ::query_scalar! ( " SELECT membership_pdf FROM user WHERE id = $1 " , user . id )
. fetch_optional ( db )
. await
. unwrap ( )
. unwrap ( ) ;
Self {
user ,
membership_pdf ,
}
}
}
2023-04-03 16:11:26 +02:00
#[ cfg(test) ]
mod test {
2023-12-23 21:27:52 +01:00
use std ::collections ::HashMap ;
2023-11-02 12:25:13 +01:00
use crate ::{ tera ::admin ::user ::UserEditForm , testdb } ;
2023-04-03 22:03:45 +02:00
2023-04-03 17:21:34 +02:00
use super ::User ;
2023-04-03 16:11:26 +02:00
use sqlx ::SqlitePool ;
2023-04-26 11:22:22 +02:00
#[ sqlx::test ]
fn test_find_correct_id ( ) {
let pool = testdb! ( ) ;
let user = User ::find_by_id ( & pool , 1 ) . await . unwrap ( ) ;
assert_eq! ( user . id , 1 ) ;
}
#[ sqlx::test ]
fn test_find_wrong_id ( ) {
let pool = testdb! ( ) ;
let user = User ::find_by_id ( & pool , 1337 ) . await ;
assert! ( user . is_none ( ) ) ;
}
#[ sqlx::test ]
fn test_find_correct_name ( ) {
let pool = testdb! ( ) ;
let user = User ::find_by_name ( & pool , " admin " . into ( ) ) . await . unwrap ( ) ;
assert_eq! ( user . id , 1 ) ;
}
#[ sqlx::test ]
fn test_find_wrong_name ( ) {
let pool = testdb! ( ) ;
let user = User ::find_by_name ( & pool , " name-does-not-exist " . into ( ) ) . await ;
assert! ( user . is_none ( ) ) ;
}
#[ sqlx::test ]
fn test_all ( ) {
let pool = testdb! ( ) ;
let res = User ::all ( & pool ) . await ;
assert! ( res . len ( ) > 3 ) ;
}
2023-07-23 12:17:57 +02:00
#[ sqlx::test ]
fn test_cox ( ) {
let pool = testdb! ( ) ;
let res = User ::cox ( & pool ) . await ;
2024-08-21 17:05:41 +02:00
assert_eq! ( res . len ( ) , 4 ) ;
2023-07-23 12:17:57 +02:00
}
2023-04-26 11:22:22 +02:00
#[ sqlx::test ]
fn test_succ_create ( ) {
let pool = testdb! ( ) ;
2023-12-23 21:27:52 +01:00
assert_eq! ( User ::create ( & pool , " new-user-name " . into ( ) ) . await , true ) ;
2023-04-26 11:22:22 +02:00
}
#[ sqlx::test ]
fn test_duplicate_name_create ( ) {
let pool = testdb! ( ) ;
2023-12-23 21:27:52 +01:00
assert_eq! ( User ::create ( & pool , " admin " . into ( ) ) . await , false ) ;
2023-04-26 11:22:22 +02:00
}
#[ sqlx::test ]
fn test_update ( ) {
let pool = testdb! ( ) ;
let user = User ::find_by_id ( & pool , 1 ) . await . unwrap ( ) ;
2023-11-02 12:25:13 +01:00
user . update (
& pool ,
UserEditForm {
id : 1 ,
dob : None ,
weight : None ,
2023-12-23 21:27:52 +01:00
sex : Some ( " m " . into ( ) ) ,
roles : HashMap ::new ( ) ,
2023-12-30 21:21:30 +01:00
member_since_date : None ,
birthdate : None ,
mail : None ,
nickname : None ,
notes : None ,
phone : None ,
address : None ,
2024-01-18 16:37:54 +01:00
family_id : None ,
2024-03-20 21:16:55 +01:00
membership_pdf : None ,
2023-11-02 12:25:13 +01:00
} ,
)
2024-10-11 12:39:23 +02:00
. await
. unwrap ( ) ;
2023-04-26 11:22:22 +02:00
let user = User ::find_by_id ( & pool , 1 ) . await . unwrap ( ) ;
2023-12-23 21:27:52 +01:00
assert_eq! ( user . sex , Some ( " m " . into ( ) ) ) ;
2023-04-26 11:22:22 +02:00
}
2023-04-03 17:21:34 +02:00
#[ sqlx::test ]
fn succ_login_with_test_db ( ) {
2023-04-03 22:03:45 +02:00
let pool = testdb! ( ) ;
2023-04-03 17:21:34 +02:00
User ::login ( & pool , " admin " . into ( ) , " admin " . into ( ) )
. await
. unwrap ( ) ;
}
#[ sqlx::test ]
fn wrong_pw ( ) {
2023-04-03 22:03:45 +02:00
let pool = testdb! ( ) ;
2023-04-03 17:21:34 +02:00
assert! ( User ::login ( & pool , " admin " . into ( ) , " admi " . into ( ) )
. await
. is_err ( ) ) ;
}
#[ sqlx::test ]
fn wrong_username ( ) {
2023-04-03 22:03:45 +02:00
let pool = testdb! ( ) ;
2023-04-03 17:21:34 +02:00
assert! ( User ::login ( & pool , " admi " . into ( ) , " admin " . into ( ) )
. await
. is_err ( ) ) ;
2023-04-03 16:11:26 +02:00
}
2023-04-26 11:22:22 +02:00
#[ sqlx::test ]
fn reset ( ) {
let pool = testdb! ( ) ;
let user = User ::find_by_id ( & pool , 1 ) . await . unwrap ( ) ;
user . reset_pw ( & pool ) . await ;
let user = User ::find_by_id ( & pool , 1 ) . await . unwrap ( ) ;
assert_eq! ( user . pw , None ) ;
}
#[ sqlx::test ]
fn update_pw ( ) {
let pool = testdb! ( ) ;
let user = User ::find_by_id ( & pool , 1 ) . await . unwrap ( ) ;
assert! ( User ::login ( & pool , " admin " . into ( ) , " abc " . into ( ) )
. await
. is_err ( ) ) ;
user . update_pw ( & pool , " abc " . into ( ) ) . await ;
User ::login ( & pool , " admin " . into ( ) , " abc " . into ( ) )
. await
. unwrap ( ) ;
}
2023-04-03 16:11:26 +02:00
}