Files
Aether/backend/db/migrations/000021_calendar.up.sql
marcandClaude Sonnet 5 87788817f8 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>
2026-09-03 22:27:33 +02:00

53 lines
2.8 KiB
SQL
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
-- 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);