| """ |
| Webhook models — webhooks, webhook_deliveries, idempotency_keys. |
| |
| Maps to AGENT.md DBML: Lines 690-718. |
| """ |
| from django.conf import settings |
| from django.db import models |
|
|
| from core.models import BaseModel |
|
|
|
|
| class Webhook(BaseModel): |
| """Org-scoped webhook endpoint configuration.""" |
|
|
| org = models.ForeignKey( |
| "organizations.Organization", |
| on_delete=models.CASCADE, |
| related_name="webhooks", |
| ) |
| url = models.URLField() |
| secret = models.CharField(max_length=255) |
| events = models.JSONField(default=list, blank=True) |
| enabled = models.BooleanField(default=True) |
|
|
| class Meta: |
| db_table = "webhooks" |
| ordering = ["-created_at"] |
|
|
| def __str__(self) -> str: |
| return f"Webhook {self.url}" |
|
|
|
|
| class WebhookDelivery(BaseModel): |
| """Records each webhook delivery attempt.""" |
|
|
| class Status(models.TextChoices): |
| SUCCESS = "success", "Success" |
| FAILED = "failed", "Failed" |
| PENDING = "pending", "Pending" |
|
|
| webhook = models.ForeignKey( |
| Webhook, |
| on_delete=models.CASCADE, |
| related_name="deliveries", |
| ) |
| payload_hash = models.CharField(max_length=64, blank=True, default="") |
| status = models.CharField(max_length=20, choices=Status.choices, default=Status.PENDING) |
| response_code = models.PositiveIntegerField(default=0) |
| attempts = models.PositiveIntegerField(default=0) |
| next_retry_at = models.DateTimeField(null=True, blank=True) |
|
|
| class Meta: |
| db_table = "webhook_deliveries" |
| ordering = ["-created_at"] |
|
|
| def __str__(self) -> str: |
| return f"Delivery {self.pk} → {self.get_status_display()}" |
|
|
|
|
| class IdempotencyKey(BaseModel): |
| """Prevents duplicate request processing.""" |
|
|
| key = models.CharField(max_length=255, unique=True) |
| user = models.ForeignKey( |
| settings.AUTH_USER_MODEL, |
| on_delete=models.CASCADE, |
| related_name="idempotency_keys", |
| ) |
| request_hash = models.CharField(max_length=64, blank=True, default="") |
| response_hash = models.CharField(max_length=64, blank=True, default="") |
| expires_at = models.DateTimeField(null=True, blank=True) |
|
|
| class Meta: |
| db_table = "idempotency_keys" |
|
|
| def __str__(self) -> str: |
| return self.key |
|
|