diff --git a/api4/post.go b/api4/post.go index d952408afa..a68ecb4dc8 100644 --- a/api4/post.go +++ b/api4/post.go @@ -13,6 +13,7 @@ import ( "github.com/mattermost/mattermost-server/v6/audit" "github.com/mattermost/mattermost-server/v6/model" "github.com/mattermost/mattermost-server/v6/shared/mlog" + "github.com/mattermost/mattermost-server/v6/web" ) func (api *API) InitPost() { @@ -503,10 +504,56 @@ func getPostThread(c *Context, w http.ResponseWriter, r *http.Request) { if c.Err != nil { return } - skipFetchThreads := r.URL.Query().Get("skipFetchThreads") == "true" - collapsedThreads := r.URL.Query().Get("collapsedThreads") == "true" - collapsedThreadsExtended := r.URL.Query().Get("collapsedThreadsExtended") == "true" - list, err := c.App.GetPostThread(c.Params.PostId, skipFetchThreads, collapsedThreads, collapsedThreadsExtended, c.AppContext.Session().UserId) + + // For now, by default we return all items unless it's set to maintain + // backwards compatibility with mobile. But when the next ESR passes, we need to + // change this to web.PerPageDefault. + perPage := 0 + if perPageStr := r.URL.Query().Get("perPage"); perPageStr != "" { + var err error + perPage, err = strconv.Atoi(perPageStr) + if err != nil || perPage > web.PerPageMaximum { + c.SetInvalidParam("perPage") + return + } + } + + var fromCreateAt int64 + if fromCreateAtStr := r.URL.Query().Get("fromCreateAt"); fromCreateAtStr != "" { + var err error + fromCreateAt, err = strconv.ParseInt(fromCreateAtStr, 10, 64) + if err != nil { + c.SetInvalidParam("fromCreateAt") + return + } + } + + fromPost := r.URL.Query().Get("fromPost") + // Either both have to be set, or none have to be set. + // Setting one and not setting the other is an error. + if (fromPost == "" && fromCreateAt != 0) || (fromPost != "" && fromCreateAt == 0) { + c.SetInvalidParam("fromPost/fromCreateAt") + return + } + + direction := "" + if dir := r.URL.Query().Get("direction"); dir != "" { + if dir != "up" && dir != "down" { + c.SetInvalidParam("direction") + return + } + direction = dir + } + opts := model.GetPostsOptions{ + SkipFetchThreads: r.URL.Query().Get("skipFetchThreads") == "true", + CollapsedThreads: r.URL.Query().Get("collapsedThreads") == "true", + CollapsedThreadsExtended: r.URL.Query().Get("collapsedThreadsExtended") == "true", + PerPage: perPage, + Direction: direction, + FromPost: fromPost, + FromCreateAt: fromCreateAt, + } + list, err := c.App.GetPostThread(c.Params.PostId, opts, c.AppContext.Session().UserId) if err != nil { c.Err = err return diff --git a/api4/post_test.go b/api4/post_test.go index b89f6511ba..6c23fcdd49 100644 --- a/api4/post_test.go +++ b/api4/post_test.go @@ -2184,6 +2184,22 @@ func TestGetPostThread(t *testing.T) { require.Error(t, err) CheckForbiddenStatus(t, resp) + // Sending some bad params + _, resp, err = client.GetPostThreadWithOpts(th.BasicPost.Id, "", model.GetPostsOptions{ + CollapsedThreads: true, + FromPost: "something", + PerPage: 10, + }) + require.Error(t, err) + CheckBadRequestStatus(t, resp) + + _, resp, err = client.GetPostThreadWithOpts(th.BasicPost.Id, "", model.GetPostsOptions{ + CollapsedThreads: true, + Direction: "sideways", + }) + require.Error(t, err) + CheckBadRequestStatus(t, resp) + client.Logout() _, resp, err = client.GetPostThread(model.NewId(), "", false) require.Error(t, err) diff --git a/app/app_iface.go b/app/app_iface.go index a7d4f31063..dd94e666d5 100644 --- a/app/app_iface.go +++ b/app/app_iface.go @@ -674,7 +674,7 @@ type AppIface interface { GetPostIdAfterTime(channelID string, time int64, collapsedThreads bool) (string, *model.AppError) GetPostIdBeforeTime(channelID string, time int64, collapsedThreads bool) (string, *model.AppError) GetPostIfAuthorized(postID string, session *model.Session) (*model.Post, *model.AppError) - GetPostThread(postID string, skipFetchThreads, collapsedThreads, collapsedThreadsExtended bool, userID string) (*model.PostList, *model.AppError) + GetPostThread(postID string, opts model.GetPostsOptions, userID string) (*model.PostList, *model.AppError) GetPosts(channelID string, offset int, limit int) (*model.PostList, *model.AppError) GetPostsAfterPost(options model.GetPostsOptions) (*model.PostList, *model.AppError) GetPostsAroundPost(before bool, options model.GetPostsOptions) (*model.PostList, *model.AppError) diff --git a/app/channel_test.go b/app/channel_test.go index 0153f723d5..7800501096 100644 --- a/app/channel_test.go +++ b/app/channel_test.go @@ -2084,7 +2084,7 @@ func TestMarkChannelAsUnreadFromPostPanic(t *testing.T) { mockPostStore := mocks.PostStore{} mockPostStore.On("GetMaxPostSize").Return(65535, nil) - mockPostStore.On("Get", context.Background(), "postID", false, false, false, "userID").Return(&model.PostList{}, nil) + mockPostStore.On("Get", context.Background(), "postID", model.GetPostsOptions{}, "userID").Return(&model.PostList{}, nil) mockPostStore.On("GetPostsAfter", mock.AnythingOfType("model.GetPostsOptions")).Return(&model.PostList{}, nil) mockPostStore.On("GetSingle", "postID", false).Return(&model.Post{ Id: "postID", diff --git a/app/file.go b/app/file.go index ccf4863575..571d6815ec 100644 --- a/app/file.go +++ b/app/file.go @@ -367,7 +367,7 @@ func (a *App) MigrateFilenamesToFileInfos(post *model.Post) []*model.FileInfo { fileMigrationLock.Lock() defer fileMigrationLock.Unlock() - result, nErr := a.Srv().Store.Post().Get(context.Background(), post.Id, false, false, false, "") + result, nErr := a.Srv().Store.Post().Get(context.Background(), post.Id, model.GetPostsOptions{}, "") if nErr != nil { mlog.Error("Unable to get post when migrating post to use FileInfos", mlog.Err(nErr), mlog.String("post_id", post.Id)) return []*model.FileInfo{} diff --git a/app/opentracing/opentracing_layer.go b/app/opentracing/opentracing_layer.go index 47fa37d670..4f8a18bd4a 100644 --- a/app/opentracing/opentracing_layer.go +++ b/app/opentracing/opentracing_layer.go @@ -7639,7 +7639,7 @@ func (a *OpenTracingAppLayer) GetPostIfAuthorized(postID string, session *model. return resultVar0, resultVar1 } -func (a *OpenTracingAppLayer) GetPostThread(postID string, skipFetchThreads bool, collapsedThreads bool, collapsedThreadsExtended bool, userID string) (*model.PostList, *model.AppError) { +func (a *OpenTracingAppLayer) GetPostThread(postID string, opts model.GetPostsOptions, userID string) (*model.PostList, *model.AppError) { origCtx := a.ctx span, newCtx := tracing.StartSpanWithParentByContext(a.ctx, "app.GetPostThread") @@ -7651,7 +7651,7 @@ func (a *OpenTracingAppLayer) GetPostThread(postID string, skipFetchThreads bool }() defer span.Finish() - resultVar0, resultVar1 := a.app.GetPostThread(postID, skipFetchThreads, collapsedThreads, collapsedThreadsExtended, userID) + resultVar0, resultVar1 := a.app.GetPostThread(postID, opts, userID) if resultVar1 != nil { span.LogFields(spanlog.Error(resultVar1)) diff --git a/app/plugin_api.go b/app/plugin_api.go index 892e2104a3..ca55060306 100644 --- a/app/plugin_api.go +++ b/app/plugin_api.go @@ -661,7 +661,7 @@ func (api *PluginAPI) DeletePost(postID string) *model.AppError { } func (api *PluginAPI) GetPostThread(postID string) (*model.PostList, *model.AppError) { - return api.app.GetPostThread(postID, false, false, false, "") + return api.app.GetPostThread(postID, model.GetPostsOptions{}, "") } func (api *PluginAPI) GetPost(postID string) (*model.Post, *model.AppError) { diff --git a/app/post.go b/app/post.go index 638009af04..7957873194 100644 --- a/app/post.go +++ b/app/post.go @@ -168,7 +168,7 @@ func (a *App) CreatePost(c *request.Context, post *model.Post, channel *model.Ch if post.RootId != "" { pchan = make(chan store.StoreResult, 1) go func() { - r, pErr := a.Srv().Store.Post().Get(sqlstore.WithMaster(context.Background()), post.RootId, false, false, false, "") + r, pErr := a.Srv().Store.Post().Get(sqlstore.WithMaster(context.Background()), post.RootId, model.GetPostsOptions{}, "") pchan <- store.StoreResult{Data: r, NErr: pErr} close(pchan) }() @@ -559,7 +559,7 @@ func (a *App) DeleteEphemeralPost(userID, postID string) { func (a *App) UpdatePost(c *request.Context, post *model.Post, safeUpdate bool) (*model.Post, *model.AppError) { post.SanitizeProps() - postLists, nErr := a.Srv().Store.Post().Get(context.Background(), post.Id, false, false, false, "") + postLists, nErr := a.Srv().Store.Post().Get(context.Background(), post.Id, model.GetPostsOptions{}, "") if nErr != nil { var nfErr *store.ErrNotFound var invErr *store.ErrInvalidInput @@ -841,8 +841,8 @@ func (a *App) GetSinglePost(postID string) (*model.Post, *model.AppError) { return post, nil } -func (a *App) GetPostThread(postID string, skipFetchThreads, collapsedThreads, collapsedThreadsExtended bool, userID string) (*model.PostList, *model.AppError) { - posts, err := a.Srv().Store.Post().Get(context.Background(), postID, skipFetchThreads, collapsedThreads, collapsedThreadsExtended, userID) +func (a *App) GetPostThread(postID string, opts model.GetPostsOptions, userID string) (*model.PostList, *model.AppError) { + posts, err := a.Srv().Store.Post().Get(context.Background(), postID, opts, userID) if err != nil { var nfErr *store.ErrNotFound var invErr *store.ErrInvalidInput @@ -887,7 +887,7 @@ func (a *App) GetFlaggedPostsForChannel(userID, channelID string, offset int, li } func (a *App) GetPermalinkPost(c *request.Context, postID string, userID string) (*model.PostList, *model.AppError) { - list, nErr := a.Srv().Store.Post().Get(context.Background(), postID, false, false, false, userID) + list, nErr := a.Srv().Store.Post().Get(context.Background(), postID, model.GetPostsOptions{}, userID) if nErr != nil { var nfErr *store.ErrNotFound var invErr *store.ErrInvalidInput @@ -1086,7 +1086,12 @@ func (a *App) GetPostsForChannelAroundLastUnread(channelID, userID string, limit return model.NewPostList(), nil } - postList, err := a.GetPostThread(lastUnreadPostId, skipFetchThreads, collapsedThreads, collapsedThreadsExtended, userID) + opts := model.GetPostsOptions{ + SkipFetchThreads: skipFetchThreads, + CollapsedThreads: collapsedThreads, + CollapsedThreadsExtended: collapsedThreadsExtended, + } + postList, err := a.GetPostThread(lastUnreadPostId, opts, userID) if err != nil { return nil, err } @@ -1533,7 +1538,7 @@ func (a *App) countMentionsFromPost(user *model.User, post *model.Post) (int, in // A mapping of thread root IDs to whether or not a post in that thread mentions the user mentionedByThread := make(map[string]bool) - thread, err := a.GetPostThread(post.Id, false, false, false, user.Id) + thread, err := a.GetPostThread(post.Id, model.GetPostsOptions{}, user.Id) if err != nil { return 0, 0, err } diff --git a/app/post_metadata.go b/app/post_metadata.go index 5c14646410..09c60c3302 100644 --- a/app/post_metadata.go +++ b/app/post_metadata.go @@ -57,6 +57,7 @@ func (a *App) PreparePostListForClient(originalList *model.PostList) *model.Post Order: originalList.Order, NextPostId: originalList.NextPostId, PrevPostId: originalList.PrevPostId, + HasNext: originalList.HasNext, } for id, originalPost := range originalList.Posts { diff --git a/model/client4.go b/model/client4.go index 000c0b4146..f0e2d2693e 100644 --- a/model/client4.go +++ b/model/client4.go @@ -3748,6 +3748,49 @@ func (c *Client4) GetPostThread(postId string, etag string, collapsedThreads boo return &list, BuildResponse(r), nil } +// GetPostThreadWithOpts gets a post with all the other posts in the same thread. +func (c *Client4) GetPostThreadWithOpts(postID string, etag string, opts GetPostsOptions) (*PostList, *Response, error) { + urlVal := c.postRoute(postID) + "/thread" + + values := url.Values{} + if opts.CollapsedThreads { + values.Set("collapsedThreads", "true") + } + if opts.CollapsedThreadsExtended { + values.Set("collapsedThreadsExtended", "true") + } + if opts.SkipFetchThreads { + values.Set("skipFetchThreads", "true") + } + if opts.PerPage != 0 { + values.Set("perPage", strconv.Itoa(opts.PerPage)) + } + if opts.FromPost != "" { + values.Set("fromPost", opts.FromPost) + } + if opts.FromCreateAt != 0 { + values.Set("fromCreateAt", strconv.FormatInt(opts.FromCreateAt, 10)) + } + if opts.Direction != "" { + values.Set("direction", opts.Direction) + } + urlVal += "?" + values.Encode() + + r, err := c.DoAPIGet(urlVal, etag) + if err != nil { + return nil, BuildResponse(r), err + } + defer closeBody(r) + var list PostList + if r.StatusCode == http.StatusNotModified { + return &list, BuildResponse(r), nil + } + if jsonErr := json.NewDecoder(r.Body).Decode(&list); jsonErr != nil { + return nil, nil, NewAppError("GetPostThread", "api.unmarshal_error", nil, jsonErr.Error(), http.StatusInternalServerError) + } + return &list, BuildResponse(r), nil +} + // GetPostsForChannel gets a page of posts with an array for ordering for a channel. func (c *Client4) GetPostsForChannel(channelId string, page, perPage int, etag string, collapsedThreads bool) (*PostList, *Response, error) { query := fmt.Sprintf("?page=%v&per_page=%v", page, perPage) diff --git a/model/post.go b/model/post.go index 8736de3a14..039f761eea 100644 --- a/model/post.go +++ b/model/post.go @@ -263,6 +263,9 @@ type GetPostsOptions struct { SkipFetchThreads bool CollapsedThreads bool CollapsedThreadsExtended bool + FromPost string // PostId after which to send the items + FromCreateAt int64 // CreateAt after which to send the items + Direction string // Only accepts up|down. Indicates the order in which to send the items. } func (o *Post) Etag() string { diff --git a/model/post_list.go b/model/post_list.go index bb28063a93..614bbf8fcb 100644 --- a/model/post_list.go +++ b/model/post_list.go @@ -14,6 +14,8 @@ type PostList struct { Posts map[string]*Post `json:"posts"` NextPostId string `json:"next_post_id"` PrevPostId string `json:"prev_post_id"` + // HasNext indicates whether there are more items to be fetched or not. + HasNext bool `json:"has_next"` } func NewPostList() *PostList { @@ -39,6 +41,7 @@ func (o *PostList) Clone() *PostList { Posts: postsCopy, NextPostId: o.NextPostId, PrevPostId: o.PrevPostId, + HasNext: o.HasNext, } } diff --git a/services/sharedchannel/permalink.go b/services/sharedchannel/permalink.go index 2a0a13dba2..e526146a51 100644 --- a/services/sharedchannel/permalink.go +++ b/services/sharedchannel/permalink.go @@ -31,7 +31,10 @@ func (scs *Service) processPermalinkToRemote(p *model.Post) string { // Extract the postID (This is simple enough not to warrant full-blown URL parsing.) lastSlash := strings.LastIndexByte(msg, '/') postID := msg[lastSlash+1:] - postList, err := scs.server.GetStore().Post().Get(context.Background(), postID, true, false, false, "") + opts := model.GetPostsOptions{ + SkipFetchThreads: true, + } + postList, err := scs.server.GetStore().Post().Get(context.Background(), postID, opts, "") if err != nil { scs.server.GetLogger().Log(mlog.LvlSharedChannelServiceWarn, "Unable to get post during replacing permalinks", mlog.Err(err)) return msg diff --git a/services/sharedchannel/permalink_test.go b/services/sharedchannel/permalink_test.go index d8640f7a99..117f23b3e6 100644 --- a/services/sharedchannel/permalink_test.go +++ b/services/sharedchannel/permalink_test.go @@ -27,7 +27,7 @@ func TestProcessPermalinkToRemote(t *testing.T) { utils.TranslationsPreInit() pl := &model.PostList{} - mockPostStore.On("Get", context.Background(), "postID", true, false, false, "").Return(pl, nil) + mockPostStore.On("Get", context.Background(), "postID", model.GetPostsOptions{SkipFetchThreads: true}, "").Return(pl, nil) mockStore.On("Post").Return(&mockPostStore) diff --git a/store/opentracinglayer/opentracinglayer.go b/store/opentracinglayer/opentracinglayer.go index 20211dbcd5..7cd9da28b6 100644 --- a/store/opentracinglayer/opentracinglayer.go +++ b/store/opentracinglayer/opentracinglayer.go @@ -5396,7 +5396,7 @@ func (s *OpenTracingLayerPostStore) DeleteOrphanedRows(limit int) (int64, error) return result, err } -func (s *OpenTracingLayerPostStore) Get(ctx context.Context, id string, skipFetchThreads bool, collapsedThreads bool, collapsedThreadsExtended bool, userID string) (*model.PostList, error) { +func (s *OpenTracingLayerPostStore) Get(ctx context.Context, id string, opts model.GetPostsOptions, userID string) (*model.PostList, error) { origCtx := s.Root.Store.Context() span, newCtx := tracing.StartSpanWithParentByContext(s.Root.Store.Context(), "PostStore.Get") s.Root.Store.SetContext(newCtx) @@ -5405,7 +5405,7 @@ func (s *OpenTracingLayerPostStore) Get(ctx context.Context, id string, skipFetc }() defer span.Finish() - result, err := s.PostStore.Get(ctx, id, skipFetchThreads, collapsedThreads, collapsedThreadsExtended, userID) + result, err := s.PostStore.Get(ctx, id, opts, userID) if err != nil { span.LogFields(spanlog.Error(err)) ext.Error.Set(span, true) diff --git a/store/retrylayer/retrylayer.go b/store/retrylayer/retrylayer.go index 5130006008..0e24bd37fc 100644 --- a/store/retrylayer/retrylayer.go +++ b/store/retrylayer/retrylayer.go @@ -6121,11 +6121,11 @@ func (s *RetryLayerPostStore) DeleteOrphanedRows(limit int) (int64, error) { } -func (s *RetryLayerPostStore) Get(ctx context.Context, id string, skipFetchThreads bool, collapsedThreads bool, collapsedThreadsExtended bool, userID string) (*model.PostList, error) { +func (s *RetryLayerPostStore) Get(ctx context.Context, id string, opts model.GetPostsOptions, userID string) (*model.PostList, error) { tries := 0 for { - result, err := s.PostStore.Get(ctx, id, skipFetchThreads, collapsedThreads, collapsedThreadsExtended, userID) + result, err := s.PostStore.Get(ctx, id, opts, userID) if err == nil { return result, nil } diff --git a/store/searchlayer/post_layer.go b/store/searchlayer/post_layer.go index 1f594cdd7b..26ca41ef68 100644 --- a/store/searchlayer/post_layer.go +++ b/store/searchlayer/post_layer.go @@ -110,7 +110,10 @@ func (s SearchPostStore) Delete(postId string, date int64, deletedByID string) e err := s.PostStore.Delete(postId, date, deletedByID) if err == nil { - postList, err2 := s.PostStore.Get(context.Background(), postId, true, false, false, "") + opts := model.GetPostsOptions{ + SkipFetchThreads: true, + } + postList, err2 := s.PostStore.Get(context.Background(), postId, opts, "") if postList != nil && len(postList.Order) > 0 { if err2 != nil { s.deletePostIndex(postList.Posts[postList.Order[0]]) diff --git a/store/sqlstore/post_store.go b/store/sqlstore/post_store.go index aaeea5246d..624da2b270 100644 --- a/store/sqlstore/post_store.go +++ b/store/sqlstore/post_store.go @@ -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 } diff --git a/store/store.go b/store/store.go index 8136639ae1..632f7c4d1b 100644 --- a/store/store.go +++ b/store/store.go @@ -322,7 +322,7 @@ type PostStore interface { SaveMultiple(posts []*model.Post) ([]*model.Post, int, error) Save(post *model.Post) (*model.Post, error) Update(newPost *model.Post, oldPost *model.Post) (*model.Post, error) - Get(ctx context.Context, id string, skipFetchThreads, collapsedThreads, collapsedThreadsExtended bool, userID string) (*model.PostList, error) + Get(ctx context.Context, id string, opts model.GetPostsOptions, userID string) (*model.PostList, error) GetSingle(id string, inclDeleted bool) (*model.Post, error) Delete(postID string, time int64, deleteByID string) error PermanentDeleteByUser(userID string) error diff --git a/store/storetest/mocks/PostStore.go b/store/storetest/mocks/PostStore.go index abfc7ef07f..94e365b4d2 100644 --- a/store/storetest/mocks/PostStore.go +++ b/store/storetest/mocks/PostStore.go @@ -123,13 +123,13 @@ func (_m *PostStore) DeleteOrphanedRows(limit int) (int64, error) { return r0, r1 } -// Get provides a mock function with given fields: ctx, id, skipFetchThreads, collapsedThreads, collapsedThreadsExtended, userID -func (_m *PostStore) Get(ctx context.Context, id string, skipFetchThreads bool, collapsedThreads bool, collapsedThreadsExtended bool, userID string) (*model.PostList, error) { - ret := _m.Called(ctx, id, skipFetchThreads, collapsedThreads, collapsedThreadsExtended, userID) +// Get provides a mock function with given fields: ctx, id, opts, userID +func (_m *PostStore) Get(ctx context.Context, id string, opts model.GetPostsOptions, userID string) (*model.PostList, error) { + ret := _m.Called(ctx, id, opts, userID) var r0 *model.PostList - if rf, ok := ret.Get(0).(func(context.Context, string, bool, bool, bool, string) *model.PostList); ok { - r0 = rf(ctx, id, skipFetchThreads, collapsedThreads, collapsedThreadsExtended, userID) + if rf, ok := ret.Get(0).(func(context.Context, string, model.GetPostsOptions, string) *model.PostList); ok { + r0 = rf(ctx, id, opts, userID) } else { if ret.Get(0) != nil { r0 = ret.Get(0).(*model.PostList) @@ -137,8 +137,8 @@ func (_m *PostStore) Get(ctx context.Context, id string, skipFetchThreads bool, } var r1 error - if rf, ok := ret.Get(1).(func(context.Context, string, bool, bool, bool, string) error); ok { - r1 = rf(ctx, id, skipFetchThreads, collapsedThreads, collapsedThreadsExtended, userID) + if rf, ok := ret.Get(1).(func(context.Context, string, model.GetPostsOptions, string) error); ok { + r1 = rf(ctx, id, opts, userID) } else { r1 = ret.Error(1) } diff --git a/store/storetest/post_store.go b/store/storetest/post_store.go index 45ddc55f9d..b24f09d9e4 100644 --- a/store/storetest/post_store.go +++ b/store/storetest/post_store.go @@ -491,14 +491,14 @@ func testPostStoreGet(t *testing.T, ss store.Store) { etag2 := ss.Post().GetEtag(o1.ChannelId, false, false) require.Equal(t, 0, strings.Index(etag2, fmt.Sprintf("%v.%v", model.CurrentVersion, o1.UpdateAt)), "Invalid Etag") - r1, err := ss.Post().Get(context.Background(), o1.Id, false, false, false, "") + r1, err := ss.Post().Get(context.Background(), o1.Id, model.GetPostsOptions{}, "") require.NoError(t, err) require.Equal(t, r1.Posts[o1.Id].CreateAt, o1.CreateAt, "invalid returned post") - _, err = ss.Post().Get(context.Background(), "123", false, false, false, "") + _, err = ss.Post().Get(context.Background(), "123", model.GetPostsOptions{}, "") require.Error(t, err, "Missing id should have failed") - _, err = ss.Post().Get(context.Background(), "", false, false, false, "") + _, err = ss.Post().Get(context.Background(), "", model.GetPostsOptions{}, "") require.Error(t, err, "should fail for blank post ids") } @@ -515,7 +515,10 @@ func testPostStoreGetForThread(t *testing.T, ss store.Store) { UpdateFollowing: true, }) require.NoError(t, err) - r1, err := ss.Post().Get(context.Background(), o1.Id, false, true, false, o1.UserId) + opts := model.GetPostsOptions{ + CollapsedThreads: true, + } + r1, err := ss.Post().Get(context.Background(), o1.Id, opts, o1.UserId) require.NoError(t, err) require.Equal(t, r1.Posts[o1.Id].CreateAt, o1.CreateAt, "invalid returned post") require.True(t, *r1.Posts[o1.Id].IsFollowing) @@ -533,7 +536,10 @@ func testPostStoreGetForThread(t *testing.T, ss store.Store) { UpdateFollowing: true, }) require.NoError(t, err) - r1, err := ss.Post().Get(context.Background(), o1.Id, false, true, false, o1.UserId) + opts := model.GetPostsOptions{ + CollapsedThreads: true, + } + r1, err := ss.Post().Get(context.Background(), o1.Id, opts, o1.UserId) require.NoError(t, err) require.Equal(t, r1.Posts[o1.Id].CreateAt, o1.CreateAt, "invalid returned post") require.False(t, *r1.Posts[o1.Id].IsFollowing) @@ -546,11 +552,113 @@ func testPostStoreGetForThread(t *testing.T, ss store.Store) { _, err = ss.Post().Save(&model.Post{ChannelId: o1.ChannelId, UserId: model.NewId(), Message: NewTestId(), RootId: o1.Id}) require.NoError(t, err) - r1, err := ss.Post().Get(context.Background(), o1.Id, false, true, false, o1.UserId) + opts := model.GetPostsOptions{ + CollapsedThreads: true, + } + r1, err := ss.Post().Get(context.Background(), o1.Id, opts, o1.UserId) require.NoError(t, err) require.Equal(t, r1.Posts[o1.Id].CreateAt, o1.CreateAt, "invalid returned post") require.Nil(t, r1.Posts[o1.Id].IsFollowing) }) + + t.Run("Pagination", func(t *testing.T) { + 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}) + require.NoError(t, err) + _, err = ss.Post().Save(&model.Post{ChannelId: o1.ChannelId, UserId: model.NewId(), Message: NewTestId(), RootId: o1.Id}) + require.NoError(t, err) + _, err = ss.Post().Save(&model.Post{ChannelId: o1.ChannelId, UserId: model.NewId(), Message: NewTestId(), RootId: o1.Id}) + require.NoError(t, err) + _, err = ss.Post().Save(&model.Post{ChannelId: o1.ChannelId, UserId: model.NewId(), Message: NewTestId(), RootId: o1.Id}) + require.NoError(t, err) + + opts := model.GetPostsOptions{ + CollapsedThreads: true, + PerPage: 2, + Direction: "down", + } + r1, err := ss.Post().Get(context.Background(), o1.Id, opts, o1.UserId) + require.NoError(t, err) + assert.Len(t, r1.Order, 3) // including the root post + assert.True(t, r1.HasNext) + + lastPostID := r1.Order[len(r1.Order)-1] + lastPostCreateAt := r1.Posts[lastPostID].CreateAt + + opts = model.GetPostsOptions{ + CollapsedThreads: true, + PerPage: 2, + Direction: "down", + FromPost: lastPostID, + FromCreateAt: lastPostCreateAt, + } + r1, err = ss.Post().Get(context.Background(), o1.Id, opts, o1.UserId) + require.NoError(t, err) + assert.Len(t, r1.Order, 3) // including the root post + assert.GreaterOrEqual(t, r1.Posts[r1.Order[len(r1.Order)-1]].CreateAt, lastPostCreateAt) + assert.False(t, r1.HasNext) + + // Going from bottom to top now. + firstPostCreateAt := r1.Posts[r1.Order[1]].CreateAt + opts = model.GetPostsOptions{ + CollapsedThreads: true, + PerPage: 2, + Direction: "up", + FromPost: r1.Order[1], + FromCreateAt: firstPostCreateAt, + } + r1, err = ss.Post().Get(context.Background(), o1.Id, opts, o1.UserId) + require.NoError(t, err) + assert.Len(t, r1.Order, 3) // including the root post + assert.LessOrEqual(t, r1.Posts[r1.Order[1]].CreateAt, firstPostCreateAt) + assert.False(t, r1.HasNext) + + // Non-CRT mode + opts = model.GetPostsOptions{ + CollapsedThreads: false, + PerPage: 2, + Direction: "down", + SkipFetchThreads: false, + } + r1, err = ss.Post().Get(context.Background(), o1.Id, opts, o1.UserId) + require.NoError(t, err) + assert.Len(t, r1.Order, 3) // including the root post + assert.True(t, r1.HasNext) + + lastPostID = r1.Order[len(r1.Order)-1] + lastPostCreateAt = r1.Posts[lastPostID].CreateAt + + opts = model.GetPostsOptions{ + CollapsedThreads: false, + PerPage: 3, + Direction: "down", + FromPost: lastPostID, + FromCreateAt: lastPostCreateAt, + SkipFetchThreads: false, + } + r1, err = ss.Post().Get(context.Background(), o1.Id, opts, o1.UserId) + require.NoError(t, err) + assert.Len(t, r1.Order, 4) // including the root post + assert.GreaterOrEqual(t, r1.Posts[r1.Order[len(r1.Order)-1]].CreateAt, lastPostCreateAt) + assert.False(t, r1.HasNext) + + // Going from bottom to top now. + firstPostCreateAt = r1.Posts[r1.Order[1]].CreateAt + opts = model.GetPostsOptions{ + CollapsedThreads: false, + PerPage: 2, + Direction: "up", + FromPost: r1.Order[1], + FromCreateAt: firstPostCreateAt, + SkipFetchThreads: false, + } + r1, err = ss.Post().Get(context.Background(), o1.Id, opts, o1.UserId) + require.NoError(t, err) + assert.Len(t, r1.Order, 3) // including the root post + assert.LessOrEqual(t, r1.Posts[r1.Order[1]].CreateAt, firstPostCreateAt) + assert.False(t, r1.HasNext) + }) } func testPostStoreGetSingle(t *testing.T, ss store.Store) { @@ -635,15 +743,15 @@ func testPostStoreUpdate(t *testing.T, ss store.Store) { o3, err = ss.Post().Save(o3) require.NoError(t, err) - r1, err := ss.Post().Get(context.Background(), o1.Id, false, false, false, "") + r1, err := ss.Post().Get(context.Background(), o1.Id, model.GetPostsOptions{}, "") require.NoError(t, err) ro1 := r1.Posts[o1.Id] - r2, err := ss.Post().Get(context.Background(), o1.Id, false, false, false, "") + r2, err := ss.Post().Get(context.Background(), o1.Id, model.GetPostsOptions{}, "") require.NoError(t, err) ro2 := r2.Posts[o2.Id] - r3, err := ss.Post().Get(context.Background(), o3.Id, false, false, false, "") + r3, err := ss.Post().Get(context.Background(), o3.Id, model.GetPostsOptions{}, "") require.NoError(t, err) ro3 := r3.Posts[o3.Id] @@ -654,7 +762,7 @@ func testPostStoreUpdate(t *testing.T, ss store.Store) { _, err = ss.Post().Update(o1a, ro1) require.NoError(t, err) - r1, err = ss.Post().Get(context.Background(), o1.Id, false, false, false, "") + r1, err = ss.Post().Get(context.Background(), o1.Id, model.GetPostsOptions{}, "") require.NoError(t, err) ro1a := r1.Posts[o1.Id] @@ -665,7 +773,7 @@ func testPostStoreUpdate(t *testing.T, ss store.Store) { _, err = ss.Post().Update(o2a, ro2) require.NoError(t, err) - r2, err = ss.Post().Get(context.Background(), o1.Id, false, false, false, "") + r2, err = ss.Post().Get(context.Background(), o1.Id, model.GetPostsOptions{}, "") require.NoError(t, err) ro2a := r2.Posts[o2.Id] @@ -676,7 +784,7 @@ func testPostStoreUpdate(t *testing.T, ss store.Store) { _, err = ss.Post().Update(o3a, ro3) require.NoError(t, err) - r3, err = ss.Post().Get(context.Background(), o3.Id, false, false, false, "") + r3, err = ss.Post().Get(context.Background(), o3.Id, model.GetPostsOptions{}, "") require.NoError(t, err) ro3a := r3.Posts[o3.Id] @@ -692,7 +800,7 @@ func testPostStoreUpdate(t *testing.T, ss store.Store) { }) require.NoError(t, err) - r4, err := ss.Post().Get(context.Background(), o4.Id, false, false, false, "") + r4, err := ss.Post().Get(context.Background(), o4.Id, model.GetPostsOptions{}, "") require.NoError(t, err) ro4 := r4.Posts[o4.Id] @@ -702,7 +810,7 @@ func testPostStoreUpdate(t *testing.T, ss store.Store) { _, err = ss.Post().Update(o4a, ro4) require.NoError(t, err) - r4, err = ss.Post().Get(context.Background(), o4.Id, false, false, false, "") + r4, err = ss.Post().Get(context.Background(), o4.Id, model.GetPostsOptions{}, "") require.NoError(t, err) ro4a := r4.Posts[o4.Id] @@ -723,7 +831,7 @@ func testPostStoreDelete(t *testing.T, ss store.Store) { o1, err := ss.Post().Save(o1) require.NoError(t, err) - r1, err := ss.Post().Get(context.Background(), o1.Id, false, false, false, "") + r1, err := ss.Post().Get(context.Background(), o1.Id, model.GetPostsOptions{}, "") require.NoError(t, err) require.Equal(t, r1.Posts[o1.Id].CreateAt, o1.CreateAt, "invalid returned post") @@ -736,7 +844,7 @@ func testPostStoreDelete(t *testing.T, ss store.Store) { assert.Equal(t, deleteByID, actual, "Expected (*Post).Props[model.PostPropsDeleteBy] to be %v but got %v.", deleteByID, actual) - r3, err := ss.Post().Get(context.Background(), o1.Id, false, false, false, "") + r3, err := ss.Post().Get(context.Background(), o1.Id, model.GetPostsOptions{}, "") require.Error(t, err, "Missing id should have failed - PostList %v", r3) etag2 := ss.Post().GetEtag(o1.ChannelId, false, false) @@ -762,10 +870,10 @@ func testPostStoreDelete1Level(t *testing.T, ss store.Store) { err = ss.Post().Delete(o1.Id, model.GetMillis(), "") require.NoError(t, err) - _, err = ss.Post().Get(context.Background(), o1.Id, false, false, false, "") + _, err = ss.Post().Get(context.Background(), o1.Id, model.GetPostsOptions{}, "") require.Error(t, err, "Deleted id should have failed") - _, err = ss.Post().Get(context.Background(), o2.Id, false, false, false, "") + _, err = ss.Post().Get(context.Background(), o2.Id, model.GetPostsOptions{}, "") require.Error(t, err, "Deleted id should have failed") } @@ -803,16 +911,16 @@ func testPostStoreDelete2Level(t *testing.T, ss store.Store) { err = ss.Post().Delete(o1.Id, model.GetMillis(), "") require.NoError(t, err) - _, err = ss.Post().Get(context.Background(), o1.Id, false, false, false, "") + _, err = ss.Post().Get(context.Background(), o1.Id, model.GetPostsOptions{}, "") require.Error(t, err, "Deleted id should have failed") - _, err = ss.Post().Get(context.Background(), o2.Id, false, false, false, "") + _, err = ss.Post().Get(context.Background(), o2.Id, model.GetPostsOptions{}, "") require.Error(t, err, "Deleted id should have failed") - _, err = ss.Post().Get(context.Background(), o3.Id, false, false, false, "") + _, err = ss.Post().Get(context.Background(), o3.Id, model.GetPostsOptions{}, "") require.Error(t, err, "Deleted id should have failed") - _, err = ss.Post().Get(context.Background(), o4.Id, false, false, false, "") + _, err = ss.Post().Get(context.Background(), o4.Id, model.GetPostsOptions{}, "") require.NoError(t, err) } @@ -878,10 +986,10 @@ func testPostStorePermDelete1Level(t *testing.T, ss store.Store) { require.EqualValues(t, 0, thread.ReplyCount) require.EqualValues(t, model.StringArray{}, thread.Participants) - _, err = ss.Post().Get(context.Background(), o1.Id, false, false, false, "") + _, err = ss.Post().Get(context.Background(), o1.Id, model.GetPostsOptions{}, "") require.NoError(t, err, "Deleted id shouldn't have failed") - _, err = ss.Post().Get(context.Background(), o2.Id, false, false, false, "") + _, err = ss.Post().Get(context.Background(), o2.Id, model.GetPostsOptions{}, "") require.Error(t, err, "Deleted id should have failed") thread, err = ss.Thread().Get(o5.Id) @@ -895,16 +1003,16 @@ func testPostStorePermDelete1Level(t *testing.T, ss store.Store) { require.NoError(t, err) require.Nil(t, thread) - _, err = ss.Post().Get(context.Background(), o3.Id, false, false, false, "") + _, err = ss.Post().Get(context.Background(), o3.Id, model.GetPostsOptions{}, "") require.Error(t, err, "Deleted id should have failed") - _, err = ss.Post().Get(context.Background(), o4.Id, false, false, false, "") + _, err = ss.Post().Get(context.Background(), o4.Id, model.GetPostsOptions{}, "") require.Error(t, err, "Deleted id should have failed") - _, err = ss.Post().Get(context.Background(), o5.Id, false, false, false, "") + _, err = ss.Post().Get(context.Background(), o5.Id, model.GetPostsOptions{}, "") require.Error(t, err, "Deleted id should have failed") - _, err = ss.Post().Get(context.Background(), o6.Id, false, false, false, "") + _, err = ss.Post().Get(context.Background(), o6.Id, model.GetPostsOptions{}, "") require.Error(t, err, "Deleted id should have failed") } @@ -934,13 +1042,13 @@ func testPostStorePermDelete1Level2(t *testing.T, ss store.Store) { err2 := ss.Post().PermanentDeleteByUser(o1.UserId) require.NoError(t, err2) - _, err = ss.Post().Get(context.Background(), o1.Id, false, false, false, "") + _, err = ss.Post().Get(context.Background(), o1.Id, model.GetPostsOptions{}, "") require.Error(t, err, "Deleted id should have failed") - _, err = ss.Post().Get(context.Background(), o2.Id, false, false, false, "") + _, err = ss.Post().Get(context.Background(), o2.Id, model.GetPostsOptions{}, "") require.Error(t, err, "Deleted id should have failed") - _, err = ss.Post().Get(context.Background(), o3.Id, false, false, false, "") + _, err = ss.Post().Get(context.Background(), o3.Id, model.GetPostsOptions{}, "") require.NoError(t, err, "Deleted id should have failed") } @@ -968,7 +1076,7 @@ func testPostStoreGetWithChildren(t *testing.T, ss store.Store) { o3, err = ss.Post().Save(o3) require.NoError(t, err) - pl, err := ss.Post().Get(context.Background(), o1.Id, false, false, false, "") + pl, err := ss.Post().Get(context.Background(), o1.Id, model.GetPostsOptions{}, "") require.NoError(t, err) require.Len(t, pl.Posts, 3, "invalid returned post") @@ -976,7 +1084,7 @@ func testPostStoreGetWithChildren(t *testing.T, ss store.Store) { dErr := ss.Post().Delete(o3.Id, model.GetMillis(), "") require.NoError(t, dErr) - pl, err = ss.Post().Get(context.Background(), o1.Id, false, false, false, "") + pl, err = ss.Post().Get(context.Background(), o1.Id, model.GetPostsOptions{}, "") require.NoError(t, err) require.Len(t, pl.Posts, 2, "invalid returned post") @@ -984,7 +1092,7 @@ func testPostStoreGetWithChildren(t *testing.T, ss store.Store) { dErr = ss.Post().Delete(o2.Id, model.GetMillis(), "") require.NoError(t, dErr) - pl, err = ss.Post().Get(context.Background(), o1.Id, false, false, false, "") + pl, err = ss.Post().Get(context.Background(), o1.Id, model.GetPostsOptions{}, "") require.NoError(t, err) require.Len(t, pl.Posts, 1, "invalid returned post") @@ -2579,23 +2687,23 @@ func testPostStoreOverwriteMultiple(t *testing.T, ss store.Store) { }) require.NoError(t, err) - r1, err := ss.Post().Get(context.Background(), o1.Id, false, false, false, "") + r1, err := ss.Post().Get(context.Background(), o1.Id, model.GetPostsOptions{}, "") require.NoError(t, err) ro1 := r1.Posts[o1.Id] - r2, err := ss.Post().Get(context.Background(), o2.Id, false, false, false, "") + r2, err := ss.Post().Get(context.Background(), o2.Id, model.GetPostsOptions{}, "") require.NoError(t, err) ro2 := r2.Posts[o2.Id] - r3, err := ss.Post().Get(context.Background(), o3.Id, false, false, false, "") + r3, err := ss.Post().Get(context.Background(), o3.Id, model.GetPostsOptions{}, "") require.NoError(t, err) ro3 := r3.Posts[o3.Id] - r4, err := ss.Post().Get(context.Background(), o4.Id, false, false, false, "") + r4, err := ss.Post().Get(context.Background(), o4.Id, model.GetPostsOptions{}, "") require.NoError(t, err) ro4 := r4.Posts[o4.Id] - r5, err := ss.Post().Get(context.Background(), o5.Id, false, false, false, "") + r5, err := ss.Post().Get(context.Background(), o5.Id, model.GetPostsOptions{}, "") require.NoError(t, err) ro5 := r5.Posts[o5.Id] @@ -2621,15 +2729,15 @@ func testPostStoreOverwriteMultiple(t *testing.T, ss store.Store) { require.NoError(t, err) require.Equal(t, -1, errIdx) - r1, nErr := ss.Post().Get(context.Background(), o1.Id, false, false, false, "") + r1, nErr := ss.Post().Get(context.Background(), o1.Id, model.GetPostsOptions{}, "") require.NoError(t, nErr) ro1a := r1.Posts[o1.Id] - r2, nErr = ss.Post().Get(context.Background(), o1.Id, false, false, false, "") + r2, nErr = ss.Post().Get(context.Background(), o1.Id, model.GetPostsOptions{}, "") require.NoError(t, nErr) ro2a := r2.Posts[o2.Id] - r3, nErr = ss.Post().Get(context.Background(), o3.Id, false, false, false, "") + r3, nErr = ss.Post().Get(context.Background(), o3.Id, model.GetPostsOptions{}, "") require.NoError(t, nErr) ro3a := r3.Posts[o3.Id] @@ -2651,11 +2759,11 @@ func testPostStoreOverwriteMultiple(t *testing.T, ss store.Store) { require.NoError(t, err) require.Equal(t, -1, errIdx) - r4, nErr := ss.Post().Get(context.Background(), o4.Id, false, false, false, "") + r4, nErr := ss.Post().Get(context.Background(), o4.Id, model.GetPostsOptions{}, "") require.NoError(t, nErr) ro4a := r4.Posts[o4.Id] - r5, nErr = ss.Post().Get(context.Background(), o5.Id, false, false, false, "") + r5, nErr = ss.Post().Get(context.Background(), o5.Id, model.GetPostsOptions{}, "") require.NoError(t, nErr) ro5a := r5.Posts[o5.Id] @@ -2697,19 +2805,19 @@ func testPostStoreOverwrite(t *testing.T, ss store.Store) { }) require.NoError(t, err) - r1, err := ss.Post().Get(context.Background(), o1.Id, false, false, false, "") + r1, err := ss.Post().Get(context.Background(), o1.Id, model.GetPostsOptions{}, "") require.NoError(t, err) ro1 := r1.Posts[o1.Id] - r2, err := ss.Post().Get(context.Background(), o2.Id, false, false, false, "") + r2, err := ss.Post().Get(context.Background(), o2.Id, model.GetPostsOptions{}, "") require.NoError(t, err) ro2 := r2.Posts[o2.Id] - r3, err := ss.Post().Get(context.Background(), o3.Id, false, false, false, "") + r3, err := ss.Post().Get(context.Background(), o3.Id, model.GetPostsOptions{}, "") require.NoError(t, err) ro3 := r3.Posts[o3.Id] - r4, err := ss.Post().Get(context.Background(), o4.Id, false, false, false, "") + r4, err := ss.Post().Get(context.Background(), o4.Id, model.GetPostsOptions{}, "") require.NoError(t, err) ro4 := r4.Posts[o4.Id] @@ -2734,15 +2842,15 @@ func testPostStoreOverwrite(t *testing.T, ss store.Store) { _, err = ss.Post().Overwrite(o3a) require.NoError(t, err) - r1, err = ss.Post().Get(context.Background(), o1.Id, false, false, false, "") + r1, err = ss.Post().Get(context.Background(), o1.Id, model.GetPostsOptions{}, "") require.NoError(t, err) ro1a := r1.Posts[o1.Id] - r2, err = ss.Post().Get(context.Background(), o1.Id, false, false, false, "") + r2, err = ss.Post().Get(context.Background(), o1.Id, model.GetPostsOptions{}, "") require.NoError(t, err) ro2a := r2.Posts[o2.Id] - r3, err = ss.Post().Get(context.Background(), o3.Id, false, false, false, "") + r3, err = ss.Post().Get(context.Background(), o3.Id, model.GetPostsOptions{}, "") require.NoError(t, err) ro3a := r3.Posts[o3.Id] @@ -2758,7 +2866,7 @@ func testPostStoreOverwrite(t *testing.T, ss store.Store) { _, err = ss.Post().Overwrite(o4a) require.NoError(t, err) - r4, err = ss.Post().Get(context.Background(), o4.Id, false, false, false, "") + r4, err = ss.Post().Get(context.Background(), o4.Id, model.GetPostsOptions{}, "") require.NoError(t, err) ro4a := r4.Posts[o4.Id] @@ -2789,15 +2897,15 @@ func testPostStoreGetPostsByIds(t *testing.T, ss store.Store) { o3, err = ss.Post().Save(o3) require.NoError(t, err) - r1, err := ss.Post().Get(context.Background(), o1.Id, false, false, false, "") + r1, err := ss.Post().Get(context.Background(), o1.Id, model.GetPostsOptions{}, "") require.NoError(t, err) ro1 := r1.Posts[o1.Id] - r2, err := ss.Post().Get(context.Background(), o2.Id, false, false, false, "") + r2, err := ss.Post().Get(context.Background(), o2.Id, model.GetPostsOptions{}, "") require.NoError(t, err) ro2 := r2.Posts[o2.Id] - r3, err := ss.Post().Get(context.Background(), o3.Id, false, false, false, "") + r3, err := ss.Post().Get(context.Background(), o3.Id, model.GetPostsOptions{}, "") require.NoError(t, err) ro3 := r3.Posts[o3.Id] @@ -2918,13 +3026,13 @@ func testPostStorePermanentDeleteBatch(t *testing.T, ss store.Store) { _, _, err = ss.Post().PermanentDeleteBatchForRetentionPolicies(0, 2000, 1000, model.RetentionPolicyCursor{}) require.NoError(t, err) - _, err = ss.Post().Get(context.Background(), o1.Id, false, false, false, "") + _, err = ss.Post().Get(context.Background(), o1.Id, model.GetPostsOptions{}, "") require.Error(t, err, "Should have not found post 1 after purge") - _, err = ss.Post().Get(context.Background(), o2.Id, false, false, false, "") + _, err = ss.Post().Get(context.Background(), o2.Id, model.GetPostsOptions{}, "") require.Error(t, err, "Should have not found post 2 after purge") - _, err = ss.Post().Get(context.Background(), o3.Id, false, false, false, "") + _, err = ss.Post().Get(context.Background(), o3.Id, model.GetPostsOptions{}, "") require.NoError(t, err, "Should have found post 3 after purge") t.Run("with pagination", func(t *testing.T) { @@ -2968,13 +3076,13 @@ func testPostStorePermanentDeleteBatch(t *testing.T, ss store.Store) { _, _, err2 = ss.Post().PermanentDeleteBatchForRetentionPolicies(0, 2000, 1000, model.RetentionPolicyCursor{}) require.NoError(t, err2) - _, err2 = ss.Post().Get(context.Background(), post.Id, false, false, false, "") + _, err2 = ss.Post().Get(context.Background(), post.Id, model.GetPostsOptions{}, "") require.NoError(t, err2, "global policy should have been ignored due to granular policy") nowMillis := post.CreateAt + *channelPolicy.PostDuration*24*60*60*1000 + 1 _, _, err2 = ss.Post().PermanentDeleteBatchForRetentionPolicies(nowMillis, 0, 1000, model.RetentionPolicyCursor{}) require.NoError(t, err2) - _, err2 = ss.Post().Get(context.Background(), post.Id, false, false, false, "") + _, err2 = ss.Post().Get(context.Background(), post.Id, model.GetPostsOptions{}, "") require.Error(t, err2, "post should have been deleted by channel policy") // Create a team policy which is stricter than the channel policy @@ -2993,7 +3101,7 @@ func testPostStorePermanentDeleteBatch(t *testing.T, ss store.Store) { nowMillis = post.CreateAt + *teamPolicy.PostDuration*24*60*60*1000 + 1 _, _, err2 = ss.Post().PermanentDeleteBatchForRetentionPolicies(nowMillis, 0, 1000, model.RetentionPolicyCursor{}) require.NoError(t, err2) - _, err2 = ss.Post().Get(context.Background(), post.Id, false, false, false, "") + _, err2 = ss.Post().Get(context.Background(), post.Id, model.GetPostsOptions{}, "") require.NoError(t, err2, "channel policy should have overridden team policy") // Delete channel policy and re-run team policy @@ -3005,7 +3113,7 @@ func testPostStorePermanentDeleteBatch(t *testing.T, ss store.Store) { _, _, err2 = ss.Post().PermanentDeleteBatchForRetentionPolicies(nowMillis, 0, 1000, model.RetentionPolicyCursor{}) require.NoError(t, err2) - _, err2 = ss.Post().Get(context.Background(), post.Id, false, false, false, "") + _, err2 = ss.Post().Get(context.Background(), post.Id, model.GetPostsOptions{}, "") require.Error(t, err2, "post should have been deleted by team policy") err2 = ss.RetentionPolicy().RemoveTeams(teamPolicy.ID, []string{team.Id}) diff --git a/store/storetest/reaction_store.go b/store/storetest/reaction_store.go index f9946521eb..e18ad85eb7 100644 --- a/store/storetest/reaction_store.go +++ b/store/storetest/reaction_store.go @@ -55,7 +55,7 @@ func testReactionSave(t *testing.T, ss store.Store) { assert.Zero(t, saved.DeleteAt, "should've saved reaction delete_at with zero value and returned it") var secondUpdateAt int64 - postList, err := ss.Post().Get(context.Background(), reaction1.PostId, false, false, false, "") + postList, err := ss.Post().Get(context.Background(), reaction1.PostId, model.GetPostsOptions{}, "") require.NoError(t, err) assert.True(t, postList.Posts[post.Id].HasReactions, "should've set HasReactions = true on post") @@ -79,7 +79,7 @@ func testReactionSave(t *testing.T, ss store.Store) { _, nErr = ss.Reaction().Save(reaction2) require.NoError(t, nErr) - postList, err = ss.Post().Get(context.Background(), reaction2.PostId, false, false, false, "") + postList, err = ss.Post().Get(context.Background(), reaction2.PostId, model.GetPostsOptions{}, "") require.NoError(t, err) assert.NotEqual(t, postList.Posts[post.Id].UpdateAt, secondUpdateAt, "should've marked post as updated even if HasReactions doesn't change") @@ -129,7 +129,7 @@ func testReactionDelete(t *testing.T, ss store.Store) { _, nErr := ss.Reaction().Save(reaction) require.NoError(t, nErr) - result, err := ss.Post().Get(context.Background(), reaction.PostId, false, false, false, "") + result, err := ss.Post().Get(context.Background(), reaction.PostId, model.GetPostsOptions{}, "") require.NoError(t, err) firstUpdateAt := result.Posts[post.Id].UpdateAt @@ -142,7 +142,7 @@ func testReactionDelete(t *testing.T, ss store.Store) { assert.Empty(t, reactions, "should've deleted reaction") - postList, err := ss.Post().Get(context.Background(), post.Id, false, false, false, "") + postList, err := ss.Post().Get(context.Background(), post.Id, model.GetPostsOptions{}, "") require.NoError(t, err) assert.False(t, postList.Posts[post.Id].HasReactions, "should've set HasReactions = false on post") @@ -507,15 +507,15 @@ func testReactionDeleteAllWithEmojiName(t *testing.T, ss store.Store, s SqlStore assert.Empty(t, returned, "should've only removed reactions with emoji name") // check that the posts are updated - postList, err := ss.Post().Get(context.Background(), post.Id, false, false, false, "") + postList, err := ss.Post().Get(context.Background(), post.Id, model.GetPostsOptions{}, "") require.NoError(t, err) assert.True(t, postList.Posts[post.Id].HasReactions, "post should still have reactions") - postList, err = ss.Post().Get(context.Background(), post2.Id, false, false, false, "") + postList, err = ss.Post().Get(context.Background(), post2.Id, model.GetPostsOptions{}, "") require.NoError(t, err) assert.True(t, postList.Posts[post2.Id].HasReactions, "post should still have reactions") - postList, err = ss.Post().Get(context.Background(), post3.Id, false, false, false, "") + postList, err = ss.Post().Get(context.Background(), post3.Id, model.GetPostsOptions{}, "") require.NoError(t, err) assert.False(t, postList.Posts[post3.Id].HasReactions, "post shouldn't have reactions any more") diff --git a/store/storetest/thread_store.go b/store/storetest/thread_store.go index 1ef77780b1..e57c37becf 100644 --- a/store/storetest/thread_store.go +++ b/store/storetest/thread_store.go @@ -80,7 +80,10 @@ func testThreadStorePopulation(t *testing.T, ss store.Store) { newPosts, errIdx, err3 := ss.Post().SaveMultiple([]*model.Post{&o2, &o3, &o4}) - olist, _ := ss.Post().Get(context.Background(), otmp.Id, true, false, false, "") + opts := model.GetPostsOptions{ + SkipFetchThreads: true, + } + olist, _ := ss.Post().Get(context.Background(), otmp.Id, opts, "") o1 := olist.Posts[olist.Order[0]] newPosts = append([]*model.Post{o1}, newPosts...) diff --git a/store/timerlayer/timerlayer.go b/store/timerlayer/timerlayer.go index b03eeeb28c..fff076ae9f 100644 --- a/store/timerlayer/timerlayer.go +++ b/store/timerlayer/timerlayer.go @@ -4890,10 +4890,10 @@ func (s *TimerLayerPostStore) DeleteOrphanedRows(limit int) (int64, error) { return result, err } -func (s *TimerLayerPostStore) Get(ctx context.Context, id string, skipFetchThreads bool, collapsedThreads bool, collapsedThreadsExtended bool, userID string) (*model.PostList, error) { +func (s *TimerLayerPostStore) Get(ctx context.Context, id string, opts model.GetPostsOptions, userID string) (*model.PostList, error) { start := timemodule.Now() - result, err := s.PostStore.Get(ctx, id, skipFetchThreads, collapsedThreads, collapsedThreadsExtended, userID) + result, err := s.PostStore.Get(ctx, id, opts, userID) elapsed := float64(timemodule.Since(start)) / float64(timemodule.Second) if s.Root.Metrics != nil {