CLI: Add command flag to show deleted user accounts #4570

Signed-off-by: Michael Mayer <michael@photoprism.app>
This commit is contained in:
Michael Mayer
2024-10-11 11:37:11 +02:00
parent 3d220227bb
commit 51bc0f1f5a
7 changed files with 43 additions and 18 deletions

View File

@@ -16,7 +16,7 @@ var AuthListCommand = cli.Command{
Name: "ls", Name: "ls",
Usage: "Lists currently authenticated users and clients", Usage: "Lists currently authenticated users and clients",
ArgsUsage: "[search]", ArgsUsage: "[search]",
Flags: append(report.CliFlags, countFlag, tokensFlag), Flags: append(report.CliFlags, CountFlag, tokensFlag),
Action: authListAction, Action: authListAction,
} }

View File

@@ -16,7 +16,7 @@ var ClientsListCommand = cli.Command{
Name: "ls", Name: "ls",
Usage: "Lists registered client applications", Usage: "Lists registered client applications",
ArgsUsage: "[search]", ArgsUsage: "[search]",
Flags: append(report.CliFlags, countFlag), Flags: append(report.CliFlags, CountFlag),
Action: clientsListAction, Action: clientsListAction,
} }

View File

@@ -71,8 +71,8 @@ var PhotoPrism = []cli.Command{
ConnectCommand, ConnectCommand,
} }
// countFlag represents a CLI flag to limit the number of report rows. // CountFlag represents a CLI flag to limit the number of report rows.
var countFlag = cli.UintFlag{ var CountFlag = cli.UintFlag{
Name: "n", Name: "n",
Usage: "`LIMIT` number of results", Usage: "`LIMIT` number of results",
Value: 100, Value: 100,

View File

@@ -79,3 +79,15 @@ var UserFlags = []cli.Flag{
Usage: UserWebDAVUsage, Usage: UserWebDAVUsage,
}, },
} }
// UserTokensFlag is a CLI flag for including security tokens in reports.
var UserTokensFlag = cli.BoolFlag{
Name: "tokens",
Usage: "show user preview and download tokens",
}
// UsersDeletedFlag is a CLI flag for finding deleted user accounts.
var UsersDeletedFlag = cli.BoolFlag{
Name: "deleted, d",
Usage: "show deleted user accounts",
}

View File

