feat(calendar): make external mutations durable

This commit is contained in:
2026-07-20 16:58:45 +02:00
parent 371a00aff5
commit a6cd64e3f9
15 changed files with 4726 additions and 152 deletions

View File

@@ -1,7 +1,7 @@
from __future__ import annotations
import uuid
from datetime import datetime
from datetime import datetime, timezone
from typing import Any
from sqlalchemy import Boolean, DateTime, ForeignKey, Index, Integer, JSON, String, Text, UniqueConstraint, text
@@ -151,4 +151,69 @@ class CalendarSyncCredential(Base, TimestampMixin):
metadata_: Mapped[dict[str, Any] | None] = mapped_column("metadata", JSON, nullable=True)
__all__ = ["CalendarCollection", "CalendarEvent", "CalendarSyncCredential", "CalendarSyncSource", "new_uuid"]
class CalendarOutboxOperation(Base, TimestampMixin):
"""Durable desired state for one external CalDAV resource.
Rows are inserted in the same transaction as the local event mutation. A
dispatcher leases and commits the row before performing network I/O, so a
worker crash can be detected and reconciled without repeating a blind
write.
"""
__tablename__ = "calendar_outbox_operations"
__table_args__ = (
UniqueConstraint("idempotency_key", name="uq_calendar_outbox_operations_idempotency_key"),
Index("ix_calendar_outbox_due", "status", "available_at", "created_at"),
Index("ix_calendar_outbox_tenant_status", "tenant_id", "status"),
Index("ix_calendar_outbox_resource", "source_id", "resource_href", "created_at"),
Index("ix_calendar_outbox_lease", "status", "lease_expires_at"),
)
id: Mapped[str] = mapped_column(String(36), primary_key=True, default=new_uuid)
tenant_id: Mapped[str] = mapped_column(String(36), nullable=False, index=True)
source_id: Mapped[str] = mapped_column(
ForeignKey("calendar_sync_sources.id", ondelete="CASCADE"),
nullable=False,
index=True,
)
event_id: Mapped[str | None] = mapped_column(
ForeignKey("calendar_events.id", ondelete="SET NULL"),
nullable=True,
index=True,
)
operation_kind: Mapped[str] = mapped_column(String(20), nullable=False, index=True)
resource_href: Mapped[str] = mapped_column(String(1000), nullable=False)
payload_ics: Mapped[str | None] = mapped_column(Text)
payload_fingerprint: Mapped[str | None] = mapped_column(String(64), nullable=True, index=True)
expected_etag: Mapped[str | None] = mapped_column(String(255))
idempotency_key: Mapped[str] = mapped_column(String(64), nullable=False)
status: Mapped[str] = mapped_column(String(30), default="pending", nullable=False, index=True)
attempt_count: Mapped[int] = mapped_column(Integer, default=0, nullable=False)
max_attempts: Mapped[int] = mapped_column(Integer, default=8, nullable=False)
available_at: Mapped[datetime] = mapped_column(
DateTime(timezone=True),
default=lambda: datetime.now(timezone.utc),
nullable=False,
index=True,
)
last_attempt_at: Mapped[datetime | None] = mapped_column(DateTime(timezone=True), nullable=True)
lease_token: Mapped[str | None] = mapped_column(String(36), nullable=True, index=True)
lease_expires_at: Mapped[datetime | None] = mapped_column(DateTime(timezone=True), nullable=True, index=True)
completed_at: Mapped[datetime | None] = mapped_column(DateTime(timezone=True), nullable=True)
reconciled_at: Mapped[datetime | None] = mapped_column(DateTime(timezone=True), nullable=True)
remote_etag: Mapped[str | None] = mapped_column(String(255))
last_error: Mapped[str | None] = mapped_column(Text)
metadata_: Mapped[dict[str, Any] | None] = mapped_column("metadata", JSON, nullable=True)
source: Mapped[CalendarSyncSource] = relationship()
event: Mapped[CalendarEvent | None] = relationship()
__all__ = [
"CalendarCollection",
"CalendarEvent",
"CalendarOutboxOperation",
"CalendarSyncCredential",
"CalendarSyncSource",
"new_uuid",
]