~bigbes/sr-ht-spec

ref: f42f81ca17c2a1a3da14002e77562b51df14b34d sr-ht-spec/cmd/specsrht/webhooks.go -rw-r--r-- 2.1 KiB
f42f81ca — Eugene Blikh feat(web): line-numbered unified prose diff replaces the block cards (spec-by6.3.5) 13 days ago
                                                                                
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
package main

import (
	"context"
	"log/slog"
	"time"

	work "git.sr.ht/~sircmpwn/dowork"
	sq "github.com/Masterminds/squirrel"
	"github.com/google/uuid"

	"sourcecraft.dev/bigbes/sr-ht-core/auth"
	"sourcecraft.dev/bigbes/sr-ht-core/webhooks"

	"sourcecraft.dev/bigbes/sr-ht-spec/graph"
	"sourcecraft.dev/bigbes/sr-ht-spec/graph/model"
	"sourcecraft.dev/bigbes/sr-ht-spec/service"
)

// webhookEventSink implements service.EventSink by enqueuing a dowork task onto
// the webhook queue. The task runs in the queue's worker context (server +
// database + config, from WithQueues), adds the owner's INTERNAL auth context,
// and calls Schedule — which needs all three. Firing is fire-and-forget: a
// webhook must never block or fail a proposal write.
type webhookEventSink struct {
	queue       *webhooks.WebhookQueue
	ownerUserID int
	ownerName   string
	log         *slog.Logger
}

func newWebhookEventSink(q *webhooks.WebhookQueue, ownerUserID int, ownerName string, log *slog.Logger) *webhookEventSink {
	return &webhookEventSink{queue: q, ownerUserID: ownerUserID, ownerName: ownerName, log: log}
}

// Compile-time assertion that the sink satisfies the service seam.
var _ service.EventSink = (*webhookEventSink)(nil)

func (s *webhookEventSink) ProposalEvent(kind service.ProposalEventKind, p service.Proposal) {
	u := uuid.New()
	payload, err := graph.NewProposalEvent(model.WebhookEvent(kind), u.String(), time.Now().UTC(), p)
	if err != nil {
		s.log.Error("build webhook payload", "err", err)
		return
	}
	event := string(kind)
	task := work.NewTask(func(ctx context.Context) error {
		// The worker context carries server+database+config; add the owner auth
		// Schedule captures (fetchSubscriptions and the delivery both need it).
		ctx = auth.Context(ctx, &auth.AuthContext{
			AuthMethod: auth.AUTH_INTERNAL, UserID: s.ownerUserID, Username: s.ownerName,
		})
		q := sq.Select().From("gql_user_wh_sub sub").Where("sub.user_id = ?", s.ownerUserID)
		s.queue.Schedule(ctx, q, "user", event, u, payload)
		return nil
	})
	// Enqueue off the write path so a full queue never blocks a merge.
	go func() { _ = s.queue.Queue.Enqueue(task) }()
}