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
@@ -26,6 +26,7 @@ import (
|
||||
"aether/internal/audit"
|
||||
"aether/internal/auth"
|
||||
"aether/internal/avatar"
|
||||
"aether/internal/calendar"
|
||||
"aether/internal/documents"
|
||||
"aether/internal/files"
|
||||
"aether/internal/gc"
|
||||
@@ -246,6 +247,10 @@ func main() {
|
||||
// WebDAV
|
||||
davHandler := appwebdav.NewHandler(authStore, fileStore, aclStore, blobStore)
|
||||
|
||||
// CalDAV
|
||||
calStore := calendar.NewStore(pool)
|
||||
calHandler := calendar.NewHandler(authStore, calStore)
|
||||
|
||||
// OIDC (optional – disabled if OIDC_ISSUER not set)
|
||||
oidcHandler, err := appoidc.New(ctx, appoidc.Config{
|
||||
Issuer: cfg.OIDCIssuer,
|
||||
@@ -306,7 +311,7 @@ func main() {
|
||||
// chi lehnt Methoden ab, die es nicht kennt – ohne diese Registrierung
|
||||
// beantwortet der Router jedes PROPFIND & Co. mit 405, bevor der
|
||||
// WebDAV-Handler (und damit dessen Auth) überhaupt erreicht wird.
|
||||
for _, m := range []string{"PROPFIND", "PROPPATCH", "MKCOL", "COPY", "MOVE", "LOCK", "UNLOCK"} {
|
||||
for _, m := range []string{"PROPFIND", "PROPPATCH", "MKCOL", "COPY", "MOVE", "LOCK", "UNLOCK", "REPORT", "MKCALENDAR"} {
|
||||
chi.RegisterMethod(m)
|
||||
}
|
||||
|
||||
@@ -352,6 +357,12 @@ func main() {
|
||||
// Prefix-Feld. Beides zusammen strippt doppelt und liefert nur noch 404.
|
||||
r.Mount("/dav", davHandler)
|
||||
|
||||
// CalDAV endpoint – eigene Basic Auth (App-Passwörter), wie /dav.
|
||||
// .well-known/caldav liegt bewusst außerhalb von /caldav: RFC 6764 sieht
|
||||
// diesen Pfad an der Wurzel vor, Clients wie DAVx5 fragen dort zuerst an.
|
||||
r.Mount("/caldav", calHandler)
|
||||
r.Get("/.well-known/caldav", calHandler.ServeWellKnown)
|
||||
|
||||
// Public config (no auth) – used by login page to discover OIDC availability
|
||||
r.Get("/api/v1/public/config", func(w http.ResponseWriter, r *http.Request) {
|
||||
cloudName := "Aether"
|
||||
|
||||
Reference in new issue
Block a user