diff --git a/cmd/eskimo-hut/kyc.go b/cmd/eskimo-hut/kyc.go index 2feb56bd..8c415d89 100644 --- a/cmd/eskimo-hut/kyc.go +++ b/cmd/eskimo-hut/kyc.go @@ -292,14 +292,6 @@ func (s *service) TryResetKYCSteps( //nolint:gocritic,funlen,gocognit,revive,cyc if err != nil { return nil, server.Unexpected(errors.Wrapf(err, "failed to CheckQuizStatus for userID:%v", req.Data.UserID)) } - kycFaceAvailable := false - if req.Data.NextKYCStep != nil && - (*req.Data.NextKYCStep == users.FacialRecognitionKYCStep || *req.Data.NextKYCStep == users.LivenessDetectionKYCStep) { - kycFaceAvailable, err = s.faceKycClient.CheckStatus(ctx, req.Data.UserID, *req.Data.NextKYCStep) - if err != nil { - return nil, server.Unexpected(err) - } - } resp, err := s.usersProcessor.TryResetKYCSteps(ctx, s.faceKycClient, req.Data.UserID) if err = errors.Wrapf(err, "failed to TryResetKYCSteps for userID:%v", req.Data.UserID); err != nil { switch { @@ -309,6 +301,14 @@ func (s *service) TryResetKYCSteps( //nolint:gocritic,funlen,gocognit,revive,cyc return nil, server.Unexpected(err) } } + kycFaceAvailable := false + if req.Data.NextKYCStep != nil && + (*req.Data.NextKYCStep == users.FacialRecognitionKYCStep || *req.Data.NextKYCStep == users.LivenessDetectionKYCStep) { + kycFaceAvailable, err = s.faceKycClient.CheckStatus(ctx, resp, *req.Data.NextKYCStep) + if err != nil { + return nil, server.Unexpected(err) + } + } return server.OK(&User{User: resp, QuizStatus: quizStatus, KycFaceAvailable: kycFaceAvailable, Checksum: resp.Checksum()}), nil } diff --git a/cmd/eskimo-hut/users.go b/cmd/eskimo-hut/users.go index a9372bbc..fb191282 100644 --- a/cmd/eskimo-hut/users.go +++ b/cmd/eskimo-hut/users.go @@ -403,7 +403,7 @@ func (s *service) DeleteUser( //nolint:gocritic // False negative. if err := server.Auth(ctx).DeleteUser(ctx, req.Data.UserID); err != nil && !errors.Is(err, auth.ErrUserNotFound) { return nil, server.Unexpected(errors.Wrapf(err, "failed to delete auth user:%#v", req.Data.UserID)) } - if err := s.faceKycClient.Reset(ctx, req.Data.UserID, false); err != nil { + if err := s.faceKycClient.Reset(ctx, &users.User{PublicUserInformation: users.PublicUserInformation{ID: req.Data.UserID}}, false); err != nil { return nil, server.Unexpected(errors.Wrapf(err, "failed to delete users face:%#v", req.Data.UserID)) } diff --git a/go.mod b/go.mod index 16057b45..6e71701d 100644 --- a/go.mod +++ b/go.mod @@ -121,7 +121,7 @@ require ( github.com/prometheus/client_model v0.6.1 // indirect github.com/prometheus/procfs v0.15.1 // indirect github.com/quic-go/qpack v0.4.0 // indirect - github.com/quic-go/quic-go v0.45.0 // indirect + github.com/quic-go/quic-go v0.45.1 // indirect github.com/refraction-networking/utls v1.6.6 // indirect github.com/rs/zerolog v1.33.0 // indirect github.com/sagikazarmark/locafero v0.6.0 // indirect diff --git a/go.sum b/go.sum index f7e20cae..30dff55f 100644 --- a/go.sum +++ b/go.sum @@ -326,8 +326,8 @@ github.com/prometheus/prometheus v0.53.0 h1:vOnhpUKrDv954jnVBvhG/ZQJ3kqscnKI+Hbd github.com/prometheus/prometheus v0.53.0/go.mod h1:RZDkzs+ShMBDkAPQkLEaLBXpjmDcjhNxU2drUVPgKUU= github.com/quic-go/qpack v0.4.0 h1:Cr9BXA1sQS2SmDUWjSofMPNKmvF6IiIfDRmgU0w1ZCo= github.com/quic-go/qpack v0.4.0/go.mod h1:UZVnYIfi5GRk+zI9UMaCPsmZ2xKJP7XBUvVyT1Knj9A= -github.com/quic-go/quic-go v0.45.0 h1:OHmkQGM37luZITyTSu6ff03HP/2IrwDX1ZFiNEhSFUE= -github.com/quic-go/quic-go v0.45.0/go.mod h1:1dLehS7TIR64+vxGR70GDcatWTOtMX2PUtnKsjbTurI= +github.com/quic-go/quic-go v0.45.1 h1:tPfeYCk+uZHjmDRwHHQmvHRYL2t44ROTujLeFVBmjCA= +github.com/quic-go/quic-go v0.45.1/go.mod h1:1dLehS7TIR64+vxGR70GDcatWTOtMX2PUtnKsjbTurI= github.com/refraction-networking/utls v1.6.6 h1:igFsYBUJPYM8Rno9xUuDoM5GQrVEqY4llzEXOkL43Ig= github.com/refraction-networking/utls v1.6.6/go.mod h1:BC3O4vQzye5hqpmDTWUqi4P5DDhzJfkV1tdqtawQIH0= github.com/rogpeppe/go-internal v1.11.0 h1:cWPaGQEPrBb5/AsnsZesgZZ9yb1OQ+GOISoDNXVBh4M= diff --git a/kyc/face/contract.go b/kyc/face/contract.go index d0420149..7c507e9a 100644 --- a/kyc/face/contract.go +++ b/kyc/face/contract.go @@ -16,8 +16,8 @@ type ( ThreeDiVi threedivi.Config `mapstructure:",squash"` //nolint:tagliatelle // . } Client interface { - Reset(ctx context.Context, userID string, fetchState bool) error - CheckStatus(ctx context.Context, userID string, nextKYCStep users.KYCStep) (available bool, err error) + Reset(ctx context.Context, user *users.User, fetchState bool) error + CheckStatus(ctx context.Context, user *users.User, nextKYCStep users.KYCStep) (available bool, err error) } ) diff --git a/kyc/face/face.go b/kyc/face/face.go index edb234ea..f4421869 100644 --- a/kyc/face/face.go +++ b/kyc/face/face.go @@ -20,22 +20,22 @@ func New(usersRep UserRepository) Client { return &client{client: threedivi.New3Divi(usersRep, &cfg.ThreeDiVi)} } -func (c *client) CheckStatus(ctx context.Context, userID string, nextKYCStep users.KYCStep) (bool, error) { +func (c *client) CheckStatus(ctx context.Context, user *users.User, nextKYCStep users.KYCStep) (bool, error) { kycFaceAvailable := false - if hasResult, err := c.client.CheckAndUpdateStatus(ctx, userID); err != nil { - return false, errors.Wrapf(err, "failed to update face auth status for user ID %s", userID) + if hasResult, err := c.client.CheckAndUpdateStatus(ctx, user); err != nil { + return false, errors.Wrapf(err, "failed to update face auth status for user ID %s", user.ID) } else if !hasResult || nextKYCStep == users.LivenessDetectionKYCStep { availabilityErr := c.client.Available(ctx) if availabilityErr == nil { kycFaceAvailable = true } else { - log.Error(errors.Wrapf(err, "face auth is unavailable for userID %v KYCStep %v", userID, nextKYCStep)) + log.Error(errors.Wrapf(err, "face auth is unavailable for userID %v KYCStep %v", user.ID, nextKYCStep)) } } return kycFaceAvailable, nil } -func (c *client) Reset(ctx context.Context, userID string, fetchState bool) error { - return errors.Wrapf(c.client.Reset(ctx, userID, fetchState), "failed to reset face auth state for userID %s", userID) +func (c *client) Reset(ctx context.Context, user *users.User, fetchState bool) error { + return errors.Wrapf(c.client.Reset(ctx, user, fetchState), "failed to reset face auth state for userID %s", user.ID) } diff --git a/kyc/face/internal/contract.go b/kyc/face/internal/contract.go index 257b2d6f..db4999e4 100644 --- a/kyc/face/internal/contract.go +++ b/kyc/face/internal/contract.go @@ -12,8 +12,8 @@ import ( type ( Client interface { Available(ctx context.Context) error - CheckAndUpdateStatus(ctx context.Context, userID string) (hasFaceKYCResult bool, err error) - Reset(ctx context.Context, userID string, fetchState bool) error + CheckAndUpdateStatus(ctx context.Context, user *users.User) (hasFaceKYCResult bool, err error) + Reset(ctx context.Context, user *users.User, fetchState bool) error } UserRepository interface { ModifyUser(ctx context.Context, usr *users.User, profilePicture *multipart.FileHeader) (*users.UserProfile, error) diff --git a/kyc/face/internal/threedivi/threedivi.go b/kyc/face/internal/threedivi/threedivi.go index 262b1d57..82bf2787 100644 --- a/kyc/face/internal/threedivi/threedivi.go +++ b/kyc/face/internal/threedivi/threedivi.go @@ -116,12 +116,12 @@ func (*threeDivi) activeUsers(data []byte) (int, error) { return openConns / connsPerUser, nil } -func (t *threeDivi) CheckAndUpdateStatus(ctx context.Context, userID string) (hasFaceKYCResult bool, err error) { - bafApplicant, err := t.searchIn3DiviForApplicant(ctx, userID) +func (t *threeDivi) CheckAndUpdateStatus(ctx context.Context, user *users.User) (hasFaceKYCResult bool, err error) { + bafApplicant, err := t.searchIn3DiviForApplicant(ctx, user.ID) if err != nil && !errors.Is(err, errFaceAuthNotStarted) { return false, errors.Wrapf(err, "failed to sync face auth status from 3divi BAF") } - usr := t.parseApplicant(userID, bafApplicant) + usr := t.parseApplicant(user, bafApplicant) hasFaceKYCResult = (usr.KYCStepPassed != nil && *usr.KYCStepPassed >= users.LivenessDetectionKYCStep) || (usr.KYCStepBlocked != nil && *usr.KYCStepBlocked > users.NoneKYCStep) _, mErr := t.users.ModifyUser(ctx, usr, nil) @@ -130,14 +130,14 @@ func (t *threeDivi) CheckAndUpdateStatus(ctx context.Context, userID string) (ha } //nolint:funlen,revive // . -func (t *threeDivi) Reset(ctx context.Context, userID string, fetchState bool) error { - bafApplicant, err := t.searchIn3DiviForApplicant(ctx, userID) +func (t *threeDivi) Reset(ctx context.Context, user *users.User, fetchState bool) error { + bafApplicant, err := t.searchIn3DiviForApplicant(ctx, user.ID) if err != nil { if errors.Is(err, errFaceAuthNotStarted) { return nil } - return errors.Wrapf(err, "failed to find matching applicant for userID %v", userID) + return errors.Wrapf(err, "failed to find matching applicant for userID %v", user.ID) } var resp *req.Response if resp, err = req. @@ -160,48 +160,71 @@ func (t *threeDivi) Reset(ctx context.Context, userID string, fetchState bool) e SetHeader("Authorization", fmt.Sprintf("Bearer %v", t.cfg.ThreeDiVi.BAFToken)). SetHeader("X-Secret-Api-Token", t.cfg.ThreeDiVi.SecretAPIToken). Delete(fmt.Sprintf("%v/publicapi/api/v2/private/Applicants/%v", t.cfg.ThreeDiVi.BAFHost, bafApplicant.ApplicantID)); err != nil { - return errors.Wrapf(err, "failed to delete face auth state for userID:%v", userID) + return errors.Wrapf(err, "failed to delete face auth state for userID:%v", user.ID) } else if statusCode := resp.GetStatusCode(); statusCode != http.StatusOK && statusCode != http.StatusNoContent { - return errors.Errorf("[%v]failed to delete face auth state for userID:%v", statusCode, userID) + return errors.Errorf("[%v]failed to delete face auth state for userID:%v", statusCode, user.ID) } else if _, err2 := resp.ToBytes(); err2 != nil { - return errors.Wrapf(err2, "failed to read body of delete face auth state request for userID:%v", userID) + return errors.Wrapf(err2, "failed to read body of delete face auth state request for userID:%v", user.ID) } else { //nolint:revive // . if fetchState { - _, err = t.CheckAndUpdateStatus(ctx, userID) + _, err = t.CheckAndUpdateStatus(ctx, user) - return errors.Wrapf(err, "failed to check user's face auth state after reset for userID %v", userID) + return errors.Wrapf(err, "failed to check user's face auth state after reset for userID %v", user.ID) } return nil } } -func (*threeDivi) parseApplicant(userID string, bafApplicant *applicant) *users.User { - usr := new(users.User) - usr.ID = userID +//nolint:funlen //. +func (*threeDivi) parseApplicant(user *users.User, bafApplicant *applicant) *users.User { + updUser := new(users.User) + updUser.ID = user.ID if bafApplicant != nil && bafApplicant.LastValidationResponse != nil && bafApplicant.Status == statusPassed { passedTime := time.New(bafApplicant.LastValidationResponse.CreatedAt) - times := []*time.Time{passedTime, passedTime} - usr.KYCStepsLastUpdatedAt = × - stepPassed := users.LivenessDetectionKYCStep - usr.KYCStepPassed = &stepPassed - } else { - var nilDates []*time.Time - usr.KYCStepsLastUpdatedAt = &nilDates - usr.KYCStepsCreatedAt = &nilDates - stepPassed := users.NoneKYCStep - usr.KYCStepPassed = &stepPassed + if user.KYCStepsCreatedAt != nil && len(*user.KYCStepsCreatedAt) >= int(users.LivenessDetectionKYCStep) { + updUser.KYCStepsCreatedAt = user.KYCStepsCreatedAt + updUser.KYCStepsLastUpdatedAt = user.KYCStepsLastUpdatedAt + (*updUser.KYCStepsCreatedAt)[stepIdx(users.FacialRecognitionKYCStep)] = passedTime + (*updUser.KYCStepsCreatedAt)[stepIdx(users.LivenessDetectionKYCStep)] = passedTime + (*updUser.KYCStepsLastUpdatedAt)[stepIdx(users.FacialRecognitionKYCStep)] = passedTime + (*updUser.KYCStepsLastUpdatedAt)[stepIdx(users.LivenessDetectionKYCStep)] = passedTime + } else { + times := []*time.Time{passedTime, passedTime} + updUser.KYCStepsLastUpdatedAt = × + stepPassed := users.LivenessDetectionKYCStep + updUser.KYCStepPassed = &stepPassed + } + } else if user.KYCStepsCreatedAt != nil && len(*user.KYCStepsCreatedAt) >= int(users.LivenessDetectionKYCStep) { + updUser.KYCStepsCreatedAt = user.KYCStepsCreatedAt + updUser.KYCStepsLastUpdatedAt = user.KYCStepsLastUpdatedAt + (*updUser.KYCStepsCreatedAt)[stepIdx(users.FacialRecognitionKYCStep)] = nil + (*updUser.KYCStepsCreatedAt)[stepIdx(users.LivenessDetectionKYCStep)] = nil + (*updUser.KYCStepsLastUpdatedAt)[stepIdx(users.FacialRecognitionKYCStep)] = nil + (*updUser.KYCStepsLastUpdatedAt)[stepIdx(users.LivenessDetectionKYCStep)] = nil } switch { case bafApplicant != nil && bafApplicant.LastValidationResponse != nil && bafApplicant.Status == statusFailed && bafApplicant.HasRiskEvents: kycStepBlocked := users.FacialRecognitionKYCStep - usr.KYCStepBlocked = &kycStepBlocked + updUser.KYCStepBlocked = &kycStepBlocked default: kycStepBlocked := users.NoneKYCStep - usr.KYCStepBlocked = &kycStepBlocked + updUser.KYCStepBlocked = &kycStepBlocked + } + user.KYCStepsLastUpdatedAt = updUser.KYCStepsLastUpdatedAt + user.KYCStepsCreatedAt = updUser.KYCStepsCreatedAt + if updUser.KYCStepPassed != nil { + user.KYCStepPassed = updUser.KYCStepPassed + } + if updUser.KYCStepBlocked != nil { + user.KYCStepBlocked = updUser.KYCStepBlocked } - return usr + return updUser +} + +func stepIdx(step users.KYCStep) int { + return int(step) - 1 } func (t *threeDivi) searchIn3DiviForApplicant(ctx context.Context, userID users.UserID) (*applicant, error) { diff --git a/kyc/social/social.go b/kyc/social/social.go index ae5c6c54..b82e58db 100644 --- a/kyc/social/social.go +++ b/kyc/social/social.go @@ -148,9 +148,11 @@ func (r *repository) VerifyPost(ctx context.Context, metadata *VerificationMetad if err != nil { return nil, errors.Wrapf(err, "failed to GetUserByID: %v", metadata.UserID) } + if err = r.validateKycStep(user.User, metadata.KYCStep, now); err != nil { return nil, errors.Wrap(err, "failed to validateKycStep") } + skippedCount, err := r.verifySkipped(ctx, metadata, now) if err != nil { return nil, errors.Wrapf(err, "failed to verifySkipped for metadata:%#v", metadata) @@ -264,14 +266,15 @@ func (r *repository) VerifyPost(ctx context.Context, metadata *VerificationMetad } func (r *repository) validateKycStep(user *users.User, kycStep users.KYCStep, now *time.Time) error { - if user.KYCStepPassed == nil || + allowSocialBeforeFace := true + if !allowSocialBeforeFace && (user.KYCStepPassed == nil || *user.KYCStepPassed < kycStep-1 || (user.KYCStepPassed != nil && *user.KYCStepPassed == kycStep-1 && user.KYCStepsLastUpdatedAt != nil && len(*user.KYCStepsLastUpdatedAt) >= int(kycStep) && !(*user.KYCStepsLastUpdatedAt)[kycStep-1].IsNil() && - now.Sub(*(*user.KYCStepsLastUpdatedAt)[kycStep-1].Time) < r.cfg.DelayBetweenSessions) { + now.Sub(*(*user.KYCStepsLastUpdatedAt)[kycStep-1].Time) < r.cfg.DelayBetweenSessions)) { return ErrNotAvailable } else if user.KYCStepPassed != nil && *user.KYCStepPassed >= kycStep { return ErrDuplicate @@ -280,7 +283,7 @@ func (r *repository) validateKycStep(user *users.User, kycStep users.KYCStep, no return nil } -//nolint:revive,funlen // Nope. +//nolint:revive,funlen,gocognit // Nope. func (r *repository) modifyUser(ctx context.Context, success, skip bool, kycStep users.KYCStep, now *time.Time, user *users.User) error { usr := new(users.User) usr.ID = user.ID @@ -288,6 +291,10 @@ func (r *repository) modifyUser(ctx context.Context, success, skip bool, kycStep switch { case success: usr.KYCStepPassed = &kycStep + if usr.KYCStepsLastUpdatedAt == nil { + emptyFaceRecognition := []*time.Time{nil, nil} + usr.KYCStepsLastUpdatedAt = &emptyFaceRecognition + } if len(*usr.KYCStepsLastUpdatedAt) < int(kycStep) { *usr.KYCStepsLastUpdatedAt = append(*usr.KYCStepsLastUpdatedAt, now) } else { diff --git a/users/contract.go b/users/contract.go index f69f2def..f6b61cd1 100644 --- a/users/contract.go +++ b/users/contract.go @@ -200,7 +200,7 @@ type ( IsEmailUsedBySomebodyElse(ctx context.Context, userID, email string) (bool, error) } ResetKycClient interface { - Reset(ctx context.Context, userID string, fetchState bool) error + Reset(ctx context.Context, user *User, fetchState bool) error } WriteRepository interface { CreateUser(ctx context.Context, usr *User, clientIP net.IP) error diff --git a/users/kyc.go b/users/kyc.go index 0ddd52c3..292c4d20 100644 --- a/users/kyc.go +++ b/users/kyc.go @@ -32,14 +32,14 @@ func (r *repository) TryResetKYCSteps(ctx context.Context, resetClient ResetKycC r.sanitizeUserForUI(&resp.User) return &resp.User, nil - } else if err = r.resetKYCSteps(ctx, resetClient, userID, resp.KYCStepsToReset); err != nil { + } else if err = r.resetKYCSteps(ctx, resetClient, &resp.User, resp.KYCStepsToReset); err != nil { return nil, errors.Wrapf(err, "failed to resetKYCSteps for userID:%v", userID) } return r.TryResetKYCSteps(ctx, resetClient, userID) } -func (r *repository) resetKYCSteps(ctx context.Context, resetClient ResetKycClient, userID string, kycStepsToBeReset []KYCStep) error { +func (r *repository) resetKYCSteps(ctx context.Context, resetClient ResetKycClient, user *User, kycStepsToBeReset []KYCStep) error { kycStepResetPipelines := make(map[KYCStep]struct{}, len(kycStepsToBeReset)) for _, kycStep := range kycStepsToBeReset { if kycStep == LivenessDetectionKYCStep || kycStep == FacialRecognitionKYCStep { @@ -54,7 +54,7 @@ func (r *repository) resetKYCSteps(ctx context.Context, resetClient ResetKycClie for kycStep := range kycStepResetPipelines { go func(step KYCStep) { defer wg.Done() - errs <- errors.Wrapf(r.resetKYCStep(ctx, resetClient, userID, step), "failed to resetKYCStep(%v) for userID:%v", step, userID) + errs <- errors.Wrapf(r.resetKYCStep(ctx, resetClient, user, step), "failed to resetKYCStep(%v) for userID:%v", step, user.ID) }(kycStep) } wg.Wait() @@ -64,21 +64,21 @@ func (r *repository) resetKYCSteps(ctx context.Context, resetClient ResetKycClie responses = append(responses, err) } if err := multierror.Append(nil, responses...).ErrorOrNil(); err != nil { - return errors.Wrapf(err, "atleast one resetKYCStep failed for userID:%v", userID) + return errors.Wrapf(err, "atleast one resetKYCStep failed for userID:%v", user.ID) } - _, err := storage.Exec(ctx, r.db, `DELETE FROM kyc_steps_reset_requests WHERE user_id = $1`, userID) + _, err := storage.Exec(ctx, r.db, `DELETE FROM kyc_steps_reset_requests WHERE user_id = $1`, user.ID) - return errors.Wrapf(err, "failed to delete kyc step reset request for userID:%v", userID) + return errors.Wrapf(err, "failed to delete kyc step reset request for userID:%v", user.ID) } -func (r *repository) resetKYCStep(ctx context.Context, resetClient ResetKycClient, userID string, step KYCStep) error { +func (r *repository) resetKYCStep(ctx context.Context, resetClient ResetKycClient, user *User, step KYCStep) error { switch step { //nolint:exhaustive // Not needed yet. case FacialRecognitionKYCStep: - if err := resetClient.Reset(ctx, userID, true); err != nil { - return errors.Wrapf(err, "failed to resetFacialRecognitionKYCStep for userID:%v", userID) + if err := resetClient.Reset(ctx, user, true); err != nil { + return errors.Wrapf(err, "failed to resetFacialRecognitionKYCStep for userID:%v", user.ID) } default: - log.Error(errors.Errorf("reset for KYCStep[%v] not implemented, userID:%v", step, userID)) + log.Error(errors.Errorf("reset for KYCStep[%v] not implemented, userID:%v", step, user.ID)) return nil } @@ -86,9 +86,9 @@ func (r *repository) resetKYCStep(ctx context.Context, resetClient ResetKycClien sql := `UPDATE kyc_steps_reset_requests SET kyc_steps_to_reset = array_remove(kyc_steps_to_reset, $2::smallint) WHERE user_id = $1` - if updated, err := storage.Exec(ctx, r.db, sql, userID, step); err != nil || updated == 0 { + if updated, err := storage.Exec(ctx, r.db, sql, user.ID, step); err != nil || updated == 0 { if updated == 0 { - err = errors.Wrapf(ErrNotFound, "failed to remove step[%v] from kyc_steps_reset_requests for userID:%v", step, userID) + err = errors.Wrapf(ErrNotFound, "failed to remove step[%v] from kyc_steps_reset_requests for userID:%v", step, user.ID) } if storage.IsErr(err, storage.ErrCheckFailed) { // This happens if the resulting array is empty, at which point we need to delete the entire entry, @@ -96,7 +96,7 @@ func (r *repository) resetKYCStep(ctx context.Context, resetClient ResetKycClien err = nil } if err != nil { - return errors.Wrapf(err, "[db]failed to resetKYCStep[%v] for userID:%v", step, userID) + return errors.Wrapf(err, "[db]failed to resetKYCStep[%v] for userID:%v", step, user.ID) } } diff --git a/users/users.go b/users/users.go index 77887310..2933e18d 100644 --- a/users/users.go +++ b/users/users.go @@ -295,9 +295,12 @@ func (r *repository) buildRepeatableKYCSteps(usr *User) { if r.cfg.IntervalBetweenRepeatableKYCSteps == 0 { log.Panic(errors.New("`intervalBetweenRepeatableKYCSteps` config is missing")) } - nextDate := (*usr.KYCStepsLastUpdatedAt)[LivenessDetectionKYCStep-1].Add(r.cfg.IntervalBetweenRepeatableKYCSteps) + date := (*usr.KYCStepsLastUpdatedAt)[LivenessDetectionKYCStep-1] repeatableKYCSteps := make(map[KYCStep]*time.Time, 1) - repeatableKYCSteps[LivenessDetectionKYCStep] = time.New(nextDate) + if !date.IsNil() { + nextDate := date.Add(r.cfg.IntervalBetweenRepeatableKYCSteps) + repeatableKYCSteps[LivenessDetectionKYCStep] = time.New(nextDate) + } usr.RepeatableKYCSteps = &repeatableKYCSteps } diff --git a/users/users_modify.go b/users/users_modify.go index 5fbde88a..7931760f 100644 --- a/users/users_modify.go +++ b/users/users_modify.go @@ -154,7 +154,7 @@ func (u *User) override(user *User) *User { return usr } -//nolint:funlen,gocognit,gocyclo,revive,cyclop // Because it's a big unitary SQL processing logic. +//nolint:funlen,gocognit,gocyclo,revive,cyclop,maintidx // Because it's a big unitary SQL processing logic. func (u *User) genSQLUpdate(ctx context.Context, agendaUserIDs []UserID) (sql string, params []any) { params = make([]any, 0) params = append(params, u.ID, u.UpdatedAt.Time) @@ -259,26 +259,37 @@ func (u *User) genSQLUpdate(ctx context.Context, agendaUserIDs []UserID) (sql st sql += fmt.Sprintf(", MINING_BLOCKCHAIN_ACCOUNT_ADDRESS = $%v", nextIndex) nextIndex++ } - if u.KYCStepsLastUpdatedAt != nil { + if u.KYCStepsLastUpdatedAt != nil { //nolint:nestif // Handling nil values. if *u.KYCStepsLastUpdatedAt == nil { sql += ", KYC_STEPS_LAST_UPDATED_AT = NULL" } else { - kycStepsLastUpdatedAt := make([]stdlibtime.Time, 0, len(*u.KYCStepsLastUpdatedAt)) + kycStepsLastUpdatedAt := make([]*stdlibtime.Time, 0, len(*u.KYCStepsLastUpdatedAt)) for _, updatedAt := range *u.KYCStepsLastUpdatedAt { - kycStepsLastUpdatedAt = append(kycStepsLastUpdatedAt, *updatedAt.Time) + if updatedAt.IsNil() { + kycStepsLastUpdatedAt = append(kycStepsLastUpdatedAt, nil) + } else { + kycStepsLastUpdatedAt = append(kycStepsLastUpdatedAt, updatedAt.Time) + } } params = append(params, kycStepsLastUpdatedAt) - sql += fmt.Sprintf(", KYC_STEPS_LAST_UPDATED_AT = NULLIF(array_remove(array[coalesce(($%[1]v::timestamp[])[1],(KYC_STEPS_LAST_UPDATED_AT)[1]),coalesce(($%[1]v::timestamp[])[2],(KYC_STEPS_LAST_UPDATED_AT)[2]),coalesce(($%[1]v::timestamp[])[3],(KYC_STEPS_LAST_UPDATED_AT)[3]),coalesce(($%[1]v::timestamp[])[4],(KYC_STEPS_LAST_UPDATED_AT)[4]),coalesce(($%[1]v::timestamp[])[5],(KYC_STEPS_LAST_UPDATED_AT)[5]),coalesce(($%[1]v::timestamp[])[6],(KYC_STEPS_LAST_UPDATED_AT)[6]),coalesce(($%[1]v::timestamp[])[7],(KYC_STEPS_LAST_UPDATED_AT)[7]),coalesce(($%[1]v::timestamp[])[8],(KYC_STEPS_LAST_UPDATED_AT)[8]),coalesce(($%[1]v::timestamp[])[9],(KYC_STEPS_LAST_UPDATED_AT)[9]),coalesce(($%[1]v::timestamp[])[10],(KYC_STEPS_LAST_UPDATED_AT)[10])],null),array[]::timestamp[]), KYC_STEPS_CREATED_AT = NULLIF(array_remove(array[coalesce((KYC_STEPS_CREATED_AT)[1],($%[1]v::timestamp[])[1]),coalesce((KYC_STEPS_CREATED_AT)[2],($%[1]v::timestamp[])[2]),coalesce((KYC_STEPS_CREATED_AT)[3],($%[1]v::timestamp[])[3]),coalesce((KYC_STEPS_CREATED_AT)[4],($%[1]v::timestamp[])[4]),coalesce((KYC_STEPS_CREATED_AT)[5],($%[1]v::timestamp[])[5]),coalesce((KYC_STEPS_CREATED_AT)[6],($%[1]v::timestamp[])[6]),coalesce((KYC_STEPS_CREATED_AT)[7],($%[1]v::timestamp[])[7]),coalesce((KYC_STEPS_CREATED_AT)[8],($%[1]v::timestamp[])[8]),coalesce((KYC_STEPS_CREATED_AT)[9],($%[1]v::timestamp[])[9]),coalesce((KYC_STEPS_CREATED_AT)[10],($%[1]v::timestamp[])[10])],null),array[]::timestamp[])", nextIndex) //nolint:lll // . + sql += fmt.Sprintf(", KYC_STEPS_LAST_UPDATED_AT = NULLIF(array[($%[1]v::timestamp[])[1],($%[1]v::timestamp[])[2]] || array_remove(array[coalesce(($%[1]v::timestamp[])[3],(KYC_STEPS_LAST_UPDATED_AT)[3]),coalesce(($%[1]v::timestamp[])[4],(KYC_STEPS_LAST_UPDATED_AT)[4]),coalesce(($%[1]v::timestamp[])[5],(KYC_STEPS_LAST_UPDATED_AT)[5]),coalesce(($%[1]v::timestamp[])[6],(KYC_STEPS_LAST_UPDATED_AT)[6]),coalesce(($%[1]v::timestamp[])[7],(KYC_STEPS_LAST_UPDATED_AT)[7]),coalesce(($%[1]v::timestamp[])[8],(KYC_STEPS_LAST_UPDATED_AT)[8]),coalesce(($%[1]v::timestamp[])[9],(KYC_STEPS_LAST_UPDATED_AT)[9]),coalesce(($%[1]v::timestamp[])[10],(KYC_STEPS_LAST_UPDATED_AT)[10])],null),array[]::timestamp[])", nextIndex) //nolint:lll // . + if u.KYCStepsCreatedAt == nil { + sql += fmt.Sprintf(", KYC_STEPS_CREATED_AT = NULLIF(array[coalesce((KYC_STEPS_CREATED_AT)[1],($%[1]v::timestamp[])[1]),coalesce((KYC_STEPS_CREATED_AT)[2],($%[1]v::timestamp[])[2])] || array_remove(array[coalesce((KYC_STEPS_CREATED_AT)[3],($%[1]v::timestamp[])[3]),coalesce((KYC_STEPS_CREATED_AT)[4],($%[1]v::timestamp[])[4]),coalesce((KYC_STEPS_CREATED_AT)[5],($%[1]v::timestamp[])[5]),coalesce((KYC_STEPS_CREATED_AT)[6],($%[1]v::timestamp[])[6]),coalesce((KYC_STEPS_CREATED_AT)[7],($%[1]v::timestamp[])[7]),coalesce((KYC_STEPS_CREATED_AT)[8],($%[1]v::timestamp[])[8]),coalesce((KYC_STEPS_CREATED_AT)[9],($%[1]v::timestamp[])[9]),coalesce((KYC_STEPS_CREATED_AT)[10],($%[1]v::timestamp[])[10])],null),array[]::timestamp[])", nextIndex) //nolint:lll // . + } nextIndex++ } } - if u.KYCStepsCreatedAt != nil { + if u.KYCStepsCreatedAt != nil { //nolint:nestif // Handling nil values. if *u.KYCStepsCreatedAt == nil { sql += ", KYC_STEPS_CREATED_AT = NULL" } else { - kycStepsCreatedAt := make([]stdlibtime.Time, 0, len(*u.KYCStepsCreatedAt)) + kycStepsCreatedAt := make([]*stdlibtime.Time, 0, len(*u.KYCStepsCreatedAt)) for _, createdAt := range *u.KYCStepsCreatedAt { - kycStepsCreatedAt = append(kycStepsCreatedAt, *createdAt.Time) + if createdAt.IsNil() { + kycStepsCreatedAt = append(kycStepsCreatedAt, nil) + } else { + kycStepsCreatedAt = append(kycStepsCreatedAt, createdAt.Time) + } } params = append(params, kycStepsCreatedAt) sql += fmt.Sprintf(", KYC_STEPS_CREATED_AT = $%[1]v::timestamp[]", nextIndex)