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

View File

@ -71,7 +71,7 @@ func (app *appContext) SetOmbiProfile(gc *gin.Context) {
var req ombiUser
gc.BindJSON(&req)
profileName := gc.Param("profile")
profile, ok := app.storage.profiles[profileName]
profile, ok := app.storage.GetProfileKey(profileName)
if !ok {
respondBool(400, false, gc)
return
@ -83,12 +83,7 @@ func (app *appContext) SetOmbiProfile(gc *gin.Context) {
return
}
profile.Ombi = template
app.storage.profiles[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
}
app.storage.SetProfileKey(profileName, profile)
respondBool(204, true, gc)
}
@ -103,17 +98,12 @@ func (app *appContext) SetOmbiProfile(gc *gin.Context) {
// @tags Ombi
func (app *appContext) DeleteOmbiProfile(gc *gin.Context) {
profileName := gc.Param("profile")
profile, ok := app.storage.profiles[profileName]
profile, ok := app.storage.GetProfileKey(profileName)
if !ok {
respondBool(400, false, gc)
return
}
profile.Ombi = nil
app.storage.profiles[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
}
app.storage.SetProfileKey(profileName, profile)
respondBool(204, true, gc)
}

View File

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

View File

@ -269,7 +269,6 @@ func (app *appContext) newUser(req newUserDTO, confirmed bool) (f errorFunc, suc
success = false
return
}
app.storage.loadProfiles()
invite, _ := app.storage.GetInvitesKey(req.Code)
app.checkInvite(req.Code, true, req.Username)
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 != "" {
app.debug.Printf("Applying settings from profile \"%s\"", invite.Profile)
var ok bool
profile, ok = app.storage.profiles[invite.Profile]
profile, ok = app.storage.GetProfileKey(invite.Profile)
if !ok {
profile = app.storage.profiles["Default"]
profile = app.storage.GetDefaultProfile()
}
if profile.Policy.BlockedTags != nil {
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 ombi map[string]interface{}
if req.From == "profile" {
app.storage.loadProfiles()
// 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)
respond(500, "Couldn't find profile", gc)
return
}
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)
respond(500, "No homescreen template available", gc)
return
}
configuration = app.storage.profiles[req.Profile].Configuration
displayprefs = app.storage.profiles[req.Profile].Displayprefs
configuration = profile.Configuration
displayprefs = profile.Displayprefs
}
policy = app.storage.profiles[req.Profile].Policy
policy = profile.Policy
if app.config.Section("ombi").Key("enabled").MustBool(false) {
profile := app.storage.profiles[req.Profile]
if profile.Ombi != nil && len(profile.Ombi) != 0 {
ombi = profile.Ombi
}

View File

@ -79,7 +79,7 @@
"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.",
"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",
"userProfilesLibraries": "Libraries",
"addProfile": "Add Profile",

View File

@ -236,6 +236,11 @@ func migrateToBadger(app *appContext) {
for k, v := range app.storage.deprecatedUserExpiries {
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.

View File

@ -312,6 +312,63 @@ func (st *Storage) DeleteUserExpiryKey(k string) {
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 {
JellyfinID string `badgerhold:"key"`
ChatID int64 `badgerhold:"index"`
@ -366,7 +423,8 @@ type userPageContent struct {
// timePattern: %Y-%m-%dT%H:%M:%S.%f
type Profile struct {
Admin bool `json:"admin,omitempty"`
Name string `badgerhold:"key"`
Admin bool `json:"admin,omitempty" badgerhold:"index"`
LibraryAccess string `json:"libraries,omitempty"`
FromUser string `json:"fromUser,omitempty"`
Policy mediabrowser.Policy `json:"policy,omitempty"`