feat(api): relier les settings DB au comportement runtime
- Ajout de DynamicSettings dans AppState (Arc<RwLock>) chargé depuis la DB - rate_limit_per_second, timeout_seconds : plus hardcodés, lus depuis settings - image_processing (format, quality, filter, max_width) : appliqués comme valeurs par défaut sur les requêtes de pages (overridables via query params) - cache.directory : lu depuis settings au lieu de la variable d'env - update_setting recharge immédiatement le DynamicSettings en mémoire pour les clés limits, image_processing et cache (sans redémarrage) - parse_filter() : mapping lanczos3/triangle/nearest → FilterType Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -6,7 +6,7 @@ use std::time::Instant;
|
||||
|
||||
use lru::LruCache;
|
||||
use sqlx::{Pool, Postgres, Row};
|
||||
use tokio::sync::{Mutex, Semaphore};
|
||||
use tokio::sync::{Mutex, RwLock, Semaphore};
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct AppState {
|
||||
@@ -18,6 +18,33 @@ pub struct AppState {
|
||||
pub page_render_limit: Arc<Semaphore>,
|
||||
pub metrics: Arc<Metrics>,
|
||||
pub read_rate_limit: Arc<Mutex<ReadRateLimit>>,
|
||||
pub settings: Arc<RwLock<DynamicSettings>>,
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct DynamicSettings {
|
||||
pub rate_limit_per_second: u32,
|
||||
pub timeout_seconds: u64,
|
||||
pub image_format: String,
|
||||
pub image_quality: u8,
|
||||
pub image_filter: String,
|
||||
pub image_max_width: u32,
|
||||
pub cache_directory: String,
|
||||
}
|
||||
|
||||
impl Default for DynamicSettings {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
rate_limit_per_second: 120,
|
||||
timeout_seconds: 12,
|
||||
image_format: "webp".to_string(),
|
||||
image_quality: 85,
|
||||
image_filter: "lanczos3".to_string(),
|
||||
image_max_width: 2160,
|
||||
cache_directory: std::env::var("IMAGE_CACHE_DIR")
|
||||
.unwrap_or_else(|_| "/tmp/stripstream-image-cache".to_string()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct Metrics {
|
||||
@@ -59,3 +86,51 @@ pub async fn load_concurrent_renders(pool: &Pool<Postgres>) -> usize {
|
||||
_ => default_concurrency,
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn load_dynamic_settings(pool: &Pool<Postgres>) -> DynamicSettings {
|
||||
let mut s = DynamicSettings::default();
|
||||
|
||||
if let Ok(Some(row)) = sqlx::query(r#"SELECT value FROM app_settings WHERE key = 'limits'"#)
|
||||
.fetch_optional(pool)
|
||||
.await
|
||||
{
|
||||
let v: serde_json::Value = row.get("value");
|
||||
if let Some(n) = v.get("rate_limit_per_second").and_then(|x| x.as_u64()) {
|
||||
s.rate_limit_per_second = n as u32;
|
||||
}
|
||||
if let Some(n) = v.get("timeout_seconds").and_then(|x| x.as_u64()) {
|
||||
s.timeout_seconds = n;
|
||||
}
|
||||
}
|
||||
|
||||
if let Ok(Some(row)) = sqlx::query(r#"SELECT value FROM app_settings WHERE key = 'image_processing'"#)
|
||||
.fetch_optional(pool)
|
||||
.await
|
||||
{
|
||||
let v: serde_json::Value = row.get("value");
|
||||
if let Some(s2) = v.get("format").and_then(|x| x.as_str()) {
|
||||
s.image_format = s2.to_string();
|
||||
}
|
||||
if let Some(n) = v.get("quality").and_then(|x| x.as_u64()) {
|
||||
s.image_quality = n.clamp(1, 100) as u8;
|
||||
}
|
||||
if let Some(s2) = v.get("filter").and_then(|x| x.as_str()) {
|
||||
s.image_filter = s2.to_string();
|
||||
}
|
||||
if let Some(n) = v.get("max_width").and_then(|x| x.as_u64()) {
|
||||
s.image_max_width = n as u32;
|
||||
}
|
||||
}
|
||||
|
||||
if let Ok(Some(row)) = sqlx::query(r#"SELECT value FROM app_settings WHERE key = 'cache'"#)
|
||||
.fetch_optional(pool)
|
||||
.await
|
||||
{
|
||||
let v: serde_json::Value = row.get("value");
|
||||
if let Some(dir) = v.get("directory").and_then(|x| x.as_str()) {
|
||||
s.cache_directory = dir.to_string();
|
||||
}
|
||||
}
|
||||
|
||||
s
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user