1// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. 2// See LICENSE.txt for license information. 3 4package searchlayer 5 6import ( 7 "context" 8 9 "github.com/pkg/errors" 10 11 "github.com/mattermost/mattermost-server/v6/model" 12 "github.com/mattermost/mattermost-server/v6/services/searchengine" 13 "github.com/mattermost/mattermost-server/v6/shared/mlog" 14 "github.com/mattermost/mattermost-server/v6/store" 15) 16 17type SearchPostStore struct { 18 store.PostStore 19 rootStore *SearchStore 20} 21 22func (s SearchPostStore) indexPost(post *model.Post) { 23 for _, engine := range s.rootStore.searchEngine.GetActiveEngines() { 24 if engine.IsIndexingEnabled() { 25 runIndexFn(engine, func(engineCopy searchengine.SearchEngineInterface) { 26 channel, chanErr := s.rootStore.Channel().Get(post.ChannelId, true) 27 if chanErr != nil { 28 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), mlog.Err(chanErr)) 29 return 30 } 31 if err := engineCopy.IndexPost(post, channel.TeamId); err != nil { 32 mlog.Warn("Encountered error indexing post", mlog.String("post_id", post.Id), mlog.String("search_engine", engineCopy.GetName()), mlog.Err(err)) 33 return 34 } 35 mlog.Debug("Indexed post in search engine", mlog.String("search_engine", engineCopy.GetName()), mlog.String("post_id", post.Id)) 36 }) 37 } 38 } 39} 40 41func (s SearchPostStore) deletePostIndex(post *model.Post) { 42 for _, engine := range s.rootStore.searchEngine.GetActiveEngines() { 43 if engine.IsIndexingEnabled() { 44 runIndexFn(engine, func(engineCopy searchengine.SearchEngineInterface) { 45 if err := engineCopy.DeletePost(post); err != nil { 46 mlog.Warn("Encountered error deleting post", mlog.String("post_id", post.Id), mlog.String("search_engine", engineCopy.GetName()), mlog.Err(err)) 47 return 48 } 49 mlog.Debug("Removed post from the index in search engine", mlog.String("search_engine", engineCopy.GetName()), mlog.String("post_id", post.Id)) 50 }) 51 } 52 } 53} 54 55func (s SearchPostStore) deleteChannelPostsIndex(channelID string) { 56 for _, engine := range s.rootStore.searchEngine.GetActiveEngines() { 57 if engine.IsIndexingEnabled() { 58 runIndexFn(engine, func(engineCopy searchengine.SearchEngineInterface) { 59 if err := engineCopy.DeleteChannelPosts(channelID); err != nil { 60 mlog.Warn("Encountered error deleting channel posts", mlog.String("channel_id", channelID), mlog.String("search_engine", engineCopy.GetName()), mlog.Err(err)) 61 return 62 } 63 mlog.Debug("Removed all channel posts from the index in search engine", mlog.String("channel_id", channelID), mlog.String("search_engine", engineCopy.GetName())) 64 }) 65 } 66 } 67} 68 69func (s SearchPostStore) deleteUserPostsIndex(userID string) { 70 for _, engine := range s.rootStore.searchEngine.GetActiveEngines() { 71 if engine.IsIndexingEnabled() { 72 runIndexFn(engine, func(engineCopy searchengine.SearchEngineInterface) { 73 if err := engineCopy.DeleteUserPosts(userID); err != nil { 74 mlog.Warn("Encountered error deleting user posts", mlog.String("user_id", userID), mlog.String("search_engine", engineCopy.GetName()), mlog.Err(err)) 75 return 76 } 77 mlog.Debug("Removed all user posts from the index in search engine", mlog.String("user_id", userID), mlog.String("search_engine", engineCopy.GetName())) 78 }) 79 } 80 } 81} 82 83func (s SearchPostStore) Update(newPost, oldPost *model.Post) (*model.Post, error) { 84 post, err := s.PostStore.Update(newPost, oldPost) 85 86 if err == nil { 87 s.indexPost(post) 88 } 89 return post, err 90} 91 92func (s *SearchPostStore) Overwrite(post *model.Post) (*model.Post, error) { 93 post, err := s.PostStore.Overwrite(post) 94 if err == nil { 95 s.indexPost(post) 96 } 97 return post, err 98} 99 100func (s SearchPostStore) Save(post *model.Post) (*model.Post, error) { 101 npost, err := s.PostStore.Save(post) 102 103 if err == nil { 104 s.indexPost(npost) 105 } 106 return npost, err 107} 108 109func (s SearchPostStore) Delete(postId string, date int64, deletedByID string) error { 110 err := s.PostStore.Delete(postId, date, deletedByID) 111 112 if err == nil { 113 postList, err2 := s.PostStore.Get(context.Background(), postId, true, false, false, "") 114 if postList != nil && len(postList.Order) > 0 { 115 if err2 != nil { 116 s.deletePostIndex(postList.Posts[postList.Order[0]]) 117 } 118 } 119 } 120 return err 121} 122 123func (s SearchPostStore) PermanentDeleteByUser(userID string) error { 124 err := s.PostStore.PermanentDeleteByUser(userID) 125 if err == nil { 126 s.deleteUserPostsIndex(userID) 127 } 128 return err 129} 130 131func (s SearchPostStore) PermanentDeleteByChannel(channelID string) error { 132 err := s.PostStore.PermanentDeleteByChannel(channelID) 133 if err == nil { 134 s.deleteChannelPostsIndex(channelID) 135 } 136 return err 137} 138 139func (s SearchPostStore) searchPostsInTeamForUserByEngine(engine searchengine.SearchEngineInterface, paramsList []*model.SearchParams, userId, teamId string, page, perPage int) (*model.PostSearchResults, error) { 140 if err := model.IsSearchParamsListValid(paramsList); err != nil { 141 return nil, err 142 } 143 144 // We only allow the user to search in channels they are a member of. 145 userChannels, err2 := s.rootStore.Channel().GetChannels(teamId, userId, paramsList[0].IncludeDeletedChannels, 0) 146 if err2 != nil { 147 return nil, errors.Wrap(err2, "error getting channel for user") 148 } 149 150 postIds, matches, err := engine.SearchPosts(userChannels, paramsList, page, perPage) 151 if err != nil { 152 return nil, err 153 } 154 155 // Get the posts 156 postList := model.NewPostList() 157 if len(postIds) > 0 { 158 posts, err := s.PostStore.GetPostsByIds(postIds) 159 if err != nil { 160 return nil, err 161 } 162 for _, p := range posts { 163 if p.DeleteAt == 0 { 164 postList.AddPost(p) 165 postList.AddOrder(p.Id) 166 } 167 } 168 } 169 170 return model.MakePostSearchResults(postList, matches), nil 171} 172 173func (s SearchPostStore) SearchPostsInTeamForUser(paramsList []*model.SearchParams, userId, teamId string, page, perPage int) (*model.PostSearchResults, error) { 174 for _, engine := range s.rootStore.searchEngine.GetActiveEngines() { 175 if engine.IsSearchEnabled() { 176 results, err := s.searchPostsInTeamForUserByEngine(engine, paramsList, userId, teamId, page, perPage) 177 if err != nil { 178 mlog.Warn("Encountered error on SearchPostsInTeamForUser.", mlog.String("search_engine", engine.GetName()), mlog.Err(err)) 179 continue 180 } 181 mlog.Debug("Using the first available search engine", mlog.String("search_engine", engine.GetName())) 182 return results, err 183 } 184 } 185 186 if *s.rootStore.getConfig().SqlSettings.DisableDatabaseSearch { 187 mlog.Debug("Returning empty results for post SearchPostsInTeam as the database search is disabled") 188 return &model.PostSearchResults{PostList: model.NewPostList(), Matches: model.PostSearchMatches{}}, nil 189 } 190 191 mlog.Debug("Using database search because no other search engine is available") 192 return s.PostStore.SearchPostsInTeamForUser(paramsList, userId, teamId, page, perPage) 193} 194