format
All checks were successful
CI/CD Pipeline / test (push) Successful in 14m37s
CI/CD Pipeline / deploy-staging (push) Has been skipped
CI/CD Pipeline / deploy-main (push) Has been skipped

This commit is contained in:
Philipp Hofer 2025-05-03 15:54:52 +02:00
parent 5b78afff63
commit 8dc55a7aad
7 changed files with 136 additions and 159 deletions

View File

@ -1,22 +1,12 @@
use super::User; use super::User;
use crate::{ use crate::{model::mail::Mail, special_user};
model::{mail::Mail, notification::Notification},
special_user,
};
use rocket::async_trait; use rocket::async_trait;
use sqlx::SqlitePool; use sqlx::SqlitePool;
special_user!(RegularUser, +"Donau Linz", -"Unterstützend", -"Förderndes Mitglied"); special_user!(FoerderndUser, +"Förderndes Mitglied");
impl RegularUser { impl FoerderndUser {
pub(crate) async fn notify(&self, db: &SqlitePool, smtp_pw: &str) -> Result<(), String> { pub(crate) async fn send_welcome_mail_to_user(
self.notify_coxes_about_new_regular(db).await;
self.send_welcome_mail_to_user(db, smtp_pw).await?;
Ok(())
}
async fn send_welcome_mail_to_user(
&self, &self,
db: &SqlitePool, db: &SqlitePool,
smtp_pw: &str, smtp_pw: &str,
@ -38,16 +28,8 @@ herzlich willkommen im ASKÖ Ruderverein Donau Linz! Wir freuen uns sehr, dich a
Um dir den Einstieg zu erleichtern, findest du in unserem Handbuch alle wichtigen Informationen über unseren Verein: https://rudernlinz.at/book. Bei weiteren Fragen stehen dir die Adressen info@rudernlinz.at (für allgemeine Fragen) und it@rudernlinz.at (bei technischen Fragen) jederzeit zur Verfügung. Um dir den Einstieg zu erleichtern, findest du in unserem Handbuch alle wichtigen Informationen über unseren Verein: https://rudernlinz.at/book. Bei weiteren Fragen stehen dir die Adressen info@rudernlinz.at (für allgemeine Fragen) und it@rudernlinz.at (bei technischen Fragen) jederzeit zur Verfügung.
Du kannst auch gerne unserer Signal-Gruppe beitreten, um auf dem Laufenden zu bleiben und dich mit anderen Mitgliedern auszutauschen: https://signal.group/#CjQKICFrq6zSsRHxrucS3jEcQn6lknEXacAykwwLV3vNLKxPEhA17jxz7cpjfu3JZokLq1TH
Für die Organisation unserer Ausfahrten nutzen wir app.rudernlinz.at. Logge dich einfach mit deinem Namen ('{0}' ohne Anführungszeichen) ein, beim ersten Mal kannst du das Passwortfeld leer lassen. Unter 'Geplante Ausfahrten' kannst du dich jederzeit zu den Ausfahrten anmelden.
Beim nächsten Treffen im Verein, erinnere jemand vom Vorstand (https://rudernlinz.at/unser-verein/vorstand/) bitte daran, deinen Fingerabdruck zu registrieren, damit du Zugang zum Bootshaus erhältst.
Damit du dich noch mehr verbunden fühlst (:-)), haben wir im Bootshaus ein WLAN für Vereinsmitglieder 'ASKÖ Ruderverein Donau Linz' eingerichtet. Das Passwort dafür lautet 'donau1921' (ohne Anführungszeichen). Bitte gib das Passwort an keine vereinsfremden Personen weiter. Damit du dich noch mehr verbunden fühlst (:-)), haben wir im Bootshaus ein WLAN für Vereinsmitglieder 'ASKÖ Ruderverein Donau Linz' eingerichtet. Das Passwort dafür lautet 'donau1921' (ohne Anführungszeichen). Bitte gib das Passwort an keine vereinsfremden Personen weiter.
Wir freuen uns darauf, dich bald am Wasser zu sehen und gemeinsam tolle Erfahrungen zu sammeln!
Riemen- & Dollenbruch Riemen- & Dollenbruch
ASKÖ Ruderverein Donau Linz", self.name), ASKÖ Ruderverein Donau Linz", self.name),
smtp_pw, smtp_pw,
@ -55,19 +37,4 @@ ASKÖ Ruderverein Donau Linz", self.name),
Ok(()) Ok(())
} }
async fn notify_coxes_about_new_regular(&self, db: &SqlitePool) {
Notification::create_for_steering_people(
db,
&format!(
"Liebe Steuerberechtigte, seit {} gibt es ein neues Mitglied: {}",
self.member_since_date.clone().unwrap(),
self.name
),
"Neues Vereinsmitglied",
None,
None,
)
.await;
}
} }

