"""AbonnementRepository — Port für E-Mail-Digest-Abos (#124). Kapselt die `email_subscriptions`-Tabelle. Der Name „Abonnement" ist die Ubiquitous-Language-Form (Kapitel 4 der DDD-Bewertung); intern heißt die Tabelle weiter `email_subscriptions`. """ from __future__ import annotations from typing import Optional, Protocol, runtime_checkable from .. import database @runtime_checkable class AbonnementRepository(Protocol): async def create( self, user_id: str, email: str, bundesland: Optional[str] = None, partei: Optional[str] = None, frequency: str = "daily", ) -> int: ... async def list_by_user(self, user_id: str) -> list[dict]: ... async def list_all(self) -> list[dict]: ... async def list_due(self, frequency: str = "daily") -> list[dict]: ... async def delete(self, user_id: str, sub_id: int) -> bool: ... async def delete_by_id(self, sub_id: int) -> bool: ... async def mark_sent(self, sub_id: int) -> None: ... class SqliteAbonnementRepository: async def create( self, user_id: str, email: str, bundesland: Optional[str] = None, partei: Optional[str] = None, frequency: str = "daily", ) -> int: return await database.create_subscription( user_id, email, bundesland, partei, frequency, ) async def list_by_user(self, user_id: str) -> list[dict]: return await database.list_subscriptions(user_id) async def list_all(self) -> list[dict]: return await database.list_all_subscriptions() async def list_due(self, frequency: str = "daily") -> list[dict]: return await database.get_all_subscriptions_due(frequency) async def delete(self, user_id: str, sub_id: int) -> bool: return await database.delete_subscription(user_id, sub_id) async def delete_by_id(self, sub_id: int) -> bool: return await database.delete_subscription_by_id(sub_id) async def mark_sent(self, sub_id: int) -> None: await database.mark_subscription_sent(sub_id) class InMemoryAbonnementRepository: """Test-Fake. Ignoriert ``last_sent``-Zeitberechnung — ``list_due`` gibt einfach alle zurück, bei denen ``last_sent`` ``None`` ist. Für Zeit-bezogene Tests explizit ``mark_sent`` nutzen.""" def __init__(self) -> None: self._subs: list[dict] = [] self._next_id = 1 async def create( self, user_id: str, email: str, bundesland: Optional[str] = None, partei: Optional[str] = None, frequency: str = "daily", ) -> int: sid = self._next_id self._next_id += 1 self._subs.append({ "id": sid, "user_id": user_id, "email": email, "bundesland": bundesland, "partei": partei, "frequency": frequency, "last_sent": None, "created_at": "", }) return sid async def list_by_user(self, user_id: str) -> list[dict]: return [dict(s) for s in self._subs if s["user_id"] == user_id] async def list_all(self) -> list[dict]: return [dict(s) for s in self._subs] async def list_due(self, frequency: str = "daily") -> list[dict]: return [ dict(s) for s in self._subs if s["frequency"] == frequency and s.get("last_sent") is None ] async def delete(self, user_id: str, sub_id: int) -> bool: for i, s in enumerate(self._subs): if s["id"] == sub_id and s["user_id"] == user_id: self._subs.pop(i) return True return False async def delete_by_id(self, sub_id: int) -> bool: for i, s in enumerate(self._subs): if s["id"] == sub_id: self._subs.pop(i) return True return False async def mark_sent(self, sub_id: int) -> None: for s in self._subs: if s["id"] == sub_id: s["last_sent"] = "sent" _default_abonnement_repo: AbonnementRepository = SqliteAbonnementRepository() def get_abonnement_repository() -> AbonnementRepository: return _default_abonnement_repo