Update
This commit is contained in:
parent
2b65b6a1c5
commit
f13c9c905f
15 changed files with 322 additions and 981 deletions
|
@ -6,6 +6,7 @@ RABBITMQ_PASSWORD="bvplan"
|
||||||
|
|
||||||
BACKEND_UNTIS_API_URL="https://mese.webuntis.com/WebUntis/api/"
|
BACKEND_UNTIS_API_URL="https://mese.webuntis.com/WebUntis/api/"
|
||||||
BACKEND_UNTIS_RPC_URL="https://mese.webuntis.com/WebUntis/jsonrpc.do"
|
BACKEND_UNTIS_RPC_URL="https://mese.webuntis.com/WebUntis/jsonrpc.do"
|
||||||
|
BACKEND_UNTIS_CLIENT_NAME="bvplan"
|
||||||
BACKEND_UNTIS_SCHOOL=
|
BACKEND_UNTIS_SCHOOL=
|
||||||
BACKEND_UNTIS_USERNAME=
|
BACKEND_UNTIS_USERNAME=
|
||||||
BACKEND_UNTIS_PASSWORD=
|
BACKEND_UNTIS_PASSWORD=
|
||||||
|
|
|
@ -3,17 +3,12 @@
|
||||||
debug/
|
debug/
|
||||||
target/
|
target/
|
||||||
|
|
||||||
# Remove Cargo.lock from gitignore if creating an executable, leave it for libraries
|
|
||||||
# More information here https://doc.rust-lang.org/cargo/guide/cargo-toml-vs-cargo-lock.html
|
|
||||||
Cargo.lock
|
|
||||||
|
|
||||||
# These are backup files generated by rustfmt
|
# These are backup files generated by rustfmt
|
||||||
**/*.rs.bk
|
**/*.rs.bk
|
||||||
|
|
||||||
# MSVC Windows builds of rustc generate these, which store debugging information
|
# MSVC Windows builds of rustc generate these, which store debugging information
|
||||||
*.pdb
|
*.pdb
|
||||||
|
|
||||||
dist/
|
|
||||||
Dockerfile
|
Dockerfile
|
||||||
.gitignore
|
.gitignore
|
||||||
.dockerignore
|
.dockerignore
|
||||||
|
|
11
backend/.gitignore
vendored
11
backend/.gitignore
vendored
|
@ -1 +1,10 @@
|
||||||
/target
|
# Generated by Cargo
|
||||||
|
# will have compiled files and executables
|
||||||
|
debug/
|
||||||
|
target/
|
||||||
|
|
||||||
|
# These are backup files generated by rustfmt
|
||||||
|
**/*.rs.bk
|
||||||
|
|
||||||
|
# MSVC Windows builds of rustc generate these, which store debugging information
|
||||||
|
*.pdb
|
||||||
|
|
59
backend/Cargo.lock
generated
59
backend/Cargo.lock
generated
|
@ -549,12 +549,12 @@ dependencies = [
|
||||||
"juniper_actix",
|
"juniper_actix",
|
||||||
"lazy_static",
|
"lazy_static",
|
||||||
"log",
|
"log",
|
||||||
"now",
|
|
||||||
"reqwest",
|
"reqwest",
|
||||||
"serde",
|
"serde",
|
||||||
"serde_json",
|
"serde_json",
|
||||||
"tikv-jemallocator",
|
"tikv-jemallocator",
|
||||||
"tokio",
|
"tokio",
|
||||||
|
"untis",
|
||||||
"url",
|
"url",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
@ -1592,9 +1592,9 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "ipnet"
|
name = "ipnet"
|
||||||
version = "2.6.0"
|
version = "2.7.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "ec947b7a4ce12e3b87e353abae7ce124d025b6c7d6c5aea5cc0bcf92e9510ded"
|
checksum = "11b0d96e660696543b251e58030cf9787df56da39dab19ad60eae7353040917e"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "is-terminal"
|
name = "is-terminal"
|
||||||
|
@ -1742,9 +1742,9 @@ checksum = "0717cef1bc8b636c6e1c1bbdefc09e6322da8a9321966e8928ef80d20f7f770f"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "linux-raw-sys"
|
name = "linux-raw-sys"
|
||||||
version = "0.1.3"
|
version = "0.1.4"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "8f9f08d8963a6c613f4b1a78f4f4a4dbfadf8e6545b2d72861731e4858b8b47f"
|
checksum = "f051f77a7c8e6957c0696eac88f26b0117e54f52d3fc682ab19397a8812846a4"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "local-channel"
|
name = "local-channel"
|
||||||
|
@ -1865,15 +1865,6 @@ dependencies = [
|
||||||
"minimal-lexical",
|
"minimal-lexical",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "now"
|
|
||||||
version = "0.1.3"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "6d89e9874397a1f0a52fc1f197a8effd9735223cb2390e9dcc83ac6cd02923d0"
|
|
||||||
dependencies = [
|
|
||||||
"chrono",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "num-integer"
|
name = "num-integer"
|
||||||
version = "0.1.45"
|
version = "0.1.45"
|
||||||
|
@ -1994,7 +1985,7 @@ checksum = "7d17b78036a60663b797adeaee46f5c9dfebb86948d1255007a1d6be0271ff99"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"instant",
|
"instant",
|
||||||
"lock_api",
|
"lock_api",
|
||||||
"parking_lot_core 0.8.5",
|
"parking_lot_core 0.8.6",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
@ -2009,9 +2000,9 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "parking_lot_core"
|
name = "parking_lot_core"
|
||||||
version = "0.8.5"
|
version = "0.8.6"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "d76e8e1493bcac0d2766c42737f34458f1c8c50c0d23bcb24ea953affb273216"
|
checksum = "60a2cfe6f0ad2bfc16aefa463b497d5c7a5ecd44a23efa72aa342d90177356dc"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"cfg-if",
|
"cfg-if",
|
||||||
"instant",
|
"instant",
|
||||||
|
@ -2036,9 +2027,9 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "paste"
|
name = "paste"
|
||||||
version = "1.0.9"
|
version = "1.0.10"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "b1de2e551fb905ac83f73f7aedf2f0cb4a0da7e35efa24a202a936269f1f18e1"
|
checksum = "cf1c2c742266c2f1041c914ba65355a83ae8747b05f208319784083583494b4b"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "percent-encoding"
|
name = "percent-encoding"
|
||||||
|
@ -2098,9 +2089,9 @@ checksum = "6ac9a59f73473f1b8d852421e59e64809f025994837ef743615c6d0c5b305160"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "polling"
|
name = "polling"
|
||||||
version = "2.5.1"
|
version = "2.5.2"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "166ca89eb77fd403230b9c156612965a81e094ec6ec3aa13663d4c8b113fa748"
|
checksum = "22122d5ec4f9fe1b3916419b76be1e80bcb93f618d071d2edf841b137b2a2bd6"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"autocfg",
|
"autocfg",
|
||||||
"cfg-if",
|
"cfg-if",
|
||||||
|
@ -2271,9 +2262,9 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "redis"
|
name = "redis"
|
||||||
version = "0.21.6"
|
version = "0.21.7"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "571c252c68d09a2ad3e49edd14e9ee48932f3e0f27b06b4ea4c9b2a706d31103"
|
checksum = "152f3863635cbb76b73bc247845781098302c6c9ad2060e1a9a7de56840346b6"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"arc-swap",
|
"arc-swap",
|
||||||
"async-trait",
|
"async-trait",
|
||||||
|
@ -2530,18 +2521,18 @@ checksum = "e25dfac463d778e353db5be2449d1cce89bd6fd23c9f1ea21310ce6e5a1b29c4"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "serde"
|
name = "serde"
|
||||||
version = "1.0.149"
|
version = "1.0.150"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "256b9932320c590e707b94576e3cc1f7c9024d0ee6612dfbcf1cb106cbe8e055"
|
checksum = "e326c9ec8042f1b5da33252c8a37e9ffbd2c9bef0155215b6e6c80c790e05f91"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"serde_derive",
|
"serde_derive",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "serde_derive"
|
name = "serde_derive"
|
||||||
version = "1.0.149"
|
version = "1.0.150"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "b4eae9b04cbffdfd550eb462ed33bc6a1b68c935127d008b27444d08380f94e4"
|
checksum = "42a3df25b0713732468deadad63ab9da1f1fd75a48a15024b50363f128db627e"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
|
@ -2988,6 +2979,20 @@ dependencies = [
|
||||||
"void",
|
"void",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "untis"
|
||||||
|
version = "0.1.0"
|
||||||
|
source = "git+https://git.dergrimm.net/dergrimm/untis.rs.git?branch=main#a6bc2bd2e947d73f18c7a118ba4836c4ea7e6531"
|
||||||
|
dependencies = [
|
||||||
|
"anyhow",
|
||||||
|
"chrono",
|
||||||
|
"cookie",
|
||||||
|
"reqwest",
|
||||||
|
"serde",
|
||||||
|
"serde_json",
|
||||||
|
"url",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "untrusted"
|
name = "untrusted"
|
||||||
version = "0.7.1"
|
version = "0.7.1"
|
||||||
|
|
|
@ -29,11 +29,11 @@ juniper = "0.15.10"
|
||||||
juniper_actix = "0.4.0"
|
juniper_actix = "0.4.0"
|
||||||
lazy_static = "1.4.0"
|
lazy_static = "1.4.0"
|
||||||
log = "0.4.17"
|
log = "0.4.17"
|
||||||
now = "0.1.3"
|
|
||||||
reqwest = { version = "0.11.13", features = ["json"] }
|
reqwest = { version = "0.11.13", features = ["json"] }
|
||||||
serde = "1.0.148"
|
serde = "1.0.148"
|
||||||
serde_json = "1.0.89"
|
serde_json = "1.0.89"
|
||||||
tokio = { version = "1.22.0", features = ["full"] }
|
tokio = { version = "1.22.0", features = ["full"] }
|
||||||
|
untis = { git = "https://git.dergrimm.net/dergrimm/untis.rs.git", branch = "main" }
|
||||||
url = "2.3.1"
|
url = "2.3.1"
|
||||||
|
|
||||||
[target.'cfg(not(target_env = "msvc"))'.dependencies]
|
[target.'cfg(not(target_env = "msvc"))'.dependencies]
|
||||||
|
|
|
@ -3,7 +3,7 @@ WORKDIR /usr/src/backend
|
||||||
|
|
||||||
FROM chef as planner
|
FROM chef as planner
|
||||||
RUN mkdir src && touch src/main.rs
|
RUN mkdir src && touch src/main.rs
|
||||||
COPY ./Cargo.toml .
|
COPY ./Cargo.toml ./Cargo.lock ./
|
||||||
RUN cargo chef prepare --recipe-path recipe.json
|
RUN cargo chef prepare --recipe-path recipe.json
|
||||||
|
|
||||||
FROM chef as builder
|
FROM chef as builder
|
||||||
|
|
|
@ -1,9 +1,3 @@
|
||||||
DROP TABLE substitution_query_results;
|
|
||||||
|
|
||||||
DROP TABLE substitution_queries;
|
|
||||||
|
|
||||||
DROP TABLE substitution_planned_queries;
|
|
||||||
|
|
||||||
DROP TABLE substitution_room;
|
DROP TABLE substitution_room;
|
||||||
|
|
||||||
DROP TABLE substitution_subject;
|
DROP TABLE substitution_subject;
|
||||||
|
@ -16,6 +10,8 @@ DROP TABLE substitutions;
|
||||||
|
|
||||||
DROP TYPE substitution_type;
|
DROP TYPE substitution_type;
|
||||||
|
|
||||||
|
DROP TABLE substitution_queries;
|
||||||
|
|
||||||
DROP TABLE timegrid_time_unit;
|
DROP TABLE timegrid_time_unit;
|
||||||
|
|
||||||
DROP TABLE timegrid_days;
|
DROP TABLE timegrid_days;
|
||||||
|
@ -34,6 +30,8 @@ DROP TABLE classes;
|
||||||
|
|
||||||
DROP TABLE teachers;
|
DROP TABLE teachers;
|
||||||
|
|
||||||
|
DROP INDEX tenants_active;
|
||||||
|
|
||||||
DROP TABLE tenants;
|
DROP TABLE tenants;
|
||||||
|
|
||||||
DROP TABLE schoolyears;
|
DROP TABLE schoolyears;
|
|
@ -18,7 +18,7 @@ CREATE TABLE tenants(
|
||||||
updated_at TIMESTAMP
|
updated_at TIMESTAMP
|
||||||
);
|
);
|
||||||
|
|
||||||
CREATE UNIQUE INDEX ON tenants(active)
|
CREATE UNIQUE INDEX tenants_active ON tenants(active)
|
||||||
WHERE
|
WHERE
|
||||||
active;
|
active;
|
||||||
|
|
||||||
|
@ -110,6 +110,17 @@ CREATE TABLE timegrid_time_unit(
|
||||||
updated_at TIMESTAMP
|
updated_at TIMESTAMP
|
||||||
);
|
);
|
||||||
|
|
||||||
|
CREATE TABLE substitution_queries(
|
||||||
|
id SERIAL PRIMARY KEY,
|
||||||
|
schoolyear_id INTEGER NOT NULL REFERENCES schoolyears(id),
|
||||||
|
date DATE NOT NULL UNIQUE,
|
||||||
|
active BOOLEAN NOT NULL,
|
||||||
|
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
updated_at TIMESTAMP
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE INDEX substitution_queries_active ON substitution_queries(active);
|
||||||
|
|
||||||
CREATE TYPE substitution_type AS ENUM (
|
CREATE TYPE substitution_type AS ENUM (
|
||||||
'cancel',
|
'cancel',
|
||||||
'subst',
|
'subst',
|
||||||
|
@ -130,6 +141,7 @@ CREATE TYPE substitution_type AS ENUM (
|
||||||
|
|
||||||
CREATE TABLE substitutions(
|
CREATE TABLE substitutions(
|
||||||
id SERIAL PRIMARY KEY,
|
id SERIAL PRIMARY KEY,
|
||||||
|
substitution_query_id INTEGER NOT NULL REFERENCES substitution_queries(id),
|
||||||
subst_type substitution_type NOT NULL,
|
subst_type substitution_type NOT NULL,
|
||||||
lesson_id INTEGER NOT NULL,
|
lesson_id INTEGER NOT NULL,
|
||||||
start_time TIME NOT NULL,
|
start_time TIME NOT NULL,
|
||||||
|
@ -142,6 +154,7 @@ CREATE TABLE substitutions(
|
||||||
CREATE TABLE substitution_classes(
|
CREATE TABLE substitution_classes(
|
||||||
id SERIAL PRIMARY KEY,
|
id SERIAL PRIMARY KEY,
|
||||||
substitution_id INTEGER NOT NULL REFERENCES substitutions(id),
|
substitution_id INTEGER NOT NULL REFERENCES substitutions(id),
|
||||||
|
position SMALLINT NOT NULL,
|
||||||
class_id INTEGER NOT NULL REFERENCES classes(id),
|
class_id INTEGER NOT NULL REFERENCES classes(id),
|
||||||
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||||
updated_at TIMESTAMP
|
updated_at TIMESTAMP
|
||||||
|
@ -150,7 +163,8 @@ CREATE TABLE substitution_classes(
|
||||||
CREATE TABLE substitution_teachers(
|
CREATE TABLE substitution_teachers(
|
||||||
id SERIAL PRIMARY KEY,
|
id SERIAL PRIMARY KEY,
|
||||||
substitution_id INTEGER NOT NULL REFERENCES substitutions(id),
|
substitution_id INTEGER NOT NULL REFERENCES substitutions(id),
|
||||||
teacher_id INTEGER NOT NULL REFERENCES teachers(id),
|
position SMALLINT NOT NULL,
|
||||||
|
teacher_id INTEGER REFERENCES teachers(id),
|
||||||
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||||
updated_at TIMESTAMP
|
updated_at TIMESTAMP
|
||||||
);
|
);
|
||||||
|
@ -158,6 +172,7 @@ CREATE TABLE substitution_teachers(
|
||||||
CREATE TABLE substitution_subjects(
|
CREATE TABLE substitution_subjects(
|
||||||
id SERIAL PRIMARY KEY,
|
id SERIAL PRIMARY KEY,
|
||||||
substitution_id INTEGER NOT NULL REFERENCES substitutions(id),
|
substitution_id INTEGER NOT NULL REFERENCES substitutions(id),
|
||||||
|
position SMALLINT NOT NULL,
|
||||||
subject_id INTEGER NOT NULL REFERENCES subjects(id),
|
subject_id INTEGER NOT NULL REFERENCES subjects(id),
|
||||||
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||||
updated_at TIMESTAMP
|
updated_at TIMESTAMP
|
||||||
|
@ -166,34 +181,8 @@ CREATE TABLE substitution_subjects(
|
||||||
CREATE TABLE substitution_rooms(
|
CREATE TABLE substitution_rooms(
|
||||||
id SERIAL PRIMARY KEY,
|
id SERIAL PRIMARY KEY,
|
||||||
substitution_id INTEGER NOT NULL REFERENCES substitutions(id),
|
substitution_id INTEGER NOT NULL REFERENCES substitutions(id),
|
||||||
room_id INTEGER NOT NULL REFERENCES rooms(id),
|
position SMALLINT NOT NULL,
|
||||||
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
room_id INTEGER REFERENCES rooms(id),
|
||||||
updated_at TIMESTAMP
|
|
||||||
);
|
|
||||||
|
|
||||||
CREATE TABLE substitution_planned_queries(
|
|
||||||
id SERIAL PRIMARY KEY,
|
|
||||||
schoolyear_id INTEGER NOT NULL REFERENCES schoolyears(id),
|
|
||||||
date DATE NOT NULL UNIQUE,
|
|
||||||
active BOOLEAN NOT NULL,
|
|
||||||
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
|
||||||
updated_at TIMESTAMP
|
|
||||||
);
|
|
||||||
|
|
||||||
CREATE INDEX ON substitution_planned_queries(active);
|
|
||||||
|
|
||||||
CREATE TABLE substitution_queries(
|
|
||||||
id SERIAL PRIMARY KEY,
|
|
||||||
substitution_planned_query_id INTEGER NOT NULL REFERENCES substitution_planned_queries(id),
|
|
||||||
queried_at TIMESTAMP NOT NULL,
|
|
||||||
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
|
||||||
updated_at TIMESTAMP
|
|
||||||
);
|
|
||||||
|
|
||||||
CREATE TABLE substitution_query_results(
|
|
||||||
id SERIAL PRIMARY KEY,
|
|
||||||
substitution_query_id INTEGER NOT NULL REFERENCES substitution_queries(id),
|
|
||||||
substitution_id INTEGER NOT NULL REFERENCES substitutions(id),
|
|
||||||
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||||
updated_at TIMESTAMP
|
updated_at TIMESTAMP
|
||||||
);
|
);
|
|
@ -1,5 +1,7 @@
|
||||||
|
use anyhow::Result;
|
||||||
use envconfig::Envconfig;
|
use envconfig::Envconfig;
|
||||||
use lazy_static::lazy_static;
|
use lazy_static::lazy_static;
|
||||||
|
use url::Url;
|
||||||
|
|
||||||
#[derive(Envconfig, Debug)]
|
#[derive(Envconfig, Debug)]
|
||||||
pub struct Config {
|
pub struct Config {
|
||||||
|
@ -15,6 +17,9 @@ pub struct Config {
|
||||||
#[envconfig(from = "BACKEND_UNTIS_RPC_URL")]
|
#[envconfig(from = "BACKEND_UNTIS_RPC_URL")]
|
||||||
pub untis_rpc_url: String,
|
pub untis_rpc_url: String,
|
||||||
|
|
||||||
|
#[envconfig(from = "BACKEND_UNTIS_CLIENT_NAME")]
|
||||||
|
pub untis_client_name: String,
|
||||||
|
|
||||||
#[envconfig(from = "BACKEND_UNTIS_SCHOOL")]
|
#[envconfig(from = "BACKEND_UNTIS_SCHOOL")]
|
||||||
pub untis_school: String,
|
pub untis_school: String,
|
||||||
|
|
||||||
|
@ -28,3 +33,18 @@ pub struct Config {
|
||||||
lazy_static! {
|
lazy_static! {
|
||||||
pub static ref CONFIG: Config = Config::init_from_env().unwrap();
|
pub static ref CONFIG: Config = Config::init_from_env().unwrap();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn untis_from_env() -> Result<untis::Client> {
|
||||||
|
Ok(untis::Client {
|
||||||
|
api_url: Url::parse(&CONFIG.untis_api_url)?,
|
||||||
|
rpc_url: untis::Client::gen_rpc_url(
|
||||||
|
Url::parse(&CONFIG.untis_rpc_url)?,
|
||||||
|
&CONFIG.untis_school,
|
||||||
|
),
|
||||||
|
client_name: CONFIG.untis_client_name.to_owned(),
|
||||||
|
username: CONFIG.untis_username.to_owned(),
|
||||||
|
password: CONFIG.untis_password.to_owned(),
|
||||||
|
session: None,
|
||||||
|
authorization: None,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
|
@ -3,7 +3,6 @@ use diesel::prelude::*;
|
||||||
use std::io::Write;
|
use std::io::Write;
|
||||||
|
|
||||||
use crate::db::schema;
|
use crate::db::schema;
|
||||||
use crate::untis;
|
|
||||||
|
|
||||||
#[derive(Identifiable, Queryable, Debug)]
|
#[derive(Identifiable, Queryable, Debug)]
|
||||||
#[diesel(table_name = schema::schoolyears)]
|
#[diesel(table_name = schema::schoolyears)]
|
||||||
|
@ -286,8 +285,29 @@ impl From<untis::RpcSubstitionType> for SubstitutionType {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Identifiable, Queryable, Debug)]
|
#[derive(Identifiable, Queryable, Associations, Debug)]
|
||||||
|
#[diesel(table_name = schema::substitution_queries)]
|
||||||
|
#[diesel(belongs_to(Schoolyear))]
|
||||||
|
pub struct SubstitutionQuery {
|
||||||
|
pub id: i32,
|
||||||
|
pub schoolyear_id: i32,
|
||||||
|
pub date: NaiveDate,
|
||||||
|
pub active: bool,
|
||||||
|
pub created_at: NaiveDateTime,
|
||||||
|
pub updated_at: Option<NaiveDateTime>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Insertable, Debug)]
|
||||||
|
#[diesel(table_name = schema::substitution_queries)]
|
||||||
|
pub struct NewSubstitutionQuery {
|
||||||
|
pub schoolyear_id: i32,
|
||||||
|
pub date: NaiveDate,
|
||||||
|
pub active: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Identifiable, Queryable, Associations, Debug)]
|
||||||
#[diesel(table_name = schema::substitutions)]
|
#[diesel(table_name = schema::substitutions)]
|
||||||
|
#[diesel(belongs_to(SubstitutionQuery))]
|
||||||
pub struct Substitution {
|
pub struct Substitution {
|
||||||
pub id: i32,
|
pub id: i32,
|
||||||
pub substitution_query_id: i32,
|
pub substitution_query_id: i32,
|
||||||
|
@ -304,12 +324,12 @@ pub struct Substitution {
|
||||||
#[derive(Insertable, Debug)]
|
#[derive(Insertable, Debug)]
|
||||||
#[diesel(table_name = schema::substitutions)]
|
#[diesel(table_name = schema::substitutions)]
|
||||||
pub struct NewSubstitution<'a> {
|
pub struct NewSubstitution<'a> {
|
||||||
|
pub substitution_query_id: i32,
|
||||||
pub subst_type: SubstitutionType,
|
pub subst_type: SubstitutionType,
|
||||||
pub lesson_id: i32,
|
pub lesson_id: i32,
|
||||||
pub start_time: NaiveTime,
|
pub start_time: NaiveTime,
|
||||||
pub end_time: NaiveTime,
|
pub end_time: NaiveTime,
|
||||||
pub text: Option<&'a str>,
|
pub text: Option<&'a str>,
|
||||||
pub active: bool,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Identifiable, Queryable, Associations, Debug)]
|
#[derive(Identifiable, Queryable, Associations, Debug)]
|
||||||
|
@ -319,6 +339,7 @@ pub struct NewSubstitution<'a> {
|
||||||
pub struct SubstitutionClass {
|
pub struct SubstitutionClass {
|
||||||
pub id: i32,
|
pub id: i32,
|
||||||
pub substitution_id: i32,
|
pub substitution_id: i32,
|
||||||
|
pub position: i16,
|
||||||
pub class_id: i32,
|
pub class_id: i32,
|
||||||
pub created_at: NaiveDateTime,
|
pub created_at: NaiveDateTime,
|
||||||
pub updated_at: Option<NaiveDateTime>,
|
pub updated_at: Option<NaiveDateTime>,
|
||||||
|
@ -328,6 +349,7 @@ pub struct SubstitutionClass {
|
||||||
#[diesel(table_name = schema::substitution_classes)]
|
#[diesel(table_name = schema::substitution_classes)]
|
||||||
pub struct NewSubstitutionClass {
|
pub struct NewSubstitutionClass {
|
||||||
pub substitution_id: i32,
|
pub substitution_id: i32,
|
||||||
|
pub position: i16,
|
||||||
pub class_id: i32,
|
pub class_id: i32,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -338,7 +360,8 @@ pub struct NewSubstitutionClass {
|
||||||
pub struct SubstitutionTeacher {
|
pub struct SubstitutionTeacher {
|
||||||
pub id: i32,
|
pub id: i32,
|
||||||
pub substitution_id: i32,
|
pub substitution_id: i32,
|
||||||
pub teacher_id: i32,
|
pub position: i16,
|
||||||
|
pub teacher_id: Option<i32>,
|
||||||
pub created_at: NaiveDateTime,
|
pub created_at: NaiveDateTime,
|
||||||
pub updated_at: Option<NaiveDateTime>,
|
pub updated_at: Option<NaiveDateTime>,
|
||||||
}
|
}
|
||||||
|
@ -347,7 +370,8 @@ pub struct SubstitutionTeacher {
|
||||||
#[diesel(table_name = schema::substitution_teachers)]
|
#[diesel(table_name = schema::substitution_teachers)]
|
||||||
pub struct NewSubstitutionTeacher {
|
pub struct NewSubstitutionTeacher {
|
||||||
pub substitution_id: i32,
|
pub substitution_id: i32,
|
||||||
pub teacher_id: i32,
|
pub position: i16,
|
||||||
|
pub teacher_id: Option<i32>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Identifiable, Queryable, Associations, Debug)]
|
#[derive(Identifiable, Queryable, Associations, Debug)]
|
||||||
|
@ -357,6 +381,7 @@ pub struct NewSubstitutionTeacher {
|
||||||
pub struct SubstitutionSubject {
|
pub struct SubstitutionSubject {
|
||||||
pub id: i32,
|
pub id: i32,
|
||||||
pub substitution_id: i32,
|
pub substitution_id: i32,
|
||||||
|
pub position: i16,
|
||||||
pub subject_id: i32,
|
pub subject_id: i32,
|
||||||
pub created_at: NaiveDateTime,
|
pub created_at: NaiveDateTime,
|
||||||
pub updated_at: Option<NaiveDateTime>,
|
pub updated_at: Option<NaiveDateTime>,
|
||||||
|
@ -366,6 +391,7 @@ pub struct SubstitutionSubject {
|
||||||
#[diesel(table_name = schema::substitution_subjects)]
|
#[diesel(table_name = schema::substitution_subjects)]
|
||||||
pub struct NewSubstitutionSubject {
|
pub struct NewSubstitutionSubject {
|
||||||
pub substitution_id: i32,
|
pub substitution_id: i32,
|
||||||
|
pub position: i16,
|
||||||
pub subject_id: i32,
|
pub subject_id: i32,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -376,7 +402,9 @@ pub struct NewSubstitutionSubject {
|
||||||
pub struct SubstitutionRoom {
|
pub struct SubstitutionRoom {
|
||||||
pub id: i32,
|
pub id: i32,
|
||||||
pub substitution_id: i32,
|
pub substitution_id: i32,
|
||||||
pub room_id: i32,
|
pub position: i16,
|
||||||
|
pub index: i16,
|
||||||
|
pub room_id: Option<i32>,
|
||||||
pub created_at: NaiveDateTime,
|
pub created_at: NaiveDateTime,
|
||||||
pub updated_at: Option<NaiveDateTime>,
|
pub updated_at: Option<NaiveDateTime>,
|
||||||
}
|
}
|
||||||
|
@ -385,62 +413,6 @@ pub struct SubstitutionRoom {
|
||||||
#[diesel(table_name = schema::substitution_rooms)]
|
#[diesel(table_name = schema::substitution_rooms)]
|
||||||
pub struct NewSubstitutionRoom {
|
pub struct NewSubstitutionRoom {
|
||||||
pub substitution_id: i32,
|
pub substitution_id: i32,
|
||||||
pub room_id: i32,
|
pub position: i16,
|
||||||
}
|
pub room_id: Option<i32>,
|
||||||
|
|
||||||
#[derive(Identifiable, Queryable, Associations, Debug)]
|
|
||||||
#[diesel(table_name = schema::substitution_planned_queries)]
|
|
||||||
#[diesel(belongs_to(Schoolyear))]
|
|
||||||
pub struct SubstitutionPlannedQuery {
|
|
||||||
pub id: i32,
|
|
||||||
pub schoolyear_id: i32,
|
|
||||||
pub date: NaiveDate,
|
|
||||||
pub active: bool,
|
|
||||||
pub created_at: NaiveDateTime,
|
|
||||||
pub updated_at: Option<NaiveDateTime>,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Insertable, Debug)]
|
|
||||||
#[diesel(table_name = schema::substitution_planned_queries)]
|
|
||||||
pub struct NewSubstitutionPlannedQuery {
|
|
||||||
pub schoolyear_id: i32,
|
|
||||||
pub date: NaiveDate,
|
|
||||||
pub active: bool,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Identifiable, Queryable, Associations, Debug)]
|
|
||||||
#[diesel(table_name = schema::substitution_queries)]
|
|
||||||
#[diesel(belongs_to(SubstitutionPlannedQuery))]
|
|
||||||
pub struct SubstitutionQuery {
|
|
||||||
pub id: i32,
|
|
||||||
pub substitution_planned_query_id: i32,
|
|
||||||
pub queried_at: NaiveDateTime,
|
|
||||||
pub created_at: NaiveDateTime,
|
|
||||||
pub updated_at: Option<NaiveDateTime>,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Insertable, Debug)]
|
|
||||||
#[diesel(table_name = schema::substitution_queries)]
|
|
||||||
pub struct NewSubstitutionQuery {
|
|
||||||
pub substitution_planned_query_id: i32,
|
|
||||||
pub queried_at: NaiveDateTime,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Identifiable, Queryable, Associations, Debug)]
|
|
||||||
#[diesel(table_name = schema::substitution_query_results)]
|
|
||||||
#[diesel(belongs_to(SubstitutionQuery))]
|
|
||||||
#[diesel(belongs_to(Substitution))]
|
|
||||||
pub struct SubstitutionQueryResult {
|
|
||||||
pub id: i32,
|
|
||||||
pub substitution_query_id: i32,
|
|
||||||
pub substitution_id: i32,
|
|
||||||
pub created_at: NaiveDateTime,
|
|
||||||
pub updated_at: Option<NaiveDateTime>,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Insertable, Debug)]
|
|
||||||
#[diesel(table_name = schema::substitution_query_results)]
|
|
||||||
pub struct NewSubstitutionQueryResult {
|
|
||||||
pub substitution_query_id: i32,
|
|
||||||
pub substitution_id: i32,
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -136,65 +136,7 @@ diesel::table! {
|
||||||
}
|
}
|
||||||
|
|
||||||
diesel::table! {
|
diesel::table! {
|
||||||
use diesel::sql_types::*;
|
substitution_queries {
|
||||||
|
|
||||||
use super::sql_types::SubstitutionType;
|
|
||||||
|
|
||||||
substitutions {
|
|
||||||
id -> Integer,
|
|
||||||
subst_type -> SubstitutionType,
|
|
||||||
lesson_id -> Integer,
|
|
||||||
start_time -> Time,
|
|
||||||
end_time -> Time,
|
|
||||||
text -> Nullable<VarChar>,
|
|
||||||
active -> Bool,
|
|
||||||
created_at -> Timestamp,
|
|
||||||
updated_at -> Nullable<Timestamp>,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
diesel::table! {
|
|
||||||
substitution_classes {
|
|
||||||
id -> Integer,
|
|
||||||
substitution_id -> Integer,
|
|
||||||
class_id -> Integer,
|
|
||||||
created_at -> Timestamp,
|
|
||||||
updated_at -> Nullable<Timestamp>,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
diesel::table! {
|
|
||||||
substitution_teachers {
|
|
||||||
id -> Integer,
|
|
||||||
substitution_id -> Integer,
|
|
||||||
teacher_id -> Integer,
|
|
||||||
created_at -> Timestamp,
|
|
||||||
updated_at -> Nullable<Timestamp>,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
diesel::table! {
|
|
||||||
substitution_subjects {
|
|
||||||
id -> Integer,
|
|
||||||
substitution_id -> Integer,
|
|
||||||
subject_id -> Integer,
|
|
||||||
created_at -> Timestamp,
|
|
||||||
updated_at -> Nullable<Timestamp>,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
diesel::table! {
|
|
||||||
substitution_rooms {
|
|
||||||
id -> Integer,
|
|
||||||
substitution_id -> Integer,
|
|
||||||
room_id -> Integer,
|
|
||||||
created_at -> Timestamp,
|
|
||||||
updated_at -> Nullable<Timestamp>,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
diesel::table! {
|
|
||||||
substitution_planned_queries {
|
|
||||||
id -> Integer,
|
id -> Integer,
|
||||||
schoolyear_id -> Integer,
|
schoolyear_id -> Integer,
|
||||||
date -> Date,
|
date -> Date,
|
||||||
|
@ -205,20 +147,62 @@ diesel::table! {
|
||||||
}
|
}
|
||||||
|
|
||||||
diesel::table! {
|
diesel::table! {
|
||||||
substitution_queries {
|
use diesel::sql_types::*;
|
||||||
|
|
||||||
|
use super::sql_types::SubstitutionType;
|
||||||
|
|
||||||
|
substitutions {
|
||||||
id -> Integer,
|
id -> Integer,
|
||||||
substitution_planned_query_id -> Integer,
|
substitution_query_id -> Integer,
|
||||||
queried_at -> Timestamp,
|
subst_type -> SubstitutionType,
|
||||||
|
lesson_id -> Integer,
|
||||||
|
start_time -> Time,
|
||||||
|
end_time -> Time,
|
||||||
|
text -> Nullable<VarChar>,
|
||||||
created_at -> Timestamp,
|
created_at -> Timestamp,
|
||||||
updated_at -> Nullable<Timestamp>,
|
updated_at -> Nullable<Timestamp>,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
diesel::table! {
|
diesel::table! {
|
||||||
substitution_query_results {
|
substitution_classes {
|
||||||
id -> Integer,
|
id -> Integer,
|
||||||
substitution_query_id -> Integer,
|
|
||||||
substitution_id -> Integer,
|
substitution_id -> Integer,
|
||||||
|
position -> SmallInt,
|
||||||
|
class_id -> Integer,
|
||||||
|
created_at -> Timestamp,
|
||||||
|
updated_at -> Nullable<Timestamp>,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
diesel::table! {
|
||||||
|
substitution_teachers {
|
||||||
|
id -> Integer,
|
||||||
|
substitution_id -> Integer,
|
||||||
|
position -> SmallInt,
|
||||||
|
teacher_id -> Nullable<Integer>,
|
||||||
|
created_at -> Timestamp,
|
||||||
|
updated_at -> Nullable<Timestamp>,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
diesel::table! {
|
||||||
|
substitution_subjects {
|
||||||
|
id -> Integer,
|
||||||
|
substitution_id -> Integer,
|
||||||
|
position -> SmallInt,
|
||||||
|
subject_id -> Integer,
|
||||||
|
created_at -> Timestamp,
|
||||||
|
updated_at -> Nullable<Timestamp>,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
diesel::table! {
|
||||||
|
substitution_rooms {
|
||||||
|
id -> Integer,
|
||||||
|
substitution_id -> Integer,
|
||||||
|
position -> SmallInt,
|
||||||
|
room_id -> Nullable<Integer>,
|
||||||
created_at -> Timestamp,
|
created_at -> Timestamp,
|
||||||
updated_at -> Nullable<Timestamp>,
|
updated_at -> Nullable<Timestamp>,
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,4 @@
|
||||||
pub mod config;
|
pub mod config;
|
||||||
pub mod db;
|
pub mod db;
|
||||||
pub mod graphql;
|
pub mod graphql;
|
||||||
pub mod untis;
|
|
||||||
pub mod worker;
|
pub mod worker;
|
||||||
|
|
|
@ -1,717 +0,0 @@
|
||||||
use anyhow::{bail, Context, Result};
|
|
||||||
use chrono::Datelike;
|
|
||||||
use cookie::Cookie;
|
|
||||||
use serde::Deserialize;
|
|
||||||
use serde_json::json;
|
|
||||||
|
|
||||||
pub const CLIENT_NAME: &str = "BVplan@OHG-Furtwangen";
|
|
||||||
|
|
||||||
fn deserialize_date<'de, D>(deserializer: D) -> Result<chrono::NaiveDate, D::Error>
|
|
||||||
where
|
|
||||||
D: serde::de::Deserializer<'de>,
|
|
||||||
{
|
|
||||||
struct Visitor;
|
|
||||||
|
|
||||||
impl<'de> serde::de::Visitor<'de> for Visitor {
|
|
||||||
type Value = chrono::NaiveDate;
|
|
||||||
|
|
||||||
fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result {
|
|
||||||
formatter.write_str("a naive date serialized as an unsigned integer")
|
|
||||||
}
|
|
||||||
|
|
||||||
fn visit_u64<E>(self, mut v: u64) -> Result<Self::Value, E>
|
|
||||||
where
|
|
||||||
E: serde::de::Error,
|
|
||||||
{
|
|
||||||
let y = (v / 10000) as i32;
|
|
||||||
v %= 10000;
|
|
||||||
let m = (v / 100) as u32;
|
|
||||||
v %= 100;
|
|
||||||
|
|
||||||
match chrono::NaiveDate::from_ymd_opt(y, m, v as u32) {
|
|
||||||
Some(x) => Ok(x),
|
|
||||||
None => Err(E::custom(format!("No such date: {}-{}-{}", y, m, v))),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
deserializer.deserialize_u64(Visitor)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn serialize_date(date: chrono::NaiveDate) -> u64 {
|
|
||||||
date.year() as u64 * 10000 + date.month() as u64 * 100 + date.day() as u64
|
|
||||||
}
|
|
||||||
|
|
||||||
fn deserialize_time<'de, D>(deserializer: D) -> Result<chrono::NaiveTime, D::Error>
|
|
||||||
where
|
|
||||||
D: serde::de::Deserializer<'de>,
|
|
||||||
{
|
|
||||||
struct Visitor;
|
|
||||||
|
|
||||||
impl<'de> serde::de::Visitor<'de> for Visitor {
|
|
||||||
type Value = chrono::NaiveTime;
|
|
||||||
|
|
||||||
fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result {
|
|
||||||
formatter.write_str("a naive time serialized as an unsigned integer")
|
|
||||||
}
|
|
||||||
|
|
||||||
fn visit_u64<E>(self, mut v: u64) -> Result<Self::Value, E>
|
|
||||||
where
|
|
||||||
E: serde::de::Error,
|
|
||||||
{
|
|
||||||
let h = v / 100;
|
|
||||||
v %= 100;
|
|
||||||
|
|
||||||
match chrono::NaiveTime::from_hms_opt(h as u32, v as u32, 0) {
|
|
||||||
Some(x) => Ok(x),
|
|
||||||
None => Err(E::custom(format!("No such time: {}:{}", h, v))),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
deserializer.deserialize_u64(Visitor)
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Deserialize, Debug)]
|
|
||||||
pub struct RpcError {
|
|
||||||
pub message: String,
|
|
||||||
pub code: i32,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Deserialize, Debug)]
|
|
||||||
pub struct RpcResponse<T> {
|
|
||||||
pub jsonrpc: String,
|
|
||||||
pub id: String,
|
|
||||||
pub result: Option<T>,
|
|
||||||
pub error: Option<RpcError>,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Deserialize, Debug)]
|
|
||||||
struct RpcEmptyResult;
|
|
||||||
|
|
||||||
#[derive(Deserialize, Debug)]
|
|
||||||
pub struct RpcLogin {
|
|
||||||
#[serde(rename = "sessionId")]
|
|
||||||
pub session_id: String,
|
|
||||||
#[serde(rename = "personType")]
|
|
||||||
pub person_type: i32,
|
|
||||||
#[serde(rename = "personId")]
|
|
||||||
pub person_id: i32,
|
|
||||||
#[serde(rename = "klasseId")]
|
|
||||||
pub class_id: i32,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Deserialize, Debug)]
|
|
||||||
pub struct RpcClass {
|
|
||||||
pub id: i32,
|
|
||||||
pub name: String,
|
|
||||||
#[serde(rename = "longName")]
|
|
||||||
pub long_name: String,
|
|
||||||
pub active: bool,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Deserialize, Debug)]
|
|
||||||
pub struct RpcSubject {
|
|
||||||
pub id: i32,
|
|
||||||
pub name: String,
|
|
||||||
#[serde(rename = "longName")]
|
|
||||||
pub long_name: String,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Deserialize, Debug)]
|
|
||||||
pub struct RpcRoom {
|
|
||||||
pub id: i32,
|
|
||||||
pub name: String,
|
|
||||||
#[serde(rename = "longName")]
|
|
||||||
pub long_name: String,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Deserialize, Debug)]
|
|
||||||
pub struct RpcDepartment {
|
|
||||||
pub id: i32,
|
|
||||||
pub name: String,
|
|
||||||
#[serde(rename = "longName")]
|
|
||||||
pub long_name: String,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Deserialize, Debug)]
|
|
||||||
pub struct RpcHoliday {
|
|
||||||
pub id: i32,
|
|
||||||
pub name: String,
|
|
||||||
#[serde(rename = "longName")]
|
|
||||||
pub long_name: String,
|
|
||||||
#[serde(rename = "startDate", deserialize_with = "deserialize_date")]
|
|
||||||
pub start_date: chrono::NaiveDate,
|
|
||||||
#[serde(rename = "endDate", deserialize_with = "deserialize_date")]
|
|
||||||
pub end_date: chrono::NaiveDate,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Deserialize, Debug)]
|
|
||||||
pub struct RpcTimegridDayTimeUnit {
|
|
||||||
#[serde(rename = "startTime", deserialize_with = "deserialize_time")]
|
|
||||||
pub start_time: chrono::NaiveTime,
|
|
||||||
#[serde(rename = "endTime", deserialize_with = "deserialize_time")]
|
|
||||||
pub end_time: chrono::NaiveTime,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Deserialize, Debug)]
|
|
||||||
pub struct RpcTimegridDay {
|
|
||||||
pub day: u8,
|
|
||||||
#[serde(rename = "timeUnits")]
|
|
||||||
pub time_units: Vec<RpcTimegridDayTimeUnit>,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Deserialize, Debug)]
|
|
||||||
pub struct RpcSchoolyear {
|
|
||||||
pub id: i32,
|
|
||||||
pub name: String,
|
|
||||||
#[serde(rename = "startDate", deserialize_with = "deserialize_date")]
|
|
||||||
pub start_date: chrono::NaiveDate,
|
|
||||||
#[serde(rename = "endDate", deserialize_with = "deserialize_date")]
|
|
||||||
pub end_date: chrono::NaiveDate,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Deserialize, Clone, Copy, Debug)]
|
|
||||||
pub enum RpcSubstitionType {
|
|
||||||
#[serde(rename = "cancel")]
|
|
||||||
Cancel,
|
|
||||||
#[serde(rename = "subst")]
|
|
||||||
Substitution,
|
|
||||||
#[serde(rename = "add")]
|
|
||||||
Additional,
|
|
||||||
#[serde(rename = "shift")]
|
|
||||||
Shifted,
|
|
||||||
#[serde(rename = "rmchg")]
|
|
||||||
RoomChange,
|
|
||||||
#[serde(rename = "rmlk")]
|
|
||||||
Locked,
|
|
||||||
#[serde(rename = "bs")]
|
|
||||||
BreakSupervision,
|
|
||||||
#[serde(rename = "oh")]
|
|
||||||
OfficeHour,
|
|
||||||
#[serde(rename = "sb")]
|
|
||||||
Standby,
|
|
||||||
#[serde(rename = "other")]
|
|
||||||
Other,
|
|
||||||
#[serde(rename = "free")]
|
|
||||||
Free,
|
|
||||||
#[serde(rename = "exam")]
|
|
||||||
Exam,
|
|
||||||
#[serde(rename = "ac")]
|
|
||||||
Activity,
|
|
||||||
#[serde(rename = "holi")]
|
|
||||||
Holiday,
|
|
||||||
#[serde(rename = "stxt")]
|
|
||||||
Text,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Deserialize, Debug)]
|
|
||||||
pub struct RpcSubstitutionId {
|
|
||||||
pub id: i32,
|
|
||||||
pub name: Option<String>,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Deserialize, Debug)]
|
|
||||||
pub struct RpcSubstitutionReschedule {
|
|
||||||
#[serde(deserialize_with = "deserialize_date")]
|
|
||||||
pub date: chrono::NaiveDate,
|
|
||||||
#[serde(rename = "startTime", deserialize_with = "deserialize_time")]
|
|
||||||
pub start_time: chrono::NaiveTime,
|
|
||||||
#[serde(rename = "endTime", deserialize_with = "deserialize_time")]
|
|
||||||
pub end_time: chrono::NaiveTime,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Deserialize, Debug)]
|
|
||||||
pub struct RpcSubstitution {
|
|
||||||
#[serde(rename = "type")]
|
|
||||||
pub subst_type: RpcSubstitionType,
|
|
||||||
#[serde(rename = "lsid")]
|
|
||||||
pub lesson_id: i32,
|
|
||||||
#[serde(rename = "startTime", deserialize_with = "deserialize_time")]
|
|
||||||
pub start_time: chrono::NaiveTime,
|
|
||||||
#[serde(rename = "endTime", deserialize_with = "deserialize_time")]
|
|
||||||
pub end_time: chrono::NaiveTime,
|
|
||||||
#[serde(rename = "txt")]
|
|
||||||
pub text: Option<String>,
|
|
||||||
#[serde(rename = "kl")]
|
|
||||||
pub classes: Vec<RpcSubstitutionId>,
|
|
||||||
#[serde(rename = "te")]
|
|
||||||
pub teachers: Vec<RpcSubstitutionId>,
|
|
||||||
#[serde(rename = "su")]
|
|
||||||
pub subjects: Vec<RpcSubstitutionId>,
|
|
||||||
#[serde(rename = "ro")]
|
|
||||||
pub rooms: Vec<RpcSubstitutionId>,
|
|
||||||
pub reschedule: Option<RpcSubstitutionReschedule>,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Deserialize, Debug)]
|
|
||||||
pub struct ApiTenant {
|
|
||||||
pub id: i32,
|
|
||||||
#[serde(rename = "displayName")]
|
|
||||||
pub display_name: String,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Deserialize, Debug)]
|
|
||||||
struct ApiDataResponse {
|
|
||||||
tenant: ApiTenant,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Deserialize, Debug)]
|
|
||||||
pub struct ApiTeacher {
|
|
||||||
pub id: i32,
|
|
||||||
pub name: String,
|
|
||||||
pub forename: String,
|
|
||||||
#[serde(rename = "longName")]
|
|
||||||
pub long_name: String,
|
|
||||||
#[serde(rename = "displayname")]
|
|
||||||
pub display_name: String,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Deserialize, Debug)]
|
|
||||||
struct ApiTeachersResponseData {
|
|
||||||
elements: Vec<ApiTeacher>,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Deserialize, Debug)]
|
|
||||||
struct ApiTeachersResponse {
|
|
||||||
data: ApiTeachersResponseData,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug)]
|
|
||||||
pub struct Client {
|
|
||||||
pub api_url: url::Url,
|
|
||||||
pub rpc_url: url::Url,
|
|
||||||
pub username: String,
|
|
||||||
pub password: String,
|
|
||||||
pub session: Option<String>,
|
|
||||||
pub authorization: Option<String>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Client {
|
|
||||||
pub fn gen_rpc_url(mut endpoint: url::Url, school: &str) -> url::Url {
|
|
||||||
endpoint.query_pairs_mut().append_pair("school", school);
|
|
||||||
|
|
||||||
endpoint
|
|
||||||
}
|
|
||||||
|
|
||||||
pub async fn login_rpc(&mut self) -> Result<RpcLogin> {
|
|
||||||
let resp: RpcResponse<RpcLogin> = reqwest::Client::new()
|
|
||||||
.get(self.rpc_url.as_str())
|
|
||||||
.header(reqwest::header::CONTENT_TYPE, "application/json")
|
|
||||||
.header(reqwest::header::ACCEPT, "application/json-rpc")
|
|
||||||
.json(&json!({
|
|
||||||
"id": "ID",
|
|
||||||
"method": "authenticate",
|
|
||||||
"params": {
|
|
||||||
"user": self.username,
|
|
||||||
"password": self.password,
|
|
||||||
"client": CLIENT_NAME,
|
|
||||||
},
|
|
||||||
"jsonrpc": "2.0"
|
|
||||||
}))
|
|
||||||
.send()
|
|
||||||
.await?
|
|
||||||
.json()
|
|
||||||
.await?;
|
|
||||||
if let Some(e) = resp.error {
|
|
||||||
bail!("RPC error: {:?}", e);
|
|
||||||
}
|
|
||||||
|
|
||||||
self.session = Some(
|
|
||||||
Cookie::new(
|
|
||||||
"JSESSIONID",
|
|
||||||
&resp
|
|
||||||
.result
|
|
||||||
.as_ref()
|
|
||||||
.context("Result null event though error not")?
|
|
||||||
.session_id,
|
|
||||||
)
|
|
||||||
.to_string(),
|
|
||||||
);
|
|
||||||
|
|
||||||
if let Some(x) = resp.result {
|
|
||||||
Ok(x)
|
|
||||||
} else {
|
|
||||||
bail!("RPC result is null");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub async fn login_api(&mut self) -> Result<String> {
|
|
||||||
let jwt = reqwest::Client::new()
|
|
||||||
.get(self.api_url.join("token/new")?)
|
|
||||||
.header(
|
|
||||||
reqwest::header::COOKIE,
|
|
||||||
self.session.as_ref().context("Not logged in")?,
|
|
||||||
)
|
|
||||||
.send()
|
|
||||||
.await?
|
|
||||||
.text()
|
|
||||||
.await?;
|
|
||||||
self.authorization = Some(jwt.to_string());
|
|
||||||
|
|
||||||
Ok(jwt)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub async fn login(&mut self) -> Result<()> {
|
|
||||||
self.login_rpc().await?;
|
|
||||||
self.login_api().await?;
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
pub async fn logout(&mut self) -> Result<()> {
|
|
||||||
let resp: RpcResponse<RpcEmptyResult> = reqwest::Client::new()
|
|
||||||
.get(self.rpc_url.as_str())
|
|
||||||
.header(reqwest::header::CONTENT_TYPE, "application/json")
|
|
||||||
.header(reqwest::header::ACCEPT, "application/json-rpc")
|
|
||||||
.header(
|
|
||||||
reqwest::header::COOKIE,
|
|
||||||
self.session.as_ref().context("Not logged in")?,
|
|
||||||
)
|
|
||||||
.json(&json!({
|
|
||||||
"id": "ID",
|
|
||||||
"method": "logout",
|
|
||||||
"params": {},
|
|
||||||
"jsonrpc": "2.0"
|
|
||||||
}))
|
|
||||||
.send()
|
|
||||||
.await?
|
|
||||||
.json()
|
|
||||||
.await?;
|
|
||||||
self.session = None;
|
|
||||||
if let Some(e) = resp.error {
|
|
||||||
bail!("RPC error: {:?}", e);
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
pub async fn classes(&self) -> Result<Vec<RpcClass>> {
|
|
||||||
let resp: RpcResponse<Vec<RpcClass>> = reqwest::Client::new()
|
|
||||||
.get(self.rpc_url.as_str())
|
|
||||||
.header(reqwest::header::CONTENT_TYPE, "application/json")
|
|
||||||
.header(reqwest::header::ACCEPT, "application/json-rpc")
|
|
||||||
.header(
|
|
||||||
reqwest::header::COOKIE,
|
|
||||||
self.session.as_ref().context("Not logged in")?,
|
|
||||||
)
|
|
||||||
.json(&json!({
|
|
||||||
"id": "ID",
|
|
||||||
"method": "getKlassen",
|
|
||||||
"params": {},
|
|
||||||
"jsonrpc": "2.0"
|
|
||||||
}))
|
|
||||||
.send()
|
|
||||||
.await?
|
|
||||||
.json()
|
|
||||||
.await?;
|
|
||||||
if let Some(e) = resp.error {
|
|
||||||
bail!("RPC error: {:?}", e);
|
|
||||||
}
|
|
||||||
|
|
||||||
if let Some(x) = resp.result {
|
|
||||||
Ok(x)
|
|
||||||
} else {
|
|
||||||
bail!("RPC result is null");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub async fn subjects(&self) -> Result<Vec<RpcSubject>> {
|
|
||||||
let resp: RpcResponse<Vec<RpcSubject>> = reqwest::Client::new()
|
|
||||||
.get(self.rpc_url.as_str())
|
|
||||||
.header(reqwest::header::CONTENT_TYPE, "application/json")
|
|
||||||
.header(reqwest::header::ACCEPT, "application/json-rpc")
|
|
||||||
.header(
|
|
||||||
reqwest::header::COOKIE,
|
|
||||||
self.session.as_ref().context("Not logged in")?,
|
|
||||||
)
|
|
||||||
.json(&json!({
|
|
||||||
"id": "ID",
|
|
||||||
"method": "getSubjects",
|
|
||||||
"params": {},
|
|
||||||
"jsonrpc": "2.0"
|
|
||||||
}))
|
|
||||||
.send()
|
|
||||||
.await?
|
|
||||||
.json()
|
|
||||||
.await?;
|
|
||||||
if let Some(e) = resp.error {
|
|
||||||
bail!("RPC error: {:?}", e);
|
|
||||||
}
|
|
||||||
|
|
||||||
if let Some(x) = resp.result {
|
|
||||||
Ok(x)
|
|
||||||
} else {
|
|
||||||
bail!("RPC result is null");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub async fn rooms(&self) -> Result<Vec<RpcRoom>> {
|
|
||||||
let resp: RpcResponse<Vec<RpcRoom>> = reqwest::Client::new()
|
|
||||||
.get(self.rpc_url.as_str())
|
|
||||||
.header(reqwest::header::CONTENT_TYPE, "application/json")
|
|
||||||
.header(reqwest::header::ACCEPT, "application/json-rpc")
|
|
||||||
.header(
|
|
||||||
reqwest::header::COOKIE,
|
|
||||||
self.session.as_ref().context("Not logged in")?,
|
|
||||||
)
|
|
||||||
.json(&json!({
|
|
||||||
"id": "ID",
|
|
||||||
"method": "getRooms",
|
|
||||||
"params": {},
|
|
||||||
"jsonrpc": "2.0"
|
|
||||||
}))
|
|
||||||
.send()
|
|
||||||
.await?
|
|
||||||
.json()
|
|
||||||
.await?;
|
|
||||||
if let Some(e) = resp.error {
|
|
||||||
bail!("RPC error: {:?}", e);
|
|
||||||
}
|
|
||||||
|
|
||||||
if let Some(x) = resp.result {
|
|
||||||
Ok(x)
|
|
||||||
} else {
|
|
||||||
bail!("RPC result is null");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub async fn departments(&self) -> Result<Vec<RpcDepartment>> {
|
|
||||||
let resp: RpcResponse<Vec<RpcDepartment>> = reqwest::Client::new()
|
|
||||||
.get(self.rpc_url.as_str())
|
|
||||||
.header(reqwest::header::CONTENT_TYPE, "application/json")
|
|
||||||
.header(reqwest::header::ACCEPT, "application/json-rpc")
|
|
||||||
.header(
|
|
||||||
reqwest::header::COOKIE,
|
|
||||||
self.session.as_ref().context("Not logged in")?,
|
|
||||||
)
|
|
||||||
.json(&json!({
|
|
||||||
"id": "ID",
|
|
||||||
"method": "getDepartments",
|
|
||||||
"params": {},
|
|
||||||
"jsonrpc": "2.0"
|
|
||||||
}))
|
|
||||||
.send()
|
|
||||||
.await?
|
|
||||||
.json()
|
|
||||||
.await?;
|
|
||||||
if let Some(e) = resp.error {
|
|
||||||
bail!("RPC error: {:?}", e);
|
|
||||||
}
|
|
||||||
|
|
||||||
if let Some(x) = resp.result {
|
|
||||||
Ok(x)
|
|
||||||
} else {
|
|
||||||
bail!("RPC result is null");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub async fn holidays(&self) -> Result<Vec<RpcHoliday>> {
|
|
||||||
let resp: RpcResponse<Vec<RpcHoliday>> = reqwest::Client::new()
|
|
||||||
.get(self.rpc_url.as_str())
|
|
||||||
.header(reqwest::header::CONTENT_TYPE, "application/json")
|
|
||||||
.header(reqwest::header::ACCEPT, "application/json-rpc")
|
|
||||||
.header(
|
|
||||||
reqwest::header::COOKIE,
|
|
||||||
self.session.as_ref().context("Not logged in")?,
|
|
||||||
)
|
|
||||||
.json(&json!({
|
|
||||||
"id": "ID",
|
|
||||||
"method": "getHolidays",
|
|
||||||
"params": {},
|
|
||||||
"jsonrpc": "2.0"
|
|
||||||
}))
|
|
||||||
.send()
|
|
||||||
.await?
|
|
||||||
.json()
|
|
||||||
.await?;
|
|
||||||
if let Some(e) = resp.error {
|
|
||||||
bail!("RPC error: {:?}", e);
|
|
||||||
}
|
|
||||||
|
|
||||||
if let Some(x) = resp.result {
|
|
||||||
Ok(x)
|
|
||||||
} else {
|
|
||||||
bail!("RPC result is null");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub async fn timegrid(&self) -> Result<Vec<RpcTimegridDay>> {
|
|
||||||
let resp: RpcResponse<Vec<RpcTimegridDay>> = reqwest::Client::new()
|
|
||||||
.get(self.rpc_url.as_str())
|
|
||||||
.header(reqwest::header::CONTENT_TYPE, "application/json")
|
|
||||||
.header(reqwest::header::ACCEPT, "application/json-rpc")
|
|
||||||
.header(
|
|
||||||
reqwest::header::COOKIE,
|
|
||||||
self.session.as_ref().context("Not logged in")?,
|
|
||||||
)
|
|
||||||
.json(&json!({
|
|
||||||
"id": "ID",
|
|
||||||
"method": "getTimegridUnits",
|
|
||||||
"params": {},
|
|
||||||
"jsonrpc": "2.0"
|
|
||||||
}))
|
|
||||||
.send()
|
|
||||||
.await?
|
|
||||||
.json()
|
|
||||||
.await?;
|
|
||||||
if let Some(e) = resp.error {
|
|
||||||
bail!("RPC error: {:?}", e);
|
|
||||||
}
|
|
||||||
|
|
||||||
if let Some(x) = resp.result {
|
|
||||||
Ok(x)
|
|
||||||
} else {
|
|
||||||
bail!("RPC result is null");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub async fn current_schoolyear(&self) -> Result<RpcSchoolyear> {
|
|
||||||
let resp: RpcResponse<RpcSchoolyear> = reqwest::Client::new()
|
|
||||||
.get(self.rpc_url.as_str())
|
|
||||||
.header(reqwest::header::CONTENT_TYPE, "application/json")
|
|
||||||
.header(reqwest::header::ACCEPT, "application/json-rpc")
|
|
||||||
.header(
|
|
||||||
reqwest::header::COOKIE,
|
|
||||||
self.session.as_ref().context("Not logged in")?,
|
|
||||||
)
|
|
||||||
.json(&json!({
|
|
||||||
"id": "ID",
|
|
||||||
"method": "getCurrentSchoolyear",
|
|
||||||
"params": {},
|
|
||||||
"jsonrpc": "2.0"
|
|
||||||
}))
|
|
||||||
.send()
|
|
||||||
.await?
|
|
||||||
.json()
|
|
||||||
.await?;
|
|
||||||
if let Some(e) = resp.error {
|
|
||||||
bail!("RPC error: {:?}", e);
|
|
||||||
}
|
|
||||||
|
|
||||||
if let Some(x) = resp.result {
|
|
||||||
Ok(x)
|
|
||||||
} else {
|
|
||||||
bail!("RPC result is null");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub async fn schoolyears(&self) -> Result<Vec<RpcSchoolyear>> {
|
|
||||||
let resp: RpcResponse<Vec<RpcSchoolyear>> = reqwest::Client::new()
|
|
||||||
.get(self.rpc_url.as_str())
|
|
||||||
.header(reqwest::header::CONTENT_TYPE, "application/json")
|
|
||||||
.header(reqwest::header::ACCEPT, "application/json-rpc")
|
|
||||||
.header(
|
|
||||||
reqwest::header::COOKIE,
|
|
||||||
self.session.as_ref().context("Not logged in")?,
|
|
||||||
)
|
|
||||||
.json(&json!({
|
|
||||||
"id": "ID",
|
|
||||||
"method": "getSchoolyears",
|
|
||||||
"params": {},
|
|
||||||
"jsonrpc": "2.0"
|
|
||||||
}))
|
|
||||||
.send()
|
|
||||||
.await?
|
|
||||||
.json()
|
|
||||||
.await?;
|
|
||||||
if let Some(e) = resp.error {
|
|
||||||
bail!("RPC error: {:?}", e);
|
|
||||||
}
|
|
||||||
|
|
||||||
if let Some(x) = resp.result {
|
|
||||||
Ok(x)
|
|
||||||
} else {
|
|
||||||
bail!("RPC result is null");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub async fn substitutions(
|
|
||||||
&self,
|
|
||||||
from: &chrono::NaiveDate,
|
|
||||||
to: &chrono::NaiveDate,
|
|
||||||
department: Option<i32>,
|
|
||||||
) -> Result<Vec<RpcSubstitution>> {
|
|
||||||
let resp: RpcResponse<Vec<RpcSubstitution>> = reqwest::Client::new()
|
|
||||||
.get(self.rpc_url.as_str())
|
|
||||||
.header(reqwest::header::CONTENT_TYPE, "application/json")
|
|
||||||
.header(reqwest::header::ACCEPT, "application/json-rpc")
|
|
||||||
.header(
|
|
||||||
reqwest::header::COOKIE,
|
|
||||||
self.session.as_ref().context("Not logged in")?,
|
|
||||||
)
|
|
||||||
.json(&json!({
|
|
||||||
"id": "ID",
|
|
||||||
"method": "getSubstitutions",
|
|
||||||
"params": {
|
|
||||||
"startDate": serialize_date(*from),
|
|
||||||
"endDate": serialize_date(*to),
|
|
||||||
"departmentId": department.map_or(0, |d| d)
|
|
||||||
},
|
|
||||||
"jsonrpc": "2.0"
|
|
||||||
}))
|
|
||||||
.send()
|
|
||||||
.await?
|
|
||||||
.json()
|
|
||||||
.await?;
|
|
||||||
if let Some(e) = resp.error {
|
|
||||||
bail!("RPC error: {:?}", e);
|
|
||||||
}
|
|
||||||
|
|
||||||
if let Some(x) = resp.result {
|
|
||||||
Ok(x)
|
|
||||||
} else {
|
|
||||||
bail!("RPC result is null");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn construct_bearer(auth: &str) -> String {
|
|
||||||
format!("Bearer {}", auth)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub async fn current_tenant(&self) -> Result<ApiTenant> {
|
|
||||||
let resp: ApiDataResponse = reqwest::Client::new()
|
|
||||||
.get(self.api_url.join("rest/view/v1/app/data")?)
|
|
||||||
.header(reqwest::header::ACCEPT, "application/json")
|
|
||||||
.header(
|
|
||||||
reqwest::header::COOKIE,
|
|
||||||
self.session.as_ref().context("Not logged in")?,
|
|
||||||
)
|
|
||||||
.header(
|
|
||||||
reqwest::header::AUTHORIZATION,
|
|
||||||
Client::construct_bearer(&self.authorization.as_ref().context("Not logged in")?),
|
|
||||||
)
|
|
||||||
.send()
|
|
||||||
.await?
|
|
||||||
.json()
|
|
||||||
.await?;
|
|
||||||
|
|
||||||
Ok(resp.tenant)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub async fn teachers(&self) -> Result<Vec<ApiTeacher>> {
|
|
||||||
let mut url = self.api_url.join("public/timetable/weekly/pageconfig")?;
|
|
||||||
url.query_pairs_mut().append_pair("type", "2");
|
|
||||||
let resp: ApiTeachersResponse = reqwest::Client::new()
|
|
||||||
.get(url)
|
|
||||||
.header(reqwest::header::ACCEPT, "application/json")
|
|
||||||
.header(
|
|
||||||
reqwest::header::COOKIE,
|
|
||||||
self.session.as_ref().context("Not logged in")?,
|
|
||||||
)
|
|
||||||
.header(
|
|
||||||
reqwest::header::AUTHORIZATION,
|
|
||||||
Client::construct_bearer(&self.authorization.as_ref().context("Not logged in")?),
|
|
||||||
)
|
|
||||||
.send()
|
|
||||||
.await?
|
|
||||||
.json()
|
|
||||||
.await?;
|
|
||||||
|
|
||||||
Ok(resp.data.elements)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub async fn exams(&self) -> Result<()> {
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -3,11 +3,8 @@ use celery::error::TaskError;
|
||||||
use celery::task::TaskResult;
|
use celery::task::TaskResult;
|
||||||
use chrono::prelude::*;
|
use chrono::prelude::*;
|
||||||
use diesel::prelude::*;
|
use diesel::prelude::*;
|
||||||
use now::DateTimeNow;
|
|
||||||
|
|
||||||
use crate::config;
|
use crate::{config, db};
|
||||||
use crate::db;
|
|
||||||
use crate::untis;
|
|
||||||
|
|
||||||
async fn fetch_schoolyears(client: &untis::Client, conn: &mut PgConnection) -> Result<i32> {
|
async fn fetch_schoolyears(client: &untis::Client, conn: &mut PgConnection) -> Result<i32> {
|
||||||
let existing_schoolyears = db::schema::schoolyears::table
|
let existing_schoolyears = db::schema::schoolyears::table
|
||||||
|
@ -272,42 +269,140 @@ async fn fetch_substitutions(
|
||||||
conn: &mut PgConnection,
|
conn: &mut PgConnection,
|
||||||
schoolyear_id: i32,
|
schoolyear_id: i32,
|
||||||
) -> Result<()> {
|
) -> Result<()> {
|
||||||
let now = Utc::now();
|
let today = Utc::now().date_naive();
|
||||||
let beginning_of_week = now.beginning_of_week().date_naive();
|
if diesel::select(diesel::dsl::not(diesel::expression::exists::exists(
|
||||||
let end_of_week = now.end_of_week().date_naive();
|
db::schema::substitution_queries::table
|
||||||
// if diesel::select(diesel::dsl::not(diesel::expression::exists::exists(
|
.filter(db::schema::substitution_queries::date.eq(today)),
|
||||||
// db::schema::substitution_queries::table
|
)))
|
||||||
// .filter(db::schema::substitution_queries::active)
|
.get_result::<bool>(conn)?
|
||||||
// .filter(db::schema::substitution_queries::end_date.eq(end_of_week)),
|
{
|
||||||
// )))
|
diesel::insert_into(db::schema::substitution_queries::table)
|
||||||
// .get_result::<bool>(conn)?
|
.values(db::models::NewSubstitutionQuery {
|
||||||
// {}
|
schoolyear_id,
|
||||||
|
date: today,
|
||||||
|
active: true,
|
||||||
|
})
|
||||||
|
.execute(conn)?;
|
||||||
|
}
|
||||||
|
|
||||||
// for query in db::schema::substitution_queries::table
|
for query in db::schema::substitution_queries::table
|
||||||
// .filter(db::schema::substitution_queries::active)
|
.filter(db::schema::substitution_queries::active)
|
||||||
// .load::<db::models::SubstitutionQuery>(conn)?
|
.load::<db::models::SubstitutionQuery>(conn)?
|
||||||
// {
|
{
|
||||||
// dbg!(&query);
|
let now = Utc::now().naive_utc();
|
||||||
// }
|
let substs = client.substitutions(&query.date, &query.date, None).await?;
|
||||||
|
for substitution in substs {
|
||||||
|
let substitution_id = diesel::insert_into(db::schema::substitutions::table)
|
||||||
|
.values(db::models::NewSubstitution {
|
||||||
|
substitution_query_id: query.id,
|
||||||
|
subst_type: substitution.subst_type.into(),
|
||||||
|
lesson_id: substitution.lesson_id,
|
||||||
|
start_time: substitution.start_time,
|
||||||
|
end_time: substitution.end_time,
|
||||||
|
text: substitution.text.as_deref(),
|
||||||
|
})
|
||||||
|
.returning(db::schema::substitutions::id)
|
||||||
|
.get_result::<i32>(conn)?;
|
||||||
|
|
||||||
|
diesel::insert_into(db::schema::substitution_classes::table)
|
||||||
|
.values(
|
||||||
|
&substitution
|
||||||
|
.classes
|
||||||
|
.iter()
|
||||||
|
.enumerate()
|
||||||
|
.map(|(i, c)| {
|
||||||
|
Ok(db::models::NewSubstitutionClass {
|
||||||
|
substitution_id,
|
||||||
|
position: i as i16,
|
||||||
|
class_id: db::schema::classes::table
|
||||||
|
.filter(db::schema::classes::untis_id.eq(c.id))
|
||||||
|
.select(db::schema::classes::id)
|
||||||
|
.get_result::<i32>(conn)?,
|
||||||
|
})
|
||||||
|
})
|
||||||
|
.collect::<Result<Vec<db::models::NewSubstitutionClass>>>()?,
|
||||||
|
)
|
||||||
|
.execute(conn)?;
|
||||||
|
diesel::insert_into(db::schema::substitution_teachers::table)
|
||||||
|
.values(
|
||||||
|
&substitution
|
||||||
|
.teachers
|
||||||
|
.iter()
|
||||||
|
.enumerate()
|
||||||
|
.map(|(i, t)| {
|
||||||
|
Ok(db::models::NewSubstitutionTeacher {
|
||||||
|
substitution_id,
|
||||||
|
position: i as i16,
|
||||||
|
teacher_id: if t.id == 0 {
|
||||||
|
None
|
||||||
|
} else {
|
||||||
|
Some(
|
||||||
|
db::schema::teachers::table
|
||||||
|
.filter(db::schema::teachers::untis_id.eq(t.id))
|
||||||
|
.select(db::schema::teachers::id)
|
||||||
|
.get_result::<i32>(conn)?,
|
||||||
|
)
|
||||||
|
},
|
||||||
|
})
|
||||||
|
})
|
||||||
|
.collect::<Result<Vec<db::models::NewSubstitutionTeacher>>>()?,
|
||||||
|
)
|
||||||
|
.execute(conn)?;
|
||||||
|
diesel::insert_into(db::schema::substitution_subjects::table)
|
||||||
|
.values(
|
||||||
|
&substitution
|
||||||
|
.subjects
|
||||||
|
.iter()
|
||||||
|
.enumerate()
|
||||||
|
.map(|(i, s)| {
|
||||||
|
Ok(db::models::NewSubstitutionSubject {
|
||||||
|
substitution_id,
|
||||||
|
position: i as i16,
|
||||||
|
subject_id: db::schema::subjects::table
|
||||||
|
.filter(db::schema::subjects::untis_id.eq(s.id))
|
||||||
|
.select(db::schema::subjects::id)
|
||||||
|
.get_result::<i32>(conn)?,
|
||||||
|
})
|
||||||
|
})
|
||||||
|
.collect::<Result<Vec<db::models::NewSubstitutionSubject>>>()?,
|
||||||
|
)
|
||||||
|
.execute(conn)?;
|
||||||
|
diesel::insert_into(db::schema::substitution_rooms::table)
|
||||||
|
.values(
|
||||||
|
&substitution
|
||||||
|
.rooms
|
||||||
|
.iter()
|
||||||
|
.enumerate()
|
||||||
|
.map(|(i, r)| {
|
||||||
|
Ok(db::models::NewSubstitutionRoom {
|
||||||
|
substitution_id,
|
||||||
|
position: i as i16,
|
||||||
|
room_id: if r.id == 0 {
|
||||||
|
None
|
||||||
|
} else {
|
||||||
|
Some(
|
||||||
|
db::schema::rooms::table
|
||||||
|
.filter(db::schema::rooms::untis_id.eq(r.id))
|
||||||
|
.select(db::schema::rooms::id)
|
||||||
|
.get_result::<i32>(conn)?,
|
||||||
|
)
|
||||||
|
},
|
||||||
|
})
|
||||||
|
})
|
||||||
|
.collect::<Result<Vec<db::models::NewSubstitutionRoom>>>()?,
|
||||||
|
)
|
||||||
|
.execute(conn)?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
#[celery::task]
|
#[celery::task]
|
||||||
pub async fn update_info() -> TaskResult<()> {
|
pub async fn update_info() -> TaskResult<()> {
|
||||||
let mut client = untis::Client {
|
let mut client = match config::untis_from_env() {
|
||||||
api_url: match url::Url::parse(&config::CONFIG.untis_api_url) {
|
|
||||||
Ok(x) => x,
|
Ok(x) => x,
|
||||||
Err(e) => return Err(TaskError::UnexpectedError(e.to_string())),
|
Err(e) => return Err(TaskError::UnexpectedError(e.to_string())),
|
||||||
},
|
|
||||||
rpc_url: match url::Url::parse(&config::CONFIG.untis_rpc_url) {
|
|
||||||
Ok(x) => untis::Client::gen_rpc_url(x, &config::CONFIG.untis_school),
|
|
||||||
Err(e) => return Err(TaskError::UnexpectedError(e.to_string())),
|
|
||||||
},
|
|
||||||
username: config::CONFIG.untis_username.to_owned(),
|
|
||||||
password: config::CONFIG.untis_password.to_owned(),
|
|
||||||
session: None,
|
|
||||||
authorization: None,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
if let Err(e) = client.login().await {
|
if let Err(e) = client.login().await {
|
||||||
|
|
|
@ -1,5 +1,25 @@
|
||||||
version: "3"
|
version: "3"
|
||||||
|
|
||||||
|
x-backend:
|
||||||
|
&backend
|
||||||
|
image: git.dergrimm.net/dergrimm/bvplan_backend:latest
|
||||||
|
build:
|
||||||
|
context: ./backend
|
||||||
|
restart: always
|
||||||
|
command: worker
|
||||||
|
depends_on:
|
||||||
|
- postgres
|
||||||
|
- rabbitmq
|
||||||
|
environment:
|
||||||
|
BACKEND_DB_URL: postgres://${POSTGRES_USER}:${POSTGRES_PASSWORD}@postgres:5432/${POSTGRES_USER}
|
||||||
|
BACKEND_AMQP_URL: amqp://${RABBITMQ_USER}:${RABBITMQ_PASSWORD}@rabbitmq:5672
|
||||||
|
BACKEND_UNTIS_API_URL: ${BACKEND_UNTIS_API_URL}
|
||||||
|
BACKEND_UNTIS_RPC_URL: ${BACKEND_UNTIS_RPC_URL}
|
||||||
|
BACKEND_UNTIS_CLIENT_NAME: ${BACKEND_UNTIS_CLIENT_NAME}
|
||||||
|
BACKEND_UNTIS_SCHOOL: ${BACKEND_UNTIS_SCHOOL}
|
||||||
|
BACKEND_UNTIS_USERNAME: ${BACKEND_UNTIS_USERNAME}
|
||||||
|
BACKEND_UNTIS_PASSWORD: ${BACKEND_UNTIS_PASSWORD}
|
||||||
|
|
||||||
services:
|
services:
|
||||||
nginx:
|
nginx:
|
||||||
image: docker.io/byjg/nginx-extras
|
image: docker.io/byjg/nginx-extras
|
||||||
|
@ -38,41 +58,12 @@ services:
|
||||||
- rabbitmq:/var/lib/rabbitmq
|
- rabbitmq:/var/lib/rabbitmq
|
||||||
|
|
||||||
worker:
|
worker:
|
||||||
image: git.dergrimm.net/dergrimm/bvplan_backend:latest
|
<<: *backend
|
||||||
build:
|
|
||||||
context: ./backend
|
|
||||||
restart: always
|
|
||||||
command: worker
|
command: worker
|
||||||
depends_on:
|
|
||||||
- postgres
|
|
||||||
- rabbitmq
|
|
||||||
environment:
|
|
||||||
BACKEND_DB_URL: postgres://${POSTGRES_USER}:${POSTGRES_PASSWORD}@postgres:5432/${POSTGRES_USER}
|
|
||||||
BACKEND_AMQP_URL: amqp://${RABBITMQ_USER}:${RABBITMQ_PASSWORD}@rabbitmq:5672
|
|
||||||
BACKEND_UNTIS_API_URL: ${BACKEND_UNTIS_API_URL}
|
|
||||||
BACKEND_UNTIS_RPC_URL: ${BACKEND_UNTIS_RPC_URL}
|
|
||||||
BACKEND_UNTIS_SCHOOL: ${BACKEND_UNTIS_SCHOOL}
|
|
||||||
BACKEND_UNTIS_USERNAME: ${BACKEND_UNTIS_USERNAME}
|
|
||||||
BACKEND_UNTIS_PASSWORD: ${BACKEND_UNTIS_PASSWORD}
|
|
||||||
|
|
||||||
api:
|
api:
|
||||||
image: git.dergrimm.net/dergrimm/bvplan_backend:latest
|
<<: *backend
|
||||||
build:
|
|
||||||
context: ./backend
|
|
||||||
restart: always
|
|
||||||
command: api
|
command: api
|
||||||
depends_on:
|
|
||||||
- postgres
|
|
||||||
- rabbitmq
|
|
||||||
- worker
|
|
||||||
environment:
|
|
||||||
BACKEND_DB_URL: postgres://${POSTGRES_USER}:${POSTGRES_PASSWORD}@postgres:5432/${POSTGRES_USER}
|
|
||||||
BACKEND_AMQP_URL: amqp://${RABBITMQ_USER}:${RABBITMQ_PASSWORD}@rabbitmq:5672
|
|
||||||
BACKEND_UNTIS_API_URL: ${BACKEND_UNTIS_API_URL}
|
|
||||||
BACKEND_UNTIS_RPC_URL: ${BACKEND_UNTIS_RPC_URL}
|
|
||||||
BACKEND_UNTIS_SCHOOL: ${BACKEND_UNTIS_SCHOOL}
|
|
||||||
BACKEND_UNTIS_USERNAME: ${BACKEND_UNTIS_USERNAME}
|
|
||||||
BACKEND_UNTIS_PASSWORD: ${BACKEND_UNTIS_PASSWORD}
|
|
||||||
|
|
||||||
volumes:
|
volumes:
|
||||||
postgres:
|
postgres:
|
||||||
|
|
Loading…
Reference in a new issue