summaryrefslogtreecommitdiff
path: root/internal/handlers/settings.go
diff options
context:
space:
mode:
authorPeter Stone <thepeterstone@gmail.com>2026-09-20 07:57:36 +0000
committerPeter Stone <thepeterstone@gmail.com>2026-09-20 07:57:36 +0000
commit4d160762aeef531208f8cf6094a0e58a9e7d53ec (patch)
tree79584994808a38a2518ad986dc739daf720d3d77 /internal/handlers/settings.go
parentc5ab2f9a7f6a0b1e456e13934268d1509504c55a (diff)
Add .ics calendar feed support
doot reads Google Calendar through a service account, by calendar ID. That cannot consume an .ics URL, and it cannot see a calendar subscribed to inside a personal Google account either, because Google does not allow re-sharing a subscribed calendar. Published schedules — sports fixtures, school terms, bin collections — are distributed almost exclusively as .ics, so none of them could be added at all. Feeds are now fetched on the normal calendar refresh and merged into calendar_events alongside Google events, so the timeline, widget and everything downstream keep seeing one list and needed no changes. Failure handling is deliberately asymmetric. A Google error is still returned as an error, unchanged, so an outage there surfaces. A single failing feed is recorded against that feed and skipped, because one dead URL should not take the whole calendar down — and last_error is shown in settings, since a feed that has quietly stopped resolving otherwise looks identical to a team with no games left. The parser handles what real feeds actually contain: line unfolding, CRLF and LF, UTC / TZID / VALUE=DATE times, and escaped text. Two details worth naming: a TZID time is wall-clock in that zone, and reading it as UTC would put a 1:15pm Pacific kickoff on the timeline at 1:15pm local — wrong by the offset but entirely plausible-looking. And non-calendar content is a hard error rather than zero events, because an HTML error page from a moved feed URL is the common failure and "no events" reads as success. RRULE is deliberately unsupported: every schedule feed this targets expands recurrence itself, one VEVENT per fixture, and a correct expansion is a large amount of subtle code. A recurring event appears once rather than wrongly. Settings gains an add/enable/disable/remove UI. webcal:// links are accepted and rewritten, since that is how feeds are usually advertised. 16 parser tests, 5 store tests. Verified against the real Seattle Seahawks feed: 21 events, all with valid times. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018AwnALZL6bD4VBtATDVv3B
Diffstat (limited to 'internal/handlers/settings.go')
-rw-r--r--internal/handlers/settings.go3
1 files changed, 3 insertions, 0 deletions
diff --git a/internal/handlers/settings.go b/internal/handlers/settings.go
index d140aaf..d6d78c0 100644
--- a/internal/handlers/settings.go
+++ b/internal/handlers/settings.go
@@ -23,6 +23,7 @@ func (h *Handler) HandleSettingsPage(w http.ResponseWriter, r *http.Request) {
}
googleTasksToken, _ := h.store.GetOAuthToken(api.GoogleTasksOAuthSource)
+ icsFeeds, _ := h.store.GetICSFeeds()
data := struct {
Configs map[string][]models.SourceConfig
@@ -34,6 +35,7 @@ func (h *Handler) HandleSettingsPage(w http.ResponseWriter, r *http.Request) {
GoogleTasksOAuthReady bool
GoogleTasksConnected bool
GoogleTasksError string
+ Feeds []icsFeedView
}{
Configs: bySource,
Sources: []string{"trello", "gcal", "gtasks"},
@@ -43,6 +45,7 @@ func (h *Handler) HandleSettingsPage(w http.ResponseWriter, r *http.Request) {
WebAuthnEnabled: h.WebAuthnEnabled,
GoogleTasksOAuthReady: h.googleTasksOAuthConfig != nil,
GoogleTasksConnected: googleTasksToken != nil,
+ Feeds: toICSFeedViews(icsFeeds),
GoogleTasksError: r.URL.Query().Get("google_tasks_error"),
}