Files
webmail-client/plugins/webmail_plugin_calendar/webmail_plugin_calendar/schemas.py
T
lukasandClaude Opus 4.8 a27755a4d3 Calendar: CalDAV two-way sync (iCloud/generic) + all-day timegrid fix
CalDAV account connect (Settings → Calendars) with RFC 6764 discovery,
per-calendar vaulted credentials, and full two-way sync verified live
against NextCloud:

- caldav.py: discovery, calendar-query REPORT (pull), PUT/DELETE with
  quoted ETags (If-Match/If-None-Match), iCloud partition-redirect
  handling, ICS build/parse; clear error codes (auth/private-host/
  unreachable) surfaced to the connect UI.
- sync.py: ctag-gated pull + push of local changes; dirty rows protected
  from being clobbered; 15-min cron + immediate enqueue on every
  create/edit/delete so changes reach the server in ~1s.
- Dirty tracking via a BEFORE INSERT/UPDATE trigger (migration 0007),
  suppressed during sync with a txn-local GUC; deletes tombstone then
  DELETE server-side. Writability now driven by server privileges.
- migration 0006: event uniqueness NULLS NOT DISTINCT — fixes duplicate
  events on every full re-pull (NULL recurrence_id never matched ON CONFLICT).
- 5 CalDAV parsing unit tests.

Calendar UI (page.js): all-day events now render in Day/3-days/Week views
(were month-only). They share the timed-event column layout so overlapping
events sit side-by-side, fill the day column vertically with a sticky label,
and are matched by start day (fixes UTC-midnight events spilling into the
next local day).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-08-08 10:27:21 +02:00

81 lines
2.2 KiB
Python

"""Request models for the calendar API (SPEC §A)."""
from __future__ import annotations
import uuid
from datetime import datetime
from pydantic import BaseModel
class CalendarIn(BaseModel):
name: str
color: str = "#3b82f6"
class CalendarPatch(BaseModel):
name: str | None = None
color: str | None = None
publish: str | None = None # "off" | "full" | "busy" — public .ics feed control
class SubscribeIn(BaseModel):
url: str # source .ics URL to subscribe to (read-only external calendar)
name: str = "" # optional calendar name (defaults to the feed's title / host)
color: str = "#6b7280"
class DavConnectIn(BaseModel):
provider: str # "icloud" | "caldav"
server_url: str = "" # required for generic caldav; ignored for known providers
username: str
password: str
class Attendee(BaseModel):
email: str
name: str = ""
# RSVP status: needs-action | accepted | declined | tentative
partstat: str = "needs-action"
class EventIn(BaseModel):
calendar_id: uuid.UUID
summary: str = ""
description: str = ""
location: str = ""
dtstart: datetime
dtend: datetime | None = None
all_day: bool = False
tz: str = "UTC"
rrule: str | None = None
reminders: list[int] = []
attendees: list[Attendee] = [] # stored in A1; iMIP invites are sent in A2
organizer: Attendee | None = None
color: str | None = None # None = inherit the calendar colour
show_as: str = "busy" # busy | free
conference_url: str = "" # Jitsi Meet video-call link; empty = none
class EditFollowingIn(BaseModel):
# ``recurrence_start`` = the clicked occurrence's original start; ``event`` = the
# edited fields. Splits the series so occurrences BEFORE it are untouched.
recurrence_start: datetime
event: EventIn
class EventPatch(BaseModel):
summary: str | None = None
description: str | None = None
location: str | None = None
dtstart: datetime | None = None
dtend: datetime | None = None
all_day: bool | None = None
tz: str | None = None
rrule: str | None = None
reminders: list[int] | None = None
attendees: list[Attendee] | None = None
color: str | None = None
show_as: str | None = None
conference_url: str | None = None