Implement GET /users/autocomplete endpoint for APIv4 (#5742)
Этот коммит содержится в:
коммит произвёл
George Goldberg
родитель
c372ceebf8
Коммит
1860d05d62
32
api/user.go
32
api/user.go
@@ -1538,11 +1538,11 @@ func searchUsers(c *Context, w http.ResponseWriter, r *http.Request) {
|
||||
var profiles []*model.User
|
||||
var err *model.AppError
|
||||
if props.InChannelId != "" {
|
||||
profiles, err = app.SearchUsersInChannel(props.InChannelId, props.Term, searchOptions)
|
||||
profiles, err = app.SearchUsersInChannel(props.InChannelId, props.Term, searchOptions, c.IsSystemAdmin())
|
||||
} else if props.NotInChannelId != "" {
|
||||
profiles, err = app.SearchUsersNotInChannel(props.TeamId, props.NotInChannelId, props.Term, searchOptions)
|
||||
profiles, err = app.SearchUsersNotInChannel(props.TeamId, props.NotInChannelId, props.Term, searchOptions, c.IsSystemAdmin())
|
||||
} else {
|
||||
profiles, err = app.SearchUsersInTeam(props.TeamId, props.Term, searchOptions)
|
||||
profiles, err = app.SearchUsersInTeam(props.TeamId, props.Term, searchOptions, c.IsSystemAdmin())
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
@@ -1550,10 +1550,6 @@ func searchUsers(c *Context, w http.ResponseWriter, r *http.Request) {
|
||||
return
|
||||
}
|
||||
|
||||
for _, p := range profiles {
|
||||
sanitizeProfile(c, p)
|
||||
}
|
||||
|
||||
w.Write([]byte(model.UserListToJson(profiles)))
|
||||
}
|
||||
|
||||
@@ -1604,20 +1600,12 @@ func autocompleteUsersInChannel(c *Context, w http.ResponseWriter, r *http.Reque
|
||||
searchOptions[store.USER_SEARCH_OPTION_NAMES_ONLY] = true
|
||||
}
|
||||
|
||||
autocomplete, err := app.AutocompleteUsersInChannel(teamId, channelId, term, searchOptions)
|
||||
autocomplete, err := app.AutocompleteUsersInChannel(teamId, channelId, term, searchOptions, c.IsSystemAdmin())
|
||||
if err != nil {
|
||||
c.Err = err
|
||||
return
|
||||
}
|
||||
|
||||
for _, p := range autocomplete.InChannel {
|
||||
sanitizeProfile(c, p)
|
||||
}
|
||||
|
||||
for _, p := range autocomplete.OutOfChannel {
|
||||
sanitizeProfile(c, p)
|
||||
}
|
||||
|
||||
w.Write([]byte(autocomplete.ToJson()))
|
||||
}
|
||||
|
||||
@@ -1642,16 +1630,12 @@ func autocompleteUsersInTeam(c *Context, w http.ResponseWriter, r *http.Request)
|
||||
searchOptions[store.USER_SEARCH_OPTION_NAMES_ONLY] = true
|
||||
}
|
||||
|
||||
autocomplete, err := app.AutocompleteUsersInTeam(teamId, term, searchOptions)
|
||||
autocomplete, err := app.AutocompleteUsersInTeam(teamId, term, searchOptions, c.IsSystemAdmin())
|
||||
if err != nil {
|
||||
c.Err = err
|
||||
return
|
||||
}
|
||||
|
||||
for _, p := range autocomplete.InTeam {
|
||||
sanitizeProfile(c, p)
|
||||
}
|
||||
|
||||
w.Write([]byte(autocomplete.ToJson()))
|
||||
}
|
||||
|
||||
@@ -1670,14 +1654,10 @@ func autocompleteUsers(c *Context, w http.ResponseWriter, r *http.Request) {
|
||||
var profiles []*model.User
|
||||
var err *model.AppError
|
||||
|
||||
if profiles, err = app.SearchUsersInTeam("", term, searchOptions); err != nil {
|
||||
if profiles, err = app.SearchUsersInTeam("", term, searchOptions, c.IsSystemAdmin()); err != nil {
|
||||
c.Err = err
|
||||
return
|
||||
}
|
||||
|
||||
for _, p := range profiles {
|
||||
sanitizeProfile(c, p)
|
||||
}
|
||||
|
||||
w.Write([]byte(model.UserListToJson(profiles)))
|
||||
}
|
||||
|
||||
@@ -92,7 +92,7 @@ func InitApi(full bool) {
|
||||
BaseRoutes.ApiRoot = app.Srv.Router.PathPrefix(model.API_URL_SUFFIX).Subrouter()
|
||||
|
||||
BaseRoutes.Users = BaseRoutes.ApiRoot.PathPrefix("/users").Subrouter()
|
||||
BaseRoutes.User = BaseRoutes.Users.PathPrefix("/{user_id:[A-Za-z0-9]+}").Subrouter()
|
||||
BaseRoutes.User = BaseRoutes.ApiRoot.PathPrefix("/users/{user_id:[A-Za-z0-9]+}").Subrouter()
|
||||
BaseRoutes.UserByUsername = BaseRoutes.Users.PathPrefix("/username/{username:[A-Za-z0-9\\_\\-\\.]+}").Subrouter()
|
||||
BaseRoutes.UserByEmail = BaseRoutes.Users.PathPrefix("/email/{email}").Subrouter()
|
||||
|
||||
|
||||
@@ -123,10 +123,11 @@ func TestGetAllTeams(t *testing.T) {
|
||||
_, resp := Client.CreateTeam(team)
|
||||
CheckNoError(t, resp)
|
||||
|
||||
rrteams, resp := Client.GetAllTeams("", 1, 1)
|
||||
rrteams, resp := Client.GetAllTeams("", 0, 1)
|
||||
CheckNoError(t, resp)
|
||||
|
||||
if len(rrteams) != 1 {
|
||||
t.Log(len(rrteams))
|
||||
t.Fatal("wrong number of teams - should be 1")
|
||||
}
|
||||
|
||||
|
||||
52
api4/user.go
52
api4/user.go
@@ -11,6 +11,7 @@ import (
|
||||
l4g "github.com/alecthomas/log4go"
|
||||
"github.com/mattermost/platform/app"
|
||||
"github.com/mattermost/platform/model"
|
||||
"github.com/mattermost/platform/store"
|
||||
"github.com/mattermost/platform/utils"
|
||||
)
|
||||
|
||||
@@ -20,6 +21,7 @@ func InitUser() {
|
||||
BaseRoutes.Users.Handle("", ApiHandler(createUser)).Methods("POST")
|
||||
BaseRoutes.Users.Handle("", ApiSessionRequired(getUsers)).Methods("GET")
|
||||
BaseRoutes.Users.Handle("/ids", ApiSessionRequired(getUsersByIds)).Methods("POST")
|
||||
BaseRoutes.Users.Handle("/autocomplete", ApiSessionRequired(autocompleteUsers)).Methods("GET")
|
||||
|
||||
BaseRoutes.User.Handle("", ApiSessionRequired(getUser)).Methods("GET")
|
||||
BaseRoutes.User.Handle("/image", ApiSessionRequired(getProfileImage)).Methods("GET")
|
||||
@@ -331,6 +333,56 @@ func getUsersByIds(c *Context, w http.ResponseWriter, r *http.Request) {
|
||||
}
|
||||
}
|
||||
|
||||
func autocompleteUsers(c *Context, w http.ResponseWriter, r *http.Request) {
|
||||
channelId := r.URL.Query().Get("in_channel")
|
||||
teamId := r.URL.Query().Get("in_team")
|
||||
name := r.URL.Query().Get("name")
|
||||
|
||||
autocomplete := new(model.UserAutocomplete)
|
||||
var err *model.AppError
|
||||
|
||||
searchOptions := map[string]bool{}
|
||||
|
||||
hideFullName := !utils.Cfg.PrivacySettings.ShowFullName
|
||||
if hideFullName && !app.SessionHasPermissionTo(c.Session, model.PERMISSION_MANAGE_SYSTEM) {
|
||||
searchOptions[store.USER_SEARCH_OPTION_NAMES_ONLY_NO_FULL_NAME] = true
|
||||
} else {
|
||||
searchOptions[store.USER_SEARCH_OPTION_NAMES_ONLY] = true
|
||||
}
|
||||
|
||||
if len(teamId) > 0 {
|
||||
if len(channelId) > 0 {
|
||||
if !app.SessionHasPermissionToChannel(c.Session, channelId, model.PERMISSION_READ_CHANNEL) {
|
||||
c.SetPermissionError(model.PERMISSION_READ_CHANNEL)
|
||||
return
|
||||
}
|
||||
|
||||
result, _ := app.AutocompleteUsersInChannel(teamId, channelId, name, searchOptions, c.IsSystemAdmin())
|
||||
autocomplete.Users = result.InChannel
|
||||
autocomplete.OutOfChannel = result.OutOfChannel
|
||||
} else {
|
||||
if !app.SessionHasPermissionToTeam(c.Session, teamId, model.PERMISSION_VIEW_TEAM) {
|
||||
c.SetPermissionError(model.PERMISSION_VIEW_TEAM)
|
||||
return
|
||||
}
|
||||
|
||||
result, _ := app.AutocompleteUsersInTeam(teamId, name, searchOptions, c.IsSystemAdmin())
|
||||
autocomplete.Users = result.InTeam
|
||||
}
|
||||
} else {
|
||||
// No permission check required
|
||||
result, _ := app.SearchUsersInTeam("", name, searchOptions, c.IsSystemAdmin())
|
||||
autocomplete.Users = result
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
c.Err = err
|
||||
return
|
||||
} else {
|
||||
w.Write([]byte((autocomplete.ToJson())))
|
||||
}
|
||||
}
|
||||
|
||||
func updateUser(c *Context, w http.ResponseWriter, r *http.Request) {
|
||||
c.RequireUserId()
|
||||
if c.Err != nil {
|
||||
|
||||
@@ -267,6 +267,127 @@ func TestGetUserByEmail(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestAutocompleteUsers(t *testing.T) {
|
||||
th := Setup().InitBasic().InitSystemAdmin()
|
||||
defer TearDown()
|
||||
Client := th.Client
|
||||
teamId := th.BasicTeam.Id
|
||||
channelId := th.BasicChannel.Id
|
||||
username := th.BasicUser.Username
|
||||
|
||||
rusers, resp := Client.AutocompleteUsersInChannel(teamId, channelId, username, "")
|
||||
CheckNoError(t, resp)
|
||||
|
||||
if len(rusers.Users) != 1 {
|
||||
t.Fatal("should have returned 1 user")
|
||||
}
|
||||
|
||||
rusers, resp = Client.AutocompleteUsersInChannel(teamId, channelId, "amazonses", "")
|
||||
CheckNoError(t, resp)
|
||||
if len(rusers.Users) != 0 {
|
||||
t.Fatal("should have returned 0 users")
|
||||
}
|
||||
|
||||
rusers, resp = Client.AutocompleteUsersInChannel(teamId, channelId, "", "")
|
||||
CheckNoError(t, resp)
|
||||
if len(rusers.Users) < 2 {
|
||||
t.Fatal("should have many users")
|
||||
}
|
||||
|
||||
rusers, resp = Client.AutocompleteUsersInTeam(teamId, username, "")
|
||||
CheckNoError(t, resp)
|
||||
|
||||
if len(rusers.Users) != 1 {
|
||||
t.Fatal("should have returned 1 user")
|
||||
}
|
||||
|
||||
rusers, resp = Client.AutocompleteUsers(username, "")
|
||||
CheckNoError(t, resp)
|
||||
|
||||
if len(rusers.Users) != 1 {
|
||||
t.Fatal("should have returned 1 users")
|
||||
}
|
||||
|
||||
rusers, resp = Client.AutocompleteUsers("", "")
|
||||
CheckNoError(t, resp)
|
||||
|
||||
if len(rusers.Users) < 2 {
|
||||
t.Fatal("should have returned many users")
|
||||
}
|
||||
|
||||
rusers, resp = Client.AutocompleteUsersInTeam(teamId, "amazonses", "")
|
||||
CheckNoError(t, resp)
|
||||
if len(rusers.Users) != 0 {
|
||||
t.Fatal("should have returned 0 users")
|
||||
}
|
||||
|
||||
rusers, resp = Client.AutocompleteUsersInTeam(teamId, "", "")
|
||||
CheckNoError(t, resp)
|
||||
if len(rusers.Users) < 2 {
|
||||
t.Fatal("should have many users")
|
||||
}
|
||||
|
||||
Client.Logout()
|
||||
_, resp = Client.AutocompleteUsersInChannel(teamId, channelId, username, "")
|
||||
CheckUnauthorizedStatus(t, resp)
|
||||
|
||||
_, resp = Client.AutocompleteUsersInTeam(teamId, username, "")
|
||||
CheckUnauthorizedStatus(t, resp)
|
||||
|
||||
_, resp = Client.AutocompleteUsers(username, "")
|
||||
CheckUnauthorizedStatus(t, resp)
|
||||
|
||||
user := th.CreateUser()
|
||||
Client.Login(user.Email, user.Password)
|
||||
_, resp = Client.AutocompleteUsersInChannel(teamId, channelId, username, "")
|
||||
CheckForbiddenStatus(t, resp)
|
||||
|
||||
_, resp = Client.AutocompleteUsersInTeam(teamId, username, "")
|
||||
CheckForbiddenStatus(t, resp)
|
||||
|
||||
_, resp = Client.AutocompleteUsers(username, "")
|
||||
CheckNoError(t, resp)
|
||||
|
||||
_, resp = th.SystemAdminClient.AutocompleteUsersInChannel(teamId, channelId, username, "")
|
||||
CheckNoError(t, resp)
|
||||
|
||||
_, resp = th.SystemAdminClient.AutocompleteUsersInTeam(teamId, username, "")
|
||||
CheckNoError(t, resp)
|
||||
|
||||
_, resp = th.SystemAdminClient.AutocompleteUsers(username, "")
|
||||
CheckNoError(t, resp)
|
||||
|
||||
// Check against privacy config settings
|
||||
namePrivacy := utils.Cfg.PrivacySettings.ShowFullName
|
||||
defer func() {
|
||||
utils.Cfg.PrivacySettings.ShowFullName = namePrivacy
|
||||
}()
|
||||
utils.Cfg.PrivacySettings.ShowFullName = false
|
||||
|
||||
th.LoginBasic()
|
||||
|
||||
rusers, resp = Client.AutocompleteUsers(username, "")
|
||||
CheckNoError(t, resp)
|
||||
|
||||
if rusers.Users[0].FirstName != "" || rusers.Users[0].LastName != "" {
|
||||
t.Fatal("should not show first/last name")
|
||||
}
|
||||
|
||||
rusers, resp = Client.AutocompleteUsersInChannel(teamId, channelId, username, "")
|
||||
CheckNoError(t, resp)
|
||||
|
||||
if rusers.Users[0].FirstName != "" || rusers.Users[0].LastName != "" {
|
||||
t.Fatal("should not show first/last name")
|
||||
}
|
||||
|
||||
rusers, resp = Client.AutocompleteUsersInTeam(teamId, username, "")
|
||||
CheckNoError(t, resp)
|
||||
|
||||
if rusers.Users[0].FirstName != "" || rusers.Users[0].LastName != "" {
|
||||
t.Fatal("should not show first/last name")
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetProfileImage(t *testing.T) {
|
||||
th := Setup().InitBasic().InitSystemAdmin()
|
||||
defer TearDown()
|
||||
|
||||
58
app/user.go
58
app/user.go
@@ -1183,31 +1183,49 @@ func VerifyUserEmail(userId string) *model.AppError {
|
||||
return nil
|
||||
}
|
||||
|
||||
func SearchUsersInChannel(channelId string, term string, searchOptions map[string]bool) ([]*model.User, *model.AppError) {
|
||||
func SearchUsersInChannel(channelId string, term string, searchOptions map[string]bool, asAdmin bool) ([]*model.User, *model.AppError) {
|
||||
if result := <-Srv.Store.User().SearchInChannel(channelId, term, searchOptions); result.Err != nil {
|
||||
return nil, result.Err
|
||||
} else {
|
||||
return result.Data.([]*model.User), nil
|
||||
users := result.Data.([]*model.User)
|
||||
|
||||
for _, user := range users {
|
||||
SanitizeProfile(user, asAdmin)
|
||||
}
|
||||
|
||||
return users, nil
|
||||
}
|
||||
}
|
||||
|
||||
func SearchUsersNotInChannel(teamId string, channelId string, term string, searchOptions map[string]bool) ([]*model.User, *model.AppError) {
|
||||
func SearchUsersNotInChannel(teamId string, channelId string, term string, searchOptions map[string]bool, asAdmin bool) ([]*model.User, *model.AppError) {
|
||||
if result := <-Srv.Store.User().SearchNotInChannel(teamId, channelId, term, searchOptions); result.Err != nil {
|
||||
return nil, result.Err
|
||||
} else {
|
||||
return result.Data.([]*model.User), nil
|
||||
users := result.Data.([]*model.User)
|
||||
|
||||
for _, user := range users {
|
||||
SanitizeProfile(user, asAdmin)
|
||||
}
|
||||
|
||||
return users, nil
|
||||
}
|
||||
}
|
||||
|
||||
func SearchUsersInTeam(teamId string, term string, searchOptions map[string]bool) ([]*model.User, *model.AppError) {
|
||||
func SearchUsersInTeam(teamId string, term string, searchOptions map[string]bool, asAdmin bool) ([]*model.User, *model.AppError) {
|
||||
if result := <-Srv.Store.User().Search(teamId, term, searchOptions); result.Err != nil {
|
||||
return nil, result.Err
|
||||
} else {
|
||||
return result.Data.([]*model.User), nil
|
||||
users := result.Data.([]*model.User)
|
||||
|
||||
for _, user := range users {
|
||||
SanitizeProfile(user, asAdmin)
|
||||
}
|
||||
|
||||
return users, nil
|
||||
}
|
||||
}
|
||||
|
||||
func AutocompleteUsersInChannel(teamId string, channelId string, term string, searchOptions map[string]bool) (*model.UserAutocompleteInChannel, *model.AppError) {
|
||||
func AutocompleteUsersInChannel(teamId string, channelId string, term string, searchOptions map[string]bool, asAdmin bool) (*model.UserAutocompleteInChannel, *model.AppError) {
|
||||
uchan := Srv.Store.User().SearchInChannel(channelId, term, searchOptions)
|
||||
nuchan := Srv.Store.User().SearchNotInChannel(teamId, channelId, term, searchOptions)
|
||||
|
||||
@@ -1216,25 +1234,43 @@ func AutocompleteUsersInChannel(teamId string, channelId string, term string, se
|
||||
if result := <-uchan; result.Err != nil {
|
||||
return nil, result.Err
|
||||
} else {
|
||||
autocomplete.InChannel = result.Data.([]*model.User)
|
||||
users := result.Data.([]*model.User)
|
||||
|
||||
for _, user := range users {
|
||||
SanitizeProfile(user, asAdmin)
|
||||
}
|
||||
|
||||
autocomplete.InChannel = users
|
||||
}
|
||||
|
||||
if result := <-nuchan; result.Err != nil {
|
||||
return nil, result.Err
|
||||
} else {
|
||||
autocomplete.OutOfChannel = result.Data.([]*model.User)
|
||||
users := result.Data.([]*model.User)
|
||||
|
||||
for _, user := range users {
|
||||
SanitizeProfile(user, asAdmin)
|
||||
}
|
||||
|
||||
autocomplete.OutOfChannel = users
|
||||
}
|
||||
|
||||
return autocomplete, nil
|
||||
}
|
||||
|
||||
func AutocompleteUsersInTeam(teamId string, term string, searchOptions map[string]bool) (*model.UserAutocompleteInTeam, *model.AppError) {
|
||||
func AutocompleteUsersInTeam(teamId string, term string, searchOptions map[string]bool, asAdmin bool) (*model.UserAutocompleteInTeam, *model.AppError) {
|
||||
autocomplete := &model.UserAutocompleteInTeam{}
|
||||
|
||||
if result := <-Srv.Store.User().Search(teamId, term, searchOptions); result.Err != nil {
|
||||
return nil, result.Err
|
||||
} else {
|
||||
autocomplete.InTeam = result.Data.([]*model.User)
|
||||
users := result.Data.([]*model.User)
|
||||
|
||||
for _, user := range users {
|
||||
SanitizeProfile(user, asAdmin)
|
||||
}
|
||||
|
||||
autocomplete.InTeam = users
|
||||
}
|
||||
|
||||
return autocomplete, nil
|
||||
|
||||
@@ -339,6 +339,39 @@ func (c *Client4) GetUserByEmail(email, etag string) (*User, *Response) {
|
||||
}
|
||||
}
|
||||
|
||||
// AutocompleteUsersInTeam returns the users on a team based on search term.
|
||||
func (c *Client4) AutocompleteUsersInTeam(teamId string, username string, etag string) (*UserAutocomplete, *Response) {
|
||||
query := fmt.Sprintf("?in_team=%v&name=%v", teamId, username)
|
||||
if r, err := c.DoApiGet(c.GetUsersRoute()+"/autocomplete"+query, etag); err != nil {
|
||||
return nil, &Response{StatusCode: r.StatusCode, Error: err}
|
||||
} else {
|
||||
defer closeBody(r)
|
||||
return UserAutocompleteFromJson(r.Body), BuildResponse(r)
|
||||
}
|
||||
}
|
||||
|
||||
// AutocompleteUsersInChannel returns the users in a channel based on search term.
|
||||
func (c *Client4) AutocompleteUsersInChannel(teamId string, channelId string, username string, etag string) (*UserAutocomplete, *Response) {
|
||||
query := fmt.Sprintf("?in_team=%v&in_channel=%v&name=%v", teamId, channelId, username)
|
||||
if r, err := c.DoApiGet(c.GetUsersRoute()+"/autocomplete"+query, etag); err != nil {
|
||||
return nil, &Response{StatusCode: r.StatusCode, Error: err}
|
||||
} else {
|
||||
defer closeBody(r)
|
||||
return UserAutocompleteFromJson(r.Body), BuildResponse(r)
|
||||
}
|
||||
}
|
||||
|
||||
// AutocompleteUsers returns the users in the system based on search term.
|
||||
func (c *Client4) AutocompleteUsers(username string, etag string) (*UserAutocomplete, *Response) {
|
||||
query := fmt.Sprintf("?name=%v", username)
|
||||
if r, err := c.DoApiGet(c.GetUsersRoute()+"/autocomplete"+query, etag); err != nil {
|
||||
return nil, &Response{StatusCode: r.StatusCode, Error: err}
|
||||
} else {
|
||||
defer closeBody(r)
|
||||
return UserAutocompleteFromJson(r.Body), BuildResponse(r)
|
||||
}
|
||||
}
|
||||
|
||||
// GetProfileImage gets user's profile image. Must be logged in or be a system administrator.
|
||||
func (c *Client4) GetProfileImage(userId, etag string) ([]byte, *Response) {
|
||||
if r, err := c.DoApiGet(c.GetUserRoute(userId)+"/image", etag); err != nil {
|
||||
|
||||
@@ -17,6 +17,31 @@ type UserAutocompleteInTeam struct {
|
||||
InTeam []*User `json:"in_team"`
|
||||
}
|
||||
|
||||
type UserAutocomplete struct {
|
||||
Users []*User `json:"users"`
|
||||
OutOfChannel []*User `json:"out_of_channel,omitempty"`
|
||||
}
|
||||
|
||||
func (o *UserAutocomplete) ToJson() string {
|
||||
b, err := json.Marshal(o)
|
||||
if err != nil {
|
||||
return ""
|
||||
} else {
|
||||
return string(b)
|
||||
}
|
||||
}
|
||||
|
||||
func UserAutocompleteFromJson(data io.Reader) *UserAutocomplete {
|
||||
decoder := json.NewDecoder(data)
|
||||
autocomplete := new(UserAutocomplete)
|
||||
err := decoder.Decode(&autocomplete)
|
||||
if err == nil {
|
||||
return autocomplete
|
||||
} else {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func (o *UserAutocompleteInChannel) ToJson() string {
|
||||
b, err := json.Marshal(o)
|
||||
if err != nil {
|
||||
|
||||
Ссылка в новой задаче
Block a user