Artifact 8fad2a91ae272cbbe753389e4c19e82818821fc617dde2a26f46302ac706fd5a:
- File
internal/notify/notify.go
— part of check-in
[37f62c9a82]
at
2026-08-16 05:04:31
on branch trunk
— notify: fire mention and activity triggers to the notify companion (0001 §5)
internal/notify: a Novu-compatible POST /v1/events/trigger client (quarry triggers, notify owns delivery). chat.post parses @mentions and fires quarry-mention per handle; wiki/ticket write ops fire quarry-activity to a repo:<name> topic. All best-effort — a notify outage never fails the user action — with idempotency keys so replays dedup. --notify enables it (disabled by default). Verified live: mention and activity triggers POSTed with the right Novu shape. (user: dmestas, size: 2380) [annotate] [blame] [check-ins using]
// Package notify is quarry's thin edge to the notify companion (0001 // §5/§8): quarry does not build notification logic, it fires triggers. // A trigger is the Novu-compatible POST /v1/events/trigger the notify // RFC preserves as its public contract; notify resolves recipients, // runs the workflow DAG, and owns channels, digests, and the inbox. // Triggers are best-effort — a notify outage must never fail the user // action that raised it. package notify import ( "bytes" "context" "encoding/json" "fmt" "net/http" "time" ) // Client posts triggers to a notify base URL. A nil Client (base "") is // the disabled no-op: Trigger returns nil without a call. type Client struct { base string http *http.Client } // New returns a client for baseURL, or a disabled client when baseURL // is empty (notify not configured). func New(baseURL string) *Client { return &Client{base: baseURL, http: &http.Client{Timeout: 5 * time.Second}} } // Recipient is a trigger's "to": a subscriber id (a quarry user name), // optionally carrying a channel identity notify can deliver on. type Recipient struct { SubscriberID string `json:"subscriberId"` Email string `json:"email,omitempty"` } type triggerBody struct { Name string `json:"name"` To Recipient `json:"to"` Payload map[string]any `json:"payload"` TransactionID string `json:"transactionId,omitempty"` } // Trigger starts the named workflow for one recipient. transactionID is // the idempotency key (notify maps it to dagnats WithIdempotencyKey), so // a replayed or duplicated event notifies at most once. A disabled // client no-ops. func (c *Client) Trigger(ctx context.Context, name string, to Recipient, payload map[string]any, transactionID string) error { if c == nil || c.base == "" { return nil } body, err := json.Marshal(triggerBody{Name: name, To: to, Payload: payload, TransactionID: transactionID}) if err != nil { return err } req, err := http.NewRequestWithContext(ctx, "POST", c.base+"/v1/events/trigger", bytes.NewReader(body)) if err != nil { return err } req.Header.Set("Content-Type", "application/json") resp, err := c.http.Do(req) if err != nil { return err } defer resp.Body.Close() if resp.StatusCode >= 300 { return fmt.Errorf("notify: trigger %q: status %d", name, resp.StatusCode) } return nil }