APIv4 put /posts/{post_id}/patch (#5883)
* APIv4 put /posts/{post_id}/patch
* Add props and edit permission
Этот коммит содержится в:
коммит произвёл
Corey Hulen
родитель
31830ffc7b
Коммит
2f15523fe8
28
api4/post.go
28
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 {
|
||||
|
||||
@@ -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()
|
||||
|
||||
35
app/post.go
35
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
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
Ссылка в новой задаче
Block a user