[MM-13500] Adds channel /search_group endpoint (#10805)
* [MM-13500] Adds channel /search_group endpoint * Add LIMIT to the queries * Fix i18n extract * Fix tests * Add a new endpoint to get profiles by group channel ids * Rebase fix
Этот коммит содержится в:
коммит произвёл
GitHub
родитель
604e247135
Коммит
9e9b008f3d
@@ -18,6 +18,7 @@ func (api *API) InitChannel() {
|
||||
api.BaseRoutes.Channels.Handle("", api.ApiSessionRequired(createChannel)).Methods("POST")
|
||||
api.BaseRoutes.Channels.Handle("/direct", api.ApiSessionRequired(createDirectChannel)).Methods("POST")
|
||||
api.BaseRoutes.Channels.Handle("/search", api.ApiSessionRequired(searchAllChannels)).Methods("POST")
|
||||
api.BaseRoutes.Channels.Handle("/group/search", api.ApiSessionRequired(searchGroupChannels)).Methods("POST")
|
||||
api.BaseRoutes.Channels.Handle("/group", api.ApiSessionRequired(createGroupChannel)).Methods("POST")
|
||||
api.BaseRoutes.Channels.Handle("/members/{user_id:[A-Za-z0-9]+}/view", api.ApiSessionRequired(viewChannel)).Methods("POST")
|
||||
api.BaseRoutes.Channels.Handle("/{channel_id:[A-Za-z0-9]+}/scheme", api.ApiSessionRequired(updateChannelScheme)).Methods("PUT")
|
||||
@@ -356,6 +357,22 @@ func createDirectChannel(c *Context, w http.ResponseWriter, r *http.Request) {
|
||||
w.Write([]byte(sc.ToJson()))
|
||||
}
|
||||
|
||||
func searchGroupChannels(c *Context, w http.ResponseWriter, r *http.Request) {
|
||||
props := model.ChannelSearchFromJson(r.Body)
|
||||
if props == nil {
|
||||
c.SetInvalidParam("channel_search")
|
||||
return
|
||||
}
|
||||
|
||||
groupChannels, err := c.App.SearchGroupChannels(c.App.Session.UserId, props.Term)
|
||||
if err != nil {
|
||||
c.Err = err
|
||||
return
|
||||
}
|
||||
|
||||
w.Write([]byte(groupChannels.ToJson()))
|
||||
}
|
||||
|
||||
func createGroupChannel(c *Context, w http.ResponseWriter, r *http.Request) {
|
||||
userIds := model.ArrayFromJson(r.Body)
|
||||
|
||||
|
||||
@@ -918,6 +918,64 @@ func TestSearchAllChannels(t *testing.T) {
|
||||
CheckForbiddenStatus(t, resp)
|
||||
}
|
||||
|
||||
func TestSearchGroupChannels(t *testing.T) {
|
||||
th := Setup().InitBasic()
|
||||
defer th.TearDown()
|
||||
Client := th.Client
|
||||
|
||||
u1 := th.CreateUserWithClient(th.SystemAdminClient)
|
||||
|
||||
// Create a group channel in which base user belongs but not sysadmin
|
||||
gc1, resp := th.Client.CreateGroupChannel([]string{th.BasicUser.Id, th.BasicUser2.Id, u1.Id})
|
||||
CheckNoError(t, resp)
|
||||
defer th.Client.DeleteChannel(gc1.Id)
|
||||
|
||||
gc2, resp := th.Client.CreateGroupChannel([]string{th.BasicUser.Id, th.BasicUser2.Id, th.SystemAdminUser.Id})
|
||||
CheckNoError(t, resp)
|
||||
defer th.Client.DeleteChannel(gc2.Id)
|
||||
|
||||
search := &model.ChannelSearch{Term: th.BasicUser2.Username}
|
||||
|
||||
// sysadmin should only find gc2 as he doesn't belong to gc1
|
||||
channels, resp := th.SystemAdminClient.SearchGroupChannels(search)
|
||||
CheckNoError(t, resp)
|
||||
|
||||
assert.Len(t, channels, 1)
|
||||
assert.Equal(t, channels[0].Id, gc2.Id)
|
||||
|
||||
// basic user should find both
|
||||
Client.Login(th.BasicUser.Username, th.BasicUser.Password)
|
||||
channels, resp = Client.SearchGroupChannels(search)
|
||||
CheckNoError(t, resp)
|
||||
|
||||
assert.Len(t, channels, 2)
|
||||
channelIds := []string{}
|
||||
for _, c := range channels {
|
||||
channelIds = append(channelIds, c.Id)
|
||||
}
|
||||
assert.ElementsMatch(t, channelIds, []string{gc1.Id, gc2.Id})
|
||||
|
||||
// searching for sysadmin, it should only find gc1
|
||||
search = &model.ChannelSearch{Term: th.SystemAdminUser.Username}
|
||||
channels, resp = Client.SearchGroupChannels(search)
|
||||
CheckNoError(t, resp)
|
||||
|
||||
assert.Len(t, channels, 1)
|
||||
assert.Equal(t, channels[0].Id, gc2.Id)
|
||||
|
||||
// with an empty search, response should be empty
|
||||
search = &model.ChannelSearch{Term: ""}
|
||||
channels, resp = Client.SearchGroupChannels(search)
|
||||
CheckNoError(t, resp)
|
||||
|
||||
assert.Len(t, channels, 0)
|
||||
|
||||
// search unprivileged, forbidden
|
||||
th.Client.Logout()
|
||||
_, resp = Client.SearchAllChannels(search)
|
||||
CheckUnauthorizedStatus(t, resp)
|
||||
}
|
||||
|
||||
func TestDeleteChannel(t *testing.T) {
|
||||
th := Setup().InitBasic()
|
||||
defer th.TearDown()
|
||||
|
||||
20
api4/user.go
20
api4/user.go
@@ -4,6 +4,7 @@
|
||||
package api4
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
@@ -25,6 +26,7 @@ func (api *API) InitUser() {
|
||||
api.BaseRoutes.Users.Handle("/search", api.ApiSessionRequired(searchUsers)).Methods("POST")
|
||||
api.BaseRoutes.Users.Handle("/autocomplete", api.ApiSessionRequired(autocompleteUsers)).Methods("GET")
|
||||
api.BaseRoutes.Users.Handle("/stats", api.ApiSessionRequired(getTotalUsersStats)).Methods("GET")
|
||||
api.BaseRoutes.Users.Handle("/group_channels", api.ApiSessionRequired(getUsersByGroupChannelIds)).Methods("POST")
|
||||
|
||||
api.BaseRoutes.User.Handle("", api.ApiSessionRequired(getUser)).Methods("GET")
|
||||
api.BaseRoutes.User.Handle("/image/default", api.ApiSessionRequiredTrustRequester(getDefaultProfileImage)).Methods("GET")
|
||||
@@ -447,6 +449,24 @@ func getTotalUsersStats(c *Context, w http.ResponseWriter, r *http.Request) {
|
||||
w.Write([]byte(stats.ToJson()))
|
||||
}
|
||||
|
||||
func getUsersByGroupChannelIds(c *Context, w http.ResponseWriter, r *http.Request) {
|
||||
channelIds := model.ArrayFromJson(r.Body)
|
||||
|
||||
if len(channelIds) == 0 {
|
||||
c.SetInvalidParam("channel_ids")
|
||||
return
|
||||
}
|
||||
|
||||
usersByChannelId, err := c.App.GetUsersByGroupChannelIds(channelIds, c.IsSystemAdmin())
|
||||
if err != nil {
|
||||
c.Err = err
|
||||
return
|
||||
}
|
||||
|
||||
b, _ := json.Marshal(usersByChannelId)
|
||||
w.Write(b)
|
||||
}
|
||||
|
||||
func getUsers(c *Context, w http.ResponseWriter, r *http.Request) {
|
||||
inTeamId := r.URL.Query().Get("in_team")
|
||||
notInTeamId := r.URL.Query().Get("not_in_team")
|
||||
|
||||
@@ -1127,6 +1127,35 @@ func TestGetUsersByIds(t *testing.T) {
|
||||
CheckUnauthorizedStatus(t, resp)
|
||||
}
|
||||
|
||||
func TestGetUsersByGroupChannelIds(t *testing.T) {
|
||||
th := Setup().InitBasic()
|
||||
defer th.TearDown()
|
||||
|
||||
gc1, err := th.App.CreateGroupChannel([]string{th.BasicUser.Id, th.SystemAdminUser.Id, th.TeamAdminUser.Id}, th.BasicUser.Id)
|
||||
require.Nil(t, err)
|
||||
|
||||
usersByChannelId, resp := th.Client.GetUsersByGroupChannelIds([]string{gc1.Id})
|
||||
CheckNoError(t, resp)
|
||||
|
||||
users, _ := usersByChannelId[gc1.Id]
|
||||
userIds := []string{}
|
||||
for _, user := range users {
|
||||
userIds = append(userIds, user.Id)
|
||||
}
|
||||
|
||||
require.ElementsMatch(t, []string{th.SystemAdminUser.Id, th.TeamAdminUser.Id}, userIds)
|
||||
|
||||
th.LoginBasic2()
|
||||
usersByChannelId, resp = th.Client.GetUsersByGroupChannelIds([]string{gc1.Id})
|
||||
|
||||
_, ok := usersByChannelId[gc1.Id]
|
||||
require.False(t, ok)
|
||||
|
||||
th.Client.Logout()
|
||||
_, resp = th.Client.GetUsersByGroupChannelIds([]string{gc1.Id})
|
||||
CheckUnauthorizedStatus(t, resp)
|
||||
}
|
||||
|
||||
func TestGetUsersByUsernames(t *testing.T) {
|
||||
th := Setup().InitBasic()
|
||||
defer th.TearDown()
|
||||
|
||||
@@ -1801,6 +1801,18 @@ func (a *App) SearchChannels(teamId string, term string) (*model.ChannelList, *m
|
||||
return a.Srv.Store.Channel().SearchInTeam(teamId, term, includeDeleted)
|
||||
}
|
||||
|
||||
func (a *App) SearchGroupChannels(userId, term string) (*model.ChannelList, *model.AppError) {
|
||||
if term == "" {
|
||||
return &model.ChannelList{}, nil
|
||||
}
|
||||
|
||||
channelList, err := a.Srv.Store.Channel().SearchGroupChannels(userId, term)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return channelList, nil
|
||||
}
|
||||
|
||||
func (a *App) SearchChannelsUserNotIn(teamId string, userId string, term string) (*model.ChannelList, *model.AppError) {
|
||||
term = strings.TrimSpace(term)
|
||||
return a.Srv.Store.Channel().SearchMore(userId, teamId, term)
|
||||
|
||||
12
app/user.go
12
app/user.go
@@ -637,6 +637,18 @@ func (a *App) GetUsersByIds(userIds []string, asAdmin bool, viewRestrictions *mo
|
||||
return a.sanitizeProfiles(result.Data.([]*model.User), asAdmin), nil
|
||||
}
|
||||
|
||||
func (a *App) GetUsersByGroupChannelIds(channelIds []string, asAdmin bool) (map[string][]*model.User, *model.AppError) {
|
||||
usersByChannelId, err := a.Srv.Store.User().GetProfileByGroupChannelIdsForUser(a.Session.UserId, channelIds)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
for channelId, userList := range usersByChannelId {
|
||||
usersByChannelId[channelId] = a.sanitizeProfiles(userList, asAdmin)
|
||||
}
|
||||
|
||||
return usersByChannelId, nil
|
||||
}
|
||||
|
||||
func (a *App) GetUsersByUsernames(usernames []string, asAdmin bool, viewRestrictions *model.ViewUsersRestrictions) ([]*model.User, *model.AppError) {
|
||||
result := <-a.Srv.Store.User().GetProfilesByUsernames(usernames, viewRestrictions)
|
||||
if result.Err != nil {
|
||||
|
||||
@@ -5658,6 +5658,10 @@
|
||||
"id": "store.sql_channel.search.app_error",
|
||||
"translation": "We encountered an error searching channels"
|
||||
},
|
||||
{
|
||||
"id": "store.sql_channel.search_group_channels.app_error",
|
||||
"translation": "Unable to get the group channels for the given user and term"
|
||||
},
|
||||
{
|
||||
"id": "store.sql_channel.set_delete_at.commit_transaction.app_error",
|
||||
"translation": "Unable to commit transaction"
|
||||
@@ -6758,6 +6762,10 @@
|
||||
"id": "store.sql_user.get_new_users.app_error",
|
||||
"translation": "We encountered an error while finding the new users"
|
||||
},
|
||||
{
|
||||
"id": "store.sql_user.get_profile_by_group_channel_ids_for_user.app_error",
|
||||
"translation": "We encountered an error while finding user profiles"
|
||||
},
|
||||
{
|
||||
"id": "store.sql_user.get_profiles.app_error",
|
||||
"translation": "We encountered an error while finding user profiles"
|
||||
|
||||
@@ -946,6 +946,20 @@ func (c *Client4) GetUsersByUsernames(usernames []string) ([]*User, *Response) {
|
||||
return UserListFromJson(r.Body), BuildResponse(r)
|
||||
}
|
||||
|
||||
// GetUsersByGroupChannelIds returns a map with channel ids as keys
|
||||
// and a list of users as values based on the provided user ids.
|
||||
func (c *Client4) GetUsersByGroupChannelIds(groupChannelIds []string) (map[string][]*User, *Response) {
|
||||
r, err := c.DoApiPost(c.GetUsersRoute()+"/group_channels", ArrayToJson(groupChannelIds))
|
||||
if err != nil {
|
||||
return nil, BuildErrorResponse(r, err)
|
||||
}
|
||||
defer closeBody(r)
|
||||
|
||||
usersByChannelId := map[string][]*User{}
|
||||
json.NewDecoder(r.Body).Decode(&usersByChannelId)
|
||||
return usersByChannelId, BuildResponse(r)
|
||||
}
|
||||
|
||||
// SearchUsers returns a list of users based on some search criteria.
|
||||
func (c *Client4) SearchUsers(search *UserSearch) ([]*User, *Response) {
|
||||
r, err := c.doApiPostBytes(c.GetUsersRoute()+"/search", search.ToJson())
|
||||
@@ -2056,6 +2070,16 @@ func (c *Client4) SearchAllChannels(search *ChannelSearch) (*ChannelListWithTeam
|
||||
return ChannelListWithTeamDataFromJson(r.Body), BuildResponse(r)
|
||||
}
|
||||
|
||||
// SearchGroupChannels returns the group channels of the user whose members' usernames match the search term.
|
||||
func (c *Client4) SearchGroupChannels(search *ChannelSearch) ([]*Channel, *Response) {
|
||||
r, err := c.DoApiPost(c.GetChannelsRoute()+"/group/search", search.ToJson())
|
||||
if err != nil {
|
||||
return nil, BuildErrorResponse(r, err)
|
||||
}
|
||||
defer closeBody(r)
|
||||
return ChannelSliceFromJson(r.Body), BuildResponse(r)
|
||||
}
|
||||
|
||||
// DeleteChannel deletes channel based on the provided channel id string.
|
||||
func (c *Client4) DeleteChannel(channelId string) (bool, *Response) {
|
||||
r, err := c.DoApiDelete(c.GetChannelRoute(channelId))
|
||||
|
||||
@@ -1473,7 +1473,7 @@ func (s SqlChannelStore) GetMemberForPost(postId string, userId string) (*model.
|
||||
Schemes TeamScheme ON Teams.SchemeId = TeamScheme.Id
|
||||
WHERE
|
||||
ChannelMembers.UserId = :UserId
|
||||
AND
|
||||
AND
|
||||
Posts.Id = :PostId`
|
||||
if err := s.GetReplica().SelectOne(&dbMember, query, map[string]interface{}{"UserId": userId, "PostId": postId}); err != nil {
|
||||
return nil, model.NewAppError("SqlChannelStore.GetMemberForPost", "store.sql_channel.get_member_for_post.app_error", nil, "postId="+postId+", err="+err.Error(), http.StatusInternalServerError)
|
||||
@@ -2237,6 +2237,104 @@ func (s SqlChannelStore) performSearch(searchQuery string, term string, paramete
|
||||
return &channels, nil
|
||||
}
|
||||
|
||||
func (s SqlChannelStore) getSearchGroupChannelsQuery(userId, term string, isPostgreSQL bool) (string, map[string]interface{}) {
|
||||
var query, baseLikeClause string
|
||||
if isPostgreSQL {
|
||||
baseLikeClause = "ARRAY_TO_STRING(ARRAY_AGG(u.Username), ', ') LIKE %s"
|
||||
query = `
|
||||
SELECT
|
||||
*
|
||||
FROM
|
||||
Channels
|
||||
WHERE
|
||||
Id IN (
|
||||
SELECT
|
||||
cc.Id
|
||||
FROM (
|
||||
SELECT
|
||||
c.Id
|
||||
FROM
|
||||
Channels c
|
||||
JOIN
|
||||
ChannelMembers cm on c.Id = cm.ChannelId
|
||||
JOIN
|
||||
Users u on u.Id = cm.UserId
|
||||
WHERE
|
||||
c.Type = 'G'
|
||||
AND
|
||||
u.Id = :UserId
|
||||
GROUP BY
|
||||
c.Id
|
||||
) cc
|
||||
JOIN
|
||||
ChannelMembers cm on cc.Id = cm.ChannelId
|
||||
JOIN
|
||||
Users u on u.Id = cm.UserId
|
||||
GROUP BY
|
||||
cc.Id
|
||||
HAVING
|
||||
%s
|
||||
LIMIT
|
||||
` + strconv.Itoa(model.CHANNEL_SEARCH_DEFAULT_LIMIT) + `
|
||||
)`
|
||||
} else {
|
||||
baseLikeClause = "GROUP_CONCAT(u.Username SEPARATOR ', ') LIKE %s"
|
||||
query = `
|
||||
SELECT
|
||||
cc.*
|
||||
FROM (
|
||||
SELECT
|
||||
c.*
|
||||
FROM
|
||||
Channels c
|
||||
JOIN
|
||||
ChannelMembers cm on c.Id = cm.ChannelId
|
||||
JOIN
|
||||
Users u on u.Id = cm.UserId
|
||||
WHERE
|
||||
c.Type = 'G'
|
||||
AND
|
||||
u.Id = :UserId
|
||||
GROUP BY
|
||||
c.Id
|
||||
) cc
|
||||
JOIN
|
||||
ChannelMembers cm on cc.Id = cm.ChannelId
|
||||
JOIN
|
||||
Users u on u.Id = cm.UserId
|
||||
GROUP BY
|
||||
cc.Id
|
||||
HAVING
|
||||
%s
|
||||
LIMIT
|
||||
` + strconv.Itoa(model.CHANNEL_SEARCH_DEFAULT_LIMIT)
|
||||
}
|
||||
|
||||
var likeClauses []string
|
||||
args := map[string]interface{}{"UserId": userId}
|
||||
terms := strings.Split(strings.ToLower(strings.Trim(term, " ")), " ")
|
||||
|
||||
for idx, term := range terms {
|
||||
argName := fmt.Sprintf("Term%v", idx)
|
||||
likeClauses = append(likeClauses, fmt.Sprintf(baseLikeClause, ":"+argName))
|
||||
args[argName] = "%" + term + "%"
|
||||
}
|
||||
|
||||
query = fmt.Sprintf(query, strings.Join(likeClauses, " AND "))
|
||||
return query, args
|
||||
}
|
||||
|
||||
func (s SqlChannelStore) SearchGroupChannels(userId, term string) (*model.ChannelList, *model.AppError) {
|
||||
isPostgreSQL := s.DriverName() == model.DATABASE_DRIVER_POSTGRES
|
||||
queryString, args := s.getSearchGroupChannelsQuery(userId, term, isPostgreSQL)
|
||||
|
||||
var groupChannels model.ChannelList
|
||||
if _, err := s.GetReplica().Select(&groupChannels, queryString, args); err != nil {
|
||||
return nil, model.NewAppError("SqlChannelStore.SearchGroupChannels", "store.sql_channel.search_group_channels.app_error", nil, "userId="+userId+", term="+term+", err="+err.Error(), http.StatusInternalServerError)
|
||||
}
|
||||
return &groupChannels, nil
|
||||
}
|
||||
|
||||
func (s SqlChannelStore) GetMembersByIds(channelId string, userIds []string) (*model.ChannelMembers, *model.AppError) {
|
||||
var dbMembers channelMemberWithSchemeRolesList
|
||||
props := make(map[string]interface{})
|
||||
|
||||
@@ -20,10 +20,11 @@ import (
|
||||
)
|
||||
|
||||
const (
|
||||
PROFILES_IN_CHANNEL_CACHE_SIZE = model.CHANNEL_CACHE_SIZE
|
||||
PROFILES_IN_CHANNEL_CACHE_SEC = 900 // 15 mins
|
||||
PROFILE_BY_IDS_CACHE_SIZE = model.SESSION_CACHE_SIZE
|
||||
PROFILE_BY_IDS_CACHE_SEC = 900 // 15 mins
|
||||
PROFILES_IN_CHANNEL_CACHE_SIZE = model.CHANNEL_CACHE_SIZE
|
||||
PROFILES_IN_CHANNEL_CACHE_SEC = 900 // 15 mins
|
||||
PROFILE_BY_IDS_CACHE_SIZE = model.SESSION_CACHE_SIZE
|
||||
PROFILE_BY_IDS_CACHE_SEC = 900 // 15 mins
|
||||
MAX_GROUP_CHANNELS_FOR_PROFILES = 50
|
||||
)
|
||||
|
||||
var (
|
||||
@@ -920,6 +921,60 @@ func (us SqlUserStore) GetProfileByIds(userIds []string, allowFromCache bool, vi
|
||||
})
|
||||
}
|
||||
|
||||
type UserWithChannel struct {
|
||||
model.User
|
||||
ChannelId string
|
||||
}
|
||||
|
||||
func (us SqlUserStore) GetProfileByGroupChannelIdsForUser(userId string, channelIds []string) (map[string][]*model.User, *model.AppError) {
|
||||
if len(channelIds) > MAX_GROUP_CHANNELS_FOR_PROFILES {
|
||||
channelIds = channelIds[0:MAX_GROUP_CHANNELS_FOR_PROFILES]
|
||||
}
|
||||
|
||||
isMemberQuery := fmt.Sprintf(`
|
||||
EXISTS(
|
||||
SELECT
|
||||
1
|
||||
FROM
|
||||
ChannelMembers
|
||||
WHERE
|
||||
UserId = '%s'
|
||||
AND
|
||||
ChannelId = cm.ChannelId
|
||||
)`, userId)
|
||||
|
||||
query := us.getQueryBuilder().
|
||||
Select("u.*, cm.ChannelId").
|
||||
From("Users u").
|
||||
Join("ChannelMembers cm ON u.Id = cm.UserId").
|
||||
Join("Channels c ON cm.ChannelId = c.Id").
|
||||
Where(sq.Eq{"c.Type": model.CHANNEL_GROUP, "cm.ChannelId": channelIds}).
|
||||
Where(isMemberQuery).
|
||||
Where(sq.NotEq{"u.Id": userId}).
|
||||
OrderBy("u.Username ASC")
|
||||
|
||||
queryString, args, err := query.ToSql()
|
||||
if err != nil {
|
||||
return nil, model.NewAppError("SqlUserStore.GetProfileByGroupChannelIdsForUser", "store.sql_user.app_error", nil, err.Error(), http.StatusInternalServerError)
|
||||
}
|
||||
|
||||
usersWithChannel := []*UserWithChannel{}
|
||||
if _, err := us.GetReplica().Select(&usersWithChannel, queryString, args...); err != nil {
|
||||
return nil, model.NewAppError("SqlUserStore.GetProfileByGroupChannelIdsForUser", "store.sql_user.get_profile_by_group_channel_ids_for_user.app_error", nil, err.Error(), http.StatusInternalServerError)
|
||||
}
|
||||
|
||||
usersByChannelId := map[string][]*model.User{}
|
||||
for _, user := range usersWithChannel {
|
||||
if val, ok := usersByChannelId[user.ChannelId]; ok {
|
||||
usersByChannelId[user.ChannelId] = append(val, &user.User)
|
||||
} else {
|
||||
usersByChannelId[user.ChannelId] = []*model.User{&user.User}
|
||||
}
|
||||
}
|
||||
|
||||
return usersByChannelId, nil
|
||||
}
|
||||
|
||||
func (us SqlUserStore) GetSystemAdminProfiles() store.StoreChannel {
|
||||
return store.Do(func(result *store.StoreResult) {
|
||||
query := us.usersQuery.
|
||||
|
||||
@@ -185,6 +185,7 @@ type ChannelStore interface {
|
||||
SearchAllChannels(term string, opts ChannelSearchOpts) StoreChannel
|
||||
SearchInTeam(teamId string, term string, includeDeleted bool) (*model.ChannelList, *model.AppError)
|
||||
SearchMore(userId string, teamId string, term string) (*model.ChannelList, *model.AppError)
|
||||
SearchGroupChannels(userId, term string) (*model.ChannelList, *model.AppError)
|
||||
GetMembersByIds(channelId string, userIds []string) (*model.ChannelMembers, *model.AppError)
|
||||
AnalyticsDeletedTypeCount(teamId string, channelType string) (int64, *model.AppError)
|
||||
GetChannelUnread(channelId, userId string) (*model.ChannelUnread, *model.AppError)
|
||||
@@ -267,6 +268,7 @@ type UserStore interface {
|
||||
GetAllProfiles(options *model.UserGetOptions) StoreChannel
|
||||
GetProfiles(options *model.UserGetOptions) StoreChannel
|
||||
GetProfileByIds(userId []string, allowFromCache bool, viewRestrictions *model.ViewUsersRestrictions) StoreChannel
|
||||
GetProfileByGroupChannelIdsForUser(userId string, channelIds []string) (map[string][]*model.User, *model.AppError)
|
||||
InvalidatProfileCacheForUser(userId string)
|
||||
GetByEmail(email string) (*model.User, *model.AppError)
|
||||
GetByAuth(authData *string, authService string) (*model.User, *model.AppError)
|
||||
|
||||
@@ -70,6 +70,7 @@ func TestChannelStore(t *testing.T, ss store.Store, s SqlSupplier) {
|
||||
t.Run("SearchAllChannels", func(t *testing.T) { testChannelStoreSearchAllChannels(t, ss) })
|
||||
t.Run("AutocompleteInTeamForSearch", func(t *testing.T) { testChannelStoreAutocompleteInTeamForSearch(t, ss, s) })
|
||||
t.Run("GetMembersByIds", func(t *testing.T) { testChannelStoreGetMembersByIds(t, ss) })
|
||||
t.Run("SearchGroupChannels", func(t *testing.T) { testChannelStoreSearchGroupChannels(t, ss) })
|
||||
t.Run("AnalyticsDeletedTypeCount", func(t *testing.T) { testChannelStoreAnalyticsDeletedTypeCount(t, ss) })
|
||||
t.Run("GetPinnedPosts", func(t *testing.T) { testChannelStoreGetPinnedPosts(t, ss) })
|
||||
t.Run("MaxChannelsPerTeam", func(t *testing.T) { testChannelStoreMaxChannelsPerTeam(t, ss) })
|
||||
@@ -2716,6 +2717,153 @@ func testChannelStoreGetMembersByIds(t *testing.T, ss store.Store) {
|
||||
}
|
||||
}
|
||||
|
||||
func testChannelStoreSearchGroupChannels(t *testing.T, ss store.Store) {
|
||||
// Users
|
||||
u1 := &model.User{}
|
||||
u1.Username = "user.one"
|
||||
u1.Email = MakeEmail()
|
||||
u1.Nickname = model.NewId()
|
||||
store.Must(ss.User().Save(u1))
|
||||
|
||||
u2 := &model.User{}
|
||||
u2.Username = "user.two"
|
||||
u2.Email = MakeEmail()
|
||||
u2.Nickname = model.NewId()
|
||||
store.Must(ss.User().Save(u2))
|
||||
|
||||
u3 := &model.User{}
|
||||
u3.Username = "user.three"
|
||||
u3.Email = MakeEmail()
|
||||
u3.Nickname = model.NewId()
|
||||
store.Must(ss.User().Save(u3))
|
||||
|
||||
u4 := &model.User{}
|
||||
u4.Username = "user.four"
|
||||
u4.Email = MakeEmail()
|
||||
u4.Nickname = model.NewId()
|
||||
store.Must(ss.User().Save(u4))
|
||||
|
||||
// Group channels
|
||||
userIds := []string{u1.Id, u2.Id, u3.Id}
|
||||
gc1 := model.Channel{}
|
||||
gc1.Name = model.GetGroupNameFromUserIds(userIds)
|
||||
gc1.DisplayName = "GroupChannel" + model.NewId()
|
||||
gc1.Type = model.CHANNEL_GROUP
|
||||
_, err := ss.Channel().Save(&gc1, -1)
|
||||
require.Nil(t, err)
|
||||
|
||||
for _, userId := range userIds {
|
||||
store.Must(ss.Channel().SaveMember(&model.ChannelMember{
|
||||
ChannelId: gc1.Id,
|
||||
UserId: userId,
|
||||
NotifyProps: model.GetDefaultChannelNotifyProps(),
|
||||
}))
|
||||
}
|
||||
|
||||
userIds = []string{u1.Id, u4.Id}
|
||||
gc2 := model.Channel{}
|
||||
gc2.Name = model.GetGroupNameFromUserIds(userIds)
|
||||
gc2.DisplayName = "GroupChannel" + model.NewId()
|
||||
gc2.Type = model.CHANNEL_GROUP
|
||||
_, err = ss.Channel().Save(&gc2, -1)
|
||||
require.Nil(t, err)
|
||||
|
||||
for _, userId := range userIds {
|
||||
store.Must(ss.Channel().SaveMember(&model.ChannelMember{
|
||||
ChannelId: gc2.Id,
|
||||
UserId: userId,
|
||||
NotifyProps: model.GetDefaultChannelNotifyProps(),
|
||||
}))
|
||||
}
|
||||
|
||||
userIds = []string{u1.Id, u2.Id, u3.Id, u4.Id}
|
||||
gc3 := model.Channel{}
|
||||
gc3.Name = model.GetGroupNameFromUserIds(userIds)
|
||||
gc3.DisplayName = "GroupChannel" + model.NewId()
|
||||
gc3.Type = model.CHANNEL_GROUP
|
||||
_, err = ss.Channel().Save(&gc3, -1)
|
||||
require.Nil(t, err)
|
||||
|
||||
for _, userId := range userIds {
|
||||
store.Must(ss.Channel().SaveMember(&model.ChannelMember{
|
||||
ChannelId: gc3.Id,
|
||||
UserId: userId,
|
||||
NotifyProps: model.GetDefaultChannelNotifyProps(),
|
||||
}))
|
||||
}
|
||||
|
||||
defer func() {
|
||||
for _, gc := range []model.Channel{gc1, gc2, gc3} {
|
||||
ss.Channel().PermanentDeleteMembersByChannel(gc3.Id)
|
||||
<-ss.Channel().PermanentDelete(gc.Id)
|
||||
}
|
||||
}()
|
||||
|
||||
testCases := []struct {
|
||||
Name string
|
||||
UserId string
|
||||
Term string
|
||||
ExpectedResult []string
|
||||
}{
|
||||
{
|
||||
Name: "Get all group channels for user1",
|
||||
UserId: u1.Id,
|
||||
Term: "",
|
||||
ExpectedResult: []string{gc1.Id, gc2.Id, gc3.Id},
|
||||
},
|
||||
{
|
||||
Name: "Get group channels for user1 and term 'three'",
|
||||
UserId: u1.Id,
|
||||
Term: "three",
|
||||
ExpectedResult: []string{gc1.Id, gc3.Id},
|
||||
},
|
||||
{
|
||||
Name: "Get group channels for user1 and term 'four two'",
|
||||
UserId: u1.Id,
|
||||
Term: "four two",
|
||||
ExpectedResult: []string{gc3.Id},
|
||||
},
|
||||
{
|
||||
Name: "Get all group channels for user2",
|
||||
UserId: u2.Id,
|
||||
Term: "",
|
||||
ExpectedResult: []string{gc1.Id, gc3.Id},
|
||||
},
|
||||
{
|
||||
Name: "Get group channels for user2 and term 'four'",
|
||||
UserId: u2.Id,
|
||||
Term: "four",
|
||||
ExpectedResult: []string{gc3.Id},
|
||||
},
|
||||
{
|
||||
Name: "Get all group channels for user4",
|
||||
UserId: u4.Id,
|
||||
Term: "",
|
||||
ExpectedResult: []string{gc2.Id, gc3.Id},
|
||||
},
|
||||
{
|
||||
Name: "Get group channels for user4 and term 'one five'",
|
||||
UserId: u4.Id,
|
||||
Term: "one five",
|
||||
ExpectedResult: []string{},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range testCases {
|
||||
t.Run(tc.Name, func(t *testing.T) {
|
||||
result, err := ss.Channel().SearchGroupChannels(tc.UserId, tc.Term)
|
||||
require.Nil(t, err)
|
||||
|
||||
resultIds := []string{}
|
||||
for _, gc := range *result {
|
||||
resultIds = append(resultIds, gc.Id)
|
||||
}
|
||||
|
||||
require.ElementsMatch(t, tc.ExpectedResult, resultIds)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func testChannelStoreAnalyticsDeletedTypeCount(t *testing.T, ss store.Store) {
|
||||
o1 := model.Channel{}
|
||||
o1.TeamId = model.NewId()
|
||||
|
||||
@@ -1283,6 +1283,31 @@ func (_m *ChannelStore) SearchAllChannels(term string, opts store.ChannelSearchO
|
||||
return r0
|
||||
}
|
||||
|
||||
// SearchGroupChannels provides a mock function with given fields: userId, term
|
||||
func (_m *ChannelStore) SearchGroupChannels(userId string, term string) (*model.ChannelList, *model.AppError) {
|
||||
ret := _m.Called(userId, term)
|
||||
|
||||
var r0 *model.ChannelList
|
||||
if rf, ok := ret.Get(0).(func(string, string) *model.ChannelList); ok {
|
||||
r0 = rf(userId, term)
|
||||
} else {
|
||||
if ret.Get(0) != nil {
|
||||
r0 = ret.Get(0).(*model.ChannelList)
|
||||
}
|
||||
}
|
||||
|
||||
var r1 *model.AppError
|
||||
if rf, ok := ret.Get(1).(func(string, string) *model.AppError); ok {
|
||||
r1 = rf(userId, term)
|
||||
} else {
|
||||
if ret.Get(1) != nil {
|
||||
r1 = ret.Get(1).(*model.AppError)
|
||||
}
|
||||
}
|
||||
|
||||
return r0, r1
|
||||
}
|
||||
|
||||
// SearchInTeam provides a mock function with given fields: teamId, term, includeDeleted
|
||||
func (_m *ChannelStore) SearchInTeam(teamId string, term string, includeDeleted bool) (*model.ChannelList, *model.AppError) {
|
||||
ret := _m.Called(teamId, term, includeDeleted)
|
||||
|
||||
@@ -381,6 +381,31 @@ func (_m *UserStore) GetNewUsersForTeam(teamId string, offset int, limit int, vi
|
||||
return r0
|
||||
}
|
||||
|
||||
// GetProfileByGroupChannelIdsForUser provides a mock function with given fields: userId, channelIds
|
||||
func (_m *UserStore) GetProfileByGroupChannelIdsForUser(userId string, channelIds []string) (map[string][]*model.User, *model.AppError) {
|
||||
ret := _m.Called(userId, channelIds)
|
||||
|
||||
var r0 map[string][]*model.User
|
||||
if rf, ok := ret.Get(0).(func(string, []string) map[string][]*model.User); ok {
|
||||
r0 = rf(userId, channelIds)
|
||||
} else {
|
||||
if ret.Get(0) != nil {
|
||||
r0 = ret.Get(0).(map[string][]*model.User)
|
||||
}
|
||||
}
|
||||
|
||||
var r1 *model.AppError
|
||||
if rf, ok := ret.Get(1).(func(string, []string) *model.AppError); ok {
|
||||
r1 = rf(userId, channelIds)
|
||||
} else {
|
||||
if ret.Get(1) != nil {
|
||||
r1 = ret.Get(1).(*model.AppError)
|
||||
}
|
||||
}
|
||||
|
||||
return r0, r1
|
||||
}
|
||||
|
||||
// GetProfileByIds provides a mock function with given fields: userId, allowFromCache, viewRestrictions
|
||||
func (_m *UserStore) GetProfileByIds(userId []string, allowFromCache bool, viewRestrictions *model.ViewUsersRestrictions) store.StoreChannel {
|
||||
ret := _m.Called(userId, allowFromCache, viewRestrictions)
|
||||
|
||||
@@ -42,6 +42,7 @@ func TestUserStore(t *testing.T, ss store.Store) {
|
||||
t.Run("GetAllProfilesInChannel", func(t *testing.T) { testUserStoreGetAllProfilesInChannel(t, ss) })
|
||||
t.Run("GetProfilesNotInChannel", func(t *testing.T) { testUserStoreGetProfilesNotInChannel(t, ss) })
|
||||
t.Run("GetProfilesByIds", func(t *testing.T) { testUserStoreGetProfilesByIds(t, ss) })
|
||||
t.Run("GetProfileByGroupChannelIdsForUser", func(t *testing.T) { testUserStoreGetProfileByGroupChannelIdsForUser(t, ss) })
|
||||
t.Run("GetProfilesByUsernames", func(t *testing.T) { testUserStoreGetProfilesByUsernames(t, ss) })
|
||||
t.Run("GetSystemAdminProfiles", func(t *testing.T) { testUserStoreGetSystemAdminProfiles(t, ss) })
|
||||
t.Run("GetByEmail", func(t *testing.T) { testUserStoreGetByEmail(t, ss) })
|
||||
@@ -1217,6 +1218,122 @@ func testUserStoreGetProfilesByIds(t *testing.T, ss store.Store) {
|
||||
})
|
||||
}
|
||||
|
||||
func testUserStoreGetProfileByGroupChannelIdsForUser(t *testing.T, ss store.Store) {
|
||||
u1 := store.Must(ss.User().Save(&model.User{
|
||||
Email: MakeEmail(),
|
||||
Username: "u1" + model.NewId(),
|
||||
})).(*model.User)
|
||||
defer func() { require.Nil(t, ss.User().PermanentDelete(u1.Id)) }()
|
||||
|
||||
u2 := store.Must(ss.User().Save(&model.User{
|
||||
Email: MakeEmail(),
|
||||
Username: "u2" + model.NewId(),
|
||||
})).(*model.User)
|
||||
defer func() { require.Nil(t, ss.User().PermanentDelete(u2.Id)) }()
|
||||
|
||||
u3 := store.Must(ss.User().Save(&model.User{
|
||||
Email: MakeEmail(),
|
||||
Username: "u3" + model.NewId(),
|
||||
})).(*model.User)
|
||||
defer func() { require.Nil(t, ss.User().PermanentDelete(u3.Id)) }()
|
||||
|
||||
u4 := store.Must(ss.User().Save(&model.User{
|
||||
Email: MakeEmail(),
|
||||
Username: "u4" + model.NewId(),
|
||||
})).(*model.User)
|
||||
defer func() { require.Nil(t, ss.User().PermanentDelete(u4.Id)) }()
|
||||
|
||||
gc1, err := ss.Channel().Save(&model.Channel{
|
||||
DisplayName: "Profiles in private",
|
||||
Name: "profiles-" + model.NewId(),
|
||||
Type: model.CHANNEL_GROUP,
|
||||
}, -1)
|
||||
require.Nil(t, err)
|
||||
|
||||
for _, uId := range []string{u1.Id, u2.Id, u3.Id} {
|
||||
store.Must(ss.Channel().SaveMember(&model.ChannelMember{
|
||||
ChannelId: gc1.Id,
|
||||
UserId: uId,
|
||||
NotifyProps: model.GetDefaultChannelNotifyProps(),
|
||||
}))
|
||||
}
|
||||
|
||||
gc2, err := ss.Channel().Save(&model.Channel{
|
||||
DisplayName: "Profiles in private",
|
||||
Name: "profiles-" + model.NewId(),
|
||||
Type: model.CHANNEL_GROUP,
|
||||
}, -1)
|
||||
require.Nil(t, err)
|
||||
|
||||
for _, uId := range []string{u1.Id, u3.Id, u4.Id} {
|
||||
store.Must(ss.Channel().SaveMember(&model.ChannelMember{
|
||||
ChannelId: gc2.Id,
|
||||
UserId: uId,
|
||||
NotifyProps: model.GetDefaultChannelNotifyProps(),
|
||||
}))
|
||||
}
|
||||
|
||||
testCases := []struct {
|
||||
Name string
|
||||
UserId string
|
||||
ChannelIds []string
|
||||
ExpectedUserIdsByChannel map[string][]string
|
||||
EnsureChannelsNotInResults []string
|
||||
}{
|
||||
{
|
||||
Name: "Get group 1 as user 1",
|
||||
UserId: u1.Id,
|
||||
ChannelIds: []string{gc1.Id},
|
||||
ExpectedUserIdsByChannel: map[string][]string{
|
||||
gc1.Id: {u2.Id, u3.Id},
|
||||
},
|
||||
EnsureChannelsNotInResults: []string{},
|
||||
},
|
||||
{
|
||||
Name: "Get groups 1 and 2 as user 1",
|
||||
UserId: u1.Id,
|
||||
ChannelIds: []string{gc1.Id, gc2.Id},
|
||||
ExpectedUserIdsByChannel: map[string][]string{
|
||||
gc1.Id: {u2.Id, u3.Id},
|
||||
gc2.Id: {u3.Id, u4.Id},
|
||||
},
|
||||
EnsureChannelsNotInResults: []string{},
|
||||
},
|
||||
{
|
||||
Name: "Get groups 1 and 2 as user 2",
|
||||
UserId: u2.Id,
|
||||
ChannelIds: []string{gc1.Id, gc2.Id},
|
||||
ExpectedUserIdsByChannel: map[string][]string{
|
||||
gc1.Id: {u1.Id, u3.Id},
|
||||
},
|
||||
EnsureChannelsNotInResults: []string{gc2.Id},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range testCases {
|
||||
t.Run(tc.Name, func(t *testing.T) {
|
||||
res, err := ss.User().GetProfileByGroupChannelIdsForUser(tc.UserId, tc.ChannelIds)
|
||||
require.Nil(t, err)
|
||||
|
||||
for channelId, expectedUsers := range tc.ExpectedUserIdsByChannel {
|
||||
users, ok := res[channelId]
|
||||
require.True(t, ok)
|
||||
|
||||
userIds := []string{}
|
||||
for _, user := range users {
|
||||
userIds = append(userIds, user.Id)
|
||||
}
|
||||
require.ElementsMatch(t, expectedUsers, userIds)
|
||||
}
|
||||
|
||||
for _, channelId := range tc.EnsureChannelsNotInResults {
|
||||
_, ok := res[channelId]
|
||||
require.False(t, ok)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func testUserStoreGetProfilesByUsernames(t *testing.T, ss store.Store) {
|
||||
teamId := model.NewId()
|
||||
team2Id := model.NewId()
|
||||
|
||||
Ссылка в новой задаче
Block a user