Compare commits
10 Commits
6b7415f0dd
...
48b8ff7bd7
Author | SHA1 | Date | |
---|---|---|---|
48b8ff7bd7 | |||
ca427153d9 | |||
a3e4a77b91 | |||
d7b5694188 | |||
c0efdf8b49 | |||
b067be722d | |||
165d68ca50 | |||
6ea404845a | |||
f79cbc36c4 | |||
1cb3090f64 |
1
.gitignore
vendored
1
.gitignore
vendored
@ -1,5 +1,4 @@
|
|||||||
/*/target
|
/*/target
|
||||||
/target
|
/target
|
||||||
terminwahl_front/dist
|
terminwahl_front/dist
|
||||||
|
|
||||||
db.sqlite*
|
db.sqlite*
|
2752
Cargo.lock
generated
2752
Cargo.lock
generated
File diff suppressed because it is too large
Load Diff
@ -1,7 +1,8 @@
|
|||||||
[workspace]
|
[workspace]
|
||||||
members = ["terminwahl_back", "terminwahl_front"]
|
members = ["terminwahl_back", "terminwahl_front"]
|
||||||
|
workspace.resolver = "2"
|
||||||
|
|
||||||
[workspace.dependencies]
|
[workspace.dependencies]
|
||||||
serde = {version="1.0", features = ["derive"]}
|
serde = { version = "1.0", features = ["derive"] }
|
||||||
serde_json = "1.0"
|
serde_json = "1.0"
|
||||||
chrono = {version="*", features = ["serde"]}
|
chrono = { version = "*", features = ["serde"] }
|
||||||
|
81
README.md
Normal file
81
README.md
Normal file
@ -0,0 +1,81 @@
|
|||||||
|
# Project Name
|
||||||
|
|
||||||
|
## Description
|
||||||
|
|
||||||
|
This project is a webpage that simplifies management of parentsday at our school.
|
||||||
|
|
||||||
|
## Installation
|
||||||
|
|
||||||
|
To install this project, follow these steps:
|
||||||
|
|
||||||
|
1. TODO
|
||||||
|
|
||||||
|
## Setup
|
||||||
|
|
||||||
|
to insert teachers use:
|
||||||
|
|
||||||
|
```sql
|
||||||
|
insert into teachers (ansprache, last_name, subject_id) values ('Frau', 'Bücher', 2);
|
||||||
|
insert into teachers (ansprache, last_name, subject_id) values ('Frau', 'Klemm', 2);
|
||||||
|
insert into teachers (ansprache, last_name, subject_id) values ('Frau', 'Vietzen/Pfab', 2);
|
||||||
|
insert into teachers (ansprache, last_name, subject_id) values ('Frau', 'Bärtle', 2);
|
||||||
|
insert into teachers (ansprache, last_name, subject_id) values ('Frau', 'Wörner', 2);
|
||||||
|
```
|
||||||
|
|
||||||
|
to make a teacher available in the current year:
|
||||||
|
|
||||||
|
```sql
|
||||||
|
insert into teacher_dates values (1,3);
|
||||||
|
insert into teacher_dates values (2,3);
|
||||||
|
insert into teacher_dates values (3,3);
|
||||||
|
insert into teacher_dates values (4,3);
|
||||||
|
insert into teacher_dates values (5,3);
|
||||||
|
insert into teacher_dates values (7,3);
|
||||||
|
insert into teacher_dates values (12,3);
|
||||||
|
insert into teacher_dates values (8,3);
|
||||||
|
insert into teacher_dates values (9,3);
|
||||||
|
insert into teacher_dates values (10,3);
|
||||||
|
insert into teacher_dates values (11,3);
|
||||||
|
insert into teacher_dates values (13,3);
|
||||||
|
insert into teacher_dates values (14,3);
|
||||||
|
insert into teacher_dates values (15,3);
|
||||||
|
insert into teacher_dates values (16,3);
|
||||||
|
```
|
||||||
|
|
||||||
|
to generate slots use:
|
||||||
|
|
||||||
|
```sql
|
||||||
|
insert into appointment_slots (start_time, end_time, date_id) values ('2023-02-28 14:00:00', '2023-02-28 14:15:00', 3);
|
||||||
|
insert into appointment_slots (start_time, end_time, date_id) values ('2023-02-28 14:15:00', '2023-02-28 14:30:00', 3);
|
||||||
|
insert into appointment_slots (start_time, end_time, date_id) values ('2023-02-28 14:30:00', '2023-02-28 14:45:00', 3);
|
||||||
|
insert into appointment_slots (start_time, end_time, date_id) values ('2023-02-28 14:45:00', '2023-02-28 15:00:00', 3);
|
||||||
|
insert into appointment_slots (start_time, end_time, date_id) values ('2023-02-28 15:15:00', '2023-02-28 15:30:00', 3);
|
||||||
|
insert into appointment_slots (start_time, end_time, date_id) values ('2023-02-28 15:30:00', '2023-02-28 15:45:00', 3);
|
||||||
|
insert into appointment_slots (start_time, end_time, date_id) values ('2023-02-28 15:45:00', '2023-02-28 16:00:00', 3);
|
||||||
|
insert into appointment_slots (start_time, end_time, date_id) values ('2023-02-28 16:00:00', '2023-02-28 16:15:00', 3);
|
||||||
|
```
|
||||||
|
|
||||||
|
## Usage
|
||||||
|
|
||||||
|
To use this project, follow these steps:
|
||||||
|
|
||||||
|
1. ...
|
||||||
|
2. ...
|
||||||
|
3. ...
|
||||||
|
|
||||||
|
## Contributing
|
||||||
|
|
||||||
|
If you would like to contribute to this project, please follow these guidelines:
|
||||||
|
|
||||||
|
1. Fork the repository.
|
||||||
|
2. Create a new branch.
|
||||||
|
3. ...
|
||||||
|
4. Submit a pull request.
|
||||||
|
|
||||||
|
## License
|
||||||
|
|
||||||
|
This project is licensed under the [MIT License](LICENSE).
|
||||||
|
|
||||||
|
## Contact
|
||||||
|
|
||||||
|
For any questions or inquiries, please contact us at [franz.dietrich@uhlandshoehe.de](mailto:franz.dietrich@uhlandshoehe.de).
|
16
install.sh
16
install.sh
@ -1,9 +1,21 @@
|
|||||||
#!/bin/bash
|
#!/bin/bash
|
||||||
|
if [ ! -f terminwahl_front/static/bulma.sass ]; then
|
||||||
|
pushd terminwahl_front/static/ || exit
|
||||||
|
wget https://github.com/jgthms/bulma/releases/download/0.9.4/bulma-0.9.4.zip
|
||||||
|
unzip bulma-0.9.4.zip
|
||||||
|
ln -s bulma/sass/ sass
|
||||||
|
ln -s bulma/bulma.sass bulma.sass
|
||||||
|
wget https://use.fontawesome.com/releases/v6.3.0/fontawesome-free-6.3.0-web.zip
|
||||||
|
unzip fontawesome-free-6.3.0-web.zip
|
||||||
|
ln -s fontawesome-free-6.3.0-web/scss/ scss
|
||||||
|
ln -s fontawesome-free-6.3.0-web/webfonts webfonts
|
||||||
|
popd || exit
|
||||||
|
fi
|
||||||
|
|
||||||
systemctl stop Terminwahl
|
systemctl stop Terminwahl
|
||||||
cp -v target/release/terminwahl_back /usr/local/bin/terminwahl_back && chown terminwahl.terminwahl /usr/local/bin/terminwahl_back
|
cp -v target/release/terminwahl_back /usr/local/bin/terminwahl_back && chown terminwahl:terminwahl /usr/local/bin/terminwahl_back
|
||||||
echo You may want to copy the database
|
echo You may want to copy the database
|
||||||
echo cp -v terminwahl_back/db.sqlite /var/local/terminwahl/db.sqlite && chown terminwahl.terminwahl /var/local/terminwahl/db.sqlite
|
echo cp -v terminwahl_back/db.sqlite /var/local/terminwahl/db.sqlite && chown terminwahl:terminwahl /var/local/terminwahl/db.sqlite
|
||||||
rsync -va --delete terminwahl_back/templates/ /var/local/terminwahl/templates/
|
rsync -va --delete terminwahl_back/templates/ /var/local/terminwahl/templates/
|
||||||
rsync -va --delete terminwahl_front/dist/ /var/local/terminwahl/dist/
|
rsync -va --delete terminwahl_front/dist/ /var/local/terminwahl/dist/
|
||||||
systemctl start Terminwahl
|
systemctl start Terminwahl
|
@ -1,31 +1,41 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "terminwahl_back"
|
name = "terminwahl_back"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
edition = "2021"
|
edition = "2024"
|
||||||
default-run = "terminwahl_back"
|
default-run = "terminwahl_back"
|
||||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
futures = "*"
|
futures = "*"
|
||||||
actix-web = "4.3"
|
actix-web = "4.5"
|
||||||
actix-rt = "2.8"
|
actix-rt = "2.8"
|
||||||
actix-files = "0.6.2"
|
actix-files = "0.6.2"
|
||||||
actix-session = { version = "0.7", features = ["cookie-session"] }
|
actix-session = { version = "0.9", features = ["cookie-session"] }
|
||||||
# sqlx is currently on version 0.3.5 in this project due to breaking changes introduced in versions
|
# sqlx is currently on version 0.3.5 in this project due to breaking changes introduced in versions
|
||||||
# beyond 0.4.0, which changed the return type of 'exectute' to a 'Done'. Also the row parsing related
|
# beyond 0.4.0, which changed the return type of 'exectute' to a 'Done'. Also the row parsing related
|
||||||
# traits have been altered. The overall architecture of this CRUD can still be reproduced with a
|
# traits have been altered. The overall architecture of this CRUD can still be reproduced with a
|
||||||
# newer version of sqlx, and the version will be updated in the future.
|
# newer version of sqlx, and the version will be updated in the future.
|
||||||
sqlx = { version = "0.6.2", features = ["sqlite", "runtime-actix-rustls", "chrono"] }
|
sqlx = { version = "0.7", features = [
|
||||||
|
"sqlite",
|
||||||
|
"runtime-tokio-rustls",
|
||||||
|
"chrono",
|
||||||
|
] }
|
||||||
uuid = { version = "1.2", features = ["serde", "v4"] }
|
uuid = { version = "1.2", features = ["serde", "v4"] }
|
||||||
dotenv = "*"
|
dotenv = "*"
|
||||||
env_logger = "0.10"
|
env_logger = "0.11"
|
||||||
log = "*"
|
log = "*"
|
||||||
lettre = {version="0.10", default-features = false, features = ["smtp-transport", "tokio1-rustls-tls", "hostname", "builder", "pool"]}
|
lettre = { version = "0.11", default-features = false, features = [
|
||||||
|
"smtp-transport",
|
||||||
|
"tokio1-rustls-tls",
|
||||||
|
"hostname",
|
||||||
|
"builder",
|
||||||
|
"pool",
|
||||||
|
] }
|
||||||
rand = "*"
|
rand = "*"
|
||||||
handlebars = {version="4.3", features=["dir_source"]}
|
handlebars = { version = "5.1", features = ["dir_source"] }
|
||||||
glob = "*"
|
glob = "*"
|
||||||
|
|
||||||
terminwahl_typen={path="../terminwahl_typen/"}
|
terminwahl_typen = { path = "../terminwahl_typen/" }
|
||||||
serde = {workspace = true}
|
serde = { workspace = true }
|
||||||
serde_json={workspace=true}
|
serde_json = { workspace = true }
|
||||||
chrono={workspace=true}
|
chrono = { workspace = true }
|
||||||
|
@ -0,0 +1,49 @@
|
|||||||
|
-- Add migration script here
|
||||||
|
CREATE TABLE date (
|
||||||
|
id INTEGER PRIMARY KEY,
|
||||||
|
name TEXT NOT NULL,
|
||||||
|
subtitle TEXT NOT NULL,
|
||||||
|
start_time DATETIME NOT NULL,
|
||||||
|
end_time DATETIME NOT NULL
|
||||||
|
);
|
||||||
|
INSERT INTO date (id, name, subtitle, start_time, end_time)
|
||||||
|
VALUES (
|
||||||
|
1,
|
||||||
|
'OS 2023',
|
||||||
|
'2023',
|
||||||
|
'2023-02-28 12:00:00',
|
||||||
|
'2023-02-28 18:00:00'
|
||||||
|
);
|
||||||
|
-- Create a temporary table
|
||||||
|
CREATE TEMPORARY TABLE temp_appointment_slots AS
|
||||||
|
SELECT *
|
||||||
|
FROM appointment_slots;
|
||||||
|
DROP TABLE appointment_slots;
|
||||||
|
-- Recreate the appointments table with the new column and foreign key constraint
|
||||||
|
CREATE TABLE appointment_slots (
|
||||||
|
id INTEGER PRIMARY KEY,
|
||||||
|
start_time DATETIME NOT NULL,
|
||||||
|
end_time DATETIME NOT NULL,
|
||||||
|
date_id INTEGER DEFAULT 1 Not NULL,
|
||||||
|
-- Add the new column
|
||||||
|
FOREIGN KEY (date_id) REFERENCES date(id) -- Add the foreign key constraint
|
||||||
|
);
|
||||||
|
-- Insert data back into the new appointments table from the temporary table
|
||||||
|
INSERT INTO appointment_slots
|
||||||
|
SELECT *,
|
||||||
|
1
|
||||||
|
FROM temp_appointment_slots;
|
||||||
|
-- Drop the temporary table
|
||||||
|
DROP TABLE temp_appointment_slots;
|
||||||
|
CREATE TABLE teacher_dates (
|
||||||
|
teacher_id INTEGER NOT NULL,
|
||||||
|
date_id INTEGER NOT NULL,
|
||||||
|
PRIMARY KEY (teacher_id, date_id),
|
||||||
|
FOREIGN KEY (teacher_id) REFERENCES teachers(id),
|
||||||
|
FOREIGN KEY (date_id) REFERENCES date(id)
|
||||||
|
);
|
||||||
|
INSERT INTO teacher_dates (teacher_id, date_id)
|
||||||
|
SELECT teachers.id AS teacher_id,
|
||||||
|
date.id AS date_id
|
||||||
|
FROM teachers,
|
||||||
|
date;
|
@ -1,26 +1,32 @@
|
|||||||
use actix_files::NamedFile;
|
use actix_files::NamedFile;
|
||||||
use actix_web::{dev, middleware::ErrorHandlerResponse, Result};
|
use actix_web::{dev, middleware::ErrorHandlerResponse, Responder as _, Result};
|
||||||
|
|
||||||
pub fn bad_request<B>(res: dev::ServiceResponse<B>) -> Result<ErrorHandlerResponse<B>> {
|
pub fn bad_request<B>(res: dev::ServiceResponse<B>) -> Result<ErrorHandlerResponse<B>> {
|
||||||
let new_resp = NamedFile::open("static/errors/400.html")?
|
let new_resp = NamedFile::open("static/errors/400.html")?
|
||||||
.set_status_code(res.status())
|
.customize()
|
||||||
.into_response(res.request())
|
.with_status(actix_web::http::StatusCode::OK)
|
||||||
|
.respond_to(res.request())
|
||||||
|
.map_into_boxed_body()
|
||||||
.map_into_right_body();
|
.map_into_right_body();
|
||||||
Ok(ErrorHandlerResponse::Response(res.into_response(new_resp)))
|
Ok(ErrorHandlerResponse::Response(res.into_response(new_resp)))
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn not_found<B>(res: dev::ServiceResponse<B>) -> Result<ErrorHandlerResponse<B>> {
|
pub fn not_found<B>(res: dev::ServiceResponse<B>) -> Result<ErrorHandlerResponse<B>> {
|
||||||
let new_resp = NamedFile::open("static/errors/404.html")?
|
let new_resp = NamedFile::open("static/errors/404.html")?
|
||||||
.set_status_code(res.status())
|
.customize()
|
||||||
.into_response(res.request())
|
.with_status(actix_web::http::StatusCode::OK)
|
||||||
|
.respond_to(res.request())
|
||||||
|
.map_into_boxed_body()
|
||||||
.map_into_right_body();
|
.map_into_right_body();
|
||||||
Ok(ErrorHandlerResponse::Response(res.into_response(new_resp)))
|
Ok(ErrorHandlerResponse::Response(res.into_response(new_resp)))
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn internal_server_error<B>(res: dev::ServiceResponse<B>) -> Result<ErrorHandlerResponse<B>> {
|
pub fn internal_server_error<B>(res: dev::ServiceResponse<B>) -> Result<ErrorHandlerResponse<B>> {
|
||||||
let new_resp = NamedFile::open("static/errors/500.html")?
|
let new_resp = NamedFile::open("static/errors/500.html")?
|
||||||
.set_status_code(res.status())
|
.customize()
|
||||||
.into_response(res.request())
|
.with_status(actix_web::http::StatusCode::OK)
|
||||||
|
.respond_to(res.request())
|
||||||
|
.map_into_boxed_body()
|
||||||
.map_into_right_body();
|
.map_into_right_body();
|
||||||
Ok(ErrorHandlerResponse::Response(res.into_response(new_resp)))
|
Ok(ErrorHandlerResponse::Response(res.into_response(new_resp)))
|
||||||
}
|
}
|
||||||
|
55
terminwahl_back/src/api/errors_alt.rs
Normal file
55
terminwahl_back/src/api/errors_alt.rs
Normal file
@ -0,0 +1,55 @@
|
|||||||
|
use std::io::Read as _;
|
||||||
|
|
||||||
|
use actix_files::NamedFile;
|
||||||
|
use actix_web::{
|
||||||
|
body::MessageBody,
|
||||||
|
dev::{self, ServiceResponse},
|
||||||
|
middleware::ErrorHandlerResponse,
|
||||||
|
Responder, Result,
|
||||||
|
};
|
||||||
|
|
||||||
|
pub fn bad_request<B: MessageBody>(
|
||||||
|
mut res: dev::ServiceResponse<B>,
|
||||||
|
) -> Result<ErrorHandlerResponse<B>> {
|
||||||
|
let mut new_resp = NamedFile::open("static/errors/400.html")?;
|
||||||
|
let mut body = String::new();
|
||||||
|
new_resp.read_to_string(&mut body);
|
||||||
|
|
||||||
|
res.response_mut().set_body(body);
|
||||||
|
|
||||||
|
/* let (req, res) = res.into_parts();
|
||||||
|
res.set_body(body);
|
||||||
|
|
||||||
|
let response = ServiceResponse::new(req, res)
|
||||||
|
.map_into_boxed_body()
|
||||||
|
.map_into_right_body(); */
|
||||||
|
|
||||||
|
Ok(ErrorHandlerResponse::Response(
|
||||||
|
res.map_into_boxed_body().map_into_right_body(),
|
||||||
|
))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn not_found<B>(res: dev::ServiceResponse<B>) -> Result<impl Responder> {
|
||||||
|
let new_resp = NamedFile::open("static/errors/404.html")?
|
||||||
|
.customize()
|
||||||
|
.with_status(res.status())
|
||||||
|
.respond_to(res.request());
|
||||||
|
|
||||||
|
Ok(new_resp)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn internal_server_error<B: MessageBody>(
|
||||||
|
res: dev::ServiceResponse<B>,
|
||||||
|
) -> Result<ErrorHandlerResponse<B>> {
|
||||||
|
let mut new_resp = NamedFile::open("static/errors/500.html")?;
|
||||||
|
let mut body = String::new();
|
||||||
|
new_resp.read_to_string(&mut body);
|
||||||
|
let (req, res) = res.into_parts();
|
||||||
|
res.set_body(body);
|
||||||
|
|
||||||
|
let response = ServiceResponse::new(req, res)
|
||||||
|
.map_into_boxed_body()
|
||||||
|
.map_into_right_body();
|
||||||
|
|
||||||
|
Ok(ErrorHandlerResponse::Response(response))
|
||||||
|
}
|
@ -1,9 +1,14 @@
|
|||||||
use actix_web::{error, web, Error, HttpResponse};
|
use actix_web::{error, web, Error, HttpResponse};
|
||||||
|
use terminwahl_typen::IdType;
|
||||||
|
|
||||||
use crate::db::{self, Pool};
|
use crate::db::{self, Pool};
|
||||||
|
|
||||||
pub async fn get_teachers_json(pool: web::Data<Pool>) -> Result<HttpResponse, Error> {
|
pub async fn get_teachers_json(
|
||||||
let tasks = db::read::get_teachers(&pool)
|
pool: web::Data<Pool>,
|
||||||
|
path: web::Path<IdType>,
|
||||||
|
) -> Result<HttpResponse, Error> {
|
||||||
|
let date_id = path.into_inner();
|
||||||
|
let tasks = db::read::get_teachers(&pool, date_id)
|
||||||
.await
|
.await
|
||||||
.map_err(error::ErrorInternalServerError)?;
|
.map_err(error::ErrorInternalServerError)?;
|
||||||
|
|
||||||
@ -17,8 +22,12 @@ pub async fn get_subjects_json(pool: web::Data<Pool>) -> Result<HttpResponse, Er
|
|||||||
Ok(HttpResponse::Ok().json(tasks))
|
Ok(HttpResponse::Ok().json(tasks))
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn get_slots_json(pool: web::Data<Pool>) -> Result<HttpResponse, Error> {
|
pub async fn get_slots_json(
|
||||||
let tasks = db::read::get_slots(&pool)
|
pool: web::Data<Pool>,
|
||||||
|
path: web::Path<IdType>,
|
||||||
|
) -> Result<HttpResponse, Error> {
|
||||||
|
let date_id = path.into_inner();
|
||||||
|
let tasks = db::read::get_slots(&pool, date_id)
|
||||||
.await
|
.await
|
||||||
.map_err(error::ErrorInternalServerError)?;
|
.map_err(error::ErrorInternalServerError)?;
|
||||||
|
|
||||||
@ -31,3 +40,11 @@ pub async fn get_unavailable_json(pool: web::Data<Pool>) -> Result<HttpResponse,
|
|||||||
|
|
||||||
Ok(HttpResponse::Ok().json(tasks))
|
Ok(HttpResponse::Ok().json(tasks))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub async fn get_dates_json(pool: web::Data<Pool>) -> Result<HttpResponse, Error> {
|
||||||
|
let dates = db::read::get_dates(&pool)
|
||||||
|
.await
|
||||||
|
.map_err(error::ErrorInternalServerError)?;
|
||||||
|
|
||||||
|
Ok(HttpResponse::Ok().json(dates))
|
||||||
|
}
|
||||||
|
@ -7,7 +7,7 @@ use lettre::{
|
|||||||
message::header::ContentType, AsyncSmtpTransport, AsyncTransport, Message, Tokio1Executor,
|
message::header::ContentType, AsyncSmtpTransport, AsyncTransport, Message, Tokio1Executor,
|
||||||
};
|
};
|
||||||
use log::debug;
|
use log::debug;
|
||||||
use rand::{distributions::Alphanumeric, thread_rng, Rng};
|
use rand::{distr::Alphanumeric, rng, Rng};
|
||||||
use serde::Serialize;
|
use serde::Serialize;
|
||||||
use serde_json::json;
|
use serde_json::json;
|
||||||
use terminwahl_typen::{AppointmentSlot, Nutzer, PlannedAppointment, RequestState, Teacher};
|
use terminwahl_typen::{AppointmentSlot, Nutzer, PlannedAppointment, RequestState, Teacher};
|
||||||
@ -48,7 +48,7 @@ pub async fn save_appointments_json(
|
|||||||
.await
|
.await
|
||||||
.map_err(error::ErrorInternalServerError)?;
|
.map_err(error::ErrorInternalServerError)?;
|
||||||
debug!("Saving appointments");
|
debug!("Saving appointments");
|
||||||
let validation_key: String = thread_rng()
|
let validation_key: String = rng()
|
||||||
.sample_iter(&Alphanumeric)
|
.sample_iter(&Alphanumeric)
|
||||||
.take(30)
|
.take(30)
|
||||||
.map(char::from)
|
.map(char::from)
|
||||||
|
@ -5,17 +5,23 @@ use serde::{Deserialize, Serialize};
|
|||||||
use sqlx::query_as;
|
use sqlx::query_as;
|
||||||
|
|
||||||
use terminwahl_typen::{
|
use terminwahl_typen::{
|
||||||
AppointmentSlot, AppointmentSlots, IdType, Nutzer, SlotId, Subject, Subjects, Teacher, Teachers,
|
AppointmentSlot, AppointmentSlots, Date, Dates, IdType, Nutzer, SlotId, Subject, Subjects,
|
||||||
|
Teacher, Teachers,
|
||||||
};
|
};
|
||||||
|
|
||||||
use super::Pool;
|
use super::Pool;
|
||||||
|
|
||||||
pub async fn get_teachers(db: &Pool) -> Result<Teachers, sqlx::Error> {
|
pub async fn get_teachers(db: &Pool, date_id: IdType) -> Result<Teachers, sqlx::Error> {
|
||||||
query_as!(
|
query_as!(
|
||||||
Teacher,
|
Teacher,
|
||||||
r#"
|
r#"
|
||||||
SELECT *
|
SELECT
|
||||||
FROM `teachers`"#,
|
id,
|
||||||
|
ansprache,
|
||||||
|
last_name,
|
||||||
|
subject_id
|
||||||
|
FROM `teachers` JOIN teacher_dates ON teachers.id = teacher_dates.teacher_id where date_id = ?"#,
|
||||||
|
date_id
|
||||||
)
|
)
|
||||||
.fetch_all(db)
|
.fetch_all(db)
|
||||||
.await
|
.await
|
||||||
@ -44,12 +50,13 @@ pub async fn get_subjects(db: &Pool) -> Result<Subjects, sqlx::Error> {
|
|||||||
.await
|
.await
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn get_slots(db: &Pool) -> Result<AppointmentSlots, sqlx::Error> {
|
pub async fn get_slots(db: &Pool, date_id: IdType) -> Result<AppointmentSlots, sqlx::Error> {
|
||||||
match query_as!(
|
match query_as!(
|
||||||
AppointmentSlot,
|
AppointmentSlot,
|
||||||
r#"
|
r#"
|
||||||
SELECT *
|
SELECT id, start_time, end_time, date_id
|
||||||
FROM `appointment_slots`"#,
|
FROM `appointment_slots` where date_id = ?"#,
|
||||||
|
date_id
|
||||||
)
|
)
|
||||||
.fetch_all(db)
|
.fetch_all(db)
|
||||||
.await
|
.await
|
||||||
@ -87,7 +94,31 @@ pub async fn get_unavailable(db: &Pool) -> Result<HashSet<SlotId>, sqlx::Error>
|
|||||||
Err(e) => Err(e),
|
Err(e) => Err(e),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
pub async fn get_dates(db: &Pool) -> Result<Dates, sqlx::Error> {
|
||||||
|
match query_as!(
|
||||||
|
Date,
|
||||||
|
r#"
|
||||||
|
SELECT *
|
||||||
|
FROM `date` WHERE end_time > datetime('now', '-14 days');"#,
|
||||||
|
)
|
||||||
|
.fetch_all(db)
|
||||||
|
.await
|
||||||
|
{
|
||||||
|
Ok(elems) => Ok(elems.into_iter().collect()),
|
||||||
|
Err(e) => Err(e),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pub async fn get_date(db: &Pool, date_id: IdType) -> Result<Date, sqlx::Error> {
|
||||||
|
query_as!(
|
||||||
|
Date,
|
||||||
|
r#"
|
||||||
|
SELECT *
|
||||||
|
FROM `date` WHERE id = ?"#,
|
||||||
|
date_id
|
||||||
|
)
|
||||||
|
.fetch_one(db)
|
||||||
|
.await
|
||||||
|
}
|
||||||
#[derive(Debug, Deserialize, Serialize, Clone)]
|
#[derive(Debug, Deserialize, Serialize, Clone)]
|
||||||
pub struct TeacherWithAppointments {
|
pub struct TeacherWithAppointments {
|
||||||
teacher: Teacher,
|
teacher: Teacher,
|
||||||
@ -109,8 +140,11 @@ pub struct AssignedAppointment {
|
|||||||
nutzer: Nutzer,
|
nutzer: Nutzer,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn get_all_teachers(db: &Pool) -> Result<Vec<TeacherWithAppointments>, sqlx::Error> {
|
pub async fn get_all_teachers(
|
||||||
let teachers = get_teachers(db).await?;
|
db: &Pool,
|
||||||
|
date_id: IdType,
|
||||||
|
) -> Result<Vec<TeacherWithAppointments>, sqlx::Error> {
|
||||||
|
let teachers = get_teachers(db, date_id).await?;
|
||||||
let mut response = Vec::new();
|
let mut response = Vec::new();
|
||||||
|
|
||||||
for teacher in teachers.into_iter() {
|
for teacher in teachers.into_iter() {
|
||||||
|
@ -7,7 +7,7 @@ use actix_web::{
|
|||||||
web, App, HttpServer,
|
web, App, HttpServer,
|
||||||
};
|
};
|
||||||
use dotenv::dotenv;
|
use dotenv::dotenv;
|
||||||
use handlebars::Handlebars;
|
use handlebars::{DirectorySourceOptions, Handlebars};
|
||||||
use lettre::{transport::smtp::authentication::Credentials, AsyncSmtpTransport, Tokio1Executor};
|
use lettre::{transport::smtp::authentication::Credentials, AsyncSmtpTransport, Tokio1Executor};
|
||||||
use log::debug;
|
use log::debug;
|
||||||
use std::env;
|
use std::env;
|
||||||
@ -41,8 +41,12 @@ async fn main() -> std::io::Result<()> {
|
|||||||
|
|
||||||
let mut handlebars = Handlebars::new();
|
let mut handlebars = Handlebars::new();
|
||||||
handlebars.register_helper("time_of", Box::new(TimeOfDate));
|
handlebars.register_helper("time_of", Box::new(TimeOfDate));
|
||||||
|
let handlebars_source = DirectorySourceOptions {
|
||||||
|
tpl_extension: ".hbs".to_string(),
|
||||||
|
..Default::default()
|
||||||
|
};
|
||||||
handlebars
|
handlebars
|
||||||
.register_templates_directory(".hbs", handlebars_templates)
|
.register_templates_directory(handlebars_templates, handlebars_source)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
log::info!("starting HTTP server at http://localhost:8080");
|
log::info!("starting HTTP server at http://localhost:8080");
|
||||||
@ -69,13 +73,18 @@ async fn main() -> std::io::Result<()> {
|
|||||||
.wrap(Logger::default())
|
.wrap(Logger::default())
|
||||||
.wrap(session_store)
|
.wrap(session_store)
|
||||||
.wrap(error_handlers)
|
.wrap(error_handlers)
|
||||||
|
.service(web::resource("/get/dates").route(web::get().to(api::read::get_dates_json)))
|
||||||
.service(
|
.service(
|
||||||
web::resource("/get/teachers").route(web::get().to(api::read::get_teachers_json)),
|
web::resource("/get/teachers/{date_key}")
|
||||||
|
.route(web::get().to(api::read::get_teachers_json)),
|
||||||
)
|
)
|
||||||
.service(
|
.service(
|
||||||
web::resource("/get/subjects").route(web::get().to(api::read::get_subjects_json)),
|
web::resource("/get/subjects").route(web::get().to(api::read::get_subjects_json)),
|
||||||
)
|
)
|
||||||
.service(web::resource("/get/slots").route(web::get().to(api::read::get_slots_json)))
|
.service(
|
||||||
|
web::resource("/get/slots/{date_key}")
|
||||||
|
.route(web::get().to(api::read::get_slots_json)),
|
||||||
|
)
|
||||||
.service(
|
.service(
|
||||||
web::resource("/get/unavailable")
|
web::resource("/get/unavailable")
|
||||||
.route(web::get().to(api::read::get_unavailable_json)),
|
.route(web::get().to(api::read::get_unavailable_json)),
|
||||||
@ -93,7 +102,7 @@ async fn main() -> std::io::Result<()> {
|
|||||||
.route(web::get().to(views::delete_appointment)),
|
.route(web::get().to(views::delete_appointment)),
|
||||||
)
|
)
|
||||||
.service(
|
.service(
|
||||||
web::resource("/export/all/{password}")
|
web::resource("/export/all/{password}/{id}")
|
||||||
.route(web::get().to(views::export_appointments)),
|
.route(web::get().to(views::export_appointments)),
|
||||||
)
|
)
|
||||||
.service(Files::new("/", wasm_statics.clone()).index_file("index.html"))
|
.service(Files::new("/", wasm_statics.clone()).index_file("index.html"))
|
||||||
|
@ -60,12 +60,12 @@ pub async fn export_appointments(
|
|||||||
_mailer: web::Data<AsyncSmtpTransport<Tokio1Executor>>,
|
_mailer: web::Data<AsyncSmtpTransport<Tokio1Executor>>,
|
||||||
handlebars: web::Data<Handlebars<'_>>,
|
handlebars: web::Data<Handlebars<'_>>,
|
||||||
css: web::Data<CssPath>,
|
css: web::Data<CssPath>,
|
||||||
path: web::Path<String>,
|
path: web::Path<(String, IdType)>,
|
||||||
) -> Result<HttpResponse, error::Error> {
|
) -> Result<HttpResponse, error::Error> {
|
||||||
let password = path.into_inner();
|
let (password, date_id) = path.into_inner();
|
||||||
dbg!(&password);
|
dbg!(&password);
|
||||||
if password == "AllExport1517" {
|
if password == "AllExport1517" {
|
||||||
match get_all_teachers(&pool).await {
|
match get_all_teachers(&pool, date_id).await {
|
||||||
Ok(teachers) => {
|
Ok(teachers) => {
|
||||||
dbg!(&teachers);
|
dbg!(&teachers);
|
||||||
let data = json!({
|
let data = json!({
|
||||||
|
@ -16,7 +16,7 @@
|
|||||||
Elternsprechtag
|
Elternsprechtag
|
||||||
</p>
|
</p>
|
||||||
<p class="subtitle">
|
<p class="subtitle">
|
||||||
Am 28.02.23
|
Am 06.03.24
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
@ -1,17 +1,17 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "terminwahl_front"
|
name = "terminwahl_front"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
edition = "2021"
|
edition = "2024"
|
||||||
|
|
||||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
|
|
||||||
yew = { version = "0.20.0", features = ["csr"] }
|
yew = { version = "0.20.0", features = ["csr"] }
|
||||||
gloo="*"
|
gloo = "*"
|
||||||
js-sys="*"
|
js-sys = "*"
|
||||||
web-sys="*"
|
web-sys = "*"
|
||||||
terminwahl_typen = {path="../terminwahl_typen/"}
|
terminwahl_typen = { path = "../terminwahl_typen/" }
|
||||||
serde = {workspace = true}
|
serde = { workspace = true }
|
||||||
serde_json={workspace=true}
|
serde_json = { workspace = true }
|
||||||
chrono={workspace=true}
|
chrono = { workspace = true }
|
||||||
|
@ -3,7 +3,7 @@
|
|||||||
|
|
||||||
<head>
|
<head>
|
||||||
<meta charset="utf-8" />
|
<meta charset="utf-8" />
|
||||||
<title>Yew App</title>
|
<title>Elternsprechtag</title>
|
||||||
<link data-trunk rel="scss" href="static/my_bulma_colors.scss" />
|
<link data-trunk rel="scss" href="static/my_bulma_colors.scss" />
|
||||||
<link data-trunk rel="copy-file" href="static/logoheader.png" />
|
<link data-trunk rel="copy-file" href="static/logoheader.png" />
|
||||||
<link data-trunk rel="copy-dir" href="static/webfonts" />
|
<link data-trunk rel="copy-dir" href="static/webfonts" />
|
||||||
@ -12,4 +12,6 @@
|
|||||||
<meta name="description" content="Termine buchen für den Lehrersprechtag der Waldorfschule Uhlandshöhe" />
|
<meta name="description" content="Termine buchen für den Lehrersprechtag der Waldorfschule Uhlandshöhe" />
|
||||||
</head>
|
</head>
|
||||||
|
|
||||||
|
<body></body>
|
||||||
|
|
||||||
</html>
|
</html>
|
@ -2,10 +2,10 @@ mod requests;
|
|||||||
use std::collections::{HashMap, HashSet};
|
use std::collections::{HashMap, HashSet};
|
||||||
|
|
||||||
use gloo::console::log;
|
use gloo::console::log;
|
||||||
use requests::{fetch_slots, fetch_teachers, fetch_unavailable, send_appointments};
|
use requests::{fetch_dates, fetch_slots, fetch_teachers, fetch_unavailable, send_appointments};
|
||||||
use terminwahl_typen::{
|
use terminwahl_typen::{
|
||||||
AppointmentSlot, AppointmentSlots, IdType, Nutzer, PlannedAppointment, RequestState, SlotId,
|
AppointmentSlot, AppointmentSlots, Date, Dates, IdType, Nutzer, PlannedAppointment,
|
||||||
Teacher, Teachers,
|
RequestState, SlotId, Teacher, Teachers,
|
||||||
};
|
};
|
||||||
use web_sys::HtmlInputElement;
|
use web_sys::HtmlInputElement;
|
||||||
use yew::prelude::*;
|
use yew::prelude::*;
|
||||||
@ -16,9 +16,12 @@ pub enum Msg {
|
|||||||
UpdateSchüler(String),
|
UpdateSchüler(String),
|
||||||
UpdateEmail(String),
|
UpdateEmail(String),
|
||||||
DataEntered(Nutzer),
|
DataEntered(Nutzer),
|
||||||
GetTeachers,
|
GetTeachers(IdType),
|
||||||
ReceivedTeachers(Teachers),
|
ReceivedTeachers(Teachers),
|
||||||
GetSlots,
|
GetDates,
|
||||||
|
ReceivedDates(Dates),
|
||||||
|
SelectDate(IdType),
|
||||||
|
GetSlots(IdType),
|
||||||
ReceivedSlots(AppointmentSlots),
|
ReceivedSlots(AppointmentSlots),
|
||||||
Selected(PlannedAppointment),
|
Selected(PlannedAppointment),
|
||||||
TooMany,
|
TooMany,
|
||||||
@ -30,6 +33,8 @@ pub enum Msg {
|
|||||||
pub struct App {
|
pub struct App {
|
||||||
nutzer: Option<Nutzer>,
|
nutzer: Option<Nutzer>,
|
||||||
tmp_nutzer: Nutzer,
|
tmp_nutzer: Nutzer,
|
||||||
|
dates: Option<Dates>,
|
||||||
|
selected_date: Option<Date>,
|
||||||
teachers: Option<Teachers>,
|
teachers: Option<Teachers>,
|
||||||
slots: Option<AppointmentSlots>,
|
slots: Option<AppointmentSlots>,
|
||||||
appointments: HashMap<SlotId, PlannedAppointment>,
|
appointments: HashMap<SlotId, PlannedAppointment>,
|
||||||
@ -54,6 +59,8 @@ impl Component for App {
|
|||||||
let app = Self {
|
let app = Self {
|
||||||
appointments: HashMap::new(),
|
appointments: HashMap::new(),
|
||||||
slots: None,
|
slots: None,
|
||||||
|
dates: None,
|
||||||
|
selected_date: None,
|
||||||
unavailable: None,
|
unavailable: None,
|
||||||
teachers: None,
|
teachers: None,
|
||||||
nutzer: None,
|
nutzer: None,
|
||||||
@ -64,8 +71,8 @@ impl Component for App {
|
|||||||
},
|
},
|
||||||
successfully_saved: None,
|
successfully_saved: None,
|
||||||
};
|
};
|
||||||
ctx.link().send_message(Msg::GetTeachers);
|
|
||||||
ctx.link().send_message(Msg::GetSlots);
|
ctx.link().send_message(Msg::GetDates);
|
||||||
app
|
app
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -85,16 +92,38 @@ impl Component for App {
|
|||||||
true
|
true
|
||||||
}
|
}
|
||||||
Msg::TooMany => todo!(),
|
Msg::TooMany => todo!(),
|
||||||
Msg::GetTeachers => {
|
Msg::GetTeachers(id) => {
|
||||||
ctx.link().send_future(fetch_teachers());
|
ctx.link().send_future(fetch_teachers(id));
|
||||||
false
|
false
|
||||||
}
|
}
|
||||||
Msg::ReceivedTeachers(teachers) => {
|
Msg::ReceivedTeachers(teachers) => {
|
||||||
self.teachers = Some(teachers);
|
self.teachers = Some(teachers);
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
Msg::GetSlots => {
|
Msg::GetDates => {
|
||||||
ctx.link().send_future(fetch_slots());
|
ctx.link().send_future(fetch_dates());
|
||||||
|
false
|
||||||
|
}
|
||||||
|
Msg::ReceivedDates(dates) => {
|
||||||
|
if dates.len() == 1 {
|
||||||
|
ctx.link()
|
||||||
|
.send_message(Msg::SelectDate(dates.first().unwrap().id))
|
||||||
|
}
|
||||||
|
self.dates = Some(dates);
|
||||||
|
true
|
||||||
|
}
|
||||||
|
Msg::SelectDate(date_id) => {
|
||||||
|
ctx.link().send_message(Msg::GetTeachers(date_id));
|
||||||
|
ctx.link().send_message(Msg::GetSlots(date_id));
|
||||||
|
let date = self
|
||||||
|
.dates
|
||||||
|
.as_ref()
|
||||||
|
.map(|dts| dts.iter().find(|d| d.id == date_id).unwrap());
|
||||||
|
self.selected_date = Some(date.expect("A date should be found").clone());
|
||||||
|
true
|
||||||
|
}
|
||||||
|
Msg::GetSlots(id) => {
|
||||||
|
ctx.link().send_future(fetch_slots(id));
|
||||||
ctx.link().send_future(fetch_unavailable());
|
ctx.link().send_future(fetch_unavailable());
|
||||||
false
|
false
|
||||||
}
|
}
|
||||||
@ -150,24 +179,45 @@ impl Component for App {
|
|||||||
html! {<>
|
html! {<>
|
||||||
<section class="hero is-warning">
|
<section class="hero is-warning">
|
||||||
<div class="hero-body">
|
<div class="hero-body">
|
||||||
<p class="title has-text-link">
|
|
||||||
{"Elternsprechtag"}
|
{if let Some(d) = self.selected_date.as_ref(){ html!{
|
||||||
</p>
|
<><p class="title has-text-link">
|
||||||
<p class="subtitle">
|
{&d.name}
|
||||||
{"Am 28.02.23"}
|
</p><p class="subtitle">
|
||||||
</p>
|
{&d.subtitle}
|
||||||
|
</p><p class="subtitle">
|
||||||
|
{"Am "}{d.start_time.format("%d.%m.%Y")}
|
||||||
|
</p></>}}else{html!(<p class="title has-text-link">{"Elternsprechtag"}</p>)}}
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
<div class="container">
|
<div class="container">
|
||||||
<div class="section">
|
<div class="section">
|
||||||
{
|
{
|
||||||
if let Some(_saved) = self.successfully_saved.as_ref(){self.view_dank_dialog(ctx)} else if self.nutzer.is_none(){
|
if let Some(dates) = self.dates.as_ref(){
|
||||||
|
if let Some(date) = self.selected_date.as_ref(){
|
||||||
|
if date.end_time < (chrono::Local::now() + chrono::Duration::days(4)).naive_local(){
|
||||||
|
html!(
|
||||||
|
<div class="columns is-centered">
|
||||||
|
<div class="column is-half">
|
||||||
|
<div class="notification is-light">
|
||||||
|
<h1>{"Die Anmeldung ist bereits geschlossen."}</h1>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
} else if let Some(_saved) = self.successfully_saved.as_ref(){
|
||||||
|
self.view_dank_dialog(ctx)
|
||||||
|
} else if self.nutzer.is_none(){
|
||||||
self.view_eingabe_daten(ctx)
|
self.view_eingabe_daten(ctx)
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
self.view_auswahl_termine(ctx)
|
self.view_auswahl_termine(ctx)
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
self.view_auswahl_date(dates, ctx)
|
||||||
|
}
|
||||||
|
}else{html!(<h1>{"Loading"}</h1>)}
|
||||||
}
|
}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -178,6 +228,35 @@ impl Component for App {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl App {
|
impl App {
|
||||||
|
fn view_auswahl_date(&self, dates: &Dates, ctx: &Context<Self>) -> Html {
|
||||||
|
let onchange = ctx.link().callback(|e: Event| {
|
||||||
|
let input: HtmlInputElement = e.target_unchecked_into();
|
||||||
|
Msg::SelectDate(input.value().parse().unwrap())
|
||||||
|
});
|
||||||
|
html! {
|
||||||
|
<div class="columns is-centered">
|
||||||
|
<div class="column is-half">
|
||||||
|
<div class="notification is-light">
|
||||||
|
<figure class="image container is-128x128" id="headerlogo">
|
||||||
|
<img src="/logoheader.png" />
|
||||||
|
</figure>
|
||||||
|
<div class="box mt-3 is-light">
|
||||||
|
<p>{"Anmeldung zum Elternsprechtag!"}</p><p>{"Bitte wählen Sie den Sprechtag zu welchem Sie sich anmelden möchten:"}</p>
|
||||||
|
</div>
|
||||||
|
<div class="select is-rounded is-fullwidth">
|
||||||
|
<select onchange={onchange}>
|
||||||
|
<option selected=true value="--">{"Bitte wählen Sie einen Termin"}</option>
|
||||||
|
{dates.iter().map(|dt|html!{
|
||||||
|
<option value={dt.id.to_string()}>{&dt.name}{" – "}{&dt.start_time.format("%d.%m.%Y")}</option>
|
||||||
|
}).collect::<Html>()}
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fn view_eingabe_daten(&self, ctx: &Context<Self>) -> Html {
|
fn view_eingabe_daten(&self, ctx: &Context<Self>) -> Html {
|
||||||
html! { <div class="columns is-centered">
|
html! { <div class="columns is-centered">
|
||||||
<div class="column is-half">
|
<div class="column is-half">
|
||||||
@ -186,7 +265,7 @@ impl App {
|
|||||||
<img src="/logoheader.png" />
|
<img src="/logoheader.png" />
|
||||||
</figure>
|
</figure>
|
||||||
<div class="box mt-3 is-light">
|
<div class="box mt-3 is-light">
|
||||||
<p>{"Anmeldung zum Elternsprechtag!"}</p><p>{"Bitte geben Sie unbedingt eine gültige E-Mail-Adresse an,
|
<p>{"Anmeldung zum "} {&self.dates.as_ref().expect("termin").first().expect("termin").name}{" am "}{&self.dates.as_ref().expect("termin").first().expect("termin").start_time.format("%d.%m.%Y")}{"!"}</p><p>{"Bitte geben Sie unbedingt eine gültige E-Mail-Adresse an,
|
||||||
da die Termine erst nach Bestätigung über den per E-Mail zugesandten Link gebucht werden."}</p>
|
da die Termine erst nach Bestätigung über den per E-Mail zugesandten Link gebucht werden."}</p>
|
||||||
</div>
|
</div>
|
||||||
<div class="field">
|
<div class="field">
|
||||||
|
@ -1,11 +1,23 @@
|
|||||||
use gloo::net::http::{Method, Request};
|
use gloo::net::http::Request;
|
||||||
use terminwahl_typen::{Nutzer, PlannedAppointment, RequestState};
|
use terminwahl_typen::{IdType, Nutzer, PlannedAppointment, RequestState};
|
||||||
|
|
||||||
use crate::Msg;
|
use crate::Msg;
|
||||||
|
|
||||||
pub async fn fetch_teachers() -> Result<Msg, Msg> {
|
pub async fn fetch_dates() -> Result<Msg, Msg> {
|
||||||
// Send the request to the specified URL.
|
// Send the request to the specified URL.
|
||||||
let response = Request::new("/get/teachers").send().await;
|
let response = Request::get("/get/dates").send().await;
|
||||||
|
// Return the ZuordnungMessage with the given network object and the response.
|
||||||
|
let response = response
|
||||||
|
.map_err(|_| Msg::AppointmentsSent(RequestState::Error))?
|
||||||
|
.json()
|
||||||
|
.await
|
||||||
|
.map_err(|_| Msg::AppointmentsSent(RequestState::Error))?;
|
||||||
|
Ok(Msg::ReceivedDates(response))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn fetch_teachers(id: IdType) -> Result<Msg, Msg> {
|
||||||
|
// Send the request to the specified URL.
|
||||||
|
let response = Request::get(&format!("/get/teachers/{}", id)).send().await;
|
||||||
// Return the ZuordnungMessage with the given network object and the response.
|
// Return the ZuordnungMessage with the given network object and the response.
|
||||||
let response = response
|
let response = response
|
||||||
.map_err(|_| Msg::AppointmentsSent(RequestState::Error))?
|
.map_err(|_| Msg::AppointmentsSent(RequestState::Error))?
|
||||||
@ -15,9 +27,9 @@ pub async fn fetch_teachers() -> Result<Msg, Msg> {
|
|||||||
Ok(Msg::ReceivedTeachers(response))
|
Ok(Msg::ReceivedTeachers(response))
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn fetch_slots() -> Result<Msg, Msg> {
|
pub async fn fetch_slots(id: IdType) -> Result<Msg, Msg> {
|
||||||
// Send the request to the specified URL.
|
// Send the request to the specified URL.
|
||||||
let response = Request::new("/get/slots").send().await;
|
let response = Request::get(&format!("/get/slots/{}", id)).send().await;
|
||||||
// Return the ZuordnungMessage with the given network object and the response.
|
// Return the ZuordnungMessage with the given network object and the response.
|
||||||
let response = response
|
let response = response
|
||||||
.map_err(|_| Msg::AppointmentsSent(RequestState::Error))?
|
.map_err(|_| Msg::AppointmentsSent(RequestState::Error))?
|
||||||
@ -29,7 +41,7 @@ pub async fn fetch_slots() -> Result<Msg, Msg> {
|
|||||||
|
|
||||||
pub async fn fetch_unavailable() -> Result<Msg, Msg> {
|
pub async fn fetch_unavailable() -> Result<Msg, Msg> {
|
||||||
// Send the request to the specified URL.
|
// Send the request to the specified URL.
|
||||||
let response = Request::new("/get/unavailable").send().await;
|
let response = Request::get("/get/unavailable").send().await;
|
||||||
// Return the ZuordnungMessage with the given network object and the response.
|
// Return the ZuordnungMessage with the given network object and the response.
|
||||||
let response = response
|
let response = response
|
||||||
.map_err(|_| Msg::AppointmentsSent(RequestState::Error))?
|
.map_err(|_| Msg::AppointmentsSent(RequestState::Error))?
|
||||||
@ -43,8 +55,7 @@ pub async fn send_appointments(
|
|||||||
appointments: Vec<PlannedAppointment>,
|
appointments: Vec<PlannedAppointment>,
|
||||||
nutzer: Nutzer,
|
nutzer: Nutzer,
|
||||||
) -> Result<Msg, Msg> {
|
) -> Result<Msg, Msg> {
|
||||||
let response = Request::new("/send/appointments")
|
let response = Request::post("/send/appointments")
|
||||||
.method(Method::POST)
|
|
||||||
.json(&(&appointments, &nutzer))
|
.json(&(&appointments, &nutzer))
|
||||||
.map_err(|_| Msg::AppointmentsSent(RequestState::Error))?
|
.map_err(|_| Msg::AppointmentsSent(RequestState::Error))?
|
||||||
.send()
|
.send()
|
||||||
|
9
terminwahl_front/static/.gitignore
vendored
9
terminwahl_front/static/.gitignore
vendored
@ -1,4 +1,7 @@
|
|||||||
bulma.sass
|
bulma.sass
|
||||||
sass/
|
sass
|
||||||
webfonts/
|
webfonts
|
||||||
scss/
|
scss
|
||||||
|
*.zip
|
||||||
|
bulma
|
||||||
|
fontawesome*
|
@ -1,12 +1,12 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "terminwahl_typen"
|
name = "terminwahl_typen"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
edition = "2021"
|
edition = "2024"
|
||||||
|
|
||||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
|
|
||||||
serde = {workspace = true}
|
serde = { workspace = true }
|
||||||
serde_json={workspace=true}
|
serde_json = { workspace = true }
|
||||||
chrono={workspace=true}
|
chrono = { workspace = true }
|
||||||
|
@ -41,6 +41,7 @@ pub struct AppointmentSlot {
|
|||||||
pub id: IdType,
|
pub id: IdType,
|
||||||
pub start_time: NaiveDateTime,
|
pub start_time: NaiveDateTime,
|
||||||
pub end_time: NaiveDateTime,
|
pub end_time: NaiveDateTime,
|
||||||
|
pub date_id: IdType,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Deserialize, Serialize, Clone)]
|
#[derive(Debug, Deserialize, Serialize, Clone)]
|
||||||
@ -84,3 +85,14 @@ impl Nutzer {
|
|||||||
!self.name.is_empty() && !self.email.is_empty() && !self.schueler.is_empty()
|
!self.name.is_empty() && !self.email.is_empty() && !self.schueler.is_empty()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Deserialize, Serialize, Clone)]
|
||||||
|
pub struct Date {
|
||||||
|
pub id: IdType,
|
||||||
|
pub name: String,
|
||||||
|
pub subtitle: String,
|
||||||
|
pub start_time: NaiveDateTime,
|
||||||
|
pub end_time: NaiveDateTime,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub type Dates = Vec<Date>;
|
||||||
|
Loading…
x
Reference in New Issue
Block a user