summaryrefslogtreecommitdiff
path: root/internal/store/ics_feeds.go
blob: 6495e13bbffc854c6110d8668b7fb64ac1a485ff (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
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
}