From ecb10ed62fdff179e34f82b0ff2569da8390f4ad Mon Sep 17 00:00:00 2001 From: Saturnino Abril Date: Sat, 22 Apr 2017 21:52:03 +0900 Subject: APIv4 DELETE /users/{user_id}/posts/{post_id}/reactions/name (#6117) * APIv4 DELETE /users/{user_id}/posts/{post_id}/reactions/name * updated v3 deleteReaction endpoint * update parameter of app.DeleteReactionForPost() * update utils.IsValidAlphaNum, add utils.IsValidAlphaNumHyphenUnderscore, and add related tests --- api4/api.go | 6 +++ api4/context.go | 16 ++++++- api4/params.go | 5 ++ api4/reaction.go | 42 +++++++++++++++++ api4/reaction_test.go | 124 ++++++++++++++++++++++++++++++++++++++++++++++++++ 5 files changed, 191 insertions(+), 2 deletions(-) (limited to 'api4') diff --git a/api4/api.go b/api4/api.go index fd9b679d2..e25494f93 100644 --- a/api4/api.go +++ b/api4/api.go @@ -48,6 +48,7 @@ type Routes struct { Post *mux.Router // 'api/v4/posts/{post_id:[A-Za-z0-9]+}' PostsForChannel *mux.Router // 'api/v4/channels/{channel_id:[A-Za-z0-9]+}/posts' PostsForUser *mux.Router // 'api/v4/users/{user_id:[A-Za-z0-9]+}/posts' + PostForUser *mux.Router // 'api/v4/users/{user_id:[A-Za-z0-9]+}/posts/{post_id:[A-Za-z0-9]+}' Files *mux.Router // 'api/v4/files' File *mux.Router // 'api/v4/files/{file_id:[A-Za-z0-9]+}' @@ -89,6 +90,8 @@ type Routes struct { Emojis *mux.Router // 'api/v4/emoji' Emoji *mux.Router // 'api/v4/emoji/{emoji_id:[A-Za-z0-9]+}' + ReactionByNameForPostForUser *mux.Router // 'api/v4/users/{user_id:[A-Za-z0-9]+}/posts/{post_id:[A-Za-z0-9]+}/reactions/{emoji_name:[A-Za-z0-9_-]+}' + Webrtc *mux.Router // 'api/v4/webrtc' } @@ -132,6 +135,7 @@ func InitApi(full bool) { BaseRoutes.Post = BaseRoutes.Posts.PathPrefix("/{post_id:[A-Za-z0-9]+}").Subrouter() BaseRoutes.PostsForChannel = BaseRoutes.Channel.PathPrefix("/posts").Subrouter() BaseRoutes.PostsForUser = BaseRoutes.User.PathPrefix("/posts").Subrouter() + BaseRoutes.PostForUser = BaseRoutes.PostsForUser.PathPrefix("/{post_id:[A-Za-z0-9]+}").Subrouter() BaseRoutes.Files = BaseRoutes.ApiRoot.PathPrefix("/files").Subrouter() BaseRoutes.File = BaseRoutes.Files.PathPrefix("/{file_id:[A-Za-z0-9]+}").Subrouter() @@ -166,6 +170,8 @@ func InitApi(full bool) { BaseRoutes.Emojis = BaseRoutes.ApiRoot.PathPrefix("/emoji").Subrouter() BaseRoutes.Emoji = BaseRoutes.Emojis.PathPrefix("/{emoji_id:[A-Za-z0-9]+}").Subrouter() + BaseRoutes.ReactionByNameForPostForUser = BaseRoutes.PostForUser.PathPrefix("/reactions/{emoji_name:[A-Za-z0-9_-]+}").Subrouter() + BaseRoutes.Webrtc = BaseRoutes.ApiRoot.PathPrefix("/webrtc").Subrouter() InitUser() diff --git a/api4/context.go b/api4/context.go index f492f2b99..c7fba4f5f 100644 --- a/api4/context.go +++ b/api4/context.go @@ -468,7 +468,7 @@ func (c *Context) RequireCategory() *Context { return c } - if !model.IsValidAlphaNum(c.Params.Category, true) { + if !model.IsValidAlphaNumHyphenUnderscore(c.Params.Category, true) { c.SetInvalidUrlParam("category") } @@ -492,13 +492,25 @@ func (c *Context) RequirePreferenceName() *Context { return c } - if !model.IsValidAlphaNum(c.Params.PreferenceName, true) { + if !model.IsValidAlphaNumHyphenUnderscore(c.Params.PreferenceName, true) { c.SetInvalidUrlParam("preference_name") } return c } +func (c *Context) RequireEmojiName() *Context { + if c.Err != nil { + return c + } + + if len(c.Params.EmojiName) == 0 || len(c.Params.EmojiName) > 64 || !model.IsValidAlphaNumHyphenUnderscore(c.Params.EmojiName, false) { + c.SetInvalidUrlParam("emoji_name") + } + + return c +} + func (c *Context) RequireHookId() *Context { if c.Err != nil { return c diff --git a/api4/params.go b/api4/params.go index a1c829f1c..5febf06fb 100644 --- a/api4/params.go +++ b/api4/params.go @@ -32,6 +32,7 @@ type ApiParams struct { TeamName string ChannelName string PreferenceName string + EmojiName string Category string Service string Page int @@ -111,6 +112,10 @@ func ApiParamsFromRequest(r *http.Request) *ApiParams { params.PreferenceName = val } + if val, ok := props["emoji_name"]; ok { + params.EmojiName = val + } + if val, err := strconv.Atoi(r.URL.Query().Get("page")); err != nil || val < 0 { params.Page = PAGE_DEFAULT } else { diff --git a/api4/reaction.go b/api4/reaction.go index 7d5952eea..6605eb070 100644 --- a/api4/reaction.go +++ b/api4/reaction.go @@ -17,6 +17,7 @@ func InitReaction() { BaseRoutes.Reactions.Handle("", ApiSessionRequired(saveReaction)).Methods("POST") BaseRoutes.Post.Handle("/reactions", ApiSessionRequired(getReactions)).Methods("GET") + BaseRoutes.ReactionByNameForPostForUser.Handle("", ApiSessionRequired(deleteReaction)).Methods("DELETE") } func saveReaction(c *Context, w http.ResponseWriter, r *http.Request) { @@ -71,3 +72,44 @@ func getReactions(c *Context, w http.ResponseWriter, r *http.Request) { return } } + +func deleteReaction(c *Context, w http.ResponseWriter, r *http.Request) { + c.RequireUserId() + if c.Err != nil { + return + } + + c.RequirePostId() + if c.Err != nil { + return + } + + c.RequireEmojiName() + if c.Err != nil { + return + } + + if !app.SessionHasPermissionToChannelByPost(c.Session, c.Params.PostId, model.PERMISSION_READ_CHANNEL) { + c.SetPermissionError(model.PERMISSION_READ_CHANNEL) + return + } + + if c.Params.UserId != c.Session.UserId && !app.SessionHasPermissionTo(c.Session, model.PERMISSION_MANAGE_SYSTEM) { + c.SetPermissionError(model.PERMISSION_MANAGE_SYSTEM) + return + } + + reaction := &model.Reaction{ + UserId: c.Params.UserId, + PostId: c.Params.PostId, + EmojiName: c.Params.EmojiName, + } + + err := app.DeleteReactionForPost(reaction) + if err != nil { + c.Err = err + return + } + + ReturnStatusOK(w) +} diff --git a/api4/reaction_test.go b/api4/reaction_test.go index 980a96d68..b80c96118 100644 --- a/api4/reaction_test.go +++ b/api4/reaction_test.go @@ -193,3 +193,127 @@ func TestGetReactions(t *testing.T) { _, resp = th.SystemAdminClient.GetReactions(postId) CheckNoError(t, resp) } + +func TestDeleteReaction(t *testing.T) { + th := Setup().InitBasic().InitSystemAdmin() + defer TearDown() + Client := th.Client + userId := th.BasicUser.Id + user2Id := th.BasicUser2.Id + postId := th.BasicPost.Id + + r1 := &model.Reaction{ + UserId: userId, + PostId: postId, + EmojiName: "smile", + } + + app.SaveReactionForPost(r1) + if reactions, err := app.GetReactionsForPost(postId); err != nil || len(reactions) != 1 { + t.Fatal("didn't save reaction correctly") + } + + ok, resp := Client.DeleteReaction(r1) + CheckNoError(t, resp) + + if !ok { + t.Fatal("should have returned true") + } + + if reactions, err := app.GetReactionsForPost(postId); err != nil || len(reactions) != 0 { + t.Fatal("should have deleted reaction") + } + + // deleting one reaction when a post has multiple reactions + r2 := &model.Reaction{ + UserId: userId, + PostId: postId, + EmojiName: "smile-", + } + + app.SaveReactionForPost(r1) + app.SaveReactionForPost(r2) + if reactions, err := app.GetReactionsForPost(postId); err != nil || len(reactions) != 2 { + t.Fatal("didn't save reactions correctly") + } + + _, resp = Client.DeleteReaction(r2) + CheckNoError(t, resp) + + if reactions, err := app.GetReactionsForPost(postId); err != nil || len(reactions) != 1 || *reactions[0] != *r1 { + t.Fatal("should have deleted 1 reaction only") + } + + // deleting a reaction made by another user + r3 := &model.Reaction{ + UserId: user2Id, + PostId: postId, + EmojiName: "smile_", + } + + th.LoginBasic2() + app.SaveReactionForPost(r3) + if reactions, err := app.GetReactionsForPost(postId); err != nil || len(reactions) != 2 { + t.Fatal("didn't save reaction correctly") + } + + th.LoginBasic() + + ok, resp = Client.DeleteReaction(r3) + CheckForbiddenStatus(t, resp) + + if ok { + t.Fatal("should have returned false") + } + + if reactions, err := app.GetReactionsForPost(postId); err != nil || len(reactions) != 2 { + t.Fatal("should have not deleted a reaction") + } + + r1.PostId = GenerateTestId() + _, resp = Client.DeleteReaction(r1) + CheckForbiddenStatus(t, resp) + + r1.PostId = "junk" + + _, resp = Client.DeleteReaction(r1) + CheckBadRequestStatus(t, resp) + + r1.PostId = postId + r1.UserId = GenerateTestId() + + _, resp = Client.DeleteReaction(r1) + CheckForbiddenStatus(t, resp) + + r1.UserId = "junk" + + _, resp = Client.DeleteReaction(r1) + CheckBadRequestStatus(t, resp) + + r1.UserId = userId + r1.EmojiName = "" + + _, resp = Client.DeleteReaction(r1) + CheckNotFoundStatus(t, resp) + + r1.EmojiName = strings.Repeat("a", 65) + + _, resp = Client.DeleteReaction(r1) + CheckBadRequestStatus(t, resp) + + Client.Logout() + r1.EmojiName = "smile" + + _, resp = Client.DeleteReaction(r1) + CheckUnauthorizedStatus(t, resp) + + _, resp = th.SystemAdminClient.DeleteReaction(r1) + CheckNoError(t, resp) + + _, resp = th.SystemAdminClient.DeleteReaction(r3) + CheckNoError(t, resp) + + if reactions, err := app.GetReactionsForPost(postId); err != nil || len(reactions) != 0 { + t.Fatal("should have deleted both reactions") + } +} -- cgit v1.2.3-1-g7c22