diff --git a/Cargo.toml b/Cargo.toml index 79ce6c4..af4b11a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -11,12 +11,11 @@ authors = ["Denys Konovalov "] rocket = { version = "0.5.0-rc.1", features = ["json"] } serde = "1.0" diesel = { version = "1.4", features = ["postgres", "serde_json"] } -reqwest = "0.11" +reqwest = { version="0.11", features = ["json"] } quickxml_to_serde = "0.4" serde_json = "1.0" jsonwebtoken = "7.2" time = "0.2" -keycloak = "14" chrono = "0.4" diff --git a/src/indiware_connector.rs b/src/indiware_connector.rs index 7704150..ce59065 100644 --- a/src/indiware_connector.rs +++ b/src/indiware_connector.rs @@ -1,7 +1,6 @@ use crate::config; use crate::schema::timetable; use crate::DbConn; -use chrono::{Duration, Local}; use diesel::{Insertable, Queryable}; use quickxml_to_serde::{xml_string_to_json, Config}; use serde_derive::{Deserialize, Serialize}; @@ -30,14 +29,11 @@ pub struct Lesson { pub id: i64, } -async fn get_today_timetable_xml() -> serde_json::value::Value { +async fn get_timetable_xml(url: &str) -> serde_json::value::Value { + dbg!(format!("{}/{}", config::TIMETABLE_URL, url)); let client = reqwest::Client::new(); let resp = client - .get(format!( - "{}/PlanKl{}.xml", - config::TIMETABLE_URL, - Local::today().format("%Y%m%d") - )) + .get(format!("{}/{}", config::TIMETABLE_URL, url)) .basic_auth(config::TIMETABLE_USER, config::TIMETABLE_PASSWORD) .send() .await @@ -45,43 +41,12 @@ async fn get_today_timetable_xml() -> serde_json::value::Value { .text() .await .unwrap(); + dbg!(&resp); xml_string_to_json(resp, &Config::new_with_defaults()).unwrap() } -async fn get_tomorrow_timetable_xml() -> serde_json::value::Value { - let client = reqwest::Client::new(); - let resp = client - .get(format!( - "{}/PlanKl{}.xml", - config::TIMETABLE_URL, - (Local::today() + Duration::days(1)).format("%Y%m%d") - )) - .basic_auth(config::TIMETABLE_USER, config::TIMETABLE_PASSWORD) - .send() - .await - .unwrap() - .text() - .await - .unwrap(); - xml_string_to_json(resp, &Config::new_with_defaults()).unwrap() -} - -async fn get_timetable_xml() -> serde_json::value::Value { - let client = reqwest::Client::new(); - let resp = client - .get(format!("{}/Klassen.xml", config::TIMETABLE_URL)) - .basic_auth(config::TIMETABLE_USER, config::TIMETABLE_PASSWORD) - .send() - .await - .unwrap() - .text() - .await - .unwrap(); - xml_string_to_json(resp, &Config::new_with_defaults()).unwrap() -} - -async fn get_today_timetable_xml_data() -> Vec { - let xml = get_today_timetable_xml().await; +async fn get_timetable_xml_data(url: &str) -> Vec { + let xml = get_timetable_xml(url).await; let classes = xml .as_object() .unwrap() @@ -96,41 +61,9 @@ async fn get_today_timetable_xml_data() -> Vec { classes.to_owned() } -async fn get_tomorrow_timetable_xml_data() -> Vec { - let xml = get_tomorrow_timetable_xml().await; - let classes = xml - .as_object() - .unwrap() - .get("VpMobil") - .unwrap() - .get("Klassen") - .unwrap() - .get("Kl") - .unwrap() - .as_array() - .unwrap(); - classes.to_owned() -} - -async fn get_timetable_xml_data() -> Vec { - let xml = get_timetable_xml().await; - let classes = xml - .as_object() - .unwrap() - .get("VpMobil") - .unwrap() - .get("Klassen") - .unwrap() - .get("Kl") - .unwrap() - .as_array() - .unwrap(); - classes.to_owned() -} - -pub async fn get_today_timetable(_conn: DbConn) -> Vec { - let xml = get_today_timetable_xml().await; - let classes = get_today_timetable_xml_data().await; +pub async fn get_timetable(_conn: DbConn, url: String) -> Vec { + let xml = get_timetable_xml(&url).await; + let classes = get_timetable_xml_data(&url).await; let mut timetable: Vec = Vec::new(); for i in classes.iter() { let mut courses: Vec = Vec::new(); @@ -221,331 +154,9 @@ pub async fn get_today_timetable(_conn: DbConn) -> Vec { timetable } -pub async fn get_tomorrow_timetable(_conn: DbConn) -> Vec { - let xml = get_tomorrow_timetable_xml().await; - let classes = get_tomorrow_timetable_xml_data().await; - let mut timetable: Vec = Vec::new(); - 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 i in &plan { - if i.as_object() != None { - courses.push(i.to_owned()); - } else { - dbg!("Failed: {:?}", &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_list = xml - .as_object() - .unwrap() - .get("VpMobil") - .unwrap() - .get("ZusatzInfo") - .unwrap_or(&empty_obj) - .get("ZiZeile") - .unwrap_or(&empty_list) - .as_array() - .unwrap_or(&empty_vec); - let mut info = String::new(); - for item in info_list { - info.push_str(item.as_str().unwrap_or("\r\n")); - info.push_str("\r\n"); - } - let response = TimetableData { - count: plan.len(), - courses, - info, - }; - let timetable_element = Timetable { - date: String::from( - xml.as_object() - .unwrap() - .get("VpMobil") - .unwrap() - .get("Kopf") - .unwrap() - .get("DatumPlan") - .unwrap() - .as_str() - .unwrap(), - ), - updated: String::from( - xml.as_object() - .unwrap() - .get("VpMobil") - .unwrap() - .get("Kopf") - .unwrap() - .get("zeitstempel") - .unwrap() - .as_str() - .unwrap(), - ), - class: String::from( - i.as_object() - .unwrap() - .get("Kurz") - .unwrap() - .as_str() - .unwrap(), - ), - timetable_data: serde_json::from_str(&json!(response).to_string()).unwrap(), - }; - timetable.push(timetable_element) - } - timetable -} - -pub async fn get_timetable(_conn: DbConn) -> Vec { - let xml = get_timetable_xml().await; - let classes = get_timetable_xml_data().await; - let mut timetable: Vec = Vec::new(); - 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 i in &plan { - if i.as_object() != None { - courses.push(i.to_owned()); - } else { - dbg!("Failed: {:?}", &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_list = xml - .as_object() - .unwrap() - .get("VpMobil") - .unwrap() - .get("ZusatzInfo") - .unwrap_or(&empty_obj) - .get("ZiZeile") - .unwrap_or(&empty_list) - .as_array() - .unwrap_or(&empty_vec); - let mut info = String::new(); - for item in info_list { - info.push_str(item.as_str().unwrap_or("\r\n")); - info.push_str("\r\n"); - } - let response = TimetableData { - count: plan.len(), - courses, - info, - }; - let timetable_element = Timetable { - date: String::from( - xml.as_object() - .unwrap() - .get("VpMobil") - .unwrap() - .get("Kopf") - .unwrap() - .get("DatumPlan") - .unwrap() - .as_str() - .unwrap(), - ), - updated: String::from( - xml.as_object() - .unwrap() - .get("VpMobil") - .unwrap() - .get("Kopf") - .unwrap() - .get("zeitstempel") - .unwrap() - .as_str() - .unwrap(), - ), - class: String::from( - i.as_object() - .unwrap() - .get("Kurz") - .unwrap() - .as_str() - .unwrap(), - ), - timetable_data: serde_json::from_str(&json!(response).to_string()).unwrap(), - }; - timetable.push(timetable_element) - } - timetable -} - -pub async fn get_today_class_timetable(_conn: DbConn, class: String) -> TimetableData { - let xml = get_today_timetable_xml().await; - let classes = get_today_timetable_xml_data().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_list = xml - .as_object() - .unwrap() - .get("VpMobil") - .unwrap() - .get("ZusatzInfo") - .unwrap_or(&empty_obj) - .get("ZiZeile") - .unwrap_or(&empty_list) - .as_array() - .unwrap_or(&empty_vec); - let mut info = String::new(); - for item in info_list { - info.push_str(item.as_str().unwrap_or("\r\n")); - info.push_str("\r\n"); - } - let mut response = TimetableData { - count: 0, - courses, - info, - }; - for i in classes.iter() { - if i.as_object() - .unwrap() - .get("Kurz") - .unwrap() - .as_str() - .unwrap() - .replace("/", "_") - == 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; - } - } - response -} - -pub async fn get_tomorrow_class_timetable(_conn: DbConn, class: String) -> TimetableData { - let xml = get_tomorrow_timetable_xml().await; - let classes = get_tomorrow_timetable_xml_data().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_list = xml - .as_object() - .unwrap() - .get("VpMobil") - .unwrap() - .get("ZusatzInfo") - .unwrap_or(&empty_obj) - .get("ZiZeile") - .unwrap_or(&empty_list) - .as_array() - .unwrap_or(&empty_vec); - let mut info = String::new(); - for item in info_list { - info.push_str(item.as_str().unwrap_or("\r\n")); - info.push_str("\r\n"); - } - let mut response = TimetableData { - count: 0, - courses, - info, - }; - for i in classes.iter() { - if i.as_object() - .unwrap() - .get("Kurz") - .unwrap() - .as_str() - .unwrap() - .replace("/", "_") - == 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; - } - } - response -} - -pub async fn get_class_timetable(_conn: DbConn, class: String) -> TimetableData { - let xml = get_timetable_xml().await; - let classes = get_timetable_xml_data().await; +pub async fn get_class_timetable(_conn: DbConn, 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()); @@ -612,7 +223,7 @@ pub async fn get_class_timetable(_conn: DbConn, class: String) -> TimetableData } pub async fn get_classes() -> Vec { - let classes = get_timetable_xml_data().await; + let classes = get_timetable_xml_data(&String::from("Klassen.xml")).await; let mut class_list: Vec = Vec::new(); for i in classes.iter() { class_list.push(String::from( @@ -628,7 +239,7 @@ pub async fn get_classes() -> Vec { } pub async fn get_class_lessons(class: String) -> Vec { - let classes = get_timetable_xml_data().await; + 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()); diff --git a/src/keycloak_connector.rs b/src/keycloak_connector.rs index 842045e..abd4304 100644 --- a/src/keycloak_connector.rs +++ b/src/keycloak_connector.rs @@ -4,11 +4,11 @@ use crate::config; use crate::{Claims, Credentials, Roles, Token, TokenStatus}; use jsonwebtoken::{encode, EncodingKey, Header}; -use keycloak::KeycloakError; use rocket::{response::status, serde::json::Json}; use serde_derive::{Deserialize, Serialize}; use serde_json::json; use std::error::Error; +use std::fmt::Display; use std::time::{Duration, SystemTime, UNIX_EPOCH}; use time::OffsetDateTime; @@ -40,6 +40,45 @@ pub struct KeycloakUser { email: String, } +#[derive(Debug, Deserialize, Serialize)] +pub struct KeycloakHttpError { + pub error: Option, + #[serde(rename = "errorMessage")] + pub error_message: Option, +} + +#[derive(Debug)] +pub enum KeycloakError { + ReqwestFailure(reqwest::Error), + HttpFailure { + status: u16, + body: Option, + text: String, + }, +} + +impl From for KeycloakError { + fn from(value: reqwest::Error) -> Self { + KeycloakError::ReqwestFailure(value) + } +} + +impl Error for KeycloakError { + fn description(&self) -> &str { + "keycloak error" + } + + fn cause(&self) -> Option<&dyn Error> { + None + } +} + +impl Display for KeycloakError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "keycloak error") + } +} + async fn error_check(response: reqwest::Response) -> Result { if !response.status().is_success() { let status = response.status().into(); diff --git a/src/main.rs b/src/main.rs index 51f53f2..64c9a86 100644 --- a/src/main.rs +++ b/src/main.rs @@ -13,6 +13,7 @@ extern crate reqwest; extern crate serde; extern crate serde_json; +use chrono::{Duration, Local}; use indiware_connector as timetable_connector; use jsonwebtoken::{decode, DecodingKey, Validation}; use keycloak_connector::KeycloakUser; @@ -163,11 +164,11 @@ async fn login( } #[get("/")] -async fn get_timetable( +async fn get_latest_timetable( conn: DbConn, _key: ApiKey<'_>, ) -> Json> { - let timetable = timetable_connector::get_timetable(conn).await; + let timetable = timetable_connector::get_timetable(conn, String::from("Klassen.xml")).await; Json::from(timetable) } @@ -176,7 +177,11 @@ async fn get_today_timetable( conn: DbConn, _key: ApiKey<'_>, ) -> Json> { - let timetable = timetable_connector::get_today_timetable(conn).await; + let timetable = timetable_connector::get_timetable( + conn, + format!("PlanKl{}.xml", Local::today().format("%Y%m%d")), + ) + .await; Json::from(timetable) } @@ -185,17 +190,25 @@ async fn get_tomorrow_timetable( conn: DbConn, _key: ApiKey<'_>, ) -> Json> { - let timetable = timetable_connector::get_tomorrow_timetable(conn).await; + let timetable = timetable_connector::get_timetable( + conn, + format!( + "PlanKl{}.xml", + (Local::today() + Duration::days(1)).format("%Y%m%d") + ), + ) + .await; Json::from(timetable) } #[get("/")] -async fn get_class_timetable( +async fn get_latest_class_timetable( conn: DbConn, class: String, _key: ApiKey<'_>, ) -> Json { - let timetable = timetable_connector::get_class_timetable(conn, class).await; + let timetable = + timetable_connector::get_class_timetable(conn, class, String::from("Klassen.xml")).await; Json::from(timetable) } @@ -205,7 +218,12 @@ async fn get_today_class_timetable( class: String, _key: ApiKey<'_>, ) -> Json { - let timetable = timetable_connector::get_today_class_timetable(conn, class).await; + let timetable = timetable_connector::get_class_timetable( + conn, + class, + format!("PlanKl{}.xml", Local::today().format("%Y%m%d")), + ) + .await; Json::from(timetable) } @@ -215,7 +233,15 @@ async fn get_tomorrow_class_timetable( class: String, _key: ApiKey<'_>, ) -> Json { - let timetable = timetable_connector::get_tomorrow_class_timetable(conn, class).await; + let timetable = timetable_connector::get_class_timetable( + conn, + class, + format!( + "PlanKl{}.xml", + (Local::today() + Duration::days(1)).format("%Y%m%d") + ), + ) + .await; Json::from(timetable) } @@ -243,8 +269,8 @@ fn rocket() -> _ { .mount( "/api/timetable", routes![ - get_timetable, - get_class_timetable, + get_latest_timetable, + get_latest_class_timetable, get_today_timetable, get_today_class_timetable, get_tomorrow_timetable,