feat(calendar): schedule durable outbox recovery
This commit is contained in:
98
src/govoplan_core/core/calendar.py
Normal file
98
src/govoplan_core/core/calendar.py
Normal file
@@ -0,0 +1,98 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from collections.abc import Mapping, Sequence
|
||||
from dataclasses import dataclass, field
|
||||
from datetime import datetime
|
||||
from typing import Protocol, runtime_checkable
|
||||
|
||||
|
||||
CAPABILITY_CALENDAR_SCHEDULING = "calendar.scheduling"
|
||||
CAPABILITY_CALENDAR_OUTBOX = "calendar.outbox"
|
||||
CALENDAR_AVAILABILITY_READ_SCOPE = "calendar:availability:read"
|
||||
CALENDAR_EVENT_WRITE_SCOPE = "calendar:event:write"
|
||||
|
||||
|
||||
class CalendarCapabilityError(ValueError):
|
||||
"""Stable error raised by Calendar capability implementations."""
|
||||
|
||||
|
||||
@dataclass(frozen=True, slots=True)
|
||||
class CalendarEventRequest:
|
||||
summary: str
|
||||
start_at: datetime
|
||||
calendar_id: str | None = None
|
||||
description: str | None = None
|
||||
location: str | None = None
|
||||
status: str = "CONFIRMED"
|
||||
transparency: str = "OPAQUE"
|
||||
classification: str = "PUBLIC"
|
||||
end_at: datetime | None = None
|
||||
timezone: str | None = None
|
||||
attendees: tuple[Mapping[str, object], ...] = ()
|
||||
categories: tuple[str, ...] = ()
|
||||
related_to: tuple[Mapping[str, object], ...] = ()
|
||||
metadata: Mapping[str, object] = field(default_factory=dict)
|
||||
|
||||
|
||||
@dataclass(frozen=True, slots=True)
|
||||
class CalendarEventRef:
|
||||
id: str
|
||||
calendar_id: str
|
||||
uid: str
|
||||
external_state: str = "local"
|
||||
outbox_operation_id: str | None = None
|
||||
|
||||
|
||||
@runtime_checkable
|
||||
class CalendarSchedulingProvider(Protocol):
|
||||
def list_freebusy(
|
||||
self,
|
||||
session: object,
|
||||
*,
|
||||
tenant_id: str,
|
||||
start_at: datetime,
|
||||
end_at: datetime,
|
||||
calendar_ids: Sequence[str] | None = None,
|
||||
) -> Sequence[Mapping[str, object]]:
|
||||
...
|
||||
|
||||
def create_event(
|
||||
self,
|
||||
session: object,
|
||||
*,
|
||||
tenant_id: str,
|
||||
user_id: str | None,
|
||||
request: CalendarEventRequest,
|
||||
) -> CalendarEventRef:
|
||||
...
|
||||
|
||||
|
||||
@runtime_checkable
|
||||
class CalendarOutboxProvider(Protocol):
|
||||
"""Stable worker boundary for Calendar-owned external side effects."""
|
||||
|
||||
def dispatch_due(
|
||||
self,
|
||||
session: object,
|
||||
*,
|
||||
tenant_id: str | None = None,
|
||||
limit: int = 50,
|
||||
) -> Mapping[str, object]:
|
||||
...
|
||||
|
||||
def calendar_scheduling_provider(registry: object | None) -> CalendarSchedulingProvider | None:
|
||||
if registry is None or not hasattr(registry, "has_capability"):
|
||||
return None
|
||||
if not registry.has_capability(CAPABILITY_CALENDAR_SCHEDULING):
|
||||
return None
|
||||
capability = registry.capability(CAPABILITY_CALENDAR_SCHEDULING)
|
||||
return capability if isinstance(capability, CalendarSchedulingProvider) else None
|
||||
|
||||
|
||||
def calendar_outbox_provider(registry: object | None) -> CalendarOutboxProvider | None:
|
||||
if registry is None or not hasattr(registry, "has_capability"):
|
||||
return None
|
||||
if not registry.has_capability(CAPABILITY_CALENDAR_OUTBOX):
|
||||
return None
|
||||
capability = registry.capability(CAPABILITY_CALENDAR_OUTBOX)
|
||||
return capability if isinstance(capability, CalendarOutboxProvider) else None
|
||||
Reference in New Issue
Block a user