Created first API Endpoints for timetable.
This commit is contained in:
parent
be8a84b094
commit
3d6ac184a4
13
Cargo.toml
13
Cargo.toml
@ -6,3 +6,16 @@ edition = "2018"
|
|||||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||||
|
|
||||||
[dependencies]
|
[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"]
|
||||||
|
5
Rocket.toml
Normal file
5
Rocket.toml
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
[debug]
|
||||||
|
port = 3000
|
||||||
|
|
||||||
|
[debug.databases]
|
||||||
|
timetable = { url = "postgres://meincantor:meincantor_password@localhost/meincantor_db" }
|
5
diesel.toml
Normal file
5
diesel.toml
Normal file
@ -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"
|
11
docker-compose.yml
Normal file
11
docker-compose.yml
Normal file
@ -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"
|
0
migrations/.gitkeep
Normal file
0
migrations/.gitkeep
Normal file
6
migrations/00000000000000_diesel_initial_setup/down.sql
Normal file
6
migrations/00000000000000_diesel_initial_setup/down.sql
Normal file
@ -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();
|
36
migrations/00000000000000_diesel_initial_setup/up.sql
Normal file
36
migrations/00000000000000_diesel_initial_setup/up.sql
Normal file
@ -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;
|
@ -0,0 +1,2 @@
|
|||||||
|
-- This file should undo anything in `up.sql`
|
||||||
|
drop table timetable;
|
@ -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
|
||||||
|
);
|
173
src/main.rs
173
src/main.rs
@ -1,3 +1,172 @@
|
|||||||
fn main() {
|
#[macro_use] extern crate rocket;
|
||||||
println!("Hello, world!");
|
|
||||||
|
#[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<rocket::serde::json::Value>
|
||||||
|
}
|
||||||
|
|
||||||
|
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<serde_json::value::Value> {
|
||||||
|
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<Vec<Timetable>> {
|
||||||
|
let xml = get_timetable_xml().await;
|
||||||
|
let classes = get_timetable_xml_data().await;
|
||||||
|
let mut timetable: Vec<Timetable> = Vec::new();
|
||||||
|
for i in classes.iter() {
|
||||||
|
let mut courses: Vec<rocket::serde::json::Value> = 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("/<class>")]
|
||||||
|
async fn get_class_timetable(_conn: DbConn, class: String) -> Json<TimetableData> {
|
||||||
|
let classes = get_timetable_xml_data().await;
|
||||||
|
let courses: Vec<rocket::serde::json::Value> = 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<Vec<String>> {
|
||||||
|
let classes = get_timetable_xml_data().await;
|
||||||
|
let mut class_list: Vec<String> = 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("/<name>")]
|
||||||
|
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])
|
||||||
}
|
}
|
||||||
|
9
src/schema.rs
Normal file
9
src/schema.rs
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
table! {
|
||||||
|
timetable (id) {
|
||||||
|
id -> Int4,
|
||||||
|
date -> Varchar,
|
||||||
|
updated -> Varchar,
|
||||||
|
class -> Varchar,
|
||||||
|
timetable_data -> Nullable<Jsonb>,
|
||||||
|
}
|
||||||
|
}
|
1
tutor
Normal file
1
tutor
Normal file
@ -0,0 +1 @@
|
|||||||
|
|
8
xml/Cargo.toml
Normal file
8
xml/Cargo.toml
Normal file
@ -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]
|
39
xml/src/main.rs
Normal file
39
xml/src/main.rs
Normal file
@ -0,0 +1,39 @@
|
|||||||
|
|
||||||
|
|
||||||
|
use quick_xml::Reader;
|
||||||
|
use quick_xml::events::Event;
|
||||||
|
|
||||||
|
let xml = r#"<tag1 att1 = "test">
|
||||||
|
<tag2><!--Test comment-->Test</tag2>
|
||||||
|
<tag2>
|
||||||
|
Test 2
|
||||||
|
</tag2>
|
||||||
|
</tag1>"#;
|
||||||
|
|
||||||
|
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::<Vec<_>>()),
|
||||||
|
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();
|
||||||
|
}
|
1
xml/target/.rustc_info.json
Normal file
1
xml/target/.rustc_info.json
Normal file
@ -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":{}}
|
3
xml/target/CACHEDIR.TAG
Normal file
3
xml/target/CACHEDIR.TAG
Normal file
@ -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/
|
0
xml/target/debug/.cargo-lock
Normal file
0
xml/target/debug/.cargo-lock
Normal file
@ -0,0 +1 @@
|
|||||||
|
This file has an mtime of when this was started.
|
@ -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#\"<tag1 att1 = \"test\">","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#\"<tag1 att1 = \"test\">\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"}
|
Reference in New Issue
Block a user