From 7d098633937a219aa96f1574fa9cc15aec1dd9fe Mon Sep 17 00:00:00 2001 From: Daenney Date: Mon, 27 Mar 2023 16:02:26 +0200 Subject: [PATCH] [feature] Add list command to admin account (#1648) * [feature] Add list command to admin account Relates to: #388 * Print booleans as yes/no too --- .../action/admin/account/account.go | 47 +++++++++++++++++++ cmd/gotosocial/admin.go | 12 +++++ internal/db/bundb/user.go | 14 ++++++ internal/db/bundb/user_test.go | 6 +++ internal/db/user.go | 2 + 5 files changed, 81 insertions(+) diff --git a/cmd/gotosocial/action/admin/account/account.go b/cmd/gotosocial/action/admin/account/account.go index 4871d89e8..4f45fd1b2 100644 --- a/cmd/gotosocial/action/admin/account/account.go +++ b/cmd/gotosocial/action/admin/account/account.go @@ -21,6 +21,8 @@ import ( "context" "errors" "fmt" + "os" + "text/tabwriter" "time" "github.com/superseriousbusiness/gotosocial/cmd/gotosocial/action" @@ -93,6 +95,51 @@ var Create action.GTSAction = func(ctx context.Context) error { return dbConn.Stop(ctx) } +// List returns all existing local accounts. +var List action.GTSAction = func(ctx context.Context) error { + var state state.State + state.Caches.Init() + state.Workers.Start() + + dbConn, err := bundb.NewBunDBService(ctx, &state) + if err != nil { + return fmt.Errorf("error creating dbservice: %s", err) + } + + // Set the state DB connection + state.DB = dbConn + + users, err := dbConn.GetAllUsers(ctx) + if err != nil { + return err + } + + fmtBool := func(b *bool) string { + if b == nil { + return "unknown" + } + if *b { + return "yes" + } + return "no" + } + + fmtDate := func(t time.Time) string { + if t.Equal(time.Time{}) { + return "no" + } + return "yes" + } + + w := tabwriter.NewWriter(os.Stdout, 0, 0, 1, ' ', 0) + fmt.Fprintln(w, "user\taccount\tapproved\tadmin\tmoderator\tsuspended\tconfirmed") + for _, u := range users { + fmt.Fprintf(w, "%s\t%s\t%s\t%s\t%s\t%s\t%s\n", u.Account.Username, u.AccountID, fmtBool(u.Approved), fmtBool(u.Admin), fmtBool(u.Moderator), fmtDate(u.Account.SuspendedAt), fmtDate(u.ConfirmedAt)) + } + w.Flush() + return nil +} + // Confirm sets a user to Approved, sets Email to the current UnconfirmedEmail value, and sets ConfirmedAt to now. var Confirm action.GTSAction = func(ctx context.Context) error { var state state.State diff --git a/cmd/gotosocial/admin.go b/cmd/gotosocial/admin.go index 185880e3d..810d57e54 100644 --- a/cmd/gotosocial/admin.go +++ b/cmd/gotosocial/admin.go @@ -54,6 +54,18 @@ func adminCommands() *cobra.Command { config.AddAdminAccountCreate(adminAccountCreateCmd) adminAccountCmd.AddCommand(adminAccountCreateCmd) + adminAccountListCmd := &cobra.Command{ + Use: "list", + Short: "list all existing local accounts", + PreRunE: func(cmd *cobra.Command, args []string) error { + return preRun(preRunArgs{cmd: cmd}) + }, + RunE: func(cmd *cobra.Command, args []string) error { + return run(cmd.Context(), account.List) + }, + } + adminAccountCmd.AddCommand(adminAccountListCmd) + adminAccountConfirmCmd := &cobra.Command{ Use: "confirm", Short: "confirm an existing local account manually, thereby skipping email confirmation", diff --git a/internal/db/bundb/user.go b/internal/db/bundb/user.go index 68fdb0652..b5dae1573 100644 --- a/internal/db/bundb/user.go +++ b/internal/db/bundb/user.go @@ -122,6 +122,20 @@ func (u *userDB) GetUserByConfirmationToken(ctx context.Context, confirmationTok }, confirmationToken) } +func (u *userDB) GetAllUsers(ctx context.Context) ([]*gtsmodel.User, db.Error) { + var users []*gtsmodel.User + q := u.conn. + NewSelect(). + Model(&users). + Relation("Account") + + if err := q.Scan(ctx); err != nil { + return nil, u.conn.ProcessError(err) + } + + return users, nil +} + func (u *userDB) PutUser(ctx context.Context, user *gtsmodel.User) db.Error { return u.state.Caches.GTS.User().Store(user, func() error { _, err := u.conn. diff --git a/internal/db/bundb/user_test.go b/internal/db/bundb/user_test.go index f2ce434c1..870a1af91 100644 --- a/internal/db/bundb/user_test.go +++ b/internal/db/bundb/user_test.go @@ -29,6 +29,12 @@ type UserTestSuite struct { BunDBStandardTestSuite } +func (suite *UserTestSuite) TestGetAllUsers() { + users, err := suite.db.GetAllUsers(context.Background()) + suite.NoError(err) + suite.Len(users, len(suite.testUsers)) +} + func (suite *UserTestSuite) TestGetUser() { user, err := suite.db.GetUserByID(context.Background(), suite.testUsers["local_account_1"].ID) suite.NoError(err) diff --git a/internal/db/user.go b/internal/db/user.go index e93ac4529..15165d0be 100644 --- a/internal/db/user.go +++ b/internal/db/user.go @@ -25,6 +25,8 @@ import ( // User contains functions related to user getting/setting/creation. type User interface { + // GetAllUsers returns all local user accounts, or an error if something goes wrong. + GetAllUsers(ctx context.Context) ([]*gtsmodel.User, Error) // GetUserByID returns one user with the given ID, or an error if something goes wrong. GetUserByID(ctx context.Context, id string) (*gtsmodel.User, Error) // GetUserByAccountID returns one user by its account ID, or an error if something goes wrong.