diff --git a/apps/carrier-connector/internal/webhook/client.go b/apps/carrier-connector/internal/webhook/client.go new file mode 100644 index 0000000..b89b3c2 --- /dev/null +++ b/apps/carrier-connector/internal/webhook/client.go @@ -0,0 +1,126 @@ +package webhook + +import ( + "bytes" + "context" + "encoding/json" + "fmt" + "net/http" + "time" +) + +// WebhookClient sends notifications to the api-server +type WebhookClient struct { + httpClient *http.Client + webhookURL string + apiKey string +} + +// NewWebhookClient creates a new webhook client +func NewWebhookClient(webhookURL, apiKey string) *WebhookClient { + return &WebhookClient{ + httpClient: &http.Client{ + Timeout: 10 * time.Second, + }, + webhookURL: webhookURL, + apiKey: apiKey, + } +} + +// WebhookEvent represents a webhook notification event +type WebhookEvent struct { + EventType string `json:"event_type"` + Timestamp time.Time `json:"timestamp"` + ProfileICCID string `json:"profile_iccid"` + Data map[string]interface{} `json:"data"` +} + +// Event types +const ( + EventProfileDownloaded = "profile.downloaded" + EventProfileActivated = "profile.activated" + EventProfileDeactivated = "profile.deactivated" + EventProfileDeleted = "profile.deleted" + EventProfileStatusChange = "profile.status_changed" + EventDownloadFailed = "download.failed" +) + +// SendNotification sends a webhook notification to the api-server +func (w *WebhookClient) SendNotification(ctx context.Context, event *WebhookEvent) error { + if w.webhookURL == "" { + return nil // Webhook disabled + } + + payload, err := json.Marshal(event) + if err != nil { + return fmt.Errorf("failed to marshal webhook event: %w", err) + } + + req, err := http.NewRequestWithContext(ctx, "POST", w.webhookURL, bytes.NewBuffer(payload)) + if err != nil { + return fmt.Errorf("failed to create webhook request: %w", err) + } + + req.Header.Set("Content-Type", "application/json") + if w.apiKey != "" { + req.Header.Set("Authorization", "Bearer "+w.apiKey) + } + + resp, err := w.httpClient.Do(req) + if err != nil { + return fmt.Errorf("failed to send webhook: %w", err) + } + defer resp.Body.Close() + + if resp.StatusCode >= 400 { + return fmt.Errorf("webhook returned status %d", resp.StatusCode) + } + + return nil +} + +// SendProfileDownloaded sends a notification when a profile is downloaded +func (w *WebhookClient) SendProfileDownloaded(ctx context.Context, iccid string, data map[string]interface{}) error { + event := &WebhookEvent{ + EventType: EventProfileDownloaded, + Timestamp: time.Now(), + ProfileICCID: iccid, + Data: data, + } + return w.SendNotification(ctx, event) +} + +// SendProfileActivated sends a notification when a profile is activated +func (w *WebhookClient) SendProfileActivated(ctx context.Context, iccid string, data map[string]interface{}) error { + event := &WebhookEvent{ + EventType: EventProfileActivated, + Timestamp: time.Now(), + ProfileICCID: iccid, + Data: data, + } + return w.SendNotification(ctx, event) +} + +// SendProfileDeleted sends a notification when a profile is deleted +func (w *WebhookClient) SendProfileDeleted(ctx context.Context, iccid string, data map[string]interface{}) error { + event := &WebhookEvent{ + EventType: EventProfileDeleted, + Timestamp: time.Now(), + ProfileICCID: iccid, + Data: data, + } + return w.SendNotification(ctx, event) +} + +// SendDownloadFailed sends a notification when a profile download fails +func (w *WebhookClient) SendDownloadFailed(ctx context.Context, iccid string, errorMessage string) error { + event := &WebhookEvent{ + EventType: EventDownloadFailed, + Timestamp: time.Now(), + ProfileICCID: iccid, + Data: map[string]interface{}{ + "error": errorMessage, + }, + } + return w.SendNotification(ctx, event) +}