"""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