Files
webmail-client/plugins/webmail_plugin_calendar/webmail_plugin_calendar/schemas.py
T
lukasandClaude Opus 4.8 e2bef3c41d M4 Calendar: publish public .ics links, subscriptions, multi-calendar settings
Calendar (builtin plugin):
- Publish read-only public .ics feed per calendar via vault-sealed token
  (full-detail or free/busy-only); unauthenticated GET /public/{token},
  404 on bad/absent token. Migration calendar0004 adds publish_token,
  publish_busy, subscription_url + partial unique index.
- Subscribe to external iCal (.ics) URLs (SSRF-guarded) as read-only
  calendars; CSRF-guarded POST /subscriptions.
- Settings > Calendars: create/edit/delete, publish toggles with live
  shareable link + copy, Add-account stubs (Google/iCloud/CalDAV, deferred),
  Subscriptions section; default calendar + default reminders; auto-detected
  time zone; color-dropdown contrast fix; 3x-larger edit arrow.
- Left panel: shared Mail-parity sidebar template (.wm-side-*), calendar
  search panel, font/spacing tuning, per-section "+" create shortcut.

Frontend: CalendarOut publish fields, api publish/subscribe, settings keys.
Backend: settings default_calendar + reminder lists.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01U72FZiF1W1pb8XxyWBTREA
2026-08-01 20:22:53 +02:00

74 lines
2.1 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 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