Files
BanGUI/backend/app/models/history.py
Lukas 7392c930d6 feat: Stage 1 — backend and frontend scaffolding
Backend (tasks 1.1, 1.5–1.8):
- pyproject.toml with FastAPI, Pydantic v2, aiosqlite, APScheduler 3.x,
  structlog, bcrypt; ruff + mypy strict configured
- Pydantic Settings (BANGUI_ prefix env vars, fail-fast validation)
- SQLite schema: settings, sessions, blocklist_sources, import_log;
  WAL mode + foreign keys; idempotent init_db()
- FastAPI app factory with lifespan (DB, aiohttp session, scheduler),
  CORS, unhandled-exception handler, GET /api/health
- Fail2BanClient: async Unix-socket wrapper using run_in_executor,
  custom error types, async context manager
- Utility modules: ip_utils, time_utils, constants
- 47 tests; ruff 0 errors; mypy --strict 0 errors

Frontend (tasks 1.2–1.4):
- Vite + React 18 + TypeScript strict; Fluent UI v9; ESLint + Prettier
- Custom brand theme (#0F6CBD, WCAG AA contrast) with light/dark variants
- Typed fetch API client (ApiError, get/post/put/del) + endpoints constants
- tsc --noEmit 0 errors
2026-02-28 21:15:01 +01:00

46 lines
1.3 KiB
Python

"""Ban history Pydantic models."""
from pydantic import BaseModel, ConfigDict, Field
class HistoryEntry(BaseModel):
"""A single historical ban record from the fail2ban database."""
model_config = ConfigDict(strict=True)
ip: str
jail: str
banned_at: str = Field(..., description="ISO 8601 UTC timestamp of the ban.")
released_at: str | None = Field(default=None, description="ISO 8601 UTC timestamp when the ban expired.")
ban_count: int = Field(..., ge=1, description="Total number of times this IP was banned.")
country: str | None = None
matched_lines: list[str] = Field(default_factory=list)
class IpTimeline(BaseModel):
"""Per-IP ban history timeline."""
model_config = ConfigDict(strict=True)
ip: str
total_bans: int = Field(..., ge=0)
total_failures: int = Field(..., ge=0)
events: list[HistoryEntry] = Field(default_factory=list)
class HistoryListResponse(BaseModel):
"""Paginated response for ``GET /api/history``."""
model_config = ConfigDict(strict=True)
entries: list[HistoryEntry] = Field(default_factory=list)
total: int = Field(..., ge=0)
class IpHistoryResponse(BaseModel):
"""Response for ``GET /api/history/{ip}``."""
model_config = ConfigDict(strict=True)
timeline: IpTimeline