Monorepo for Tangled
at 74318eac9fdd72cf69e916276814351931ed0dcb 304 lines 7.8 kB view raw
1package repo 2 3import ( 4 "net/http" 5 "strconv" 6 "strings" 7 8 "github.com/go-chi/chi/v5" 9 "tangled.org/core/appview/db" 10 "tangled.org/core/appview/models" 11 "tangled.org/core/appview/pages" 12) 13 14// Webhooks displays the webhooks settings page 15func (rp *Repo) Webhooks(w http.ResponseWriter, r *http.Request) { 16 l := rp.logger.With("handler", "Webhooks") 17 18 f, err := rp.repoResolver.Resolve(r) 19 if err != nil { 20 l.Error("failed to get repo and knot", "err", err) 21 w.WriteHeader(http.StatusBadRequest) 22 return 23 } 24 25 user := rp.oauth.GetMultiAccountUser(r) 26 27 webhooks, err := db.GetWebhooksForRepo(rp.db, f.RepoAt()) 28 if err != nil { 29 l.Error("failed to get webhooks", "err", err) 30 rp.pages.Notice(w, "webhooks-error", "Failed to load webhooks") 31 return 32 } 33 34 rp.pages.RepoWebhooksSettings(w, pages.RepoWebhooksSettingsParams{ 35 LoggedInUser: user, 36 RepoInfo: rp.repoResolver.GetRepoInfo(r, user), 37 Webhooks: webhooks, 38 }) 39} 40 41// AddWebhook creates a new webhook 42func (rp *Repo) AddWebhook(w http.ResponseWriter, r *http.Request) { 43 l := rp.logger.With("handler", "AddWebhook") 44 45 f, err := rp.repoResolver.Resolve(r) 46 if err != nil { 47 l.Error("failed to get repo and knot", "err", err) 48 w.WriteHeader(http.StatusBadRequest) 49 return 50 } 51 52 url := strings.TrimSpace(r.FormValue("url")) 53 if url == "" { 54 rp.pages.Notice(w, "webhooks-error", "Webhook URL is required") 55 return 56 } 57 58 if !strings.HasPrefix(url, "http://") && !strings.HasPrefix(url, "https://") { 59 rp.pages.Notice(w, "webhooks-error", "Webhook URL must start with http:// or https://") 60 return 61 } 62 63 secret := strings.TrimSpace(r.FormValue("secret")) 64 // if secret is empty, we don't sign 65 66 active := r.FormValue("active") == "on" 67 68 events := []string{} 69 if r.FormValue("event_push") == "on" { 70 events = append(events, string(models.WebhookEventPush)) 71 } 72 73 if len(events) == 0 { 74 rp.pages.Notice(w, "webhooks-error", "Push events must be enabled") 75 return 76 } 77 78 webhook := &models.Webhook{ 79 RepoAt: f.RepoAt(), 80 Url: url, 81 Secret: secret, 82 Active: active, 83 Events: events, 84 } 85 86 tx, err := rp.db.Begin() 87 if err != nil { 88 l.Error("failed to start transaction", "err", err) 89 rp.pages.Notice(w, "webhooks-error", "Failed to create webhook") 90 return 91 } 92 defer tx.Rollback() 93 94 if err := db.AddWebhook(tx, webhook); err != nil { 95 l.Error("failed to add webhook", "err", err) 96 rp.pages.Notice(w, "webhooks-error", "Failed to create webhook") 97 return 98 } 99 100 if err := tx.Commit(); err != nil { 101 l.Error("failed to commit transaction", "err", err) 102 rp.pages.Notice(w, "webhooks-error", "Failed to create webhook") 103 return 104 } 105 106 rp.pages.HxRefresh(w) 107} 108 109// UpdateWebhook updates an existing webhook 110func (rp *Repo) UpdateWebhook(w http.ResponseWriter, r *http.Request) { 111 l := rp.logger.With("handler", "UpdateWebhook") 112 113 f, err := rp.repoResolver.Resolve(r) 114 if err != nil { 115 l.Error("failed to get repo and knot", "err", err) 116 w.WriteHeader(http.StatusBadRequest) 117 return 118 } 119 120 idStr := chi.URLParam(r, "id") 121 id, err := strconv.ParseInt(idStr, 10, 64) 122 if err != nil { 123 l.Error("invalid webhook id", "err", err) 124 w.WriteHeader(http.StatusBadRequest) 125 return 126 } 127 128 webhook, err := db.GetWebhook(rp.db, id) 129 if err != nil { 130 l.Error("failed to get webhook", "err", err) 131 rp.pages.Notice(w, "webhooks-error", "Webhook not found") 132 return 133 } 134 135 // Verify webhook belongs to this repo 136 if webhook.RepoAt != f.RepoAt() { 137 l.Error("webhook does not belong to repo", "webhook_repo", webhook.RepoAt, "current_repo", f.RepoAt()) 138 w.WriteHeader(http.StatusForbidden) 139 return 140 } 141 142 url := strings.TrimSpace(r.FormValue("url")) 143 if url != "" { 144 if !strings.HasPrefix(url, "http://") && !strings.HasPrefix(url, "https://") { 145 rp.pages.Notice(w, "webhooks-error", "Webhook URL must start with http:// or https://") 146 return 147 } 148 webhook.Url = url 149 } 150 151 secret := strings.TrimSpace(r.FormValue("secret")) 152 if secret != "" { 153 webhook.Secret = secret 154 } 155 156 webhook.Active = r.FormValue("active") == "on" 157 158 // Parse events - only push events are supported for now 159 events := []string{} 160 if r.FormValue("event_push") == "on" { 161 events = append(events, string(models.WebhookEventPush)) 162 } 163 164 if len(events) > 0 { 165 webhook.Events = events 166 } 167 168 tx, err := rp.db.Begin() 169 if err != nil { 170 l.Error("failed to start transaction", "err", err) 171 rp.pages.Notice(w, "webhooks-error", "Failed to update webhook") 172 return 173 } 174 defer tx.Rollback() 175 176 if err := db.UpdateWebhook(tx, webhook); err != nil { 177 l.Error("failed to update webhook", "err", err) 178 rp.pages.Notice(w, "webhooks-error", "Failed to update webhook") 179 return 180 } 181 182 if err := tx.Commit(); err != nil { 183 l.Error("failed to commit transaction", "err", err) 184 rp.pages.Notice(w, "webhooks-error", "Failed to update webhook") 185 return 186 } 187 188 rp.pages.HxRefresh(w) 189} 190 191// DeleteWebhook deletes a webhook 192func (rp *Repo) DeleteWebhook(w http.ResponseWriter, r *http.Request) { 193 l := rp.logger.With("handler", "DeleteWebhook") 194 195 f, err := rp.repoResolver.Resolve(r) 196 if err != nil { 197 l.Error("failed to get repo and knot", "err", err) 198 w.WriteHeader(http.StatusBadRequest) 199 return 200 } 201 202 idStr := chi.URLParam(r, "id") 203 id, err := strconv.ParseInt(idStr, 10, 64) 204 if err != nil { 205 l.Error("invalid webhook id", "err", err) 206 w.WriteHeader(http.StatusBadRequest) 207 return 208 } 209 210 webhook, err := db.GetWebhook(rp.db, id) 211 if err != nil { 212 l.Error("failed to get webhook", "err", err) 213 w.WriteHeader(http.StatusNotFound) 214 return 215 } 216 217 // Verify webhook belongs to this repo 218 if webhook.RepoAt != f.RepoAt() { 219 l.Error("webhook does not belong to repo", "webhook_repo", webhook.RepoAt, "current_repo", f.RepoAt()) 220 w.WriteHeader(http.StatusForbidden) 221 return 222 } 223 224 tx, err := rp.db.Begin() 225 if err != nil { 226 l.Error("failed to start transaction", "err", err) 227 rp.pages.Notice(w, "webhooks-error", "Failed to delete webhook") 228 return 229 } 230 defer tx.Rollback() 231 232 if err := db.DeleteWebhook(tx, id); err != nil { 233 l.Error("failed to delete webhook", "err", err) 234 rp.pages.Notice(w, "webhooks-error", "Failed to delete webhook") 235 return 236 } 237 238 if err := tx.Commit(); err != nil { 239 l.Error("failed to commit transaction", "err", err) 240 rp.pages.Notice(w, "webhooks-error", "Failed to delete webhook") 241 return 242 } 243 244 rp.pages.HxRefresh(w) 245} 246 247// ToggleWebhook toggles the active state of a webhook 248func (rp *Repo) ToggleWebhook(w http.ResponseWriter, r *http.Request) { 249 l := rp.logger.With("handler", "ToggleWebhook") 250 251 f, err := rp.repoResolver.Resolve(r) 252 if err != nil { 253 l.Error("failed to get repo and knot", "err", err) 254 w.WriteHeader(http.StatusBadRequest) 255 return 256 } 257 258 idStr := chi.URLParam(r, "id") 259 id, err := strconv.ParseInt(idStr, 10, 64) 260 if err != nil { 261 l.Error("invalid webhook id", "err", err) 262 w.WriteHeader(http.StatusBadRequest) 263 return 264 } 265 266 webhook, err := db.GetWebhook(rp.db, id) 267 if err != nil { 268 l.Error("failed to get webhook", "err", err) 269 w.WriteHeader(http.StatusNotFound) 270 return 271 } 272 273 // Verify webhook belongs to this repo 274 if webhook.RepoAt != f.RepoAt() { 275 l.Error("webhook does not belong to repo", "webhook_repo", webhook.RepoAt, "current_repo", f.RepoAt()) 276 w.WriteHeader(http.StatusForbidden) 277 return 278 } 279 280 // Toggle the active state 281 webhook.Active = !webhook.Active 282 283 tx, err := rp.db.Begin() 284 if err != nil { 285 l.Error("failed to start transaction", "err", err) 286 rp.pages.Notice(w, "webhooks-error", "Failed to toggle webhook") 287 return 288 } 289 defer tx.Rollback() 290 291 if err := db.UpdateWebhook(tx, webhook); err != nil { 292 l.Error("failed to update webhook", "err", err) 293 rp.pages.Notice(w, "webhooks-error", "Failed to toggle webhook") 294 return 295 } 296 297 if err := tx.Commit(); err != nil { 298 l.Error("failed to commit transaction", "err", err) 299 rp.pages.Notice(w, "webhooks-error", "Failed to toggle webhook") 300 return 301 } 302 303 rp.pages.HxRefresh(w) 304}