feat(notifications): added notifications business logic
This commit is contained in:
@@ -0,0 +1,183 @@
|
||||
from typing import override
|
||||
|
||||
from django.db import models
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
|
||||
from apps.core.models import BaseModel
|
||||
|
||||
|
||||
class ChannelType(models.TextChoices):
|
||||
TELEGRAM = "telegram", _("Telegram")
|
||||
SMTP = "smtp", _("SMTP Email")
|
||||
|
||||
|
||||
class NotificationEventType(models.TextChoices):
|
||||
EXPERIMENT_STARTED = "experiment_started", _("Experiment started")
|
||||
EXPERIMENT_PAUSED = "experiment_paused", _("Experiment paused")
|
||||
EXPERIMENT_COMPLETED = "experiment_completed", _("Experiment completed")
|
||||
GUARDRAIL_TRIGGERED = "guardrail_triggered", _("Guardrail triggered")
|
||||
REVIEW_REQUESTED = "review_requested", _("Review requested")
|
||||
REVIEW_APPROVED = "review_approved", _("Review approved")
|
||||
REVIEW_REJECTED = "review_rejected", _("Review rejected")
|
||||
|
||||
|
||||
class NotificationStatus(models.TextChoices):
|
||||
PENDING = "pending", _("Pending")
|
||||
SENT = "sent", _("Sent")
|
||||
FAILED = "failed", _("Failed")
|
||||
|
||||
|
||||
class NotificationChannel(BaseModel):
|
||||
channel_type = models.CharField(
|
||||
max_length=20,
|
||||
choices=ChannelType.choices,
|
||||
verbose_name=_("channel type"),
|
||||
)
|
||||
name = models.CharField(
|
||||
max_length=200,
|
||||
verbose_name=_("name"),
|
||||
)
|
||||
config = models.JSONField(
|
||||
default=dict,
|
||||
blank=True,
|
||||
verbose_name=_("configuration"),
|
||||
help_text=_("Provider-specific settings (tokens, chat IDs, SMTP host, etc.)"),
|
||||
)
|
||||
is_active = models.BooleanField(
|
||||
default=True,
|
||||
verbose_name=_("is active"),
|
||||
)
|
||||
created_at = models.DateTimeField(
|
||||
auto_now_add=True,
|
||||
verbose_name=_("created at"),
|
||||
)
|
||||
updated_at = models.DateTimeField(
|
||||
auto_now=True,
|
||||
verbose_name=_("updated at"),
|
||||
)
|
||||
|
||||
class Meta:
|
||||
verbose_name = _("notification channel")
|
||||
verbose_name_plural = _("notification channels")
|
||||
ordering = ["-created_at"]
|
||||
|
||||
@override
|
||||
def __str__(self) -> str:
|
||||
return f"{self.name} ({self.channel_type})"
|
||||
|
||||
|
||||
class NotificationRule(BaseModel):
|
||||
event_type = models.CharField(
|
||||
max_length=30,
|
||||
choices=NotificationEventType.choices,
|
||||
verbose_name=_("event type"),
|
||||
)
|
||||
channel = models.ForeignKey(
|
||||
NotificationChannel,
|
||||
on_delete=models.CASCADE,
|
||||
related_name="rules",
|
||||
verbose_name=_("channel"),
|
||||
)
|
||||
experiment = models.ForeignKey(
|
||||
"experiments.Experiment",
|
||||
on_delete=models.CASCADE,
|
||||
related_name="notification_rules",
|
||||
verbose_name=_("experiment"),
|
||||
null=True,
|
||||
blank=True,
|
||||
help_text=_("If null, applies to all experiments."),
|
||||
)
|
||||
is_active = models.BooleanField(
|
||||
default=True,
|
||||
verbose_name=_("is active"),
|
||||
)
|
||||
created_at = models.DateTimeField(
|
||||
auto_now_add=True,
|
||||
verbose_name=_("created at"),
|
||||
)
|
||||
updated_at = models.DateTimeField(
|
||||
auto_now=True,
|
||||
verbose_name=_("updated at"),
|
||||
)
|
||||
|
||||
class Meta:
|
||||
verbose_name = _("notification rule")
|
||||
verbose_name_plural = _("notification rules")
|
||||
ordering = ["-created_at"]
|
||||
indexes = [
|
||||
models.Index(
|
||||
fields=["event_type", "is_active"],
|
||||
name="idx_rule_event_active",
|
||||
),
|
||||
]
|
||||
|
||||
@override
|
||||
def __str__(self) -> str:
|
||||
scope = self.experiment.name if self.experiment else "all"
|
||||
return f"{self.event_type} -> {self.channel.name} ({scope})"
|
||||
|
||||
|
||||
class NotificationLog(BaseModel):
|
||||
rule = models.ForeignKey(
|
||||
NotificationRule,
|
||||
on_delete=models.SET_NULL,
|
||||
null=True,
|
||||
related_name="logs",
|
||||
verbose_name=_("rule"),
|
||||
)
|
||||
channel = models.ForeignKey(
|
||||
NotificationChannel,
|
||||
on_delete=models.SET_NULL,
|
||||
null=True,
|
||||
related_name="logs",
|
||||
verbose_name=_("channel"),
|
||||
)
|
||||
event_type = models.CharField(
|
||||
max_length=30,
|
||||
choices=NotificationEventType.choices,
|
||||
verbose_name=_("event type"),
|
||||
)
|
||||
event_key = models.CharField(
|
||||
max_length=255,
|
||||
verbose_name=_("dedup key"),
|
||||
db_index=True,
|
||||
)
|
||||
payload = models.JSONField(
|
||||
default=dict,
|
||||
verbose_name=_("payload"),
|
||||
)
|
||||
status = models.CharField(
|
||||
max_length=10,
|
||||
choices=NotificationStatus.choices,
|
||||
default=NotificationStatus.PENDING,
|
||||
verbose_name=_("status"),
|
||||
)
|
||||
error = models.TextField(
|
||||
blank=True,
|
||||
default="",
|
||||
verbose_name=_("error details"),
|
||||
)
|
||||
created_at = models.DateTimeField(
|
||||
auto_now_add=True,
|
||||
verbose_name=_("created at"),
|
||||
)
|
||||
sent_at = models.DateTimeField(
|
||||
null=True,
|
||||
blank=True,
|
||||
verbose_name=_("sent at"),
|
||||
)
|
||||
|
||||
class Meta:
|
||||
verbose_name = _("notification log")
|
||||
verbose_name_plural = _("notification logs")
|
||||
ordering = ["-created_at"]
|
||||
indexes = [
|
||||
models.Index(
|
||||
fields=["status", "-created_at"],
|
||||
name="idx_notif_log_status",
|
||||
),
|
||||
]
|
||||
|
||||
@override
|
||||
def __str__(self) -> str:
|
||||
return f"Notification({self.event_type}, {self.status})"
|
||||
Reference in New Issue
Block a user