From 43d08a014bae1a24b18a138d9212035688b4e8cd Mon Sep 17 00:00:00 2001 From: Franz Dietrich Date: Thu, 2 Feb 2023 12:56:42 +0100 Subject: [PATCH] Funktionierende zu und absage. --- terminwahl_back/src/api/write.rs | 7 +-- terminwahl_back/src/db/read.rs | 2 +- terminwahl_back/src/db/write.rs | 66 +++++++++++++++++++- terminwahl_back/src/lib.rs | 16 +++++ terminwahl_back/src/main.rs | 7 ++- terminwahl_back/src/views.rs | 53 +++++++++++----- terminwahl_back/templates/confirmed.html.hbs | 20 +++++- terminwahl_back/templates/email_confirm.hbs | 22 +++---- terminwahl_front/src/main.rs | 55 ++++++++++++---- 9 files changed, 200 insertions(+), 48 deletions(-) diff --git a/terminwahl_back/src/api/write.rs b/terminwahl_back/src/api/write.rs index 0dd52b4..9180776 100644 --- a/terminwahl_back/src/api/write.rs +++ b/terminwahl_back/src/api/write.rs @@ -8,14 +8,11 @@ use log::debug; use rand::{distributions::Alphanumeric, thread_rng, Rng}; use serde::Serialize; use serde_json::json; -use terminwahl_typen::{ - AppointmentSlot, IdType, Nutzer, PlannedAppointment, RequestState, Teacher, -}; +use terminwahl_typen::{AppointmentSlot, Nutzer, PlannedAppointment, RequestState, Teacher}; use crate::db::{ self, read::{get_slot_by_id, get_teacher_by_id}, - write::confirm_appointments, Pool, }; @@ -26,7 +23,7 @@ pub struct FullAppointment { } impl FullAppointment { - async fn get(pool: &Pool, teacher_id: i64, slot_id: i64) -> Result> { + pub async fn get(pool: &Pool, teacher_id: i64, slot_id: i64) -> Result> { Ok(Self { teacher: get_teacher_by_id(pool, teacher_id) .await diff --git a/terminwahl_back/src/db/read.rs b/terminwahl_back/src/db/read.rs index 9dbb9c3..db5c724 100644 --- a/terminwahl_back/src/db/read.rs +++ b/terminwahl_back/src/db/read.rs @@ -76,7 +76,7 @@ pub async fn get_unavailable(db: &Pool) -> Result, sqlx::Error> SlotId, r#" SELECT teacher_id, slot_id - FROM `appointments` WHERE datetime(expires) > datetime('now');"#, + FROM `appointments` WHERE datetime(expires) > datetime('now') OR expires is NULL;"#, ) .fetch_all(db) .await diff --git a/terminwahl_back/src/db/write.rs b/terminwahl_back/src/db/write.rs index c055833..4de9c5e 100644 --- a/terminwahl_back/src/db/write.rs +++ b/terminwahl_back/src/db/write.rs @@ -1,7 +1,10 @@ use chrono::{Duration, Local}; -use sqlx::query; +use futures::future; +use sqlx::{query, query_as}; use terminwahl_typen::{IdType, Nutzer, PlannedAppointment}; +use crate::api::write::FullAppointment; + use super::Pool; pub async fn save_appointments( @@ -51,12 +54,69 @@ pub async fn save_nutzer(pool: &Pool, nutzer: &Nutzer) -> Result Result<(), sqlx::Error> { +pub async fn confirm_appointments( + pool: &Pool, + validation_key: &str, +) -> Result, sqlx::Error> { let _ = query!( "UPDATE appointments SET expires = NULL WHERE validation_key = ?", validation_key ) .execute(pool) .await?; - Ok(()) + let appointments = query_as!( + PlannedAppointment, + "Select teacher_id, slot_id from appointments WHERE validation_key = ? and expires is NULL", + validation_key + ) + .fetch_all(pool) + .await?; + + let full_appointments = future::try_join_all(appointments.into_iter().map( + |PlannedAppointment { + teacher_id, + slot_id, + }| { FullAppointment::get(pool, teacher_id, slot_id) }, + )) + .await + .expect("Failed to get full list of appointments"); + + Ok(full_appointments) +} + +pub async fn cancel_appointment( + pool: &Pool, + teacher_id: IdType, + slot_id: IdType, + validation_key: &str, +) -> Result, sqlx::Error> { + let _ = query!( + "DELETE FROM appointments WHERE teacher_id = ? AND slot_id = ? AND validation_key = ?", + teacher_id, + slot_id, + validation_key + ) + .execute(pool) + .await?; + + // Fetch the remaining appointments + let appointments = query_as!( + PlannedAppointment, + "Select teacher_id, slot_id from appointments WHERE validation_key = ? and expires is NULL", + validation_key + ) + .fetch_all(pool) + .await?; + + // Fetch the teacher names and times + let full_appointments = future::try_join_all(appointments.into_iter().map( + |PlannedAppointment { + teacher_id, + slot_id, + }| { FullAppointment::get(pool, teacher_id, slot_id) }, + )) + .await + .expect("Failed to get full list of appointments"); + + Ok(full_appointments) } diff --git a/terminwahl_back/src/lib.rs b/terminwahl_back/src/lib.rs index d02eb17..9c8932f 100644 --- a/terminwahl_back/src/lib.rs +++ b/terminwahl_back/src/lib.rs @@ -2,3 +2,19 @@ pub mod api; pub mod db; pub mod handlebars_helper; pub mod views; + +pub struct CssPath { + pub path: String, +} + +impl Default for CssPath { + fn default() -> Self { + let css_path = glob::glob("terminwahl_front/dist/my_bulma_colors*.css") + .expect("Failed to find css file") + .next() + .expect("Failed to find file") + .expect("Failed to find file"); + let css_file = css_path.file_name().unwrap().to_str().unwrap().to_owned(); + Self { path: css_file } + } +} diff --git a/terminwahl_back/src/main.rs b/terminwahl_back/src/main.rs index 3b8c6a3..3c4ad7d 100644 --- a/terminwahl_back/src/main.rs +++ b/terminwahl_back/src/main.rs @@ -11,7 +11,7 @@ use handlebars::Handlebars; use lettre::{transport::smtp::authentication::Credentials, SmtpTransport}; use log::debug; use std::env; -use terminwahl_back::{api, db, handlebars_helper::TimeOfDate, views}; +use terminwahl_back::{api, db, handlebars_helper::TimeOfDate, views, CssPath}; #[actix_web::main] async fn main() -> std::io::Result<()> { @@ -58,6 +58,7 @@ async fn main() -> std::io::Result<()> { .app_data(web::Data::new(pool.clone())) .app_data(web::Data::new(smtp_pool.clone())) .app_data(web::Data::new(handlebars.clone())) + .app_data(web::Data::new(CssPath::default())) .wrap(Logger::default()) .wrap(session_store) .wrap(error_handlers) @@ -80,6 +81,10 @@ async fn main() -> std::io::Result<()> { web::resource("/confirm/{validation_key}") .route(web::get().to(views::confirm_validation_key)), ) + .service( + web::resource("/cancel/{teacher_id}/{slot_id}/{validation_key}") + .route(web::get().to(views::delete_appointment)), + ) .service(Files::new("/", "./terminwahl_front/dist/").index_file("index.html")) }) .bind(("127.0.0.1", 8080))? diff --git a/terminwahl_back/src/views.rs b/terminwahl_back/src/views.rs index 2999e29..5a40573 100644 --- a/terminwahl_back/src/views.rs +++ b/terminwahl_back/src/views.rs @@ -1,29 +1,54 @@ use actix_web::{error, web, HttpResponse}; -use glob::glob; use handlebars::Handlebars; use lettre::SmtpTransport; use serde_json::json; -use terminwahl_typen::RequestState; +use terminwahl_typen::IdType; -use crate::db::{write::confirm_appointments, Pool}; +use crate::{ + db::{ + write::{cancel_appointment, confirm_appointments}, + Pool, + }, + CssPath, +}; pub async fn confirm_validation_key( pool: web::Data, - mailer: web::Data, + _mailer: web::Data, + css: web::Data, handlebars: web::Data>, validation_key: web::Path, ) -> Result { - let css_path = glob("terminwahl_front/dist/my_bulma_colors*.css") - .expect("Failed to find css file") - .next() - .expect("Failed to find file") - .expect("Failed to find file"); - let css_file = css_path.file_name().unwrap().to_str(); - let data = json!({ - "css_file" : css_file, - }); match confirm_appointments(&pool, &validation_key).await { - Ok(_) => Ok(HttpResponse::Ok().body(handlebars.render("confirmed.html", &data).unwrap())), + Ok(appointments) => Ok({ + let data = json!({ + "css_file" : css.path, + "appointments": appointments, + "validation_key": validation_key.into_inner(), + }); + HttpResponse::Ok().body(handlebars.render("confirmed.html", &data).unwrap()) + }), + Err(e) => Err(error::ErrorBadRequest(e)), + } +} + +pub async fn delete_appointment( + pool: web::Data, + _mailer: web::Data, + handlebars: web::Data>, + css: web::Data, + path: web::Path<(IdType, IdType, String)>, +) -> Result { + let (teacher_id, slot_id, validation_key) = path.into_inner(); + match cancel_appointment(&pool, teacher_id, slot_id, &validation_key).await { + Ok(appointments) => { + let data = json!({ + "css_file" : css.path, + "appointments": appointments, + "validation_key": validation_key, + }); + Ok(HttpResponse::Ok().body(handlebars.render("confirmed.html", &data).unwrap())) + } Err(e) => Err(error::ErrorBadRequest(e)), } } diff --git a/terminwahl_back/templates/confirmed.html.hbs b/terminwahl_back/templates/confirmed.html.hbs index ea8eed1..94677ed 100644 --- a/terminwahl_back/templates/confirmed.html.hbs +++ b/terminwahl_back/templates/confirmed.html.hbs @@ -23,7 +23,25 @@

Bestätigt

+

Die unten stehenden Treffen sind bestätigt.

+

Sollten Sie eines oder mehrere Treffen doch nicht wahrnehmen können, + sagen Sie diese bitte möglichst früh ab, sodass Sie von anderen wiederum gebucht werden können.

+
+ {{#each appointments as |appoint|}} +
+
+
+ {{appoint.teacher.ansprache}} {{appoint.teacher.last_name}}: {{time_of appoint.slot.start_time}} - {{time_of appoint.slot.end_time}} +
+
+ + {{/each}} +
- \ No newline at end of file diff --git a/terminwahl_back/templates/email_confirm.hbs b/terminwahl_back/templates/email_confirm.hbs index a3d4890..515926e 100644 --- a/terminwahl_back/templates/email_confirm.hbs +++ b/terminwahl_back/templates/email_confirm.hbs @@ -1,21 +1,21 @@ Sehr geehrte/r {{ nutzer.name }}, -bitte bestätigen Sie durch klick auf den folgenden Link ihre Anmeldung für die Termine - -Alle Treffen bestätigen: -https://elternsprechtag.uhle.cloud/confirm/{{ validation_key }} - -Wenn Sie ein Treffen absagen wollen klicken Sie trotzdem auf den obigen Bestätigungslink und zusätzlich auf den passenden Absage-Link unten. -Auch bestätigte Treffen können abgesagt werden. - -Liste der Treffen - mit Absage-Links, sollten Sie zu einem Treffen doch nicht können. +bitte bestätigen Sie Ihre Anmeldung zu folgenden Terminen durch Anklicken des unten stehenden Links: {{#each appointments as |appoint|}} {{appoint.teacher.ansprache}} {{appoint.teacher.last_name}}: {{time_of appoint.slot.start_time}} - {{time_of appoint.slot.end_time}} - Dieses Treffen Absagen: - https://elternsprechtag.uhle.cloud/cancel/{{appoint.teacher.id}}/{{appoint.slot.id}}/{{ ../validation_key }} {{/each}} +Alle Termine bestätigen: +https://elternsprechtag.uhle.cloud/confirm/{{ validation_key }} + +Wenn Sie einen Termin absagen möchten, klicken Sie bitte trotzdem auf den Bestätigungslink oben. +Dort können Sie dann einzelne oder alle Termine stornieren. +Dies funktioniert auch, wenn Sie die Termine bereits bestätigt haben. + +Wenn Sie länger als 3 Stunden gewartet haben, werden die Termine wieder freigegeben. +Neue Termine müssen Sie wieder über die ursprüngliche Seite buchen. + Vielen Dank für Ihre Anmeldung! Das Oberstufenkollegium \ No newline at end of file diff --git a/terminwahl_front/src/main.rs b/terminwahl_front/src/main.rs index a8e71cb..c391ec2 100644 --- a/terminwahl_front/src/main.rs +++ b/terminwahl_front/src/main.rs @@ -161,7 +161,7 @@ impl Component for App {
{ - if self.nutzer.is_none(){ + if let Some(_saved) = self.successfully_saved.as_ref(){self.view_dank_dialog(ctx)} else if self.nutzer.is_none(){ self.view_eingabe_daten(ctx) } else @@ -186,8 +186,8 @@ impl App {
-

{"Anmeldung zum Elternsprechtag!"}

{"Bitte geben Sie unbedingt eine valide Emailadresse an, - da die Termine erst nach Bestätigung über den per Email zugesandten Link gebucht sind."}

+

{"Anmeldung zum Elternsprechtag!"}

{"Bitte geben Sie unbedingt eine gültige E-Mail-Adresse an, + da die Termine erst nach Bestätigung über den per E-Mail zugesandten Link gebucht werden."}

@@ -227,9 +227,9 @@ impl App {

{"Datenschutzerklärung:"}

-

{"Mit dem klick auf Weiter bestätigen Sie, dass das Lehrerkollegium die hier - und im folgenden eingegebenen Daten zur Organisation des Elternsprechtags speichert, verarbeitet und verwendet. - Die Daten werden nur für diesen Zweck verwendet."}

+

{"Mit dem Klick auf Weiter bestätigen Sie, dass das Lehrerkollegium die hier + und im Folgenden eingegebenen Daten zur Organisation des Elternsprechtages speichert, verarbeitet und nutzt. + Die Daten werden ausschließlich zu diesem Zweck verwendet."}

{ if self.tmp_nutzer.validate() { @@ -278,6 +278,37 @@ impl App { } } + fn view_dank_dialog(&self, _ctx: &Context) -> Html { + html! {<> +
+
+
+
+
+ {"Erfolgreich abgeschickt, aber noch nicht fertig!"} +
+
+
+
+

{"Sie haben die Termine erfolgreich vorgemerkt. Um sie endgültig zu buchen müssen Sie:"}

+

    +
  1. {"In Ihren E-Mails die E-Mail mit dem Betreff \"Elternsprechtag: Bestätigen Sie Ihre Termine\" suchen."}
  2. +
  3. {"Innerhalb von 3 Stunden auf den darin enthaltenen "}{"Link klicken."}
  4. +

+ +

{"Sie können auch mehrmals auf den Link klicken, den Sie erhalten haben. Sie können dort Ihre Termine einsehen und gegebenenfalls absagen. + Wenn Sie einen Termin absagen, tun Sie dies bitte so früh wie möglich, damit der Termin wieder für andere frei wird."}

+

{"Wenn Sie einen anderen Termin buchen möchten, müssen Sie sich erneut über diese Seite anmelden, Sie erhalten dann einen zweiten Link. + Achtung: Die Termine, die Sie zuerst gebucht haben, werden dadurch "}{"nicht"}{" gelöscht."}

+
+
+
+
+
+ + } + } + fn anleitung(&self) -> Html { html!(
@@ -289,16 +320,16 @@ impl App {
    -
  • {"Termine sind erst gebucht, wenn Sie:"} +
  • {"Die Termine sind erst gebucht, wenn Sie:"}
    • {"ganz unten auf Absenden geklickt haben"}
    • -
    • {"Sie die Buchung innerhalb von 3 Stunden über den link in der email bestätigen."}
    • +
    • {"Sie die Buchung innerhalb von 3 Stunden über den Link in der E-Mail bestätigen."}
  • {"Maximal 3 Termine pro Elternhaus, da die Anzahl der Termine begrenzt ist."}
  • -
  • {"Buchen Sie die Termine so, dass sie zwischen zwei Terminen mindestens einen Slot Pause haben für Raumsuche usw."}
  • -
  • {"Sprechen Sie vor allem mit den LehrerInnen, wo ihre Kinder Probleme haben."}
  • -
  • {"Sollten Sie dringenden Gesprächsbedarf haben, aber alle Termine sind voll, melden Sie sich wie gehabt bei den entsprechenden LehrerInnen"}
  • -
  • {"Sollten Sie ihren Termin nicht wahrnehmen können, sagen Sie ihn möglichst früh ab, dass er erneut belegt werden kann."}
  • +
  • {"Buchen Sie die Termine so, dass Sie zwischen zwei Terminen eine Pause für die Raumsuche haben."}
  • +
  • {"Sprechen Sie vor allem mit den LehrerInnen, deren Fächer Ihren Kindern Probleme bereiten."}
  • +
  • {"Sollten Sie dringenden Gesprächsbedarf haben, aber alle Termine sind voll, melden Sie sich wie gewohnt bei den entsprechenden LehrerInnen"}
  • +
  • {"Wenn Sie Ihren Termin nicht wahrnehmen können, sagen Sie ihn so früh wie möglich ab, damit er neu vergeben werden kann."}