package services import ( "bytes" "encoding/json" "fmt" "net/http" "os" "time" ) // FCMService handles Firebase Cloud Messaging type FCMService struct { serverKey string projectID string client *http.Client } // NewFCMService creates a new FCM service func NewFCMService() *FCMService { serverKey := os.Getenv("FCM_SERVER_KEY") projectID := os.Getenv("FCM_PROJECT_ID") if serverKey == "" && projectID == "" { // Return nil if not configured - notifications will still work without push return nil } return &FCMService{ serverKey: serverKey, projectID: projectID, client: &http.Client{ Timeout: 10 * time.Second, }, } } // FCMMessage represents an FCM push message type FCMMessage struct { To string `json:"to,omitempty"` Notification FCMNotification `json:"notification"` Data map[string]string `json:"data,omitempty"` Priority string `json:"priority"` } type FCMNotification struct { Title string `json:"title"` Body string `json:"body"` Icon string `json:"icon,omitempty"` Badge string `json:"badge,omitempty"` } // Send sends a push notification to a device func (s *FCMService) Send(token, title, body string, data map[string]string) error { if s == nil || s.serverKey == "" { // FCM not configured, skip silently return nil } message := FCMMessage{ To: token, Notification: FCMNotification{ Title: title, Body: body, Icon: "/icon-192x192.png", }, Data: data, Priority: "high", } jsonData, err := json.Marshal(message) if err != nil { return err } req, err := http.NewRequest("POST", "https://fcm.googleapis.com/fcm/send", bytes.NewBuffer(jsonData)) if err != nil { return err } req.Header.Set("Content-Type", "application/json") req.Header.Set("Authorization", "key="+s.serverKey) resp, err := s.client.Do(req) if err != nil { return err } defer resp.Body.Close() if resp.StatusCode != http.StatusOK { return fmt.Errorf("FCM returned status %d", resp.StatusCode) } return nil } // SendMulticast sends to multiple devices func (s *FCMService) SendMulticast(tokens []string, title, body string, data map[string]string) []error { var errors []error for _, token := range tokens { if err := s.Send(token, title, body, data); err != nil { errors = append(errors, err) } } return errors }