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 @@
+