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 ```
Этот коммит содержится в:
коммит произвёл
GitHub
родитель
ad5f57b161
Коммит
c1f3827801
55
api4/post.go
55
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
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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{}
|
||||
|
||||
@@ -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))
|
||||
|
||||
@@ -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) {
|
||||
|
||||
19
app/post.go
19
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
|
||||
}
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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]])
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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})
|
||||
|
||||
@@ -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")
|
||||
|
||||
|
||||
@@ -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...)
|
||||
|
||||
@@ -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 {
|
||||
|
||||
Ссылка в новой задаче
Block a user