Adding ability to fetch posts around a specifc post

Этот коммит содержится в:
Christopher Speller
2015-11-03 11:34:52 -05:00
родитель 65acb1b201
Коммит 188182a3a4
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