Skip to content

Commit

Permalink
implemented a calendar frontend
Browse files Browse the repository at this point in the history
  • Loading branch information
CommanderStorm committed Jan 18, 2023
1 parent 88c2e42 commit 47293ee
Show file tree
Hide file tree
Showing 6 changed files with 275 additions and 4 deletions.
114 changes: 114 additions & 0 deletions calendar/src/calendar.rs
@@ -0,0 +1,114 @@
use crate::scraping::tumonline_calendar::XMLEvent;
use crate::utils;
use actix_web::web::Data;
use actix_web::{get, web, HttpResponse};
use chrono::NaiveDateTime;
use diesel::prelude::*;
use serde::{Deserialize, Serialize};
use std::fmt::Debug;
use tokio::sync::Mutex;

#[derive(Deserialize, Debug)]
pub struct CalendarQueryArgs {
start: NaiveDateTime, // eg. 2022-01-01T00:00:00
end: NaiveDateTime, // eg. 2022-01-07T00:00:00
}

pub fn configure(cfg: &mut web::ServiceConfig) {
cfg.service(calendar_handler);
}

#[get("/{id}")]
pub async fn calendar_handler(
params: web::Path<String>,
web::Query(args): web::Query<CalendarQueryArgs>,
last_sync: Data<Mutex<Option<NaiveDateTime>>>,
) -> HttpResponse {
let last_sync = match *last_sync.lock().await {
Some(last_sync) => last_sync,
None => {
return HttpResponse::ServiceUnavailable().body("Waiting for first sync with TUMonline")
}
};

let id = params.into_inner();
let conn = &mut utils::establish_connection();
use crate::schema::calendar::dsl::*;
let results = calendar
.filter(key.eq(&id))
.filter(dtstart.ge(&args.start))
.filter(dtend.ge(&args.end))
.load::<XMLEvent>(conn);
match results {
Ok(result) => {
let events = result.into_iter().map(Event::from).collect();
HttpResponse::Ok().json(Events { events, last_sync })
}
Err(_) => HttpResponse::NotFound()
.content_type("text/plain")
.body("Not found"),
}
}

#[derive(Serialize, Debug)]
struct Events {
events: Vec<Event>,
last_sync: NaiveDateTime,
}

#[derive(Serialize, Debug)]
struct Event {
title: String,
start: NaiveDateTime,
end: NaiveDateTime,
entry_type: EventType,
}

#[derive(Serialize, Debug)]
enum EventType {
Lecture(String),
Exercise,
Exam,
Barred,
Other(String),
}
impl EventType {
fn from(xml_event: &XMLEvent) -> Self {
// only used for the lecture type
let course_type_name = xml_event
.course_type_name
.clone()
.unwrap_or_else(|| "Course type is unknown".to_string());
match xml_event.single_event_type_id.as_str() {
"SPERRE" => return EventType::Barred,
"PT" => return EventType::Exam,
"P" => return EventType::Lecture(course_type_name), // Prüfung (geplant) is sometimes used for lectures
_ => {}
}
match xml_event.event_type_id.as_str() {
"LV" => EventType::Lecture(course_type_name),
"PT" => EventType::Exam,
"EX" => EventType::Exercise,
_ => match &xml_event.event_type_name {
Some(event_type_name) => EventType::Other(format!(
"{}: {}",
xml_event.single_event_type_name, event_type_name
)),
None => EventType::Other(xml_event.single_event_type_name.clone()),
},
}
}
}

impl From<XMLEvent> for Event {
fn from(xml_event: XMLEvent) -> Self {
let _type = EventType::from(&xml_event);
let title = xml_event.event_title;
Self {
title,
start: xml_event.dtstart,
end: xml_event.dtend,
entry_type: _type,
}
}
}
11 changes: 10 additions & 1 deletion calendar/src/main.rs
@@ -1,9 +1,11 @@
mod calendar;
mod schema;
mod scraping;
mod utils;

use actix_cors::Cors;
use actix_web::{get, middleware, web, App, HttpResponse, HttpServer};
use tokio::sync::Mutex;

const MAX_JSON_PAYLOAD: usize = 1024 * 1024; // 1 MB

