diff --git a/Dockerfile b/Dockerfile index 761dbf8..2d56d87 100644 --- a/Dockerfile +++ b/Dockerfile @@ -18,6 +18,7 @@ COPY crates/adapters/template-askama/Cargo.toml crates/adapters/template-askam COPY crates/application/Cargo.toml crates/application/Cargo.toml COPY crates/domain/Cargo.toml crates/domain/Cargo.toml COPY crates/presentation/Cargo.toml crates/presentation/Cargo.toml +COPY crates/tui/Cargo.toml crates/tui/Cargo.toml # Stub every crate so cargo can resolve and fetch deps RUN find crates -name "Cargo.toml" | sed 's|/Cargo.toml||' | \ diff --git a/crates/application/src/config.rs b/crates/application/src/config.rs index c64eeff..72143a7 100644 --- a/crates/application/src/config.rs +++ b/crates/application/src/config.rs @@ -2,6 +2,7 @@ pub struct AppConfig { pub allow_registration: bool, pub base_url: String, + pub rate_limit: u64, } impl AppConfig { @@ -11,6 +12,10 @@ impl AppConfig { .unwrap_or(false); let base_url = std::env::var("BASE_URL") .unwrap_or_else(|_| "http://localhost:3000".to_string()); - Self { allow_registration, base_url } + let rate_limit = std::env::var("RATE_LIMIT") + .ok() + .and_then(|v| v.parse().ok()) + .unwrap_or(20); + Self { allow_registration, base_url, rate_limit } } } diff --git a/crates/presentation/src/event_handlers.rs b/crates/presentation/src/event_handlers.rs index ded1938..340a05c 100644 --- a/crates/presentation/src/event_handlers.rs +++ b/crates/presentation/src/event_handlers.rs @@ -160,7 +160,7 @@ mod tests { auth_service: Arc::new(PanicAuth), password_hasher: Arc::new(PanicHasher), user_repository: Arc::new(PanicUserRepo), - config: AppConfig { allow_registration: false, base_url: "http://localhost:3000".to_string() }, + config: AppConfig { allow_registration: false, base_url: "http://localhost:3000".to_string(), rate_limit: 20 }, } } diff --git a/crates/presentation/src/extractors.rs b/crates/presentation/src/extractors.rs index 4deb4f4..434bc33 100644 --- a/crates/presentation/src/extractors.rs +++ b/crates/presentation/src/extractors.rs @@ -175,7 +175,7 @@ mod tests { auth_service: Arc::new(PanicAuth), password_hasher: Arc::new(PanicHasher), user_repository: Arc::new(PanicUserRepo), - config: application::config::AppConfig { allow_registration: false, base_url: "http://localhost:3000".to_string() }, + config: application::config::AppConfig { allow_registration: false, base_url: "http://localhost:3000".to_string(), rate_limit: 20 }, }, html_renderer: Arc::new(PanicRenderer), rss_renderer: Arc::new(PanicRssRenderer), @@ -282,7 +282,7 @@ mod tests { auth_service: Arc::new(PanicAuth2), password_hasher: Arc::new(PanicHasher2), user_repository: Arc::new(PanicUserRepo2), - config: application::config::AppConfig { allow_registration: false, base_url: "http://localhost:3000".to_string() }, + config: application::config::AppConfig { allow_registration: false, base_url: "http://localhost:3000".to_string(), rate_limit: 20 }, }, html_renderer: Arc::new(PanicRenderer2), rss_renderer: Arc::new(PanicRssRenderer2), @@ -341,7 +341,7 @@ mod tests { auth_service: Arc::new(RejectingAuth), password_hasher: Arc::new(PanicHasher3), user_repository: Arc::new(PanicUserRepo3), - config: application::config::AppConfig { allow_registration: false, base_url: "http://localhost:3000".to_string() }, + config: application::config::AppConfig { allow_registration: false, base_url: "http://localhost:3000".to_string(), rate_limit: 20 }, }, html_renderer: Arc::new(PanicRenderer3), rss_renderer: Arc::new(PanicRssRenderer3), diff --git a/crates/presentation/src/routes.rs b/crates/presentation/src/routes.rs index 190f9f2..6550615 100644 --- a/crates/presentation/src/routes.rs +++ b/crates/presentation/src/routes.rs @@ -9,8 +9,6 @@ use tower_http::{services::ServeDir, trace::TraceLayer}; use crate::{handlers, state::AppState}; -const API_RATE_LIMIT: u64 = 20; // 20 requests per minute globally for API routes - /// Simple global rate limiter: tracks request count per 60-second window. /// Not per-IP — suitable for a low-traffic personal app. #[derive(Clone)] @@ -48,17 +46,17 @@ impl RateLimiter { } pub fn build_router(state: AppState) -> Router { + let rate_limit = state.app_ctx.config.rate_limit; Router::new() - .merge(html_routes()) - .merge(api_routes()) + .merge(html_routes(rate_limit)) + .merge(api_routes(rate_limit)) .nest_service("/static", ServeDir::new("static")) .layer(TraceLayer::new_for_http()) .with_state(state) } -fn html_routes() -> Router { - // Auth routes: 20 requests per minute globally. - let limiter = RateLimiter::new(API_RATE_LIMIT); +fn html_routes(rate_limit: u64) -> Router { + let limiter = RateLimiter::new(rate_limit); let auth = Router::new() .route( "/login", @@ -110,8 +108,8 @@ fn html_routes() -> Router { ) } -fn api_routes() -> Router { - let limiter = RateLimiter::new(API_RATE_LIMIT); +fn api_routes(rate_limit: u64) -> Router { + let limiter = RateLimiter::new(rate_limit); let auth_rate_limit = middleware::from_fn(move |req: axum::extract::Request, next: middleware::Next| { let limiter = limiter.clone(); diff --git a/crates/presentation/tests/api_test.rs b/crates/presentation/tests/api_test.rs index 5670d31..515f37b 100644 --- a/crates/presentation/tests/api_test.rs +++ b/crates/presentation/tests/api_test.rs @@ -105,7 +105,7 @@ async fn test_app() -> Router { auth_service: Arc::new(PanicAuth), password_hasher: Arc::new(PanicHasher), user_repository: Arc::new(NobodyUserRepo), - config: AppConfig { allow_registration: false, base_url: "http://localhost:3000".to_string() }, + config: AppConfig { allow_registration: false, base_url: "http://localhost:3000".to_string(), rate_limit: 20 }, }, html_renderer: Arc::new(AskamaHtmlRenderer::new()), rss_renderer: Arc::new(RssAdapter::new("http://localhost:3000".into())),