Fix notification System

This commit is contained in:
2026-03-27 15:13:14 +01:00
parent a3247a69b6
commit e390d344ff
13 changed files with 1692 additions and 46 deletions
+125 -1
View File
@@ -22,7 +22,7 @@ from sqlalchemy import (
ARRAY, BigInteger, Boolean, DateTime, Enum, ForeignKey,
Integer, String, Text, func,
)
from sqlalchemy.dialects.postgresql import UUID
from sqlalchemy.dialects.postgresql import JSONB, UUID
from sqlalchemy.orm import DeclarativeBase, Mapped, mapped_column, relationship
@@ -202,3 +202,127 @@ class Attachment(Base):
# Relazione inversa verso Message
message: Mapped["Message"] = relationship("Message", back_populates="attachments")
# ─── Modelli Notifiche ────────────────────────────────────────────────────────
NotifChannelType = Enum(
"webhook", "email", "telegram", "whatsapp",
name="notification_channel_type",
create_type=False,
)
NotifStatus = Enum(
"pending", "sent", "failed", "skipped",
name="notification_status",
create_type=False,
)
class NotificationChannel(Base):
"""
Canale di notifica configurato da un tenant.
Corrisponde alla tabella `notification_channels` nel DB.
"""
__tablename__ = "notification_channels"
id: Mapped[uuid.UUID] = mapped_column(
UUID(as_uuid=True), primary_key=True, default=uuid.uuid4
)
tenant_id: Mapped[uuid.UUID] = mapped_column(UUID(as_uuid=True), nullable=False)
name: Mapped[str] = mapped_column(String(255), nullable=False)
channel_type: Mapped[str] = mapped_column(NotifChannelType, nullable=False)
is_active: Mapped[bool] = mapped_column(Boolean, nullable=False, default=True)
config: Mapped[dict | None] = mapped_column(JSONB, nullable=True)
config_enc: Mapped[str | None] = mapped_column(Text, nullable=True)
consecutive_failures: Mapped[int] = mapped_column(Integer, nullable=False, default=0)
circuit_open_until: Mapped[datetime | None] = mapped_column(
DateTime(timezone=True), nullable=True
)
created_by: Mapped[uuid.UUID | None] = mapped_column(UUID(as_uuid=True), nullable=True)
created_at: Mapped[datetime] = mapped_column(
DateTime(timezone=True), server_default=func.now()
)
updated_at: Mapped[datetime] = mapped_column(
DateTime(timezone=True), server_default=func.now(), onupdate=func.now()
)
rules: Mapped[list["NotificationRule"]] = relationship(
"NotificationRule", back_populates="channel", lazy="select"
)
logs: Mapped[list["NotificationLog"]] = relationship(
"NotificationLog", back_populates="channel", lazy="select"
)
class NotificationRule(Base):
"""
Regola evento PEC -> canale di notifica.
Corrisponde alla tabella `notification_rules` nel DB.
"""
__tablename__ = "notification_rules"
id: Mapped[uuid.UUID] = mapped_column(
UUID(as_uuid=True), primary_key=True, default=uuid.uuid4
)
tenant_id: Mapped[uuid.UUID] = mapped_column(UUID(as_uuid=True), nullable=False)
channel_id: Mapped[uuid.UUID] = mapped_column(
UUID(as_uuid=True),
ForeignKey("notification_channels.id", ondelete="CASCADE"),
nullable=False,
)
name: Mapped[str] = mapped_column(String(255), nullable=False)
event_type: Mapped[str] = mapped_column(String(100), nullable=False)
filter: Mapped[dict | None] = mapped_column(JSONB, nullable=True)
is_active: Mapped[bool] = mapped_column(Boolean, nullable=False, default=True)
created_at: Mapped[datetime] = mapped_column(
DateTime(timezone=True), server_default=func.now()
)
channel: Mapped["NotificationChannel"] = relationship(
"NotificationChannel", back_populates="rules"
)
class NotificationLog(Base):
"""
Log di ogni tentativo di notifica con retry e circuit breaker.
Corrisponde alla tabella `notification_log` nel DB.
"""
__tablename__ = "notification_log"
id: Mapped[uuid.UUID] = mapped_column(
UUID(as_uuid=True), primary_key=True, default=uuid.uuid4
)
tenant_id: Mapped[uuid.UUID] = mapped_column(UUID(as_uuid=True), nullable=False)
channel_id: Mapped[uuid.UUID] = mapped_column(
UUID(as_uuid=True),
ForeignKey("notification_channels.id", ondelete="CASCADE"),
nullable=False,
)
rule_id: Mapped[uuid.UUID | None] = mapped_column(
UUID(as_uuid=True),
ForeignKey("notification_rules.id", ondelete="SET NULL"),
nullable=True,
)
event_type: Mapped[str] = mapped_column(String(100), nullable=False)
event_payload: Mapped[dict | None] = mapped_column(JSONB, nullable=True)
status: Mapped[str] = mapped_column(NotifStatus, nullable=False, default="pending")
attempt_count: Mapped[int] = mapped_column(Integer, nullable=False, default=0)
max_attempts: Mapped[int] = mapped_column(Integer, nullable=False, default=3)
next_retry_at: Mapped[datetime | None] = mapped_column(
DateTime(timezone=True), nullable=True
)
last_error: Mapped[str | None] = mapped_column(Text, nullable=True)
http_status: Mapped[int | None] = mapped_column(Integer, nullable=True)
sent_at: Mapped[datetime | None] = mapped_column(DateTime(timezone=True), nullable=True)
created_at: Mapped[datetime] = mapped_column(
DateTime(timezone=True), server_default=func.now()
)
channel: Mapped["NotificationChannel"] = relationship(
"NotificationChannel", back_populates="logs"
)