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 }