Adding the new search engine abstraction (#13304)
* WIP * Adding bleve to go modules * WIP * Adding missing files from searchengine implementation * WIP * WIP * WIP * WIP * WIP * WIP * User and channel indexing and searches implemented * Make bleve tests run with in-memory indexes * Implement post index and deletion tests * Initial commits for the search layer * Removing unnecesary indexing * WIP * WIP * More fixes for tests * Adding the search layer * Finishing the migration of searchers to the layer * Removing unnecesary code * Allowing multiple engines active at the same time * WIP * Add simple post search * Print information when using bleve * Adding some debugging to understand better how the searches are working * Making more dynamic config of search engines * Add post search basics * Adding the Purge API endpoint * Fixing bleve config updates * Adding missed file * Regenerating search engine mocks * Adding missed v5 to modules imports * fixing i18n * Fixing some test around search engine * Removing all bleve traces * Cleaning up the vendors directory and go.mod/go.sum files * Regenerating timer layer * Adding properly the license * Fixing govet shadow error * Fixing some tests * Fixing TestSearchPostsFromUser * Fixing another test * Fixing more tests * Fixing more tests * Removing SearchEngine redundant text from searchengine module code * Fixing some reindexing problems in members updates * Fixing tests * Addressing PR comments * Reverting go.mod and go.sum * Addressing PR comments * Fixing tests compilation * Fixing govet * Adding search engine stop method * Being more explicit on where we use includeDeleted * Adding GetSqlSupplier test helper method * Mocking elasticsearch start function * Fixing tests Co-authored-by: Miguel de la Cruz <miguel@mcrx.me> Co-authored-by: mattermod <mattermod@users.noreply.github.com>
Этот коммит содержится в:
коммит произвёл
GitHub
родитель
e2883bfe5f
Коммит
c66e182b08
207
store/searchlayer/channel_layer.go
Обычный файл
207
store/searchlayer/channel_layer.go
Обычный файл
@@ -0,0 +1,207 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
package searchlayer
|
||||
|
||||
import (
|
||||
"github.com/mattermost/mattermost-server/v5/mlog"
|
||||
"github.com/mattermost/mattermost-server/v5/model"
|
||||
"github.com/mattermost/mattermost-server/v5/services/searchengine"
|
||||
"github.com/mattermost/mattermost-server/v5/store"
|
||||
)
|
||||
|
||||
type SearchChannelStore struct {
|
||||
store.ChannelStore
|
||||
rootStore *SearchStore
|
||||
}
|
||||
|
||||
func (c *SearchChannelStore) deleteChannelIndex(channel *model.Channel) {
|
||||
if channel.Type == model.CHANNEL_OPEN {
|
||||
for _, engine := range c.rootStore.searchEngine.GetActiveEngines() {
|
||||
if engine.IsIndexingEnabled() {
|
||||
go (func(engineCopy searchengine.SearchEngineInterface) {
|
||||
if err := engineCopy.DeleteChannel(channel); err != nil {
|
||||
mlog.Error("Encountered error deleting channel", mlog.String("channel_id", channel.Id), mlog.String("search_engine", engineCopy.GetName()), mlog.Err(err))
|
||||
}
|
||||
mlog.Debug("Removed channel from index in search engine", mlog.String("search_engine", engineCopy.GetName()), mlog.String("channel_id", channel.Id))
|
||||
})(engine)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (c *SearchChannelStore) indexChannel(channel *model.Channel) {
|
||||
if channel.Type == model.CHANNEL_OPEN {
|
||||
for _, engine := range c.rootStore.searchEngine.GetActiveEngines() {
|
||||
if engine.IsIndexingEnabled() {
|
||||
go (func(engineCopy searchengine.SearchEngineInterface) {
|
||||
if err := engineCopy.IndexChannel(channel); err != nil {
|
||||
mlog.Error("Encountered error indexing channel", mlog.String("channel_id", channel.Id), mlog.String("search_engine", engineCopy.GetName()), mlog.Err(err))
|
||||
}
|
||||
mlog.Debug("Indexed channel in search engine", mlog.String("search_engine", engineCopy.GetName()), mlog.String("channel_id", channel.Id))
|
||||
})(engine)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (c *SearchChannelStore) Save(channel *model.Channel, maxChannels int64) (*model.Channel, *model.AppError) {
|
||||
newChannel, err := c.ChannelStore.Save(channel, maxChannels)
|
||||
if err == nil {
|
||||
c.indexChannel(newChannel)
|
||||
}
|
||||
return newChannel, err
|
||||
}
|
||||
|
||||
func (c *SearchChannelStore) Update(channel *model.Channel) (*model.Channel, *model.AppError) {
|
||||
updatedChannel, err := c.ChannelStore.Update(channel)
|
||||
if err == nil {
|
||||
c.indexChannel(updatedChannel)
|
||||
}
|
||||
return updatedChannel, err
|
||||
}
|
||||
|
||||
func (c *SearchChannelStore) UpdateMember(cm *model.ChannelMember) (*model.ChannelMember, *model.AppError) {
|
||||
member, err := c.ChannelStore.UpdateMember(cm)
|
||||
if err == nil {
|
||||
c.rootStore.indexUserFromID(cm.UserId)
|
||||
channel, channelErr := c.ChannelStore.Get(member.ChannelId, true)
|
||||
if channelErr != nil {
|
||||
mlog.Error("Encountered error indexing user in channel", mlog.String("channel_id", member.ChannelId), mlog.Err(err))
|
||||
} else {
|
||||
c.rootStore.indexUserFromID(channel.CreatorId)
|
||||
}
|
||||
}
|
||||
return member, err
|
||||
}
|
||||
|
||||
func (c *SearchChannelStore) SaveMember(cm *model.ChannelMember) (*model.ChannelMember, *model.AppError) {
|
||||
member, err := c.ChannelStore.SaveMember(cm)
|
||||
if err == nil {
|
||||
c.rootStore.indexUserFromID(cm.UserId)
|
||||
channel, channelErr := c.ChannelStore.Get(member.ChannelId, true)
|
||||
if channelErr != nil {
|
||||
mlog.Error("Encountered error indexing user in channel", mlog.String("channel_id", member.ChannelId), mlog.Err(err))
|
||||
} else {
|
||||
c.rootStore.indexUserFromID(channel.CreatorId)
|
||||
}
|
||||
}
|
||||
return member, err
|
||||
}
|
||||
|
||||
func (c *SearchChannelStore) RemoveMember(channelId, userIdToRemove string) *model.AppError {
|
||||
err := c.ChannelStore.RemoveMember(channelId, userIdToRemove)
|
||||
if err == nil {
|
||||
c.rootStore.indexUserFromID(userIdToRemove)
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
func (c *SearchChannelStore) CreateDirectChannel(user *model.User, otherUser *model.User) (*model.Channel, *model.AppError) {
|
||||
channel, err := c.ChannelStore.CreateDirectChannel(user, otherUser)
|
||||
if err == nil {
|
||||
c.rootStore.indexUserFromID(user.Id)
|
||||
c.rootStore.indexUserFromID(otherUser.Id)
|
||||
}
|
||||
return channel, err
|
||||
}
|
||||
|
||||
func (c *SearchChannelStore) AutocompleteInTeam(teamId string, term string, includeDeleted bool) (*model.ChannelList, *model.AppError) {
|
||||
var channelList *model.ChannelList
|
||||
var err *model.AppError
|
||||
|
||||
allFailed := true
|
||||
for _, engine := range c.rootStore.searchEngine.GetActiveEngines() {
|
||||
if engine.IsAutocompletionEnabled() {
|
||||
channelList, err = c.searchAutocompleteChannels(engine, teamId, term, includeDeleted)
|
||||
if err != nil {
|
||||
mlog.Error("Encountered error on AutocompleteChannels through SearchEngine. Falling back to default autocompletion.", mlog.String("search_engine", engine.GetName()), mlog.Err(err))
|
||||
continue
|
||||
}
|
||||
allFailed = false
|
||||
mlog.Debug("Using the first available search engine", mlog.String("search_engine", engine.GetName()))
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if allFailed {
|
||||
mlog.Debug("Using database search because no other search engine is available")
|
||||
channelList, err = c.ChannelStore.AutocompleteInTeam(teamId, term, includeDeleted)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
return channelList, err
|
||||
}
|
||||
|
||||
func (c *SearchChannelStore) searchAutocompleteChannels(engine searchengine.SearchEngineInterface, teamId, term string, includeDeleted bool) (*model.ChannelList, *model.AppError) {
|
||||
channelIds, err := engine.SearchChannels(teamId, term)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
channelList := model.ChannelList{}
|
||||
if len(channelIds) > 0 {
|
||||
channels, err := c.ChannelStore.GetChannelsByIds(channelIds, includeDeleted)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
for _, ch := range channels {
|
||||
channelList = append(channelList, ch)
|
||||
}
|
||||
}
|
||||
|
||||
return &channelList, nil
|
||||
}
|
||||
|
||||
func (c *SearchChannelStore) PermanentDeleteMembersByUser(userId string) *model.AppError {
|
||||
err := c.ChannelStore.PermanentDeleteMembersByUser(userId)
|
||||
if err == nil {
|
||||
c.rootStore.indexUserFromID(userId)
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
func (c *SearchChannelStore) RemoveAllDeactivatedMembers(channelId string) *model.AppError {
|
||||
profiles, errProfiles := c.rootStore.User().GetAllProfilesInChannel(channelId, true)
|
||||
if errProfiles != nil {
|
||||
mlog.Error("Encountered error indexing users for channel", mlog.String("channel_id", channelId), mlog.Err(errProfiles))
|
||||
}
|
||||
|
||||
err := c.ChannelStore.RemoveAllDeactivatedMembers(channelId)
|
||||
if err == nil && errProfiles == nil {
|
||||
for _, user := range profiles {
|
||||
if user.DeleteAt != 0 {
|
||||
c.rootStore.indexUser(user)
|
||||
}
|
||||
}
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
func (c *SearchChannelStore) PermanentDeleteMembersByChannel(channelId string) *model.AppError {
|
||||
profiles, errProfiles := c.rootStore.User().GetAllProfilesInChannel(channelId, true)
|
||||
if errProfiles != nil {
|
||||
mlog.Error("Encountered error indexing users for channel", mlog.String("channel_id", channelId), mlog.Err(errProfiles))
|
||||
}
|
||||
|
||||
err := c.ChannelStore.PermanentDeleteMembersByChannel(channelId)
|
||||
if err == nil && errProfiles == nil {
|
||||
for _, user := range profiles {
|
||||
c.rootStore.indexUser(user)
|
||||
}
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
func (c *SearchChannelStore) PermanentDelete(channelId string) *model.AppError {
|
||||
channel, channelErr := c.ChannelStore.Get(channelId, true)
|
||||
if channelErr != nil {
|
||||
mlog.Error("Encountered error deleting channel", mlog.String("channel_id", channelId), mlog.Err(channelErr))
|
||||
}
|
||||
err := c.ChannelStore.PermanentDelete(channelId)
|
||||
if err == nil && channelErr == nil {
|
||||
c.deleteChannelIndex(channel)
|
||||
}
|
||||
return err
|
||||
}
|
||||
93
store/searchlayer/layer.go
Обычный файл
93
store/searchlayer/layer.go
Обычный файл
@@ -0,0 +1,93 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
package searchlayer
|
||||
|
||||
import (
|
||||
"github.com/mattermost/mattermost-server/v5/mlog"
|
||||
"github.com/mattermost/mattermost-server/v5/model"
|
||||
"github.com/mattermost/mattermost-server/v5/services/searchengine"
|
||||
"github.com/mattermost/mattermost-server/v5/store"
|
||||
)
|
||||
|
||||
type SearchStore struct {
|
||||
store.Store
|
||||
searchEngine *searchengine.Broker
|
||||
user *SearchUserStore
|
||||
team *SearchTeamStore
|
||||
channel *SearchChannelStore
|
||||
post *SearchPostStore
|
||||
}
|
||||
|
||||
func NewSearchLayer(baseStore store.Store, searchEngine *searchengine.Broker) SearchStore {
|
||||
searchStore := SearchStore{
|
||||
Store: baseStore,
|
||||
searchEngine: searchEngine,
|
||||
}
|
||||
searchStore.channel = &SearchChannelStore{ChannelStore: baseStore.Channel(), rootStore: &searchStore}
|
||||
searchStore.post = &SearchPostStore{PostStore: baseStore.Post(), rootStore: &searchStore}
|
||||
searchStore.team = &SearchTeamStore{TeamStore: baseStore.Team(), rootStore: &searchStore}
|
||||
searchStore.user = &SearchUserStore{UserStore: baseStore.User(), rootStore: &searchStore}
|
||||
|
||||
return searchStore
|
||||
}
|
||||
|
||||
func (s SearchStore) Channel() store.ChannelStore {
|
||||
return s.channel
|
||||
}
|
||||
|
||||
func (s SearchStore) Post() store.PostStore {
|
||||
return s.post
|
||||
}
|
||||
|
||||
func (s SearchStore) Team() store.TeamStore {
|
||||
return s.team
|
||||
}
|
||||
|
||||
func (s SearchStore) User() store.UserStore {
|
||||
return s.user
|
||||
}
|
||||
|
||||
func (s SearchStore) indexUserFromID(userId string) {
|
||||
user, err := s.User().Get(userId)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
s.indexUser(user)
|
||||
}
|
||||
|
||||
func (s SearchStore) indexUser(user *model.User) {
|
||||
for _, engine := range s.searchEngine.GetActiveEngines() {
|
||||
if engine.IsIndexingEnabled() {
|
||||
go (func(engineCopy searchengine.SearchEngineInterface) {
|
||||
userTeams, err := s.Team().GetTeamsByUserId(user.Id)
|
||||
if err != nil {
|
||||
mlog.Error("Encountered error indexing user", mlog.String("user_id", user.Id), mlog.String("search_engine", engineCopy.GetName()), mlog.Err(err))
|
||||
return
|
||||
}
|
||||
|
||||
userTeamsIds := []string{}
|
||||
for _, team := range userTeams {
|
||||
userTeamsIds = append(userTeamsIds, team.Id)
|
||||
}
|
||||
|
||||
userChannelMembers, err := s.Channel().GetAllChannelMembersForUser(user.Id, false, true)
|
||||
if err != nil {
|
||||
mlog.Error("Encountered error indexing user", mlog.String("user_id", user.Id), mlog.String("search_engine", engineCopy.GetName()), mlog.Err(err))
|
||||
return
|
||||
}
|
||||
|
||||
userChannelsIds := []string{}
|
||||
for channelId := range userChannelMembers {
|
||||
userChannelsIds = append(userChannelsIds, channelId)
|
||||
}
|
||||
|
||||
if err := engineCopy.IndexUser(user, userTeamsIds, userChannelsIds); err != nil {
|
||||
mlog.Error("Encountered error indexing user", mlog.String("user_id", user.Id), mlog.String("search_engine", engineCopy.GetName()), mlog.Err(err))
|
||||
return
|
||||
}
|
||||
mlog.Debug("Indexed user in search engine", mlog.String("search_engine", engineCopy.GetName()), mlog.String("user_id", user.Id))
|
||||
})(engine)
|
||||
}
|
||||
}
|
||||
}
|
||||
126
store/searchlayer/post_layer.go
Обычный файл
126
store/searchlayer/post_layer.go
Обычный файл
@@ -0,0 +1,126 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
package searchlayer
|
||||
|
||||
import (
|
||||
"github.com/mattermost/mattermost-server/v5/mlog"
|
||||
"github.com/mattermost/mattermost-server/v5/model"
|
||||
"github.com/mattermost/mattermost-server/v5/services/searchengine"
|
||||
"github.com/mattermost/mattermost-server/v5/store"
|
||||
)
|
||||
|
||||
type SearchPostStore struct {
|
||||
store.PostStore
|
||||
rootStore *SearchStore
|
||||
}
|
||||
|
||||
func (s SearchPostStore) indexPost(post *model.Post) {
|
||||
for _, engine := range s.rootStore.searchEngine.GetActiveEngines() {
|
||||
if engine.IsIndexingEnabled() {
|
||||
go (func(engineCopy searchengine.SearchEngineInterface) {
|
||||
channel, chanErr := s.rootStore.Channel().GetForPost(post.Id)
|
||||
if chanErr != nil {
|
||||
mlog.Error("Couldn't get channel for post for SearchEngine indexing.", mlog.String("channel_id", post.ChannelId), mlog.String("search_engine", engineCopy.GetName()), mlog.String("post_id", post.Id))
|
||||
return
|
||||
}
|
||||
if err := engineCopy.IndexPost(post, channel.TeamId); err != nil {
|
||||
mlog.Error("Encountered error indexing post", mlog.String("post_id", post.Id), mlog.String("search_engine", engineCopy.GetName()), mlog.Err(err))
|
||||
}
|
||||
mlog.Debug("Indexed post in search engine", mlog.String("search_engine", engineCopy.GetName()), mlog.String("post_id", post.Id))
|
||||
})(engine)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (s SearchPostStore) deletePostIndex(post *model.Post) {
|
||||
for _, engine := range s.rootStore.searchEngine.GetActiveEngines() {
|
||||
if engine.IsIndexingEnabled() {
|
||||
go (func(engineCopy searchengine.SearchEngineInterface) {
|
||||
if err := engineCopy.DeletePost(post); err != nil {
|
||||
mlog.Error("Encountered error deleting post", mlog.String("post_id", post.Id), mlog.String("search_engine", engineCopy.GetName()), mlog.Err(err))
|
||||
}
|
||||
mlog.Debug("Removed post from the index in search engine", mlog.String("search_engine", engineCopy.GetName()), mlog.String("post_id", post.Id))
|
||||
})(engine)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (s SearchPostStore) Update(newPost, oldPost *model.Post) (*model.Post, *model.AppError) {
|
||||
post, err := s.PostStore.Update(newPost, oldPost)
|
||||
|
||||
if err == nil {
|
||||
s.indexPost(post)
|
||||
}
|
||||
return post, err
|
||||
}
|
||||
|
||||
func (s SearchPostStore) Save(post *model.Post) (*model.Post, *model.AppError) {
|
||||
npost, err := s.PostStore.Save(post)
|
||||
|
||||
if err == nil {
|
||||
s.indexPost(npost)
|
||||
}
|
||||
return npost, err
|
||||
}
|
||||
|
||||
func (s SearchPostStore) Delete(postId string, date int64, deletedByID string) *model.AppError {
|
||||
err := s.PostStore.Delete(postId, date, deletedByID)
|
||||
|
||||
if err == nil {
|
||||
postList, err2 := s.PostStore.Get(postId, true)
|
||||
if postList != nil && len(postList.Order) > 0 {
|
||||
if err2 != nil {
|
||||
s.deletePostIndex(postList.Posts[postList.Order[0]])
|
||||
}
|
||||
}
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
func (s SearchPostStore) searchPostsInTeamForUserByEngine(engine searchengine.SearchEngineInterface, paramsList []*model.SearchParams, userId, teamId string, isOrSearch, includeDeletedChannels bool, page, perPage int) (*model.PostSearchResults, *model.AppError) {
|
||||
// We only allow the user to search in channels they are a member of.
|
||||
userChannels, err := s.rootStore.Channel().GetChannels(teamId, userId, includeDeletedChannels)
|
||||
if err != nil {
|
||||
mlog.Error("error getting channel for user", mlog.Err(err))
|
||||
return nil, err
|
||||
}
|
||||
|
||||
postIds, matches, err := engine.SearchPosts(userChannels, paramsList, page, perPage)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Get the posts
|
||||
postList := model.NewPostList()
|
||||
if len(postIds) > 0 {
|
||||
posts, err := s.PostStore.GetPostsByIds(postIds)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
for _, p := range posts {
|
||||
if p.DeleteAt == 0 {
|
||||
postList.AddPost(p)
|
||||
postList.AddOrder(p.Id)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return model.MakePostSearchResults(postList, matches), nil
|
||||
}
|
||||
|
||||
func (s SearchPostStore) SearchPostsInTeamForUser(paramsList []*model.SearchParams, userId, teamId string, isOrSearch, includeDeletedChannels bool, page, perPage int) (*model.PostSearchResults, *model.AppError) {
|
||||
for _, engine := range s.rootStore.searchEngine.GetActiveEngines() {
|
||||
if engine.IsSearchEnabled() {
|
||||
results, err := s.searchPostsInTeamForUserByEngine(engine, paramsList, userId, teamId, isOrSearch, includeDeletedChannels, page, perPage)
|
||||
if err != nil {
|
||||
mlog.Error("Encountered error on SearchPostsInTeamForUser.", mlog.String("search_engine", engine.GetName()), mlog.Err(err))
|
||||
continue
|
||||
}
|
||||
mlog.Debug("Using the first available search engine", mlog.String("search_engine", engine.GetName()))
|
||||
return results, err
|
||||
}
|
||||
}
|
||||
mlog.Debug("Using database search because no other search engine is available")
|
||||
return s.PostStore.SearchPostsInTeamForUser(paramsList, userId, teamId, isOrSearch, includeDeletedChannels, page, perPage)
|
||||
}
|
||||
46
store/searchlayer/team_layer.go
Обычный файл
46
store/searchlayer/team_layer.go
Обычный файл
@@ -0,0 +1,46 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
package searchlayer
|
||||
|
||||
import (
|
||||
model "github.com/mattermost/mattermost-server/v5/model"
|
||||
store "github.com/mattermost/mattermost-server/v5/store"
|
||||
)
|
||||
|
||||
type SearchTeamStore struct {
|
||||
store.TeamStore
|
||||
rootStore *SearchStore
|
||||
}
|
||||
|
||||
func (s SearchTeamStore) SaveMember(teamMember *model.TeamMember, maxUsersPerTeam int) (*model.TeamMember, *model.AppError) {
|
||||
member, err := s.TeamStore.SaveMember(teamMember, maxUsersPerTeam)
|
||||
if err == nil {
|
||||
s.rootStore.indexUserFromID(member.UserId)
|
||||
}
|
||||
return member, err
|
||||
}
|
||||
|
||||
func (s SearchTeamStore) UpdateMember(teamMember *model.TeamMember) (*model.TeamMember, *model.AppError) {
|
||||
member, err := s.TeamStore.UpdateMember(teamMember)
|
||||
if err == nil {
|
||||
s.rootStore.indexUserFromID(member.UserId)
|
||||
}
|
||||
return member, err
|
||||
}
|
||||
|
||||
func (s SearchTeamStore) RemoveMember(teamId string, userId string) *model.AppError {
|
||||
err := s.TeamStore.RemoveMember(teamId, userId)
|
||||
if err == nil {
|
||||
s.rootStore.indexUserFromID(userId)
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
func (s SearchTeamStore) RemoveAllMembersByUser(userId string) *model.AppError {
|
||||
err := s.TeamStore.RemoveAllMembersByUser(userId)
|
||||
if err == nil {
|
||||
s.rootStore.indexUserFromID(userId)
|
||||
}
|
||||
return err
|
||||
}
|
||||
192
store/searchlayer/user_layer.go
Обычный файл
192
store/searchlayer/user_layer.go
Обычный файл
@@ -0,0 +1,192 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
package searchlayer
|
||||
|
||||
import (
|
||||
"strings"
|
||||
|
||||
"github.com/mattermost/mattermost-server/v5/mlog"
|
||||
"github.com/mattermost/mattermost-server/v5/model"
|
||||
"github.com/mattermost/mattermost-server/v5/services/searchengine"
|
||||
"github.com/mattermost/mattermost-server/v5/store"
|
||||
)
|
||||
|
||||
type SearchUserStore struct {
|
||||
store.UserStore
|
||||
rootStore *SearchStore
|
||||
}
|
||||
|
||||
func (s *SearchUserStore) deleteUserIndex(user *model.User) {
|
||||
for _, engine := range s.rootStore.searchEngine.GetActiveEngines() {
|
||||
if engine.IsIndexingEnabled() {
|
||||
go (func(engineCopy searchengine.SearchEngineInterface) {
|
||||
if err := engineCopy.DeleteUser(user); err != nil {
|
||||
mlog.Error("Encountered error deleting user", mlog.String("user_id", user.Id), mlog.String("search_engine", engineCopy.GetName()), mlog.Err(err))
|
||||
return
|
||||
}
|
||||
mlog.Debug("Removed user from the index in search engine", mlog.String("search_engine", engineCopy.GetName()), mlog.String("user_id", user.Id))
|
||||
})(engine)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (s *SearchUserStore) Search(teamId, term string, options *model.UserSearchOptions) ([]*model.User, *model.AppError) {
|
||||
for _, engine := range s.rootStore.searchEngine.GetActiveEngines() {
|
||||
if engine.IsSearchEnabled() {
|
||||
listOfAllowedChannels, err := s.getListOfAllowedChannelsForTeam(teamId, options.ViewRestrictions)
|
||||
if err != nil {
|
||||
mlog.Error("Encountered error on Search.", mlog.String("search_engine", engine.GetName()), mlog.Err(err))
|
||||
continue
|
||||
}
|
||||
if len(listOfAllowedChannels) == 0 {
|
||||
return []*model.User{}, nil
|
||||
}
|
||||
|
||||
usersIds, err := engine.SearchUsersInTeam(teamId, listOfAllowedChannels, term, options)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
|
||||
users, err := s.UserStore.GetProfileByIds(usersIds, nil, false)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
|
||||
mlog.Debug("Using the first available search engine", mlog.String("search_engine", engine.GetName()))
|
||||
return users, nil
|
||||
}
|
||||
}
|
||||
mlog.Debug("Using database search because no other search engine is available")
|
||||
return s.UserStore.Search(teamId, term, options)
|
||||
}
|
||||
|
||||
func (s *SearchUserStore) Update(user *model.User, trustedUpdateData bool) (*model.UserUpdate, *model.AppError) {
|
||||
userUpdate, err := s.UserStore.Update(user, trustedUpdateData)
|
||||
|
||||
if err == nil {
|
||||
s.rootStore.indexUser(userUpdate.New)
|
||||
}
|
||||
return userUpdate, err
|
||||
}
|
||||
|
||||
func (s *SearchUserStore) Save(user *model.User) (*model.User, *model.AppError) {
|
||||
nuser, err := s.UserStore.Save(user)
|
||||
|
||||
if err == nil {
|
||||
s.rootStore.indexUser(nuser)
|
||||
}
|
||||
return nuser, err
|
||||
}
|
||||
|
||||
func (s *SearchUserStore) PermanentDelete(userId string) *model.AppError {
|
||||
user, userErr := s.UserStore.Get(userId)
|
||||
if userErr != nil {
|
||||
mlog.Error("Encountered error deleting user", mlog.String("user_id", userId), mlog.Err(userErr))
|
||||
}
|
||||
err := s.UserStore.PermanentDelete(userId)
|
||||
if err == nil && userErr == nil {
|
||||
s.deleteUserIndex(user)
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
func (s *SearchUserStore) autocompleteUsersInChannelByEngine(engine searchengine.SearchEngineInterface, teamId, channelId, term string, options *model.UserSearchOptions) (*model.UserAutocompleteInChannel, *model.AppError) {
|
||||
var err *model.AppError
|
||||
uchanIds := []string{}
|
||||
nuchanIds := []string{}
|
||||
if options.ListOfAllowedChannels != nil && !strings.Contains(strings.Join(options.ListOfAllowedChannels, "."), channelId) {
|
||||
nuchanIds, err = engine.SearchUsersInTeam(teamId, options.ListOfAllowedChannels, term, options)
|
||||
} else {
|
||||
uchanIds, nuchanIds, err = engine.SearchUsersInChannel(teamId, channelId, options.ListOfAllowedChannels, term, options)
|
||||
}
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
uchan := make(chan store.StoreResult, 1)
|
||||
go func() {
|
||||
users, err := s.UserStore.GetProfileByIds(uchanIds, nil, false)
|
||||
uchan <- store.StoreResult{Data: users, Err: err}
|
||||
close(uchan)
|
||||
}()
|
||||
|
||||
nuchan := make(chan store.StoreResult, 1)
|
||||
go func() {
|
||||
users, err := s.UserStore.GetProfileByIds(nuchanIds, nil, false)
|
||||
nuchan <- store.StoreResult{Data: users, Err: err}
|
||||
close(nuchan)
|
||||
}()
|
||||
|
||||
autocomplete := &model.UserAutocompleteInChannel{}
|
||||
|
||||
result := <-uchan
|
||||
if result.Err != nil {
|
||||
return nil, result.Err
|
||||
}
|
||||
inUsers := result.Data.([]*model.User)
|
||||
autocomplete.InChannel = inUsers
|
||||
|
||||
result = <-nuchan
|
||||
if result.Err != nil {
|
||||
return nil, result.Err
|
||||
}
|
||||
outUsers := result.Data.([]*model.User)
|
||||
autocomplete.OutOfChannel = outUsers
|
||||
|
||||
return autocomplete, nil
|
||||
}
|
||||
|
||||
func (s *SearchUserStore) getListOfAllowedChannelsForTeam(teamId string, viewRestrictions *model.ViewUsersRestrictions) ([]string, *model.AppError) {
|
||||
var listOfAllowedChannels []string
|
||||
if viewRestrictions == nil || strings.Contains(strings.Join(viewRestrictions.Teams, "."), teamId) {
|
||||
channels, err := s.rootStore.Channel().GetTeamChannels(teamId)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
channelIds := []string{}
|
||||
for _, channel := range *channels {
|
||||
channelIds = append(channelIds, channel.Id)
|
||||
}
|
||||
|
||||
return channelIds, nil
|
||||
}
|
||||
|
||||
channels, err := s.rootStore.Channel().GetChannelsByIds(viewRestrictions.Channels, false)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
for _, c := range channels {
|
||||
if c.TeamId == teamId {
|
||||
listOfAllowedChannels = append(listOfAllowedChannels, c.Id)
|
||||
}
|
||||
}
|
||||
|
||||
return listOfAllowedChannels, nil
|
||||
}
|
||||
|
||||
func (s *SearchUserStore) AutocompleteUsersInChannel(teamId, channelId, term string, options *model.UserSearchOptions) (*model.UserAutocompleteInChannel, *model.AppError) {
|
||||
for _, engine := range s.rootStore.searchEngine.GetActiveEngines() {
|
||||
if engine.IsAutocompletionEnabled() {
|
||||
listOfAllowedChannels, err := s.getListOfAllowedChannelsForTeam(teamId, options.ViewRestrictions)
|
||||
if err != nil {
|
||||
mlog.Error("Encountered error on AutocompleteUsersInChannel.", mlog.String("search_engine", engine.GetName()), mlog.Err(err))
|
||||
continue
|
||||
}
|
||||
if len(listOfAllowedChannels) == 0 {
|
||||
return &model.UserAutocompleteInChannel{}, nil
|
||||
}
|
||||
options.ListOfAllowedChannels = listOfAllowedChannels
|
||||
autocomplete, err := s.autocompleteUsersInChannelByEngine(engine, teamId, channelId, term, options)
|
||||
if err != nil {
|
||||
mlog.Error("Encountered error on AutocompleteUsersInChannel.", mlog.String("search_engine", engine.GetName()), mlog.Err(err))
|
||||
continue
|
||||
}
|
||||
mlog.Debug("Using the first available search engine", mlog.String("search_engine", engine.GetName()))
|
||||
return autocomplete, err
|
||||
}
|
||||
}
|
||||
|
||||
mlog.Debug("Using database search because no other search engine is available")
|
||||
return s.UserStore.AutocompleteUsersInChannel(teamId, channelId, term, options)
|
||||
}
|
||||
Ссылка в новой задаче
Block a user