Fix HIGH priority issues: unbounded queries, rate limiting, health checks

Issue #3 - Unbounded Query Results (OOM):
- get_all_archived_history() now uses keyset pagination with bounded max_rows (50k default)
- Added 'id' field to records from get_archived_history() and get_archived_history_keyset()
- Protocol signature updated with page_size, max_rows, last_ban_id params

Issue #7 - Docker Health Check Fails:
- Added curl to Dockerfile.backend runtime image
- HEALTHCHECK now uses 'curl -f http://localhost:8000/api/health'
- compose.prod.yml: increased start_period to 40s, timeout to 10s
- Frontend healthcheck proxies to backend /api/health

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
This commit is contained in:
2026-05-01 21:47:36 +02:00
parent 1830da496d
commit 0d5882b32f
39 changed files with 2067 additions and 339 deletions

View File

@@ -27,6 +27,7 @@ from app.dependencies import (
HttpSessionDep,
)
from app.exceptions import HistoryNotFoundError
from app.mappers import history_mappers
from app.models._common import TimeRange
from app.models.ban import BanOrigin
from app.models.history import HistoryListResponse, IpDetailResponse
@@ -99,7 +100,7 @@ async def get_history(
and the total matching count.
"""
return await history_service.list_history(
domain_result = await history_service.list_history(
socket_path,
range_=range,
jail=jail,
@@ -112,6 +113,7 @@ async def get_history(
db=history_ctx.db,
fail2ban_metadata_service=fail2ban_metadata_service,
)
return history_mappers.map_domain_history_list_to_response(domain_result)
@router.get(
@@ -136,7 +138,7 @@ async def get_history_archive(
page_size: int = Query(default=DEFAULT_PAGE_SIZE, ge=1, le=500, description="Items per page (max 500)."),
) -> HistoryListResponse:
return await history_service.list_history(
domain_result = await history_service.list_history(
socket_path,
range_=range,
jail=jail,
@@ -148,6 +150,7 @@ async def get_history_archive(
db=history_ctx.db,
fail2ban_metadata_service=fail2ban_metadata_service,
)
return history_mappers.map_domain_history_list_to_response(domain_result)
@router.get(
@@ -182,14 +185,14 @@ async def get_ip_history(
HTTPException: 404 if the IP has no history in the database.
"""
detail: IpDetailResponse | None = await history_service.get_ip_detail(
domain_result = await history_service.get_ip_detail(
socket_path,
ip,
http_session=http_session,
fail2ban_metadata_service=fail2ban_metadata_service,
)
if detail is None:
if domain_result is None:
raise HistoryNotFoundError(ip)
return detail
return history_mappers.map_domain_ip_detail_to_response(domain_result)