MM-11210 Add "GET /posts/unread" API to support landing on the last unread post (#11486)

* [MM-11210] Add API GET 'api/v4/channels/{channel_id:[A-Za-z0-9]+}/posts/unread' for scrolling overhaul (#9108)

* Add API GET 'api/v4/channels/{channel_id:[A-Za-z0-9]+}/posts/unread'

* add constants

* refactor GetPostSince and added more tests

* move constants to app package

* [MM-11528 &&  MM-11583] Add userId to in the "posts/unread" path and update test with time delay to fix intermittent failure (#9229)

* add userId to in the "posts/unread" path and update test with time delay to fix intermittent failure

* add limit before and after to query

* remove time delay on test and put pretermined value of Post.CreateAt

* Fix conflict

* [MM-11876] Add cursor to posts list such as next_post_id and previous_post_id (#9707)

* add cursor to posts list such as next_post_id and previous_post_id

add publish previous_post_id on WEBSOCKET_EVENT_POSTED and only get next or previous post IDs if necessary

revert change on adding previous_post_id in WEBSOCKET_EVENT_POSTED

add missing strings import

fix merge conflicts

* update per comment

* update per feedback

* corrected the logic in getting the next and previous post ID

* fix logic to determine next and post IDs, and rename function to have suffix of "Time"

* rearrange logics and add mote tests

* fix merge conflict

* fix missing message when using unread API (#10233)

* MM-15569 Fixes failing test on TestGetPostsForChannelAroundLastUnread (#11039)

* Fix missing posts when getting posts since

* revert changes to GetPostsSince

* migrate Post.GetPostAfterTime and Post.GetPostBeforeTime to sync by default

* revert change to cacheItem

* Fix post ID validation, build query on squirrel and only return post ID as necessary
Этот коммит содержится в:
Saturnino Abril
2019-07-04 05:23:04 +08:00
коммит произвёл Sudheer
родитель f56a8f5a99
Коммит b832985f1d
12 изменённых файлов: 1085 добавлений и 32 удалений

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

@@ -9,6 +9,7 @@ import (
"strconv"
"time"
"github.com/mattermost/mattermost-server/app"
"github.com/mattermost/mattermost-server/model"
)
@@ -22,6 +23,8 @@ func (api *API) InitPost() {
api.BaseRoutes.PostsForChannel.Handle("", api.ApiSessionRequired(getPostsForChannel)).Methods("GET")
api.BaseRoutes.PostsForUser.Handle("/flagged", api.ApiSessionRequired(getFlaggedPostsForUser)).Methods("GET")
api.BaseRoutes.ChannelForUser.Handle("/posts/unread", api.ApiSessionRequired(getPostsForChannelAroundLastUnread)).Methods("GET")
api.BaseRoutes.Team.Handle("/posts/search", api.ApiSessionRequired(searchPosts)).Methods("POST")
api.BaseRoutes.Post.Handle("", api.ApiSessionRequired(updatePost)).Methods("PUT")
api.BaseRoutes.Post.Handle("/patch", api.ApiSessionRequired(patchPost)).Methods("PUT")
@@ -109,12 +112,20 @@ func getPostsForChannel(c *Context, w http.ResponseWriter, r *http.Request) {
}
afterPost := r.URL.Query().Get("after")
beforePost := r.URL.Query().Get("before")
sinceString := r.URL.Query().Get("since")
if len(afterPost) > 0 && !model.IsValidId(afterPost) {
c.SetInvalidParam("after")
return
}
beforePost := r.URL.Query().Get("before")
if len(beforePost) > 0 && !model.IsValidId(beforePost) {
c.SetInvalidParam("before")
return
}
sinceString := r.URL.Query().Get("since")
var since int64
var parseError error
if len(sinceString) > 0 {
since, parseError = strconv.ParseInt(sinceString, 10, 64)
if parseError != nil {
@@ -123,7 +134,11 @@ func getPostsForChannel(c *Context, w http.ResponseWriter, r *http.Request) {
}
}
if !c.App.SessionHasPermissionToChannel(c.App.Session, c.Params.ChannelId, model.PERMISSION_READ_CHANNEL) {
channelId := c.Params.ChannelId
page := c.Params.Page
perPage := c.Params.PerPage
if !c.App.SessionHasPermissionToChannel(c.App.Session, channelId, model.PERMISSION_READ_CHANNEL) {
c.SetPermissionError(model.PERMISSION_READ_CHANNEL)
return
}
@@ -133,31 +148,31 @@ func getPostsForChannel(c *Context, w http.ResponseWriter, r *http.Request) {
etag := ""
if since > 0 {
list, err = c.App.GetPostsSince(c.Params.ChannelId, since)
list, err = c.App.GetPostsSince(channelId, since)
} else if len(afterPost) > 0 {
etag = c.App.GetPostsEtag(c.Params.ChannelId)
etag = c.App.GetPostsEtag(channelId)
if c.HandleEtag(etag, "Get Posts After", w, r) {
return
}
list, err = c.App.GetPostsAfterPost(c.Params.ChannelId, afterPost, c.Params.Page, c.Params.PerPage)
list, err = c.App.GetPostsAfterPost(channelId, afterPost, page, perPage)
} else if len(beforePost) > 0 {
etag = c.App.GetPostsEtag(c.Params.ChannelId)
etag = c.App.GetPostsEtag(channelId)
if c.HandleEtag(etag, "Get Posts Before", w, r) {
return
}
list, err = c.App.GetPostsBeforePost(c.Params.ChannelId, beforePost, c.Params.Page, c.Params.PerPage)
list, err = c.App.GetPostsBeforePost(channelId, beforePost, page, perPage)
} else {
etag = c.App.GetPostsEtag(c.Params.ChannelId)
etag = c.App.GetPostsEtag(channelId)
if c.HandleEtag(etag, "Get Posts", w, r) {
return
}
list, err = c.App.GetPostsPage(c.Params.ChannelId, c.Params.Page, c.Params.PerPage)
list, err = c.App.GetPostsPage(channelId, page, perPage)
}
if err != nil {
@@ -169,7 +184,56 @@ func getPostsForChannel(c *Context, w http.ResponseWriter, r *http.Request) {
w.Header().Set(model.HEADER_ETAG_SERVER, etag)
}
w.Write([]byte(c.App.PreparePostListForClient(list).ToJson()))
c.App.AddCursorIdsForPostList(list, afterPost, beforePost, since, page, perPage)
clientPostList := c.App.PreparePostListForClient(list)
w.Write([]byte(clientPostList.ToJson()))
}
func getPostsForChannelAroundLastUnread(c *Context, w http.ResponseWriter, r *http.Request) {
c.RequireUserId().RequireChannelId()
if c.Err != nil {
return
}
userId := c.Params.UserId
if !c.App.SessionHasPermissionToUser(c.App.Session, userId) {
c.SetPermissionError(model.PERMISSION_EDIT_OTHER_USERS)
return
}
channelId := c.Params.ChannelId
if !c.App.SessionHasPermissionToChannel(c.App.Session, channelId, model.PERMISSION_READ_CHANNEL) {
c.SetPermissionError(model.PERMISSION_READ_CHANNEL)
return
}
postList, err := c.App.GetPostsForChannelAroundLastUnread(channelId, userId, c.Params.LimitBefore, c.Params.LimitAfter)
if err != nil {
c.Err = err
return
}
etag := ""
if len(postList.Order) == 0 {
etag = c.App.GetPostsEtag(channelId)
if c.HandleEtag(etag, "Get Posts", w, r) {
return
}
postList, err = c.App.GetPostsPage(channelId, app.PAGE_DEFAULT, c.Params.LimitBefore)
}
postList.NextPostId = c.App.GetNextPostIdFromPostList(postList)
postList.PrevPostId = c.App.GetPrevPostIdFromPostList(postList)
clientPostList := c.App.PreparePostListForClient(postList)
if len(etag) > 0 {
w.Header().Set(model.HEADER_ETAG_SERVER, etag)
}
w.Write([]byte(clientPostList.ToJson()))
}
func getFlaggedPostsForUser(c *Context, w http.ResponseWriter, r *http.Request) {

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

@@ -16,6 +16,7 @@ import (
"time"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"github.com/mattermost/mattermost-server/app"
"github.com/mattermost/mattermost-server/model"
@@ -911,6 +912,13 @@ func TestGetPostsForChannel(t *testing.T) {
t.Log(posts.Posts)
t.Fatal("should return 2 posts")
}
// "since" query to return empty NextPostId and PrevPostId
if posts.NextPostId != "" {
t.Fatal("should return an empty NextPostId")
}
if posts.PrevPostId != "" {
t.Fatal("should return an empty PrevPostId")
}
found := make([]bool, 2)
for _, p := range posts.Posts {
@@ -945,6 +953,97 @@ func TestGetPostsForChannel(t *testing.T) {
_, resp = th.SystemAdminClient.GetPostsForChannel(th.BasicChannel.Id, 0, 60, "")
CheckNoError(t, resp)
// more tests for next_post_id, prev_post_id, and order
// There are 12 posts composed of first 2 system messages and 10 created posts
Client.Login(th.BasicUser.Email, th.BasicUser.Password)
th.CreatePost() // post6
post7 := th.CreatePost()
post8 := th.CreatePost()
th.CreatePost() // post9
post10 := th.CreatePost()
// get the system post IDs posted before the created posts above
posts, resp = Client.GetPostsBefore(th.BasicChannel.Id, post1.Id, 0, 2, "")
systemPostId1 := posts.Order[1]
// similar to '/posts'
posts, resp = Client.GetPostsForChannel(th.BasicChannel.Id, 0, 60, "")
CheckNoError(t, resp)
if len(posts.Order) != 12 || posts.Order[0] != post10.Id || posts.Order[11] != systemPostId1 {
t.Fatal("should return 12 posts and match order")
}
if posts.NextPostId != "" {
t.Fatal("should return an empty NextPostId")
}
if posts.PrevPostId != "" {
t.Fatal("should return an empty PrevPostId")
}
// similar to '/posts?per_page=3'
posts, resp = Client.GetPostsForChannel(th.BasicChannel.Id, 0, 3, "")
CheckNoError(t, resp)
if len(posts.Order) != 3 || posts.Order[0] != post10.Id || posts.Order[2] != post8.Id {
t.Fatal("should return 3 posts and match order")
}
if posts.NextPostId != "" {
t.Fatal("should return an empty NextPostId")
}
if posts.PrevPostId != post7.Id {
t.Fatal("should return post7.Id as PrevPostId")
}
// similar to '/posts?per_page=3&page=1'
posts, resp = Client.GetPostsForChannel(th.BasicChannel.Id, 1, 3, "")
CheckNoError(t, resp)
if len(posts.Order) != 3 || posts.Order[0] != post7.Id || posts.Order[2] != post5.Id {
t.Fatal("should return 3 posts and match order")
}
if posts.NextPostId != post8.Id {
t.Fatal("should return post8.Id as NextPostId")
}
if posts.PrevPostId != post4.Id {
t.Fatal("should return post4.Id as PrevPostId")
}
// similar to '/posts?per_page=3&page=2'
posts, resp = Client.GetPostsForChannel(th.BasicChannel.Id, 2, 3, "")
CheckNoError(t, resp)
if len(posts.Order) != 3 || posts.Order[0] != post4.Id || posts.Order[2] != post2.Id {
t.Fatal("should return 3 posts and match order")
}
if posts.NextPostId != post5.Id {
t.Fatal("should return post5.Id as NextPostId")
}
if posts.PrevPostId != post1.Id {
t.Fatal("should return post1.Id as PrevPostId")
}
// similar to '/posts?per_page=3&page=3'
posts, resp = Client.GetPostsForChannel(th.BasicChannel.Id, 3, 3, "")
CheckNoError(t, resp)
if len(posts.Order) != 3 || posts.Order[0] != post1.Id || posts.Order[2] != systemPostId1 {
t.Fatal("should return 3 posts and match order")
}
if posts.NextPostId != post2.Id {
t.Fatal("should return post2.Id as NextPostId")
}
if posts.PrevPostId != "" {
t.Fatal("should return an empty PrevPostId")
}
// similar to '/posts?per_page=3&page=4'
posts, resp = Client.GetPostsForChannel(th.BasicChannel.Id, 4, 3, "")
CheckNoError(t, resp)
if len(posts.Order) != 0 {
t.Fatal("should return 0 post")
}
if posts.NextPostId != "" {
t.Fatal("should return an empty NextPostId")
}
if posts.PrevPostId != "" {
t.Fatal("should return an empty PrevPostId")
}
}
func TestGetFlaggedPostsForUser(t *testing.T) {
@@ -1190,7 +1289,7 @@ func TestGetFlaggedPostsForUser(t *testing.T) {
CheckNoError(t, resp)
}
func TestGetPostsAfterAndBefore(t *testing.T) {
func TestGetPostsBefore(t *testing.T) {
th := Setup().InitBasic()
defer th.TearDown()
Client := th.Client
@@ -1223,24 +1322,208 @@ func TestGetPostsAfterAndBefore(t *testing.T) {
}
}
posts, resp = Client.GetPostsBefore(th.BasicChannel.Id, post3.Id, 1, 1, "")
if posts.NextPostId != post3.Id {
t.Fatal("should match NextPostId")
}
if posts.PrevPostId != "" {
t.Fatal("should match empty PrevPostId")
}
posts, resp = Client.GetPostsBefore(th.BasicChannel.Id, post4.Id, 1, 1, "")
CheckNoError(t, resp)
if len(posts.Posts) != 1 {
t.Fatal("too many posts returned")
}
posts, resp = Client.GetPostsBefore(th.BasicChannel.Id, "junk", 1, 1, "")
CheckNoError(t, resp)
if len(posts.Posts) != 0 {
t.Fatal("should have no posts")
if posts.Order[0] != post2.Id {
t.Fatal("should match returned post")
}
if posts.NextPostId != post3.Id {
t.Fatal("should match NextPostId")
}
if posts.PrevPostId != post1.Id {
t.Fatal("should match PrevPostId")
}
posts, resp = Client.GetPostsAfter(th.BasicChannel.Id, post3.Id, 0, 100, "")
posts, resp = Client.GetPostsBefore(th.BasicChannel.Id, "junk", 1, 1, "")
CheckBadRequestStatus(t, resp)
posts, resp = Client.GetPostsBefore(th.BasicChannel.Id, post5.Id, 0, 3, "")
CheckNoError(t, resp)
found = make([]bool, 2)
if len(posts.Posts) != 3 {
t.Fatal("should match length of posts returned")
}
if posts.Order[0] != post4.Id {
t.Fatal("should match returned post")
}
if posts.Order[2] != post2.Id {
t.Fatal("should match returned post")
}
if posts.NextPostId != post5.Id {
t.Fatal("should match NextPostId")
}
if posts.PrevPostId != post1.Id {
t.Fatal("should match PrevPostId")
}
// get the system post IDs posted before the created posts above
posts, resp = Client.GetPostsBefore(th.BasicChannel.Id, post1.Id, 0, 2, "")
CheckNoError(t, resp)
systemPostId2 := posts.Order[0]
systemPostId1 := posts.Order[1]
posts, resp = Client.GetPostsBefore(th.BasicChannel.Id, post5.Id, 1, 3, "")
CheckNoError(t, resp)
if len(posts.Posts) != 3 {
t.Fatal("should match length of posts returned")
}
if posts.Order[0] != post1.Id {
t.Fatal("should match returned post")
}
if posts.Order[1] != systemPostId2 {
t.Fatal("should match returned post")
}
if posts.Order[2] != systemPostId1 {
t.Fatal("should match returned post")
}
if posts.NextPostId != post2.Id {
t.Fatal("should match NextPostId")
}
if posts.PrevPostId != "" {
t.Fatal("should return empty PrevPostId")
}
// more tests for next_post_id, prev_post_id, and order
// There are 12 posts composed of first 2 system messages and 10 created posts
post6 := th.CreatePost()
th.CreatePost() // post7
post8 := th.CreatePost()
post9 := th.CreatePost()
th.CreatePost() // post10
// similar to '/posts?before=post9'
posts, resp = Client.GetPostsBefore(th.BasicChannel.Id, post9.Id, 0, 60, "")
CheckNoError(t, resp)
if len(posts.Order) != 10 || posts.Order[0] != post8.Id || posts.Order[9] != systemPostId1 {
t.Fatal("should return 10 posts and match order")
}
if posts.NextPostId != post9.Id {
t.Fatal("should return post9.Id as NextPostId")
}
if posts.PrevPostId != "" {
t.Fatal("should return an empty PrevPostId")
}
// similar to '/posts?before=post9&per_page=3'
posts, resp = Client.GetPostsBefore(th.BasicChannel.Id, post9.Id, 0, 3, "")
CheckNoError(t, resp)
if len(posts.Order) != 3 || posts.Order[0] != post8.Id || posts.Order[2] != post6.Id {
t.Fatal("should return 3 posts and match order")
}
if posts.NextPostId != post9.Id {
t.Fatal("should return post9.Id as NextPostId")
}
if posts.PrevPostId != post5.Id {
t.Fatal("should return post5.Id as PrevPostId")
}
// similar to '/posts?before=post9&per_page=3&page=1'
posts, resp = Client.GetPostsBefore(th.BasicChannel.Id, post9.Id, 1, 3, "")
CheckNoError(t, resp)
if len(posts.Order) != 3 || posts.Order[0] != post5.Id || posts.Order[2] != post3.Id {
t.Fatal("should return 3 posts and match order")
}
if posts.NextPostId != post6.Id {
t.Fatal("should return post6.Id as NextPostId")
}
if posts.PrevPostId != post2.Id {
t.Fatal("should return post2.Id as PrevPostId")
}
// similar to '/posts?before=post9&per_page=3&page=2'
posts, resp = Client.GetPostsBefore(th.BasicChannel.Id, post9.Id, 2, 3, "")
CheckNoError(t, resp)
if len(posts.Order) != 3 || posts.Order[0] != post2.Id || posts.Order[2] != systemPostId2 {
t.Fatal("should return 3 posts and match order")
}
if posts.NextPostId != post3.Id {
t.Fatal("should return post3.Id as NextPostId")
}
if posts.PrevPostId != systemPostId1 {
t.Fatal("should return systemPostId1 as PrevPostId")
}
// similar to '/posts?before=post1&per_page=3'
posts, resp = Client.GetPostsBefore(th.BasicChannel.Id, post1.Id, 0, 3, "")
CheckNoError(t, resp)
if len(posts.Order) != 2 || posts.Order[0] != systemPostId2 || posts.Order[1] != systemPostId1 {
t.Fatal("should return 2 posts and match order")
}
if posts.NextPostId != post1.Id {
t.Fatal("should return post1.Id as NextPostId")
}
if posts.PrevPostId != "" {
t.Fatal("should return an empty PrevPostId")
}
// similar to '/posts?before=systemPostId1'
posts, resp = Client.GetPostsBefore(th.BasicChannel.Id, systemPostId1, 0, 60, "")
CheckNoError(t, resp)
if len(posts.Order) != 0 {
t.Fatal("should return 0 post")
}
if posts.NextPostId != systemPostId1 {
t.Fatal("should return systemPostId1 as NextPostId")
}
if posts.PrevPostId != "" {
t.Fatal("should return an empty PrevPostId")
}
// similar to '/posts?before=systemPostId1&per_page=60&page=1'
posts, resp = Client.GetPostsBefore(th.BasicChannel.Id, systemPostId1, 1, 60, "")
CheckNoError(t, resp)
if len(posts.Order) != 0 {
t.Fatal("should return 0 post")
}
if posts.NextPostId != "" {
t.Fatal("should return an empty NextPostId")
}
if posts.PrevPostId != "" {
t.Fatal("should return an empty PrevPostId")
}
// similar to '/posts?before=non-existent-post'
nonExistentPostId := model.NewId()
posts, resp = Client.GetPostsBefore(th.BasicChannel.Id, nonExistentPostId, 0, 60, "")
CheckNoError(t, resp)
if len(posts.Order) != 0 {
t.Fatal("should return 0 post")
}
if posts.NextPostId != nonExistentPostId {
t.Fatal("should return nonExistentPostId as NextPostId")
}
if posts.PrevPostId != "" {
t.Fatal("should return an empty PrevPostId")
}
}
func TestGetPostsAfter(t *testing.T) {
th := Setup().InitBasic()
defer th.TearDown()
Client := th.Client
post1 := th.CreatePost()
post2 := th.CreatePost()
post3 := th.CreatePost()
post4 := th.CreatePost()
post5 := th.CreatePost()
posts, resp := Client.GetPostsAfter(th.BasicChannel.Id, post3.Id, 0, 100, "")
CheckNoError(t, resp)
found := make([]bool, 2)
for _, p := range posts.Posts {
if p.Id == post4.Id {
found[0] = true
@@ -1259,18 +1542,298 @@ func TestGetPostsAfterAndBefore(t *testing.T) {
}
}
posts, resp = Client.GetPostsAfter(th.BasicChannel.Id, post3.Id, 1, 1, "")
if posts.NextPostId != "" {
t.Fatal("should match empty NextPostId")
}
if posts.PrevPostId != post3.Id {
t.Fatal("should match PrevPostId")
}
posts, resp = Client.GetPostsAfter(th.BasicChannel.Id, post2.Id, 1, 1, "")
CheckNoError(t, resp)
if len(posts.Posts) != 1 {
t.Fatal("too many posts returned")
}
if posts.Order[0] != post4.Id {
t.Fatal("should match returned post")
}
if posts.NextPostId != post5.Id {
t.Fatal("should match NextPostId")
}
if posts.PrevPostId != post3.Id {
t.Fatal("should match PrevPostId")
}
posts, resp = Client.GetPostsAfter(th.BasicChannel.Id, "junk", 1, 1, "")
CheckBadRequestStatus(t, resp)
posts, resp = Client.GetPostsAfter(th.BasicChannel.Id, post1.Id, 0, 3, "")
CheckNoError(t, resp)
if len(posts.Posts) != 0 {
t.Fatal("should have no posts")
if len(posts.Posts) != 3 {
t.Fatal("should match length of posts returned")
}
if posts.Order[0] != post4.Id {
t.Fatal("should match returned post")
}
if posts.Order[2] != post2.Id {
t.Fatal("should match returned post")
}
if posts.NextPostId != post5.Id {
t.Fatal("should match NextPostId")
}
if posts.PrevPostId != post1.Id {
t.Fatal("should match PrevPostId")
}
posts, resp = Client.GetPostsAfter(th.BasicChannel.Id, post1.Id, 1, 3, "")
CheckNoError(t, resp)
if len(posts.Posts) != 1 {
t.Fatal("should match length of posts returned")
}
if posts.Order[0] != post5.Id {
t.Fatal("should match returned post")
}
if posts.NextPostId != "" {
t.Fatal("should match NextPostId")
}
if posts.PrevPostId != post4.Id {
t.Fatal("should match PrevPostId")
}
// more tests for next_post_id, prev_post_id, and order
// There are 12 posts composed of first 2 system messages and 10 created posts
post6 := th.CreatePost()
th.CreatePost() // post7
post8 := th.CreatePost()
post9 := th.CreatePost()
post10 := th.CreatePost()
// similar to '/posts?after=post2'
posts, resp = Client.GetPostsAfter(th.BasicChannel.Id, post2.Id, 0, 60, "")
CheckNoError(t, resp)
if len(posts.Order) != 8 || posts.Order[0] != post10.Id || posts.Order[7] != post3.Id {
t.Fatal("should return 8 posts and match order")
}
if posts.NextPostId != "" {
t.Fatal("should return an empty NextPostId")
}
if posts.PrevPostId != post2.Id {
t.Fatal("should return post2.Id as PrevPostId")
}
// similar to '/posts?after=post2&per_page=3'
posts, resp = Client.GetPostsAfter(th.BasicChannel.Id, post2.Id, 0, 3, "")
CheckNoError(t, resp)
if len(posts.Order) != 3 || posts.Order[0] != post5.Id || posts.Order[2] != post3.Id {
t.Fatal("should return 3 posts and match order")
}
if posts.NextPostId != post6.Id {
t.Fatal("should return post6.Id as NextPostId")
}
if posts.PrevPostId != post2.Id {
t.Fatal("should return post2.Id as PrevPostId")
}
// similar to '/posts?after=post2&per_page=3&page=1'
posts, resp = Client.GetPostsAfter(th.BasicChannel.Id, post2.Id, 1, 3, "")
CheckNoError(t, resp)
if len(posts.Order) != 3 || posts.Order[0] != post8.Id || posts.Order[2] != post6.Id {
t.Fatal("should return 3 posts and match order")
}
if posts.NextPostId != post9.Id {
t.Fatal("should return post9.Id as NextPostId")
}
if posts.PrevPostId != post5.Id {
t.Fatal("should return post5.Id as PrevPostId")
}
// similar to '/posts?after=post2&per_page=3&page=2'
posts, resp = Client.GetPostsAfter(th.BasicChannel.Id, post2.Id, 2, 3, "")
CheckNoError(t, resp)
if len(posts.Order) != 2 || posts.Order[0] != post10.Id || posts.Order[1] != post9.Id {
t.Fatal("should return 2 posts and match order")
}
if posts.NextPostId != "" {
t.Fatal("should return an empty NextPostId")
}
if posts.PrevPostId != post8.Id {
t.Fatal("should return post8.Id as PrevPostId")
}
// similar to '/posts?after=post10'
posts, resp = Client.GetPostsAfter(th.BasicChannel.Id, post10.Id, 0, 60, "")
CheckNoError(t, resp)
if len(posts.Order) != 0 {
t.Fatal("should return 0 post")
}
if posts.NextPostId != "" {
t.Fatal("should return an empty NextPostId")
}
if posts.PrevPostId != post10.Id {
t.Fatal("should return post10.Id as PrevPostId")
}
// similar to '/posts?after=post10&page=1'
posts, resp = Client.GetPostsAfter(th.BasicChannel.Id, post10.Id, 1, 60, "")
CheckNoError(t, resp)
if len(posts.Order) != 0 {
t.Fatal("should return 0 post")
}
if posts.NextPostId != "" {
t.Fatal("should return an empty NextPostId")
}
if posts.PrevPostId != "" {
t.Fatal("should return an empty PrevPostId")
}
// similar to '/posts?after=non-existent-post'
nonExistentPostId := model.NewId()
posts, resp = Client.GetPostsAfter(th.BasicChannel.Id, nonExistentPostId, 0, 60, "")
CheckNoError(t, resp)
if len(posts.Order) != 0 {
t.Fatal("should return 0 post")
}
if posts.NextPostId != "" {
t.Fatal("should return an empty NextPostId")
}
if posts.PrevPostId != nonExistentPostId {
t.Fatal("should return nonExistentPostId as PrevPostId")
}
}
func TestGetPostsForChannelAroundLastUnread(t *testing.T) {
th := Setup().InitBasic()
defer th.TearDown()
Client := th.Client
userId := th.BasicUser.Id
channelId := th.BasicChannel.Id
// 12 posts = 2 systems posts + 10 created posts below
post1 := th.CreatePost()
post2 := th.CreatePost()
post3 := th.CreatePost()
post4 := th.CreatePost()
th.CreatePost() // post5
post6 := th.CreatePost()
post7 := th.CreatePost()
post8 := th.CreatePost()
post9 := th.CreatePost()
post10 := th.CreatePost()
// All returned posts are all read by the user, since it's created by the user itself.
posts, resp := Client.GetPostsAroundLastUnread(userId, channelId, 20, 20)
CheckNoError(t, resp)
if len(posts.Order) != 12 {
t.Fatal("Should return 12 posts only since there's no unread post")
}
// Set channel member's last viewed to 0.
// All returned posts are latest posts as if all previous posts were already read by the user.
channelMember, err := th.App.Srv.Store.Channel().GetMember(channelId, userId)
require.Nil(t, err)
channelMember.LastViewedAt = 0
_, err = th.App.Srv.Store.Channel().UpdateMember(channelMember)
require.Nil(t, err)
th.App.Srv.Store.Post().InvalidateLastPostTimeCache(channelId)
posts, resp = Client.GetPostsAroundLastUnread(userId, channelId, 20, 20)
CheckNoError(t, resp)
if len(posts.Order) != 12 {
t.Fatal("Should return 12 posts only since there's no unread post")
}
// get the first system post generated before the created posts above
posts, resp = Client.GetPostsBefore(th.BasicChannel.Id, post1.Id, 0, 2, "")
CheckNoError(t, resp)
systemPostId1 := posts.Order[1]
// Set channel member's last viewed before post1.
channelMember, err = th.App.Srv.Store.Channel().GetMember(channelId, userId)
require.Nil(t, err)
channelMember.LastViewedAt = post1.CreateAt - 1
_, err = th.App.Srv.Store.Channel().UpdateMember(channelMember)
require.Nil(t, err)
th.App.Srv.Store.Post().InvalidateLastPostTimeCache(channelId)
posts, resp = Client.GetPostsAroundLastUnread(userId, channelId, 3, 3)
CheckNoError(t, resp)
if len(posts.Order) != 5 || posts.Order[0] != post3.Id || posts.Order[4] != systemPostId1 {
t.Fatal("Should return 5 posts and match order")
}
if posts.NextPostId != post4.Id {
t.Fatal("should return post4.Id as NextPostId")
}
if posts.PrevPostId != "" {
t.Fatal("should return an empty PrevPostId")
}
// Set channel member's last viewed before post6.
channelMember, err = th.App.Srv.Store.Channel().GetMember(channelId, userId)
require.Nil(t, err)
channelMember.LastViewedAt = post6.CreateAt - 1
_, err = th.App.Srv.Store.Channel().UpdateMember(channelMember)
require.Nil(t, err)
th.App.Srv.Store.Post().InvalidateLastPostTimeCache(channelId)
posts, resp = Client.GetPostsAroundLastUnread(userId, channelId, 3, 3)
CheckNoError(t, resp)
if len(posts.Order) != 6 || posts.Order[0] != post8.Id || posts.Order[5] != post3.Id {
t.Fatal("Should return 6 posts and match order")
}
if posts.NextPostId != post9.Id {
t.Fatal("should return post8.Id as NextPostId")
}
if posts.PrevPostId != post2.Id {
t.Fatal("should return post2.Id as PrevPostId")
}
// Set channel member's last viewed before post10.
channelMember, err = th.App.Srv.Store.Channel().GetMember(channelId, userId)
require.Nil(t, err)
channelMember.LastViewedAt = post10.CreateAt - 1
_, err = th.App.Srv.Store.Channel().UpdateMember(channelMember)
require.Nil(t, err)
th.App.Srv.Store.Post().InvalidateLastPostTimeCache(channelId)
posts, resp = Client.GetPostsAroundLastUnread(userId, channelId, 3, 3)
CheckNoError(t, resp)
if len(posts.Order) != 4 || posts.Order[0] != post10.Id || posts.Order[3] != post7.Id {
t.Fatal("Should return 4 posts and match order")
}
if posts.NextPostId != "" {
t.Fatal("should return an empty NextPostId")
}
if posts.PrevPostId != post6.Id {
t.Fatal("should return post6.Id as PrevPostId")
}
// Set channel member's last viewed equal to post10.
channelMember, err = th.App.Srv.Store.Channel().GetMember(channelId, userId)
require.Nil(t, err)
channelMember.LastViewedAt = post10.CreateAt
_, err = th.App.Srv.Store.Channel().UpdateMember(channelMember)
require.Nil(t, err)
th.App.Srv.Store.Post().InvalidateLastPostTimeCache(channelId)
posts, resp = Client.GetPostsAroundLastUnread(userId, channelId, 3, 3)
CheckNoError(t, resp)
if len(posts.Order) != 3 || posts.Order[0] != post10.Id || posts.Order[2] != post8.Id {
t.Fatal("Should return 3 posts and match order")
}
if posts.NextPostId != "" {
t.Fatal("should return an empty NextPostId")
}
if posts.PrevPostId != post7.Id {
t.Fatal("should return post7.Id as PrevPostId")
}
}

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

@@ -20,6 +20,7 @@ import (
const (
PENDING_POST_IDS_CACHE_SIZE = 25000
PENDING_POST_IDS_CACHE_TTL = 30 * time.Second
PAGE_DEFAULT = 0
)
func (a *App) CreatePostAsUser(post *model.Post, currentSessionId string) (*model.Post, *model.AppError) {
@@ -674,6 +675,126 @@ func (a *App) GetPostsAroundPost(postId, channelId string, offset, limit int, be
return a.Srv.Store.Post().GetPostsAfter(channelId, postId, limit, offset)
}
func (a *App) GetPostAfterTime(channelId string, time int64) (*model.Post, *model.AppError) {
return a.Srv.Store.Post().GetPostAfterTime(channelId, time)
}
func (a *App) GetPostIdAfterTime(channelId string, time int64) (string, *model.AppError) {
return a.Srv.Store.Post().GetPostIdAfterTime(channelId, time)
}
func (a *App) GetPostIdBeforeTime(channelId string, time int64) (string, *model.AppError) {
return a.Srv.Store.Post().GetPostIdBeforeTime(channelId, time)
}
func (a *App) GetNextPostIdFromPostList(postList *model.PostList) string {
if len(postList.Order) > 0 {
firstPostId := postList.Order[0]
firstPost := postList.Posts[firstPostId]
nextPostId, err := a.GetPostIdAfterTime(firstPost.ChannelId, firstPost.CreateAt)
if err != nil {
mlog.Warn("GetNextPostIdFromPostList: failed in getting next post", mlog.Err(err))
}
return nextPostId
}
return ""
}
func (a *App) GetPrevPostIdFromPostList(postList *model.PostList) string {
if len(postList.Order) > 0 {
lastPostId := postList.Order[len(postList.Order)-1]
lastPost := postList.Posts[lastPostId]
previousPostId, err := a.GetPostIdBeforeTime(lastPost.ChannelId, lastPost.CreateAt)
if err != nil {
mlog.Warn("GetPrevPostIdFromPostList: failed in getting previous post", mlog.Err(err))
}
return previousPostId
}
return ""
}
// AddCursorIdsForPostList adds NextPostId and PrevPostId as cursor to the PostList.
// The conditional blocks ensure that it sets those cursor IDs immediately as afterPost, beforePost or empty,
// and only query to database whenever necessary.
func (a *App) AddCursorIdsForPostList(originalList *model.PostList, afterPost, beforePost string, since int64, page, perPage int) {
prevPostIdSet := false
prevPostId := ""
nextPostIdSet := false
nextPostId := ""
if since > 0 { // "since" query to return empty NextPostId and PrevPostId
nextPostIdSet = true
prevPostIdSet = true
} else if afterPost != "" {
if page == 0 {
prevPostId = afterPost
prevPostIdSet = true
}
if len(originalList.Order) < perPage {
nextPostIdSet = true
}
} else if beforePost != "" {
if page == 0 {
nextPostId = beforePost
nextPostIdSet = true
}
if len(originalList.Order) < perPage {
prevPostIdSet = true
}
}
if !nextPostIdSet {
nextPostId = a.GetNextPostIdFromPostList(originalList)
}
if !prevPostIdSet {
prevPostId = a.GetPrevPostIdFromPostList(originalList)
}
originalList.NextPostId = nextPostId
originalList.PrevPostId = prevPostId
}
func (a *App) GetPostsForChannelAroundLastUnread(channelId, userId string, limitBefore, limitAfter int) (*model.PostList, *model.AppError) {
var member *model.ChannelMember
var err *model.AppError
if member, err = a.GetChannelMember(channelId, userId); err != nil {
return nil, err
} else if member.LastViewedAt == 0 {
return model.NewPostList(), nil
}
lastUnreadPost, err := a.GetPostAfterTime(channelId, member.LastViewedAt)
if err != nil {
return nil, err
} else if lastUnreadPost == nil {
return model.NewPostList(), nil
}
var postList *model.PostList
if postList, err = a.GetPostsBeforePost(channelId, lastUnreadPost.Id, PAGE_DEFAULT, limitBefore); err != nil {
return nil, err
}
if postListAfter, err := a.GetPostsAfterPost(channelId, lastUnreadPost.Id, PAGE_DEFAULT, limitAfter-1); err != nil {
return nil, err
} else if postListAfter != nil {
postList.Extend(postListAfter)
}
postList.AddPost(lastUnreadPost)
postList.AddOrder(lastUnreadPost.Id)
postList.SortByCreateAt()
return postList, nil
}
func (a *App) DeletePost(postId, deleteByID string) (*model.Post, *model.AppError) {
post, err := a.Srv.Store.Post().GetSingle(postId)
if err != nil {

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

@@ -40,8 +40,10 @@ func (a *App) InitPostMetadata() {
func (a *App) PreparePostListForClient(originalList *model.PostList) *model.PostList {
list := &model.PostList{
Posts: make(map[string]*model.Post, len(originalList.Posts)),
Order: originalList.Order, // Note that this uses the original Order array, so it isn't a deep copy
Posts: make(map[string]*model.Post, len(originalList.Posts)),
Order: originalList.Order,
NextPostId: originalList.NextPostId,
PrevPostId: originalList.PrevPostId,
}
for id, originalPost := range originalList.Posts {

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

@@ -6122,6 +6122,14 @@
"id": "store.sql_post.get_parents_posts.app_error",
"translation": "Unable to get the parent post for the channel"
},
{
"id": "store.sql_post.get_post_after_time.app_error",
"translation": "Unable to get post after time bound"
},
{
"id": "store.sql_post.get_post_id_around.app_error",
"translation": "Unable to get post around time bound"
},
{
"id": "store.sql_post.get_posts.app_error",
"translation": "Limit exceeded for paging"

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

@@ -2509,6 +2509,17 @@ func (c *Client4) GetPostsBefore(channelId, postId string, page, perPage int, et
return PostListFromJson(r.Body), BuildResponse(r)
}
// GetPostsAroundLastUnread gets a list of posts around last unread post by a user in a channel.
func (c *Client4) GetPostsAroundLastUnread(userId, channelId string, limitBefore, limitAfter int) (*PostList, *Response) {
query := fmt.Sprintf("?limit_before=%v&limit_after=%v", limitBefore, limitAfter)
if r, err := c.DoApiGet(c.GetUserRoute(userId)+c.GetChannelRoute(channelId)+"/posts/unread"+query, ""); err != nil {
return nil, BuildErrorResponse(r, err)
} else {
defer closeBody(r)
return PostListFromJson(r.Body), BuildResponse(r)
}
}
// SearchPosts returns any posts with matching terms string.
func (c *Client4) SearchPosts(teamId string, terms string, isOrSearch bool) (*PostList, *Response) {
params := SearchParameter{

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

@@ -10,14 +10,18 @@ import (
)
type PostList struct {
Order []string `json:"order"`
Posts map[string]*Post `json:"posts"`
Order []string `json:"order"`
Posts map[string]*Post `json:"posts"`
NextPostId string `json:"next_post_id"`
PrevPostId string `json:"prev_post_id"`
}
func NewPostList() *PostList {
return &PostList{
Order: make([]string, 0),
Posts: make(map[string]*Post),
Order: make([]string, 0),
Posts: make(map[string]*Post),
NextPostId: "",
PrevPostId: "",
}
}

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

@@ -4,6 +4,7 @@
package sqlstore
import (
"database/sql"
"fmt"
"net/http"
"regexp"
@@ -334,7 +335,7 @@ func (s *SqlPostStore) InvalidateLastPostTimeCache(channelId string) {
func (s *SqlPostStore) GetEtag(channelId string, allowFromCache bool) string {
if allowFromCache {
if cacheItem, ok := s.lastPostTimeCache.Get(channelId); ok {
if cacheItem, ok := s.lastPostTimeCache.Get(channelId); ok && cacheItem.(int64) > 0 {
if s.metrics != nil {
s.metrics.IncrementMemCacheHitCounter("Last Post Time")
}
@@ -661,6 +662,78 @@ func (s *SqlPostStore) getPostsAround(channelId string, postId string, limit int
return list, nil
}
func (s *SqlPostStore) GetPostIdBeforeTime(channelId string, time int64) (string, *model.AppError) {
return s.getPostIdAroundTime(channelId, time, true)
}
func (s *SqlPostStore) GetPostIdAfterTime(channelId string, time int64) (string, *model.AppError) {
return s.getPostIdAroundTime(channelId, time, false)
}
func (s *SqlPostStore) getPostIdAroundTime(channelId string, time int64, before bool) (string, *model.AppError) {
var direction sq.Sqlizer
var sort string
if before {
direction = sq.Lt{"CreateAt": time}
sort = "DESC"
} else {
direction = sq.Gt{"CreateAt": time}
sort = "ASC"
}
query := s.getQueryBuilder().
Select("Id").
From("Posts").
Where(sq.And{
direction,
sq.Eq{"ChannelId": channelId},
sq.Eq{"DeleteAt": int(0)},
}).
OrderBy("CreateAt " + sort).
Limit(1)
queryString, args, err := query.ToSql()
if err != nil {
return "", model.NewAppError("SqlPostStore.getPostIdAroundTime", "store.sql_post.get_post_id_around.app_error", nil, err.Error(), http.StatusInternalServerError)
}
var postId string
if err := s.GetMaster().SelectOne(&postId, queryString, args...); err != nil {
if err != sql.ErrNoRows {
return "", model.NewAppError("SqlPostStore.getPostIdAroundTime", "store.sql_post.get_post_id_around.app_error", nil, "channelId="+channelId+err.Error(), http.StatusInternalServerError)
}
}
return postId, nil
}
func (s *SqlPostStore) GetPostAfterTime(channelId string, time int64) (*model.Post, *model.AppError) {
query := s.getQueryBuilder().
Select("*").
From("Posts").
Where(sq.And{
sq.Gt{"CreateAt": time},
sq.Eq{"ChannelId": channelId},
sq.Eq{"DeleteAt": int(0)},
}).
OrderBy("CreateAt ASC").
Limit(1)
queryString, args, err := query.ToSql()
if err != nil {
return nil, model.NewAppError("SqlPostStore.GetPostAfterTime", "store.sql_post.get_post_after_time.app_error", nil, err.Error(), http.StatusInternalServerError)
}
var post *model.Post
if err := s.GetMaster().SelectOne(&post, queryString, args...); err != nil {
if err != sql.ErrNoRows {
return nil, model.NewAppError("SqlPostStore.GetPostAfterTime", "store.sql_post.get_post_after_time.app_error", nil, "channelId="+channelId+err.Error(), http.StatusInternalServerError)
}
}
return post, nil
}
func (s *SqlPostStore) getRootPosts(channelId string, offset int, limit int) store.StoreChannel {
return store.Do(func(result *store.StoreResult) {
var posts []*model.Post

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

@@ -226,6 +226,9 @@ type PostStore interface {
GetPostsBefore(channelId string, postId string, numPosts int, offset int) (*model.PostList, *model.AppError)
GetPostsAfter(channelId string, postId string, numPosts int, offset int) (*model.PostList, *model.AppError)
GetPostsSince(channelId string, time int64, allowFromCache bool) (*model.PostList, *model.AppError)
GetPostAfterTime(channelId string, time int64) (*model.Post, *model.AppError)
GetPostIdAfterTime(channelId string, time int64) (string, *model.AppError)
GetPostIdBeforeTime(channelId string, time int64) (string, *model.AppError)
GetEtag(channelId string, allowFromCache bool) string
Search(teamId string, userId string, params *model.SearchParams) StoreChannel
AnalyticsUserCountsWithPostsByDay(teamId string) (model.AnalyticsRows, *model.AppError)

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

@@ -310,6 +310,77 @@ func (_m *PostStore) GetParentsForExportAfter(limit int, afterId string) ([]*mod
return r0, r1
}
// GetPostAfterTime provides a mock function with given fields: channelId, time
func (_m *PostStore) GetPostAfterTime(channelId string, time int64) (*model.Post, *model.AppError) {
ret := _m.Called(channelId, time)
var r0 *model.Post
if rf, ok := ret.Get(0).(func(string, int64) *model.Post); ok {
r0 = rf(channelId, time)
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).(*model.Post)
}
}
var r1 *model.AppError
if rf, ok := ret.Get(1).(func(string, int64) *model.AppError); ok {
r1 = rf(channelId, time)
} else {
if ret.Get(1) != nil {
r1 = ret.Get(1).(*model.AppError)
}
}
return r0, r1
}
// GetPostIdAfterTime provides a mock function with given fields: channelId, time
func (_m *PostStore) GetPostIdAfterTime(channelId string, time int64) (string, *model.AppError) {
ret := _m.Called(channelId, time)
var r0 string
if rf, ok := ret.Get(0).(func(string, int64) string); ok {
r0 = rf(channelId, time)
} else {
r0 = ret.Get(0).(string)
}
var r1 *model.AppError
if rf, ok := ret.Get(1).(func(string, int64) *model.AppError); ok {
r1 = rf(channelId, time)
} else {
if ret.Get(1) != nil {
r1 = ret.Get(1).(*model.AppError)
}
}
return r0, r1
}
// GetPostIdBeforeTime provides a mock function with given fields: channelId, time
func (_m *PostStore) GetPostIdBeforeTime(channelId string, time int64) (string, *model.AppError) {
ret := _m.Called(channelId, time)
var r0 string
if rf, ok := ret.Get(0).(func(string, int64) string); ok {
r0 = rf(channelId, time)
} else {
r0 = ret.Get(0).(string)
}
var r1 *model.AppError
if rf, ok := ret.Get(1).(func(string, int64) *model.AppError); ok {
r1 = rf(channelId, time)
} else {
if ret.Get(1) != nil {
r1 = ret.Get(1).(*model.AppError)
}
}
return r0, r1
}
// GetPosts provides a mock function with given fields: channelId, offset, limit, allowFromCache
func (_m *PostStore) GetPosts(channelId string, offset int, limit int, allowFromCache bool) (*model.PostList, *model.AppError) {
ret := _m.Called(channelId, offset, limit, allowFromCache)

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

@@ -33,6 +33,7 @@ func TestPostStore(t *testing.T, ss store.Store, s SqlSupplier) {
t.Run("GetPostsWithDetails", func(t *testing.T) { testPostStoreGetPostsWithDetails(t, ss) })
t.Run("GetPostsBeforeAfter", func(t *testing.T) { testPostStoreGetPostsBeforeAfter(t, ss) })
t.Run("GetPostsSince", func(t *testing.T) { testPostStoreGetPostsSince(t, ss) })
t.Run("GetPostBeforeAfter", func(t *testing.T) { testPostStoreGetPostBeforeAfter(t, ss) })
t.Run("Search", func(t *testing.T) { testPostStoreSearch(t, ss) })
t.Run("UserCountsWithPostsByDay", func(t *testing.T) { testUserCountsWithPostsByDay(t, ss) })
t.Run("PostCountsByDay", func(t *testing.T) { testPostCountsByDay(t, ss) })
@@ -1027,6 +1028,118 @@ func testPostStoreGetPostsSince(t *testing.T, ss store.Store) {
}
}
func testPostStoreGetPostBeforeAfter(t *testing.T, ss store.Store) {
channelId := model.NewId()
o0 := &model.Post{}
o0.ChannelId = channelId
o0.UserId = model.NewId()
o0.Message = "zz" + model.NewId() + "b"
_, err := ss.Post().Save(o0)
require.Nil(t, err)
time.Sleep(2 * time.Millisecond)
o1 := &model.Post{}
o1.ChannelId = channelId
o1.Type = model.POST_JOIN_CHANNEL
o1.UserId = model.NewId()
o1.Message = "system_join_channel message"
_, err = ss.Post().Save(o1)
require.Nil(t, err)
time.Sleep(2 * time.Millisecond)
o0a := &model.Post{}
o0a.ChannelId = channelId
o0a.UserId = model.NewId()
o0a.Message = "zz" + model.NewId() + "b"
o0a.ParentId = o1.Id
o0a.RootId = o1.Id
_, err = ss.Post().Save(o0a)
require.Nil(t, err)
time.Sleep(2 * time.Millisecond)
o0b := &model.Post{}
o0b.ChannelId = channelId
o0b.UserId = model.NewId()
o0b.Message = "deleted message"
o0b.ParentId = o1.Id
o0b.RootId = o1.Id
o0b.DeleteAt = 1
_, err = ss.Post().Save(o0b)
require.Nil(t, err)
time.Sleep(2 * time.Millisecond)
otherChannelPost := &model.Post{}
otherChannelPost.ChannelId = model.NewId()
otherChannelPost.UserId = model.NewId()
otherChannelPost.Message = "zz" + model.NewId() + "b"
_, err = ss.Post().Save(otherChannelPost)
require.Nil(t, err)
time.Sleep(2 * time.Millisecond)
o2 := &model.Post{}
o2.ChannelId = channelId
o2.UserId = model.NewId()
o2.Message = "zz" + model.NewId() + "b"
_, err = ss.Post().Save(o2)
require.Nil(t, err)
time.Sleep(2 * time.Millisecond)
o2a := &model.Post{}
o2a.ChannelId = channelId
o2a.UserId = model.NewId()
o2a.Message = "zz" + model.NewId() + "b"
o2a.ParentId = o2.Id
o2a.RootId = o2.Id
_, err = ss.Post().Save(o2a)
require.Nil(t, err)
rPostId1, err := ss.Post().GetPostIdBeforeTime(channelId, o0a.CreateAt)
if rPostId1 != o1.Id || err != nil {
t.Fatal("should return before post o1")
}
rPostId1, err = ss.Post().GetPostIdAfterTime(channelId, o0b.CreateAt)
if rPostId1 != o2.Id || err != nil {
t.Fatal("should return before post o2")
}
rPost1, err := ss.Post().GetPostAfterTime(channelId, o0b.CreateAt)
if rPost1.Id != o2.Id || err != nil {
t.Fatal("should return before post o2")
}
rPostId2, err := ss.Post().GetPostIdBeforeTime(channelId, o0.CreateAt)
if rPostId2 != "" || err != nil {
t.Fatal("should return no post")
}
rPostId2, err = ss.Post().GetPostIdAfterTime(channelId, o0.CreateAt)
if rPostId2 != o1.Id || err != nil {
t.Fatal("should return before post o1")
}
rPost2, err := ss.Post().GetPostAfterTime(channelId, o0.CreateAt)
if rPost2.Id != o1.Id || err != nil {
t.Fatal("should return before post o1")
}
rPostId3, err := ss.Post().GetPostIdBeforeTime(channelId, o2a.CreateAt)
if rPostId3 != o2.Id || err != nil {
t.Fatal("should return before post o2")
}
rPostId3, err = ss.Post().GetPostIdAfterTime(channelId, o2a.CreateAt)
if rPostId3 != "" || err != nil {
t.Fatal("should return no post")
}
rPost3, err := ss.Post().GetPostAfterTime(channelId, o2a.CreateAt)
if rPost3 != nil || err != nil {
t.Fatal("should return no post")
}
}
func testPostStoreSearch(t *testing.T, ss store.Store) {
teamId := model.NewId()
userId := model.NewId()

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

@@ -18,6 +18,8 @@ const (
PER_PAGE_MAXIMUM = 200
LOGS_PER_PAGE_DEFAULT = 10000
LOGS_PER_PAGE_MAXIMUM = 10000
LIMIT_DEFAULT = 60
LIMIT_MAXIMUM = 200
)
type Params struct {
@@ -68,6 +70,8 @@ type Params struct {
IncludeMemberCount bool
NotAssociatedToGroup string
ExcludeDefaultChannels bool
LimitAfter int
LimitBefore int
GroupIDs string
IncludeTotalCount bool
}
@@ -226,6 +230,22 @@ func ParamsFromRequest(r *http.Request) *Params {
params.LogsPerPage = val
}
if val, err := strconv.Atoi(query.Get("limit_after")); err != nil || val < 0 {
params.LimitAfter = LIMIT_DEFAULT
} else if val > LIMIT_MAXIMUM {
params.LimitAfter = LIMIT_MAXIMUM
} else {
params.LimitAfter = val
}
if val, err := strconv.Atoi(query.Get("limit_before")); err != nil || val < 0 {
params.LimitBefore = LIMIT_DEFAULT
} else if val > LIMIT_MAXIMUM {
params.LimitBefore = LIMIT_MAXIMUM
} else {
params.LimitBefore = val
}
if val, ok := props["syncable_id"]; ok {
params.SyncableId = val
}