feat(calendar): Phase 1 – CalDAV lesend und schreibend, ein Kalender pro Nutzer
Neues, eigenständiges Modul internal/calendar: implementiert caldav.Backend (emersion/go-webdav) gegen zwei neue Tabellen (cal_calendars, cal_objects). Endpunkt /caldav/ mit eigener Basic Auth (nur App-Passwörter, wie /dav), plus /.well-known/caldav für die automatische Einrichtung nach RFC 6764. Gegen echte Container getestet (nicht nur go build): Discovery-Kette Principal -> Home-Set -> automatisch angelegter Standardkalender, PUT/GET/DELETE eines Termins, REPORT mit Zeitraumfilter (Treffer und Nicht-Treffer), If-Match/If-None-Match-Vorbedingungen (412), und dass ein fremder Nutzer auf einen Kalender-Pfad mit 404 statt 403 abgewiesen wird. Zwei Fallen unterwegs gefunden und dokumentiert: - go-webdav vergleicht von CurrentUserPrincipal/CalendarHomeSetPath zurückgegebene Pfade direkt gegen r.URL.Path (inklusive Prefix) und bestimmt den Ressourcentyp allein an der Segmentzahl dahinter – der Nutzername beim Principal steht deshalb in einem Segment (principals-max), nicht als Unterpfad. - chi kennt REPORT/MKCALENDAR nicht von Haus aus (nur PROPFIND & Co. waren für /dav bereits registriert) und hätte sie sonst mit 405 abgewiesen, bevor der CalDAV-Handler überhaupt lief. Migration 000021 nur mit CREATE TABLE IF NOT EXISTS, wie in kalender-plan.md festgelegt. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
1 parent
e44bed0a34
commit
87788817f8
11 files changed
+819
-1
No files matched your search
@@ -0,0 +1,2 @@
|
||||
DROP TABLE IF EXISTS cal_objects;
|
||||
DROP TABLE IF EXISTS cal_calendars;
|
||||
@@ -0,0 +1,52 @@
|
||||
-- Kalender, Phase 1: ein Kalender pro Nutzer, CalDAV lesend und schreibend.
|
||||
--
|
||||
-- Nur CREATE TABLE, nichts an bestehenden Tabellen geändert. Siehe
|
||||
-- kalender-plan.md, Abschnitt "Migrationen": eine fehlschlagende Migration
|
||||
-- verhindert den Start der ganzen Cloud, deshalb hier ausschließlich die
|
||||
-- risikoärmste Operation, die es gibt – idempotent mit IF NOT EXISTS.
|
||||
|
||||
CREATE TABLE IF NOT EXISTS cal_calendars (
|
||||
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
owner_id UUID NOT NULL REFERENCES users(id) ON DELETE CASCADE,
|
||||
name TEXT NOT NULL,
|
||||
-- calendar-color (Apple-Namensraum, kein RFC, aber von Nextcloud,
|
||||
-- Thunderbird und DAVx5 unterstützt) – siehe kalender-plan.md.
|
||||
color TEXT NOT NULL DEFAULT '#4285f4',
|
||||
description TEXT NOT NULL DEFAULT '',
|
||||
-- Meldet der Kalender VTODO von Anfang an, muss ein Client sein Konto
|
||||
-- nicht neu einrichten, wenn Aufgaben später dazukommen.
|
||||
supports_todo BOOLEAN NOT NULL DEFAULT TRUE,
|
||||
-- Aendert sich bei jeder Aenderung an einem Termin dieses Kalenders.
|
||||
-- Daran erkennt der Client "hier gibt es etwas Neues", ohne jedes
|
||||
-- einzelne Objekt abzufragen.
|
||||
ctag BIGINT NOT NULL DEFAULT 1,
|
||||
created_at TIMESTAMPTZ NOT NULL DEFAULT now()
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS cal_objects (
|
||||
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
calendar_id UUID NOT NULL REFERENCES cal_calendars(id) ON DELETE CASCADE,
|
||||
-- UID aus der .ics-Datei – der Ausweis des Termins, bleibt beim
|
||||
-- Verschieben/Aendern gleich. Eindeutig je Kalender, nicht global.
|
||||
uid TEXT NOT NULL,
|
||||
-- 'VEVENT' oder 'VTODO'. Fuer die Ansicht spaeter, nicht fuer CalDAV
|
||||
-- selbst - der Client fragt nach Zeitraum, nicht nach Typ.
|
||||
component TEXT NOT NULL,
|
||||
-- Das vollstaendige .ics, byte-genau wie vom Client geschickt. Siehe
|
||||
-- kalender-plan.md: nie zerlegen und neu zusammensetzen, sonst gehen
|
||||
-- Felder verloren, die der Server nicht kennt (Anhaenge, Herstellerfelder).
|
||||
data TEXT NOT NULL,
|
||||
-- Aendert sich bei jedem Schreiben. Clients schicken es beim Aendern
|
||||
-- mit ("nur wenn es noch das hier ist") - das loest gleichzeitiges
|
||||
-- Schreiben zweier Geraete/Nutzer ohne zusaetzlichen Code.
|
||||
etag TEXT NOT NULL,
|
||||
-- Abgeleitet aus DTSTART/DTEND, nur zum Vorsortieren. Massgeblich fuer
|
||||
-- den Zeitraumfilter bleibt "data" selbst (RRULE eingeschlossen).
|
||||
dtstart TIMESTAMPTZ,
|
||||
dtend TIMESTAMPTZ,
|
||||
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
|
||||
updated_at TIMESTAMPTZ NOT NULL DEFAULT now(),
|
||||
UNIQUE (calendar_id, uid)
|
||||
);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS cal_objects_calendar_idx ON cal_objects(calendar_id);
|
||||
Reference in new issue
Block a user