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:
2026-04-25 18:44:59 +02:00
parent 420ea18fd9
commit ac2028e1c2
6 changed files with 132 additions and 51 deletions

View File

@@ -69,6 +69,39 @@ from fail2ban.client.csocket import CSSocket # noqa: E402
- `print()` for logging — use `structlog`.
- `json.loads` / `json.dumps` on Pydantic models — use `.model_dump()` / `.model_validate()`.
### Timestamp Handling
Timestamp consistency is critical for accurate ban history queries across the dashboard and history endpoints. Follow these rules:
**Rule 1: Use consistent UTC timestamps**
- All timestamps in the database are stored as Unix epochs (seconds since 1970-01-01 UTC).
- fail2ban stores timestamps using `time.time()`, which is always UTC epoch seconds.
- When querying fail2ban's SQLite database by timestamp, use `app.utils.time_utils.since_unix()` (not manual datetime calculations).
**Rule 2: Time-range windows include a 60-second slack**
- The `since_unix()` function includes a 60-second slack window (`TIME_RANGE_SLACK_SECONDS` in `app.utils.constants`).
- This slack accommodates:
- Clock drift between the local system and fail2ban.
- Test seeding delays when timestamps are manually set to exact boundaries.
- The slack ensures that dashboard and history queries return consistent row counts for the same time range.
**Rule 3: Never duplicate timestamp calculation logic**
- All services that query by time range must import and use `since_unix()`.
- Do not recalculate timestamps locally using `datetime` or `time` modules in service code.
- If you need a timestamp for a time range, use `since_unix()`.
**Example:**
```python
from app.utils.time_utils import since_unix
# Get all bans from the last 24 hours (with 60-second slack)
since_ts: int = since_unix("24h")
rows = await db.execute(
"SELECT * FROM bans WHERE timeofban >= ?",
(since_ts,)
)
```
---
## 3. Project Structure