@@ -15,7 +15,7 @@ import (
var UsersListCommand = cli.Command{ var UsersListCommand = cli.Command{
Name: "ls", Name: "ls",
Usage: "Lists registered user accounts", Usage: "Lists registered user accounts",
Flags: append(report.CliFlags, countFlag), Flags: append(report.CliFlags, CountFlag, UsersDeletedFlag),
Action: usersListAction, Action: usersListAction,
} }
@@ -26,8 +26,12 @@ func usersListAction(ctx *cli.Context) error {
cols := []string{"UID", "Username", "Role", "Authentication", "Super Admin", "Web Login", "Last Login", "WebDAV"} cols := []string{"UID", "Username", "Role", "Authentication", "Super Admin", "Web Login", "Last Login", "WebDAV"}
if ctx.Bool("deleted") {
cols = append(cols, "Deleted At")
}
// Fetch users from database. // Fetch users from database.
users, err := query.Users(ctx.Int("n"), 0, "", ctx.Args().First()) users, err := query.Users(ctx.Int("n"), 0, "", ctx.Args().First(), ctx.Bool("deleted"))
if err != nil { if err != nil {
return err return err
@@ -42,9 +46,6 @@ func usersListAction(ctx *cli.Context) error {
rows = make([][]string, len(users)) rows = make([][]string, len(users))
// Show log message.
log.Infof("found %s", english.Plural(len(users), "user", "users"))
// Display report. // Display report.
for i, user := range users { for i, user := range users {
rows[i] = []string{ rows[i] = []string{
@@ -57,6 +58,10 @@ func usersListAction(ctx *cli.Context) error {
report.DateTime(user.LoginAt), report.DateTime(user.LoginAt),
report.Bool(user.CanUseWebDAV(), report.Enabled, report.Disabled), report.Bool(user.CanUseWebDAV(), report.Enabled, report.Disabled),
} }
if ctx.Bool("deleted") {
rows[i] = append(rows[i], report.DateTime(user.DeletedAt))
}
} }
result, err := report.RenderFormat(rows, cols, report.CliFormat(ctx)) result, err := report.RenderFormat(rows, cols, report.CliFormat(ctx))

View File

@@ -42,13 +42,14 @@ func CountUsers(registered, active bool, roles, excludeRoles []string) (count in
return count return count
} }
// Users finds users and returns them. // Users finds user accounts based on the specified parameters.
func Users(limit, offset int, sortOrder, search string) (result entity.Users, err error) { func Users(limit, offset int, sortOrder, search string, deleted bool) (result entity.Users, err error) {
result = entity.Users{} result = entity.Users{}
stmt := Db() stmt := UnscopedDb()
search = strings.TrimSpace(search) search = strings.TrimSpace(search)
// Filter user accounts to be returned.
if search == "all" { if search == "all" {
// Don't filter. // Don't filter.
} else if id := txt.Int(search); id != 0 { } else if id := txt.Int(search); id != 0 {
@@ -61,10 +62,17 @@ func Users(limit, offset int, sortOrder, search string) (result entity.Users, er
stmt = stmt.Where("id > 0") stmt = stmt.Where("id > 0")
} }
// Hide deleted user accounts?
if !deleted {
stmt = stmt.Where("deleted_at IS NULL")
}
// Set result sort order.
if sortOrder == "" { if sortOrder == "" {
sortOrder = "id" sortOrder = "id"
} }
// Limit number of results.
if limit > 0 { if limit > 0 {
stmt = stmt.Limit(limit) stmt = stmt.Limit(limit)

View File

@@ -45,28 +45,28 @@ func TestCountUsers(t *testing.T) {
func TestUsers(t *testing.T) { func TestUsers(t *testing.T) {
t.Run("Default", func(t *testing.T) { t.Run("Default", func(t *testing.T) {
if results, err := Users(0, 0, "", ""); err != nil { if results, err := Users(0, 0, "", "", false); err != nil {
t.Fatal(err) t.Fatal(err)
} else { } else {
assert.LessOrEqual(t, 2, len(results)) assert.LessOrEqual(t, 2, len(results))
} }
}) })
t.Run("Limit", func(t *testing.T) { t.Run("Limit", func(t *testing.T) {
if results, err := Users(1, 0, "", ""); err != nil { if results, err := Users(1, 0, "", "", false); err != nil {
t.Fatal(err) t.Fatal(err)
} else { } else {
assert.LessOrEqual(t, 1, len(results)) assert.LessOrEqual(t, 1, len(results))
} }
}) })
t.Run("Offset", func(t *testing.T) { t.Run("Offset", func(t *testing.T) {
if results, err := Users(0, 1, "", ""); err != nil { if results, err := Users(0, 1, "", "", false); err != nil {
t.Fatal(err) t.Fatal(err)
} else { } else {
assert.LessOrEqual(t, 2, len(results)) assert.LessOrEqual(t, 2, len(results))
} }
}) })
t.Run("SearchAlice", func(t *testing.T) { t.Run("SearchAlice", func(t *testing.T) {
if results, err := Users(100, 0, "", "alice"); err != nil { if results, err := Users(100, 0, "", "alice", false); err != nil {
t.Fatal(err) t.Fatal(err)
} else { } else {
assert.LessOrEqual(t, 1, len(results)) assert.LessOrEqual(t, 1, len(results))
@@ -78,14 +78,14 @@ func TestUsers(t *testing.T) {
} }
}) })
t.Run("SortByID", func(t *testing.T) { t.Run("SortByID", func(t *testing.T) {
if results, err := Users(100, 0, "id", ""); err != nil { if results, err := Users(100, 0, "id", "", false); err != nil {
t.Fatal(err) t.Fatal(err)
} else { } else {
assert.LessOrEqual(t, 2, len(results)) assert.LessOrEqual(t, 2, len(results))
} }
}) })
t.Run("SearchAliceSortByID", func(t *testing.T) { t.Run("SearchAliceSortByID", func(t *testing.T) {
if results, err := Users(100, 0, "id", "alice"); err != nil { if results, err := Users(100, 0, "id", "alice", false); err != nil {
t.Fatal(err) t.Fatal(err)
} else { } else {
assert.LessOrEqual(t, 1, len(results)) assert.LessOrEqual(t, 1, len(results))