feat: add CalDAV bidirectional sync service (Phase 3O)

Implements CalDAV sync using github.com/emersion/go-webdav:

- CalDAVService with background polling (configurable per-tenant interval)
- Push: deadlines -> VTODO, appointments -> VEVENT on create/update/delete
- Pull: periodic fetch from CalDAV, reconcile with local DB
- Conflict resolution: KanzlAI wins dates/status, CalDAV wins notes/description
- Conflicts logged as case_events with caldav_conflict type
- UID pattern: kanzlai-{deadline|appointment}-{uuid}@kanzlai.msbls.de
- CalDAV config per tenant in tenants.settings JSONB

Endpoints:
- POST /api/caldav/sync — trigger full sync for current tenant
- GET /api/caldav/status — last sync time, item counts, errors

8 unit tests for UID generation, parsing, path construction, config parsing.
This commit is contained in:
m
2026-03-25 14:01:30 +01:00
parent 2cf01073a3
commit 785df2ced4
7 changed files with 929 additions and 7 deletions

View File

@@ -12,7 +12,7 @@ import (
"mgit.msbls.de/m/KanzlAI-mGMT/internal/services"
)
func New(db *sqlx.DB, authMW *auth.Middleware, cfg *config.Config) http.Handler {
func New(db *sqlx.DB, authMW *auth.Middleware, cfg *config.Config, calDAVSvc *services.CalDAVService) http.Handler {
mux := http.NewServeMux()
// Services
@@ -118,6 +118,13 @@ func New(db *sqlx.DB, authMW *auth.Middleware, cfg *config.Config) http.Handler
scoped.HandleFunc("POST /api/ai/summarize-case", aiH.SummarizeCase)
}
// CalDAV sync endpoints
if calDAVSvc != nil {
calDAVH := handlers.NewCalDAVHandler(calDAVSvc)
scoped.HandleFunc("POST /api/caldav/sync", calDAVH.TriggerSync)
scoped.HandleFunc("GET /api/caldav/status", calDAVH.GetStatus)
}
// Wire: auth -> tenant routes go directly, scoped routes get tenant resolver
api.Handle("/api/", tenantResolver.Resolve(scoped))