summaryrefslogtreecommitdiff
path: root/internal/store/ics_feeds.go
diff options
context:
space:
mode:
Diffstat (limited to 'internal/store/ics_feeds.go')
-rw-r--r--internal/store/ics_feeds.go95
1 files changed, 95 insertions, 0 deletions
diff --git a/internal/store/ics_feeds.go b/internal/store/ics_feeds.go
new file mode 100644
index 0000000..6495e13
--- /dev/null
+++ b/internal/store/ics_feeds.go
@@ -0,0 +1,95 @@
+package store
+
+import (
+ "database/sql"
+ "time"
+)
+
+// ICSFeed is a subscribed iCalendar feed.
+type ICSFeed struct {
+ ID int64
+ Name string
+ URL string
+ Enabled bool
+ LastFetchedAt *time.Time
+ LastError string
+ EventCount int
+ CreatedAt time.Time
+}
+
+// AddICSFeed stores a feed. Re-adding an existing URL updates its name and
+// re-enables it rather than failing on the unique constraint — "add it again"
+// is the obvious way to try to fix a feed that was turned off.
+func (s *Store) AddICSFeed(name, url string) error {
+ _, err := s.db.Exec(`
+ INSERT INTO ics_feeds (name, url, enabled)
+ VALUES (?, ?, 1)
+ ON CONFLICT(url) DO UPDATE SET name = excluded.name, enabled = 1
+ `, name, url)
+ return err
+}
+
+// GetICSFeeds returns every feed, enabled or not, newest name-order first.
+func (s *Store) GetICSFeeds() ([]ICSFeed, error) {
+ return s.queryICSFeeds(`
+ SELECT id, name, url, enabled, last_fetched_at, COALESCE(last_error, ''), event_count, created_at
+ FROM ics_feeds ORDER BY name`)
+}
+
+// GetEnabledICSFeeds returns only the feeds that should be fetched.
+func (s *Store) GetEnabledICSFeeds() ([]ICSFeed, error) {
+ return s.queryICSFeeds(`
+ SELECT id, name, url, enabled, last_fetched_at, COALESCE(last_error, ''), event_count, created_at
+ FROM ics_feeds WHERE enabled = 1 ORDER BY name`)
+}
+
+func (s *Store) queryICSFeeds(query string) ([]ICSFeed, error) {
+ rows, err := s.db.Query(query)
+ if err != nil {
+ return nil, err
+ }
+ defer func() { _ = rows.Close() }()
+
+ var feeds []ICSFeed
+ for rows.Next() {
+ var f ICSFeed
+ var lastFetched sql.NullTime
+ if err := rows.Scan(&f.ID, &f.Name, &f.URL, &f.Enabled, &lastFetched,
+ &f.LastError, &f.EventCount, &f.CreatedAt); err != nil {
+ return nil, err
+ }
+ if lastFetched.Valid {
+ t := lastFetched.Time
+ f.LastFetchedAt = &t
+ }
+ feeds = append(feeds, f)
+ }
+ return feeds, rows.Err()
+}
+
+// SetICSFeedEnabled turns a feed on or off without losing it.
+func (s *Store) SetICSFeedEnabled(id int64, enabled bool) error {
+ _, err := s.db.Exec(`UPDATE ics_feeds SET enabled = ? WHERE id = ?`, enabled, id)
+ return err
+}
+
+// DeleteICSFeed removes a feed entirely.
+func (s *Store) DeleteICSFeed(id int64) error {
+ _, err := s.db.Exec(`DELETE FROM ics_feeds WHERE id = ?`, id)
+ return err
+}
+
+// RecordICSFeedResult stores the outcome of a fetch. A successful fetch clears
+// any previous error, so a feed that recovers stops being reported as broken.
+func (s *Store) RecordICSFeedResult(id int64, eventCount int, fetchErr error) error {
+ errText := ""
+ if fetchErr != nil {
+ errText = fetchErr.Error()
+ }
+ _, err := s.db.Exec(`
+ UPDATE ics_feeds
+ SET last_fetched_at = CURRENT_TIMESTAMP, last_error = ?, event_count = ?
+ WHERE id = ?
+ `, errText, eventCount, id)
+ return err
+}