use actix_web::error::ErrorNotFound; use actix_web::{HttpRequest, HttpResponse, Result, error::ErrorInternalServerError, web}; use log::{error, info, warn}; use super::common::{ create_file_response_with_analytics, create_vercel_compatible_response, is_vercel_request, }; use crate::app_state::AppState; use crate::handlers::serve_file::serve_file; use crate::lookup::{find_file_by_pattern, get_mime_type}; use crate::s3_utils::{check_file_exists, load_file_from_s3, upload_to_s3}; use crate::thumbnail::parse_file_path; // Удалена дублирующая функция, используется из common модуля /// Обработчик для скачивания файла /// без генерации миниатюр - это делает Vercel Edge API #[allow(clippy::collapsible_if)] pub async fn proxy_handler( req: HttpRequest, requested_res: web::Path, state: web::Data, ) -> Result { let start_time = std::time::Instant::now(); info!("GET {} [START]", requested_res); let normalized_path = if requested_res.ends_with("/webp") { info!("Converting to WebP format: {}", requested_res); requested_res.replace("/webp", "") } else { requested_res.to_string() }; // Парсим GET запрос let (base_filename, requested_width, extension) = parse_file_path(&normalized_path); let ext = extension.as_str().to_lowercase(); let filekey = format!("{}.{}", base_filename, &ext); info!( "Parsed request - base: {}, width: {}, ext: {}", base_filename, requested_width, ext ); // Caching handled by Vercel Edge - focus on fast file serving let content_type = match get_mime_type(&ext) { Some(mime) => mime.to_string(), None => match find_file_by_pattern(None, &base_filename).await { Ok(Some(found_file)) => { if let Some(found_ext) = found_file.split('.').next_back() { get_mime_type(found_ext) .unwrap_or("application/octet-stream") .to_string() } else { "application/octet-stream".to_string() } } _ => { error!("Unsupported file format for: {}", base_filename); return Err(ErrorInternalServerError("Unsupported file format")); } }, }; info!("Content-Type: {}", content_type); return match state.get_path(&filekey).await { Ok(Some(stored_path)) => { warn!("Found stored path in DB: {}", stored_path); warn!( "Checking Storj path - bucket: {}, path: {}", state.bucket, stored_path ); if check_file_exists(&state.storj_client, &state.bucket, &stored_path).await? { warn!("File exists in Storj: {}", stored_path); // Просто отдаем файл, миниатюры генерирует Vercel Edge API serve_file(&stored_path, &state, &req).await } else { warn!( "Attempting to load from AWS - bucket: {}, path: {}", state.bucket, stored_path ); // Определяем тип медиа из content_type let media_type = content_type.split("/").next().unwrap_or("image"); // Создаем варианты путей с обоими регистрами расширения let paths_lower = vec![ stored_path.clone(), // format!("production/{}", stored_path), format!("production/{}/{}", media_type, stored_path), ]; // Создаем те же пути, но с оригинальным регистром расширения let orig_ext = extension.as_str(); // оригинальное расширение let orig_stored_path = format!("{}.{}", base_filename, orig_ext); let paths_orig = vec![ orig_stored_path.clone(), // format!("production/{}", orig_stored_path), format!("production/{}/{}", media_type, orig_stored_path), ]; // Объединяем все пути для проверки let all_paths = paths_lower.into_iter().chain(paths_orig.into_iter()); for path in all_paths { warn!("Trying AWS path: {}", path); match load_file_from_s3(&state.aws_client, &state.bucket, &path).await { Ok(filedata) => { warn!( "Successfully loaded file from AWS, size: {} bytes", filedata.len() ); warn!("Attempting to upload to Storj with key: {}", filekey); if let Err(e) = upload_to_s3( &state.storj_client, &state.bucket, &filekey, filedata.clone(), &content_type, ) .await { error!("Failed to upload to Storj: {} - Error: {}", filekey, e); } else { warn!("Successfully uploaded to Storj: {}", filekey); } let elapsed = start_time.elapsed(); info!("File served from AWS in {:?}: {}", elapsed, path); // Используем Vercel-совместимый ответ для Vercel запросов if is_vercel_request(&req) { let etag = format!("\"{:x}\"", md5::compute(&filedata)); return Ok(create_vercel_compatible_response( &content_type, filedata, &etag, )); } else { return Ok(create_file_response_with_analytics( &content_type, filedata, &req, &path, )); } } Err(err) => { warn!("Failed to load from AWS path {}: {:?}", path, err); continue; } } } error!("Failed to load from any AWS path for: {}", stored_path); Err(ErrorInternalServerError("Failed to load file from AWS")) } } Ok(None) => { warn!("No stored path found in DB for: {}", filekey); let ct_parts = content_type.split("/").collect::>(); // Создаем два варианта пути - с оригинальным расширением и с нижним регистром let filepath_lower = format!("production/{}/{}.{}", ct_parts[0], base_filename, ext); let filepath_orig = format!("production/{}/{}.{}", ct_parts[0], base_filename, extension); warn!( "Looking up files with paths: {} or {} in bucket: {}", filepath_lower, filepath_orig, state.bucket ); // Проверяем существование файла с обоими вариантами расширения let exists_in_aws_lower = check_file_exists(&state.aws_client, &state.bucket, &filepath_lower).await?; let exists_in_aws_orig = check_file_exists(&state.aws_client, &state.bucket, &filepath_orig).await?; let filepath = if exists_in_aws_orig { filepath_orig } else if exists_in_aws_lower { filepath_lower } else { // Если файл не найден ни с одним из расширений, используем нижний регистр по умолчанию filepath_lower }; let exists_in_storj = check_file_exists(&state.storj_client, &state.bucket, &filepath).await?; warn!("Checking existence in Storj: {}", exists_in_storj); if exists_in_storj { warn!("file {} exists in storj, serving directly", filepath); // Файл существует в Storj, отдаем его напрямую return serve_file(&filepath, &state, &req).await; } else { warn!("file {} does not exist in storj", filepath); } let exists_in_aws = check_file_exists(&state.aws_client, &state.bucket, &filepath).await?; warn!("Checking existence in AWS: {}", exists_in_aws); if exists_in_aws { warn!("File found in AWS, attempting to download: {}", filepath); match load_file_from_s3(&state.aws_client, &state.bucket, &filepath).await { Ok(filedata) => { warn!( "Successfully downloaded file from AWS, size: {} bytes", filedata.len() ); if let Err(e) = upload_to_s3( &state.storj_client, &state.bucket, &filekey, filedata.clone(), &content_type, ) .await { warn!("cannot upload to storj: {}", e); } else { warn!("file {} uploaded to storj", filekey); state.set_path(&filekey, &filepath).await; } let elapsed = start_time.elapsed(); info!("File served from AWS in {:?}: {}", elapsed, filepath); // Используем Vercel-совместимый ответ для Vercel запросов if is_vercel_request(&req) { let etag = format!("\"{:x}\"", md5::compute(&filedata)); Ok(create_vercel_compatible_response( &content_type, filedata, &etag, )) } else { Ok(create_file_response_with_analytics( &content_type, filedata, &req, &filepath, )) } } Err(e) => { error!("Failed to download from AWS: {} - Error: {}", filepath, e); Err(ErrorInternalServerError(e)) } } } else { error!("File not found in either Storj or AWS: {}", filepath); Err(ErrorNotFound("file does not exist")) } } Err(e) => { let elapsed = start_time.elapsed(); error!( "Database error while getting path: {} in {:?} - Full error: {:?}", filekey, elapsed, e ); Err(ErrorInternalServerError(e)) } }; }