158 lines
5.1 KiB
Go
158 lines
5.1 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)
|
|
}
|
|
|
|
func (s *FCMService) SendMulticast(ctx context.Context, tokens []string, title, body string, data map[string]string) error {
|
|
if len(tokens) == 0 {
|
|
return nil
|
|
}
|
|
|
|
client, err := s.getClient(ctx)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
message := &messaging.MulticastMessage{
|
|
Tokens: tokens,
|
|
Notification: &messaging.Notification{
|
|
Title: title,
|
|
Body: body,
|
|
},
|
|
Data: data,
|
|
}
|
|
|
|
_, err = client.SendEachForMulticast(ctx, message)
|
|
return err
|
|
}
|
|
|
|
// 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
|
|
}
|