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:
marcandClaude Sonnet 5 committed 2026-09-03 22:27:33 +02:00
1 parent e44bed0a34
commit 87788817f8
11 files changed
+819 -1

No files matched your search

+41
View File
@@ -0,0 +1,41 @@
// Package calendar ist ein eigenständiges Modul, kein Kernbestandteil:
// CalDAV für Termine und Aufgaben. Es importiert nichts aus files, sharing
// oder admin – nur auth und acl, wie jeder andere Bereich auch. Ist es
// abgeschaltet, gibt es keine Route und keinen Menüpunkt; die Tabellen
// bleiben dann einfach ungenutzt liegen. Siehe kalender-plan.md.
package calendar
import (
"time"
"github.com/google/uuid"
)
// Calendar ist eine Sammlung von Terminen/Aufgaben – das, was ein Client als
// einen Kalender in seiner Liste zeigt.
type Calendar struct {
ID uuid.UUID `json:"id"`
OwnerID uuid.UUID `json:"owner_id"`
Name string `json:"name"`
Color string `json:"color"`
Description string `json:"description"`
SupportsTodo bool `json:"supports_todo"`
CTag int64 `json:"ctag"`
CreatedAt time.Time `json:"created_at"`
}
// Object ist ein einzelner Termin oder eine Aufgabe. Data ist die
// vollständige .ics-Datei, unverändert wie vom Client geschickt – siehe
// kalender-plan.md, Abschnitt "Felder".
type Object struct {
ID uuid.UUID `json:"id"`
CalendarID uuid.UUID `json:"calendar_id"`
UID string `json:"uid"`
Component string `json:"component"` // "VEVENT" | "VTODO"
Data string `json:"-"`
ETag string `json:"etag"`
DTStart *time.Time `json:"dtstart,omitempty"`
DTEnd *time.Time `json:"dtend,omitempty"`
CreatedAt time.Time `json:"created_at"`
UpdatedAt time.Time `json:"updated_at"`
}