From dff266ec01d9966d8002f096a027a490d26e1b37 Mon Sep 17 00:00:00 2001 From: Umputun Date: Tue, 15 Jan 2019 17:05:30 -0600 Subject: [PATCH] add detection of reply and disable edit for such comments #248 --- backend/app/store/service/service.go | 25 ++++++++++ backend/app/store/service/service_test.go | 58 ++++++++++++++++++++++- 2 files changed, 82 insertions(+), 1 deletion(-) diff --git a/backend/app/store/service/service.go b/backend/app/store/service/service.go index 1603dc89..f2e17e4f 100644 --- a/backend/app/store/service/service.go +++ b/backend/app/store/service/service.go @@ -49,6 +49,7 @@ type PostMetaData struct { } const defaultCommentMaxSize = 2000 +const maxLastCommentsReply = 1000 // UnlimitedVotes doesn't restrict MaxVotes const UnlimitedVotes = -1 @@ -179,6 +180,10 @@ func (s *DataStore) EditComment(locator store.Locator, commentID string, req Edi return comment, errors.Errorf("too late to edit %s", commentID) } + if s.HasReplies(comment) { + return comment, errors.Errorf("parent comment with reply can't be edited, %s", commentID) + } + if req.Delete { // delete request comment.Deleted = true return comment, s.Delete(locator, commentID, store.SoftDelete) @@ -196,6 +201,26 @@ func (s *DataStore) EditComment(locator store.Locator, commentID string, req Edi return comment, err } +// HasReplies checks if there is any reply to the comments +// Loads last maxLastCommentsReply comments and compare parent id to the comment's id +// TODO: add caching? +func (s *DataStore) HasReplies(comment store.Comment) bool { + comments, err := s.Last(comment.Locator.SiteID, maxLastCommentsReply) + if err != nil { + log.Printf("[WARN] can't get last comments for reply check, %v", err) + return false + } + + for _, c := range comments { + if c.ParentID != "" && !c.Deleted && c.User.ID != comment.User.ID { // not interested in replies to yourself and top level + if c.ParentID == comment.ID { + return true + } + } + } + return false +} + // SetTitle puts title from the locator.URL page and overwrites any existing title func (s *DataStore) SetTitle(locator store.Locator, commentID string) (comment store.Comment, err error) { if s.TitleExtractor == nil { diff --git a/backend/app/store/service/service_test.go b/backend/app/store/service/service_test.go index 609e53f9..ee5454c1 100644 --- a/backend/app/store/service/service_test.go +++ b/backend/app/store/service/service_test.go @@ -12,7 +12,7 @@ import ( "testing" "time" - "github.com/coreos/bbolt" + bolt "github.com/coreos/bbolt" "github.com/pkg/errors" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" @@ -404,6 +404,32 @@ func TestService_EditCommentDurationFailed(t *testing.T) { assert.NotNil(t, err) } +func TestService_EditCommentReplyFailed(t *testing.T) { + defer os.Remove(testDb) + b := DataStore{Interface: prepStoreEngine(t), AdminStore: admin.NewStaticKeyStore("secret 123")} + + res, err := b.Last("radio-t", 0) + t.Logf("%+v", res[1]) + assert.Nil(t, err) + assert.Equal(t, 2, len(res)) + assert.Nil(t, res[1].Edit) + + reply := store.Comment{ + ID: "123456", + ParentID: "id-1", + Text: "some text", + Timestamp: time.Date(2017, 12, 20, 15, 18, 22, 0, time.Local), + Locator: store.Locator{URL: "https://radio-t.com", SiteID: "radio-t"}, + User: store.User{ID: "user2", Name: "user name 2"}, + } + _, err = b.Create(reply) + assert.NoError(t, err) + + _, err = b.EditComment(store.Locator{URL: "https://radio-t.com", SiteID: "radio-t"}, res[1].ID, + EditRequest{Orig: "yyy", Text: "xxx", Summary: "my edit"}) + assert.EqualError(t, err, "parent comment with reply can't be edited, id-1") +} + func TestService_ValidateComment(t *testing.T) { b := DataStore{MaxCommentSize: 2000, AdminStore: admin.NewStaticKeyStore("secret 123")} @@ -523,6 +549,36 @@ func TestService_IsAdmin(t *testing.T) { assert.True(t, b.IsAdmin("radio-t", "user2")) } +func TestService_HasReplies(t *testing.T) { + defer os.Remove(testDb) + + // two comments for https://radio-t.com, no reply + b := DataStore{Interface: prepStoreEngine(t), EditDuration: 100 * time.Millisecond, + AdminStore: admin.NewStaticStore("secret 123", []string{"user2"}, "user@email.com")} + + comment := store.Comment{ + ID: "id-1", + Text: `some text, link`, + Timestamp: time.Date(2017, 12, 20, 15, 18, 22, 0, time.Local), + Locator: store.Locator{URL: "https://radio-t.com", SiteID: "radio-t"}, + User: store.User{ID: "user1", Name: "user name"}, + } + + assert.False(t, b.HasReplies(comment)) + + reply := store.Comment{ + ID: "123456", + ParentID: "id-1", + Text: "some text", + Timestamp: time.Date(2017, 12, 20, 15, 18, 22, 0, time.Local), + Locator: store.Locator{URL: "https://radio-t.com", SiteID: "radio-t"}, + User: store.User{ID: "user2", Name: "user name 2"}, + } + _, err := b.Create(reply) + assert.NoError(t, err) + assert.True(t, b.HasReplies(comment)) +} + // makes new boltdb, put two records func prepStoreEngine(t *testing.T) engine.Interface { os.Remove(testDb)