Add printable export

This commit is contained in:
Franz Dietrich 2023-02-11 09:31:16 +01:00
parent 3b78ef37dc
commit d5194e82f8
Signed by: dietrich
GPG Key ID: F0CE5A20AB5C4B27
8 changed files with 198 additions and 17 deletions

2
.env
View File

@ -1,5 +1,5 @@
DATABASE_URL="sqlite://terminwahl_back/db.sqlite" DATABASE_URL="sqlite://terminwahl_back/db.sqlite"
RUST_LOG="debug" RUST_LOG="debug,sqlx=error"
SMTP_USER="SMTP_USERNAME" SMTP_USER="SMTP_USERNAME"
SMTP_PASSWORD="SMTP_PASSWORD" SMTP_PASSWORD="SMTP_PASSWORD"
PORT="8087" PORT="8087"

View File

@ -19,12 +19,12 @@ use crate::db::{
}; };
#[derive(Serialize)] #[derive(Serialize)]
pub struct FullAppointment { pub struct AppointmentTeacherSlot {
pub teacher: Teacher, pub teacher: Teacher,
pub slot: AppointmentSlot, pub slot: AppointmentSlot,
} }
impl FullAppointment { impl AppointmentTeacherSlot {
pub 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 { Ok(Self {
teacher: get_teacher_by_id(pool, teacher_id) teacher: get_teacher_by_id(pool, teacher_id)
@ -62,7 +62,7 @@ pub async fn save_appointments_json(
|PlannedAppointment { |PlannedAppointment {
teacher_id, teacher_id,
slot_id, slot_id,
}| { FullAppointment::get(&pool, teacher_id, slot_id) }, }| { AppointmentTeacherSlot::get(&pool, teacher_id, slot_id) },
)) ))
.await .await
.expect("Failed to get full list of appointments"); .expect("Failed to get full list of appointments");
@ -80,7 +80,7 @@ pub async fn save_appointments_json(
} }
pub async fn send_confirmation_request<'a>( pub async fn send_confirmation_request<'a>(
appointments: &Vec<FullAppointment>, appointments: &Vec<AppointmentTeacherSlot>,
nutzer: &Nutzer, nutzer: &Nutzer,
validation_key: &str, validation_key: &str,
handlebars: &Handlebars<'a>, handlebars: &Handlebars<'a>,

View File

@ -1,9 +1,11 @@
use std::collections::HashSet; use std::collections::HashSet;
use chrono::NaiveDateTime;
use serde::{Deserialize, Serialize};
use sqlx::query_as; use sqlx::query_as;
use terminwahl_typen::{ use terminwahl_typen::{
AppointmentSlot, AppointmentSlots, IdType, SlotId, Subject, Subjects, Teacher, Teachers, AppointmentSlot, AppointmentSlots, IdType, Nutzer, SlotId, Subject, Subjects, Teacher, Teachers,
}; };
use super::Pool; use super::Pool;
@ -85,3 +87,74 @@ pub async fn get_unavailable(db: &Pool) -> Result<HashSet<SlotId>, sqlx::Error>
Err(e) => Err(e), Err(e) => Err(e),
} }
} }
#[derive(Debug, Deserialize, Serialize, Clone)]
pub struct TeacherWithAppointments {
teacher: Teacher,
appointments: Vec<AssignedAppointment>,
}
#[derive(Debug, Deserialize, Serialize, Clone)]
pub struct FullAppointment {
id: IdType,
teacher_id: IdType,
slot_id: IdType,
nutzer_id: IdType,
expires: Option<NaiveDateTime>,
}
#[derive(Debug, Deserialize, Serialize, Clone)]
pub struct AssignedAppointment {
appointment: FullAppointment,
slot: AppointmentSlot,
nutzer: Nutzer,
}
pub async fn get_all_teachers(db: &Pool) -> Result<Vec<TeacherWithAppointments>, sqlx::Error> {
let teachers = get_teachers(db).await?;
let mut response = Vec::new();
for teacher in teachers.into_iter() {
let teacher_id: i64 = teacher.id;
let mut appointments: Vec<FullAppointment> = query_as!(
FullAppointment,
r#"
SELECT id,teacher_id,slot_id,nutzer_id,expires
FROM `appointments` WHERE teacher_id = ?"#,
teacher_id
)
.fetch_all(db)
.await?;
appointments.sort_by(|a, b| a.slot_id.cmp(&b.slot_id));
let mut assigned_appointments: Vec<AssignedAppointment> = Vec::new();
for appointment in appointments.into_iter() {
let slot = query_as!(
AppointmentSlot,
r#"
SELECT *
FROM `appointment_slots` WHERE id = ? "#,
appointment.slot_id
)
.fetch_one(db)
.await?;
let nutzer = query_as!(
Nutzer,
r#"
SELECT name, schueler as schüler,email
FROM `nutzer` WHERE id = ? "#,
appointment.nutzer_id
)
.fetch_one(db)
.await?;
assigned_appointments.push(AssignedAppointment {
appointment,
slot,
nutzer,
})
}
response.push(TeacherWithAppointments {
teacher,
appointments: assigned_appointments,
})
}
Ok(response)
}

