Skip to content

Commit 331329c

Browse files
committed
Add endpoint for fetching groups with filters
1 parent a96b6e6 commit 331329c

File tree

3 files changed

+65
-19
lines changed

3 files changed

+65
-19
lines changed

coderd/httpapi/queryparams.go

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -144,6 +144,19 @@ func (p *QueryParamParser) RequiredNotEmpty(queryParam ...string) *QueryParamPar
144144
return p
145145
}
146146

147+
// UUIDorName will parse a string as a UUID, if it fails, it uses the "fetchByName"
148+
// function to return a UUID based on the value as a string.
149+
// This is useful when fetching something like an organization by ID or by name.
150+
func (p *QueryParamParser) UUIDorName(vals url.Values, def uuid.UUID, queryParam string, fetchByName func(name string) (uuid.UUID, error)) uuid.UUID {
151+
return ParseCustom(p, vals, def, queryParam, func(v string) (uuid.UUID, error) {
152+
id, err := uuid.Parse(v)
153+
if err == nil {
154+
return id, nil
155+
}
156+
return fetchByName(v)
157+
})
158+
}
159+
147160
func (p *QueryParamParser) UUIDorMe(vals url.Values, def uuid.UUID, me uuid.UUID, queryParam string) uuid.UUID {
148161
return ParseCustom(p, vals, def, queryParam, func(v string) (uuid.UUID, error) {
149162
if v == "me" {

enterprise/coderd/coderd.go

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -342,15 +342,20 @@ func New(ctx context.Context, options *Options) (_ *API, err error) {
342342
r.Get("/", api.templateACL)
343343
r.Patch("/", api.patchTemplateACL)
344344
})
345-
r.Route("/groups/{group}", func(r chi.Router) {
345+
r.Route("/groups", func(r chi.Router) {
346346
r.Use(
347347
api.templateRBACEnabledMW,
348348
apiKeyMiddleware,
349-
httpmw.ExtractGroupParam(api.Database),
350349
)
351-
r.Get("/", api.group)
352-
r.Patch("/", api.patchGroup)
353-
r.Delete("/", api.deleteGroup)
350+
r.Get("/", api.groups)
351+
r.Route("/{group}", func(r chi.Router) {
352+
r.Use(
353+
httpmw.ExtractGroupParam(api.Database),
354+
)
355+
r.Get("/", api.group)
356+
r.Patch("/", api.patchGroup)
357+
r.Delete("/", api.deleteGroup)
358+
})
354359
})
355360
r.Route("/workspace-quota", func(r chi.Router) {
356361
r.Use(

enterprise/coderd/groups.go

Lines changed: 42 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -9,13 +9,11 @@ import (
99
"github.com/google/uuid"
1010
"golang.org/x/xerrors"
1111

12-
"github.com/coder/coder/v2/coderd"
1312
"github.com/coder/coder/v2/coderd/audit"
1413
"github.com/coder/coder/v2/coderd/database"
1514
"github.com/coder/coder/v2/coderd/database/db2sdk"
1615
"github.com/coder/coder/v2/coderd/httpapi"
1716
"github.com/coder/coder/v2/coderd/httpmw"
18-
"github.com/coder/coder/v2/coderd/rbac/policy"
1917
"github.com/coder/coder/v2/codersdk"
2018
)
2119

@@ -394,30 +392,60 @@ func (api *API) group(rw http.ResponseWriter, r *http.Request) {
394392
// @Success 200 {array} codersdk.Group
395393
// @Router /organizations/{organization}/groups [get]
396394
func (api *API) groupsByOrganization(rw http.ResponseWriter, r *http.Request) {
395+
var (
396+
org = httpmw.OrganizationParam(r)
397+
)
398+
values := r.URL.Query()
399+
values.Set("organization", org.ID.String())
400+
r.URL.RawQuery = values.Encode()
401+
397402
api.groups(rw, r)
398403
}
399404

405+
// @Summary Get groups
406+
// @ID get-groups
407+
// @Security CoderSessionToken
408+
// @Produce json
409+
// @Tags Enterprise
410+
// @Param organization query string true "Organization ID or name"
411+
// @Param has_member query string true "User ID or name"
412+
// @Success 200 {array} codersdk.Group
413+
// @Router /groups [get]
400414
func (api *API) groups(rw http.ResponseWriter, r *http.Request) {
401415
var (
402416
ctx = r.Context()
403-
org = httpmw.OrganizationParam(r)
404417
)
405418

406-
groups, err := api.Database.GetGroups(ctx, database.GetGroupsParams{
407-
OrganizationID: org.ID,
419+
var filter database.GetGroupsParams
420+
parser := httpapi.NewQueryParamParser()
421+
// Organization selector can be an org ID or name
422+
filter.OrganizationID = parser.UUIDorName(r.URL.Query(), uuid.Nil, "has_member", func(orgName string) (uuid.UUID, error) {
423+
org, err := api.Database.GetOrganizationByName(ctx, orgName)
424+
if err != nil {
425+
return uuid.Nil, xerrors.Errorf("organization %q not found", orgName)
426+
}
427+
return org.ID, nil
408428
})
409-
if err != nil && !errors.Is(err, sql.ErrNoRows) {
410-
httpapi.InternalServerError(rw, err)
429+
430+
// has_member selector can be a user ID or username
431+
filter.HasMemberID = parser.UUIDorName(r.URL.Query(), uuid.Nil, "has_member", func(username string) (uuid.UUID, error) {
432+
user, err := api.Database.GetUserByEmailOrUsername(ctx, database.GetUserByEmailOrUsernameParams{
433+
Username: username,
434+
Email: "",
435+
})
436+
if err != nil {
437+
return uuid.Nil, xerrors.Errorf("user %q not found", username)
438+
}
439+
return user.ID, nil
440+
})
441+
442+
groups, err := api.Database.GetGroups(ctx, filter)
443+
if httpapi.Is404Error(err) {
444+
httpapi.ResourceNotFound(rw)
411445
return
412446
}
413-
414-
// Filter groups based on rbac permissions
415-
groups, err = coderd.AuthorizeFilter(api.AGPL.HTTPAuth, r, policy.ActionRead, groups)
416447
if err != nil {
417-
httpapi.Write(ctx, rw, http.StatusInternalServerError, codersdk.Response{
418-
Message: "Internal error fetching groups.",
419-
Detail: err.Error(),
420-
})
448+
httpapi.InternalServerError(rw, err)
421449
return
422450
}
423451

0 commit comments

Comments
 (0)