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