Refactor ban management with domain models and mappers
- Add ban domain model for core business logic separation - Implement mapper pattern for DTO/domain conversions - Update ban service with new domain-driven approach - Refactor router endpoints to use new architecture - Add comprehensive mapper tests - Update documentation with architecture changes Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
This commit is contained in:
110
backend/app/models/ban_domain.py
Normal file
110
backend/app/models/ban_domain.py
Normal file
@@ -0,0 +1,110 @@
|
||||
"""Ban domain models (DTOs).
|
||||
|
||||
Internal domain-focused models used by ban_service. These represent the
|
||||
business domain layer and are independent of HTTP response shapes.
|
||||
|
||||
Response models are defined in `app.models.ban` and mappers convert domain
|
||||
models to response models at the router boundary.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from dataclasses import dataclass
|
||||
from typing import Literal
|
||||
|
||||
# Domain-specific ban origin type
|
||||
BanOriginDomain = Literal["blocklist", "selfblock"]
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class DomainActiveBan:
|
||||
"""A currently active ban entry (domain model).
|
||||
|
||||
This is the service-layer representation, independent of API response shape.
|
||||
"""
|
||||
|
||||
ip: str
|
||||
jail: str
|
||||
banned_at: str | None = None
|
||||
expires_at: str | None = None
|
||||
ban_count: int = 1
|
||||
country: str | None = None
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class DomainActiveBanList:
|
||||
"""List of currently active bans (domain model)."""
|
||||
|
||||
bans: list[DomainActiveBan]
|
||||
total: int
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class DomainDashboardBanItem:
|
||||
"""A single row in the dashboard ban-list table (domain model).
|
||||
|
||||
Populated from the fail2ban database and enriched with geo data.
|
||||
"""
|
||||
|
||||
ip: str
|
||||
jail: str
|
||||
banned_at: str
|
||||
service: str | None = None
|
||||
country_code: str | None = None
|
||||
country_name: str | None = None
|
||||
asn: str | None = None
|
||||
org: str | None = None
|
||||
ban_count: int = 1
|
||||
origin: BanOriginDomain = "selfblock"
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class DomainDashboardBanList:
|
||||
"""Paginated dashboard ban-list (domain model)."""
|
||||
|
||||
items: list[DomainDashboardBanItem]
|
||||
total: int
|
||||
page: int
|
||||
page_size: int
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class DomainBansByCountry:
|
||||
"""Bans aggregated by country (domain model)."""
|
||||
|
||||
countries: dict[str, int]
|
||||
country_names: dict[str, str]
|
||||
items: list[DomainDashboardBanItem]
|
||||
total: int
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class DomainBanTrendBucket:
|
||||
"""A single time bucket in the ban trend series (domain model)."""
|
||||
|
||||
timestamp: str
|
||||
count: int
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class DomainBanTrend:
|
||||
"""Ban trend data over time (domain model)."""
|
||||
|
||||
buckets: list[DomainBanTrendBucket]
|
||||
bucket_size: str
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class DomainJailBanCount:
|
||||
"""Ban count for a single jail (domain model)."""
|
||||
|
||||
jail: str
|
||||
count: int
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class DomainBansByJail:
|
||||
"""Bans aggregated by jail (domain model)."""
|
||||
|
||||
jails: list[DomainJailBanCount]
|
||||
total: int
|
||||
Reference in New Issue
Block a user