[0.6.1] - 2025-09-02
Some checks failed
Deploy / deploy (push) Has been skipped
CI / lint (push) Failing after 8s
CI / test (push) Failing after 10m26s

### 🚀 Изменено - Упрощение архитектуры
- **Генерация миниатюр**: Полностью удалена из 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:
2025-09-02 14:00:54 +03:00
parent b876564f4a
commit 7973ba0027
32 changed files with 1168 additions and 3855 deletions

View File

@@ -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);