Skip to content

Commit 5f4955d

Browse files
committed
added private endpoints working
1 parent 363c7ce commit 5f4955d

8 files changed

Lines changed: 305 additions & 4 deletions

File tree

examples/private_endpoints.rs

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
use osars::{
2+
CallRequest, Client, CreateParserRequest, LessonRequest, UpdateCallsRequest,
3+
UpdateGroupsRequest, UpdateLessonsRequest,
4+
};
5+
6+
#[tokio::main]
7+
async fn main() -> Result<(), Box<dyn std::error::Error>> {
8+
let client = Client::new("https://api.thisishyum.ru/schedule_api/tyumen");
9+
10+
// Authenticated client with token
11+
let auth_client = client.authenticated().with_token("your_token_here");
12+
13+
// Work with admin Endpoints
14+
let admin_api = auth_client.admin();
15+
16+
// Create parser
17+
let create_request = CreateParserRequest {
18+
college_name: "TKPST".to_string(),
19+
campus_names: vec!["Lunacharskogo".to_string(), "Samartseva".to_string()],
20+
};
21+
22+
let response = admin_api.create_parser(create_request).await?;
23+
println!("Created parser with token: {}", response.token);
24+
25+
// Remove parser
26+
admin_api.delete_parser(123).await?;
27+
println!("Parser deleted");
28+
29+
// Work with parser
30+
let parser_api = auth_client.parser();
31+
32+
// Update groups
33+
let groups_request = UpdateGroupsRequest {
34+
campus_id: 1,
35+
student_group_names: vec!["Group A".to_string(), "Group B".to_string()],
36+
};
37+
parser_api.update_groups(groups_request).await?;
38+
39+
// Update call schedule
40+
let calls_request = UpdateCallsRequest {
41+
calls: vec![CallRequest {
42+
weekday: 1,
43+
begins: "09:00:00".to_string(),
44+
ends: "10:30:00".to_string(),
45+
order: 1,
46+
}],
47+
};
48+
parser_api.update_calls(calls_request).await?;
49+
50+
// Add lessons
51+
let lessons_request = UpdateLessonsRequest {
52+
lessons: vec![LessonRequest {
53+
group_id: 1,
54+
order: 1,
55+
title: "Mathematics".to_string(),
56+
teacher: "Dr. Smith".to_string(),
57+
cabinet: "Room 101".to_string(),
58+
date: "2023-10-01".to_string(),
59+
}],
60+
};
61+
parser_api.add_lessons(lessons_request).await?;
62+
63+
Ok(())
64+
}

src/api/admin.rs

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
use crate::{AuthenticatedClient, CreateParserRequest, CreateParserResponse, error::Result};
2+
3+
pub struct AdminApi {
4+
client: AuthenticatedClient,
5+
}
6+
7+
impl AdminApi {
8+
pub fn new(client: AuthenticatedClient) -> Self {
9+
Self { client }
10+
}
11+
12+
/// Create a new parser
13+
pub async fn create_parser(
14+
&self,
15+
request: CreateParserRequest,
16+
) -> Result<CreateParserResponse> {
17+
let path = "/admin/parser";
18+
self.client
19+
.client
20+
.post_json(path, Some(&request), Some(&self.client.auth))
21+
.await
22+
}
23+
24+
/// Delete a parser
25+
pub async fn delete_parser(&self, parser_id: u32) -> Result<()> {
26+
let path = format!("/admin/parser/{}", parser_id);
27+
self.client
28+
.client
29+
.delete_json(&path, Some(&self.client.auth))
30+
.await
31+
}
32+
}

src/api/mod.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,14 @@
1+
pub mod admin;
12
pub mod colleges;
23
pub mod groups;
4+
pub mod parser;
35
pub mod schedules;
46

7+
pub use admin::AdminApi;
58
pub use colleges::CampusQuery;
69
pub use colleges::CampusesQuery;
710
pub use colleges::CollegeQuery;
811
pub use colleges::CollegesQuery;
912
pub use groups::GroupsQuery;
13+
pub use parser::ParserApi;
1014
pub use schedules::ScheduleQuery;

