managarten/services/mana-notify/internal/channel/webhook.go
Till JS 7e931b1c6d refactor(services): rename Go services, remove -go suffix
mana-search-go → mana-search
mana-notify-go → mana-notify
mana-crawler-go → mana-crawler
mana-api-gateway-go → mana-api-gateway

Legacy NestJS versions are deleted, suffix no longer needed.
Updated all references in docker-compose, CLAUDE.md, package.json,
Forgejo workflows, and service package.json files.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-28 10:18:40 +01:00

91 lines
2.1 KiB
Go

package channel
import (
"bytes"
"context"
"encoding/json"
"fmt"
"io"
"log/slog"
"net/http"
"time"
)
type WebhookService struct {
client *http.Client
}
func NewWebhookService() *WebhookService {
return &WebhookService{
client: &http.Client{Timeout: 10 * time.Second},
}
}
type WebhookMessage struct {
URL string
Method string // POST or PUT
Headers map[string]string
Body map[string]any
Timeout int // ms
}
type WebhookResult struct {
Success bool
StatusCode int
Error string
DurationMs int
}
func (s *WebhookService) Send(ctx context.Context, msg *WebhookMessage) WebhookResult {
start := time.Now()
method := msg.Method
if method == "" {
method = http.MethodPost
}
timeout := 10 * time.Second
if msg.Timeout > 0 {
timeout = time.Duration(msg.Timeout) * time.Millisecond
}
ctx, cancel := context.WithTimeout(ctx, timeout)
defer cancel()
body, err := json.Marshal(msg.Body)
if err != nil {
return WebhookResult{Success: false, Error: "marshal error", DurationMs: int(time.Since(start).Milliseconds())}
}
req, err := http.NewRequestWithContext(ctx, method, msg.URL, bytes.NewReader(body))
if err != nil {
return WebhookResult{Success: false, Error: err.Error(), DurationMs: int(time.Since(start).Milliseconds())}
}
req.Header.Set("Content-Type", "application/json")
req.Header.Set("User-Agent", "ManaNotify/1.0")
for k, v := range msg.Headers {
req.Header.Set(k, v)
}
resp, err := s.client.Do(req)
durationMs := int(time.Since(start).Milliseconds())
if err != nil {
slog.Error("webhook send failed", "url", msg.URL, "error", err)
return WebhookResult{Success: false, Error: err.Error(), DurationMs: durationMs}
}
defer resp.Body.Close()
io.Copy(io.Discard, resp.Body)
success := resp.StatusCode >= 200 && resp.StatusCode < 300
if !success {
return WebhookResult{
Success: false,
StatusCode: resp.StatusCode,
Error: fmt.Sprintf("webhook returned %d", resp.StatusCode),
DurationMs: durationMs,
}
}
return WebhookResult{Success: true, StatusCode: resp.StatusCode, DurationMs: durationMs}
}