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.
69 lines
1.7 KiB
Go
69 lines
1.7 KiB
Go
package handlers
|
|
|
|
import (
|
|
"net/http"
|
|
|
|
"mgit.msbls.de/m/KanzlAI-mGMT/internal/auth"
|
|
"mgit.msbls.de/m/KanzlAI-mGMT/internal/services"
|
|
)
|
|
|
|
// CalDAVHandler handles CalDAV sync HTTP endpoints.
|
|
type CalDAVHandler struct {
|
|
svc *services.CalDAVService
|
|
}
|
|
|
|
// NewCalDAVHandler creates a new CalDAV handler.
|
|
func NewCalDAVHandler(svc *services.CalDAVService) *CalDAVHandler {
|
|
return &CalDAVHandler{svc: svc}
|
|
}
|
|
|
|
// TriggerSync handles POST /api/caldav/sync — triggers a full sync for the current tenant.
|
|
func (h *CalDAVHandler) TriggerSync(w http.ResponseWriter, r *http.Request) {
|
|
tenantID, ok := auth.TenantFromContext(r.Context())
|
|
if !ok {
|
|
writeError(w, http.StatusUnauthorized, "no tenant context")
|
|
return
|
|
}
|
|
|
|
cfg, err := h.svc.LoadTenantConfig(tenantID)
|
|
if err != nil {
|
|
writeError(w, http.StatusBadRequest, err.Error())
|
|
return
|
|
}
|
|
|
|
status, err := h.svc.SyncTenant(r.Context(), tenantID, *cfg)
|
|
if err != nil {
|
|
// Still return the status — it contains partial results + error info
|
|
writeJSON(w, http.StatusOK, map[string]any{
|
|
"status": "completed_with_errors",
|
|
"sync": status,
|
|
})
|
|
return
|
|
}
|
|
|
|
writeJSON(w, http.StatusOK, map[string]any{
|
|
"status": "ok",
|
|
"sync": status,
|
|
})
|
|
}
|
|
|
|
// GetStatus handles GET /api/caldav/status — returns last sync status.
|
|
func (h *CalDAVHandler) GetStatus(w http.ResponseWriter, r *http.Request) {
|
|
tenantID, ok := auth.TenantFromContext(r.Context())
|
|
if !ok {
|
|
writeError(w, http.StatusUnauthorized, "no tenant context")
|
|
return
|
|
}
|
|
|
|
status := h.svc.GetStatus(tenantID)
|
|
if status == nil {
|
|
writeJSON(w, http.StatusOK, map[string]any{
|
|
"status": "no_sync_yet",
|
|
"last_sync_at": nil,
|
|
})
|
|
return
|
|
}
|
|
|
|
writeJSON(w, http.StatusOK, status)
|
|
}
|