19 Commits

Author SHA1 Message Date
67d5df9c18 Merge pull request 'Fill acitivites from various activities; Fixes #972' (#977) from single-user-edit-page into staging
All checks were successful
CI/CD Pipeline / test (push) Successful in 15m29s
CI/CD Pipeline / deploy-staging (push) Successful in 7m49s
CI/CD Pipeline / deploy-main (push) Has been skipped
Reviewed-on: #977
2025-05-04 19:05:38 +02:00
3ffc44a5a2 Merge pull request 'single-user-edit-page' (#975) from single-user-edit-page into staging
All checks were successful
CI/CD Pipeline / test (push) Successful in 15m18s
CI/CD Pipeline / deploy-staging (push) Successful in 7m57s
CI/CD Pipeline / deploy-main (push) Has been skipped
Reviewed-on: #975
2025-05-04 10:48:41 +02:00
bd2686fa9c Merge pull request 'add notes' (#973) from single-user-edit-page into staging
All checks were successful
CI/CD Pipeline / test (push) Successful in 14m2s
CI/CD Pipeline / deploy-staging (push) Successful in 7m5s
CI/CD Pipeline / deploy-main (push) Has been skipped
Reviewed-on: #973
2025-05-03 21:37:31 +02:00
495ee666cd Merge pull request 'single-user-edit-page' (#971) from single-user-edit-page into staging
All checks were successful
CI/CD Pipeline / test (push) Successful in 15m33s
CI/CD Pipeline / deploy-staging (push) Successful in 7m46s
CI/CD Pipeline / deploy-main (push) Has been skipped
Reviewed-on: #971
2025-05-03 19:20:17 +02:00
5296b6a6c1 Merge pull request 'single-user-edit-page' (#970) from single-user-edit-page into staging
All checks were successful
CI/CD Pipeline / test (push) Successful in 13m54s
CI/CD Pipeline / deploy-staging (push) Successful in 7m8s
CI/CD Pipeline / deploy-main (push) Has been skipped
Reviewed-on: #970
2025-05-03 13:46:39 +02:00
49e657ab54 Merge pull request 'single-user-edit-page' (#968) from single-user-edit-page into staging
All checks were successful
CI/CD Pipeline / test (push) Successful in 13m27s
CI/CD Pipeline / deploy-staging (push) Successful in 6m34s
CI/CD Pipeline / deploy-main (push) Has been skipped
Reviewed-on: #968
2025-05-02 18:16:53 +02:00
25bbaca0d3 Merge pull request 'show payment status in user view; Fixes #965' (#967) from single-user-edit-page into staging
All checks were successful
CI/CD Pipeline / test (push) Successful in 14m54s
CI/CD Pipeline / deploy-staging (push) Successful in 8m6s
CI/CD Pipeline / deploy-main (push) Has been skipped
Reviewed-on: #967
2025-04-30 23:32:36 +02:00
26038eabe4 Merge pull request 'single-user-edit-page' (#966) from single-user-edit-page into staging
All checks were successful
CI/CD Pipeline / test (push) Successful in 14m39s
CI/CD Pipeline / deploy-staging (push) Successful in 7m3s
CI/CD Pipeline / deploy-main (push) Has been skipped
Reviewed-on: #966
2025-04-30 22:32:46 +02:00
57acd92e7c Merge pull request 'fix-list-scheckbuch' (#960) from fix-list-scheckbuch into staging
All checks were successful
CI/CD Pipeline / test (push) Successful in 14m37s
CI/CD Pipeline / deploy-staging (push) Successful in 7m3s
CI/CD Pipeline / deploy-main (push) Has been skipped
Reviewed-on: #960
2025-04-29 23:01:50 +02:00
c136c60e62 Merge pull request 'separate-scheckbuch-user' (#948) from separate-scheckbuch-user into staging
All checks were successful
CI/CD Pipeline / test (push) Successful in 14m17s
CI/CD Pipeline / deploy-staging (push) Successful in 6m45s
CI/CD Pipeline / deploy-main (push) Has been skipped
Reviewed-on: #948
2025-04-29 21:06:00 +02:00
a5e90ea014 Merge pull request 'log-event-updates' (#946) from log-event-updates into staging
Some checks failed
CI/CD Pipeline / test (push) Failing after 10m29s
CI/CD Pipeline / deploy-staging (push) Has been skipped
CI/CD Pipeline / deploy-main (push) Has been skipped
Reviewed-on: #946
2025-04-29 20:37:11 +02:00
f0f3909239 Merge pull request 'format-cal-according-to-standard' (#944) from format-cal-according-to-standard into staging
All checks were successful
CI/CD Pipeline / test (push) Successful in 14m35s
CI/CD Pipeline / deploy-staging (push) Successful in 21m47s
CI/CD Pipeline / deploy-main (push) Has been skipped
Reviewed-on: #944
2025-04-28 22:20:39 +02:00
1438bbe3a8 Merge pull request 'hide-box' (#935) from hide-box into staging
All checks were successful
CI/CD Pipeline / test (push) Successful in 16m53s
CI/CD Pipeline / deploy-staging (push) Successful in 7m59s
CI/CD Pipeline / deploy-main (push) Has been skipped
Reviewed-on: #935
2025-04-19 21:30:20 +02:00
a910cd745d Merge pull request 'document nextcloud integration, for future nextcloud setups' (#933) from doc-nextcloud-integration into staging
All checks were successful
CI/CD Pipeline / test (push) Successful in 15m47s
CI/CD Pipeline / deploy-staging (push) Successful in 7m25s
CI/CD Pipeline / deploy-main (push) Has been skipped
Reviewed-on: #933
2025-04-19 09:19:58 +02:00
6265440288 Merge pull request 'zero-rower-events' (#931) from zero-rower-events into staging
All checks were successful
CI/CD Pipeline / test (push) Successful in 16m10s
CI/CD Pipeline / deploy-staging (push) Successful in 7m42s
CI/CD Pipeline / deploy-main (push) Has been skipped
Reviewed-on: #931
2025-04-19 00:22:27 +02:00
3baed66ebc Merge pull request 'also be able to cancel trips (not only events)' (#929) from zero-rower-events into staging
Some checks failed
CI/CD Pipeline / test (push) Failing after 13m39s
CI/CD Pipeline / deploy-staging (push) Has been skipped
CI/CD Pipeline / deploy-main (push) Has been skipped
Reviewed-on: #929
2025-04-18 23:32:34 +02:00
499ce06438 Merge pull request 'zero-rower-events; Fixes #913' (#927) from zero-rower-events into staging
Some checks failed
CI/CD Pipeline / test (push) Has started running
CI/CD Pipeline / deploy-staging (push) Has been cancelled
CI/CD Pipeline / deploy-main (push) Has been cancelled
Reviewed-on: #927
2025-04-18 23:12:41 +02:00
67e5277c62 Merge pull request 'remove unused dep; cargo clippy' (#925) from simple-nx-auth into staging
All checks were successful
CI/CD Pipeline / test (push) Successful in 15m20s
CI/CD Pipeline / deploy-staging (push) Successful in 7m38s
CI/CD Pipeline / deploy-main (push) Has been skipped
Reviewed-on: #925
2025-04-18 17:45:44 +02:00
ce154bf060 Merge pull request 'simple-nx-auth' (#923) from simple-nx-auth into staging
All checks were successful
CI/CD Pipeline / test (push) Successful in 16m3s
CI/CD Pipeline / deploy-staging (push) Successful in 7m41s
CI/CD Pipeline / deploy-main (push) Has been skipped
Reviewed-on: #923
2025-04-18 17:10:44 +02:00
54 changed files with 317 additions and 1282 deletions

View File

@@ -4,8 +4,4 @@
.h2 {
@apply font-bold uppercase tracking-wide text-center rounded-t-md text-primary-950 dark:text-white bg-gray-200 dark:bg-primary-950 bg-opacity-80 text-lg px-3 py-3;
}
.h3 {
@apply text-center text-xl uppercase tracking-wide font-bold text-primary-900 dark:text-white;
}

View File

@@ -11,10 +11,6 @@
@apply text-white hover:text-primary-100 underline;
}
&-black {
@apply text-black hover:text-primary-950 dark:text-white hover:dark:text-primary-300 underline;
}
&-no-underline {
@apply no-underline;
}

View File

@@ -8,7 +8,7 @@ use rot::rest;
use rot::tera;
use rot::{scheduled, tera::Config};
use sqlx::{ConnectOptions, pool::PoolOptions, sqlite::SqliteConnectOptions};
use sqlx::{pool::PoolOptions, sqlite::SqliteConnectOptions, ConnectOptions};
#[macro_use]
extern crate rocket;

View File

@@ -1,6 +1,6 @@
use std::ops::DerefMut;
use super::{role::Role, user::User};
use super::user::User;
use chrono::NaiveDateTime;
use serde::{Deserialize, Serialize};
use sqlx::{FromRow, Sqlite, SqlitePool, Transaction};
@@ -21,7 +21,6 @@ pub struct ActivityBuilder {
}
impl ActivityBuilder {
#[must_use]
pub fn new(text: &str) -> Self {
Self {
text: text.into(),
@@ -30,7 +29,6 @@ impl ActivityBuilder {
}
}
#[must_use]
pub fn relevant_for_user(self, user: &User) -> Self {
Self {
relevant_for: format!("{}user-{};", self.relevant_for, user.id),
@@ -38,14 +36,6 @@ impl ActivityBuilder {
}
}
#[must_use]
pub fn relevant_for_role(self, role: &Role) -> Self {
Self {
relevant_for: format!("{}role-{};", self.relevant_for, role.id),
..self
}
}
pub async fn save(self, db: &SqlitePool) {
Activity::create(db, &self.text, &self.relevant_for, self.keep_until).await;
}

View File

@@ -2,8 +2,8 @@ use std::ops::DerefMut;
use chrono::NaiveDateTime;
use itertools::Itertools;
use rocket::FromForm;
use rocket::serde::{Deserialize, Serialize};
use rocket::FromForm;
use sqlx::{FromRow, Sqlite, SqlitePool, Transaction};
use crate::model::boathouse::Boathouse;

View File

@@ -1,7 +1,7 @@
use crate::model::{boat::Boat, user::User};
use chrono::NaiveDateTime;
use rocket::FromForm;
use rocket::serde::{Deserialize, Serialize};
use rocket::FromForm;
use sqlx::{FromRow, SqlitePool};
use super::log::Log;

View File

@@ -2,8 +2,8 @@ use std::io::Write;
use chrono::{Duration, NaiveDate, NaiveTime};
use ics::{
ICalendar,
properties::{DtEnd, DtStart, Summary},
ICalendar,
};
use serde::Serialize;
use sqlx::{FromRow, Row, SqlitePool};
@@ -578,11 +578,6 @@ mod test {
let today = Local::now().date_naive().format("%Y%m%d").to_string();
let actual = Event::get_ics_feed(&pool).await;
assert_eq!(
format!(
"BEGIN:VCALENDAR\r\nVERSION:2.0\r\nPRODID:ics-rs\r\nBEGIN:VEVENT\r\nUID:event-1@rudernlinz.at\r\nDTSTAMP:19900101T180000\r\nDTSTART:{today}T100000\r\nDTEND:{today}T130000\r\nSUMMARY:test-planned-event \r\nEND:VEVENT\r\nEND:VCALENDAR\r\n"
),
actual
);
assert_eq!(format!("BEGIN:VCALENDAR\r\nVERSION:2.0\r\nPRODID:ics-rs\r\nBEGIN:VEVENT\r\nUID:event-1@rudernlinz.at\r\nDTSTAMP:19900101T180000\r\nDTSTART:{today}T100000\r\nDTEND:{today}T130000\r\nSUMMARY:test-planned-event \r\nEND:VEVENT\r\nEND:VCALENDAR\r\n"), actual);
}
}

View File

@@ -1,7 +1,7 @@
use std::ops::DerefMut;
use serde::Serialize;
use sqlx::{FromRow, Sqlite, SqlitePool, Transaction, sqlite::SqliteQueryResult};
use sqlx::{sqlite::SqliteQueryResult, FromRow, Sqlite, SqlitePool, Transaction};
use super::user::User;

View File

@@ -823,13 +823,7 @@ ORDER BY departure DESC
if difference > Duration::hours(1) {
let vorstand = Role::find_by_name(db, "Vorstand").await.unwrap();
let logbook = LogbookWithBoatAndRowers::from(db, self.clone()).await;
let mut msg = format!(
"{} hat folgenden Logbuch-Eintrag jetzt gelöscht, welcher bereits vor über einer Stunde begonnen wurde: Schiffsführer: {}, Steuerperson: {}, Abfahrt: {}",
user.name,
logbook.steering_user.name,
logbook.steering_user.name,
logbook.logbook.departure.format("%Y-%m-%d %H:%M")
);
let mut msg = format!("{} hat folgenden Logbuch-Eintrag jetzt gelöscht, welcher bereits vor über einer Stunde begonnen wurde: Schiffsführer: {}, Steuerperson: {}, Abfahrt: {}", user.name, logbook.steering_user.name, logbook.steering_user.name, logbook.logbook.departure.format("%Y-%m-%d %H:%M"));
if let Some(destination) = logbook.logbook.destination {
msg.push_str(&format!(", Ziel: {}", destination));
} else {

View File

@@ -1,9 +1,9 @@
use std::{error::Error, fs};
use lettre::{
Address, Message, SmtpTransport, Transport,
message::{Attachment, MultiPart, SinglePart, header::ContentType},
message::{header::ContentType, Attachment, MultiPart, SinglePart},
transport::smtp::authentication::Credentials,
Address, Message, SmtpTransport, Transport,
};
use sqlx::{Sqlite, SqlitePool, Transaction};

View File

@@ -1,6 +1,6 @@
use std::io::Write;
use ics::{ICalendar, components::Property};
use ics::{components::Property, ICalendar};
use sqlx::SqlitePool;
use crate::model::{event::Event, trip::Trip, user::User};

View File

@@ -1,6 +1,5 @@
use std::{cmp::Ordering, fmt::Display, ops::DerefMut};
use super::{activity::ActivityBuilder, user::AdminUser};
use serde::{Deserialize, Serialize};
use sqlx::{FromRow, Sqlite, SqlitePool, Transaction};
@@ -135,30 +134,6 @@ WHERE name like ?
.ok()
}
pub async fn update(
&self,
db: &SqlitePool,
updated_by: &AdminUser,
formatted_name: &str,
desc: &str,
) -> Result<(), String> {
sqlx::query!(
"UPDATE role SET formatted_name=?, desc=? WHERE id=?",
formatted_name,
desc,
self.id
)
.execute(db)
.await
.map_err(|e| e.to_string())?;
ActivityBuilder::new(&format!(
"{updated_by} hat Rolle {self} von {self:#?} auf FORMATTED_NAME={formatted_name}, DESC={desc} aktualisiert."
)).relevant_for_role(self).save(db).await;
Ok(())
}
pub async fn names_from_role(&self, db: &SqlitePool) -> Vec<String> {
let query = format!(
"SELECT u.name

View File

@@ -567,11 +567,9 @@ mod test {
let last_notification = &Notification::for_user(&pool, &cox).await[0];
assert!(
last_notification
.message
.starts_with("cox2 hat eine Ausfahrt zur selben Zeit")
);
assert!(last_notification
.message
.starts_with("cox2 hat eine Ausfahrt zur selben Zeit"));
}
#[sqlx::test]

View File

@@ -20,7 +20,7 @@ impl User {
let note = note.trim();
ActivityBuilder::new(&format!("({updated_by}) {note}"))
.relevant_for_user(user)
.relevant_for_user(&user)
.save(db)
.await;
@@ -48,9 +48,7 @@ impl User {
let msg = match &self.mail {
Some(old_mail) => {
format!(
"{updated_by} hat die Mail-Adresse von {self} von {old_mail} auf {new_mail} geändert."
)
format!("{updated_by} hat die Mail-Adresse von {self} von {old_mail} auf {new_mail} geändert.")
}
None => {
format!("{updated_by} eine neue Mail-Adresse für {self} hinzugefügt: {new_mail}")
@@ -89,15 +87,9 @@ impl User {
query.execute(db).await.unwrap(); //Okay, because we can only create a User of a valid id
let msg = match &self.phone {
Some(old_phone) if new_phone.is_empty() => format!(
"{updated_by} hat die Telefonnummer von {self} entfernt (alte Nummer: {old_phone})"
),
Some(old_phone) => format!(
"{updated_by} hat die Telefonnummer von {self} von {old_phone} auf {new_phone} geändert."
),
None => format!(
"{updated_by} hat eine neue Telefonnummer für {self} hinzugefügt: {new_phone}"
),
Some(old_phone) if new_phone.is_empty() => format!("{updated_by} hat die Telefonnummer von {self} entfernt (alte Nummer: {old_phone})"),
Some(old_phone) => format!("{updated_by} hat die Telefonnummer von {self} von {old_phone} auf {new_phone} geändert."),
None => format!("{updated_by} hat eine neue Telefonnummer für {self} hinzugefügt: {new_phone}")
};
ActivityBuilder::new(&msg)
@@ -115,7 +107,7 @@ impl User {
let new_address = new_address.trim();
let query = if new_address.is_empty() {
if self.address.is_none() {
if !self.address.is_none() {
return; // nothing to do
}
sqlx::query!("UPDATE user SET address = NULL where id = ?", self.id)
@@ -134,13 +126,9 @@ impl User {
query.execute(db).await.unwrap(); //Okay, because we can only create a User of a valid id
let msg = match &self.address {
Some(old_address) if new_address.is_empty() => format!(
"{updated_by} hat die Adresse von {self} entfernt (alte Adresse: {old_address})"
),
Some(old_address) => format!(
"{updated_by} hat die Adresse von {self} von {old_address} auf {new_address} geändert."
),
None => format!("{updated_by} hat eine Adresse für {self} hinzugefügt: {new_address}"),
Some(old_address) if new_address.is_empty() => format!("{updated_by} hat die Adresse von {self} entfernt (alte Adresse: {old_address})"),
Some(old_address) => format!("{updated_by} hat die Adresse von {self} von {old_address} auf {new_address} geändert."),
None => format!("{updated_by} hat eine Adresse für {self} hinzugefügt: {new_address}")
};
ActivityBuilder::new(&msg)
@@ -169,15 +157,9 @@ impl User {
query.execute(db).await.unwrap(); //Okay, because we can only create a User of a valid id
let msg = match &self.nickname {
Some(old_nickname) if new_nickname.is_empty() => format!(
"{updated_by} hat den Sitznamen von {self} entfernt (alter Spitzname: {old_nickname})"
),
Some(old_nickname) => format!(
"{updated_by} hat den Spitznamen von {self} von {old_nickname} auf {new_nickname} geändert."
),
None => format!(
"{updated_by} hat einen neuen Spitznamen für {self} hinzugefügt: {new_nickname}"
),
Some(old_nickname) if new_nickname.is_empty() => format!("{updated_by} hat den Sitznamen von {self} entfernt (alter Spitzname: {old_nickname})"),
Some(old_nickname) => format!("{updated_by} hat den Spitznamen von {self} von {old_nickname} auf {new_nickname} geändert."),
None => format!("{updated_by} hat einen neuen Spitznamen für {self} hinzugefügt: {new_nickname}")
};
ActivityBuilder::new(&msg)
.relevant_for_user(self)
@@ -203,12 +185,8 @@ impl User {
.unwrap(); //Okay, because we can only create a User of a valid id
let msg = match &self.member_since_date {
Some(old_member_since_date) => format!(
"{updated_by} hat das Beitrittsdatum von {self} von {old_member_since_date} auf {new_member_since_date} geändert."
),
None => format!(
"{updated_by} hat ein neues Beitrittsdatum für {self} hinzugefügt: {new_member_since_date}"
),
Some(old_member_since_date) => format!("{updated_by} hat das Beitrittsdatum von {self} von {old_member_since_date} auf {new_member_since_date} geändert."),
None => format!("{updated_by} hat ein neues Beitrittsdatum für {self} hinzugefügt: {new_member_since_date}")
};
ActivityBuilder::new(&msg)
@@ -232,13 +210,9 @@ impl User {
.await
.unwrap(); //Okay, because we can only create a User of a valid id
let msg = match &self.birthdate {
Some(old_birthdate) => format!(
"{updated_by} hat das Geburtsdatum von {self} von {old_birthdate} auf {new_birthdate} geändert."
),
None => {
format!("{updated_by} hat ein Geburtsdatum für {self} hinzugefügt: {new_birthdate}")
}
let msg = match &self.birthdate{
Some(old_birthdate) => format!("{updated_by} hat das Geburtsdatum von {self} von {old_birthdate} auf {new_birthdate} geändert."),
None => format!("{updated_by} hat ein Geburtsdatum für {self} hinzugefügt: {new_birthdate}")
};
ActivityBuilder::new(&msg)
@@ -298,7 +272,7 @@ impl User {
let bootsfuehrer = Role::find_by_name(db, "Bootsführer").await.unwrap();
match (old_skill, skill) {
(None, new) if new == Some(cox.clone()) => {
(old, new) if old == None && new == Some(cox.clone()) => {
self.add_role(db, updated_by, &cox).await?;
Notification::create_for_role(
db,
@@ -335,7 +309,7 @@ impl User {
.save(db)
.await;
}
(old, None) => {
(old, new) if new == None => {
if let Some(old) = old {
self.remove_role(db, updated_by, &old).await?;
let vorstand = Role::find_by_name(db, "Vorstand").await.unwrap();
@@ -386,7 +360,7 @@ impl User {
ActivityBuilder::new(&format!(
"{updated_by} hat die Ermäßigung von {self} von {old} auf {new} geändert"
))
.relevant_for_user(self)
.relevant_for_user(&self)
.save(db)
.await;
@@ -400,9 +374,7 @@ impl User {
role: &Role,
) -> Result<(), String> {
if !self.has_role(db, &role.name).await {
return Err(format!(
"Kann Rolle {role} von User {self} nicht entfernen, da der User die Rolle gar nicht hat"
));
return Err(format!("Kann Rolle {role} von User {self} nicht entfernen, da der User die Rolle gar nicht hat"));
}
sqlx::query!(
@@ -414,14 +386,12 @@ impl User {
.await
.unwrap();
if !role.hide_in_lists && role.cluster.is_none() {
ActivityBuilder::new(&format!(
"{updated_by} hat die Rolle {role} von {self} entfernt."
))
.relevant_for_user(self)
.save(db)
.await;
}
ActivityBuilder::new(&format!(
"{updated_by} hat die Rolle {role} von {self} entfernt."
))
.relevant_for_user(&self)
.save(db)
.await;
Ok(())
}
@@ -445,7 +415,7 @@ impl User {
ActivityBuilder::new(&format!(
"{updated_by} hat den Bezahlstatus von {self} auf 'nicht bezahlt' gesetzt."
))
.relevant_for_user(self)
.relevant_for_user(&self)
.save(db)
.await;
}
@@ -468,7 +438,7 @@ impl User {
ActivityBuilder::new(&format!(
"{updated_by} hat den Bezahlstatus von {self} auf 'bezahlt' gesetzt."
))
.relevant_for_user(self)
.relevant_for_user(&self)
.save(db)
.await;
}
@@ -480,9 +450,7 @@ impl User {
role: &Role,
) -> Result<(), String> {
if self.has_role(db, &role.name).await {
return Err(format!(
"Kann Rolle {role} von User {self} nicht hinzufügen, da der User die Rolle schon hat"
));
return Err(format!("Kann Rolle {role} von User {self} nicht hinzufügen, da der User die Rolle schon hat"));
}
sqlx::query!(
@@ -501,11 +469,11 @@ impl User {
)
})?;
if !role.hide_in_lists && role.cluster.is_none() {
if !role.hide_in_lists {
ActivityBuilder::new(&format!(
"{updated_by} hat die Rolle '{role}' dem Benutzer {self} hinzugefügt."
))
.relevant_for_user(self)
.relevant_for_user(&self)
.save(db)
.await;
}
@@ -523,7 +491,7 @@ impl User {
return Err(format!("User {self} hat bereits eine Beitrittserklärung."));
}
if membership_pdf.len() == 0 {
return Err("Keine Beitrittserklärung mitgeschickt.".to_string());
return Err(format!("Keine Beitrittserklärung mitgeschickt."));
}
let mut stream = membership_pdf.open().await.unwrap();
@@ -541,7 +509,7 @@ impl User {
ActivityBuilder::new(&format!(
"{updated_by} hat die Mitgliedserklärung (PDF) für user {self} hinzugefügt."
))
.relevant_for_user(self)
.relevant_for_user(&self)
.save(db)
.await;

View File

@@ -1,7 +1,7 @@
use super::User;
use crate::{
BOAT_STORAGE, EINSCHREIBGEBUEHR, FAMILY_THREE_OR_MORE, FAMILY_TWO, FOERDERND, REGULAR,
RENNRUDERBEITRAG, STUDENT_OR_PUPIL, UNTERSTUETZEND, model::family::Family,
model::family::Family, BOAT_STORAGE, EINSCHREIBGEBUEHR, FAMILY_THREE_OR_MORE, FAMILY_TWO,
FOERDERND, REGULAR, RENNRUDERBEITRAG, STUDENT_OR_PUPIL, UNTERSTUETZEND,
};
use chrono::{Datelike, Local, NaiveDate};
use serde::Serialize;

View File

@@ -1,17 +1,13 @@
use super::{ManageUserUser, User, regular::ClubMember};
use super::User;
use crate::{
NonEmptyString,
model::{activity::ActivityBuilder, mail::Mail, notification::Notification, role::Role},
model::{activity::ActivityBuilder, mail::Mail},
special_user,
};
use chrono::NaiveDate;
use rocket::{async_trait, fs::TempFile};
use rocket::async_trait;
use sqlx::SqlitePool;
special_user!(FoerderndUser, +"Förderndes Mitglied");
impl ClubMember for FoerderndUser {}
impl FoerderndUser {
pub(crate) async fn send_welcome_mail_to_user(
&self,
@@ -45,57 +41,10 @@ ASKÖ Ruderverein Donau Linz", self.name),
ActivityBuilder::new(&format!(
"User {self} hat die Info-Mail bzgl. neues förderndes Mitglied (Handbuch und WLAN Infos) an {mail} gesendet bekommen"
))
.relevant_for_user(self)
.relevant_for_user(&self)
.save(db)
.await;
Ok(())
}
pub(crate) async fn create(
db: &SqlitePool,
created_by: &ManageUserUser,
smtp_pw: &str,
name: NonEmptyString,
mail: &str,
financial: Option<Role>,
birthdate: &NaiveDate,
member_since: &NaiveDate,
phone: NonEmptyString,
address: NonEmptyString,
membership_pdf: &TempFile<'_>,
) -> Result<(), String> {
let role = Role::find_by_name(db, "Förderndes Mitglied").await.unwrap();
let user = Self::create_member(
db,
created_by,
&role,
name,
mail,
financial,
birthdate,
member_since,
phone,
address,
membership_pdf,
)
.await?;
let user = Self::new(db, &user).await.unwrap();
user.send_welcome_mail_to_user(db, smtp_pw).await?;
if let Some(vorstand) = Role::find_by_name(db, "Vorstand").await {
Notification::create_for_role(
db,
&vorstand,
&format!("Lieber Vorstand, es gibt ein neues förderndes Mitglied: {user}"),
"Neues unterstützendes Vereinsmitglied",
None,
None,
)
.await;
}
Ok(())
}
}

View File

@@ -36,19 +36,19 @@ impl Member {
}
pub(crate) fn is_club_member(&self) -> bool {
matches!(
self,
Member::Regular(_) | Member::Foerdernd(_) | Member::Unterstuetzend(_)
)
match self {
Member::Regular(_) | Member::Foerdernd(_) | Member::Unterstuetzend(_) => true,
_ => false,
}
}
pub(crate) fn supposed_to_pay(&self) -> bool {
matches!(
self,
match self {
Member::Schnupperant(_)
| Member::Scheckbuch(_)
| Member::Regular(_)
| Member::Foerdernd(_)
| Member::Unterstuetzend(_)
)
| Member::Scheckbuch(_)
| Member::Regular(_)
| Member::Foerdernd(_)
| Member::Unterstuetzend(_) => true,
_ => false,
}
}
}

View File

@@ -458,7 +458,7 @@ ASKÖ Ruderverein Donau Linz", self.name),
smtp_pw,
).await?;
ActivityBuilder::new(&format!("User {self} hat eine Mail bekommen, dass seine 5 Ausfahrten mit der heutigen Ausfahrt aufgebraucht sind, und dass der nächste Schritt eine Vereinsmitgliedschaft wäre (inkl. Links zu Beitrittserklärung + Info, dass sie an info@ geschickt werden soll.")).relevant_for_user(self).save_tx(db).await;
ActivityBuilder::new(&format!("User {self} hat eine Mail bekommen, dass seine 5 Ausfahrten mit der heutigen Ausfahrt aufgebraucht sind, und dass der nächste Schritt eine Vereinsmitgliedschaft wäre (inkl. Links zu Beitrittserklärung + Info, dass sie an info@ geschickt werden soll.")).relevant_for_user(&self).save_tx(db).await;
Ok(())
}
@@ -541,7 +541,7 @@ ASKÖ Ruderverein Donau Linz", self.name),
// TODO: add responsible person
ActivityBuilder::new(&format!("Passwort von User {self} wurde zurückgesetzt."))
.relevant_for_user(self)
.relevant_for_user(&self)
.save(db)
.await;
}
@@ -555,7 +555,7 @@ ASKÖ Ruderverein Donau Linz", self.name),
ActivityBuilder::new(&format!(
"Passwort von User {self} wurde erfolgreich geändert."
))
.relevant_for_user(self)
.relevant_for_user(&self)
.save(db)
.await;
}
@@ -578,18 +578,18 @@ ASKÖ Ruderverein Donau Linz", self.name),
.await
.unwrap(); //Okay, because we can only create a User of a valid id
ActivityBuilder::new(&format!("User {self} hat sich eingeloggt."))
.relevant_for_user(self)
.relevant_for_user(&self)
.save(db)
.await;
}
pub async fn delete(&self, db: &SqlitePool, deleted_by: &ManageUserUser) {
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
ActivityBuilder::new(&format!("User {self} wurde von {deleted_by} gelöscht."))
.relevant_for_user(self)
ActivityBuilder::new(&format!("User {self} wurde gelöscht."))
.relevant_for_user(&self)
.save(db)
.await;
}
@@ -684,7 +684,7 @@ ASKÖ Ruderverein Donau Linz", self.name),
)
.await;
ActivityBuilder::new(&format!("5 Scheckbuchausfahrten von {self} wurden mit der heutigen Ausfahrt aufgebraucht. Info-Mail wurde an {self} geschickt + alle Steuerberechtigten informiert, dass wir pot. ein neues Mitglied haben"))
.relevant_for_user(self)
.relevant_for_user(&self)
.save_tx(db)
.await;
}
@@ -702,7 +702,7 @@ ASKÖ Ruderverein Donau Linz", self.name),
)
.await;
ActivityBuilder::new(&format!("{self} hat nun bereits die {amount_trips}. seiner 5 Scheckbuchausfahrten absolviert. Vorstand wurde via Notification informiert."))
.relevant_for_user(self)
.relevant_for_user(&self)
.save_tx(db)
.await;
}
@@ -727,7 +727,7 @@ ASKÖ Ruderverein Donau Linz", self.name),
ActivityBuilder::new(&format!(
"{self} hat das heurige Fahrtenabzeichen geschafft! Der Vorstand + {self} wurde via Notification informiert."
))
.relevant_for_user(self)
.relevant_for_user(&self)
.save_tx(db)
.await;
@@ -749,7 +749,7 @@ ASKÖ Ruderverein Donau Linz", self.name),
)
.await;
ActivityBuilder::new(&format!("{self} hat den Äquatorpreis in {level} geschafft! Der Vorstand + {self} wurde via Notification informiert."))
.relevant_for_user(self)
.relevant_for_user(&self)
.save_tx(db)
.await;
@@ -812,7 +812,7 @@ macro_rules! special_user {
#[async_trait]
impl<'r> rocket::request::FromRequest<'r> for $name {
type Error = $crate::model::user::LoginError;
type Error = crate::model::user::LoginError;
async fn from_request(req: &'r rocket::request::Request<'_>) -> rocket::request::Outcome<Self, Self::Error> {
let db = req.rocket().state::<SqlitePool>().unwrap();
match User::from_request(req).await {

View File

@@ -1,67 +1,13 @@
use super::{ManageUserUser, User};
use super::User;
use crate::{
NonEmptyString,
model::{activity::ActivityBuilder, mail::Mail, notification::Notification, role::Role},
model::{activity::ActivityBuilder, mail::Mail},
special_user,
};
use chrono::NaiveDate;
use rocket::{async_trait, fs::TempFile, tokio::io::AsyncReadExt};
use rocket::async_trait;
use sqlx::SqlitePool;
special_user!(RegularUser, +"Donau Linz");
pub trait ClubMember {
async fn create_member(
db: &SqlitePool,
created_by: &ManageUserUser,
role: &Role,
name: NonEmptyString,
mail: &str,
financial: Option<Role>,
birthdate: &NaiveDate,
member_since: &NaiveDate,
phone: NonEmptyString,
address: NonEmptyString,
membership_pdf: &TempFile<'_>,
) -> Result<User, String> {
if membership_pdf.len() == 0 {
return Err("Keine Beitrittserklärung mitgeschickt.".to_string());
}
let mut stream = membership_pdf.open().await.unwrap();
let mut buffer = Vec::new();
stream.read_to_end(&mut buffer).await.unwrap();
let name = name.as_str();
let phone = phone.as_str();
let address = address.as_str();
sqlx::query!(
"INSERT INTO user(name, member_since_date, birthdate, mail, phone, address, membership_pdf)
VALUES (?,?,?,?,?,?,?)",
name, member_since, birthdate, mail, phone, address,buffer
)
.execute(db)
.await
.map_err(|e| e.to_string())?;
let user = User::find_by_name(db, name).await.unwrap();
user.change_financial(db, created_by, financial).await?;
user.add_role(db, created_by, role).await?;
ActivityBuilder::new(&format!(
"{created_by} hat Mitglied {user} mit der Rolle {role} angelegt."
))
.relevant_for_user(&user)
.save(db)
.await;
Ok(user)
}
}
impl ClubMember for RegularUser {}
impl RegularUser {
pub(crate) async fn send_welcome_mail_to_user(
&self,
@@ -101,54 +47,10 @@ ASKÖ Ruderverein Donau Linz", self.name),
).await?;
ActivityBuilder::new(&format!("Willkommensmail für {self} wurde an {mail} verschickt (Handbuch, Signal-Gruppe, App-Info, Fingerprint, WLAN)."))
.relevant_for_user(self)
.relevant_for_user(&self)
.save(db)
.await;
Ok(())
}
pub(crate) async fn create(
db: &SqlitePool,
created_by: &ManageUserUser,
smtp_pw: &str,
name: NonEmptyString,
mail: &str,
financial: Option<Role>,
birthdate: &NaiveDate,
member_since: &NaiveDate,
phone: NonEmptyString,
address: NonEmptyString,
membership_pdf: &TempFile<'_>,
) -> Result<(), String> {
let role = Role::find_by_name(db, "Donau Linz").await.unwrap();
let user = Self::create_member(
db,
created_by,
&role,
name,
mail,
financial,
birthdate,
member_since,
phone,
address,
membership_pdf,
)
.await?;
let user = Self::new(db, &user).await.unwrap();
user.send_welcome_mail_to_user(db, smtp_pw).await?;
Notification::create_for_steering_people(
db,
&format!("Liebe Steuerberechtigte, es gibt ein neues Mitglied: {user} 🎉"),
"Neues Vereinsmitglied",
None,
None,
)
.await;
Ok(())
}
}

View File

@@ -2,13 +2,12 @@ use super::foerdernd::FoerderndUser;
use super::regular::RegularUser;
use super::unterstuetzend::UnterstuetzendUser;
use super::{ManageUserUser, User};
use crate::NonEmptyString;
use crate::model::activity::ActivityBuilder;
use crate::model::role::Role;
use crate::NonEmptyString;
use crate::{
SCHECKBUCH,
model::{mail::Mail, notification::Notification},
special_user,
special_user, SCHECKBUCH,
};
use chrono::NaiveDate;
use rocket::async_trait;
@@ -86,7 +85,7 @@ impl ScheckbuchUser {
.await;
ActivityBuilder::new(&format!(
"{changed_by} hat den Scheckbuch-User {self} auf ein reguläres Mitglied upgegraded! Die Steuerpersonen wurden via Notification informiert."
"{changed_by} hat den Scheckbuch-User {self} auf ein reguläres Mitglied upgegraded!"
))
.relevant_for_user(&self)
.save(db)
@@ -215,7 +214,7 @@ impl ScheckbuchUser {
ActivityBuilder::new(&format!(
"{self} hat eine Info-Mail bekommen (Erklärung Scheckbuch, Ruderapp) und alle Steuerberechtigten wurden informiert."
))
.relevant_for_user(self)
.relevant_for_user(&self)
.save(db)
.await;
@@ -234,7 +233,7 @@ impl ScheckbuchUser {
};
Mail::send_single(
db,
mail,
&mail,
"ASKÖ Ruderverein Donau Linz | Dein Scheckbuch wartet auf Dich",
format!(
"Hallo {0},
@@ -267,38 +266,4 @@ ASKÖ Ruderverein Donau Linz", self.name, SCHECKBUCH/100),
)
.await;
}
pub(crate) async fn create(
db: &SqlitePool,
created_by: &ManageUserUser,
smtp_pw: &str,
name: NonEmptyString,
mail: &str,
) -> Result<(), String> {
let role = Role::find_by_name(db, "scheckbuch").await.unwrap();
let name = name.as_str();
sqlx::query!(
"INSERT INTO user(name, mail)
VALUES (?,?)",
name,
mail
)
.execute(db)
.await
.map_err(|e| e.to_string())?;
let user = User::find_by_name(db, name).await.unwrap();
user.add_role(db, created_by, &role).await?;
let user = Self::new(db, &user).await.unwrap();
user.notify(db, smtp_pw).await?;
ActivityBuilder::new(&format!("{created_by} hat Scheckbuch {user} angelegt."))
.relevant_for_user(&user)
.save(db)
.await;
Ok(())
}
}

View File

@@ -4,9 +4,9 @@ use super::scheckbuch::ScheckbuchUser;
use super::schnupperinterest::SchnupperInterestUser;
use super::unterstuetzend::UnterstuetzendUser;
use super::{ManageUserUser, User};
use crate::NonEmptyString;
use crate::model::activity::ActivityBuilder;
use crate::model::role::Role;
use crate::NonEmptyString;
use crate::{
model::{mail::Mail, notification::Notification},
special_user,
@@ -108,7 +108,7 @@ impl SchnupperantUser {
self.user.add_role(db, changed_by, &scheckbook).await?;
if let Some(no_einschreibgebuehr) = Role::find_by_name(db, "no-einschreibgebuehr").await {
self.add_role(db, changed_by, &no_einschreibgebuehr)
self.add_role(db, &changed_by, &no_einschreibgebuehr)
.await
.expect("role doesn't have a group");
}
@@ -208,7 +208,7 @@ impl SchnupperantUser {
self.user.remove_role(db, changed_by, &scheckbook).await?;
self.user.add_role(db, changed_by, &unterstuetzend).await?;
if let Some(no_einschreibgebuehr) = Role::find_by_name(db, "no-einschreibgebuehr").await {
self.add_role(db, changed_by, &no_einschreibgebuehr)
self.add_role(db, &changed_by, &no_einschreibgebuehr)
.await
.expect("role doesn't have a group");
}
@@ -272,7 +272,7 @@ impl SchnupperantUser {
self.user.remove_role(db, changed_by, &scheckbook).await?;
self.user.add_role(db, changed_by, &unterstuetzend).await?;
if let Some(no_einschreibgebuehr) = Role::find_by_name(db, "no-einschreibgebuehr").await {
self.add_role(db, changed_by, &no_einschreibgebuehr)
self.add_role(db, &changed_by, &no_einschreibgebuehr)
.await
.expect("role doesn't have a group");
}
@@ -313,7 +313,7 @@ impl SchnupperantUser {
ActivityBuilder::new(&format!(
"{self} hat eine Mail bekommen (Inhalt: wir freuen uns auf ihn + senden detailliertere Infos später zu) und die Schnupperbetreuer wurden via Notification informiert."
))
.relevant_for_user(self)
.relevant_for_user(&self)
.save(db)
.await;
@@ -332,7 +332,7 @@ impl SchnupperantUser {
};
Mail::send_single(
db,
mail,
&mail,
"ASKÖ Ruderverein Donau Linz | Anmeldung Schnupperkurs",
format!(
"Hallo {0},
@@ -363,40 +363,4 @@ ASKÖ Ruderverein Donau Linz", self.name),
.await;
}
}
pub(crate) async fn create(
db: &SqlitePool,
created_by: &ManageUserUser,
smtp_pw: &str,
name: NonEmptyString,
mail: &str,
) -> Result<(), String> {
let role = Role::find_by_name(db, "schnupperant").await.unwrap();
let name = name.as_str();
sqlx::query!(
"INSERT INTO user(name, mail)
VALUES (?,?)",
name,
mail
)
.execute(db)
.await
.map_err(|e| e.to_string())?;
let user = User::find_by_name(db, name).await.unwrap();
user.add_role(db, created_by, &role).await?;
let user = Self::new(db, &user).await.unwrap();
user.notify(db, smtp_pw).await?;
ActivityBuilder::new(&format!(
"{created_by} hat {user} zur fixen Schnupperkurs-Anmeldung hinzugefügt."
))
.relevant_for_user(&user)
.save(db)
.await;
Ok(())
}
}

View File

@@ -1,7 +1,6 @@
use super::scheckbuch::ScheckbuchUser;
use super::schnupperant::SchnupperantUser;
use super::{ManageUserUser, User};
use crate::NonEmptyString;
use crate::model::activity::ActivityBuilder;
use crate::model::role::Role;
use crate::{model::notification::Notification, special_user};
@@ -99,7 +98,7 @@ impl SchnupperInterestUser {
ActivityBuilder::new(&format!(
"Der Schnupperbetreuer hat eine Info via Notification bekommen, dass {self} Interesse an einen Schnupperkurs hat."
))
.relevant_for_user(self)
.relevant_for_user(&self)
.save(db)
.await;
@@ -122,41 +121,4 @@ impl SchnupperInterestUser {
.await;
}
}
pub(crate) async fn create(
db: &SqlitePool,
created_by: &ManageUserUser,
name: NonEmptyString,
mail: &str,
) -> Result<(), String> {
let role = Role::find_by_name(db, "schnupper-interessierte")
.await
.unwrap();
let name = name.as_str();
sqlx::query!(
"INSERT INTO user(name, mail)
VALUES (?,?)",
name,
mail
)
.execute(db)
.await
.map_err(|e| e.to_string())?;
let user = User::find_by_name(db, name).await.unwrap();
user.add_role(db, created_by, &role).await?;
let user = Self::new(db, &user).await.unwrap();
user.notify(db).await?;
ActivityBuilder::new(&format!(
"{created_by} hat Schnupper-Interessierten {user} angelegt."
))
.relevant_for_user(&user)
.save(db)
.await;
Ok(())
}
}

View File

@@ -1,17 +1,13 @@
use super::{ManageUserUser, User, regular::ClubMember};
use super::User;
use crate::{
NonEmptyString,
model::{activity::ActivityBuilder, mail::Mail, notification::Notification, role::Role},
model::{activity::ActivityBuilder, mail::Mail},
special_user,
};
use chrono::NaiveDate;
use rocket::{async_trait, fs::TempFile};
use rocket::async_trait;
use sqlx::SqlitePool;
special_user!(UnterstuetzendUser, +"Unterstützend");
impl ClubMember for UnterstuetzendUser {}
impl UnterstuetzendUser {
pub(crate) async fn send_welcome_mail_to_user(
&self,
@@ -45,57 +41,10 @@ ASKÖ Ruderverein Donau Linz", self.name),
ActivityBuilder::new(&format!(
"{self} hat eine Mail an {mail} bekommen, mit Infos dass er/sie nun ein unterstützendes Mitglied ist (Handbuch, WLAN)."
))
.relevant_for_user(self)
.relevant_for_user(&self)
.save(db)
.await;
Ok(())
}
pub(crate) async fn create(
db: &SqlitePool,
created_by: &ManageUserUser,
smtp_pw: &str,
name: NonEmptyString,
mail: &str,
financial: Option<Role>,
birthdate: &NaiveDate,
member_since: &NaiveDate,
phone: NonEmptyString,
address: NonEmptyString,
membership_pdf: &TempFile<'_>,
) -> Result<(), String> {
let role = Role::find_by_name(db, "Unterstützend").await.unwrap();
let user = Self::create_member(
db,
created_by,
&role,
name,
mail,
financial,
birthdate,
member_since,
phone,
address,
membership_pdf,
)
.await?;
let user = Self::new(db, &user).await.unwrap();
user.send_welcome_mail_to_user(db, smtp_pw).await?;
if let Some(vorstand) = Role::find_by_name(db, "Vorstand").await {
Notification::create_for_role(
db,
&vorstand,
&format!("Lieber Vorstand, es gibt ein neues unterstützendes Mitglied: {user}"),
"Neues unterstützendes Vereinsmitglied",
None,
None,
)
.await;
}
Ok(())
}
}

View File

@@ -1,4 +1,4 @@
use rocket::{Build, FromForm, Rocket, State, form::Form, post, routes};
use rocket::{form::Form, post, routes, Build, FromForm, Rocket, State};
use serde_json::json;
use sqlx::SqlitePool;

View File

@@ -96,9 +96,7 @@ struct DailyWeather {
}
fn fetch(api_key: &str) -> Result<Data, String> {
let url = format!(
"https://api.openweathermap.org/data/3.0/onecall?lat=48.31970&lon=14.29451&units=metric&exclude=current,minutely,hourly,alert&appid={api_key}"
);
let url = format!("https://api.openweathermap.org/data/3.0/onecall?lat=48.31970&lon=14.29451&units=metric&exclude=current,minutely,hourly,alert&appid={api_key}");
match ureq::get(&url).call() {
Ok(mut response) => {

View File

@@ -5,14 +5,13 @@ use crate::model::{
user::{User, UserWithDetails, VorstandUser},
};
use rocket::{
Route, State,
form::Form,
get, post,
request::FlashMessage,
response::{Flash, Redirect},
routes,
routes, Route, State,
};
use rocket_dyn_templates::{Template, tera::Context};
use rocket_dyn_templates::{tera::Context, Template};
use sqlx::SqlitePool;
#[get("/boat")]
@@ -246,11 +245,9 @@ mod test {
let rocket = rocket::build().manage(db.clone());
let rocket = crate::tera::config(rocket);
assert!(
Boat::find_by_name(&db, "completely-new-boat".into())
.await
.is_none()
);
assert!(Boat::find_by_name(&db, "completely-new-boat".into())
.await
.is_none());
let client = Client::tracked(rocket).await.unwrap();
let login = client

View File

@@ -1,9 +1,8 @@
use rocket::{
FromForm, Route, State,
form::Form,
get, post, put,
response::{Flash, Redirect},
routes,
routes, FromForm, Route, State,
};
use serde::Serialize;
use sqlx::SqlitePool;
@@ -33,8 +32,8 @@ async fn create(
let trip_details_id = TripDetails::create(db, data.tripdetails).await;
let trip_details = TripDetails::find_by_id(db, trip_details_id).await.unwrap(); //Okay, bc. we
//just created
//the object
//just created
//the object
Event::create(
db,

View File

@@ -1,9 +1,9 @@
use rocket::form::Form;
use rocket::fs::TempFile;
use rocket::response::{Flash, Redirect};
use rocket::{FromForm, post};
use rocket::{Route, State, get, request::FlashMessage, routes};
use rocket_dyn_templates::{Template, tera::Context};
use rocket::{get, request::FlashMessage, routes, Route, State};
use rocket::{post, FromForm};
use rocket_dyn_templates::{tera::Context, Template};
use sqlx::SqlitePool;
use crate::model::log::Log;

View File

@@ -1,6 +1,6 @@
use csv::ReaderBuilder;
use rocket::{FromForm, Route, State, form::Form, get, post, routes};
use rocket_dyn_templates::{Template, context};
use rocket::{form::Form, get, post, routes, FromForm, Route, State};
use rocket_dyn_templates::{context, Template};
use sqlx::SqlitePool;
use crate::{
@@ -12,7 +12,6 @@ pub mod boat;
pub mod event;
pub mod mail;
pub mod notification;
pub mod role;
pub mod schnupper;
pub mod user;
@@ -82,7 +81,6 @@ pub fn routes() -> Vec<Route> {
ret.append(&mut notification::routes());
ret.append(&mut mail::routes());
ret.append(&mut event::routes());
ret.append(&mut role::routes());
ret.append(&mut routes![rss, show_rss, show_list, list]);
ret
}

View File

@@ -6,14 +6,13 @@ use crate::model::{
};
use itertools::Itertools;
use rocket::{
FromForm, Route, State,
form::Form,
get, post,
request::FlashMessage,
response::{Flash, Redirect},
routes,
routes, FromForm, Route, State,
};
use rocket_dyn_templates::{Template, tera::Context};
use rocket_dyn_templates::{tera::Context, Template};
use sqlx::SqlitePool;
#[get("/notification")]

View File

@@ -1,64 +0,0 @@
use crate::model::{
role::Role,
user::{AdminUser, UserWithDetails, VorstandUser},
};
use rocket::{
form::Form,
get, post,
request::FlashMessage,
response::{Flash, Redirect},
routes, FromForm, Route, State,
};
use rocket_dyn_templates::{tera::Context, Template};
use sqlx::SqlitePool;
#[get("/role")]
async fn index(
db: &State<SqlitePool>,
admin: VorstandUser,
flash: Option<FlashMessage<'_>>,
) -> Template {
let roles = Role::all(db).await;
let mut context = Context::new();
if let Some(msg) = flash {
context.insert("flash", &msg.into_inner());
}
context.insert("roles", &roles);
context.insert(
"loggedin_user",
&UserWithDetails::from_user(admin.user, db).await,
);
Template::render("admin/role", context.into_json())
}
#[derive(FromForm)]
pub struct RoleToUpdate<'r> {
pub formatted_name: &'r str,
pub desc: &'r str,
}
#[post("/role/<role_id>", data = "<data>")]
async fn update(
db: &State<SqlitePool>,
data: Form<RoleToUpdate<'_>>,
role_id: i32,
admin: AdminUser,
) -> Flash<Redirect> {
let role = Role::find_by_id(db, role_id).await;
let Some(role) = role else {
return Flash::error(Redirect::to("/admin/role"), "Role does not exist!");
};
match role
.update(db, &admin, &data.formatted_name, &data.desc)
.await
{
Ok(_) => Flash::success(Redirect::to("/admin/role"), "Rolle bearbeitet"),
Err(e) => Flash::error(Redirect::to("/admin/role"), e),
}
}
pub fn routes() -> Vec<Route> {
routes![index, update]
}

View File

@@ -3,8 +3,8 @@ use crate::model::{
user::{SchnupperBetreuerUser, User, UserWithDetails},
};
use futures::future::join_all;
use rocket::{Route, State, get, request::FlashMessage, routes};
use rocket_dyn_templates::{Template, tera::Context};
use rocket::{get, request::FlashMessage, routes, Route, State};
use rocket_dyn_templates::{tera::Context, Template};
use sqlx::SqlitePool;
#[get("/schnupper")]

View File

@@ -4,13 +4,11 @@ use crate::{
family::Family,
log::Log,
logbook::Logbook,
mail::valid_mails,
role::Role,
user::{
clubmember::ClubMemberUser, foerdernd::FoerderndUser, member::Member,
regular::RegularUser, scheckbuch::ScheckbuchUser, schnupperant::SchnupperantUser,
schnupperinterest::SchnupperInterestUser, unterstuetzend::UnterstuetzendUser,
AdminUser, AllowedToEditPaymentStatusUser, ManageUserUser, User, UserWithDetails,
clubmember::ClubMemberUser, member::Member, scheckbuch::ScheckbuchUser,
schnupperant::SchnupperantUser, schnupperinterest::SchnupperInterestUser, AdminUser,
AllowedToEditPaymentStatusUser, ManageUserUser, User, UserWithDetails,
UserWithMembershipPdf, UserWithRolesAndMembershipPdf, VorstandUser,
},
},
@@ -66,7 +64,6 @@ async fn index(
let allowed_to_edit = ManageUserUser::new(db, &user).await.is_some();
let users: Vec<UserWithRolesAndMembershipPdf> = join_all(user_futures).await;
let financial = Role::all_cluster(db, "financial").await;
let roles = Role::all(db).await;
let families = Family::all_with_members(db).await;
@@ -78,7 +75,6 @@ async fn index(
context.insert("allowed_to_edit", &allowed_to_edit);
context.insert("users", &users);
context.insert("roles", &roles);
context.insert("financial", &financial);
context.insert("families", &families);
context.insert("loggedin_user", &UserWithDetails::from_user(user, db).await);
@@ -99,7 +95,6 @@ async fn index_admin(
let users: Vec<UserWithRolesAndMembershipPdf> = join_all(user_futures).await;
let user: User = user.user;
let financial = Role::all_cluster(db, "financial").await;
let allowed_to_edit = ManageUserUser::new(db, &user).await.is_some();
let roles = Role::all(db).await;
@@ -112,7 +107,6 @@ async fn index_admin(
context.insert("allowed_to_edit", &allowed_to_edit);
context.insert("users", &users);
context.insert("roles", &roles);
context.insert("financial", &financial);
context.insert("families", &families);
context.insert("loggedin_user", &UserWithDetails::from_user(user, db).await);
@@ -132,12 +126,6 @@ async fn view(
format!("User mit ID {} gibts ned", user),
));
};
if user.name == "Externe Steuerperson" {
return Err(Flash::error(
Redirect::to("/admin/user"),
"Diese besondere Person kannst du dir leider nicht anschauen, mein lieber neugieriger Ruderant!"
));
}
let member = Member::from(db, user.clone()).await;
let fee = user.fee(db).await;
@@ -292,7 +280,7 @@ async fn delete(db: &State<SqlitePool>, admin: ManageUserUser, user: i32) -> Fla
Log::create(db, format!("{} deleted user: {user:?}", admin.user.name)).await;
match user {
Some(user) => {
user.delete(db, &admin).await;
user.delete(db).await;
Flash::success(
Redirect::to("/admin/user"),
format!("Benutzer {} gelöscht", user.name),
@@ -475,13 +463,13 @@ async fn change_skill(
);
};
let skill = if data.skill_id.is_empty() {
let skill = if &data.skill_id == "" {
None
} else {
let Ok(skill_id) = data.skill_id.parse() else {
return Flash::error(
Redirect::to(format!("/admin/user/{id}")),
"Skill_id is not a number",
format!("Skill_id is not a number"),
);
};
Role::find_by_id(db, skill_id).await
@@ -515,13 +503,13 @@ async fn change_financial(
);
};
let financial = if data.financial_id.is_empty() {
let financial = if &data.financial_id == "" {
None
} else {
let Ok(financial_id) = data.financial_id.parse() else {
return Flash::error(
Redirect::to(format!("/admin/user/{id}")),
"Finacial_id is not a number",
format!("Finacial_id is not a number"),
);
};
Role::find_by_id(db, financial_id).await
@@ -911,7 +899,7 @@ async fn schnupperant_to_regular(
return Flash::error(
Redirect::to(format!("/admin/user/{id}")),
"Membertype gibts ned",
);
)
}
};
@@ -1030,7 +1018,7 @@ async fn scheckbook_to_regular(
return Flash::error(
Redirect::to(format!("/admin/user/{id}")),
"Membertype gibts ned",
);
)
}
};
@@ -1062,7 +1050,7 @@ async fn change_membertype(
);
};
let Some(user) = ClubMemberUser::new(db, &user).await else {
let Some(user) = ClubMemberUser::new(&db, &user).await else {
return Flash::error(
Redirect::to("/admin/user"),
format!("User {user} ist kein Vereinsmitglied"),
@@ -1075,9 +1063,9 @@ async fn change_membertype(
"foerdernd" => user.move_to_foerdernd(db, &admin).await,
_ => {
return Flash::error(
Redirect::to(format!("/admin/user/{id}")),
"Membertype gibt's ned",
);
Redirect::to(format!("/admin/user/{{ id }}")),
format!("Membertype gibt's ned"),
)
}
};
@@ -1104,7 +1092,7 @@ async fn schnupperant_to_scheckbook(
);
};
let Some(user) = SchnupperantUser::new(db, &user).await else {
let Some(user) = SchnupperantUser::new(&db, &user).await else {
return Flash::error(
Redirect::to(format!("/admin/user/{id}")),
format!("User {user} ist kein Schnupperant"),
@@ -1134,7 +1122,7 @@ async fn schnupperinterest_to_schnupperant(
);
};
let Some(user) = SchnupperInterestUser::new(db, &user).await else {
let Some(user) = SchnupperInterestUser::new(&db, &user).await else {
return Flash::error(
Redirect::to(format!("/admin/user/{id}")),
format!("User {user} ist kein Schnupperinteressierter"),
@@ -1163,7 +1151,7 @@ async fn schnupperant_to_schnupperinterest(
);
};
let Some(user) = SchnupperantUser::new(db, &user).await else {
let Some(user) = SchnupperantUser::new(&db, &user).await else {
return Flash::error(
Redirect::to(format!("/admin/user/{id}")),
format!("User {user} ist kein Schnupperant"),
@@ -1192,7 +1180,7 @@ async fn schnupperinterest_to_scheckbuch(
);
};
let Some(user) = SchnupperInterestUser::new(db, &user).await else {
let Some(user) = SchnupperInterestUser::new(&db, &user).await else {
return Flash::error(
Redirect::to(format!("/admin/user/{id}")),
format!("User {user} ist kein Schnupperinteressierter"),
@@ -1208,236 +1196,19 @@ async fn schnupperinterest_to_scheckbuch(
}
}
#[derive(FromForm, Debug)]
pub struct AddClubMemberForm<'a> {
name: String,
mail: String,
financial_id: String,
membertype: String,
member_since: String,
birthdate: String,
phone: String,
address: String,
membership_pdf: TempFile<'a>,
}
#[post("/user/new/clubmember", data = "<data>")]
async fn add_club_member(
db: &State<SqlitePool>,
data: Form<AddClubMemberForm<'_>>,
admin: ManageUserUser,
config: &State<Config>,
) -> Flash<Redirect> {
if !valid_mails(&data.mail) {
return Flash::error(
Redirect::to("/admin/user"),
format!(
"{} ist kein gültiges Format für eine Mailadresse",
&data.mail
),
);
}
let financial = if data.financial_id.is_empty() {
None
} else {
let Ok(financial_id) = data.financial_id.parse() else {
return Flash::error(Redirect::to("/admin/user"), "Finacial_id is not a number");
};
Role::find_by_id(db, financial_id).await
};
let Ok(birthdate) = NaiveDate::parse_from_str(&data.birthdate, "%Y-%m-%d") else {
return Flash::error(
Redirect::to("/admin/user/"),
format!(
"Geburtsdatum {} ist nicht im YYYY-MM-DD Format",
&data.birthdate
),
);
};
let Ok(member_since) = NaiveDate::parse_from_str(&data.member_since, "%Y-%m-%d") else {
return Flash::error(
Redirect::to("/admin/user"),
format!(
"Beitrittsdatum {} ist nicht im YYYY-MM-DD Format",
&data.birthdate
),
);
};
let Ok(phone) = data.phone.clone().try_into() else {
return Flash::error(
Redirect::to("/admin/user"),
"Vereinsmitglied braucht eine Telefonnummer",
);
};
let Ok(address) = data.address.clone().try_into() else {
return Flash::error(
Redirect::to("/admin/user"),
"Vereinsmitglied braucht eine Adresse",
);
};
let Ok(name) = data.name.clone().try_into() else {
return Flash::error(
Redirect::to("/admin/user"),
"Vereinsmitglied braucht einen Namen",
);
};
let response = match &*data.membertype {
"regular" => {
RegularUser::create(
db,
&admin,
&config.smtp_pw,
name,
&data.mail,
financial,
&birthdate,
&member_since,
phone,
address,
&data.membership_pdf,
)
.await
}
"unterstuetzend" => {
UnterstuetzendUser::create(
db,
&admin,
&config.smtp_pw,
name,
&data.mail,
financial,
&birthdate,
&member_since,
phone,
address,
&data.membership_pdf,
)
.await
}
"foerdernd" => {
FoerderndUser::create(
db,
&admin,
&config.smtp_pw,
name,
&data.mail,
financial,
&birthdate,
&member_since,
phone,
address,
&data.membership_pdf,
)
.await
}
_ => return Flash::error(Redirect::to("/admin/user"), "Membertype gibts ned"),
};
match response {
Ok(_) => Flash::success(Redirect::to("/admin/user"), "Mitglied erfolgreich erstellt"),
Err(e) => Flash::error(Redirect::to("/admin/user"), e),
}
}
#[derive(FromForm, Debug)]
pub struct AddScheckbuchForm {
name: String,
mail: String,
}
#[post("/user/new/scheckbuch", data = "<data>")]
async fn add_scheckbuch(
db: &State<SqlitePool>,
data: Form<AddScheckbuchForm>,
admin: ManageUserUser,
config: &State<Config>,
) -> Flash<Redirect> {
if !valid_mails(&data.mail) {
return Flash::error(
Redirect::to("/admin/user"),
format!(
"{} ist kein gültiges Format für eine Mailadresse",
&data.mail
),
);
}
let Ok(name) = data.name.clone().try_into() else {
return Flash::error(
Redirect::to("/admin/user"),
"Scheckbuch braucht einen Namen",
);
};
match ScheckbuchUser::create(db, &admin, &config.smtp_pw, name, &data.mail).await {
Ok(_) => Flash::success(
Redirect::to("/admin/user"),
"Scheckbuch erfolgreich erstellt",
),
Err(e) => Flash::error(Redirect::to("/admin/user"), e),
}
}
#[derive(FromForm, Debug)]
pub struct AddSchnupperForm {
name: String,
mail: String,
schnupper_type: String,
}
#[post("/user/new/schnupper", data = "<data>")]
async fn add_schnupper(
db: &State<SqlitePool>,
data: Form<AddSchnupperForm>,
admin: ManageUserUser,
config: &State<Config>,
) -> Flash<Redirect> {
if !valid_mails(&data.mail) {
return Flash::error(
Redirect::to("/admin/user"),
format!(
"{} ist kein gültiges Format für eine Mailadresse",
&data.mail
),
);
}
let Ok(name) = data.name.clone().try_into() else {
return Flash::error(
Redirect::to("/admin/user"),
"Schnupperer braucht einen Namen",
);
};
let response = match &*data.schnupper_type {
"schnupperInterested" => SchnupperInterestUser::create(db, &admin, name, &data.mail).await,
"schnupperant" => {
SchnupperantUser::create(db, &admin, &config.smtp_pw, name, &data.mail).await
}
_ => return Flash::error(Redirect::to("/admin/user"), "Schnuppertyp gibts ned"),
};
match response {
Ok(_) => Flash::success(
Redirect::to("/admin/user"),
"Schnupperer erfolgreich erstellt",
),
Err(e) => Flash::error(Redirect::to("/admin/user"), e),
}
}
pub fn routes() -> Vec<Route> {
routes![
index,
index_admin,
view,
resetpw,
//create_scheckbuch,
delete,
fees,
fees_paid,
scheckbuch,
download_membership_pdf,
// Updates
//
update_mail,
update_phone,
update_nickname,
@@ -1451,7 +1222,7 @@ pub fn routes() -> Vec<Route> {
add_role,
add_note,
remove_role,
// Moves
//
scheckbook_to_regular,
schnupperant_to_regular,
schnupperant_to_scheckbook,
@@ -1459,9 +1230,5 @@ pub fn routes() -> Vec<Route> {
schnupperant_to_schnupperinterest,
schnupperinterest_to_scheckbuch,
change_membertype,
// Add
add_club_member,
add_scheckbuch,
add_schnupper,
]
}

View File

@@ -1,5 +1,4 @@
use rocket::{
FromForm, Request, Route, State,
form::Form,
get,
http::{Cookie, CookieJar},
@@ -9,8 +8,9 @@ use rocket::{
response::{Flash, Redirect},
routes,
time::{Duration, OffsetDateTime},
FromForm, Request, Route, State,
};
use rocket_dyn_templates::{Template, context, tera};
use rocket_dyn_templates::{context, tera, Template};
use sqlx::SqlitePool;
use crate::model::{
@@ -73,10 +73,7 @@ async fn login(
);
}
Err(_) => {
return Flash::error(
Redirect::to("/auth"),
"Falscher Benutzername/Passwort. Du bist Vereinsmitglied und der Login klappt nicht? Kontaktiere unseren Schriftführer oder schreibe eine Mail an info@rudernlinz.at!",
);
return Flash::error(Redirect::to("/auth"), "Falscher Benutzername/Passwort. Du bist Vereinsmitglied und der Login klappt nicht? Kontaktiere unseren Schriftführer oder schreibe eine Mail an info@rudernlinz.at!");
}
};

View File

@@ -3,8 +3,8 @@ use crate::model::{
role::Role,
user::{User, UserWithDetails, VorstandUser},
};
use rocket::{Route, State, get, request::FlashMessage, routes};
use rocket_dyn_templates::{Template, tera::Context};
use rocket::{get, request::FlashMessage, routes, Route, State};
use rocket_dyn_templates::{tera::Context, Template};
use sqlx::SqlitePool;
#[get("/achievement")]

View File

@@ -4,14 +4,13 @@ use crate::model::{
user::{AdminUser, UserWithDetails, VorstandUser},
};
use rocket::{
FromForm, Route, State,
form::Form,
get, post,
request::FlashMessage,
response::{Flash, Redirect},
routes,
routes, FromForm, Route, State,
};
use rocket_dyn_templates::{Template, tera::Context};
use rocket_dyn_templates::{tera::Context, Template};
use sqlx::SqlitePool;
#[get("/boathouse")]

View File

@@ -1,10 +1,9 @@
use rocket::{
FromForm, Route, State,
form::Form,
get, post,
request::FlashMessage,
response::{Flash, Redirect},
routes,
routes, FromForm, Route, State,
};
use rocket_dyn_templates::Template;
use sqlx::SqlitePool;

View File

@@ -1,11 +1,10 @@
use chrono::NaiveDate;
use rocket::{
FromForm, Route, State,
form::Form,
get, post,
request::FlashMessage,
response::{Flash, Redirect},
routes,
routes, FromForm, Route, State,
};
use rocket_dyn_templates::Template;
use sqlx::SqlitePool;

View File

@@ -1,9 +1,8 @@
use rocket::{
FromForm, Route, State,
form::Form,
get, post,
response::{Flash, Redirect},
routes,
routes, FromForm, Route, State,
};
use sqlx::SqlitePool;
@@ -23,7 +22,7 @@ async fn create_ergo(
) -> Flash<Redirect> {
let trip_details_id = TripDetails::create(db, data.into_inner()).await;
let trip_details = TripDetails::find_by_id(db, trip_details_id).await.unwrap(); //Okay, bc just
//created
//created
Trip::new_own_ergo(db, &cox, trip_details).await; //TODO: fix
//Log::create(
@@ -46,7 +45,7 @@ async fn create(
) -> Flash<Redirect> {
let trip_details_id = TripDetails::create(db, data.into_inner()).await;
let trip_details = TripDetails::find_by_id(db, trip_details_id).await.unwrap(); //Okay, bc just
//created
//created
Trip::new_own(db, &cox, trip_details).await; //TODO: fix
//Log::create(
@@ -138,10 +137,9 @@ async fn join(db: &State<SqlitePool>, planned_event_id: i64, cox: SteeringUser)
.await;
Flash::success(Redirect::to("/planned"), "Danke für's helfen!")
}
Err(CoxHelpError::CanceledEvent) => Flash::error(
Redirect::to("/planned"),
"Die Ausfahrt wurde leider abgesagt...",
),
Err(CoxHelpError::CanceledEvent) => {
Flash::error(Redirect::to("/planned"), "Die Ausfahrt wurde leider abgesagt...")
}
Err(CoxHelpError::AlreadyRegisteredAsCox) => {
Flash::error(Redirect::to("/planned"), "Du hilfst bereits aus!")
}
@@ -149,10 +147,9 @@ async fn join(db: &State<SqlitePool>, planned_event_id: i64, cox: SteeringUser)
Redirect::to("/planned"),
"Du hast dich bereits als Ruderer angemeldet!",
),
Err(CoxHelpError::DetailsLocked) => Flash::error(
Redirect::to("/planned"),
"Die Bootseinteilung wurde bereits gemacht. Wenn du noch steuern möchtest, frag bitte bei einer bereits angemeldeten Steuerperson nach, ob das noch möglich ist.",
),
Err(CoxHelpError::DetailsLocked) => {
Flash::error(Redirect::to("/planned"), "Die Bootseinteilung wurde bereits gemacht. Wenn du noch steuern möchtest, frag bitte bei einer bereits angemeldeten Steuerperson nach, ob das noch möglich ist.")
}
}
} else {
Flash::error(Redirect::to("/planned"), "Event gibt's nicht")
@@ -200,10 +197,9 @@ async fn remove(
Flash::success(Redirect::to("/planned"), "Erfolgreich abgemeldet!")
}
Err(TripHelpDeleteError::DetailsLocked) => Flash::error(
Redirect::to("/planned"),
"Die Bootseinteilung wurde bereits gemacht. Wenn du doch nicht steuern kannst, melde dich bitte unbedingt schnellstmöglich bei einer anderen Steuerperson!",
),
Err(TripHelpDeleteError::DetailsLocked) => {
Flash::error(Redirect::to("/planned"), "Die Bootseinteilung wurde bereits gemacht. Wenn du doch nicht steuern kannst, melde dich bitte unbedingt schnellstmöglich bei einer anderen Steuerperson!")
}
Err(TripHelpDeleteError::CoxNotHelping) => {
Flash::error(Redirect::to("/planned"), "Steuermann hilft nicht aus...")
}

View File

@@ -2,7 +2,6 @@ use std::env;
use chrono::Utc;
use rocket::{
FromForm, Route, State,
form::Form,
fs::TempFile,
get,
@@ -10,9 +9,9 @@ use rocket::{
post,
request::FlashMessage,
response::{Flash, Redirect},
routes,
routes, FromForm, Route, State,
};
use rocket_dyn_templates::{Template, context};
use rocket_dyn_templates::{context, Template};
use serde::Serialize;
use sqlx::SqlitePool;
use tera::Context;

View File

@@ -110,13 +110,10 @@ async fn index(
#[get("/show", rank = 3)]
async fn show(db: &State<SqlitePool>, user: DonauLinzUser) -> Template {
let logs = Logbook::completed(db).await;
let boats = Boat::all(db).await;
let users = User::all(db).await;
let logtypes = LogType::all(db).await;
Template::render(
"log.completed",
context!(logs, boats, users, logtypes, loggedin_user: &UserWithDetails::from_user(user.into_inner(), db).await),
context!(logs, loggedin_user: &UserWithDetails::from_user(user.into_inner(), db).await),
)
}
@@ -218,77 +215,31 @@ async fn create_logbook(
user: &DonauLinzUser,
smtp_pw: &str,
) -> Flash<Redirect> {
match Logbook::create(db, data.into_inner(), user, smtp_pw).await {
Ok(msg) => Flash::success(
Redirect::to("/log"),
format!("Ausfahrt erfolgreich hinzugefügt{msg}"),
),
Err(LogbookCreateError::BoatAlreadyOnWater) => {
Flash::error(Redirect::to("/log"), "Boot schon am Wasser")
}
Err(LogbookCreateError::RowerAlreadyOnWater(rower)) => Flash::error(
Redirect::to("/log"),
format!("Ruderer {} schon am Wasser", rower.name),
),
Err(LogbookCreateError::BoatLocked) => Flash::error(Redirect::to("/log"), "Boot gesperrt"),
Err(LogbookCreateError::BoatNotFound) => {
Flash::error(Redirect::to("/log"), "Boot gibt's ned")
}
Err(LogbookCreateError::TooManyRowers(expected, actual)) => Flash::error(
Redirect::to("/log"),
format!(
"Zu viele Ruderer (Boot fasst maximal {expected}, es wurden jedoch {actual} Ruderer ausgewählt)"
),
),
Err(LogbookCreateError::RowerCreateError(rower, e)) => Flash::error(
Redirect::to("/log"),
format!("Fehler bei Ruderer {rower}: {e}"),
),
Err(LogbookCreateError::ArrivalNotAfterDeparture) => Flash::error(
Redirect::to("/log"),
"Ankunftszeit kann nicht vor der Abfahrtszeit sein",
),
Err(LogbookCreateError::UserNotAllowedToUseBoat) => Flash::error(
Redirect::to("/log"),
"Schiffsführer darf dieses Boot nicht verwenden",
),
Err(LogbookCreateError::SteeringPersonNotInRowers) => Flash::error(
Redirect::to("/log"),
"Steuerperson nicht in Liste der Ruderer!",
),
Err(LogbookCreateError::ShipmasterNotInRowers) => Flash::error(
Redirect::to("/log"),
"Schiffsführer nicht in Liste der Ruderer!",
),
Err(LogbookCreateError::NotYourEntry) => {
Flash::error(Redirect::to("/log"), "Nicht deine Ausfahrt!")
}
Err(LogbookCreateError::ArrivalSetButNotRemainingTwo) => Flash::error(
Redirect::to("/log"),
"Ankunftszeit gesetzt aber nicht Distanz + Strecke",
),
Err(LogbookCreateError::OnlyAllowedToEndTripsEndingToday) => Flash::error(
Redirect::to("/log"),
"Nur Ausfahrten, die in der letzten Woche enden dürfen eingetragen werden. Für einen Nachtrag schreibe alle Daten an den Vorstand (info@rudernlinz.at).",
),
Err(LogbookCreateError::CantChangeHandoperatableStatusForThisBoat) => Flash::error(
Redirect::to("/log"),
"Handsteuer-Status dieses Boots kann nicht verändert werden.",
),
Err(LogbookCreateError::TooFast(km, min)) => Flash::error(
Redirect::to("/log"),
format!(
"KM zu groß für die eingegebene Dauer ({km} km in {min} Minuten). Bitte überprüfe deine Start- und Endzeit und versuche es erneut."
),
),
Err(LogbookCreateError::AlreadyFinalized) => Flash::error(
Redirect::to("/log"),
"Logbucheintrag wurde bereits abgeschlossen.",
),
Err(LogbookCreateError::ExternalSteeringPersonMustSteerOrShipmaster) => Flash::error(
Redirect::to("/log"),
"Wenn du eine 'Externe Steuerperson' hinzufügst, muss diese steuern oder Schiffsführer sein!",
),
match Logbook::create(
db,
data.into_inner(),
user, smtp_pw
)
.await
{
Ok(msg) => Flash::success(Redirect::to("/log"), format!("Ausfahrt erfolgreich hinzugefügt{msg}")),
Err(LogbookCreateError::BoatAlreadyOnWater) => Flash::error(Redirect::to("/log"), "Boot schon am Wasser"),
Err(LogbookCreateError::RowerAlreadyOnWater(rower)) => Flash::error(Redirect::to("/log"), format!("Ruderer {} schon am Wasser", rower.name)),
Err(LogbookCreateError::BoatLocked) => Flash::error(Redirect::to("/log"),"Boot gesperrt"),
Err(LogbookCreateError::BoatNotFound) => Flash::error(Redirect::to("/log"), "Boot gibt's ned"),
Err(LogbookCreateError::TooManyRowers(expected, actual)) => Flash::error(Redirect::to("/log"), format!("Zu viele Ruderer (Boot fasst maximal {expected}, es wurden jedoch {actual} Ruderer ausgewählt)")),
Err(LogbookCreateError::RowerCreateError(rower, e)) => Flash::error(Redirect::to("/log"), format!("Fehler bei Ruderer {rower}: {e}")),
Err(LogbookCreateError::ArrivalNotAfterDeparture) => Flash::error(Redirect::to("/log"), "Ankunftszeit kann nicht vor der Abfahrtszeit sein"),
Err(LogbookCreateError::UserNotAllowedToUseBoat) => Flash::error(Redirect::to("/log"), "Schiffsführer darf dieses Boot nicht verwenden"),
Err(LogbookCreateError::SteeringPersonNotInRowers) => Flash::error(Redirect::to("/log"), "Steuerperson nicht in Liste der Ruderer!"),
Err(LogbookCreateError::ShipmasterNotInRowers) => Flash::error(Redirect::to("/log"), "Schiffsführer nicht in Liste der Ruderer!"),
Err(LogbookCreateError::NotYourEntry) => Flash::error(Redirect::to("/log"), "Nicht deine Ausfahrt!"),
Err(LogbookCreateError::ArrivalSetButNotRemainingTwo) => Flash::error(Redirect::to("/log"), "Ankunftszeit gesetzt aber nicht Distanz + Strecke"),
Err(LogbookCreateError::OnlyAllowedToEndTripsEndingToday) => Flash::error(Redirect::to("/log"), "Nur Ausfahrten, die in der letzten Woche enden dürfen eingetragen werden. Für einen Nachtrag schreibe alle Daten an den Vorstand (info@rudernlinz.at)."),
Err(LogbookCreateError::CantChangeHandoperatableStatusForThisBoat) => Flash::error(Redirect::to("/log"), "Handsteuer-Status dieses Boots kann nicht verändert werden."),
Err(LogbookCreateError::TooFast(km, min)) => Flash::error(Redirect::to("/log"), format!("KM zu groß für die eingegebene Dauer ({km} km in {min} Minuten). Bitte überprüfe deine Start- und Endzeit und versuche es erneut.")),
Err(LogbookCreateError::AlreadyFinalized) => Flash::error(Redirect::to("/log"), "Logbucheintrag wurde bereits abgeschlossen."),
Err(LogbookCreateError::ExternalSteeringPersonMustSteerOrShipmaster) => Flash::error(Redirect::to("/log"), "Wenn du eine 'Externe Steuerperson' hinzufügst, muss diese steuern oder Schiffsführer sein!"),
}
}
@@ -361,13 +312,7 @@ async fn update(
let data = data.into_inner();
let Some(logbook) = Logbook::find_by_id(db, data.id).await else {
return Flash::error(
Redirect::to("/log"),
format!(
"Logbucheintrag kann nicht bearbeitet werden, da es einen Logbuch-Eintrag mit ID={} nicht gibt",
data.id
),
);
return Flash::error(Redirect::to("/log"), format!("Logbucheintrag kann nicht bearbeitet werden, da es einen Logbuch-Eintrag mit ID={} nicht gibt", data.id));
};
match logbook.update(db, data.clone(), &user.user).await {
@@ -408,36 +353,14 @@ async fn home_logbook(
);
};
match logbook.home(db, user, data.into_inner(), smtp_pw).await {
match logbook.home(db,user, data.into_inner(), smtp_pw).await {
Ok(_) => Flash::success(Redirect::to("/log"), "Ausfahrt korrekt eingetragen"),
Err(LogbookUpdateError::TooManyRowers(expected, actual)) => Flash::error(
Redirect::to("/log"),
format!(
"Zu viele Ruderer (Boot fasst maximal {expected}, es wurden jedoch {actual} Ruderer ausgewählt)"
),
),
Err(LogbookUpdateError::OnlyAllowedToEndTripsEndingToday) => Flash::error(
Redirect::to("/log"),
"Nur Ausfahrten, die heute enden dürfen eingetragen werden. Für einen Nachtrag schreibe alle Daten dem Vorstand an info@rudernlinz.at.",
),
Err(LogbookUpdateError::TooFast(km, min)) => Flash::error(
Redirect::to("/log"),
format!(
"KM zu groß für die eingegebene Dauer ({km} km in {min} Minuten). Bitte überprüfe deine Start- und Endzeit und versuche es erneut."
),
),
Err(LogbookUpdateError::AlreadyFinalized) => Flash::error(
Redirect::to("/log"),
"Logbucheintrag wurde bereits abgeschlossen.",
),
Err(LogbookUpdateError::ExternalSteeringPersonMustSteerOrShipmaster) => Flash::error(
Redirect::to("/log"),
"Wenn du eine 'Externe Steuerperson' hinzufügst, muss diese steuern oder Schiffsführer sein!",
),
Err(LogbookUpdateError::BoatAlreadyOnWater) => Flash::error(
Redirect::to("/log"),
"Das Boot war in diesem Zeitraum schon am Wasser. Bitte überprüfe das Datum und die Zeit.",
),
Err(LogbookUpdateError::TooManyRowers(expected, actual)) => Flash::error(Redirect::to("/log"), format!("Zu viele Ruderer (Boot fasst maximal {expected}, es wurden jedoch {actual} Ruderer ausgewählt)")),
Err(LogbookUpdateError::OnlyAllowedToEndTripsEndingToday) => Flash::error(Redirect::to("/log"), "Nur Ausfahrten, die heute enden dürfen eingetragen werden. Für einen Nachtrag schreibe alle Daten dem Vorstand an info@rudernlinz.at."),
Err(LogbookUpdateError::TooFast(km, min)) => Flash::error(Redirect::to("/log"), format!("KM zu groß für die eingegebene Dauer ({km} km in {min} Minuten). Bitte überprüfe deine Start- und Endzeit und versuche es erneut.")),
Err(LogbookUpdateError::AlreadyFinalized) => Flash::error(Redirect::to("/log"), "Logbucheintrag wurde bereits abgeschlossen."),
Err(LogbookUpdateError::ExternalSteeringPersonMustSteerOrShipmaster) => Flash::error(Redirect::to("/log"), "Wenn du eine 'Externe Steuerperson' hinzufügst, muss diese steuern oder Schiffsführer sein!"),
Err(LogbookUpdateError::BoatAlreadyOnWater) => Flash::error(Redirect::to("/log"), "Das Boot war in diesem Zeitraum schon am Wasser. Bitte überprüfe das Datum und die Zeit."),
Err(e) => Flash::error(
Redirect::to("/log"),
format!("Eintrag {logbook_id} konnte nicht abgesendet werden (Fehler: {e:?})!"),

View File

@@ -1,4 +1,4 @@
use rocket::{Route, State, get, http::ContentType, routes};
use rocket::{get, http::ContentType, routes, Route, State};
use sqlx::SqlitePool;
use crate::model::{event::Event, personal::cal::get_personal_cal, user::User};

View File

@@ -2,7 +2,7 @@ use std::{fs::OpenOptions, io::Write};
use chrono::{Datelike, Local};
use rocket::{
Build, Data, FromForm, Request, Rocket, State, catch, catchers,
catch, catchers,
fairing::{AdHoc, Fairing, Info, Kind},
form::Form,
fs::FileServer,
@@ -13,6 +13,7 @@ use rocket::{
response::{Flash, Redirect},
routes,
time::{Duration, OffsetDateTime},
Build, Data, FromForm, Request, Rocket, State,
};
use rocket_dyn_templates::Template;
use serde::Deserialize;
@@ -20,7 +21,6 @@ use sqlx::SqlitePool;
use tera::Context;
use crate::{
SCHECKBUCH,
model::{
logbook::Logbook,
notification::Notification,
@@ -28,6 +28,7 @@ use crate::{
role::Role,
user::{User, UserWithDetails},
},
SCHECKBUCH,
};
pub(crate) mod admin;
@@ -201,10 +202,7 @@ async fn blogpost_unpublished(
#[catch(403)] //forbidden
fn forbidden_error() -> Flash<Redirect> {
Flash::error(
Redirect::to("/"),
"Keine Berechtigung für diese Aktion. Wenn du der Meinung bist, dass du das machen darfst, melde dich bitte bei it@rudernlinz.at.",
)
Flash::error(Redirect::to("/"), "Keine Berechtigung für diese Aktion. Wenn du der Meinung bist, dass du das machen darfst, melde dich bitte bei it@rudernlinz.at.")
}
struct Usage {}
@@ -330,13 +328,11 @@ mod test {
assert_eq!(response.status(), Status::Ok);
assert!(
response
.into_string()
.await
.unwrap()
.contains("Ruderassistent")
);
assert!(response
.into_string()
.await
.unwrap()
.contains("Ruderassistent"));
}
#[sqlx::test]

View File

@@ -1,7 +1,7 @@
use rocket::{
Route, State, get,
get,
response::{Flash, Redirect},
routes,
routes, Route, State,
};
use sqlx::SqlitePool;

View File

@@ -1,15 +1,14 @@
use rocket::{
Route, State, get,
get,
request::FlashMessage,
response::{Flash, Redirect},
routes,
routes, Route, State,
};
use rocket_dyn_templates::Template;
use sqlx::SqlitePool;
use tera::Context;
use crate::{
AMOUNT_DAYS_TO_SHOW_TRIPS_AHEAD,
model::{
log::Log,
tripdetails::TripDetails,
@@ -17,6 +16,7 @@ use crate::{
user::{AllowedForPlannedTripsUser, User, UserWithDetails},
usertrip::{UserTrip, UserTripDeleteError, UserTripError},
},
AMOUNT_DAYS_TO_SHOW_TRIPS_AHEAD,
};
#[get("/")]
@@ -81,15 +81,14 @@ async fn join(
),
)
.await;
} else {
}else{
Log::create(
db,
format!(
"User {} registered the guest '{}' for trip_details.id={}",
user.name, registered_user, trip_details_id
),
)
.await;
).await;
}
Flash::success(Redirect::to("/planned"), "Erfolgreich angemeldet!")
}
@@ -99,10 +98,9 @@ async fn join(
Err(UserTripError::AlreadyRegistered) => {
Flash::error(Redirect::to("/planned"), "Du nimmst bereits teil!")
}
Err(UserTripError::AlreadyRegisteredAsCox) => Flash::error(
Redirect::to("/planned"),
"Du hilfst bereits als Steuerperson aus!",
),
Err(UserTripError::AlreadyRegisteredAsCox) => {
Flash::error(Redirect::to("/planned"), "Du hilfst bereits als Steuerperson aus!")
}
Err(UserTripError::CantRegisterAtOwnEvent) => Flash::error(
Redirect::to("/planned"),
"Du kannst bei einer selbst ausgeschriebenen Fahrt nicht mitrudern ;)",
@@ -162,10 +160,7 @@ async fn remove_guest(
)
.await;
Flash::error(
Redirect::to("/planned"),
"Die Bootseinteilung wurde bereits gemacht. Wenn du doch nicht mitrudern kannst, melde dich bitte unbedingt schnellstmöglich bei einer angemeldeten Steuerperson!",
)
Flash::error(Redirect::to("/planned"), "Die Bootseinteilung wurde bereits gemacht. Wenn du doch nicht mitrudern kannst, melde dich bitte unbedingt schnellstmöglich bei einer angemeldeten Steuerperson!")
}
Err(UserTripDeleteError::GuestNotParticipating) => {
Flash::error(Redirect::to("/planned"), "Gast nicht angemeldet.")
@@ -216,10 +211,7 @@ async fn remove(
)
.await;
Flash::error(
Redirect::to("/planned"),
"Das Boot ist bereits eingeteilt. Bitte kontaktiere den Schiffsführer (Nummern siehe Signalgruppe) falls du dich doch abmelden willst.",
)
Flash::error(Redirect::to("/planned"), "Das Boot ist bereits eingeteilt. Bitte kontaktiere den Schiffsführer (Nummern siehe Signalgruppe) falls du dich doch abmelden willst.")
}
Err(UserTripDeleteError::NotVisibleToUser) => {
Log::create(
@@ -231,10 +223,7 @@ async fn remove(
)
.await;
Flash::error(
Redirect::to("/planned"),
"Abmeldung nicht möglich, da du dieses Event eigentlich gar nicht sehen solltest...",
)
Flash::error(Redirect::to("/planned"), "Abmeldung nicht möglich, da du dieses Event eigentlich gar nicht sehen solltest...")
}
Err(_) => {
panic!("Not possible to be here");

View File

@@ -1,5 +1,5 @@
use rocket::{Route, State, get, routes};
use rocket_dyn_templates::{Template, context};
use rocket::{get, routes, Route, State};
use rocket_dyn_templates::{context, Template};
use sqlx::SqlitePool;
use crate::model::{

View File

@@ -1,11 +1,10 @@
use chrono::NaiveDate;
use rocket::{
FromForm, Route, State,
form::Form,
get, post,
request::FlashMessage,
response::{Flash, Redirect},
routes,
routes, FromForm, Route, State,
};
use rocket_dyn_templates::Template;
use sqlx::SqlitePool;

View File

@@ -3,3 +3,34 @@ INSERT INTO user(name) VALUES('Marie');
INSERT INTO "user_role" (user_id, role_id) VALUES((SELECT id from user where name = 'Marie'),(SELECT id FROM role where name = 'Donau Linz'));
INSERT INTO user(name) VALUES('Philipp');
INSERT INTO "user_role" (user_id, role_id) VALUES((SELECT id from user where name = 'Philipp'),(SELECT id FROM role where name = 'Donau Linz'));
ALTER TABLE role ADD COLUMN formatted_name text;
ALTER TABLE role ADD COLUMN desc text;
ALTER TABLE role ADD COLUMN hide_in_lists BOOLEAN NOT NULL DEFAULT false;
UPDATE role SET hide_in_lists=true WHERE name='paid';
UPDATE role SET hide_in_lists=true WHERE name='ergo';
UPDATE role SET desc='Can do ANYTHING.' WHERE name='admin';
UPDATE role SET desc='Kann Ausfahrten ausschreiben und kann alle Boote die in Linz lagern verwenden.', formatted_name='Steuerperson' WHERE name='cox';
UPDATE role SET desc='Darf reparierte Bootschäden verifizieren und wird über Bootsschäden informiert.', formatted_name='Bootsreparateur' WHERE name='tech';
UPDATE role SET desc = null WHERE name='Rechnungsprüfer';
UPDATE role SET desc='Darf Boote die in Ottensheim lagern verwenden.' WHERE name='Rennrudern';
UPDATE role SET desc='Haben zahlreiche Berechtigungen, siehe den Vorstand-Block im Menü.' WHERE name='Vorstand';
UPDATE role SET desc='Können Events ausschreiben und bearbeiten.', formatted_name='Eventmanager' WHERE name='manage_events';
UPDATE role SET desc='Sieht Details zum Schnupperkurs (Teilnehmer, Bezahlstatus, ...)' WHERE name='schnupper-betreuer';
UPDATE role SET desc=null WHERE name='kassier';
UPDATE role SET desc=null WHERE name='schriftfuehrer';
UPDATE role SET desc='Entfernt bei der Gebührenberechnung die Einschreibgebühr.' WHERE name='no-einschreibgebuehr';
UPDATE role SET desc='Es können Logbucheinträge im Nachhinein hinzugefügt werden. Idealerweise diese Rolle nur kurzfristig vergeben.' WHERE name='allow-retroactive-logbookentries';
UPDATE role SET desc='Erlaubt den Login auf der Wordpress-Website um zB Artikel zu schreiben.' WHERE name='allow_website_login';
UPDATE role SET desc='Muss nur den halben Rennruderbeitrag bezahlen (da zB erst in der 2. Jahreshälfte dazugestoßen wurde)' WHERE name='half-rennrudern';
UPDATE role SET desc='Muss keinen Rennruderbeitrag bezahlen, obwohl man in Rennruder-Gruppe ist.' WHERE name='renntrainer';
CREATE TABLE activity (
id INTEGER PRIMARY KEY AUTOINCREMENT,
created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
text TEXT NOT NULL,
relevant_for TEXT NOT NULL, -- e.g. user_id=123;trip_id=456
keep_until DATETIME -- OPTIONAL field
);
delete from role where name='Anwärter';

View File

@@ -1,37 +0,0 @@
{% import "includes/macros" as macros %}
{% import "includes/forms/boat" as boat %}
{% extends "base" %}
{% block content %}
<div class="max-w-screen-lg w-full dark:text-white">
<h1 class="h1">Rolle</h1>
<div class="grid ">
<div class="bg-white dark:bg-primary-900 text-black dark:text-white rounded-md block shadow mt-5"
role="alert">
<h2 class="h2">Rolle</h2>
{% for role in roles %}
<div data-filterable="true"
data-filter="{{ role.name }}"
class="w-full border-t">
<form action="/admin/role/{{ role.id }}"
data-filterable="true"
method="post"
class="bg-white dark:bg-primary-900 p-4 w-full">
<div class="w-full">
<input type="hidden" name="id" value="{{ role.id }}" />
<div class="font-bold mb-1 text-black dark:text-white">
{{ role.name }}
<br />
</div>
<div class="grid md:grid-cols-3 gap-3">
{{ macros::input(label='Formatierter Name', name='formatted_name', type='text', value=role.formatted_name) }}
{{ macros::input(label='Beschreibung', name='desc', type='text', value=role.desc) }}
<input value="Ändern" type="submit" class="w-28 btn btn-primary" />
</div>
</div>
</form>
</div>
{% endfor %}
</div>
</div>
</div>
{% endblock content %}

View File

@@ -8,136 +8,25 @@
<summary class="px-3 cursor-pointer text-md font-bold text-primary-950 dark:text-white">
Neue Person hinzufügen
</summary>
<div class="grid sm:grid-cols-3 gap-3 mt-3">
<button type="button"
onclick="document.getElementById('add-clubuser').showModal()"
class="btn btn-primary">Vereinsmitglied</button>
<button type="button"
onclick="document.getElementById('add-scheckbuch').showModal()"
class="btn btn-dark">Scheckbuch</button>
<button type="button"
onclick="document.getElementById('add-schnupperkurs').showModal()"
class="btn btn-dark">Schnupperkurs</button>
</div>
<dialog id="add-clubuser"
class="max-w-screen-sm w-full dark:bg-primary-900 dark:text-white rounded-md"
onclick="document.getElementById('add-clubuser').close()">
<div onclick="event.stopPropagation();" class="p-3">
<button type="button"
onclick="document.getElementById('add-clubuser').close()"
title="Schließen"
class="sidebar-close border-0 bg-primary-100 focus:bg-primary-50 text-black flex items-center justify-center transform rotate-45 absolute right-0 mr-3">
<svg class="inline h-5 w-5"
width="16"
height="16"
fill="currentColor"
viewBox="0 0 16 16">
<path d="M8 4a.5.5 0 0 1 .5.5v3h3a.5.5 0 0 1 0 1h-3v3a.5.5 0 0 1-1 0v-3h-3a.5.5 0 0 1 0-1h3v-3A.5.5 0 0 1 8 4z"></path>
</svg>
</button>
<div class="mt-8">
<h2 class="h3 mb-3">Neues Vereinsmitglied</h2>
<form action="/admin/user/new/clubmember"
method="post"
enctype="multipart/form-data"
class="grid gap-3">
<div>
<label for="membertype" class="text-sm text-gray-600 dark:text-gray-100">Mitgliedstyp</label>
<select name="membertype" id="membertype" class="input rounded-md ">
<option selected="" value="regular">Reguläres Vereinsmitglied</option>
<option value="unterstuetzend">Unterstützendes Vereinsmitglied</option>
<option value="foerdernd">Förderndes Vereinsmitglied</option>
</select>
</div>
{{ macros::input(label='Name', name='name', type="text", required=true) }}
{{ macros::input(label='Mailadresse', name='mail', type="email", required=true, placeholder='user@mail.at') }}
{{ macros::select(label="Finanzielles", data=financial, name='financial_id', display=['name'], default="Keine Ermäßigung") }}
{{ macros::input(label='Mitglied seit', name='member_since', type="date", value=now() | date(), required=true) }}
{{ macros::input(label='Geburtsdatum', name='birthdate', type="date", required=true) }}
{{ macros::input(label='Telefonnummer', name='phone', type="text", required=true) }}
{{ macros::input(label='Adresse', name='address', type="text", required=true) }}
{{ macros::input(label='Beitrittserklärung', name='membership_pdf', type="file", accept='application/pdf', required=true) }}
<input value="Neues Vereinsmitglied anlegen"
type="submit"
class="btn btn-primary" />
</form>
</div>
</div>
</dialog>
<dialog id="add-scheckbuch"
class="max-w-screen-sm w-full dark:bg-primary-900 dark:text-white rounded-md"
onclick="document.getElementById('add-scheckbuch').close()">
<div onclick="event.stopPropagation();" class="p-3">
<button type="button"
onclick="document.getElementById('add-scheckbuch').close()"
title="Schließen"
class="sidebar-close border-0 bg-primary-100 focus:bg-primary-50 text-black flex items-center justify-center transform rotate-45 absolute right-0 mr-3">
<svg class="inline h-5 w-5"
width="16"
height="16"
fill="currentColor"
viewBox="0 0 16 16">
<path d="M8 4a.5.5 0 0 1 .5.5v3h3a.5.5 0 0 1 0 1h-3v3a.5.5 0 0 1-1 0v-3h-3a.5.5 0 0 1 0-1h3v-3A.5.5 0 0 1 8 4z"></path>
</svg>
</button>
<div class="mt-8">
<h2 class="h3 mb-3">Neues Scheckbuch</h2>
<form action="/admin/user/new/scheckbuch"
method="post"
enctype="multipart/form-data"
class="grid gap-3">
{{ macros::input(label='Name', name='name', type="text", required=true) }}
{{ macros::input(label='Mailadresse', name='mail', type="email", required=true, placeholder='user@mail.at') }}
<input value="Neues Scheckbuch anlegen"
type="submit"
class="btn btn-primary" />
</form>
</div>
</div>
</dialog>
<dialog id="add-schnupperkurs"
class="max-w-screen-sm w-full dark:bg-primary-900 dark:text-white rounded-md"
onclick="document.getElementById('add-schnupperkurs').close()">
<div onclick="event.stopPropagation();" class="p-3">
<button type="button"
onclick="document.getElementById('add-schnupperkurs').close()"
title="Schließen"
class="sidebar-close border-0 bg-primary-100 focus:bg-primary-50 text-black flex items-center justify-center transform rotate-45 absolute right-0 mr-3">
<svg class="inline h-5 w-5"
width="16"
height="16"
fill="currentColor"
viewBox="0 0 16 16">
<path d="M8 4a.5.5 0 0 1 .5.5v3h3a.5.5 0 0 1 0 1h-3v3a.5.5 0 0 1-1 0v-3h-3a.5.5 0 0 1 0-1h3v-3A.5.5 0 0 1 8 4z"></path>
</svg>
</button>
<div class="mt-8">
<form action="/admin/user/new/schnupper"
method="post"
enctype="multipart/form-data"
class="grid gap-3">
<h2 class="h3 mb-3">Neuer Schnupperant</h2>
<form action="/admin/user/new"
onsubmit="return confirm('Willst du wirklich einen neuen Benutzer anlegen?');"
method="post"
class="flex mt-4 rounded-md sm:flex items-end justify-between">
<div class="w-full">
<div>
<label for="schnupper_type" class="text-sm text-gray-600 dark:text-gray-100">Typ</label>
<select name="schnupper_type" id="schnupper_type" class="input rounded-md ">
<option value="schnupperInterested">Interessiert am Schnupperkurs</option>
<option value="schnupperant">Fixe Schnupperkurs-Anmeldung</option>
</select>
<label for="name" class="sr-only">Name</label>
<input type="text"
name="name"
class="input rounded-md w-100"
placeholder="Name" />
</div>
{{ macros::input(label='Name', name='name', type="text", required=true) }}
{{ macros::input(label='Mailadresse', name='mail', type="email", required=true, placeholder='user@mail.at') }}
{{ macros::select(label="Finanzielles", data=financial, name='financial_id', display=['name'], default="Keine Ermäßigung") }}
<input value="Hinzufügen" type="submit" class="btn btn-primary" />
</form>
</div>
</div>
</dialog>
</div>
<div class="text-right ml-3">
<input value="Hinzufügen"
type="submit"
class="w-28 mt-2 sm:mt-0 rounded-md bg-primary-500 px-3 py-2 text-sm font-semibold text-white hover:bg-primary-500 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-primary-600 cursor-pointer" />
</div>
</form>
</details>
{% endif %}
<!-- START filterBar -->

View File

@@ -7,8 +7,8 @@
<a href="/admin/user" class="link link-primary link-no-underline">&larr; Userverwaltung</a>
{% endif %}
<h1 class="h1">{{ user.name }}</h1>
<div class="grid sm:grid-cols-2 gap-8 my-8">
<div class="bg-white dark:bg-primary-900 text-black dark:text-white rounded-md block shadow">
<div class="grid sm:grid-cols-2 gap-3">
<div class="bg-white dark:bg-primary-900 text-black dark:text-white rounded-md block shadow mt-5">
<h2 class="h2">
Grunddaten
<br />
@@ -53,7 +53,7 @@
</div>
</div>
</div>
<div class="bg-white dark:bg-primary-900 text-black dark:text-white rounded-md block shadow">
<div class="bg-white dark:bg-primary-900 text-black dark:text-white rounded-md block shadow mt-5">
<h2 class="h2">
Mitgliedschaft
<br />
@@ -132,7 +132,7 @@
</div>
</div>
<dialog id="change-member-type"
class="max-w-screen-sm w-full dark:bg-primary-900 dark:text-white rounded-md"
class="max-w-screen-sm w-full dark:bg-primary-600 dark:text-white rounded-md"
onclick="document.getElementById('change-member-type').close()">
<div onclick="event.stopPropagation();" class="p-3">
<button type="button"
@@ -175,27 +175,19 @@
{% endfor %}
</div>
</div>
<div class="grid pt-3">
<a href="/admin/user/{{ user.id }}/delete"
class="btn btn-alert"
onclick="return confirm('Willst du die Daten von {{ user.name }} wirklich? Seine restlichen Scheckbuch-Ausfahrten entfallen damit...');">
{% include "includes/delete-icon" %}
Daten löschen
</a>
</div>
{% endif %}
{% elif "SchnupperInterest" in member %}
{% if allowed_to_edit %}
<div class="grid pt-3">
<a href="/admin/user/{{ user.id }}/schnupperinterest-to-schnupperant"
class="btn btn-dark"
onclick="return confirm('Hat sich \'{{ user.name }}\' wirklich zum Kurs angemeldet?');">Zum Schnupperkurs angemeldet</a>
</div>
<div class="grid pt-3">
<a href="/admin/user/{{ user.id }}/schnupperinterest-to-scheckbuch"
class="btn btn-dark"
onclick="return confirm('Willst du \'{{ user.name }}\' wirklich auf ein Scheckbuch umwandeln?');">In Scheckbuch umwandeln</a>
</div>
<div class="grid pt-3">
<a href="/admin/user/{{ user.id }}/schnupperinterest-to-schnupperant"
class="btn btn-dark"
onclick="return confirm('Hat sich \'{{ user.name }}\' wirklich zum Kurs angemeldet?');">Zum Schnupperkurs angemeldet</a>
</div>
<div class="grid pt-3">
<a href="/admin/user/{{ user.id }}/delete"
class="btn btn-alert"
@@ -235,7 +227,7 @@
class="btn btn-primary">Zu Vereinsmitglied umwandeln</button>
</div>
<dialog id="call-for-action"
class="max-w-screen-sm w-full dark:bg-primary-900 dark:text-white rounded-md"
class="max-w-screen-sm w-full dark:bg-primary-600 dark:text-white rounded-md"
onclick="document.getElementById('call-for-action').close()">
<div onclick="event.stopPropagation();" class="p-3">
<button type="button"
@@ -280,12 +272,20 @@
</div>
</div>
</dialog>
<div class="grid pt-3">
<a href="/admin/user/{{ user.id }}/delete"
class="btn btn-alert"
onclick="return confirm('Willst du die Daten von {{ user.name }} wirklich? Seine restlichen Scheckbuch-Ausfahrten entfallen damit...');">
{% include "includes/delete-icon" %}
Daten löschen
</a>
</div>
{% endif %}
{% endif %}
</div>
</div>
{% if is_clubmember %}
<div class="bg-white dark:bg-primary-900 text-black dark:text-white rounded-md block shadow">
<div class="bg-white dark:bg-primary-900 text-black dark:text-white rounded-md block shadow mt-5">
<h2 class="h2">Rollen</h2>
<div>
<ul class="divide-y divide-gray-200 dark:divide-primary-60 w-full">
@@ -318,7 +318,7 @@
class="btn btn-primary w-full">Rolle hinzufügen</button>
</div>
<dialog id="role-modal"
class="max-w-screen-sm w-full dark:bg-primary-900 dark:text-white rounded-md"
class="max-w-screen-sm w-full dark:bg-primary-600 dark:text-white rounded-md"
onclick="document.getElementById('role-modal').close()">
<div onclick="event.stopPropagation();" class="p-3">
<button type="button"
@@ -363,7 +363,7 @@
</div>
{% endif %}
{% if supposed_to_pay %}
<div class="bg-white dark:bg-primary-900 text-black dark:text-white rounded-md block shadow">
<div class="bg-white dark:bg-primary-900 text-black dark:text-white rounded-md block shadow mt-5">
<h2 class="h2">💸-Beitrag</h2>
<div class="mx-3 divide-y divide-gray-200 dark:divide-primary-600">
<div class="py-3">
@@ -385,9 +385,7 @@
{% endif %}
{% else %}
{% if "paid" in user.roles %}
✅ {% for key, value in member %}
{% if loop.first %}{{ key }}{% endif %}
{% endfor %} hat schon bezahlt
✅ {{ member | keys }} hat schon bezahlt
{% else %}
{% for key, value in member %}
@@ -400,7 +398,7 @@
</div>
</div>
{% endif %}
<div class="bg-white dark:bg-primary-900 text-black dark:text-white rounded-md block shadow">
<div class="bg-white dark:bg-primary-900 text-black dark:text-white rounded-md block shadow mt-5">
<h2 class="h2">Aktivitäten</h2>
<div class="mx-3 divide-y divide-gray-200 dark:divide-primary-600">
<div class="py-3">
@@ -414,13 +412,13 @@
</div>
</div>
</div>
<div class="bg-white dark:bg-primary-900 text-black dark:text-white rounded-md block shadow">
<div class="bg-white dark:bg-primary-900 text-black dark:text-white rounded-md block shadow mt-5">
<h2 class="h2">Ergo-Challenge</h2>
<div class="mx-3">
<div class="grid gap-3 pb-3 mt-3">
{{ macros::inputgroup(label='DOB', name='dob', type="text", value=user.dob, readonly=allowed_to_edit == false) }}
{{ macros::inputgroup(label='Weight (kg)', name='weight', type="text", value=user.weight, readonly=allowed_to_edit == false) }}
{{ macros::inputgroup(label='Sex', name='sex', type="text", value=user.sex, readonly=allowed_to_edit == false) }}
<div class="mx-3 divide-y divide-gray-200 dark:divide-primary-600">
<div class="py-3">
{{ macros::input(label='DOB', name='dob', type="text", value=user.dob, readonly=allowed_to_edit == false) }}
{{ macros::input(label='Weight (kg)', name='weight', type="text", value=user.weight, readonly=allowed_to_edit == false) }}
{{ macros::input(label='Sex', name='sex', type="text", value=user.sex, readonly=allowed_to_edit == false) }}
</div>
</div>
</div>

View File

@@ -183,6 +183,8 @@
<div class="border-t bg-white dark:bg-primary-900 py-3 px-4 relative"
data-filterable="true"
data-filter="{{ log.boat.name }} {% for rower in log.rowers %}{{ rower.name }}{% endfor %}">
<details>
<summary style="list-style: none;">
{% if log.logtype and not hide_type %}
<div class="absolute top-0 right-0 bg-primary-100 rounded-bl-md text-primary-950 text-xs w-32 px-2 py-1 text-center font-bold">
{% if log.logtype == 1 %}
@@ -197,15 +199,7 @@
</div>
{% endif %}
<div {% if log.logtype %}class="mt-4 sm:mt-0"{% endif %}>
{% if allowed_to_edit %}
<a href="#"
onclick="document.getElementById('change-{{ log.id }}').showModal()"
class="link link-black font-bold">{{ log.boat.name }}</a>
{% else %}
<strong class="text-black dark:text-white">
{{ log.boat.name }}
</strong>
{% endif %}
<strong class="text-black dark:text-white">{{ log.boat.name }}</strong>
<small class="text-gray-600 dark:text-gray-100">({{ log.shipmaster_user.name -}}
{% if log.shipmaster_only_steering %}
- handgesteuert
@@ -258,65 +252,35 @@
{% endif %}
{% endif %}
</div>
{% if allowed_to_edit %}
<dialog id="change-{{ log.id }}"
class="max-w-screen-sm w-full dark:bg-primary-900 dark:text-white rounded-md"
onclick="document.getElementById('change-{{ log.id }}').close()">
<div onclick="event.stopPropagation();" class="p-3">
<button type="button"
onclick="document.getElementById('change-{{ log.id }}').close()"
title="Schließen"
class="sidebar-close border-0 bg-primary-100 focus:bg-primary-50 text-black flex items-center justify-center transform rotate-45 absolute right-0 mr-3">
<svg class="inline h-5 w-5"
width="16"
height="16"
fill="currentColor"
viewBox="0 0 16 16">
<path d="M8 4a.5.5 0 0 1 .5.5v3h3a.5.5 0 0 1 0 1h-3v3a.5.5 0 0 1-1 0v-3h-3a.5.5 0 0 1 0-1h3v-3A.5.5 0 0 1 8 4z"></path>
</svg>
</button>
<div class="mt-8">
<h2 class="h3">Eintrag '{{ log.boat.name }}' ändern </h2>
<p class="text-center mb-3">ID: {{ log.id }}</p>
<form action="/log/update" method="post" class="grid gap-3">
</summary>
{% if allowed_to_edit %}
<form action="/log/update" method="post">
<input type="hidden" name="id" value="{{ log.id }}" />
<input type="hidden" name="boat_id" value="{{ log.boat_id }}" />
<input type="hidden" name="shipmaster" value="{{ log.shipmaster }}" />
<input type="hidden"
name="steering_person"
value="{{ log.steering_person }}" />
{{ macros::select(label="Boot", data=boats, name="boat_id", id="boat_id{{ log.id }}", selected_id=log.boat.id ,display=["name", " (","amount_seats", " x)"]) }}
{{ macros::select(label="Schiffsführer", data=log.rowers, name="shipmaster", id="shipmaster{{ log.id }}", selected_id=log.shipmaster_user.id) }}
{{ macros::checkbox(label='Handgesteuert', name='shipmaster_only_steering', id=log.shipmaster_only_steering,checked=log.shipmaster_only_steering) }}
<div>
<label for="departure" class=" text-sm text-gray-600 dark:text-white ">
Abfahrt
</label>
<input type="datetime-local" class="input rounded-md" name="departure" value="{{ log.departure }}" />
</div>
<div>
<label for="arrival" class=" text-sm text-gray-600 dark:text-white ">
Ankunft
</label>
<input type="datetime-local" class="input rounded-md" name="arrival" value="{{ log.arrival }}" />
</div>
Handgesteuert:
<input type="checkbox"
name="shipmaster_only_steering"
{% if log.shipmaster_only_steering %}checked="checked"{% endif %} />
<input type="datetime-local" name="departure" value="{{ log.departure }}" />
<input type="datetime-local" name="arrival" value="{{ log.arrival }}" />
<input type="hidden" name="destination" value="{{ log.destination }}" />
<input type="hidden" name="distance_in_km" value="{{ log.distance_in_km }}" />
<input type="hidden" name="comments" value="{{ log.comments }}" />
<input type="hidden" name="logtype" value="{{ log.logtype }}" />
<input type="submit" class="btn btn-primary" value="Updaten" />
<input type="submit" value="Updaten" />
</form>
<a href="/log/{{ log.id }}/delete"
class="w-28 btn btn-alert mt-3"
class="w-28 btn btn-alert"
onclick="return confirm('Willst du diesen Logbucheintrag wirklich löschen?');">
{% include "includes/delete-icon" %}
Löschen
</a>
</div>
</div>
</dialog>
{% endif %}
{% endif %}
</details>
</div>
{% endmacro show_old %}
{% macro home(log) %}

View File

@@ -156,7 +156,7 @@ function setChoiceByLabel(choicesInstance, label) {
</header>
<div class="h-8"></div>
{% endmacro header %}
{% macro input(label, name, type, required=false, class='rounded-md', value='', min='', hide_label=false, id='', autofocus=false, wrapper_class='', pattern='', readonly=false, accept='', placeholder='') %}
{% macro input(label, name, type, required=false, class='rounded-md', value='', min='', hide_label=false, id='', autofocus=false, wrapper_class='', pattern='', readonly=false, accept='') %}
<div class="{{ wrapper_class }}">
<label for="{{ name }}"
class="{% if hide_label %} sr-only {% else %} text-sm text-gray-600 dark:text-white {% endif %}">
@@ -169,7 +169,7 @@ function setChoiceByLabel(choicesInstance, label) {
{% if required %}required{% endif %}
value="{{ value }}"
class="input {{ class }}"
placeholder="{% if hide_label %}{{ label }}{% endif %}{% if placeholder %}{{ placeholder }}{% endif %}"
placeholder="{% if hide_label %}{{ label }}{% endif %}"
{% if min is defined %}min="{{ min }}"{% endif %}
{% if autofocus %}autofocus{% endif %}
{% if accept %}accept="{{ accept }}"{% endif %}

View File

@@ -431,9 +431,6 @@
<li class="py-1">
<a href="/admin/rss" class="block w-100 py-2 hover:text-primary-600">Logs</a>
</li>
<li class="py-1">
<a href="/admin/role" class="block w-100 py-2 hover:text-primary-600">Rollen</a>
</li>
<li class="py-1">
<a href="/admin/list" class="block w-100 py-2 hover:text-primary-600">Fingerabdruck-Liste überprüfen</a>
</li>