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>
53 lines
2.8 KiB
SQL
53 lines
2.8 KiB
SQL
-- 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);
|