From 9e5034ebabd5dacc3ef8cdbb82e1c35b9de9fb2a Mon Sep 17 00:00:00 2001 From: Harvey Tindall Date: Wed, 6 Sep 2023 22:00:44 +0100 Subject: [PATCH] referrals: enable referral for users & profiles Enabling for individual users works, as does adding a template to a profile. Removing/Disabling for both needs to be completed. --- api-profiles.go | 82 +++++++++++++++++++++++++++++++++++--- api-userpage.go | 2 +- api-users.go | 1 + config/config-base.json | 4 +- html/admin.html | 21 +++++++++- lang/admin/en-us.json | 4 +- lang/common/en-us.json | 3 +- models.go | 12 +++--- router.go | 2 + ts/admin.ts | 1 + ts/modules/accounts.ts | 35 +++++++++++------ ts/modules/profiles.ts | 87 ++++++++++++++++++++++++++++++++++++++++- ts/typings/d.ts | 1 + 13 files changed, 224 insertions(+), 31 deletions(-) diff --git a/api-profiles.go b/api-profiles.go index b118838..5220782 100644 --- a/api-profiles.go +++ b/api-profiles.go @@ -1,9 +1,11 @@ package main import ( + "strconv" "time" "github.com/gin-gonic/gin" + "github.com/lithammer/shortuuid/v3" "github.com/timshannon/badgerhold/v4" ) @@ -19,13 +21,23 @@ func (app *appContext) GetProfiles(gc *gin.Context) { DefaultProfile: app.storage.GetDefaultProfile().Name, Profiles: map[string]profileDTO{}, } + referralsEnabled := app.config.Section("user_page").Key("referrals").MustBool(false) + baseInv := Invite{} for _, p := range app.storage.GetProfiles() { - out.Profiles[p.Name] = profileDTO{ - Admin: p.Admin, - LibraryAccess: p.LibraryAccess, - FromUser: p.FromUser, - Ombi: p.Ombi != nil, + pdto := profileDTO{ + Admin: p.Admin, + LibraryAccess: p.LibraryAccess, + FromUser: p.FromUser, + Ombi: p.Ombi != nil, + ReferralsEnabled: false, } + if referralsEnabled { + err := app.storage.db.Get(p.ReferralTemplateKey, &baseInv) + if p.ReferralTemplateKey != "" && err == nil { + pdto.ReferralsEnabled = true + } + } + out.Profiles[p.Name] = pdto } gc.JSON(200, out) } @@ -111,3 +123,63 @@ func (app *appContext) DeleteProfile(gc *gin.Context) { app.storage.DeleteProfileKey(name) respondBool(200, true, gc) } + +// @Summary Enable referrals for a profile, sourced from the given invite by its code. +// @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." +// @Success 200 {object} boolResponse +// @Failure 400 {object} stringResponse +// @Failure 500 {object} stringResponse +// @Router /profiles/referral/{profile}/{invite} [post] +// @Security Bearer +// @tags Profiles & Settings +func (app *appContext) EnableReferralForProfile(gc *gin.Context) { + profileName := gc.Param("profile") + invCode := gc.Param("invite") + inv, ok := app.storage.GetInvitesKey(invCode) + if !ok { + respond(400, "Invalid invite code", gc) + app.err.Printf("\"%s\": Failed to enable referrals: invite not found", profileName) + return + } + profile, ok := app.storage.GetProfileKey(profileName) + if !ok { + respond(400, "Invalid profile", gc) + app.err.Printf("\"%s\": Failed to enable referrals: profile not found", profileName) + return + } + + // Generate new code for referral template + inv.Code = shortuuid.New() + // make sure code doesn't begin with number + _, err := strconv.Atoi(string(inv.Code[0])) + for err == nil { + inv.Code = shortuuid.New() + _, err = strconv.Atoi(string(inv.Code[0])) + } + inv.Created = time.Now() + inv.ValidTill = inv.Created.Add(REFERRAL_EXPIRY_DAYS * 24 * time.Hour) + inv.IsReferral = true + // Since this is a template for multiple users, ReferrerJellyfinID is not set. + // inv.ReferrerJellyfinID = ... + + app.storage.SetInvitesKey(inv.Code, inv) + + profile.ReferralTemplateKey = inv.Code + + app.storage.SetProfileKey(profile.Name, profile) + + respondBool(200, true, gc) +} + +// @Summary Disable referrals for a profile, and removes the referral template. no-op if not enabled. +// @Produce json +// @Param profile path string true "name of profile to enable referrals for." +// @Success 200 {object} boolResponse +// @Router /profiles/referral/{profile} [delete] +// @Security Bearer +// @tags Profiles & Settings +func (app *appContext) DisableReferralForProfile(gc *gin.Context) { + respondBool(200, true, gc) +} diff --git a/api-userpage.go b/api-userpage.go index 30f50a6..367448a 100644 --- a/api-userpage.go +++ b/api-userpage.go @@ -646,7 +646,7 @@ func (app *appContext) GetMyReferral(gc *gin.Context) { err := app.storage.db.Find(&inv, badgerhold.Where("ReferrerJellyfinID").Eq(gc.GetString("jfId"))) if err != nil { // 2. Look for a template matching the key found in the user storage - // Since this key is shared between a profile, we make a copy. + // 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 { diff --git a/api-users.go b/api-users.go index 43a9acb..ce5441a 100644 --- a/api-users.go +++ b/api-users.go @@ -657,6 +657,7 @@ func (app *appContext) EnableReferralForUsers(gc *gin.Context) { return } + app.debug.Printf("Found referral template in profile: %+v\n", profile.ReferralTemplateKey) } else if mode == "invite" { // Get the invite, and modify it to turn it into a referral err := app.storage.db.Get(source, &baseInv) diff --git a/config/config-base.json b/config/config-base.json index f567927..dd96956 100644 --- a/config/config-base.json +++ b/config/config-base.json @@ -385,7 +385,7 @@ "enabled": { "name": "Enabled", "required": false, - "requires_restart": false, + "requires_restart": true, "type": "bool", "value": true }, @@ -409,7 +409,7 @@ "referrals": { "name": "User Referrals", "required": false, - "requires_restart": false, + "requires_restart": true, "type": "bool", "value": true, "description": "Users are given their own \"invite\" to send to others." diff --git a/html/admin.html b/html/admin.html index 538cbc6..8eac284 100644 --- a/html/admin.html +++ b/html/admin.html @@ -135,6 +135,20 @@ + {{ end }} {{ .strings.modifySettings }} - {{ .strings.enableReferrals }} + {{ if .referralsEnabled }} + {{ .strings.enableReferrals }} + {{ end }} {{ .strings.extendExpiry }}