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.Team.Handle("/posts/search", ApiSessionRequired(searchPosts)).Methods("POST")
|
||||||
BaseRoutes.Post.Handle("", ApiSessionRequired(updatePost)).Methods("PUT")
|
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) {
|
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()))
|
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) {
|
func getFileInfosForPost(c *Context, w http.ResponseWriter, r *http.Request) {
|
||||||
c.RequirePostId()
|
c.RequirePostId()
|
||||||
if c.Err != nil {
|
if c.Err != nil {
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ package api4
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"net/http"
|
"net/http"
|
||||||
|
"reflect"
|
||||||
"strconv"
|
"strconv"
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
@@ -167,6 +168,107 @@ func TestUpdatePost(t *testing.T) {
|
|||||||
CheckUnauthorizedStatus(t, resp)
|
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) {
|
func TestGetPostsForChannel(t *testing.T) {
|
||||||
th := Setup().InitBasic().InitSystemAdmin()
|
th := Setup().InitBasic().InitSystemAdmin()
|
||||||
defer TearDown()
|
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 = *oldPost
|
||||||
|
|
||||||
newPost.Message = post.Message
|
newPost.Message = post.Message
|
||||||
|
newPost.Props = post.Props
|
||||||
newPost.EditAt = model.GetMillis()
|
newPost.EditAt = model.GetMillis()
|
||||||
newPost.Hashtags, _ = model.ParseHashtags(post.Message)
|
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 {
|
if result := <-Srv.Store.Post().Update(newPost, oldPost); result.Err != nil {
|
||||||
return nil, result.Err
|
return nil, result.Err
|
||||||
} else {
|
} else {
|
||||||
rpost := result.Data.(*model.Post)
|
rpost := result.Data.(*model.Post)
|
||||||
|
|
||||||
message := model.NewWebSocketEvent(model.WEBSOCKET_EVENT_POST_EDITED, "", rpost.ChannelId, "", nil)
|
sendUpdatedPostEvent(rpost)
|
||||||
message.Add("post", rpost.ToJson())
|
|
||||||
|
|
||||||
go Publish(message)
|
|
||||||
|
|
||||||
InvalidateCacheForChannelPosts(rpost.ChannelId)
|
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) {
|
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 {
|
if result := <-Srv.Store.Post().GetPosts(channelId, page*perPage, perPage, true); result.Err != nil {
|
||||||
return nil, result.Err
|
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.
|
// GetPost gets a single post.
|
||||||
func (c *Client4) GetPost(postId string, etag string) (*Post, *Response) {
|
func (c *Client4) GetPost(postId string, etag string) (*Post, *Response) {
|
||||||
if r, err := c.DoApiGet(c.GetPostRoute(postId), etag); err != nil {
|
if r, err := c.DoApiGet(c.GetPostRoute(postId), etag); err != nil {
|
||||||
|
|||||||
@@ -54,6 +54,14 @@ type Post struct {
|
|||||||
HasReactions bool `json:"has_reactions,omitempty"`
|
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 {
|
func (o *Post) ToJson() string {
|
||||||
b, err := json.Marshal(o)
|
b, err := json.Marshal(o)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -190,3 +198,45 @@ func (o *Post) AddProp(key string, value interface{}) {
|
|||||||
func (o *Post) IsSystemMessage() bool {
|
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
|
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