[0.6.1] - 2025-09-02
Some checks failed
CI / lint (push) Successful in 2m11s
Deploy / deploy (push) Has been skipped
CI / test (push) Failing after 9m6s

### 🚀 Изменено - Упрощение архитектуры
- **Генерация миниатюр**: Полностью удалена из Quoter, теперь управляется Vercel Edge API
- **Очистка legacy кода**: Удалены все функции генерации миниатюр и сложность
- **Документация**: Сокращена с 17 файлов до 7, следуя принципам KISS/DRY
- **Смена фокуса**: Quoter теперь сосредоточен на upload + storage, Vercel обрабатывает миниатюры
- **Логирование запросов**: Добавлена аналитика источников для оптимизации CORS whitelist
- **Реализация таймаутов**: Добавлены настраиваемые таймауты для S3, Redis и внешних операций
- **Упрощенная безопасность**: Удален сложный rate limiting, оставлена только необходимая защита upload
- **Vercel интеграция**: Добавлена поддержка Vercel Edge API с CORS и оптимизированными заголовками
- **Redis graceful fallback**: Приложение теперь работает без Redis с предупреждениями вместо паники
- **Умная логика ответов**: Автоматическое определение Vercel запросов и оптимизированные заголовки
- **Консолидация документации**: Объединены 4 Vercel документа в один comprehensive guide

### 📝 Обновлено
- Консолидирована документация в практическую структуру:
  - Основной README.md с быстрым стартом
  - docs/SETUP.md для конфигурации и развертывания
  - Упрощенный features.md с фокусом на основную функциональность
  - docs/vercel-frontend-migration.md - единый comprehensive guide для Vercel интеграции
- Добавлен акцент на Vercel по всему коду и документации
- Обновлены URL patterns в документации: quoter.discours.io → files.dscrs.site

### 🗑️ Удалено
- Избыточные файлы документации (api-reference, deployment, development, и т.д.)
- Дублирующийся контент в нескольких документах
- Излишне детальная документация для простого файлового прокси
- 4 отдельных Vercel документа: vercel-thumbnails.md, vercel-integration.md, hybrid-architecture.md, vercel-og-integration.md

💋 **Упрощение**: KISS принцип применен - убрали избыточность, оставили суть.
This commit is contained in:
2025-09-02 14:39:54 +03:00
parent 7973ba0027
commit 7746d1f38e
21 changed files with 458 additions and 863 deletions

View File

@@ -9,7 +9,7 @@ use std::{env, time::Duration};
#[derive(Clone)]
pub struct AppState {
pub redis: MultiplexedConnection,
pub redis: Option<MultiplexedConnection>,
pub storj_client: S3Client,
pub aws_client: S3Client,
pub bucket: String,
@@ -32,15 +32,28 @@ impl AppState {
let redis_url = env::var("REDIS_URL").expect("REDIS_URL must be set");
let redis_client = RedisClient::open(redis_url).expect("Invalid Redis URL");
// Устанавливаем таймаут для Redis операций
let redis_connection = tokio::time::timeout(
// Устанавливаем таймаут для Redis операций с graceful fallback
let redis_connection = match tokio::time::timeout(
Duration::from_secs(security_config.request_timeout_seconds),
redis_client.get_multiplexed_async_connection(),
)
.await
.map_err(|_| "Redis connection timeout")
.expect("Failed to connect to Redis within timeout")
.expect("Redis connection failed");
{
Ok(Ok(conn)) => {
log::info!("Redis connection established");
Some(conn)
}
Ok(Err(e)) => {
log::warn!("⚠️ Redis connection failed: {}", e);
log::warn!("⚠️ Running in fallback mode without Redis (quotas disabled)");
None
}
Err(_) => {
log::warn!("⚠️ Redis connection timeout");
log::warn!("⚠️ Running in fallback mode without Redis (quotas disabled)");
None
}
};
// Получаем конфигурацию для S3 (Storj)
let s3_access_key = env::var("STORJ_ACCESS_KEY").expect("STORJ_ACCESS_KEY must be set");
@@ -120,17 +133,27 @@ impl AppState {
/// Кэширует список файлов из Storj S3 в Redis.
pub async fn cache_filelist(&self) {
warn!("caching AWS filelist...");
let mut redis = self.redis.clone();
// Проверяем доступность Redis
let Some(mut redis) = self.redis.clone() else {
warn!("⚠️ Redis not available, skipping filelist caching");
return;
};
// Запрашиваем список файлов из Storj S3
let filelist = get_s3_filelist(&self.aws_client, &self.bucket).await;
for [filename, filepath] in filelist.clone() {
// Сохраняем список файлов в Redis, используя HSET для каждого файла
let _: () = redis
.hset(PATH_MAPPING_KEY, filename.clone(), filepath)
.await
.unwrap();
if let Err(e) = tokio::time::timeout(
self.request_timeout,
redis.hset::<_, _, _, ()>(PATH_MAPPING_KEY, filename.clone(), filepath),
)
.await
{
warn!("⚠️ Redis operation failed: {}", e);
break;
}
}
warn!("cached {} files", filelist.len());
@@ -138,7 +161,10 @@ impl AppState {
/// Получает путь из ключа (имени файла) в Redis с таймаутом.
pub async fn get_path(&self, filename: &str) -> Result<Option<String>, actix_web::Error> {
let mut redis = self.redis.clone();
let Some(mut redis) = self.redis.clone() else {
warn!("⚠️ Redis not available, returning None for path lookup");
return Ok(None);
};
let new_path: Option<String> =
tokio::time::timeout(self.request_timeout, redis.hget(PATH_MAPPING_KEY, filename))
@@ -150,20 +176,33 @@ impl AppState {
}
pub async fn set_path(&self, filename: &str, filepath: &str) {
let mut redis = self.redis.clone();
let Some(mut redis) = self.redis.clone() else {
warn!(
"⚠️ Redis not available, skipping path caching for {}",
filename
);
return;
};
let _: () = tokio::time::timeout(
if let Err(e) = tokio::time::timeout(
self.request_timeout,
redis.hset(PATH_MAPPING_KEY, filename, filepath),
redis.hset::<_, _, _, ()>(PATH_MAPPING_KEY, filename, filepath),
)
.await
.unwrap_or_else(|_| panic!("Redis timeout when caching file {} in Redis", filename))
.unwrap_or_else(|_| panic!("Failed to cache file {} in Redis", filename));
{
warn!("⚠️ Redis operation failed for {}: {}", filename, e);
}
}
/// создает или получает текущее значение квоты пользователя с таймаутом
pub async fn get_or_create_quota(&self, user_id: &str) -> Result<u64, actix_web::Error> {
let mut redis = self.redis.clone();
let Some(mut redis) = self.redis.clone() else {
warn!(
"⚠️ Redis not available, returning default quota for user {}",
user_id
);
return Ok(0); // Возвращаем 0 как fallback
};
let quota_key = format!("quota:{}", user_id);
// Попытка получить квоту из Redis с таймаутом
@@ -194,7 +233,13 @@ impl AppState {
user_id: &str,
bytes: u64,
) -> Result<u64, actix_web::Error> {
let mut redis = self.redis.clone();
let Some(mut redis) = self.redis.clone() else {
warn!(
"⚠️ Redis not available, skipping quota increment for user {}",
user_id
);
return Ok(0); // Возвращаем 0 как fallback
};
let quota_key = format!("quota:{}", user_id);
// Проверяем, существует ли ключ в Redis с таймаутом