From 6ce9954d544fad5a2b0571513592477c89379737 Mon Sep 17 00:00:00 2001 From: Mariusz Banach Date: Tue, 17 Feb 2026 23:01:15 +0100 Subject: [PATCH] MAESTRO: add backend settings config --- ...-header-analyzer-Phase-01-Project-Setup.md | 2 +- backend/app/core/__init__.py | 0 backend/app/core/config.py | 64 +++++++++++++++++++ backend/pyproject.toml | 1 + backend/requirements.txt | 1 + backend/tests/test_config.py | 29 +++++++++ 6 files changed, 96 insertions(+), 1 deletion(-) create mode 100644 backend/app/core/__init__.py create mode 100644 backend/app/core/config.py create mode 100644 backend/tests/test_config.py diff --git a/Auto Run Docs/SpecKit-web-header-analyzer-Phase-01-Project-Setup.md b/Auto Run Docs/SpecKit-web-header-analyzer-Phase-01-Project-Setup.md index d95284a..306e4a2 100644 --- a/Auto Run Docs/SpecKit-web-header-analyzer-Phase-01-Project-Setup.md +++ b/Auto Run Docs/SpecKit-web-header-analyzer-Phase-01-Project-Setup.md @@ -63,7 +63,7 @@ The hacker-themed dark colour palette (from spec FR-14): - [x] T001 Initialise Python 3.13 backend project with FastAPI, uvicorn, pytest, pytest-asyncio, httpx, and ruff in `backend/pyproject.toml`. Create `backend/requirements.txt`, `backend/app/__init__.py`, and `backend/app/main.py` with minimal FastAPI app skeleton that returns 200 on root endpoint - [x] T002 Initialise NextJS + TypeScript frontend project in `frontend/`. Configure `frontend/tsconfig.json` with strict mode. Install and configure Tailwind CSS in `frontend/tailwind.config.ts` with hacker-themed colour palette (#1e1e2e backgrounds, red spam, amber suspicious, green clean). Install FontAwesome packages. Configure eslint and prettier in `frontend/.eslintrc.json` and `frontend/.prettierrc`. Install Playwright (`npm init playwright@latest`) with `frontend/playwright.config.ts` and `@axe-core/playwright`. Configure `webServer` array for both uvicorn (port 8000) and NextJS (port 3000), `testDir: './e2e'`, `fullyParallel: true`. Exclude `e2e/` from vitest config -- [ ] T003 [P] Create `backend/app/core/config.py` with Pydantic BaseSettings for CORS origins, rate limit thresholds, analysis timeout (30s), and debug flag — all configurable via environment variables +- [x] T003 [P] Create `backend/app/core/config.py` with Pydantic BaseSettings for CORS origins, rate limit thresholds, analysis timeout (30s), and debug flag — all configurable via environment variables - [ ] T004 [P] Create `frontend/src/types/analysis.ts` with TypeScript interfaces: `HeaderInput`, `AnalysisConfig` (test IDs, resolve flag, decode-all flag), `TestResult` (id, name, header, value, analysis, description, severity, status), `AnalysisReport` (results, hopChain, securityAppliances, metadata), `AnalysisProgress` (current test, total, percentage, elapsed). Refer to `.specify/specs/1-web-header-analyzer/data-model.md` for the complete entity definitions - [ ] T005 [P] Create `frontend/src/lib/api-client.ts` — API client abstraction wrapping fetch with base URL from environment, JSON defaults, error handling, and typed response generics. Must support SSE streaming via ReadableStream for the analysis endpoint - [ ] T006 [P] Create `frontend/src/styles/design-tokens.ts` — centralised design tokens: colours (#1e1e2e background, #282a36 surface, #f8f8f2 text, #ff5555 spam, #ffb86c suspicious, #50fa7b clean, #bd93f9 accent), font families (mono/sans), spacing scale, and border-radius values diff --git a/backend/app/core/__init__.py b/backend/app/core/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/backend/app/core/config.py b/backend/app/core/config.py new file mode 100644 index 0000000..7df31d1 --- /dev/null +++ b/backend/app/core/config.py @@ -0,0 +1,64 @@ +from __future__ import annotations + +import json +from functools import lru_cache +from typing import Any + +from pydantic import Field, field_validator +from pydantic_settings import BaseSettings, SettingsConfigDict + + +class Settings(BaseSettings): + """Application configuration loaded from environment variables. + + Environment variables are prefixed with `WHA_` by default. + """ + + model_config = SettingsConfigDict(env_prefix="WHA_", case_sensitive=False) + + cors_origins: list[str] = Field( + default_factory=lambda: ["http://localhost:3000"], + description="Allowed CORS origins.", + ) + rate_limit_requests: int = Field( + default=60, + ge=1, + description="Max requests per window for rate limiting.", + ) + rate_limit_window_seconds: int = Field( + default=60, + ge=1, + description="Rate limit window in seconds.", + ) + analysis_timeout_seconds: int = Field( + default=30, + ge=1, + description="Hard timeout for analysis in seconds.", + ) + debug: bool = Field(default=False, description="Enable debug mode.") + + @field_validator("cors_origins", mode="before") + @classmethod + def parse_cors_origins(cls, value: Any) -> list[str]: + if value is None: + return ["http://localhost:3000"] + if isinstance(value, list): + return [str(item).strip() for item in value if str(item).strip()] + if isinstance(value, str): + text = value.strip() + if not text: + return [] + if text.startswith("["): + try: + parsed = json.loads(text) + if isinstance(parsed, list): + return [str(item).strip() for item in parsed if str(item).strip()] + except json.JSONDecodeError: + pass + return [item.strip() for item in text.split(",") if item.strip()] + return [str(value).strip()] + + +@lru_cache +def get_settings() -> Settings: + return Settings() diff --git a/backend/pyproject.toml b/backend/pyproject.toml index 8d32a60..b033c4d 100644 --- a/backend/pyproject.toml +++ b/backend/pyproject.toml @@ -10,6 +10,7 @@ readme = "../README.md" requires-python = ">=3.13" dependencies = [ "fastapi>=0.110", + "pydantic-settings>=2.2", "uvicorn[standard]>=0.27", ] diff --git a/backend/requirements.txt b/backend/requirements.txt index 37dd5d7..1084bbb 100644 --- a/backend/requirements.txt +++ b/backend/requirements.txt @@ -1,4 +1,5 @@ fastapi>=0.110 +pydantic-settings>=2.2 uvicorn[standard]>=0.27 pytest>=8.0 pytest-asyncio>=0.23 diff --git a/backend/tests/test_config.py b/backend/tests/test_config.py new file mode 100644 index 0000000..3534278 --- /dev/null +++ b/backend/tests/test_config.py @@ -0,0 +1,29 @@ +from app.core.config import Settings + + +def test_settings_defaults() -> None: + settings = Settings() + assert settings.analysis_timeout_seconds == 30 + assert settings.debug is False + assert settings.rate_limit_requests == 60 + assert settings.rate_limit_window_seconds == 60 + assert settings.cors_origins == ["http://localhost:3000"] + + +def test_settings_env_override(monkeypatch) -> None: + monkeypatch.setenv( + "WHA_CORS_ORIGINS", + '["https://example.com", "http://localhost:3000"]', + ) + monkeypatch.setenv("WHA_RATE_LIMIT_REQUESTS", "10") + monkeypatch.setenv("WHA_RATE_LIMIT_WINDOW_SECONDS", "120") + monkeypatch.setenv("WHA_ANALYSIS_TIMEOUT_SECONDS", "45") + monkeypatch.setenv("WHA_DEBUG", "true") + + settings = Settings() + + assert settings.cors_origins == ["https://example.com", "http://localhost:3000"] + assert settings.rate_limit_requests == 10 + assert settings.rate_limit_window_seconds == 120 + assert settings.analysis_timeout_seconds == 45 + assert settings.debug is True