From 588520914cc705f8596149681ce6fc17d5d883e6 Mon Sep 17 00:00:00 2001
From: Philipp Hofer
Date: Wed, 16 Apr 2025 10:18:27 +0200
Subject: [PATCH 01/20] add nextcloud auth route
---
src/tera/mod.rs | 17 +++++++++++++++--
1 file changed, 15 insertions(+), 2 deletions(-)
diff --git a/src/tera/mod.rs b/src/tera/mod.rs
index 486661e..1396a76 100644
--- a/src/tera/mod.rs
+++ b/src/tera/mod.rs
@@ -7,7 +7,7 @@ use rocket::{
form::Form,
fs::FileServer,
get,
- http::Cookie,
+ http::{Cookie, Status},
post,
request::FlashMessage,
response::{Flash, Redirect},
@@ -123,11 +123,23 @@ async fn wikiauth(db: &State, login: Form>) -> String
"FAIL".into()
}
+#[get("/?&")]
+async fn nextcloud_auth(db: &State, username: String, password: String) -> Status {
+ if let Ok(user) = User::login(db, &username, &password).await {
+ if user.has_role(db, "admin").await {
+ return Status::Ok;
+ }
+ if user.has_role(db, "Vorstand").await {
+ return Status::Ok;
+ }
+ }
+ Status::Unauthorized
+}
+
#[catch(401)] //Unauthorized
fn unauthorized_error(req: &Request) -> Redirect {
// Save the URL the user tried to access, to be able to go there once logged in
let mut redirect_cookie = Cookie::new("redirect_url", format!("{}", req.uri()));
- println!("{}", req.uri());
redirect_cookie.set_expires(OffsetDateTime::now_utc() + Duration::hours(1));
req.cookies().add_private(redirect_cookie);
@@ -265,6 +277,7 @@ pub fn config(rocket: Rocket) -> Rocket {
.mount("/", routes![index, steering, impressum])
.mount("/auth", auth::routes())
.mount("/wikiauth", routes![wikiauth])
+ .mount("/nxauth", routes![nextcloud_auth])
.mount("/new-blogpost", routes![new_blogpost])
.mount("/blogpost-unpublished", routes![blogpost_unpublished])
.mount("/log", log::routes())
From 2b79df8e4290c6d7f2800c7086cf815aae54bf83 Mon Sep 17 00:00:00 2001
From: Philipp Hofer
Date: Wed, 16 Apr 2025 10:46:19 +0200
Subject: [PATCH 02/20] no funny business w/ get params
---
Cargo.lock | 1 +
Cargo.toml | 1 +
src/tera/mod.rs | 56 +++++++++++++++++++++++++++++++++++++++++++++----
3 files changed, 54 insertions(+), 4 deletions(-)
diff --git a/Cargo.lock b/Cargo.lock
index b3f3426..ea63be2 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -2544,6 +2544,7 @@ name = "rot"
version = "0.1.0"
dependencies = [
"argon2",
+ "base64",
"chrono",
"chrono-tz 0.10.3",
"csv",
diff --git a/Cargo.toml b/Cargo.toml
index 4a16639..921b158 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -29,6 +29,7 @@ job_scheduler_ng = "2.0"
ureq = { version = "3.0", features = ["json"] }
regex = "1.10"
urlencoding = "2.1"
+base64 = "0.22"
[target.'cfg(not(windows))'.dependencies]
openssl = { version = "0.10", features = [ "vendored" ] }
diff --git a/src/tera/mod.rs b/src/tera/mod.rs
index 1396a76..44d0890 100644
--- a/src/tera/mod.rs
+++ b/src/tera/mod.rs
@@ -9,7 +9,7 @@ use rocket::{
get,
http::{Cookie, Status},
post,
- request::FlashMessage,
+ request::{FlashMessage, FromRequest, Outcome},
response::{Flash, Redirect},
routes,
time::{Duration, OffsetDateTime},
@@ -123,9 +123,57 @@ async fn wikiauth(db: &State, login: Form>) -> String
"FAIL".into()
}
-#[get("/?&")]
-async fn nextcloud_auth(db: &State, username: String, password: String) -> Status {
- if let Ok(user) = User::login(db, &username, &password).await {
+struct BasicAuth {
+ username: String,
+ password: String,
+}
+
+#[rocket::async_trait]
+impl<'r> FromRequest<'r> for BasicAuth {
+ type Error = ();
+
+ async fn from_request(request: &'r Request<'_>) -> Outcome {
+ // Get the Authorization header
+ let auth_header = match request.headers().get_one("Authorization") {
+ Some(h) => h,
+ None => return Outcome::Failure((Status::Unauthorized, ())),
+ };
+
+ // Check if it's a Basic auth header
+ if !auth_header.starts_with("Basic ") {
+ return Outcome::Failure((Status::Unauthorized, ()));
+ }
+
+ // Decode the base64 credentials
+ let credentials = match BASE64.decode(auth_header[6..].as_bytes()) {
+ Ok(c) => c,
+ Err(_) => return Outcome::Failure((Status::Unauthorized, ())),
+ };
+
+ // Convert to UTF-8 string
+ let credentials_str = match str::from_utf8(&credentials) {
+ Ok(s) => s,
+ Err(_) => return Outcome::Failure((Status::Unauthorized, ())),
+ };
+
+ // Split into username and password
+ let mut parts = credentials_str.splitn(2, ':');
+ let username = match parts.next() {
+ Some(u) => u.to_string(),
+ None => return Outcome::Failure((Status::Unauthorized, ())),
+ };
+ let password = match parts.next() {
+ Some(p) => p.to_string(),
+ None => return Outcome::Failure((Status::Unauthorized, ())),
+ };
+
+ Outcome::Success(BasicAuth { username, password })
+ }
+}
+
+#[get("/")]
+async fn nextcloud_auth(db: &State, auth: BasicAuth) -> Status {
+ if let Ok(user) = User::login(db, &auth.username, &auth.password).await {
if user.has_role(db, "admin").await {
return Status::Ok;
}
From dc2ee38aa024816b1f9892d1cab333e0e496ae14 Mon Sep 17 00:00:00 2001
From: Philipp Hofer
Date: Wed, 16 Apr 2025 10:56:57 +0200
Subject: [PATCH 03/20] no funny business w/ get params
---
src/tera/mod.rs | 17 +++++++++--------
1 file changed, 9 insertions(+), 8 deletions(-)
diff --git a/src/tera/mod.rs b/src/tera/mod.rs
index 44d0890..44cba1a 100644
--- a/src/tera/mod.rs
+++ b/src/tera/mod.rs
@@ -30,6 +30,7 @@ use crate::{
},
SCHECKBUCH,
};
+use base64::alphabet::STANDARD;
pub(crate) mod admin;
mod auth;
@@ -136,35 +137,35 @@ impl<'r> FromRequest<'r> for BasicAuth {
// Get the Authorization header
let auth_header = match request.headers().get_one("Authorization") {
Some(h) => h,
- None => return Outcome::Failure((Status::Unauthorized, ())),
+ None => return Outcome::Error((Status::Unauthorized, ())),
};
// Check if it's a Basic auth header
if !auth_header.starts_with("Basic ") {
- return Outcome::Failure((Status::Unauthorized, ()));
+ return Outcome::Error((Status::Unauthorized, ()));
}
// Decode the base64 credentials
- let credentials = match BASE64.decode(auth_header[6..].as_bytes()) {
+ let credentials = match base64::decode(&auth_header[6..]) {
Ok(c) => c,
- Err(_) => return Outcome::Failure((Status::Unauthorized, ())),
+ Err(_) => return Outcome::Error((Status::Unauthorized, ())),
};
// Convert to UTF-8 string
- let credentials_str = match str::from_utf8(&credentials) {
+ let credentials_str = match std::str::from_utf8(&credentials) {
Ok(s) => s,
- Err(_) => return Outcome::Failure((Status::Unauthorized, ())),
+ Err(_) => return Outcome::Error((Status::Unauthorized, ())),
};
// Split into username and password
let mut parts = credentials_str.splitn(2, ':');
let username = match parts.next() {
Some(u) => u.to_string(),
- None => return Outcome::Failure((Status::Unauthorized, ())),
+ None => return Outcome::Error((Status::Unauthorized, ())),
};
let password = match parts.next() {
Some(p) => p.to_string(),
- None => return Outcome::Failure((Status::Unauthorized, ())),
+ None => return Outcome::Error((Status::Unauthorized, ())),
};
Outcome::Success(BasicAuth { username, password })
From 4ce9a573fe9087f6a29ef25664360182168e056e Mon Sep 17 00:00:00 2001
From: Philipp Hofer
Date: Wed, 16 Apr 2025 11:31:46 +0200
Subject: [PATCH 04/20] 400 instead of 303
---
src/tera/mod.rs | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/src/tera/mod.rs b/src/tera/mod.rs
index 44cba1a..3e403fa 100644
--- a/src/tera/mod.rs
+++ b/src/tera/mod.rs
@@ -182,7 +182,7 @@ async fn nextcloud_auth(db: &State, auth: BasicAuth) -> Status {
return Status::Ok;
}
}
- Status::Unauthorized
+ Status::BadRequest
}
#[catch(401)] //Unauthorized
From cf90ab6e1ade17e5cf6a0bad521b3ef84d390a54 Mon Sep 17 00:00:00 2001
From: Philipp Hofer
Date: Thu, 17 Apr 2025 20:38:41 +0200
Subject: [PATCH 05/20] allow others to send fee reminder thus reducing my bus
factor
---
Rocket.toml | 2 +-
src/model/mail.rs | 29 +++++++++++++-------
src/model/user/mod.rs | 1 +
src/tera/admin/mail.rs | 50 +++++++++++++++++++++++++++-------
src/tera/mod.rs | 1 -
templates/admin/mail.html.tera | 26 ++++++++++++++++++
6 files changed, 87 insertions(+), 22 deletions(-)
diff --git a/Rocket.toml b/Rocket.toml
index cc93d2b..4b4856e 100644
--- a/Rocket.toml
+++ b/Rocket.toml
@@ -2,7 +2,7 @@
secret_key = "/NtVGizglEoyoxBLzsRDWTy4oAG1qDw4J4O+CWJSv+fypD7W9sam8hUY4j90EZsbZk8wEradS5zBoWtWKi3k8w=="
rss_key = "rss-key-for-ci"
limits = { file = "10 MiB", data-form = "10 MiB"}
-smtp_pw = "8kIjlLH79Ky6D3j"
+smtp_pw = "8kIjlLH79Ky6D3jQ"
usage_log_path = "./usage.txt"
openweathermap_key = "c8dab8f91b5b815d76e9879cbaecd8d5"
wordpress_key = "pw-to-allow-sending-notifications"
diff --git a/src/model/mail.rs b/src/model/mail.rs
index 3d3794b..9879630 100644
--- a/src/model/mail.rs
+++ b/src/model/mail.rs
@@ -151,10 +151,15 @@ impl Mail {
false
}
- pub async fn fees(db: &SqlitePool, smtp_pw: String) {
+ pub async fn fees(db: &SqlitePool, smtp_pw: String, test: Option) {
let users = User::all_payer_groups(db).await;
for user in users {
- if !user.has_role(db, "paid").await {
+ if let Some(test) = &test {
+ if user.id != test.id {
+ continue;
+ }
+ }
+ if !user.has_role(db, "paid").await || test.is_some() {
let mut is_family = false;
let mut send_to = String::new();
match Family::find_by_opt_id(db, user.family_id).await {
@@ -196,11 +201,10 @@ dein Vereinsbeitrag für das aktuelle Jahr beträgt {}€",
))
}
content.push_str("\nBitte überweise diesen auf folgendes Konto: IBAN: AT58 2032 0321 0072 9256. Auf https://app.rudernlinz.at/planned findest du einen QR Code, den du mit deiner Bankapp scannen kannst um alle Eingaben bereits ausgefüllt zu haben.\n\n\
-Falls die Berechnung nicht stimmt (korrekte Preise findest du unter https://rudernlinz.at/unser-verein/gebuhren/) melde dich bitte bei it@rudernlinz.at. @Studenten: Bitte die aktuelle Studienbestätigung an it@rudernlinz.at schicken.\n\n\
+Falls die Berechnung nicht stimmt (korrekte Preise findest du unter https://rudernlinz.at/unser-verein/gebuhren/) melde dich bitte bei kassier@rudernlinz.at. @Studenten: Bitte die aktuelle Studienbestätigung an kassier@rudernlinz.at schicken.\n\n\
Wenn du die Vereinsgebühren schon bezahlt hast, kannst du diese Mail einfach ignorieren.\n\n
Beste Grüße\n\
-Der Vorstand
- ");
+Der Vorstand");
let mut email = Message::builder()
.from(
"ASKÖ Ruderverein Donau Linz "
@@ -208,7 +212,7 @@ Der Vorstand
.unwrap(),
)
.reply_to(
- "ASKÖ Ruderverein Donau Linz "
+ "ASKÖ Ruderverein Donau Linz "
.parse()
.unwrap(),
)
@@ -253,11 +257,16 @@ Der Vorstand
}
}
- pub async fn fees_final(db: &SqlitePool, smtp_pw: String) {
+ pub async fn fees_final(db: &SqlitePool, smtp_pw: String, test: Option) {
let users = User::all_payer_groups(db).await;
for user in users {
+ if let Some(test) = &test {
+ if user.id != test.id {
+ continue;
+ }
+ }
if let Some(fee) = user.fee(db).await {
- if !fee.paid {
+ if !fee.paid || test.is_some() {
let mut is_family = false;
let mut send_to = String::new();
match Family::find_by_opt_id(db, user.family_id).await {
@@ -282,7 +291,7 @@ Der Vorstand
"Liebes Vereinsmitglied, \n\n\
wir möchten darauf hinweisen, dass wir deinen Mitgliedsbeitrag für das laufende Jahr bislang nicht verbuchen konnten. Es besteht die Möglichkeit, dass es sich hierbei um ein Versehen unsererseits handelt. Solltest du den Betrag bereits überwiesen haben, bitte kurz auf diese E-Mail antworten, damit wir es richtigstellen können.
-Falls die Zahlung noch nicht erfolgt ist, bitten wir um umgehende Überweisung des ausstehenden Betrags, spätestens jedoch bis zum 31. März, auf unser Bankkonto.\n\n\
+Falls die Zahlung noch nicht erfolgt ist, bitten wir um umgehende Überweisung des ausstehenden Betrags, spätestens jedoch binnen 14 Tagen, auf unser Bankkonto.\n\n\
Dein Vereinsbeitrag für das aktuelle Jahr beträgt {}€",
fees.sum_in_cents / 100,
);
@@ -317,7 +326,7 @@ Der Vorstand");
.unwrap(),
)
.reply_to(
- "ASKÖ Ruderverein Donau Linz "
+ "ASKÖ Ruderverein Donau Linz "
.parse()
.unwrap(),
)
diff --git a/src/model/user/mod.rs b/src/model/user/mod.rs
index 38862e3..b37879d 100644
--- a/src/model/user/mod.rs
+++ b/src/model/user/mod.rs
@@ -1182,6 +1182,7 @@ special_user!(VorstandUser, +"admin", +"Vorstand");
special_user!(EventUser, +"manage_events");
special_user!(AllowedToEditPaymentStatusUser, +"kassier", +"admin");
special_user!(ManageUserUser, +"admin", +"schriftfuehrer");
+special_user!(AllowedToSendFeeReminderUser, +"admin", +"schriftfuehrer", +"kassier");
special_user!(AllowedToUpdateTripToAlwaysBeShownUser, +"admin");
#[derive(FromRow, Serialize, Deserialize, Clone, Debug)]
diff --git a/src/tera/admin/mail.rs b/src/tera/admin/mail.rs
index dd9c8d3..953bf23 100644
--- a/src/tera/admin/mail.rs
+++ b/src/tera/admin/mail.rs
@@ -9,8 +9,8 @@ use sqlx::SqlitePool;
use crate::model::log::Log;
use crate::model::mail::Mail;
use crate::model::role::Role;
-use crate::model::user::UserWithDetails;
-use crate::model::user::{AdminUser, VorstandUser};
+use crate::model::user::VorstandUser;
+use crate::model::user::{AllowedToSendFeeReminderUser, UserWithDetails};
use crate::tera::Config;
#[get("/mail")]
@@ -35,21 +35,51 @@ async fn index(
}
#[get("/mail/fee")]
-async fn fee(db: &State, admin: AdminUser, config: &State) -> &'static str {
+async fn fee(
+ db: &State,
+ admin: AllowedToSendFeeReminderUser,
+ config: &State,
+) -> Flash {
Log::create(db, format!("{admin:?} trying to send fee")).await;
- Mail::fees(db, config.smtp_pw.clone()).await;
- "SUCC"
+ Mail::fees(db, config.smtp_pw.clone(), None).await;
+ Log::create(db, "Mail successfully sent".into()).await;
+ Flash::success(Redirect::to("/admin/mail"), "Mail versendet")
+}
+
+#[get("/mail/fee/test")]
+async fn fee_test(
+ db: &State,
+ admin: AllowedToSendFeeReminderUser,
+ config: &State,
+) -> Flash {
+ Log::create(db, format!("{admin:?} trying to send test fee")).await;
+ Mail::fees(db, config.smtp_pw.clone(), Some(admin.user)).await;
+ Log::create(db, "Mail successfully sent".into()).await;
+ Flash::success(Redirect::to("/admin/mail"), "Mail versendet")
}
#[get("/mail/fee-final")]
async fn fee_final(
db: &State,
- admin: AdminUser,
+ admin: AllowedToSendFeeReminderUser,
config: &State,
-) -> &'static str {
+) -> Flash {
Log::create(db, format!("{admin:?} trying to send fee_final")).await;
- Mail::fees_final(db, config.smtp_pw.clone()).await;
- "SUCC"
+ Mail::fees_final(db, config.smtp_pw.clone(), None).await;
+ Log::create(db, "Mail successfully sent".into()).await;
+ Flash::success(Redirect::to("/admin/mail"), "Mail versendet")
+}
+
+#[get("/mail/fee-final/test")]
+async fn fee_final_test(
+ db: &State,
+ admin: AllowedToSendFeeReminderUser,
+ config: &State,
+) -> Flash {
+ Log::create(db, format!("{admin:?} trying to send test fee_final")).await;
+ Mail::fees_final(db, config.smtp_pw.clone(), Some(admin.user)).await;
+ Log::create(db, "Mail successfully sent".into()).await;
+ Flash::success(Redirect::to("/admin/mail"), "Mail versendet")
}
#[derive(FromForm, Debug)]
@@ -79,7 +109,7 @@ async fn update(
}
pub fn routes() -> Vec {
- routes![index, update, fee, fee_final]
+ routes![index, update, fee, fee_test, fee_final, fee_final_test]
}
#[cfg(test)]
diff --git a/src/tera/mod.rs b/src/tera/mod.rs
index 486661e..ac3a130 100644
--- a/src/tera/mod.rs
+++ b/src/tera/mod.rs
@@ -127,7 +127,6 @@ async fn wikiauth(db: &State, login: Form>) -> String
fn unauthorized_error(req: &Request) -> Redirect {
// Save the URL the user tried to access, to be able to go there once logged in
let mut redirect_cookie = Cookie::new("redirect_url", format!("{}", req.uri()));
- println!("{}", req.uri());
redirect_cookie.set_expires(OffsetDateTime::now_utc() + Duration::hours(1));
req.cookies().add_private(redirect_cookie);
diff --git a/templates/admin/mail.html.tera b/templates/admin/mail.html.tera
index 6c97d4c..d17e998 100644
--- a/templates/admin/mail.html.tera
+++ b/templates/admin/mail.html.tera
@@ -22,6 +22,32 @@
+
+
Mitglieds-Beitrags-Info
+
+
+
+
Unfreundliche Zahlungsaufforderung
+
+
{% endblock content %}
From db429b6fe31b263ab62b4b5c818171721a99920c Mon Sep 17 00:00:00 2001
From: Philipp Hofer
Date: Thu, 17 Apr 2025 20:42:36 +0200
Subject: [PATCH 06/20] high security application...
---
Rocket.toml | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/Rocket.toml b/Rocket.toml
index 4b4856e..88a9b6b 100644
--- a/Rocket.toml
+++ b/Rocket.toml
@@ -2,7 +2,7 @@
secret_key = "/NtVGizglEoyoxBLzsRDWTy4oAG1qDw4J4O+CWJSv+fypD7W9sam8hUY4j90EZsbZk8wEradS5zBoWtWKi3k8w=="
rss_key = "rss-key-for-ci"
limits = { file = "10 MiB", data-form = "10 MiB"}
-smtp_pw = "8kIjlLH79Ky6D3jQ"
+smtp_pw = "my-smtp-password"
usage_log_path = "./usage.txt"
-openweathermap_key = "c8dab8f91b5b815d76e9879cbaecd8d5"
+openweathermap_key = "openweather-key"
wordpress_key = "pw-to-allow-sending-notifications"
From b0a2d3d5393705fdc9fb15fab8acd5fc865e331c Mon Sep 17 00:00:00 2001
From: Philipp Hofer
Date: Thu, 17 Apr 2025 21:52:24 +0200
Subject: [PATCH 07/20] better description of the button
---
templates/admin/mail.html.tera | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/templates/admin/mail.html.tera b/templates/admin/mail.html.tera
index d17e998..c80db2e 100644
--- a/templates/admin/mail.html.tera
+++ b/templates/admin/mail.html.tera
@@ -44,7 +44,7 @@
- An ALLE Mitglieder versenden
+ An ALLE Mitglieder versenden, die noch nicht bezahlt haben
From 36245fd0f7404c17708476f942a716b8531bbc75 Mon Sep 17 00:00:00 2001
From: Philipp Hofer
Date: Thu, 17 Apr 2025 22:00:15 +0200
Subject: [PATCH 08/20] use maries' magic css skills to unbreak signal links on
mobile; Fixes #891
---
templates/index.html.tera | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/templates/index.html.tera b/templates/index.html.tera
index 0edb986..ea8b689 100644
--- a/templates/index.html.tera
+++ b/templates/index.html.tera
@@ -333,7 +333,7 @@
Signal-Gruppenchat Steuerpersonen Donau Linz
- Mit diesem Link kannst du unserer Signal Gruppe beitreten: https://signal.group/#CjQKIHJInNb3zSVW7ipLo7_ygIqVxhxUaaNYx4sy2jdklLsIEhBHJNM2KZM1UnBdQxWy_Gdp
+ Mit diesem Link kannst du unserer Signal Gruppe beitreten: https://signal.group/#CjQKIHJInNb3zSVW7ipLo7_ygIqVxhxUaaNYx4sy2jdklLsIEhBHJNM2KZM1UnBdQxWy_Gdp
From 0059dfe96ff6227bbf29acfa71bab90efe3887f0 Mon Sep 17 00:00:00 2001
From: Philipp Hofer
Date: Fri, 18 Apr 2025 17:04:10 +0200
Subject: [PATCH 09/20] simple nx auth
---
src/tera/mod.rs | 14 ++++++++++++++
1 file changed, 14 insertions(+)
diff --git a/src/tera/mod.rs b/src/tera/mod.rs
index ac3a130..91f6ecb 100644
--- a/src/tera/mod.rs
+++ b/src/tera/mod.rs
@@ -123,6 +123,19 @@ async fn wikiauth(db: &State, login: Form>) -> String
"FAIL".into()
}
+#[post("/", data = "")]
+async fn nextcloud_auth(db: &State, login: Form>) -> String {
+ if let Ok(user) = User::login(db, login.name, login.password).await {
+ if user.has_role(db, "admin").await {
+ return String::from("SUCC");
+ }
+ if user.has_role(db, "Vorstand").await {
+ return String::from("SUCC");
+ }
+ }
+ "FAIL".into()
+}
+
#[catch(401)] //Unauthorized
fn unauthorized_error(req: &Request) -> Redirect {
// Save the URL the user tried to access, to be able to go there once logged in
@@ -264,6 +277,7 @@ pub fn config(rocket: Rocket) -> Rocket {
.mount("/", routes![index, steering, impressum])
.mount("/auth", auth::routes())
.mount("/wikiauth", routes![wikiauth])
+ .mount("/nxauth", routes![nextcloud_auth])
.mount("/new-blogpost", routes![new_blogpost])
.mount("/blogpost-unpublished", routes![blogpost_unpublished])
.mount("/log", log::routes())
From 37b6ea60574c928d70ee50026fabd2805fb4e3f6 Mon Sep 17 00:00:00 2001
From: Philipp Hofer
Date: Fri, 18 Apr 2025 17:44:21 +0200
Subject: [PATCH 10/20] remove unused dep; cargo clippy
---
Cargo.lock | 1 -
Cargo.toml | 1 -
src/model/tripdetails.rs | 2 +-
src/tera/mod.rs | 5 ++---
4 files changed, 3 insertions(+), 6 deletions(-)
diff --git a/Cargo.lock b/Cargo.lock
index ea63be2..b3f3426 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -2544,7 +2544,6 @@ name = "rot"
version = "0.1.0"
dependencies = [
"argon2",
- "base64",
"chrono",
"chrono-tz 0.10.3",
"csv",
diff --git a/Cargo.toml b/Cargo.toml
index 921b158..4a16639 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -29,7 +29,6 @@ job_scheduler_ng = "2.0"
ureq = { version = "3.0", features = ["json"] }
regex = "1.10"
urlencoding = "2.1"
-base64 = "0.22"
[target.'cfg(not(windows))'.dependencies]
openssl = { version = "0.10", features = [ "vendored" ] }
diff --git a/src/model/tripdetails.rs b/src/model/tripdetails.rs
index ff813cb..eebafce 100644
--- a/src/model/tripdetails.rs
+++ b/src/model/tripdetails.rs
@@ -196,7 +196,7 @@ WHERE day = ? AND planned_starting_time = ?
.fetch_one(db)
.await
.unwrap(); //TODO: fixme
- let amount_currently_registered = i64::from(amount_currently_registered.count);
+ let amount_currently_registered = amount_currently_registered.count;
amount_currently_registered >= self.max_people
}
diff --git a/src/tera/mod.rs b/src/tera/mod.rs
index b9e8dcb..91f6ecb 100644
--- a/src/tera/mod.rs
+++ b/src/tera/mod.rs
@@ -7,9 +7,9 @@ use rocket::{
form::Form,
fs::FileServer,
get,
- http::{Cookie, Status},
+ http::Cookie,
post,
- request::{FlashMessage, FromRequest, Outcome},
+ request::FlashMessage,
response::{Flash, Redirect},
routes,
time::{Duration, OffsetDateTime},
@@ -30,7 +30,6 @@ use crate::{
},
SCHECKBUCH,
};
-use base64::alphabet::STANDARD;
pub(crate) mod admin;
mod auth;
From 10740f988dd145f78d6f58932d803df3a1db29bc Mon Sep 17 00:00:00 2001
From: Philipp Hofer
Date: Fri, 18 Apr 2025 23:01:17 +0200
Subject: [PATCH 11/20] reduce amount of magic values, goal is to have specific
states -> e.g. cancelled
---
src/model/event.rs | 32 +++++++++++++++++++++-----------
src/model/mod.rs | 4 ++--
src/model/notification.rs | 2 +-
src/model/trip.rs | 18 ++++++++++++------
src/model/tripdetails.rs | 2 +-
templates/planned.html.tera | 15 +++++++--------
6 files changed, 44 insertions(+), 29 deletions(-)
diff --git a/src/model/event.rs b/src/model/event.rs
index fe63998..90eb9c0 100644
--- a/src/model/event.rs
+++ b/src/model/event.rs
@@ -34,11 +34,13 @@ pub struct Event {
}
#[derive(Serialize, Debug)]
-pub struct EventWithUserAndTriptype {
+pub struct EventWithDetails {
#[serde(flatten)]
pub event: Event,
trip_type: Option,
+ tripdetails: TripDetails,
cox_needed: bool,
+ cancelled: bool,
cox: Vec,
rower: Vec,
}
@@ -116,6 +118,12 @@ pub struct EventUpdate<'a> {
pub trip_type_id: Option,
}
+impl EventUpdate<'_> {
+ fn cancelled(&self) -> bool {
+ self.max_people == -1
+ }
+}
+
impl Event {
pub async fn find_by_id(db: &SqlitePool, id: i64) -> Option {
sqlx::query_as!(
@@ -134,16 +142,13 @@ WHERE planned_event.id like ?
.ok()
}
- pub async fn get_pinned_for_day(
- db: &SqlitePool,
- day: NaiveDate,
- ) -> Vec {
+ pub async fn get_pinned_for_day(db: &SqlitePool, day: NaiveDate) -> Vec {
let mut events = Self::get_for_day(db, day).await;
events.retain(|e| e.event.always_show);
events
}
- pub async fn get_for_day(db: &SqlitePool, day: NaiveDate) -> Vec {
+ pub async fn get_for_day(db: &SqlitePool, day: NaiveDate) -> Vec {
let day = format!("{day}");
let events = sqlx::query_as!(
Event,
@@ -164,10 +169,15 @@ WHERE day=?",
if let Some(trip_type_id) = event.trip_type_id {
trip_type = TripType::find_by_id(db, trip_type_id).await;
}
- ret.push(EventWithUserAndTriptype {
+ let tripdetails = TripDetails::find_by_id(db, event.trip_details_id)
+ .await
+ .expect("db constraints");
+ ret.push(EventWithDetails {
cox_needed: event.planned_amount_cox > cox.len() as i64,
cox,
rower: Registration::all_rower(db, event.trip_details_id).await,
+ cancelled: tripdetails.cancelled(),
+ tripdetails,
event,
trip_type,
});
@@ -315,7 +325,7 @@ WHERE trip_details.id=?
.unwrap(); //Okay, as planned_event can only be created with proper DB backing
let tripdetails = self.trip_details(db).await;
- let was_already_cancelled = tripdetails.max_people == 0;
+ let was_already_cancelled = tripdetails.cancelled();
sqlx::query!(
"UPDATE trip_details SET max_people = ?, notes = ?, always_show = ?, is_locked = ?, trip_type_id = ? WHERE id = ?",
@@ -340,7 +350,7 @@ WHERE trip_details.id=?
.await;
}
- if update.max_people == 0 && !was_already_cancelled {
+ if update.cancelled() && !was_already_cancelled {
let coxes = Registration::all_cox(db, self.id).await;
for user in coxes {
if let Some(user) = User::find_by_name(db, &user.name).await {
@@ -389,7 +399,7 @@ WHERE trip_details.id=?
}
}
}
- if update.max_people > 0 && was_already_cancelled {
+ if !update.cancelled() && was_already_cancelled {
Notification::delete_by_action(
db,
&format!("remove_user_trip_with_trip_details_id:{}", tripdetails.id),
@@ -427,7 +437,7 @@ WHERE trip_details.id=?
}
pub fn is_cancelled(&self) -> bool {
- self.max_people == 0
+ self.max_people == -1
}
pub async fn get_ics_feed(db: &SqlitePool) -> String {
diff --git a/src/model/mod.rs b/src/model/mod.rs
index 3a76436..b9d45d3 100644
--- a/src/model/mod.rs
+++ b/src/model/mod.rs
@@ -6,7 +6,7 @@ use waterlevel::WaterlevelDay;
use crate::AMOUNT_DAYS_TO_SHOW_TRIPS_AHEAD;
use self::{
- event::{Event, EventWithUserAndTriptype},
+ event::{Event, EventWithDetails},
trip::{Trip, TripWithUserAndType},
waterlevel::Waterlevel,
weather::Weather,
@@ -44,7 +44,7 @@ pub mod weather;
#[derive(Serialize, Debug)]
pub struct Day {
day: NaiveDate,
- events: Vec,
+ events: Vec,
trips: Vec,
is_pinned: bool,
regular_sees_this_day: bool,
diff --git a/src/model/notification.rs b/src/model/notification.rs
index 8a49f37..1c74a1a 100644
--- a/src/model/notification.rs
+++ b/src/model/notification.rs
@@ -278,7 +278,7 @@ mod test {
let cancel_update = EventUpdate {
name: &event.name,
planned_amount_cox: event.planned_amount_cox as i32,
- max_people: 0,
+ max_people: -1,
notes: event.notes.as_deref(),
always_show: event.always_show,
is_locked: event.is_locked,
diff --git a/src/model/trip.rs b/src/model/trip.rs
index eb5f82f..7565ea0 100644
--- a/src/model/trip.rs
+++ b/src/model/trip.rs
@@ -46,6 +46,12 @@ pub struct TripUpdate<'a> {
pub is_locked: bool,
}
+impl<'a> TripUpdate<'a> {
+ fn cancelled(&self) -> bool {
+ self.max_people == -1
+ }
+}
+
impl TripWithUserAndType {
pub async fn from(db: &SqlitePool, trip: Trip) -> Self {
let mut trip_type = None;
@@ -245,7 +251,7 @@ WHERE trip.id=?
return Err(CoxHelpError::DetailsLocked);
}
- if event.max_people == 0 {
+ if event.is_cancelled() {
return Err(CoxHelpError::CanceledEvent);
}
@@ -309,9 +315,9 @@ WHERE day=?
};
let tripdetails = TripDetails::find_by_id(db, trip_details_id).await.unwrap();
- let was_already_cancelled = tripdetails.max_people == 0;
+ let was_already_cancelled = tripdetails.cancelled();
- let is_locked = if update.max_people == 0 {
+ let is_locked = if update.cancelled() {
false
} else {
update.is_locked
@@ -329,7 +335,7 @@ WHERE day=?
.await
.unwrap(); //Okay, as trip_details can only be created with proper DB backing
- if update.max_people == 0 && !was_already_cancelled {
+ if update.cancelled() && !was_already_cancelled {
let rowers = TripWithUserAndType::from(db, update.trip.clone())
.await
.rower;
@@ -368,7 +374,7 @@ WHERE day=?
.await;
}
- if update.max_people > 0 && was_already_cancelled {
+ if !update.cancelled() && was_already_cancelled {
Notification::delete_by_action(
db,
&format!("remove_user_trip_with_trip_details_id:{}", trip_details_id),
@@ -463,7 +469,7 @@ WHERE day=?
}
fn is_cancelled(&self) -> bool {
- self.max_people == 0
+ self.max_people == -1
}
}
diff --git a/src/model/tripdetails.rs b/src/model/tripdetails.rs
index eebafce..46b74f5 100644
--- a/src/model/tripdetails.rs
+++ b/src/model/tripdetails.rs
@@ -95,7 +95,7 @@ WHERE day = ? AND planned_starting_time = ?
}
pub fn cancelled(&self) -> bool {
- self.max_people == 0
+ self.max_people == -1
}
/// This function is called when a person registers to a trip or when the cox changes the
diff --git a/templates/planned.html.tera b/templates/planned.html.tera
index 4310925..e315430 100644
--- a/templates/planned.html.tera
+++ b/templates/planned.html.tera
@@ -124,7 +124,7 @@
{% if event.always_show and not day.regular_sees_this_day %}
🔮
{% endif -%}
- {%- if event.max_people == 0 %}
+ {%- if event.cancelled %}
⚠ Absage
{{ event.planned_starting_time }}
Uhr
@@ -202,7 +202,7 @@
{# --- START List Coxes --- #}
{% if event.planned_amount_cox > 0 %}
- {% if event.max_people == 0 %}
+ {% if event.cancelled %}
{{ macros::box(participants=event.cox, empty_seats="", header='Absage', bg='[#f43f5e]') }}
{% else %}
{% if amount_cox_missing > 0 %}
@@ -215,7 +215,7 @@
{# --- END List Coxes --- #}
{# --- START List Rowers --- #}
{% set amount_cur_rower = event.rower | length %}
- {% if event.max_people == 0 %}
+ {% if event.cancelled %}
{{ macros::box(header='Absage', bg='[#f43f5e]', participants=event.rower, trip_details_id=event.trip_details_id, allow_removing="manage_events" in loggedin_user.roles) }}
{% else %}
{{ macros::box(participants=event.rower, empty_seats=event.max_people - amount_cur_rower, bg='primary-100', color='black', trip_details_id=event.trip_details_id, allow_removing="manage_events" in loggedin_user.roles) }}
@@ -240,7 +240,7 @@
{{ macros::input(label='Titel', name='name', type='input', value=event.name) }}
- {{ macros::input(label='Anzahl Ruderer', name='max_people', type='number', required=true, value=event.max_people, min='1') }}
+ {{ macros::input(label='Anzahl Ruderer', name='max_people', type='number', required=true, value=event.max_people, min='0') }}
{{ macros::input(label='Anzahl Steuerleute', name='planned_amount_cox', type='number', value=event.planned_amount_cox, required=true, min='0') }}
{{ macros::checkbox(label='Immer anzeigen', name='always_show', id=event.id,checked=event.always_show) }}
{{ macros::checkbox(label='Gesperrt', name='is_locked', id=event.id,checked=event.is_locked) }}
@@ -260,7 +260,7 @@
{% else %}
- {% if event.max_people == 0 %}
+ {% if event.cancelled %}
Wenn du deine Absage absagen (:^)) willst, einfach entsprechende Anzahl an Ruderer oben eintragen.
{% else %}
@@ -269,9 +269,8 @@
{{ macros::input(label='Grund der Absage', name='notes', type='input', value='') }}
- {{ macros::input(label='', name='max_people', type='hidden', value=0) }}
+ {{ macros::input(label='', name='max_people', type='hidden', value=-1) }}
{{ macros::input(label='', name='name', type='hidden', value=event.name) }}
- {{ macros::input(label='', name='max_people', type='hidden', value=event.max_people) }}
{{ macros::input(label='', name='planned_amount_cox', type='hidden', value=event.planned_amount_cox) }}
{{ macros::input(label='', name='always_show', type='hidden', value=event.always_show) }}
{{ macros::input(label='', name='is_locked', type='hidden', value=event.is_locked) }}
@@ -398,7 +397,7 @@