78 lines
2.8 KiB
Go
78 lines
2.8 KiB
Go
package processor
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"fmt"
|
|
"strings"
|
|
"time"
|
|
|
|
"github.com/google/uuid"
|
|
|
|
"apskel-pos-be/internal/logger"
|
|
"apskel-pos-be/internal/repository"
|
|
)
|
|
|
|
// ErrInvalidCustomerDevice wraps every rejection of a device registration.
|
|
var ErrInvalidCustomerDevice = errors.New("invalid customer device")
|
|
|
|
type customerPushSender interface {
|
|
SendMulticastNotification(ctx context.Context, tokens []string, title string, body string, data map[string]string) error
|
|
}
|
|
|
|
// CustomerDeviceProcessor keeps the customer app's FCM tokens and sends push
|
|
// notifications to a customer's devices.
|
|
type CustomerDeviceProcessor struct {
|
|
repo repository.CustomerDeviceRepository
|
|
// Nil when FCM is not configured; notifications are then skipped.
|
|
fcm customerPushSender
|
|
now func() time.Time
|
|
}
|
|
|
|
func NewCustomerDeviceProcessor(repo repository.CustomerDeviceRepository, fcm customerPushSender) *CustomerDeviceProcessor {
|
|
return &CustomerDeviceProcessor{repo: repo, fcm: fcm, now: time.Now}
|
|
}
|
|
|
|
// Register saves the FCM token the app got for this device. The app calls it after
|
|
// login and whenever FCM gives it a new token.
|
|
func (p *CustomerDeviceProcessor) Register(ctx context.Context, device repository.CustomerDevice) error {
|
|
device.DeviceID = strings.TrimSpace(device.DeviceID)
|
|
device.FCMToken = strings.TrimSpace(device.FCMToken)
|
|
switch {
|
|
case device.DeviceID == "" || len(device.DeviceID) > 255:
|
|
return fmt.Errorf("%w: device_id is required, at most 255 characters", ErrInvalidCustomerDevice)
|
|
case device.FCMToken == "" || len(device.FCMToken) > 512:
|
|
return fmt.Errorf("%w: fcm_token is required, at most 512 characters", ErrInvalidCustomerDevice)
|
|
}
|
|
if device.Platform != nil {
|
|
platform := strings.ToLower(strings.TrimSpace(*device.Platform))
|
|
if platform != "android" && platform != "ios" && platform != "web" {
|
|
return fmt.Errorf("%w: platform must be android, ios or web", ErrInvalidCustomerDevice)
|
|
}
|
|
device.Platform = &platform
|
|
}
|
|
return p.repo.Register(ctx, device, p.now())
|
|
}
|
|
|
|
// Unregister forgets a device, so it stops getting the customer's notifications.
|
|
func (p *CustomerDeviceProcessor) Unregister(ctx context.Context, customerID uuid.UUID, deviceID string) error {
|
|
return p.repo.Unregister(ctx, customerID, strings.TrimSpace(deviceID))
|
|
}
|
|
|
|
// Notify pushes a notification to every device of the customer through FCM. A
|
|
// customer without a registered device gets nothing, which is not an error.
|
|
func (p *CustomerDeviceProcessor) Notify(ctx context.Context, customerID uuid.UUID, title, body string, data map[string]string) error {
|
|
if p.fcm == nil {
|
|
logger.NonContext.Info(fmt.Sprintf("FCM is not configured; not notifying customer %s", customerID))
|
|
return nil
|
|
}
|
|
tokens, err := p.repo.ListTokens(ctx, customerID)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if len(tokens) == 0 {
|
|
return nil
|
|
}
|
|
return p.fcm.SendMulticastNotification(ctx, tokens, title, body, data)
|
|
}
|