feat(calendar): schedule durable outbox recovery

This commit is contained in:
2026-07-20 16:57:01 +02:00
parent a2320fcb5d
commit 15596f0742
4 changed files with 186 additions and 1 deletions

View File

@@ -3,6 +3,7 @@ from __future__ import annotations
from celery import Celery
from govoplan_core.core.campaigns import CAPABILITY_CAMPAIGNS_DELIVERY_TASKS, CampaignDeliveryTaskProvider
from govoplan_core.core.calendar import CAPABILITY_CALENDAR_OUTBOX, CalendarOutboxProvider
from govoplan_core.core.module_management import load_startup_enabled_modules, startup_candidate_module_ids
from govoplan_core.core.modules import ModuleContext
from govoplan_core.core.notifications import CAPABILITY_NOTIFICATIONS_DISPATCH, NotificationDispatchProvider
@@ -27,10 +28,18 @@ celery.conf.update(
"govoplan.campaigns.append_sent": {"queue": "append_sent"},
"govoplan.notifications.deliver": {"queue": "notifications"},
"govoplan.notifications.deliver_pending": {"queue": "notifications"},
"govoplan.calendar.dispatch_outbox": {"queue": "calendar"},
},
worker_prefetch_multiplier=1,
task_acks_late=True,
task_reject_on_worker_lost=True,
beat_schedule={
"calendar-outbox-every-minute": {
"task": "govoplan.calendar.dispatch_outbox",
"schedule": 60.0,
"args": (None, 100),
},
},
)
@@ -67,6 +76,16 @@ def _notification_dispatch() -> NotificationDispatchProvider:
return capability
def _calendar_outbox() -> CalendarOutboxProvider | None:
registry = _platform_registry()
if not registry.has_capability(CAPABILITY_CALENDAR_OUTBOX):
return None
capability = registry.require_capability(CAPABILITY_CALENDAR_OUTBOX)
if not isinstance(capability, CalendarOutboxProvider):
raise RuntimeError("Calendar outbox capability is invalid")
return capability
@celery.task(name="govoplan.campaigns.send_email", bind=True, max_retries=0)
def send_email(self, job_id: str):
"""Send one explicitly queued campaign job.
@@ -111,3 +130,18 @@ def deliver_pending_notifications(self, tenant_id: str | None = None, limit: int
with get_database().SessionLocal() as session:
return dict(_notification_dispatch().deliver_pending(session, tenant_id=tenant_id, limit=limit))
@celery.task(name="govoplan.calendar.dispatch_outbox", bind=True, max_retries=0)
def dispatch_calendar_outbox(self, tenant_id: str | None = None, limit: int = 50):
"""Drain durable Calendar operations; retry timing lives in the database."""
from govoplan_core.db.session import get_database
with get_database().SessionLocal() as session:
provider = _calendar_outbox()
if provider is None:
return {"processed": 0, "succeeded": 0, "retrying": 0, "failed": 0, "operations": []}
result = dict(provider.dispatch_due(session, tenant_id=tenant_id, limit=limit))
session.commit()
return result