- 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
106 lines
2.3 KiB
Go
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
|
|
}
|