package logic import ( "context" "strings" "time" deviceentity "spark-server/internal/module/device/entity" "spark-server/internal/pkg/errs" "spark-server/internal/pkg/i18n" "spark-server/internal/pkg/reqctx" ) // RegisterInput is supplied at login before JWT issue. type RegisterInput struct { DeviceType deviceentity.DeviceType DeviceName string IDFV string AndroidID string ClientIP string } // RegistryRow is the persisted physical device. type RegistryRow struct { DeviceID uint64 FingerprintHash string DeviceType deviceentity.DeviceType DeviceName string IDFV string AndroidID string LastClientIP string UpdatedAt time.Time } type Registry interface { Upsert(ctx context.Context, row *RegistryRow) error } type Service struct { reg Registry } func NewService(reg Registry) *Service { return &Service{reg: reg} } func (s *Service) Register(ctx context.Context, in RegisterInput) (uint64, error) { if s == nil || s.reg == nil { return 0, errs.ErrAvailable } deviceID, hash, err := DeviceIDFromFingerprint(FingerprintInput{ DeviceType: in.DeviceType, IDFV: in.IDFV, AndroidID: in.AndroidID, }) if err != nil { return 0, errs.ErrInvalidArgument.WithMsg(i18n.T(reqctx.Lang(ctx), i18n.KeyDeviceIdentityInvalid, nil)) } now := time.Now() ip := strings.TrimSpace(in.ClientIP) if ip == "" { ip = reqctx.RealIP(ctx) } row := &RegistryRow{ DeviceID: deviceID, FingerprintHash: hash, DeviceType: in.DeviceType, DeviceName: strings.TrimSpace(in.DeviceName), IDFV: strings.TrimSpace(in.IDFV), AndroidID: strings.TrimSpace(in.AndroidID), LastClientIP: ip, UpdatedAt: now, } if err := s.reg.Upsert(ctx, row); err != nil { return 0, err } return deviceID, nil }