feat: Projektfundament mit Datenmodell, Migrationen und Seed
- Repo-Struktur für Backend, Frontend, Dokumentation und Gitea-Workflows - FastAPI-Skeleton mit pydantic-settings und RFC-7807-artigem Fehlerformat - Vollständiges Datenmodell (15 Tabellen) als SQLAlchemy-2.0-Modelle - Alembic gegen die Async-Engine inklusive Erstmigration - Idempotenter Seed für den deutschen Kategoriebaum und Standardregeln - Endpunkte /api/health und /api/version - pytest-Infrastruktur mit transaktionsisolierten Fixtures Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_014e7t8UpmoVNMtWivY5LiSH
This commit is contained in:
@@ -0,0 +1,73 @@
|
||||
"""Benachrichtigungsregeln und Versandprotokoll."""
|
||||
|
||||
from datetime import date, datetime
|
||||
|
||||
from sqlalchemy import (
|
||||
Boolean,
|
||||
Date,
|
||||
DateTime,
|
||||
ForeignKey,
|
||||
Index,
|
||||
Integer,
|
||||
String,
|
||||
Text,
|
||||
UniqueConstraint,
|
||||
)
|
||||
from sqlalchemy.orm import Mapped, mapped_column, relationship
|
||||
|
||||
from app.db.base import Base, CreatedAtMixin
|
||||
from app.models.enums import (
|
||||
NotificationChannel,
|
||||
NotificationStatus,
|
||||
NotificationType,
|
||||
pg_enum,
|
||||
)
|
||||
|
||||
|
||||
class NotificationRule(Base, CreatedAtMixin):
|
||||
"""Eine Regel pro Ereignistyp und Kanal."""
|
||||
|
||||
__tablename__ = "notification_rule"
|
||||
|
||||
id: Mapped[int] = mapped_column(Integer, primary_key=True)
|
||||
type: Mapped[NotificationType] = mapped_column(
|
||||
pg_enum(NotificationType, "notification_type"), nullable=False
|
||||
)
|
||||
lead_days: Mapped[int] = mapped_column(Integer, nullable=False, default=7)
|
||||
channel: Mapped[NotificationChannel] = mapped_column(
|
||||
pg_enum(NotificationChannel, "notification_channel"), nullable=False
|
||||
)
|
||||
target: Mapped[str | None] = mapped_column(Text, nullable=True)
|
||||
is_active: Mapped[bool] = mapped_column(Boolean, nullable=False, default=True)
|
||||
|
||||
logs: Mapped[list["NotificationLog"]] = relationship(
|
||||
back_populates="rule", cascade="all, delete-orphan"
|
||||
)
|
||||
|
||||
|
||||
class NotificationLog(Base):
|
||||
"""Versandprotokoll. `dedupe_day` verhindert Doppelversand am selben Tag."""
|
||||
|
||||
__tablename__ = "notification_log"
|
||||
|
||||
id: Mapped[int] = mapped_column(Integer, primary_key=True)
|
||||
rule_id: Mapped[int] = mapped_column(
|
||||
ForeignKey("notification_rule.id", ondelete="CASCADE"), nullable=False
|
||||
)
|
||||
ref_type: Mapped[str] = mapped_column(String(40), nullable=False)
|
||||
ref_id: Mapped[str] = mapped_column(String(80), nullable=False)
|
||||
dedupe_day: Mapped[date] = mapped_column(Date, nullable=False)
|
||||
sent_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), nullable=False)
|
||||
status: Mapped[NotificationStatus] = mapped_column(
|
||||
pg_enum(NotificationStatus, "notification_status"), nullable=False
|
||||
)
|
||||
error: Mapped[str | None] = mapped_column(Text, nullable=True)
|
||||
|
||||
rule: Mapped[NotificationRule] = relationship(back_populates="logs")
|
||||
|
||||
__table_args__ = (
|
||||
UniqueConstraint(
|
||||
"rule_id", "ref_type", "ref_id", "dedupe_day", name="uq_notification_log_rule_id"
|
||||
),
|
||||
Index("ix_notification_log_sent_at", "sent_at"),
|
||||
)
|
||||
Reference in New Issue
Block a user