This repository has been archived on 2023-06-26. You can view files and clone it, but cannot push or open issues or pull requests.
Files
meincantor-api/src/indiware_connector.rs

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(&nothing);
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(&nothing);
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(&nothing);
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
}