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
}
|