subscription.go
⎇
Raw
1package handler
2
3import (
4 "database/sql"
5 "net/http"
6 "strconv"
7 "strings"
8 "time"
9
10 "github.com/go-chi/chi/v5"
11
12 "vidarchive/internal/models"
13)
14
15func (h *Handler) Subscriptions(w http.ResponseWriter, r *http.Request) {
16 subscriptions, err := h.subscriptionSvc.GetAll()
17 if err != nil {
18 http.Error(w, err.Error(), http.StatusInternalServerError)
19 return
20 }
21 presets, err := h.presetSvc.GetAll()
22 if err != nil {
23 http.Error(w, err.Error(), http.StatusInternalServerError)
24 return
25 }
26
27 h.renderWithRequest(w, r, "subscriptions", PageData{
28 Title: "Subscriptions",
29 ActiveTab: "subscriptions",
30 Data: struct {
31 Subscriptions []*models.Subscription
32 Presets []*models.Preset
33 }{
34 Subscriptions: subscriptions,
35 Presets: presets,
36 },
37 })
38}
39
40// subscriptionFromForm builds and validates a Subscription from form values,
41// shared by create and update. It resolves/validates the schedule into a canonical
42// cron expression and requires an output directory (the subscription owns it).
43func (h *Handler) subscriptionFromForm(r *http.Request) (*models.Subscription, error) {
44 name := strings.TrimSpace(r.FormValue("name"))
45 url := strings.TrimSpace(r.FormValue("url"))
46 if name == "" {
47 return nil, errBadRequest("a name is required")
48 }
49 if url == "" {
50 return nil, errBadRequest("URL is required")
51 }
52
53 outputDir := strings.TrimSpace(r.FormValue("output_dir"))
54 if outputDir == "" {
55 return nil, errBadRequest("an output directory is required — the subscription owns this folder")
56 }
57
58 refreshMode := r.FormValue("refresh_mode")
59 switch refreshMode {
60 case "overwrite", "skip", "metadata":
61 default:
62 refreshMode = "overwrite"
63 }
64
65 scheduleKind := r.FormValue("schedule_kind")
66 cronExpr, err := h.subscriptionSvc.CronExprFor(scheduleKind, strings.TrimSpace(r.FormValue("cron_expr")))
67 if err != nil {
68 return nil, errBadRequest(err.Error())
69 }
70
71 sub := &models.Subscription{
72 Name: name,
73 URL: url,
74 Enabled: true,
75 RefreshMode: refreshMode,
76 ScheduleKind: scheduleKind,
77 CronExpr: cronExpr,
78 FormatOverride: r.FormValue("format_override"),
79 CustomFlags: r.FormValue("custom_flags"),
80 OutputDir: outputDir,
81 PruneRemoved: r.FormValue("prune_removed") == "1",
82 }
83 if pid := r.FormValue("preset_id"); pid != "" {
84 if id, err := strconv.ParseInt(pid, 10, 64); err == nil {
85 sub.PresetID = sql.NullInt64{Int64: id, Valid: true}
86 }
87 }
88 return sub, nil
89}
90
91func (h *Handler) CreateSubscription(w http.ResponseWriter, r *http.Request) {
92 if err := r.ParseForm(); err != nil {
93 http.Error(w, err.Error(), http.StatusBadRequest)
94 return
95 }
96
97 sub, err := h.subscriptionFromForm(r)
98 if err != nil {
99 http.Error(w, err.Error(), http.StatusBadRequest)
100 return
101 }
102
103 if next, err := h.subscriptionSvc.ComputeNextRun(sub, time.Now()); err == nil {
104 sub.NextRunAt = sql.NullTime{Time: next, Valid: true}
105 }
106
107 if err := h.subscriptionSvc.Create(sub); err != nil {
108 http.Error(w, err.Error(), http.StatusInternalServerError)
109 return
110 }
111 http.Redirect(w, r, "/subscriptions", http.StatusSeeOther)
112}
113
114func (h *Handler) UpdateSubscription(w http.ResponseWriter, r *http.Request) {
115 id, err := strconv.ParseInt(chi.URLParam(r, "id"), 10, 64)
116 if err != nil {
117 http.Error(w, "Invalid ID", http.StatusBadRequest)
118 return
119 }
120 if err := r.ParseForm(); err != nil {
121 http.Error(w, err.Error(), http.StatusBadRequest)
122 return
123 }
124
125 existing, err := h.subscriptionSvc.GetByID(id)
126 if err != nil {
127 http.Error(w, "Not found", http.StatusNotFound)
128 return
129 }
130
131 sub, err := h.subscriptionFromForm(r)
132 if err != nil {
133 http.Error(w, err.Error(), http.StatusBadRequest)
134 return
135 }
136 sub.ID = id
137 sub.Enabled = existing.Enabled
138 // Recompute the next run from the (possibly changed) schedule.
139 if next, err := h.subscriptionSvc.ComputeNextRun(sub, time.Now()); err == nil {
140 sub.NextRunAt = sql.NullTime{Time: next, Valid: true}
141 }
142
143 if err := h.subscriptionSvc.Update(sub); err != nil {
144 http.Error(w, err.Error(), http.StatusInternalServerError)
145 return
146 }
147 http.Redirect(w, r, "/subscriptions", http.StatusSeeOther)
148}
149
150func (h *Handler) ToggleSubscription(w http.ResponseWriter, r *http.Request) {
151 id, err := strconv.ParseInt(chi.URLParam(r, "id"), 10, 64)
152 if err != nil {
153 http.Error(w, "Invalid ID", http.StatusBadRequest)
154 return
155 }
156 sub, err := h.subscriptionSvc.GetByID(id)
157 if err != nil {
158 http.Error(w, "Not found", http.StatusNotFound)
159 return
160 }
161 if err := h.subscriptionSvc.SetEnabled(id, !sub.Enabled); err != nil {
162 http.Error(w, err.Error(), http.StatusInternalServerError)
163 return
164 }
165 http.Redirect(w, r, "/subscriptions", http.StatusSeeOther)
166}
167
168func (h *Handler) RunSubscription(w http.ResponseWriter, r *http.Request) {
169 id, err := strconv.ParseInt(chi.URLParam(r, "id"), 10, 64)
170 if err != nil {
171 http.Error(w, "Invalid ID", http.StatusBadRequest)
172 return
173 }
174 sub, err := h.subscriptionSvc.GetByID(id)
175 if err != nil {
176 http.Error(w, "Not found", http.StatusNotFound)
177 return
178 }
179 download, err := h.downloadSvc.CreateForSubscription(sub)
180 if err != nil {
181 http.Error(w, err.Error(), http.StatusInternalServerError)
182 return
183 }
184 h.workerPool.Submit(download)
185 http.Redirect(w, r, "/queue", http.StatusSeeOther)
186}
187
188func (h *Handler) DeleteSubscription(w http.ResponseWriter, r *http.Request) {
189 id, err := strconv.ParseInt(chi.URLParam(r, "id"), 10, 64)
190 if err != nil {
191 http.Error(w, "Invalid ID", http.StatusBadRequest)
192 return
193 }
194 if err := h.subscriptionSvc.Delete(id); err != nil {
195 http.Error(w, err.Error(), http.StatusInternalServerError)
196 return
197 }
198 http.Redirect(w, r, "/subscriptions", http.StatusSeeOther)
199}
200
201// errBadRequest is a small sentinel-style error carrying a user-facing message.
202type errBadRequest string
203
204func (e errBadRequest) Error() string { return string(e) }
205