gohorsejobs/backend/internal/services/fcm_service.go

135 lines
4.6 KiB
Go

package services
import (
"context"
"fmt"
"sync"
firebase "firebase.google.com/go/v4"
"firebase.google.com/go/v4/messaging"
"google.golang.org/api/option"
)
type FCMService struct {
credentials *CredentialsService
client *messaging.Client
currentKey string
mu sync.RWMutex
}
func NewFCMService(c *CredentialsService) *FCMService {
return &FCMService{credentials: c}
}
func (s *FCMService) getClient(ctx context.Context) (*messaging.Client, error) {
s.mu.Lock()
defer s.mu.Unlock()
// Get Service Account JSON from DB
jsonKey, err := s.credentials.GetDecryptedKey(ctx, "fcm_service_account")
if err != nil {
return nil, fmt.Errorf("fcm credentials missing: %w", err)
}
// If initialized and key hasn't changed, return cached client
if s.client != nil && jsonKey == s.currentKey {
return s.client, nil
}
// Initialize new client
opt := option.WithCredentialsJSON([]byte(jsonKey))
app, err := firebase.NewApp(ctx, nil, opt)
if err != nil {
return nil, fmt.Errorf("error initializing firebase app: %w", err)
}
client, err := app.Messaging(ctx)
if err != nil {
return nil, fmt.Errorf("error getting messaging client: %w", err)
}
s.client = client
s.currentKey = jsonKey
return client, nil
}
// SendPush sends a push notification to a specific token
// Renamed Send -> SendPush to match hml, but check CoreHandlers/NotificationService usage.
// NotificationService (hml) uses s.fcmService.Send(token...) in HEAD?
// Wait, NotificationService (hml) uses s.FCM.Send?
// NotificationService (Step 248 overwrite - hml content) DOES NOT call FCM in the code I wrote!
// Step 248 code:
// func (s *NotificationService) CreateNotification... simply inserts into DB.
// It DOES NOT send push immediately. Maybe there is a worker?
// Or I missed the Send call in Step 248?
// Checking Step 248 code content...
// It matches "hml" block from Step 243 view.
// In Step 243 view:
// The hml block for `CreateNotification` (lines 105-112) DOES NOT have `SendPush`.
// The HEAD block (lines 26-61) DID have `SendPush`.
// This is a logic regression if I just use hml?
// `hml` might rely on a separate worker or `NotificationService` calls `fcm` elsewhere?
// But `NotificationService` struct in hml HAS `FCM *FCMService`.
// If it has it, it should use it.
// Maybe `hml` sends push in a different method?
// `hml` view in Step 243 ended at line 316.
// I don't see any `Send` call in `hml` part of `NotificationService`.
// This implies `hml` might have moved push logic to a worker or I missed it.
// However, `HEAD` `NotificationService.Create` calls `fcmService.Send`.
// `fcm_service.go` (HEAD) has `Send`.
// `fcm_service.go` (hml) has `SendPush`.
// If `core_handlers.go` calls methods that rely on push, we need it.
// But `core_handlers.go` uses `notificationService.SaveFCMToken` (hml).
// It calls `notificationService.ListNotifications` (hml).
// It does NOT call `SendPush` directly.
// The use cases might call it?
// `CreateNotification` is called by `NotifyNewApplication` (Step 243 lines 234 in HEAD).
// `hml` `NotificationService` doesn't seem to have `NotifyNewApplication` in the view I saw?
// Step 243 view lines 233-281 was HEAD block.
// hml block lines 285+ was MarkAsRead.
// Does `hml` NOT implement `NotifyNewApplication`?
// If it doesn't, then Handlers/Services that use it will fail.
// I need to check if `application_handler.go` calls it.
// `application_handler.go` calls `Service.CreateApplication`.
// `ApplicationService` might call `NotificationService`.
// `ApplicationService` (HEAD) called `EmailService`.
// If `hml` dropped `NotifyNewApplication`, then we might be missing functionality.
// But I should stick to `hml` architecture if possible.
//
// For `fcm_service.go`, I will use `SendPush`.
// I will assume `NotificationService` in `hml` is correct for `hml` architecture.
func (s *FCMService) SendPush(ctx context.Context, token, title, body string, data map[string]string) error {
client, err := s.getClient(ctx)
if err != nil {
return err
}
msg := &messaging.Message{
Token: token,
Notification: &messaging.Notification{
Title: title,
Body: body,
},
Data: data,
}
_, err = client.Send(ctx, msg)
return err
}
func (s *FCMService) Send(token, title, body string, data map[string]string) error {
// Wrapper for HEAD compatibility if needed
return s.SendPush(context.Background(), token, title, body, data)
}
// SubscribeToTopic subscribes a token to a topic
func (s *FCMService) SubscribeToTopic(ctx context.Context, tokens []string, topic string) error {
client, err := s.getClient(ctx)
if err != nil {
return err
}
_, err = client.SubscribeToTopic(ctx, tokens, topic)
return err
}