99 lines
2.1 KiB
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,
|
|
})
|
|
}
|