From b82979a6fe958f79d34e79bdae79093efc3967ba Mon Sep 17 00:00:00 2001 From: ice-myles <96409608+ice-myles@users.noreply.github.com> Date: Wed, 13 Nov 2024 22:23:10 +0300 Subject: [PATCH] Option to use mandatody tasks verification. Replaced signup tenants calls to Verify function usage from linking package. --- application.yaml | 12 +- cmd/eskimo-hut/eskimo.go | 4 +- go.mod | 12 +- go.sum | 24 ++-- kyc/linking/linking.go | 4 +- kyc/verification_scenarios/contract.go | 17 ++- .../verification_scenarios.go | 134 +++++++++++++----- .../verification_scenarios_test.go | 16 +-- users/DDL.sql | 2 + users/contract.go | 1 + users/users_modify.go | 5 + 11 files changed, 151 insertions(+), 80 deletions(-) diff --git a/application.yaml b/application.yaml index aee67f5e..6830b232 100644 --- a/application.yaml +++ b/application.yaml @@ -89,11 +89,13 @@ kyc/linking: kyc/coinDistributionEligibility: tenant: sunwaves config-json-url1: https://somewhere.com/something/somebogus.json - # If urls does not match hostname>/$tenant/ schema. - # tenantURLs: - # sunwaves: https://localhost:7443/ - # callfluent: https://localhost:7444/ - # doctorx: https://localhost:7445/ + # Comment if don't need in mandarody scenarios. + mandatoryScenarios: + - join_twitter + - join_telegram + - join_cmc + - signup_callfluent + - signup_doctorx kyc/quiz: environment: local enable-alerts: false diff --git a/cmd/eskimo-hut/eskimo.go b/cmd/eskimo-hut/eskimo.go index d72b5ee2..f33789eb 100644 --- a/cmd/eskimo-hut/eskimo.go +++ b/cmd/eskimo-hut/eskimo.go @@ -408,8 +408,8 @@ func (s *service) GetUserByUsername( //nolint:gocritic // False negative. // @Router /v1r/kyc/verifyCoinDistributionEligibility/users/{userId} [GET]. func (s *service) GetPendingKYCVerificationScenarios( //nolint:gocritic // . ctx context.Context, - req *server.Request[GetRequiredVerificationEligibilityScenariosArg, []*verificationscenarios.Scenario], -) (*server.Response[[]*verificationscenarios.Scenario], *server.Response[server.ErrorResponse]) { + req *server.Request[GetRequiredVerificationEligibilityScenariosArg, []verificationscenarios.Scenario], +) (*server.Response[[]verificationscenarios.Scenario], *server.Response[server.ErrorResponse]) { ctx = users.ContextWithAuthorization(ctx, req.Data.Authorization) //nolint:revive // . scenarios, err := s.verificationScenariosRepository.GetPendingVerificationScenarios(ctx, req.Data.UserID) if err = errors.Wrapf(err, "failed to GetRequiredVerificationEligibilityScenarios for userID:%v", req.Data.UserID); err != nil { diff --git a/go.mod b/go.mod index 1a936851..fd292ab6 100644 --- a/go.mod +++ b/go.mod @@ -10,7 +10,7 @@ require ( github.com/google/uuid v1.6.0 github.com/hashicorp/go-multierror v1.1.1 github.com/ice-blockchain/go-tarantool-client v0.0.0-20230327200757-4fc71fa3f7bb - github.com/ice-blockchain/santa v1.168.0 + github.com/ice-blockchain/santa v1.174.0 github.com/ice-blockchain/wintr v1.154.0 github.com/imroc/req/v3 v3.48.0 github.com/ip2location/ip2location-go/v9 v9.7.0 @@ -31,7 +31,7 @@ require ( require ( cel.dev/expr v0.18.0 // indirect cloud.google.com/go v0.116.0 // indirect - cloud.google.com/go/auth v0.10.1 // indirect + cloud.google.com/go/auth v0.10.2 // indirect cloud.google.com/go/auth/oauth2adapt v0.2.5 // indirect cloud.google.com/go/compute/metadata v0.5.2 // indirect cloud.google.com/go/firestore v1.17.0 // indirect @@ -103,7 +103,7 @@ require ( github.com/google/pprof v0.0.0-20241101162523-b92577c0c142 // indirect github.com/google/s2a-go v0.1.8 // indirect github.com/googleapis/enterprise-certificate-proxy v0.3.4 // indirect - github.com/googleapis/gax-go/v2 v2.13.0 // indirect + github.com/googleapis/gax-go/v2 v2.14.0 // indirect github.com/grafana/regexp v0.0.0-20240518133315-a468a5bfb3bc // indirect github.com/hashicorp/errwrap v1.1.0 // indirect github.com/hashicorp/hcl v1.0.0 // indirect @@ -188,9 +188,9 @@ require ( golang.org/x/tools v0.27.0 // indirect google.golang.org/api v0.205.0 // indirect google.golang.org/appengine/v2 v2.0.6 // indirect - google.golang.org/genproto v0.0.0-20241104194629-dd2ea8efbc28 // indirect - google.golang.org/genproto/googleapis/api v0.0.0-20241104194629-dd2ea8efbc28 // indirect - google.golang.org/genproto/googleapis/rpc v0.0.0-20241104194629-dd2ea8efbc28 // indirect + google.golang.org/genproto v0.0.0-20241113154021-e0fbfb71d213 // indirect + google.golang.org/genproto/googleapis/api v0.0.0-20241113154021-e0fbfb71d213 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20241113154021-e0fbfb71d213 // indirect google.golang.org/grpc v1.68.0 // indirect google.golang.org/grpc/stats/opentelemetry v0.0.0-20241028142157-ada6787961b3 // indirect google.golang.org/protobuf v1.35.1 // indirect diff --git a/go.sum b/go.sum index 79643809..6d4d0aa4 100644 --- a/go.sum +++ b/go.sum @@ -3,8 +3,8 @@ cel.dev/expr v0.18.0/go.mod h1:MrpN08Q+lEBs+bGYdLxxHkZoUSsCp0nSKTs0nTymJgw= cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= cloud.google.com/go v0.116.0 h1:B3fRrSDkLRt5qSHWe40ERJvhvnQwdZiHu0bJOpldweE= cloud.google.com/go v0.116.0/go.mod h1:cEPSRWPzZEswwdr9BxE6ChEn01dWlTaF05LiC2Xs70U= -cloud.google.com/go/auth v0.10.1 h1:TnK46qldSfHWt2a0b/hciaiVJsmDXWy9FqyUan0uYiI= -cloud.google.com/go/auth v0.10.1/go.mod h1:xxA5AqpDrvS+Gkmo9RqrGGRh6WSNKKOXhY3zNOr38tI= +cloud.google.com/go/auth v0.10.2 h1:oKF7rgBfSHdp/kuhXtqU/tNDr0mZqhYbEh+6SiqzkKo= +cloud.google.com/go/auth v0.10.2/go.mod h1:xxA5AqpDrvS+Gkmo9RqrGGRh6WSNKKOXhY3zNOr38tI= cloud.google.com/go/auth/oauth2adapt v0.2.5 h1:2p29+dePqsCHPP1bqDJcKj4qxRyYCcbzKpFyKGt3MTk= cloud.google.com/go/auth/oauth2adapt v0.2.5/go.mod h1:AlmsELtlEBnaNTL7jCj8VQFLy6mbZv0s4Q7NGBeQ5E8= cloud.google.com/go/compute/metadata v0.5.2 h1:UxK4uu/Tn+I3p2dYWTfiX4wva7aYlKixAHn3fyqngqo= @@ -238,8 +238,8 @@ github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/googleapis/enterprise-certificate-proxy v0.3.4 h1:XYIDZApgAnrN1c855gTgghdIA6Stxb52D5RnLI1SLyw= github.com/googleapis/enterprise-certificate-proxy v0.3.4/go.mod h1:YKe7cfqYXjKGpGvmSg28/fFvhNzinZQm8DGnaburhGA= -github.com/googleapis/gax-go/v2 v2.13.0 h1:yitjD5f7jQHhyDsnhKEBU52NdvvdSeGzlAnDPT0hH1s= -github.com/googleapis/gax-go/v2 v2.13.0/go.mod h1:Z/fvTZXF8/uw7Xu5GuslPw+bplx6SS338j1Is2S+B7A= +github.com/googleapis/gax-go/v2 v2.14.0 h1:f+jMrjBPl+DL9nI4IQzLUxMq7XrAqFYB7hBPqMNIe8o= +github.com/googleapis/gax-go/v2 v2.14.0/go.mod h1:lhBCnjdLrWRaPvLWhmc8IS24m9mr07qSYnHncrgo+zk= github.com/grafana/regexp v0.0.0-20240518133315-a468a5bfb3bc h1:GN2Lv3MGO7AS6PrRoT6yV5+wkrOpcszoIsO4+4ds248= github.com/grafana/regexp v0.0.0-20240518133315-a468a5bfb3bc/go.mod h1:+JKpmjMGhpgPL+rXZ5nsZieVzvarn86asRlBg4uNGnk= github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= @@ -251,8 +251,8 @@ github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4= github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= github.com/ice-blockchain/go-tarantool-client v0.0.0-20230327200757-4fc71fa3f7bb h1:8TnFP3mc7O+tc44kv2e0/TpZKnEVUaKH+UstwfBwRkk= github.com/ice-blockchain/go-tarantool-client v0.0.0-20230327200757-4fc71fa3f7bb/go.mod h1:ZsQU7i3mxhgBBu43Oev7WPFbIjP4TniN/b1UPNGbrq8= -github.com/ice-blockchain/santa v1.168.0 h1:7FQp239sV02w7EejDr4j74Q9OTikzdZEtPcWtR5K8XE= -github.com/ice-blockchain/santa v1.168.0/go.mod h1:xXI7k0+QSgwxvZ8IgPDddmnJxpTJO3IG7Xa7v1E7RGs= +github.com/ice-blockchain/santa v1.174.0 h1:MUKMSHdmfkCqPxHf0mje6SqMuGH7y8HmmbSWEHsySmE= +github.com/ice-blockchain/santa v1.174.0/go.mod h1:wA5bA3h6zz0kYt6s3aJ9dDsvWt8SdxYwAUrOJQXpC1E= github.com/ice-blockchain/wintr v1.154.0 h1:yZSQtAEwGHTSmJ5pXjX0tpui1TNnG615QfpBkhY99a4= github.com/ice-blockchain/wintr v1.154.0/go.mod h1:DoUn66XJGzPzfCZTsHyMjfgj2aVLGvjqDSuKj2pa3KE= github.com/imroc/req/v3 v3.48.0 h1:IYuMGetuwLzOOTzDCquDqs912WNwpsPK0TBXWPIvoqg= @@ -574,12 +574,12 @@ google.golang.org/appengine/v2 v2.0.6/go.mod h1:WoEXGoXNfa0mLvaH5sV3ZSGXwVmy8yf7 google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo= -google.golang.org/genproto v0.0.0-20241104194629-dd2ea8efbc28 h1:KJjNNclfpIkVqrZlTWcgOOaVQ00LdBnoEaRfkUx760s= -google.golang.org/genproto v0.0.0-20241104194629-dd2ea8efbc28/go.mod h1:mt9/MofW7AWQ+Gy179ChOnvmJatV8YHUmrcedo9CIFI= -google.golang.org/genproto/googleapis/api v0.0.0-20241104194629-dd2ea8efbc28 h1:M0KvPgPmDZHPlbRbaNU1APr28TvwvvdUPlSv7PUvy8g= -google.golang.org/genproto/googleapis/api v0.0.0-20241104194629-dd2ea8efbc28/go.mod h1:dguCy7UOdZhTvLzDyt15+rOrawrpM4q7DD9dQ1P11P4= -google.golang.org/genproto/googleapis/rpc v0.0.0-20241104194629-dd2ea8efbc28 h1:XVhgTWWV3kGQlwJHR3upFWZeTsei6Oks1apkZSeonIE= -google.golang.org/genproto/googleapis/rpc v0.0.0-20241104194629-dd2ea8efbc28/go.mod h1:GX3210XPVPUjJbTUbvwI8f2IpZDMZuPJWDzDuebbviI= +google.golang.org/genproto v0.0.0-20241113154021-e0fbfb71d213 h1:aK1CSq3+WIXZj6XcWhK0fAb303nF8sta2eca0DSARwQ= +google.golang.org/genproto v0.0.0-20241113154021-e0fbfb71d213/go.mod h1:Q5m6g8b5KaFFzsQFIGdJkSJDGeJiybVenoYFMMa3ohI= +google.golang.org/genproto/googleapis/api v0.0.0-20241113154021-e0fbfb71d213 h1:cNftAhx0Q32f3Fz2+BLargfsMD6pGINE+/mUZneTMyk= +google.golang.org/genproto/googleapis/api v0.0.0-20241113154021-e0fbfb71d213/go.mod h1:Yo94eF2nj7igQt+TiJ49KxjIH8ndLYPZMIRSiRcEbg0= +google.golang.org/genproto/googleapis/rpc v0.0.0-20241113154021-e0fbfb71d213 h1:L+WcQXqkyf5MX6g7AudgYEuJjmYbqSRkTmJqGfAPw+Y= +google.golang.org/genproto/googleapis/rpc v0.0.0-20241113154021-e0fbfb71d213/go.mod h1:GX3210XPVPUjJbTUbvwI8f2IpZDMZuPJWDzDuebbviI= google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY= diff --git a/kyc/linking/linking.go b/kyc/linking/linking.go index 92cbe0a2..9663ba7e 100644 --- a/kyc/linking/linking.go +++ b/kyc/linking/linking.go @@ -140,7 +140,7 @@ func (l *linker) Get(ctx context.Context, userID UserID) (allLinkedProfiles Link } func (l *linker) verifyToken(ctx context.Context, userID, tenant, token string) (remoteID UserID, hasFaceResult bool, err error) { - usr, err := FetchTokenData(ctx, tenant, token, l.host, l.cfg.TenantURLs) + usr, err := fetchTokenData(ctx, tenant, token, l.host, l.cfg.TenantURLs) if err != nil { if errors.Is(err, ErrRemoteUserNotFound) { return "", false, errors.Wrapf(ErrNotOwnRemoteUser, "token is not belong to %v", userID) @@ -156,7 +156,7 @@ func (l *linker) verifyToken(ctx context.Context, userID, tenant, token string) } //nolint:funlen // Single http call. -func FetchTokenData(ctx context.Context, tenant, token, host string, tenantURLs map[Tenant]string) (*users.User, error) { +func fetchTokenData(ctx context.Context, tenant, token, host string, tenantURLs map[Tenant]string) (*users.User, error) { tok, err := server.Auth(ctx).ParseToken(token, false) if err != nil { return nil, errors.Wrapf(err, "invalid token passed") diff --git a/kyc/verification_scenarios/contract.go b/kyc/verification_scenarios/contract.go index e9ad52b8..77e45b2d 100644 --- a/kyc/verification_scenarios/contract.go +++ b/kyc/verification_scenarios/contract.go @@ -32,6 +32,8 @@ const ( CoinDistributionScenarioSignUpSauces TenantScenario = "signup_sauces" CoinDistributionScenarioSignUpDoctorx TenantScenario = "signup_doctorx" CoinDistributionScenarioSignUpTokero TenantScenario = "signup_tokero" + + singUpPrefix = "signup" ) // . @@ -43,12 +45,12 @@ var ( type ( Tenant string - Token string Scenario string - TenantScenario string + Token = string + TenantScenario = string Repository interface { VerifyScenarios(ctx context.Context, metadata *VerificationMetadata) error - GetPendingVerificationScenarios(ctx context.Context, userID string) ([]*Scenario, error) + GetPendingVerificationScenarios(ctx context.Context, userID string) ([]Scenario, error) } UserRepository interface { io.Closer @@ -101,9 +103,10 @@ type ( host string } config struct { - TenantURLs map[string]string `yaml:"tenantURLs" mapstructure:"tenantURLs"` //nolint:tagliatelle // . - kycConfigJSON1 *atomic.Pointer[social.KycConfigJSON] - Tenant string `yaml:"tenant" mapstructure:"tenant"` - ConfigJSONURL1 string `yaml:"config-json-url1" mapstructure:"config-json-url1"` //nolint:tagliatelle // . + TenantURLs map[string]string `yaml:"tenantURLs" mapstructure:"tenantURLs"` //nolint:tagliatelle // . + kycConfigJSON1 *atomic.Pointer[social.KycConfigJSON] + Tenant string `yaml:"tenant" mapstructure:"tenant"` + ConfigJSONURL1 string `yaml:"config-json-url1" mapstructure:"config-json-url1"` //nolint:tagliatelle // . + MandatoryScenarios []Scenario `yaml:"mandatoryScenarios" mapstructure:"mandatoryScenarios"` //nolint:tagliatelle // . } ) diff --git a/kyc/verification_scenarios/verification_scenarios.go b/kyc/verification_scenarios/verification_scenarios.go index 47c5cbb2..01a0a046 100644 --- a/kyc/verification_scenarios/verification_scenarios.go +++ b/kyc/verification_scenarios/verification_scenarios.go @@ -51,11 +51,10 @@ func (r *repository) VerifyScenarios(ctx context.Context, metadata *Verification if err != nil { return errors.Wrapf(err, "failed to get user by id: %v", metadata.UserID) } - completedSantaTasks, err := r.getCompletedSantaTasks(ctx, usr.ID) + pendingScenarios, err := r.getPendingScenarios(ctx, usr.User) if err != nil { - return errors.Wrapf(err, "failed to getCompletedSantaTasks for userID: %v", usr.ID) + return errors.Wrapf(err, "can't call getPendingScenarios for %v", usr.ID) } - pendingScenarios := r.getPendingScenarios(usr.User, completedSantaTasks) if len(pendingScenarios) == 0 || !isScenarioPending(pendingScenarios, string(metadata.ScenarioEnum)) { return errors.Wrapf(ErrNoPendingScenarios, "no pending scenarios for user: %v", metadata.UserID) } @@ -71,59 +70,101 @@ func (r *repository) VerifyScenarios(ctx context.Context, metadata *Verification case CoinDistributionScenarioTelegram: case CoinDistributionScenarioSignUpTenants: skippedTokenCount := 0 - linkedUserIDs := make(map[linking.Tenant]users.UserID, 0) + tenantTokens := make(map[linking.Tenant]linking.Token, len(metadata.TenantTokens)) for tenantScenario, token := range metadata.TenantTokens { - if !isScenarioPending(pendingScenarios, string(tenantScenario)) { + if !isScenarioPending(pendingScenarios, tenantScenario) { skippedTokenCount++ continue } - splitted := strings.Split(string(tenantScenario), "_") - tenantUsr, fErr := linking.FetchTokenData(ctx, splitted[1], string(token), r.host, r.cfg.TenantURLs) - if fErr != nil { - if errors.Is(fErr, linking.ErrRemoteUserNotFound) { - return errors.Wrapf(linking.ErrNotOwnRemoteUser, "foreign token of userID:%v for the tenant: %v", metadata.UserID, tenantScenario) - } - - return errors.Wrapf(fErr, "failed to fetch remote user data for %v", metadata.UserID) - } - if tenantUsr.CreatedAt == nil || tenantUsr.ReferredBy == "" || tenantUsr.Username == "" { - return errors.Wrapf(linking.ErrNotOwnRemoteUser, "foreign token of userID:%v for the tenant: %v", metadata.UserID, tenantScenario) - } - userIDScenarioMap[tenantScenario] = tenantUsr.ID - linkedUserIDs[splitted[1]] = tenantUsr.ID + splitted := strings.Split(tenantScenario, "_") // Here we have: signup_. + tenantTokens[splitted[1]] = token } if skippedTokenCount == len(metadata.TenantTokens) { return errors.Wrapf(ErrWrongTenantTokens, "no pending tenant tokens for userID:%v", metadata.UserID) } - if sErr := r.linkerRepo.StoreLinkedAccounts(ctx, now, usr.ID, "", linkedUserIDs); sErr != nil { - return errors.Wrap(sErr, "failed to store linked accounts") + var links linking.LinkedProfiles + links, _, err = r.linkerRepo.Verify(ctx, now, usr.ID, tenantTokens) + if err != nil { + if errors.Is(err, linking.ErrRemoteUserNotFound) { + return errors.Wrapf(linking.ErrNotOwnRemoteUser, "foreign token of userID:%v", metadata.UserID) + } + + return errors.Wrapf(err, "failed to verify linking user for userID:%v", metadata.UserID) + } + for tenant, linkedUser := range links { + for inputTenant := range tenantTokens { + if inputTenant == tenant { + userIDScenarioMap[singUpPrefix+"_"+tenant] = linkedUser + } + } } } - return errors.Wrapf(r.setCompletedDistributionScenario(ctx, usr.User, metadata.ScenarioEnum, userIDScenarioMap), + return errors.Wrapf(r.setCompletedDistributionScenario(ctx, usr.User, metadata.ScenarioEnum, userIDScenarioMap, pendingScenarios), "failed to setCompletedDistributionScenario for userID:%v", metadata.UserID) } -func (r *repository) GetPendingVerificationScenarios(ctx context.Context, userID string) ([]*Scenario, error) { +func (r *repository) GetPendingVerificationScenarios(ctx context.Context, userID string) ([]Scenario, error) { usr, err := r.userRepo.GetUserByID(ctx, userID) if err != nil { return nil, errors.Wrapf(err, "failed to get user by id: %v", userID) } - completedSantaTasks, err := r.getCompletedSantaTasks(ctx, usr.ID) + pendingScenarios, err := r.getPendingScenarios(ctx, usr.User) if err != nil { - return nil, errors.Wrapf(err, "failed to getCompletedSantaTasks for userID: %v", usr.ID) + return nil, errors.Wrapf(err, "can't call getPendingScenarios for %v", usr.ID) } - pendingScenarios := r.getPendingScenarios(usr.User, completedSantaTasks) sort.SliceStable(pendingScenarios, func(i, j int) bool { - return scenarioOrder[*pendingScenarios[i]] < scenarioOrder[*pendingScenarios[j]] + return scenarioOrder[pendingScenarios[i]] < scenarioOrder[pendingScenarios[j]] }) return pendingScenarios, nil } +func (r *repository) getPendingScenarios(ctx context.Context, usr *users.User) (scenarios []Scenario, err error) { + if r.isMandatoryScenariosEnabled() { + if isDistributionScenariosVerified(usr) { + return []Scenario{}, nil + } + + return r.getMandatoryScenarios(usr), nil + } + completedSantaTasks, err := r.getCompletedSantaTasks(ctx, usr.ID) + if err != nil { + return nil, errors.Wrapf(err, "failed to getCompletedSantaTasks for userID: %v", usr.ID) + } + + return r.getUsualPendingScenarios(usr, completedSantaTasks), nil +} + +func isDistributionScenariosVerified(usr *users.User) bool { + return usr.DistributionScenariosVerified != nil && *usr.DistributionScenariosVerified +} + +func (r *repository) getMandatoryScenarios(usr *users.User) []Scenario { + if usr.DistributionScenariosCompleted == nil { + return r.cfg.MandatoryScenarios + } + scenarios := make([]Scenario, 0) + for _, mandatoryScenario := range r.cfg.MandatoryScenarios { + found := false + for _, completedScenario := range *usr.DistributionScenariosCompleted { + if completedScenario == string(mandatoryScenario) { + found = true + + break + } + } + if !found { + scenarios = append(scenarios, mandatoryScenario) + } + } + + return scenarios +} + //nolint:funlen,gocognit,gocyclo,revive,cyclop // . -func (r *repository) getPendingScenarios(usr *users.User, completedSantaTasks []*tasks.Task) []*Scenario { +func (r *repository) getUsualPendingScenarios(usr *users.User, completedSantaTasks []*tasks.Task) []Scenario { var ( joinBulllishCMCTaskCompleted, joinIONCMCTaskCompleted, joinWatchlistCMCTaskCompleted, cmcScenarioCompleted = false, false, false, false joinTwitterTaskCompleted, twitterScenarioCompleted = false, false @@ -149,12 +190,12 @@ func (r *repository) getPendingScenarios(usr *users.User, completedSantaTasks [] default: splitted := strings.Split(completedScenario, "_") if splitted[0] == singUpPrefix { - tenantsScenariosCompleted[TenantScenario(completedScenario)] = true + tenantsScenariosCompleted[completedScenario] = true } } } } - scenarios := make([]*Scenario, 0) + scenarios := make([]Scenario, 0) for _, task := range completedSantaTasks { switch task.Type { //nolint:exhaustive // We handle only tasks related to distribution verification. case tasks.JoinBullishCMCType: @@ -171,33 +212,33 @@ func (r *repository) getPendingScenarios(usr *users.User, completedSantaTasks [] if splitted := strings.Split(string(task.Type), "_"); len(splitted) > 1 && splitted[0] == singUpPrefix { if completed, ok := tenantsScenariosCompleted[TenantScenario(task.Type)]; !ok || !completed { scenario := Scenario(task.Type) - scenarios = append(scenarios, &scenario) + scenarios = append(scenarios, scenario) } } } } if joinBulllishCMCTaskCompleted && joinIONCMCTaskCompleted && joinWatchlistCMCTaskCompleted && !cmcScenarioCompleted { val := CoinDistributionScenarioCmc - scenarios = append(scenarios, &val) + scenarios = append(scenarios, val) } if joinTwitterTaskCompleted && !twitterScenarioCompleted { val := CoinDistributionScenarioTwitter - scenarios = append(scenarios, &val) + scenarios = append(scenarios, val) } if joinTelegramTaskCompleted && !telegramScenarioCompleted { val := CoinDistributionScenarioTelegram - scenarios = append(scenarios, &val) + scenarios = append(scenarios, val) } return scenarios } -func isScenarioPending(pendingScenarios []*Scenario, scenario string) bool { +func isScenarioPending(pendingScenarios []Scenario, scenario string) bool { if scenario == string(CoinDistributionScenarioSignUpTenants) { return true } for _, pending := range pendingScenarios { - if string(*pending) == scenario { + if string(pending) == scenario { return true } } @@ -205,8 +246,9 @@ func isScenarioPending(pendingScenarios []*Scenario, scenario string) bool { return false } +//nolint:funlen // . func (r *repository) setCompletedDistributionScenario( - ctx context.Context, usr *users.User, scenario Scenario, userIDMap map[TenantScenario]users.UserID, + ctx context.Context, usr *users.User, scenario Scenario, userIDMap map[TenantScenario]users.UserID, pendingTasks []Scenario, ) error { var lenScenarios int if usr.DistributionScenariosCompleted != nil { @@ -220,12 +262,24 @@ func (r *repository) setCompletedDistributionScenario( scenarios = append(scenarios, string(scenario)) } else { for tenant := range userIDMap { - scenarios = append(scenarios, string(tenant)) + scenarios = append(scenarios, tenant) } } updUsr := new(users.User) updUsr.ID = usr.ID updUsr.DistributionScenariosCompleted = &scenarios + completedCount := 0 + for _, completed := range scenarios { + for _, pending := range pendingTasks { + if completed == string(pending) { + completedCount++ + } + } + } + if completedCount == len(pendingTasks) { + trueVal := true + updUsr.DistributionScenariosVerified = &trueVal + } _, err := r.userRepo.ModifyUser(ctx, updUsr, nil) return errors.Wrapf(err, "failed to set completed distribution scenarios:%v", scenarios) @@ -423,3 +477,7 @@ func (r *repository) syncKYCConfigJSON1(ctx context.Context) error { return nil } } + +func (r *repository) isMandatoryScenariosEnabled() bool { + return len(r.cfg.MandatoryScenarios) > 0 +} diff --git a/kyc/verification_scenarios/verification_scenarios_test.go b/kyc/verification_scenarios/verification_scenarios_test.go index 28841a3c..8852a970 100644 --- a/kyc/verification_scenarios/verification_scenarios_test.go +++ b/kyc/verification_scenarios/verification_scenarios_test.go @@ -3,6 +3,7 @@ package verificationscenarios import ( + "context" "testing" "github.com/stretchr/testify/require" @@ -104,7 +105,8 @@ func TestGetPendingKYCVerificationScenarios(t *testing.T) { } } repo := &repository{} - pendingScenarios := repo.getPendingScenarios(usr, tsks) + pendingScenarios, err := repo.getPendingScenarios(context.TODO(), usr) + require.NoError(t, err) require.NotNil(t, pendingScenarios) require.Len(t, pendingScenarios, len(tt.expectedPendingScenarios)) for _, expectedScenario := range tt.expectedPendingScenarios { @@ -115,35 +117,33 @@ func TestGetPendingKYCVerificationScenarios(t *testing.T) { } func TestIsScenarioPending(t *testing.T) { - cmcScenario := CoinDistributionScenarioCmc - signUpTenantsScenario := CoinDistributionScenarioSignUpTenants tests := []struct { name string - pendingScenarios []*Scenario + pendingScenarios []Scenario scenario Scenario expectedIsPending bool }{ { name: "Scenario is pending", - pendingScenarios: []*Scenario{&cmcScenario, &signUpTenantsScenario}, + pendingScenarios: []Scenario{CoinDistributionScenarioCmc, CoinDistributionScenarioSignUpTenants}, scenario: CoinDistributionScenarioCmc, expectedIsPending: true, }, { name: "Scenario is not pending", - pendingScenarios: []*Scenario{&cmcScenario}, + pendingScenarios: []Scenario{CoinDistributionScenarioCmc}, scenario: CoinDistributionScenarioTelegram, expectedIsPending: false, }, { name: "Scenario is CoinDistributionScenarioSignUpTenants, we return true to check tenants tokens later", - pendingScenarios: []*Scenario{&cmcScenario}, + pendingScenarios: []Scenario{CoinDistributionScenarioCmc}, scenario: CoinDistributionScenarioSignUpTenants, expectedIsPending: true, }, { name: "No pending scenarios", - pendingScenarios: []*Scenario{}, + pendingScenarios: []Scenario{}, scenario: CoinDistributionScenarioCmc, expectedIsPending: false, }, diff --git a/users/DDL.sql b/users/DDL.sql index bd54f3ad..8dc411c8 100644 --- a/users/DDL.sql +++ b/users/DDL.sql @@ -14,6 +14,7 @@ CREATE TABLE IF NOT EXISTS users ( random_referred_by BOOLEAN NOT NULL DEFAULT FALSE, verified BOOLEAN NOT NULL DEFAULT FALSE, t1_referrals_sharing_enabled BOOLEAN, + distribution_scenarios_verified BOOLEAN NOT NULL DEFAULT FALSE, claimed_by_third_party text, client_data text, hidden_profile_elements text[], @@ -121,6 +122,7 @@ ALTER TABLE users ADD COLUMN IF NOT EXISTS kyc_steps_last_updated_at timestamp[] ALTER TABLE users ADD COLUMN IF NOT EXISTS kyc_steps_created_at timestamp[]; ALTER TABLE users ADD COLUMN IF NOT EXISTS telegram_user_id text; ALTER TABLE users ADD COLUMN IF NOT EXISTS distribution_scenarios_completed text[]; +ALTER TABLE users ADD COLUMN IF NOT EXISTS distribution_scenarios_verified BOOLEAN NOT NULL DEFAULT FALSE; DO $$ BEGIN if NOT exists (select constraint_name from information_schema.table_constraints where table_name = 'users' and constraint_name = 'users_telegram_user_id_key') then ALTER TABLE users ADD CONSTRAINT users_telegram_user_id_key UNIQUE (telegram_user_id); diff --git a/users/contract.go b/users/contract.go index e26c23bc..2b2fd186 100644 --- a/users/contract.go +++ b/users/contract.go @@ -113,6 +113,7 @@ type ( HiddenProfileElements *Enum[HiddenProfileElement] `json:"hiddenProfileElements,omitempty" swaggertype:"array,string" example:"level" enums:"globalRank,referralCount,level,role,badges" db:"hidden_profile_elements"` //nolint:lll // . DistributionScenariosCompleted *Enum[string] `json:"distributionScenariosCompleted,omitempty" swaggerignore:"true" db:"distribution_scenarios_completed" enums:"join_cmc,join_twitter,join_telegram,signup_sunwaves,signup_doctorx,signup_callfluent,signup_sealsend,signup_sauces,signup_tokero"` //nolint:lll // . RandomReferredBy *bool `json:"randomReferredBy,omitempty" example:"true" swaggerignore:"true" db:"random_referred_by"` + DistributionScenariosVerified *bool `json:"distributionScenariosVerified,omitempty" example:"true" swaggerignore:"true" db:"distribution_scenarios_verified"` //nolint:lll // . Verified *bool `json:"verified" example:"true" db:"verified"` QuizCompleted *bool `json:"-" db:"quiz_completed"` T1ReferralsSharingEnabled *bool `json:"t1ReferralsSharingEnabled" example:"true" db:"t1_referrals_sharing_enabled"` diff --git a/users/users_modify.go b/users/users_modify.go index 23121ee0..0c6dae6e 100644 --- a/users/users_modify.go +++ b/users/users_modify.go @@ -339,6 +339,11 @@ func (u *User) genSQLUpdate(ctx context.Context, agendaUserIDs []UserID) (sql st sql += fmt.Sprintf(", DISTRIBUTION_SCENARIOS_COMPLETED = $%v", nextIndex) nextIndex++ } + if u.DistributionScenariosVerified != nil { + params = append(params, u.DistributionScenariosVerified) + sql += fmt.Sprintf(", DISTRIBUTION_SCENARIOS_VERIFIED = $%v", nextIndex) + nextIndex++ + } sql += " WHERE ID = $1"