Skip to content

Commit 45ee4f2

Browse files
committed
feat: Add listing workspaces
1 parent da36812 commit 45ee4f2

File tree

12 files changed

+276
-93
lines changed

12 files changed

+276
-93
lines changed

coderd/coderd.go

Lines changed: 22 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -107,6 +107,9 @@ func New(options *Options) (http.Handler, func()) {
107107
r.Get("/", api.templatesByOrganization)
108108
r.Get("/{templatename}", api.templateByOrganizationAndName)
109109
})
110+
r.Route("/members", func(r chi.Router) {
111+
r.Get("/{username}", api.organizationMemberByUsername)
112+
})
110113
})
111114
r.Route("/parameters/{scope}/{id}", func(r chi.Router) {
112115
r.Use(httpmw.ExtractAPIKey(options.Database, nil))
@@ -160,7 +163,7 @@ func New(options *Options) (http.Handler, func()) {
160163
r.Get("/", api.users)
161164
r.Route("/{user}", func(r chi.Router) {
162165
r.Use(httpmw.ExtractUserParam(options.Database))
163-
r.Get("/", api.userByName)
166+
r.Get("/", api.user)
164167
r.Put("/profile", api.putUserProfile)
165168
r.Get("/organizations", api.organizationsByUser)
166169
r.Post("/organizations", api.postOrganizationsByUser)
@@ -180,6 +183,7 @@ func New(options *Options) (http.Handler, func()) {
180183
})
181184
})
182185
})
186+
183187
r.Route("/workspaceagents", func(r chi.Router) {
184188
r.Post("/azure-instance-identity", api.postWorkspaceAuthAzureInstanceIdentity)
185189
r.Post("/aws-instance-identity", api.postWorkspaceAuthAWSInstanceIdentity)
@@ -210,22 +214,23 @@ func New(options *Options) (http.Handler, func()) {
210214
)
211215
r.Get("/", api.workspaceResource)
212216
})
213-
r.Route("/workspaces/{workspace}", func(r chi.Router) {
214-
r.Use(
215-
httpmw.ExtractAPIKey(options.Database, nil),
216-
httpmw.ExtractWorkspaceParam(options.Database),
217-
)
218-
r.Get("/", api.workspace)
219-
r.Route("/builds", func(r chi.Router) {
220-
r.Get("/", api.workspaceBuilds)
221-
r.Post("/", api.postWorkspaceBuilds)
222-
r.Get("/{workspacebuildname}", api.workspaceBuildByName)
223-
})
224-
r.Route("/autostart", func(r chi.Router) {
225-
r.Put("/", api.putWorkspaceAutostart)
226-
})
227-
r.Route("/autostop", func(r chi.Router) {
228-
r.Put("/", api.putWorkspaceAutostop)
217+
r.Route("/workspaces", func(r chi.Router) {
218+
r.Use(httpmw.ExtractAPIKey(options.Database, nil))
219+
r.Get("/", api.workspaces)
220+
r.Route("/{workspace}", func(r chi.Router) {
221+
r.Use(httpmw.ExtractWorkspaceParam(options.Database))
222+
r.Get("/", api.workspace)
223+
r.Route("/builds", func(r chi.Router) {
224+
r.Get("/", api.workspaceBuilds)
225+
r.Post("/", api.postWorkspaceBuilds)
226+
r.Get("/{workspacebuildname}", api.workspaceBuildByName)
227+
})
228+
r.Route("/autostart", func(r chi.Router) {
229+
r.Put("/", api.putWorkspaceAutostart)
230+
})
231+
r.Route("/autostop", func(r chi.Router) {
232+
r.Put("/", api.putWorkspaceAutostop)
233+
})
229234
})
230235
})
231236
r.Route("/workspacebuilds/{workspacebuild}", func(r chi.Router) {

coderd/database/databasefake/databasefake.go

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -233,6 +233,13 @@ func (q *fakeQuerier) GetUsers(_ context.Context, params database.GetUsersParams
233233
return tmp, nil
234234
}
235235

236+
func (q *fakeQuerier) GetWorkspaces(_ context.Context) ([]database.Workspace, error) {
237+
q.mutex.RLock()
238+
defer q.mutex.RUnlock()
239+
240+
return q.workspaces, nil
241+
}
242+
236243
func (q *fakeQuerier) GetWorkspacesByTemplateID(_ context.Context, arg database.GetWorkspacesByTemplateIDParams) ([]database.Workspace, error) {
237244
q.mutex.RLock()
238245
defer q.mutex.RUnlock()

coderd/database/querier.go

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

coderd/database/queries.sql.go

Lines changed: 40 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

coderd/database/queries/workspaces.sql

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,12 @@ WHERE
88
LIMIT
99
1;
1010

11+
-- name: GetWorkspaces :many
12+
SELECT
13+
*
14+
FROM
15+
workspaces;
16+
1117
-- name: GetWorkspacesByTemplateID :many
1218
SELECT
1319
*

coderd/organizations.go

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,45 @@ func (*api) organization(rw http.ResponseWriter, r *http.Request) {
2222
httpapi.Write(rw, http.StatusOK, convertOrganization(organization))
2323
}
2424

25+
func (api *api) organizationMemberByUsername(rw http.ResponseWriter, r *http.Request) {
26+
organization := httpmw.OrganizationParam(r)
27+
28+
user, err := api.Database.GetUserByEmailOrUsername(r.Context(), database.GetUserByEmailOrUsernameParams{
29+
Username: chi.URLParam(r, "username"),
30+
})
31+
if errors.Is(err, sql.ErrNoRows) {
32+
httpapi.Write(rw, http.StatusNotFound, httpapi.Response{
33+
Message: "User doesn't exist with that name!",
34+
})
35+
return
36+
}
37+
if err != nil {
38+
httpapi.Write(rw, http.StatusInternalServerError, httpapi.Response{
39+
Message: fmt.Sprintf("get user by email: %s", err),
40+
})
41+
return
42+
}
43+
44+
member, err := api.Database.GetOrganizationMemberByUserID(r.Context(), database.GetOrganizationMemberByUserIDParams{
45+
OrganizationID: organization.ID,
46+
UserID: user.ID,
47+
})
48+
if errors.Is(err, sql.ErrNoRows) {
49+
httpapi.Write(rw, http.StatusForbidden, httpapi.Response{
50+
Message: "User is not a member of this organization!",
51+
})
52+
return
53+
}
54+
if err != nil {
55+
httpapi.Write(rw, http.StatusInternalServerError, httpapi.Response{
56+
Message: fmt.Sprintf("get organization member: %s", err),
57+
})
58+
return
59+
}
60+
61+
httpapi.Write(rw, http.StatusOK, member)
62+
}
63+
2564
func (api *api) provisionerDaemonsByOrganization(rw http.ResponseWriter, r *http.Request) {
2665
daemons, err := api.Database.GetProvisionerDaemons(r.Context())
2766
if errors.Is(err, sql.ErrNoRows) {

coderd/organizations_test.go

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -187,3 +187,24 @@ func TestTemplateByOrganizationAndName(t *testing.T) {
187187
require.NoError(t, err)
188188
})
189189
}
190+
191+
func TestOrganizationMemberByUsername(t *testing.T) {
192+
t.Parallel()
193+
t.Run("NotExist", func(t *testing.T) {
194+
t.Parallel()
195+
client := coderdtest.New(t, nil)
196+
user := coderdtest.CreateFirstUser(t, client)
197+
_, err := client.OrganizationMemberByUsername(context.Background(), user.OrganizationID, "someone")
198+
var apiErr *codersdk.Error
199+
require.ErrorAs(t, err, &apiErr)
200+
require.Equal(t, http.StatusNotFound, apiErr.StatusCode())
201+
})
202+
t.Run("Success", func(t *testing.T) {
203+
t.Parallel()
204+
client := coderdtest.New(t, nil)
205+
user := coderdtest.CreateFirstUser(t, client)
206+
me, _ := client.User(context.Background(), codersdk.Me)
207+
_, err := client.OrganizationMemberByUsername(context.Background(), user.OrganizationID, me.Username)
208+
require.NoError(t, err)
209+
})
210+
}

coderd/users.go

Lines changed: 3 additions & 76 deletions
Original file line numberDiff line numberDiff line change
@@ -324,7 +324,7 @@ func (api *api) postUsers(rw http.ResponseWriter, r *http.Request) {
324324

325325
// Returns the parameterized user requested. All validation
326326
// is completed in the middleware for this route.
327-
func (*api) userByName(rw http.ResponseWriter, r *http.Request) {
327+
func (*api) user(rw http.ResponseWriter, r *http.Request) {
328328
user := httpmw.UserParam(r)
329329

330330
httpapi.Write(rw, http.StatusOK, convertUser(user))
@@ -841,86 +841,13 @@ func (api *api) workspacesByUser(rw http.ResponseWriter, r *http.Request) {
841841
})
842842
return
843843
}
844-
workspaceIDs := make([]uuid.UUID, 0, len(workspaces))
845-
templateIDs := make([]uuid.UUID, 0, len(workspaces))
846-
for _, workspace := range workspaces {
847-
workspaceIDs = append(workspaceIDs, workspace.ID)
848-
templateIDs = append(templateIDs, workspace.TemplateID)
849-
}
850-
workspaceBuilds, err := api.Database.GetWorkspaceBuildsByWorkspaceIDsWithoutAfter(r.Context(), workspaceIDs)
851-
if errors.Is(err, sql.ErrNoRows) {
852-
err = nil
853-
}
854-
if err != nil {
855-
httpapi.Write(rw, http.StatusInternalServerError, httpapi.Response{
856-
Message: fmt.Sprintf("get workspace builds: %s", err),
857-
})
858-
return
859-
}
860-
templates, err := api.Database.GetTemplatesByIDs(r.Context(), templateIDs)
861-
if errors.Is(err, sql.ErrNoRows) {
862-
err = nil
863-
}
844+
apiWorkspaces, err := convertWorkspaces(r.Context(), api.Database, workspaces)
864845
if err != nil {
865846
httpapi.Write(rw, http.StatusInternalServerError, httpapi.Response{
866-
Message: fmt.Sprintf("get templates: %s", err),
847+
Message: fmt.Sprintf("convert workspaces: %s", err),
867848
})
868849
return
869850
}
870-
jobIDs := make([]uuid.UUID, 0, len(workspaceBuilds))
871-
for _, build := range workspaceBuilds {
872-
jobIDs = append(jobIDs, build.JobID)
873-
}
874-
jobs, err := api.Database.GetProvisionerJobsByIDs(r.Context(), jobIDs)
875-
if errors.Is(err, sql.ErrNoRows) {
876-
err = nil
877-
}
878-
if err != nil {
879-
httpapi.Write(rw, http.StatusInternalServerError, httpapi.Response{
880-
Message: fmt.Sprintf("get provisioner jobs: %s", err),
881-
})
882-
return
883-
}
884-
885-
buildByWorkspaceID := map[uuid.UUID]database.WorkspaceBuild{}
886-
for _, workspaceBuild := range workspaceBuilds {
887-
buildByWorkspaceID[workspaceBuild.WorkspaceID] = workspaceBuild
888-
}
889-
templateByID := map[uuid.UUID]database.Template{}
890-
for _, template := range templates {
891-
templateByID[template.ID] = template
892-
}
893-
jobByID := map[uuid.UUID]database.ProvisionerJob{}
894-
for _, job := range jobs {
895-
jobByID[job.ID] = job
896-
}
897-
apiWorkspaces := make([]codersdk.Workspace, 0, len(workspaces))
898-
for _, workspace := range workspaces {
899-
build, exists := buildByWorkspaceID[workspace.ID]
900-
if !exists {
901-
httpapi.Write(rw, http.StatusInternalServerError, httpapi.Response{
902-
Message: fmt.Sprintf("build not found for workspace %q", workspace.Name),
903-
})
904-
return
905-
}
906-
template, exists := templateByID[workspace.TemplateID]
907-
if !exists {
908-
httpapi.Write(rw, http.StatusInternalServerError, httpapi.Response{
909-
Message: fmt.Sprintf("template not found for workspace %q", workspace.Name),
910-
})
911-
return
912-
}
913-
job, exists := jobByID[build.JobID]
914-
if !exists {
915-
httpapi.Write(rw, http.StatusInternalServerError, httpapi.Response{
916-
Message: fmt.Sprintf("build job not found for workspace %q", workspace.Name),
917-
})
918-
return
919-
}
920-
apiWorkspaces = append(apiWorkspaces,
921-
convertWorkspace(workspace, convertWorkspaceBuild(build, convertProvisionerJob(job)), template))
922-
}
923-
924851
httpapi.Write(rw, http.StatusOK, apiWorkspaces)
925852
}
926853

0 commit comments

Comments
 (0)