// 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 { // Проверяем наличие файла в 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, ) -> Result { // Получаем токен из заголовка авторизации 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, state: web::Data, ) -> Result { // весь запрошенный путь 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 }