use actix_multipart::Multipart; use actix_web::{web, HttpRequest, HttpResponse, Result}; use log::{error, warn}; use crate::app_state::AppState; use crate::auth::{get_id_by_token, user_added_file}; use crate::handlers::MAX_USER_QUOTA_BYTES; use crate::lookup::store_file_info; use crate::s3_utils::{self, generate_key_with_extension, upload_to_s3}; use futures::TryStreamExt; // use crate::thumbnail::convert_heic_to_jpeg; /// Обработчик для аплоада файлов. 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 current_quota: u64 = state.get_or_create_quota(&user_id).await.unwrap_or(0); let mut body = "ok".to_string(); while let Ok(Some(field)) = payload.try_next().await { let mut field = field; let mut file_bytes = Vec::new(); let mut file_size: u64 = 0; // Читаем данные файла while let Ok(Some(chunk)) = field.try_next().await { file_size += chunk.len() as u64; file_bytes.extend_from_slice(&chunk); } // Определяем MIME-тип из содержимого файла let detected_mime_type = match s3_utils::detect_mime_type(&file_bytes) { Some(mime) => mime, None => { warn!("Неподдерживаемый формат файла"); return Err(actix_web::error::ErrorUnsupportedMediaType( "Неподдерживаемый формат файла", )); } }; // Для HEIC файлов просто сохраняем как есть let (file_bytes, content_type) = if detected_mime_type == "image/heic" { warn!("HEIC support is temporarily disabled, saving original file"); (file_bytes, detected_mime_type) } else { (file_bytes, detected_mime_type) }; // Получаем расширение из MIME-типа let extension = match s3_utils::get_extension_from_mime(&content_type) { Some(ext) => ext, None => { warn!("Неподдерживаемый тип содержимого: {}", content_type); return Err(actix_web::error::ErrorUnsupportedMediaType( "Неподдерживаемый тип содержимого", )); } }; // Проверяем, что добавление файла не превышает лимит квоты if current_quota + file_size > MAX_USER_QUOTA_BYTES { warn!( "Quota would exceed limit: current={}, adding={}, limit={}", current_quota, file_size, MAX_USER_QUOTA_BYTES ); return Err(actix_web::error::ErrorUnauthorized("Quota exceeded")); } // Генерируем имя файла с правильным расширением let filename = format!("{}.{}", uuid::Uuid::new_v4(), extension); // Загружаем файл в S3 storj match upload_to_s3( &state.storj_client, &state.bucket, &filename, file_bytes, &content_type, ) .await { Ok(_) => { warn!( "file {} uploaded to storj, incrementing quota by {} bytes", filename, file_size ); if let Err(e) = state.increment_uploaded_bytes(&user_id, file_size).await { error!("Failed to increment quota: {}", e); return Err(e); } // Сохраняем информацию о файле в Redis let mut redis = state.redis.clone(); store_file_info(&mut redis, &filename, &content_type).await?; user_added_file(&mut redis, &user_id, &filename).await?; // Сохраняем маппинг пути let generated_key = generate_key_with_extension(filename.clone(), content_type.clone()); state.set_path(&filename, &generated_key).await; if let Ok(new_quota) = state.get_or_create_quota(&user_id).await { warn!("New quota for user {}: {} bytes", user_id, new_quota); } body = filename; } Err(e) => { warn!("Failed to upload to storj: {}", e); return Err(actix_web::error::ErrorInternalServerError(e)); } } } Ok(HttpResponse::Ok().body(body)) }