Add ICS/webcal HTTP subscription calendars

Users can now subscribe to a remote ICS/webcal feed from the dashboard
(name + color), the same way they set up a real calendar or the virtual
Birthdays calendar. Subscriptions are read-only, per-user, and share the
"/cal/home/<name>/" namespace with real calendars and "birthdays" (name
collisions are rejected in both directions).

- internal/db: new ics_subscriptions table + CRUD (internal/db/ics.go);
  CreateCalendarWithColor checks for a colliding subscription name.
- internal/icssub: shared HTTP-fetch + TTL cache (15 min) for remote ICS
  calendars, with webcal:// -> https:// rewriting and stale-on-error
  fallback, used by both the web UI and the CalDAV backend.
- internal/web: dashboard "Subscribe to an ICS/webcal calendar" form,
  color picker, delete button (internal/web/ics.go,
  templates/dashboard.templ); month view renders subscription events in
  their chosen color, read-only (internal/web/calendar.go).
- internal/caldav: subscriptions are exposed as read-only calendars
  (internal/caldav/ics.go) - listed in PROPFIND, events served via
  GET/REPORT, PUT/DELETE on individual events rejected with 403, but
  DELETE on the calendar itself unsubscribes; calendar-color is injected
  the same way as for real calendars and Birthdays.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
This commit is contained in:
2026-08-20 22:10:49 +02:00
co-authored by Copilot
parent 2cb6e98db6
commit 1707ab4060
12 changed files with 818 additions and 104 deletions
+8
View File
@@ -93,6 +93,14 @@ func (s *Server) resourceCards(username string) ([]templates.ResourceCard, error
resources = append(resources, card)
}
subs, err := s.dbase.ListICSSubscriptions(username)
if err != nil {
return nil, fmt.Errorf("listing ics subscriptions: %w", err)
}
for _, sub := range subs {
resources = append(resources, templates.ResourceCard{Kind: "ics", Name: sub.Name, Color: sub.Color, URL: sub.URL})
}
return resources, nil
}