Added from:, in:, and channel: search modifiers
Этот коммит содержится в:
10
api/post.go
10
api/post.go
@@ -680,16 +680,16 @@ func searchPosts(c *Context, w http.ResponseWriter, r *http.Request) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
hashtagTerms, plainTerms := model.ParseHashtags(terms)
|
plainSearchParams, hashtagSearchParams := model.ParseSearchParams(terms)
|
||||||
|
|
||||||
var hchan store.StoreChannel
|
var hchan store.StoreChannel
|
||||||
if len(hashtagTerms) != 0 {
|
if hashtagSearchParams != nil {
|
||||||
hchan = Srv.Store.Post().Search(c.Session.TeamId, c.Session.UserId, hashtagTerms, true)
|
hchan = Srv.Store.Post().Search(c.Session.TeamId, c.Session.UserId, hashtagSearchParams)
|
||||||
}
|
}
|
||||||
|
|
||||||
var pchan store.StoreChannel
|
var pchan store.StoreChannel
|
||||||
if len(plainTerms) != 0 {
|
if plainSearchParams != nil {
|
||||||
pchan = Srv.Store.Post().Search(c.Session.TeamId, c.Session.UserId, terms, false)
|
pchan = Srv.Store.Post().Search(c.Session.TeamId, c.Session.UserId, plainSearchParams)
|
||||||
}
|
}
|
||||||
|
|
||||||
mainList := &model.PostList{}
|
mainList := &model.PostList{}
|
||||||
|
|||||||
122
api/post_test.go
122
api/post_test.go
@@ -406,6 +406,128 @@ func TestSearchHashtagPosts(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestSearchPostsInChannel(t *testing.T) {
|
||||||
|
Setup()
|
||||||
|
|
||||||
|
team := &model.Team{DisplayName: "Name", Name: "z-z-" + model.NewId() + "a", Email: "test@nowhere.com", Type: model.TEAM_OPEN}
|
||||||
|
team = Client.Must(Client.CreateTeam(team)).Data.(*model.Team)
|
||||||
|
|
||||||
|
user1 := &model.User{TeamId: team.Id, Email: model.NewId() + "corey@test.com", Nickname: "Corey Hulen", Password: "pwd"}
|
||||||
|
user1 = Client.Must(Client.CreateUser(user1, "")).Data.(*model.User)
|
||||||
|
store.Must(Srv.Store.User().VerifyEmail(user1.Id))
|
||||||
|
|
||||||
|
Client.LoginByEmail(team.Name, user1.Email, "pwd")
|
||||||
|
|
||||||
|
channel1 := &model.Channel{DisplayName: "TestGetPosts", Name: "a" + model.NewId() + "a", Type: model.CHANNEL_OPEN, TeamId: team.Id}
|
||||||
|
channel1 = Client.Must(Client.CreateChannel(channel1)).Data.(*model.Channel)
|
||||||
|
|
||||||
|
post1 := &model.Post{ChannelId: channel1.Id, Message: "sgtitlereview with space"}
|
||||||
|
post1 = Client.Must(Client.CreatePost(post1)).Data.(*model.Post)
|
||||||
|
|
||||||
|
channel2 := &model.Channel{DisplayName: "TestGetPosts", Name: "a" + model.NewId() + "a", Type: model.CHANNEL_OPEN, TeamId: team.Id}
|
||||||
|
channel2 = Client.Must(Client.CreateChannel(channel2)).Data.(*model.Channel)
|
||||||
|
|
||||||
|
post2 := &model.Post{ChannelId: channel2.Id, Message: "sgtitlereview\n with return"}
|
||||||
|
post2 = Client.Must(Client.CreatePost(post2)).Data.(*model.Post)
|
||||||
|
|
||||||
|
post3 := &model.Post{ChannelId: channel2.Id, Message: "other message with no return"}
|
||||||
|
post3 = Client.Must(Client.CreatePost(post3)).Data.(*model.Post)
|
||||||
|
|
||||||
|
if result := Client.Must(Client.SearchPosts("channel:")).Data.(*model.PostList); len(result.Order) != 0 {
|
||||||
|
t.Fatalf("wrong number of posts returned %v", len(result.Order))
|
||||||
|
}
|
||||||
|
|
||||||
|
if result := Client.Must(Client.SearchPosts("in:")).Data.(*model.PostList); len(result.Order) != 0 {
|
||||||
|
t.Fatalf("wrong number of posts returned %v", len(result.Order))
|
||||||
|
}
|
||||||
|
|
||||||
|
if result := Client.Must(Client.SearchPosts("channel:" + channel1.Name)).Data.(*model.PostList); len(result.Order) != 1 {
|
||||||
|
t.Fatalf("wrong number of posts returned %v", len(result.Order))
|
||||||
|
}
|
||||||
|
|
||||||
|
if result := Client.Must(Client.SearchPosts("in: " + channel2.Name)).Data.(*model.PostList); len(result.Order) != 2 {
|
||||||
|
t.Fatalf("wrong number of posts returned %v", len(result.Order))
|
||||||
|
}
|
||||||
|
|
||||||
|
if result := Client.Must(Client.SearchPosts("channel: " + channel2.Name)).Data.(*model.PostList); len(result.Order) != 2 {
|
||||||
|
t.Fatalf("wrong number of posts returned %v", len(result.Order))
|
||||||
|
}
|
||||||
|
|
||||||
|
if result := Client.Must(Client.SearchPosts("ChAnNeL: " + channel2.Name)).Data.(*model.PostList); len(result.Order) != 2 {
|
||||||
|
t.Fatalf("wrong number of posts returned %v", len(result.Order))
|
||||||
|
}
|
||||||
|
|
||||||
|
if result := Client.Must(Client.SearchPosts("sgtitlereview")).Data.(*model.PostList); len(result.Order) != 2 {
|
||||||
|
t.Fatalf("wrong number of posts returned %v", len(result.Order))
|
||||||
|
}
|
||||||
|
|
||||||
|
if result := Client.Must(Client.SearchPosts("sgtitlereview in:")).Data.(*model.PostList); len(result.Order) != 2 {
|
||||||
|
t.Fatalf("wrong number of posts returned %v", len(result.Order))
|
||||||
|
}
|
||||||
|
|
||||||
|
if result := Client.Must(Client.SearchPosts("sgtitlereview channel:" + channel1.Name)).Data.(*model.PostList); len(result.Order) != 1 {
|
||||||
|
t.Fatalf("wrong number of posts returned %v", len(result.Order))
|
||||||
|
}
|
||||||
|
|
||||||
|
if result := Client.Must(Client.SearchPosts("sgtitlereview in: " + channel2.Name)).Data.(*model.PostList); len(result.Order) != 1 {
|
||||||
|
t.Fatalf("wrong number of posts returned %v", len(result.Order))
|
||||||
|
}
|
||||||
|
|
||||||
|
if result := Client.Must(Client.SearchPosts("sgtitlereview channel: " + channel2.Name)).Data.(*model.PostList); len(result.Order) != 1 {
|
||||||
|
t.Fatalf("wrong number of posts returned %v", len(result.Order))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSearchPostsFromUser(t *testing.T) {
|
||||||
|
Setup()
|
||||||
|
|
||||||
|
team := &model.Team{DisplayName: "Name", Name: "z-z-" + model.NewId() + "a", Email: "test@nowhere.com", Type: model.TEAM_OPEN}
|
||||||
|
team = Client.Must(Client.CreateTeam(team)).Data.(*model.Team)
|
||||||
|
|
||||||
|
user1 := &model.User{TeamId: team.Id, Email: model.NewId() + "corey@test.com", Nickname: "Corey Hulen", Password: "pwd"}
|
||||||
|
user1 = Client.Must(Client.CreateUser(user1, "")).Data.(*model.User)
|
||||||
|
store.Must(Srv.Store.User().VerifyEmail(user1.Id))
|
||||||
|
|
||||||
|
Client.LoginByEmail(team.Name, user1.Email, "pwd")
|
||||||
|
|
||||||
|
channel1 := &model.Channel{DisplayName: "TestGetPosts", Name: "a" + model.NewId() + "a", Type: model.CHANNEL_OPEN, TeamId: team.Id}
|
||||||
|
channel1 = Client.Must(Client.CreateChannel(channel1)).Data.(*model.Channel)
|
||||||
|
|
||||||
|
channel2 := &model.Channel{DisplayName: "TestGetPosts", Name: "a" + model.NewId() + "a", Type: model.CHANNEL_OPEN, TeamId: team.Id}
|
||||||
|
channel2 = Client.Must(Client.CreateChannel(channel2)).Data.(*model.Channel)
|
||||||
|
|
||||||
|
post1 := &model.Post{ChannelId: channel1.Id, Message: "sgtitlereview with space"}
|
||||||
|
post1 = Client.Must(Client.CreatePost(post1)).Data.(*model.Post)
|
||||||
|
|
||||||
|
user2 := &model.User{TeamId: team.Id, Email: model.NewId() + "corey@test.com", Nickname: "Corey Hulen", Password: "pwd"}
|
||||||
|
user2 = Client.Must(Client.CreateUser(user2, "")).Data.(*model.User)
|
||||||
|
store.Must(Srv.Store.User().VerifyEmail(user2.Id))
|
||||||
|
|
||||||
|
Client.LoginByEmail(team.Name, user2.Email, "pwd")
|
||||||
|
Client.Must(Client.JoinChannel(channel1.Id))
|
||||||
|
Client.Must(Client.JoinChannel(channel2.Id))
|
||||||
|
|
||||||
|
post2 := &model.Post{ChannelId: channel2.Id, Message: "sgtitlereview\n with return"}
|
||||||
|
post2 = Client.Must(Client.CreatePost(post2)).Data.(*model.Post)
|
||||||
|
|
||||||
|
if result := Client.Must(Client.SearchPosts("from: " + user1.Username)).Data.(*model.PostList); len(result.Order) != 1 {
|
||||||
|
t.Fatalf("wrong number of posts returned %v", len(result.Order))
|
||||||
|
}
|
||||||
|
|
||||||
|
// note that this includes the "User2 has joined the channel" system messages
|
||||||
|
if result := Client.Must(Client.SearchPosts("from: " + user2.Username)).Data.(*model.PostList); len(result.Order) != 3 {
|
||||||
|
t.Fatalf("wrong number of posts returned %v", len(result.Order))
|
||||||
|
}
|
||||||
|
|
||||||
|
if result := Client.Must(Client.SearchPosts("from: " + user2.Username + " sgtitlereview")).Data.(*model.PostList); len(result.Order) != 1 {
|
||||||
|
t.Fatalf("wrong number of posts returned %v", len(result.Order))
|
||||||
|
}
|
||||||
|
|
||||||
|
if result := Client.Must(Client.SearchPosts("from: " + user2.Username + " in:" + channel1.Name)).Data.(*model.PostList); len(result.Order) != 1 {
|
||||||
|
t.Fatalf("wrong number of posts returned %v", len(result.Order))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestGetPostsCache(t *testing.T) {
|
func TestGetPostsCache(t *testing.T) {
|
||||||
Setup()
|
Setup()
|
||||||
|
|
||||||
|
|||||||
130
model/search_params.go
Обычный файл
130
model/search_params.go
Обычный файл
@@ -0,0 +1,130 @@
|
|||||||
|
// Copyright (c) 2015 Mattermost, Inc. All Rights Reserved.
|
||||||
|
// See License.txt for license information.
|
||||||
|
|
||||||
|
package model
|
||||||
|
|
||||||
|
import (
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
type SearchParams struct {
|
||||||
|
Terms string
|
||||||
|
IsHashtag bool
|
||||||
|
InChannel string
|
||||||
|
FromUser string
|
||||||
|
}
|
||||||
|
|
||||||
|
var searchFlags = [...]string{"from", "channel", "in"}
|
||||||
|
|
||||||
|
func splitWords(text string) []string {
|
||||||
|
words := []string{}
|
||||||
|
|
||||||
|
for _, word := range strings.Fields(text) {
|
||||||
|
word = puncStart.ReplaceAllString(word, "")
|
||||||
|
word = puncEnd.ReplaceAllString(word, "")
|
||||||
|
|
||||||
|
if len(word) != 0 {
|
||||||
|
words = append(words, word)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return words
|
||||||
|
}
|
||||||
|
|
||||||
|
func parseSearchFlags(input []string) ([]string, map[string]string) {
|
||||||
|
words := []string{}
|
||||||
|
flags := make(map[string]string)
|
||||||
|
|
||||||
|
skipNextWord := false
|
||||||
|
for i, word := range input {
|
||||||
|
if skipNextWord {
|
||||||
|
skipNextWord = false
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
isFlag := false
|
||||||
|
|
||||||
|
if colon := strings.Index(word, ":"); colon != -1 {
|
||||||
|
flag := word[:colon]
|
||||||
|
value := word[colon+1:]
|
||||||
|
|
||||||
|
for _, searchFlag := range searchFlags {
|
||||||
|
// check for case insensitive equality
|
||||||
|
if strings.EqualFold(flag, searchFlag) {
|
||||||
|
if value != "" {
|
||||||
|
flags[searchFlag] = value
|
||||||
|
isFlag = true
|
||||||
|
} else if i < len(input)-1 {
|
||||||
|
flags[searchFlag] = input[i+1]
|
||||||
|
skipNextWord = true
|
||||||
|
isFlag = true
|
||||||
|
}
|
||||||
|
|
||||||
|
if isFlag {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if !isFlag {
|
||||||
|
words = append(words, word)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return words, flags
|
||||||
|
}
|
||||||
|
|
||||||
|
func ParseSearchParams(text string) (*SearchParams, *SearchParams) {
|
||||||
|
words, flags := parseSearchFlags(splitWords(text))
|
||||||
|
|
||||||
|
hashtagTerms := []string{}
|
||||||
|
plainTerms := []string{}
|
||||||
|
|
||||||
|
for _, word := range words {
|
||||||
|
if validHashtag.MatchString(word) {
|
||||||
|
hashtagTerms = append(hashtagTerms, word)
|
||||||
|
} else {
|
||||||
|
plainTerms = append(plainTerms, word)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
inChannel := flags["channel"]
|
||||||
|
if inChannel == "" {
|
||||||
|
inChannel = flags["in"]
|
||||||
|
}
|
||||||
|
|
||||||
|
fromUser := flags["from"]
|
||||||
|
|
||||||
|
var plainParams *SearchParams
|
||||||
|
if len(plainTerms) > 0 {
|
||||||
|
plainParams = &SearchParams{
|
||||||
|
Terms: strings.Join(plainTerms, " "),
|
||||||
|
IsHashtag: false,
|
||||||
|
InChannel: inChannel,
|
||||||
|
FromUser: fromUser,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var hashtagParams *SearchParams
|
||||||
|
if len(hashtagTerms) > 0 {
|
||||||
|
hashtagParams = &SearchParams{
|
||||||
|
Terms: strings.Join(hashtagTerms, " "),
|
||||||
|
IsHashtag: true,
|
||||||
|
InChannel: inChannel,
|
||||||
|
FromUser: fromUser,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// special case for when no terms are specified but we still have a filter
|
||||||
|
if plainParams == nil && hashtagParams == nil && (inChannel != "" || fromUser != "") {
|
||||||
|
plainParams = &SearchParams{
|
||||||
|
Terms: "",
|
||||||
|
IsHashtag: false,
|
||||||
|
InChannel: inChannel,
|
||||||
|
FromUser: fromUser,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return plainParams, hashtagParams
|
||||||
|
}
|
||||||
70
model/search_params_test.go
Обычный файл
70
model/search_params_test.go
Обычный файл
@@ -0,0 +1,70 @@
|
|||||||
|
// Copyright (c) 2015 Mattermost, Inc. All Rights Reserved.
|
||||||
|
// See License.txt for license information.
|
||||||
|
|
||||||
|
package model
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestParseSearchFlags(t *testing.T) {
|
||||||
|
if words, flags := parseSearchFlags(splitWords("")); len(words) != 0 {
|
||||||
|
t.Fatal("got words from empty input")
|
||||||
|
} else if len(flags) != 0 {
|
||||||
|
t.Fatal("got flags from empty input")
|
||||||
|
}
|
||||||
|
|
||||||
|
if words, flags := parseSearchFlags(splitWords("word")); len(words) != 1 || words[0] != "word" {
|
||||||
|
t.Fatalf("got incorrect words %v", words)
|
||||||
|
} else if len(flags) != 0 {
|
||||||
|
t.Fatalf("got incorrect flags %v", flags)
|
||||||
|
}
|
||||||
|
|
||||||
|
if words, flags := parseSearchFlags(splitWords("apple banana cherry")); len(words) != 3 || words[0] != "apple" || words[1] != "banana" || words[2] != "cherry" {
|
||||||
|
t.Fatalf("got incorrect words %v", words)
|
||||||
|
} else if len(flags) != 0 {
|
||||||
|
t.Fatalf("got incorrect flags %v", flags)
|
||||||
|
}
|
||||||
|
|
||||||
|
if words, flags := parseSearchFlags(splitWords("apple banana from:chan")); len(words) != 2 || words[0] != "apple" || words[1] != "banana" {
|
||||||
|
t.Fatalf("got incorrect words %v", words)
|
||||||
|
} else if len(flags) != 1 || flags["from"] != "chan" {
|
||||||
|
t.Fatalf("got incorrect flags %v", flags)
|
||||||
|
}
|
||||||
|
|
||||||
|
if words, flags := parseSearchFlags(splitWords("apple banana from: chan")); len(words) != 2 || words[0] != "apple" || words[1] != "banana" {
|
||||||
|
t.Fatalf("got incorrect words %v", words)
|
||||||
|
} else if len(flags) != 1 || flags["from"] != "chan" {
|
||||||
|
t.Fatalf("got incorrect flags %v", flags)
|
||||||
|
}
|
||||||
|
|
||||||
|
if words, flags := parseSearchFlags(splitWords("apple banana in: chan")); len(words) != 2 || words[0] != "apple" || words[1] != "banana" {
|
||||||
|
t.Fatalf("got incorrect words %v", words)
|
||||||
|
} else if len(flags) != 1 || flags["in"] != "chan" {
|
||||||
|
t.Fatalf("got incorrect flags %v", flags)
|
||||||
|
}
|
||||||
|
|
||||||
|
if words, flags := parseSearchFlags(splitWords("apple banana channel:chan")); len(words) != 2 || words[0] != "apple" || words[1] != "banana" {
|
||||||
|
t.Fatalf("got incorrect words %v", words)
|
||||||
|
} else if len(flags) != 1 || flags["channel"] != "chan" {
|
||||||
|
t.Fatalf("got incorrect flags %v", flags)
|
||||||
|
}
|
||||||
|
|
||||||
|
if words, flags := parseSearchFlags(splitWords("fruit: cherry")); len(words) != 2 || words[0] != "fruit:" || words[1] != "cherry" {
|
||||||
|
t.Fatalf("got incorrect words %v", words)
|
||||||
|
} else if len(flags) != 0 {
|
||||||
|
t.Fatalf("got incorrect flags %v", flags)
|
||||||
|
}
|
||||||
|
|
||||||
|
if words, flags := parseSearchFlags(splitWords("channel:")); len(words) != 1 || words[0] != "channel:" {
|
||||||
|
t.Fatalf("got incorrect words %v", words)
|
||||||
|
} else if len(flags) != 0 {
|
||||||
|
t.Fatalf("got incorrect flags %v", flags)
|
||||||
|
}
|
||||||
|
|
||||||
|
if words, flags := parseSearchFlags(splitWords("channel: first in: second from:")); len(words) != 1 || words[0] != "from:" {
|
||||||
|
t.Fatalf("got incorrect words %v", words)
|
||||||
|
} else if len(flags) != 2 || flags["channel"] != "first" || flags["in"] != "second" {
|
||||||
|
t.Fatalf("got incorrect flags %v", flags)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -242,10 +242,10 @@ func Etag(parts ...interface{}) string {
|
|||||||
|
|
||||||
var validHashtag = regexp.MustCompile(`^(#[A-Za-z]+[A-Za-z0-9_\-]*[A-Za-z0-9])$`)
|
var validHashtag = regexp.MustCompile(`^(#[A-Za-z]+[A-Za-z0-9_\-]*[A-Za-z0-9])$`)
|
||||||
var puncStart = regexp.MustCompile(`^[.,()&$!\[\]{}"':;\\]+`)
|
var puncStart = regexp.MustCompile(`^[.,()&$!\[\]{}"':;\\]+`)
|
||||||
var puncEnd = regexp.MustCompile(`[.,()&$#!\[\]{}"':;\\]+$`)
|
var puncEnd = regexp.MustCompile(`[.,()&$#!\[\]{}"';\\]+$`)
|
||||||
|
|
||||||
func ParseHashtags(text string) (string, string) {
|
func ParseHashtags(text string) (string, string) {
|
||||||
words := strings.Split(strings.Replace(text, "\n", " ", -1), " ")
|
words := strings.Fields(text)
|
||||||
|
|
||||||
hashtagString := ""
|
hashtagString := ""
|
||||||
plainString := ""
|
plainString := ""
|
||||||
|
|||||||
@@ -407,15 +407,23 @@ var specialSearchChar = []string{
|
|||||||
"@",
|
"@",
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s SqlPostStore) Search(teamId string, userId string, terms string, isHashtagSearch bool) StoreChannel {
|
func (s SqlPostStore) Search(teamId string, userId string, params *model.SearchParams) StoreChannel {
|
||||||
storeChannel := make(StoreChannel)
|
storeChannel := make(StoreChannel)
|
||||||
|
|
||||||
go func() {
|
go func() {
|
||||||
result := StoreResult{}
|
result := StoreResult{}
|
||||||
|
|
||||||
termMap := map[string]bool{}
|
termMap := map[string]bool{}
|
||||||
|
terms := params.Terms
|
||||||
|
|
||||||
|
if terms == "" && params.InChannel == "" && params.FromUser == "" {
|
||||||
|
result.Data = []*model.Post{}
|
||||||
|
storeChannel <- result
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
searchType := "Message"
|
searchType := "Message"
|
||||||
if isHashtagSearch {
|
if params.IsHashtag {
|
||||||
searchType = "Hashtags"
|
searchType = "Hashtags"
|
||||||
for _, term := range strings.Split(terms, " ") {
|
for _, term := range strings.Split(terms, " ") {
|
||||||
termMap[term] = true
|
termMap[term] = true
|
||||||
@@ -430,63 +438,85 @@ func (s SqlPostStore) Search(teamId string, userId string, terms string, isHasht
|
|||||||
var posts []*model.Post
|
var posts []*model.Post
|
||||||
|
|
||||||
if utils.Cfg.SqlSettings.DriverName == model.DATABASE_DRIVER_POSTGRES {
|
if utils.Cfg.SqlSettings.DriverName == model.DATABASE_DRIVER_POSTGRES {
|
||||||
|
// Parse text for wildcards
|
||||||
|
if wildcard, err := regexp.Compile("\\*($| )"); err == nil {
|
||||||
|
terms = wildcard.ReplaceAllLiteralString(terms, ":* ")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
searchQuery := `
|
||||||
|
SELECT
|
||||||
|
*
|
||||||
|
FROM
|
||||||
|
Posts
|
||||||
|
WHERE
|
||||||
|
DeleteAt = 0
|
||||||
|
POST_FILTER
|
||||||
|
AND ChannelId IN (
|
||||||
|
SELECT
|
||||||
|
Id
|
||||||
|
FROM
|
||||||
|
Channels,
|
||||||
|
ChannelMembers
|
||||||
|
WHERE
|
||||||
|
Id = ChannelId
|
||||||
|
AND TeamId = :TeamId
|
||||||
|
AND UserId = :UserId
|
||||||
|
AND DeleteAt = 0
|
||||||
|
CHANNEL_FILTER)
|
||||||
|
SEARCH_CLAUSE
|
||||||
|
ORDER BY CreateAt DESC
|
||||||
|
LIMIT 100`
|
||||||
|
|
||||||
|
if params.InChannel != "" {
|
||||||
|
searchQuery = strings.Replace(searchQuery, "CHANNEL_FILTER", "AND Name = :InChannel", 1)
|
||||||
|
} else {
|
||||||
|
searchQuery = strings.Replace(searchQuery, "CHANNEL_FILTER", "", 1)
|
||||||
|
}
|
||||||
|
|
||||||
|
if params.FromUser != "" {
|
||||||
|
searchQuery = strings.Replace(searchQuery, "POST_FILTER", `
|
||||||
|
AND UserId IN (
|
||||||
|
SELECT
|
||||||
|
Id
|
||||||
|
FROM
|
||||||
|
Users
|
||||||
|
WHERE
|
||||||
|
TeamId = :TeamId
|
||||||
|
AND Username = :FromUser)`, 1)
|
||||||
|
} else {
|
||||||
|
searchQuery = strings.Replace(searchQuery, "POST_FILTER", "", 1)
|
||||||
|
}
|
||||||
|
|
||||||
|
if terms == "" {
|
||||||
|
// we've already confirmed that we have a channel or user to search for
|
||||||
|
searchQuery = strings.Replace(searchQuery, "SEARCH_CLAUSE", "", 1)
|
||||||
|
} else if utils.Cfg.SqlSettings.DriverName == model.DATABASE_DRIVER_POSTGRES {
|
||||||
// Parse text for wildcards
|
// Parse text for wildcards
|
||||||
if wildcard, err := regexp.Compile("\\*($| )"); err == nil {
|
if wildcard, err := regexp.Compile("\\*($| )"); err == nil {
|
||||||
terms = wildcard.ReplaceAllLiteralString(terms, ":* ")
|
terms = wildcard.ReplaceAllLiteralString(terms, ":* ")
|
||||||
}
|
}
|
||||||
|
|
||||||
searchQuery := fmt.Sprintf(`SELECT
|
|
||||||
*
|
|
||||||
FROM
|
|
||||||
Posts
|
|
||||||
WHERE
|
|
||||||
DeleteAt = 0
|
|
||||||
AND ChannelId IN (SELECT
|
|
||||||
Id
|
|
||||||
FROM
|
|
||||||
Channels,
|
|
||||||
ChannelMembers
|
|
||||||
WHERE
|
|
||||||
Id = ChannelId AND TeamId = $1
|
|
||||||
AND UserId = $2
|
|
||||||
AND DeleteAt = 0)
|
|
||||||
AND %s @@ to_tsquery($3)
|
|
||||||
ORDER BY CreateAt DESC
|
|
||||||
LIMIT 100`, searchType)
|
|
||||||
|
|
||||||
terms = strings.Join(strings.Fields(terms), " | ")
|
terms = strings.Join(strings.Fields(terms), " | ")
|
||||||
|
|
||||||
_, err := s.GetReplica().Select(&posts, searchQuery, teamId, userId, terms)
|
searchClause := fmt.Sprintf("AND %s @@ to_tsquery(:Terms)", searchType)
|
||||||
if err != nil {
|
searchQuery = strings.Replace(searchQuery, "SEARCH_CLAUSE", searchClause, 1)
|
||||||
result.Err = model.NewAppError("SqlPostStore.Search", "We encounted an error while searching for posts", "teamId="+teamId+", err="+err.Error())
|
|
||||||
|
|
||||||
}
|
|
||||||
} else if utils.Cfg.SqlSettings.DriverName == model.DATABASE_DRIVER_MYSQL {
|
} else if utils.Cfg.SqlSettings.DriverName == model.DATABASE_DRIVER_MYSQL {
|
||||||
searchQuery := fmt.Sprintf(`SELECT
|
searchClause := fmt.Sprintf("AND MATCH (%s) AGAINST (:Terms IN BOOLEAN MODE)", searchType)
|
||||||
*
|
searchQuery = strings.Replace(searchQuery, "SEARCH_CLAUSE", searchClause, 1)
|
||||||
FROM
|
}
|
||||||
Posts
|
|
||||||
WHERE
|
|
||||||
DeleteAt = 0
|
|
||||||
AND ChannelId IN (SELECT
|
|
||||||
Id
|
|
||||||
FROM
|
|
||||||
Channels,
|
|
||||||
ChannelMembers
|
|
||||||
WHERE
|
|
||||||
Id = ChannelId AND TeamId = ?
|
|
||||||
AND UserId = ?
|
|
||||||
AND DeleteAt = 0)
|
|
||||||
AND MATCH (%s) AGAINST (? IN BOOLEAN MODE)
|
|
||||||
ORDER BY CreateAt DESC
|
|
||||||
LIMIT 100`, searchType)
|
|
||||||
|
|
||||||
_, err := s.GetReplica().Select(&posts, searchQuery, teamId, userId, terms)
|
queryParams := map[string]interface{}{
|
||||||
|
"TeamId": teamId,
|
||||||
|
"UserId": userId,
|
||||||
|
"Terms": terms,
|
||||||
|
"InChannel": params.InChannel,
|
||||||
|
"FromUser": params.FromUser,
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err := s.GetReplica().Select(&posts, searchQuery, queryParams)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
result.Err = model.NewAppError("SqlPostStore.Search", "We encounted an error while searching for posts", "teamId="+teamId+", err="+err.Error())
|
result.Err = model.NewAppError("SqlPostStore.Search", "We encounted an error while searching for posts", "teamId="+teamId+", err="+err.Error())
|
||||||
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
list := &model.PostList{Order: make([]string, 0, len(posts))}
|
list := &model.PostList{Order: make([]string, 0, len(posts))}
|
||||||
|
|||||||
@@ -525,57 +525,57 @@ func TestPostStoreSearch(t *testing.T) {
|
|||||||
o5.Hashtags = "#secret #howdy"
|
o5.Hashtags = "#secret #howdy"
|
||||||
o5 = (<-store.Post().Save(o5)).Data.(*model.Post)
|
o5 = (<-store.Post().Save(o5)).Data.(*model.Post)
|
||||||
|
|
||||||
r1 := (<-store.Post().Search(teamId, userId, "corey", false)).Data.(*model.PostList)
|
r1 := (<-store.Post().Search(teamId, userId, &model.SearchParams{Terms: "corey", IsHashtag: false})).Data.(*model.PostList)
|
||||||
if len(r1.Order) != 1 && r1.Order[0] != o1.Id {
|
if len(r1.Order) != 1 && r1.Order[0] != o1.Id {
|
||||||
t.Fatal("returned wrong search result")
|
t.Fatal("returned wrong search result")
|
||||||
}
|
}
|
||||||
|
|
||||||
r3 := (<-store.Post().Search(teamId, userId, "new", false)).Data.(*model.PostList)
|
r3 := (<-store.Post().Search(teamId, userId, &model.SearchParams{Terms: "new", IsHashtag: false})).Data.(*model.PostList)
|
||||||
if len(r3.Order) != 2 && r3.Order[0] != o1.Id {
|
if len(r3.Order) != 2 && r3.Order[0] != o1.Id {
|
||||||
t.Fatal("returned wrong search result")
|
t.Fatal("returned wrong search result")
|
||||||
}
|
}
|
||||||
|
|
||||||
r4 := (<-store.Post().Search(teamId, userId, "john", false)).Data.(*model.PostList)
|
r4 := (<-store.Post().Search(teamId, userId, &model.SearchParams{Terms: "john", IsHashtag: false})).Data.(*model.PostList)
|
||||||
if len(r4.Order) != 1 && r4.Order[0] != o2.Id {
|
if len(r4.Order) != 1 && r4.Order[0] != o2.Id {
|
||||||
t.Fatal("returned wrong search result")
|
t.Fatal("returned wrong search result")
|
||||||
}
|
}
|
||||||
|
|
||||||
r5 := (<-store.Post().Search(teamId, userId, "matter*", false)).Data.(*model.PostList)
|
r5 := (<-store.Post().Search(teamId, userId, &model.SearchParams{Terms: "matter*", IsHashtag: false})).Data.(*model.PostList)
|
||||||
if len(r5.Order) != 1 && r5.Order[0] != o1.Id {
|
if len(r5.Order) != 1 && r5.Order[0] != o1.Id {
|
||||||
t.Fatal("returned wrong search result")
|
t.Fatal("returned wrong search result")
|
||||||
}
|
}
|
||||||
|
|
||||||
r6 := (<-store.Post().Search(teamId, userId, "#hashtag", true)).Data.(*model.PostList)
|
r6 := (<-store.Post().Search(teamId, userId, &model.SearchParams{Terms: "#hashtag", IsHashtag: true})).Data.(*model.PostList)
|
||||||
if len(r6.Order) != 1 && r6.Order[0] != o4.Id {
|
if len(r6.Order) != 1 && r6.Order[0] != o4.Id {
|
||||||
t.Fatal("returned wrong search result")
|
t.Fatal("returned wrong search result")
|
||||||
}
|
}
|
||||||
|
|
||||||
r7 := (<-store.Post().Search(teamId, userId, "#secret", true)).Data.(*model.PostList)
|
r7 := (<-store.Post().Search(teamId, userId, &model.SearchParams{Terms: "#secret", IsHashtag: true})).Data.(*model.PostList)
|
||||||
if len(r7.Order) != 1 && r7.Order[0] != o5.Id {
|
if len(r7.Order) != 1 && r7.Order[0] != o5.Id {
|
||||||
t.Fatal("returned wrong search result")
|
t.Fatal("returned wrong search result")
|
||||||
}
|
}
|
||||||
|
|
||||||
r8 := (<-store.Post().Search(teamId, userId, "@thisshouldmatchnothing", true)).Data.(*model.PostList)
|
r8 := (<-store.Post().Search(teamId, userId, &model.SearchParams{Terms: "@thisshouldmatchnothing", IsHashtag: true})).Data.(*model.PostList)
|
||||||
if len(r8.Order) != 0 {
|
if len(r8.Order) != 0 {
|
||||||
t.Fatal("returned wrong search result")
|
t.Fatal("returned wrong search result")
|
||||||
}
|
}
|
||||||
|
|
||||||
r9 := (<-store.Post().Search(teamId, userId, "mattermost jersey", false)).Data.(*model.PostList)
|
r9 := (<-store.Post().Search(teamId, userId, &model.SearchParams{Terms: "mattermost jersey", IsHashtag: false})).Data.(*model.PostList)
|
||||||
if len(r9.Order) != 2 {
|
if len(r9.Order) != 2 {
|
||||||
t.Fatal("returned wrong search result")
|
t.Fatal("returned wrong search result")
|
||||||
}
|
}
|
||||||
|
|
||||||
r10 := (<-store.Post().Search(teamId, userId, "matter* jer*", false)).Data.(*model.PostList)
|
r10 := (<-store.Post().Search(teamId, userId, &model.SearchParams{Terms: "matter* jer*", IsHashtag: false})).Data.(*model.PostList)
|
||||||
if len(r10.Order) != 2 {
|
if len(r10.Order) != 2 {
|
||||||
t.Fatal("returned wrong search result")
|
t.Fatal("returned wrong search result")
|
||||||
}
|
}
|
||||||
|
|
||||||
r11 := (<-store.Post().Search(teamId, userId, "message blargh", false)).Data.(*model.PostList)
|
r11 := (<-store.Post().Search(teamId, userId, &model.SearchParams{Terms: "message blargh", IsHashtag: false})).Data.(*model.PostList)
|
||||||
if len(r11.Order) != 1 {
|
if len(r11.Order) != 1 {
|
||||||
t.Fatal("returned wrong search result")
|
t.Fatal("returned wrong search result")
|
||||||
}
|
}
|
||||||
|
|
||||||
r12 := (<-store.Post().Search(teamId, userId, "blargh>", false)).Data.(*model.PostList)
|
r12 := (<-store.Post().Search(teamId, userId, &model.SearchParams{Terms: "blargh>", IsHashtag: false})).Data.(*model.PostList)
|
||||||
if len(r12.Order) != 1 {
|
if len(r12.Order) != 1 {
|
||||||
t.Fatal("returned wrong search result")
|
t.Fatal("returned wrong search result")
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -84,7 +84,7 @@ type PostStore interface {
|
|||||||
GetPosts(channelId string, offset int, limit int) StoreChannel
|
GetPosts(channelId string, offset int, limit int) StoreChannel
|
||||||
GetPostsSince(channelId string, time int64) StoreChannel
|
GetPostsSince(channelId string, time int64) StoreChannel
|
||||||
GetEtag(channelId string) StoreChannel
|
GetEtag(channelId string) StoreChannel
|
||||||
Search(teamId string, userId string, terms string, isHashtagSearch bool) StoreChannel
|
Search(teamId string, userId string, params *model.SearchParams) StoreChannel
|
||||||
GetForExport(channelId string) StoreChannel
|
GetForExport(channelId string) StoreChannel
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Ссылка в новой задаче
Block a user