From a66c522b73f7746b6632a0e24e3556a9248ed440 Mon Sep 17 00:00:00 2001 From: Harvey Tindall Date: Fri, 10 Nov 2023 15:07:29 +0000 Subject: [PATCH] referrals: add "use expiry" option adds an option when enabling referrals to use the duration of the source invited (i.e., months, days, hours) for the referral invite. If enabled, the user won't be able to make a new referral link after it expires. For referrals enabled for new users via a profile, the clock starts ticking as soon as the account is created. --- api-invites.go | 19 ++++++++++++++++++- api-profiles.go | 12 ++++++++++-- api-userpage.go | 21 +++++++++++++++++++-- api-users.go | 26 +++++++++++++++++++++++--- html/admin.html | 10 ++++++++++ html/user.html | 2 +- lang/admin/en-us.json | 2 ++ lang/form/en-us.json | 1 + models.go | 3 ++- router.go | 4 ++-- storage.go | 18 +++++++++--------- ts/modules/accounts.ts | 4 +++- ts/modules/profiles.ts | 4 +++- ts/user.ts | 15 +++++++++++++++ 14 files changed, 118 insertions(+), 23 deletions(-) diff --git a/api-invites.go b/api-invites.go index 248eab8..eb6e810 100644 --- a/api-invites.go +++ b/api-invites.go @@ -45,14 +45,24 @@ func (app *appContext) checkInvites() { app.storage.SetInvitesKey(data.Code, data) } - if data.IsReferral { + if data.IsReferral && (!data.UseReferralExpiry || data.ReferrerJellyfinID == "") { continue } expiry := data.ValidTill if !currentTime.After(expiry) { continue } + app.debug.Printf("Housekeeping: Deleting old invite %s", data.Code) + + // Disable referrals for the user if UseReferralExpiry is enabled, so no new ones are made. + if data.IsReferral && data.UseReferralExpiry && data.ReferrerJellyfinID != "" { + user, ok := app.storage.GetEmailsKey(data.ReferrerJellyfinID) + if ok { + user.ReferralTemplateKey = "" + app.storage.SetEmailsKey(data.ReferrerJellyfinID, user) + } + } notify := data.Notify if emailEnabled && app.config.Section("notifications").Key("enabled").MustBool(false) && len(notify) != 0 { app.debug.Printf("%s: Expiry notification", data.Code) @@ -136,6 +146,13 @@ func (app *appContext) checkInvite(code string, used bool, username string) bool } wait.Wait() } + if inv.IsReferral && inv.ReferrerJellyfinID != "" && inv.UseReferralExpiry { + user, ok := app.storage.GetEmailsKey(inv.ReferrerJellyfinID) + if ok { + user.ReferralTemplateKey = "" + app.storage.SetEmailsKey(inv.ReferrerJellyfinID, user) + } + } match = false app.storage.DeleteInvitesKey(code) app.storage.SetActivityKey(shortuuid.New(), Activity{ diff --git a/api-profiles.go b/api-profiles.go index b1594b2..ccbac7d 100644 --- a/api-profiles.go +++ b/api-profiles.go @@ -130,15 +130,17 @@ func (app *appContext) DeleteProfile(gc *gin.Context) { // @Produce json // @Param profile path string true "name of profile to enable referrals for." // @Param invite path string true "invite code to create referral template from." +// @Param useExpiry path string true "with-expiry or none." // @Success 200 {object} boolResponse // @Failure 400 {object} stringResponse // @Failure 500 {object} stringResponse -// @Router /profiles/referral/{profile}/{invite} [post] +// @Router /profiles/referral/{profile}/{invite}/{useExpiry} [post] // @Security Bearer // @tags Profiles & Settings func (app *appContext) EnableReferralForProfile(gc *gin.Context) { profileName := gc.Param("profile") invCode := gc.Param("invite") + useExpiry := gc.Param("useExpiry") == "with-expiry" inv, ok := app.storage.GetInvitesKey(invCode) if !ok { respond(400, "Invalid invite code", gc) @@ -154,9 +156,15 @@ func (app *appContext) EnableReferralForProfile(gc *gin.Context) { // Generate new code for referral template inv.Code = GenerateInviteCode() + expiryDelta := inv.ValidTill.Sub(inv.Created) inv.Created = time.Now() - inv.ValidTill = inv.Created.Add(REFERRAL_EXPIRY_DAYS * 24 * time.Hour) + if useExpiry { + inv.ValidTill = inv.Created.Add(expiryDelta) + } else { + inv.ValidTill = inv.Created.Add(REFERRAL_EXPIRY_DAYS * 24 * time.Hour) + } inv.IsReferral = true + inv.UseReferralExpiry = useExpiry // Since this is a template for multiple users, ReferrerJellyfinID is not set. // inv.ReferrerJellyfinID = ... diff --git a/api-userpage.go b/api-userpage.go index 97684ec..f519524 100644 --- a/api-userpage.go +++ b/api-userpage.go @@ -746,21 +746,37 @@ func (app *appContext) GetMyReferral(gc *gin.Context) { // Since this key is shared between users in a profile, we make a copy. user, ok := app.storage.GetEmailsKey(gc.GetString("jfId")) err = app.storage.db.Get(user.ReferralTemplateKey, &inv) - if !ok || err != nil { + if !ok || err != nil || user.ReferralTemplateKey == "" { app.debug.Printf("Ignoring referral request, couldn't find template.") respondBool(400, false, gc) return } inv.Code = GenerateInviteCode() + expiryDelta := inv.ValidTill.Sub(inv.Created) inv.Created = time.Now() - inv.ValidTill = inv.Created.Add(REFERRAL_EXPIRY_DAYS * 24 * time.Hour) + if inv.UseReferralExpiry { + inv.ValidTill = inv.Created.Add(expiryDelta) + } else { + inv.ValidTill = inv.Created.Add(REFERRAL_EXPIRY_DAYS * 24 * time.Hour) + } inv.IsReferral = true inv.ReferrerJellyfinID = gc.GetString("jfId") app.storage.SetInvitesKey(inv.Code, inv) } else if time.Now().After(inv.ValidTill) { // 3. We found an invite for us, but it's expired. // We delete it from storage, and put it back with a fresh code and expiry. + // If UseReferralExpiry is enabled, we delete it and return nothing. app.storage.DeleteInvitesKey(inv.Code) + if inv.UseReferralExpiry { + user, ok := app.storage.GetEmailsKey(gc.GetString("jfId")) + if ok { + user.ReferralTemplateKey = "" + app.storage.SetEmailsKey(gc.GetString("jfId"), user) + } + app.debug.Printf("Ignoring referral request, expired.") + respondBool(400, false, gc) + return + } inv.Code = GenerateInviteCode() inv.Created = time.Now() inv.ValidTill = inv.Created.Add(REFERRAL_EXPIRY_DAYS * 24 * time.Hour) @@ -771,5 +787,6 @@ func (app *appContext) GetMyReferral(gc *gin.Context) { RemainingUses: inv.RemainingUses, NoLimit: inv.NoLimit, Expiry: inv.ValidTill.Unix(), + UseExpiry: inv.UseReferralExpiry, }) } diff --git a/api-users.go b/api-users.go index e8dd451..d735215 100644 --- a/api-users.go +++ b/api-users.go @@ -367,6 +367,19 @@ func (app *appContext) newUser(req newUserDTO, confirmed bool) (f errorFunc, suc emailStore.ReferralTemplateKey = profile.ReferralTemplateKey // Store here, just incase email are disabled (whether this is even possible, i don't know) app.storage.SetEmailsKey(id, emailStore) + + // If UseReferralExpiry is enabled, create the ref now so the clock starts ticking + refInv := Invite{} + err = app.storage.db.Get(profile.ReferralTemplateKey, &refInv) + if refInv.UseReferralExpiry { + refInv.Code = GenerateInviteCode() + expiryDelta := refInv.ValidTill.Sub(refInv.Created) + refInv.Created = time.Now() + refInv.ValidTill = refInv.Created.Add(expiryDelta) + refInv.IsReferral = true + refInv.ReferrerJellyfinID = id + app.storage.SetInvitesKey(refInv.Code, refInv) + } } } // if app.config.Section("password_resets").Key("enabled").MustBool(false) { @@ -729,10 +742,11 @@ func (app *appContext) ExtendExpiry(gc *gin.Context) { // @Param EnableDisableReferralDTO body EnableDisableReferralDTO true "List of users" // @Param mode path string true "mode of template sourcing from 'invite' or 'profile'." // @Param source path string true "invite code or profile name, depending on what mode is." +// @Param useExpiry path string true "with-expiry or none." // @Success 200 {object} boolResponse // @Failure 400 {object} boolResponse // @Failure 500 {object} boolResponse -// @Router /users/referral/{mode}/{source} [post] +// @Router /users/referral/{mode}/{source}/{useExpiry} [post] // @Security Bearer // @tags Users func (app *appContext) EnableReferralForUsers(gc *gin.Context) { @@ -740,7 +754,7 @@ func (app *appContext) EnableReferralForUsers(gc *gin.Context) { gc.BindJSON(&req) mode := gc.Param("mode") source := gc.Param("source") - + useExpiry := gc.Param("useExpiry") == "with-expiry" baseInv := Invite{} if mode == "profile" { profile, ok := app.storage.GetProfileKey(source) @@ -768,10 +782,16 @@ func (app *appContext) EnableReferralForUsers(gc *gin.Context) { // 2. Generate referral invite. inv := baseInv inv.Code = GenerateInviteCode() + expiryDelta := inv.ValidTill.Sub(inv.Created) inv.Created = time.Now() - inv.ValidTill = inv.Created.Add(REFERRAL_EXPIRY_DAYS * 24 * time.Hour) + if useExpiry { + inv.ValidTill = inv.Created.Add(expiryDelta) + } else { + inv.ValidTill = inv.Created.Add(REFERRAL_EXPIRY_DAYS * 24 * time.Hour) + } inv.IsReferral = true inv.ReferrerJellyfinID = u + inv.UseReferralExpiry = useExpiry app.storage.SetInvitesKey(inv.Code, inv) } } diff --git a/html/admin.html b/html/admin.html index df6cfdf..5a519cb 100644 --- a/html/admin.html +++ b/html/admin.html @@ -130,6 +130,11 @@
+