diff --git a/api/system/spec.json b/api/system/spec.json index cc124cea3..9c1af7c4b 100644 --- a/api/system/spec.json +++ b/api/system/spec.json @@ -697,6 +697,18 @@ "path": "/", "parameters": { "get": [ + { + "name": "userID", + "type": "[]string", + "required": false, + "title": "Filter by user ID" + }, + { + "name": "roleID", + "type": "[]string", + "required": false, + "title": "Filter by role membership" + }, { "type": "string", "name": "query", diff --git a/api/system/spec/user.json b/api/system/spec/user.json index e96cdec36..d6aa56088 100644 --- a/api/system/spec/user.json +++ b/api/system/spec/user.json @@ -23,6 +23,18 @@ "Path": "/", "Parameters": { "get": [ + { + "name": "userID", + "required": false, + "title": "Filter by user ID", + "type": "[]string" + }, + { + "name": "roleID", + "required": false, + "title": "Filter by role membership", + "type": "[]string" + }, { "name": "query", "required": false, diff --git a/docs/system/README.md b/docs/system/README.md index 6ed3b4208..e81bcb130 100644 --- a/docs/system/README.md +++ b/docs/system/README.md @@ -1106,6 +1106,8 @@ An organisation may have many roles. Roles may have many channels available. Acc | Parameter | Type | Method | Description | Default | Required? | | --------- | ---- | ------ | ----------- | ------- | --------- | +| userID | []string | GET | Filter by user ID | N/A | NO | +| roleID | []string | GET | Filter by role membership | N/A | NO | | query | string | GET | Search query to match against users | N/A | NO | | username | string | GET | Search username to match against users | N/A | NO | | email | string | GET | Search email to match against users | N/A | NO | diff --git a/system/repository/user.go b/system/repository/user.go index fab0f66cc..b7ecaae93 100644 --- a/system/repository/user.go +++ b/system/repository/user.go @@ -20,7 +20,6 @@ type ( FindByUsername(username string) (*types.User, error) FindByHandle(handle string) (*types.User, error) FindByID(id uint64) (*types.User, error) - FindByIDs(id ...uint64) (types.UserSet, error) Find(filter types.UserFilter) (set types.UserSet, f types.UserFilter, err error) Total() uint @@ -111,19 +110,6 @@ func (r user) FindByID(id uint64) (*types.User, error) { return r.findBy("id", id) } -func (r user) FindByIDs(IDs ...uint64) (types.UserSet, error) { - if len(IDs) == 0 { - return nil, nil - } - - var ( - query = r.query().Where("u.id IN (?)", IDs) - uu = types.UserSet{} - ) - - return uu, r.fetchSet(&uu, query) -} - func (r user) Find(filter types.UserFilter) (set types.UserSet, f types.UserFilter, err error) { f = filter q := r.queryNoFilter() @@ -139,6 +125,21 @@ func (r user) Find(filter types.UserFilter) (set types.UserSet, f types.UserFilt q = q.Where(squirrel.Eq{"u.suspended_at": nil}) } + if len(f.UserID) > 0 { + q = q.Where(squirrel.Eq{"u.ID": f.UserID}) + } + + if len(f.RoleID) > 0 { + or := squirrel.Or{} + // Due to lack of support for more exotic expressions (slice of values inside subquery) + // we'll use set of OR expressions as a workaround + for _, roleID := range f.RoleID { + or = append(or, squirrel.Expr("u.ID IN (SELECT rel_user FROM sys_role_member WHERE rel_role IN (?))", roleID)) + } + + q = q.Where(or) + } + if f.Query != "" { qs := f.Query + "%" q = q.Where(squirrel.Or{ diff --git a/system/rest/request/user.go b/system/rest/request/user.go index 09976c6f4..c8a9fef4a 100644 --- a/system/rest/request/user.go +++ b/system/rest/request/user.go @@ -34,6 +34,8 @@ var _ = multipart.FileHeader{} // User list request parameters type UserList struct { + UserID []string + RoleID []string Query string Username string Email string @@ -53,6 +55,8 @@ func NewUserList() *UserList { func (r UserList) Auditable() map[string]interface{} { var out = map[string]interface{}{} + out["userID"] = r.UserID + out["roleID"] = r.RoleID out["query"] = r.Query out["username"] = r.Username out["email"] = r.Email @@ -94,6 +98,18 @@ func (r *UserList) Fill(req *http.Request) (err error) { post[name] = string(param[0]) } + if val, ok := urlQuery["userID[]"]; ok { + r.UserID = parseStrings(val) + } else if val, ok = urlQuery["userID"]; ok { + r.UserID = parseStrings(val) + } + + if val, ok := urlQuery["roleID[]"]; ok { + r.RoleID = parseStrings(val) + } else if val, ok = urlQuery["roleID"]; ok { + r.RoleID = parseStrings(val) + } + if val, ok := get["query"]; ok { r.Query = val } diff --git a/system/rest/user.go b/system/rest/user.go index ca8b5df72..1f7e8df3e 100644 --- a/system/rest/user.go +++ b/system/rest/user.go @@ -36,6 +36,8 @@ func (User) New() *User { func (ctrl User) List(ctx context.Context, r *request.UserList) (interface{}, error) { f := types.UserFilter{ + UserID: payload.ParseUInt64s(r.UserID), + RoleID: payload.ParseUInt64s(r.RoleID), Query: r.Query, Email: r.Email, Username: r.Username, diff --git a/system/service/user.go b/system/service/user.go index e389b4147..45b99be74 100644 --- a/system/service/user.go +++ b/system/service/user.go @@ -73,7 +73,6 @@ type ( FindByEmail(email string) (*types.User, error) FindByHandle(handle string) (*types.User, error) FindByID(id uint64) (*types.User, error) - FindByIDs(id ...uint64) (types.UserSet, error) Find(types.UserFilter) (types.UserSet, types.UserFilter, error) Create(input *types.User) (*types.User, error) @@ -156,12 +155,6 @@ func (svc user) proc(u *types.User, err error) (*types.User, error) { return u, nil } -func (svc user) FindByIDs(userIDs ...uint64) (types.UserSet, error) { - uu, err := svc.user.FindByIDs(userIDs...) - uu, _, err = svc.procSet(uu, types.UserFilter{}, err) - return uu, err -} - func (svc user) Find(f types.UserFilter) (types.UserSet, types.UserFilter, error) { if f.IncDeleted || f.IncSuspended { // If list with deleted or suspended users is requested diff --git a/system/types/user.go b/system/types/user.go index bbb216950..8b870d90a 100644 --- a/system/types/user.go +++ b/system/types/user.go @@ -44,6 +44,8 @@ type ( } UserFilter struct { + UserID []uint64 `json:"userID"` + RoleID []uint64 `json:"roleID"` Query string `json:"query"` Email string `json:"email"` Username string `json:"username"`