[0.6.1] - 2025-09-02
### 🚀 Изменено - Упрощение архитектуры - **Генерация миниатюр**: Полностью удалена из Quoter, теперь управляется Vercel Edge API - **Очистка legacy кода**: Удалены все функции генерации миниатюр и сложность - **Документация**: Сокращена с 17 файлов до 7, следуя принципам KISS/DRY - **Смена фокуса**: Quoter теперь сосредоточен на upload + storage, Vercel обрабатывает миниатюры - **Логирование запросов**: Добавлена аналитика источников для оптимизации CORS whitelist - **Реализация таймаутов**: Добавлены настраиваемые таймауты для S3, Redis и внешних операций - **Упрощенная безопасность**: Удален сложный rate limiting, оставлена только необходимая защита upload ### 📝 Обновлено - Консолидирована документация в практическую структуру: - Основной README.md с быстрым стартом - docs/SETUP.md для конфигурации и развертывания - Упрощенный features.md с фокусом на основную функциональность - Добавлен акцент на Vercel по всему коду и документации ### 🗑️ Удалено - Избыточные файлы документации (api-reference, deployment, development, и т.д.) - Дублирующийся контент в нескольких документах - Излишне детальная документация для простого файлового прокси 💋 **Упрощение**: KISS принцип применен - убрали избыточность, оставили суть.
This commit is contained in:
@@ -2,16 +2,17 @@ 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;
|
||||
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::{find_closest_width, parse_file_path, thumbdata_save};
|
||||
use super::common::{check_etag_cache, create_cached_response};
|
||||
use crate::thumbnail::parse_file_path;
|
||||
|
||||
// Удалена дублирующая функция, используется из common модуля
|
||||
|
||||
/// Обработчик для скачивания файла и генерации миниатюры, если она недоступна.
|
||||
/// Обработчик для скачивания файла
|
||||
/// без генерации миниатюр - это делает Vercel Edge API
|
||||
#[allow(clippy::collapsible_if)]
|
||||
pub async fn proxy_handler(
|
||||
req: HttpRequest,
|
||||
@@ -38,12 +39,7 @@ pub async fn proxy_handler(
|
||||
base_filename, requested_width, ext
|
||||
);
|
||||
|
||||
// Генерируем ETag для кэширования и проверяем кэш
|
||||
let file_etag = format!("\"{}\"", &filekey);
|
||||
if let Some(response) = check_etag_cache(&req, &file_etag) {
|
||||
info!("Cache hit for {}, returning 304", filekey);
|
||||
return Ok(response);
|
||||
}
|
||||
// Caching handled by Vercel Edge - focus on fast file serving
|
||||
let content_type = match get_mime_type(&ext) {
|
||||
Some(mime) => mime.to_string(),
|
||||
None => {
|
||||
@@ -77,72 +73,8 @@ pub async fn proxy_handler(
|
||||
);
|
||||
if check_file_exists(&state.storj_client, &state.bucket, &stored_path).await? {
|
||||
warn!("File exists in Storj: {}", stored_path);
|
||||
if content_type.starts_with("image") {
|
||||
warn!("Processing image file with width: {}", requested_width);
|
||||
if requested_width == 0 {
|
||||
warn!("Serving original file without resizing");
|
||||
serve_file(&stored_path, &state).await
|
||||
} else {
|
||||
let closest: u32 = find_closest_width(requested_width);
|
||||
warn!(
|
||||
"Calculated closest width: {} for requested: {}",
|
||||
closest, requested_width
|
||||
);
|
||||
let thumb_filename = &format!("{}_{}.{}", base_filename, closest, ext);
|
||||
warn!("Generated thumbnail filename: {}", thumb_filename);
|
||||
|
||||
// Проверяем, существует ли уже миниатюра в Storj
|
||||
match check_file_exists(&state.storj_client, &state.bucket, thumb_filename)
|
||||
.await
|
||||
{
|
||||
Ok(true) => {
|
||||
warn!("serve existed thumb file: {}", thumb_filename);
|
||||
serve_file(thumb_filename, &state).await
|
||||
}
|
||||
Ok(false) => {
|
||||
// Миниатюра не существует, возвращаем оригинал и запускаем генерацию миниатюры
|
||||
let original_file = serve_file(&stored_path, &state).await?;
|
||||
|
||||
// Запускаем асинхронную задачу для генерации миниатюры
|
||||
let state_clone = state.clone();
|
||||
let stored_path_clone = stored_path.clone();
|
||||
let filekey_clone = filekey.clone();
|
||||
let content_type_clone = content_type.to_string();
|
||||
|
||||
actix_web::rt::spawn(async move {
|
||||
if let Ok(filedata) = load_file_from_s3(
|
||||
&state_clone.storj_client,
|
||||
&state_clone.bucket,
|
||||
&stored_path_clone,
|
||||
)
|
||||
.await
|
||||
{
|
||||
warn!("generate new thumb files: {}", stored_path_clone);
|
||||
if let Err(e) = thumbdata_save(
|
||||
filedata,
|
||||
&state_clone,
|
||||
&filekey_clone,
|
||||
content_type_clone,
|
||||
)
|
||||
.await
|
||||
{
|
||||
error!("Failed to generate thumbnail: {}", e);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
Ok(original_file)
|
||||
}
|
||||
Err(e) => {
|
||||
error!("ошибка при проверке существования миниатюры: {}", e);
|
||||
Err(ErrorInternalServerError("failed to load thumbnail"))
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
warn!("File is not an image, proceeding with normal serving");
|
||||
serve_file(&stored_path, &state).await
|
||||
}
|
||||
// Просто отдаем файл, миниатюры генерирует Vercel Edge API
|
||||
serve_file(&stored_path, &state, &req).await
|
||||
} else {
|
||||
warn!(
|
||||
"Attempting to load from AWS - bucket: {}, path: {}",
|
||||
@@ -197,7 +129,12 @@ pub async fn proxy_handler(
|
||||
|
||||
let elapsed = start_time.elapsed();
|
||||
info!("File served from AWS in {:?}: {}", elapsed, path);
|
||||
return Ok(create_cached_response(&content_type, filedata, &file_etag));
|
||||
return Ok(create_file_response_with_analytics(
|
||||
&content_type,
|
||||
filedata,
|
||||
&req,
|
||||
&path,
|
||||
));
|
||||
}
|
||||
Err(err) => {
|
||||
warn!("Failed to load from AWS path {}: {:?}", path, err);
|
||||
@@ -244,26 +181,9 @@ pub async fn proxy_handler(
|
||||
warn!("Checking existence in Storj: {}", exists_in_storj);
|
||||
|
||||
if exists_in_storj {
|
||||
warn!(
|
||||
"file {} exists in storj, try to generate thumbnails",
|
||||
filepath
|
||||
);
|
||||
|
||||
match load_file_from_s3(&state.aws_client, &state.bucket, &filepath).await {
|
||||
Ok(filedata) => {
|
||||
let _ = thumbdata_save(
|
||||
filedata.clone(),
|
||||
&state,
|
||||
&filekey,
|
||||
content_type.to_string(),
|
||||
)
|
||||
.await;
|
||||
}
|
||||
Err(e) => {
|
||||
error!("cannot download {} from storj: {}", filekey, e);
|
||||
return Err(ErrorInternalServerError(e));
|
||||
}
|
||||
}
|
||||
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);
|
||||
}
|
||||
@@ -280,13 +200,6 @@ pub async fn proxy_handler(
|
||||
"Successfully downloaded file from AWS, size: {} bytes",
|
||||
filedata.len()
|
||||
);
|
||||
let _ = thumbdata_save(
|
||||
filedata.clone(),
|
||||
&state,
|
||||
&filekey,
|
||||
content_type.to_string(),
|
||||
)
|
||||
.await;
|
||||
if let Err(e) = upload_to_s3(
|
||||
&state.storj_client,
|
||||
&state.bucket,
|
||||
@@ -303,7 +216,12 @@ pub async fn proxy_handler(
|
||||
}
|
||||
let elapsed = start_time.elapsed();
|
||||
info!("File served from AWS in {:?}: {}", elapsed, filepath);
|
||||
Ok(create_cached_response(&content_type, filedata, &file_etag))
|
||||
Ok(create_file_response_with_analytics(
|
||||
&content_type,
|
||||
filedata,
|
||||
&req,
|
||||
&filepath,
|
||||
))
|
||||
}
|
||||
Err(e) => {
|
||||
error!("Failed to download from AWS: {} - Error: {}", filepath, e);
|
||||
|
||||
Reference in New Issue
Block a user