Monorepo for Tangled
tangled.org
1package db
2
3import (
4 "database/sql"
5 "fmt"
6 "strings"
7 "time"
8
9 "tangled.org/core/appview/models"
10 "tangled.org/core/orm"
11)
12
13// GetWebhooks returns all webhooks for a repository
14func GetWebhooks(e Execer, filters ...orm.Filter) ([]models.Webhook, error) {
15 var conditions []string
16 var args []any
17 for _, filter := range filters {
18 conditions = append(conditions, filter.Condition())
19 args = append(args, filter.Arg()...)
20 }
21
22 whereClause := ""
23 if conditions != nil {
24 whereClause = " where " + strings.Join(conditions, " and ")
25 }
26
27 query := fmt.Sprintf(`
28 select
29 id,
30 repo_did,
31 url,
32 secret,
33 active,
34 events,
35 created_at,
36 updated_at
37 from webhooks
38 %s
39 order by created_at desc
40 `, whereClause)
41
42 rows, err := e.Query(query, args...)
43 if err != nil {
44 return nil, fmt.Errorf("failed to query webhooks: %w", err)
45 }
46 defer rows.Close()
47
48 var webhooks []models.Webhook
49 for rows.Next() {
50 var wh models.Webhook
51 var createdAt, updatedAt, eventsStr string
52 var secret sql.NullString
53 var active int
54
55 err := rows.Scan(
56 &wh.Id,
57 &wh.RepoDid,
58 &wh.Url,
59 &secret,
60 &active,
61 &eventsStr,
62 &createdAt,
63 &updatedAt,
64 )
65 if err != nil {
66 return nil, fmt.Errorf("failed to scan webhook: %w", err)
67 }
68
69 if secret.Valid {
70 wh.Secret = secret.String
71 }
72 wh.Active = active == 1
73 if eventsStr != "" {
74 wh.Events = strings.Split(eventsStr, ",")
75 }
76
77 if t, err := time.Parse(time.RFC3339, createdAt); err == nil {
78 wh.CreatedAt = t
79 }
80 if t, err := time.Parse(time.RFC3339, updatedAt); err == nil {
81 wh.UpdatedAt = t
82 }
83
84 webhooks = append(webhooks, wh)
85 }
86
87 if err = rows.Err(); err != nil {
88 return nil, fmt.Errorf("failed to iterate webhooks: %w", err)
89 }
90
91 return webhooks, nil
92}
93
94// GetWebhook returns a single webhook by ID
95func GetWebhook(e Execer, id int64) (*models.Webhook, error) {
96 webhooks, err := GetWebhooks(e, orm.FilterEq("id", id))
97 if err != nil {
98 return nil, err
99 }
100
101 if len(webhooks) == 0 {
102 return nil, sql.ErrNoRows
103 }
104
105 if len(webhooks) != 1 {
106 return nil, fmt.Errorf("expected 1 webhook, got %d", len(webhooks))
107 }
108
109 return &webhooks[0], nil
110}
111
112// AddWebhook creates a new webhook
113func AddWebhook(e Execer, webhook *models.Webhook) error {
114 eventsStr := strings.Join(webhook.Events, ",")
115 active := 0
116 if webhook.Active {
117 active = 1
118 }
119
120 result, err := e.Exec(`
121 insert into webhooks (repo_did, url, secret, active, events)
122 values (?, ?, ?, ?, ?)
123 `, string(webhook.RepoDid), webhook.Url, webhook.Secret, active, eventsStr)
124
125 if err != nil {
126 return fmt.Errorf("failed to insert webhook: %w", err)
127 }
128
129 id, err := result.LastInsertId()
130 if err != nil {
131 return fmt.Errorf("failed to get webhook id: %w", err)
132 }
133
134 webhook.Id = id
135 return nil
136}
137
138// UpdateWebhook updates an existing webhook
139func UpdateWebhook(e Execer, webhook *models.Webhook) error {
140 eventsStr := strings.Join(webhook.Events, ",")
141 active := 0
142 if webhook.Active {
143 active = 1
144 }
145
146 _, err := e.Exec(`
147 update webhooks
148 set url = ?, secret = ?, active = ?, events = ?, updated_at = strftime('%Y-%m-%dT%H:%M:%SZ', 'now')
149 where id = ?
150 `, webhook.Url, webhook.Secret, active, eventsStr, webhook.Id)
151
152 if err != nil {
153 return fmt.Errorf("failed to update webhook: %w", err)
154 }
155
156 return nil
157}
158
159// DeleteWebhook deletes a webhook
160func DeleteWebhook(e Execer, id int64) error {
161 _, err := e.Exec(`delete from webhooks where id = ?`, id)
162 if err != nil {
163 return fmt.Errorf("failed to delete webhook: %w", err)
164 }
165 return nil
166}
167
168// AddWebhookDelivery records a webhook delivery attempt
169func AddWebhookDelivery(e Execer, delivery *models.WebhookDelivery) error {
170 success := 0
171 if delivery.Success {
172 success = 1
173 }
174
175 result, err := e.Exec(`
176 insert into webhook_deliveries (
177 webhook_id,
178 event,
179 delivery_id,
180 url,
181 request_body,
182 response_code,
183 response_body,
184 success
185 ) values (?, ?, ?, ?, ?, ?, ?, ?)
186 `,
187 delivery.WebhookId,
188 delivery.Event,
189 delivery.DeliveryId,
190 delivery.Url,
191 delivery.RequestBody,
192 delivery.ResponseCode,
193 delivery.ResponseBody,
194 success,
195 )
196
197 if err != nil {
198 return fmt.Errorf("failed to insert webhook delivery: %w", err)
199 }
200
201 id, err := result.LastInsertId()
202 if err != nil {
203 return fmt.Errorf("failed to get delivery id: %w", err)
204 }
205
206 delivery.Id = id
207 return nil
208}
209
210// GetWebhookDeliveries returns recent deliveries for a webhook
211func GetWebhookDeliveries(e Execer, webhookId int64, limit int) ([]models.WebhookDelivery, error) {
212 if limit <= 0 {
213 limit = 20
214 }
215
216 query := `
217 select
218 id,
219 webhook_id,
220 event,
221 delivery_id,
222 url,
223 request_body,
224 response_code,
225 response_body,
226 success,
227 created_at
228 from webhook_deliveries
229 where webhook_id = ?
230 order by created_at desc
231 limit ?
232 `
233
234 rows, err := e.Query(query, webhookId, limit)
235 if err != nil {
236 return nil, fmt.Errorf("failed to query webhook deliveries: %w", err)
237 }
238 defer rows.Close()
239
240 var deliveries []models.WebhookDelivery
241 for rows.Next() {
242 var d models.WebhookDelivery
243 var createdAt string
244 var success int
245 var responseCode sql.NullInt64
246 var responseBody sql.NullString
247
248 err := rows.Scan(
249 &d.Id,
250 &d.WebhookId,
251 &d.Event,
252 &d.DeliveryId,
253 &d.Url,
254 &d.RequestBody,
255 &responseCode,
256 &responseBody,
257 &success,
258 &createdAt,
259 )
260 if err != nil {
261 return nil, fmt.Errorf("failed to scan delivery: %w", err)
262 }
263
264 d.Success = success == 1
265 if responseCode.Valid {
266 d.ResponseCode = int(responseCode.Int64)
267 }
268 if responseBody.Valid {
269 d.ResponseBody = responseBody.String
270 }
271
272 if t, err := time.Parse(time.RFC3339, createdAt); err == nil {
273 d.CreatedAt = t
274 }
275
276 deliveries = append(deliveries, d)
277 }
278
279 if err = rows.Err(); err != nil {
280 return nil, fmt.Errorf("failed to iterate deliveries: %w", err)
281 }
282
283 return deliveries, nil
284}
285
286// GetWebhooksForRepo is a convenience function to get all webhooks for a repository
287func GetWebhooksForRepo(e Execer, repoDid string) ([]models.Webhook, error) {
288 return GetWebhooks(e, orm.FilterEq("repo_did", repoDid))
289}
290
291// GetActiveWebhooksForRepo returns only active webhooks for a repository
292func GetActiveWebhooksForRepo(e Execer, repoDid string) ([]models.Webhook, error) {
293 return GetWebhooks(e,
294 orm.FilterEq("repo_did", repoDid),
295 orm.FilterEq("active", 1),
296 )
297}