View File

@ -3,7 +3,7 @@ use futures::future;
use sqlx::{query, query_as}; use sqlx::{query, query_as};
use terminwahl_typen::{IdType, Nutzer, PlannedAppointment}; use terminwahl_typen::{IdType, Nutzer, PlannedAppointment};
use crate::api::write::FullAppointment; use crate::api::write::AppointmentTeacherSlot;
use super::Pool; use super::Pool;
@ -57,7 +57,7 @@ pub async fn save_nutzer(pool: &Pool, nutzer: &Nutzer) -> Result<IdType, sqlx::E
pub async fn confirm_appointments( pub async fn confirm_appointments(
pool: &Pool, pool: &Pool,
validation_key: &str, validation_key: &str,
) -> Result<Vec<FullAppointment>, sqlx::Error> { ) -> Result<Vec<AppointmentTeacherSlot>, sqlx::Error> {
let _ = query!( let _ = query!(
"UPDATE appointments SET expires = NULL WHERE validation_key = ?", "UPDATE appointments SET expires = NULL WHERE validation_key = ?",
validation_key validation_key
@ -76,7 +76,7 @@ pub async fn confirm_appointments(
|PlannedAppointment { |PlannedAppointment {
teacher_id, teacher_id,
slot_id, slot_id,
}| { FullAppointment::get(pool, teacher_id, slot_id) }, }| { AppointmentTeacherSlot::get(pool, teacher_id, slot_id) },
)) ))
.await .await
.expect("Failed to get full list of appointments"); .expect("Failed to get full list of appointments");
@ -89,7 +89,7 @@ pub async fn cancel_appointment(
teacher_id: IdType, teacher_id: IdType,
slot_id: IdType, slot_id: IdType,
validation_key: &str, validation_key: &str,
) -> Result<Vec<FullAppointment>, sqlx::Error> { ) -> Result<Vec<AppointmentTeacherSlot>, sqlx::Error> {
let _ = query!( let _ = query!(
"DELETE FROM appointments WHERE teacher_id = ? AND slot_id = ? AND validation_key = ?", "DELETE FROM appointments WHERE teacher_id = ? AND slot_id = ? AND validation_key = ?",
teacher_id, teacher_id,
@ -113,7 +113,7 @@ pub async fn cancel_appointment(
|PlannedAppointment { |PlannedAppointment {
teacher_id, teacher_id,
slot_id, slot_id,
}| { FullAppointment::get(pool, teacher_id, slot_id) }, }| { AppointmentTeacherSlot::get(pool, teacher_id, slot_id) },
)) ))
.await .await
.expect("Failed to get full list of appointments"); .expect("Failed to get full list of appointments");

View File

@ -92,6 +92,10 @@ async fn main() -> std::io::Result<()> {
web::resource("/cancel/{teacher_id}/{slot_id}/{validation_key}") web::resource("/cancel/{teacher_id}/{slot_id}/{validation_key}")
.route(web::get().to(views::delete_appointment)), .route(web::get().to(views::delete_appointment)),
) )
.service(
web::resource("/export/all/{password}")
.route(web::get().to(views::export_appointments)),
)
.service(Files::new("/", wasm_statics.clone()).index_file("index.html")) .service(Files::new("/", wasm_statics.clone()).index_file("index.html"))
}) })
.bind(("127.0.0.1", port))? .bind(("127.0.0.1", port))?

View File

@ -6,6 +6,7 @@ use terminwahl_typen::IdType;
use crate::{ use crate::{
db::{ db::{
read::get_all_teachers,
write::{cancel_appointment, confirm_appointments}, write::{cancel_appointment, confirm_appointments},
Pool, Pool,
}, },
@ -52,3 +53,30 @@ pub async fn delete_appointment(
Err(e) => Err(error::ErrorBadRequest(e)), Err(e) => Err(error::ErrorBadRequest(e)),
} }
} }
pub async fn export_appointments(
pool: web::Data<Pool>,
_mailer: web::Data<AsyncSmtpTransport<Tokio1Executor>>,
handlebars: web::Data<Handlebars<'_>>,
css: web::Data<CssPath>,
path: web::Path<String>,
) -> Result<HttpResponse, error::Error> {
let password = path.into_inner();
dbg!(&password);
if password == "AllExport1517" {
match get_all_teachers(&pool).await {
Ok(teachers) => {
dbg!(&teachers);
let data = json!({
"css_file" : css.path,
"teachers": teachers,
});
Ok(HttpResponse::Ok()
.body(handlebars.render("lehrer_einzeln.html", &data).unwrap()))
}
Err(e) => Err(error::ErrorBadRequest(e)),
}
} else {
Err(error::ErrorBadRequest("Wrong Password"))
}
}

