Move history archive max timestamp query into repository
This commit is contained in:
@@ -81,6 +81,8 @@ Reference: `Docs/Refactoring.md` for full analysis of each issue.
|
|||||||
|
|
||||||
**Docs changes needed:** None beyond `Refactoring.md`.
|
**Docs changes needed:** None beyond `Refactoring.md`.
|
||||||
|
|
||||||
|
**Status:** Completed ✅
|
||||||
|
|
||||||
**Why this is needed:** Raw SQL in the service layer bypasses the repository abstraction. It makes the service harder to test (requires a real DB schema) and harder to maintain (schema changes must be tracked in the service, not just the repository).
|
**Why this is needed:** Raw SQL in the service layer bypasses the repository abstraction. It makes the service harder to test (requires a real DB schema) and harder to maintain (schema changes must be tracked in the service, not just the repository).
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|||||||
@@ -36,6 +36,15 @@ async def archive_ban_event(
|
|||||||
return inserted
|
return inserted
|
||||||
|
|
||||||
|
|
||||||
|
async def get_max_timeofban(db: aiosqlite.Connection) -> int | None:
|
||||||
|
"""Return the latest archived ban timestamp or ``None`` when empty."""
|
||||||
|
async with db.execute("SELECT MAX(timeofban) FROM history_archive") as cursor:
|
||||||
|
row = await cursor.fetchone()
|
||||||
|
if row is None or row[0] is None:
|
||||||
|
return None
|
||||||
|
return int(row[0])
|
||||||
|
|
||||||
|
|
||||||
async def get_archived_history(
|
async def get_archived_history(
|
||||||
db: aiosqlite.Connection,
|
db: aiosqlite.Connection,
|
||||||
since: int | None = None,
|
since: int | None = None,
|
||||||
|
|||||||
@@ -28,7 +28,7 @@ from app.models.history import (
|
|||||||
IpTimelineEvent,
|
IpTimelineEvent,
|
||||||
)
|
)
|
||||||
from app.repositories import fail2ban_db_repo
|
from app.repositories import fail2ban_db_repo
|
||||||
from app.repositories.history_archive_repo import archive_ban_event
|
from app.repositories.history_archive_repo import archive_ban_event, get_max_timeofban
|
||||||
from app.services.fail2ban_metadata_service import default_fail2ban_metadata_service
|
from app.services.fail2ban_metadata_service import default_fail2ban_metadata_service
|
||||||
from app.utils.fail2ban_db_utils import parse_data_json, ts_to_iso
|
from app.utils.fail2ban_db_utils import parse_data_json, ts_to_iso
|
||||||
|
|
||||||
@@ -67,11 +67,7 @@ _HISTORY_SYNC_BACKFILL_WINDOW: int = 648000
|
|||||||
|
|
||||||
async def _get_last_archive_ts(db: aiosqlite.Connection) -> int | None:
|
async def _get_last_archive_ts(db: aiosqlite.Connection) -> int | None:
|
||||||
"""Return the most recent archived ban timestamp, or ``None`` if empty."""
|
"""Return the most recent archived ban timestamp, or ``None`` if empty."""
|
||||||
async with db.execute("SELECT MAX(timeofban) FROM history_archive") as cur:
|
return await get_max_timeofban(db)
|
||||||
row = await cur.fetchone()
|
|
||||||
if row is None or row[0] is None:
|
|
||||||
return None
|
|
||||||
return int(row[0])
|
|
||||||
|
|
||||||
|
|
||||||
async def sync_from_fail2ban_db(
|
async def sync_from_fail2ban_db(
|
||||||
|
|||||||
@@ -9,7 +9,12 @@ import aiosqlite
|
|||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
from app.db import init_db
|
from app.db import init_db
|
||||||
from app.repositories.history_archive_repo import archive_ban_event, get_archived_history, purge_archived_history
|
from app.repositories.history_archive_repo import (
|
||||||
|
archive_ban_event,
|
||||||
|
get_archived_history,
|
||||||
|
get_max_timeofban,
|
||||||
|
purge_archived_history,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture
|
@pytest.fixture
|
||||||
@@ -52,6 +57,25 @@ async def test_get_archived_history_filtering_and_pagination(app_db: str) -> Non
|
|||||||
assert rows[0]["ip"] == "2.2.2.2"
|
assert rows[0]["ip"] == "2.2.2.2"
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.asyncio
|
||||||
|
async def test_get_max_timeofban_returns_latest_timestamp(app_db: str) -> None:
|
||||||
|
async with aiosqlite.connect(app_db) as db:
|
||||||
|
await archive_ban_event(db, "sshd", "1.1.1.1", 1000, 1, "{}", "ban")
|
||||||
|
await archive_ban_event(db, "sshd", "1.1.1.2", 2000, 1, "{}", "ban")
|
||||||
|
|
||||||
|
max_ts = await get_max_timeofban(db)
|
||||||
|
|
||||||
|
assert max_ts == 2000
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.asyncio
|
||||||
|
async def test_get_max_timeofban_returns_none_for_empty_archive(app_db: str) -> None:
|
||||||
|
async with aiosqlite.connect(app_db) as db:
|
||||||
|
max_ts = await get_max_timeofban(db)
|
||||||
|
|
||||||
|
assert max_ts is None
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.asyncio
|
@pytest.mark.asyncio
|
||||||
async def test_purge_archived_history(app_db: str) -> None:
|
async def test_purge_archived_history(app_db: str) -> None:
|
||||||
now = int(time.time())
|
now = int(time.time())
|
||||||
|
|||||||
Reference in New Issue
Block a user