gohorsejobs/backend/internal/services/fcm_service.go
Tiago Yamamoto 63023b922f feat(notifications): implementar sistema de notificações e FCM
- Migration 017: tabelas notifications e fcm_tokens
- Models: Notification, FCMToken
- NotificationService: CRUD, push notifications helper
- FCMService: Firebase Cloud Messaging integration
- NotificationHandler: endpoints REST
- Rotas autenticadas: /api/v1/notifications/*

Endpoints:
- GET /api/v1/notifications
- GET /api/v1/notifications/unread-count
- PUT /api/v1/notifications/read-all
- PUT /api/v1/notifications/{id}/read
- DELETE /api/v1/notifications/{id}
- POST /api/v1/notifications/fcm-token
- DELETE /api/v1/notifications/fcm-token
2025-12-27 11:24:27 -03:00

106 lines
2.3 KiB
Go

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
}