package ironnotify import ( "bytes" "context" "encoding/json" "fmt" "io" "net/http" "net/url" "strconv" "time" ) // Transport handles HTTP communication with the IronNotify API. type Transport struct { baseURL string apiKey string debug bool httpClient *http.Client } // NewTransport creates a new Transport. func NewTransport(baseURL, apiKey string, timeout time.Duration, debug bool) *Transport { return &Transport{ baseURL: baseURL, apiKey: apiKey, debug: debug, httpClient: &http.Client{ Timeout: timeout, }, } } // Send sends a notification payload to the server. func (t *Transport) Send(ctx context.Context, payload *NotificationPayload) SendResult { data, err := json.Marshal(payload) if err != nil { return SendResult{Success: false, Error: err.Error()} } req, err := http.NewRequestWithContext(ctx, "POST", t.baseURL+"/api/v1/notify", bytes.NewReader(data)) if err != nil { return SendResult{Success: false, Error: err.Error()} } req.Header.Set("Content-Type", "application/json") req.Header.Set("Authorization", "Bearer "+t.apiKey) if t.debug { fmt.Printf("[IronNotify] Sending notification: %s\n", payload.EventType) } resp, err := t.httpClient.Do(req) if err != nil { return SendResult{Success: false, Error: err.Error()} } defer resp.Body.Close() body, _ := io.ReadAll(resp.Body) if resp.StatusCode >= 200 && resp.StatusCode < 300 { var result struct { NotificationID string `json:"notificationId"` } if err := json.Unmarshal(body, &result); err == nil { return SendResult{Success: true, NotificationID: result.NotificationID} } return SendResult{Success: true} } var errorResp struct { Error string `json:"error"` } if err := json.Unmarshal(body, &errorResp); err == nil && errorResp.Error != "" { return SendResult{Success: false, Error: errorResp.Error} } return SendResult{Success: false, Error: fmt.Sprintf("HTTP %d: %s", resp.StatusCode, string(body))} } // GetNotifications retrieves notifications from the server. func (t *Transport) GetNotifications(ctx context.Context, limit, offset int, unreadOnly bool) ([]Notification, error) { u, err := url.Parse(t.baseURL + "/api/v1/notifications") if err != nil { return nil, err } q := u.Query() if limit > 0 { q.Set("limit", strconv.Itoa(limit)) } if offset > 0 { q.Set("offset", strconv.Itoa(offset)) } if unreadOnly { q.Set("unread_only", "true") } u.RawQuery = q.Encode() req, err := http.NewRequestWithContext(ctx, "GET", u.String(), nil) if err != nil { return nil, err } req.Header.Set("Authorization", "Bearer "+t.apiKey) resp, err := t.httpClient.Do(req) if err != nil { return nil, err } defer resp.Body.Close() if resp.StatusCode != http.StatusOK { body, _ := io.ReadAll(resp.Body) return nil, fmt.Errorf("HTTP %d: %s", resp.StatusCode, string(body)) } var notifications []Notification if err := json.NewDecoder(resp.Body).Decode(¬ifications); err != nil { return nil, err } return notifications, nil } // GetUnreadCount retrieves the unread notification count. func (t *Transport) GetUnreadCount(ctx context.Context) (int, error) { req, err := http.NewRequestWithContext(ctx, "GET", t.baseURL+"/api/v1/notifications/unread-count", nil) if err != nil { return 0, err } req.Header.Set("Authorization", "Bearer "+t.apiKey) resp, err := t.httpClient.Do(req) if err != nil { return 0, err } defer resp.Body.Close() if resp.StatusCode != http.StatusOK { body, _ := io.ReadAll(resp.Body) return 0, fmt.Errorf("HTTP %d: %s", resp.StatusCode, string(body)) } var result struct { Count int `json:"count"` } if err := json.NewDecoder(resp.Body).Decode(&result); err != nil { return 0, err } return result.Count, nil } // MarkAsRead marks a notification as read. func (t *Transport) MarkAsRead(ctx context.Context, notificationID string) error { req, err := http.NewRequestWithContext(ctx, "POST", t.baseURL+"/api/v1/notifications/"+notificationID+"/read", nil) if err != nil { return err } req.Header.Set("Authorization", "Bearer "+t.apiKey) resp, err := t.httpClient.Do(req) if err != nil { return err } defer resp.Body.Close() if resp.StatusCode >= 200 && resp.StatusCode < 300 { return nil } body, _ := io.ReadAll(resp.Body) return fmt.Errorf("HTTP %d: %s", resp.StatusCode, string(body)) } // MarkAllAsRead marks all notifications as read. func (t *Transport) MarkAllAsRead(ctx context.Context) error { req, err := http.NewRequestWithContext(ctx, "POST", t.baseURL+"/api/v1/notifications/read-all", nil) if err != nil { return err } req.Header.Set("Authorization", "Bearer "+t.apiKey) resp, err := t.httpClient.Do(req) if err != nil { return err } defer resp.Body.Close() if resp.StatusCode >= 200 && resp.StatusCode < 300 { return nil } body, _ := io.ReadAll(resp.Body) return fmt.Errorf("HTTP %d: %s", resp.StatusCode, string(body)) } // IsOnline checks if the API is reachable. func (t *Transport) IsOnline(ctx context.Context) bool { req, err := http.NewRequestWithContext(ctx, "GET", t.baseURL+"/health", nil) if err != nil { return false } resp, err := t.httpClient.Do(req) if err != nil { return false } defer resp.Body.Close() return resp.StatusCode == http.StatusOK } // Close closes the transport. func (t *Transport) Close() { t.httpClient.CloseIdleConnections() }