A vibe coded tangled fork which supports pijul.

appview/notify: move logging and webhook notifiers to their own packages

Signed-off-by: Anirudh Oppiliappan <anirudh@tangled.org>

authored by

Anirudh Oppiliappan and committed by tangled.org 13ec3ea8 88b2cbf2

+24 -43
+7 -10
appview/notify/logging_notifier.go appview/notify/logging/notifier.go
··· 1 - package notify 1 + package logging 2 2 3 3 import ( 4 4 "context" 5 5 "log/slog" 6 6 7 + "github.com/bluesky-social/indigo/atproto/syntax" 7 8 "tangled.org/core/appview/models" 9 + "tangled.org/core/appview/notify" 8 10 tlog "tangled.org/core/log" 9 - 10 - "github.com/bluesky-social/indigo/atproto/syntax" 11 11 ) 12 12 13 13 type loggingNotifier struct { 14 - inner Notifier 14 + inner notify.Notifier 15 15 logger *slog.Logger 16 16 } 17 17 18 - func NewLoggingNotifier(inner Notifier, logger *slog.Logger) Notifier { 19 - return &loggingNotifier{ 20 - inner, 21 - logger, 22 - } 18 + func NewLoggingNotifier(inner notify.Notifier, logger *slog.Logger) notify.Notifier { 19 + return &loggingNotifier{inner, logger} 23 20 } 24 21 25 - var _ Notifier = &loggingNotifier{} 22 + var _ notify.Notifier = &loggingNotifier{} 26 23 27 24 func (l *loggingNotifier) NewRepo(ctx context.Context, repo *models.Repo) { 28 25 ctx = tlog.IntoContext(ctx, tlog.SubLogger(l.logger, "NewRepo"))
+13 -30
appview/notify/webhook_notifier.go appview/notify/webhook/notifier.go
··· 1 - package notify 1 + package webhook 2 2 3 3 import ( 4 4 "bytes" ··· 17 17 "github.com/google/uuid" 18 18 "tangled.org/core/appview/db" 19 19 "tangled.org/core/appview/models" 20 + "tangled.org/core/appview/notify" 20 21 "tangled.org/core/log" 21 22 ) 22 23 23 - type WebhookNotifier struct { 24 - BaseNotifier 24 + type Notifier struct { 25 + notify.BaseNotifier 25 26 db *db.DB 26 27 logger *slog.Logger 27 28 client *http.Client 28 29 } 29 30 30 - func NewWebhookNotifier(database *db.DB) *WebhookNotifier { 31 - return &WebhookNotifier{ 31 + func NewNotifier(database *db.DB) *Notifier { 32 + return &Notifier{ 32 33 db: database, 33 34 logger: log.New("webhook-notifier"), 34 35 client: &http.Client{ ··· 37 38 } 38 39 } 39 40 40 - // Push implements the Notifier interface for git push events 41 - func (w *WebhookNotifier) Push(ctx context.Context, repo *models.Repo, ref, oldSha, newSha, committerDid string) { 41 + var _ notify.Notifier = &Notifier{} 42 + 43 + func (w *Notifier) Push(ctx context.Context, repo *models.Repo, ref, oldSha, newSha, committerDid string) { 42 44 webhooks, err := db.GetActiveWebhooksForRepo(w.db, repo.RepoAt()) 43 45 if err != nil { 44 46 w.logger.Error("failed to get webhooks for repo", "repo", repo.RepoAt(), "err", err) 45 47 return 46 48 } 47 49 48 - // check if any webhooks are subscribed to push events 49 50 var pushWebhooks []models.Webhook 50 51 for _, webhook := range webhooks { 51 52 if webhook.HasEvent(models.WebhookEventPush) { ··· 63 64 return 64 65 } 65 66 66 - // Send webhooks 67 67 for _, webhook := range pushWebhooks { 68 68 go w.sendWebhook(ctx, webhook, string(models.WebhookEventPush), payload) 69 69 } 70 70 } 71 71 72 - func (w *WebhookNotifier) Clone(ctx context.Context, repo *models.Repo) {} 73 - 74 - // buildPushPayload creates the webhook payload 75 - func (w *WebhookNotifier) buildPushPayload(repo *models.Repo, ref, oldSha, newSha, committerDid string) (*models.WebhookPayload, error) { 72 + func (w *Notifier) buildPushPayload(repo *models.Repo, ref, oldSha, newSha, committerDid string) (*models.WebhookPayload, error) { 76 73 owner := repo.Did 77 74 78 75 pusher := committerDid ··· 80 77 pusher = owner 81 78 } 82 79 83 - // Build repository object 84 80 repository := models.WebhookRepository{ 85 81 Name: repo.Name, 86 82 FullName: fmt.Sprintf("%s/%s", repo.Did, repo.Name), ··· 96 92 }, 97 93 } 98 94 99 - // Add optional fields 100 95 if repo.Website != "" { 101 96 repository.Website = repo.Website 102 97 } ··· 105 100 repository.OpenIssues = repo.RepoStats.IssueCount.Open 106 101 } 107 102 108 - // Build payload 109 103 payload := &models.WebhookPayload{ 110 104 Ref: ref, 111 105 Before: oldSha, ··· 119 113 return payload, nil 120 114 } 121 115 122 - // sendWebhook sends the webhook http request 123 - func (w *WebhookNotifier) sendWebhook(ctx context.Context, webhook models.Webhook, event string, payload *models.WebhookPayload) { 116 + func (w *Notifier) sendWebhook(ctx context.Context, webhook models.Webhook, event string, payload *models.WebhookPayload) { 124 117 deliveryId := uuid.New().String() 125 118 126 119 payloadBytes, err := json.Marshal(payload) ··· 157 150 RequestBody: string(payloadBytes), 158 151 } 159 152 160 - // retry webhook delivery with exponential backoff 161 153 retryOpts := []retry.Option{ 162 154 retry.Attempts(3), 163 155 retry.Delay(1 * time.Second), ··· 172 164 }), 173 165 retry.Context(ctx), 174 166 retry.RetryIf(func(err error) bool { 175 - // only retry on network errors or 5xx responses 176 - if err != nil { 177 - return true 178 - } 179 - return false 167 + return err != nil 180 168 }), 181 169 } 182 170 ··· 187 175 if err != nil { 188 176 return err 189 177 } 190 - 191 - // retry on 5xx server errors 192 178 if resp.StatusCode >= 500 { 193 179 defer resp.Body.Close() 194 180 return fmt.Errorf("server error: %d", resp.StatusCode) 195 181 } 196 - 197 182 return nil 198 183 }, retryOpts...) 199 184 ··· 207 192 delivery.ResponseCode = resp.StatusCode 208 193 delivery.Success = resp.StatusCode >= 200 && resp.StatusCode < 300 209 194 210 - // Read response body (limit to 10KB) 211 195 bodyBytes, err := io.ReadAll(io.LimitReader(resp.Body, 10*1024)) 212 196 if err != nil { 213 197 w.logger.Warn("failed to read webhook response body", "webhook_id", webhook.Id, "err", err) ··· 233 217 } 234 218 } 235 219 236 - // computeSignature computes HMAC-SHA256 signature for the payload 237 - func (w *WebhookNotifier) computeSignature(payload []byte, secret string) string { 220 + func (w *Notifier) computeSignature(payload []byte, secret string) string { 238 221 mac := hmac.New(sha256.New, []byte(secret)) 239 222 mac.Write(payload) 240 223 return hex.EncodeToString(mac.Sum(nil))
+4 -3
appview/state/state.go
··· 21 21 "tangled.org/core/appview/models" 22 22 "tangled.org/core/appview/notify" 23 23 dbnotify "tangled.org/core/appview/notify/db" 24 + lognotify "tangled.org/core/appview/notify/logging" 24 25 phnotify "tangled.org/core/appview/notify/posthog" 26 + whnotify "tangled.org/core/appview/notify/webhook" 25 27 "tangled.org/core/appview/oauth" 26 28 "tangled.org/core/appview/pages" 27 29 "tangled.org/core/appview/reporesolver" ··· 168 170 } 169 171 notifiers = append(notifiers, indexer) 170 172 171 - // Add webhook notifier 172 - notifiers = append(notifiers, notify.NewWebhookNotifier(d)) 173 + notifiers = append(notifiers, whnotify.NewNotifier(d)) 173 174 174 175 notifier := notify.NewMergedNotifier(notifiers) 175 - notifier = notify.NewLoggingNotifier(notifier, tlog.SubLogger(logger, "notify")) 176 + notifier = lognotify.NewLoggingNotifier(notifier, tlog.SubLogger(logger, "notify")) 176 177 177 178 var cfClient *cloudflare.Client 178 179 if config.Cloudflare.ApiToken != "" {