From f160adcd1cb5e4687c321d2926030a7511f96c3d Mon Sep 17 00:00:00 2001 From: Gabriel Kaszewski Date: Tue, 2 Jun 2026 23:41:08 +0200 Subject: [PATCH] feat: wrapup env vars + render concurrency semaphore --- crates/application/src/config.rs | 24 +++++++++++++++++++ crates/application/src/test_helpers.rs | 6 +++++ .../application/src/wrapup/event_handler.rs | 13 +++++++++- .../src/wrapup/handle_requested.rs | 7 +++--- 4 files changed, 46 insertions(+), 4 deletions(-) diff --git a/crates/application/src/config.rs b/crates/application/src/config.rs index c97b906..82d16df 100644 --- a/crates/application/src/config.rs +++ b/crates/application/src/config.rs @@ -3,6 +3,15 @@ pub struct AppConfig { pub allow_registration: bool, pub base_url: String, pub rate_limit: u64, + pub wrapup: WrapUpConfig, +} + +#[derive(Clone)] +pub struct WrapUpConfig { + pub font_path: Option, + pub logo_path: Option, + pub ffmpeg_path: String, + pub max_concurrent_renders: usize, } impl AppConfig { @@ -20,6 +29,21 @@ impl AppConfig { allow_registration, base_url, rate_limit, + wrapup: WrapUpConfig::from_env(), + } + } +} + +impl WrapUpConfig { + pub fn from_env() -> Self { + Self { + font_path: std::env::var("WRAPUP_FONT_PATH").ok(), + logo_path: std::env::var("WRAPUP_LOGO_PATH").ok(), + ffmpeg_path: std::env::var("FFMPEG_PATH").unwrap_or_else(|_| "ffmpeg".to_string()), + max_concurrent_renders: std::env::var("WRAPUP_MAX_CONCURRENT") + .ok() + .and_then(|v| v.parse().ok()) + .unwrap_or(2), } } } diff --git a/crates/application/src/test_helpers.rs b/crates/application/src/test_helpers.rs index 6999733..aef7ca9 100644 --- a/crates/application/src/test_helpers.rs +++ b/crates/application/src/test_helpers.rs @@ -89,6 +89,12 @@ impl TestContextBuilder { allow_registration: true, base_url: "http://localhost:3000".into(), rate_limit: 20, + wrapup: crate::config::WrapUpConfig { + font_path: None, + logo_path: None, + ffmpeg_path: "ffmpeg".into(), + max_concurrent_renders: 2, + }, }, } } diff --git a/crates/application/src/wrapup/event_handler.rs b/crates/application/src/wrapup/event_handler.rs index c85ab05..d96ca8a 100644 --- a/crates/application/src/wrapup/event_handler.rs +++ b/crates/application/src/wrapup/event_handler.rs @@ -1,17 +1,25 @@ +use std::sync::Arc; + use async_trait::async_trait; use domain::errors::DomainError; use domain::events::DomainEvent; use domain::ports::EventHandler; +use tokio::sync::Semaphore; use crate::context::AppContext; pub struct WrapUpEventHandler { ctx: AppContext, + semaphore: Arc, } impl WrapUpEventHandler { pub fn new(ctx: AppContext) -> Self { - Self { ctx } + let max = ctx.config.wrapup.max_concurrent_renders; + Self { + ctx, + semaphore: Arc::new(Semaphore::new(max)), + } } } @@ -25,6 +33,9 @@ impl EventHandler for WrapUpEventHandler { start_date, end_date, } => { + let _permit = self.semaphore.acquire().await.map_err(|_| { + DomainError::InfrastructureError("render semaphore closed".into()) + })?; super::handle_requested::execute( &self.ctx, wrapup_id.clone(), diff --git a/crates/application/src/wrapup/handle_requested.rs b/crates/application/src/wrapup/handle_requested.rs index 337f9cf..94403fd 100644 --- a/crates/application/src/wrapup/handle_requested.rs +++ b/crates/application/src/wrapup/handle_requested.rs @@ -39,13 +39,14 @@ pub async fn execute( // Optionally render video (non-fatal) if let Some(ref renderer) = ctx.services.video_renderer { let poster_images = resolve_poster_images(ctx, &report).await; + let wc = &ctx.config.wrapup; let config = VideoRenderConfig { slide_duration_secs: 4, transition_duration_secs: 0.8, resolution: (1080, 1920), - ffmpeg_path: "ffmpeg".to_string(), - font_path: None, - logo_path: None, + ffmpeg_path: wc.ffmpeg_path.clone(), + font_path: wc.font_path.clone(), + logo_path: wc.logo_path.clone(), }; match renderer.render(&report, poster_images, &config).await { Ok(video_bytes) => {