mirror of
https://github.com/n08i40k/schedule-parser-rusted.git
synced 2025-12-06 09:47:50 +03:00
feat!: add telegram auth and async refactor
- Removed "/schedule/update-download-url" endpoint, this mechanism was replaced by Yandex Cloud FaaS. Ура :) - Improved schedule caching mechanism. - Added Telegram WebApp authentication support. - Reworked endpoints responses and errors mechanism. - Refactored application state management. - Make synchronous database operations, middlewares and extractors to asynchronous. - Made user password field optional to support multiple auth methods. - Renamed users table column "version" to "android_version" and made it nullable.
This commit is contained in:
@@ -1,14 +1,12 @@
|
||||
use crate::xls_downloader::interface::{FetchError, FetchOk, FetchResult, XLSDownloader};
|
||||
use chrono::{DateTime, Utc};
|
||||
use std::env;
|
||||
use std::sync::Arc;
|
||||
|
||||
pub struct BasicXlsDownloader {
|
||||
pub url: Option<String>,
|
||||
user_agent: String,
|
||||
}
|
||||
|
||||
async fn fetch_specified(url: &String, user_agent: &String, head: bool) -> FetchResult {
|
||||
async fn fetch_specified(url: &str, head: bool) -> FetchResult {
|
||||
let client = reqwest::Client::new();
|
||||
|
||||
let response = if head {
|
||||
@@ -16,59 +14,47 @@ async fn fetch_specified(url: &String, user_agent: &String, head: bool) -> Fetch
|
||||
} else {
|
||||
client.get(url)
|
||||
}
|
||||
.header("User-Agent", user_agent.clone())
|
||||
.header("User-Agent", ua_generator::ua::spoof_chrome_ua())
|
||||
.send()
|
||||
.await;
|
||||
.await
|
||||
.map_err(|e| FetchError::unknown(Arc::new(e)))?;
|
||||
|
||||
match response {
|
||||
Ok(r) => {
|
||||
if r.status().as_u16() != 200 {
|
||||
return Err(FetchError::BadStatusCode(r.status().as_u16()));
|
||||
}
|
||||
|
||||
let headers = r.headers();
|
||||
|
||||
let content_type = headers.get("Content-Type");
|
||||
let etag = headers.get("etag");
|
||||
let last_modified = headers.get("last-modified");
|
||||
let date = headers.get("date");
|
||||
|
||||
if content_type.is_none() {
|
||||
Err(FetchError::BadHeaders("Content-Type".to_string()))
|
||||
} else if etag.is_none() {
|
||||
Err(FetchError::BadHeaders("ETag".to_string()))
|
||||
} else if last_modified.is_none() {
|
||||
Err(FetchError::BadHeaders("Last-Modified".to_string()))
|
||||
} else if date.is_none() {
|
||||
Err(FetchError::BadHeaders("Date".to_string()))
|
||||
} else if content_type.unwrap() != "application/vnd.ms-excel" {
|
||||
Err(FetchError::BadContentType(
|
||||
content_type.unwrap().to_str().unwrap().to_string(),
|
||||
))
|
||||
} else {
|
||||
let etag = etag.unwrap().to_str().unwrap().to_string();
|
||||
let last_modified =
|
||||
DateTime::parse_from_rfc2822(&last_modified.unwrap().to_str().unwrap())
|
||||
.unwrap()
|
||||
.with_timezone(&Utc);
|
||||
|
||||
Ok(if head {
|
||||
FetchOk::head(etag, last_modified)
|
||||
} else {
|
||||
FetchOk::get(etag, last_modified, r.bytes().await.unwrap().to_vec())
|
||||
})
|
||||
}
|
||||
}
|
||||
Err(error) => Err(FetchError::Unknown(Arc::new(error))),
|
||||
if response.status().as_u16() != 200 {
|
||||
return Err(FetchError::bad_status_code(response.status().as_u16()));
|
||||
}
|
||||
|
||||
let headers = response.headers();
|
||||
|
||||
let content_type = headers
|
||||
.get("Content-Type")
|
||||
.ok_or(FetchError::bad_headers("Content-Type"))?;
|
||||
|
||||
if !headers.contains_key("etag") {
|
||||
return Err(FetchError::bad_headers("etag"));
|
||||
}
|
||||
|
||||
let last_modified = headers
|
||||
.get("last-modified")
|
||||
.ok_or(FetchError::bad_headers("last-modified"))?;
|
||||
|
||||
if content_type != "application/vnd.ms-excel" {
|
||||
return Err(FetchError::bad_content_type(content_type.to_str().unwrap()));
|
||||
}
|
||||
|
||||
let last_modified = DateTime::parse_from_rfc2822(&last_modified.to_str().unwrap())
|
||||
.unwrap()
|
||||
.with_timezone(&Utc);
|
||||
|
||||
Ok(if head {
|
||||
FetchOk::head(last_modified)
|
||||
} else {
|
||||
FetchOk::get(last_modified, response.bytes().await.unwrap().to_vec())
|
||||
})
|
||||
}
|
||||
|
||||
impl BasicXlsDownloader {
|
||||
pub fn new() -> Self {
|
||||
BasicXlsDownloader {
|
||||
url: None,
|
||||
user_agent: env::var("REQWEST_USER_AGENT").expect("USER_AGENT must be set"),
|
||||
}
|
||||
BasicXlsDownloader { url: None }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -77,15 +63,15 @@ impl XLSDownloader for BasicXlsDownloader {
|
||||
if self.url.is_none() {
|
||||
Err(FetchError::NoUrlProvided)
|
||||
} else {
|
||||
fetch_specified(self.url.as_ref().unwrap(), &self.user_agent, head).await
|
||||
fetch_specified(&*self.url.as_ref().unwrap(), head).await
|
||||
}
|
||||
}
|
||||
|
||||
async fn set_url(&mut self, url: String) -> FetchResult {
|
||||
let result = fetch_specified(&url, &self.user_agent, true).await;
|
||||
async fn set_url(&mut self, url: &str) -> FetchResult {
|
||||
let result = fetch_specified(url, true).await;
|
||||
|
||||
if let Ok(_) = result {
|
||||
self.url = Some(url);
|
||||
self.url = Some(url.to_string());
|
||||
}
|
||||
|
||||
result
|
||||
@@ -94,17 +80,16 @@ impl XLSDownloader for BasicXlsDownloader {
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::xls_downloader::basic_impl::{fetch_specified, BasicXlsDownloader};
|
||||
use crate::xls_downloader::basic_impl::{BasicXlsDownloader, fetch_specified};
|
||||
use crate::xls_downloader::interface::{FetchError, XLSDownloader};
|
||||
|
||||
#[tokio::test]
|
||||
async fn bad_url() {
|
||||
let url = "bad_url".to_string();
|
||||
let user_agent = String::new();
|
||||
let url = "bad_url";
|
||||
|
||||
let results = [
|
||||
fetch_specified(&url, &user_agent, true).await,
|
||||
fetch_specified(&url, &user_agent, false).await,
|
||||
fetch_specified(url, true).await,
|
||||
fetch_specified(url, false).await,
|
||||
];
|
||||
|
||||
assert!(results[0].is_err());
|
||||
@@ -113,18 +98,17 @@ mod tests {
|
||||
|
||||
#[tokio::test]
|
||||
async fn bad_status_code() {
|
||||
let url = "https://www.google.com/not-found".to_string();
|
||||
let user_agent = String::new();
|
||||
let url = "https://www.google.com/not-found";
|
||||
|
||||
let results = [
|
||||
fetch_specified(&url, &user_agent, true).await,
|
||||
fetch_specified(&url, &user_agent, false).await,
|
||||
fetch_specified(url, true).await,
|
||||
fetch_specified(url, false).await,
|
||||
];
|
||||
|
||||
assert!(results[0].is_err());
|
||||
assert!(results[1].is_err());
|
||||
|
||||
let expected_error = FetchError::BadStatusCode(404);
|
||||
let expected_error = FetchError::BadStatusCode { status_code: 404 };
|
||||
|
||||
assert_eq!(*results[0].as_ref().err().unwrap(), expected_error);
|
||||
assert_eq!(*results[1].as_ref().err().unwrap(), expected_error);
|
||||
@@ -132,18 +116,19 @@ mod tests {
|
||||
|
||||
#[tokio::test]
|
||||
async fn bad_headers() {
|
||||
let url = "https://www.google.com/favicon.ico".to_string();
|
||||
let user_agent = String::new();
|
||||
let url = "https://www.google.com/favicon.ico";
|
||||
|
||||
let results = [
|
||||
fetch_specified(&url, &user_agent, true).await,
|
||||
fetch_specified(&url, &user_agent, false).await,
|
||||
fetch_specified(url, true).await,
|
||||
fetch_specified(url, false).await,
|
||||
];
|
||||
|
||||
assert!(results[0].is_err());
|
||||
assert!(results[1].is_err());
|
||||
|
||||
let expected_error = FetchError::BadHeaders("ETag".to_string());
|
||||
let expected_error = FetchError::BadHeaders {
|
||||
expected_header: "ETag".to_string(),
|
||||
};
|
||||
|
||||
assert_eq!(*results[0].as_ref().err().unwrap(), expected_error);
|
||||
assert_eq!(*results[1].as_ref().err().unwrap(), expected_error);
|
||||
@@ -151,12 +136,11 @@ mod tests {
|
||||
|
||||
#[tokio::test]
|
||||
async fn bad_content_type() {
|
||||
let url = "https://s3.aero-storage.ldragol.ru/679e5d1145a6ad00843ad3f1/67ddb59fd46303008396ac96%2Fexample.txt".to_string();
|
||||
let user_agent = String::new();
|
||||
let url = "https://s3.aero-storage.ldragol.ru/679e5d1145a6ad00843ad3f1/67ddb59fd46303008396ac96%2Fexample.txt";
|
||||
|
||||
let results = [
|
||||
fetch_specified(&url, &user_agent, true).await,
|
||||
fetch_specified(&url, &user_agent, false).await,
|
||||
fetch_specified(url, true).await,
|
||||
fetch_specified(url, false).await,
|
||||
];
|
||||
|
||||
assert!(results[0].is_err());
|
||||
@@ -165,12 +149,11 @@ mod tests {
|
||||
|
||||
#[tokio::test]
|
||||
async fn ok() {
|
||||
let url = "https://s3.aero-storage.ldragol.ru/679e5d1145a6ad00843ad3f1/67ddb5fad46303008396ac97%2Fschedule.xls".to_string();
|
||||
let user_agent = String::new();
|
||||
let url = "https://s3.aero-storage.ldragol.ru/679e5d1145a6ad00843ad3f1/67ddb5fad46303008396ac97%2Fschedule.xls";
|
||||
|
||||
let results = [
|
||||
fetch_specified(&url, &user_agent, true).await,
|
||||
fetch_specified(&url, &user_agent, false).await,
|
||||
fetch_specified(url, true).await,
|
||||
fetch_specified(url, false).await,
|
||||
];
|
||||
|
||||
assert!(results[0].is_ok());
|
||||
@@ -179,7 +162,7 @@ mod tests {
|
||||
|
||||
#[tokio::test]
|
||||
async fn downloader_set_ok() {
|
||||
let url = "https://s3.aero-storage.ldragol.ru/679e5d1145a6ad00843ad3f1/67ddb5fad46303008396ac97%2Fschedule.xls".to_string();
|
||||
let url = "https://s3.aero-storage.ldragol.ru/679e5d1145a6ad00843ad3f1/67ddb5fad46303008396ac97%2Fschedule.xls";
|
||||
|
||||
let mut downloader = BasicXlsDownloader::new();
|
||||
|
||||
@@ -188,7 +171,7 @@ mod tests {
|
||||
|
||||
#[tokio::test]
|
||||
async fn downloader_set_err() {
|
||||
let url = "bad_url".to_string();
|
||||
let url = "bad_url";
|
||||
|
||||
let mut downloader = BasicXlsDownloader::new();
|
||||
|
||||
@@ -197,7 +180,7 @@ mod tests {
|
||||
|
||||
#[tokio::test]
|
||||
async fn downloader_ok() {
|
||||
let url = "https://s3.aero-storage.ldragol.ru/679e5d1145a6ad00843ad3f1/67ddb5fad46303008396ac97%2Fschedule.xls".to_string();
|
||||
let url = "https://s3.aero-storage.ldragol.ru/679e5d1145a6ad00843ad3f1/67ddb5fad46303008396ac97%2Fschedule.xls";
|
||||
|
||||
let mut downloader = BasicXlsDownloader::new();
|
||||
|
||||
|
||||
Reference in New Issue
Block a user