src/api/parser.rs

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
use crate::{
2+
AuthenticatedClient, UpdateCallsRequest, UpdateGroupsRequest, UpdateLessonsRequest,
3+
error::Result,
4+
};
5+
6+
pub struct ParserApi {
7+
client: AuthenticatedClient,
8+
}
9+
10+
impl ParserApi {
11+
pub fn new(client: AuthenticatedClient) -> Self {
12+
Self { client }
13+
}
14+
15+
/// Update groups for a campus
16+
pub async fn update_groups(&self, request: UpdateGroupsRequest) -> Result<()> {
17+
let path = "/parser/groups";
18+
self.client
19+
.client
20+
.post_json(path, Some(&request), Some(&self.client.auth))
21+
.await
22+
}
23+
24+
/// Update call schedule
25+
pub async fn update_calls(&self, request: UpdateCallsRequest) -> Result<()> {
26+
let path = "/parser/calls";
27+
self.client
28+
.client
29+
.post_json(path, Some(&request), Some(&self.client.auth))
30+
.await
31+
}
32+
33+
/// Add lessons
34+
pub async fn add_lessons(&self, request: UpdateLessonsRequest) -> Result<()> {
35+
let path = "/parser/lessons";
36+
self.client
37+
.client
38+
.post_json(path, Some(&request), Some(&self.client.auth))
39+
.await
40+
}
41+
}

src/auth.rs

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
use crate::AdminApi;
2+
use crate::ParserApi;
3+
use std::sync::Arc;
4+
5+
#[derive(Debug, Clone)]
6+
pub struct Auth {
7+
pub token: Option<String>,
8+
}
9+
10+
impl Auth {
11+
pub fn new(token: Option<String>) -> Self {
12+
Self { token }
13+
}
14+
15+
pub fn with_token(mut self, token: &str) -> Self {
16+
self.token = Some(token.to_string());
17+
self
18+
}
19+
20+
pub fn clear_token(mut self) -> Self {
21+
self.token = None;
22+
self
23+
}
24+
25+
pub(crate) fn apply_to_request(
26+
&self,
27+
request: reqwest::RequestBuilder,
28+
) -> reqwest::RequestBuilder {
29+
if let Some(token) = &self.token {
30+
request.bearer_auth(token)
31+
} else {
32+
request
33+
}
34+
}
35+
}
36+
37+
#[derive(Debug, Clone)]
38+
pub struct AuthenticatedClient {
39+
pub client: crate::Client,
40+
pub auth: Auth,
41+
}
42+
43+
impl AuthenticatedClient {
44+
pub fn new(client: crate::Client) -> Self {
45+
Self {
46+
client,
47+
auth: Auth::new(None),
48+
}
49+
}
50+
51+
pub fn with_token(mut self, token: &str) -> Self {
52+
self.auth = self.auth.with_token(token);
53+
self
54+
}
55+
56+
pub fn admin(&self) -> AdminApi {
57+
AdminApi::new(self.clone())
58+
}
59+
60+
pub fn parser(&self) -> ParserApi {
61+
ParserApi::new(self.clone())
62+
}
63+
}

src/client.rs

Lines changed: 98 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,8 @@
1+
use crate::Auth;
2+
use crate::api::{CampusQuery, CampusesQuery, CollegeQuery, CollegesQuery};
3+
use crate::auth::AuthenticatedClient;
4+
use crate::error::Result;
5+
use crate::{GroupsQuery, ScheduleQuery, error::Error};
16
/// A client for interacting with the educational schedule API.
27
///
38
/// The `Client` provides methods to query colleges, campuses, groups, and schedules.
@@ -14,10 +19,6 @@
1419
#[cfg(feature = "logging")]
1520
use tracing::{debug, error};
1621

