storj/satellite/rewards/rewards.go
Yingrong Zhao efa2c776b7
satellite/satellitedb: add updateEarnedCredits method for user_credits table (#2609)
* parent 13dd501042
author Yingrong Zhao <yingrong.zhao@gmail.com> 1563560530 -0400
committer Yingrong Zhao <yingrong.zhao@gmail.com> 1563581673 -0400

parent 13dd501042
author Yingrong Zhao <yingrong.zhao@gmail.com> 1563560530 -0400
committer Yingrong Zhao <yingrong.zhao@gmail.com> 1563581428 -0400

satellite/console: add referral link logic (#2576)

* setup referral route

* referredBy

* add user id

* modify user query

* separate optional field from userInfo

* get current reward on init of satellite gui

* remove unsed code

* fix format

* only apply 0 credit on registration

* only pass required information for rewards

* fix time parsing

* fix test and linter

* rename method

* add todo

* remove user referral logic

* add null check and fix format

* get current offer

* remove partnerID on CreateUser struct

* fix storj-sim user creation

* only redeem credit when there's an offer

* fix default offer configuration

* fix migration

* Add helper function for get correct credit duration

* add comment

* only store userid into user_credit table

* add check for partner id to set correct offer type

* change free credit to use invitee credits

* remove unecessary code

* add credit update in activateAccount

* remove unused code

* fix format

* close reader and fix front-end build

* move create credit logic into CreateUser method

* when there's no offer set, user flow shouldn't be interrupted by referral program

* add appropriate error messages

* remove unused code

* add comment

* add error class for no current offer error

* add error class for credits update

* add comment for migration

* only log secret when it's in debug level

* fix typo

* add testdata
2019-07-30 09:21:00 -04:00

232 lines
5.4 KiB
Go

// Copyright (C) 2019 Storj Labs, Inc.
// See LICENSE for copying information
package rewards
import (
"context"
"time"
"github.com/zeebo/errs"
"storj.io/storj/internal/currency"
)
var (
// MaxRedemptionErr is the error class used when an offer has reached its redemption capacity
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")
)
// DB holds information about offer
type DB interface {
ListAll(ctx context.Context) (Offers, error)
GetCurrentByType(ctx context.Context, offerType OfferType) (*Offer, error)
Create(ctx context.Context, offer *NewOffer) (*Offer, error)
Finish(ctx context.Context, offerID int) error
}
// NewOffer holds information that's needed for creating a new offer
type NewOffer struct {
Name string
Description string
AwardCredit currency.USD
InviteeCredit currency.USD
RedeemableCap int
AwardCreditDurationDays int
InviteeCreditDurationDays int
ExpiresAt time.Time
Status OfferStatus
Type OfferType
}
// FormatPartnerName formats partner's name into combination of its partnerID and name
func (o NewOffer) FormatPartnerName() string {
if o.Type != Partner {
return o.Name
}
partnerInfo := PartnerInfo{
ID: LoadPartnerInfos()[o.Name].ID,
Name: o.Name,
}
return partnerInfo.FormattedName()
}
// UpdateOffer holds fields needed for update an offer
type UpdateOffer struct {
ID int
Status OfferStatus
ExpiresAt time.Time
}
// OfferType indicates the type of an offer
type OfferType int
const (
// Invalid is a default value for offers that don't have correct type associated with it
Invalid = OfferType(0)
// FreeCredit is a type of offers used for Free Credit Program
FreeCredit = OfferType(1)
// Referral is a type of offers used for Referral Program
Referral = OfferType(2)
// Partner is a type of offers used for Open Source Partner Program
Partner = OfferType(3)
)
// OfferStatus represents the different stage an offer can have in its life-cycle.
type OfferStatus int
const (
// Done is the status of an offer that is no longer in use.
Done = OfferStatus(iota)
// Active is the status of an offer that is currently in use.
Active
// Default is the status of an offer when there is no active offer.
Default
)
// Offer contains info needed for giving users free credits through different offer programs
type Offer struct {
ID int
Name string
Description string
AwardCredit currency.USD
InviteeCredit currency.USD
AwardCreditDurationDays int
InviteeCreditDurationDays int
RedeemableCap int
ExpiresAt time.Time
CreatedAt time.Time
Status OfferStatus
Type OfferType
}
// IsEmpty evaluates whether or not an on offer is empty
func (o Offer) IsEmpty() bool {
return o.Name == ""
}
// Offers contains a slice of offers.
type Offers []Offer
// OrganizedOffers contains a list of offers organized by status.
type OrganizedOffers struct {
Active Offer
Default Offer
Done Offers
}
// OpenSourcePartner contains all data for an Open Source Partner.
type OpenSourcePartner struct {
PartnerInfo
PartnerOffers OrganizedOffers
}
// PartnerSet contains a list of Open Source Partners.
type PartnerSet []OpenSourcePartner
// OfferSet provides a separation of marketing offers by type.
type OfferSet struct {
ReferralOffers OrganizedOffers
FreeCredits OrganizedOffers
PartnerTables PartnerSet
}
// OrganizeOffersByStatus organizes offers by OfferStatus.
func (offers Offers) OrganizeOffersByStatus() OrganizedOffers {
var oo OrganizedOffers
for _, offer := range offers {
switch offer.Status {
case Active:
oo.Active = offer
case Default:
oo.Default = offer
case Done:
oo.Done = append(oo.Done, offer)
}
}
return oo
}
// OrganizeOffersByType organizes offers by OfferType.
func (offers Offers) OrganizeOffersByType() OfferSet {
var (
fc, ro, p Offers
offerSet OfferSet
)
for _, offer := range offers {
switch offer.Type {
case FreeCredit:
fc = append(fc, offer)
case Referral:
ro = append(ro, offer)
case Partner:
p = append(p, offer)
default:
continue
}
}
offerSet.FreeCredits = fc.OrganizeOffersByStatus()
offerSet.ReferralOffers = ro.OrganizeOffersByStatus()
offerSet.PartnerTables = organizePartnerData(p)
return offerSet
}
// createPartnerSet generates a PartnerSet from the config file.
func createPartnerSet() PartnerSet {
partners := LoadPartnerInfos()
var ps PartnerSet
for _, partner := range partners {
ps = append(ps, OpenSourcePartner{
PartnerInfo: PartnerInfo{
Name: partner.Name,
ID: partner.ID,
},
})
}
return ps
}
// matchOffersToPartnerSet assigns offers to the partner they belong to.
func matchOffersToPartnerSet(offers Offers, partnerSet PartnerSet) PartnerSet {
for i := range partnerSet {
var partnerOffersByName Offers
for _, o := range offers {
if o.Name == partnerSet[i].PartnerInfo.FormattedName() {
partnerOffersByName = append(partnerOffersByName, o)
}
}
partnerSet[i].PartnerOffers = partnerOffersByName.OrganizeOffersByStatus()
}
return 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())
return partnerData
}