Compare commits

..

No commits in common. "87c0a2d54160d862cec526c9af9638063fbb2dd3" and "c708a4b79b0ba082c35905400fd35b96958c96ab" have entirely different histories.

16 changed files with 88 additions and 498 deletions

3
.env
View File

@ -1,4 +1,3 @@
DATABASE_URL="sqlite://terminwahl_back/db.sqlite"
RUST_LOG="debug"
DATABASE_URL="sqlite://db.sqlite"
SMTP_USER="SMTP_USERNAME"
SMTP_PASSWORD="SMTP_PASSWORD"

168
Cargo.lock generated
View File

@ -329,9 +329,9 @@ checksum = "619743e34b5ba4e9703bba34deac3427c72507c7159f5fd030aea8cac0cfe341"
[[package]]
name = "async-trait"
version = "0.1.64"
version = "0.1.63"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1cd7fce9ba8c3c042128ce72d8b2ddbf3a05747efb67ea0313c635e10bda47a2"
checksum = "eff18d764974428cf3a9328e23fc5c986f5fbed46e6cd4cdf42544df5d297ec1"
dependencies = [
"proc-macro2",
"quote",
@ -430,9 +430,9 @@ checksum = "14c189c53d098945499cdfa7ecc63567cf3886b3332b312a5b4585d8d3a6a610"
[[package]]
name = "bytes"
version = "1.4.0"
version = "1.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "89b2fd2a0dcf38d7971e2194b6b6eebab45ae01067456a7fd93d5547a61b70be"
checksum = "dfb24e866b15a1af2a1b663f10c6b6b8f397a84aadb828f12e5b289ec23a3a3c"
[[package]]
name = "bytestring"
@ -445,9 +445,9 @@ dependencies = [
[[package]]
name = "cc"
version = "1.0.79"
version = "1.0.78"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "50d30906286121d95be3d479533b458f87493b30a4b5f79a607db8f5d11aa91f"
checksum = "a20104e2335ce8a659d6dd92a51a767a0c062599c73b343fd152cb401e828c3d"
dependencies = [
"jobserver",
]
@ -545,9 +545,9 @@ dependencies = [
[[package]]
name = "crc"
version = "3.0.1"
version = "3.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "86ec7a15cbe22e59248fc7eadb1907dab5ba09372595da4d73dd805ed4417dfe"
checksum = "53757d12b596c16c78b83458d732a5d1a17ab3f53f2f7412f6fb57cc8a140ab3"
dependencies = [
"crc-catalog",
]
@ -805,13 +805,12 @@ dependencies = [
[[package]]
name = "futures"
version = "0.3.26"
version = "0.3.25"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "13e2792b0ff0340399d58445b88fd9770e3489eff258a4cbc1523418f12abf84"
checksum = "38390104763dc37a5145a53c29c63c1290b5d316d6086ec32c293f6736051bb0"
dependencies = [
"futures-channel",
"futures-core",
"futures-executor",
"futures-io",
"futures-sink",
"futures-task",
@ -820,9 +819,9 @@ dependencies = [
[[package]]
name = "futures-channel"
version = "0.3.26"
version = "0.3.25"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2e5317663a9089767a1ec00a487df42e0ca174b61b4483213ac24448e4664df5"
checksum = "52ba265a92256105f45b719605a571ffe2d1f0fea3807304b522c1d778f79eed"
dependencies = [
"futures-core",
"futures-sink",
@ -830,15 +829,15 @@ dependencies = [
[[package]]
name = "futures-core"
version = "0.3.26"
version = "0.3.25"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ec90ff4d0fe1f57d600049061dc6bb68ed03c7d2fbd697274c41805dcb3f8608"
checksum = "04909a7a7e4633ae6c4a9ab280aeb86da1236243a77b694a49eacd659a4bd3ac"
[[package]]
name = "futures-executor"
version = "0.3.26"
version = "0.3.25"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e8de0a35a6ab97ec8869e32a2473f4b1324459e14c29275d14b10cb1fd19b50e"
checksum = "7acc85df6714c176ab5edf386123fafe217be88c0840ec11f199441134a074e2"
dependencies = [
"futures-core",
"futures-task",
@ -858,15 +857,15 @@ dependencies = [
[[package]]
name = "futures-io"
version = "0.3.26"
version = "0.3.25"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bfb8371b6fb2aeb2d280374607aeabfc99d95c72edfe51692e42d3d7f0d08531"
checksum = "00f5fb52a06bdcadeb54e8d3671f8888a39697dcb0b81b23b55174030427f4eb"
[[package]]
name = "futures-macro"
version = "0.3.26"
version = "0.3.25"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "95a73af87da33b5acf53acfebdc339fe592ecf5357ac7c0a7734ab9d8c876a70"
checksum = "bdfb8ce053d86b91919aad980c220b1fb8401a9394410e1c289ed7e66b61835d"
dependencies = [
"proc-macro2",
"quote",
@ -875,21 +874,21 @@ dependencies = [
[[package]]
name = "futures-sink"
version = "0.3.26"
version = "0.3.25"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f310820bb3e8cfd46c80db4d7fb8353e15dfff853a127158425f31e0be6c8364"
checksum = "39c15cf1a4aa79df40f1bb462fb39676d0ad9e366c2a33b590d7c66f4f81fcf9"
[[package]]
name = "futures-task"
version = "0.3.26"
version = "0.3.25"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dcf79a1bf610b10f42aea489289c5a2c478a786509693b80cd39c44ccd936366"
checksum = "2ffb393ac5d9a6eaa9d3fdf37ae2776656b706e200c8e16b1bdb227f5198e6ea"
[[package]]
name = "futures-util"
version = "0.3.26"
version = "0.3.25"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9c1d6de3acfef38d2be4b1f543f553131788603495be83da675e180c8d6b7bd1"
checksum = "197676987abd2f9cadff84926f410af1c183608d36641465df73ae8211dc65d6"
dependencies = [
"futures-channel",
"futures-core",
@ -934,12 +933,6 @@ dependencies = [
"polyval",
]
[[package]]
name = "glob"
version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d2fabcfbdc87f4758337ca535fb41a6d701b65693ce38287d856d1674551ec9b"
[[package]]
name = "gloo"
version = "0.8.0"
@ -1124,21 +1117,6 @@ dependencies = [
"tracing",
]
[[package]]
name = "handlebars"
version = "4.3.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "035ef95d03713f2c347a72547b7cd38cbc9af7cd51e6099fb62d586d4a6dee3a"
dependencies = [
"log",
"pest",
"pest_derive",
"serde",
"serde_json",
"thiserror",
"walkdir",
]
[[package]]
name = "hashbrown"
version = "0.12.3"
@ -1640,50 +1618,6 @@ version = "2.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "478c572c3d73181ff3c2539045f6eb99e5491218eae919370993b890cdbdd98e"
[[package]]
name = "pest"
version = "2.5.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4ab62d2fa33726dbe6321cc97ef96d8cde531e3eeaf858a058de53a8a6d40d8f"
dependencies = [
"thiserror",
"ucd-trie",
]
[[package]]
name = "pest_derive"
version = "2.5.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8bf026e2d0581559db66d837fe5242320f525d85c76283c61f4d51a1238d65ea"
dependencies = [
"pest",
"pest_generator",
]
[[package]]
name = "pest_generator"
version = "2.5.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2b27bd18aa01d91c8ed2b61ea23406a676b42d82609c6e2581fba42f0c15f17f"
dependencies = [
"pest",
"pest_meta",
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "pest_meta"
version = "2.5.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9f02b677c1859756359fc9983c2e56a0237f18624a3789528804406b7e915e5d"
dependencies = [
"once_cell",
"pest",
"sha2",
]
[[package]]
name = "pin-project"
version = "1.0.12"
@ -1953,15 +1887,6 @@ version = "1.0.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7b4b9743ed687d4b4bcedf9ff5eaa7398495ae14e61cba0a295704edbc7decde"
[[package]]
name = "same-file"
version = "1.0.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502"
dependencies = [
"winapi-util",
]
[[package]]
name = "scopeguard"
version = "1.1.0"
@ -2260,12 +2185,8 @@ dependencies = [
"chrono",
"dotenv",
"env_logger",
"futures",
"glob",
"handlebars",
"lettre",
"log",
"rand",
"serde",
"serde_json",
"sqlx",
@ -2371,9 +2292,9 @@ checksum = "cda74da7e1a664f795bb1f8a87ec406fb89a02522cf6e50620d016add6dbbf5c"
[[package]]
name = "tokio"
version = "1.25.0"
version = "1.24.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c8e00990ebabbe4c14c08aca901caed183ecd5c09562a12c824bb53d3c3fd3af"
checksum = "597a12a59981d9e3c38d216785b0c37399f6e415e8d0712047620f189371b0bb"
dependencies = [
"autocfg",
"bytes",
@ -2463,12 +2384,6 @@ version = "1.16.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "497961ef93d974e23eb6f433eb5fe1b7930b659f06d12dec6fc44a8f554c0bba"
[[package]]
name = "ucd-trie"
version = "0.1.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9e79c4d996edb816c91e4308506774452e55e95c3c9de07b6729e17e15a5ef81"
[[package]]
name = "unicase"
version = "2.6.0"
@ -2501,9 +2416,9 @@ dependencies = [
[[package]]
name = "unicode-segmentation"
version = "1.10.1"
version = "1.10.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1dd624098567895118886609431a7c3b8f516e41d30e0643f03d94592a147e36"
checksum = "0fdbf052a0783de01e944a6ce7a8cb939e295b1e7be835a1112c3b9a7f047a5a"
[[package]]
name = "unicode-width"
@ -2566,17 +2481,6 @@ version = "0.9.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f"
[[package]]
name = "walkdir"
version = "2.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "808cf2735cd4b6866113f648b791c6adc5714537bc222d9347bb203386ffda56"
dependencies = [
"same-file",
"winapi",
"winapi-util",
]
[[package]]
name = "wasi"
version = "0.10.0+wasi-snapshot-preview1"
@ -2814,18 +2718,18 @@ dependencies = [
[[package]]
name = "zstd"
version = "0.12.3+zstd.1.5.2"
version = "0.12.2+zstd.1.5.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "76eea132fb024e0e13fd9c2f5d5d595d8a967aa72382ac2f9d39fcc95afd0806"
checksum = "e9262a83dc741c0b0ffec209881b45dbc232c21b02a2b9cb1adb93266e41303d"
dependencies = [
"zstd-safe",
]
[[package]]
name = "zstd-safe"
version = "6.0.3+zstd.1.5.2"
version = "6.0.2+zstd.1.5.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "68e4a3f57d13d0ab7e478665c60f35e2a613dcd527851c2c7287ce5c787e134a"
checksum = "a6cf39f730b440bab43da8fb5faf5f254574462f73f260f85f7987f32154ff17"
dependencies = [
"libc",
"zstd-sys",
@ -2833,9 +2737,9 @@ dependencies = [
[[package]]
name = "zstd-sys"
version = "2.0.6+zstd.1.5.2"
version = "2.0.5+zstd.1.5.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "68a3f9792c0c3dc6c165840a75f47ae1f4da402c2d006881129579f6597e801b"
checksum = "edc50ffce891ad571e9f9afe5039c4837bede781ac4bb13052ed7ae695518596"
dependencies = [
"cc",
"libc",

View File

@ -6,7 +6,6 @@ default-run = "terminwahl_back"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
futures = "*"
actix-web = "4.3"
actix-rt = "2.8"
actix-files = "0.6.2"
@ -21,9 +20,6 @@ dotenv = "*"
env_logger = "0.10"
log = "*"
lettre = {version="0.10", default-features = false, features = ["smtp-transport", "tokio1-rustls-tls", "hostname", "builder", "pool"]}
rand = "*"
handlebars = {version="4.3", features=["dir_source"]}
glob = "*"
terminwahl_typen={path="../terminwahl_typen/"}
serde = {workspace = true}

View File

@ -14,9 +14,6 @@ CREATE TABLE appointments (
id INTEGER PRIMARY KEY,
teacher_id INTEGER NOT NULL,
slot_id INTEGER NOT NULL,
nutzer_id INTEGER NOT NULL,
validation_key TEXT NOT NULL,
expires DATETIME,
FOREIGN KEY(teacher_id) REFERENCES teachers(id),
FOREIGN KEY(slot_id) REFERENCES appointment_slots(id)
);
@ -24,10 +21,4 @@ CREATE TABLE appointment_slots (
id INTEGER PRIMARY KEY,
start_time DATETIME NOT NULL,
end_time DATETIME NOT NULL
);
CREATE TABLE nutzer (
id INTEGER PRIMARY KEY,
name TEXT NOT NULL,
schueler TEXT NOT NULL,
email TEXT NOT NULL
);

View File

@ -1,134 +1,37 @@
use std::error::Error;
use actix_web::{error, web, HttpResponse};
use futures::future;
use handlebars::Handlebars;
use lettre::{message::header::ContentType, Message, SmtpTransport, Transport};
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 lettre::{
transport::smtp::{authentication::Credentials, SmtpTransportBuilder},
Message, SmtpTransport, Transport,
};
use terminwahl_typen::{PlannedAppointment, RequestState};
use crate::db::{
self,
read::{get_slot_by_id, get_teacher_by_id},
write::confirm_appointments,
Pool,
};
#[derive(Serialize)]
pub struct FullAppointment {
pub teacher: Teacher,
pub slot: AppointmentSlot,
}
impl FullAppointment {
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
.expect("Teacher not found"),
slot: get_slot_by_id(pool, slot_id).await.expect("Slot not found"),
})
}
}
use crate::db::{self, Pool};
pub async fn save_appointments_json(
pool: web::Data<Pool>,
mailer: web::Data<SmtpTransport>,
handlebars: web::Data<Handlebars<'_>>,
input: web::Json<(Vec<PlannedAppointment>, Nutzer)>,
appointments: web::Json<Vec<PlannedAppointment>>,
) -> Result<HttpResponse, error::Error> {
debug!("Extracting data");
let (appointments, nutzer) = input.into_inner();
debug!("Saving user");
let nutzer_id = db::write::save_nutzer(&pool, &nutzer)
.await
.map_err(error::ErrorInternalServerError)?;
debug!("Saving appointments");
let validation_key: String = thread_rng()
.sample_iter(&Alphanumeric)
.take(30)
.map(char::from)
.collect();
db::write::save_appointments(&pool, &appointments, nutzer_id, &validation_key)
db::write::save_appointments(&pool, &appointments)
.await
.map_err(error::ErrorInternalServerError)?;
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");
let email = Message::builder()
.from(
"Franz Dietrich <franz.dietrich@uhlandshoehe.de>"
.parse()
.unwrap(),
)
.to("Franz Dietrich <dietrich@teilgedanken.de>".parse().unwrap())
.subject("Happy new year")
.body(String::from("Be happy!"))
.unwrap();
let mail_result = send_confirmation_request(
&full_appointments,
&nutzer,
&validation_key,
&handlebars,
&mailer,
);
Ok(HttpResponse::Ok().json(mail_result))
}
pub fn send_confirmation_request(
appointments: &Vec<FullAppointment>,
nutzer: &Nutzer,
validation_key: &str,
handlebars: &Handlebars,
mailer: &SmtpTransport,
) -> RequestState {
let data = json! {
{
"appointments": appointments,
"nutzer": nutzer,
"validation_key": validation_key
}};
debug!("{:?}", handlebars.get_templates());
if let Ok(email_text) = handlebars.render("email_confirm", &data) {
let email = match Message::builder()
.from(
"Franz Dietrich <franz.dietrich@uhlandshoehe.de>"
.parse()
.expect("Should not fail"),
)
.to(
match format!("{} <{}>", nutzer.name, nutzer.email).parse() {
Ok(v) => v,
Err(_) => return RequestState::Error,
},
)
.subject("Elternsprechtag: Bestätigen Sie Ihre Termine")
.header(ContentType::TEXT_PLAIN)
.body(
match lettre::message::Body::new_with_encoding(
email_text,
lettre::message::header::ContentTransferEncoding::Base64,
) {
Ok(body) => body,
Err(_) => return RequestState::Error,
},
) {
Ok(message) => message,
Err(_) => return RequestState::Error,
};
// Send the email
match mailer.send(&email) {
Ok(_) => RequestState::Success,
Err(e) => {
debug!("Failed to send: {e}");
RequestState::Error
}
}
} else {
RequestState::Error
// Send the email
match mailer.send(&email) {
Ok(_) => println!("Email sent successfully!"),
Err(e) => panic!("Could not send email: {:?}", e),
}
Ok(HttpResponse::Ok().json(RequestState::Success))
}

View File

@ -3,7 +3,7 @@ use std::collections::HashSet;
use sqlx::query_as;
use terminwahl_typen::{
AppointmentSlot, AppointmentSlots, IdType, SlotId, Subject, Subjects, Teacher, Teachers,
AppointmentSlot, AppointmentSlots, SlotId, Subject, Subjects, Teacher, Teachers,
};
use super::Pool;
@ -19,18 +19,6 @@ pub async fn get_teachers(db: &Pool) -> Result<Teachers, sqlx::Error> {
.await
}
pub async fn get_teacher_by_id(db: &Pool, teacher_id: IdType) -> Result<Teacher, sqlx::Error> {
query_as!(
Teacher,
r#"
SELECT *
FROM `teachers` WHERE id = ?"#,
teacher_id
)
.fetch_one(db)
.await
}
pub async fn get_subjects(db: &Pool) -> Result<Subjects, sqlx::Error> {
query_as!(
Subject,
@ -56,27 +44,12 @@ pub async fn get_slots(db: &Pool) -> Result<AppointmentSlots, sqlx::Error> {
Err(e) => Err(e),
}
}
pub async fn get_slot_by_id(db: &Pool, slot_id: IdType) -> Result<AppointmentSlot, sqlx::Error> {
match query_as!(
AppointmentSlot,
r#"
SELECT *
FROM `appointment_slots` WHERE id = ?"#,
slot_id
)
.fetch_one(db)
.await
{
Ok(slot) => Ok(slot),
Err(e) => Err(e),
}
}
pub async fn get_unavailable(db: &Pool) -> Result<HashSet<SlotId>, sqlx::Error> {
match query_as!(
SlotId,
r#"
SELECT teacher_id, slot_id
FROM `appointments` WHERE datetime(expires) > datetime('now');"#,
FROM `appointments`"#,
)
.fetch_all(db)
.await

View File

@ -1,62 +1,17 @@
use chrono::{Duration, Local};
use sqlx::query;
use terminwahl_typen::{IdType, Nutzer, PlannedAppointment};
use terminwahl_typen::PlannedAppointment;
use super::Pool;
pub async fn save_appointments(
pool: &Pool,
appointments: &[PlannedAppointment],
nutzer_id: IdType,
validation_key: &str,
) -> Result<(), sqlx::Error> {
for appointment in appointments {
let _ = query!("DELETE FROM appointments WHERE datetime(expires) < datetime('now');")
sqlx::query("INSERT INTO appointments (teacher_id, slot_id) VALUES ($1, $2)")
.bind(appointment.teacher_id)
.bind(appointment.slot_id)
.execute(pool)
.await;
let now = Local::now().naive_local();
let in_three_hours = now + Duration::hours(3);
query!(
"INSERT INTO appointments (teacher_id, slot_id, nutzer_id, validation_key, expires) VALUES ($1, $2, $3, $4, $5)",
appointment.teacher_id,
appointment.slot_id,
nutzer_id,
validation_key,
in_three_hours
)
.execute(pool)
.await?;
.await?;
}
Ok(())
}
pub async fn save_nutzer(pool: &Pool, nutzer: &Nutzer) -> Result<IdType, sqlx::Error> {
query!(
"INSERT INTO nutzer (name, schueler, email) VALUES ($1, $2, $3)",
nutzer.name,
nutzer.schüler,
nutzer.email
)
.execute(pool)
.await?;
let db_nutzer = query!(
"SELECT id FROM nutzer WHERE name = ? and email = ?",
nutzer.name,
nutzer.email
)
.fetch_one(pool)
.await?;
Ok(db_nutzer.id)
}
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(())
}

View File

@ -1,28 +0,0 @@
use handlebars::{Context, Handlebars, Helper, HelperDef, HelperResult, Output, RenderContext};
// implement by a structure impls HelperDef
#[derive(Clone, Copy)]
pub struct TimeOfDate;
impl HelperDef for TimeOfDate {
fn call<'reg: 'rc, 'rc>(
&self,
h: &Helper,
_: &Handlebars,
_: &Context,
_: &mut RenderContext,
out: &mut dyn Output,
) -> HelperResult {
let date = h
.param(0)
.and_then(|v| v.value().as_str())
.unwrap_or("")
.to_owned();
let time_reversed = date.chars().rev();
let only_time: String = time_reversed.take(8).collect();
let only_minutes: String = only_time.chars().rev().take(5).collect();
out.write(&only_minutes)?;
Ok(())
}
}

View File

@ -1,4 +1,2 @@
pub mod api;
pub mod db;
pub mod handlebars_helper;
pub mod views;

View File

@ -7,11 +7,9 @@ use actix_web::{
web, App, HttpServer,
};
use dotenv::dotenv;
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};
#[actix_web::main]
async fn main() -> std::io::Result<()> {
@ -32,14 +30,7 @@ async fn main() -> std::io::Result<()> {
// Connection pool settings
.build();
let mut handlebars = Handlebars::new();
handlebars.register_helper("time_of", Box::new(TimeOfDate));
handlebars
.register_templates_directory(".hbs", "terminwahl_back/templates")
.unwrap();
log::info!("starting HTTP server at http://localhost:8080");
debug!("{:?}", handlebars.get_templates());
HttpServer::new(move || {
log::debug!("Constructing the App");
@ -57,7 +48,6 @@ async fn main() -> std::io::Result<()> {
App::new()
.app_data(web::Data::new(pool.clone()))
.app_data(web::Data::new(smtp_pool.clone()))
.app_data(web::Data::new(handlebars.clone()))
.wrap(Logger::default())
.wrap(session_store)
.wrap(error_handlers)
@ -76,10 +66,6 @@ async fn main() -> std::io::Result<()> {
web::resource("/send/appointments")
.route(web::post().to(api::write::save_appointments_json)),
)
.service(
web::resource("/confirm/{validation_key}")
.route(web::get().to(views::confirm_validation_key)),
)
.service(Files::new("/", "./terminwahl_front/dist/").index_file("index.html"))
})
.bind(("127.0.0.1", 8080))?

View File

@ -1,29 +0,0 @@
use actix_web::{error, web, HttpResponse};
use glob::glob;
use handlebars::Handlebars;
use lettre::SmtpTransport;
use serde_json::json;
use terminwahl_typen::RequestState;
use crate::db::{write::confirm_appointments, Pool};
pub async fn confirm_validation_key(
pool: web::Data<Pool>,
mailer: web::Data<SmtpTransport>,
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())),
Err(e) => Err(error::ErrorBadRequest(e)),
}
}

View File

@ -1,29 +0,0 @@
<!DOCTYPE html>
<html lang="de">
<head>
<meta charset="utf-8" />
<title>Yew App</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" />
</head>
<body>
<section class="hero is-warning">
<div class="hero-body">
<p class="title has-text-link">
Elternsprechtag
</p>
<p class="subtitle">
Am 28.02.23
</p>
</div>
</section>
<div class="container">
<div class="section">
<h1 class="title is-1">Bestätigt</h1>
</div>
</div>
</body>
</html>

View File

@ -1,21 +0,0 @@
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.
{{#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}}
Vielen Dank für Ihre Anmeldung!
Das Oberstufenkollegium

View File

@ -3,9 +3,10 @@ use std::collections::{HashMap, HashSet};
use gloo::console::log;
use requests::{fetch_slots, fetch_teachers, fetch_unavailable, send_appointments};
use serde::{Deserialize, Serialize};
use terminwahl_typen::{
AppointmentSlot, AppointmentSlots, IdType, Nutzer, PlannedAppointment, RequestState, SlotId,
Teacher, Teachers,
AppointmentSlot, AppointmentSlots, IdType, PlannedAppointment, RequestState, SlotId, Teacher,
Teachers,
};
use web_sys::HtmlInputElement;
use yew::prelude::*;
@ -37,6 +38,13 @@ pub struct App {
successfully_saved: Option<RequestState>,
}
#[derive(Debug, Deserialize, Serialize, Clone)]
pub struct Nutzer {
name: String,
schüler: String,
email: String,
}
impl From<Result<Msg, Msg>> for Msg {
fn from(value: Result<Msg, Msg>) -> Self {
match value {
@ -46,6 +54,12 @@ impl From<Result<Msg, Msg>> for Msg {
}
}
impl Nutzer {
fn validate(&self) -> bool {
!self.name.is_empty() && !self.email.is_empty() && !self.schüler.is_empty()
}
}
impl Component for App {
type Message = Msg;
type Properties = ();
@ -123,13 +137,7 @@ impl Component for App {
if (1..=3).contains(&self.appointments.len()) {
let values = self.appointments.clone().into_values();
let appointments: Vec<PlannedAppointment> = values.collect();
ctx.link().send_future(send_appointments(
appointments,
self.nutzer
.as_ref()
.expect("This should always exist")
.clone(),
));
ctx.link().send_future(send_appointments(appointments));
true
} else {
true

View File

@ -1,5 +1,5 @@
use gloo::net::http::{Method, Request};
use terminwahl_typen::{Nutzer, PlannedAppointment, RequestState};
use terminwahl_typen::{PlannedAppointment, RequestState};
use crate::Msg;
@ -39,13 +39,10 @@ pub async fn fetch_unavailable() -> Result<Msg, Msg> {
Ok(Msg::ReceivedUnavailable(response))
}
pub async fn send_appointments(
appointments: Vec<PlannedAppointment>,
nutzer: Nutzer,
) -> Result<Msg, Msg> {
pub async fn send_appointments(appointments: Vec<PlannedAppointment>) -> Result<Msg, Msg> {
let response = Request::new("/send/appointments")
.method(Method::POST)
.json(&(&appointments, &nutzer))
.json(&appointments)
.map_err(|_| Msg::AppointmentsSent(RequestState::Error))?
.send()
.await;

View File

@ -71,16 +71,3 @@ pub enum RequestState {
Message(String),
Error,
}
#[derive(Debug, Deserialize, Serialize, Clone)]
pub struct Nutzer {
pub name: String,
pub schüler: String,
pub email: String,
}
impl Nutzer {
pub fn validate(&self) -> bool {
!self.name.is_empty() && !self.email.is_empty() && !self.schüler.is_empty()
}
}