Skip to content

Commit

Permalink
satellite/console: add usersDB method GetExpiresBeforeWithStatus
Browse files Browse the repository at this point in the history
Add new type console.TrialNotificationStatus to define values in users
column trial_notifications. Add usersDB method
GetExpiresBeforeWithStatus, which takes a TrialNotificationStatus
and a timestamp and returns free tier users where trial_expiration is
before the timestamp argument and trial_notifications is equal to the
TrialNotificationStatus argument.

updates storj/storj-private#608

Change-Id: Iaaa33cd8a102e5e4e754c6c808d5bda69c6454a9
  • Loading branch information
cam-a authored and Storj Robot committed Mar 15, 2024
1 parent 048e16e commit 0d9fa41
Show file tree
Hide file tree
Showing 3 changed files with 146 additions and 2 deletions.
19 changes: 17 additions & 2 deletions satellite/console/users.go
Expand Up @@ -24,6 +24,8 @@ type Users interface {
Get(ctx context.Context, id uuid.UUID) (*User, error)
// GetExpiredFreeTrialsAfter is a method for querying users in free trial from the database with trial expiry (after).
GetExpiredFreeTrialsAfter(ctx context.Context, after time.Time, cursor UserCursor) (*UsersPage, error)
// GetExpiresBeforeWithStatus returns users with a particular trial notification status and whose trial expires before 'expiresBefore'.
GetExpiresBeforeWithStatus(ctx context.Context, notificationStatus TrialNotificationStatus, expiresBefore time.Time) ([]*User, error)
// GetUnverifiedNeedingReminder gets unverified users needing a reminder to verify their email.
GetUnverifiedNeedingReminder(ctx context.Context, firstReminder, secondReminder, cutoff time.Time) ([]*User, error)
// UpdateVerificationReminders increments verification_reminders.
Expand Down Expand Up @@ -335,8 +337,9 @@ type UpdateUserRequest struct {
ActivationCode *string
SignupId *string

TrialExpiration **time.Time
UpgradeTime *time.Time
TrialExpiration **time.Time
TrialNotifications *TrialNotificationStatus
UpgradeTime *time.Time
}

// UserSettings contains configurations for a user.
Expand Down Expand Up @@ -380,3 +383,15 @@ type SetUpAccountRequest struct {
FunctionalArea *string `json:"functionalArea"`
HaveSalesContact bool `json:"haveSalesContact"`
}

// TrialNotificationStatus is an enum representing a type of trial notification.
type TrialNotificationStatus int

const (
// NoTrialNotification represents the default state of no email notification sent.
NoTrialNotification TrialNotificationStatus = iota
// TrialExpirationReminder represents trial expiration reminder has been sent.
TrialExpirationReminder
// TrialExpired represents trial expired notification has been sent.
TrialExpired
)
31 changes: 31 additions & 0 deletions satellite/satellitedb/users.go
Expand Up @@ -226,6 +226,34 @@ func (users *users) GetByEmail(ctx context.Context, email string) (_ *console.Us
return userFromDBX(ctx, user)
}

// GetExpiresBeforeWithStatus returns users with a particular trial notification status and whose trial expires before 'expiresBefore'.
func (users *users) GetExpiresBeforeWithStatus(ctx context.Context, notificationStatus console.TrialNotificationStatus, expiresBefore time.Time) (needNotification []*console.User, err error) {
defer mon.Task()(&ctx)(&err)

rows, err := users.db.Query(ctx, `
SELECT id, email
FROM users
WHERE paid_tier = false
AND trial_notifications = $1
AND trial_expiration < $2
`, notificationStatus, expiresBefore)
if err != nil {
return nil, err
}
defer func() { err = errs.Combine(err, rows.Close()) }()

for rows.Next() {
var user console.User
err = rows.Scan(&user.ID, &user.Email)
if err != nil {
return nil, err
}
needNotification = append(needNotification, &user)
}

return needNotification, rows.Err()
}

// GetUnverifiedNeedingReminder returns users in need of a reminder to verify their email.
func (users *users) GetUnverifiedNeedingReminder(ctx context.Context, firstReminder, secondReminder, cutoff time.Time) (usersNeedingReminder []*console.User, err error) {
defer mon.Task()(&ctx)(&err)
Expand Down Expand Up @@ -745,6 +773,9 @@ func toUpdateUser(request console.UpdateUserRequest) (*dbx.User_Update_Fields, e
if request.TrialExpiration != nil {
update.TrialExpiration = dbx.User_TrialExpiration_Raw(*request.TrialExpiration)
}
if request.TrialNotifications != nil {
update.TrialNotifications = dbx.User_TrialNotifications(int(*request.TrialNotifications))
}
if request.UpgradeTime != nil {
update.UpgradeTime = dbx.User_UpgradeTime(*request.UpgradeTime)
}
Expand Down
98 changes: 98 additions & 0 deletions satellite/satellitedb/users_test.go
Expand Up @@ -20,6 +20,104 @@ import (
"storj.io/storj/satellite/satellitedb/satellitedbtest"
)

func TestGetExpiresBeforeWithStatus(t *testing.T) {
satellitedbtest.Run(t, func(ctx *testcontext.Context, t *testing.T, db satellite.DB) {
users := db.Console().Users()

// insert paid_tier user to ensure it is never returned from GetExpiresBeforeWithStatus
proUser := testrand.UUID()
_, err := users.Insert(ctx, &console.User{
ID: proUser,
FullName: "test",
Email: "userone@mail.test",
PasswordHash: []byte("testpassword"),
})
require.NoError(t, err)

boolPtr := true
require.NoError(t, users.Update(ctx, proUser, console.UpdateUserRequest{
PaidTier: &boolPtr,
}))

u, err := users.Get(ctx, proUser)
require.NoError(t, err)
require.True(t, u.PaidTier)
require.Nil(t, u.TrialExpiration)
require.Zero(t, u.TrialNotifications)

now := time.Now()
tomorrow := now.Add(24 * time.Hour)
dayAfterTomorrow := tomorrow.Add(24 * time.Hour)

// insert free trial user with no trial notification and expires tomorrow
trialUserNeedsReminder := testrand.UUID()
_, err = users.Insert(ctx, &console.User{
ID: trialUserNeedsReminder,
FullName: "test",
Email: "usertwo@mail.test",
PasswordHash: []byte("testpassword"),
TrialExpiration: &tomorrow,
})
require.NoError(t, err)

u, err = users.Get(ctx, trialUserNeedsReminder)
require.NoError(t, err)
require.False(t, u.PaidTier)
require.Equal(t, tomorrow.Truncate(time.Millisecond), u.TrialExpiration.Truncate(time.Millisecond))
require.Zero(t, u.TrialNotifications)

// insert free trial user who already got reminder and expires tomorrow
trialUserAlreadyReminded := testrand.UUID()
_, err = users.Insert(ctx, &console.User{
ID: trialUserAlreadyReminded,
FullName: "test",
Email: "usertwo@mail.test",
PasswordHash: []byte("testpassword"),
TrialExpiration: &tomorrow,
})
require.NoError(t, err)

notifiedStatus := console.TrialExpirationReminder
require.NoError(t, users.Update(ctx, trialUserAlreadyReminded, console.UpdateUserRequest{
TrialNotifications: &notifiedStatus,
}))

u, err = users.Get(ctx, trialUserAlreadyReminded)
require.NoError(t, err)
require.False(t, u.PaidTier)
require.Equal(t, tomorrow.Truncate(time.Millisecond), u.TrialExpiration.Truncate(time.Millisecond))
require.Equal(t, int(notifiedStatus), u.TrialNotifications)

u, err = users.Get(ctx, trialUserAlreadyReminded)
require.NoError(t, err)
require.Equal(t, int(console.TrialExpirationReminder), u.TrialNotifications)

// test with var now as expiresBefore arg. Expect trialUserNeedsReminder not returned
// since expiration, tomorrow, is after expiresBefore arg.
needExpirationReminder, err := users.GetExpiresBeforeWithStatus(ctx, console.NoTrialNotification, now)
require.NoError(t, err)
require.Len(t, needExpirationReminder, 0)

// test with var dayAfterTomorrow as expiresBefore arg. Expect trialUserNeedsReminder returned
// since expiration, tomorrow, is before expiresBefore arg and trial_notifications matches notificationStatus arg.
needExpirationReminder, err = users.GetExpiresBeforeWithStatus(ctx, console.NoTrialNotification, dayAfterTomorrow)
require.NoError(t, err)
require.Len(t, needExpirationReminder, 1)
require.Equal(t, trialUserNeedsReminder, needExpirationReminder[0].ID)

// test with var now as expiresBefore arg. Expect trialUserAlreadyReminded not returned
// since expiration, tomorrow, is after expiresBefore arg.
needExpiredNotification, err := users.GetExpiresBeforeWithStatus(ctx, console.TrialExpirationReminder, now)
require.NoError(t, err)
require.Len(t, needExpiredNotification, 0)

needExpiredNotification, err = users.GetExpiresBeforeWithStatus(ctx, console.TrialExpirationReminder, dayAfterTomorrow)
require.NoError(t, err)
require.Len(t, needExpiredNotification, 1)
require.Equal(t, trialUserAlreadyReminded, needExpiredNotification[0].ID)
})
}

func TestGetUnverifiedNeedingReminderCutoff(t *testing.T) {
satellitedbtest.Run(t, func(ctx *testcontext.Context, t *testing.T, db satellite.DB) {
users := db.Console().Users()
Expand Down

0 comments on commit 0d9fa41

Please sign in to comment.