Merge pull request #1285 from mattermost/plt-824

PLT-824 Adding ability to fetch posts around a specifc post
Этот коммит содержится в:
Joram Wilander
2015-11-04 07:51:19 -05:00
родитель 87c4b4fbf4 188182a3a4
Коммит 0cc985f6f4
6 изменённых файлов: 366 добавлений и 0 удалений

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

@@ -31,6 +31,8 @@ func InitPost(r *mux.Router) {
sr.Handle("/posts/{time:[0-9]+}", ApiUserRequiredActivity(getPostsSince, false)).Methods("GET")
sr.Handle("/post/{post_id:[A-Za-z0-9]+}", ApiUserRequired(getPost)).Methods("GET")
sr.Handle("/post/{post_id:[A-Za-z0-9]+}/delete", ApiUserRequired(deletePost)).Methods("POST")
sr.Handle("/post/{post_id:[A-Za-z0-9]+}/before/{offset:[0-9]+}/{num_posts:[0-9]+}", ApiUserRequired(getPostsBefore)).Methods("GET")
sr.Handle("/post/{post_id:[A-Za-z0-9]+}/after/{offset:[0-9]+}/{num_posts:[0-9]+}", ApiUserRequired(getPostsAfter)).Methods("GET")
}
func createPost(c *Context, w http.ResponseWriter, r *http.Request) {
@@ -812,6 +814,70 @@ func deletePost(c *Context, w http.ResponseWriter, r *http.Request) {
}
}
func getPostsBefore(c *Context, w http.ResponseWriter, r *http.Request) {
getPostsBeforeOrAfter(c, w, r, true)
}
func getPostsAfter(c *Context, w http.ResponseWriter, r *http.Request) {
getPostsBeforeOrAfter(c, w, r, false)
}
func getPostsBeforeOrAfter(c *Context, w http.ResponseWriter, r *http.Request, before bool) {
params := mux.Vars(r)
id := params["id"]
if len(id) != 26 {
c.SetInvalidParam("getPostsBeforeOrAfter", "channelId")
return
}
postId := params["post_id"]
if len(postId) != 26 {
c.SetInvalidParam("getPostsBeforeOrAfter", "postId")
return
}
numPosts, err := strconv.Atoi(params["num_posts"])
if err != nil || numPosts <= 0 {
c.SetInvalidParam("getPostsBeforeOrAfter", "numPosts")
return
}
offset, err := strconv.Atoi(params["offset"])
if err != nil || offset < 0 {
c.SetInvalidParam("getPostsBeforeOrAfter", "offset")
return
}
cchan := Srv.Store.Channel().CheckPermissionsTo(c.Session.TeamId, id, c.Session.UserId)
// We can do better than this etag in this situation
etagChan := Srv.Store.Post().GetEtag(id)
if !c.HasPermissionsToChannel(cchan, "getPostsBeforeOrAfter") {
return
}
etag := (<-etagChan).Data.(string)
if HandleEtag(etag, w, r) {
return
}
var pchan store.StoreChannel
if before {
pchan = Srv.Store.Post().GetPostsBefore(id, postId, numPosts, offset)
} else {
pchan = Srv.Store.Post().GetPostsAfter(id, postId, numPosts, offset)
}
if result := <-pchan; result.Err != nil {
c.Err = result.Err
return
} else {
list := result.Data.(*model.PostList)
w.Header().Set(model.HEADER_ETAG_SERVER, etag)
w.Write([]byte(list.ToJson()))
}
}
func searchPosts(c *Context, w http.ResponseWriter, r *http.Request) {
terms := r.FormValue("terms")

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

@@ -329,6 +329,83 @@ func TestGetPostsSince(t *testing.T) {
}
}
func TestGetPostsBeforeAfter(t *testing.T) {
Setup()
team := &model.Team{DisplayName: "Name", Name: "z-z-" + model.NewId() + "a", Email: "test@nowhere.com", Type: model.TEAM_OPEN}
team = Client.Must(Client.CreateTeam(team)).Data.(*model.Team)
user1 := &model.User{TeamId: team.Id, Email: model.NewId() + "corey@test.com", Nickname: "Corey Hulen", Password: "pwd"}
user1 = Client.Must(Client.CreateUser(user1, "")).Data.(*model.User)
store.Must(Srv.Store.User().VerifyEmail(user1.Id))
Client.LoginByEmail(team.Name, user1.Email, "pwd")
channel1 := &model.Channel{DisplayName: "TestGetPosts", Name: "a" + model.NewId() + "a", Type: model.CHANNEL_OPEN, TeamId: team.Id}
channel1 = Client.Must(Client.CreateChannel(channel1)).Data.(*model.Channel)
time.Sleep(10 * time.Millisecond)
post0 := &model.Post{ChannelId: channel1.Id, Message: "a" + model.NewId() + "a"}
post0 = Client.Must(Client.CreatePost(post0)).Data.(*model.Post)
time.Sleep(10 * time.Millisecond)
post1 := &model.Post{ChannelId: channel1.Id, Message: "a" + model.NewId() + "a"}
post1 = Client.Must(Client.CreatePost(post1)).Data.(*model.Post)
time.Sleep(10 * time.Millisecond)
post1a1 := &model.Post{ChannelId: channel1.Id, Message: "a" + model.NewId() + "a", RootId: post1.Id}
post1a1 = Client.Must(Client.CreatePost(post1a1)).Data.(*model.Post)
time.Sleep(10 * time.Millisecond)
post2 := &model.Post{ChannelId: channel1.Id, Message: "a" + model.NewId() + "a"}
post2 = Client.Must(Client.CreatePost(post2)).Data.(*model.Post)
time.Sleep(10 * time.Millisecond)
post3 := &model.Post{ChannelId: channel1.Id, Message: "a" + model.NewId() + "a"}
post3 = Client.Must(Client.CreatePost(post3)).Data.(*model.Post)
time.Sleep(10 * time.Millisecond)
post3a1 := &model.Post{ChannelId: channel1.Id, Message: "a" + model.NewId() + "a", RootId: post3.Id}
post3a1 = Client.Must(Client.CreatePost(post3a1)).Data.(*model.Post)
r1 := Client.Must(Client.GetPostsBefore(channel1.Id, post1a1.Id, 0, 10, "")).Data.(*model.PostList)
if r1.Order[0] != post1.Id {
t.Fatal("wrong order")
}
if r1.Order[1] != post0.Id {
t.Fatal("wrong order")
}
if len(r1.Posts) != 2 {
t.Fatal("wrong size")
}
r2 := Client.Must(Client.GetPostsAfter(channel1.Id, post3a1.Id, 0, 3, "")).Data.(*model.PostList)
if len(r2.Posts) != 0 {
t.Fatal("should have been empty")
}
post2.Message = "new message"
Client.Must(Client.UpdatePost(post2))
r3 := Client.Must(Client.GetPostsAfter(channel1.Id, post1a1.Id, 0, 2, "")).Data.(*model.PostList)
if r3.Order[0] != post3.Id {
t.Fatal("wrong order")
}
if r3.Order[1] != post2.Id {
t.Fatal("wrong order")
}
if len(r3.Order) != 2 {
t.Fatal("missing post update")
}
}
func TestSearchPosts(t *testing.T) {
Setup()

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

@@ -618,6 +618,24 @@ func (c *Client) GetPostsSince(channelId string, time int64) (*Result, *AppError
}
}
func (c *Client) GetPostsBefore(channelId string, postid string, offset int, limit int, etag string) (*Result, *AppError) {
if r, err := c.DoApiGet(fmt.Sprintf("/channels/%v/post/%v/before/%v/%v", channelId, postid, offset, limit), "", etag); err != nil {
return nil, err
} else {
return &Result{r.Header.Get(HEADER_REQUEST_ID),
r.Header.Get(HEADER_ETAG_SERVER), PostListFromJson(r.Body)}, nil
}
}
func (c *Client) GetPostsAfter(channelId string, postid string, offset int, limit int, etag string) (*Result, *AppError) {
if r, err := c.DoApiGet(fmt.Sprintf("/channels/%v/post/%v/after/%v/%v", channelId, postid, offset, limit), "", etag); err != nil {
return nil, err
} else {
return &Result{r.Header.Get(HEADER_REQUEST_ID),
r.Header.Get(HEADER_ETAG_SERVER), PostListFromJson(r.Body)}, nil
}
}
func (c *Client) GetPost(channelId string, postId string, etag string) (*Result, *AppError) {
if r, err := c.DoApiGet(fmt.Sprintf("/channels/%v/post/%v", channelId, postId), "", etag); err != nil {
return nil, err

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

@@ -332,6 +332,104 @@ func (s SqlPostStore) GetPostsSince(channelId string, time int64) StoreChannel {
return storeChannel
}
func (s SqlPostStore) GetPostsBefore(channelId string, postId string, numPosts int, offset int) StoreChannel {
return s.getPostsAround(channelId, postId, numPosts, offset, true)
}
func (s SqlPostStore) GetPostsAfter(channelId string, postId string, numPosts int, offset int) StoreChannel {
return s.getPostsAround(channelId, postId, numPosts, offset, false)
}
func (s SqlPostStore) getPostsAround(channelId string, postId string, numPosts int, offset int, before bool) StoreChannel {
storeChannel := make(StoreChannel)
go func() {
result := StoreResult{}
var direction string
var sort string
if before {
direction = "<"
sort = "DESC"
} else {
direction = ">"
sort = "ASC"
}
var posts []*model.Post
var parents []*model.Post
_, err1 := s.GetReplica().Select(&posts,
`(SELECT
*
FROM
Posts
WHERE
(CreateAt `+direction+` (SELECT CreateAt FROM Posts WHERE Id = :PostId)
AND ChannelId = :ChannelId
AND DeleteAt = 0)
ORDER BY CreateAt `+sort+`
LIMIT :NumPosts
OFFSET :Offset)`,
map[string]interface{}{"ChannelId": channelId, "PostId": postId, "NumPosts": numPosts, "Offset": offset})
_, err2 := s.GetReplica().Select(&parents,
`(SELECT
*
FROM
Posts
WHERE
Id
IN
(SELECT * FROM (SELECT
RootId
FROM
Posts
WHERE
(CreateAt `+direction+` (SELECT CreateAt FROM Posts WHERE Id = :PostId)
AND ChannelId = :ChannelId
AND DeleteAt = 0)
ORDER BY CreateAt `+sort+`
LIMIT :NumPosts
OFFSET :Offset)
temp_tab))
ORDER BY CreateAt DESC`,
map[string]interface{}{"ChannelId": channelId, "PostId": postId, "NumPosts": numPosts, "Offset": offset})
if err1 != nil {
result.Err = model.NewAppError("SqlPostStore.GetPostContext", "We couldn't get the posts for the channel", "channelId="+channelId+err1.Error())
} else if err2 != nil {
result.Err = model.NewAppError("SqlPostStore.GetPostContext", "We couldn't get the parent posts for the channel", "channelId="+channelId+err2.Error())
} else {
list := &model.PostList{Order: make([]string, 0, len(posts))}
// We need to flip the order if we selected backwards
if before {
for _, p := range posts {
list.AddPost(p)
list.AddOrder(p.Id)
}
} else {
l := len(posts)
for i := range posts {
list.AddPost(posts[l-i-1])
list.AddOrder(posts[l-i-1].Id)
}
}
for _, p := range parents {
list.AddPost(p)
}
result.Data = list
}
storeChannel <- result
close(storeChannel)
}()
return storeChannel
}
func (s SqlPostStore) getRootPosts(channelId string, offset int, limit int) StoreChannel {
storeChannel := make(StoreChannel)

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

@@ -383,6 +383,111 @@ func TestPostStoreGetPostsWtihDetails(t *testing.T) {
}
}
func TestPostStoreGetPostsBeforeAfter(t *testing.T) {
Setup()
o0 := &model.Post{}
o0.ChannelId = model.NewId()
o0.UserId = model.NewId()
o0.Message = "a" + model.NewId() + "b"
o0 = (<-store.Post().Save(o0)).Data.(*model.Post)
time.Sleep(2 * time.Millisecond)
o1 := &model.Post{}
o1.ChannelId = model.NewId()
o1.UserId = model.NewId()
o1.Message = "a" + model.NewId() + "b"
o1 = (<-store.Post().Save(o1)).Data.(*model.Post)
time.Sleep(2 * time.Millisecond)
o2 := &model.Post{}
o2.ChannelId = o1.ChannelId
o2.UserId = model.NewId()
o2.Message = "a" + model.NewId() + "b"
o2.ParentId = o1.Id
o2.RootId = o1.Id
o2 = (<-store.Post().Save(o2)).Data.(*model.Post)
time.Sleep(2 * time.Millisecond)
o2a := &model.Post{}
o2a.ChannelId = o1.ChannelId
o2a.UserId = model.NewId()
o2a.Message = "a" + model.NewId() + "b"
o2a.ParentId = o1.Id
o2a.RootId = o1.Id
o2a = (<-store.Post().Save(o2a)).Data.(*model.Post)
time.Sleep(2 * time.Millisecond)
o3 := &model.Post{}
o3.ChannelId = o1.ChannelId
o3.UserId = model.NewId()
o3.Message = "a" + model.NewId() + "b"
o3.ParentId = o1.Id
o3.RootId = o1.Id
o3 = (<-store.Post().Save(o3)).Data.(*model.Post)
time.Sleep(2 * time.Millisecond)
o4 := &model.Post{}
o4.ChannelId = o1.ChannelId
o4.UserId = model.NewId()
o4.Message = "a" + model.NewId() + "b"
o4 = (<-store.Post().Save(o4)).Data.(*model.Post)
time.Sleep(2 * time.Millisecond)
o5 := &model.Post{}
o5.ChannelId = o1.ChannelId
o5.UserId = model.NewId()
o5.Message = "a" + model.NewId() + "b"
o5.ParentId = o4.Id
o5.RootId = o4.Id
o5 = (<-store.Post().Save(o5)).Data.(*model.Post)
r1 := (<-store.Post().GetPostsBefore(o1.ChannelId, o1.Id, 4, 0)).Data.(*model.PostList)
if len(r1.Posts) != 0 {
t.Fatal("Wrong size")
}
r2 := (<-store.Post().GetPostsAfter(o1.ChannelId, o1.Id, 4, 0)).Data.(*model.PostList)
if r2.Order[0] != o4.Id {
t.Fatal("invalid order")
}
if r2.Order[1] != o3.Id {
t.Fatal("invalid order")
}
if r2.Order[2] != o2a.Id {
t.Fatal("invalid order")
}
if r2.Order[3] != o2.Id {
t.Fatal("invalid order")
}
if len(r2.Posts) != 5 {
t.Fatal("wrong size")
}
r3 := (<-store.Post().GetPostsBefore(o3.ChannelId, o3.Id, 2, 0)).Data.(*model.PostList)
if r3.Order[0] != o2a.Id {
t.Fatal("invalid order")
}
if r3.Order[1] != o2.Id {
t.Fatal("invalid order")
}
if len(r3.Posts) != 3 {
t.Fatal("wrong size")
}
if r3.Posts[o1.Id].Message != o1.Message {
t.Fatal("Missing parent")
}
}
func TestPostStoreGetPostsSince(t *testing.T) {
Setup()
o0 := &model.Post{}

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

@@ -86,6 +86,8 @@ type PostStore interface {
Get(id string) StoreChannel
Delete(postId string, time int64) StoreChannel
GetPosts(channelId string, offset int, limit int) StoreChannel
GetPostsBefore(channelId string, postId string, numPosts int, offset int) StoreChannel
GetPostsAfter(channelId string, postId string, numPosts int, offset int) StoreChannel
GetPostsSince(channelId string, time int64) StoreChannel
GetEtag(channelId string) StoreChannel
Search(teamId string, userId string, params *model.SearchParams) StoreChannel