package notify
import (
"context"
"encoding/json"
"fmt"
)
// ReplyEndpoint is notify's record of where it delivers the forge's
// reply events (RFC 0014 Phase 1).
type ReplyEndpoint struct {
ID string `json:"id"`
URL string `json:"url"`
}
// EnsureReplyEndpoint registers url as a webhook endpoint in notify's
// delivery stream, once. An endpoint already carrying that URL is left
// as it is; otherwise one is created, active, signing with secret.
//
// The secret travels in the create request because quarry mints it:
// the forge verifies every delivery against its own copy, and an
// endpoint notify created with a secret of its own choosing would
// leave quarry with nothing to verify. A notify that does not yet
// honour the field still creates the endpoint, and its unsigned
// deliveries are refused at the forge — closed, not silent.
func (c *Client) EnsureReplyEndpoint(ctx context.Context, url, secret string) (ep ReplyEndpoint, made bool, err error) {
if c == nil || c.base == "" || url == "" {
return ReplyEndpoint{}, false, nil
}
status, body, err := c.do(ctx, "GET", "/v1/webhooks/endpoints", nil)
if err != nil {
return ReplyEndpoint{}, false, fmt.Errorf("notify: list endpoints: %w", err)
}
if status >= 300 {
return ReplyEndpoint{}, false, fmt.Errorf("notify: list endpoints: status %d", status)
}
var have struct {
Data []ReplyEndpoint `json:"data"`
}
if err := json.Unmarshal(body, &have); err != nil {
return ReplyEndpoint{}, false, fmt.Errorf("notify: list endpoints: %w", err)
}
for _, e := range have.Data {
if e.URL == url {
return e, false, nil
}
}
status, body, err = c.post(ctx, "/v1/webhooks/endpoints",
map[string]any{"url": url, "active": true, "secret": secret})
if err != nil {
return ReplyEndpoint{}, false, fmt.Errorf("notify: create endpoint: %w", err)
}
if status >= 300 {
return ReplyEndpoint{}, false, fmt.Errorf("notify: create endpoint: status %d", status)
}
if err := json.Unmarshal(body, &ep); err != nil {
return ReplyEndpoint{}, false, fmt.Errorf("notify: create endpoint: %w", err)
}
ep.URL = url
return ep, true, nil
}