425 lines
14 KiB
Rust
Executable File
425 lines
14 KiB
Rust
Executable File
// GCG.MeinCantor.API - The server-part of GCG.MeinCantor - The school application for the Georg-Cantor-Gymnasium
|
|
// Copyright (C) 2021-2022 Denys Konovalov
|
|
|
|
// This program is free software: you can redistribute it and/or modify
|
|
// it under the terms of the GNU Affero General Public License as published
|
|
// by the Free Software Foundation, either version 3 of the License, or
|
|
// any later version.
|
|
|
|
// This program is distributed in the hope that it will be useful,
|
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
// GNU Affero General Public License for more details.
|
|
|
|
// You should have received a copy of the GNU Affero General Public License
|
|
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
|
|
use crate::config;
|
|
use quickxml_to_serde::{xml_string_to_json, Config};
|
|
use serde_derive::{Deserialize, Serialize};
|
|
use serde_json::{json, Map};
|
|
use std::env;
|
|
|
|
#[derive(Serialize, Deserialize, Clone)]
|
|
pub struct Timetable {
|
|
pub date: String,
|
|
pub updated: String,
|
|
pub class: String,
|
|
pub timetable_data: serde_json::Value,
|
|
}
|
|
|
|
#[derive(Serialize, Deserialize)]
|
|
pub struct TimetableData {
|
|
pub count: usize,
|
|
pub courses: Vec<rocket::serde::json::Value>,
|
|
pub info: String,
|
|
}
|
|
|
|
#[derive(Serialize, Deserialize, Debug)]
|
|
pub struct Lesson {
|
|
pub subject: String,
|
|
pub teacher: String,
|
|
pub id: i64,
|
|
}
|
|
|
|
async fn get_timetable_xml(url: &str) -> serde_json::value::Value {
|
|
let resp = reqwest::Client::new()
|
|
.get(format!(
|
|
"{}/{}",
|
|
env::var("IW_TIMETABLE").unwrap_or_else(|_| config::IW_TIMETABLE.to_string()),
|
|
url
|
|
))
|
|
.basic_auth(
|
|
env::var("IW_TIMETABLE_USER").unwrap_or_else(|_| config::IW_TIMETABLE_USER.to_string()),
|
|
Some(
|
|
env::var("IW_TIMETABLE_PASSWORD")
|
|
.unwrap_or_else(|_| config::IW_TIMETABLE_PASSWORD.to_string()),
|
|
),
|
|
)
|
|
.send()
|
|
.await
|
|
.unwrap()
|
|
.text()
|
|
.await
|
|
.unwrap();
|
|
xml_string_to_json(resp, &Config::new_with_defaults()).unwrap()
|
|
}
|
|
|
|
async fn get_timetable_xml_data(url: &str) -> Vec<serde_json::value::Value> {
|
|
get_timetable_xml(url)
|
|
.await
|
|
.as_object()
|
|
.unwrap()
|
|
.get("VpMobil")
|
|
.unwrap()
|
|
.get("Klassen")
|
|
.unwrap()
|
|
.get("Kl")
|
|
.unwrap()
|
|
.as_array()
|
|
.unwrap()
|
|
.to_owned()
|
|
}
|
|
|
|
pub async fn get_timetable(url: String) -> Vec<Timetable> {
|
|
let xml = get_timetable_xml(&url).await;
|
|
let classes = get_timetable_xml_data(&url).await;
|
|
let mut timetable: Vec<Timetable> = Vec::new();
|
|
//dbg!(&classes);
|
|
for i in classes.iter() {
|
|
let mut courses: Vec<rocket::serde::json::Value> = Vec::new();
|
|
let nothing = json!([""]);
|
|
let std = i
|
|
.as_object()
|
|
.unwrap()
|
|
.get("Pl")
|
|
.unwrap()
|
|
.as_object()
|
|
.unwrap()
|
|
.get("Std")
|
|
.unwrap_or(¬hing);
|
|
let mut plan = vec![];
|
|
if std.is_array() {
|
|
plan.extend(std.as_array().unwrap().iter().cloned())
|
|
} else if std.is_object() {
|
|
plan.push(std.clone())
|
|
}
|
|
for x in &plan {
|
|
if x.as_object() != None {
|
|
courses.push(x.to_owned());
|
|
} else {
|
|
dbg!("Failed to decode plan: {:?}", &i);
|
|
}
|
|
}
|
|
let empty_list = serde_json::Value::Array(vec![]);
|
|
let empty_obj = serde_json::Value::Object(Map::new());
|
|
let empty_vec = Vec::new();
|
|
let info_value = xml
|
|
.as_object()
|
|
.unwrap()
|
|
.get("VpMobil")
|
|
.unwrap()
|
|
.get("ZusatzInfo")
|
|
.unwrap_or(&empty_obj)
|
|
.get("ZiZeile")
|
|
.unwrap_or(&empty_list);
|
|
let mut info = String::new();
|
|
if info_value.is_array() {
|
|
for item in info_value.as_array().unwrap_or(&empty_vec) {
|
|
info.push_str(item.as_str().unwrap_or("\r\n"));
|
|
info.push_str("\r\n");
|
|
}
|
|
} else if info_value.is_string() {
|
|
info.push_str(info_value.as_str().unwrap_or(""));
|
|
}
|
|
let header = xml
|
|
.as_object()
|
|
.unwrap()
|
|
.get("VpMobil")
|
|
.unwrap()
|
|
.get("Kopf")
|
|
.unwrap();
|
|
let timetable_element = Timetable {
|
|
date: String::from(header.get("DatumPlan").unwrap().as_str().unwrap()),
|
|
updated: String::from(header.get("zeitstempel").unwrap().as_str().unwrap()),
|
|
class: String::from(
|
|
i.as_object()
|
|
.unwrap()
|
|
.get("Kurz")
|
|
.unwrap()
|
|
.as_str()
|
|
.unwrap(),
|
|
),
|
|
timetable_data: json!(TimetableData {
|
|
count: plan.len(),
|
|
courses,
|
|
info,
|
|
}),
|
|
};
|
|
timetable.push(timetable_element)
|
|
}
|
|
let normal_classes: Vec<Timetable> = timetable
|
|
.iter()
|
|
.cloned()
|
|
.filter(|e| !e.class.contains("11") && !e.class.contains("12"))
|
|
.collect();
|
|
let mut timetable_refactored: Vec<Timetable> = Vec::new();
|
|
timetable_refactored.extend(normal_classes);
|
|
for year in ["11", "12"] {
|
|
let class: Vec<Timetable> = timetable
|
|
.iter()
|
|
.cloned()
|
|
.filter(|e| e.class.contains(year))
|
|
.collect();
|
|
let timetable_data = {
|
|
let mut courses: Vec<serde_json::Value> = Vec::new();
|
|
let mut info: String = String::new();
|
|
for i in class {
|
|
let td_obj = i.timetable_data.as_object().unwrap();
|
|
courses.extend(
|
|
td_obj
|
|
.get("courses")
|
|
.unwrap()
|
|
.as_array()
|
|
.unwrap()
|
|
.to_owned(),
|
|
);
|
|
info = td_obj.get("info").unwrap().as_str().unwrap().to_owned();
|
|
}
|
|
courses.sort_by(|a, b| {
|
|
fn st_nr(obj: &serde_json::Value) -> i64 {
|
|
obj.as_object()
|
|
.unwrap()
|
|
.get("St")
|
|
.unwrap_or(&json!([""]))
|
|
.as_i64()
|
|
.unwrap_or_default()
|
|
}
|
|
fn fa_abc(obj: &serde_json::Value) -> String {
|
|
obj.as_object()
|
|
.unwrap()
|
|
.get("Fa")
|
|
.unwrap_or(&json!([""]))
|
|
.as_str()
|
|
.unwrap_or_default()
|
|
.to_owned()
|
|
}
|
|
if st_nr(a) == st_nr(b) {
|
|
fa_abc(a).cmp(&fa_abc(b))
|
|
} else {
|
|
st_nr(a).cmp(&st_nr(b))
|
|
}
|
|
});
|
|
TimetableData {
|
|
count: courses.len(),
|
|
courses,
|
|
info,
|
|
}
|
|
};
|
|
let header = xml
|
|
.as_object()
|
|
.unwrap()
|
|
.get("VpMobil")
|
|
.unwrap()
|
|
.get("Kopf")
|
|
.unwrap();
|
|
let timetable = Timetable {
|
|
date: String::from(header.get("DatumPlan").unwrap().as_str().unwrap()),
|
|
updated: String::from(header.get("zeitstempel").unwrap().as_str().unwrap()),
|
|
class: year.to_string(),
|
|
timetable_data: json!(timetable_data),
|
|
};
|
|
timetable_refactored.push(timetable);
|
|
}
|
|
timetable_refactored
|
|
}
|
|
|
|
pub async fn get_class_timetable(class: String, url: String) -> TimetableData {
|
|
let xml = get_timetable_xml(&url).await;
|
|
let classes = get_timetable_xml_data(&url).await;
|
|
let courses: Vec<rocket::serde::json::Value> = Vec::new();
|
|
let empty_list = serde_json::Value::Array(vec![]);
|
|
let empty_obj = serde_json::Value::Object(Map::new());
|
|
let empty_vec = Vec::new();
|
|
let info_value = xml
|
|
.as_object()
|
|
.unwrap()
|
|
.get("VpMobil")
|
|
.unwrap()
|
|
.get("ZusatzInfo")
|
|
.unwrap_or(&empty_obj)
|
|
.get("ZiZeile")
|
|
.unwrap_or(&empty_list);
|
|
let mut info = String::new();
|
|
if info_value.is_array() {
|
|
for item in info_value.as_array().unwrap_or(&empty_vec) {
|
|
info.push_str(item.as_str().unwrap_or("\r\n"));
|
|
info.push_str("\r\n");
|
|
}
|
|
} else if info_value.is_string() {
|
|
info.push_str(info_value.as_str().unwrap_or(""));
|
|
}
|
|
let mut response = TimetableData {
|
|
count: 0,
|
|
courses,
|
|
info,
|
|
};
|
|
for i in classes.iter() {
|
|
let current_class = i
|
|
.as_object()
|
|
.unwrap()
|
|
.get("Kurz")
|
|
.unwrap()
|
|
.as_str()
|
|
.unwrap()
|
|
.replace('/', "_");
|
|
let contains_class = current_class.contains(&class);
|
|
if class == current_class {
|
|
let nothing = json!([""]);
|
|
let std = i
|
|
.as_object()
|
|
.unwrap()
|
|
.get("Pl")
|
|
.unwrap()
|
|
.as_object()
|
|
.unwrap()
|
|
.get("Std")
|
|
.unwrap_or(¬hing);
|
|
let mut plan = vec![];
|
|
if std.is_array() {
|
|
plan.extend(std.as_array().unwrap().iter().cloned())
|
|
} else if std.is_object() {
|
|
plan.push(std.clone())
|
|
}
|
|
response.count = plan.len();
|
|
for i in plan {
|
|
if i.as_object() != None {
|
|
response.courses.push(i.to_owned());
|
|
} else {
|
|
dbg!("Failed: {:?}", &i);
|
|
}
|
|
}
|
|
break;
|
|
} else if (class == *"11" || class == *"12") && contains_class {
|
|
let nothing = json!([""]);
|
|
let std = i
|
|
.as_object()
|
|
.unwrap()
|
|
.get("Pl")
|
|
.unwrap()
|
|
.as_object()
|
|
.unwrap()
|
|
.get("Std")
|
|
.unwrap_or(¬hing);
|
|
let mut plan = vec![];
|
|
if std.is_array() {
|
|
plan.extend(std.as_array().unwrap().iter().cloned())
|
|
} else if std.is_object() {
|
|
plan.push(std.clone())
|
|
}
|
|
for i in plan {
|
|
if i.as_object() != None {
|
|
response.courses.push(i.to_owned());
|
|
} else {
|
|
dbg!("Failed: {:?}", &i);
|
|
}
|
|
}
|
|
response.courses.sort_by(|a, b| {
|
|
fn st_nr(obj: &serde_json::Value) -> i64 {
|
|
obj.as_object()
|
|
.unwrap()
|
|
.get("St")
|
|
.unwrap_or(&json!([""]))
|
|
.as_i64()
|
|
.unwrap_or_default()
|
|
}
|
|
fn fa_abc(obj: &serde_json::Value) -> String {
|
|
obj.as_object()
|
|
.unwrap()
|
|
.get("Fa")
|
|
.unwrap_or(&json!([""]))
|
|
.as_str()
|
|
.unwrap_or_default()
|
|
.to_owned()
|
|
}
|
|
if st_nr(a) == st_nr(b) {
|
|
fa_abc(a).cmp(&fa_abc(b))
|
|
} else {
|
|
st_nr(a).cmp(&st_nr(b))
|
|
}
|
|
});
|
|
response.count = response.courses.len();
|
|
}
|
|
}
|
|
response
|
|
}
|
|
|
|
pub async fn get_classes() -> Vec<String> {
|
|
let classes = get_timetable_xml_data(&String::from("Klassen.xml")).await;
|
|
let mut class_list: Vec<String> = Vec::new();
|
|
for i in classes.iter() {
|
|
let current_class = i
|
|
.as_object()
|
|
.unwrap()
|
|
.get("Kurz")
|
|
.unwrap()
|
|
.as_str()
|
|
.unwrap()
|
|
.to_string();
|
|
if current_class.contains("11") && !class_list.contains(&"11".to_string()) {
|
|
class_list.push("11".to_string());
|
|
} else if current_class.contains("12") && !class_list.contains(&"12".to_string()) {
|
|
class_list.push("12".to_string());
|
|
} else if !current_class.contains("11") && !current_class.contains("12") {
|
|
class_list.push(current_class);
|
|
}
|
|
}
|
|
class_list
|
|
}
|
|
|
|
pub async fn get_class_lessons(class: String) -> Vec<Lesson> {
|
|
let classes = get_timetable_xml_data(&String::from("Klassen.xml")).await;
|
|
let mut lesson_list: Vec<Lesson> = Vec::new();
|
|
for i in classes.iter() {
|
|
let empty_list = serde_json::Value::Array(Vec::new());
|
|
let current_class = i
|
|
.as_object()
|
|
.unwrap()
|
|
.get("Kurz")
|
|
.unwrap()
|
|
.as_str()
|
|
.unwrap()
|
|
.replace('/', "_");
|
|
let contains_class = current_class.contains(&class);
|
|
if (class == current_class) || ((class == *"11" || class == *"12") && contains_class) {
|
|
let class_lessons = i
|
|
.as_object()
|
|
.unwrap()
|
|
.get("Unterricht")
|
|
.unwrap()
|
|
.as_object()
|
|
.unwrap()
|
|
.get("Ue")
|
|
.unwrap_or(&empty_list)
|
|
.as_array()
|
|
.unwrap();
|
|
for lesson in class_lessons.iter() {
|
|
let lesson = lesson
|
|
.as_object()
|
|
.unwrap()
|
|
.get("UeNr")
|
|
.unwrap()
|
|
.as_object()
|
|
.unwrap();
|
|
lesson_list.push(Lesson {
|
|
subject: lesson.get("@UeFa").unwrap().as_str().unwrap().to_string(),
|
|
teacher: lesson.get("@UeLe").unwrap().as_str().unwrap().to_string(),
|
|
id: lesson.get("#text").unwrap().as_i64().unwrap(),
|
|
})
|
|
}
|
|
}
|
|
}
|
|
lesson_list.sort_by(|a, b| a.subject.cmp(&b.subject));
|
|
lesson_list
|
|
}
|