emails funktionieren
This commit is contained in:
parent
c708a4b79b
commit
00aae2dfca
3
.env
3
.env
@ -1,3 +1,4 @@
|
||||
DATABASE_URL="sqlite://db.sqlite"
|
||||
DATABASE_URL="sqlite://terminwahl_back/db.sqlite"
|
||||
RUST_LOG="debug"
|
||||
SMTP_USER="SMTP_USERNAME"
|
||||
SMTP_PASSWORD="SMTP_PASSWORD"
|
159
Cargo.lock
generated
159
Cargo.lock
generated
@ -329,9 +329,9 @@ checksum = "619743e34b5ba4e9703bba34deac3427c72507c7159f5fd030aea8cac0cfe341"
|
||||
|
||||
[[package]]
|
||||
name = "async-trait"
|
||||
version = "0.1.63"
|
||||
version = "0.1.64"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "eff18d764974428cf3a9328e23fc5c986f5fbed46e6cd4cdf42544df5d297ec1"
|
||||
checksum = "1cd7fce9ba8c3c042128ce72d8b2ddbf3a05747efb67ea0313c635e10bda47a2"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
@ -430,9 +430,9 @@ checksum = "14c189c53d098945499cdfa7ecc63567cf3886b3332b312a5b4585d8d3a6a610"
|
||||
|
||||
[[package]]
|
||||
name = "bytes"
|
||||
version = "1.3.0"
|
||||
version = "1.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "dfb24e866b15a1af2a1b663f10c6b6b8f397a84aadb828f12e5b289ec23a3a3c"
|
||||
checksum = "89b2fd2a0dcf38d7971e2194b6b6eebab45ae01067456a7fd93d5547a61b70be"
|
||||
|
||||
[[package]]
|
||||
name = "bytestring"
|
||||
@ -445,9 +445,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "cc"
|
||||
version = "1.0.78"
|
||||
version = "1.0.79"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a20104e2335ce8a659d6dd92a51a767a0c062599c73b343fd152cb401e828c3d"
|
||||
checksum = "50d30906286121d95be3d479533b458f87493b30a4b5f79a607db8f5d11aa91f"
|
||||
dependencies = [
|
||||
"jobserver",
|
||||
]
|
||||
@ -545,9 +545,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "crc"
|
||||
version = "3.0.0"
|
||||
version = "3.0.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "53757d12b596c16c78b83458d732a5d1a17ab3f53f2f7412f6fb57cc8a140ab3"
|
||||
checksum = "86ec7a15cbe22e59248fc7eadb1907dab5ba09372595da4d73dd805ed4417dfe"
|
||||
dependencies = [
|
||||
"crc-catalog",
|
||||
]
|
||||
@ -805,9 +805,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "futures"
|
||||
version = "0.3.25"
|
||||
version = "0.3.26"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "38390104763dc37a5145a53c29c63c1290b5d316d6086ec32c293f6736051bb0"
|
||||
checksum = "13e2792b0ff0340399d58445b88fd9770e3489eff258a4cbc1523418f12abf84"
|
||||
dependencies = [
|
||||
"futures-channel",
|
||||
"futures-core",
|
||||
@ -819,9 +819,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "futures-channel"
|
||||
version = "0.3.25"
|
||||
version = "0.3.26"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "52ba265a92256105f45b719605a571ffe2d1f0fea3807304b522c1d778f79eed"
|
||||
checksum = "2e5317663a9089767a1ec00a487df42e0ca174b61b4483213ac24448e4664df5"
|
||||
dependencies = [
|
||||
"futures-core",
|
||||
"futures-sink",
|
||||
@ -829,15 +829,15 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "futures-core"
|
||||
version = "0.3.25"
|
||||
version = "0.3.26"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "04909a7a7e4633ae6c4a9ab280aeb86da1236243a77b694a49eacd659a4bd3ac"
|
||||
checksum = "ec90ff4d0fe1f57d600049061dc6bb68ed03c7d2fbd697274c41805dcb3f8608"
|
||||
|
||||
[[package]]
|
||||
name = "futures-executor"
|
||||
version = "0.3.25"
|
||||
version = "0.3.26"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7acc85df6714c176ab5edf386123fafe217be88c0840ec11f199441134a074e2"
|
||||
checksum = "e8de0a35a6ab97ec8869e32a2473f4b1324459e14c29275d14b10cb1fd19b50e"
|
||||
dependencies = [
|
||||
"futures-core",
|
||||
"futures-task",
|
||||
@ -857,15 +857,15 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "futures-io"
|
||||
version = "0.3.25"
|
||||
version = "0.3.26"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "00f5fb52a06bdcadeb54e8d3671f8888a39697dcb0b81b23b55174030427f4eb"
|
||||
checksum = "bfb8371b6fb2aeb2d280374607aeabfc99d95c72edfe51692e42d3d7f0d08531"
|
||||
|
||||
[[package]]
|
||||
name = "futures-macro"
|
||||
version = "0.3.25"
|
||||
version = "0.3.26"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bdfb8ce053d86b91919aad980c220b1fb8401a9394410e1c289ed7e66b61835d"
|
||||
checksum = "95a73af87da33b5acf53acfebdc339fe592ecf5357ac7c0a7734ab9d8c876a70"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
@ -874,21 +874,21 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "futures-sink"
|
||||
version = "0.3.25"
|
||||
version = "0.3.26"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "39c15cf1a4aa79df40f1bb462fb39676d0ad9e366c2a33b590d7c66f4f81fcf9"
|
||||
checksum = "f310820bb3e8cfd46c80db4d7fb8353e15dfff853a127158425f31e0be6c8364"
|
||||
|
||||
[[package]]
|
||||
name = "futures-task"
|
||||
version = "0.3.25"
|
||||
version = "0.3.26"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2ffb393ac5d9a6eaa9d3fdf37ae2776656b706e200c8e16b1bdb227f5198e6ea"
|
||||
checksum = "dcf79a1bf610b10f42aea489289c5a2c478a786509693b80cd39c44ccd936366"
|
||||
|
||||
[[package]]
|
||||
name = "futures-util"
|
||||
version = "0.3.25"
|
||||
version = "0.3.26"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "197676987abd2f9cadff84926f410af1c183608d36641465df73ae8211dc65d6"
|
||||
checksum = "9c1d6de3acfef38d2be4b1f543f553131788603495be83da675e180c8d6b7bd1"
|
||||
dependencies = [
|
||||
"futures-channel",
|
||||
"futures-core",
|
||||
@ -1117,6 +1117,21 @@ 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"
|
||||
@ -1618,6 +1633,50 @@ 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"
|
||||
@ -1887,6 +1946,15 @@ 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"
|
||||
@ -2185,8 +2253,10 @@ dependencies = [
|
||||
"chrono",
|
||||
"dotenv",
|
||||
"env_logger",
|
||||
"handlebars",
|
||||
"lettre",
|
||||
"log",
|
||||
"rand",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"sqlx",
|
||||
@ -2292,9 +2362,9 @@ checksum = "cda74da7e1a664f795bb1f8a87ec406fb89a02522cf6e50620d016add6dbbf5c"
|
||||
|
||||
[[package]]
|
||||
name = "tokio"
|
||||
version = "1.24.2"
|
||||
version = "1.25.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "597a12a59981d9e3c38d216785b0c37399f6e415e8d0712047620f189371b0bb"
|
||||
checksum = "c8e00990ebabbe4c14c08aca901caed183ecd5c09562a12c824bb53d3c3fd3af"
|
||||
dependencies = [
|
||||
"autocfg",
|
||||
"bytes",
|
||||
@ -2384,6 +2454,12 @@ 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"
|
||||
@ -2416,9 +2492,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "unicode-segmentation"
|
||||
version = "1.10.0"
|
||||
version = "1.10.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0fdbf052a0783de01e944a6ce7a8cb939e295b1e7be835a1112c3b9a7f047a5a"
|
||||
checksum = "1dd624098567895118886609431a7c3b8f516e41d30e0643f03d94592a147e36"
|
||||
|
||||
[[package]]
|
||||
name = "unicode-width"
|
||||
@ -2481,6 +2557,17 @@ 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"
|
||||
@ -2718,18 +2805,18 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "zstd"
|
||||
version = "0.12.2+zstd.1.5.2"
|
||||
version = "0.12.3+zstd.1.5.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e9262a83dc741c0b0ffec209881b45dbc232c21b02a2b9cb1adb93266e41303d"
|
||||
checksum = "76eea132fb024e0e13fd9c2f5d5d595d8a967aa72382ac2f9d39fcc95afd0806"
|
||||
dependencies = [
|
||||
"zstd-safe",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "zstd-safe"
|
||||
version = "6.0.2+zstd.1.5.2"
|
||||
version = "6.0.3+zstd.1.5.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a6cf39f730b440bab43da8fb5faf5f254574462f73f260f85f7987f32154ff17"
|
||||
checksum = "68e4a3f57d13d0ab7e478665c60f35e2a613dcd527851c2c7287ce5c787e134a"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"zstd-sys",
|
||||
@ -2737,9 +2824,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "zstd-sys"
|
||||
version = "2.0.5+zstd.1.5.2"
|
||||
version = "2.0.6+zstd.1.5.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "edc50ffce891ad571e9f9afe5039c4837bede781ac4bb13052ed7ae695518596"
|
||||
checksum = "68a3f9792c0c3dc6c165840a75f47ae1f4da402c2d006881129579f6597e801b"
|
||||
dependencies = [
|
||||
"cc",
|
||||
"libc",
|
||||
|
@ -20,6 +20,8 @@ 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"]}
|
||||
|
||||
terminwahl_typen={path="../terminwahl_typen/"}
|
||||
serde = {workspace = true}
|
||||
|
@ -14,6 +14,9 @@ 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)
|
||||
);
|
||||
@ -22,3 +25,9 @@ CREATE TABLE appointment_slots (
|
||||
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
|
||||
);
|
@ -1,37 +1,101 @@
|
||||
use actix_web::{error, web, HttpResponse};
|
||||
use lettre::{
|
||||
transport::smtp::{authentication::Credentials, SmtpTransportBuilder},
|
||||
Message, SmtpTransport, Transport,
|
||||
};
|
||||
use terminwahl_typen::{PlannedAppointment, RequestState};
|
||||
use handlebars::Handlebars;
|
||||
use lettre::{message::header::ContentType, Message, SmtpTransport, Transport};
|
||||
use log::debug;
|
||||
use rand::{distributions::Alphanumeric, thread_rng, Rng};
|
||||
use serde_json::json;
|
||||
use terminwahl_typen::{Nutzer, PlannedAppointment, RequestState};
|
||||
|
||||
use crate::db::{self, Pool};
|
||||
use crate::db::{self, write::confirm_appointments, Pool};
|
||||
|
||||
pub async fn save_appointments_json(
|
||||
pool: web::Data<Pool>,
|
||||
mailer: web::Data<SmtpTransport>,
|
||||
appointments: web::Json<Vec<PlannedAppointment>>,
|
||||
handlebars: web::Data<Handlebars<'_>>,
|
||||
input: web::Json<(Vec<PlannedAppointment>, Nutzer)>,
|
||||
) -> Result<HttpResponse, error::Error> {
|
||||
db::write::save_appointments(&pool, &appointments)
|
||||
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)
|
||||
.await
|
||||
.map_err(error::ErrorInternalServerError)?;
|
||||
|
||||
let email = Message::builder()
|
||||
let mail_result = send_confirmation_request(&nutzer, &validation_key, &handlebars, &mailer);
|
||||
|
||||
Ok(HttpResponse::Ok().json(mail_result))
|
||||
}
|
||||
|
||||
pub fn send_confirmation_request(
|
||||
nutzer: &Nutzer,
|
||||
validation_key: &str,
|
||||
handlebars: &Handlebars,
|
||||
mailer: &SmtpTransport,
|
||||
) -> RequestState {
|
||||
let data = json! {
|
||||
{
|
||||
"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()
|
||||
.unwrap(),
|
||||
.expect("Should not fail"),
|
||||
)
|
||||
.to("Franz Dietrich <dietrich@teilgedanken.de>".parse().unwrap())
|
||||
.subject("Happy new year")
|
||||
.body(String::from("Be happy!"))
|
||||
.unwrap();
|
||||
.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(_) => println!("Email sent successfully!"),
|
||||
Err(e) => panic!("Could not send email: {:?}", e),
|
||||
Ok(_) => RequestState::Success,
|
||||
Err(e) => {
|
||||
debug!("Failed to send: {e}");
|
||||
RequestState::Error
|
||||
}
|
||||
}
|
||||
} else {
|
||||
RequestState::Error
|
||||
}
|
||||
}
|
||||
|
||||
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> {
|
||||
match confirm_appointments(&pool, &validation_key).await {
|
||||
Ok(_) => Ok(HttpResponse::Ok().json(RequestState::Success)),
|
||||
Err(e) => Err(error::ErrorBadRequest(e)),
|
||||
}
|
||||
|
||||
Ok(HttpResponse::Ok().json(RequestState::Success))
|
||||
}
|
||||
|
@ -49,7 +49,7 @@ pub async fn get_unavailable(db: &Pool) -> Result<HashSet<SlotId>, sqlx::Error>
|
||||
SlotId,
|
||||
r#"
|
||||
SELECT teacher_id, slot_id
|
||||
FROM `appointments`"#,
|
||||
FROM `appointments` WHERE datetime(expires) > datetime('now');"#,
|
||||
)
|
||||
.fetch_all(db)
|
||||
.await
|
||||
|
@ -1,17 +1,62 @@
|
||||
use terminwahl_typen::PlannedAppointment;
|
||||
use chrono::{Duration, Local};
|
||||
use sqlx::query;
|
||||
use terminwahl_typen::{IdType, Nutzer, 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 {
|
||||
sqlx::query("INSERT INTO appointments (teacher_id, slot_id) VALUES ($1, $2)")
|
||||
.bind(appointment.teacher_id)
|
||||
.bind(appointment.slot_id)
|
||||
let _ = query!("DELETE FROM appointments WHERE datetime(expires) < datetime('now');")
|
||||
.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?;
|
||||
}
|
||||
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(())
|
||||
}
|
||||
|
@ -7,7 +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};
|
||||
|
||||
@ -30,7 +32,13 @@ async fn main() -> std::io::Result<()> {
|
||||
// Connection pool settings
|
||||
.build();
|
||||
|
||||
let mut handlebars = Handlebars::new();
|
||||
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");
|
||||
@ -48,6 +56,7 @@ 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)
|
||||
@ -66,6 +75,10 @@ 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(api::write::confirm_validation_key)),
|
||||
)
|
||||
.service(Files::new("/", "./terminwahl_front/dist/").index_file("index.html"))
|
||||
})
|
||||
.bind(("127.0.0.1", 8080))?
|
||||
|
8
terminwahl_back/templates/email_confirm.hbs
Normal file
8
terminwahl_back/templates/email_confirm.hbs
Normal file
@ -0,0 +1,8 @@
|
||||
Sehr geehrte/r {{ nutzer.name }},
|
||||
|
||||
bitte bestätigen Sie durch klick auf den folgenden Link ihre Anmeldung für die Termine
|
||||
|
||||
https://elternsprechtag.uhle.cloud/confirm/{{ validation_key }}
|
||||
|
||||
Vielen Dank für Ihre Anmeldung!
|
||||
Das Oberstufenkollegium
|
@ -3,10 +3,9 @@ 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, PlannedAppointment, RequestState, SlotId, Teacher,
|
||||
Teachers,
|
||||
AppointmentSlot, AppointmentSlots, IdType, Nutzer, PlannedAppointment, RequestState, SlotId,
|
||||
Teacher, Teachers,
|
||||
};
|
||||
use web_sys::HtmlInputElement;
|
||||
use yew::prelude::*;
|
||||
@ -38,13 +37,6 @@ 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 {
|
||||
@ -54,12 +46,6 @@ 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 = ();
|
||||
@ -137,7 +123,13 @@ 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));
|
||||
ctx.link().send_future(send_appointments(
|
||||
appointments,
|
||||
self.nutzer
|
||||
.as_ref()
|
||||
.expect("This should always exist")
|
||||
.clone(),
|
||||
));
|
||||
true
|
||||
} else {
|
||||
true
|
||||
|
@ -1,5 +1,5 @@
|
||||
use gloo::net::http::{Method, Request};
|
||||
use terminwahl_typen::{PlannedAppointment, RequestState};
|
||||
use terminwahl_typen::{Nutzer, PlannedAppointment, RequestState};
|
||||
|
||||
use crate::Msg;
|
||||
|
||||
@ -39,10 +39,13 @@ pub async fn fetch_unavailable() -> Result<Msg, Msg> {
|
||||
Ok(Msg::ReceivedUnavailable(response))
|
||||
}
|
||||
|
||||
pub async fn send_appointments(appointments: Vec<PlannedAppointment>) -> Result<Msg, Msg> {
|
||||
pub async fn send_appointments(
|
||||
appointments: Vec<PlannedAppointment>,
|
||||
nutzer: Nutzer,
|
||||
) -> Result<Msg, Msg> {
|
||||
let response = Request::new("/send/appointments")
|
||||
.method(Method::POST)
|
||||
.json(&appointments)
|
||||
.json(&(&appointments, &nutzer))
|
||||
.map_err(|_| Msg::AppointmentsSent(RequestState::Error))?
|
||||
.send()
|
||||
.await;
|
||||
|
@ -71,3 +71,16 @@ 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()
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user