Refactor geo re-resolve endpoint into geo_service and add typed response
This commit is contained in:
@@ -57,6 +57,19 @@ class GeoCacheStatsResponse(BaseModel):
|
||||
dirty_size: int = Field(..., description="Number of newly resolved entries not yet flushed to disk.")
|
||||
|
||||
|
||||
class GeoReResolveResponse(BaseModel):
|
||||
"""Response for ``POST /api/geo/re-resolve``.
|
||||
|
||||
Reports how many previously unresolved IPs were retried and how many
|
||||
gained a resolved country code after the re-resolve operation.
|
||||
"""
|
||||
|
||||
model_config = ConfigDict(strict=True)
|
||||
|
||||
resolved: int = Field(..., description="Number of IPs successfully resolved.")
|
||||
total: int = Field(..., description="Number of IPs retried.")
|
||||
|
||||
|
||||
class IpLookupResponse(BaseModel):
|
||||
"""Response for ``GET /api/geo/lookup/{ip}``.
|
||||
|
||||
|
||||
@@ -11,8 +11,6 @@ from __future__ import annotations
|
||||
from typing import TYPE_CHECKING, Annotated
|
||||
|
||||
if TYPE_CHECKING:
|
||||
import aiohttp
|
||||
|
||||
from app.services.jail_service import IpLookupResult
|
||||
|
||||
import aiosqlite
|
||||
@@ -20,12 +18,11 @@ from fastapi import APIRouter, Depends, HTTPException, Path, status
|
||||
|
||||
from app.dependencies import (
|
||||
AuthDep,
|
||||
DbDep,
|
||||
Fail2BanSocketDep,
|
||||
HttpSessionDep,
|
||||
get_db,
|
||||
)
|
||||
from app.models.geo import GeoCacheStatsResponse, GeoDetail, GeoInfo, IpLookupResponse
|
||||
from app.models.geo import GeoCacheStatsResponse, GeoDetail, GeoInfo, GeoReResolveResponse, IpLookupResponse
|
||||
from app.services import geo_service, jail_service
|
||||
from app.utils.fail2ban_client import Fail2BanConnectionError
|
||||
|
||||
@@ -136,12 +133,13 @@ async def geo_stats(
|
||||
@router.post(
|
||||
"/re-resolve",
|
||||
summary="Re-resolve all IPs whose country could not be determined",
|
||||
response_model=GeoReResolveResponse,
|
||||
)
|
||||
async def re_resolve_geo(
|
||||
_auth: AuthDep,
|
||||
db: Annotated[aiosqlite.Connection, Depends(get_db)],
|
||||
http_session: HttpSessionDep,
|
||||
) -> dict[str, int]:
|
||||
) -> GeoReResolveResponse:
|
||||
"""Retry geo resolution for every IP in ``geo_cache`` with a null country.
|
||||
|
||||
Clears the in-memory negative cache first so that previously failing IPs
|
||||
@@ -153,22 +151,6 @@ async def re_resolve_geo(
|
||||
http_session: Shared HTTP session for geo lookups.
|
||||
|
||||
Returns:
|
||||
JSON object ``{"resolved": N, "total": M}`` where *N* is the number
|
||||
of IPs that gained a country code and *M* is the total number of IPs
|
||||
that were retried.
|
||||
A :class:`~app.models.geo.GeoReResolveResponse` with retry counts.
|
||||
"""
|
||||
# Collect all IPs in geo_cache that still lack a country code.
|
||||
unresolved = await geo_service.get_unresolved_ips(db)
|
||||
|
||||
if not unresolved:
|
||||
return {"resolved": 0, "total": 0}
|
||||
|
||||
# Clear negative cache so these IPs bypass the TTL check.
|
||||
geo_service.clear_neg_cache()
|
||||
|
||||
geo_map = await geo_service.lookup_batch(unresolved, http_session, db=db)
|
||||
|
||||
resolved_count = sum(
|
||||
1 for info in geo_map.values() if info.country_code is not None
|
||||
)
|
||||
return {"resolved": resolved_count, "total": len(unresolved)}
|
||||
return await geo_service.re_resolve_all(db, http_session)
|
||||
|
||||
@@ -187,6 +187,41 @@ async def get_unresolved_ips(db: aiosqlite.Connection) -> list[str]:
|
||||
return await geo_cache_repo.get_unresolved_ips(db)
|
||||
|
||||
|
||||
async def re_resolve_all(
|
||||
db: aiosqlite.Connection,
|
||||
http_session: aiohttp.ClientSession,
|
||||
) -> dict[str, int]:
|
||||
"""Retry geo resolution for all unresolved cache entries.
|
||||
|
||||
This helper clears the in-memory negative cache before attempting a
|
||||
fresh batch lookup, then returns counters for how many IPs were retried
|
||||
and how many gained a resolved country code.
|
||||
|
||||
Args:
|
||||
db: BanGUI application database connection.
|
||||
http_session: Shared aiohttp client session.
|
||||
|
||||
Returns:
|
||||
A dict with ``resolved`` and ``total`` counts.
|
||||
"""
|
||||
unresolved = await get_unresolved_ips(db)
|
||||
if not unresolved:
|
||||
return {"resolved": 0, "total": 0}
|
||||
|
||||
clear_neg_cache()
|
||||
geo_map = await lookup_batch(unresolved, http_session, db=db)
|
||||
resolved_count = sum(
|
||||
1 for info in geo_map.values() if info.country_code is not None
|
||||
)
|
||||
|
||||
log.info(
|
||||
"geo_re_resolve_complete",
|
||||
total=len(unresolved),
|
||||
resolved=resolved_count,
|
||||
)
|
||||
return {"resolved": resolved_count, "total": len(unresolved)}
|
||||
|
||||
|
||||
def init_geoip(mmdb_path: str | None) -> None:
|
||||
"""Initialise the MaxMind GeoLite2-Country database reader.
|
||||
|
||||
|
||||
Reference in New Issue
Block a user