mirror of
https://github.com/muety/wakapi.git
synced 2023-08-10 21:12:56 +03:00
fix: track subscription renewal date
This commit is contained in:
parent
23f9787b69
commit
3512db5ca4
35
migrations/20230219_add_subscription_renewal.go
Normal file
35
migrations/20230219_add_subscription_renewal.go
Normal file
@ -0,0 +1,35 @@
|
||||
package migrations
|
||||
|
||||
import (
|
||||
"github.com/emvi/logbuch"
|
||||
"github.com/muety/wakapi/config"
|
||||
"github.com/muety/wakapi/models"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
func init() {
|
||||
const name = "20230219-add_subscription_renewal"
|
||||
f := migrationFunc{
|
||||
name: name,
|
||||
f: func(db *gorm.DB, cfg *config.Config) error {
|
||||
if hasRun(name, db) {
|
||||
return nil
|
||||
}
|
||||
|
||||
migrator := db.Migrator()
|
||||
|
||||
if migrator.HasColumn(&models.User{}, "subscription_renewal") {
|
||||
logbuch.Info("running migration '%s'", name)
|
||||
|
||||
if err := db.Exec("UPDATE users SET subscription_renewal = subscribed_until WHERE subscribed_until is not null").Error; err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
setHasRun(name, db)
|
||||
return nil
|
||||
},
|
||||
}
|
||||
|
||||
registerPostMigration(f)
|
||||
}
|
@ -15,29 +15,30 @@ func init() {
|
||||
}
|
||||
|
||||
type User struct {
|
||||
ID string `json:"id" gorm:"primary_key"`
|
||||
ApiKey string `json:"api_key" gorm:"unique; default:NULL"`
|
||||
Email string `json:"email" gorm:"index:idx_user_email; size:255"`
|
||||
Location string `json:"location"`
|
||||
Password string `json:"-"`
|
||||
CreatedAt CustomTime `gorm:"type:timestamp; default:CURRENT_TIMESTAMP" swaggertype:"string" format:"date" example:"2006-01-02 15:04:05.000"`
|
||||
LastLoggedInAt CustomTime `gorm:"type:timestamp; default:CURRENT_TIMESTAMP" swaggertype:"string" format:"date" example:"2006-01-02 15:04:05.000"`
|
||||
ShareDataMaxDays int `json:"-"`
|
||||
ShareEditors bool `json:"-" gorm:"default:false; type:bool"`
|
||||
ShareLanguages bool `json:"-" gorm:"default:false; type:bool"`
|
||||
ShareProjects bool `json:"-" gorm:"default:false; type:bool"`
|
||||
ShareOSs bool `json:"-" gorm:"default:false; type:bool; column:share_oss"`
|
||||
ShareMachines bool `json:"-" gorm:"default:false; type:bool"`
|
||||
ShareLabels bool `json:"-" gorm:"default:false; type:bool"`
|
||||
IsAdmin bool `json:"-" gorm:"default:false; type:bool"`
|
||||
HasData bool `json:"-" gorm:"default:false; type:bool"`
|
||||
WakatimeApiKey string `json:"-"` // for relay middleware and imports
|
||||
WakatimeApiUrl string `json:"-"` // for relay middleware and imports
|
||||
ResetToken string `json:"-"`
|
||||
ReportsWeekly bool `json:"-" gorm:"default:false; type:bool"`
|
||||
PublicLeaderboard bool `json:"-" gorm:"default:false; type:bool"`
|
||||
SubscribedUntil *CustomTime `json:"-" gorm:"type:timestamp" swaggertype:"string" format:"date" example:"2006-01-02 15:04:05.000"`
|
||||
StripeCustomerId string `json:"-"`
|
||||
ID string `json:"id" gorm:"primary_key"`
|
||||
ApiKey string `json:"api_key" gorm:"unique; default:NULL"`
|
||||
Email string `json:"email" gorm:"index:idx_user_email; size:255"`
|
||||
Location string `json:"location"`
|
||||
Password string `json:"-"`
|
||||
CreatedAt CustomTime `gorm:"type:timestamp; default:CURRENT_TIMESTAMP" swaggertype:"string" format:"date" example:"2006-01-02 15:04:05.000"`
|
||||
LastLoggedInAt CustomTime `gorm:"type:timestamp; default:CURRENT_TIMESTAMP" swaggertype:"string" format:"date" example:"2006-01-02 15:04:05.000"`
|
||||
ShareDataMaxDays int `json:"-"`
|
||||
ShareEditors bool `json:"-" gorm:"default:false; type:bool"`
|
||||
ShareLanguages bool `json:"-" gorm:"default:false; type:bool"`
|
||||
ShareProjects bool `json:"-" gorm:"default:false; type:bool"`
|
||||
ShareOSs bool `json:"-" gorm:"default:false; type:bool; column:share_oss"`
|
||||
ShareMachines bool `json:"-" gorm:"default:false; type:bool"`
|
||||
ShareLabels bool `json:"-" gorm:"default:false; type:bool"`
|
||||
IsAdmin bool `json:"-" gorm:"default:false; type:bool"`
|
||||
HasData bool `json:"-" gorm:"default:false; type:bool"`
|
||||
WakatimeApiKey string `json:"-"` // for relay middleware and imports
|
||||
WakatimeApiUrl string `json:"-"` // for relay middleware and imports
|
||||
ResetToken string `json:"-"`
|
||||
ReportsWeekly bool `json:"-" gorm:"default:false; type:bool"`
|
||||
PublicLeaderboard bool `json:"-" gorm:"default:false; type:bool"`
|
||||
SubscribedUntil *CustomTime `json:"-" gorm:"type:timestamp" swaggertype:"string" format:"date" example:"2006-01-02 15:04:05.000"`
|
||||
SubscriptionRenewal *CustomTime `json:"-" gorm:"type:timestamp" swaggertype:"string" format:"date" example:"2006-01-02 15:04:05.000"`
|
||||
StripeCustomerId string `json:"-"`
|
||||
}
|
||||
|
||||
type Login struct {
|
||||
|
@ -278,14 +278,17 @@ func (h *SubscriptionHandler) handleSubscriptionEvent(subscription *stripe.Subsc
|
||||
if user.SubscribedUntil == nil || !user.SubscribedUntil.T().Equal(until.T()) {
|
||||
hasSubscribed = true
|
||||
user.SubscribedUntil = &until
|
||||
user.SubscriptionRenewal = &until
|
||||
logbuch.Info("user %s got active subscription %s until %v", user.ID, subscription.ID, user.SubscribedUntil)
|
||||
}
|
||||
|
||||
if cancelAt := time.Unix(subscription.CancelAt, 0); !cancelAt.IsZero() && cancelAt.After(time.Now()) {
|
||||
user.SubscriptionRenewal = nil
|
||||
logbuch.Info("user %s chose to cancel subscription %s by %v", user.ID, subscription.ID, cancelAt)
|
||||
}
|
||||
case "canceled", "unpaid":
|
||||
case "canceled", "unpaid", "incomplete_expired":
|
||||
user.SubscribedUntil = nil
|
||||
user.SubscriptionRenewal = nil
|
||||
logbuch.Info("user %s's subscription %s got canceled, because of status update to '%s'", user.ID, subscription.ID, subscription.Status)
|
||||
default:
|
||||
logbuch.Info("got subscription (%s) status update to '%s' for user '%s'", subscription.ID, subscription.Status, user.ID)
|
||||
|
@ -180,6 +180,7 @@ func (srv *MiscService) ComputeOldestHeartbeats() {
|
||||
// - Subscriptions are enabled on the server (aka. users can do something about their old data getting cleaned up)
|
||||
// - User has an e-mail address configured
|
||||
// - User's subscription has expired or is about to expire soon
|
||||
// - User doesn't have upcoming auto-renewal (i.e. chose to cancel at some date in the future)
|
||||
// - The user has gotten no such e-mail before recently
|
||||
// Note: only one mail will be sent for either "expired" or "about to expire" state.
|
||||
func (srv *MiscService) NotifyExpiringSubscription() {
|
||||
@ -187,6 +188,7 @@ func (srv *MiscService) NotifyExpiringSubscription() {
|
||||
return
|
||||
}
|
||||
|
||||
now := time.Now()
|
||||
logbuch.Info("notifying users about soon to expire subscriptions")
|
||||
|
||||
users, err := srv.userService.GetAll()
|
||||
@ -210,10 +212,22 @@ func (srv *MiscService) NotifyExpiringSubscription() {
|
||||
config.Log().Warn("invalid state: user '%s' has active subscription but no e-mail address set", u.ID)
|
||||
}
|
||||
|
||||
var alreadySent bool
|
||||
if kvs, ok := subscriptionReminders[u.ID]; ok && len(kvs) > 0 {
|
||||
// don't send again while subscription hasn't had chance to have been renewed
|
||||
if sendDate, err := time.Parse(time.RFC822Z, kvs[0].Value); err == nil && now.Sub(sendDate) <= notifyBeforeSubscriptionExpiry {
|
||||
alreadySent = true
|
||||
} else if err != nil {
|
||||
config.Log().Error("failed to parse date for last sent subscription notification mail for user '%s', %v", u.ID, err)
|
||||
alreadySent = true
|
||||
}
|
||||
}
|
||||
|
||||
// skip users without e-mail address
|
||||
// skip users who already received a notification before
|
||||
// skip users who either never had a subscription before or intentionally deleted it
|
||||
if _, ok := subscriptionReminders[u.ID]; ok || u.Email == "" || u.SubscribedUntil == nil {
|
||||
// skip users who have upcoming auto-renewal (everyone except users who chose to cancel subscription at later date)
|
||||
if alreadySent || u.Email == "" || u.SubscribedUntil == nil || (u.SubscriptionRenewal != nil && u.SubscriptionRenewal.T().After(now)) {
|
||||
continue
|
||||
}
|
||||
|
||||
|
@ -729,7 +729,12 @@
|
||||
<span class="font-semibold text-gray-300">Subscription status:</span>
|
||||
<span class="text-gray-600 ml-1 text-sm">
|
||||
{{ if .User.HasActiveSubscription }}
|
||||
<span class="font-semibold text-green-500 text-base">Active</span> (until {{ .User.SubscribedUntil.T | date }})
|
||||
<span class="font-semibold text-green-500 text-base">Active</span>
|
||||
{{ if .User.SubscriptionRenewal }}
|
||||
(automatically renews at {{ .User.SubscriptionRenewal.T | date }})
|
||||
{{ else }}
|
||||
(until {{ .User.SubscribedUntil.T | date }})
|
||||
{{ end }}
|
||||
{{ else }}
|
||||
<span class="font-semibold text-red-500 text-base">Inactive</span>
|
||||
{{ end }}
|
||||
@ -746,7 +751,7 @@
|
||||
</form>
|
||||
{{ else }}
|
||||
<form action="subscription/portal" method="post" class="mt-8 mb-8" id="form-subscription-portal">
|
||||
<button type="submit" class="btn-danger">Cancel subscription</button>
|
||||
<button type="submit" class="btn-primary">Manage subscription</button>
|
||||
</form>
|
||||
{{ end }}
|
||||
</div>
|
||||
|
Loading…
Reference in New Issue
Block a user