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