- 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>
111 lines
2.4 KiB
Python
111 lines
2.4 KiB
Python
"""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
|