17-
use crate::api::{CampusQuery, CampusesQuery, CollegeQuery, CollegesQuery};
18-
use crate::error::Result;
19-
use crate::{GroupsQuery, ScheduleQuery, error::Error};
20-
2122
#[derive(Debug, Clone)]
2223
pub struct Client {
2324
pub(crate) base_url: String,
@@ -216,6 +217,95 @@ impl Client {
216217
))
217218
}
218219
}
220+
pub(crate) async fn post_json<T, B>(
221+
&self,
222+
path: &str,
223+
body: Option<&B>,
224+
auth: Option<&Auth>,
225+
) -> Result<T>
226+
where
227+
T: serde::de::DeserializeOwned,
228+
B: serde::Serialize,
229+
{
230+
let url = format!("{}{}", self.base_url, path);
231+
#[cfg(feature = "logging")]
232+
debug!("POST {}", url);
233+
234+
let mut request = self.http_client.post(&url);
235+
236+
if let Some(auth) = auth {
237+
request = auth.apply_to_request(request);
238+
}
239+
240+
if let Some(body) = body {
241+
request = request.json(body);
242+
}
243+
244+
let response = request.send().await.map_err(crate::error::Error::Reqwest)?;
245+
246+
self.handle_response(response).await
247+
}
248+
249+
pub(crate) async fn delete_json<T>(&self, path: &str, auth: Option<&Auth>) -> Result<T>
250+
where
251+
T: serde::de::DeserializeOwned,
252+
{
253+
let url = format!("{}{}", self.base_url, path);
254+
#[cfg(feature = "logging")]
255+
debug!("DELETE {}", url);
256+
257+
let mut request = self.http_client.delete(&url);
258+
259+
if let Some(auth) = auth {
260+
request = auth.apply_to_request(request);
261+
}
262+
263+
let response = request.send().await.map_err(crate::error::Error::Reqwest)?;
264+
265+
self.handle_response(response).await
266+
}
267+
268+
async fn handle_response<T>(&self, response: reqwest::Response) -> Result<T>
269+
where
270+
T: serde::de::DeserializeOwned,
271+
{
272+
let status = response.status();
273+
let raw_body = response
274+
.text()
275+
.await
276+
.map_err(crate::error::Error::Reqwest)?;
277+
278+
#[cfg(feature = "logging")]
279+
{
280+
if status.is_success() {
281+
debug!("Success {}: raw response = {}", status, raw_body);
282+
} else {
283+
error!("API error {}: raw response = {}", status, raw_body);
284+
}
285+
}
286+
287+
if status.is_success() {
288+
if raw_body.is_empty() {
289+
// Handle empty response for DELETE and some POST requests
290+
serde_json::from_str("null").map_err(|e| {
291+
#[cfg(feature = "logging")]
292+
error!("JSON parse error for empty response: {}", e);
293+
crate::error::Error::Serialization(e)
294+
})
295+
} else {
296+
serde_json::from_str(&raw_body).map_err(|e| {
297+
#[cfg(feature = "logging")]
298+
error!("JSON parse error: {}\nRaw body: {}", e, raw_body);
299+
crate::error::Error::Serialization(e)
300+
})
301+
}
302+
} else {
303+
Err(crate::error::Error::from_response(
304+
status.as_u16(),
305+
raw_body,
306+
))
307+
}
308+
}
219309

220310
/// Creates a query to list groups for a campus.
221311
///
@@ -252,6 +342,10 @@ impl Client {
252342
pub fn tomorrow(&self, group_id: u32) -> ScheduleQuery {
253343
self.schedule(group_id).tomorrow()
254344
}
345+
/// Create an authenticated client for private endpoints
346+
pub fn authenticated(&self) -> AuthenticatedClient {
347+
AuthenticatedClient::new(self.clone())
348+
}
255349
}
256350

257351
#[cfg(test)]

src/lib.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,13 @@
11
pub mod api;
2+
pub mod auth;
23
pub mod client;
34
pub mod error;
45
pub mod logging;
56
pub mod models;
67
pub mod utils;
78

89
pub use api::*;
10+
pub use auth::*;
911
pub use client::*;
1012
pub use error::{Error, Result};
1113
pub use models::*;

src/models/mod.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ pub use campus::Campus;
1111
pub use college::College;
1212
pub use group::Group;
1313
pub use lesson::Lesson;
14+
pub use requests::*;
1415
pub use schedule::Schedule;
1516
use std::fmt;
1617

0 commit comments

Comments
 (0)