split update methods into two for offer data (#2133)

* change Offers interface to separate Update method into two.

* Implement Finish and Redeem method to avoid concurrent updates

* Implement FinishOffer and RedeemOffer service methods

* add tests

* fix linting issue

* add tests for checking Finish and Redeem's results to work as expected

* fix linting error
This commit is contained in:
Yingrong Zhao 2019-06-12 11:53:19 -04:00 committed by Faris Huskovic
parent 14486b1885
commit a4d6e8c8ca
5 changed files with 69 additions and 25 deletions

View File

@ -18,7 +18,8 @@ type Offers interface {
ListAll(ctx context.Context) ([]Offer, error)
GetCurrentByType(ctx context.Context, offerType OfferType) (*Offer, error)
Create(ctx context.Context, offer *NewOffer) (*Offer, error)
Update(ctx context.Context, offer *UpdateOffer) error
Redeem(ctx context.Context, offerID int) error
Finish(ctx context.Context, offerID int) error
}
// NewOffer holds information that's needed for creating a new offer
@ -42,10 +43,9 @@ type NewOffer struct {
// UpdateOffer holds fields needed for update an offer
type UpdateOffer struct {
ID int
Status OfferStatus
NumRedeemed int
ExpiresAt time.Time
ID int
Status OfferStatus
ExpiresAt time.Time
}
// OfferType indicates the type of an offer

View File

@ -59,13 +59,25 @@ func TestOffer_Database(t *testing.T) {
require.Equal(t, new, c)
update := &marketing.UpdateOffer{
ID: new.ID,
Status: marketing.Done,
NumRedeemed: new.NumRedeemed,
ExpiresAt: time.Now(),
ID: new.ID,
Status: marketing.Done,
ExpiresAt: time.Now(),
}
err = planet.Satellites[0].DB.Marketing().Offers().Update(ctx, update)
err = planet.Satellites[0].DB.Marketing().Offers().Redeem(ctx, update.ID)
require.NoError(t, err)
err = planet.Satellites[0].DB.Marketing().Offers().Finish(ctx, update.ID)
require.NoError(t, err)
current, err := planet.Satellites[0].DB.Marketing().Offers().ListAll(ctx)
require.NoError(t, err)
if new.Status == marketing.Default {
require.Equal(t, new.NumRedeemed, current[i].NumRedeemed)
} else {
require.Equal(t, new.NumRedeemed+1, current[i].NumRedeemed)
}
require.Equal(t, marketing.Done, current[i].Status)
}
// create with expired offer

View File

@ -53,7 +53,7 @@ func (s *Service) ListAllOffers(ctx context.Context) (offers []Offer, err error)
func (s *Service) GetCurrentOfferByType(ctx context.Context, offerType OfferType) (offer *Offer, err error) {
defer mon.Task()(&ctx)(&err)
offer, err = s.db.Offers().GetCurrentByType(ctx, offer.Type)
offer, err = s.db.Offers().GetCurrentByType(ctx, offerType)
if err != nil {
return nil, Error.Wrap(err)
}
@ -78,14 +78,27 @@ func (s *Service) InsertNewOffer(ctx context.Context, offer *NewOffer) (o *Offer
return o, nil
}
// UpdateOffer modifies an existing offer in the db when the offer status is set to NoStatus
func (s *Service) UpdateOffer(ctx context.Context, offer *UpdateOffer) (err error) {
// RedeemOffer adds 1 to the number of redeemed for an offer
func (s *Service) RedeemOffer(ctx context.Context, uo *UpdateOffer) (err error) {
defer mon.Task()(&ctx)(&err)
if offer.Status == Default {
offer.NumRedeemed = 0
if uo.Status == Default {
return nil
}
err = s.db.Offers().Update(ctx, offer)
err = s.db.Offers().Redeem(ctx, uo.ID)
if err != nil {
return Error.Wrap(err)
}
return nil
}
// FinishOffer updates an active offer's status to be Done and its expiration time to be now
func (s *Service) FinishOffer(ctx context.Context, oID int) (err error) {
defer mon.Task()(&ctx)(&err)
err = s.db.Offers().Finish(ctx, oID)
if err != nil {
return Error.Wrap(err)
}

View File

@ -653,6 +653,12 @@ func (m *lockedOffers) Create(ctx context.Context, offer *marketing.NewOffer) (*
return m.db.Create(ctx, offer)
}
func (m *lockedOffers) Finish(ctx context.Context, offerId int) error {
m.Lock()
defer m.Unlock()
return m.db.Finish(ctx, offerId)
}
func (m *lockedOffers) GetCurrentByType(ctx context.Context, offerType marketing.OfferType) (*marketing.Offer, error) {
m.Lock()
defer m.Unlock()
@ -665,10 +671,10 @@ func (m *lockedOffers) ListAll(ctx context.Context) ([]marketing.Offer, error) {
return m.db.ListAll(ctx)
}
func (m *lockedOffers) Update(ctx context.Context, offer *marketing.UpdateOffer) error {
func (m *lockedOffers) Redeem(ctx context.Context, offerId int) error {
m.Lock()
defer m.Unlock()
return m.db.Update(ctx, offer)
return m.db.Redeem(ctx, offerId)
}
// Orders returns database for orders

View File

@ -104,15 +104,28 @@ func (offers *offers) Create(ctx context.Context, o *marketing.NewOffer) (*marke
return newOffer, marketing.OffersErr.Wrap(tx.Commit())
}
// Update modifies an offer entry's status and amount of offers redeemed based on offer id
func (offers *offers) Update(ctx context.Context, o *marketing.UpdateOffer) error {
updateFields := dbx.Offer_Update_Fields{
Status: dbx.Offer_Status(int(o.Status)),
NumRedeemed: dbx.Offer_NumRedeemed(o.NumRedeemed),
ExpiresAt: dbx.Offer_ExpiresAt(o.ExpiresAt),
// Redeem adds 1 to the amount of offers redeemed based on offer id
func (offers *offers) Redeem(ctx context.Context, oID int) error {
statement := offers.db.Rebind(
`UPDATE offers SET num_redeemed = num_redeemed + 1 where id = ? AND status = ? AND num_redeemed < redeemable_cap`,
)
_, err := offers.db.DB.ExecContext(ctx, statement, oID, marketing.Active)
if err != nil {
return marketing.OffersErr.Wrap(err)
}
offerID := dbx.Offer_Id(o.ID)
return nil
}
// Finish changes the offer status to be Done and its expiration date to be now based on offer id
func (offers *offers) Finish(ctx context.Context, oID int) error {
updateFields := dbx.Offer_Update_Fields{
Status: dbx.Offer_Status(int(marketing.Done)),
ExpiresAt: dbx.Offer_ExpiresAt(time.Now().UTC()),
}
offerID := dbx.Offer_Id(oID)
_, err := offers.db.Update_Offer_By_Id(ctx, offerID, updateFields)
if err != nil {