"""Datenmodelle des Plugins „benachrichtigung“. Alle Tabellen tragen den Präfix `benachrichtigung_` und nutzen portable Spaltentypen (SQLite ↔ Postgres). Der Kern bleibt unverändert — auch die Benutzer-Tabelle wird nicht angetastet; Kanal-Kontaktdaten (E-Mail-Adresse, Telegram-Chat-ID) liegen in der eigenen Kontakt-Tabelle. `extend_existing` erlaubt das erneute Ausführen des Moduls durch den Plugin-Loader (jeder App-Start lädt das Plugin frisch bzw. Tests können es über einen zweiten Pfad importieren), ohne dass die gemeinsame Metadata doppelt definierte Tabellen meldet. Die Modelle definieren bewusst keine ORM-Indizes: Bei erneuter Ausführung würde ein deklarativ definierter Index doppelt im Table-Objekt landen und die Migration würde ihn zweimal anlegen. Die Datenmengen sind klein — auf Indizes wird verzichtet. """ from __future__ import annotations from datetime import datetime from sqlalchemy import Boolean, DateTime, ForeignKey, String, Text, func from sqlalchemy.orm import Mapped, mapped_column from redaktionskern.db import Base #: Gültige Kanal-Schlüssel (Reihenfolge = Anzeigereihenfolge im Formular). KANAELLE: dict[str, str] = { "email": "E-Mail", "telegram": "Telegram", "inapp": "In-App", } STANDARD_KANAELLE = "inapp" class Benachrichtigung(Base): """Eine In-App-Nachricht im Portal (Posteingang des Benutzers).""" __tablename__ = "benachrichtigung" __table_args__ = {"extend_existing": True} id: Mapped[int] = mapped_column(primary_key=True) user_id: Mapped[int] = mapped_column(ForeignKey("users.id")) titel: Mapped[str] = mapped_column(String(300)) text: Mapped[str] = mapped_column(Text) kategorie: Mapped[str] = mapped_column(String(100), default="allgemein") gelesen: Mapped[bool] = mapped_column(Boolean, default=False) created_at: Mapped[datetime] = mapped_column(DateTime, server_default=func.now()) class Praeferenz(Base): """Kanal-Präferenzen eines Benutzers (mehrere Kanäle gleichzeitig möglich).""" __tablename__ = "benachrichtigung_praeferenz" __table_args__ = {"extend_existing": True} user_id: Mapped[int] = mapped_column(ForeignKey("users.id"), primary_key=True) #: Komma-separierte Kanal-Schlüssel, z. B. "email,inapp". kanaele: Mapped[str] = mapped_column(String(200), default=STANDARD_KANAELLE) aktualisiert_at: Mapped[datetime] = mapped_column( DateTime, server_default=func.now() ) def kanaele_als_liste(self) -> list[str]: gueltig = [k for k in self.kanaele.split(",") if k in KANAELLE] return gueltige_reihenfolge(gueltig) def gueltige_reihenfolge(auswahl: list[str]) -> list[str]: """Sortiert eine Kanal-Auswahl in die definierte Anzeigereihenfolge.""" return [k for k in KANAELLE if k in auswahl] class Kontakt(Base): """Kanal-spezifische Kontaktdaten eines Benutzers (pro Benutzer höchstens eine Zeile).""" __tablename__ = "benachrichtigung_kontakt" __table_args__ = {"extend_existing": True} user_id: Mapped[int] = mapped_column(ForeignKey("users.id"), primary_key=True) email: Mapped[str | None] = mapped_column(String(320), nullable=True) telegram_chat_id: Mapped[str | None] = mapped_column(String(50), nullable=True)