1
0
mirror of https://github.com/hrfee/jfa-go.git synced 2024-12-22 17:10:10 +00:00

db: migrate user profiles

This commit is contained in:
Harvey Tindall 2023-06-24 21:29:16 +01:00
parent 63948a6de0
commit a735e4ff29
Signed by: hrfee
GPG Key ID: BBC65952848FB1A2
7 changed files with 105 additions and 61 deletions

View File

@ -10,6 +10,7 @@ import (
"github.com/gin-gonic/gin" "github.com/gin-gonic/gin"
"github.com/itchyny/timefmt-go" "github.com/itchyny/timefmt-go"
"github.com/lithammer/shortuuid/v3" "github.com/lithammer/shortuuid/v3"
"github.com/timshannon/badgerhold/v4"
) )
func (app *appContext) checkInvites() { func (app *appContext) checkInvites() {
@ -202,7 +203,7 @@ func (app *appContext) GenerateInvite(gc *gin.Context) {
} }
} }
if req.Profile != "" { if req.Profile != "" {
if _, ok := app.storage.profiles[req.Profile]; ok { if _, ok := app.storage.GetProfileKey(req.Profile); ok {
invite.Profile = req.Profile invite.Profile = req.Profile
} else { } else {
invite.Profile = "Default" invite.Profile = "Default"
@ -283,17 +284,18 @@ func (app *appContext) GetInvites(gc *gin.Context) {
} }
invites = append(invites, invite) invites = append(invites, invite)
} }
profiles := make([]string, len(app.storage.profiles)) fullProfileList := app.storage.GetProfiles()
if len(app.storage.profiles) != 0 { profiles := make([]string, len(fullProfileList))
profiles[0] = app.storage.defaultProfile if len(profiles) != 0 {
defaultProfile := app.storage.GetDefaultProfile()
profiles[0] = defaultProfile.Name
i := 1 i := 1
if len(app.storage.profiles) > 1 { if len(fullProfileList) > 1 {
for p := range app.storage.profiles { app.storage.db.ForEach(badgerhold.Where("Name").Ne(profiles[0]), func(p *Profile) error {
if p != app.storage.defaultProfile { profiles[i] = p.Name
profiles[i] = p
i++ i++
} return nil
} })
} }
} }
resp := getInvitesDTO{ resp := getInvitesDTO{
@ -316,7 +318,7 @@ func (app *appContext) SetProfile(gc *gin.Context) {
gc.BindJSON(&req) gc.BindJSON(&req)
app.debug.Printf("%s: Setting profile to \"%s\"", req.Invite, req.Profile) app.debug.Printf("%s: Setting profile to \"%s\"", req.Invite, req.Profile)
// "" means "Don't apply profile" // "" means "Don't apply profile"
if _, ok := app.storage.profiles[req.Profile]; !ok && req.Profile != "" { if _, ok := app.storage.GetProfileKey(req.Profile); !ok && req.Profile != "" {
app.err.Printf("%s: Profile \"%s\" not found", req.Invite, req.Profile) app.err.Printf("%s: Profile \"%s\" not found", req.Invite, req.Profile)
respond(500, "Profile not found", gc) respond(500, "Profile not found", gc)
return return

View File

@ -71,7 +71,7 @@ func (app *appContext) SetOmbiProfile(gc *gin.Context) {
var req ombiUser var req ombiUser
gc.BindJSON(&req) gc.BindJSON(&req)
profileName := gc.Param("profile") profileName := gc.Param("profile")
profile, ok := app.storage.profiles[profileName] profile, ok := app.storage.GetProfileKey(profileName)
if !ok { if !ok {
respondBool(400, false, gc) respondBool(400, false, gc)
return return
@ -83,12 +83,7 @@ func (app *appContext) SetOmbiProfile(gc *gin.Context) {
return return
} }
profile.Ombi = template profile.Ombi = template
app.storage.profiles[profileName] = profile app.storage.SetProfileKey(profileName, profile)
if err := app.storage.storeProfiles(); err != nil {
respond(500, "Failed to store profile", gc)
app.err.Printf("Failed to store profiles: %v", err)
return
}
respondBool(204, true, gc) respondBool(204, true, gc)
} }
@ -103,17 +98,12 @@ func (app *appContext) SetOmbiProfile(gc *gin.Context) {
// @tags Ombi // @tags Ombi
func (app *appContext) DeleteOmbiProfile(gc *gin.Context) { func (app *appContext) DeleteOmbiProfile(gc *gin.Context) {
profileName := gc.Param("profile") profileName := gc.Param("profile")
profile, ok := app.storage.profiles[profileName] profile, ok := app.storage.GetProfileKey(profileName)
if !ok { if !ok {
respondBool(400, false, gc) respondBool(400, false, gc)
return return
} }
profile.Ombi = nil profile.Ombi = nil
app.storage.profiles[profileName] = profile app.storage.SetProfileKey(profileName, profile)
if err := app.storage.storeProfiles(); err != nil {
respond(500, "Failed to store profile", gc)
app.err.Printf("Failed to store profiles: %v", err)
return
}
respondBool(204, true, gc) respondBool(204, true, gc)
} }

View File

@ -4,6 +4,7 @@ import (
"time" "time"
"github.com/gin-gonic/gin" "github.com/gin-gonic/gin"
"github.com/timshannon/badgerhold/v4"
) )
// @Summary Get a list of profiles // @Summary Get a list of profiles
@ -13,14 +14,13 @@ import (
// @Security Bearer // @Security Bearer
// @tags Profiles & Settings // @tags Profiles & Settings
func (app *appContext) GetProfiles(gc *gin.Context) { func (app *appContext) GetProfiles(gc *gin.Context) {
app.storage.loadProfiles()
app.debug.Println("Profiles requested") app.debug.Println("Profiles requested")
out := getProfilesDTO{ out := getProfilesDTO{
DefaultProfile: app.storage.defaultProfile, DefaultProfile: app.storage.GetDefaultProfile().Name,
Profiles: map[string]profileDTO{}, Profiles: map[string]profileDTO{},
} }
for name, p := range app.storage.profiles { for _, p := range app.storage.GetProfiles() {
out.Profiles[name] = profileDTO{ out.Profiles[p.Name] = profileDTO{
Admin: p.Admin, Admin: p.Admin,
LibraryAccess: p.LibraryAccess, LibraryAccess: p.LibraryAccess,
FromUser: p.FromUser, FromUser: p.FromUser,
@ -42,20 +42,20 @@ func (app *appContext) SetDefaultProfile(gc *gin.Context) {
req := profileChangeDTO{} req := profileChangeDTO{}
gc.BindJSON(&req) gc.BindJSON(&req)
app.info.Printf("Setting default profile to \"%s\"", req.Name) app.info.Printf("Setting default profile to \"%s\"", req.Name)
if _, ok := app.storage.profiles[req.Name]; !ok { if _, ok := app.storage.GetProfileKey(req.Name); !ok {
app.err.Printf("Profile not found: \"%s\"", req.Name) app.err.Printf("Profile not found: \"%s\"", req.Name)
respond(500, "Profile not found", gc) respond(500, "Profile not found", gc)
return return
} }
for name, profile := range app.storage.profiles { app.storage.db.ForEach(&badgerhold.Query{}, func(profile *Profile) error {
if name == req.Name { if profile.Name == req.Name {
profile.Admin = true profile.Default = true
app.storage.profiles[name] = profile
} else { } else {
profile.Admin = false profile.Default = false
} }
} app.storage.SetProfileKey(profile.Name, *profile)
app.storage.defaultProfile = req.Name return nil
})
respondBool(200, true, gc) respondBool(200, true, gc)
} }
@ -92,10 +92,7 @@ func (app *appContext) CreateProfile(gc *gin.Context) {
return return
} }
} }
app.storage.loadProfiles() app.storage.SetProfileKey(req.Name, profile)
app.storage.profiles[req.Name] = profile
app.storage.storeProfiles()
app.storage.loadProfiles()
respondBool(200, true, gc) respondBool(200, true, gc)
} }
@ -110,12 +107,6 @@ func (app *appContext) DeleteProfile(gc *gin.Context) {
req := profileChangeDTO{} req := profileChangeDTO{}
gc.BindJSON(&req) gc.BindJSON(&req)
name := req.Name name := req.Name
if _, ok := app.storage.profiles[name]; ok { app.storage.DeleteProfileKey(name)
if app.storage.defaultProfile == name {
app.storage.defaultProfile = ""
}
delete(app.storage.profiles, name)
}
app.storage.storeProfiles()
respondBool(200, true, gc) respondBool(200, true, gc)
} }

View File

@ -269,7 +269,6 @@ func (app *appContext) newUser(req newUserDTO, confirmed bool) (f errorFunc, suc
success = false success = false
return return
} }
app.storage.loadProfiles()
invite, _ := app.storage.GetInvitesKey(req.Code) invite, _ := app.storage.GetInvitesKey(req.Code)
app.checkInvite(req.Code, true, req.Username) app.checkInvite(req.Code, true, req.Username)
if emailEnabled && app.config.Section("notifications").Key("enabled").MustBool(false) { if emailEnabled && app.config.Section("notifications").Key("enabled").MustBool(false) {
@ -301,9 +300,9 @@ func (app *appContext) newUser(req newUserDTO, confirmed bool) (f errorFunc, suc
if invite.Profile != "" { if invite.Profile != "" {
app.debug.Printf("Applying settings from profile \"%s\"", invite.Profile) app.debug.Printf("Applying settings from profile \"%s\"", invite.Profile)
var ok bool var ok bool
profile, ok = app.storage.profiles[invite.Profile] profile, ok = app.storage.GetProfileKey(invite.Profile)
if !ok { if !ok {
profile = app.storage.profiles["Default"] profile = app.storage.GetDefaultProfile()
} }
if profile.Policy.BlockedTags != nil { if profile.Policy.BlockedTags != nil {
app.debug.Printf("Applying policy from profile \"%s\"", invite.Profile) app.debug.Printf("Applying policy from profile \"%s\"", invite.Profile)
@ -1008,25 +1007,24 @@ func (app *appContext) ApplySettings(gc *gin.Context) {
var displayprefs map[string]interface{} var displayprefs map[string]interface{}
var ombi map[string]interface{} var ombi map[string]interface{}
if req.From == "profile" { if req.From == "profile" {
app.storage.loadProfiles()
// Check profile exists & isn't empty // Check profile exists & isn't empty
if _, ok := app.storage.profiles[req.Profile]; !ok || app.storage.profiles[req.Profile].Policy.BlockedTags == nil { profile, ok := app.storage.GetProfileKey(req.Profile)
if !ok || profile.Policy.BlockedTags == nil {
app.err.Printf("Couldn't find profile \"%s\" or profile was empty", req.Profile) app.err.Printf("Couldn't find profile \"%s\" or profile was empty", req.Profile)
respond(500, "Couldn't find profile", gc) respond(500, "Couldn't find profile", gc)
return return
} }
if req.Homescreen { if req.Homescreen {
if app.storage.profiles[req.Profile].Configuration.GroupedFolders == nil || len(app.storage.profiles[req.Profile].Displayprefs) == 0 { if profile.Configuration.GroupedFolders == nil || len(profile.Displayprefs) == 0 {
app.err.Printf("No homescreen saved in profile \"%s\"", req.Profile) app.err.Printf("No homescreen saved in profile \"%s\"", req.Profile)
respond(500, "No homescreen template available", gc) respond(500, "No homescreen template available", gc)
return return
} }
configuration = app.storage.profiles[req.Profile].Configuration configuration = profile.Configuration
displayprefs = app.storage.profiles[req.Profile].Displayprefs displayprefs = profile.Displayprefs
} }
policy = app.storage.profiles[req.Profile].Policy policy = profile.Policy
if app.config.Section("ombi").Key("enabled").MustBool(false) { if app.config.Section("ombi").Key("enabled").MustBool(false) {
profile := app.storage.profiles[req.Profile]
if profile.Ombi != nil && len(profile.Ombi) != 0 { if profile.Ombi != nil && len(profile.Ombi) != 0 {
ombi = profile.Ombi ombi = profile.Ombi
} }

View File

@ -79,7 +79,7 @@
"ombiProfile": "Ombi user profile", "ombiProfile": "Ombi user profile",
"ombiUserDefaultsDescription": "Create an Ombi user and configure it, then select it below. It's settings/permissions will be stored and applied to new Ombi users created by jfa-go when this profile is selected.", "ombiUserDefaultsDescription": "Create an Ombi user and configure it, then select it below. It's settings/permissions will be stored and applied to new Ombi users created by jfa-go when this profile is selected.",
"userProfiles": "User Profiles", "userProfiles": "User Profiles",
"userProfilesDescription": "Profiles are applied to users when they create an account. A profile include library access rights and homescreen layout.", "userProfilesDescription": "Profiles are applied to users when they create an account. A profile includes library access rights and homescreen layout.",
"userProfilesIsDefault": "Default", "userProfilesIsDefault": "Default",
"userProfilesLibraries": "Libraries", "userProfilesLibraries": "Libraries",
"addProfile": "Add Profile", "addProfile": "Add Profile",

View File

@ -236,6 +236,11 @@ func migrateToBadger(app *appContext) {
for k, v := range app.storage.deprecatedUserExpiries { for k, v := range app.storage.deprecatedUserExpiries {
app.storage.SetUserExpiryKey(k, UserExpiry{Expiry: v}) app.storage.SetUserExpiryKey(k, UserExpiry{Expiry: v})
} }
app.storage.loadProfiles()
for k, v := range app.storage.profiles {
app.storage.SetProfileKey(k, v)
}
} }
// Migrate between hyphenated & non-hyphenated user IDs. Doesn't seem to happen anymore, so disabled. // Migrate between hyphenated & non-hyphenated user IDs. Doesn't seem to happen anymore, so disabled.

View File

@ -312,6 +312,63 @@ func (st *Storage) DeleteUserExpiryKey(k string) {
st.db.Delete(k, UserExpiry{}) st.db.Delete(k, UserExpiry{})
} }
// GetProfiles returns a copy of the store.
func (st *Storage) GetProfiles() []Profile {
result := []Profile{}
err := st.db.Find(&result, &badgerhold.Query{})
if err != nil {
// fmt.Printf("Failed to find profiles: %v\n", err)
}
return result
}
// GetProfileKey returns the value stored in the store's key.
func (st *Storage) GetProfileKey(k string) (Profile, bool) {
result := Profile{}
err := st.db.Get(k, &result)
ok := true
if err != nil {
// fmt.Printf("Failed to find profile: %v\n", err)
ok = false
}
return result, ok
}
// SetProfileKey stores value v in key k.
func (st *Storage) SetProfileKey(k string, v Profile) {
v.Name = k
v.Admin = v.Policy.IsAdministrator
if v.Policy.EnabledFolders != nil {
if len(v.Policy.EnabledFolders) == 0 {
v.LibraryAccess = "All"
} else {
v.LibraryAccess = strconv.Itoa(len(v.Policy.EnabledFolders))
}
}
if v.FromUser == "" {
v.FromUser = "Unknown"
}
err := st.db.Upsert(k, v)
if err != nil {
// fmt.Printf("Failed to set profile: %v\n", err)
}
}
// DeleteProfileKey deletes value at key k.
func (st *Storage) DeleteProfileKey(k string) {
st.db.Delete(k, Profile{})
}
// GetDefaultProfile returns the first profile set as default, or anything available if there isn't one.
func (st *Storage) GetDefaultProfile() Profile {
defaultProfile := Profile{}
err := st.db.FindOne(&defaultProfile, badgerhold.Where("Default").Eq(true))
if err != nil {
st.db.FindOne(&defaultProfile, &badgerhold.Query{})
}
return defaultProfile
}
type TelegramUser struct { type TelegramUser struct {
JellyfinID string `badgerhold:"key"` JellyfinID string `badgerhold:"key"`
ChatID int64 `badgerhold:"index"` ChatID int64 `badgerhold:"index"`
@ -366,7 +423,8 @@ type userPageContent struct {
// timePattern: %Y-%m-%dT%H:%M:%S.%f // timePattern: %Y-%m-%dT%H:%M:%S.%f
type Profile struct { type Profile struct {
Admin bool `json:"admin,omitempty"` Name string `badgerhold:"key"`
Admin bool `json:"admin,omitempty" badgerhold:"index"`
LibraryAccess string `json:"libraries,omitempty"` LibraryAccess string `json:"libraries,omitempty"`
FromUser string `json:"fromUser,omitempty"` FromUser string `json:"fromUser,omitempty"`
Policy mediabrowser.Policy `json:"policy,omitempty"` Policy mediabrowser.Policy `json:"policy,omitempty"`