Add pre-commit config

This commit is contained in:
2023-09-24 22:55:14 +02:00
parent 8fa9684b02
commit 72ca7a07fe
13 changed files with 231 additions and 220 deletions

7
.pre-commit-config.yaml Normal file
View File

@@ -0,0 +1,7 @@
repos:
- repo: https://github.com/doublify/pre-commit-rust
rev: v1.0
hooks:
- id: fmt
- id: cargo-check
- id: clippy

View File

@@ -23,15 +23,13 @@ pub struct Config {
pub files_api_key: String, pub files_api_key: String,
pub files_url: String, pub files_url: String,
pub sentry_dsn: String pub sentry_dsn: String,
} }
fn get_env(env: &'static str) -> String { fn get_env(env: &'static str) -> String {
std::env::var(env).unwrap_or_else(|_| panic!("Cannot get the {} env variable", env)) std::env::var(env).unwrap_or_else(|_| panic!("Cannot get the {} env variable", env))
} }
impl Config { impl Config {
pub fn load() -> Config { pub fn load() -> Config {
Config { Config {
@@ -57,12 +55,9 @@ impl Config {
files_api_key: get_env("FILES_SERVER_API_KEY"), files_api_key: get_env("FILES_SERVER_API_KEY"),
files_url: get_env("FILES_SERVER_URL"), files_url: get_env("FILES_SERVER_URL"),
sentry_dsn: get_env("SENTRY_DSN") sentry_dsn: get_env("SENTRY_DSN"),
} }
} }
} }
pub static CONFIG: Lazy<Config> = Lazy::new(Config::load);
pub static CONFIG: Lazy<Config> = Lazy::new(|| {
Config::load()
});

View File

@@ -1,5 +1,4 @@
use crate::{prisma::PrismaClient, config::CONFIG}; use crate::{config::CONFIG, prisma::PrismaClient};
pub async fn get_prisma_client() -> PrismaClient { pub async fn get_prisma_client() -> PrismaClient {
let database_url: String = format!( let database_url: String = format!(

View File

@@ -1,16 +1,15 @@
pub mod config; pub mod config;
pub mod db; pub mod db;
pub mod prisma; pub mod prisma;
pub mod views;
pub mod services; pub mod services;
pub mod views;
use sentry::{integrations::debug_images::DebugImagesIntegration, types::Dsn, ClientOptions};
use std::{net::SocketAddr, str::FromStr}; use std::{net::SocketAddr, str::FromStr};
use sentry::{ClientOptions, types::Dsn, integrations::debug_images::DebugImagesIntegration};
use tracing::info; use tracing::info;
use crate::views::get_router; use crate::views::get_router;
#[tokio::main] #[tokio::main]
async fn main() { async fn main() {
let options = ClientOptions { let options = ClientOptions {

View File

@@ -1,7 +1,8 @@
// Code generated by Prisma Client Rust. DO NOT EDIT // Code generated by Prisma Client Rust. DO NOT EDIT
pub static DATAMODEL_STR: &str = #![allow(clippy::all)]
include_str!("../prisma/schema.prisma");
pub static DATAMODEL_STR: &str = include_str!("../prisma/schema.prisma");
static DATABASE_STR: &str = "postgresql"; static DATABASE_STR: &str = "postgresql";
pub async fn new_client() -> Result<PrismaClient, ::prisma_client_rust::NewClientError> { pub async fn new_client() -> Result<PrismaClient, ::prisma_client_rust::NewClientError> {
PrismaClient::_builder().build().await PrismaClient::_builder().build().await
@@ -21,9 +22,7 @@ pub mod cached_file {
pub mod id { pub mod id {
use super::super::*; use super::super::*;
use super::{ use super::{OrderByParam, SetParam, UncheckedSetParam, UniqueWhereParam, WhereParam};
OrderByParam, SetParam, UncheckedSetParam, UniqueWhereParam, WhereParam,
};
pub const NAME: &str = "id"; pub const NAME: &str = "id";
pub struct Set(pub i32); pub struct Set(pub i32);
impl From<Set> for SetParam { impl From<Set> for SetParam {
@@ -92,9 +91,7 @@ pub mod cached_file {
pub mod object_id { pub mod object_id {
use super::super::*; use super::super::*;
use super::{ use super::{OrderByParam, SetParam, UncheckedSetParam, WhereParam};
OrderByParam, SetParam, UncheckedSetParam, WhereParam,
};
pub const NAME: &str = "object_id"; pub const NAME: &str = "object_id";
pub struct Set(pub i32); pub struct Set(pub i32);
impl From<Set> for SetParam { impl From<Set> for SetParam {
@@ -167,9 +164,7 @@ pub mod cached_file {
pub mod object_type { pub mod object_type {
use super::super::*; use super::super::*;
use super::{ use super::{OrderByParam, SetParam, UncheckedSetParam, WhereParam};
OrderByParam, SetParam, UncheckedSetParam, WhereParam,
};
pub const NAME: &str = "object_type"; pub const NAME: &str = "object_type";
pub struct Set(pub String); pub struct Set(pub String);
impl From<Set> for SetParam { impl From<Set> for SetParam {
@@ -234,9 +229,7 @@ pub mod cached_file {
pub mod message_id { pub mod message_id {
use super::super::*; use super::super::*;
use super::{ use super::{OrderByParam, SetParam, UncheckedSetParam, UniqueWhereParam, WhereParam};
OrderByParam, SetParam, UncheckedSetParam, UniqueWhereParam, WhereParam,
};
pub const NAME: &str = "message_id"; pub const NAME: &str = "message_id";
pub struct Set(pub i64); pub struct Set(pub i64);
impl From<Set> for SetParam { impl From<Set> for SetParam {
@@ -309,9 +302,7 @@ pub mod cached_file {
pub mod chat_id { pub mod chat_id {
use super::super::*; use super::super::*;
use super::{ use super::{OrderByParam, SetParam, UncheckedSetParam, WhereParam};
OrderByParam, SetParam, UncheckedSetParam, WhereParam,
};
pub const NAME: &str = "chat_id"; pub const NAME: &str = "chat_id";
pub struct Set(pub i64); pub struct Set(pub i64);
impl From<Set> for SetParam { impl From<Set> for SetParam {

View File

@@ -60,7 +60,7 @@ pub async fn get_books(
("page", page.to_string()), ("page", page.to_string()),
("size", page_size.to_string()), ("size", page_size.to_string()),
("uploaded_gte", uploaded_gte), ("uploaded_gte", uploaded_gte),
("uploaded_lte", uploaded_lte) ("uploaded_lte", uploaded_lte),
]; ];
_make_request("/api/v1/books/base/", params).await _make_request("/api/v1/books/base/", params).await

View File

@@ -1,6 +1,5 @@
use serde::Deserialize; use serde::Deserialize;
#[derive(Deserialize, Debug, Clone)] #[derive(Deserialize, Debug, Clone)]
pub struct Source { pub struct Source {
pub id: u32, pub id: u32,
@@ -41,7 +40,7 @@ pub struct BookWithRemote {
#[derive(Deserialize, Debug, Clone)] #[derive(Deserialize, Debug, Clone)]
pub struct BaseBook { pub struct BaseBook {
pub id: i32, pub id: i32,
pub available_types: Vec<String> pub available_types: Vec<String>,
} }
impl BookWithRemote { impl BookWithRemote {
@@ -59,7 +58,6 @@ impl BookWithRemote {
} }
} }
impl BookAuthor { impl BookAuthor {
pub fn get_caption(self) -> String { pub fn get_caption(self) -> String {
let mut parts: Vec<String> = vec![]; let mut parts: Vec<String> = vec![];
@@ -82,21 +80,13 @@ impl BookAuthor {
} }
} }
impl BookWithRemote { impl BookWithRemote {
pub fn get_caption(self) -> String { pub fn get_caption(self) -> String {
let BookWithRemote { let BookWithRemote { title, authors, .. } = self;
title,
authors,
..
} = self;
let caption_title = format!("📖 {title}"); let caption_title = format!("📖 {title}");
let author_captions: Vec<String> = authors let author_captions: Vec<String> = authors.into_iter().map(|a| a.get_caption()).collect();
.into_iter()
.map(|a| a.get_caption())
.collect();
let mut author_parts: Vec<String> = vec![]; let mut author_parts: Vec<String> = vec![];
let mut author_parts_len = 3; let mut author_parts_len = 3;
@@ -116,7 +106,6 @@ impl BookWithRemote {
} }
} }
#[derive(Deserialize, Debug, Clone)] #[derive(Deserialize, Debug, Clone)]
pub struct Page<T> { pub struct Page<T> {
pub items: Vec<T>, pub items: Vec<T>,

View File

@@ -1,4 +1,4 @@
use std::io::{Write, Seek, SeekFrom}; use std::io::{Seek, SeekFrom, Write};
use bytes::Buf; use bytes::Buf;
use futures::TryStreamExt; use futures::TryStreamExt;
@@ -7,7 +7,6 @@ use tempfile::SpooledTempFile;
use tokio::io::AsyncRead; use tokio::io::AsyncRead;
use tokio_util::compat::FuturesAsyncReadCompatExt; use tokio_util::compat::FuturesAsyncReadCompatExt;
pub struct DownloadResult { pub struct DownloadResult {
pub response: Response, pub response: Response,
pub filename: String, pub filename: String,

View File

@@ -5,14 +5,12 @@ use serde::Deserialize;
use crate::config::CONFIG; use crate::config::CONFIG;
#[derive(Deserialize)] #[derive(Deserialize)]
pub struct FilenameData { pub struct FilenameData {
pub filename: String, pub filename: String,
pub filename_ascii: String pub filename_ascii: String,
} }
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
struct DownloadError { struct DownloadError {
status_code: StatusCode, status_code: StatusCode,
@@ -26,11 +24,10 @@ impl fmt::Display for DownloadError {
impl std::error::Error for DownloadError {} impl std::error::Error for DownloadError {}
pub async fn download_from_downloader( pub async fn download_from_downloader(
source_id: u32, source_id: u32,
remote_id: u32, remote_id: u32,
object_type: String object_type: String,
) -> Result<Response, Box<dyn std::error::Error + Send + Sync>> { ) -> Result<Response, Box<dyn std::error::Error + Send + Sync>> {
let url = format!( let url = format!(
"{}/download/{source_id}/{remote_id}/{object_type}", "{}/download/{source_id}/{remote_id}/{object_type}",
@@ -45,16 +42,17 @@ pub async fn download_from_downloader(
.error_for_status()?; .error_for_status()?;
if response.status() == StatusCode::NO_CONTENT { if response.status() == StatusCode::NO_CONTENT {
return Err(Box::new(DownloadError { status_code: StatusCode::NO_CONTENT })) return Err(Box::new(DownloadError {
status_code: StatusCode::NO_CONTENT,
}));
}; };
Ok(response) Ok(response)
} }
pub async fn get_filename( pub async fn get_filename(
object_id: i32, object_id: i32,
object_type: String object_type: String,
) -> Result<FilenameData, Box<dyn std::error::Error + Send + Sync>> { ) -> Result<FilenameData, Box<dyn std::error::Error + Send + Sync>> {
let url = format!( let url = format!(
"{}/filename/{object_id}/{object_type}", "{}/filename/{object_id}/{object_type}",
@@ -70,8 +68,6 @@ pub async fn get_filename(
match response.json::<FilenameData>().await { match response.json::<FilenameData>().await {
Ok(v) => Ok(v), Ok(v) => Ok(v),
Err(err) => { Err(err) => Err(Box::new(err)),
Err(Box::new(err))
},
} }
} }

View File

@@ -2,17 +2,21 @@ use std::io::Read;
use async_stream::stream; use async_stream::stream;
use bytes::Bytes; use bytes::Bytes;
use minio_rsc::{provider::StaticProvider, Minio, types::args::{ObjectArgs, PresignedArgs}, errors::MinioError}; use minio_rsc::{
errors::MinioError,
provider::StaticProvider,
types::args::{ObjectArgs, PresignedArgs},
Minio,
};
use tempfile::SpooledTempFile; use tempfile::SpooledTempFile;
use crate::config; use crate::config;
pub fn get_minio() -> Minio { pub fn get_minio() -> Minio {
let provider = StaticProvider::new( let provider = StaticProvider::new(
&config::CONFIG.minio_access_key, &config::CONFIG.minio_access_key,
&config::CONFIG.minio_secret_key, &config::CONFIG.minio_secret_key,
None None,
); );
Minio::builder() Minio::builder()
@@ -23,8 +27,9 @@ pub fn get_minio() -> Minio {
.unwrap() .unwrap()
} }
pub fn get_stream(
pub fn get_stream(mut temp_file: Box<dyn Read + Send>) -> impl futures_core::Stream<Item = Result<Bytes, MinioError>> { mut temp_file: Box<dyn Read + Send>,
) -> impl futures_core::Stream<Item = Result<Bytes, MinioError>> {
stream! { stream! {
let mut buf = [0; 2048]; let mut buf = [0; 2048];
@@ -38,8 +43,10 @@ pub fn get_stream(mut temp_file: Box<dyn Read + Send>) -> impl futures_core::Str
} }
} }
pub async fn upload_to_minio(
pub async fn upload_to_minio(archive: SpooledTempFile, filename: String) -> Result<String, Box<dyn std::error::Error + Send + Sync>> { archive: SpooledTempFile,
filename: String,
) -> Result<String, Box<dyn std::error::Error + Send + Sync>> {
let minio = get_minio(); let minio = get_minio();
let is_bucket_exist = match minio.bucket_exists(&config::CONFIG.minio_bucket).await { let is_bucket_exist = match minio.bucket_exists(&config::CONFIG.minio_bucket).await {
@@ -53,20 +60,24 @@ pub async fn upload_to_minio(archive: SpooledTempFile, filename: String) -> Resu
let data_stream = get_stream(Box::new(archive)); let data_stream = get_stream(Box::new(archive));
if let Err(err) = minio.put_object_stream( if let Err(err) = minio
ObjectArgs::new(&config::CONFIG.minio_bucket, filename.clone()), .put_object_stream(
Box::pin(data_stream) ObjectArgs::new(&config::CONFIG.minio_bucket, filename.clone()),
).await { Box::pin(data_stream),
)
.await
{
return Err(Box::new(err)); return Err(Box::new(err));
} }
let link = match minio.presigned_get_object( let link = match minio
PresignedArgs::new(&config::CONFIG.minio_bucket, filename) .presigned_get_object(PresignedArgs::new(&config::CONFIG.minio_bucket, filename))
).await { .await
{
Ok(v) => v, Ok(v) => v,
Err(err) => { Err(err) => {
return Err(Box::new(err)); return Err(Box::new(err));
}, }
}; };
Ok(link) Ok(link)

View File

@@ -1,8 +1,8 @@
pub mod book_library; pub mod book_library;
pub mod download_utils; pub mod download_utils;
pub mod telegram_files;
pub mod downloader; pub mod downloader;
pub mod minio; pub mod minio;
pub mod telegram_files;
use chrono::Duration; use chrono::Duration;
use serde::Serialize; use serde::Serialize;
@@ -10,16 +10,25 @@ use tracing::log;
use crate::{prisma::cached_file, views::Database}; use crate::{prisma::cached_file, views::Database};
use self::{download_utils::{DownloadResult, response_to_tempfile}, telegram_files::{download_from_telegram_files, UploadData, upload_to_telegram_files}, downloader::{get_filename, FilenameData, download_from_downloader}, book_library::{get_book, types::BaseBook, get_books}, minio::upload_to_minio}; use self::{
book_library::{get_book, get_books, types::BaseBook},
download_utils::{response_to_tempfile, DownloadResult},
downloader::{download_from_downloader, get_filename, FilenameData},
minio::upload_to_minio,
telegram_files::{download_from_telegram_files, upload_to_telegram_files, UploadData},
};
pub async fn get_cached_file_or_cache( pub async fn get_cached_file_or_cache(
object_id: i32, object_id: i32,
object_type: String, object_type: String,
db: Database db: Database,
) -> Option<cached_file::Data> { ) -> Option<cached_file::Data> {
let cached_file = db.cached_file() let cached_file = db
.find_unique(cached_file::object_id_object_type(object_id, object_type.clone())) .cached_file()
.find_unique(cached_file::object_id_object_type(
object_id,
object_type.clone(),
))
.exec() .exec()
.await .await
.unwrap(); .unwrap();
@@ -30,80 +39,77 @@ pub async fn get_cached_file_or_cache(
} }
} }
pub async fn cache_file( pub async fn cache_file(
object_id: i32, object_id: i32,
object_type: String, object_type: String,
db: Database db: Database,
) -> Option<cached_file::Data> { ) -> Option<cached_file::Data> {
let book = match get_book(object_id).await { let book = match get_book(object_id).await {
Ok(v) => v, Ok(v) => v,
Err(err) => { Err(err) => {
log::error!("{:?}", err); log::error!("{:?}", err);
return None; return None;
}, }
}; };
let downloader_result = match download_from_downloader( let downloader_result =
book.source.id, match download_from_downloader(book.source.id, book.remote_id, object_type.clone()).await {
book.remote_id, Ok(v) => v,
object_type.clone() Err(err) => {
).await { log::error!("{:?}", err);
return None;
}
};
let UploadData {
chat_id,
message_id,
} = match upload_to_telegram_files(downloader_result, book.get_caption()).await {
Ok(v) => v, Ok(v) => v,
Err(err) => { Err(err) => {
log::error!("{:?}", err); log::error!("{:?}", err);
return None; return None;
}, }
};
let UploadData { chat_id, message_id } = match upload_to_telegram_files(
downloader_result,
book.get_caption()
).await {
Ok(v) => v,
Err(err) => {
log::error!("{:?}", err);
return None;
},
}; };
Some( Some(
db db.cached_file()
.cached_file() .create(object_id, object_type, message_id, chat_id, vec![])
.create( .exec()
object_id, .await
object_type, .unwrap(),
message_id,
chat_id,
vec![]
)
.exec()
.await
.unwrap()
) )
} }
pub async fn download_from_cache( pub async fn download_from_cache(
cached_data: cached_file::Data, cached_data: cached_file::Data,
db: Database db: Database,
) -> Option<DownloadResult> { ) -> Option<DownloadResult> {
let response_task = tokio::task::spawn(download_from_telegram_files(cached_data.message_id, cached_data.chat_id)); let response_task = tokio::task::spawn(download_from_telegram_files(
let filename_task = tokio::task::spawn(get_filename(cached_data.object_id, cached_data.object_type.clone())); cached_data.message_id,
cached_data.chat_id,
));
let filename_task = tokio::task::spawn(get_filename(
cached_data.object_id,
cached_data.object_type.clone(),
));
let book_task = tokio::task::spawn(get_book(cached_data.object_id)); let book_task = tokio::task::spawn(get_book(cached_data.object_id));
let response = match response_task.await.unwrap() { let response = match response_task.await.unwrap() {
Ok(v) => v, Ok(v) => v,
Err(err) => { Err(err) => {
db.cached_file() db.cached_file()
.delete(cached_file::object_id_object_type(cached_data.object_id, cached_data.object_type.clone())) .delete(cached_file::object_id_object_type(
cached_data.object_id,
cached_data.object_type.clone(),
))
.exec() .exec()
.await .await
.unwrap(); .unwrap();
log::error!("{:?}", err); log::error!("{:?}", err);
return None; return None;
}, }
}; };
let filename_data = match filename_task.await.unwrap() { let filename_data = match filename_task.await.unwrap() {
@@ -122,14 +128,17 @@ pub async fn download_from_cache(
} }
}; };
let FilenameData {filename, filename_ascii} = filename_data; let FilenameData {
filename,
filename_ascii,
} = filename_data;
let caption = book.get_caption(); let caption = book.get_caption();
Some(DownloadResult { Some(DownloadResult {
response, response,
filename, filename,
filename_ascii, filename_ascii,
caption caption,
}) })
} }
@@ -138,13 +147,13 @@ pub struct FileLinkResult {
pub link: String, pub link: String,
pub filename: String, pub filename: String,
pub filename_ascii: String, pub filename_ascii: String,
pub caption: String pub caption: String,
} }
pub async fn get_download_link( pub async fn get_download_link(
object_id: i32, object_id: i32,
object_type: String, object_type: String,
db: Database db: Database,
) -> Result<Option<FileLinkResult>, Box<dyn std::error::Error + Send + Sync>> { ) -> Result<Option<FileLinkResult>, Box<dyn std::error::Error + Send + Sync>> {
let cached_file = match get_cached_file_or_cache(object_id, object_type, db.clone()).await { let cached_file = match get_cached_file_or_cache(object_id, object_type, db.clone()).await {
Some(v) => v, Some(v) => v,
@@ -156,7 +165,12 @@ pub async fn get_download_link(
None => return Ok(None), None => return Ok(None),
}; };
let DownloadResult { mut response, filename, filename_ascii, caption } = data; let DownloadResult {
mut response,
filename,
filename_ascii,
caption,
} = data;
let tempfile = match response_to_tempfile(&mut response).await { let tempfile = match response_to_tempfile(&mut response).await {
Some(v) => v.0, Some(v) => v.0,
@@ -172,11 +186,12 @@ pub async fn get_download_link(
link, link,
filename, filename,
filename_ascii, filename_ascii,
caption caption,
})) }))
} }
pub async fn get_books_for_update() -> Result<Vec<BaseBook>, Box<dyn std::error::Error + Send + Sync>> { pub async fn get_books_for_update(
) -> Result<Vec<BaseBook>, Box<dyn std::error::Error + Send + Sync>> {
let mut result: Vec<BaseBook> = vec![]; let mut result: Vec<BaseBook> = vec![];
let page_size = 50; let page_size = 50;
@@ -187,12 +202,8 @@ pub async fn get_books_for_update() -> Result<Vec<BaseBook>, Box<dyn std::error:
let uploaded_gte = subset_3.format("%Y-%m-%d").to_string(); let uploaded_gte = subset_3.format("%Y-%m-%d").to_string();
let uploaded_lte = now.format("%Y-%m-%d").to_string(); let uploaded_lte = now.format("%Y-%m-%d").to_string();
let first_page = match get_books( let first_page = match get_books(1, page_size, uploaded_gte.clone(), uploaded_lte.clone()).await
1, {
page_size,
uploaded_gte.clone(),
uploaded_lte.clone()
).await {
Ok(v) => v, Ok(v) => v,
Err(err) => return Err(err), Err(err) => return Err(err),
}; };
@@ -203,45 +214,51 @@ pub async fn get_books_for_update() -> Result<Vec<BaseBook>, Box<dyn std::error:
let page_count = first_page.pages; let page_count = first_page.pages;
while current_page <= page_count { while current_page <= page_count {
let page = match get_books(current_page, page_size, uploaded_gte.clone(), uploaded_lte.clone()).await { let page = match get_books(
current_page,
page_size,
uploaded_gte.clone(),
uploaded_lte.clone(),
)
.await
{
Ok(v) => v, Ok(v) => v,
Err(err) => return Err(err), Err(err) => return Err(err),
}; };
result.extend(page.items); result.extend(page.items);
current_page += 1; current_page += 1;
}; }
Ok(result) Ok(result)
} }
pub async fn start_update_cache(db: Database) {
pub async fn start_update_cache(
db: Database
) {
let books = match get_books_for_update().await { let books = match get_books_for_update().await {
Ok(v) => v, Ok(v) => v,
Err(err) => { Err(err) => {
log::error!("{:?}", err); log::error!("{:?}", err);
return; return;
}, }
}; };
for book in books { for book in books {
'types: for available_type in book.available_types { 'types: for available_type in book.available_types {
let cached_file = match db let cached_file = match db
.cached_file() .cached_file()
.find_unique( .find_unique(cached_file::object_id_object_type(
cached_file::object_id_object_type(book.id, available_type.clone()) book.id,
) available_type.clone(),
))
.exec() .exec()
.await { .await
Ok(v) => v, {
Err(err) => { Ok(v) => v,
log::error!("{:?}", err); Err(err) => {
continue 'types; log::error!("{:?}", err);
} continue 'types;
}; }
};
if cached_file.is_some() { if cached_file.is_some() {
continue 'types; continue 'types;

View File

@@ -1,27 +1,28 @@
use base64::{engine::general_purpose, Engine}; use base64::{engine::general_purpose, Engine};
use reqwest::{Response, multipart::{Form, Part}, header}; use reqwest::{
header,
multipart::{Form, Part},
Response,
};
use serde::Deserialize; use serde::Deserialize;
use crate::config::CONFIG; use crate::config::CONFIG;
#[derive(Deserialize)] #[derive(Deserialize)]
pub struct UploadData { pub struct UploadData {
pub chat_id: i64, pub chat_id: i64,
pub message_id: i64 pub message_id: i64,
} }
#[derive(Deserialize)] #[derive(Deserialize)]
pub struct UploadResult { pub struct UploadResult {
pub backend: String, pub backend: String,
pub data: UploadData pub data: UploadData,
} }
pub async fn download_from_telegram_files( pub async fn download_from_telegram_files(
message_id: i64, message_id: i64,
chat_id: i64 chat_id: i64,
) -> Result<Response, Box<dyn std::error::Error + Send + Sync>> { ) -> Result<Response, Box<dyn std::error::Error + Send + Sync>> {
let url = format!( let url = format!(
"{}/api/v1/files/download_by_message/{chat_id}/{message_id}", "{}/api/v1/files/download_by_message/{chat_id}/{message_id}",
@@ -38,15 +39,11 @@ pub async fn download_from_telegram_files(
Ok(response) Ok(response)
} }
pub async fn upload_to_telegram_files( pub async fn upload_to_telegram_files(
data_response: Response, data_response: Response,
caption: String caption: String,
) -> Result<UploadData, Box<dyn std::error::Error + Send + Sync>> { ) -> Result<UploadData, Box<dyn std::error::Error + Send + Sync>> {
let url = format!( let url = format!("{}/api/v1/files/upload/", CONFIG.files_url);
"{}/api/v1/files/upload/",
CONFIG.files_url
);
let headers = data_response.headers(); let headers = data_response.headers();
@@ -60,19 +57,14 @@ pub async fn upload_to_telegram_files(
let base64_encoder = general_purpose::STANDARD; let base64_encoder = general_purpose::STANDARD;
let filename = std::str::from_utf8( let filename = std::str::from_utf8(
&base64_encoder &base64_encoder
.decode( .decode(headers.get("x-filename-b64-ascii").unwrap())
headers .unwrap(),
.get("x-filename-b64-ascii") )
.unwrap() .unwrap()
) .to_string();
.unwrap(),
)
.unwrap()
.to_string();
let part = Part::stream(data_response) let part = Part::stream(data_response).file_name(filename.clone());
.file_name(filename.clone());
let form = Form::new() let form = Form::new()
.text("caption", caption) .text("caption", caption)
@@ -90,8 +82,6 @@ pub async fn upload_to_telegram_files(
match response.json::<UploadResult>().await { match response.json::<UploadResult>().await {
Ok(v) => Ok(v.data), Ok(v) => Ok(v.data),
Err(err) => { Err(err) => Err(Box::new(err)),
Err(Box::new(err))
},
} }
} }

View File

@@ -1,13 +1,31 @@
use axum::{Router, response::{Response, IntoResponse, AppendHeaders}, http::{StatusCode, self, Request, header}, middleware::{Next, self}, Extension, routing::{get, delete, post}, extract::Path, Json, body::StreamBody}; use axum::{
body::StreamBody,
extract::Path,
http::{self, header, Request, StatusCode},
middleware::{self, Next},
response::{AppendHeaders, IntoResponse, Response},
routing::{delete, get, post},
Extension, Json, Router,
};
use axum_prometheus::PrometheusMetricLayer; use axum_prometheus::PrometheusMetricLayer;
use tokio_util::io::ReaderStream;
use tower_http::trace::{TraceLayer, self};
use tracing::{Level, log};
use std::sync::Arc;
use base64::{engine::general_purpose, Engine}; use base64::{engine::general_purpose, Engine};
use std::sync::Arc;
use tokio_util::io::ReaderStream;
use tower_http::trace::{self, TraceLayer};
use tracing::{log, Level};
use crate::{config::CONFIG, db::get_prisma_client, prisma::{PrismaClient, cached_file::{self}}, services::{get_cached_file_or_cache, download_from_cache, download_utils::get_response_async_read, start_update_cache, get_download_link}}; use crate::{
config::CONFIG,
db::get_prisma_client,
prisma::{
cached_file::{self},
PrismaClient,
},
services::{
download_from_cache, download_utils::get_response_async_read, get_cached_file_or_cache,
get_download_link, start_update_cache,
},
};
pub type Database = Arc<PrismaClient>; pub type Database = Arc<PrismaClient>;
@@ -15,7 +33,7 @@ pub type Database = Arc<PrismaClient>;
async fn get_cached_file( async fn get_cached_file(
Path((object_id, object_type)): Path<(i32, String)>, Path((object_id, object_type)): Path<(i32, String)>,
Extension(Ext { db, .. }): Extension<Ext> Extension(Ext { db, .. }): Extension<Ext>,
) -> impl IntoResponse { ) -> impl IntoResponse {
match get_cached_file_or_cache(object_id, object_type, db).await { match get_cached_file_or_cache(object_id, object_type, db).await {
Some(cached_file) => Json(cached_file).into_response(), Some(cached_file) => Json(cached_file).into_response(),
@@ -25,20 +43,22 @@ async fn get_cached_file(
async fn download_cached_file( async fn download_cached_file(
Path((object_id, object_type)): Path<(i32, String)>, Path((object_id, object_type)): Path<(i32, String)>,
Extension(Ext { db }): Extension<Ext> Extension(Ext { db }): Extension<Ext>,
) -> impl IntoResponse { ) -> impl IntoResponse {
let cached_file = match get_cached_file_or_cache(object_id, object_type.clone(), db.clone()).await { let cached_file =
Some(cached_file) => cached_file, match get_cached_file_or_cache(object_id, object_type.clone(), db.clone()).await {
None => return StatusCode::NO_CONTENT.into_response(), Some(cached_file) => cached_file,
}; None => return StatusCode::NO_CONTENT.into_response(),
};
let data = match download_from_cache(cached_file, db.clone()).await { let data = match download_from_cache(cached_file, db.clone()).await {
Some(v) => v, Some(v) => v,
None => { None => {
let cached_file = match get_cached_file_or_cache(object_id, object_type, db.clone()).await { let cached_file =
Some(v) => v, match get_cached_file_or_cache(object_id, object_type, db.clone()).await {
None => return StatusCode::NO_CONTENT.into_response(), Some(v) => v,
}; None => return StatusCode::NO_CONTENT.into_response(),
};
match download_from_cache(cached_file, db).await { match download_from_cache(cached_file, db).await {
Some(v) => v, Some(v) => v,
@@ -68,8 +88,8 @@ async fn download_cached_file(
), ),
( (
header::HeaderName::from_static("x-caption-b64"), header::HeaderName::from_static("x-caption-b64"),
encoder.encode(caption) encoder.encode(caption),
) ),
]); ]);
(headers, body).into_response() (headers, body).into_response()
@@ -77,28 +97,30 @@ async fn download_cached_file(
async fn get_link( async fn get_link(
Path((object_id, object_type)): Path<(i32, String)>, Path((object_id, object_type)): Path<(i32, String)>,
Extension(Ext { db, .. }): Extension<Ext> Extension(Ext { db, .. }): Extension<Ext>,
) -> impl IntoResponse { ) -> impl IntoResponse {
match get_download_link(object_id, object_type.clone(), db.clone()).await { match get_download_link(object_id, object_type.clone(), db.clone()).await {
Ok(data) => { Ok(data) => match data {
match data { Some(data) => Json(data).into_response(),
Some(data) => Json(data).into_response(), None => StatusCode::NO_CONTENT.into_response(),
None => StatusCode::NO_CONTENT.into_response(),
}
}, },
Err(err) => { Err(err) => {
log::error!("{:?}", err); log::error!("{:?}", err);
StatusCode::NO_CONTENT.into_response() StatusCode::NO_CONTENT.into_response()
}, }
} }
} }
async fn delete_cached_file( async fn delete_cached_file(
Path((object_id, object_type)): Path<(i32, String)>, Path((object_id, object_type)): Path<(i32, String)>,
Extension(Ext { db, .. }): Extension<Ext> Extension(Ext { db, .. }): Extension<Ext>,
) -> impl IntoResponse { ) -> impl IntoResponse {
let cached_file = db.cached_file() let cached_file = db
.find_unique(cached_file::object_id_object_type(object_id, object_type.clone())) .cached_file()
.find_unique(cached_file::object_id_object_type(
object_id,
object_type.clone(),
))
.exec() .exec()
.await .await
.unwrap(); .unwrap();
@@ -112,14 +134,12 @@ async fn delete_cached_file(
.unwrap(); .unwrap();
Json(v).into_response() Json(v).into_response()
}, }
None => StatusCode::NOT_FOUND.into_response(), None => StatusCode::NOT_FOUND.into_response(),
} }
} }
async fn update_cache( async fn update_cache(Extension(Ext { db, .. }): Extension<Ext>) -> impl IntoResponse {
Extension(Ext { db, .. }): Extension<Ext>
) -> impl IntoResponse {
tokio::spawn(start_update_cache(db)); tokio::spawn(start_update_cache(db));
StatusCode::OK.into_response() StatusCode::OK.into_response()
@@ -127,9 +147,9 @@ async fn update_cache(
// //
async fn auth<B>(req: Request<B>, next: Next<B>) -> Result<Response, StatusCode> { async fn auth<B>(req: Request<B>, next: Next<B>) -> Result<Response, StatusCode> {
let auth_header = req.headers() let auth_header = req
.headers()
.get(http::header::AUTHORIZATION) .get(http::header::AUTHORIZATION)
.and_then(|header| header.to_str().ok()); .and_then(|header| header.to_str().ok());
@@ -146,13 +166,11 @@ async fn auth<B>(req: Request<B>, next: Next<B>) -> Result<Response, StatusCode>
Ok(next.run(req).await) Ok(next.run(req).await)
} }
#[derive(Clone)] #[derive(Clone)]
struct Ext { struct Ext {
pub db: Arc<PrismaClient>, pub db: Arc<PrismaClient>,
} }
pub async fn get_router() -> Router { pub async fn get_router() -> Router {
let db = Arc::new(get_prisma_client().await); let db = Arc::new(get_prisma_client().await);
@@ -162,26 +180,26 @@ pub async fn get_router() -> Router {
let app_router = Router::new() let app_router = Router::new()
.route("/:object_id/:object_type/", get(get_cached_file)) .route("/:object_id/:object_type/", get(get_cached_file))
.route("/download/:object_id/:object_type/", get(download_cached_file)) .route(
"/download/:object_id/:object_type/",
get(download_cached_file),
)
.route("/link/:object_id/:object_type/", get(get_link)) .route("/link/:object_id/:object_type/", get(get_link))
.route("/:object_id/:object_type/", delete(delete_cached_file)) .route("/:object_id/:object_type/", delete(delete_cached_file))
.route("/update_cache", post(update_cache)) .route("/update_cache", post(update_cache))
.layer(middleware::from_fn(auth)) .layer(middleware::from_fn(auth))
.layer(Extension(ext)) .layer(Extension(ext))
.layer(prometheus_layer); .layer(prometheus_layer);
let metric_router = Router::new() let metric_router =
.route("/metrics", get(|| async move { metric_handle.render() })); Router::new().route("/metrics", get(|| async move { metric_handle.render() }));
Router::new() Router::new()
.nest("/api/v1/", app_router) .nest("/api/v1/", app_router)
.nest("/", metric_router) .nest("/", metric_router)
.layer( .layer(
TraceLayer::new_for_http() TraceLayer::new_for_http()
.make_span_with(trace::DefaultMakeSpan::new() .make_span_with(trace::DefaultMakeSpan::new().level(Level::INFO))
.level(Level::INFO)) .on_response(trace::DefaultOnResponse::new().level(Level::INFO)),
.on_response(trace::DefaultOnResponse::new()
.level(Level::INFO)),
) )
} }