Commit 2f15523f authored by Saturnino Abril's avatar Saturnino Abril Committed by Corey Hulen
Browse files

APIv4 put /posts/{post_id}/patch (#5883)

* APIv4 put /posts/{post_id}/patch

* Add props and edit permission
parent 31830ffc
......@@ -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 {
......
......@@ -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()
......
......@@ -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
......
......@@ -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 {
......
......@@ -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
}
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment