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

@@ -433,6 +433,7 @@ async def preview_blocklist(
source_id: int,
http_session: HttpSessionDep,
blocklist_ctx: BlocklistServiceContextDep,
settings: SettingsDep,
_auth: AuthDep,
) -> PreviewResponse:
"""Download and preview a sample of a blocklist source.
@@ -455,7 +456,9 @@ async def preview_blocklist(
raise BlocklistSourceNotFoundError(source_id)
try:
domain_result = await blocklist_service.preview_source(source.url, http_session)
domain_result = await blocklist_service.preview_source(
source.url, http_session, sample_lines=settings.blocklist_preview_max_lines
)
return blocklist_mappers.map_domain_preview_result_to_response(domain_result)
except ValueError as exc:
raise BadRequestError(f"Could not fetch blocklist: {exc}") from exc

View File

@@ -24,6 +24,7 @@ from app.dependencies import (
GeoCacheDep,
HttpSessionDep,
ServerStatusDep,
SettingsDep,
)
from app.mappers import (
map_domain_ban_trend_to_response,
@@ -101,13 +102,14 @@ async def get_dashboard_bans(
socket_path: Fail2BanSocketDep,
http_session: HttpSessionDep,
geo_cache: GeoCacheDep,
settings: SettingsDep,
range: TimeRange = Query(default=_DEFAULT_RANGE, description="Time-range preset."),
source: Literal["fail2ban", "archive"] = Query(
default="fail2ban",
description="Data source: 'fail2ban' or 'archive'.",
),
page: int = Query(default=1, ge=1, description="1-based page number."),
page_size: int = Query(default=DEFAULT_PAGE_SIZE, ge=1, le=500, description="Items per page."),
page_size: int = Query(default=DEFAULT_PAGE_SIZE, ge=1, description="Items per page."),
origin: BanOrigin | None = Query(
default=None,
description="Filter by ban origin: 'blocklist' or 'selfblock'. Omit for all.",
@@ -143,6 +145,7 @@ async def get_dashboard_bans(
source=source,
page=page,
page_size=page_size,
max_page_size=settings.max_page_size,
http_session=http_session,
app_db=ban_ctx.db,
geo_cache=geo_cache,

View File

@@ -25,6 +25,7 @@ from app.dependencies import (
Fail2BanSocketDep,
HistoryServiceContextDep,
HttpSessionDep,
SettingsDep,
)
from app.exceptions import HistoryNotFoundError
from app.mappers import history_mappers
@@ -54,6 +55,7 @@ async def get_history(
socket_path: Fail2BanSocketDep,
http_session: HttpSessionDep,
fail2ban_metadata_service: Fail2BanMetadataServiceDep,
settings: SettingsDep,
range: TimeRange | None = Query(
default=None,
description="Optional time-range filter. Omit for all-time.",
@@ -78,8 +80,7 @@ async def get_history(
page_size: int = Query(
default=DEFAULT_PAGE_SIZE,
ge=1,
le=500,
description="Items per page (max 500).",
description="Items per page.",
),
) -> HistoryListResponse:
"""Return a paginated list of historical bans with optional filters.
@@ -114,6 +115,7 @@ async def get_history(
source=source,
page=page,
page_size=page_size,
max_page_size=settings.max_page_size,
http_session=http_session,
db=history_ctx.db,
fail2ban_metadata_service=fail2ban_metadata_service,
@@ -138,6 +140,7 @@ async def get_history_archive(
socket_path: Fail2BanSocketDep,
http_session: HttpSessionDep,
fail2ban_metadata_service: Fail2BanMetadataServiceDep,
settings: SettingsDep,
range: TimeRange | None = Query(
default=None,
description="Optional time-range filter. Omit for all-time.",
@@ -145,7 +148,7 @@ async def get_history_archive(
jail: str | None = Query(default=None, description="Restrict results to this jail name."),
ip: str | None = Query(default=None, description="Restrict results to IPs matching this prefix."),
page: int = Query(default=1, ge=1, description="1-based page number."),
page_size: int = Query(default=DEFAULT_PAGE_SIZE, ge=1, le=500, description="Items per page (max 500)."),
page_size: int = Query(default=DEFAULT_PAGE_SIZE, ge=1, description="Items per page."),
) -> HistoryListResponse:
domain_result = await history_service.list_history(
@@ -156,6 +159,7 @@ async def get_history_archive(
source="archive",
page=page,
page_size=page_size,
max_page_size=settings.max_page_size,
http_session=http_session,
db=history_ctx.db,
fail2ban_metadata_service=fail2ban_metadata_service,