From fc1675d3c9a3ba98a4bb45467b4022133214672c Mon Sep 17 00:00:00 2001 From: clD11 <23483715+clD11@users.noreply.github.com> Date: Tue, 29 Oct 2024 18:22:12 +0000 Subject: [PATCH 1/6] refactor: disable tos (#2684) * refactor: disable tos * test: remove tos assertion from test --- services/rewards/controllers_test.go | 1 - services/rewards/service.go | 1 - 2 files changed, 2 deletions(-) diff --git a/services/rewards/controllers_test.go b/services/rewards/controllers_test.go index 1275a2234..049bf5d65 100644 --- a/services/rewards/controllers_test.go +++ b/services/rewards/controllers_test.go @@ -114,7 +114,6 @@ func TestGetParametersController(t *testing.T) { assert.Equal(t, "processing", params.PayoutStatus.Uphold) assert.ElementsMatch(t, []float64{3, 5, 7, 10, 20}, params.AutoContribute.Choices) assert.Equal(t, float64(10), params.BATRate) - assert.Equal(t, 1, params.TOSVersion) } func setupRouter(s *Service) *chi.Mux { diff --git a/services/rewards/service.go b/services/rewards/service.go index 9c49741a4..d032880ad 100644 --- a/services/rewards/service.go +++ b/services/rewards/service.go @@ -153,7 +153,6 @@ func (s *Service) GetParameters(ctx context.Context, currency *BaseCurrency) (*P DefaultTipChoices: getTipChoices(ctx), DefaultMonthlyChoices: getMonthlyChoices(ctx), }, - TOSVersion: s.cfg.TOSVersion, } vbatDeadline, ok := ctx.Value(appctx.ParametersVBATDeadlineCTXKey).(time.Time) From 81d63db3201fb6a82a1cc93463899c1207bea09f Mon Sep 17 00:00:00 2001 From: clD11 <23483715+clD11@users.noreply.github.com> Date: Wed, 30 Oct 2024 11:45:28 +0000 Subject: [PATCH 2/6] feat: add new get cards endpoint (#2683) --- services/rewards/cmd/rest_run.go | 49 ++++++-- services/rewards/controllers_test.go | 2 +- services/rewards/handler/handler.go | 52 ++++++++ services/rewards/handler/handler_test.go | 144 +++++++++++++++++++++++ services/rewards/model/model.go | 7 ++ services/rewards/service.go | 36 +++++- services/rewards/service_test.go | 103 ++++++++++++++++ 7 files changed, 375 insertions(+), 18 deletions(-) create mode 100644 services/rewards/handler/handler.go create mode 100644 services/rewards/handler/handler_test.go create mode 100644 services/rewards/model/model.go create mode 100644 services/rewards/service_test.go diff --git a/services/rewards/cmd/rest_run.go b/services/rewards/cmd/rest_run.go index 0fba4538c..5e8a93670 100644 --- a/services/rewards/cmd/rest_run.go +++ b/services/rewards/cmd/rest_run.go @@ -10,15 +10,18 @@ import ( "strconv" "time" + "github.com/getsentry/sentry-go" + "github.com/go-chi/chi" + "github.com/spf13/cobra" + "github.com/spf13/viper" + cmdutils "github.com/brave-intl/bat-go/cmd" appctx "github.com/brave-intl/bat-go/libs/context" + "github.com/brave-intl/bat-go/libs/handlers" "github.com/brave-intl/bat-go/libs/middleware" "github.com/brave-intl/bat-go/services/cmd" "github.com/brave-intl/bat-go/services/rewards" - "github.com/getsentry/sentry-go" - "github.com/go-chi/chi" - "github.com/spf13/cobra" - "github.com/spf13/viper" + "github.com/brave-intl/bat-go/services/rewards/handler" ) // RestRun - Main entrypoint of the REST subcommand @@ -48,7 +51,6 @@ func RestRun(command *cobra.Command, args []string) { ctx = context.WithValue(ctx, appctx.ParametersVBATDeadlineCTXKey, viper.GetTime("vbat-deadline")) ctx = context.WithValue(ctx, appctx.ParametersTransitionCTXKey, viper.GetBool("transition")) - // parse default-monthly-choices and default-tip-choices var monthlyChoices []float64 if err := viper.UnmarshalKey("default-monthly-choices", &monthlyChoices); err != nil { @@ -75,8 +77,24 @@ func RestRun(command *cobra.Command, args []string) { lg.Fatal().Err(err).Msg("error retrieving rewards terms of service version") } - cfg := rewards.Config{ + // Get the bucket from the context and not os.Getenv so we don't diverge. GetParameters uses the context on + // each request and this will need to be refactored before we can remove it. + cardsBucket, ok := ctx.Value(appctx.ParametersMergeBucketCTXKey).(string) + if !ok { + lg.Fatal().Err(err).Msg("failed to get envar for cards bucket") + } + + cardsKey := "cards.json" + if ck := os.Getenv("CARDS-KEY"); ck != "" { + cardsKey = ck + } + + cfg := &rewards.Config{ TOSVersion: tosVersion, + Cards: &rewards.CardsConfig{ + Bucket: cardsBucket, + Key: cardsKey, + }, } s, err := rewards.InitService(ctx, cfg) @@ -86,12 +104,12 @@ func RestRun(command *cobra.Command, args []string) { lg.Info().Str("service", fmt.Sprintf("%+v", s)).Msg("initialized service") - // do rest endpoints r := cmd.SetupRouter(ctx) - r.Get("/v1/parameters", middleware.InstrumentHandler( - "GetParametersHandler", rewards.GetParametersHandler(s)).ServeHTTP) - // make sure exceptions go to sentry + r.Get("/v1/parameters", middleware.InstrumentHandler("GetParametersHandler", rewards.GetParametersHandler(s)).ServeHTTP) + + r.Mount("/v1/cards", newCardsRouter(s)) + defer sentry.Flush(time.Second * 2) go func() { @@ -102,7 +120,6 @@ func RestRun(command *cobra.Command, args []string) { } }() - // setup server, and run srv := http.Server{ Addr: viper.GetString("address"), Handler: chi.ServerBaseContext(ctx, r), @@ -115,3 +132,13 @@ func RestRun(command *cobra.Command, args []string) { lg.Fatal().Err(err).Msg("HTTP server start failed!") } } + +func newCardsRouter(svc *rewards.Service) chi.Router { + cardsRouter := chi.NewRouter() + + ch := handler.NewCardsHandler(svc) + + cardsRouter.Method(http.MethodGet, "/", middleware.InstrumentHandler("GetCards", handlers.AppHandler(ch.GetCardsHandler))) + + return cardsRouter +} diff --git a/services/rewards/controllers_test.go b/services/rewards/controllers_test.go index 049bf5d65..c0296ae27 100644 --- a/services/rewards/controllers_test.go +++ b/services/rewards/controllers_test.go @@ -84,7 +84,7 @@ func TestGetParametersController(t *testing.T) { }) s := &Service{ - cfg: Config{TOSVersion: 1}, + cfg: &Config{TOSVersion: 1}, ratios: mockRatios, s3Client: mockS3, cacheMu: new(sync.RWMutex), diff --git a/services/rewards/handler/handler.go b/services/rewards/handler/handler.go new file mode 100644 index 000000000..965739762 --- /dev/null +++ b/services/rewards/handler/handler.go @@ -0,0 +1,52 @@ +package handler + +import ( + "context" + "net/http" + + "github.com/brave-intl/bat-go/libs/handlers" + "github.com/brave-intl/bat-go/libs/logging" + "github.com/brave-intl/bat-go/services/rewards" + "github.com/brave-intl/bat-go/services/rewards/model" +) + +type cardService interface { + GetCardsAsBytes(ctx context.Context) (rewards.CardBytes, error) +} + +type CardsHandler struct { + cardSvc cardService +} + +func NewCardsHandler(cardSvc cardService) *CardsHandler { + return &CardsHandler{ + cardSvc: cardSvc, + } +} + +const errSomethingWentWrong model.Error = "something went wrong" + +func (c *CardsHandler) GetCardsHandler(w http.ResponseWriter, r *http.Request) *handlers.AppError { + ctx := r.Context() + + l := logging.Logger(ctx, "handler").With().Str("func", "GetCardsHandler").Logger() + + cards, err := c.cardSvc.GetCardsAsBytes(ctx) + if err != nil { + l.Err(err).Msg("failed to get cards as bytes") + + return handlers.WrapError(errSomethingWentWrong, errSomethingWentWrong.Error(), http.StatusInternalServerError) + } + + w.WriteHeader(http.StatusOK) + + w.Header().Set("Content-Type", "application/json") + + if _, err := w.Write(cards); err != nil { + l.Err(err).Msg("failed to write response") + + return handlers.WrapError(errSomethingWentWrong, errSomethingWentWrong.Error(), http.StatusInternalServerError) + } + + return nil +} diff --git a/services/rewards/handler/handler_test.go b/services/rewards/handler/handler_test.go new file mode 100644 index 000000000..787345124 --- /dev/null +++ b/services/rewards/handler/handler_test.go @@ -0,0 +1,144 @@ +package handler + +import ( + "context" + "encoding/json" + "net/http" + "net/http/httptest" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + "github.com/brave-intl/bat-go/libs/handlers" + "github.com/brave-intl/bat-go/services/rewards" + "github.com/brave-intl/bat-go/services/rewards/model" +) + +func TestService_GetCards(t *testing.T) { + type card struct { + Title string `json:"title"` + Description string `json:"description"` + URL string `json:"url"` + Thumbnail string `json:"thumbnail"` + } + + type cards struct { + CommunityCard []card `json:"community-card"` + MerchStoreCard []card `json:"merch-store-card"` + } + + type tcGiven struct { + cardSvc cardService + } + + type tcExpected struct { + code int + cards cards + err *handlers.AppError + } + + type testCase struct { + name string + given tcGiven + exp tcExpected + } + + tests := []testCase{ + { + name: "error_cards_as_bytes", + given: tcGiven{ + cardSvc: &mockCardService{ + fnGetCards: func(ctx context.Context) (rewards.CardBytes, error) { + return nil, model.Error("error") + }, + }, + }, + exp: tcExpected{ + code: http.StatusInternalServerError, + err: &handlers.AppError{ + Message: errSomethingWentWrong.Error(), + Code: http.StatusInternalServerError, + Cause: errSomethingWentWrong, + }, + }, + }, + + { + name: "success", + given: tcGiven{ + cardSvc: &mockCardService{ + fnGetCards: func(ctx context.Context) (rewards.CardBytes, error) { + cards := rewards.CardBytes(`{ "community-card": [{"title": "", "description": "", "url": "", "thumbnail": ""}], "merch-store-card": [{"title": "", "description": "", "url": "", "thumbnail": ""}] }`) + + return cards, nil + }, + }, + }, + exp: tcExpected{ + code: http.StatusOK, + cards: cards{ + CommunityCard: []card{ + { + Title: "", + Description: "", + URL: "", + Thumbnail: "", + }, + }, + MerchStoreCard: []card{ + { + Title: "", + Description: "", + URL: "", + Thumbnail: "", + }, + }, + }, + }, + }, + } + + for i := range tests { + tc := tests[i] + + t.Run(tc.name, func(t *testing.T) { + r := httptest.NewRequest(http.MethodGet, "/cards", nil) + + rw := httptest.NewRecorder() + + ch := NewCardsHandler(tc.given.cardSvc) + + actual := ch.GetCardsHandler(rw, r) + + if actual != nil { + actual.ServeHTTP(rw, r) + assert.Equal(t, tc.exp.code, rw.Code) + assert.Equal(t, tc.exp.err.Code, actual.Code) + assert.Equal(t, tc.exp.err.Cause, actual.Cause) + assert.Contains(t, actual.Message, tc.exp.err.Message) + return + } + + assert.Equal(t, tc.exp.code, rw.Code) + + var body cards + err := json.Unmarshal(rw.Body.Bytes(), &body) + require.NoError(t, err) + + assert.Equal(t, tc.exp.cards, body) + }) + } +} + +type mockCardService struct { + fnGetCards func(ctx context.Context) (rewards.CardBytes, error) +} + +func (m *mockCardService) GetCardsAsBytes(ctx context.Context) (rewards.CardBytes, error) { + if m.fnGetCards == nil { + return rewards.CardBytes{}, nil + } + + return m.fnGetCards(ctx) +} diff --git a/services/rewards/model/model.go b/services/rewards/model/model.go new file mode 100644 index 000000000..de2288f60 --- /dev/null +++ b/services/rewards/model/model.go @@ -0,0 +1,7 @@ +package model + +type Error string + +func (e Error) Error() string { + return string(e) +} diff --git a/services/rewards/service.go b/services/rewards/service.go index d032880ad..04f450f46 100644 --- a/services/rewards/service.go +++ b/services/rewards/service.go @@ -4,6 +4,7 @@ import ( "context" "errors" "fmt" + "io" "strings" "sync" "time" @@ -17,30 +18,39 @@ import ( srv "github.com/brave-intl/bat-go/libs/service" ) -// Config contains the rewards.Service configuration. +type s3Service interface { + GetObject(ctx context.Context, params *s3.GetObjectInput, optFns ...func(*s3.Options)) (*s3.GetObjectOutput, error) +} + +type CardsConfig struct { + Bucket string + Key string +} + type Config struct { TOSVersion int + Cards *CardsConfig } -// Service contains datastore type Service struct { - cfg Config + cfg *Config lastPollTime time.Time lastPayoutStatus *custodian.PayoutStatus lastCustodianRegions *custodian.Regions cacheMu *sync.RWMutex jobs []srv.Job ratios ratios.Client - s3Client appaws.S3GetObjectAPI + // Stop using the interface in lib and use s3Service. + s3Client appaws.S3GetObjectAPI + s3Svc s3Service } -// Jobs - Implement srv.JobService interface func (s *Service) Jobs() []srv.Job { return s.jobs } // InitService initializes a new instance of the rewards service. -func InitService(ctx context.Context, cfg Config) (*Service, error) { +func InitService(ctx context.Context, cfg *Config) (*Service, error) { ratiosCl, err := ratios.NewWithContext(ctx) if err != nil { return nil, fmt.Errorf("failed to initialize ratios client: %w", err) @@ -61,6 +71,8 @@ func InitService(ctx context.Context, cfg Config) (*Service, error) { jobs: []srv.Job{}, ratios: ratiosCl, s3Client: s3client, + + s3Svc: s3client, }, nil } @@ -167,3 +179,15 @@ func (s *Service) GetParameters(ctx context.Context, currency *BaseCurrency) (*P return params, nil } + +type CardBytes []byte + +func (s *Service) GetCardsAsBytes(ctx context.Context) (CardBytes, error) { + out, err := s.s3Svc.GetObject(ctx, &s3.GetObjectInput{Bucket: &s.cfg.Cards.Bucket, Key: &s.cfg.Cards.Key}) + if err != nil { + return nil, err + } + defer func() { _ = out.Body.Close() }() + + return io.ReadAll(out.Body) +} diff --git a/services/rewards/service_test.go b/services/rewards/service_test.go new file mode 100644 index 000000000..4249b130a --- /dev/null +++ b/services/rewards/service_test.go @@ -0,0 +1,103 @@ +package rewards + +import ( + "bytes" + "context" + "io" + "testing" + + "github.com/aws/aws-sdk-go-v2/service/s3" + "github.com/stretchr/testify/assert" + + "github.com/brave-intl/bat-go/services/rewards/model" +) + +func TestService_GetCards(t *testing.T) { + type tcGiven struct { + cfg *Config + s3Svc s3Service + } + + type tcExpected struct { + cards CardBytes + err error + } + + type testCase struct { + name string + given tcGiven + exp tcExpected + } + + tests := []testCase{ + { + name: "error_get_object", + given: tcGiven{ + cfg: &Config{ + Cards: &CardsConfig{}, + }, + s3Svc: &mockS3Service{ + fnGetObject: func(ctx context.Context, params *s3.GetObjectInput, optFns ...func(*s3.Options)) (*s3.GetObjectOutput, error) { + return nil, model.Error("error") + }, + }, + }, + exp: tcExpected{ + err: model.Error("error"), + }, + }, + + { + name: "success", + given: tcGiven{ + cfg: &Config{ + Cards: &CardsConfig{}, + }, + s3Svc: &mockS3Service{ + fnGetObject: func(ctx context.Context, params *s3.GetObjectInput, optFns ...func(*s3.Options)) (*s3.GetObjectOutput, error) { + cards := CardBytes(`{ "card": [{"title": "", "description": "", "url": "", "thumbnail": ""}] }`) + + out := &s3.GetObjectOutput{ + Body: io.NopCloser(bytes.NewReader(cards)), + } + + return out, nil + }, + }, + }, + exp: tcExpected{ + cards: CardBytes(`{ "card": [{"title": "", "description": "", "url": "", "thumbnail": ""}] }`), + }, + }, + } + + for i := range tests { + tc := tests[i] + + t.Run(tc.name, func(t *testing.T) { + s := &Service{ + cfg: tc.given.cfg, + s3Svc: tc.given.s3Svc, + } + + ctx := context.Background() + + actual, err := s.GetCardsAsBytes(ctx) + + assert.ErrorIs(t, err, tc.exp.err) + assert.Equal(t, tc.exp.cards, actual) + }) + } +} + +type mockS3Service struct { + fnGetObject func(ctx context.Context, params *s3.GetObjectInput, optFns ...func(*s3.Options)) (*s3.GetObjectOutput, error) +} + +func (m *mockS3Service) GetObject(ctx context.Context, params *s3.GetObjectInput, optFns ...func(*s3.Options)) (*s3.GetObjectOutput, error) { + if m.fnGetObject == nil { + return &s3.GetObjectOutput{}, nil + } + + return m.fnGetObject(ctx, params, optFns...) +} From 4477d199ad1d2b3e12c717b0e9dca15bf2037ac0 Mon Sep 17 00:00:00 2001 From: clD11 <23483715+clD11@users.noreply.github.com> Date: Thu, 31 Oct 2024 07:43:34 +0000 Subject: [PATCH 3/6] refactor: add read limit to get cards as bytes (#2695) --- services/rewards/service.go | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/services/rewards/service.go b/services/rewards/service.go index 04f450f46..2bfdf00b6 100644 --- a/services/rewards/service.go +++ b/services/rewards/service.go @@ -18,6 +18,10 @@ import ( srv "github.com/brave-intl/bat-go/libs/service" ) +const ( + reqBodyLimit10MB = 10 << 20 +) + type s3Service interface { GetObject(ctx context.Context, params *s3.GetObjectInput, optFns ...func(*s3.Options)) (*s3.GetObjectOutput, error) } @@ -189,5 +193,5 @@ func (s *Service) GetCardsAsBytes(ctx context.Context) (CardBytes, error) { } defer func() { _ = out.Body.Close() }() - return io.ReadAll(out.Body) + return io.ReadAll(io.LimitReader(out.Body, reqBodyLimit10MB)) } From 787c945572c291d0dfcf4eac22ae2b6ec3054f25 Mon Sep 17 00:00:00 2001 From: clD11 <23483715+clD11@users.noreply.github.com> Date: Thu, 31 Oct 2024 07:50:54 +0000 Subject: [PATCH 4/6] refactor: fix typo in stripe error (#2697) --- services/skus/stripe.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/services/skus/stripe.go b/services/skus/stripe.go index f3da7edaa..ac2fc44d9 100644 --- a/services/skus/stripe.go +++ b/services/skus/stripe.go @@ -13,7 +13,7 @@ import ( const ( errStripeSkipEvent = model.Error("stripe: skip webhook event") errStripeUnsupportedEvent = model.Error("stripe: unsupported webhook event") - errStripeNoInvoiceSub = model.Error("strupe: no invoice subscription") + errStripeNoInvoiceSub = model.Error("stripe: no invoice subscription") errStripeNoInvoiceLines = model.Error("stripe: no invoice lines") errStripeOrderIDMissing = model.Error("stripe: order_id missing") errStripeInvalidSubPeriod = model.Error("stripe: invalid subscription period") From ee8691af381ea8366d00a08664845c3af002f648 Mon Sep 17 00:00:00 2001 From: clD11 <23483715+clD11@users.noreply.github.com> Date: Thu, 31 Oct 2024 08:25:32 +0000 Subject: [PATCH 5/6] refactor: remove s3Client interface from rewards and cleanup (#2690) * refactor: remove s3Client interface from rewards and cleanup * refactor: format rewards get parameters code --- services/rewards/controllers.go | 48 ++++++++----------- services/rewards/controllers_test.go | 71 ++++++++-------------------- services/rewards/service.go | 39 +++++---------- 3 files changed, 52 insertions(+), 106 deletions(-) diff --git a/services/rewards/controllers.go b/services/rewards/controllers.go index 4ab78dde1..e7a267945 100644 --- a/services/rewards/controllers.go +++ b/services/rewards/controllers.go @@ -9,48 +9,38 @@ import ( "github.com/brave-intl/bat-go/libs/logging" ) -// GetParametersHandler - handler to get reward parameters func GetParametersHandler(service *Service) handlers.AppHandler { - return handlers.AppHandler(func(w http.ResponseWriter, r *http.Request) *handlers.AppError { - // get context from request - ctx := r.Context() - - var ( - currencyInput = r.URL.Query().Get("currency") - parameters *ParametersV1 - err error - ) - + return func(w http.ResponseWriter, r *http.Request) *handlers.AppError { + var currencyInput = r.URL.Query().Get("currency") if currencyInput == "" { currencyInput = "USD" } - // get logger from context - logger := logging.Logger(ctx, "rewards.GetParametersHandler") + ctx := r.Context() + + lg := logging.Logger(ctx, "rewards").With().Str("func", "GetParametersHandler").Logger() + + currency := new(BaseCurrency) + if err := inputs.DecodeAndValidate(ctx, currency, []byte(currencyInput)); err != nil { + lg.Error().Err(err).Msg("failed decode and validate") - // in here we need to validate our currency - var currency = new(BaseCurrency) - if err = inputs.DecodeAndValidate(ctx, currency, []byte(currencyInput)); err != nil { if errors.Is(err, ErrBaseCurrencyInvalid) { - logger.Error().Err(err).Msg("invalid currency input from caller") - return handlers.ValidationError( - "Error validating currency url parameter", - map[string]interface{}{ - "err": err.Error(), - "currency": "invalid currency", - }, - ) + return handlers.ValidationError("Error validating currency url parameter", map[string]interface{}{ + "err": err.Error(), + "currency": "invalid currency", + }) } - // degraded, unknown error when validating/decoding - logger.Error().Err(err).Msg("unforseen error in decode and validation") + return handlers.WrapError(err, "degraded: ", http.StatusInternalServerError) } - parameters, err = service.GetParameters(ctx, currency) + parameters, err := service.GetParameters(ctx, currency) if err != nil { - logger.Error().Err(err).Msg("failed to get reward parameters") + lg.Error().Err(err).Msg("failed to get reward parameters") + return handlers.WrapError(err, "failed to get parameters", http.StatusInternalServerError) } + return handlers.RenderContent(ctx, parameters, w, http.StatusOK) - }) + } } diff --git a/services/rewards/controllers_test.go b/services/rewards/controllers_test.go index c0296ae27..2f52f5fa8 100644 --- a/services/rewards/controllers_test.go +++ b/services/rewards/controllers_test.go @@ -23,12 +23,6 @@ import ( "github.com/golang/mock/gomock" ) -type mockGetObjectAPI func(ctx context.Context, params *s3.GetObjectInput, optFns ...func(*s3.Options)) (*s3.GetObjectOutput, error) - -func (m mockGetObjectAPI) GetObject(ctx context.Context, params *s3.GetObjectInput, optFns ...func(*s3.Options)) (*s3.GetObjectOutput, error) { - return m(ctx, params, optFns...) -} - func TestGetParametersController(t *testing.T) { mockCtrl := gomock.NewController(t) defer mockCtrl.Finish() @@ -40,54 +34,29 @@ func TestGetParametersController(t *testing.T) { "usd": decimal.New(10, 0), }}, nil) - var mockS3PayoutStatus = mockGetObjectAPI(func(ctx context.Context, params *s3.GetObjectInput, optFns ...func(*s3.Options)) (*s3.GetObjectOutput, error) { - return &s3.GetObjectOutput{ - Body: io.NopCloser(bytes.NewBufferString(`{ - "uphold":"processing", - "gemini":"off", - "bitflyer":"off", - "unverified":"off" + mockS3Svc := &mockS3Service{ + fnGetObject: func(ctx context.Context, params *s3.GetObjectInput, optFns ...func(*s3.Options)) (*s3.GetObjectOutput, error) { + if *params.Key == "payout-status.json" { + body := io.NopCloser(bytes.NewBufferString(`{"uphold":"processing","gemini":"off","bitflyer":"off","unverified":"off"}`)) + + return &s3.GetObjectOutput{Body: body}, nil } - `)), - }, nil - }) - - var mockS3CustodianRegions = mockGetObjectAPI(func(ctx context.Context, params *s3.GetObjectInput, optFns ...func(*s3.Options)) (*s3.GetObjectOutput, error) { - return &s3.GetObjectOutput{ - Body: io.NopCloser(bytes.NewBufferString(`{ - "uphold": { - "allow": [], - "block": [] - }, - "gemini": { - "allow": [], - "block": [] - }, - "bitflyer": { - "allow": [], - "block": [] - } - }`)), - }, nil - }) - - var mockS3 = mockGetObjectAPI(func(ctx context.Context, params *s3.GetObjectInput, optFns ...func(*s3.Options)) (*s3.GetObjectOutput, error) { - if *params.Key == "payout-status.json" { - return mockS3PayoutStatus(ctx, params, optFns...) - } - - if *params.Key == "custodian-regions.json" { - return mockS3CustodianRegions(ctx, params, optFns...) - } - - return nil, errors.New("invalid key") - }) + + if *params.Key == "custodian-regions.json" { + body := io.NopCloser(bytes.NewBufferString(`{"uphold":{"allow":[],"block":[]},"gemini":{"allow":[],"block":[]},"bitflyer":{"allow":[],"block":[]}}`)) + + return &s3.GetObjectOutput{Body: body}, nil + } + + return nil, errors.New("invalid key") + }, + } s := &Service{ - cfg: &Config{TOSVersion: 1}, - ratios: mockRatios, - s3Client: mockS3, - cacheMu: new(sync.RWMutex), + cfg: &Config{TOSVersion: 1}, + ratios: mockRatios, + cacheMu: new(sync.RWMutex), + s3Svc: mockS3Svc, } req, err := http.NewRequest(http.MethodGet, "/v1/parameters", nil) diff --git a/services/rewards/service.go b/services/rewards/service.go index 2bfdf00b6..9d4935062 100644 --- a/services/rewards/service.go +++ b/services/rewards/service.go @@ -10,6 +10,7 @@ import ( "time" "github.com/aws/aws-sdk-go-v2/service/s3" + appaws "github.com/brave-intl/bat-go/libs/aws" "github.com/brave-intl/bat-go/libs/clients/ratios" appctx "github.com/brave-intl/bat-go/libs/context" @@ -44,9 +45,7 @@ type Service struct { cacheMu *sync.RWMutex jobs []srv.Job ratios ratios.Client - // Stop using the interface in lib and use s3Service. - s3Client appaws.S3GetObjectAPI - s3Svc s3Service + s3Svc s3Service } func (s *Service) Jobs() []srv.Job { @@ -70,17 +69,14 @@ func InitService(ctx context.Context, cfg *Config) (*Service, error) { s3client := s3.NewFromConfig(awsCfg) return &Service{ - cfg: cfg, - cacheMu: new(sync.RWMutex), - jobs: []srv.Job{}, - ratios: ratiosCl, - s3Client: s3client, - - s3Svc: s3client, + cfg: cfg, + cacheMu: new(sync.RWMutex), + jobs: []srv.Job{}, + ratios: ratiosCl, + s3Svc: s3client, }, nil } -// GetParameters - respond to caller with the rewards parameters func (s *Service) GetParameters(ctx context.Context, currency *BaseCurrency) (*ParametersV1, error) { if currency == nil { currency = new(BaseCurrency) @@ -89,16 +85,12 @@ func (s *Service) GetParameters(ctx context.Context, currency *BaseCurrency) (*P var currencyStr = strings.ToLower(currency.String()) - lg := logging.Logger(ctx, "rewards.GetParameters") - rateData, err := s.ratios.FetchRate(ctx, "bat", currencyStr) if err != nil { - lg.Error().Err(err).Msg("failed to fetch rate from ratios") return nil, fmt.Errorf("failed to fetch rate from ratios: %w", err) } if rateData == nil { - lg.Error().Msg("empty response from ratios") return nil, errors.New("empty response from ratios") } @@ -110,7 +102,6 @@ func (s *Service) GetParameters(ctx context.Context, currency *BaseCurrency) (*P defaultChoice = choices[0] } - // if there is a default choice configured use it if dc := getDefaultChoice(ctx); dc > 0 { defaultChoice = dc } @@ -122,38 +113,34 @@ func (s *Service) GetParameters(ctx context.Context, currency *BaseCurrency) (*P s.cacheMu.RUnlock() if time.Now().After(lastPollTime.Add(15 * time.Minute)) { - // merge in static s3 attributes into response var ( payoutStatus *custodian.PayoutStatus custodianRegions *custodian.Regions bucket, ok = ctx.Value(appctx.ParametersMergeBucketCTXKey).(string) ) - lg.Debug().Str("bucket", bucket).Msg("merge bucket env var") + if ok { - // get payout status - lg.Debug().Str("bucket", bucket).Msg("extracting payout status") - payoutStatus, err = custodian.ExtractPayoutStatus(ctx, s.s3Client, bucket) + payoutStatus, err = custodian.ExtractPayoutStatus(ctx, s.s3Svc, bucket) if err != nil { return nil, fmt.Errorf("failed to get payout status parameters: %w", err) } - lg.Debug().Str("bucket", bucket).Str("payout status", fmt.Sprintf("%+v", *payoutStatus)).Msg("payout status") - // get the custodian regions - lg.Debug().Str("bucket", bucket).Msg("extracting custodian regions") - custodianRegions, err = custodian.ExtractCustodianRegions(ctx, s.s3Client, bucket) + custodianRegions, err = custodian.ExtractCustodianRegions(ctx, s.s3Svc, bucket) if err != nil { return nil, fmt.Errorf("failed to get custodian regions parameters: %w", err) } - lg.Debug().Str("bucket", bucket).Str("custodian regions", fmt.Sprintf("%+v", *custodianRegions)).Msg("custodianRegions") } + s.cacheMu.Lock() s.lastPayoutStatus = payoutStatus // update the payout status s.lastCustodianRegions = custodianRegions // update the custodian regions s.lastPollTime = time.Now() // update the time to now s.cacheMu.Unlock() } + s.cacheMu.RLock() defer s.cacheMu.RUnlock() + payoutStatus := s.lastPayoutStatus custodianRegions := s.lastCustodianRegions From 6dd1958d084771052941038bfb7ae6a515618add Mon Sep 17 00:00:00 2001 From: Pavel Brm <5097196+pavelbrm@users.noreply.github.com> Date: Thu, 31 Oct 2024 22:14:00 +1300 Subject: [PATCH 6/6] fix: improve email handling when creating a stripe checkout session (#2698) --- services/skus/service.go | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/services/skus/service.go b/services/skus/service.go index 2ae70701a..7eb4eb7a2 100644 --- a/services/skus/service.go +++ b/services/skus/service.go @@ -2741,10 +2741,12 @@ func createStripeSession(ctx context.Context, cl stripeClient, req createStripeS LineItems: req.items, } - if custID, ok := cl.FindCustomer(ctx, req.email); ok { - params.Customer = &custID.ID - } else { - if req.email != "" { + // Email might not be given. + // This could happen while recreating a session, and the email was not extracted from the old one. + if req.email != "" { + if cust, ok := cl.FindCustomer(ctx, req.email); ok && cust.Email != "" { + params.Customer = &cust.ID + } else { params.CustomerEmail = &req.email } }