snapshot of https://github.com/mattermost/mattermost.git (master, 2026-09-16; blobs > 131072B omitted)
Some checks failed
Server CI / Postgres (shard 0) (push) Has been skipped
Server CI / Postgres (shard 3) (push) Has been skipped
BuildEnv Docker Image / build-image (push) Failing after 4s
BuildEnv Docker Image / build-image-fips (push) Failing after 4s
Migration Review & Release Notes / Generate migration review & release notes (push) Failing after 3s
Server CI / Build Environment (push) Failing after 3s
Server CI / Postgres (shard 1) (push) Has been skipped
Tools CI / check-style (mattermost-govet) (push) Failing after 4s
Server CI / Postgres (shard 2) (push) Has been skipped
Tools CI / check-style (mmgotool) (push) Failing after 3s
Server CI / Elasticsearch v8 Compatibility (push) Has been skipped
Server CI / OpenSearch v2 Compatibility (push) Has been skipped
Server CI / Postgres FIPS (shard 0) (push) Has been skipped
Tools CI / Test (mattermost-govet) (push) Failing after 3s
Server CI / Postgres FIPS (shard 1) (push) Has been skipped
Server CI / Postgres FIPS (shard 2) (push) Has been skipped
Tools CI / Test (mmgotool) (push) Failing after 3s
Server CI / Postgres FIPS (shard 3) (push) Has been skipped
Server CI / Run mmctl tests (push) Has been skipped
Server CI / Run mmctl tests (FIPS) (push) Has been skipped
Server CI / Merge Postgres Test Results (push) Has been skipped
Server CI / Merge Postgres FIPS Test Results (push) Has been skipped
Server CI / Check backport migrations (push) Has been skipped
Server CI / Check generated files (push) Has been skipped
Server CI / check-style (push) Has been skipped
Server CI / Vet API (push) Has been skipped
Server CI / Build mattermost server app (push) Has been skipped
Server CI / Server CI Complete (push) Failing after 2s
Server CI / Server CI Artifacts (push) Has been skipped
Server CI / Sentry Upload (push) Has been skipped
Server CI / Server CI Report (push) Failing after 5m1s
Server CI Nightly Race / Race Detector (push) Has been skipped
CodeQL / Analyze (go) (push) Has been cancelled
CodeQL / Analyze (javascript) (push) Has been cancelled
Webapp CI Cache Warm / Warm Webapp node_modules Cache (push) Has been cancelled
E2E CI Cache Warm / Warm E2E npm Registry Cache (push) Has been cancelled
API / build (push) Has been cancelled
Web App CI / check-types (push) Has been cancelled
Web App CI / check-lint (push) Has been cancelled
Web App CI / check-i18n (push) Has been cancelled
Web App CI / check-external-links (push) Has been cancelled
Web App CI / test (platform) (push) Has been cancelled
Web App CI / test (mattermost-redux) (push) Has been cancelled
Web App CI / test (channels shard 1/4) (push) Has been cancelled
Web App CI / test (channels shard 2/4) (push) Has been cancelled
Web App CI / test (channels shard 3/4) (push) Has been cancelled
Web App CI / test (channels shard 4/4) (push) Has been cancelled
Web App CI / upload-coverage (push) Has been cancelled
Web App CI / build (push) Has been cancelled
YAML Lint / yamllint (push) Has been cancelled
Push mirrored docker images / cd/Push mirrored docker images (push) Has been cancelled
docs-cd / Build and deploy docs (push) Has been cancelled
Docs CI / build (push) Has been cancelled
E2E Cache Warm: Cypress deps / Warm Cypress deps cache (push) Has been cancelled
Scorecards supply-chain security / Scorecard analysis (push) Has been cancelled
Server CI Weekly / Postgres FIPS (push) Has been skipped
Server CI Weekly / Run mmctl tests (FIPS) (push) Has been skipped
Server CI Weekly / Postgres with binary parameters (push) Has been skipped
Server CI Weekly / Build Environment (push) Failing after 1s
Server CI Cache Warm / Warm Go Module and Build Cache (push) Failing after 2s
Server CI Nightly Race / Build Environment (push) Failing after 1s
Some checks failed
Server CI / Postgres (shard 0) (push) Has been skipped
Server CI / Postgres (shard 3) (push) Has been skipped
BuildEnv Docker Image / build-image (push) Failing after 4s
BuildEnv Docker Image / build-image-fips (push) Failing after 4s
Migration Review & Release Notes / Generate migration review & release notes (push) Failing after 3s
Server CI / Build Environment (push) Failing after 3s
Server CI / Postgres (shard 1) (push) Has been skipped
Tools CI / check-style (mattermost-govet) (push) Failing after 4s
Server CI / Postgres (shard 2) (push) Has been skipped
Tools CI / check-style (mmgotool) (push) Failing after 3s
Server CI / Elasticsearch v8 Compatibility (push) Has been skipped
Server CI / OpenSearch v2 Compatibility (push) Has been skipped
Server CI / Postgres FIPS (shard 0) (push) Has been skipped
Tools CI / Test (mattermost-govet) (push) Failing after 3s
Server CI / Postgres FIPS (shard 1) (push) Has been skipped
Server CI / Postgres FIPS (shard 2) (push) Has been skipped
Tools CI / Test (mmgotool) (push) Failing after 3s
Server CI / Postgres FIPS (shard 3) (push) Has been skipped
Server CI / Run mmctl tests (push) Has been skipped
Server CI / Run mmctl tests (FIPS) (push) Has been skipped
Server CI / Merge Postgres Test Results (push) Has been skipped
Server CI / Merge Postgres FIPS Test Results (push) Has been skipped
Server CI / Check backport migrations (push) Has been skipped
Server CI / Check generated files (push) Has been skipped
Server CI / check-style (push) Has been skipped
Server CI / Vet API (push) Has been skipped
Server CI / Build mattermost server app (push) Has been skipped
Server CI / Server CI Complete (push) Failing after 2s
Server CI / Server CI Artifacts (push) Has been skipped
Server CI / Sentry Upload (push) Has been skipped
Server CI / Server CI Report (push) Failing after 5m1s
Server CI Nightly Race / Race Detector (push) Has been skipped
CodeQL / Analyze (go) (push) Has been cancelled
CodeQL / Analyze (javascript) (push) Has been cancelled
Webapp CI Cache Warm / Warm Webapp node_modules Cache (push) Has been cancelled
E2E CI Cache Warm / Warm E2E npm Registry Cache (push) Has been cancelled
API / build (push) Has been cancelled
Web App CI / check-types (push) Has been cancelled
Web App CI / check-lint (push) Has been cancelled
Web App CI / check-i18n (push) Has been cancelled
Web App CI / check-external-links (push) Has been cancelled
Web App CI / test (platform) (push) Has been cancelled
Web App CI / test (mattermost-redux) (push) Has been cancelled
Web App CI / test (channels shard 1/4) (push) Has been cancelled
Web App CI / test (channels shard 2/4) (push) Has been cancelled
Web App CI / test (channels shard 3/4) (push) Has been cancelled
Web App CI / test (channels shard 4/4) (push) Has been cancelled
Web App CI / upload-coverage (push) Has been cancelled
Web App CI / build (push) Has been cancelled
YAML Lint / yamllint (push) Has been cancelled
Push mirrored docker images / cd/Push mirrored docker images (push) Has been cancelled
docs-cd / Build and deploy docs (push) Has been cancelled
Docs CI / build (push) Has been cancelled
E2E Cache Warm: Cypress deps / Warm Cypress deps cache (push) Has been cancelled
Scorecards supply-chain security / Scorecard analysis (push) Has been cancelled
Server CI Weekly / Postgres FIPS (push) Has been skipped
Server CI Weekly / Run mmctl tests (FIPS) (push) Has been skipped
Server CI Weekly / Postgres with binary parameters (push) Has been skipped
Server CI Weekly / Build Environment (push) Failing after 1s
Server CI Cache Warm / Warm Go Module and Build Cache (push) Failing after 2s
Server CI Nightly Race / Build Environment (push) Failing after 1s
This commit is contained in:
396
server/channels/store/searchlayer/channel_layer.go
Normal file
396
server/channels/store/searchlayer/channel_layer.go
Normal file
@@ -0,0 +1,396 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
package searchlayer
|
||||
|
||||
import (
|
||||
"github.com/pkg/errors"
|
||||
|
||||
"github.com/mattermost/mattermost/server/public/model"
|
||||
"github.com/mattermost/mattermost/server/public/shared/mlog"
|
||||
"github.com/mattermost/mattermost/server/public/shared/request"
|
||||
"github.com/mattermost/mattermost/server/v8/channels/store"
|
||||
"github.com/mattermost/mattermost/server/v8/platform/services/searchengine"
|
||||
)
|
||||
|
||||
type SearchChannelStore struct {
|
||||
store.ChannelStore
|
||||
rootStore *SearchStore
|
||||
}
|
||||
|
||||
func (c *SearchChannelStore) deleteChannelIndex(rctx request.CTX, channel *model.Channel) {
|
||||
if channel.Type == model.ChannelTypeOpen {
|
||||
for _, engine := range c.rootStore.searchEngine.GetActiveEngines() {
|
||||
if engine.IsIndexingEnabled() {
|
||||
runIndexFn(rctx, engine, func(engineCopy searchengine.SearchEngineInterface) {
|
||||
if err := engineCopy.DeleteChannel(channel); err != nil {
|
||||
rctx.Logger().Warn("Encountered error deleting channel", mlog.String("channel_id", channel.Id), mlog.String("search_engine", engineCopy.GetName()), mlog.Err(err))
|
||||
return
|
||||
}
|
||||
rctx.Logger().Debug("Removed channel from index in search engine", mlog.String("search_engine", engineCopy.GetName()), mlog.String("channel_id", channel.Id))
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (c *SearchChannelStore) indexChannel(rctx request.CTX, channel *model.Channel) {
|
||||
// Space backing channels are internal and never surface in channel search.
|
||||
if channel.IsSpace() {
|
||||
return
|
||||
}
|
||||
|
||||
var userIDs, teamMemberIDs []string
|
||||
var err error
|
||||
if channel.Type == model.ChannelTypePrivate {
|
||||
userIDs, err = c.GetAllChannelMemberIdsByChannelId(channel.Id)
|
||||
if err != nil {
|
||||
rctx.Logger().Warn("Encountered error while indexing channel", mlog.String("channel_id", channel.Id), mlog.Err(err))
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
teamMemberIDs, err = c.GetTeamMembersForChannel(rctx, channel.Id)
|
||||
if err != nil {
|
||||
rctx.Logger().Warn("Encountered error while indexing channel", mlog.String("channel_id", channel.Id), mlog.Err(err))
|
||||
return
|
||||
}
|
||||
|
||||
for _, engine := range c.rootStore.searchEngine.GetActiveEngines() {
|
||||
if engine.IsIndexingEnabled() {
|
||||
runIndexFn(rctx, engine, func(engineCopy searchengine.SearchEngineInterface) {
|
||||
if err := engineCopy.IndexChannel(rctx, channel, userIDs, teamMemberIDs); err != nil {
|
||||
rctx.Logger().Warn("Encountered error indexing channel", mlog.String("channel_id", channel.Id), mlog.String("search_engine", engineCopy.GetName()), mlog.Err(err))
|
||||
return
|
||||
}
|
||||
rctx.Logger().Debug("Indexed channel in search engine", mlog.String("search_engine", engineCopy.GetName()), mlog.String("channel_id", channel.Id))
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (c *SearchChannelStore) bulkIndexChannels(rctx request.CTX, channels []*model.Channel, teamMemberIDs []string) {
|
||||
// Util function to get userIDs, only for private channels
|
||||
getUserIDsForPrivateChannel := func(channel *model.Channel) ([]string, error) {
|
||||
if channel.Type != model.ChannelTypePrivate {
|
||||
return []string{}, nil
|
||||
}
|
||||
return c.GetAllChannelMemberIdsByChannelId(channel.Id)
|
||||
}
|
||||
|
||||
for _, engine := range c.rootStore.searchEngine.GetActiveEngines() {
|
||||
if !engine.IsIndexingEnabled() {
|
||||
continue
|
||||
}
|
||||
|
||||
runIndexFn(rctx, engine, func(engineCopy searchengine.SearchEngineInterface) {
|
||||
appErr := engineCopy.SyncBulkIndexChannels(rctx, channels, getUserIDsForPrivateChannel, teamMemberIDs)
|
||||
if appErr != nil {
|
||||
rctx.Logger().Error("Failed to synchronously bulk-index channels.", mlog.String("search_engine", engineCopy.GetName()), mlog.Err(appErr))
|
||||
return
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func (c *SearchChannelStore) Save(rctx request.CTX, channel *model.Channel, maxChannels int64, channelOptions ...model.ChannelOption) (*model.Channel, error) {
|
||||
newChannel, err := c.ChannelStore.Save(rctx, channel, maxChannels, channelOptions...)
|
||||
if err == nil {
|
||||
c.indexChannel(rctx, newChannel)
|
||||
}
|
||||
return newChannel, err
|
||||
}
|
||||
|
||||
func (c *SearchChannelStore) Update(rctx request.CTX, channel *model.Channel) (*model.Channel, error) {
|
||||
// Fetch existing channel to detect type changes for post reindexing.
|
||||
oldChannel, getErr := c.ChannelStore.Get(channel.Id, true)
|
||||
if getErr != nil {
|
||||
rctx.Logger().Warn("Failed to fetch channel before update; channel type change detection skipped",
|
||||
mlog.String("channel_id", channel.Id),
|
||||
mlog.Err(getErr))
|
||||
}
|
||||
|
||||
updatedChannel, err := c.ChannelStore.Update(rctx, channel)
|
||||
if err == nil {
|
||||
c.indexChannel(rctx, updatedChannel)
|
||||
// If channel type changed, reindex all posts so their channel_type field is updated.
|
||||
if getErr == nil && oldChannel.Type != updatedChannel.Type {
|
||||
c.reindexChannelPosts(rctx, channel.Id, updatedChannel.Type)
|
||||
}
|
||||
}
|
||||
return updatedChannel, err
|
||||
}
|
||||
|
||||
func (c *SearchChannelStore) reindexChannelPosts(rctx request.CTX, channelID string, channelType model.ChannelType) {
|
||||
for _, engine := range c.rootStore.searchEngine.GetActiveEngines() {
|
||||
if engine.IsIndexingEnabled() {
|
||||
runIndexFn(rctx, engine, func(engineCopy searchengine.SearchEngineInterface) {
|
||||
rctx.Logger().Info("Starting reindexChannelPosts",
|
||||
mlog.String("channel_id", channelID),
|
||||
mlog.String("channel_type", string(channelType)),
|
||||
mlog.String("search_engine", engineCopy.GetName()))
|
||||
|
||||
if err := engineCopy.UpdatePostsChannelTypeByChannelId(rctx, channelID, string(channelType)); err != nil {
|
||||
rctx.Logger().Error("Failed to update channel_type on posts in reindexChannelPosts. Consider running a full bulk index",
|
||||
mlog.String("channel_id", channelID),
|
||||
mlog.String("channel_type", string(channelType)),
|
||||
mlog.String("search_engine", engineCopy.GetName()),
|
||||
mlog.Err(err))
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (c *SearchChannelStore) UpdateMember(rctx request.CTX, cm *model.ChannelMember) (*model.ChannelMember, error) {
|
||||
member, err := c.ChannelStore.UpdateMember(rctx, cm)
|
||||
if err == nil {
|
||||
c.rootStore.indexUserFromID(rctx, cm.UserId)
|
||||
channel, channelErr := c.ChannelStore.Get(member.ChannelId, true)
|
||||
if channelErr != nil {
|
||||
rctx.Logger().Warn("Encountered error indexing user in channel", mlog.String("channel_id", member.ChannelId), mlog.Err(channelErr))
|
||||
} else {
|
||||
c.indexChannel(rctx, channel)
|
||||
c.rootStore.indexUserFromID(rctx, channel.CreatorId)
|
||||
}
|
||||
}
|
||||
return member, err
|
||||
}
|
||||
|
||||
func (c *SearchChannelStore) SaveMember(rctx request.CTX, cm *model.ChannelMember) (*model.ChannelMember, error) {
|
||||
member, err := c.ChannelStore.SaveMember(rctx, cm)
|
||||
if err == nil {
|
||||
c.rootStore.indexUserFromID(rctx, cm.UserId)
|
||||
channel, channelErr := c.ChannelStore.Get(member.ChannelId, true)
|
||||
if channelErr != nil {
|
||||
rctx.Logger().Warn("Encountered error indexing user in channel", mlog.String("channel_id", member.ChannelId), mlog.Err(channelErr))
|
||||
} else {
|
||||
c.indexChannel(rctx, channel)
|
||||
c.rootStore.indexUserFromID(rctx, channel.CreatorId)
|
||||
}
|
||||
}
|
||||
return member, err
|
||||
}
|
||||
|
||||
func (c *SearchChannelStore) RemoveMember(rctx request.CTX, channelID, userIdToRemove string) error {
|
||||
err := c.ChannelStore.RemoveMember(rctx, channelID, userIdToRemove)
|
||||
if err == nil {
|
||||
c.rootStore.indexUserFromID(rctx, userIdToRemove)
|
||||
}
|
||||
|
||||
// return the removal result, not the re-index Get's — that Get returns
|
||||
// not-found for space backing channels (excluded from the generic Get)
|
||||
// and must not mask a successful removal.
|
||||
channel, getErr := c.ChannelStore.Get(channelID, true)
|
||||
if getErr == nil {
|
||||
c.indexChannel(rctx, channel)
|
||||
}
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
func (c *SearchChannelStore) RemoveMembers(rctx request.CTX, channelID string, userIds []string) error {
|
||||
if err := c.ChannelStore.RemoveMembers(rctx, channelID, userIds); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
channel, err := c.ChannelStore.Get(channelID, true)
|
||||
if err == nil {
|
||||
c.indexChannel(rctx, channel)
|
||||
}
|
||||
|
||||
for _, uid := range userIds {
|
||||
c.rootStore.indexUserFromID(rctx, uid)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *SearchChannelStore) CreateDirectChannel(rctx request.CTX, user *model.User, otherUser *model.User, channelOptions ...model.ChannelOption) (*model.Channel, error) {
|
||||
channel, err := c.ChannelStore.CreateDirectChannel(rctx, user, otherUser, channelOptions...)
|
||||
if err == nil {
|
||||
c.rootStore.indexUserFromID(rctx, user.Id)
|
||||
c.rootStore.indexUserFromID(rctx, otherUser.Id)
|
||||
c.indexChannel(rctx, channel)
|
||||
}
|
||||
return channel, err
|
||||
}
|
||||
|
||||
func (c *SearchChannelStore) SaveDirectChannel(rctx request.CTX, directchannel *model.Channel, member1 *model.ChannelMember, member2 *model.ChannelMember) (*model.Channel, error) {
|
||||
channel, err := c.ChannelStore.SaveDirectChannel(rctx, directchannel, member1, member2)
|
||||
if err == nil {
|
||||
c.rootStore.indexUserFromID(rctx, member1.UserId)
|
||||
c.rootStore.indexUserFromID(rctx, member2.UserId)
|
||||
c.indexChannel(rctx, channel)
|
||||
}
|
||||
return channel, err
|
||||
}
|
||||
|
||||
func (c *SearchChannelStore) SaveBoardChannel(rctx request.CTX, channel *model.Channel, maxChannelsPerTeam int64, view *model.View) (*model.Channel, *model.View, error) {
|
||||
// Board channels are not indexed — they must stay invisible to channel search/autocomplete.
|
||||
return c.ChannelStore.SaveBoardChannel(rctx, channel, maxChannelsPerTeam, view)
|
||||
}
|
||||
|
||||
func (c *SearchChannelStore) Autocomplete(rctx request.CTX, userID, term string, includeDeleted, isGuest bool) (model.ChannelListWithTeamData, error) {
|
||||
var channelList model.ChannelListWithTeamData
|
||||
var err error
|
||||
|
||||
allFailed := true
|
||||
for _, engine := range c.rootStore.searchEngine.GetActiveEngines() {
|
||||
if engine.IsAutocompletionEnabled() {
|
||||
channelList, err = c.searchAutocompleteChannelsAllTeams(engine, userID, term, includeDeleted, isGuest)
|
||||
if err != nil {
|
||||
rctx.Logger().Warn("Encountered error on AutocompleteChannels through SearchEngine. Falling back to default autocompletion.", mlog.String("search_engine", engine.GetName()), mlog.Err(err))
|
||||
continue
|
||||
}
|
||||
allFailed = false
|
||||
rctx.Logger().Debug("Using the first available search engine", mlog.String("search_engine", engine.GetName()))
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if allFailed {
|
||||
rctx.Logger().Debug("Using database search because no other search engine is available")
|
||||
channelList, err = c.ChannelStore.Autocomplete(rctx, userID, term, includeDeleted, isGuest)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "Failed to autocomplete channels in team")
|
||||
}
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
return channelList, err
|
||||
}
|
||||
|
||||
return channelList, nil
|
||||
}
|
||||
|
||||
func (c *SearchChannelStore) AutocompleteInTeam(rctx request.CTX, teamID, userID, term string, includeDeleted, isGuest bool) (model.ChannelList, error) {
|
||||
var channelList model.ChannelList
|
||||
var err error
|
||||
|
||||
allFailed := true
|
||||
for _, engine := range c.rootStore.searchEngine.GetActiveEngines() {
|
||||
if engine.IsAutocompletionEnabled() {
|
||||
channelList, err = c.searchAutocompleteChannels(engine, teamID, userID, term, includeDeleted, isGuest)
|
||||
if err != nil {
|
||||
rctx.Logger().Warn("Encountered error on AutocompleteChannels through SearchEngine. Falling back to default autocompletion.", mlog.String("search_engine", engine.GetName()), mlog.Err(err))
|
||||
continue
|
||||
}
|
||||
allFailed = false
|
||||
rctx.Logger().Debug("Using the first available search engine", mlog.String("search_engine", engine.GetName()))
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if allFailed {
|
||||
rctx.Logger().Debug("Using database search because no other search engine is available")
|
||||
channelList, err = c.ChannelStore.AutocompleteInTeam(rctx, teamID, userID, term, includeDeleted, isGuest)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "Failed to autocomplete channels in team")
|
||||
}
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
return channelList, err
|
||||
}
|
||||
|
||||
return channelList, nil
|
||||
}
|
||||
|
||||
func (c *SearchChannelStore) searchAutocompleteChannels(engine searchengine.SearchEngineInterface, teamId, userID, term string, includeDeleted, isGuest bool) (model.ChannelList, error) {
|
||||
channelIds, err := engine.SearchChannels(teamId, userID, term, isGuest, includeDeleted)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
channelList := model.ChannelList{}
|
||||
var nErr error
|
||||
if len(channelIds) > 0 {
|
||||
channelList, nErr = c.ChannelStore.GetChannelsByIds(channelIds, includeDeleted)
|
||||
if nErr != nil {
|
||||
return nil, errors.Wrap(nErr, "Failed to get channels by ids")
|
||||
}
|
||||
}
|
||||
|
||||
return channelList, nil
|
||||
}
|
||||
|
||||
func (c *SearchChannelStore) searchAutocompleteChannelsAllTeams(engine searchengine.SearchEngineInterface, userID, term string, includeDeleted, isGuest bool) (model.ChannelListWithTeamData, error) {
|
||||
channelIds, err := engine.SearchChannels("", userID, term, isGuest, includeDeleted)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
channelList := model.ChannelListWithTeamData{}
|
||||
var nErr error
|
||||
if len(channelIds) > 0 {
|
||||
channelList, nErr = c.ChannelStore.GetChannelsWithTeamDataByIds(channelIds, includeDeleted)
|
||||
if nErr != nil {
|
||||
return nil, errors.Wrap(nErr, "Failed to get channels by ids")
|
||||
}
|
||||
}
|
||||
|
||||
return channelList, nil
|
||||
}
|
||||
|
||||
func (c *SearchChannelStore) PermanentDeleteMembersByUser(rctx request.CTX, userId string) error {
|
||||
channels, errGetChannels := c.ChannelStore.GetChannelsByUser(userId, false, 0, -1, "")
|
||||
if errGetChannels != nil {
|
||||
rctx.Logger().Warn("Encountered error indexing channel after removing user", mlog.String("user_id", userId), mlog.Err(errGetChannels))
|
||||
}
|
||||
|
||||
err := c.ChannelStore.PermanentDeleteMembersByUser(rctx, userId)
|
||||
if err == nil {
|
||||
c.rootStore.indexUserFromID(rctx, userId)
|
||||
if errGetChannels == nil {
|
||||
for _, ch := range channels {
|
||||
c.indexChannel(rctx, ch)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
func (c *SearchChannelStore) RemoveAllDeactivatedMembers(rctx request.CTX, channelId string) error {
|
||||
profiles, errProfiles := c.rootStore.User().GetAllProfilesInChannel(rctx, channelId, true)
|
||||
if errProfiles != nil {
|
||||
rctx.Logger().Warn("Encountered error indexing users for channel", mlog.String("channel_id", channelId), mlog.Err(errProfiles))
|
||||
}
|
||||
|
||||
err := c.ChannelStore.RemoveAllDeactivatedMembers(rctx, channelId)
|
||||
if err == nil && errProfiles == nil {
|
||||
for _, user := range profiles {
|
||||
if user.DeleteAt != 0 {
|
||||
c.rootStore.indexUser(rctx, user)
|
||||
}
|
||||
}
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
func (c *SearchChannelStore) PermanentDeleteMembersByChannel(rctx request.CTX, channelId string) error {
|
||||
profiles, errProfiles := c.rootStore.User().GetAllProfilesInChannel(rctx, channelId, true)
|
||||
if errProfiles != nil {
|
||||
rctx.Logger().Warn("Encountered error indexing users for channel", mlog.String("channel_id", channelId), mlog.Err(errProfiles))
|
||||
}
|
||||
|
||||
err := c.ChannelStore.PermanentDeleteMembersByChannel(rctx, channelId)
|
||||
if err == nil && errProfiles == nil {
|
||||
for _, user := range profiles {
|
||||
c.rootStore.indexUser(rctx, user)
|
||||
}
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
func (c *SearchChannelStore) PermanentDelete(rctx request.CTX, channelId string) error {
|
||||
channel, channelErr := c.ChannelStore.Get(channelId, true)
|
||||
if channelErr != nil {
|
||||
rctx.Logger().Warn("Encountered error deleting channel", mlog.String("channel_id", channelId), mlog.Err(channelErr))
|
||||
}
|
||||
err := c.ChannelStore.PermanentDelete(rctx, channelId)
|
||||
if err == nil && channelErr == nil {
|
||||
c.deleteChannelIndex(rctx, channel)
|
||||
}
|
||||
return err
|
||||
}
|
||||
221
server/channels/store/searchlayer/file_info_layer.go
Normal file
221
server/channels/store/searchlayer/file_info_layer.go
Normal file
@@ -0,0 +1,221 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
package searchlayer
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/mattermost/mattermost/server/public/model"
|
||||
"github.com/mattermost/mattermost/server/public/shared/mlog"
|
||||
"github.com/mattermost/mattermost/server/public/shared/request"
|
||||
"github.com/mattermost/mattermost/server/v8/channels/store"
|
||||
"github.com/mattermost/mattermost/server/v8/platform/services/searchengine"
|
||||
)
|
||||
|
||||
type SearchFileInfoStore struct {
|
||||
store.FileInfoStore
|
||||
rootStore *SearchStore
|
||||
}
|
||||
|
||||
func (s SearchFileInfoStore) indexFile(rctx request.CTX, file *model.FileInfo) {
|
||||
for _, engine := range s.rootStore.searchEngine.GetActiveEngines() {
|
||||
if engine.IsIndexingEnabled() {
|
||||
runIndexFn(rctx, engine, func(engineCopy searchengine.SearchEngineInterface) {
|
||||
if file.PostId == "" && file.CreatorId != model.BookmarkFileOwner {
|
||||
return
|
||||
}
|
||||
channelId := file.ChannelId
|
||||
if channelId == "" {
|
||||
rctx.Logger().Error("Couldn't associate file with a channel for file for SearchEngine indexing.", mlog.String("search_engine", engineCopy.GetName()), mlog.String("file_info_id", file.Id))
|
||||
return
|
||||
}
|
||||
|
||||
if err := engineCopy.IndexFile(file, channelId); err != nil {
|
||||
rctx.Logger().Error("Encountered error indexing file", mlog.String("file_info_id", file.Id), mlog.String("search_engine", engineCopy.GetName()), mlog.Err(err))
|
||||
return
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (s SearchFileInfoStore) deleteFileIndex(rctx request.CTX, fileID string) {
|
||||
for _, engine := range s.rootStore.searchEngine.GetActiveEngines() {
|
||||
if engine.IsIndexingEnabled() {
|
||||
runIndexFn(rctx, engine, func(engineCopy searchengine.SearchEngineInterface) {
|
||||
if err := engineCopy.DeleteFile(fileID); err != nil {
|
||||
rctx.Logger().Error("Encountered error deleting file", mlog.String("file_info_id", fileID), mlog.String("search_engine", engineCopy.GetName()), mlog.Err(err))
|
||||
return
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (s SearchFileInfoStore) deleteFileIndexForUser(rctx request.CTX, userID string) {
|
||||
for _, engine := range s.rootStore.searchEngine.GetActiveEngines() {
|
||||
if engine.IsIndexingEnabled() {
|
||||
runIndexFn(rctx, engine, func(engineCopy searchengine.SearchEngineInterface) {
|
||||
if err := engineCopy.DeleteUserFiles(rctx, userID); err != nil {
|
||||
rctx.Logger().Error("Encountered error deleting files for user", mlog.String("user_id", userID), mlog.String("search_engine", engineCopy.GetName()), mlog.Err(err))
|
||||
return
|
||||
}
|
||||
rctx.Logger().Debug("Removed user's files from the index in search engine", mlog.String("search_engine", engineCopy.GetName()), mlog.String("user_id", userID))
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//nolint:unused // Temporarily unused until the post_id is indexed with the file
|
||||
func (s SearchFileInfoStore) deleteFileIndexForPost(rctx request.CTX, postID string) {
|
||||
for _, engine := range s.rootStore.searchEngine.GetActiveEngines() {
|
||||
if engine.IsIndexingEnabled() {
|
||||
runIndexFn(rctx, engine, func(engineCopy searchengine.SearchEngineInterface) {
|
||||
if err := engineCopy.DeletePostFiles(rctx, postID); err != nil {
|
||||
rctx.Logger().Error("Encountered error deleting files for post", mlog.String("post_id", postID), mlog.String("search_engine", engineCopy.GetName()), mlog.Err(err))
|
||||
return
|
||||
}
|
||||
rctx.Logger().Debug("Removed post's files from the index in search engine", mlog.String("search_engine", engineCopy.GetName()), mlog.String("post_id", postID))
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (s SearchFileInfoStore) deleteFileIndexBatch(rctx request.CTX, endTime, limit int64) {
|
||||
for _, engine := range s.rootStore.searchEngine.GetActiveEngines() {
|
||||
if engine.IsIndexingEnabled() {
|
||||
runIndexFn(rctx, engine, func(engineCopy searchengine.SearchEngineInterface) {
|
||||
if err := engineCopy.DeleteFilesBatch(rctx, endTime, limit); err != nil {
|
||||
rctx.Logger().Error("Encountered error deleting a batch of files", mlog.Int("limit", limit), mlog.Int("end_time", endTime), mlog.String("search_engine", engineCopy.GetName()), mlog.Err(err))
|
||||
return
|
||||
}
|
||||
rctx.Logger().Debug("Removed batch of files from the index in search engine", mlog.String("search_engine", engineCopy.GetName()), mlog.Int("end_time", endTime), mlog.Int("limit", limit))
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (s SearchFileInfoStore) Save(rctx request.CTX, info *model.FileInfo) (*model.FileInfo, error) {
|
||||
nfile, err := s.FileInfoStore.Save(rctx, info)
|
||||
if err == nil {
|
||||
s.indexFile(rctx, nfile)
|
||||
}
|
||||
return nfile, err
|
||||
}
|
||||
|
||||
func (s SearchFileInfoStore) SetContent(rctx request.CTX, fileID, content string) error {
|
||||
err := s.FileInfoStore.SetContent(rctx, fileID, content)
|
||||
if err == nil {
|
||||
nfile, err2 := s.FileInfoStore.GetFromMaster(fileID)
|
||||
if err2 == nil {
|
||||
nfile.Content = content
|
||||
s.indexFile(rctx, nfile)
|
||||
}
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
func (s SearchFileInfoStore) AttachToPost(rctx request.CTX, fileId, postId, channelId, creatorId string) error {
|
||||
err := s.FileInfoStore.AttachToPost(rctx, fileId, postId, channelId, creatorId)
|
||||
if err == nil {
|
||||
nFileInfo, err2 := s.FileInfoStore.GetFromMaster(fileId)
|
||||
if err2 == nil {
|
||||
s.indexFile(rctx, nFileInfo)
|
||||
}
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
func (s SearchFileInfoStore) DeleteForPost(rctx request.CTX, postID string) (string, error) {
|
||||
// temporary workaround because deleteFileIndexForPost is not working due to the post_id not being indexed with the file
|
||||
files, err := s.FileInfoStore.GetForPost(postID, false, true, true)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("failed to get files for post %s: %w", postID, err)
|
||||
}
|
||||
result, err := s.FileInfoStore.DeleteForPost(rctx, postID)
|
||||
if err == nil {
|
||||
for _, file := range files {
|
||||
s.deleteFileIndex(rctx, file.Id)
|
||||
}
|
||||
}
|
||||
return result, err
|
||||
}
|
||||
|
||||
func (s SearchFileInfoStore) PermanentDeleteForPost(rctx request.CTX, postID string) error {
|
||||
// temporary workaround because deleteFileIndexForPost is not working due to the post_id not being indexed with the file
|
||||
files, err := s.FileInfoStore.GetForPost(postID, false, true, true)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = s.FileInfoStore.PermanentDeleteForPost(rctx, postID)
|
||||
if err == nil {
|
||||
for _, file := range files {
|
||||
s.deleteFileIndex(rctx, file.Id)
|
||||
}
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
func (s SearchFileInfoStore) PermanentDelete(rctx request.CTX, fileId string) error {
|
||||
err := s.FileInfoStore.PermanentDelete(rctx, fileId)
|
||||
if err == nil {
|
||||
s.deleteFileIndex(rctx, fileId)
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
func (s SearchFileInfoStore) PermanentDeleteBatch(rctx request.CTX, endTime int64, limit int64) (int64, error) {
|
||||
result, err := s.FileInfoStore.PermanentDeleteBatch(rctx, endTime, limit)
|
||||
if err == nil {
|
||||
s.deleteFileIndexBatch(rctx, endTime, limit)
|
||||
}
|
||||
return result, err
|
||||
}
|
||||
|
||||
func (s SearchFileInfoStore) PermanentDeleteByUser(rctx request.CTX, userId string) (int64, error) {
|
||||
result, err := s.FileInfoStore.PermanentDeleteByUser(rctx, userId)
|
||||
if err == nil {
|
||||
s.deleteFileIndexForUser(rctx, userId)
|
||||
}
|
||||
return result, err
|
||||
}
|
||||
|
||||
func (s SearchFileInfoStore) Search(rctx request.CTX, paramsList []*model.SearchParams, userId, teamId string, page, perPage int) (*model.FileInfoList, error) {
|
||||
for _, engine := range s.rootStore.searchEngine.GetActiveEngines() {
|
||||
if engine.IsSearchEnabled() {
|
||||
userChannels, nErr := s.rootStore.Channel().GetChannels(teamId, userId, &model.ChannelSearchOpts{
|
||||
IncludeDeleted: paramsList[0].IncludeDeletedChannels,
|
||||
LastDeleteAt: 0,
|
||||
})
|
||||
if nErr != nil {
|
||||
return nil, nErr
|
||||
}
|
||||
fileIds, appErr := engine.SearchFiles(userChannels, paramsList, page, perPage)
|
||||
if appErr != nil {
|
||||
rctx.Logger().Error("Encountered error on Search.", mlog.String("search_engine", engine.GetName()), mlog.Err(appErr))
|
||||
continue
|
||||
}
|
||||
|
||||
// Get the files
|
||||
filesList := model.NewFileInfoList()
|
||||
if len(fileIds) > 0 {
|
||||
files, nErr := s.FileInfoStore.GetByIds(fileIds, false, true, false)
|
||||
if nErr != nil {
|
||||
return nil, nErr
|
||||
}
|
||||
for _, f := range files {
|
||||
filesList.AddFileInfo(f)
|
||||
filesList.AddOrder(f.Id)
|
||||
}
|
||||
}
|
||||
return filesList, nil
|
||||
}
|
||||
}
|
||||
|
||||
if *s.rootStore.getConfig().SqlSettings.DisableDatabaseSearch {
|
||||
return model.NewFileInfoList(), nil
|
||||
}
|
||||
|
||||
return s.FileInfoStore.Search(rctx, paramsList, userId, teamId, page, perPage)
|
||||
}
|
||||
156
server/channels/store/searchlayer/layer.go
Normal file
156
server/channels/store/searchlayer/layer.go
Normal file
@@ -0,0 +1,156 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
package searchlayer
|
||||
|
||||
import (
|
||||
"sync/atomic"
|
||||
|
||||
"github.com/mattermost/mattermost/server/public/model"
|
||||
"github.com/mattermost/mattermost/server/public/shared/mlog"
|
||||
"github.com/mattermost/mattermost/server/public/shared/request"
|
||||
"github.com/mattermost/mattermost/server/public/utils"
|
||||
"github.com/mattermost/mattermost/server/v8/channels/store"
|
||||
"github.com/mattermost/mattermost/server/v8/platform/services/searchengine"
|
||||
)
|
||||
|
||||
type SearchStore struct {
|
||||
store.Store
|
||||
searchEngine *searchengine.Broker
|
||||
user *SearchUserStore
|
||||
team *SearchTeamStore
|
||||
channel *SearchChannelStore
|
||||
post *SearchPostStore
|
||||
fileInfo *SearchFileInfoStore
|
||||
configValue atomic.Pointer[model.Config]
|
||||
}
|
||||
|
||||
func NewSearchLayer(baseStore store.Store, searchEngine *searchengine.Broker, cfg *model.Config) *SearchStore {
|
||||
searchStore := &SearchStore{
|
||||
Store: baseStore,
|
||||
searchEngine: searchEngine,
|
||||
}
|
||||
searchStore.configValue.Store(cfg)
|
||||
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}
|
||||
searchStore.fileInfo = &SearchFileInfoStore{FileInfoStore: baseStore.FileInfo(), rootStore: searchStore}
|
||||
|
||||
return searchStore
|
||||
}
|
||||
|
||||
func (s *SearchStore) UpdateConfig(cfg *model.Config) {
|
||||
s.configValue.Store(cfg)
|
||||
}
|
||||
|
||||
func (s *SearchStore) getConfig() *model.Config {
|
||||
return s.configValue.Load()
|
||||
}
|
||||
|
||||
func (s *SearchStore) Channel() store.ChannelStore {
|
||||
return s.channel
|
||||
}
|
||||
|
||||
func (s *SearchStore) Post() store.PostStore {
|
||||
return s.post
|
||||
}
|
||||
|
||||
func (s *SearchStore) FileInfo() store.FileInfoStore {
|
||||
return s.fileInfo
|
||||
}
|
||||
|
||||
func (s *SearchStore) Team() store.TeamStore {
|
||||
return s.team
|
||||
}
|
||||
|
||||
func (s *SearchStore) User() store.UserStore {
|
||||
return s.user
|
||||
}
|
||||
|
||||
func (s *SearchStore) indexUserFromID(rctx request.CTX, userId string) {
|
||||
user, err := s.User().Get(rctx, userId)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
s.indexUser(rctx, user)
|
||||
}
|
||||
|
||||
func (s *SearchStore) indexUser(rctx request.CTX, user *model.User) {
|
||||
for _, engine := range s.searchEngine.GetActiveEngines() {
|
||||
if engine.IsIndexingEnabled() {
|
||||
runIndexFn(rctx, engine, func(engineCopy searchengine.SearchEngineInterface) {
|
||||
userTeams, nErr := s.Team().GetTeamsByUserId(user.Id)
|
||||
if nErr != nil {
|
||||
rctx.Logger().Error("Encountered error indexing user", mlog.String("user_id", user.Id), mlog.String("search_engine", engineCopy.GetName()), mlog.Err(nErr))
|
||||
return
|
||||
}
|
||||
|
||||
userTeamsIds := []string{}
|
||||
for _, team := range userTeams {
|
||||
userTeamsIds = append(userTeamsIds, team.Id)
|
||||
}
|
||||
|
||||
userChannelMembers, err := s.Channel().GetAllChannelMembersForUser(rctx, user.Id, false, true)
|
||||
if err != nil {
|
||||
rctx.Logger().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(rctx, user, userTeamsIds, userChannelsIds); err != nil {
|
||||
rctx.Logger().Error("Encountered error indexing user", mlog.String("user_id", user.Id), mlog.String("search_engine", engineCopy.GetName()), mlog.Err(err))
|
||||
return
|
||||
}
|
||||
rctx.Logger().Debug("Indexed user in search engine", mlog.String("search_engine", engineCopy.GetName()), mlog.String("user_id", user.Id))
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (s *SearchStore) indexChannelsForTeam(rctx request.CTX, teamID string) {
|
||||
const perPage = 100
|
||||
var (
|
||||
channels []*model.Channel
|
||||
)
|
||||
|
||||
channels, err := utils.Pager(func(page int) ([]*model.Channel, error) {
|
||||
return s.channel.GetPublicChannelsForTeam(teamID, page*perPage, perPage)
|
||||
}, perPage)
|
||||
if err != nil {
|
||||
rctx.Logger().Warn("Encountered error while retrieving public channels for indexing", mlog.String("team_id", teamID), mlog.Err(err))
|
||||
return
|
||||
}
|
||||
|
||||
if len(channels) == 0 {
|
||||
return
|
||||
}
|
||||
|
||||
// Use master context to avoid replica lag issues when reading team members
|
||||
masterRctx := store.RequestContextWithMaster(rctx)
|
||||
teamMemberIDs, err := s.channel.GetTeamMembersForChannel(masterRctx, channels[0].Id)
|
||||
if err != nil {
|
||||
rctx.Logger().Warn("Encountered error while retrieving team members for channel", mlog.String("channel_id", channels[0].Id), mlog.Err(err))
|
||||
return
|
||||
}
|
||||
|
||||
s.channel.bulkIndexChannels(rctx, channels, teamMemberIDs)
|
||||
}
|
||||
|
||||
// Runs an indexing function synchronously or asynchronously depending on the engine
|
||||
func runIndexFn(rctx request.CTX, engine searchengine.SearchEngineInterface, indexFn func(searchengine.SearchEngineInterface)) {
|
||||
if engine.IsIndexingSync() {
|
||||
indexFn(engine)
|
||||
if err := engine.RefreshIndexes(rctx); err != nil {
|
||||
rctx.Logger().Error("Encountered error refresh the indexes", mlog.Err(err))
|
||||
}
|
||||
} else {
|
||||
go (func(engineCopy searchengine.SearchEngineInterface) {
|
||||
indexFn(engineCopy)
|
||||
})(engine)
|
||||
}
|
||||
}
|
||||
50
server/channels/store/searchlayer/layer_test.go
Normal file
50
server/channels/store/searchlayer/layer_test.go
Normal file
@@ -0,0 +1,50 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
package searchlayer_test
|
||||
|
||||
import (
|
||||
"os"
|
||||
"sync"
|
||||
"testing"
|
||||
|
||||
"github.com/mattermost/mattermost/server/public/model"
|
||||
"github.com/mattermost/mattermost/server/public/shared/mlog"
|
||||
"github.com/mattermost/mattermost/server/v8/channels/store/searchlayer"
|
||||
"github.com/mattermost/mattermost/server/v8/channels/store/sqlstore"
|
||||
"github.com/mattermost/mattermost/server/v8/channels/store/storetest"
|
||||
"github.com/mattermost/mattermost/server/v8/channels/testlib"
|
||||
"github.com/mattermost/mattermost/server/v8/platform/services/searchengine"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
// Test to verify race condition on UpdateConfig. The test must run with -race flag in order to verify
|
||||
// that there is no race. Ref: (#MM-30868)
|
||||
func TestUpdateConfigRace(t *testing.T) {
|
||||
logger := mlog.CreateTestLogger(t)
|
||||
|
||||
driverName := os.Getenv("MM_SQLSETTINGS_DRIVERNAME")
|
||||
if driverName == "" {
|
||||
driverName = model.DatabaseDriverPostgres
|
||||
}
|
||||
settings := storetest.MakeSqlSettings(driverName)
|
||||
store, err := sqlstore.New(*settings, logger, nil, sqlstore.DisableMorphLogging())
|
||||
require.NoError(t, err)
|
||||
|
||||
cfg := &model.Config{}
|
||||
cfg.SetDefaults()
|
||||
cfg.ClusterSettings.GossipPort = new(9999)
|
||||
searchEngine := searchengine.NewBroker(cfg)
|
||||
layer := searchlayer.NewSearchLayer(&testlib.TestStore{Store: store}, searchEngine, cfg)
|
||||
var wg sync.WaitGroup
|
||||
|
||||
wg.Add(5)
|
||||
for range 5 {
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
layer.UpdateConfig(cfg.Clone())
|
||||
}()
|
||||
}
|
||||
|
||||
wg.Wait()
|
||||
}
|
||||
211
server/channels/store/searchlayer/post_layer.go
Normal file
211
server/channels/store/searchlayer/post_layer.go
Normal file
@@ -0,0 +1,211 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
package searchlayer
|
||||
|
||||
import (
|
||||
"github.com/pkg/errors"
|
||||
|
||||
"github.com/mattermost/mattermost/server/public/model"
|
||||
"github.com/mattermost/mattermost/server/public/shared/mlog"
|
||||
"github.com/mattermost/mattermost/server/public/shared/request"
|
||||
"github.com/mattermost/mattermost/server/v8/channels/store"
|
||||
"github.com/mattermost/mattermost/server/v8/platform/services/searchengine"
|
||||
)
|
||||
|
||||
type SearchPostStore struct {
|
||||
store.PostStore
|
||||
rootStore *SearchStore
|
||||
}
|
||||
|
||||
func (s SearchPostStore) indexPost(rctx request.CTX, post *model.Post) {
|
||||
for _, engine := range s.rootStore.searchEngine.GetActiveEngines() {
|
||||
if engine.IsIndexingEnabled() {
|
||||
runIndexFn(rctx, engine, func(engineCopy searchengine.SearchEngineInterface) {
|
||||
if post.Type == model.PostTypeBurnOnRead {
|
||||
return
|
||||
}
|
||||
// FIXME(IntegratedBoardMVP): Temporarily excluded
|
||||
if post.Type == model.PostTypeCard {
|
||||
return
|
||||
}
|
||||
channel, chanErr := s.rootStore.Channel().Get(post.ChannelId, true)
|
||||
if chanErr != nil {
|
||||
rctx.Logger().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), mlog.Err(chanErr))
|
||||
return
|
||||
}
|
||||
if err := engineCopy.IndexPost(post, channel.TeamId, string(channel.Type)); err != nil {
|
||||
rctx.Logger().Warn("Encountered error indexing post", mlog.String("post_id", post.Id), mlog.String("search_engine", engineCopy.GetName()), mlog.Err(err))
|
||||
return
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (s SearchPostStore) deletePostIndex(rctx request.CTX, post *model.Post) {
|
||||
for _, engine := range s.rootStore.searchEngine.GetActiveEngines() {
|
||||
if engine.IsIndexingEnabled() {
|
||||
runIndexFn(rctx, engine, func(engineCopy searchengine.SearchEngineInterface) {
|
||||
if err := engineCopy.DeletePost(post); err != nil {
|
||||
rctx.Logger().Warn("Encountered error deleting post", mlog.String("post_id", post.Id), mlog.String("search_engine", engineCopy.GetName()), mlog.Err(err))
|
||||
return
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (s SearchPostStore) deleteChannelPostsIndex(rctx request.CTX, channelID string) {
|
||||
for _, engine := range s.rootStore.searchEngine.GetActiveEngines() {
|
||||
if engine.IsIndexingEnabled() {
|
||||
runIndexFn(rctx, engine, func(engineCopy searchengine.SearchEngineInterface) {
|
||||
if err := engineCopy.DeleteChannelPosts(rctx, channelID); err != nil {
|
||||
rctx.Logger().Warn("Encountered error deleting channel posts", mlog.String("channel_id", channelID), mlog.String("search_engine", engineCopy.GetName()), mlog.Err(err))
|
||||
return
|
||||
}
|
||||
rctx.Logger().Debug("Removed all channel posts from the index in search engine", mlog.String("channel_id", channelID), mlog.String("search_engine", engineCopy.GetName()))
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (s SearchPostStore) deleteUserPostsIndex(rctx request.CTX, userID string) {
|
||||
for _, engine := range s.rootStore.searchEngine.GetActiveEngines() {
|
||||
if engine.IsIndexingEnabled() {
|
||||
runIndexFn(rctx, engine, func(engineCopy searchengine.SearchEngineInterface) {
|
||||
if err := engineCopy.DeleteUserPosts(rctx, userID); err != nil {
|
||||
rctx.Logger().Warn("Encountered error deleting user posts", mlog.String("user_id", userID), mlog.String("search_engine", engineCopy.GetName()), mlog.Err(err))
|
||||
return
|
||||
}
|
||||
rctx.Logger().Debug("Removed all user posts from the index in search engine", mlog.String("user_id", userID), mlog.String("search_engine", engineCopy.GetName()))
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (s SearchPostStore) Update(rctx request.CTX, newPost, oldPost *model.Post) (*model.Post, error) {
|
||||
post, err := s.PostStore.Update(rctx, newPost, oldPost)
|
||||
|
||||
if err == nil {
|
||||
s.indexPost(rctx, post)
|
||||
}
|
||||
return post, err
|
||||
}
|
||||
|
||||
func (s *SearchPostStore) Overwrite(rctx request.CTX, post *model.Post) (*model.Post, error) {
|
||||
post, err := s.PostStore.Overwrite(rctx, post)
|
||||
if err == nil {
|
||||
s.indexPost(rctx, post)
|
||||
}
|
||||
return post, err
|
||||
}
|
||||
|
||||
func (s SearchPostStore) Save(rctx request.CTX, post *model.Post) (*model.Post, error) {
|
||||
npost, err := s.PostStore.Save(rctx, post)
|
||||
|
||||
if err == nil {
|
||||
s.indexPost(rctx, npost)
|
||||
}
|
||||
return npost, err
|
||||
}
|
||||
|
||||
func (s SearchPostStore) Delete(rctx request.CTX, postId string, date int64, deletedByID string) error {
|
||||
err := s.PostStore.Delete(rctx, postId, date, deletedByID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
post, err := s.PostStore.GetSingle(rctx, postId, true)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
s.deletePostIndex(rctx, post)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s SearchPostStore) PermanentDelete(rctx request.CTX, postID string) error {
|
||||
// Get full post struct for later
|
||||
post, err := s.PostStore.GetSingle(rctx, postID, true)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = s.PostStore.PermanentDelete(rctx, postID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
s.deletePostIndex(rctx, post)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s SearchPostStore) PermanentDeleteByUser(rctx request.CTX, userID string) error {
|
||||
err := s.PostStore.PermanentDeleteByUser(rctx, userID)
|
||||
if err == nil {
|
||||
s.deleteUserPostsIndex(rctx, userID)
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
func (s SearchPostStore) PermanentDeleteByChannel(rctx request.CTX, channelID string) error {
|
||||
err := s.PostStore.PermanentDeleteByChannel(rctx, channelID)
|
||||
if err == nil {
|
||||
s.deleteChannelPostsIndex(rctx, channelID)
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
func (s SearchPostStore) searchPostsForUserByEngine(engine searchengine.SearchEngineInterface, paramsList []*model.SearchParams, userId, teamId string, page, perPage int) (*model.PostSearchResults, error) {
|
||||
if err := model.IsSearchParamsListValid(paramsList); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// We only allow the user to search in channels they are a member of.
|
||||
userChannels, err2 := s.rootStore.Channel().GetChannels(teamId, userId,
|
||||
&model.ChannelSearchOpts{
|
||||
IncludeDeleted: paramsList[0].IncludeDeletedChannels,
|
||||
LastDeleteAt: 0,
|
||||
})
|
||||
if err2 != nil {
|
||||
return nil, errors.Wrap(err2, "error getting channel for user")
|
||||
}
|
||||
|
||||
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) SearchPostsForUser(rctx request.CTX, paramsList []*model.SearchParams, userId, teamId string, page, perPage int) (*model.PostSearchResults, error) {
|
||||
for _, engine := range s.rootStore.searchEngine.GetActiveEngines() {
|
||||
if engine.IsSearchEnabled() {
|
||||
results, err := s.searchPostsForUserByEngine(engine, paramsList, userId, teamId, page, perPage)
|
||||
if err != nil {
|
||||
rctx.Logger().Warn("Encountered error on SearchPostsInTeamForUser.", mlog.String("search_engine", engine.GetName()), mlog.Err(err))
|
||||
continue
|
||||
}
|
||||
return results, err
|
||||
}
|
||||
}
|
||||
|
||||
if *s.rootStore.getConfig().SqlSettings.DisableDatabaseSearch {
|
||||
return &model.PostSearchResults{PostList: model.NewPostList(), Matches: model.PostSearchMatches{}}, nil
|
||||
}
|
||||
|
||||
return s.PostStore.SearchPostsForUser(rctx, paramsList, userId, teamId, page, perPage)
|
||||
}
|
||||
67
server/channels/store/searchlayer/team_layer.go
Normal file
67
server/channels/store/searchlayer/team_layer.go
Normal file
@@ -0,0 +1,67 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
package searchlayer
|
||||
|
||||
import (
|
||||
model "github.com/mattermost/mattermost/server/public/model"
|
||||
"github.com/mattermost/mattermost/server/public/shared/request"
|
||||
store "github.com/mattermost/mattermost/server/v8/channels/store"
|
||||
)
|
||||
|
||||
type SearchTeamStore struct {
|
||||
store.TeamStore
|
||||
rootStore *SearchStore
|
||||
}
|
||||
|
||||
func (s SearchTeamStore) SaveMember(rctx request.CTX, teamMember *model.TeamMember, maxUsersPerTeam int) (*model.TeamMember, error) {
|
||||
member, err := s.TeamStore.SaveMember(rctx, teamMember, maxUsersPerTeam)
|
||||
if err == nil {
|
||||
// Nothing to do if search engine is not active
|
||||
if s.rootStore.searchEngine.ActiveEngine() != "database" && s.rootStore.searchEngine.ActiveEngine() != "none" {
|
||||
s.rootStore.indexUserFromID(rctx, member.UserId)
|
||||
s.rootStore.indexChannelsForTeam(rctx, member.TeamId)
|
||||
}
|
||||
}
|
||||
return member, err
|
||||
}
|
||||
|
||||
func (s SearchTeamStore) UpdateMember(rctx request.CTX, teamMember *model.TeamMember) (*model.TeamMember, error) {
|
||||
member, err := s.TeamStore.UpdateMember(rctx, teamMember)
|
||||
if err == nil {
|
||||
s.rootStore.indexUserFromID(rctx, member.UserId)
|
||||
}
|
||||
return member, err
|
||||
}
|
||||
|
||||
func (s SearchTeamStore) RemoveMember(rctx request.CTX, teamId string, userId string) error {
|
||||
err := s.TeamStore.RemoveMember(rctx, teamId, userId)
|
||||
if err == nil {
|
||||
// Nothing to do if search engine is not active
|
||||
if s.rootStore.searchEngine.ActiveEngine() != "database" && s.rootStore.searchEngine.ActiveEngine() != "none" {
|
||||
s.rootStore.indexUserFromID(rctx, userId)
|
||||
s.rootStore.indexChannelsForTeam(rctx, teamId)
|
||||
}
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
func (s SearchTeamStore) RemoveAllMembersByUser(rctx request.CTX, userId string) error {
|
||||
if s.rootStore.searchEngine.ActiveEngine() != "database" && s.rootStore.searchEngine.ActiveEngine() != "none" {
|
||||
memberships, err := s.TeamStore.GetTeamsForUser(rctx, userId, "", true)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for _, membership := range memberships {
|
||||
s.rootStore.indexChannelsForTeam(rctx, membership.TeamId)
|
||||
}
|
||||
}
|
||||
|
||||
err := s.TeamStore.RemoveAllMembersByUser(rctx, userId)
|
||||
if err == nil {
|
||||
if s.rootStore.searchEngine.ActiveEngine() != "database" && s.rootStore.searchEngine.ActiveEngine() != "none" {
|
||||
s.rootStore.indexUserFromID(rctx, userId)
|
||||
}
|
||||
}
|
||||
return err
|
||||
}
|
||||
234
server/channels/store/searchlayer/user_layer.go
Normal file
234
server/channels/store/searchlayer/user_layer.go
Normal file
@@ -0,0 +1,234 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
package searchlayer
|
||||
|
||||
import (
|
||||
"strings"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
|
||||
"github.com/mattermost/mattermost/server/public/model"
|
||||
"github.com/mattermost/mattermost/server/public/shared/mlog"
|
||||
"github.com/mattermost/mattermost/server/public/shared/request"
|
||||
"github.com/mattermost/mattermost/server/v8/channels/store"
|
||||
"github.com/mattermost/mattermost/server/v8/platform/services/searchengine"
|
||||
)
|
||||
|
||||
type SearchUserStore struct {
|
||||
store.UserStore
|
||||
rootStore *SearchStore
|
||||
}
|
||||
|
||||
func (s *SearchUserStore) deleteUserIndex(rctx request.CTX, user *model.User) {
|
||||
for _, engine := range s.rootStore.searchEngine.GetActiveEngines() {
|
||||
if engine.IsIndexingEnabled() {
|
||||
runIndexFn(rctx, engine, func(engineCopy searchengine.SearchEngineInterface) {
|
||||
if err := engineCopy.DeleteUser(user); err != nil {
|
||||
rctx.Logger().Error("Encountered error deleting user", mlog.String("user_id", user.Id), mlog.String("search_engine", engineCopy.GetName()), mlog.Err(err))
|
||||
return
|
||||
}
|
||||
rctx.Logger().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(rctx request.CTX, teamId, term string, options *model.UserSearchOptions) ([]*model.User, error) {
|
||||
for _, engine := range s.rootStore.searchEngine.GetActiveEngines() {
|
||||
if engine.IsSearchEnabled() {
|
||||
listOfAllowedChannels, nErr := s.getListOfAllowedChannels(teamId, "", options.ViewRestrictions)
|
||||
if nErr != nil {
|
||||
rctx.Logger().Warn("Encountered error on Search.", mlog.String("search_engine", engine.GetName()), mlog.Err(nErr))
|
||||
continue
|
||||
}
|
||||
|
||||
if listOfAllowedChannels != nil && len(listOfAllowedChannels) == 0 {
|
||||
return []*model.User{}, nil
|
||||
}
|
||||
|
||||
sanitizedTerm := sanitizeSearchTerm(term)
|
||||
|
||||
usersIds, err := engine.SearchUsersInTeam(teamId, listOfAllowedChannels, sanitizedTerm, options)
|
||||
if err != nil {
|
||||
rctx.Logger().Warn("Encountered error on Search", mlog.String("search_engine", engine.GetName()), mlog.Err(err))
|
||||
continue
|
||||
}
|
||||
|
||||
users, nErr := s.UserStore.GetProfileByIds(rctx, usersIds, nil, false)
|
||||
if nErr != nil {
|
||||
rctx.Logger().Warn("Encountered error on Search", mlog.String("search_engine", engine.GetName()), mlog.Err(nErr))
|
||||
continue
|
||||
}
|
||||
|
||||
rctx.Logger().Debug("Using the first available search engine", mlog.String("search_engine", engine.GetName()))
|
||||
return users, nil
|
||||
}
|
||||
}
|
||||
|
||||
rctx.Logger().Debug("Using database search because no other search engine is available")
|
||||
|
||||
return s.UserStore.Search(rctx, teamId, term, options)
|
||||
}
|
||||
|
||||
func (s *SearchUserStore) Update(rctx request.CTX, user *model.User, trustedUpdateData bool) (*model.UserUpdate, error) {
|
||||
userUpdate, err := s.UserStore.Update(rctx, user, trustedUpdateData)
|
||||
|
||||
if err == nil {
|
||||
s.rootStore.indexUser(rctx, userUpdate.New)
|
||||
}
|
||||
return userUpdate, err
|
||||
}
|
||||
|
||||
func (s *SearchUserStore) Save(rctx request.CTX, user *model.User) (*model.User, error) {
|
||||
nuser, err := s.UserStore.Save(rctx, user)
|
||||
|
||||
if err == nil {
|
||||
s.rootStore.indexUser(rctx, nuser)
|
||||
}
|
||||
return nuser, err
|
||||
}
|
||||
|
||||
func (s *SearchUserStore) PermanentDelete(rctx request.CTX, userId string) error {
|
||||
user, userErr := s.UserStore.Get(rctx, userId)
|
||||
if userErr != nil {
|
||||
rctx.Logger().Warn("Encountered error deleting user", mlog.String("user_id", userId), mlog.Err(userErr))
|
||||
}
|
||||
err := s.UserStore.PermanentDelete(rctx, userId)
|
||||
if err == nil && userErr == nil {
|
||||
s.deleteUserIndex(rctx, user)
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
func (s *SearchUserStore) autocompleteUsersInChannelByEngine(rctx request.CTX, engine searchengine.SearchEngineInterface, teamId, channelId, term string, options *model.UserSearchOptions) (*model.UserAutocompleteInChannel, error) {
|
||||
var err *model.AppError
|
||||
uchanIds := []string{}
|
||||
nuchanIds := []string{}
|
||||
sanitizedTerm := sanitizeSearchTerm(term)
|
||||
if channelId != "" && options.ListOfAllowedChannels != nil && !strings.Contains(strings.Join(options.ListOfAllowedChannels, "."), channelId) {
|
||||
nuchanIds, err = engine.SearchUsersInTeam(teamId, options.ListOfAllowedChannels, sanitizedTerm, options)
|
||||
} else {
|
||||
uchanIds, nuchanIds, err = engine.SearchUsersInChannel(teamId, channelId, options.ListOfAllowedChannels, sanitizedTerm, options)
|
||||
}
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
uchan := make(chan store.StoreResult[[]*model.User], 1)
|
||||
go func() {
|
||||
users, nErr := s.UserStore.GetProfileByIds(rctx, uchanIds, nil, false)
|
||||
uchan <- store.StoreResult[[]*model.User]{Data: users, NErr: nErr}
|
||||
close(uchan)
|
||||
}()
|
||||
|
||||
nuchan := make(chan store.StoreResult[[]*model.User], 1)
|
||||
go func() {
|
||||
users, nErr := s.UserStore.GetProfileByIds(rctx, nuchanIds, nil, false)
|
||||
nuchan <- store.StoreResult[[]*model.User]{Data: users, NErr: nErr}
|
||||
close(nuchan)
|
||||
}()
|
||||
|
||||
autocomplete := &model.UserAutocompleteInChannel{}
|
||||
|
||||
result := <-uchan
|
||||
if result.NErr != nil {
|
||||
return nil, errors.Wrap(result.NErr, "failed to get user profiles by ids")
|
||||
}
|
||||
autocomplete.InChannel = result.Data
|
||||
|
||||
result = <-nuchan
|
||||
if result.NErr != nil {
|
||||
return nil, errors.Wrap(result.NErr, "failed to get user profiles by ids")
|
||||
}
|
||||
autocomplete.OutOfChannel = result.Data
|
||||
|
||||
return autocomplete, nil
|
||||
}
|
||||
|
||||
// getListOfAllowedChannels return the list of allowed channels to search user based on the
|
||||
//
|
||||
// next scenarios:
|
||||
// - If there isn't view restrictions (team or channel) and no team id to filter them, then all
|
||||
// channels are allowed (nil return)
|
||||
// - If we receive a team Id and either we don't have view restrictions or the provided team id is included in the
|
||||
// list of restricted teams, then we return all the team channels
|
||||
// - If we don't receive team id or the provided team id is not in the list of allowed teams to search of and we
|
||||
// don't have channel restrictions then we return an empty result because we cannot get channels
|
||||
// - If we receive channels restrictions we get:
|
||||
// - If we don't have team id, we get those restricted channels (guest accounts and quick search)
|
||||
// - If we have a team id then we only return those restricted channels that belongs to that team
|
||||
func (s *SearchUserStore) getListOfAllowedChannels(teamId, channelId string, viewRestrictions *model.ViewUsersRestrictions) ([]string, error) {
|
||||
var listOfAllowedChannels []string
|
||||
if viewRestrictions == nil && teamId == "" {
|
||||
// nil return without error means all channels are allowed
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
if teamId != "" && (viewRestrictions == nil || strings.Contains(strings.Join(viewRestrictions.Teams, "."), teamId)) {
|
||||
channels, err := s.rootStore.Channel().GetTeamChannels(teamId)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "failed to get team channels")
|
||||
}
|
||||
for _, channel := range channels {
|
||||
listOfAllowedChannels = append(listOfAllowedChannels, channel.Id)
|
||||
}
|
||||
|
||||
if channelId != "" {
|
||||
ch, err := s.rootStore.Channel().Get(channelId, true)
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "failed to get channel with id: %s", channelId)
|
||||
}
|
||||
// Check if DM/GM channel, and add to the list.
|
||||
// This is because GetTeamChannels does not return DM/GM channels.
|
||||
// And since the channelId is passed from the API layer, it is already
|
||||
// auth checked to confirm that the user has permission.
|
||||
if ch.IsGroupOrDirect() {
|
||||
listOfAllowedChannels = append(listOfAllowedChannels, channelId)
|
||||
}
|
||||
}
|
||||
return listOfAllowedChannels, nil
|
||||
}
|
||||
|
||||
if len(viewRestrictions.Channels) > 0 {
|
||||
channels, err := s.rootStore.Channel().GetChannelsByIds(viewRestrictions.Channels, false)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "failed to get channels by ids")
|
||||
}
|
||||
for _, c := range channels {
|
||||
if teamId == "" || (teamId != "" && c.TeamId == teamId) {
|
||||
listOfAllowedChannels = append(listOfAllowedChannels, c.Id)
|
||||
}
|
||||
}
|
||||
return listOfAllowedChannels, nil
|
||||
}
|
||||
|
||||
return []string{}, nil
|
||||
}
|
||||
|
||||
func (s *SearchUserStore) AutocompleteUsersInChannel(rctx request.CTX, teamId, channelId, term string, options *model.UserSearchOptions) (*model.UserAutocompleteInChannel, error) {
|
||||
for _, engine := range s.rootStore.searchEngine.GetActiveEngines() {
|
||||
if engine.IsAutocompletionEnabled() {
|
||||
listOfAllowedChannels, nErr := s.getListOfAllowedChannels(teamId, channelId, options.ViewRestrictions)
|
||||
if nErr != nil {
|
||||
rctx.Logger().Warn("Encountered error on AutocompleteUsersInChannel.", mlog.String("search_engine", engine.GetName()), mlog.Err(nErr))
|
||||
continue
|
||||
}
|
||||
if listOfAllowedChannels != nil && len(listOfAllowedChannels) == 0 {
|
||||
return &model.UserAutocompleteInChannel{}, nil
|
||||
}
|
||||
options.ListOfAllowedChannels = listOfAllowedChannels
|
||||
|
||||
autocomplete, nErr := s.autocompleteUsersInChannelByEngine(rctx, engine, teamId, channelId, term, options)
|
||||
if nErr != nil {
|
||||
rctx.Logger().Warn("Encountered error on AutocompleteUsersInChannel.", mlog.String("search_engine", engine.GetName()), mlog.Err(nErr))
|
||||
continue
|
||||
}
|
||||
rctx.Logger().Debug("Using the first available search engine", mlog.String("search_engine", engine.GetName()))
|
||||
return autocomplete, nil
|
||||
}
|
||||
}
|
||||
|
||||
rctx.Logger().Debug("Using database search because no other search engine is available")
|
||||
return s.UserStore.AutocompleteUsersInChannel(rctx, teamId, channelId, term, options)
|
||||
}
|
||||
12
server/channels/store/searchlayer/utils.go
Normal file
12
server/channels/store/searchlayer/utils.go
Normal file
@@ -0,0 +1,12 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
package searchlayer
|
||||
|
||||
import (
|
||||
"strings"
|
||||
)
|
||||
|
||||
func sanitizeSearchTerm(term string) string {
|
||||
return strings.TrimLeft(term, "@")
|
||||
}
|
||||
Reference in New Issue
Block a user