View File

@ -0,0 +1,76 @@
<!DOCTYPE html>
<html lang="de">
<head>
<meta charset="utf-8" />
<title>Elternsprechtagtermine</title>
<link rel="stylesheet" href="/{{css_file}}" />
<meta
name="keywords"
content="Termine,Waldorfschule,Lehrersprechtag,Lehrerinnensprechtag"
/>
<meta name="author" content="Franz Dietrich" />
<meta
name="description"
content="Termine buchen für den Lehrersprechtag der Waldorfschule Uhlandshöhe"
/>
<style>
@media print {
.card {
page-break-after: always;
}
.columns {
display: block !important;
}
}
</style>
</head>
<body>
<section class="section">
<div class="container">
<div class="columns is-multiline is-centered">
{{#each teachers as |lehrer|}}
<div
class="column is-12-tablet is-4-desktop"
>
<div class="card">
<header class="card-header">
<p class="card-header-title">
{{lehrer.teacher.ansprache}}
{{lehrer.teacher.last_name}}
</p>
</header>
<div class="card-content">
<div class="content">
<table class="table is-striped is-fullwidth">
<thead>
<tr>
<th>Zeit</th>
<th>Name</th>
</tr>
</thead>
<tbody>
{{#each lehrer.appointments as |appointment|}}
<tr>
<th>
{{time_of appointment.slot.start_time}}
{{time_of appointment.slot.end_time}}
</th>
<td>
{{appointment.nutzer.name}}
</td>
</tr>
{{/each}}
</tbody>
</table>
</div>
</div>
</div>
</div>
{{/each}}
</div>
</div>
</section>
</body>
</html>

View File

@ -5,7 +5,7 @@ use serde::{Deserialize, Serialize};
pub type IdType = i64; pub type IdType = i64;
#[derive(Debug, Deserialize, Serialize)] #[derive(Debug, Deserialize, Serialize, Clone)]
pub struct Teacher { pub struct Teacher {
pub id: IdType, pub id: IdType,
pub ansprache: String, pub ansprache: String,
@ -15,14 +15,14 @@ pub struct Teacher {
pub type Teachers = Vec<Teacher>; pub type Teachers = Vec<Teacher>;
#[derive(Debug, Deserialize, Serialize)] #[derive(Debug, Deserialize, Serialize, Clone)]
pub struct Subject { pub struct Subject {
pub id: IdType, pub id: IdType,
pub name: String, pub name: String,
} }
pub type Subjects = Vec<Subject>; pub type Subjects = Vec<Subject>;
#[derive(Debug, Deserialize, Serialize)] #[derive(Debug, Deserialize, Serialize, Clone)]
pub struct Appointment { pub struct Appointment {
pub id: IdType, pub id: IdType,
pub teacher_id: IdType, pub teacher_id: IdType,
@ -36,14 +36,14 @@ pub struct PlannedAppointment {
pub type AppointmentSlots = HashMap<IdType, AppointmentSlot>; pub type AppointmentSlots = HashMap<IdType, AppointmentSlot>;
#[derive(Debug, Deserialize, Serialize)] #[derive(Debug, Deserialize, Serialize, Clone)]
pub struct AppointmentSlot { pub struct AppointmentSlot {
pub id: IdType, pub id: IdType,
pub start_time: NaiveDateTime, pub start_time: NaiveDateTime,
pub end_time: NaiveDateTime, pub end_time: NaiveDateTime,
} }
#[derive(Debug, Deserialize, Serialize)] #[derive(Debug, Deserialize, Serialize, Clone)]
pub struct OccupiedSlot { pub struct OccupiedSlot {
pub id: IdType, pub id: IdType,
pub teacher_id: IdType, pub teacher_id: IdType,
@ -65,7 +65,7 @@ impl SlotId {
} }
} }
#[derive(Debug, Deserialize, Serialize)] #[derive(Debug, Deserialize, Serialize, Clone)]
pub enum RequestState { pub enum RequestState {
Success, Success,
Message(String), Message(String),