gohorsejobs/backend/internal/services/fcm_service.go
Tiago Yamamoto 841b1d780c feat: Email System, Avatar Upload, Email Templates UI, and Public Job Posting
- Backend: Email producer (LavinMQ), EmailService interface
- Backend: CRUD API for email_templates and email_settings
- Backend: avatar_url field in users table + UpdateMyProfile support
- Backend: StorageService for pre-signed URLs
- NestJS: Email consumer with Nodemailer and Handlebars
- Frontend: Email Templates admin pages (list/edit)
- Frontend: Updated profileApi.uploadAvatar with pre-signed URL flow
- Frontend: New /post-job public page (company registration + job creation wizard)
- Migrations: 027_create_email_system.sql, 028_add_avatar_url_to_users.sql
2025-12-26 12:21:34 -03:00

85 lines
1.9 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
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
}
// 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
}