diff --git a/rezvrh_api/src/main.rs b/rezvrh_api/src/main.rs index e23ba8f..7af51a5 100644 --- a/rezvrh_api/src/main.rs +++ b/rezvrh_api/src/main.rs @@ -1,12 +1,12 @@ use axum::{ - extract::Query, + extract::{Path, Query}, http::{HeaderMap, StatusCode}, response::{IntoResponse, Response}, routing::get, Json, Router, }; use base64::prelude::*; -use rezvrh_scraper::{Bakalari, Error as BakalariError}; +use rezvrh_scraper::{Bakalari, Error as BakalariError, Timetable, Type, Which}; use thiserror::Error; // Extract basic auth from headers @@ -26,6 +26,8 @@ enum ApiError { BadUrl, #[error("Scrape error: {0}")] ScrapeError(#[from] BakalariError), + #[error("invalid or missing selector")] + InvalidSelector, } impl IntoResponse for ApiError { @@ -35,6 +37,9 @@ impl IntoResponse for ApiError { (StatusCode::INTERNAL_SERVER_ERROR, err.to_string()).into_response() } Self::BadUrl => (StatusCode::BAD_REQUEST, "Invalid or missing URL").into_response(), + Self::InvalidSelector => { + (StatusCode::BAD_REQUEST, "Invalid or missing selector").into_response() + } } } } @@ -44,8 +49,8 @@ struct GetQuery { url: String, } -async fn get_api(headers: &HeaderMap, query: &Query) -> Result { - let url = query.url.parse().map_err(|_| ApiError::BadUrl)?; +async fn get_api(headers: &HeaderMap, url: &str) -> Result { + let url = url.parse().map_err(|_| ApiError::BadUrl)?; match auth(headers) { Some((username, password)) => { Ok(Bakalari::from_creds_no_store((&username, &password), url).await?) @@ -58,7 +63,7 @@ async fn get_rooms( headers: HeaderMap, query: Query, ) -> Result>, ApiError> { - let bakalari = get_api(&headers, &query).await?; + let bakalari = get_api(&headers, &query.url).await?; let classes = bakalari.get_objects(rezvrh_scraper::Type::Room); Ok(Json(classes)) } @@ -67,7 +72,7 @@ async fn get_classes( headers: HeaderMap, query: Query, ) -> Result>, ApiError> { - let bakalari = get_api(&headers, &query).await?; + let bakalari = get_api(&headers, &query.url).await?; let classes = bakalari.get_objects(rezvrh_scraper::Type::Class); Ok(Json(classes)) } @@ -76,11 +81,50 @@ async fn get_teachers( headers: HeaderMap, query: Query, ) -> Result>, ApiError> { - let bakalari = get_api(&headers, &query).await?; + let bakalari = get_api(&headers, &query.url).await?; let classes = bakalari.get_objects(rezvrh_scraper::Type::Teacher); Ok(Json(classes)) } +async fn get_class_timetable( + Path((class_name, which)): Path<(String, Which)>, + headers: HeaderMap, + query: Query, +) -> Result, ApiError> { + let bakalari = get_api(&headers, &query.url).await?; + let selector = bakalari + .get_selector(Type::Class, &class_name) + .ok_or(ApiError::InvalidSelector)?; + let timetable = bakalari.get_timetable(which, &selector).await?; + Ok(Json(timetable)) +} + +async fn get_teacher_timetable( + Path((teacher_name, which)): Path<(String, Which)>, + headers: HeaderMap, + query: Query, +) -> Result, ApiError> { + let bakalari = get_api(&headers, &query.url).await?; + let selector = bakalari + .get_selector(Type::Teacher, &teacher_name) + .ok_or(ApiError::InvalidSelector)?; + let timetable = bakalari.get_timetable(which, &selector).await?; + Ok(Json(timetable)) +} + +async fn get_room_timetable( + Path((room_name, which)): Path<(String, Which)>, + headers: HeaderMap, + query: Query, +) -> Result, ApiError> { + let bakalari = get_api(&headers, &query.url).await?; + let selector = bakalari + .get_selector(Type::Room, &room_name) + .ok_or(ApiError::InvalidSelector)?; + let timetable = bakalari.get_timetable(which, &selector).await?; + Ok(Json(timetable)) +} + /* async fn get_timetable( headers: HeaderMap, @@ -98,7 +142,16 @@ async fn main() -> anyhow::Result<()> { .route("/", get(|| async { "Hello, World!" })) .route("/classes", get(get_classes)) .route("/rooms", get(get_rooms)) - .route("/teachers", get(get_teachers)); + .route("/teachers", get(get_teachers)) + .route( + "/timetable/class/:class_name/:which", + get(get_class_timetable), + ) + .route( + "/timetable/teacher/:teacher_name/:which", + get(get_teacher_timetable), + ) + .route("/timetable/room/:room_name/:which", get(get_room_timetable)); // run our app with hyper, listening globally on port 3000 let listener = tokio::net::TcpListener::bind("0.0.0.0:3000").await?; diff --git a/rezvrh_scraper/src/lib.rs b/rezvrh_scraper/src/lib.rs index 653c879..75a529d 100644 --- a/rezvrh_scraper/src/lib.rs +++ b/rezvrh_scraper/src/lib.rs @@ -3,5 +3,6 @@ mod modules; pub use modules::bakalari::Bakalari; pub use modules::bakalari::RequestError as Error; pub use modules::timetable::RawType as Type; +pub use modules::timetable::Type as Selector; pub use modules::timetable::Which; pub use modules::timetable::Timetable; diff --git a/rezvrh_scraper/src/modules/timetable.rs b/rezvrh_scraper/src/modules/timetable.rs index bfd0909..87aa934 100644 --- a/rezvrh_scraper/src/modules/timetable.rs +++ b/rezvrh_scraper/src/modules/timetable.rs @@ -13,7 +13,8 @@ mod lesson; mod util; /// Which timetable to get -#[derive(Debug, Display, PartialEq, Eq, Hash, Clone, Copy)] +#[derive(Debug, Display, PartialEq, Eq, Hash, Clone, Copy, Serialize, Deserialize)] +#[serde(rename_all = "lowercase")] pub enum Which { /// Permanent timetable Permanent, diff --git a/rezvrh_scraper/src/modules/timetable/lesson.rs b/rezvrh_scraper/src/modules/timetable/lesson.rs index 8aeed8b..b125e46 100644 --- a/rezvrh_scraper/src/modules/timetable/lesson.rs +++ b/rezvrh_scraper/src/modules/timetable/lesson.rs @@ -13,6 +13,7 @@ mod parser; /// Struct that hold one lesson of timetable #[derive(Debug, Serialize, Deserialize)] +#[serde(rename_all = "lowercase")] pub enum Lesson { Regular { class: String,