subscription.go
⎇
Raw
1package repository
2
3import (
4 "database/sql"
5 "fmt"
6 "time"
7
8 "vidarchive/internal/models"
9)
10
11type SubscriptionRepository struct {
12 db *sql.DB
13}
14
15func NewSubscriptionRepository(db *sql.DB) *SubscriptionRepository {
16 return &SubscriptionRepository{db: db}
17}
18
19const subscriptionColumns = `id, name, url, enabled, refresh_mode, schedule_kind, cron_expr,
20 preset_id, format_override, custom_flags, output_dir, prune_removed,
21 last_run_at, next_run_at, last_status, created_at`
22
23func (r *SubscriptionRepository) Create(s *models.Subscription) error {
24 result, err := r.db.Exec(
25 `INSERT INTO subscriptions
26 (name, url, enabled, refresh_mode, schedule_kind, cron_expr, preset_id,
27 format_override, custom_flags, output_dir, prune_removed, next_run_at)
28 VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
29 s.Name, s.URL, boolToInt(s.Enabled), s.RefreshMode, s.ScheduleKind, s.CronExpr,
30 s.PresetID, s.FormatOverride, s.CustomFlags, s.OutputDir, boolToInt(s.PruneRemoved),
31 s.NextRunAt,
32 )
33 if err != nil {
34 return err
35 }
36 id, err := result.LastInsertId()
37 if err != nil {
38 return fmt.Errorf("subscription last insert id: %w", err)
39 }
40 s.ID = id
41 return nil
42}
43
44func (r *SubscriptionRepository) GetByID(id int64) (*models.Subscription, error) {
45 row := r.db.QueryRow(`SELECT `+subscriptionColumns+` FROM subscriptions WHERE id = ?`, id)
46 return scanSubscription(row)
47}
48
49func (r *SubscriptionRepository) GetAll() ([]*models.Subscription, error) {
50 rows, err := r.db.Query(`SELECT ` + subscriptionColumns + ` FROM subscriptions ORDER BY created_at DESC`)
51 if err != nil {
52 return nil, err
53 }
54 defer rows.Close()
55 return scanSubscriptions(rows)
56}
57
58// GetDue returns enabled subscriptions whose next run is at or before now (or
59// has never run). The scheduler uses this to decide what to enqueue.
60func (r *SubscriptionRepository) GetDue(now time.Time) ([]*models.Subscription, error) {
61 rows, err := r.db.Query(
62 `SELECT `+subscriptionColumns+`
63 FROM subscriptions
64 WHERE enabled = 1 AND (next_run_at IS NULL OR next_run_at <= ?)
65 ORDER BY created_at ASC`,
66 now,
67 )
68 if err != nil {
69 return nil, err
70 }
71 defer rows.Close()
72 return scanSubscriptions(rows)
73}
74
75func (r *SubscriptionRepository) Update(s *models.Subscription) error {
76 _, err := r.db.Exec(
77 `UPDATE subscriptions SET name=?, url=?, enabled=?, refresh_mode=?, schedule_kind=?,
78 cron_expr=?, preset_id=?, format_override=?, custom_flags=?, output_dir=?,
79 prune_removed=?, next_run_at=?
80 WHERE id=?`,
81 s.Name, s.URL, boolToInt(s.Enabled), s.RefreshMode, s.ScheduleKind, s.CronExpr,
82 s.PresetID, s.FormatOverride, s.CustomFlags, s.OutputDir, boolToInt(s.PruneRemoved),
83 s.NextRunAt, s.ID,
84 )
85 return err
86}
87
88func (r *SubscriptionRepository) SetEnabled(id int64, enabled bool) error {
89 _, err := r.db.Exec(`UPDATE subscriptions SET enabled = ? WHERE id = ?`, boolToInt(enabled), id)
90 return err
91}
92
93// MarkRun records a run's outcome and the computed next run time.
94func (r *SubscriptionRepository) MarkRun(id int64, lastRunAt, nextRunAt time.Time, status string) error {
95 _, err := r.db.Exec(
96 `UPDATE subscriptions SET last_run_at = ?, next_run_at = ?, last_status = ? WHERE id = ?`,
97 lastRunAt, nextRunAt, status, id,
98 )
99 return err
100}
101
102func (r *SubscriptionRepository) Delete(id int64) error {
103 _, err := r.db.Exec(`DELETE FROM subscriptions WHERE id = ?`, id)
104 return err
105}
106
107func scanSubscriptions(rows *sql.Rows) ([]*models.Subscription, error) {
108 var subs []*models.Subscription
109 for rows.Next() {
110 s, err := scanSubscription(rows)
111 if err != nil {
112 return nil, err
113 }
114 subs = append(subs, s)
115 }
116 return subs, rows.Err()
117}
118
119func scanSubscription(row interface{ Scan(...interface{}) error }) (*models.Subscription, error) {
120 var s models.Subscription
121 var enabled, pruneRemoved int
122 var refreshMode, scheduleKind, cronExpr, formatOverride, customFlags, outputDir sql.NullString
123 err := row.Scan(
124 &s.ID, &s.Name, &s.URL, &enabled, &refreshMode, &scheduleKind, &cronExpr,
125 &s.PresetID, &formatOverride, &customFlags, &outputDir, &pruneRemoved,
126 &s.LastRunAt, &s.NextRunAt, &s.LastStatus, &s.CreatedAt,
127 )
128 if err != nil {
129 return nil, err
130 }
131 s.Enabled = enabled == 1
132 s.PruneRemoved = pruneRemoved == 1
133 s.RefreshMode = refreshMode.String
134 s.ScheduleKind = scheduleKind.String
135 s.CronExpr = cronExpr.String
136 s.FormatOverride = formatOverride.String
137 s.CustomFlags = customFlags.String
138 s.OutputDir = outputDir.String
139 return &s, nil
140}
141