Expand Down Expand Up @@ -31,8 +33,10 @@ async fn health_handler() -> HttpResponse {
async fn main() -> std::io::Result<()> {
env_logger::init_from_env(env_logger::Env::default().default_filter_or("info"));

let last_sync = web::Data::new(Mutex::new(None));
let cloned_last_sync = last_sync.clone();
actix_rt::spawn(async move {
scraping::continous_scraping::start_scraping().await;
scraping::continous_scraping::start_scraping(cloned_last_sync).await;
});
HttpServer::new(move || {
let cors = Cors::default()
Expand All @@ -46,6 +50,11 @@ async fn main() -> std::io::Result<()> {
.wrap(middleware::Logger::default().exclude("/api/calendar/health"))
.wrap(middleware::Compress::default())
.app_data(web::JsonConfig::default().limit(MAX_JSON_PAYLOAD))
.service(
web::scope("/api/calendar")
.configure(calendar::configure)
.app_data(last_sync.clone()),
)
.service(source_code_handler)
.service(health_handler)
})
Expand Down
10 changes: 8 additions & 2 deletions calendar/src/scraping/continous_scraping.rs
Expand Up @@ -2,21 +2,27 @@ use crate::scraping::task::ScrapeRoomTask;
use crate::scraping::tumonline_calendar::{Strategy, XMLEvents};
use crate::utils;
use crate::utils::statistics::Statistic;
use actix_web::web::Data;
use awc::{Client, Connector};
use chrono::Datelike;
use chrono::{Datelike, NaiveDateTime, Utc};
use diesel::prelude::*;
use futures::future::join_all;
use log::{error, info, warn};
use std::time::{Duration, Instant};
use tokio::sync::Mutex;
use tokio::time::sleep;

pub async fn start_scraping() {
pub async fn start_scraping(last_sync: Data<Mutex<Option<NaiveDateTime>>>) {
let mut interval = actix_rt::time::interval(Duration::from_secs(60 * 60 * 24)); //24h
loop {
interval.tick().await;
delete_scraped_results();
scrape_to_db(4).await;
promote_scraped_results_to_prod();
{
let mut last_scrape = last_sync.lock().await;
*last_scrape = Some(Utc::now().naive_utc());
}
}
}

Expand Down
2 changes: 1 addition & 1 deletion calendar/src/scraping/mod.rs
@@ -1,3 +1,3 @@
pub mod continous_scraping;
mod task;
mod tumonline_calendar;
pub mod tumonline_calendar;
131 changes: 131 additions & 0 deletions openapi.yaml
Expand Up @@ -549,6 +549,93 @@ paths:
- Not found
tags:
- core
'/api/calendar/{id}':
get:
operationId: calendar
summary: Get a entry-preview
description: |
This returns the a 1200x630px preview for the entry (room/building/..).
This is usefull for implementing custom OpenGraph images for detail previews.
parameters:
- name: start
in: query
description: 'The first allowed time the calendar would like to display'
required: true
schema:
type: string
format: date-time
- name: end
in: query
description: 'The last allowed time the calendar would like to display'
required: true
schema:
type: string
format: date-time
- name: id
in: path
description: string you want to search for
required: true
schema:
type: string
examples:
'5304':
summary: A normal building
value: '5304'
root:
summary: The root entry of the NavigaTUM-data
value: root
garching:
summary: The garching campus
value: garching
mri:
summary: The MRI campus
value: garching
mi:
summary: 'A large, (custom named) building'
value: mi
mw:
summary: 'An other large, (custom named) building'
value: mw
fsmpi:
summary: A regular room
value: 5606.EG.036
rechnerhalle:
summary: A room with custom props added
value: 5605.EG.011
tb-chemie:
summary: A virtual room
value: 5401.01.100A
humangenetik:
summary: A virtual room
value: 1543
responses:
'200':
description: More entries of the calendar in the requested time span
content:
application/json:
schema:
$ref: '#/components/schemas/CalendarResponse'
'404':
description: Invalid input
content:
text/plain:
schema:
type: string
example: Not found
enum:
- Not found
'503':
description: Not Ready, please retry later
content:
text/plain:
schema:
type: string
example: Waiting for first sync with TUMonline
enum:
- Waiting for first sync with TUMonline
tags:
- core
'/api/preview/{id}':
get:
operationId: previews
Expand Down Expand Up @@ -1673,6 +1760,50 @@ components:
- parent_names
- props
- ranking_factors
CalendarResponse:
type: object
properties:
entries:
description: The entries of the requested
type: array
items:
$ref: '#/components/schemas/CalendarEntry'
last_sync:
description: When the last sync with TUMonline happened.
type: string
format: date-time
example: '2018-01-01T00:00:00'
required:
- entries
- last_sync
CalendarEntry:
type: object
properties:
title:
description: The title of the Entry
type: string
example: 'Quantenteleportation'
entry_type:
description: The title of the Entry
type: string
enum:
- Lecture
- Excercise
start:
description: The start of the entry
type: string
format: date-time
example: '2018-01-01T00:00:00'
end:
description: The end of the entry
type: string
format: date-time
example: '2018-01-01T00:00:00'
required:
- title
- entry_type
- start
- end
RankingFactors:
type: object
properties:
Expand Down
11 changes: 11 additions & 0 deletions server/src/main.rs
@@ -1,5 +1,6 @@
use actix_cors::Cors;
use actix_web::{get, middleware, web, App, HttpResponse, HttpServer};
use tokio::sync::Mutex;

mod core;
mod maps;
Expand Down Expand Up @@ -33,6 +34,11 @@ async fn health_handler() -> HttpResponse {
async fn main() -> std::io::Result<()> {
env_logger::init_from_env(env_logger::Env::default().default_filter_or("info"));

let last_sync = web::Data::new(Mutex::new(None));
let cloned_last_sync = last_sync.clone();
actix_rt::spawn(async move {
continous_scraping::start_scraping(cloned_last_sync).await;
});
HttpServer::new(move || {
let cors = Cors::default()
.allow_any_origin()
Expand All @@ -47,6 +53,11 @@ async fn main() -> std::io::Result<()> {
.app_data(web::JsonConfig::default().limit(MAX_JSON_PAYLOAD))
.service(source_code_handler)
.service(health_handler)
.service(
web::scope("/api/calendar")
.configure(calendar::configure)
.app_data(last_sync.clone()),
)
.service(web::scope("/api/preview").configure(maps::configure))
.service(web::scope("/api").configure(core::configure))
})
Expand Down

0 comments on commit 47293ee

Please sign in to comment.