From 13ec3ea83b075975ecb899be4f0e9d990b5bec61 Mon Sep 17 00:00:00 2001 From: Anirudh Oppiliappan Date: Thu, 02 Apr 2026 08:37:08 +0000 Subject: [PATCH] appview/notify: move logging and webhook notifiers to their own packages Signed-off-by: Anirudh Oppiliappan --- appview/notify/logging/notifier.go | 17 +++++++---------- appview/notify/webhook/notifier.go | 43 +++++++++++++------------------------------ appview/state/state.go | 7 ++++--- 3 file(s) changed, 24 insertion(s)(+), 43 deletion(s)(-) diff --git a/appview/notify/logging_notifier.go b/appview/notify/logging/notifier.go rename from appview/notify/logging_notifier.go rename to appview/notify/logging/notifier.go --- a/appview/notify/logging_notifier.go +++ b/appview/notify/logging/notifier.go @@ -1,28 +1,25 @@ -package notify +package logging import ( "context" "log/slog" + "github.com/bluesky-social/indigo/atproto/syntax" "tangled.org/core/appview/models" + "tangled.org/core/appview/notify" tlog "tangled.org/core/log" - - "github.com/bluesky-social/indigo/atproto/syntax" ) type loggingNotifier struct { - inner Notifier + inner notify.Notifier logger *slog.Logger } -func NewLoggingNotifier(inner Notifier, logger *slog.Logger) Notifier { - return &loggingNotifier{ - inner, - logger, - } +func NewLoggingNotifier(inner notify.Notifier, logger *slog.Logger) notify.Notifier { + return &loggingNotifier{inner, logger} } -var _ Notifier = &loggingNotifier{} +var _ notify.Notifier = &loggingNotifier{} func (l *loggingNotifier) NewRepo(ctx context.Context, repo *models.Repo) { ctx = tlog.IntoContext(ctx, tlog.SubLogger(l.logger, "NewRepo")) diff --git a/appview/notify/webhook_notifier.go b/appview/notify/webhook/notifier.go rename from appview/notify/webhook_notifier.go rename to appview/notify/webhook/notifier.go --- a/appview/notify/webhook_notifier.go +++ b/appview/notify/webhook/notifier.go @@ -1,4 +1,4 @@ -package notify +package webhook import ( "bytes" @@ -17,18 +17,19 @@ "github.com/avast/retry-go/v4" "github.com/google/uuid" "tangled.org/core/appview/db" "tangled.org/core/appview/models" + "tangled.org/core/appview/notify" "tangled.org/core/log" ) -type WebhookNotifier struct { - BaseNotifier +type Notifier struct { + notify.BaseNotifier db *db.DB logger *slog.Logger client *http.Client } -func NewWebhookNotifier(database *db.DB) *WebhookNotifier { - return &WebhookNotifier{ +func NewNotifier(database *db.DB) *Notifier { + return &Notifier{ db: database, logger: log.New("webhook-notifier"), client: &http.Client{ @@ -37,15 +38,15 @@ }, } } -// Push implements the Notifier interface for git push events -func (w *WebhookNotifier) Push(ctx context.Context, repo *models.Repo, ref, oldSha, newSha, committerDid string) { +var _ notify.Notifier = &Notifier{} + +func (w *Notifier) Push(ctx context.Context, repo *models.Repo, ref, oldSha, newSha, committerDid string) { webhooks, err := db.GetActiveWebhooksForRepo(w.db, repo.RepoAt()) if err != nil { w.logger.Error("failed to get webhooks for repo", "repo", repo.RepoAt(), "err", err) return } - // check if any webhooks are subscribed to push events var pushWebhooks []models.Webhook for _, webhook := range webhooks { if webhook.HasEvent(models.WebhookEventPush) { @@ -63,16 +64,12 @@ w.logger.Error("failed to build push payload", "repo", repo.RepoAt(), "err", err) return } - // Send webhooks for _, webhook := range pushWebhooks { go w.sendWebhook(ctx, webhook, string(models.WebhookEventPush), payload) } } -func (w *WebhookNotifier) Clone(ctx context.Context, repo *models.Repo) {} - -// buildPushPayload creates the webhook payload -func (w *WebhookNotifier) buildPushPayload(repo *models.Repo, ref, oldSha, newSha, committerDid string) (*models.WebhookPayload, error) { +func (w *Notifier) buildPushPayload(repo *models.Repo, ref, oldSha, newSha, committerDid string) (*models.WebhookPayload, error) { owner := repo.Did pusher := committerDid @@ -80,7 +77,6 @@ if committerDid == "" { pusher = owner } - // Build repository object repository := models.WebhookRepository{ Name: repo.Name, FullName: fmt.Sprintf("%s/%s", repo.Did, repo.Name), @@ -96,7 +92,6 @@ Did: owner, }, } - // Add optional fields if repo.Website != "" { repository.Website = repo.Website } @@ -105,7 +100,6 @@ repository.StarsCount = repo.RepoStats.StarCount repository.OpenIssues = repo.RepoStats.IssueCount.Open } - // Build payload payload := &models.WebhookPayload{ Ref: ref, Before: oldSha, @@ -119,8 +113,7 @@ return payload, nil } -// sendWebhook sends the webhook http request -func (w *WebhookNotifier) sendWebhook(ctx context.Context, webhook models.Webhook, event string, payload *models.WebhookPayload) { +func (w *Notifier) sendWebhook(ctx context.Context, webhook models.Webhook, event string, payload *models.WebhookPayload) { deliveryId := uuid.New().String() payloadBytes, err := json.Marshal(payload) @@ -157,7 +150,6 @@ Url: webhook.Url, RequestBody: string(payloadBytes), } - // retry webhook delivery with exponential backoff retryOpts := []retry.Option{ retry.Attempts(3), retry.Delay(1 * time.Second), @@ -172,11 +164,7 @@ "err", err) }), retry.Context(ctx), retry.RetryIf(func(err error) bool { - // only retry on network errors or 5xx responses - if err != nil { - return true - } - return false + return err != nil }), } @@ -187,13 +175,10 @@ resp, err = w.client.Do(req) if err != nil { return err } - - // retry on 5xx server errors if resp.StatusCode >= 500 { defer resp.Body.Close() return fmt.Errorf("server error: %d", resp.StatusCode) } - return nil }, retryOpts...) @@ -207,7 +192,6 @@ delivery.ResponseCode = resp.StatusCode delivery.Success = resp.StatusCode >= 200 && resp.StatusCode < 300 - // Read response body (limit to 10KB) bodyBytes, err := io.ReadAll(io.LimitReader(resp.Body, 10*1024)) if err != nil { w.logger.Warn("failed to read webhook response body", "webhook_id", webhook.Id, "err", err) @@ -233,8 +217,7 @@ w.logger.Error("failed to record webhook delivery", "webhook_id", webhook.Id, "err", err) } } -// computeSignature computes HMAC-SHA256 signature for the payload -func (w *WebhookNotifier) computeSignature(payload []byte, secret string) string { +func (w *Notifier) computeSignature(payload []byte, secret string) string { mac := hmac.New(sha256.New, []byte(secret)) mac.Write(payload) return hex.EncodeToString(mac.Sum(nil)) diff --git a/appview/state/state.go b/appview/state/state.go --- a/appview/state/state.go +++ b/appview/state/state.go @@ -21,7 +21,9 @@ "tangled.org/core/appview/mentions" "tangled.org/core/appview/models" "tangled.org/core/appview/notify" dbnotify "tangled.org/core/appview/notify/db" + lognotify "tangled.org/core/appview/notify/logging" phnotify "tangled.org/core/appview/notify/posthog" + whnotify "tangled.org/core/appview/notify/webhook" "tangled.org/core/appview/oauth" "tangled.org/core/appview/pages" "tangled.org/core/appview/reporesolver" @@ -168,11 +170,10 @@ notifiers = append(notifiers, phnotify.NewPosthogNotifier(posthog)) } notifiers = append(notifiers, indexer) - // Add webhook notifier - notifiers = append(notifiers, notify.NewWebhookNotifier(d)) + notifiers = append(notifiers, whnotify.NewNotifier(d)) notifier := notify.NewMergedNotifier(notifiers) - notifier = notify.NewLoggingNotifier(notifier, tlog.SubLogger(logger, "notify")) + notifier = lognotify.NewLoggingNotifier(notifier, tlog.SubLogger(logger, "notify")) var cfClient *cloudflare.Client if config.Cloudflare.ApiToken != "" { -- tangled.sh