From acfce21724ee350075854bb2c86e30f0cc7debc8 Mon Sep 17 00:00:00 2001 From: Umputun Date: Wed, 3 Jan 2018 13:34:18 -0600 Subject: [PATCH] add comment update (edit once) --- app/rest/server.go | 45 +++++++++++++++++++++++++++++++++++++++ app/store/bolt_test.go | 3 +-- app/store/service.go | 25 +++++++++++++++++++++- app/store/service_test.go | 21 ++++++++++++++++++ app/store/store.go | 7 ++++++ remark.rest | 12 +++++++++++ 6 files changed, 110 insertions(+), 3 deletions(-) diff --git a/app/rest/server.go b/app/rest/server.go index 6c830e32..9a863e20 100644 --- a/app/rest/server.go +++ b/app/rest/server.go @@ -89,6 +89,7 @@ func (s *Server) Run() { // protected routes, require auth rapi.With(auth.Auth(s.SessionStore, s.Admins, maybeDevMode(auth.Full))).Group(func(rauth chi.Router) { rauth.Post("/comment", s.createCommentCtrl) + rauth.Put("/comment/{id}", s.updateCommentCtrl) rauth.Get("/user", s.userInfoCtrl) rauth.Put("/vote/{id}", s.voteCtrl) @@ -148,6 +149,50 @@ func (s *Server) createCommentCtrl(w http.ResponseWriter, r *http.Request) { render.JSON(w, r, JSON{"id": id, "loc": comment.Locator}) } +// PUT /comment/{id}?site=siteID&url=post-url - update comment +func (s *Server) updateCommentCtrl(w http.ResponseWriter, r *http.Request) { + + edit := struct { + Text string + Summary string + }{} + + if err := render.DecodeJSON(r.Body, &edit); err != nil { + common.SendErrorJSON(w, r, http.StatusBadRequest, err, "can't bind comment") + return + } + + user, err := common.GetUserInfo(r) + if err != nil { // this not suppose to happen (handled by Auth), just dbl-check + common.SendErrorJSON(w, r, http.StatusUnauthorized, err, "can't get user info") + return + } + locator := store.Locator{SiteID: r.URL.Query().Get("site"), URL: r.URL.Query().Get("url")} + id := chi.URLParam(r, "id") + log.Printf("[DEBUG] update comment %s, %+v", id, edit) + + var currComment store.Comment + if currComment, err = s.DataService.Get(locator, id); err != nil { + common.SendErrorJSON(w, r, http.StatusBadRequest, err, "can't find comment") + return + } + + if currComment.User.ID != user.ID { + common.SendErrorJSON(w, r, http.StatusForbidden, errors.New("rejected"), "can not edit comments for other users") + return + } + + res, err := s.DataService.EditComment(locator, id, edit.Text, store.Edit{Summary: edit.Summary}) + if err != nil { + common.SendErrorJSON(w, r, http.StatusBadRequest, err, "can't update comment") + return + } + + s.respCache.Flush() // reset all caches + + render.JSON(w, r, JSON{"id": id, "loc": locator, "comment": res}) +} + // DELETE /comment/{id}?site=siteID&url=post-url func (s *Server) deleteCommentCtrl(w http.ResponseWriter, r *http.Request) { diff --git a/app/store/bolt_test.go b/app/store/bolt_test.go index 9c663ee9..7e407d8a 100644 --- a/app/store/bolt_test.go +++ b/app/store/bolt_test.go @@ -11,9 +11,8 @@ import ( var testDb = "/tmp/test-remark.db" func TestBoltDB_CreateAndFind(t *testing.T) { - var b Interface + var b Interface = prep(t) defer os.Remove(testDb) - b = prep(t) res, err := b.Find(Request{Locator: Locator{URL: "https://radio-t.com", SiteID: "radio-t"}}) assert.Nil(t, err) diff --git a/app/store/service.go b/app/store/service.go index e3d361a0..4be80327 100644 --- a/app/store/service.go +++ b/app/store/service.go @@ -1,6 +1,10 @@ package store -import "github.com/pkg/errors" +import ( + "time" + + "github.com/pkg/errors" +) // Service wraps store.Interface with additional methods type Service struct { @@ -39,3 +43,22 @@ func (s *Service) Vote(locator Locator, commentID string, userID string, val boo return comment, s.Put(locator, comment) } + +// EditComment to edit text and update Edit info +func (s *Service) EditComment(locator Locator, commentID string, text string, edit Edit) (comment Comment, err error) { + comment, err = s.Get(locator, commentID) + if err != nil { + return comment, err + } + // edit allowed only once + if !comment.Edit.Timestamp.IsZero() { + return comment, errors.Errorf("comment %s already edited at %s", commentID, comment.Edit.Timestamp) + } + + comment.Text = text + comment.Edit = edit + comment.Edit.Timestamp = time.Now() + comment = sanitizeComment(comment) + err = s.Put(locator, comment) + return comment, err +} diff --git a/app/store/service_test.go b/app/store/service_test.go index 288dcbd3..64b1f7b0 100644 --- a/app/store/service_test.go +++ b/app/store/service_test.go @@ -55,3 +55,24 @@ func TestBoltDB_Pin(t *testing.T) { assert.Nil(t, err) assert.Equal(t, false, c.Pin) } + +func TestBoltDB_EditComment(t *testing.T) { + defer os.Remove(testDb) + b := Service{Interface: prep(t)} + + res, err := b.Last(Locator{URL: "https://radio-t.com", SiteID: "radio-t"}, 0) + t.Logf("%+v", res[0]) + assert.Nil(t, err) + assert.Equal(t, 2, len(res)) + assert.Equal(t, Edit{}, res[0].Edit) + + comment, err := b.EditComment(Locator{URL: "https://radio-t.com", SiteID: "radio-t"}, res[0].ID, "xxx", Edit{Summary: "my edit"}) + assert.Nil(t, err) + assert.Equal(t, "my edit", comment.Edit.Summary) + assert.Equal(t, "xxx", comment.Text) + + c, err := b.GetByID(Locator{URL: "https://radio-t.com", SiteID: "radio-t"}, res[0].ID) + assert.Nil(t, err) + assert.Equal(t, "my edit", c.Edit.Summary) + assert.Equal(t, "xxx", c.Text) +} diff --git a/app/store/store.go b/app/store/store.go index 9b8e033c..a71126b3 100644 --- a/app/store/store.go +++ b/app/store/store.go @@ -25,6 +25,7 @@ type Comment struct { Votes map[string]bool `json:"votes"` Timestamp time.Time `json:"time"` Pin bool `json:"pin,omitempty"` + Edit Edit `json:"edit,omitempty"` } // Locator keeps site and url of the post @@ -44,6 +45,12 @@ type User struct { IP string `json:"-"` } +// Edit indication +type Edit struct { + Timestamp time.Time `json:"time"` + Summary string `json:"summary"` +} + // Request is a container for all finds type Request struct { Locator Locator `json:"locator"` diff --git a/remark.rest b/remark.rest index 87c0defa..2eec031e 100644 --- a/remark.rest +++ b/remark.rest @@ -25,6 +25,15 @@ Content-Type: application/json } } +### update comment +POST https://demo.remark42.com/api/v1/comment/a2ddb8d2f65008ee1a1e3af8df0f26beb042309c?site=remark&url=https://radio-t.com/blah1 +Content-Type: application/json + +{ + "text": "edit comment blah http://radio-t.com 12345", + "summary": "fix blah" +} + ### pin comment PUT https://demo.remark42.com/api/v1/admin/pin/3665976683?site=remark&url=https://radio-t.com/p/2017/12/16/podcast-576/&pin=1 @@ -37,6 +46,9 @@ GET https://demo.remark42.com/api/v1/user ### get comment by id GET https://demo.remark42.com/api/v1/id/3665976683?site=remark&url=https://radio-t.com/p/2017/12/16/podcast-576/ +### get comment by id 2 +GET https://demo.remark42.com/api/v1/id/a2ddb8d2f65008ee1a1e3af8df0f26beb042309c?site=remark&url=https://radio-t.com/blah1 + ### get comment by user id GET https://demo.remark42.com/api/v1/comments?site=remark&user=umputun