// 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 . 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, 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 { 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 { let xml = get_timetable_xml(&url).await; let classes = get_timetable_xml_data(&url).await; let mut timetable: Vec = Vec::new(); //dbg!(&classes); for i in classes.iter() { let mut courses: Vec = 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 .iter() .cloned() .filter(|e| !e.class.contains("11") && !e.class.contains("12")) .collect(); let mut timetable_refactored: Vec = Vec::new(); timetable_refactored.extend(normal_classes); for year in ["11", "12"] { let class: Vec = timetable .iter() .cloned() .filter(|e| e.class.contains(year)) .collect(); let timetable_data = { let mut courses: Vec = 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 = 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 { let classes = get_timetable_xml_data(&String::from("Klassen.xml")).await; let mut class_list: Vec = 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 { let classes = get_timetable_xml_data(&String::from("Klassen.xml")).await; let mut lesson_list: Vec = 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 }