Signed-off-by: Anirudh Oppiliappan anirudh@tangled.org
+7
appview/repo/router.go
+7
appview/repo/router.go
···
87
87
r.Put("/branches/default", rp.SetDefaultBranch)
88
88
r.Put("/secrets", rp.Secrets)
89
89
r.Delete("/secrets", rp.Secrets)
90
+
r.With(mw.RepoPermissionMiddleware("repo:owner")).Route("/hooks", func(r chi.Router) {
91
+
r.Get("/", rp.Webhooks)
92
+
r.Post("/", rp.AddWebhook)
93
+
r.Put("/{id}", rp.UpdateWebhook)
94
+
r.Delete("/{id}", rp.DeleteWebhook)
95
+
r.Post("/{id}/toggle", rp.ToggleWebhook)
96
+
})
90
97
})
91
98
})
92
99
+3
appview/repo/settings.go
+3
appview/repo/settings.go
+304
appview/repo/webhooks.go
+304
appview/repo/webhooks.go
···
1
+
package repo
2
+
3
+
import (
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
15
+
func (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
42
+
func (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
110
+
func (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
192
+
func (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
248
+
func (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
+
}
History
6 rounds
1 comment
anirudh.fi
submitted
#5
1 commit
expand
collapse
appview/repo: add webhook management endpoints
Signed-off-by: Anirudh Oppiliappan <anirudh@tangled.org>
2/3 failed, 1/3 success
expand
collapse
expand 0 comments
pull request successfully merged
anirudh.fi
submitted
#4
1 commit
expand
collapse
appview/repo: add webhook management endpoints
Signed-off-by: Anirudh Oppiliappan <anirudh@tangled.org>
2/3 failed, 1/3 success
expand
collapse
expand 0 comments
anirudh.fi
submitted
#3
1 commit
expand
collapse
appview/repo: add webhook management endpoints
Signed-off-by: Anirudh Oppiliappan <anirudh@tangled.org>
2/3 failed, 1/3 success
expand
collapse
expand 0 comments
anirudh.fi
submitted
#2
1 commit
expand
collapse
appview/repo: add webhook management endpoints
Signed-off-by: Anirudh Oppiliappan <anirudh@tangled.org>
2/3 failed, 1/3 success
expand
collapse
expand 0 comments
anirudh.fi
submitted
#1
1 commit
expand
collapse
appview/repo: add webhook management endpoints
Signed-off-by: Anirudh Oppiliappan <anirudh@tangled.org>
2/3 failed, 1/3 success
expand
collapse
expand 1 comment
anirudh.fi
submitted
#0
1 commit
expand
collapse
appview/repo: add webhook management endpoints
Signed-off-by: Anirudh Oppiliappan <anirudh@tangled.org>
changeset lgtm otherwise!