allow cancel of events
Some checks failed
CI/CD Pipeline / deploy-staging (push) Blocked by required conditions
CI/CD Pipeline / deploy-main (push) Blocked by required conditions
CI/CD Pipeline / test (push) Has been cancelled

This commit is contained in:
philipp 2024-05-21 22:16:42 +02:00
parent 3b75f38dca
commit 1f0de7abf4
9 changed files with 246 additions and 69 deletions

View File

@ -152,6 +152,31 @@ ORDER BY read_at DESC, created_at DESC;
}
}
}
// Cox read notification about cancelled event
let re = Regex::new(r"^remove_trip_by_planned_event:(\d+)$").unwrap();
if let Some(caps) = re.captures(action) {
if let Some(matched) = caps.get(1) {
if let Ok(number) = matched.as_str().parse::<i32>() {
let _ = sqlx::query!(
"DELETE FROM trip WHERE cox_id = ? AND planned_event_id = ?",
self.user_id,
number
)
.execute(db)
.await
.unwrap();
}
}
}
}
}
pub(crate) async fn delete_by_action(db: &sqlx::Pool<Sqlite>, action: &str) {
sqlx::query!(
"DELETE FROM notification WHERE action_after_reading=? and read_at is null",
action
)
.execute(db)
.await
.unwrap();
}
}

View File

