From 41b9426670000db74615383e3d2a9a6865bb2ec1 Mon Sep 17 00:00:00 2001 From: Albrecht Degering Date: Tue, 21 Jul 2026 12:33:43 +0200 Subject: [PATCH] Refactor calendar event update flow --- src/govoplan_calendar/backend/service.py | 109 +++++++++++++++++++---- 1 file changed, 93 insertions(+), 16 deletions(-) diff --git a/src/govoplan_calendar/backend/service.py b/src/govoplan_calendar/backend/service.py index 7d737d9..5ba30a5 100644 --- a/src/govoplan_calendar/backend/service.py +++ b/src/govoplan_calendar/backend/service.py @@ -2918,7 +2918,13 @@ def create_event(session: Session, *, tenant_id: str, user_id: str | None, paylo return event -def update_event(session: Session, *, tenant_id: str, user_id: str | None, event_id: str, payload: CalendarEventUpdateRequest) -> CalendarEvent: +def _event_update_calendar_ids( + session: Session, + *, + tenant_id: str, + event_id: str, + payload: CalendarEventUpdateRequest, +) -> tuple[str, str]: event_locator = ( session.query(CalendarEvent.calendar_id) .filter( @@ -2934,6 +2940,16 @@ def update_event(session: Session, *, tenant_id: str, user_id: str | None, event target_calendar_id = payload.calendar_id or original_calendar_id if payload.calendar_id is not None: get_calendar(session, tenant_id=tenant_id, calendar_id=target_calendar_id) + return original_calendar_id, target_calendar_id + + +def _lock_event_update_sources( + session: Session, + *, + tenant_id: str, + original_calendar_id: str, + target_calendar_id: str, +) -> tuple[CalendarSyncSource | None, CalendarSyncSource | None]: original_source = active_sync_source_for_calendar( session, tenant_id=tenant_id, @@ -2953,27 +2969,52 @@ def update_event(session: Session, *, tenant_id: str, user_id: str | None, event original_source = locked_sources[original_source.id] if target_source is not None: target_source = locked_sources[target_source.id] - event = get_event(session, tenant_id=tenant_id, event_id=event_id) - previous = calendar_event_change_payload(event, prefix="previous_") - original_caldav = event.source_kind == "caldav" and bool(event.source_href) + return original_source, target_source + + +def _assert_event_update_sync_fields( + payload: CalendarEventUpdateRequest, + *, + original_source: CalendarSyncSource | None, + target_source: CalendarSyncSource | None, +) -> None: supplied_sync_fields = {"source_kind", "source_href", "etag"} & payload.model_fields_set if (original_source is not None or target_source is not None) and supplied_sync_fields: raise CalendarError( "source_kind, source_href, and etag are sync-owned fields on synchronized calendars" ) - if payload.calendar_id is not None: - assert_sync_mutation_allowed(target_source) - if payload.calendar_id != event.calendar_id and original_source and original_caldav: - assert_sync_mutation_allowed(original_source) - from govoplan_calendar.backend.outbox import enqueue_caldav_delete - enqueue_caldav_delete(session, source=original_source, event_model=event) - event.source_kind = "local" - event.source_href = None - event.etag = None - event.calendar_id = payload.calendar_id - else: + +def _move_event_for_update( + session: Session, + *, + event: CalendarEvent, + payload: CalendarEventUpdateRequest, + original_source: CalendarSyncSource | None, + target_source: CalendarSyncSource | None, +) -> None: + if payload.calendar_id is None: assert_sync_mutation_allowed(original_source) + return + assert_sync_mutation_allowed(target_source) + original_caldav = event.source_kind == "caldav" and bool(event.source_href) + if payload.calendar_id != event.calendar_id and original_source and original_caldav: + assert_sync_mutation_allowed(original_source) + from govoplan_calendar.backend.outbox import enqueue_caldav_delete + + enqueue_caldav_delete(session, source=original_source, event_model=event) + event.source_kind = "local" + event.source_href = None + event.etag = None + event.calendar_id = payload.calendar_id + + +def _apply_event_update_values( + event: CalendarEvent, + *, + user_id: str | None, + payload: CalendarEventUpdateRequest, +) -> None: scalar_fields = ( "sequence", "summary", @@ -3002,7 +3043,13 @@ def update_event(session: Session, *, tenant_id: str, user_id: str | None, event for attr in scalar_fields: value = getattr(payload, attr) if value is not None: - setattr(event, attr, value.upper() if attr in {"status", "transparency", "classification"} and isinstance(value, str) else value) + value = ( + value.upper() + if attr in {"status", "transparency", "classification"} + and isinstance(value, str) + else value + ) + setattr(event, attr, value) if payload.start_at is not None: event.start_at = normalize_datetime(payload.start_at) if "end_at" in payload.model_fields_set: @@ -3012,6 +3059,36 @@ def update_event(session: Session, *, tenant_id: str, user_id: str | None, event event.updated_by_user_id = user_id if payload.sequence is None: event.sequence += 1 + + +def update_event(session: Session, *, tenant_id: str, user_id: str | None, event_id: str, payload: CalendarEventUpdateRequest) -> CalendarEvent: + original_calendar_id, target_calendar_id = _event_update_calendar_ids( + session, + tenant_id=tenant_id, + event_id=event_id, + payload=payload, + ) + original_source, target_source = _lock_event_update_sources( + session, + tenant_id=tenant_id, + original_calendar_id=original_calendar_id, + target_calendar_id=target_calendar_id, + ) + event = get_event(session, tenant_id=tenant_id, event_id=event_id) + previous = calendar_event_change_payload(event, prefix="previous_") + _assert_event_update_sync_fields( + payload, + original_source=original_source, + target_source=target_source, + ) + _move_event_for_update( + session, + event=event, + payload=payload, + original_source=original_source, + target_source=target_source, + ) + _apply_event_update_values(event, user_id=user_id, payload=payload) validate_event_time(event) event.raw_ics = None session.flush()