rowt/src/model/user.rs

1372 lines
44 KiB
Rust
Raw Normal View History

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},
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::{
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-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
}
#[derive(Debug, Serialize, Deserialize)]
pub struct UserWithDetails {
#[serde(flatten)]
pub user: User,
2024-04-17 13:51:47 +02:00
pub amount_unread_notifications: i32,
pub allowed_to_steer: bool,
pub on_water: bool,
pub roles: Vec<String>,
}
impl UserWithDetails {
pub async fn from_user(user: User, db: &SqlitePool) -> Self {
let allowed_to_steer = user.allowed_to_steer(db).await;
Self {
on_water: user.on_water(db).await,
roles: user.roles(db).await,
amount_unread_notifications: user.amount_unread_notifications(db).await,
allowed_to_steer,
user,
}
}
}
2023-04-03 17:21:34 +02:00
#[derive(Debug)]
2023-04-03 16:11:26 +02:00
pub enum LoginError {
InvalidAuthenticationCombo,
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
}
#[derive(Debug, Serialize)]
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-03-06 13:27:03 +01:00
impl Default for Fee {
fn default() -> Self {
Self::new()
}
}
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,
}
}
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;
}
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 {
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
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",
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
}
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
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
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,
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
}
pub async fn fee(&self, db: &SqlitePool) -> Option<Fee> {
if !self.has_role(db, "Donau Linz").await {
return None;
}
if self.deleted {
return None;
}
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
}
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();
}
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);
}
}
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);
}
}
}
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
}
} 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() {
if self.has_role(db, "Student").await || self.has_role(db, "Schüler").await {
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);
} else {
if halfprice {
fee.add("Mitgliedsbeitrag (Halbpreis)".into(), REGULAR / 2);
} else {
fee.add("Mitgliedsbeitrag".into(), REGULAR);
}
}
}
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
}
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
}
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,
}
}
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
pub async fn roles(&self, db: &SqlitePool) -> Vec<String> {
sqlx::query!(
"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;",
self.id
)
.fetch_all(db)
.await
.unwrap()
.into_iter().map(|r| r.name).collect()
}
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()
}
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
)
.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
}
pub async fn on_water(&self, db: &SqlitePool) -> bool {
2023-07-30 22:59:47 +02:00
if sqlx::query!(
"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-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
}
pub async fn all_with_role(db: &SqlitePool, role: &Role) -> Vec<Self> {
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> {
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 u
JOIN user_role ur ON u.id = ur.user_id
WHERE ur.role_id = ? AND deleted = 0
ORDER BY name;
", role.id
)
.fetch_all(db.deref_mut())
.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> {
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
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
}
pub async fn create(db: &SqlitePool, name: &str) -> bool {
2024-05-04 18:19:07 +02:00
let name = name.trim();
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()
}
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
}
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) {
family_id = Some(Family::insert_tx(&mut db).await)
2024-01-18 16:37:54 +01: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
)
.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
)
.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
// handle roles
sqlx::query!("DELETE FROM user_role WHERE user_id = ?", self.id)
.execute(db.deref_mut())
.await
.unwrap();
for role_id in data.roles.into_keys() {
let role = Role::find_by_id_tx(&mut db, role_id.parse::<i32>().unwrap())
.await
.unwrap();
self.add_role_tx(&mut db, &role).await?;
}
db.commit().await.map_err(|e| e.to_string())?;
Ok(())
2023-04-04 10:44:14 +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
.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 {
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",
"m-birner",
"s-sollberger",
"d-kortschak",
"wwwadmin",
"wadminw",
"admin",
"m sageder",
"d kortschak",
"a almousa",
"p hofer",
"s sollberger",
"n sageder",
"wp-system",
"s.sollberger",
"m.birner",
"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-05-27 08:32:00 +02:00
.contains(&name.as_str())
{
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();
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();
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);
}
}
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
}
pub(crate) async fn amount_days_to_show(&self, db: &SqlitePool) -> i64 {
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
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()
+ 1;
if days_left_in_year <= 31 {
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
}
}
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
}
pub(crate) async fn received_new_logentry(
&self,
db: &mut Transaction<'_, Sqlite>,
smtp_pw: &str,
) {
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;
}
Notification::create_for_steering_people_tx(
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;
}
}
// TODO: check fahrtenabzeichen fertig?
// TODO: check äquatorpreis geschafft?
}
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") {
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();
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-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;
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));
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
},
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-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-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)*) => {
{
let mut has_positive_role = false;
2024-08-19 10:34:37 +02:00
$(
if $user.has_role($db, $role).await {
has_positive_role = true;
}
2024-08-19 10:34:37 +02:00
)*
has_positive_role
2024-08-19 10:34:37 +02:00
$(
&& !$user.has_role($db, $neg_role).await
2024-08-19 10:34:37 +02: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");
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");
special_user!(AllowedToEditPaymentStatusUser, +"kassier", +"admin");
2024-08-19 13:23:08 +02:00
special_user!(ManageUserUser, +"admin", +"schriftfuehrer");
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 {
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;
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!();
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!();
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,
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
},
)
.await
.unwrap();
2023-04-26 11:22:22 +02:00
let user = User::find_by_id(&pool, 1).await.unwrap();
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
}