diff --git a/.env b/.env index fa15c38..1c93e91 100644 --- a/.env +++ b/.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" \ No newline at end of file diff --git a/Cargo.lock b/Cargo.lock index 73adb93..733a880 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -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", diff --git a/terminwahl_back/Cargo.toml b/terminwahl_back/Cargo.toml index 8f0fc4d..fe14164 100644 --- a/terminwahl_back/Cargo.toml +++ b/terminwahl_back/Cargo.toml @@ -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} diff --git a/terminwahl_back/migrations/20230127154843_initial.sql b/terminwahl_back/migrations/20230127154843_initial.sql index fe9e37d..799ae8b 100644 --- a/terminwahl_back/migrations/20230127154843_initial.sql +++ b/terminwahl_back/migrations/20230127154843_initial.sql @@ -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) ); @@ -21,4 +24,10 @@ 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 ); \ No newline at end of file diff --git a/terminwahl_back/src/api/write.rs b/terminwahl_back/src/api/write.rs index f9a42c9..9db503e 100644 --- a/terminwahl_back/src/api/write.rs +++ b/terminwahl_back/src/api/write.rs @@ -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, mailer: web::Data, - appointments: web::Json>, + handlebars: web::Data>, + input: web::Json<(Vec, Nutzer)>, ) -> Result { - 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() - .from( - "Franz Dietrich " - .parse() - .unwrap(), - ) - .to("Franz Dietrich ".parse().unwrap()) - .subject("Happy new year") - .body(String::from("Be happy!")) - .unwrap(); + let mail_result = send_confirmation_request(&nutzer, &validation_key, &handlebars, &mailer); - // 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)) + 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 " + .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 + } +} + +pub async fn confirm_validation_key( + pool: web::Data, + mailer: web::Data, + handlebars: web::Data>, + validation_key: web::Path, +) -> Result { + match confirm_appointments(&pool, &validation_key).await { + Ok(_) => Ok(HttpResponse::Ok().json(RequestState::Success)), + Err(e) => Err(error::ErrorBadRequest(e)), + } } diff --git a/terminwahl_back/src/db/read.rs b/terminwahl_back/src/db/read.rs index 343abef..c6b4cfe 100644 --- a/terminwahl_back/src/db/read.rs +++ b/terminwahl_back/src/db/read.rs @@ -49,7 +49,7 @@ pub async fn get_unavailable(db: &Pool) -> Result, sqlx::Error> SlotId, r#" SELECT teacher_id, slot_id - FROM `appointments`"#, + FROM `appointments` WHERE datetime(expires) > datetime('now');"#, ) .fetch_all(db) .await diff --git a/terminwahl_back/src/db/write.rs b/terminwahl_back/src/db/write.rs index 488ae66..c055833 100644 --- a/terminwahl_back/src/db/write.rs +++ b/terminwahl_back/src/db/write.rs @@ -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?; + .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 { + 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(()) +} diff --git a/terminwahl_back/src/main.rs b/terminwahl_back/src/main.rs index 3e62420..98afd40 100644 --- a/terminwahl_back/src/main.rs +++ b/terminwahl_back/src/main.rs @@ -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))? diff --git a/terminwahl_back/templates/email_confirm.hbs b/terminwahl_back/templates/email_confirm.hbs new file mode 100644 index 0000000..2097fde --- /dev/null +++ b/terminwahl_back/templates/email_confirm.hbs @@ -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 \ No newline at end of file diff --git a/terminwahl_front/src/main.rs b/terminwahl_front/src/main.rs index 2e0b09f..a8e71cb 100644 --- a/terminwahl_front/src/main.rs +++ b/terminwahl_front/src/main.rs @@ -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, } -#[derive(Debug, Deserialize, Serialize, Clone)] -pub struct Nutzer { - name: String, - schüler: String, - email: String, -} - impl From> for Msg { fn from(value: Result) -> Self { match value { @@ -54,12 +46,6 @@ impl From> 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 = 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 diff --git a/terminwahl_front/src/requests.rs b/terminwahl_front/src/requests.rs index ae0dabf..701bb67 100644 --- a/terminwahl_front/src/requests.rs +++ b/terminwahl_front/src/requests.rs @@ -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 { Ok(Msg::ReceivedUnavailable(response)) } -pub async fn send_appointments(appointments: Vec) -> Result { +pub async fn send_appointments( + appointments: Vec, + nutzer: Nutzer, +) -> Result { let response = Request::new("/send/appointments") .method(Method::POST) - .json(&appointments) + .json(&(&appointments, &nutzer)) .map_err(|_| Msg::AppointmentsSent(RequestState::Error))? .send() .await; diff --git a/terminwahl_typen/src/lib.rs b/terminwahl_typen/src/lib.rs index 799a957..5263d0b 100644 --- a/terminwahl_typen/src/lib.rs +++ b/terminwahl_typen/src/lib.rs @@ -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() + } +}