Funktionierende zu und absage.

This commit is contained in:
2023-02-02 12:56:42 +01:00
parent 87c0a2d541
commit 43d08a014b
9 changed files with 200 additions and 48 deletions
+2 -5
View File
@@ -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<Self, Box<dyn Error>> {
pub async fn get(pool: &Pool, teacher_id: i64, slot_id: i64) -> Result<Self, Box<dyn Error>> {
Ok(Self {
teacher: get_teacher_by_id(pool, teacher_id)
.await
+1 -1
View File
@@ -76,7 +76,7 @@ pub async fn get_unavailable(db: &Pool) -> Result<HashSet<SlotId>, 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
+63 -3
View File
@@ -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<IdType, sqlx::E
Ok(db_nutzer.id)
}
pub async fn confirm_appointments(pool: &Pool, validation_key: &str) -> Result<(), sqlx::Error> {
pub async fn confirm_appointments(
pool: &Pool,
validation_key: &str,
) -> Result<Vec<FullAppointment>, 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<Vec<FullAppointment>, 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)
}
+16
View File
@@ -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 }
}
}
+6 -1
View File
@@ -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))?
+39 -14
View File
@@ -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<Pool>,
mailer: web::Data<SmtpTransport>,
_mailer: web::Data<SmtpTransport>,
css: web::Data<CssPath>,
handlebars: web::Data<Handlebars<'_>>,
validation_key: web::Path<String>,
) -> Result<HttpResponse, error::Error> {
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<Pool>,
_mailer: web::Data<SmtpTransport>,
handlebars: web::Data<Handlebars<'_>>,
css: web::Data<CssPath>,
path: web::Path<(IdType, IdType, String)>,
) -> Result<HttpResponse, error::Error> {
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)),
}
}
+19 -1
View File
@@ -23,7 +23,25 @@
<div class="container">
<div class="section">
<h1 class="title is-1">Bestätigt</h1>
<p>Die unten stehenden Treffen sind bestätigt.</p>
<p> 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.</p>
<div class="mt-6">
{{#each appointments as |appoint|}}
<div class="columns is-centered"><div class="column is-6">
<div class="box"><div class="columns is-vcentered">
<div class="column">
<b>{{appoint.teacher.ansprache}} {{appoint.teacher.last_name}}: {{time_of appoint.slot.start_time}} - {{time_of appoint.slot.end_time}}</b>
</div>
<div class="column">
<a class="button is-small" href="/cancel/{{appoint.teacher.id}}/{{appoint.slot.id}}/{{ ../validation_key }}">
<span class="fas fa-ban fa-fw"></span>
<span class="ml-2">Dieses Treffen Absagen</span>
</a></div></div></div></div></div>
{{/each}}
</div></div>
</div>
</div>
</body>
</html>
+11 -11
View File
@@ -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