Files
zoe-shaldon/server/internal/webhook/webhook.go

99 lines
2.1 KiB
Go

package webhook
import (
"bytes"
"crypto/hmac"
"crypto/sha256"
"encoding/hex"
"encoding/json"
"fmt"
"io"
"net/http"
"time"
"wedding-server/internal/config"
"wedding-server/internal/store"
)
type Client struct {
cfg config.WebhookConfig
httpClient *http.Client
}
func New(cfg config.WebhookConfig) *Client {
return &Client{
cfg: cfg,
httpClient: &http.Client{Timeout: time.Duration(cfg.TimeoutSeconds) * time.Second},
}
}
func (c *Client) post(url, event string, data any) error {
if url == "" {
return nil
}
body, err := json.Marshal(map[string]any{
"event": event,
"sentAt": time.Now().UTC(),
"data": data,
})
if err != nil {
return err
}
req, err := http.NewRequest(http.MethodPost, url, bytes.NewReader(body))
if err != nil {
return err
}
req.Header.Set("Content-Type", "application/json")
for k, v := range c.cfg.Headers {
req.Header.Set(k, v)
}
if c.cfg.Secret != "" {
req.Header.Set("X-Webhook-Signature", sign(c.cfg.Secret, body))
}
resp, err := c.httpClient.Do(req)
if err != nil {
return err
}
defer resp.Body.Close()
io.Copy(io.Discard, resp.Body)
if resp.StatusCode >= 300 {
return fmt.Errorf("webhook %s: unexpected status %d", url, resp.StatusCode)
}
return nil
}
func sign(secret string, body []byte) string {
mac := hmac.New(sha256.New, []byte(secret))
mac.Write(body)
return "sha256=" + hex.EncodeToString(mac.Sum(nil))
}
func (c *Client) SendRSVP(r store.RSVP) error {
guests := make([]map[string]any, len(r.Guests))
for i, g := range r.Guests {
guests[i] = map[string]any{"name": g.Name, "isChild": g.IsChild}
}
return c.post(c.cfg.RSVPURL, "rsvp.created", map[string]any{
"guests": guests,
"message": r.Message,
})
}
func (c *Client) SendPhotoUpload(u store.PhotoUpload, files []store.PhotoFile) error {
urls := make([]string, len(files))
for i, f := range files {
urls[i] = f.URL
}
return c.post(c.cfg.PhotoURL, "photos.uploaded", map[string]any{
"name": u.Name,
"message": u.Message,
"email": u.Email,
"phone": u.Phone,
"photoUrls": urls,
})
}