-
-
Notifications
You must be signed in to change notification settings - Fork 79
Expand file tree
/
Copy pathwebhook.v
More file actions
211 lines (190 loc) · 4.7 KB
/
webhook.v
File metadata and controls
211 lines (190 loc) · 4.7 KB
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
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
// Copyright (c) 2019-2026 Alexander Medvednikov. All rights reserved.
// Use of this source code is governed by a GPL license that can be found in the LICENSE file.
module main
import time
import net.http
import crypto.hmac
import crypto.sha256
import encoding.hex
import json
pub struct WebhookIssuePayload {
action string
repo string
title string
author string
}
pub struct WebhookPrPayload {
action string
repo string
number int
title string
author string
head string
base string
}
pub struct WebhookCommentPayload {
action string
repo string
target string // 'issue' or 'pr'
number int
author string
text string
}
pub struct WebhookReleasePayload {
action string
repo string
tag string
author string
}
pub struct WebhookPushPayload {
repo string
ref string
author string
}
struct Webhook {
id int @[primary; sql: serial]
mut:
repo_id int
url string
secret string
events string // comma-separated: push,issue,pr,comment,release
is_active bool
created_at int
last_status int
last_delivery int
}
struct WebhookDelivery {
id int @[primary; sql: serial]
mut:
webhook_id int
event string
status_code int
response_body string
created_at int
}
fn (w &Webhook) has_event(name string) bool {
if w.events == '' {
return true
}
for ev in w.events.split(',') {
if ev.trim_space() == name {
return true
}
}
return false
}
fn (w &Webhook) event_list() []string {
mut out := []string{}
for ev in w.events.split(',') {
t := ev.trim_space()
if t != '' {
out << t
}
}
return out
}
fn (mut app App) add_webhook(repo_id int, url string, secret string, events string) ! {
wh := Webhook{
repo_id: repo_id
url: url
secret: secret
events: events
is_active: true
created_at: int(time.now().unix())
}
sql app.db {
insert wh into Webhook
}!
}
fn (mut app App) list_repo_webhooks(repo_id int) []Webhook {
return sql app.db {
select from Webhook where repo_id == repo_id order by id desc
} or { []Webhook{} }
}
fn (mut app App) find_webhook_by_id(id int) ?Webhook {
rows := sql app.db {
select from Webhook where id == id limit 1
} or { []Webhook{} }
if rows.len == 0 {
return none
}
return rows.first()
}
fn (mut app App) delete_webhook(id int) ! {
sql app.db {
delete from Webhook where id == id
}!
sql app.db {
delete from WebhookDelivery where webhook_id == id
}!
}
fn (mut app App) delete_repo_webhooks(repo_id int) ! {
whs := app.list_repo_webhooks(repo_id)
for wh in whs {
app.delete_webhook(wh.id) or {}
}
}
fn (mut app App) toggle_webhook(id int, active bool) ! {
sql app.db {
update Webhook set is_active = active where id == id
}!
}
fn (mut app App) record_webhook_delivery(webhook_id int, event string, status int, body string) {
d := WebhookDelivery{
webhook_id: webhook_id
event: event
status_code: status
response_body: body
created_at: int(time.now().unix())
}
sql app.db {
insert d into WebhookDelivery
} or { return }
sql app.db {
update Webhook set last_status = status, last_delivery = d.created_at where id == webhook_id
} or { return }
}
fn (mut app App) recent_webhook_deliveries(webhook_id int, limit int) []WebhookDelivery {
return sql app.db {
select from WebhookDelivery where webhook_id == webhook_id order by id desc limit limit
} or { []WebhookDelivery{} }
}
// dispatch_webhook fires a webhook delivery in a background spawn.
// payload is any serializable value; it's JSON-encoded with `json.encode`.
fn (mut app App) dispatch_webhook[T](repo_id int, event string, payload T) {
body := json.encode(payload)
app.fan_out_webhook(repo_id, event, body)
}
fn (mut app App) fan_out_webhook(repo_id int, event string, body string) {
hooks := app.list_repo_webhooks(repo_id)
for wh in hooks {
if !wh.is_active {
continue
}
if !wh.has_event(event) {
continue
}
spawn app.deliver_webhook(wh, event, body)
}
}
fn (mut app App) deliver_webhook(wh Webhook, event string, body string) {
mut signature := ''
if wh.secret != '' {
sig_bytes := hmac.new(wh.secret.bytes(), body.bytes(), sha256.sum, sha256.block_size)
signature = 'sha256=' + hex.encode(sig_bytes)
}
mut req := http.new_request(.post, wh.url, body)
req.header.add(.content_type, 'application/json')
req.header.add_custom('X-Gitly-Event', event) or {}
if signature != '' {
req.header.add_custom('X-Gitly-Signature', signature) or {}
}
req.read_timeout = 10 * time.second
req.write_timeout = 10 * time.second
resp := req.do() or {
app.record_webhook_delivery(wh.id, event, 0, err.str())
return
}
preview := if resp.body.len > 500 { resp.body[..500] } else { resp.body }
app.record_webhook_delivery(wh.id, event, resp.status_code, preview)
}