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)
 | 
			
		||||
);
 | 
			
		||||
@ -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
 | 
			
		||||
);
 | 
			
		||||
@ -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()
 | 
			
		||||
        .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(&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 <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
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
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)),
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -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?;
 | 
			
		||||
            .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…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user