mirror of
https://github.com/navidrome/navidrome.git
synced 2026-05-03 06:51:16 +00:00
Merge 53641dae88df31e3bf9b1e7381586ac39e8fc26b into 64a9260174cd7c7c27f2b82c9ebaa4657afaeea6
This commit is contained in:
commit
2f078f38e8
28
cmd/pls.go
28
cmd/pls.go
@ -1,7 +1,6 @@
|
|||||||
package cmd
|
package cmd
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
|
||||||
"encoding/csv"
|
"encoding/csv"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"errors"
|
"errors"
|
||||||
@ -10,11 +9,8 @@ import (
|
|||||||
"strconv"
|
"strconv"
|
||||||
|
|
||||||
"github.com/Masterminds/squirrel"
|
"github.com/Masterminds/squirrel"
|
||||||
"github.com/navidrome/navidrome/core/auth"
|
|
||||||
"github.com/navidrome/navidrome/db"
|
|
||||||
"github.com/navidrome/navidrome/log"
|
"github.com/navidrome/navidrome/log"
|
||||||
"github.com/navidrome/navidrome/model"
|
"github.com/navidrome/navidrome/model"
|
||||||
"github.com/navidrome/navidrome/persistence"
|
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -66,9 +62,7 @@ var (
|
|||||||
)
|
)
|
||||||
|
|
||||||
func runExporter() {
|
func runExporter() {
|
||||||
sqlDB := db.Db()
|
ds, ctx := getContext()
|
||||||
ds := persistence.New(sqlDB)
|
|
||||||
ctx := auth.WithAdminUser(context.Background(), ds)
|
|
||||||
playlist, err := ds.Playlist(ctx).GetWithTracks(playlistID, true, false)
|
playlist, err := ds.Playlist(ctx).GetWithTracks(playlistID, true, false)
|
||||||
if err != nil && !errors.Is(err, model.ErrNotFound) {
|
if err != nil && !errors.Is(err, model.ErrNotFound) {
|
||||||
log.Fatal("Error retrieving playlist", "name", playlistID, err)
|
log.Fatal("Error retrieving playlist", "name", playlistID, err)
|
||||||
@ -105,26 +99,14 @@ func runList() {
|
|||||||
log.Fatal("Invalid output format. Must be one of csv, json", "format", outputFormat)
|
log.Fatal("Invalid output format. Must be one of csv, json", "format", outputFormat)
|
||||||
}
|
}
|
||||||
|
|
||||||
sqlDB := db.Db()
|
ds, ctx := getContext()
|
||||||
ds := persistence.New(sqlDB)
|
|
||||||
ctx := auth.WithAdminUser(context.Background(), ds)
|
|
||||||
|
|
||||||
options := model.QueryOptions{Sort: "owner_name"}
|
options := model.QueryOptions{Sort: "owner_name"}
|
||||||
|
|
||||||
if userID != "" {
|
if userID != "" {
|
||||||
user, err := ds.User(ctx).FindByUsername(userID)
|
user, err := getUser(userID, ds, ctx)
|
||||||
|
if err != nil {
|
||||||
if err != nil && !errors.Is(err, model.ErrNotFound) {
|
log.Fatal(ctx, "Error retrieving user", "username or id", userID)
|
||||||
log.Fatal("Error retrieving user by name", "name", userID, err)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if errors.Is(err, model.ErrNotFound) {
|
|
||||||
user, err = ds.User(ctx).Get(userID)
|
|
||||||
if err != nil {
|
|
||||||
log.Fatal("Error retrieving user by id", "id", userID, err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
options.Filters = squirrel.Eq{"owner_id": user.ID}
|
options.Filters = squirrel.Eq{"owner_id": user.ID}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
445
cmd/user.go
Normal file
445
cmd/user.go
Normal file
@ -0,0 +1,445 @@
|
|||||||
|
package cmd
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/csv"
|
||||||
|
"encoding/json"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
"syscall"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/Masterminds/squirrel"
|
||||||
|
"github.com/navidrome/navidrome/log"
|
||||||
|
"github.com/navidrome/navidrome/model"
|
||||||
|
"github.com/spf13/cobra"
|
||||||
|
"golang.org/x/term"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
email string
|
||||||
|
libraryIds []int
|
||||||
|
name string
|
||||||
|
password string
|
||||||
|
|
||||||
|
removeEmail bool
|
||||||
|
removeName bool
|
||||||
|
setAdmin bool
|
||||||
|
setPassword bool
|
||||||
|
setRegularUser bool
|
||||||
|
)
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
rootCmd.AddCommand(userRoot)
|
||||||
|
|
||||||
|
userCreateCommand.Flags().StringVarP(&userID, "username", "u", "", "username")
|
||||||
|
|
||||||
|
userCreateCommand.Flags().BoolVar(&setPassword, "set-password", false, "If set, the user's new password will be prompted on the CLI")
|
||||||
|
userCreateCommand.Flags().StringVarP(&password, "password", "p", "", "Set the user's password. Note that this will be captured in terminal history")
|
||||||
|
userCreateCommand.MarkFlagsMutuallyExclusive("password", "set-password")
|
||||||
|
|
||||||
|
userCreateCommand.Flags().StringVarP(&email, "email", "e", "", "New user email")
|
||||||
|
userCreateCommand.Flags().IntSliceVar(&libraryIds, "library-ids", []int{}, "Set the user's accessible libraries. If empty, the user can access all libraries. This is incompatible with admin, as admin can always access all libraries")
|
||||||
|
|
||||||
|
userCreateCommand.Flags().BoolVarP(&setAdmin, "admin", "a", false, "If set, make the user an admin. This user will have access to every library")
|
||||||
|
userCreateCommand.Flags().StringVar(&name, "name", "", "New user's name (this is separate from username used to log in)")
|
||||||
|
|
||||||
|
_ = userCreateCommand.MarkFlagRequired("username")
|
||||||
|
userCreateCommand.MarkFlagsOneRequired("password", "set-password")
|
||||||
|
|
||||||
|
userRoot.AddCommand(userCreateCommand)
|
||||||
|
|
||||||
|
userDeleteCommand.Flags().StringVarP(&userID, "user", "u", "", "username or id")
|
||||||
|
_ = userDeleteCommand.MarkFlagRequired("user")
|
||||||
|
userRoot.AddCommand(userDeleteCommand)
|
||||||
|
|
||||||
|
userEditCommand.Flags().StringVarP(&userID, "user", "u", "", "username or id")
|
||||||
|
|
||||||
|
userEditCommand.Flags().BoolVar(&setAdmin, "set-admin", false, "If set, make the user an admin")
|
||||||
|
userEditCommand.Flags().BoolVar(&setRegularUser, "set-regular", false, "If set, make the user a non-admin")
|
||||||
|
userEditCommand.MarkFlagsMutuallyExclusive("set-admin", "set-regular")
|
||||||
|
|
||||||
|
userEditCommand.Flags().BoolVar(&removeEmail, "remove-email", false, "If set, clear the user's email")
|
||||||
|
userEditCommand.Flags().StringVarP(&email, "email", "e", "", "New user email")
|
||||||
|
userEditCommand.MarkFlagsMutuallyExclusive("email", "remove-email")
|
||||||
|
|
||||||
|
userEditCommand.Flags().BoolVar(&removeName, "remove-name", false, "If set, clear the user's name")
|
||||||
|
userEditCommand.Flags().StringVar(&name, "name", "", "New user name (this is separate from username used to log in)")
|
||||||
|
userEditCommand.MarkFlagsMutuallyExclusive("name", "remove-name")
|
||||||
|
|
||||||
|
userEditCommand.Flags().BoolVar(&setPassword, "set-password", false, "If set, the user's new password will be prompted on the CLI")
|
||||||
|
userEditCommand.Flags().StringVarP(&password, "password", "p", "", "Set the user's password. Note that this will be captured in terminal history")
|
||||||
|
userEditCommand.MarkFlagsMutuallyExclusive("password", "set-password")
|
||||||
|
|
||||||
|
userEditCommand.Flags().IntSliceVar(&libraryIds, "library-ids", []int{}, "Set the user's accessible libraries by id")
|
||||||
|
|
||||||
|
_ = userEditCommand.MarkFlagRequired("user")
|
||||||
|
userRoot.AddCommand(userEditCommand)
|
||||||
|
|
||||||
|
userListCommand.Flags().StringVarP(&outputFormat, "format", "f", "csv", "output format [supported values: csv, json]")
|
||||||
|
userRoot.AddCommand(userListCommand)
|
||||||
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
userRoot = &cobra.Command{
|
||||||
|
Use: "user",
|
||||||
|
Short: "Administer users",
|
||||||
|
Long: "Create, delete, list, or update users",
|
||||||
|
}
|
||||||
|
|
||||||
|
userCreateCommand = &cobra.Command{
|
||||||
|
Use: "create",
|
||||||
|
Aliases: []string{"c"},
|
||||||
|
Short: "Create a new user",
|
||||||
|
Run: func(cmd *cobra.Command, args []string) {
|
||||||
|
runCreateUser()
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
userDeleteCommand = &cobra.Command{
|
||||||
|
Use: "delete",
|
||||||
|
Aliases: []string{"d"},
|
||||||
|
Short: "Deletes an existing user",
|
||||||
|
Run: func(cmd *cobra.Command, args []string) {
|
||||||
|
runDeleteUser()
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
userEditCommand = &cobra.Command{
|
||||||
|
Use: "edit",
|
||||||
|
Aliases: []string{"e"},
|
||||||
|
Short: "Edit a user",
|
||||||
|
Long: "Edit the password, admin status, and/or library access",
|
||||||
|
Run: func(cmd *cobra.Command, args []string) {
|
||||||
|
runUserEdit()
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
userListCommand = &cobra.Command{
|
||||||
|
Use: "list",
|
||||||
|
Short: "List users",
|
||||||
|
Run: func(cmd *cobra.Command, args []string) {
|
||||||
|
runUserList()
|
||||||
|
},
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
func promptPassword() string {
|
||||||
|
for {
|
||||||
|
fmt.Print("Enter new password (press enter with no password to cancel): ")
|
||||||
|
// This cast is necessary for some platforms
|
||||||
|
password, err := term.ReadPassword(int(syscall.Stdin)) //nolint:unconvert
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal("Error getting password", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
fmt.Print("\nConfirm new password (press enter with no password to cancel): ")
|
||||||
|
confirmation, err := term.ReadPassword(int(syscall.Stdin)) //nolint:unconvert
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal("Error getting password confirmation", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// clear the line.
|
||||||
|
fmt.Println()
|
||||||
|
|
||||||
|
pass := string(password)
|
||||||
|
confirm := string(confirmation)
|
||||||
|
|
||||||
|
if pass == "" {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
if pass == confirm {
|
||||||
|
return pass
|
||||||
|
}
|
||||||
|
|
||||||
|
fmt.Println("Password and password confirmation do not match")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func runCreateUser() {
|
||||||
|
if password == "" {
|
||||||
|
password = promptPassword()
|
||||||
|
if password == "" {
|
||||||
|
log.Fatal("Empty password provided, user creation cancelled")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
user := model.User{
|
||||||
|
UserName: userID,
|
||||||
|
Email: email,
|
||||||
|
Name: name,
|
||||||
|
IsAdmin: setAdmin,
|
||||||
|
NewPassword: password,
|
||||||
|
}
|
||||||
|
|
||||||
|
ds, ctx := getContext()
|
||||||
|
|
||||||
|
err := ds.WithTx(func(tx model.DataStore) error {
|
||||||
|
existingUser, err := tx.User(ctx).FindByUsername(userID)
|
||||||
|
if existingUser != nil {
|
||||||
|
return fmt.Errorf("existing user '%s'", userID)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err != nil && !errors.Is(err, model.ErrNotFound) {
|
||||||
|
return fmt.Errorf("failed to check existing username: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(libraryIds) > 0 && !setAdmin {
|
||||||
|
user.Libraries, err = tx.Library(ctx).GetAll(model.QueryOptions{Filters: squirrel.Eq{"id": libraryIds}})
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(user.Libraries) != len(libraryIds) {
|
||||||
|
return fmt.Errorf("not all available libraries found. Requested ids: %v, Found libraries: %d", libraryIds, len(user.Libraries))
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
user.Libraries, err = tx.Library(ctx).GetAll()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
err = tx.User(ctx).Put(&user)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(ctx, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Info(ctx, "Successfully created user", "id", user.ID, "username", user.UserName)
|
||||||
|
}
|
||||||
|
|
||||||
|
func runDeleteUser() {
|
||||||
|
ds, ctx := getContext()
|
||||||
|
|
||||||
|
var err error
|
||||||
|
var user *model.User
|
||||||
|
|
||||||
|
err = ds.WithTx(func(tx model.DataStore) error {
|
||||||
|
count, err := tx.User(ctx).CountAll()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if count == 1 {
|
||||||
|
return errors.New("refusing to delete the last user")
|
||||||
|
}
|
||||||
|
|
||||||
|
user, err = getUser(userID, tx, ctx)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return tx.User(ctx).Delete(user.ID)
|
||||||
|
})
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(ctx, "Failed to delete user", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Info(ctx, "Deleted user", "username", user.UserName)
|
||||||
|
}
|
||||||
|
|
||||||
|
func runUserEdit() {
|
||||||
|
ds, ctx := getContext()
|
||||||
|
|
||||||
|
var err error
|
||||||
|
var user *model.User
|
||||||
|
changes := []string{}
|
||||||
|
|
||||||
|
err = ds.WithTx(func(tx model.DataStore) error {
|
||||||
|
user, err = getUser(userID, tx, ctx)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(libraryIds) > 0 && !setAdmin {
|
||||||
|
libraries, err := tx.Library(ctx).GetAll(model.QueryOptions{Filters: squirrel.Eq{"id": libraryIds}})
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(libraries) != len(libraryIds) {
|
||||||
|
return fmt.Errorf("not all available libraries found. Requested ids: %v, Found libraries: %d", libraryIds, len(libraries))
|
||||||
|
}
|
||||||
|
|
||||||
|
user.Libraries = libraries
|
||||||
|
changes = append(changes, "updated library ids")
|
||||||
|
}
|
||||||
|
|
||||||
|
if setAdmin && !user.IsAdmin {
|
||||||
|
libraries, err := tx.Library(ctx).GetAll()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
user.IsAdmin = true
|
||||||
|
user.Libraries = libraries
|
||||||
|
changes = append(changes, "set admin")
|
||||||
|
}
|
||||||
|
|
||||||
|
if setRegularUser && user.IsAdmin {
|
||||||
|
user.IsAdmin = false
|
||||||
|
changes = append(changes, "set regular user")
|
||||||
|
}
|
||||||
|
|
||||||
|
if setPassword {
|
||||||
|
password = promptPassword()
|
||||||
|
}
|
||||||
|
|
||||||
|
if password != "" {
|
||||||
|
user.NewPassword = password
|
||||||
|
changes = append(changes, "updated password")
|
||||||
|
}
|
||||||
|
|
||||||
|
if email != "" && email != user.Email {
|
||||||
|
user.Email = email
|
||||||
|
changes = append(changes, "updated email")
|
||||||
|
} else if removeEmail && user.Email != "" {
|
||||||
|
user.Email = ""
|
||||||
|
changes = append(changes, "removed email")
|
||||||
|
}
|
||||||
|
|
||||||
|
if name != "" && name != user.Name {
|
||||||
|
user.Name = name
|
||||||
|
changes = append(changes, "updated name")
|
||||||
|
} else if removeName && user.Name != "" {
|
||||||
|
user.Name = ""
|
||||||
|
changes = append(changes, "removed name")
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(changes) == 0 {
|
||||||
|
log.Info(ctx, "No changes for user", "user", user.UserName)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
err = tx.User(ctx).Put(user)
|
||||||
|
return err
|
||||||
|
})
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(ctx, "Failed to update user", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Info(ctx, "Updated user", "user", user.Name, "changes", strings.Join(changes, ", "))
|
||||||
|
}
|
||||||
|
|
||||||
|
type displayLibrary struct {
|
||||||
|
ID int `json:"id"`
|
||||||
|
Path string `json:"path"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type displayUser struct {
|
||||||
|
Id string `json:"id"`
|
||||||
|
Username string `json:"username"`
|
||||||
|
Name string `json:"name"`
|
||||||
|
Email string `json:"email"`
|
||||||
|
Admin bool `json:"admin"`
|
||||||
|
CreatedAt time.Time `json:"createdAt"`
|
||||||
|
UpdatedAt time.Time `json:"updatedAt"`
|
||||||
|
LastAccess *time.Time `json:"lastAccess"`
|
||||||
|
LastLogin *time.Time `json:"lastLogin"`
|
||||||
|
Libraries []displayLibrary `json:"libraries"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func runUserList() {
|
||||||
|
if outputFormat != "csv" && outputFormat != "json" {
|
||||||
|
log.Fatal("Invalid output format. Must be one of csv, json", "format", outputFormat)
|
||||||
|
}
|
||||||
|
|
||||||
|
ds, ctx := getContext()
|
||||||
|
|
||||||
|
users, err := ds.User(ctx).ReadAll()
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(ctx, "Failed to retrieve users", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
userList := users.(model.Users)
|
||||||
|
|
||||||
|
if outputFormat == "csv" {
|
||||||
|
w := csv.NewWriter(os.Stdout)
|
||||||
|
_ = w.Write([]string{
|
||||||
|
"user id",
|
||||||
|
"username",
|
||||||
|
"user's name",
|
||||||
|
"user email",
|
||||||
|
"admin",
|
||||||
|
"created at",
|
||||||
|
"updated at",
|
||||||
|
"last access",
|
||||||
|
"last login",
|
||||||
|
"libraries",
|
||||||
|
})
|
||||||
|
for _, user := range userList {
|
||||||
|
paths := make([]string, len(user.Libraries))
|
||||||
|
|
||||||
|
for idx, library := range user.Libraries {
|
||||||
|
paths[idx] = fmt.Sprintf("%d:%s", library.ID, library.Path)
|
||||||
|
}
|
||||||
|
|
||||||
|
var lastAccess, lastLogin string
|
||||||
|
|
||||||
|
if user.LastAccessAt != nil {
|
||||||
|
lastAccess = user.LastAccessAt.Format(time.RFC3339Nano)
|
||||||
|
} else {
|
||||||
|
lastAccess = "never"
|
||||||
|
}
|
||||||
|
|
||||||
|
if user.LastLoginAt != nil {
|
||||||
|
lastLogin = user.LastLoginAt.Format(time.RFC3339Nano)
|
||||||
|
} else {
|
||||||
|
lastLogin = "never"
|
||||||
|
}
|
||||||
|
|
||||||
|
_ = w.Write([]string{
|
||||||
|
user.ID,
|
||||||
|
user.UserName,
|
||||||
|
user.Name,
|
||||||
|
user.Email,
|
||||||
|
strconv.FormatBool(user.IsAdmin),
|
||||||
|
user.CreatedAt.Format(time.RFC3339Nano),
|
||||||
|
user.UpdatedAt.Format(time.RFC3339Nano),
|
||||||
|
lastAccess,
|
||||||
|
lastLogin,
|
||||||
|
fmt.Sprintf("'%s'", strings.Join(paths, ",")),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
w.Flush()
|
||||||
|
} else {
|
||||||
|
users := make([]displayUser, len(userList))
|
||||||
|
for idx, user := range userList {
|
||||||
|
paths := make([]displayLibrary, len(user.Libraries))
|
||||||
|
|
||||||
|
for idx, library := range user.Libraries {
|
||||||
|
paths[idx].ID = library.ID
|
||||||
|
paths[idx].Path = library.Path
|
||||||
|
}
|
||||||
|
|
||||||
|
users[idx].Id = user.ID
|
||||||
|
users[idx].Username = user.UserName
|
||||||
|
users[idx].Name = user.Name
|
||||||
|
users[idx].Email = user.Email
|
||||||
|
users[idx].Admin = user.IsAdmin
|
||||||
|
users[idx].CreatedAt = user.CreatedAt
|
||||||
|
users[idx].UpdatedAt = user.UpdatedAt
|
||||||
|
users[idx].LastAccess = user.LastAccessAt
|
||||||
|
users[idx].LastLogin = user.LastLoginAt
|
||||||
|
users[idx].Libraries = paths
|
||||||
|
}
|
||||||
|
|
||||||
|
j, _ := json.Marshal(users)
|
||||||
|
fmt.Printf("%s\n", j)
|
||||||
|
}
|
||||||
|
}
|
||||||
34
cmd/utils.go
Normal file
34
cmd/utils.go
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
package cmd
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"errors"
|
||||||
|
|
||||||
|
"github.com/navidrome/navidrome/core/auth"
|
||||||
|
"github.com/navidrome/navidrome/db"
|
||||||
|
"github.com/navidrome/navidrome/model"
|
||||||
|
"github.com/navidrome/navidrome/persistence"
|
||||||
|
)
|
||||||
|
|
||||||
|
func getContext() (model.DataStore, context.Context) {
|
||||||
|
sqlDB := db.Db()
|
||||||
|
ds := persistence.New(sqlDB)
|
||||||
|
return ds, auth.WithAdminUser(context.Background(), ds)
|
||||||
|
}
|
||||||
|
|
||||||
|
func getUser(id string, ds model.DataStore, ctx context.Context) (*model.User, error) {
|
||||||
|
user, err := ds.User(ctx).FindByUsername(id)
|
||||||
|
|
||||||
|
if err != nil && !errors.Is(err, model.ErrNotFound) {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if errors.Is(err, model.ErrNotFound) {
|
||||||
|
user, err = ds.User(ctx).Get(id)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return user, nil
|
||||||
|
}
|
||||||
1
go.mod
1
go.mod
@ -66,6 +66,7 @@ require (
|
|||||||
golang.org/x/net v0.47.0
|
golang.org/x/net v0.47.0
|
||||||
golang.org/x/sync v0.18.0
|
golang.org/x/sync v0.18.0
|
||||||
golang.org/x/sys v0.38.0
|
golang.org/x/sys v0.38.0
|
||||||
|
golang.org/x/term v0.37.0
|
||||||
golang.org/x/text v0.31.0
|
golang.org/x/text v0.31.0
|
||||||
golang.org/x/time v0.14.0
|
golang.org/x/time v0.14.0
|
||||||
google.golang.org/protobuf v1.36.10
|
google.golang.org/protobuf v1.36.10
|
||||||
|
|||||||
2
go.sum
2
go.sum
@ -363,6 +363,8 @@ golang.org/x/term v0.12.0/go.mod h1:owVbMEjm3cBLCHdkQu9b1opXd4ETQWc3BhuQGKgXgvU=
|
|||||||
golang.org/x/term v0.17.0/go.mod h1:lLRBjIVuehSbZlaOtGMbcMncT+aqLLLmKrsjNrUguwk=
|
golang.org/x/term v0.17.0/go.mod h1:lLRBjIVuehSbZlaOtGMbcMncT+aqLLLmKrsjNrUguwk=
|
||||||
golang.org/x/term v0.20.0/go.mod h1:8UkIAJTvZgivsXaD6/pH6U9ecQzZ45awqEOzuCvwpFY=
|
golang.org/x/term v0.20.0/go.mod h1:8UkIAJTvZgivsXaD6/pH6U9ecQzZ45awqEOzuCvwpFY=
|
||||||
golang.org/x/term v0.27.0/go.mod h1:iMsnZpn0cago0GOrHO2+Y7u7JPn5AylBrcoWkElMTSM=
|
golang.org/x/term v0.27.0/go.mod h1:iMsnZpn0cago0GOrHO2+Y7u7JPn5AylBrcoWkElMTSM=
|
||||||
|
golang.org/x/term v0.37.0 h1:8EGAD0qCmHYZg6J17DvsMy9/wJ7/D/4pV/wfnld5lTU=
|
||||||
|
golang.org/x/term v0.37.0/go.mod h1:5pB4lxRNYYVZuTLmy8oR2BH8dflOR+IbTYFD8fi3254=
|
||||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||||
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
|
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
|
||||||
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||||
|
|||||||
@ -42,7 +42,9 @@ func (u User) HasLibraryAccess(libraryID int) bool {
|
|||||||
type Users []User
|
type Users []User
|
||||||
|
|
||||||
type UserRepository interface {
|
type UserRepository interface {
|
||||||
|
ResourceRepository
|
||||||
CountAll(...QueryOptions) (int64, error)
|
CountAll(...QueryOptions) (int64, error)
|
||||||
|
Delete(id string) error
|
||||||
Get(id string) (*User, error)
|
Get(id string) (*User, error)
|
||||||
Put(*User) error
|
Put(*User) error
|
||||||
UpdateLastLoginAt(id string) error
|
UpdateLastLoginAt(id string) error
|
||||||
|
|||||||
@ -3,6 +3,7 @@ package plugins
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"errors"
|
"errors"
|
||||||
|
"time"
|
||||||
|
|
||||||
"github.com/navidrome/navidrome/conf"
|
"github.com/navidrome/navidrome/conf"
|
||||||
"github.com/navidrome/navidrome/conf/configtest"
|
"github.com/navidrome/navidrome/conf/configtest"
|
||||||
@ -23,6 +24,7 @@ var _ = Describe("Adapter Media Agent", func() {
|
|||||||
// Ensure plugins folder is set to testdata
|
// Ensure plugins folder is set to testdata
|
||||||
DeferCleanup(configtest.SetupConfig())
|
DeferCleanup(configtest.SetupConfig())
|
||||||
conf.Server.Plugins.Folder = testDataDir
|
conf.Server.Plugins.Folder = testDataDir
|
||||||
|
conf.Server.DevPluginCompilationTimeout = 2 * time.Minute
|
||||||
|
|
||||||
mgr = createManager(nil, metrics.NewNoopInstance())
|
mgr = createManager(nil, metrics.NewNoopInstance())
|
||||||
mgr.ScanPlugins()
|
mgr.ScanPlugins()
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user