@@ -227,6 +227,7 @@ func Workspaces(ctx context.Context, db database.Store, query string, page coder
227
227
filter .OrganizationID = parseOrganization (ctx ,db ,parser ,values ,"organization" )
228
228
filter .Shared = parser .NullableBoolean (values , sql.NullBool {},"shared" )
229
229
filter .SharedWithUserID = parseUser (ctx ,db ,parser ,values ,"shared_with_user" )
230
+ filter .SharedWithGroupID = parseGroup (ctx ,db ,parser ,values ,"shared_with_group" )
230
231
231
232
type paramMatch struct {
232
233
name string
@@ -383,6 +384,66 @@ func parseUser(ctx context.Context, db database.Store, parser *httpapi.QueryPara
383
384
})
384
385
}
385
386
387
+ // Parse a group filter value into a group UUID.
388
+ // Supported formats:
389
+ // - <group-uuid>
390
+ // - <organization-name>/<group-name>
391
+ // - <group-name> (resolved in the default organization)
392
+ func parseGroup (ctx context.Context ,db database.Store ,parser * httpapi.QueryParamParser ,vals url.Values ,queryParam string ) uuid.UUID {
393
+ return httpapi .ParseCustom (parser ,vals ,uuid .Nil ,queryParam ,func (v string ) (uuid.UUID ,error ) {
394
+ if v == "" {
395
+ return uuid .Nil ,nil
396
+ }
397
+ groupID ,err := uuid .Parse (v )
398
+ if err == nil {
399
+ return groupID ,nil
400
+ }
401
+
402
+ var groupName string
403
+ var org database.Organization
404
+ parts := strings .Split (v ,"/" )
405
+ switch len (parts ) {
406
+ case 1 :
407
+ dbOrg ,err := db .GetDefaultOrganization (ctx )
408
+ if err != nil {
409
+ return uuid .Nil ,xerrors .New ("fetching default organization" )
410
+ }
411
+ org = dbOrg
412
+ groupName = parts [0 ]
413
+ case 2 :
414
+ orgName := parts [0 ]
415
+ if err := codersdk .NameValid (orgName );err != nil {
416
+ return uuid .Nil ,xerrors .Errorf ("invalid organization name %w" ,err )
417
+ }
418
+ dbOrg ,err := db .GetOrganizationByName (ctx , database.GetOrganizationByNameParams {
419
+ Name :orgName ,
420
+ })
421
+ if err != nil {
422
+ return uuid .Nil ,xerrors .Errorf ("organization %q either does not exist, or you are unauthorized to view it" ,orgName )
423
+ }
424
+ org = dbOrg
425
+
426
+ groupName = parts [1 ]
427
+
428
+ default :
429
+ return uuid .Nil ,xerrors .New ("invalid organization or group name, the filter must be in the pattern of <organization name>/<group name>" )
430
+ }
431
+
432
+ if err := codersdk .GroupNameValid (groupName );err != nil {
433
+ return uuid .Nil ,xerrors .Errorf ("invalid group name %w" ,err )
434
+ }
435
+
436
+ group ,err := db .GetGroupByOrgAndName (ctx , database.GetGroupByOrgAndNameParams {
437
+ OrganizationID :org .ID ,
438
+ Name :groupName ,
439
+ })
440
+ if err != nil {
441
+ return uuid .Nil ,xerrors .Errorf ("group %q either does not exist, does not belong to the organization %q, or you are unauthorized to view it" ,groupName ,org .Name )
442
+ }
443
+ return group .ID ,nil
444
+ })
445
+ }
446
+
386
447
// splitQueryParameterByDelimiter takes a query string and splits it into the individual elements
387
448
// of the query. Each element is separated by a delimiter. All quoted strings are
388
449
// kept as a single element.