diff --git a/Cargo.toml b/Cargo.toml index 5ff87af..161d10d 100755 --- a/Cargo.toml +++ b/Cargo.toml @@ -10,18 +10,10 @@ authors = ["Denys Konovalov "] [dependencies] rocket = { version = "0.5.0-rc.1", features = ["json"] } serde = "1.0" -diesel = { version = "1.4", features = ["postgres", "serde_json"] } reqwest = { version="0.11", features = ["json"] } -quickxml_to_serde = "0.4" +quickxml_to_serde = "0.5" serde_json = "1.0" -jsonwebtoken = "7.2" -time = "0.2" +serde_derive = "1.0" +jsonwebtoken = "8.1" +time = "0.3" chrono = "0.4" - - -[dependencies.serde_derive] -version = "1.0" - -[dependencies.rocket_sync_db_pools] -version = "0.1.0-rc.1" -features = ["diesel_postgres_pool"] diff --git a/Dockerfile b/Dockerfile index 4b2031a..bc3f849 100755 --- a/Dockerfile +++ b/Dockerfile @@ -3,13 +3,13 @@ WORKDIR /app COPY . . RUN cargo install --path . -FROM debian:buster-slim as runner +FROM debian:bullseye-slim as runner RUN apt-get update \ - && apt-get install -y ca-certificates tzdata libssl-dev libpq5 \ + && apt-get install -y ca-certificates tzdata libssl-dev \ && rm -rf /var/lib/apt/lists/* COPY --from=builder /usr/local/cargo/bin/api /usr/local/bin/api ENV ROCKET_ADDRESS=0.0.0.0 -EXPOSE 3000 +EXPOSE 8000 CMD ["api"] diff --git a/README.md b/README.md index e69410a..fba8d16 100755 --- a/README.md +++ b/README.md @@ -1,2 +1,70 @@ # meincantor-api The API backend for the GCG.MeinCantor school platform built with Rocket.rs in Rust. + +It includes a plugin for receiving data from Indiware Mobil and a Keycloak authentication extension. + +See the repository of the [main application](https://git.cantorgymnasium.de/cantortechnik/meincantor-app) for additional information. + +## Building + +Dependencies: +- `openssl-devel` + +```bash +cargo build --release +``` + +## Deployment + +### docker-compose + +``` +version: "3.1" +services: + api: + image: lxdb/meincantor-api + restart: always + ports: + - 8000:8000 + environment: + IW_TIMETABLE_URL: https://stundenplan24.de/EXAMPLE_SCHOOL/mobil/mobdaten + IW_TIMETABLE_USER: EXAMPLE_USER + IW_TIMETABLE_PASSWORD: EXAMPLE_PASSWORD + JWT_SECRET: EXAMPLE_SECRET + JWT_ISSUER: Georg-Cantor-Gymnasium Halle(Saale) + KC_OPENID_TOKEN_ENDPOINT: https://example.keycloak.com/auth/realms/EXAMPLE_REALM/protocol/openid-connect/token + KC_OPENID_USERINFO_ENDPOINT: https://example.keycloak.com/auth/realms/EXAMPLE_REALM/protocol/openid-connect/userinfo + KC_CLIENT_ID: EXAMPLE_CLIENT + volumes: + - ./static:/app/static +``` + +1. Create folder `mkdir meincantor-api` +2. Create `docker-compose.yml` file with the content above +3. Start with `docker-compose up -d` + + +### local + +1. Edit `src/config.rs` +2. Rebuild the binary +3. Execute the binary with `./target/release/api` + +## Licensing + +``` +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 . +``` diff --git a/Rocket.toml b/Rocket.toml deleted file mode 100755 index f73d90a..0000000 --- a/Rocket.toml +++ /dev/null @@ -1,11 +0,0 @@ -[debug] -port = 3000 - -[debug.databases] -timetable = { url = "postgres://meincantor:meincantor_password@localhost/meincantor_db" } - -[release] -port = 3000 - -[release.databases] -timetable = { url = "postgres://meincantor:meincantor_password@localhost/meincantor_db" } diff --git a/diesel.toml b/diesel.toml deleted file mode 100755 index 92267c8..0000000 --- a/diesel.toml +++ /dev/null @@ -1,5 +0,0 @@ -# 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 index e65bfb9..6de64d4 100755 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -1,16 +1,10 @@ version: "3.1" services: - postgres: - image: postgres:13-alpine - restart: always - environment: - POSTGRES_PASSWORD: meincantor_password - POSTGRES_USER: meincantor - POSTGRES_DB: meincantor_db - expose: '5432' api: image: lxdb/meincantor-api restart: always + ports: + - 8000:8000 environment: IW_TIMETABLE_URL: https://stundenplan24.de/EXAMPLE_SCHOOL/mobil/mobdaten IW_TIMETABLE_USER: EXAMPLE_USER @@ -20,6 +14,5 @@ services: KC_OPENID_TOKEN_ENDPOINT: https://example.keycloak.com/auth/realms/EXAMPLE_REALM/protocol/openid-connect/token KC_OPENID_USERINFO_ENDPOINT: https://example.keycloak.com/auth/realms/EXAMPLE_REALM/protocol/openid-connect/userinfo KC_CLIENT_ID: EXAMPLE_CLIENT - ROCKET_DATABASES: '{timetable={url="postgres://meincantor:meincantor_password@postgres/meincantor_db"}}' volumes: - ./static:/app/static diff --git a/migrations/.gitkeep b/migrations/.gitkeep deleted file mode 100755 index e69de29..0000000 diff --git a/migrations/00000000000000_diesel_initial_setup/down.sql b/migrations/00000000000000_diesel_initial_setup/down.sql deleted file mode 100755 index a9f5260..0000000 --- a/migrations/00000000000000_diesel_initial_setup/down.sql +++ /dev/null @@ -1,6 +0,0 @@ --- 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 deleted file mode 100755 index d68895b..0000000 --- a/migrations/00000000000000_diesel_initial_setup/up.sql +++ /dev/null @@ -1,36 +0,0 @@ --- 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 deleted file mode 100755 index 9309cae..0000000 --- a/migrations/2021-06-25-153658_create_class_timetable/down.sql +++ /dev/null @@ -1,2 +0,0 @@ --- 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 deleted file mode 100755 index 4a4f346..0000000 --- a/migrations/2021-06-25-153658_create_class_timetable/up.sql +++ /dev/null @@ -1,8 +0,0 @@ --- 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/config.rs b/src/config.rs index bc5572d..7967061 100755 --- a/src/config.rs +++ b/src/config.rs @@ -1,3 +1,19 @@ +// 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 . + // Timetable source pub static IW_TIMETABLE_URL: &str = "https://stundenplan24.de/EXAMPLE_SCHOOL/mobil/mobdaten"; pub static IW_TIMETABLE_USER: &str = "EXAMPLE_USER"; diff --git a/src/indiware_connector.rs b/src/indiware_connector.rs index f0dfea2..902d53f 100755 --- a/src/indiware_connector.rs +++ b/src/indiware_connector.rs @@ -1,14 +1,26 @@ +// 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 crate::schema::timetable; -use crate::DbConn; -use diesel::{Insertable, Queryable}; use quickxml_to_serde::{xml_string_to_json, Config}; use serde_derive::{Deserialize, Serialize}; use serde_json::{json, Map}; use std::env; -#[derive(Queryable, Serialize, Insertable, Deserialize, Clone)] -#[table_name = "timetable"] +#[derive(Serialize, Deserialize, Clone)] pub struct Timetable { pub date: String, pub updated: String, @@ -70,7 +82,7 @@ async fn get_timetable_xml_data(url: &str) -> Vec { classes.to_owned() } -pub async fn get_timetable(_conn: DbConn, url: String) -> Vec { +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(); @@ -322,7 +334,7 @@ pub async fn get_timetable(_conn: DbConn, url: String) -> Vec { timetable_refactored } -pub async fn get_class_timetable(_conn: DbConn, class: String, url: String) -> TimetableData { +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(); diff --git a/src/keycloak_connector.rs b/src/keycloak_connector.rs index 932d6e9..1bef467 100755 --- a/src/keycloak_connector.rs +++ b/src/keycloak_connector.rs @@ -1,3 +1,19 @@ +// 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 . + extern crate reqwest; use crate::config; @@ -11,7 +27,7 @@ use std::env; use std::error::Error; use std::fmt::Display; use std::time::{Duration, SystemTime, UNIX_EPOCH}; -use time::OffsetDateTime; +use time::{macros::format_description, OffsetDateTime}; #[derive(Debug, Deserialize, Serialize)] pub struct KeycloakAdminToken { @@ -228,7 +244,9 @@ pub async fn login( TokenStatus::Success => { let userinfo = get_keycloak_userinfo(token.token.clone()).await.unwrap(); let system_time = OffsetDateTime::now_utc(); - let datetime = system_time.format("%d/%m/%Y %T"); + let datetime = system_time.format(format_description!( + "[day]/[month]/[year] [hour]:[minute]:[second]" + )); let my_claims = Claims { iss: env::var("JWT_ISSUER").unwrap_or(config::JWT_ISSUER.to_string()), user: userinfo.preferred_username, @@ -236,7 +254,7 @@ pub async fn login( groups: userinfo.groups, blacklist: userinfo.blacklist.unwrap_or_default(), whitelist: userinfo.whitelist.unwrap_or_default(), - jid: (credentials.devid + "@" + &datetime), + jid: (credentials.devid + "@" + &datetime.unwrap()), exp: SystemTime::now() .duration_since(UNIX_EPOCH) .expect("Time went backwards") diff --git a/src/main.rs b/src/main.rs index b84fa9e..981b951 100755 --- a/src/main.rs +++ b/src/main.rs @@ -1,13 +1,25 @@ -#[macro_use] -extern crate rocket; +// 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 . #[macro_use] -extern crate diesel; +extern crate rocket; mod config; mod indiware_connector; mod keycloak_connector; -mod schema; extern crate reqwest; extern crate serde; @@ -23,13 +35,9 @@ use rocket::{ response::status, serde::json::Json, }; -use rocket_sync_db_pools::{database, diesel::PgConnection}; use serde_derive::{Deserialize, Serialize}; use std::env; -#[database("timetable")] -pub struct DbConn(PgConnection); - #[derive(Debug, Serialize, Deserialize)] pub struct Credentials { user: String, @@ -85,10 +93,8 @@ impl<'r> FromRequest<'r> for ApiKey<'r> { async fn from_request(req: &'r Request<'_>) -> Outcome { /// Returns true if `key` is a valid API key string. fn is_valid(key: &str) -> bool { - let validation = Validation { - validate_exp: false, - ..Default::default() - }; + let mut validation = Validation::default(); + validation.validate_exp = false; let token = decode::( key, &DecodingKey::from_secret( @@ -102,10 +108,8 @@ impl<'r> FromRequest<'r> for ApiKey<'r> { } fn has_permissions(key: &str, uri: &str) -> bool { - let validation = Validation { - validate_exp: false, - ..Default::default() - }; + let mut validation = Validation::default(); + validation.validate_exp = false; let standard_permissions = vec![ String::from("/api/timetable"), String::from("/api/classes"), @@ -172,44 +176,38 @@ async fn login( } #[get("/latest")] -async fn get_latest_timetable( - conn: DbConn, - _key: ApiKey<'_>, -) -> Json> { - let timetable = timetable_connector::get_timetable(conn, String::from("Klassen.xml")).await; +async fn get_latest_timetable(_key: ApiKey<'_>) -> Json> { + let timetable = timetable_connector::get_timetable(String::from("Klassen.xml")).await; Json::from(timetable) } #[get("/")] async fn get_timetable_file( - conn: DbConn, _key: ApiKey<'_>, date: String, ) -> Json> { - let timetable = timetable_connector::get_timetable(conn, format!("PlanKl{}.xml", date)).await; + let timetable = timetable_connector::get_timetable(format!("PlanKl{}.xml", date)).await; Json::from(timetable) } #[get("/latest/")] async fn get_latest_class_timetable( - conn: DbConn, class: String, _key: ApiKey<'_>, ) -> Json { let timetable = - timetable_connector::get_class_timetable(conn, class, String::from("Klassen.xml")).await; + timetable_connector::get_class_timetable(class, String::from("Klassen.xml")).await; Json::from(timetable) } #[get("//")] async fn get_class_timetable_file( - conn: DbConn, class: String, _key: ApiKey<'_>, date: String, ) -> Json { let timetable = - timetable_connector::get_class_timetable(conn, class, format!("PlanKl{}.xml", date)).await; + timetable_connector::get_class_timetable(class, format!("PlanKl{}.xml", date)).await; Json::from(timetable) } @@ -231,7 +229,6 @@ async fn get_class_lessons( #[launch] fn rocket() -> _ { rocket::build() - .attach(DbConn::fairing()) .mount("/", FileServer::from(relative!("static"))) .mount("/login", routes![login]) .mount( diff --git a/src/schema.rs b/src/schema.rs deleted file mode 100755 index 8fe8d00..0000000 --- a/src/schema.rs +++ /dev/null @@ -1,9 +0,0 @@ -table! { - timetable (id) { - id -> Int4, - date -> Varchar, - updated -> Varchar, - class -> Varchar, - timetable_data -> Nullable, - } -}