MM-38164: Paginate the GetPostThread API (#19485)

We implement a cursor based pagination model
to page through the posts in a given thread.

The cursor is a combination of the post.CreateAt+
post.Id to differentiate multiple posts in a given
timestamp.

Some additional parameters like direction, fromPost,
fromCreateAt and perPage were introduced to implement
this.

```release-note
NONE
```
Этот коммит содержится в:
Agniva De Sarker
2022-03-24 12:51:41 +05:30
коммит произвёл GitHub
родитель ad5f57b161
Коммит c1f3827801
24 изменённых файлов: 470 добавлений и 112 удалений

Просмотреть файл

@@ -547,7 +547,7 @@ func (s *SqlPostStore) buildFlaggedPostChannelFilterClause(channelId string, que
return "AND ChannelId = ?", append(queryParams, channelId)
}
func (s *SqlPostStore) getPostWithCollapsedThreads(id, userID string, extended bool) (*model.PostList, error) {
func (s *SqlPostStore) getPostWithCollapsedThreads(id, userID string, opts model.GetPostsOptions) (*model.PostList, error) {
if id == "" {
return nil, store.NewErrInvalidInput("Post", "id", id)
}
@@ -582,12 +582,71 @@ func (s *SqlPostStore) getPostWithCollapsedThreads(id, userID string, extended b
}
posts := []*model.Post{}
err = s.GetReplicaX().Select(&posts, "SELECT * FROM Posts WHERE Posts.RootId = ? AND DeleteAt = 0", id)
query := s.getQueryBuilder().
Select("*").
From("Posts").
Where(sq.Eq{
"RootId": id,
"DeleteAt": 0,
})
var sort string
if opts.Direction != "" {
if opts.Direction == "up" {
sort = "DESC"
} else if opts.Direction == "down" {
sort = "ASC"
}
}
if sort != "" {
query = query.OrderBy("CreateAt " + sort + ", Id " + sort)
}
if opts.FromPost != "" && opts.FromCreateAt != 0 {
if opts.Direction == "down" {
query = query.Where(sq.Or{
sq.Gt{"Posts.CreateAt": opts.FromCreateAt},
sq.And{
sq.Eq{"Posts.CreateAt": opts.FromCreateAt},
sq.Gt{"Posts.Id": opts.FromPost},
},
})
} else {
query = query.Where(sq.Or{
sq.Lt{"Posts.CreateAt": opts.FromCreateAt},
sq.And{
sq.Eq{"Posts.CreateAt": opts.FromCreateAt},
sq.Lt{"Posts.Id": opts.FromPost},
},
})
}
}
if opts.PerPage != 0 {
query = query.Limit(uint64(opts.PerPage + 1))
}
sql, args, err := query.ToSql()
if err != nil {
return nil, errors.Wrap(err, "getPostWithCollapsedThreads_Tosql")
}
err = s.GetReplicaX().Select(&posts, sql, args...)
if err != nil {
return nil, errors.Wrapf(err, "failed to find Posts for thread %s", id)
}
list, err := s.prepareThreadedResponse([]*postWithExtra{&post}, extended, false)
var hasNext bool
if opts.PerPage != 0 {
if len(posts) == opts.PerPage+1 {
hasNext = true
}
}
if hasNext {
// Shave off the last item.
posts = posts[:len(posts)-1]
}
list, err := s.prepareThreadedResponse([]*postWithExtra{&post}, opts.CollapsedThreadsExtended, false)
if err != nil {
return nil, err
}
@@ -595,12 +654,14 @@ func (s *SqlPostStore) getPostWithCollapsedThreads(id, userID string, extended b
list.AddPost(p)
list.AddOrder(p.Id)
}
list.HasNext = hasNext
return list, nil
}
func (s *SqlPostStore) Get(ctx context.Context, id string, skipFetchThreads, collapsedThreads, collapsedThreadsExtended bool, userID string) (*model.PostList, error) {
if collapsedThreads {
return s.getPostWithCollapsedThreads(id, userID, collapsedThreadsExtended)
func (s *SqlPostStore) Get(ctx context.Context, id string, opts model.GetPostsOptions, userID string) (*model.PostList, error) {
if opts.CollapsedThreads {
return s.getPostWithCollapsedThreads(id, userID, opts)
}
pl := model.NewPostList()
@@ -620,7 +681,7 @@ func (s *SqlPostStore) Get(ctx context.Context, id string, skipFetchThreads, col
}
pl.AddPost(&post)
pl.AddOrder(id)
if !skipFetchThreads {
if !opts.SkipFetchThreads {
rootId := post.RootId
if rootId == "" {
@@ -631,16 +692,78 @@ func (s *SqlPostStore) Get(ctx context.Context, id string, skipFetchThreads, col
return nil, errors.Wrapf(err, "invalid rootId with value=%s", rootId)
}
query := s.getQueryBuilder().
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(sq.Or{
sq.Eq{"p.Id": rootId},
sq.Eq{"p.RootId": rootId},
}).
Where(sq.Eq{"p.DeleteAt": 0})
var sort string
if opts.Direction != "" {
if opts.Direction == "up" {
sort = "DESC"
} else if opts.Direction == "down" {
sort = "ASC"
}
}
if sort != "" {
query = query.OrderBy("CreateAt " + sort + ", Id " + sort)
}
if opts.FromPost != "" && opts.FromCreateAt != 0 {
if opts.Direction == "down" {
query = query.Where(sq.Or{
sq.Gt{"p.CreateAt": opts.FromCreateAt},
sq.And{
sq.Eq{"p.CreateAt": opts.FromCreateAt},
sq.Gt{"p.Id": opts.FromPost},
},
})
} else {
query = query.Where(sq.Or{
sq.Lt{"p.CreateAt": opts.FromCreateAt},
sq.And{
sq.Eq{"p.CreateAt": opts.FromCreateAt},
sq.Lt{"p.Id": opts.FromPost},
},
})
}
}
if opts.PerPage != 0 {
query = query.Limit(uint64(opts.PerPage + 1))
}
sql, args, err := query.ToSql()
if err != nil {
return nil, errors.Wrap(err, "Get_Tosql")
}
posts := []*model.Post{}
err = s.GetReplicaX().Select(&posts, "SELECT *, (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 (Id = ? OR RootId = ?) AND DeleteAt = 0", rootId, rootId)
err = s.GetReplicaX().Select(&posts, sql, args...)
if err != nil {
return nil, errors.Wrap(err, "failed to find Posts")
}
var hasNext bool
if opts.PerPage != 0 {
if len(posts) == opts.PerPage+1 {
hasNext = true
}
}
if hasNext {
// Shave off the last item
posts = posts[:len(posts)-1]
}
for _, p := range posts {
pl.AddPost(p)
pl.AddOrder(p.Id)
}
pl.HasNext = hasNext
}
return pl, nil
}