feat: release regional radar and mailbox integrations
deploy / deploy (push) Canceled after 0s

This commit is contained in:
Jens
2026-07-22 05:12:07 +02:00
parent 598d3ec18a
commit 551d0f46c2
131 changed files with 6209 additions and 336 deletions
+109 -2
View File
@@ -62,7 +62,9 @@ class Source(TimeStampedModel):
ordering = ["name"]
constraints = [
models.UniqueConstraint(
fields=["domain", "source_type"], name="unique_domain_source_type"
fields=["source_type", "base_url"],
condition=~models.Q(base_url=""),
name="unique_source_type_base_url_nonempty",
)
]
@@ -315,8 +317,101 @@ class RawDocument(TimeStampedModel):
return self.final_url or self.url or f"Document {self.pk}"
class MailboxConnection(TimeStampedModel):
class Platform(models.TextChoices):
VDAB = "vdab", "VDAB"
INDEED = "indeed", "Indeed"
LINKEDIN = "linkedin", "LinkedIn"
ICTJOB = "ictjob", "ictjob.be"
JOBAT = "jobat", "Jobat"
STEPSTONE = "stepstone", "StepStone"
CAREERJET = "careerjet", "Careerjet"
RANDSTAD = "randstad", "Randstad"
ROBERTHALF = "roberthalf", "Robert Half"
class Provider(models.TextChoices):
GMAIL = "gmail", "Gmail"
OUTLOOK = "outlook", "Outlook / Microsoft 365"
CUSTOM = "custom", "Andere IMAP-provider"
class PollInterval(models.IntegerChoices):
FIVE_MINUTES = 5, "Elke 5 minuten"
FIFTEEN_MINUTES = 15, "Elke 15 minuten"
THIRTY_MINUTES = 30, "Elke 30 minuten"
HOURLY = 60, "Elk uur"
THREE_HOURS = 180, "Elke 3 uur"
SIX_HOURS = 360, "Elke 6 uur"
PROVIDER_HOSTS = {
Provider.GMAIL: "imap.gmail.com",
Provider.OUTLOOK: "outlook.office365.com",
}
user = models.ForeignKey(
settings.AUTH_USER_MODEL,
on_delete=models.CASCADE,
related_name="mailbox_connections",
)
platform = models.CharField(max_length=24, choices=Platform.choices)
provider = models.CharField(max_length=24, choices=Provider.choices)
custom_host = models.CharField(max_length=255, blank=True)
port = models.PositiveIntegerField(default=993)
username = models.CharField(max_length=320)
encrypted_password = models.TextField()
mailbox = models.CharField(max_length=255, default="INBOX")
enabled = models.BooleanField(default=True)
poll_interval_minutes = models.PositiveIntegerField(
choices=PollInterval.choices,
default=PollInterval.THIRTY_MINUTES,
)
next_poll_at = models.DateTimeField(default=timezone.now, db_index=True)
last_polled_at = models.DateTimeField(null=True, blank=True)
last_success_at = models.DateTimeField(null=True, blank=True)
last_error_category = models.CharField(max_length=80, blank=True)
last_error_message = models.CharField(max_length=500, blank=True)
lease_token = models.CharField(max_length=64, blank=True)
lease_expires_at = models.DateTimeField(null=True, blank=True, db_index=True)
class Meta:
ordering = ["platform"]
constraints = [
models.UniqueConstraint(
fields=["user", "platform"], name="unique_mailbox_platform_per_user"
)
]
def __str__(self) -> str:
return f"{self.get_platform_display()} · {self.username}"
@property
def imap_host(self) -> str:
return self.PROVIDER_HOSTS.get(self.provider, self.custom_host).strip().lower()
def clean(self) -> None:
host = self.imap_host
if (
not host
or "://" in host
or "/" in host
or "@" in host
or any(c.isspace() for c in host)
):
raise ValidationError({"custom_host": "Vul een geldige IMAP-hostnaam in."})
if not 1 <= self.port <= 65535:
raise ValidationError({"port": "De IMAP-poort moet tussen 1 en 65535 liggen."})
if "\n" in self.mailbox or "\r" in self.mailbox:
raise ValidationError({"mailbox": "De mailboxnaam bevat ongeldige tekens."})
class EmailMessageRecord(TimeStampedModel):
message_id = models.CharField(max_length=998, unique=True)
mailbox_connection = models.ForeignKey(
MailboxConnection,
on_delete=models.CASCADE,
null=True,
blank=True,
related_name="messages",
)
message_id = models.CharField(max_length=998)
mailbox = models.CharField(max_length=255, default="INBOX")
sender = models.CharField(max_length=500, blank=True)
subject = models.CharField(max_length=998, blank=True)
@@ -330,6 +425,18 @@ class EmailMessageRecord(TimeStampedModel):
class Meta:
ordering = ["-received_at", "-created_at"]
constraints = [
models.UniqueConstraint(
fields=["mailbox_connection", "message_id"],
condition=models.Q(mailbox_connection__isnull=False),
name="unique_message_per_mailbox_connection",
),
models.UniqueConstraint(
fields=["message_id"],
condition=models.Q(mailbox_connection__isnull=True),
name="unique_legacy_email_message_id",
),
]
def __str__(self) -> str:
return self.subject or self.message_id