Merge branch 'master' into MM-45118_my_top_dms
Этот коммит содержится в:
@@ -784,6 +784,13 @@ func (s *SqlPostStore) Get(ctx context.Context, id string, opts model.GetPostsOp
|
||||
}
|
||||
|
||||
for _, p := range posts {
|
||||
if p.Id == id {
|
||||
// Based on the conditions above such as sq.Or{ sq.Eq{"p.Id": rootId}, sq.Eq{"p.RootId": rootId}, }
|
||||
// posts may contain the "id" post which has already been fetched and added in the "pl"
|
||||
// So, skip the "id" to avoid duplicate entry of the post
|
||||
continue
|
||||
}
|
||||
|
||||
pl.AddPost(p)
|
||||
pl.AddOrder(p.Id)
|
||||
}
|
||||
@@ -2188,7 +2195,7 @@ func (s *SqlPostStore) AnalyticsPostCountsByDay(options *model.AnalyticsPostCoun
|
||||
|
||||
func (s *SqlPostStore) AnalyticsPostCount(options *model.PostCountOptions) (int64, error) {
|
||||
query := s.getQueryBuilder().
|
||||
Select("COUNT(p.Id) AS Value").
|
||||
Select("COUNT(*) AS Value").
|
||||
From("Posts p")
|
||||
|
||||
if options.TeamId != "" {
|
||||
@@ -2840,8 +2847,23 @@ func (s *SqlPostStore) updateThreadAfterReplyDeletion(transaction *sqlxTxWrapper
|
||||
}
|
||||
}
|
||||
|
||||
lastReplyAtSubquery := sq.Select("COALESCE(MAX(CreateAt), 0)").
|
||||
From("Posts").
|
||||
Where(sq.Eq{
|
||||
"RootId": rootId,
|
||||
"DeleteAt": 0,
|
||||
})
|
||||
|
||||
lastReplyCountSubquery := sq.Select("Count(*)").
|
||||
From("Posts").
|
||||
Where(sq.Eq{
|
||||
"RootId": rootId,
|
||||
"DeleteAt": 0,
|
||||
})
|
||||
|
||||
updateQueryString, updateArgs, err := updateQuery.
|
||||
Set("ReplyCount", sq.Expr("ReplyCount - 1")).
|
||||
Set("LastReplyAt", lastReplyAtSubquery).
|
||||
Set("ReplyCount", lastReplyCountSubquery).
|
||||
Where(sq.And{
|
||||
sq.Eq{"PostId": rootId},
|
||||
sq.Gt{"ReplyCount": 0},
|
||||
|
||||
@@ -1500,17 +1500,20 @@ func generateSearchQuery(query sq.SelectBuilder, terms []string, fields []string
|
||||
for _, term := range terms {
|
||||
searchFields := []string{}
|
||||
termArgs := []any{}
|
||||
var dbSpecificTerm string
|
||||
|
||||
for _, field := range fields {
|
||||
if isPostgreSQL {
|
||||
searchFields = append(searchFields, fmt.Sprintf("lower(%s) LIKE lower(?) escape '*' ", field))
|
||||
searchFields = append(searchFields, fmt.Sprintf("to_tsvector(lower(%[1]s)) @@ to_tsquery(concat(lower(?),':*'))", field))
|
||||
dbSpecificTerm = strings.TrimLeft(term, "@")
|
||||
} else {
|
||||
searchFields = append(searchFields, fmt.Sprintf("%s LIKE ? escape '*' ", field))
|
||||
dbSpecificTerm = fmt.Sprintf("%s%%", strings.TrimLeft(term, "@"))
|
||||
}
|
||||
termArgs = append(termArgs, fmt.Sprintf("%s%%", strings.TrimLeft(term, "@")))
|
||||
termArgs = append(termArgs, dbSpecificTerm)
|
||||
}
|
||||
query = query.Where(fmt.Sprintf("(%s)", strings.Join(searchFields, " OR ")), termArgs...)
|
||||
}
|
||||
|
||||
return query
|
||||
}
|
||||
|
||||
|
||||
@@ -569,6 +569,7 @@ func testPostStoreGetForThread(t *testing.T, ss store.Store) {
|
||||
})
|
||||
|
||||
t.Run("Pagination", func(t *testing.T) {
|
||||
t.Skip("MM-46134")
|
||||
o1, err := ss.Post().Save(&model.Post{ChannelId: model.NewId(), UserId: model.NewId(), Message: NewTestId()})
|
||||
require.NoError(t, err)
|
||||
_, err = ss.Post().Save(&model.Post{ChannelId: o1.ChannelId, UserId: model.NewId(), Message: NewTestId(), RootId: o1.Id})
|
||||
@@ -644,7 +645,7 @@ func testPostStoreGetForThread(t *testing.T, ss store.Store) {
|
||||
}
|
||||
r1, err = ss.Post().Get(context.Background(), o1.Id, opts, o1.UserId, map[string]bool{})
|
||||
require.NoError(t, err)
|
||||
assert.Len(t, r1.Order, 3) // including the root post
|
||||
assert.Len(t, r1.Order, 2) // including the root post
|
||||
assert.True(t, r1.HasNext)
|
||||
|
||||
lastPostID = r1.Order[len(r1.Order)-1]
|
||||
@@ -676,7 +677,7 @@ func testPostStoreGetForThread(t *testing.T, ss store.Store) {
|
||||
}
|
||||
r1, err = ss.Post().Get(context.Background(), o1.Id, opts, o1.UserId, map[string]bool{})
|
||||
require.NoError(t, err)
|
||||
assert.Len(t, r1.Order, 3) // including the root post
|
||||
assert.Len(t, r1.Order, 2) // including the root post
|
||||
assert.LessOrEqual(t, r1.Posts[r1.Order[1]].CreateAt, firstPostCreateAt)
|
||||
assert.False(t, r1.HasNext)
|
||||
|
||||
@@ -980,6 +981,74 @@ func testPostStoreDelete(t *testing.T, ss store.Store) {
|
||||
_, err = ss.Post().Get(context.Background(), rootPost2.Id, model.GetPostsOptions{}, "", map[string]bool{})
|
||||
require.NoError(t, err)
|
||||
})
|
||||
|
||||
t.Run("thread with multiple replies, update thread last reply at", func(t *testing.T) {
|
||||
// Create a root post
|
||||
rootPost1, err := ss.Post().Save(&model.Post{
|
||||
ChannelId: model.NewId(),
|
||||
UserId: model.NewId(),
|
||||
Message: NewTestId(),
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
// Reply to that root post
|
||||
replyPost1, err := ss.Post().Save(&model.Post{
|
||||
ChannelId: rootPost1.ChannelId,
|
||||
UserId: model.NewId(),
|
||||
Message: NewTestId(),
|
||||
RootId: rootPost1.Id,
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
// Reply to that root post a second time
|
||||
replyPost2, err := ss.Post().Save(&model.Post{
|
||||
ChannelId: rootPost1.ChannelId,
|
||||
UserId: model.NewId(),
|
||||
Message: NewTestId(),
|
||||
RootId: rootPost1.Id,
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
// Reply to that root post a third time
|
||||
replyPost3, err := ss.Post().Save(&model.Post{
|
||||
ChannelId: rootPost1.ChannelId,
|
||||
UserId: model.NewId(),
|
||||
Message: NewTestId(),
|
||||
RootId: rootPost1.Id,
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
thread, err := ss.Thread().Get(rootPost1.Id)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, replyPost3.CreateAt, thread.LastReplyAt)
|
||||
|
||||
// Delete the reply previous to last
|
||||
err = ss.Post().Delete(replyPost2.Id, model.GetMillis(), "")
|
||||
require.NoError(t, err)
|
||||
|
||||
thread, err = ss.Thread().Get(rootPost1.Id)
|
||||
require.NoError(t, err)
|
||||
// last reply at should be unchanged
|
||||
require.Equal(t, replyPost3.CreateAt, thread.LastReplyAt)
|
||||
|
||||
// Delete the last reply
|
||||
err = ss.Post().Delete(replyPost3.Id, model.GetMillis(), "")
|
||||
require.NoError(t, err)
|
||||
|
||||
thread, err = ss.Thread().Get(rootPost1.Id)
|
||||
require.NoError(t, err)
|
||||
// last reply at should have changed
|
||||
require.Equal(t, replyPost1.CreateAt, thread.LastReplyAt)
|
||||
|
||||
// Delete the last reply
|
||||
err = ss.Post().Delete(replyPost1.Id, model.GetMillis(), "")
|
||||
require.NoError(t, err)
|
||||
|
||||
thread, err = ss.Thread().Get(rootPost1.Id)
|
||||
require.NoError(t, err)
|
||||
// last reply at should be 0
|
||||
require.Equal(t, int64(0), thread.LastReplyAt)
|
||||
})
|
||||
}
|
||||
|
||||
func testPostStorePermDelete1Level(t *testing.T, ss store.Store) {
|
||||
|
||||
@@ -94,6 +94,7 @@ func TestUserStore(t *testing.T, ss store.Store, s SqlStore) {
|
||||
t.Run("ResetLastPictureUpdate", func(t *testing.T) { testUserStoreResetLastPictureUpdate(t, ss) })
|
||||
t.Run("GetKnownUsers", func(t *testing.T) { testGetKnownUsers(t, ss) })
|
||||
t.Run("GetUsersWithInvalidEmails", func(t *testing.T) { testGetUsersWithInvalidEmails(t, ss) })
|
||||
t.Run("SearchMultilingual", func(t *testing.T) { testUserStoreSearchUsersMultilingual(t, ss, s) })
|
||||
}
|
||||
|
||||
func testUserStoreSave(t *testing.T, ss store.Store) {
|
||||
@@ -6010,3 +6011,174 @@ func testGetUsersWithInvalidEmails(t *testing.T, ss store.Store) {
|
||||
require.NoError(t, err)
|
||||
assert.Len(t, users, 1)
|
||||
}
|
||||
|
||||
func testUserStoreSearchUsersMultilingual(t *testing.T, ss store.Store, s SqlStore) {
|
||||
u1 := &model.User{
|
||||
Username: "test1" + model.NewId(),
|
||||
FirstName: "Inígo",
|
||||
LastName: "Martínez",
|
||||
Nickname: "Berridi",
|
||||
Email: MakeEmail(),
|
||||
}
|
||||
_, err := ss.User().Save(u1)
|
||||
require.NoError(t, err)
|
||||
defer func() { require.NoError(t, ss.User().PermanentDelete(u1.Id)) }()
|
||||
|
||||
u2 := &model.User{
|
||||
Username: "test2" + model.NewId(),
|
||||
FirstName: "Zinëdìne",
|
||||
LastName: "Zidanë",
|
||||
Email: MakeEmail(),
|
||||
}
|
||||
_, err = ss.User().Save(u2)
|
||||
require.NoError(t, err)
|
||||
defer func() { require.NoError(t, ss.User().PermanentDelete(u2.Id)) }()
|
||||
|
||||
u3 := &model.User{
|
||||
Username: "test3" + model.NewId(),
|
||||
FirstName: "Thomas ",
|
||||
LastName: "Müller",
|
||||
Nickname: "Fußballspieler",
|
||||
Email: MakeEmail(),
|
||||
}
|
||||
_, err = ss.User().Save(u3)
|
||||
require.NoError(t, err)
|
||||
defer func() { require.NoError(t, ss.User().PermanentDelete(u3.Id)) }()
|
||||
|
||||
u4 := &model.User{
|
||||
Username: "test4" + model.NewId(),
|
||||
FirstName: "Jérémie ",
|
||||
LastName: "Jéry",
|
||||
Email: MakeEmail(),
|
||||
}
|
||||
_, err = ss.User().Save(u4)
|
||||
require.NoError(t, err)
|
||||
defer func() { require.NoError(t, ss.User().PermanentDelete(u4.Id)) }()
|
||||
|
||||
// The users returned from the database will have AuthData as an empty string.
|
||||
nilAuthData := new(string)
|
||||
*nilAuthData = ""
|
||||
u1.AuthData = nilAuthData
|
||||
u2.AuthData = nilAuthData
|
||||
u3.AuthData = nilAuthData
|
||||
u4.AuthData = nilAuthData
|
||||
|
||||
testCases := []struct {
|
||||
Description string
|
||||
Term string
|
||||
Options *model.UserSearchOptions
|
||||
ExpectedPostgres []*model.User
|
||||
ExpectedMysql []*model.User
|
||||
Language string
|
||||
}{
|
||||
{
|
||||
"search test1 player",
|
||||
"inig",
|
||||
&model.UserSearchOptions{
|
||||
AllowFullNames: true,
|
||||
Limit: model.UserSearchDefaultLimit,
|
||||
},
|
||||
[]*model.User{u1},
|
||||
[]*model.User{u1},
|
||||
"spanish",
|
||||
},
|
||||
{
|
||||
"search test2 player",
|
||||
"zine",
|
||||
&model.UserSearchOptions{
|
||||
AllowFullNames: true,
|
||||
Limit: model.UserSearchDefaultLimit,
|
||||
},
|
||||
[]*model.User{u2},
|
||||
[]*model.User{u2},
|
||||
"french",
|
||||
},
|
||||
{
|
||||
"search test2 player",
|
||||
"zidane",
|
||||
&model.UserSearchOptions{
|
||||
AllowFullNames: true,
|
||||
Limit: model.UserSearchDefaultLimit,
|
||||
},
|
||||
[]*model.User{u2},
|
||||
[]*model.User{u2},
|
||||
"french",
|
||||
},
|
||||
{
|
||||
"search test3 player",
|
||||
"muller",
|
||||
&model.UserSearchOptions{
|
||||
AllowFullNames: true,
|
||||
Limit: model.UserSearchDefaultLimit,
|
||||
},
|
||||
[]*model.User{u3},
|
||||
[]*model.User{u3},
|
||||
"german",
|
||||
},
|
||||
{
|
||||
"search test3 player",
|
||||
"muller",
|
||||
&model.UserSearchOptions{
|
||||
AllowFullNames: true,
|
||||
Limit: model.UserSearchDefaultLimit,
|
||||
},
|
||||
[]*model.User{},
|
||||
[]*model.User{u3},
|
||||
"english",
|
||||
},
|
||||
{
|
||||
"search test4 player",
|
||||
"jere",
|
||||
&model.UserSearchOptions{
|
||||
AllowFullNames: true,
|
||||
Limit: model.UserSearchDefaultLimit,
|
||||
},
|
||||
[]*model.User{u4},
|
||||
[]*model.User{u4},
|
||||
"spanish",
|
||||
},
|
||||
{
|
||||
"search test4 player",
|
||||
"jere",
|
||||
&model.UserSearchOptions{
|
||||
AllowFullNames: true,
|
||||
Limit: model.UserSearchDefaultLimit,
|
||||
},
|
||||
[]*model.User{},
|
||||
[]*model.User{u4},
|
||||
"english",
|
||||
},
|
||||
}
|
||||
|
||||
var initialDefaultTextSearchConfig string
|
||||
if s.DriverName() == model.DatabaseDriverPostgres {
|
||||
error := s.GetMasterX().Get(&initialDefaultTextSearchConfig, `SHOW default_text_search_config`)
|
||||
require.NoError(t, error)
|
||||
}
|
||||
|
||||
for _, testCase := range testCases {
|
||||
if s.DriverName() == model.DatabaseDriverPostgres {
|
||||
_, error := s.GetMasterX().Exec("SET default_text_search_config TO '" + testCase.Language + "'")
|
||||
require.NoError(t, error)
|
||||
}
|
||||
t.Run(testCase.Description, func(t *testing.T) {
|
||||
users, err := ss.User().SearchWithoutTeam(
|
||||
testCase.Term,
|
||||
testCase.Options,
|
||||
)
|
||||
|
||||
if s.DriverName() != model.DatabaseDriverPostgres {
|
||||
require.NoError(t, err)
|
||||
assertUsers(t, testCase.ExpectedMysql, users)
|
||||
} else {
|
||||
require.NoError(t, err)
|
||||
assertUsers(t, testCase.ExpectedPostgres, users)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
if s.DriverName() == model.DatabaseDriverPostgres {
|
||||
_, error := s.GetMasterX().Exec("SET default_text_search_config TO '" + initialDefaultTextSearchConfig + "'")
|
||||
require.NoError(t, error)
|
||||
}
|
||||
}
|
||||
|
||||
Ссылка в новой задаче
Block a user