Skip to content

Commit ce83541

Browse files
committed
Add simple api
1 parent 4da8e89 commit ce83541

File tree

13 files changed

+466
-44
lines changed

13 files changed

+466
-44
lines changed

Cargo.lock

Lines changed: 334 additions & 28 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,4 +4,5 @@ resolver = "2"
44
members = [
55
"rezvrh_scraper",
66
"rezvrh_cli",
7+
"rezvrh_api",
78
]

rezvrh_api/Cargo.toml

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
[package]
2+
name = "rezvrh_api"
3+
version = "0.1.0"
4+
edition = "2021"
5+
6+
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
7+
8+
[dependencies]
9+
anyhow = "1.0"
10+
axum = "0.7"
11+
base64 = "0.21.7"
12+
rezvrh_scraper = { path = "../rezvrh_scraper" }
13+
serde = { version = "1.0.196", features = ["derive"] }
14+
serde_json = "1.0.113"
15+
thiserror = "1.0.56"
16+
tokio = { version = "1", features = ["macros", "rt-multi-thread"] }

rezvrh_api/src/main.rs

Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,96 @@
1+
use axum::{
2+
extract::Query,
3+
http::{HeaderMap, StatusCode},
4+
response::{IntoResponse, Response},
5+
routing::get,
6+
Json, Router,
7+
};
8+
use base64::prelude::*;
9+
use rezvrh_scraper::{Bakalari, Error as BakalariError};
10+
use thiserror::Error;
11+
12+
// Extract basic auth from headers
13+
fn auth(headers: &HeaderMap) -> Option<(String, String)> {
14+
let auth = headers.get("authorization")?;
15+
let auth = auth.to_str().ok()?;
16+
let auth = auth.strip_prefix("Basic ")?;
17+
let auth = BASE64_STANDARD.decode(auth).ok()?;
18+
let auth = String::from_utf8(auth).ok()?;
19+
let (username, password) = auth.split_once(':')?;
20+
Some((username.to_string(), password.to_string()))
21+
}
22+
23+
#[derive(Debug, Error)]
24+
enum ApiError {
25+
#[error("Unauthorized")]
26+
Unauthorized,
27+
#[error("Bad URL")]
28+
BadUrl,
29+
#[error("Scrape error: {0}")]
30+
ScrapeError(#[from] BakalariError),
31+
}
32+
33+
impl IntoResponse for ApiError {
34+
fn into_response(self) -> Response {
35+
match self {
36+
Self::Unauthorized => StatusCode::UNAUTHORIZED.into_response(),
37+
Self::ScrapeError(err) => {
38+
(StatusCode::INTERNAL_SERVER_ERROR, err.to_string()).into_response()
39+
}
40+
Self::BadUrl => (StatusCode::BAD_REQUEST, "No URL provided").into_response(),
41+
}
42+
}
43+
}
44+
45+
#[derive(serde::Deserialize)]
46+
struct GetQuery {
47+
url: String,
48+
}
49+
50+
async fn get_api(headers: HeaderMap, query: Query<GetQuery>) -> Result<Bakalari, ApiError> {
51+
let (username, password) = auth(&headers).ok_or(ApiError::Unauthorized)?;
52+
let url = query.url.parse().map_err(|_| ApiError::BadUrl)?;
53+
Ok(Bakalari::from_creds_no_store((&username, &password), url).await?)
54+
}
55+
56+
async fn get_rooms(
57+
headers: HeaderMap,
58+
query: Query<GetQuery>,
59+
) -> Result<Json<Vec<String>>, ApiError> {
60+
let bakalari = get_api(headers, query).await?;
61+
let classes = bakalari.get_objects(rezvrh_scraper::Type::Room);
62+
Ok(Json(classes))
63+
}
64+
65+
async fn get_classes(
66+
headers: HeaderMap,
67+
query: Query<GetQuery>,
68+
) -> Result<Json<Vec<String>>, ApiError> {
69+
let bakalari = get_api(headers, query).await?;
70+
let classes = bakalari.get_objects(rezvrh_scraper::Type::Class);
71+
Ok(Json(classes))
72+
}
73+
74+
async fn get_teachers(
75+
headers: HeaderMap,
76+
query: Query<GetQuery>,
77+
) -> Result<Json<Vec<String>>, ApiError> {
78+
let bakalari = get_api(headers, query).await?;
79+
let classes = bakalari.get_objects(rezvrh_scraper::Type::Teacher);
80+
Ok(Json(classes))
81+
}
82+
83+
#[tokio::main]
84+
async fn main() -> anyhow::Result<()> {
85+
// build our application with a single route
86+
let app = Router::new()
87+
.route("/", get(|| async { "Hello, World!" }))
88+
.route("/classes", get(get_classes))
89+
.route("/rooms", get(get_rooms))
90+
.route("/teachers", get(get_teachers));
91+
92+
// run our app with hyper, listening globally on port 3000
93+
let listener = tokio::net::TcpListener::bind("0.0.0.0:3000").await?;
94+
axum::serve(listener, app).await?;
95+
Ok(())
96+
}

rezvrh_cli/Cargo.toml

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,11 +7,12 @@ description = "A CLI tool for rezvrh"
77
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
88

99
[dependencies]
10-
anyhow = "1.0.79"
11-
clap = { version = "4.4.18", features = ["derive"] }
10+
anyhow = "1"
11+
clap = { version = "4.4", features = ["derive"] }
1212
tokio = { version = "1", features = ["full"] }
1313
serde = { version = "1.0", features = ["derive"] }
1414
reqwest = "0.11"
15-
inquire = "0.6.2"
15+
inquire = "0.6"
1616
serde_json = "1.0"
1717
rezvrh_scraper = { path = "../rezvrh_scraper" }
18+
md-5 = "0.10.6"

rezvrh_scraper/Cargo.toml

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,10 +8,10 @@ description = "Bakalari scraper"
88

99
[dependencies]
1010
chrono = { version = "0.4", features = ["serde"] }
11-
derive_more = { version = "1.0.0-beta.6", features = ["full"] }
12-
once_cell = "1.19.0"
11+
derive_more = { version = "1.0.0-beta", features = ["full"] }
12+
once_cell = "1"
1313
reqwest = "0.11"
14-
scraper = "0.18.1"
14+
scraper = "0.18"
1515
serde = { version = "1.0", features = ["derive"] }
1616
serde_json = "1.0"
1717
thiserror = "1.0"

rezvrh_scraper/src/lib.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
mod modules;
22

33
pub use modules::bakalari::Bakalari;
4+
pub use modules::bakalari::RequestError as Error;
45
pub use modules::timetable::RawType as Type;
56
pub use modules::timetable::Which;

rezvrh_scraper/src/modules/bakalari/info.rs

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -49,11 +49,13 @@ impl Bakalari {
4949
/// Get list of objects
5050
#[must_use]
5151
pub fn get_objects(&self, typ: RawType) -> Vec<String> {
52-
match typ {
52+
let mut obj = match typ {
5353
RawType::Class => self.get_classes(),
5454
RawType::Teacher => self.get_teachers(),
5555
RawType::Room => self.get_rooms(),
56-
}
56+
};
57+
obj.sort();
58+
obj
5759
}
5860

5961
/// Get selector

rezvrh_scraper/src/modules/bakalari/other.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ impl Bakalari {
1212
let client = self.client();
1313
let res = client
1414
.reqwest_client()
15-
.get(client.url().join("/timetable/public").unwrap())
15+
.get(client.url().join("timetable/public").unwrap())
1616
.header("Cookie", format!("BakaAuth={}", self.get_token().await?))
1717
.send()
1818
.await?;

rezvrh_scraper/src/modules/bakalari/timetable.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ impl Bakalari {
2020
.get(
2121
client
2222
.url()
23-
.join(&format!("/timetable/public/{which}/{timetable_type}"))
23+
.join(&format!("timetable/public/{which}/{timetable_type}"))
2424
.unwrap(),
2525
)
2626
.header("Cookie", format!("BakaAuth={}", self.get_token().await?))

rezvrh_scraper/src/modules/bakalari/util.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,7 @@ pub async fn get_info(
4949
RequestError,
5050
> {
5151
let res = client
52-
.get(url.join("/timetable/public").unwrap())
52+
.get(url.join("timetable/public").unwrap())
5353
.header("Cookie", format!("BakaAuth={token}"))
5454
.send()
5555
.await?;

rezvrh_scraper/src/modules/timetable/day.rs

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -30,10 +30,8 @@ pub enum ParseError {
3030
Lesson(#[from] LessonParseError),
3131
}
3232

33-
static NAME_SELECTOR: Lazy<Selector> =
34-
Lazy::new(|| Selector::parse("span.bk-day-day").unwrap());
35-
static DATE_SELECTOR: Lazy<Selector> =
36-
Lazy::new(|| Selector::parse("span.bk-day-date").unwrap());
33+
static NAME_SELECTOR: Lazy<Selector> = Lazy::new(|| Selector::parse("span.bk-day-day").unwrap());
34+
static DATE_SELECTOR: Lazy<Selector> = Lazy::new(|| Selector::parse("span.bk-day-date").unwrap());
3735
static CELL_SELECTOR: Lazy<Selector> =
3836
Lazy::new(|| Selector::parse("div.bk-timetable-cell").unwrap());
3937

rezvrh_scraper/src/modules/timetable/hour.rs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,8 @@ impl Hour {
4949
let num = single_iter(num.text(), || ParseError::NoNumText)?;
5050
let num = num.parse::<usize>().map_err(ParseError::ParseNum)?;
5151
if num != i + 1 {
52-
return Err(ParseError::MismatchedNum);
52+
println!("num: {num}, i: {i}");
53+
//return Err(ParseError::MismatchedNum);
5354
}
5455

5556
let mut times = hour.select(&TIMES_SELECTOR);

0 commit comments

Comments
 (0)