Files
quoter/src/handlers.rs
Untone 3ff90ba4f3
Some checks failed
deploy / deploy (push) Failing after 4s
0.0.3-desirable
2024-08-31 03:32:37 +03:00

184 lines
7.5 KiB
Rust
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// handlers.rs
use crate::app_state::AppState;
use crate::auth::{get_id_by_token, user_added_file};
use crate::s3_utils::{
check_file_exists, generate_key_with_extension, load_file_from_s3, upload_to_s3,
};
use crate::thumbnail::{find_closest_width, generate_thumbnails, parse_thumbnail_request, ALLOWED_THUMBNAIL_WIDTHS};
use actix_multipart::Multipart;
use actix_web::error::ErrorInternalServerError;
use actix_web::{web, HttpRequest, HttpResponse, Result};
use futures::StreamExt;
use mime_guess::MimeGuess;
pub const MAX_WEEK_BYTES: u64 = 2 * 1024 * 1024 * 1024; // Лимит квоты на пользователя: 2 ГБ в неделю
/// Функция для обслуживания файла по заданному пути.
async fn serve_file(file_key: &str, state: &AppState) -> Result<HttpResponse, actix_web::Error> {
// Проверяем наличие файла в Storj S3
if !check_file_exists(&state.s3_client, &state.s3_bucket, file_key).await? {
return Err(ErrorInternalServerError("File not found in S3"));
}
let checked_filekey = state.get_path(file_key).await.unwrap().unwrap();
// Получаем объект из Storj S3
let get_object_output = state
.s3_client
.get_object()
.bucket(&state.s3_bucket)
.key(checked_filekey)
.send()
.await
.map_err(|_| ErrorInternalServerError("Failed to get object from S3"))?;
let data: aws_sdk_s3::primitives::AggregatedBytes = get_object_output
.body
.collect()
.await
.map_err(|_| ErrorInternalServerError("Failed to read object body"))?;
let data_bytes = data.into_bytes();
let mime_type = MimeGuess::from_path(file_key).first_or_octet_stream(); // Определяем MIME-тип файла
Ok(HttpResponse::Ok()
.content_type(mime_type.as_ref())
.body(data_bytes))
}
/// Обработчик для аплоада файлов.
pub async fn upload_handler(
req: HttpRequest,
mut payload: Multipart,
state: web::Data<AppState>,
) -> Result<HttpResponse, actix_web::Error> {
// Получаем токен из заголовка авторизации
let token = req
.headers()
.get("Authorization")
.and_then(|header_value| header_value.to_str().ok());
if token.is_none() {
return Err(actix_web::error::ErrorUnauthorized("Unauthorized")); // Если токен отсутствует, возвращаем ошибку
}
let user_id = get_id_by_token(token.unwrap()).await?;
// Получаем текущую квоту пользователя
let this_week_amount: u64 = state.get_or_create_quota(&user_id).await.unwrap_or(0);
while let Some(field) = payload.next().await {
let mut field = field?;
let content_type = field.content_type().unwrap().to_string();
let file_name = field
.content_disposition()
.unwrap()
.get_filename()
.map(|f| f.to_string());
if let Some(name) = file_name {
let mut file_bytes = Vec::new();
let mut file_size: u64 = 0;
// Читаем данные файла
while let Some(chunk) = field.next().await {
let data = chunk?;
file_size += data.len() as u64;
file_bytes.extend_from_slice(&data);
}
// Проверяем, что добавление файла не превышает лимит квоты
if this_week_amount + file_size > MAX_WEEK_BYTES {
return Err(actix_web::error::ErrorUnauthorized("Quota exceeded"));
// Квота превышена
}
// Инкрементируем квоту пользователя
let _ = state.increment_uploaded_bytes(&user_id, file_size).await?;
// Определяем правильное расширение и ключ для S3
let file_key = generate_key_with_extension(name, content_type.to_owned());
// Загружаем файл в S3
upload_to_s3(
&state.s3_client,
&state.s3_bucket,
&file_key,
file_bytes,
&content_type,
)
.await?;
// Сохраняем информацию о загруженном файле для пользователя
user_added_file(&mut state.redis.clone(), &user_id, &file_key).await?;
}
}
Ok(HttpResponse::Ok().json("File uploaded successfully"))
}
/// Обработчик для скачивания файла и генерации миниатюры, если она недоступна.
pub async fn proxy_handler(
_req: HttpRequest,
path: web::Path<String>,
state: web::Data<AppState>,
) -> Result<HttpResponse, actix_web::Error> {
// весь запрошенный путь
let requested_path = state.get_path(&path).await.unwrap().unwrap();
// имя файла
let filename_with_extension = requested_path.split("/").last().unwrap();
// убираем расширение файла
let requested_filekey = filename_with_extension
.rsplit_once('.')
.map(|(name, _ext)| name)
.unwrap_or(filename_with_extension); // Если расширение отсутствует, возвращаем оригинальное имя
// Проверяем, запрошена ли миниатюра
if let Some((base_filename, requested_width, _ext)) =
parse_thumbnail_request(&requested_filekey)
{
// Находим ближайший подходящий размер
let closest_width = find_closest_width(requested_width);
let thumbnail_key = format!("{}_{}", base_filename, closest_width);
// Проверяем наличие миниатюры в кэше
let cached_files = state.get_cached_file_list().await;
if !cached_files.contains(&thumbnail_key) {
if cached_files.contains(&base_filename) {
// Загружаем оригинальный файл из S3
let original_data =
load_file_from_s3(&state.s3_client, &state.s3_bucket, &base_filename).await?;
// Генерируем миниатюру для ближайшего подходящего размера
let image = image::load_from_memory(&original_data).map_err(|_| {
ErrorInternalServerError("Failed to load image for thumbnail generation")
})?;
let thumbnails_bytes =
generate_thumbnails(&image, &ALLOWED_THUMBNAIL_WIDTHS).await?;
let thumbnail_bytes = thumbnails_bytes[&closest_width].clone();
// Загружаем миниатюру в S3
upload_to_s3(
&state.s3_client,
&state.s3_bucket,
&thumbnail_key,
thumbnail_bytes.clone(),
"image/jpeg",
)
.await?;
return Ok(HttpResponse::Ok()
.content_type("image/jpeg")
.body(thumbnail_bytes));
}
} else {
// Если миниатюра уже есть в кэше, просто возвращаем её
return serve_file(&thumbnail_key, &state).await;
}
}
// Если запрошен целый файл
serve_file(&requested_filekey, &state).await
}