-- 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);