satellite/rewards: ensure that partner information is asked from a service (#3275)
This commit is contained in:
parent
bee1acef4e
commit
9c59efd33d
@ -46,6 +46,7 @@ import (
|
||||
"storj.io/storj/satellite/payments/paymentsconfig"
|
||||
"storj.io/storj/satellite/payments/stripecoinpayments"
|
||||
"storj.io/storj/satellite/repair/irreparable"
|
||||
"storj.io/storj/satellite/rewards"
|
||||
"storj.io/storj/satellite/vouchers"
|
||||
)
|
||||
|
||||
@ -119,6 +120,8 @@ type API struct {
|
||||
}
|
||||
|
||||
Marketing struct {
|
||||
PartnersService *rewards.PartnersService
|
||||
|
||||
Listener net.Listener
|
||||
Endpoint *marketingweb.Server
|
||||
}
|
||||
@ -384,6 +387,36 @@ func NewAPI(log *zap.Logger, full *identity.FullIdentity, db DB, pointerDB metai
|
||||
}
|
||||
}
|
||||
|
||||
{ // setup marketing portal
|
||||
log.Debug("Satellite API Process setting up marketing server")
|
||||
|
||||
peer.Marketing.PartnersService = rewards.NewPartnersService(
|
||||
peer.Log.Named("partners"),
|
||||
rewards.DefaultPartnersDB,
|
||||
[]string{
|
||||
"https://us-central-1.tardigrade.io/",
|
||||
"https://asia-east-1.tardigrade.io/",
|
||||
"https://europe-west-1.tardigrade.io/",
|
||||
},
|
||||
)
|
||||
|
||||
peer.Marketing.Listener, err = net.Listen("tcp", config.Marketing.Address)
|
||||
if err != nil {
|
||||
return nil, errs.Combine(err, peer.Close())
|
||||
}
|
||||
|
||||
peer.Marketing.Endpoint, err = marketingweb.NewServer(
|
||||
peer.Log.Named("marketing:endpoint"),
|
||||
config.Marketing,
|
||||
peer.DB.Rewards(),
|
||||
peer.Marketing.PartnersService,
|
||||
peer.Marketing.Listener,
|
||||
)
|
||||
if err != nil {
|
||||
return nil, errs.Combine(err, peer.Close())
|
||||
}
|
||||
}
|
||||
|
||||
{ // setup console
|
||||
log.Debug("Satellite API Process setting up console")
|
||||
consoleConfig := config.Console
|
||||
@ -400,6 +433,7 @@ func NewAPI(log *zap.Logger, full *identity.FullIdentity, db DB, pointerDB metai
|
||||
&consoleauth.Hmac{Secret: []byte(consoleConfig.AuthTokenSecret)},
|
||||
peer.DB.Console(),
|
||||
peer.DB.Rewards(),
|
||||
peer.Marketing.PartnersService,
|
||||
peer.Payments.Accounts,
|
||||
consoleConfig.PasswordCost,
|
||||
)
|
||||
@ -415,24 +449,6 @@ func NewAPI(log *zap.Logger, full *identity.FullIdentity, db DB, pointerDB metai
|
||||
)
|
||||
}
|
||||
|
||||
{ // setup marketing portal
|
||||
log.Debug("Satellite API Process setting up marketing server")
|
||||
marketingConfig := config.Marketing
|
||||
peer.Marketing.Listener, err = net.Listen("tcp", marketingConfig.Address)
|
||||
if err != nil {
|
||||
return nil, errs.Combine(err, peer.Close())
|
||||
}
|
||||
peer.Marketing.Endpoint, err = marketingweb.NewServer(
|
||||
peer.Log.Named("marketing:endpoint"),
|
||||
marketingConfig,
|
||||
peer.DB.Rewards(),
|
||||
peer.Marketing.Listener,
|
||||
)
|
||||
if err != nil {
|
||||
return nil, errs.Combine(err, peer.Close())
|
||||
}
|
||||
}
|
||||
|
||||
{ // setup node stats endpoint
|
||||
log.Debug("Satellite API Process setting up node stats endpoint")
|
||||
peer.NodeStats.Endpoint = nodestats.NewEndpoint(
|
||||
|
@ -50,6 +50,16 @@ func TestGrapqhlMutation(t *testing.T) {
|
||||
|
||||
log := zaptest.NewLogger(t)
|
||||
|
||||
partnersService := rewards.NewPartnersService(
|
||||
log.Named("partners"),
|
||||
rewards.DefaultPartnersDB,
|
||||
[]string{
|
||||
"https://us-central-1.tardigrade.io/",
|
||||
"https://asia-east-1.tardigrade.io/",
|
||||
"https://europe-west-1.tardigrade.io/",
|
||||
},
|
||||
)
|
||||
|
||||
paymentsConfig := stripecoinpayments.Config{}
|
||||
payments := stripecoinpayments.NewService(log, paymentsConfig, db.Customers(), db.CoinpaymentsTransactions())
|
||||
|
||||
@ -58,6 +68,7 @@ func TestGrapqhlMutation(t *testing.T) {
|
||||
&consoleauth.Hmac{Secret: []byte("my-suppa-secret-key")},
|
||||
db.Console(),
|
||||
db.Rewards(),
|
||||
partnersService,
|
||||
payments.Accounts(),
|
||||
console.TestPasswordCost,
|
||||
)
|
||||
|
@ -21,6 +21,7 @@ import (
|
||||
"storj.io/storj/satellite/console/consoleweb/consoleql"
|
||||
"storj.io/storj/satellite/mailservice"
|
||||
"storj.io/storj/satellite/payments/stripecoinpayments"
|
||||
"storj.io/storj/satellite/rewards"
|
||||
"storj.io/storj/satellite/satellitedb/satellitedbtest"
|
||||
)
|
||||
|
||||
@ -31,6 +32,16 @@ func TestGraphqlQuery(t *testing.T) {
|
||||
|
||||
log := zaptest.NewLogger(t)
|
||||
|
||||
partnersService := rewards.NewPartnersService(
|
||||
log.Named("partners"),
|
||||
rewards.DefaultPartnersDB,
|
||||
[]string{
|
||||
"https://us-central-1.tardigrade.io/",
|
||||
"https://asia-east-1.tardigrade.io/",
|
||||
"https://europe-west-1.tardigrade.io/",
|
||||
},
|
||||
)
|
||||
|
||||
paymentsConfig := stripecoinpayments.Config{}
|
||||
payments := stripecoinpayments.NewService(log, paymentsConfig, db.Customers(), db.CoinpaymentsTransactions())
|
||||
|
||||
@ -39,6 +50,7 @@ func TestGraphqlQuery(t *testing.T) {
|
||||
&consoleauth.Hmac{Secret: []byte("my-suppa-secret-key")},
|
||||
db.Console(),
|
||||
db.Rewards(),
|
||||
partnersService,
|
||||
payments.Accounts(),
|
||||
console.TestPasswordCost,
|
||||
)
|
||||
|
@ -69,6 +69,7 @@ type Service struct {
|
||||
log *zap.Logger
|
||||
store DB
|
||||
rewards rewards.DB
|
||||
partners *rewards.PartnersService
|
||||
accounts payments.Accounts
|
||||
|
||||
passwordCost int
|
||||
@ -80,7 +81,7 @@ type PaymentsService struct {
|
||||
}
|
||||
|
||||
// NewService returns new instance of Service
|
||||
func NewService(log *zap.Logger, signer Signer, store DB, rewards rewards.DB, accounts payments.Accounts, passwordCost int) (*Service, error) {
|
||||
func NewService(log *zap.Logger, signer Signer, store DB, rewards rewards.DB, partners *rewards.PartnersService, accounts payments.Accounts, passwordCost int) (*Service, error) {
|
||||
if signer == nil {
|
||||
return nil, errs.New("signer can't be nil")
|
||||
}
|
||||
@ -99,6 +100,7 @@ func NewService(log *zap.Logger, signer Signer, store DB, rewards rewards.DB, ac
|
||||
Signer: signer,
|
||||
store: store,
|
||||
rewards: rewards,
|
||||
partners: partners,
|
||||
accounts: accounts,
|
||||
passwordCost: passwordCost,
|
||||
}, nil
|
||||
@ -232,7 +234,8 @@ func (s *Service) CreateUser(ctx context.Context, user CreateUser, tokenSecret R
|
||||
s.log.Error("internal error", zap.Error(err))
|
||||
return nil, ErrConsoleInternal.Wrap(err)
|
||||
}
|
||||
currentReward, err := offers.GetActiveOffer(offerType, user.PartnerID)
|
||||
|
||||
currentReward, err := s.partners.GetActiveOffer(ctx, offers, offerType, user.PartnerID)
|
||||
if err != nil && !rewards.NoCurrentOfferErr.Has(err) {
|
||||
s.log.Error("internal error", zap.Error(err))
|
||||
return nil, ErrConsoleInternal.Wrap(err)
|
||||
@ -633,7 +636,8 @@ func (s *Service) GetCurrentRewardByType(ctx context.Context, offerType rewards.
|
||||
s.log.Error("internal error", zap.Error(err))
|
||||
return nil, ErrConsoleInternal.Wrap(err)
|
||||
}
|
||||
return offers.GetActiveOffer(offerType, "")
|
||||
|
||||
return s.partners.GetActiveOffer(ctx, offers, offerType, "")
|
||||
}
|
||||
|
||||
// GetUserCreditUsage is a method for querying users' credit information up until now
|
||||
|
@ -1,20 +1,24 @@
|
||||
// Copyright (C) 2019 Storj Labs, Inc.
|
||||
// See LICENSE for copying information
|
||||
|
||||
package rewards
|
||||
package marketingweb
|
||||
|
||||
import "sort"
|
||||
import (
|
||||
"context"
|
||||
|
||||
"storj.io/storj/satellite/rewards"
|
||||
)
|
||||
|
||||
// OrganizedOffers contains a list of offers organized by status.
|
||||
type OrganizedOffers struct {
|
||||
Active Offer
|
||||
Default Offer
|
||||
Done Offers
|
||||
Active rewards.Offer
|
||||
Default rewards.Offer
|
||||
Done rewards.Offers
|
||||
}
|
||||
|
||||
// OpenSourcePartner contains all data for an Open Source Partner.
|
||||
type OpenSourcePartner struct {
|
||||
PartnerInfo
|
||||
rewards.PartnerInfo
|
||||
PartnerOffers OrganizedOffers
|
||||
}
|
||||
|
||||
@ -28,22 +32,17 @@ type OfferSet struct {
|
||||
PartnerTables PartnerSet
|
||||
}
|
||||
|
||||
type referralInfo struct {
|
||||
UserID string
|
||||
PartnerID string
|
||||
}
|
||||
|
||||
// OrganizeOffersByStatus organizes offers by OfferStatus.
|
||||
func (offers Offers) OrganizeOffersByStatus() OrganizedOffers {
|
||||
func (server *Server) OrganizeOffersByStatus(offers rewards.Offers) OrganizedOffers {
|
||||
var oo OrganizedOffers
|
||||
|
||||
for _, offer := range offers {
|
||||
switch offer.Status {
|
||||
case Active:
|
||||
case rewards.Active:
|
||||
oo.Active = offer
|
||||
case Default:
|
||||
case rewards.Default:
|
||||
oo.Default = offer
|
||||
case Done:
|
||||
case rewards.Done:
|
||||
oo.Done = append(oo.Done, offer)
|
||||
}
|
||||
}
|
||||
@ -51,56 +50,48 @@ func (offers Offers) OrganizeOffersByStatus() OrganizedOffers {
|
||||
}
|
||||
|
||||
// OrganizeOffersByType organizes offers by OfferType.
|
||||
func (offers Offers) OrganizeOffersByType() OfferSet {
|
||||
func (server *Server) OrganizeOffersByType(offers rewards.Offers) OfferSet {
|
||||
var (
|
||||
fc, ro, p Offers
|
||||
fc, ro, p rewards.Offers
|
||||
offerSet OfferSet
|
||||
)
|
||||
|
||||
for _, offer := range offers {
|
||||
switch offer.Type {
|
||||
case FreeCredit:
|
||||
case rewards.FreeCredit:
|
||||
fc = append(fc, offer)
|
||||
case Referral:
|
||||
case rewards.Referral:
|
||||
ro = append(ro, offer)
|
||||
case Partner:
|
||||
case rewards.Partner:
|
||||
p = append(p, offer)
|
||||
default:
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
offerSet.FreeCredits = fc.OrganizeOffersByStatus()
|
||||
offerSet.ReferralOffers = ro.OrganizeOffersByStatus()
|
||||
offerSet.PartnerTables = organizePartnerData(p)
|
||||
offerSet.FreeCredits = server.OrganizeOffersByStatus(fc)
|
||||
offerSet.ReferralOffers = server.OrganizeOffersByStatus(ro)
|
||||
offerSet.PartnerTables = server.organizePartnerData(p)
|
||||
return offerSet
|
||||
}
|
||||
|
||||
// createPartnerSet generates a PartnerSet from the config file.
|
||||
func createPartnerSet() PartnerSet {
|
||||
partners := LoadPartnerInfos()
|
||||
func (server *Server) createPartnerSet() PartnerSet {
|
||||
all, _ := server.partners.All(context.TODO()) // TODO: don't ignore error
|
||||
|
||||
var ps PartnerSet
|
||||
keys := make([]string, len(partners))
|
||||
i := 0
|
||||
for k := range partners {
|
||||
keys[i] = k
|
||||
i++
|
||||
}
|
||||
|
||||
sort.Strings(keys)
|
||||
|
||||
for _, key := range keys {
|
||||
for _, partner := range all {
|
||||
ps = append(ps, OpenSourcePartner{
|
||||
PartnerInfo: partners[key],
|
||||
PartnerInfo: partner,
|
||||
})
|
||||
}
|
||||
return ps
|
||||
}
|
||||
|
||||
// matchOffersToPartnerSet assigns offers to the partner they belong to.
|
||||
func matchOffersToPartnerSet(offers Offers, partnerSet PartnerSet) PartnerSet {
|
||||
func (server *Server) matchOffersToPartnerSet(offers rewards.Offers, partnerSet PartnerSet) PartnerSet {
|
||||
for i := range partnerSet {
|
||||
var partnerOffersByName Offers
|
||||
var partnerOffersByName rewards.Offers
|
||||
|
||||
for _, o := range offers {
|
||||
if o.Name == partnerSet[i].PartnerInfo.Name {
|
||||
@ -108,7 +99,7 @@ func matchOffersToPartnerSet(offers Offers, partnerSet PartnerSet) PartnerSet {
|
||||
}
|
||||
}
|
||||
|
||||
partnerSet[i].PartnerOffers = partnerOffersByName.OrganizeOffersByStatus()
|
||||
partnerSet[i].PartnerOffers = server.OrganizeOffersByStatus(partnerOffersByName)
|
||||
}
|
||||
|
||||
return partnerSet
|
||||
@ -117,16 +108,7 @@ func matchOffersToPartnerSet(offers Offers, partnerSet PartnerSet) PartnerSet {
|
||||
// organizePartnerData returns a list of Open Source Partners
|
||||
// whose offers have been organized by status, type, and
|
||||
// assigned to the correct partner.
|
||||
func organizePartnerData(offers Offers) PartnerSet {
|
||||
partnerData := matchOffersToPartnerSet(offers, createPartnerSet())
|
||||
func (server *Server) organizePartnerData(offers rewards.Offers) PartnerSet {
|
||||
partnerData := server.matchOffersToPartnerSet(offers, server.createPartnerSet())
|
||||
return partnerData
|
||||
}
|
||||
|
||||
// getTardigradeDomains returns domain names for tardigrade satellites
|
||||
func getTardigradeDomains() []string {
|
||||
return []string{
|
||||
"https://us-central-1.tardigrade.io/",
|
||||
"https://asia-east-1.tardigrade.io/",
|
||||
"https://europe-west-1.tardigrade.io/",
|
||||
}
|
||||
}
|
@ -33,11 +33,15 @@ type Config struct {
|
||||
//
|
||||
// architecture: Endpoint
|
||||
type Server struct {
|
||||
log *zap.Logger
|
||||
config Config
|
||||
listener net.Listener
|
||||
server http.Server
|
||||
db rewards.DB
|
||||
log *zap.Logger
|
||||
config Config
|
||||
|
||||
listener net.Listener
|
||||
server http.Server
|
||||
|
||||
rewards rewards.DB
|
||||
partners *rewards.PartnersService
|
||||
|
||||
templateDir string
|
||||
templates struct {
|
||||
home *template.Template
|
||||
@ -58,12 +62,14 @@ func (s *Server) commonPages() []string {
|
||||
}
|
||||
|
||||
// NewServer creates new instance of offersweb server
|
||||
func NewServer(logger *zap.Logger, config Config, db rewards.DB, listener net.Listener) (*Server, error) {
|
||||
func NewServer(logger *zap.Logger, config Config, rewards rewards.DB, partners *rewards.PartnersService, listener net.Listener) (*Server, error) {
|
||||
s := &Server{
|
||||
log: logger,
|
||||
config: config,
|
||||
listener: listener,
|
||||
db: db,
|
||||
|
||||
rewards: rewards,
|
||||
partners: partners,
|
||||
}
|
||||
|
||||
logger.Sugar().Debugf("Starting Marketing Admin UI on %s...", s.listener.Addr().String())
|
||||
@ -93,14 +99,14 @@ func (s *Server) GetOffers(w http.ResponseWriter, req *http.Request) {
|
||||
return
|
||||
}
|
||||
|
||||
offers, err := s.db.ListAll(req.Context())
|
||||
offers, err := s.rewards.ListAll(req.Context())
|
||||
if err != nil {
|
||||
s.log.Error("failed to retrieve all offers", zap.Error(err))
|
||||
s.serveInternalError(w, req, err)
|
||||
return
|
||||
}
|
||||
|
||||
if err := s.templates.home.ExecuteTemplate(w, "base", offers.OrganizeOffersByType()); err != nil {
|
||||
if err := s.templates.home.ExecuteTemplate(w, "base", s.OrganizeOffersByType(offers)); err != nil {
|
||||
s.log.Error("failed to execute template", zap.Error(err))
|
||||
}
|
||||
}
|
||||
@ -135,7 +141,7 @@ func (s *Server) parseTemplates() (err error) {
|
||||
|
||||
s.templates.home, err = template.New("home-page").Funcs(template.FuncMap{
|
||||
"BaseURL": s.GetBaseURL,
|
||||
"ReferralLink": rewards.GeneratePartnerLink,
|
||||
"ReferralLink": s.generatePartnerLink,
|
||||
}).ParseFiles(homeFiles...)
|
||||
if err != nil {
|
||||
return Error.Wrap(err)
|
||||
@ -165,6 +171,10 @@ func (s *Server) parseTemplates() (err error) {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *Server) generatePartnerLink(offerName string) ([]string, error) {
|
||||
return s.partners.GeneratePartnerLink(context.TODO(), offerName)
|
||||
}
|
||||
|
||||
// CreateOffer handles requests to create new offers.
|
||||
func (s *Server) CreateOffer(w http.ResponseWriter, req *http.Request) {
|
||||
offer, err := parseOfferForm(w, req)
|
||||
@ -190,7 +200,7 @@ func (s *Server) CreateOffer(w http.ResponseWriter, req *http.Request) {
|
||||
return
|
||||
}
|
||||
|
||||
if _, err := s.db.Create(req.Context(), &offer); err != nil {
|
||||
if _, err := s.rewards.Create(req.Context(), &offer); err != nil {
|
||||
s.log.Error("failed to insert new offer", zap.Error(err))
|
||||
s.serveBadRequest(w, req, err)
|
||||
return
|
||||
@ -208,7 +218,7 @@ func (s *Server) StopOffer(w http.ResponseWriter, req *http.Request) {
|
||||
return
|
||||
}
|
||||
|
||||
if err := s.db.Finish(req.Context(), offerID); err != nil {
|
||||
if err := s.rewards.Finish(req.Context(), offerID); err != nil {
|
||||
s.log.Error("failed to stop offer", zap.Error(err))
|
||||
s.serveInternalError(w, req, err)
|
||||
return
|
||||
|
44
satellite/rewards/partner.go
Normal file
44
satellite/rewards/partner.go
Normal file
@ -0,0 +1,44 @@
|
||||
// Copyright (C) 2019 Storj Labs, Inc.
|
||||
// See LICENSE for copying information.
|
||||
|
||||
package rewards
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"github.com/zeebo/errs"
|
||||
)
|
||||
|
||||
// PartnerList defines a json struct for defining partners.
|
||||
type PartnerList struct {
|
||||
Partners []PartnerInfo
|
||||
}
|
||||
|
||||
// PartnerInfo contains information about a partner.
|
||||
type PartnerInfo struct {
|
||||
Name string
|
||||
ID string
|
||||
}
|
||||
|
||||
// UserAgent returns partners cano user agent.
|
||||
func (p *PartnerInfo) UserAgent() string { return p.Name }
|
||||
|
||||
// CanonicalUserAgentProduct returns canonicalizes the user agent product, which is suitable for lookups.
|
||||
func CanonicalUserAgentProduct(product string) string { return strings.ToLower(product) }
|
||||
|
||||
// PartnersListFromJSONFile loads a json definition of partners.
|
||||
func PartnersListFromJSONFile(path string) (*PartnerList, error) {
|
||||
file, err := os.Open(path)
|
||||
if err != nil {
|
||||
return nil, Error.Wrap(err)
|
||||
}
|
||||
defer func() {
|
||||
err = errs.Combine(err, Error.Wrap(file.Close()))
|
||||
}()
|
||||
|
||||
var list PartnerList
|
||||
err = json.NewDecoder(file).Decode(&list)
|
||||
return &list, Error.Wrap(err)
|
||||
}
|
@ -3,192 +3,115 @@
|
||||
|
||||
package rewards
|
||||
|
||||
import (
|
||||
"encoding/base64"
|
||||
"encoding/json"
|
||||
"path"
|
||||
// DefaultPartnersDB is current default settings.
|
||||
var DefaultPartnersDB = func() PartnersDB {
|
||||
list := DefaultPartners()
|
||||
db, err := NewPartnersStaticDB(&list)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return db
|
||||
}()
|
||||
|
||||
"github.com/zeebo/errs"
|
||||
)
|
||||
|
||||
var (
|
||||
// NoMatchPartnerIDErr is the error class used when an offer has reached its redemption capacity
|
||||
NoMatchPartnerIDErr = errs.Class("partner not exist")
|
||||
)
|
||||
|
||||
// PartnerInfo contains the name and ID of an Open Source Partner
|
||||
type PartnerInfo struct {
|
||||
ID, Name string
|
||||
}
|
||||
|
||||
// Partners contains a list of partners.
|
||||
type Partners map[string]PartnerInfo
|
||||
|
||||
// LoadPartnerInfos returns our current Open Source Partners.
|
||||
func LoadPartnerInfos() Partners {
|
||||
return Partners{
|
||||
"120bf202-8252-437e-ac12-0e364bee852e": PartnerInfo{
|
||||
// DefaultPartners lists Storj default open-source partners.
|
||||
func DefaultPartners() PartnerList {
|
||||
return PartnerList{
|
||||
Partners: []PartnerInfo{{
|
||||
Name: "Blocknify",
|
||||
ID: "120bf202-8252-437e-ac12-0e364bee852e",
|
||||
},
|
||||
"53688ea5-8695-4060-a2c6-b56969217909": PartnerInfo{
|
||||
}, {
|
||||
Name: "Breaker",
|
||||
ID: "53688ea5-8695-4060-a2c6-b56969217909",
|
||||
},
|
||||
"2fb801c6-a6d7-4d82-a838-32fef98cc398": PartnerInfo{
|
||||
}, {
|
||||
Name: "Confluent",
|
||||
ID: "2fb801c6-a6d7-4d82-a838-32fef98cc398",
|
||||
},
|
||||
"e28c8847-b323-4a7d-8111-25a0578a58bb": PartnerInfo{
|
||||
}, {
|
||||
Name: "Consensys",
|
||||
ID: "e28c8847-b323-4a7d-8111-25a0578a58bb",
|
||||
},
|
||||
"0af89ac1-0189-42c6-a47c-e169780b3818": PartnerInfo{
|
||||
}, {
|
||||
Name: "Couchbase",
|
||||
ID: "0af89ac1-0189-42c6-a47c-e169780b3818",
|
||||
},
|
||||
"881b92f6-77aa-42ee-961a-b80009d45dd8": PartnerInfo{
|
||||
}, {
|
||||
Name: "Digital Ocean",
|
||||
ID: "881b92f6-77aa-42ee-961a-b80009d45dd8",
|
||||
},
|
||||
"cadac3fb-6a3f-4d17-9748-cc66d0617d55": PartnerInfo{
|
||||
}, {
|
||||
Name: "Deloitte",
|
||||
ID: "cadac3fb-6a3f-4d17-9748-cc66d0617d55",
|
||||
},
|
||||
"53fb82d7-73ff-4a1a-ab0c-6968cffc850e": PartnerInfo{
|
||||
}, {
|
||||
Name: "DVLabs",
|
||||
ID: "53fb82d7-73ff-4a1a-ab0c-6968cffc850e",
|
||||
},
|
||||
"86c33256-cded-434c-aaac-405343974394": PartnerInfo{
|
||||
}, {
|
||||
Name: "Fluree",
|
||||
ID: "86c33256-cded-434c-aaac-405343974394",
|
||||
},
|
||||
"3e1b911a-c778-47ea-878c-9f3f264f8bc1": PartnerInfo{
|
||||
}, {
|
||||
Name: "Flexential",
|
||||
ID: "3e1b911a-c778-47ea-878c-9f3f264f8bc1",
|
||||
},
|
||||
"706011f3-400e-45eb-a796-90cce2a7d67e": PartnerInfo{
|
||||
}, {
|
||||
Name: "Heroku",
|
||||
ID: "706011f3-400e-45eb-a796-90cce2a7d67e",
|
||||
},
|
||||
"1519bdee-ed18-45fe-86c6-4c7fa9668a14": PartnerInfo{
|
||||
}, {
|
||||
Name: "Infura",
|
||||
ID: "1519bdee-ed18-45fe-86c6-4c7fa9668a14",
|
||||
},
|
||||
"e56c6a65-d5bf-457a-a414-e55c36624f73": PartnerInfo{
|
||||
}, {
|
||||
Name: "GroundX",
|
||||
ID: "e56c6a65-d5bf-457a-a414-e55c36624f73",
|
||||
},
|
||||
"8ee019ef-2aae-4867-9c18-41c65ea318c4": PartnerInfo{
|
||||
}, {
|
||||
Name: "MariaDB",
|
||||
ID: "8ee019ef-2aae-4867-9c18-41c65ea318c4",
|
||||
},
|
||||
"bbd340b2-0ae4-4254-af90-eaba6c273abb": PartnerInfo{
|
||||
}, {
|
||||
Name: "MongoDB",
|
||||
ID: "bbd340b2-0ae4-4254-af90-eaba6c273abb",
|
||||
},
|
||||
"3405a882-0cb2-4f91-a6e0-21be193b80e5": PartnerInfo{
|
||||
}, {
|
||||
Name: "Netki",
|
||||
ID: "3405a882-0cb2-4f91-a6e0-21be193b80e5",
|
||||
},
|
||||
"a1ba07a4-e095-4a43-914c-1d56c9ff5afd": PartnerInfo{
|
||||
}, {
|
||||
Name: "FileZilla",
|
||||
ID: "a1ba07a4-e095-4a43-914c-1d56c9ff5afd",
|
||||
},
|
||||
"e50a17b3-4d82-4da7-8719-09312a83685d": PartnerInfo{
|
||||
}, {
|
||||
Name: "InfluxDB",
|
||||
ID: "e50a17b3-4d82-4da7-8719-09312a83685d",
|
||||
},
|
||||
"c10228c2-af70-4e4d-be49-e8bfbe9ca8ef": PartnerInfo{
|
||||
}, {
|
||||
Name: "Mysterium Network",
|
||||
ID: "c10228c2-af70-4e4d-be49-e8bfbe9ca8ef",
|
||||
},
|
||||
"OSPP005": PartnerInfo{
|
||||
}, {
|
||||
Name: "Kafka",
|
||||
ID: "OSPP005",
|
||||
},
|
||||
"5bffe844-5da7-4aa9-bf37-7d695cf819f2": PartnerInfo{
|
||||
}, {
|
||||
Name: "Minio",
|
||||
ID: "5bffe844-5da7-4aa9-bf37-7d695cf819f2",
|
||||
},
|
||||
"42f588fb-f39d-4886-81af-b614ca16ce37": PartnerInfo{
|
||||
}, {
|
||||
Name: "Nextcloud",
|
||||
ID: "42f588fb-f39d-4886-81af-b614ca16ce37",
|
||||
},
|
||||
"3b53a9b3-2005-476c-9ffd-894ed832abe4": PartnerInfo{
|
||||
}, {
|
||||
Name: "Node Haven",
|
||||
ID: "3b53a9b3-2005-476c-9ffd-894ed832abe4",
|
||||
},
|
||||
"dc01ed96-2990-4819-9cb3-45d4846b9ad1": PartnerInfo{
|
||||
}, {
|
||||
Name: "Plesk",
|
||||
ID: "dc01ed96-2990-4819-9cb3-45d4846b9ad1",
|
||||
},
|
||||
"b02b9f0d-fac7-439c-8ba2-0c4634d5826f": PartnerInfo{
|
||||
}, {
|
||||
Name: "Pydio",
|
||||
ID: "b02b9f0d-fac7-439c-8ba2-0c4634d5826f",
|
||||
},
|
||||
"57855387-5a58-4a2b-97d2-15b1d76eea3c": PartnerInfo{
|
||||
}, {
|
||||
Name: "Raiden Network",
|
||||
ID: "57855387-5a58-4a2b-97d2-15b1d76eea3c",
|
||||
},
|
||||
"4400d796-3777-4964-8536-22a4ae439ed3": PartnerInfo{
|
||||
}, {
|
||||
Name: "Satoshi Soup",
|
||||
ID: "4400d796-3777-4964-8536-22a4ae439ed3",
|
||||
},
|
||||
"6e40f882-ef77-4a5d-b5ad-18525d3df023": PartnerInfo{
|
||||
}, {
|
||||
Name: "Sirin Labs",
|
||||
ID: "6e40f882-ef77-4a5d-b5ad-18525d3df023",
|
||||
},
|
||||
"b6114126-c06d-49f9-8d23-3e0dd2e350ab": PartnerInfo{
|
||||
}, {
|
||||
Name: "Status Messenger",
|
||||
ID: "b6114126-c06d-49f9-8d23-3e0dd2e350ab",
|
||||
},
|
||||
"aeedbe32-1519-4320-b2f4-33725c65af54": PartnerInfo{
|
||||
}, {
|
||||
Name: "Temporal",
|
||||
ID: "aeedbe32-1519-4320-b2f4-33725c65af54",
|
||||
},
|
||||
"7bf23e53-6393-4bd0-8bf9-53ecf0de742f": PartnerInfo{
|
||||
}, {
|
||||
Name: "Terminal.co",
|
||||
ID: "7bf23e53-6393-4bd0-8bf9-53ecf0de742f",
|
||||
},
|
||||
"8cd605fa-ad00-45b6-823e-550eddc611d6": PartnerInfo{
|
||||
}, {
|
||||
Name: "Zenko",
|
||||
ID: "8cd605fa-ad00-45b6-823e-550eddc611d6",
|
||||
},
|
||||
}},
|
||||
}
|
||||
}
|
||||
|
||||
// GeneratePartnerLink returns base64 encoded partner referral link
|
||||
func GeneratePartnerLink(offerName string) ([]string, error) {
|
||||
pID, err := GetPartnerID(offerName)
|
||||
if err != nil {
|
||||
return nil, errs.Wrap(err)
|
||||
}
|
||||
referralInfo := &referralInfo{UserID: "", PartnerID: pID}
|
||||
refJSON, err := json.Marshal(referralInfo)
|
||||
if err != nil {
|
||||
return nil, errs.Wrap(err)
|
||||
}
|
||||
|
||||
domains := getTardigradeDomains()
|
||||
referralLinks := make([]string, len(domains))
|
||||
encoded := base64.StdEncoding.EncodeToString(refJSON)
|
||||
|
||||
for i, url := range domains {
|
||||
referralLinks[i] = path.Join(url, "ref", encoded)
|
||||
}
|
||||
|
||||
return referralLinks, nil
|
||||
}
|
||||
|
||||
// GetPartnerID returns partner ID based on partner name
|
||||
func GetPartnerID(partnerName string) (partnerID string, err error) {
|
||||
partners := LoadPartnerInfos()
|
||||
for i := range partners {
|
||||
if partners[i].Name == partnerName {
|
||||
return partners[i].ID, nil
|
||||
}
|
||||
}
|
||||
|
||||
return "", errs.New("partner id not found")
|
||||
}
|
||||
|
50
satellite/rewards/partners_db_test.go
Normal file
50
satellite/rewards/partners_db_test.go
Normal file
@ -0,0 +1,50 @@
|
||||
// Copyright (C) 2019 Storj Labs, Inc.
|
||||
// See LICENSE for copying information.
|
||||
|
||||
package rewards_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"storj.io/storj/internal/testcontext"
|
||||
"storj.io/storj/satellite/rewards"
|
||||
)
|
||||
|
||||
func TestStaticDB(t *testing.T) {
|
||||
ctx := testcontext.New(t)
|
||||
defer ctx.Cleanup()
|
||||
|
||||
world := rewards.PartnerInfo{
|
||||
Name: "World",
|
||||
ID: "WORLD0",
|
||||
}
|
||||
|
||||
hello := rewards.PartnerInfo{
|
||||
Name: "Hello",
|
||||
ID: "11111111-1111-1111-1111-111111111111",
|
||||
}
|
||||
|
||||
db, err := rewards.NewPartnersStaticDB(&rewards.PartnerList{
|
||||
Partners: []rewards.PartnerInfo{world, hello},
|
||||
})
|
||||
require.NotNil(t, db)
|
||||
require.NoError(t, err)
|
||||
|
||||
byID, err := db.ByID(ctx, "WORLD0")
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, world, byID)
|
||||
|
||||
byName, err := db.ByName(ctx, "World")
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, world, byName)
|
||||
|
||||
byUserAgent, err := db.ByUserAgent(ctx, "wOrLd")
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, world, byUserAgent)
|
||||
|
||||
all, err := db.All(ctx)
|
||||
require.NoError(t, err)
|
||||
require.EqualValues(t, []rewards.PartnerInfo{hello, world}, all)
|
||||
}
|
121
satellite/rewards/partners_service.go
Normal file
121
satellite/rewards/partners_service.go
Normal file
@ -0,0 +1,121 @@
|
||||
// Copyright (C) 2019 Storj Labs, Inc.
|
||||
// See LICENSE for copying information.
|
||||
|
||||
package rewards
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/base64"
|
||||
"encoding/json"
|
||||
"path"
|
||||
|
||||
"github.com/zeebo/errs"
|
||||
"go.uber.org/zap"
|
||||
)
|
||||
|
||||
var (
|
||||
// Error is the default error class for partners package.
|
||||
Error = errs.Class("partners error class")
|
||||
|
||||
// ErrNotExist is returned when a particular partner does not exist.
|
||||
ErrNotExist = errs.Class("partner does not exist")
|
||||
)
|
||||
|
||||
// PartnersDB allows access to partners database.
|
||||
//
|
||||
// architecture: Database
|
||||
type PartnersDB interface {
|
||||
// All returns all partners.
|
||||
All(ctx context.Context) ([]PartnerInfo, error)
|
||||
// ByName returns partner definitions for a given name.
|
||||
ByName(ctx context.Context, name string) (PartnerInfo, error)
|
||||
// ByID returns partner definition corresponding to an id.
|
||||
ByID(ctx context.Context, id string) (PartnerInfo, error)
|
||||
// ByUserAgent returns partner definition corresponding to an user agent string.
|
||||
ByUserAgent(ctx context.Context, agent string) (PartnerInfo, error)
|
||||
}
|
||||
|
||||
// PartnersService allows manipulating and accessing partner information.
|
||||
//
|
||||
// architecture: Service
|
||||
type PartnersService struct {
|
||||
log *zap.Logger
|
||||
db PartnersDB
|
||||
domains []string
|
||||
}
|
||||
|
||||
// NewPartnersService returns a service for handling partner information.
|
||||
func NewPartnersService(log *zap.Logger, db PartnersDB, domains []string) *PartnersService {
|
||||
return &PartnersService{
|
||||
log: log,
|
||||
db: db,
|
||||
domains: domains,
|
||||
}
|
||||
}
|
||||
|
||||
// GeneratePartnerLink returns base64 encoded partner referral link.
|
||||
func (service *PartnersService) GeneratePartnerLink(ctx context.Context, offerName string) ([]string, error) {
|
||||
partner, err := service.db.ByName(ctx, offerName)
|
||||
if err != nil {
|
||||
return nil, Error.Wrap(err)
|
||||
}
|
||||
|
||||
type info struct {
|
||||
UserID string
|
||||
PartnerID string
|
||||
}
|
||||
|
||||
referralInfo := &info{UserID: "", PartnerID: partner.ID}
|
||||
refJSON, err := json.Marshal(referralInfo)
|
||||
if err != nil {
|
||||
return nil, errs.Wrap(err)
|
||||
}
|
||||
// TODO: why is this using base64?
|
||||
encoded := base64.StdEncoding.EncodeToString(refJSON)
|
||||
|
||||
var links []string
|
||||
for _, domain := range service.domains {
|
||||
links = append(links, path.Join(domain, "ref", encoded))
|
||||
}
|
||||
|
||||
return links, nil
|
||||
}
|
||||
|
||||
// GetActiveOffer returns an offer that is active based on its type
|
||||
func (service *PartnersService) GetActiveOffer(ctx context.Context, offers Offers, offerType OfferType, partnerID string) (offer *Offer, err error) {
|
||||
if len(offers) < 1 {
|
||||
return nil, NoCurrentOfferErr.New("no active offers")
|
||||
}
|
||||
switch offerType {
|
||||
case Partner:
|
||||
if partnerID == "" {
|
||||
return nil, errs.New("partner ID is empty")
|
||||
}
|
||||
partnerInfo, err := service.db.ByID(ctx, partnerID)
|
||||
if err != nil {
|
||||
return nil, NoMatchPartnerIDErr.Wrap(err)
|
||||
}
|
||||
for i := range offers {
|
||||
if offers[i].Name == partnerInfo.Name {
|
||||
offer = &offers[i]
|
||||
}
|
||||
}
|
||||
default:
|
||||
if len(offers) > 1 {
|
||||
return nil, errs.New("multiple active offers found")
|
||||
}
|
||||
offer = &offers[0]
|
||||
}
|
||||
|
||||
return offer, nil
|
||||
}
|
||||
|
||||
// PartnerByName looks up partner by name.
|
||||
func (service *PartnersService) PartnerByName(ctx context.Context, name string) (PartnerInfo, error) {
|
||||
return service.db.ByName(ctx, name)
|
||||
}
|
||||
|
||||
// All returns all partners.
|
||||
func (service *PartnersService) All(ctx context.Context) ([]PartnerInfo, error) {
|
||||
return service.db.All(ctx)
|
||||
}
|
91
satellite/rewards/partners_staticdb.go
Normal file
91
satellite/rewards/partners_staticdb.go
Normal file
@ -0,0 +1,91 @@
|
||||
// Copyright (C) 2019 Storj Labs, Inc.
|
||||
// See LICENSE for copying information.
|
||||
|
||||
package rewards
|
||||
|
||||
import (
|
||||
"context"
|
||||
"sort"
|
||||
|
||||
"github.com/zeebo/errs"
|
||||
)
|
||||
|
||||
// PartnersStaticDB implements partner lookup based on a static definition.
|
||||
type PartnersStaticDB struct {
|
||||
list *PartnerList
|
||||
byName map[string]PartnerInfo
|
||||
byID map[string]PartnerInfo
|
||||
byUserAgent map[string]PartnerInfo
|
||||
}
|
||||
|
||||
var _ PartnersDB = (*PartnersStaticDB)(nil)
|
||||
|
||||
// NewPartnersStaticDB creates a new PartnersStaticDB.
|
||||
func NewPartnersStaticDB(list *PartnerList) (*PartnersStaticDB, error) {
|
||||
db := &PartnersStaticDB{
|
||||
list: list,
|
||||
byName: map[string]PartnerInfo{},
|
||||
byID: map[string]PartnerInfo{},
|
||||
byUserAgent: map[string]PartnerInfo{},
|
||||
}
|
||||
|
||||
sort.Slice(list.Partners, func(i, k int) bool {
|
||||
return list.Partners[i].Name < list.Partners[k].Name
|
||||
})
|
||||
|
||||
var errg errs.Group
|
||||
for _, p := range list.Partners {
|
||||
if _, exists := db.byName[p.Name]; exists {
|
||||
errg.Add(Error.New("name %q already exists", p.Name))
|
||||
} else {
|
||||
db.byName[p.Name] = p
|
||||
}
|
||||
|
||||
if _, exists := db.byID[p.ID]; exists {
|
||||
errg.Add(Error.New("id %q already exists", p.ID))
|
||||
} else {
|
||||
db.byID[p.ID] = p
|
||||
}
|
||||
|
||||
useragent := CanonicalUserAgentProduct(p.UserAgent())
|
||||
if _, exists := db.byUserAgent[useragent]; exists {
|
||||
errg.Add(Error.New("user agent %q already exists", useragent))
|
||||
} else {
|
||||
db.byUserAgent[useragent] = p
|
||||
}
|
||||
}
|
||||
|
||||
return db, errg.Err()
|
||||
}
|
||||
|
||||
// All returns all partners.
|
||||
func (db *PartnersStaticDB) All(ctx context.Context) ([]PartnerInfo, error) {
|
||||
return append([]PartnerInfo{}, db.list.Partners...), nil
|
||||
}
|
||||
|
||||
// ByName returns partner definitions for a given name.
|
||||
func (db *PartnersStaticDB) ByName(ctx context.Context, name string) (PartnerInfo, error) {
|
||||
partner, ok := db.byName[name]
|
||||
if !ok {
|
||||
return PartnerInfo{}, ErrNotExist.New("%q", name)
|
||||
}
|
||||
return partner, nil
|
||||
}
|
||||
|
||||
// ByID returns partner definition corresponding to an id.
|
||||
func (db *PartnersStaticDB) ByID(ctx context.Context, id string) (PartnerInfo, error) {
|
||||
partner, ok := db.byID[id]
|
||||
if !ok {
|
||||
return PartnerInfo{}, ErrNotExist.New("%q", id)
|
||||
}
|
||||
return partner, nil
|
||||
}
|
||||
|
||||
// ByUserAgent returns partner definition corresponding to an user agent product string.
|
||||
func (db *PartnersStaticDB) ByUserAgent(ctx context.Context, agent string) (PartnerInfo, error) {
|
||||
partner, ok := db.byUserAgent[CanonicalUserAgentProduct(agent)]
|
||||
if !ok {
|
||||
return PartnerInfo{}, ErrNotExist.New("%q", agent)
|
||||
}
|
||||
return partner, nil
|
||||
}
|
@ -17,6 +17,8 @@ var (
|
||||
MaxRedemptionErr = errs.Class("offer redemption has reached its capacity")
|
||||
// NoCurrentOfferErr is the error class used when no current offer is set
|
||||
NoCurrentOfferErr = errs.Class("no current offer")
|
||||
// NoMatchPartnerIDErr is the error class used when an offer has reached its redemption capacity
|
||||
NoMatchPartnerIDErr = errs.Class("partner not exist")
|
||||
)
|
||||
|
||||
// DB holds information about offer
|
||||
@ -83,13 +85,10 @@ const (
|
||||
type OfferStatus int
|
||||
|
||||
const (
|
||||
|
||||
// Done is the status of an offer that is no longer in use.
|
||||
Done = OfferStatus(iota)
|
||||
|
||||
// Default is the status of an offer when there is no active offer.
|
||||
Default
|
||||
|
||||
// Active is the status of an offer that is currently in use.
|
||||
Active
|
||||
)
|
||||
@ -120,35 +119,6 @@ func (o Offer) IsEmpty() bool {
|
||||
return o.Name == ""
|
||||
}
|
||||
|
||||
// GetActiveOffer returns an offer that is active based on its type
|
||||
func (offers Offers) GetActiveOffer(offerType OfferType, partnerID string) (offer *Offer, err error) {
|
||||
if len(offers) < 1 {
|
||||
return nil, NoCurrentOfferErr.New("no active offers")
|
||||
}
|
||||
switch offerType {
|
||||
case Partner:
|
||||
if partnerID == "" {
|
||||
return nil, errs.New("partner ID is empty")
|
||||
}
|
||||
partnerInfo, ok := LoadPartnerInfos()[partnerID]
|
||||
if !ok {
|
||||
return nil, NoMatchPartnerIDErr.New("no partnerInfo found")
|
||||
}
|
||||
for i := range offers {
|
||||
if offers[i].Name == partnerInfo.Name {
|
||||
offer = &offers[i]
|
||||
}
|
||||
}
|
||||
default:
|
||||
if len(offers) > 1 {
|
||||
return nil, errs.New("multiple active offers found")
|
||||
}
|
||||
offer = &offers[0]
|
||||
}
|
||||
|
||||
return offer, nil
|
||||
}
|
||||
|
||||
// IsDefault checks if a offer's status is default
|
||||
func (status OfferStatus) IsDefault() bool {
|
||||
return status == Default
|
||||
|
@ -71,10 +71,11 @@ func TestOffer_Database(t *testing.T) {
|
||||
require.NoError(t, err)
|
||||
var pID string
|
||||
if new.Type == rewards.Partner {
|
||||
pID, err = rewards.GetPartnerID(new.Name)
|
||||
partner, err := planet.Satellites[0].API.Marketing.PartnersService.PartnerByName(ctx, new.Name)
|
||||
require.NoError(t, err)
|
||||
pID = partner.ID
|
||||
}
|
||||
c, err := offers.GetActiveOffer(new.Type, pID)
|
||||
c, err := planet.Satellites[0].API.Marketing.PartnersService.GetActiveOffer(ctx, offers, new.Type, pID)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, new, c)
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user