* 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 * Search tests * Fix tests * Fix mod * Fixing searchEngine for test helpers with store mocks * Remove loglines * Fix i18n strings * Migrate search posts tests * Fix linter * Do not run search tests if -short flag is enabled * Migrate back store tests that didn't belong to the searchlayer * Fix scopelint issues Co-authored-by: Jesús Espino <jespinog@gmail.com>
206 строки
6.8 KiB
Go
206 строки
6.8 KiB
Go
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
|
// See LICENSE.txt for license information.
|
|
|
|
package searchlayer
|
|
|
|
import (
|
|
"net/http"
|
|
"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() {
|
|
runIndexFn(engine, 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))
|
|
})
|
|
}
|
|
}
|
|
}
|
|
|
|
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 {
|
|
mlog.Error("Encountered error on Search", mlog.String("search_engine", engine.GetName()), mlog.Err(err))
|
|
continue
|
|
}
|
|
|
|
users, err := s.UserStore.GetProfileByIds(usersIds, nil, false)
|
|
if err != nil {
|
|
mlog.Error("Encountered error on Search", 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 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) {
|
|
if len(teamId) == 0 {
|
|
return nil, model.NewAppError("SearchUserStore", "store.search_user_store.empty_team_id", nil, "", http.StatusInternalServerError)
|
|
}
|
|
|
|
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
|
|
}
|
|
|
|
if len(viewRestrictions.Channels) == 0 {
|
|
return []string{}, 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)
|
|
}
|