refactor(backend): clean up jail service, add error handling service

- Extract jail status/processing to helper functions
- Add error_handling.py service for centralized error handling
- Update config.py with validation and defaults
- Update .env.example with all config options
- Remove obsolete Tasks.md, add Service-Development.md
- Minor fixes across routers and services

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
This commit is contained in:
2026-05-03 17:40:37 +02:00
parent 2df029f7e8
commit 2f9fc8076d
15 changed files with 332 additions and 154 deletions

View File

@@ -47,7 +47,6 @@ from app.utils.async_utils import logged_task
from app.utils.constants import (
DEFAULT_PAGE_SIZE,
FAIL2BAN_SOCKET_TIMEOUT,
MAX_PAGE_SIZE,
)
from app.utils.fail2ban_client import (
Fail2BanClient,
@@ -87,7 +86,11 @@ async def get_fail2ban_db_path(socket_path: str) -> str:
async def ban_ip(socket_path: str, jail: str, ip: str) -> None:
"""Ban an IP address in the specified jail."""
"""Ban an IP address in the specified jail.
Error contract: ABORT_ON_ERROR. Raises JailNotFoundError or JailOperationError.
Router converts to HTTP 404 or 409.
"""
try:
ipaddress.ip_address(ip)
except ValueError as exc:
@@ -348,6 +351,7 @@ async def list_bans(
source: str = "fail2ban",
page: int = 1,
page_size: int = DEFAULT_PAGE_SIZE,
max_page_size: int = 500,
http_session: aiohttp.ClientSession | None = None,
app_db: aiosqlite.Connection | None = None,
geo_cache: GeoCache | None = None,
@@ -375,8 +379,9 @@ async def list_bans(
range_: Time-range preset (``"24h"``, ``"7d"``, ``"30d"``, or
``"365d"``).
page: 1-based page number (default: ``1``).
page_size: Maximum items per page, capped at ``MAX_PAGE_SIZE``
page_size: Maximum items per page, capped at ``max_page_size``
(default: ``100``).
max_page_size: Deployment-configured maximum page size (default: ``500``).
http_session: Optional shared :class:`aiohttp.ClientSession`. When
provided, :meth:`GeoCache.lookup_batch` is used
for efficient bulk geo resolution.
@@ -393,7 +398,7 @@ async def list_bans(
"""
since: int = since_unix(range_)
effective_page_size: int = min(page_size, MAX_PAGE_SIZE)
effective_page_size: int = min(page_size, max_page_size)
offset: int = (page - 1) * effective_page_size
if source not in ("fail2ban", "archive"):