[MM-42191]: Include deleted posts (#19985)
* MM-42191: Include deleted posts: Add includeDeleted query parameter for getPostsForChannel * MM-42191: Fix error typo for includeDeleted query parameter * MM-42191: Include deleted posts: Set permission error when deleted posts are requested by non system admins * MM-42191: Include deleted posts: Refactor replyCountSubQuery and conditions when includeDeleted is not presented, refactor getRootPosts * MM-42191: Include deleted posts: Refactor getRootPosts function along with skipFetchThreads and includeDeleted * MM-42191: Include deleted posts: Rename includeDeleted to include_deleted param * MM-42191: Include deleted posts: Fix failed posts unit tests * MM-42191: Include deleted posts: Add missing include deleted option in multiple queries * MM-42191: Include deleted posts: Add tests for include deleted option in TestGetPostsForChannel, TestGetPostsBefore, TestGetPostsAfter * MM-42191: include deleted posts: Add tests cases for post store test * MM-42191: Include deleted posts: Add extra unit test to ensure not returning deleted posts when IncludeDelete is false Co-authored-by: Mattermod <mattermod@users.noreply.github.com>
Этот коммит содержится в:
коммит произвёл
GitHub
родитель
1d8bb0605c
Коммит
b9834a2fc2
@@ -1168,13 +1168,13 @@ func (s *SqlPostStore) GetPosts(options model.GetPostsOptions, _ bool, sanitizeO
|
||||
|
||||
rpc := make(chan store.StoreResult, 1)
|
||||
go func() {
|
||||
posts, err := s.getRootPosts(options.ChannelId, offset, options.PerPage, options.SkipFetchThreads)
|
||||
posts, err := s.getRootPosts(options.ChannelId, offset, options.PerPage, options.SkipFetchThreads, options.IncludeDeleted)
|
||||
rpc <- store.StoreResult{Data: posts, NErr: err}
|
||||
close(rpc)
|
||||
}()
|
||||
cpc := make(chan store.StoreResult, 1)
|
||||
go func() {
|
||||
posts, err := s.getParentsPosts(options.ChannelId, offset, options.PerPage, options.SkipFetchThreads)
|
||||
posts, err := s.getParentsPosts(options.ChannelId, offset, options.PerPage, options.SkipFetchThreads, options.IncludeDeleted)
|
||||
cpc <- store.StoreResult{Data: posts, NErr: err}
|
||||
close(cpc)
|
||||
}()
|
||||
@@ -1440,13 +1440,18 @@ func (s *SqlPostStore) getPostsAround(before bool, options model.GetPostsOptions
|
||||
)
|
||||
}
|
||||
query := s.getQueryBuilder().Select(columns...)
|
||||
replyCountSubQuery := s.getQueryBuilder().Select("COUNT(*)").From("Posts").Where(sq.Expr("Posts.RootId = (CASE WHEN p.RootId = '' THEN p.Id ELSE p.RootId END) AND Posts.DeleteAt = 0"))
|
||||
replyCountSubQuery := s.getQueryBuilder().Select("COUNT(*)").From("Posts").Where(sq.Expr("Posts.RootId = (CASE WHEN p.RootId = '' THEN p.Id ELSE p.RootId END)"))
|
||||
|
||||
conditions := sq.And{
|
||||
sq.Expr(`CreateAt `+direction+` (SELECT CreateAt FROM Posts WHERE Id = ?)`, options.PostId),
|
||||
sq.Eq{"p.ChannelId": options.ChannelId},
|
||||
sq.Eq{"p.DeleteAt": int(0)},
|
||||
}
|
||||
|
||||
if !options.IncludeDeleted {
|
||||
replyCountSubQuery = replyCountSubQuery.Where(sq.Expr("Posts.DeleteAt = 0"))
|
||||
conditions = append(conditions, sq.Eq{"p.DeleteAt": int(0)})
|
||||
}
|
||||
|
||||
if options.CollapsedThreads {
|
||||
conditions = append(conditions, sq.Eq{"RootId": ""})
|
||||
query = query.LeftJoin("Threads ON Threads.PostId = p.Id").LeftJoin("ThreadMemberships ON ThreadMemberships.PostId = p.Id AND ThreadMemberships.UserId=?", options.UserId)
|
||||
@@ -1492,10 +1497,13 @@ func (s *SqlPostStore) getPostsAround(before bool, options model.GetPostsOptions
|
||||
Where(sq.And{
|
||||
idQuery,
|
||||
sq.Eq{"p.ChannelId": options.ChannelId},
|
||||
sq.Eq{"p.DeleteAt": 0},
|
||||
}).
|
||||
OrderBy("CreateAt DESC")
|
||||
|
||||
if !options.IncludeDeleted {
|
||||
rootQuery = rootQuery.Where(sq.Eq{"p.DeleteAt": 0})
|
||||
}
|
||||
|
||||
rootQueryString, rootArgs, nErr := rootQuery.ToSql()
|
||||
|
||||
if nErr != nil {
|
||||
@@ -1620,14 +1628,21 @@ func (s *SqlPostStore) GetPostAfterTime(channelId string, time int64, collapsedT
|
||||
return &post, nil
|
||||
}
|
||||
|
||||
func (s *SqlPostStore) getRootPosts(channelId string, offset int, limit int, skipFetchThreads bool) ([]*model.Post, error) {
|
||||
func (s *SqlPostStore) getRootPosts(channelId string, offset int, limit int, skipFetchThreads bool, includeDeleted bool) ([]*model.Post, error) {
|
||||
posts := []*model.Post{}
|
||||
var fetchQuery string
|
||||
if skipFetchThreads {
|
||||
fetchQuery = "SELECT p.*, (SELECT COUNT(*) FROM Posts WHERE Posts.RootId = (CASE WHEN p.RootId = '' THEN p.Id ELSE p.RootId END) AND Posts.DeleteAt = 0) as ReplyCount FROM Posts p WHERE p.ChannelId = ? AND p.DeleteAt = 0 ORDER BY p.CreateAt DESC LIMIT ? OFFSET ?"
|
||||
fetchQuery = "SELECT p.*, (SELECT COUNT(*) FROM Posts WHERE Posts.RootId = (CASE WHEN p.RootId = '' THEN p.Id ELSE p.RootId END)) as ReplyCount FROM Posts p WHERE p.ChannelId = ? ORDER BY p.CreateAt DESC LIMIT ? OFFSET ?"
|
||||
if !includeDeleted {
|
||||
fetchQuery = "SELECT p.*, (SELECT COUNT(*) FROM Posts WHERE Posts.RootId = (CASE WHEN p.RootId = '' THEN p.Id ELSE p.RootId END) AND Posts.DeleteAt = 0) as ReplyCount FROM Posts p WHERE p.ChannelId = ? AND p.DeleteAt = 0 ORDER BY p.CreateAt DESC LIMIT ? OFFSET ?"
|
||||
}
|
||||
} else {
|
||||
fetchQuery = "SELECT * FROM Posts WHERE Posts.ChannelId = ? AND Posts.DeleteAt = 0 ORDER BY Posts.CreateAt DESC LIMIT ? OFFSET ?"
|
||||
fetchQuery = "SELECT * FROM Posts WHERE Posts.ChannelId = ? ORDER BY Posts.CreateAt DESC LIMIT ? OFFSET ?"
|
||||
if !includeDeleted {
|
||||
fetchQuery = "SELECT * FROM Posts WHERE Posts.ChannelId = ? AND Posts.DeleteAt = 0 ORDER BY Posts.CreateAt DESC LIMIT ? OFFSET ?"
|
||||
}
|
||||
}
|
||||
|
||||
err := s.GetReplicaX().Select(&posts, fetchQuery, channelId, limit, offset)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "failed to find Posts")
|
||||
@@ -1635,9 +1650,14 @@ func (s *SqlPostStore) getRootPosts(channelId string, offset int, limit int, ski
|
||||
return posts, nil
|
||||
}
|
||||
|
||||
func (s *SqlPostStore) getParentsPosts(channelId string, offset int, limit int, skipFetchThreads bool) ([]*model.Post, error) {
|
||||
func (s *SqlPostStore) getParentsPosts(channelId string, offset int, limit int, skipFetchThreads bool, includeDeleted bool) ([]*model.Post, error) {
|
||||
if s.DriverName() == model.DatabaseDriverPostgres {
|
||||
return s.getParentsPostsPostgreSQL(channelId, offset, limit, skipFetchThreads)
|
||||
return s.getParentsPostsPostgreSQL(channelId, offset, limit, skipFetchThreads, includeDeleted)
|
||||
}
|
||||
|
||||
deleteAtCondition := "AND DeleteAt = 0"
|
||||
if includeDeleted {
|
||||
deleteAtCondition = ""
|
||||
}
|
||||
|
||||
// query parent Ids first
|
||||
@@ -1651,9 +1671,8 @@ func (s *SqlPostStore) getParentsPosts(channelId string, offset int, limit int,
|
||||
FROM
|
||||
Posts
|
||||
WHERE
|
||||
Posts.ChannelId = ?
|
||||
AND Posts.DeleteAt = 0
|
||||
ORDER BY Posts.CreateAt DESC
|
||||
ChannelId = ? ` + deleteAtCondition + `
|
||||
ORDER BY CreateAt DESC
|
||||
LIMIT ? OFFSET ?) q
|
||||
WHERE q.RootId != ''`
|
||||
|
||||
@@ -1669,7 +1688,11 @@ func (s *SqlPostStore) getParentsPosts(channelId string, offset int, limit int,
|
||||
var where sq.Sqlizer
|
||||
where = sq.Eq{"p.Id": roots}
|
||||
if skipFetchThreads {
|
||||
cols = append(cols, "(SELECT COUNT(*) FROM Posts WHERE Posts.RootId = (CASE WHEN p.RootId = '' THEN p.Id ELSE p.RootId END) AND Posts.DeleteAt = 0) as ReplyCount")
|
||||
col := "(SELECT COUNT(*) FROM Posts WHERE Posts.RootId = (CASE WHEN p.RootId = '' THEN p.Id ELSE p.RootId END)) as ReplyCount"
|
||||
if !includeDeleted {
|
||||
col = "(SELECT COUNT(*) FROM Posts WHERE Posts.RootId = (CASE WHEN p.RootId = '' THEN p.Id ELSE p.RootId END) AND Posts.DeleteAt = 0) as ReplyCount"
|
||||
}
|
||||
cols = append(cols, col)
|
||||
} else {
|
||||
where = sq.Or{
|
||||
where,
|
||||
@@ -1683,10 +1706,13 @@ func (s *SqlPostStore) getParentsPosts(channelId string, offset int, limit int,
|
||||
Where(sq.And{
|
||||
where,
|
||||
sq.Eq{"p.ChannelId": channelId},
|
||||
sq.Eq{"p.DeleteAt": 0},
|
||||
}).
|
||||
OrderBy("p.CreateAt")
|
||||
|
||||
if !includeDeleted {
|
||||
query = query.Where(sq.Eq{"p.DeleteAt": 0})
|
||||
}
|
||||
|
||||
sql, args, err := query.ToSql()
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "ParentPosts_Tosql")
|
||||
@@ -1700,15 +1726,25 @@ func (s *SqlPostStore) getParentsPosts(channelId string, offset int, limit int,
|
||||
return posts, nil
|
||||
}
|
||||
|
||||
func (s *SqlPostStore) getParentsPostsPostgreSQL(channelId string, offset int, limit int, skipFetchThreads bool) ([]*model.Post, error) {
|
||||
func (s *SqlPostStore) getParentsPostsPostgreSQL(channelId string, offset int, limit int, skipFetchThreads bool, includeDeleted bool) ([]*model.Post, error) {
|
||||
posts := []*model.Post{}
|
||||
replyCountQuery := ""
|
||||
onStatement := "q1.RootId = q2.Id"
|
||||
if skipFetchThreads {
|
||||
replyCountQuery = ` ,(SELECT COUNT(*) FROM Posts WHERE Posts.RootId = (CASE WHEN q2.RootId = '' THEN q2.Id ELSE q2.RootId END) AND Posts.DeleteAt = 0) as ReplyCount`
|
||||
replyCountQuery = ` ,(SELECT COUNT(*) FROM Posts WHERE Posts.RootId = (CASE WHEN q2.RootId = '' THEN q2.Id ELSE q2.RootId END)) as ReplyCount`
|
||||
if !includeDeleted {
|
||||
replyCountQuery = ` ,(SELECT COUNT(*) FROM Posts WHERE Posts.RootId = (CASE WHEN q2.RootId = '' THEN q2.Id ELSE q2.RootId END) AND Posts.DeleteAt = 0) as ReplyCount`
|
||||
}
|
||||
} else {
|
||||
onStatement += " OR q1.RootId = q2.RootId"
|
||||
}
|
||||
|
||||
deleteAtQueryCondition := "AND q2.DeleteAt = 0"
|
||||
deleteAtSubQueryCondition := "AND Posts.DeleteAt = 0"
|
||||
if includeDeleted {
|
||||
deleteAtQueryCondition, deleteAtSubQueryCondition = "", ""
|
||||
}
|
||||
|
||||
err := s.GetReplicaX().Select(&posts,
|
||||
`SELECT q2.*`+replyCountQuery+`
|
||||
FROM
|
||||
@@ -1722,15 +1758,13 @@ func (s *SqlPostStore) getParentsPostsPostgreSQL(channelId string, offset int, l
|
||||
FROM
|
||||
Posts
|
||||
WHERE
|
||||
Posts.ChannelId = ?
|
||||
AND Posts.DeleteAt = 0
|
||||
Posts.ChannelId = ? `+deleteAtSubQueryCondition+`
|
||||
ORDER BY Posts.CreateAt DESC
|
||||
LIMIT ? OFFSET ?) q3
|
||||
WHERE q3.RootId != '') q1
|
||||
ON `+onStatement+`
|
||||
WHERE
|
||||
q2.ChannelId = ?
|
||||
AND q2.DeleteAt = 0
|
||||
q2.ChannelId = ? `+deleteAtQueryCondition+`
|
||||
ORDER BY q2.CreateAt`, channelId, limit, offset, channelId)
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "failed to find Posts with channelId=%s", channelId)
|
||||
|
||||
@@ -1946,6 +1946,73 @@ func testPostStoreGetPosts(t *testing.T, ss store.Store) {
|
||||
assert.Equal(t, int64(1), postList.Posts[post5.Id].ReplyCount)
|
||||
assert.Equal(t, int64(1), postList.Posts[post6.Id].ReplyCount)
|
||||
})
|
||||
|
||||
t.Run("should return all posts in a channel included deleted posts", func(t *testing.T) {
|
||||
err := ss.Post().Delete(post1.Id, 1, userId)
|
||||
require.NoError(t, err)
|
||||
|
||||
postList, err := ss.Post().GetPosts(model.GetPostsOptions{ChannelId: channelId, Page: 0, PerPage: 30, SkipFetchThreads: false, IncludeDeleted: true}, false, map[string]bool{})
|
||||
require.NoError(t, err)
|
||||
|
||||
assert.Equal(t, []string{
|
||||
post6.Id,
|
||||
post5.Id,
|
||||
post4.Id,
|
||||
post3.Id,
|
||||
post2.Id,
|
||||
post1.Id,
|
||||
}, postList.Order)
|
||||
|
||||
assert.Len(t, postList.Posts, 6)
|
||||
assert.NotNil(t, postList.Posts[post1.Id])
|
||||
assert.NotNil(t, postList.Posts[post2.Id])
|
||||
assert.NotNil(t, postList.Posts[post3.Id])
|
||||
assert.NotNil(t, postList.Posts[post4.Id])
|
||||
assert.NotNil(t, postList.Posts[post5.Id])
|
||||
assert.NotNil(t, postList.Posts[post6.Id])
|
||||
})
|
||||
|
||||
t.Run("should return all posts in a channel included deleted posts without threads", func(t *testing.T) {
|
||||
err := ss.Post().Delete(post5.Id, 1, userId)
|
||||
require.NoError(t, err)
|
||||
|
||||
postList, err := ss.Post().GetPosts(model.GetPostsOptions{ChannelId: channelId, Page: 0, PerPage: 30, SkipFetchThreads: true, IncludeDeleted: true}, false, map[string]bool{})
|
||||
require.NoError(t, err)
|
||||
|
||||
assert.Equal(t, []string{
|
||||
post6.Id,
|
||||
post5.Id,
|
||||
post4.Id,
|
||||
post3.Id,
|
||||
post2.Id,
|
||||
post1.Id,
|
||||
}, postList.Order)
|
||||
|
||||
assert.Len(t, postList.Posts, 6)
|
||||
assert.NotNil(t, postList.Posts[post5.Id])
|
||||
assert.NotNil(t, postList.Posts[post6.Id])
|
||||
assert.Equal(t, int64(1), postList.Posts[post5.Id].ReplyCount)
|
||||
assert.Equal(t, int64(1), postList.Posts[post6.Id].ReplyCount)
|
||||
})
|
||||
|
||||
t.Run("should return the lasts posts created in channel without include deleted posts", func(t *testing.T) {
|
||||
err := ss.Post().Delete(post6.Id, 1, userId)
|
||||
require.NoError(t, err)
|
||||
|
||||
postList, err := ss.Post().GetPosts(model.GetPostsOptions{ChannelId: channelId, Page: 0, PerPage: 30, SkipFetchThreads: true, IncludeDeleted: false}, false, map[string]bool{})
|
||||
require.NoError(t, err)
|
||||
|
||||
assert.Equal(t, []string{
|
||||
post4.Id,
|
||||
post3.Id,
|
||||
post2.Id,
|
||||
}, postList.Order)
|
||||
|
||||
assert.Len(t, postList.Posts, 3)
|
||||
assert.NotNil(t, postList.Posts[post2.Id])
|
||||
assert.NotNil(t, postList.Posts[post3.Id])
|
||||
assert.NotNil(t, postList.Posts[post4.Id])
|
||||
})
|
||||
}
|
||||
|
||||
func testPostStoreGetPostBeforeAfter(t *testing.T, ss store.Store) {
|
||||
|
||||
Ссылка в новой задаче
Block a user