Reapply "feat(loyalty): EnakPoint & EnakCoin" (#32)
This reverts commit 4e24f9bbb0.
This commit is contained in:
@@ -0,0 +1,77 @@
|
||||
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)
|
||||
}
|
||||
Reference in New Issue
Block a user