summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--api4/post.go28
-rw-r--r--api4/post_test.go102
-rw-r--r--app/post.go35
-rw-r--r--model/client4.go10
-rw-r--r--model/post.go50
5 files changed, 221 insertions, 4 deletions
diff --git a/api4/post.go b/api4/post.go
index 329241139..af5fc8cfa 100644
--- a/api4/post.go
+++ b/api4/post.go
@@ -25,6 +25,7 @@ func InitPost() {
BaseRoutes.Team.Handle("/posts/search", ApiSessionRequired(searchPosts)).Methods("POST")
BaseRoutes.Post.Handle("", ApiSessionRequired(updatePost)).Methods("PUT")
+ BaseRoutes.Post.Handle("/patch", ApiSessionRequired(patchPost)).Methods("PUT")
}
func createPost(c *Context, w http.ResponseWriter, r *http.Request) {
@@ -245,6 +246,33 @@ func updatePost(c *Context, w http.ResponseWriter, r *http.Request) {
w.Write([]byte(rpost.ToJson()))
}
+func patchPost(c *Context, w http.ResponseWriter, r *http.Request) {
+ c.RequirePostId()
+ if c.Err != nil {
+ return
+ }
+
+ post := model.PostPatchFromJson(r.Body)
+
+ if post == nil {
+ c.SetInvalidParam("post")
+ return
+ }
+
+ if !app.SessionHasPermissionToPost(c.Session, c.Params.PostId, model.PERMISSION_EDIT_OTHERS_POSTS) {
+ c.SetPermissionError(model.PERMISSION_EDIT_OTHERS_POSTS)
+ return
+ }
+
+ patchedPost, err := app.PatchPost(c.Params.PostId, post)
+ if err != nil {
+ c.Err = err
+ return
+ }
+
+ w.Write([]byte(patchedPost.ToJson()))
+}
+
func getFileInfosForPost(c *Context, w http.ResponseWriter, r *http.Request) {
c.RequirePostId()
if c.Err != nil {
diff --git a/api4/post_test.go b/api4/post_test.go
index 9e0880004..8f954efaa 100644
--- a/api4/post_test.go
+++ b/api4/post_test.go
@@ -5,6 +5,7 @@ package api4
import (
"net/http"
+ "reflect"
"strconv"
"testing"
"time"
@@ -167,6 +168,107 @@ func TestUpdatePost(t *testing.T) {
CheckUnauthorizedStatus(t, resp)
}
+func TestPatchPost(t *testing.T) {
+ th := Setup().InitBasic().InitSystemAdmin()
+ defer TearDown()
+ Client := th.Client
+ channel := th.BasicChannel
+
+ isLicensed := utils.IsLicensed
+ license := utils.License
+ allowEditPost := *utils.Cfg.ServiceSettings.AllowEditPost
+ defer func() {
+ utils.IsLicensed = isLicensed
+ utils.License = license
+ *utils.Cfg.ServiceSettings.AllowEditPost = allowEditPost
+ utils.SetDefaultRolesBasedOnConfig()
+ }()
+ utils.IsLicensed = true
+ utils.License = &model.License{Features: &model.Features{}}
+ utils.License.Features.SetDefaults()
+
+ *utils.Cfg.ServiceSettings.AllowEditPost = model.ALLOW_EDIT_POST_ALWAYS
+ utils.SetDefaultRolesBasedOnConfig()
+
+ post := &model.Post{
+ ChannelId: channel.Id,
+ IsPinned: true,
+ Message: "#hashtag a message",
+ Props: model.StringInterface{"channel_header": "old_header"},
+ FileIds: model.StringArray{"file1", "file2"},
+ HasReactions: true,
+ }
+ post, _ = Client.CreatePost(post)
+
+ patch := &model.PostPatch{}
+
+ patch.IsPinned = new(bool)
+ *patch.IsPinned = false
+ patch.Message = new(string)
+ *patch.Message = "#otherhashtag other message"
+ patch.Props = new(model.StringInterface)
+ *patch.Props = model.StringInterface{"channel_header": "new_header"}
+ patch.FileIds = new(model.StringArray)
+ *patch.FileIds = model.StringArray{"file1", "otherfile2", "otherfile3"}
+ patch.HasReactions = new(bool)
+ *patch.HasReactions = false
+
+ rpost, resp := Client.PatchPost(post.Id, patch)
+ CheckNoError(t, resp)
+
+ if rpost.IsPinned != false {
+ t.Fatal("IsPinned did not update properly")
+ }
+ if rpost.Message != "#otherhashtag other message" {
+ t.Fatal("Message did not update properly")
+ }
+ if len(rpost.Props) != 1 {
+ t.Fatal("Props did not update properly")
+ }
+ if !reflect.DeepEqual(rpost.Props, *patch.Props) {
+ t.Fatal("Props did not update properly")
+ }
+ if rpost.Hashtags != "#otherhashtag" {
+ t.Fatal("Message did not update properly")
+ }
+ if len(rpost.FileIds) != 3 {
+ t.Fatal("FileIds did not update properly")
+ }
+ if !reflect.DeepEqual(rpost.FileIds, *patch.FileIds) {
+ t.Fatal("FileIds did not update properly")
+ }
+ if rpost.HasReactions != false {
+ t.Fatal("HasReactions did not update properly")
+ }
+
+ if r, err := Client.DoApiPut("/posts/"+post.Id+"/patch", "garbage"); err == nil {
+ t.Fatal("should have errored")
+ } else {
+ if r.StatusCode != http.StatusBadRequest {
+ t.Log("actual: " + strconv.Itoa(r.StatusCode))
+ t.Log("expected: " + strconv.Itoa(http.StatusBadRequest))
+ t.Fatal("wrong status code")
+ }
+ }
+
+ _, resp = Client.PatchPost("junk", patch)
+ CheckBadRequestStatus(t, resp)
+
+ _, resp = Client.PatchPost(GenerateTestId(), patch)
+ CheckForbiddenStatus(t, resp)
+
+ Client.Logout()
+ _, resp = Client.PatchPost(post.Id, patch)
+ CheckUnauthorizedStatus(t, resp)
+
+ th.LoginTeamAdmin()
+ _, resp = Client.PatchPost(post.Id, patch)
+ CheckNoError(t, resp)
+
+ _, resp = th.SystemAdminClient.PatchPost(post.Id, patch)
+ CheckNoError(t, resp)
+}
+
func TestGetPostsForChannel(t *testing.T) {
th := Setup().InitBasic().InitSystemAdmin()
defer TearDown()
diff --git a/app/post.go b/app/post.go
index bfd0fccc5..375b775e8 100644
--- a/app/post.go
+++ b/app/post.go
@@ -299,18 +299,19 @@ func UpdatePost(post *model.Post) (*model.Post, *model.AppError) {
*newPost = *oldPost
newPost.Message = post.Message
+ newPost.Props = post.Props
newPost.EditAt = model.GetMillis()
newPost.Hashtags, _ = model.ParseHashtags(post.Message)
+ newPost.IsPinned = post.IsPinned
+ newPost.HasReactions = post.HasReactions
+ newPost.FileIds = post.FileIds
if result := <-Srv.Store.Post().Update(newPost, oldPost); result.Err != nil {
return nil, result.Err
} else {
rpost := result.Data.(*model.Post)
- message := model.NewWebSocketEvent(model.WEBSOCKET_EVENT_POST_EDITED, "", rpost.ChannelId, "", nil)
- message.Add("post", rpost.ToJson())
-
- go Publish(message)
+ sendUpdatedPostEvent(rpost)
InvalidateCacheForChannelPosts(rpost.ChannelId)
@@ -318,6 +319,32 @@ func UpdatePost(post *model.Post) (*model.Post, *model.AppError) {
}
}
+func PatchPost(postId string, patch *model.PostPatch) (*model.Post, *model.AppError) {
+ post, err := GetSinglePost(postId)
+ if err != nil {
+ return nil, err
+ }
+
+ post.Patch(patch)
+
+ updatedPost, err := UpdatePost(post)
+ if err != nil {
+ return nil, err
+ }
+
+ sendUpdatedPostEvent(updatedPost)
+ InvalidateCacheForChannelPosts(updatedPost.ChannelId)
+
+ return updatedPost, nil
+}
+
+func sendUpdatedPostEvent(post *model.Post) {
+ message := model.NewWebSocketEvent(model.WEBSOCKET_EVENT_POST_EDITED, "", post.ChannelId, "", nil)
+ message.Add("post", post.ToJson())
+
+ go Publish(message)
+}
+
func GetPostsPage(channelId string, page int, perPage int) (*model.PostList, *model.AppError) {
if result := <-Srv.Store.Post().GetPosts(channelId, page*perPage, perPage, true); result.Err != nil {
return nil, result.Err
diff --git a/model/client4.go b/model/client4.go
index 72d8951b9..5259cb915 100644
--- a/model/client4.go
+++ b/model/client4.go
@@ -1193,6 +1193,16 @@ func (c *Client4) UpdatePost(postId string, post *Post) (*Post, *Response) {
}
}
+// PatchPost partially updates a post. Any missing fields are not updated.
+func (c *Client4) PatchPost(postId string, patch *PostPatch) (*Post, *Response) {
+ if r, err := c.DoApiPut(c.GetPostRoute(postId)+"/patch", patch.ToJson()); err != nil {
+ return nil, &Response{StatusCode: r.StatusCode, Error: err}
+ } else {
+ defer closeBody(r)
+ return PostFromJson(r.Body), BuildResponse(r)
+ }
+}
+
// GetPost gets a single post.
func (c *Client4) GetPost(postId string, etag string) (*Post, *Response) {
if r, err := c.DoApiGet(c.GetPostRoute(postId), etag); err != nil {
diff --git a/model/post.go b/model/post.go
index c419deb56..0d9651924 100644
--- a/model/post.go
+++ b/model/post.go
@@ -54,6 +54,14 @@ type Post struct {
HasReactions bool `json:"has_reactions,omitempty"`
}
+type PostPatch struct {
+ IsPinned *bool `json:"is_pinned"`
+ Message *string `json:"message"`
+ Props *StringInterface `json:"props"`
+ FileIds *StringArray `json:"file_ids"`
+ HasReactions *bool `json:"has_reactions"`
+}
+
func (o *Post) ToJson() string {
b, err := json.Marshal(o)
if err != nil {
@@ -190,3 +198,45 @@ func (o *Post) AddProp(key string, value interface{}) {
func (o *Post) IsSystemMessage() bool {
return len(o.Type) >= len(POST_SYSTEM_MESSAGE_PREFIX) && o.Type[:len(POST_SYSTEM_MESSAGE_PREFIX)] == POST_SYSTEM_MESSAGE_PREFIX
}
+
+func (p *Post) Patch(patch *PostPatch) {
+ if patch.IsPinned != nil {
+ p.IsPinned = *patch.IsPinned
+ }
+
+ if patch.Message != nil {
+ p.Message = *patch.Message
+ }
+
+ if patch.Props != nil {
+ p.Props = *patch.Props
+ }
+
+ if patch.FileIds != nil {
+ p.FileIds = *patch.FileIds
+ }
+
+ if patch.HasReactions != nil {
+ p.HasReactions = *patch.HasReactions
+ }
+}
+
+func (o *PostPatch) ToJson() string {
+ b, err := json.Marshal(o)
+ if err != nil {
+ return ""
+ }
+
+ return string(b)
+}
+
+func PostPatchFromJson(data io.Reader) *PostPatch {
+ decoder := json.NewDecoder(data)
+ var post PostPatch
+ err := decoder.Decode(&post)
+ if err != nil {
+ return nil
+ }
+
+ return &post
+}