diff --git a/application.yaml b/application.yaml index 8299327a..0645117d 100644 --- a/application.yaml +++ b/application.yaml @@ -127,6 +127,7 @@ auth/email-link/2: users: &users maxReferralsCount: 200 defaultReferralName: bogus + adoptionUrl: https://localhost:3443/v1r/tokenomics-statistics/adoption kyc: kyc-step1-reset-url: https://localhost:443/v1w/face-auth/ disableConsumer: false diff --git a/cmd/eskimo-hut/eskimo.go b/cmd/eskimo-hut/eskimo.go index 2f6c37ec..91418fc7 100644 --- a/cmd/eskimo-hut/eskimo.go +++ b/cmd/eskimo-hut/eskimo.go @@ -34,8 +34,9 @@ type ( Offset uint64 `form:"offset" example:"5"` } GetUserGrowthArg struct { - TZ string `form:"tz" example:"+4:30"` - Days uint64 `form:"days" example:"7"` + Authorization string `header:"Authorization" swaggerignore:"true" required:"true" example:"some token"` + TZ string `form:"tz" example:"+4:30"` + Days uint64 `form:"days" example:"7"` } GetReferralAcquisitionHistoryArg struct { UserID string `uri:"userId" required:"true" example:"did:ethr:0x4B73C58370AEfcEf86A6021afCDe5673511376B2"` @@ -247,6 +248,7 @@ func (s *service) GetUserGrowth( //nolint:gocritic,funlen // False negative. tz = t.Location() } } + ctx = users.ContextWithAuthorization(ctx, req.Data.Authorization) //nolint:revive // . result, err := s.usersProcessor.GetUserGrowth(ctx, req.Data.Days, tz) if err != nil { return nil, server.Unexpected(errors.Wrapf(err, "failed to get user growth stats for: %#v", req.Data)) diff --git a/go.mod b/go.mod index 05ee0dd2..56c770e9 100644 --- a/go.mod +++ b/go.mod @@ -140,7 +140,7 @@ require ( github.com/quic-go/qpack v0.5.1 // indirect github.com/quic-go/quic-go v0.47.0 // indirect github.com/refraction-networking/utls v1.6.7 // indirect - github.com/rogpeppe/go-internal v1.12.0 // indirect + github.com/rogpeppe/go-internal v1.13.1 // indirect github.com/rs/zerolog v1.33.0 // indirect github.com/sagikazarmark/locafero v0.6.0 // indirect github.com/sagikazarmark/slog-shim v0.1.0 // indirect @@ -166,11 +166,11 @@ require ( go.opentelemetry.io/contrib/detectors/gcp v1.30.0 // indirect go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.55.0 // indirect go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.55.0 // indirect - go.opentelemetry.io/otel v1.30.0 // indirect - go.opentelemetry.io/otel/metric v1.30.0 // indirect - go.opentelemetry.io/otel/sdk v1.30.0 // indirect - go.opentelemetry.io/otel/sdk/metric v1.30.0 // indirect - go.opentelemetry.io/otel/trace v1.30.0 // indirect + go.opentelemetry.io/otel v1.31.0 // indirect + go.opentelemetry.io/otel/metric v1.31.0 // indirect + go.opentelemetry.io/otel/sdk v1.31.0 // indirect + go.opentelemetry.io/otel/sdk/metric v1.31.0 // indirect + go.opentelemetry.io/otel/trace v1.31.0 // indirect go.uber.org/atomic v1.11.0 // indirect go.uber.org/mock v0.4.0 // indirect go.uber.org/multierr v1.11.0 // indirect diff --git a/go.sum b/go.sum index 8b01547d..dd814373 100644 --- a/go.sum +++ b/go.sum @@ -364,8 +364,8 @@ github.com/redis/go-redis/v9 v9.6.1 h1:HHDteefn6ZkTtY5fGUE8tj8uy85AHk6zP7CpzIAM0 github.com/redis/go-redis/v9 v9.6.1/go.mod h1:0C0c6ycQsdpVNQpxb1njEQIqkx5UcsM8FJCQLgE9+RA= github.com/refraction-networking/utls v1.6.7 h1:zVJ7sP1dJx/WtVuITug3qYUq034cDq9B2MR1K67ULZM= github.com/refraction-networking/utls v1.6.7/go.mod h1:BC3O4vQzye5hqpmDTWUqi4P5DDhzJfkV1tdqtawQIH0= -github.com/rogpeppe/go-internal v1.12.0 h1:exVL4IDcn6na9z1rAb56Vxr+CgyK3nn3O+epU5NdKM8= -github.com/rogpeppe/go-internal v1.12.0/go.mod h1:E+RYuTGaKKdloAfM02xzb0FW3Paa99yedzYV+kq4uf4= +github.com/rogpeppe/go-internal v1.13.1 h1:KvO1DLK/DRN07sQ1LQKScxyZJuNnedQ5/wKSR38lUII= +github.com/rogpeppe/go-internal v1.13.1/go.mod h1:uMEvuHeurkdAXX61udpOXGD/AzZDWNMNyH2VO9fmH0o= github.com/rs/xid v1.5.0/go.mod h1:trrq9SKmegXys3aeAKXMUTdJsYXVwGY3RLcfgqegfbg= github.com/rs/zerolog v1.33.0 h1:1cU2KZkvPxNyfgEmhHAz/1A9Bz+llsdYzklWFzgp0r8= github.com/rs/zerolog v1.33.0/go.mod h1:/7mN4D5sKwJLZQ2b/znpjC3/GQWY/xaDXUM0kKWRHss= @@ -444,16 +444,16 @@ go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.5 go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.55.0/go.mod h1:LqaApwGx/oUmzsbqxkzuBvyoPpkxk3JQWnqfVrJ3wCA= go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.55.0 h1:ZIg3ZT/aQ7AfKqdwp7ECpOK6vHqquXXuyTjIO8ZdmPs= go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.55.0/go.mod h1:DQAwmETtZV00skUwgD6+0U89g80NKsJE3DCKeLLPQMI= -go.opentelemetry.io/otel v1.30.0 h1:F2t8sK4qf1fAmY9ua4ohFS/K+FUuOPemHUIXHtktrts= -go.opentelemetry.io/otel v1.30.0/go.mod h1:tFw4Br9b7fOS+uEao81PJjVMjW/5fvNCbpsDIXqP0pc= -go.opentelemetry.io/otel/metric v1.30.0 h1:4xNulvn9gjzo4hjg+wzIKG7iNFEaBMX00Qd4QIZs7+w= -go.opentelemetry.io/otel/metric v1.30.0/go.mod h1:aXTfST94tswhWEb+5QjlSqG+cZlmyXy/u8jFpor3WqQ= -go.opentelemetry.io/otel/sdk v1.30.0 h1:cHdik6irO49R5IysVhdn8oaiR9m8XluDaJAs4DfOrYE= -go.opentelemetry.io/otel/sdk v1.30.0/go.mod h1:p14X4Ok8S+sygzblytT1nqG98QG2KYKv++HE0LY/mhg= -go.opentelemetry.io/otel/sdk/metric v1.30.0 h1:QJLT8Pe11jyHBHfSAgYH7kEmT24eX792jZO1bo4BXkM= -go.opentelemetry.io/otel/sdk/metric v1.30.0/go.mod h1:waS6P3YqFNzeP01kuo/MBBYqaoBJl7efRQHOaydhy1Y= -go.opentelemetry.io/otel/trace v1.30.0 h1:7UBkkYzeg3C7kQX8VAidWh2biiQbtAKjyIML8dQ9wmc= -go.opentelemetry.io/otel/trace v1.30.0/go.mod h1:5EyKqTzzmyqB9bwtCCq6pDLktPK6fmGf/Dph+8VI02o= +go.opentelemetry.io/otel v1.31.0 h1:NsJcKPIW0D0H3NgzPDHmo0WW6SptzPdqg/L1zsIm2hY= +go.opentelemetry.io/otel v1.31.0/go.mod h1:O0C14Yl9FgkjqcCZAsE053C13OaddMYr/hz6clDkEJE= +go.opentelemetry.io/otel/metric v1.31.0 h1:FSErL0ATQAmYHUIzSezZibnyVlft1ybhy4ozRPcF2fE= +go.opentelemetry.io/otel/metric v1.31.0/go.mod h1:C3dEloVbLuYoX41KpmAhOqNriGbA+qqH6PQ5E5mUfnY= +go.opentelemetry.io/otel/sdk v1.31.0 h1:xLY3abVHYZ5HSfOg3l2E5LUj2Cwva5Y7yGxnSW9H5Gk= +go.opentelemetry.io/otel/sdk v1.31.0/go.mod h1:TfRbMdhvxIIr/B2N2LQW2S5v9m3gOQ/08KsbbO5BPT0= +go.opentelemetry.io/otel/sdk/metric v1.31.0 h1:i9hxxLJF/9kkvfHppyLL55aW7iIJz4JjxTeYusH7zMc= +go.opentelemetry.io/otel/sdk/metric v1.31.0/go.mod h1:CRInTMVvNhUKgSAMbKyTMxqOBC0zgyxzW55lZzX43Y8= +go.opentelemetry.io/otel/trace v1.31.0 h1:ffjsj1aRouKewfr85U2aGagJ46+MvodynlQ1HYdmJys= +go.opentelemetry.io/otel/trace v1.31.0/go.mod h1:TXZkRk7SM2ZQLtR6eoAWQFIHPvzQ06FJAsO1tJg480A= go.uber.org/atomic v1.11.0 h1:ZvwS0R+56ePWxUNi+Atn9dWONBPp/AUETXlHW0DxSjE= go.uber.org/atomic v1.11.0/go.mod h1:LUxbIzbOniOlMKjJjyPfpl4v+PKK2cNJn91OQbhoJI0= go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= diff --git a/users/contract.go b/users/contract.go index 200e8686..9d7a6724 100644 --- a/users/contract.go +++ b/users/contract.go @@ -299,6 +299,7 @@ type ( } // | config holds the configuration of this package mounted from `application.yaml`. config struct { + AdoptionURL string `yaml:"adoptionUrl" mapstructure:"adoptionUrl"` DefaultReferralName string `yaml:"defaultReferralName"` messagebroker.Config `mapstructure:",squash"` //nolint:tagliatelle // Nope. GlobalAggregationInterval struct { diff --git a/users/user_growth.go b/users/user_growth.go index 6d5d0273..be07de5c 100644 --- a/users/user_growth.go +++ b/users/user_growth.go @@ -6,14 +6,17 @@ import ( "context" "fmt" "math" + "net/http" "strings" stdlibtime "time" "github.com/goccy/go-json" + "github.com/imroc/req/v3" "github.com/pkg/errors" messagebroker "github.com/ice-blockchain/wintr/connectors/message_broker" storage "github.com/ice-blockchain/wintr/connectors/storage/v2" + "github.com/ice-blockchain/wintr/log" "github.com/ice-blockchain/wintr/time" ) @@ -27,8 +30,52 @@ func (r *repository) GetUserGrowth(ctx context.Context, days uint64, tz *stdlibt if err != nil { return nil, errors.Wrapf(err, "failed to getGlobalValues for keys:%#v", keys) } + totalActiveUsers, err := r.getAdoptionTotalActiveUsersValue(ctx) + if err != nil { + return nil, errors.Wrap(err, "failed to getAdoptionTotalActiveUsersValue") + } - return r.aggregateGlobalValuesToGrowth(days, now, values, keys, tz), nil + return r.aggregateGlobalValuesToGrowth(days, now, values, keys, tz, totalActiveUsers), nil +} + +//nolint:funlen // . +func (r *repository) getAdoptionTotalActiveUsersValue(ctx context.Context) (totalActiveUsers uint64, err error) { + resp, err := req. + SetContext(ctx). + SetRetryCount(25). //nolint:gomnd,mnd // . + SetRetryBackoffInterval(10*stdlibtime.Millisecond, 1*stdlibtime.Second). //nolint:gomnd,mnd // . + SetRetryHook(func(resp *req.Response, err error) { + if err != nil { + log.Error(errors.Wrap(err, "failed to fetch adoption, retrying...")) //nolint:revive // . + } else { + log.Error(errors.Errorf("failed to fetch users adoption with status code:%v, retrying...", resp.GetStatusCode())) //nolint:revive // . + } + }). + SetRetryCondition(func(resp *req.Response, err error) bool { + return err != nil || resp.GetStatusCode() != http.StatusOK + }). + SetHeader("Accept", "application/json"). + SetHeader("Accept", "application/json"). + SetHeader("Authorization", authorization(ctx)). + SetHeader("Cache-Control", "no-cache, no-store, must-revalidate"). + SetHeader("Pragma", "no-cache"). + SetHeader("Expires", "0"). + Get(r.cfg.AdoptionURL) + if err != nil { + return 0, errors.Wrapf(err, "failed to get fetch `%v`", r.cfg.AdoptionURL) + } + data, err2 := resp.ToBytes() + if err2 != nil { + return 0, errors.Wrapf(err2, "failed to read body of `%v`", r.cfg.AdoptionURL) + } + var adoption struct { + TotalActiveUsers uint64 `json:"totalActiveUsers" example:"11"` + } + if err = json.UnmarshalContext(ctx, data, &adoption); err != nil { + return 0, errors.Wrapf(err, "failed to unmarshal into %#v, data: %v", adoption, string(data)) + } + + return adoption.TotalActiveUsers, nil } func (r *repository) generateUserGrowthKeys(now *time.Time, days uint64) []string { @@ -49,11 +96,11 @@ func (r *repository) aggregateGlobalValuesToGrowth( values []*GlobalUnsigned, keys []string, tz *stdlibtime.Location, + totalActiveUsers uint64, ) *UserGrowthStatistics { nsSinceParentIntervalZeroValue := r.cfg.nanosSinceGlobalAggregationIntervalParentZeroValue(now) stats := make([]*UserCountTimeSeriesDataPoint, days, days) //nolint:gosimple // . - var activeNow, activeMaxPerParent, dayIdx uint64 - nowKey := r.totalActiveUsersGlobalChildKey(now.Time) + var activeMaxPerParent, dayIdx uint64 nowInTZ := time.New(now.In(tz)) for ix, key := range keys { if ix == 0 { @@ -96,13 +143,8 @@ func (r *repository) aggregateGlobalValuesToGrowth( } activeMaxPerParent = 0 dayIdx++ - } else { - if key == nowKey { - activeNow = val - } - if val > activeMaxPerParent { - activeMaxPerParent = val - } + } else if val > activeMaxPerParent { + activeMaxPerParent = val } } stats[dayIdx-1].UserCount.Active = activeMaxPerParent @@ -111,7 +153,7 @@ func (r *repository) aggregateGlobalValuesToGrowth( return &UserGrowthStatistics{ TimeSeries: stats, UserCount: UserCount{ - Active: activeNow, + Active: totalActiveUsers, Total: values[0].Value, }, } @@ -307,3 +349,9 @@ func NanosSinceMidnight(now *time.Time) stdlibtime.Duration { stdlibtime.Duration(now.Minute())*stdlibtime.Minute + stdlibtime.Duration(now.Hour())*stdlibtime.Hour } + +func authorization(ctx context.Context) (authorization string) { + authorization, _ = ctx.Value(authorizationCtxValueKey).(string) //nolint:errcheck // Not needed. + + return +}