@ -8,7 +8,7 @@ use ics::{
use serde::Serialize;
use sqlx::{FromRow, Row, SqlitePool};
use super::{tripdetails::TripDetails, triptype::TripType, user::User};
use super::{notification::Notification, tripdetails::TripDetails, triptype::TripType, user::User};
#[derive(Serialize, Clone, FromRow, Debug, PartialEq)]
pub struct PlannedEvent {
@ -17,7 +17,7 @@ pub struct PlannedEvent {
planned_amount_cox: i64,
trip_details_id: i64,
pub planned_starting_time: String,
max_people: i64,
pub(crate) max_people: i64,
pub day: String,
pub notes: Option<String>,
pub allow_guests: bool,
@ -227,6 +227,9 @@ INNER JOIN trip_details ON planned_event.trip_details_id = trip_details.id",
.await
.unwrap(); //Okay, as planned_event can only be created with proper DB backing
let tripdetails = self.trip_details(db).await;
let was_already_cancelled = tripdetails.max_people == 0;
sqlx::query!(
"UPDATE trip_details SET max_people = ?, notes = ?, always_show = ?, is_locked = ? WHERE id = ?",
max_people,
@ -238,6 +241,69 @@ INNER JOIN trip_details ON planned_event.trip_details_id = trip_details.id",
.execute(db)
.await
.unwrap(); //Okay, as planned_event can only be created with proper DB backing
if max_people == 0 && !was_already_cancelled {
let coxes = Registration::all_cox(db, self.id).await;
for user in coxes {
if let Some(user) = User::find_by_name(db, &user.name).await {
let notes = match notes {
Some(n) if !n.is_empty() => n,
_ => ".",
};
Notification::create(
db,
&user,
&format!(
"Die Ausfahrt {} am {} um {} wurde abgesagt{}",
self.name, self.day, self.planned_starting_time, notes
),
"Absage Ausfahrt",
None,
Some(&format!("remove_trip_by_planned_event:{}", self.id)),
)
.await;
}
}
let rower = Registration::all_rower(db, self.trip_details_id).await;
for user in rower {
if let Some(user) = User::find_by_name(db, &user.name).await {
let notes = match notes {
Some(n) if !n.is_empty() => n,
_ => ".",
};
Notification::create(
db,
&user,
&format!(
"Die Ausfahrt {} am {} um {} wurde abgesagt{}",
self.name, self.day, self.planned_starting_time, notes
),
"Absage Ausfahrt",
None,
Some(&format!(
"remove_user_trip_with_trip_details_id:{}",
tripdetails.id
)),
)
.await;
}
}
}
if max_people > 0 && was_already_cancelled {
Notification::delete_by_action(
db,
&format!("remove_user_trip_with_trip_details_id:{}", tripdetails.id),
)
.await;
Notification::delete_by_action(
db,
&format!("remove_trip_by_planned_event:{}", self.id),
)
.await;
}
}
pub async fn delete(&self, db: &SqlitePool) -> Result<(), String> {
@ -250,7 +316,7 @@ INNER JOIN trip_details ON planned_event.trip_details_id = trip_details.id",
"Event kann nicht gelöscht werden, weil mind. 1 Ruderer angemeldet ist.".into(),
);
}
if Registration::all_cox(db, self.trip_details_id).await.len() > 0 {
if Registration::all_cox(db, self.id).await.len() > 0 {
return Err(
"Event kann nicht gelöscht werden, weil mind. 1 Steuerperson angemeldet ist."
.into(),
@ -326,7 +392,7 @@ mod test {
let pool = testdb!();
let planned_event = PlannedEvent::find_by_id(&pool, 1).await.unwrap();
planned_event.delete(&pool).await;
planned_event.delete(&pool).await.unwrap();
let res =
PlannedEvent::get_for_day(&pool, NaiveDate::from_ymd_opt(1970, 1, 1).unwrap()).await;

View File

@ -137,6 +137,10 @@ WHERE trip.id=?
return Err(CoxHelpError::DetailsLocked);
}
if planned_event.max_people == 0 {
return Err(CoxHelpError::CanceledEvent);
}
match sqlx::query!(
"INSERT INTO trip (cox_id, planned_event_id) VALUES(?, ?)",
cox.id,
@ -193,6 +197,9 @@ WHERE day=?
return Err(TripUpdateError::TripDetailsDoesNotExist); //TODO: Remove?
};
let tripdetails = TripDetails::find_by_id(db, trip_details_id).await.unwrap();
let was_already_cancelled = tripdetails.max_people == 0;
sqlx::query!(
"UPDATE trip_details SET max_people = ?, notes = ?, trip_type_id = ?, always_show = ?, is_locked = ? WHERE id = ?",
max_people,
@ -206,7 +213,7 @@ WHERE day=?
.await
.unwrap(); //Okay, as trip_details can only be created with proper DB backing
if max_people == 0 {
if max_people == 0 && !was_already_cancelled {
let rowers = TripWithUserAndType::from(db, trip.clone()).await.rower;
for user in rowers {
if let Some(user) = User::find_by_name(db, &user.name).await {
@ -234,6 +241,14 @@ WHERE day=?
}
}
if max_people > 0 && was_already_cancelled {
Notification::delete_by_action(
db,
&format!("remove_user_trip_with_trip_details_id:{}", trip_details_id),
)
.await;
}
let trip_details = TripDetails::find_by_id(db, trip_details_id).await.unwrap();
trip_details.check_free_spaces(db).await;
@ -318,6 +333,7 @@ pub enum CoxHelpError {
AlreadyRegisteredAsRower,
AlreadyRegisteredAsCox,
DetailsLocked,
CanceledEvent,
}
#[derive(Debug, PartialEq)]

View File

@ -435,7 +435,7 @@ WHERE id like ?
"
SELECT id, name, pw, deleted, last_access, dob, weight, sex, member_since_date, birthdate, mail, nickname, notes, phone, address, family_id
FROM user
WHERE name like ?
WHERE name=?
",
name
)

View File

@ -97,6 +97,9 @@ async fn join(db: &State<SqlitePool>, planned_event_id: i64, cox: CoxUser) -> Fl
.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::AlreadyRegisteredAsCox) => {
Flash::error(Redirect::to("/planned"), "Du hilfst bereits aus!")
}

View File

@ -8,7 +8,9 @@
<div class="bg-white dark:bg-primary-900 text-black dark:text-white rounded-md block shadow mt-5"
role="alert">
<h2 class="h2">Gruppe</h2>
<form action="/admin/notification/group" method="post" class="grid gap-3 p-3">
<form action="/admin/notification/group"
method="post"
class="grid gap-3 p-3">
{{ macros::select(label="Gruppe", data=roles, name="role_id") }}
{{ macros::input(label="Überschrift", name="category", type="text", required=true) }}
{{ macros::input(label="Nachricht", name="message", type="text", required=true) }}
@ -18,7 +20,9 @@
<div class="bg-white dark:bg-primary-900 text-black dark:text-white rounded-md block shadow mt-5"
role="alert">
<h2 class="h2">Person</h2>
<form action="/admin/notification/user" method="post" class="grid gap-3 p-3">
<form action="/admin/notification/user"
method="post"
class="grid gap-3 p-3">
{{ macros::select(label="Person", data=users, name="user_id") }}
{{ macros::input(label="Überschrift", name="category", type="text", required=true) }}
{{ macros::input(label="Nachricht", name="message", type="text", required=true) }}

View File

@ -46,20 +46,42 @@
<div class="p-3">
Folgende personenbezogenen haben wir von dir gespeichert:
<ul>
<li><strong>Name:</strong> {{ loggedin_user.name }}</li>
<li><strong>Passwort:</strong> (verschlüsselt als argon Hash)</li>
<li><strong>Letzter Zugriff:</strong> {{ loggedin_user.last_access }}</li>
<li><strong>Mitglied seit:</strong> {{ loggedin_user.member_since_date }}</li>
<li><strong>Geburtsdatum:</strong> {{ loggedin_user.birthdate }}</li>
<li><strong>Mail:</strong> {{ loggedin_user.mail }}</li>
{% if loggedin_user.nickname %}<li><strong>Spitzname:</strong> {{ loggedin_user.nickname }}</li>{% endif %}
<li><strong>Telefonnummer:</strong> {{ loggedin_user.phone }}</li>
<li><strong>Adresse:</strong> {{ loggedin_user.address }}</li>
<li>
<strong>Name:</strong> {{ loggedin_user.name }}
</li>
<li>
<strong>Passwort:</strong> (verschlüsselt als argon Hash)
</li>
<li>
<strong>Letzter Zugriff:</strong> {{ loggedin_user.last_access }}
</li>
<li>
<strong>Mitglied seit:</strong> {{ loggedin_user.member_since_date }}
</li>
<li>
<strong>Geburtsdatum:</strong> {{ loggedin_user.birthdate }}
</li>
<li>
<strong>Mail:</strong> {{ loggedin_user.mail }}
</li>
{% if loggedin_user.nickname %}
<li>
<strong>Spitzname:</strong> {{ loggedin_user.nickname }}
</li>
{% endif %}
<li>
<strong>Telefonnummer:</strong> {{ loggedin_user.phone }}
</li>
<li>
<strong>Adresse:</strong> {{ loggedin_user.address }}
</li>
<li>(Beitrittserklärung)</li>
{% if loggedin_user.family_id %}
<li>Verbindung zu Familienmitglied (gespeichert um Familientarif anstatt Vollmitglied zu haben)</li>
{% endif %}
<li><strong>Rollen:</strong> {{ loggedin_user.roles }} (werden für verschiedene Funktionen im Ruderassistenten verwendet)</li>
<li>
<strong>Rollen:</strong> {{ loggedin_user.roles }} (werden für verschiedene Funktionen im Ruderassistenten verwendet)
</li>
<li>Anmeldungen zu Ausfahrten</li>
<li>Anmeldungen zu Events (zB Fetzenfahrt, Anrudern, USI-Rudern, ...)</li>
<li>Logbucheinträge</li>

View File

@ -1,38 +1,39 @@
<footer class="bg-primary-950 dark:bg-primary-900 text-white w-full flex justify-center p-3">
<div class="max-w-screen-xl">
<div class="w-full flex justify-between items-center">
<div>
<span class="text-[#ff0000]">&hearts;</span>
Erstellt vom ASKÖ Ruderverein Donau Linz <a class="underline" onclick="alert('Wir suchen kreative und motivierte Köpfe, die diesen Ruderassistenten mitgestalten möchten. Das Backend ist in Rust (Rocket), das Frontend in TypeScript und Teraform, wobei wir mit dem Gedanken spielen, zu Svelte(Kit) zu wechseln.\n\nWenn du Lust hast, deine Skills in ein Projekt zu stecken, das Wellen schlagen wird, dann komm an Bord! Wir sind offen für frische Ideen, haben jedoch auch selber noch genügend; langweilig wird uns bestimmt nicht.\n\nWirf den Anker bei uns ausi und melde dich bei Marie oder Philipp oder it@rudernlinz.at für eine Zukunft ohne optische Kenterung in Form von hässlichen Alerts ;)');">... und dir?</a>
</div>
<div>
<button id="theme-toggle-js"
type="button"
data-theme="light"
class="btn btn-primary">
<span class="hidden dark:inline">
<svg xmlns="http://www.w3.org/2000/svg"
width="16"
height="16"
fill="currentColor"
viewBox="0 0 16 16">
<path d="M8 11a3 3 0 1 1 0-6 3 3 0 0 1 0 6zm0 1a4 4 0 1 0 0-8 4 4 0 0 0 0 8zM8 0a.5.5 0 0 1 .5.5v2a.5.5 0 0 1-1 0v-2A.5.5 0 0 1 8 0zm0 13a.5.5 0 0 1 .5.5v2a.5.5 0 0 1-1 0v-2A.5.5 0 0 1 8 13zm8-5a.5.5 0 0 1-.5.5h-2a.5.5 0 0 1 0-1h2a.5.5 0 0 1 .5.5zM3 8a.5.5 0 0 1-.5.5h-2a.5.5 0 0 1 0-1h2A.5.5 0 0 1 3 8zm10.657-5.657a.5.5 0 0 1 0 .707l-1.414 1.415a.5.5 0 1 1-.707-.708l1.414-1.414a.5.5 0 0 1 .707 0zm-9.193 9.193a.5.5 0 0 1 0 .707L3.05 13.657a.5.5 0 0 1-.707-.707l1.414-1.414a.5.5 0 0 1 .707 0zm9.193 2.121a.5.5 0 0 1-.707 0l-1.414-1.414a.5.5 0 0 1 .707-.707l1.414 1.414a.5.5 0 0 1 0 .707zM4.464 4.465a.5.5 0 0 1-.707 0L2.343 3.05a.5.5 0 1 1 .707-.707l1.414 1.414a.5.5 0 0 1 0 .708z" />
</svg>
</span>
<span class="inline dark:hidden">
<svg xmlns="http://www.w3.org/2000/svg"
width="16"
height="16"
fill="currentColor"
viewBox="0 0 16 16">
<path d="M6 .278a.768.768 0 0 1 .08.858 7.208 7.208 0 0 0-.878 3.46c0 4.021 3.278 7.277 7.318 7.277.527 0 1.04-.055 1.533-.16a.787.787 0 0 1 .81.316.733.733 0 0 1-.031.893A8.349 8.349 0 0 1 8.344 16C3.734 16 0 12.286 0 7.71 0 4.266 2.114 1.312 5.124.06A.752.752 0 0 1 6 .278zM4.858 1.311A7.269 7.269 0 0 0 1.025 7.71c0 4.02 3.279 7.276 7.319 7.276a7.316 7.316 0 0 0 5.205-2.162c-.337.042-.68.063-1.029.063-4.61 0-8.343-3.714-8.343-8.29 0-1.167.242-2.278.681-3.286z" />
</svg>
</span>
</button>
</div>
<div>
<span class="text-[#ff0000]">&hearts;</span>
Erstellt vom ASKÖ Ruderverein Donau Linz <a class="underline"
onclick="alert('Wir suchen kreative und motivierte Köpfe, die diesen Ruderassistenten mitgestalten möchten. Das Backend ist in Rust (Rocket), das Frontend in TypeScript und Teraform, wobei wir mit dem Gedanken spielen, zu Svelte(Kit) zu wechseln.\n\nWenn du Lust hast, deine Skills in ein Projekt zu stecken, das Wellen schlagen wird, dann komm an Bord! Wir sind offen für frische Ideen, haben jedoch auch selber noch genügend; langweilig wird uns bestimmt nicht.\n\nWirf den Anker bei uns ausi und melde dich bei Marie oder Philipp oder it@rudernlinz.at für eine Zukunft ohne optische Kenterung in Form von hässlichen Alerts ;)');">... und dir?</a>
</div>
<div>
<button id="theme-toggle-js"
type="button"
data-theme="light"
class="btn btn-primary">
<span class="hidden dark:inline">
<svg xmlns="http://www.w3.org/2000/svg"
width="16"
height="16"
fill="currentColor"
viewBox="0 0 16 16">
<path d="M8 11a3 3 0 1 1 0-6 3 3 0 0 1 0 6zm0 1a4 4 0 1 0 0-8 4 4 0 0 0 0 8zM8 0a.5.5 0 0 1 .5.5v2a.5.5 0 0 1-1 0v-2A.5.5 0 0 1 8 0zm0 13a.5.5 0 0 1 .5.5v2a.5.5 0 0 1-1 0v-2A.5.5 0 0 1 8 13zm8-5a.5.5 0 0 1-.5.5h-2a.5.5 0 0 1 0-1h2a.5.5 0 0 1 .5.5zM3 8a.5.5 0 0 1-.5.5h-2a.5.5 0 0 1 0-1h2A.5.5 0 0 1 3 8zm10.657-5.657a.5.5 0 0 1 0 .707l-1.414 1.415a.5.5 0 1 1-.707-.708l1.414-1.414a.5.5 0 0 1 .707 0zm-9.193 9.193a.5.5 0 0 1 0 .707L3.05 13.657a.5.5 0 0 1-.707-.707l1.414-1.414a.5.5 0 0 1 .707 0zm9.193 2.121a.5.5 0 0 1-.707 0l-1.414-1.414a.5.5 0 0 1 .707-.707l1.414 1.414a.5.5 0 0 1 0 .707zM4.464 4.465a.5.5 0 0 1-.707 0L2.343 3.05a.5.5 0 1 1 .707-.707l1.414 1.414a.5.5 0 0 1 0 .708z" />
</svg>
</span>
<span class="inline dark:hidden">
<svg xmlns="http://www.w3.org/2000/svg"
width="16"
height="16"
fill="currentColor"
viewBox="0 0 16 16">
<path d="M6 .278a.768.768 0 0 1 .08.858 7.208 7.208 0 0 0-.878 3.46c0 4.021 3.278 7.277 7.318 7.277.527 0 1.04-.055 1.533-.16a.787.787 0 0 1 .81.316.733.733 0 0 1-.031.893A8.349 8.349 0 0 1 8.344 16C3.734 16 0 12.286 0 7.71 0 4.266 2.114 1.312 5.124.06A.752.752 0 0 1 6 .278zM4.858 1.311A7.269 7.269 0 0 0 1.025 7.71c0 4.02 3.279 7.276 7.319 7.276a7.316 7.316 0 0 0 5.205-2.162c-.337.042-.68.063-1.029.063-4.61 0-8.343-3.714-8.343-8.29 0-1.167.242-2.278.681-3.286z" />
</svg>
</span>
</button>
</div>
</div>
<div class="mt-3">
<a class="underline" href="/impressum">Impressum</a>
<a class="underline" href="/impressum">Impressum</a>
</div>
</div>
</footer>

View File

@ -99,15 +99,27 @@
style="order: {{ planned_event.planned_starting_time | replace(from=":", to="") }}">
<div class="flex justify-between items-center">
<div class="mr-1">
<strong class="text-primary-900 dark:text-white">
{{ planned_event.planned_starting_time }}
Uhr
</strong>
<small class="text-gray-600 dark:text-gray-100">({{ planned_event.name }}
{%- if planned_event.trip_type %}
- {{ planned_event.trip_type.icon | safe }} {{ planned_event.trip_type.name }}
{%- endif -%}
)</small>
{% if planned_event.max_people == 0 %}
<strong class="text-[#f43f5e]">&#9888; Absage
{{ planned_event.planned_starting_time }}
Uhr
</strong>
<small class="text-[#f43f5e]">({{ planned_event.name }}
{%- if planned_event.trip_type %}
- {{ planned_event.trip_type.icon | safe }} {{ planned_event.trip_type.name }}
{%- endif -%}
)</small>
{% else %}
<strong class="text-primary-900 dark:text-white">
{{ planned_event.planned_starting_time }}
Uhr
</strong>
<small class="text-gray-600 dark:text-gray-100">({{ planned_event.name }}
{%- if planned_event.trip_type %}
- {{ planned_event.trip_type.icon | safe }} {{ planned_event.trip_type.name }}
{%- endif -%}
)</small>
{% endif %}
<br />
<a href="#" data-sidebar="true" data-trigger="sidebar" data-header="<strong>{{ planned_event.planned_starting_time }} Uhr</strong> ({{ planned_event.name }})
{% if planned_event.trip_type %}<small class='block'>{{ planned_event.trip_type.desc }}</small>{% endif %}
@ -165,16 +177,22 @@
<div id="event{{ planned_event.trip_details_id }}">
{# --- START List Coxes --- #}
{% if planned_event.planned_amount_cox > 0 %}
{% if amount_cox_missing > 0 %}
{{ macros::box(participants=planned_event.cox, empty_seats=planned_event.planned_amount_cox - amount_cur_cox, header='Noch benötigte Steuerleute:', text='Keine Steuerleute angemeldet') }}
{% if planned_event.max_people == 0 %}
{{ macros::box(participants=planned_event.cox, empty_seats="", header='Absage', bg='[#f43f5e]') }}
{% else %}
{{ macros::box(participants=planned_event.cox, empty_seats="", header='Genügend Steuerleute haben sich angemeldet :-)', text='Keine Steuerleute angemeldet') }}
{% if amount_cox_missing > 0 %}
{{ macros::box(participants=planned_event.cox, empty_seats=planned_event.planned_amount_cox - amount_cur_cox, header='Noch benötigte Steuerleute:', text='Keine Steuerleute angemeldet') }}
{% else %}
{{ macros::box(participants=planned_event.cox, empty_seats="", header='Genügend Steuerleute haben sich angemeldet :-)', text='Keine Steuerleute angemeldet') }}
{% endif %}
{% endif %}
{% endif %}
{# --- END List Coxes --- #}
{# --- START List Rowers --- #}
{% if planned_event.max_people > 0 %}
{% set amount_cur_rower = planned_event.rower | length %}
{% set amount_cur_rower = planned_event.rower | length %}
{% if planned_event.max_people == 0 %}
{{ macros::box(header='Absage', bg='[#f43f5e]', participants=planned_event.rower, trip_details_id=planned_event.trip_details_id, allow_removing="planned_event" in loggedin_user.roles) }}
{% else %}
{{ macros::box(participants=planned_event.rower, empty_seats=planned_event.max_people - amount_cur_rower, bg='primary-100', color='black', trip_details_id=planned_event.trip_details_id, allow_removing="planned_event" in loggedin_user.roles) }}
{% endif %}
{# --- END List Rowers --- #}
@ -208,13 +226,35 @@
</div>
{# --- END Edit Form --- #}
{# --- START Delete Btn --- #}
<div class="text-right">
<a href="/admin/planned-event/{{ planned_event.id }}/delete"
class="inline-block btn btn-alert">
{% include "includes/delete-icon" %}
Termin löschen
</a>
</div>
{% if planned_event.rower | length == 0 and amount_cur_cox == 0 %}
<div class="text-right mt-6">
<a href="/admin/planned-event/{{ planned_event.id }}/delete"
class="inline-block btn btn-alert">
{% include "includes/delete-icon" %}
Termin löschen
</a>
</div>
{% else %}
{% if planned_event.max_people == 0 %}
Wenn du deine Absage absagen (:^)) willst, einfach entsprechende Anzahl an Ruderer oben eintragen.
{% else %}
<div class="bg-gray-100 dark:bg-primary-900 p-3 mt-4 rounded-md">
<h3 class="text-primary-950 dark:text-white font-bold uppercase tracking-wide mb-2">Event absagen</h3>
<form action="/admin/planned-event" method="post" class="grid">
<input type="hidden" name="_method" value="put" />
<input type="hidden" name="id" value="{{ planned_event.id }}" />
{{ macros::input(label='', name='max_people', type='hidden', value=0) }}
{{ macros::input(label='', name='name', type='hidden', value=planned_event.name) }}
{{ macros::input(label='', name='max_people', type='hidden', value=planned_event.max_people) }}
{{ macros::input(label='', name='planned_amount_cox', type='hidden', value=planned_event.planned_amount_cox) }}
{{ macros::input(label='', name='notes', type='hidden', value=planned_event.notes) }}
{{ macros::input(label='', name='always_show', type='hidden', value=planned_event.always_show) }}
{{ macros::input(label='', name='is_locked', type='hidden', value=planned_event.is_locked) }}
<input value="Ausfahrt absagen" class="btn btn-alert" type="submit" />
</form>
</div>
{% endif %}
{% endif %}
{% endif %}
{# --- END Delete Btn --- #}
</div>