Implement GET /users/autocomplete endpoint for APIv4 (#5742)

Этот коммит содержится в:
Joram Wilander
2017-03-13 08:29:41 -04:00
коммит произвёл George Goldberg
родитель c372ceebf8
Коммит 1860d05d62
8 изменённых файлов: 287 добавлений и 39 удалений

Просмотреть файл

@@ -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")
}

Просмотреть файл

@@ -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()

Просмотреть файл

@@ -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 {