View File

@ -31,9 +31,11 @@ use scheckbuch::ScheckbuchUser;
mod basic; mod basic;
mod fee; mod fee;
pub(crate) mod foerdernd;
pub(crate) mod member; pub(crate) mod member;
pub(crate) mod regular; pub(crate) mod regular;
pub(crate) mod scheckbuch; pub(crate) mod scheckbuch;
pub(crate) mod unterstuetzend;
#[derive(FromRow, Serialize, Deserialize, Clone, Debug, Eq, Hash, PartialEq)] #[derive(FromRow, Serialize, Deserialize, Clone, Debug, Eq, Hash, PartialEq)]
pub struct User { pub struct User {

View File

@ -1,8 +1,5 @@
use super::User; use super::User;
use crate::{ use crate::{model::mail::Mail, special_user};
model::{mail::Mail, notification::Notification},
special_user,
};
use rocket::async_trait; use rocket::async_trait;
use sqlx::SqlitePool; use sqlx::SqlitePool;
@ -48,6 +45,4 @@ ASKÖ Ruderverein Donau Linz", self.name),
Ok(()) Ok(())
} }
async fn notify_coxes_about_new_regular(&self, db: &SqlitePool) {}
} }

View File

@ -1,4 +1,6 @@
use super::foerdernd::FoerderndUser;
use super::regular::RegularUser; use super::regular::RegularUser;
use super::unterstuetzend::UnterstuetzendUser;
use super::{ManageUserUser, User}; use super::{ManageUserUser, User};
use crate::model::role::Role; use crate::model::role::Role;
use crate::NonEmptyString; use crate::NonEmptyString;
@ -87,6 +89,7 @@ impl ScheckbuchUser {
pub(crate) async fn convert_to_unterstuetzend_user( pub(crate) async fn convert_to_unterstuetzend_user(
self, self,
db: &SqlitePool, db: &SqlitePool,
smtp_pw: &str,
changed_by: &ManageUserUser, changed_by: &ManageUserUser,
member_since: &NaiveDate, member_since: &NaiveDate,
birthdate: &NaiveDate, birthdate: &NaiveDate,
@ -112,6 +115,10 @@ impl ScheckbuchUser {
self.user.remove_role(db, changed_by, &scheckbook).await?; self.user.remove_role(db, changed_by, &scheckbook).await?;
self.user.add_role(db, changed_by, &unterstuetzend).await?; self.user.add_role(db, changed_by, &unterstuetzend).await?;
let unterstuetzend = UnterstuetzendUser::new(db, &self.user).await.unwrap();
unterstuetzend
.send_welcome_mail_to_user(db, smtp_pw)
.await?;
if let Some(vorstand) = Role::find_by_name(db, "vorstand").await { if let Some(vorstand) = Role::find_by_name(db, "vorstand").await {
Notification::create_for_role( Notification::create_for_role(
db, db,
@ -134,6 +141,7 @@ impl ScheckbuchUser {
pub(crate) async fn convert_to_foerdernd_user( pub(crate) async fn convert_to_foerdernd_user(
self, self,
db: &SqlitePool, db: &SqlitePool,
smtp_pw: &str,
changed_by: &ManageUserUser, changed_by: &ManageUserUser,
member_since: &NaiveDate, member_since: &NaiveDate,
birthdate: &NaiveDate, birthdate: &NaiveDate,
@ -159,6 +167,8 @@ impl ScheckbuchUser {
self.user.remove_role(db, changed_by, &scheckbook).await?; self.user.remove_role(db, changed_by, &scheckbook).await?;
self.user.add_role(db, changed_by, &unterstuetzend).await?; self.user.add_role(db, changed_by, &unterstuetzend).await?;
let foerdernd = FoerderndUser::new(db, &self.user).await.unwrap();
foerdernd.send_welcome_mail_to_user(db, smtp_pw).await?;
if let Some(vorstand) = Role::find_by_name(db, "vorstand").await { if let Some(vorstand) = Role::find_by_name(db, "vorstand").await {
Notification::create_for_role( Notification::create_for_role(
db, db,

View File

@ -1,31 +1,40 @@
use super::User; use super::User;
use crate::{ use crate::{model::mail::Mail, special_user};
model::{mail::Mail, notification::Notification},
special_user,
};
use rocket::async_trait; use rocket::async_trait;
use sqlx::SqlitePool; use sqlx::SqlitePool;
special_user!(UnterstuetzendUser, +"Unterstützend"); special_user!(UnterstuetzendUser, +"Unterstützend");
impl UnterstuetzendUser { impl UnterstuetzendUser {
pub(crate) async fn notify(&self, db: &SqlitePool, smtp_pw: &str) -> Result<(), String> { pub(crate) async fn send_welcome_mail_to_user(
self.notify_coxes_about_new_unterstuetzend(db).await; &self,
db: &SqlitePool,
smtp_pw: &str,
) -> Result<(), String> {
let Some(mail) = &self.mail else {
return Err(format!(
"Couldn't send welcome mail, as the user {self} has no mail..."
));
};
Mail::send_single(
db,
mail,
"Willkommen im ASKÖ Ruderverein Donau Linz!",
format!(
"Hallo {0},
herzlich willkommen im ASKÖ Ruderverein Donau Linz! Wir freuen uns sehr, dich als neues Mitglied in unserem Verein begrüßen zu dürfen.
Um dir den Einstieg zu erleichtern, findest du in unserem Handbuch alle wichtigen Informationen über unseren Verein: https://rudernlinz.at/book. Bei weiteren Fragen stehen dir die Adressen info@rudernlinz.at (für allgemeine Fragen) und it@rudernlinz.at (bei technischen Fragen) jederzeit zur Verfügung.
Damit du dich noch mehr verbunden fühlst (:-)), haben wir im Bootshaus ein WLAN für Vereinsmitglieder 'ASKÖ Ruderverein Donau Linz' eingerichtet. Das Passwort dafür lautet 'donau1921' (ohne Anführungszeichen). Bitte gib das Passwort an keine vereinsfremden Personen weiter.
Riemen- & Dollenbruch
ASKÖ Ruderverein Donau Linz", self.name),
smtp_pw,
).await?;
Ok(()) Ok(())
} }
async fn notify_vorstand_about_new_unterstuetzend(&self, db: &SqlitePool) {
Notification::create_for_steering_people(
db,
&format!(
"Lieber Vorstand, es gibt ein neues unterstützendes Mitglied: {}",
self.name
),
"Neues unterstützendes Vereinsmitglied",
None,
None,
)
.await;
}
} }

View File

@ -893,6 +893,7 @@ async fn scheckbook_to_regular(
"unterstuetzend" => { "unterstuetzend" => {
user.convert_to_unterstuetzend_user( user.convert_to_unterstuetzend_user(
db, db,
&config.smtp_pw,
&admin, &admin,
&member_since, &member_since,
&birthdate, &birthdate,
@ -905,6 +906,7 @@ async fn scheckbook_to_regular(
"foerdernd" => { "foerdernd" => {
user.convert_to_foerdernd_user( user.convert_to_foerdernd_user(
db, db,
&config.smtp_pw,
&admin, &admin,
&member_since, &member_since,
&birthdate, &birthdate,

View File

@ -5,8 +5,7 @@
<div class="max-w-screen-lg w-full"> <div class="max-w-screen-lg w-full">
<h1 class="h1">{{ user.name }}</h1> <h1 class="h1">{{ user.name }}</h1>
<div class="grid sm:grid-cols-2 gap-3"> <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" <div class="bg-white dark:bg-primary-900 text-black dark:text-white rounded-md block shadow mt-5">
>
<h2 class="h2"> <h2 class="h2">
Grunddaten Grunddaten
<br /> <br />
@ -32,9 +31,9 @@
<span>Notizen: to be replaced with activity :-)</span> <span>Notizen: to be replaced with activity :-)</span>
{% if user.pw and allowed_to_edit %} {% if user.pw and allowed_to_edit %}
<div class="text-right"> <div class="text-right">
<a class="block my-1 font-normal text-[#f43f5e] dark:text-primary-200 hover:text-primary-900 dark:hover:text-primary-300 underline" <a class="block my-1 font-normal text-[#f43f5e] dark:text-primary-200 hover:text-primary-900 dark:hover:text-primary-300 underline"
href="/admin/user/{{ user.id }}/reset-pw" href="/admin/user/{{ user.id }}/reset-pw"
onclick="return confirm('Willst du wirklich das Passwort zurücksetzen?');">Passwort zurücksetzen</a> onclick="return confirm('Willst du wirklich das Passwort zurücksetzen?');">Passwort zurücksetzen</a>
</div> </div>
{% endif %} {% endif %}
</div> </div>
@ -111,14 +110,14 @@
{% elif "Scheckbuch" in member %} {% elif "Scheckbuch" in member %}
<div class="grid gap-3 pb-3"> <div class="grid gap-3 pb-3">
<div class="max-h-60 overflow-y-scroll"> <div class="max-h-60 overflow-y-scroll">
{% for log in logbook %} {% for log in logbook %}
{{ log::show_old(log=log, state="completed", only_ones=false, index=loop.index, allowed_to_edit=false) }} {{ log::show_old(log=log, state="completed", only_ones=false, index=loop.index, allowed_to_edit=false) }}
{% endfor %} {% endfor %}
</div> </div>
{% if allowed_to_edit %} {% if allowed_to_edit %}
<button type="button" <button type="button"
onclick="document.getElementById('call-for-action').showModal()" onclick="document.getElementById('call-for-action').showModal()"
class="btn btn-primary">Zu Vereinsmitglied umwandeln</button> class="btn btn-primary">Zu Vereinsmitglied umwandeln</button>
{% endif %} {% endif %}
</div> </div>
<dialog id="call-for-action" <dialog id="call-for-action"
@ -146,13 +145,9 @@
<label for="membertype" class="text-sm text-gray-600 dark:text-gray-100">Mitgliedstyp</label> <label for="membertype" class="text-sm text-gray-600 dark:text-gray-100">Mitgliedstyp</label>
<select name="membertype" id="membertype" class="input rounded-md "> <select name="membertype" id="membertype" class="input rounded-md ">
<option selected="" value="regular">Reguläres Vereinsmitglied</option> <option selected="" value="regular">Reguläres Vereinsmitglied</option>
<option value="unterstuetzend"> <option value="unterstuetzend">Unterstützendes Vereinsmitglied</option>
Unterstützendes Vereinsmitglied <option value="foerdend">Förderndes Vereinsmitglied</option>
</option> </select>
<option value="foerdend">
Förderndes Vereinsmitglied
</option>
</select>
</div> </div>
{{ macros::input(label='Mitglied seit', name='member_since', type="date", value=now() | date(), required=true) }} {{ macros::input(label='Mitglied seit', name='member_since', type="date", value=now() | date(), required=true) }}
{{ macros::input(label='Geburtsdatum', name='birthdate', type="date", value=user.birthdate, required=true) }} {{ macros::input(label='Geburtsdatum', name='birthdate', type="date", value=user.birthdate, required=true) }}
@ -170,85 +165,85 @@
</div> </div>
</div> </div>
{% if is_clubmember %} {% if is_clubmember %}
<div class="bg-white dark:bg-primary-900 text-black dark:text-white rounded-md block shadow mt-5" <div class="bg-white dark:bg-primary-900 text-black dark:text-white rounded-md block shadow mt-5">
>
<h2 class="h2">Rollen</h2> <h2 class="h2">Rollen</h2>
<div> <div>
<ul class="divide-y divide-gray-200 dark:divide-primary-60 w-full"> <ul class="divide-y divide-gray-200 dark:divide-primary-60 w-full">
{% for role in user.proper_roles -%} {% for role in user.proper_roles -%}
{% if not role.cluster and not role.hide_in_lists %} {% if not role.cluster and not role.hide_in_lists %}
<li class="flex w-full justify-between items-center p-3 {% if allowed_to_edit %} hover:bg-gray-100 dark:hover:bg-primary-950 {% endif %}"> <li class="flex w-full justify-between items-center p-3 {% if allowed_to_edit %}hover:bg-gray-100 dark:hover:bg-primary-950{% endif %}">
<span> <span>
<strong> <strong>
{% if role.formatted_name %} {% if role.formatted_name %}
{{ role.formatted_name }} {{ role.formatted_name }}
{% else %} {% else %}
{{ role.name }} {{ role.name }}
{% endif %} {% endif %}
</strong> </strong>
<br /> <br />
<small>{{ role.desc }}</small> <small>{{ role.desc }}</small>
</span> </span>
{% if allowed_to_edit %} {% if allowed_to_edit %}
<a href="/admin/user/{{ user.id }}/remove-role/{{ role.id }}" <a href="/admin/user/{{ user.id }}/remove-role/{{ role.id }}"
onclick="return confirm('Willst du die Rolle \'{{ role.name }}\' von {{ user.name }} wirklich entfernen?');">🗑️</a> onclick="return confirm('Willst du die Rolle \'{{ role.name }}\' von {{ user.name }} wirklich entfernen?');">🗑️</a>
{% endif %} {% endif %}
</li> </li>
{% endif %} {% endif %}
{% endfor %} {% endfor %}
</ul> </ul>
{% if allowed_to_edit %} {% if allowed_to_edit %}
<div class="m-3"> <div class="m-3">
<button type="button" <button type="button"
onclick="document.getElementById('role-modal').showModal()" onclick="document.getElementById('role-modal').showModal()"
class="btn btn-primary w-full">Rolle hinzufügen</button> class="btn btn-primary w-full">Rolle hinzufügen</button>
</div> </div>
<dialog id="role-modal" <dialog id="role-modal"
class="max-w-screen-sm w-full dark:bg-primary-600 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()"> onclick="document.getElementById('role-modal').close()">
<div onclick="event.stopPropagation();" class="p-3"> <div onclick="event.stopPropagation();" class="p-3">
<button type="button" <button type="button"
onclick="document.getElementById('role-modal').close()" onclick="document.getElementById('role-modal').close()"
title="Schließen" 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"> 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" <svg class="inline h-5 w-5"
width="16" width="16"
height="16" height="16"
fill="currentColor" fill="currentColor"
viewBox="0 0 16 16"> 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> <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> </svg>
</button> </button>
<div class="mt-8"> <div class="mt-8">
<form action="/admin/user/{{ user.id }}/add-role" method="post" class="grid gap-3"> <form action="/admin/user/{{ user.id }}/add-role"
<div> method="post"
<label for="role_id" class="text-sm text-gray-600 dark:text-gray-100">Rollen</label> class="grid gap-3">
<select name="role_id" id="role_id" class="input rounded-md "> <div>
{% for role in roles %} <label for="role_id" class="text-sm text-gray-600 dark:text-gray-100">Rollen</label>
{% if not role.cluster and role not in user.proper_roles and not role.hide_in_lists %} <select name="role_id" id="role_id" class="input rounded-md ">
<option value="{{ role.id }}"> {% for role in roles %}
{% if role.formatted_name %} {% if not role.cluster and role not in user.proper_roles and not role.hide_in_lists %}
{{ role.formatted_name }} <option value="{{ role.id }}">
{% else %} {% if role.formatted_name %}
{{ role.name }} {{ role.formatted_name }}
{% endif %} {% else %}
</option> {{ role.name }}
{% endif %} {% endif %}
{% endfor %} </option>
</select> {% endif %}
{% endfor %}
</select>
</div> </div>
<input value="Rolle hinzufügen" type="submit" class="btn btn-primary" /> <input value="Rolle hinzufügen" type="submit" class="btn btn-primary" />
</form> </form>
</div> </div>
</div> </div>
</dialog> </dialog>
{% endif %} {% endif %}
</div> </div>
</div> </div>
{% endif %} {% endif %}
{% if supposed_to_pay %} {% if supposed_to_pay %}
<div class="bg-white dark:bg-primary-900 text-black dark:text-white rounded-md block shadow mt-5" <div class="bg-white dark:bg-primary-900 text-black dark:text-white rounded-md block shadow mt-5">
>
<h2 class="h2">💸-Beitrag</h2> <h2 class="h2">💸-Beitrag</h2>
<div class="mx-3 divide-y divide-gray-200 dark:divide-primary-600"> <div class="mx-3 divide-y divide-gray-200 dark:divide-primary-600">
<div class="py-3"> <div class="py-3">
@ -283,8 +278,7 @@
</div> </div>
</div> </div>
{% endif %} {% endif %}
<div class="bg-white dark:bg-primary-900 text-black dark:text-white rounded-md block shadow mt-5" <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> <h2 class="h2">Aktivitäten</h2>
<div class="mx-3 divide-y divide-gray-200 dark:divide-primary-600"> <div class="mx-3 divide-y divide-gray-200 dark:divide-primary-600">
<div class="py-3"> <div class="py-3">
@ -295,8 +289,7 @@
</div> </div>
</div> </div>
</div> </div>
<div class="bg-white dark:bg-primary-900 text-black dark:text-white rounded-md block shadow mt-5" <div class="bg-white dark:bg-primary-900 text-black dark:text-white rounded-md block shadow mt-5">
>
<h2 class="h2">TODO</h2> <h2 class="h2">TODO</h2>
<div class="border-t bg-white dark:bg-primary-900 py-3 px-4 relative"> <div class="border-t bg-white dark:bg-primary-900 py-3 px-4 relative">
<span class="text-black dark:text-white cursor-pointer"> <span class="text-black dark:text-white cursor-pointer">
@ -354,8 +347,7 @@
</form> </form>
</div> </div>
</div> </div>
<div class="bg-white dark:bg-primary-900 text-black dark:text-white rounded-md block shadow mt-5" <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> <h2 class="h2">Ergo-Challenge</h2>
<div class="mx-3 divide-y divide-gray-200 dark:divide-primary-600"> <div class="mx-3 divide-y divide-gray-200 dark:divide-primary-600">
<div class="py-3"> <div class="py-3">