Fix: Consolidate divergent _since_unix implementations (T-09)
Consolidate the two divergent implementations of _since_unix from ban_service.py and history_service.py into a single shared utility function in time_utils.py. Changes: - Move _since_unix to app/utils/time_utils.py with consistent time.time() approach - Move TIME_RANGE_SLACK_SECONDS constant to app/utils/constants.py - Update ban_service.py to import since_unix from time_utils - Update history_service.py to import since_unix from time_utils - Both services now use the same window boundary calculation with 60-second slack - Add comprehensive tests for the shared since_unix function - Document timestamp handling rationale in Backend-Development.md This ensures dashboard and history queries return consistent row counts for the same time range by using the same timestamp calculation and slack window across all services. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
This commit is contained in:
@@ -13,7 +13,6 @@ from __future__ import annotations
|
||||
import asyncio
|
||||
import contextlib
|
||||
import ipaddress
|
||||
import time
|
||||
from typing import TYPE_CHECKING, cast
|
||||
|
||||
import structlog
|
||||
@@ -23,7 +22,6 @@ from app.models.ban import (
|
||||
BLOCKLIST_JAIL,
|
||||
BUCKET_SECONDS,
|
||||
BUCKET_SIZE_LABEL,
|
||||
TIME_RANGE_SECONDS,
|
||||
ActiveBan,
|
||||
ActiveBanListResponse,
|
||||
BanOrigin,
|
||||
@@ -57,6 +55,7 @@ from app.utils.fail2ban_response import (
|
||||
ok,
|
||||
to_dict,
|
||||
)
|
||||
from app.utils.time_utils import since_unix
|
||||
|
||||
if TYPE_CHECKING:
|
||||
import aiohttp
|
||||
@@ -318,33 +317,6 @@ async def get_active_bans(
|
||||
log.info("active_bans_fetched", total=len(bans))
|
||||
return ActiveBanListResponse(bans=bans, total=len(bans))
|
||||
|
||||
|
||||
_TIME_RANGE_SLACK_SECONDS: int = 60
|
||||
|
||||
|
||||
def _since_unix(range_: TimeRange) -> int:
|
||||
"""Return the Unix timestamp representing the start of the time window.
|
||||
|
||||
Uses :func:`time.time` (always UTC epoch seconds on all platforms) to be
|
||||
consistent with how fail2ban stores ``timeofban`` values in its SQLite
|
||||
database. fail2ban records ``time.time()`` values directly, so
|
||||
comparing against a timezone-aware ``datetime.now(UTC).timestamp()`` would
|
||||
theoretically produce the same number but using :func:`time.time` avoids
|
||||
any tz-aware datetime pitfalls on misconfigured systems.
|
||||
|
||||
Args:
|
||||
range_: One of the supported time-range presets.
|
||||
|
||||
Returns:
|
||||
Unix timestamp (seconds since epoch) equal to *now − range_* with a
|
||||
small slack window for clock drift and test seeding delays.
|
||||
"""
|
||||
seconds: int = TIME_RANGE_SECONDS[range_]
|
||||
return int(time.time()) - seconds - _TIME_RANGE_SLACK_SECONDS
|
||||
|
||||
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Public API
|
||||
# ---------------------------------------------------------------------------
|
||||
@@ -401,7 +373,7 @@ async def list_bans(
|
||||
paginated items and total count.
|
||||
"""
|
||||
|
||||
since: int = _since_unix(range_)
|
||||
since: int = since_unix(range_)
|
||||
effective_page_size: int = min(page_size, MAX_PAGE_SIZE)
|
||||
offset: int = (page - 1) * effective_page_size
|
||||
|
||||
@@ -570,7 +542,7 @@ async def bans_by_country(
|
||||
aggregation and the companion ban list.
|
||||
"""
|
||||
|
||||
since: int = _since_unix(range_)
|
||||
since: int = since_unix(range_)
|
||||
|
||||
if source not in ("fail2ban", "archive"):
|
||||
raise ValueError(f"Unsupported source: {source!r}")
|
||||
@@ -820,7 +792,7 @@ async def ban_trend(
|
||||
:class:`~app.models.ban.BanTrendResponse` with a full bucket list
|
||||
and the human-readable bucket-size label.
|
||||
"""
|
||||
since: int = _since_unix(range_)
|
||||
since: int = since_unix(range_)
|
||||
bucket_secs: int = BUCKET_SECONDS[range_]
|
||||
num_buckets: int = bucket_count(range_)
|
||||
|
||||
@@ -920,7 +892,7 @@ async def bans_by_jail(
|
||||
:class:`~app.models.ban.BansByJailResponse` with per-jail counts
|
||||
sorted descending and the total ban count.
|
||||
"""
|
||||
since: int = _since_unix(range_)
|
||||
since: int = since_unix(range_)
|
||||
|
||||
if source not in ("fail2ban", "archive"):
|
||||
raise ValueError(f"Unsupported source: {source!r}")
|
||||
|
||||
Reference in New Issue
Block a user