diff --git a/coderd/coderd.go b/coderd/coderd.go index dde99869c5464..0f3c4537f6a71 100644 --- a/coderd/coderd.go +++ b/coderd/coderd.go @@ -145,6 +145,7 @@ func New(options *Options) (http.Handler, func()) { r.Group(func(r chi.Router) { r.Use(httpmw.ExtractAPIKey(options.Database, nil)) r.Post("/", api.postUsers) + r.Get("/", api.users) r.Route("/{user}", func(r chi.Router) { r.Use(httpmw.ExtractUserParam(options.Database)) r.Get("/", api.userByName) diff --git a/coderd/database/databasefake/databasefake.go b/coderd/database/databasefake/databasefake.go index 8e582d85c5ddc..aac3531a77c29 100644 --- a/coderd/database/databasefake/databasefake.go +++ b/coderd/database/databasefake/databasefake.go @@ -164,6 +164,13 @@ func (q *fakeQuerier) GetUserCount(_ context.Context) (int64, error) { return int64(len(q.users)), nil } +func (q *fakeQuerier) GetUsers(_ context.Context) ([]database.User, error) { + q.mutex.RLock() + defer q.mutex.RUnlock() + + return q.users, nil +} + func (q *fakeQuerier) GetWorkspacesByTemplateID(_ context.Context, arg database.GetWorkspacesByTemplateIDParams) ([]database.Workspace, error) { q.mutex.RLock() defer q.mutex.RUnlock() diff --git a/coderd/database/querier.go b/coderd/database/querier.go index 073113a2451df..d993a5ab31075 100644 --- a/coderd/database/querier.go +++ b/coderd/database/querier.go @@ -38,6 +38,7 @@ type querier interface { GetUserByEmailOrUsername(ctx context.Context, arg GetUserByEmailOrUsernameParams) (User, error) GetUserByID(ctx context.Context, id uuid.UUID) (User, error) GetUserCount(ctx context.Context) (int64, error) + GetUsers(ctx context.Context) ([]User, error) GetWorkspaceAgentByAuthToken(ctx context.Context, authToken uuid.UUID) (WorkspaceAgent, error) GetWorkspaceAgentByID(ctx context.Context, id uuid.UUID) (WorkspaceAgent, error) GetWorkspaceAgentByInstanceID(ctx context.Context, authInstanceID string) (WorkspaceAgent, error) diff --git a/coderd/database/queries.sql.go b/coderd/database/queries.sql.go index b8550e38a1180..49fb4dc759fa2 100644 --- a/coderd/database/queries.sql.go +++ b/coderd/database/queries.sql.go @@ -1851,6 +1851,46 @@ func (q *sqlQuerier) GetUserCount(ctx context.Context) (int64, error) { return count, err } +const getUsers = `-- name: GetUsers :many +SELECT + id, email, name, revoked, login_type, hashed_password, created_at, updated_at, username +FROM + users +` + +func (q *sqlQuerier) GetUsers(ctx context.Context) ([]User, error) { + rows, err := q.db.QueryContext(ctx, getUsers) + if err != nil { + return nil, err + } + defer rows.Close() + var items []User + for rows.Next() { + var i User + if err := rows.Scan( + &i.ID, + &i.Email, + &i.Name, + &i.Revoked, + &i.LoginType, + &i.HashedPassword, + &i.CreatedAt, + &i.UpdatedAt, + &i.Username, + ); err != nil { + return nil, err + } + items = append(items, i) + } + if err := rows.Close(); err != nil { + return nil, err + } + if err := rows.Err(); err != nil { + return nil, err + } + return items, nil +} + const insertUser = `-- name: InsertUser :one INSERT INTO users ( diff --git a/coderd/database/queries/users.sql b/coderd/database/queries/users.sql index f41a96877d4d6..46e8c330ef0f1 100644 --- a/coderd/database/queries/users.sql +++ b/coderd/database/queries/users.sql @@ -51,3 +51,9 @@ SET updated_at = $5 WHERE id = $1 RETURNING *; + +-- name: GetUsers :many +SELECT + * +FROM + users; diff --git a/coderd/users.go b/coderd/users.go index 6fcab0814033f..aff5443d27f54 100644 --- a/coderd/users.go +++ b/coderd/users.go @@ -23,6 +23,25 @@ import ( "github.com/coder/coder/cryptorand" ) +// Lists all the users +func (api *api) users(rw http.ResponseWriter, r *http.Request) { + users, err := api.Database.GetUsers(r.Context()) + + if err != nil { + httpapi.Write(rw, http.StatusInternalServerError, httpapi.Response{ + Message: fmt.Sprintf("get users: %s", err.Error()), + }) + return + } + + var res []codersdk.User + for _, user := range users { + res = append(res, convertUser(user)) + } + + httpapi.Write(rw, http.StatusOK, res) +} + // Returns whether the initial user has been created or not. func (api *api) firstUser(rw http.ResponseWriter, r *http.Request) { userCount, err := api.Database.GetUserCount(r.Context()) diff --git a/coderd/users_test.go b/coderd/users_test.go index d733f022ae560..35bfea01e9ece 100644 --- a/coderd/users_test.go +++ b/coderd/users_test.go @@ -313,6 +313,21 @@ func TestUserByName(t *testing.T) { require.NoError(t, err) } +func TestGetUsers(t *testing.T) { + t.Parallel() + client := coderdtest.New(t, nil) + user := coderdtest.CreateFirstUser(t, client) + client.CreateUser(context.Background(), codersdk.CreateUserRequest{ + Email: "bruno@coder.com", + Username: "bruno", + Password: "password", + OrganizationID: user.OrganizationID, + }) + users, err := client.GetUsers(context.Background()) + require.NoError(t, err) + require.Len(t, users, 2) +} + func TestOrganizationsByUser(t *testing.T) { t.Parallel() client := coderdtest.New(t, nil) diff --git a/codersdk/users.go b/codersdk/users.go index d6a920a4c4bdc..a2939afd9bbc7 100644 --- a/codersdk/users.go +++ b/codersdk/users.go @@ -136,6 +136,19 @@ func (c *Client) UpdateUserProfile(ctx context.Context, userID uuid.UUID, req Up return user, json.NewDecoder(res.Body).Decode(&user) } +func (c *Client) GetUsers(ctx context.Context) ([]User, error) { + res, err := c.request(ctx, http.MethodGet, "/api/v2/users", nil) + if err != nil { + return []User{}, err + } + defer res.Body.Close() + if res.StatusCode != http.StatusOK { + return []User{}, readBodyAsError(res) + } + var users []User + return users, json.NewDecoder(res.Body).Decode(&users) +} + // CreateAPIKey generates an API key for the user ID provided. func (c *Client) CreateAPIKey(ctx context.Context, userID uuid.UUID) (*GenerateAPIKeyResponse, error) { res, err := c.request(ctx, http.MethodPost, fmt.Sprintf("/api/v2/users/%s/keys", uuidOrMe(userID)), nil)