diff --git a/Cargo.toml b/Cargo.toml index 34f0ff7..c011c6b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -6,3 +6,16 @@ edition = "2018" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] +rocket = { version = "0.5.0-rc.1", features = ["json"] } +serde = "1.0" +diesel = { version = "1.4", features = ["postgres", "serde_json"] } +reqwest = "0.11" +quickxml_to_serde = "0.4" +serde_json = "1.0.64" + +[dependencies.serde_derive] +version = "1.0" + +[dependencies.rocket_sync_db_pools] +version = "0.1.0-rc.1" +features = ["diesel_postgres_pool"] diff --git a/Rocket.toml b/Rocket.toml new file mode 100644 index 0000000..5f4bad3 --- /dev/null +++ b/Rocket.toml @@ -0,0 +1,5 @@ +[debug] +port = 3000 + +[debug.databases] +timetable = { url = "postgres://meincantor:meincantor_password@localhost/meincantor_db" } diff --git a/diesel.toml b/diesel.toml new file mode 100644 index 0000000..92267c8 --- /dev/null +++ b/diesel.toml @@ -0,0 +1,5 @@ +# For documentation on how to configure this file, +# see diesel.rs/guides/configuring-diesel-cli + +[print_schema] +file = "src/schema.rs" diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000..962eb0e --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,11 @@ +version: "3.1" +services: + postgres: + image: postgres:13-alpine + restart: always + environment: + POSTGRES_PASSWORD: meincantor_password + POSTGRES_USER: meincantor + POSTGRES_DB: meincantor_db + ports: + - "5432:5432" diff --git a/migrations/.gitkeep b/migrations/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/migrations/00000000000000_diesel_initial_setup/down.sql b/migrations/00000000000000_diesel_initial_setup/down.sql new file mode 100644 index 0000000..a9f5260 --- /dev/null +++ b/migrations/00000000000000_diesel_initial_setup/down.sql @@ -0,0 +1,6 @@ +-- This file was automatically created by Diesel to setup helper functions +-- and other internal bookkeeping. This file is safe to edit, any future +-- changes will be added to existing projects as new migrations. + +DROP FUNCTION IF EXISTS diesel_manage_updated_at(_tbl regclass); +DROP FUNCTION IF EXISTS diesel_set_updated_at(); diff --git a/migrations/00000000000000_diesel_initial_setup/up.sql b/migrations/00000000000000_diesel_initial_setup/up.sql new file mode 100644 index 0000000..d68895b --- /dev/null +++ b/migrations/00000000000000_diesel_initial_setup/up.sql @@ -0,0 +1,36 @@ +-- This file was automatically created by Diesel to setup helper functions +-- and other internal bookkeeping. This file is safe to edit, any future +-- changes will be added to existing projects as new migrations. + + + + +-- Sets up a trigger for the given table to automatically set a column called +-- `updated_at` whenever the row is modified (unless `updated_at` was included +-- in the modified columns) +-- +-- # Example +-- +-- ```sql +-- CREATE TABLE users (id SERIAL PRIMARY KEY, updated_at TIMESTAMP NOT NULL DEFAULT NOW()); +-- +-- SELECT diesel_manage_updated_at('users'); +-- ``` +CREATE OR REPLACE FUNCTION diesel_manage_updated_at(_tbl regclass) RETURNS VOID AS $$ +BEGIN + EXECUTE format('CREATE TRIGGER set_updated_at BEFORE UPDATE ON %s + FOR EACH ROW EXECUTE PROCEDURE diesel_set_updated_at()', _tbl); +END; +$$ LANGUAGE plpgsql; + +CREATE OR REPLACE FUNCTION diesel_set_updated_at() RETURNS trigger AS $$ +BEGIN + IF ( + NEW IS DISTINCT FROM OLD AND + NEW.updated_at IS NOT DISTINCT FROM OLD.updated_at + ) THEN + NEW.updated_at := current_timestamp; + END IF; + RETURN NEW; +END; +$$ LANGUAGE plpgsql; diff --git a/migrations/2021-06-25-153658_create_class_timetable/down.sql b/migrations/2021-06-25-153658_create_class_timetable/down.sql new file mode 100644 index 0000000..9309cae --- /dev/null +++ b/migrations/2021-06-25-153658_create_class_timetable/down.sql @@ -0,0 +1,2 @@ +-- This file should undo anything in `up.sql` +drop table timetable; diff --git a/migrations/2021-06-25-153658_create_class_timetable/up.sql b/migrations/2021-06-25-153658_create_class_timetable/up.sql new file mode 100644 index 0000000..4a4f346 --- /dev/null +++ b/migrations/2021-06-25-153658_create_class_timetable/up.sql @@ -0,0 +1,8 @@ +-- Your SQL goes here +create table timetable ( + id serial primary key, + date varchar(255) not null, + updated varchar(255) not null, + class varchar(255) not null, + timetable_data jsonb +); diff --git a/src/main.rs b/src/main.rs index e7a11a9..0398c7f 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,3 +1,172 @@ -fn main() { - println!("Hello, world!"); +#[macro_use] extern crate rocket; + +#[macro_use] extern crate diesel; + +mod schema; + +extern crate serde; + +use rocket::serde::json::{Json, json}; +use crate::schema::timetable; +use diesel::{Queryable, Insertable}; +use serde_derive::{Serialize, Deserialize}; +use rocket_sync_db_pools::{database, diesel::PgConnection}; +use reqwest; +use quickxml_to_serde::{xml_string_to_json, Config}; +extern crate serde_json; +mod config; + +#[database("timetable")] +struct DbConn(PgConnection); + +#[derive(Queryable, Serialize, Insertable, Deserialize)] +#[table_name="timetable"] +struct Timetable { + date: String, + updated: String, + class: String, + timetable_data: serde_json::Value +} + +#[derive(Serialize, Deserialize)] +struct TimetableData { + count: usize, + courses: Vec +} + +async fn get_timetable_xml() -> serde_json::value::Value { + let client = reqwest::Client::new(); + let resp = client + .get(config::TIMETABLE_URL) + .basic_auth(config::TIMETABLE_USER, config::TIMETABLE_PASSWORD) + .send().await.unwrap() + .text().await.unwrap(); + let xml = xml_string_to_json(resp, &Config::new_with_defaults()).unwrap(); + xml +} + +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().to_owned() +} + +#[get("/")] +async fn get_timetable(_conn: DbConn) -> Json> { + 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 plan = i + .as_object().unwrap() + .get("Pl").unwrap() + .as_object().unwrap() + .get("Std").unwrap_or(¬hing) + .as_array().unwrap(); + for i in plan { + if i.as_object() != None { + courses.push(i.to_owned()); + } else { + dbg!("Failed: {:?}", &i); + } + } + let response = TimetableData { + count: plan.len(), + courses: courses + }; + 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) + } + Json::from(timetable) +} + +#[get("/")] +async fn get_class_timetable(_conn: DbConn, class: String) -> Json { + let classes = get_timetable_xml_data().await; + let courses: Vec = Vec::new(); + let mut response = TimetableData { + count: 0, + courses: courses + }; + for i in classes.iter() { + if i + .as_object().unwrap() + .get("Kurz").unwrap() + .as_str().unwrap() + .replace("/", "_") == class { + let nothing = json!([""]); + let plan = i + .as_object().unwrap() + .get("Pl").unwrap() + .as_object().unwrap() + .get("Std").unwrap_or(¬hing) + .as_array().unwrap(); + response.count = plan.len(); + for i in plan { + if i.as_object() != None { + response.courses.push(i.to_owned()); + } else { + dbg!("Failed: {:?}", &i); + } + } + break; + } + } + Json::from(response) +} + +#[get("/classes")] +async fn get_classes() -> Json> { + let classes = get_timetable_xml_data().await; + let mut class_list: Vec = Vec::new(); + for i in classes.iter() { + class_list.push(i.as_object().unwrap() + .get("Kurz").unwrap() + .as_str().unwrap() + .replace("/", "_")) + } + Json::from(class_list) +} + +#[get("/")] +fn hello() -> &'static str { + "Hello, World!" +} + +#[get("/")] +fn hello_name(name: String) -> String { + format!("Hello, {}!", name) +} + +#[launch] +fn rocket() -> _ { + rocket::build() + .attach(DbConn::fairing()) + .mount("/hello", routes![hello, hello_name]) + .mount("/timetable", routes![get_timetable, get_class_timetable, get_classes]) } diff --git a/src/schema.rs b/src/schema.rs new file mode 100644 index 0000000..8fe8d00 --- /dev/null +++ b/src/schema.rs @@ -0,0 +1,9 @@ +table! { + timetable (id) { + id -> Int4, + date -> Varchar, + updated -> Varchar, + class -> Varchar, + timetable_data -> Nullable, + } +} diff --git a/tutor b/tutor new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/tutor @@ -0,0 +1 @@ + diff --git a/xml/Cargo.toml b/xml/Cargo.toml new file mode 100644 index 0000000..9cdf821 --- /dev/null +++ b/xml/Cargo.toml @@ -0,0 +1,8 @@ +[package] +name = "xml" +version = "0.1.0" +edition = "2018" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] diff --git a/xml/src/main.rs b/xml/src/main.rs new file mode 100644 index 0000000..496519d --- /dev/null +++ b/xml/src/main.rs @@ -0,0 +1,39 @@ + + +use quick_xml::Reader; +use quick_xml::events::Event; + +let xml = r#" + Test + + Test 2 + + "#; + +let mut reader = Reader::from_str(xml); +reader.trim_text(true); + +let mut count = 0; +let mut txt = Vec::new(); +let mut buf = Vec::new(); + +// The `Reader` does not implement `Iterator` because it outputs borrowed data (`Cow`s) +loop { + match reader.read_event(&mut buf) { + Ok(Event::Start(ref e)) => { + match e.name() { + b"tag1" => println!("attributes values: {:?}", + e.attributes().map(|a| a.unwrap().value).collect::>()), + b"tag2" => count += 1, + _ => (), + } + }, + Ok(Event::Text(e)) => txt.push(e.unescape_and_decode(&reader).unwrap()), + Ok(Event::Eof) => break, // exits the loop when reaching end of file + Err(e) => panic!("Error at position {}: {:?}", reader.buffer_position(), e), + _ => (), // There are several other `Event`s we do not consider here + } + + // if we don't keep a borrow elsewhere, we can clear the buffer to keep memory usage low + buf.clear(); +} diff --git a/xml/target/.rustc_info.json b/xml/target/.rustc_info.json new file mode 100644 index 0000000..8e48b92 --- /dev/null +++ b/xml/target/.rustc_info.json @@ -0,0 +1 @@ +{"rustc_fingerprint":6590233451751340509,"outputs":{"17598535894874457435":{"success":true,"status":"","code":0,"stdout":"rustc 1.53.0 (53cb7b09b 2021-06-17)\nbinary: rustc\ncommit-hash: 53cb7b09b00cbea8754ffb78e7e3cb521cb8af4b\ncommit-date: 2021-06-17\nhost: x86_64-unknown-linux-gnu\nrelease: 1.53.0\nLLVM version: 12.0.1\n","stderr":""},"931469667778813386":{"success":true,"status":"","code":0,"stdout":"___\nlib___.rlib\nlib___.so\nlib___.so\nlib___.a\nlib___.so\n/home/denyskon/.rustup/toolchains/stable-x86_64-unknown-linux-gnu\ndebug_assertions\nproc_macro\ntarget_arch=\"x86_64\"\ntarget_endian=\"little\"\ntarget_env=\"gnu\"\ntarget_family=\"unix\"\ntarget_feature=\"fxsr\"\ntarget_feature=\"sse\"\ntarget_feature=\"sse2\"\ntarget_os=\"linux\"\ntarget_pointer_width=\"64\"\ntarget_vendor=\"unknown\"\nunix\n","stderr":""},"2797684049618456168":{"success":false,"status":"exit status: 1","code":1,"stdout":"","stderr":"error: `-Csplit-debuginfo` is unstable on this platform\n\n"}},"successes":{}} \ No newline at end of file diff --git a/xml/target/CACHEDIR.TAG b/xml/target/CACHEDIR.TAG new file mode 100644 index 0000000..20d7c31 --- /dev/null +++ b/xml/target/CACHEDIR.TAG @@ -0,0 +1,3 @@ +Signature: 8a477f597d28d172789f06886806bc55 +# This file is a cache directory tag created by cargo. +# For information about cache directory tags see https://bford.info/cachedir/ diff --git a/xml/target/debug/.cargo-lock b/xml/target/debug/.cargo-lock new file mode 100644 index 0000000..e69de29 diff --git a/xml/target/debug/.fingerprint/xml-af563bb74dd611b8/invoked.timestamp b/xml/target/debug/.fingerprint/xml-af563bb74dd611b8/invoked.timestamp new file mode 100644 index 0000000..e00328d --- /dev/null +++ b/xml/target/debug/.fingerprint/xml-af563bb74dd611b8/invoked.timestamp @@ -0,0 +1 @@ +This file has an mtime of when this was started. \ No newline at end of file diff --git a/xml/target/debug/.fingerprint/xml-af563bb74dd611b8/output-bin-xml b/xml/target/debug/.fingerprint/xml-af563bb74dd611b8/output-bin-xml new file mode 100644 index 0000000..6ee4ad6 --- /dev/null +++ b/xml/target/debug/.fingerprint/xml-af563bb74dd611b8/output-bin-xml @@ -0,0 +1,2 @@ +{"message":"expected item, found keyword `let`","code":null,"level":"error","spans":[{"file_name":"src/main.rs","byte_start":56,"byte_end":59,"line_start":6,"line_end":6,"column_start":1,"column_end":4,"is_primary":true,"text":[{"text":"let xml = r#\"","highlight_start":1,"highlight_end":4}],"label":"expected item","suggested_replacement":null,"suggestion_applicability":null,"expansion":null}],"children":[],"rendered":"\u001b[0m\u001b[1m\u001b[38;5;9merror\u001b[0m\u001b[0m\u001b[1m: expected item, found keyword `let`\u001b[0m\n\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[38;5;12m--> \u001b[0m\u001b[0msrc/main.rs:6:1\u001b[0m\n\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[38;5;12m|\u001b[0m\n\u001b[0m\u001b[1m\u001b[38;5;12m6\u001b[0m\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[38;5;12m| \u001b[0m\u001b[0mlet xml = r#\"\u001b[0m\n\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[38;5;12m| \u001b[0m\u001b[0m\u001b[1m\u001b[38;5;9m^^^\u001b[0m\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[38;5;9mexpected item\u001b[0m\n\n"} +{"message":"aborting due to previous error","code":null,"level":"error","spans":[],"children":[],"rendered":"\u001b[0m\u001b[1m\u001b[38;5;9merror\u001b[0m\u001b[0m\u001b[1m: aborting due to previous error\u001b[0m\n\n"}