diff --git a/backend/app/rest/api/admin.go b/backend/app/rest/api/admin.go index 4a03cac9..39057c1d 100644 --- a/backend/app/rest/api/admin.go +++ b/backend/app/rest/api/admin.go @@ -28,7 +28,7 @@ type admin struct { type adminStore interface { Delete(locator store.Locator, commentID string, mode store.DeleteMode) error - DeleteUser(siteID string, userID string) error + DeleteUser(siteID string, userID string, mode store.DeleteMode) error User(siteID, userID string, limit, skip int, user store.User) ([]store.Comment, error) IsBlocked(siteID string, userID string) bool SetBlock(siteID string, userID string, status bool, ttl time.Duration) error @@ -64,7 +64,7 @@ func (a *admin) deleteUserCtrl(w http.ResponseWriter, r *http.Request) { siteID := r.URL.Query().Get("site") log.Printf("[INFO] delete all user comments for %s, site %s", userID, siteID) - if err := a.dataService.DeleteUser(siteID, userID); err != nil { + if err := a.dataService.DeleteUser(siteID, userID, store.HardDelete); err != nil { rest.SendErrorJSON(w, r, http.StatusInternalServerError, err, "can't delete user", rest.ErrInternal) return } @@ -109,7 +109,7 @@ func (a *admin) deleteMeRequestCtrl(w http.ResponseWriter, r *http.Request) { return } - if err = a.dataService.DeleteUser(claims.Audience, claims.User.ID); err != nil { + if err = a.dataService.DeleteUser(claims.Audience, claims.User.ID, store.HardDelete); err != nil { rest.SendErrorJSON(w, r, http.StatusBadRequest, err, "can't delete user", rest.ErrNoAccess) return } @@ -145,9 +145,9 @@ func (a *admin) setBlockCtrl(w http.ResponseWriter, r *http.Request) { return } - // delete comments for blocked user + // delete comments for blocked user. if blockStatus { - if err := a.dataService.DeleteUser(siteID, userID); err != nil { + if err := a.dataService.DeleteUser(siteID, userID, store.SoftDelete); err != nil { log.Printf("[WARN] can't delete comments for blocked user %s on site %s, %v", userID, siteID, err) } } diff --git a/backend/app/rest/api/admin_test.go b/backend/app/rest/api/admin_test.go index 796ab2e9..a7346f7c 100644 --- a/backend/app/rest/api/admin_test.go +++ b/backend/app/rest/api/admin_test.go @@ -353,13 +353,23 @@ func TestAdmin_Block(t *testing.T) { assert.False(t, srv.adminRest.dataService.IsBlocked("radio-t", "user1")) assert.False(t, srv.adminRest.dataService.IsBlocked("radio-t", "user2")) - } func TestAdmin_BlockedList(t *testing.T) { - ts, _, teardown := startupT(t) + ts, srv, teardown := startupT(t) defer teardown() + c1 := store.Comment{Text: "test test #1", Locator: store.Locator{SiteID: "radio-t", + URL: "https://radio-t.com/blah"}, User: store.User{Name: "user1 name", ID: "user1"}} + c2 := store.Comment{Text: "test test #2", ParentID: "p1", Locator: store.Locator{SiteID: "radio-t", + URL: "https://radio-t.com/blah"}, User: store.User{Name: "user2 name", ID: "user2"}} + + // write comments for user1 and user2 + _, err := srv.DataService.Create(c1) + assert.Nil(t, err) + _, err = srv.DataService.Create(c2) + assert.Nil(t, err) + // block user1 req, err := http.NewRequest(http.MethodPut, fmt.Sprintf("%s/api/v1/admin/user/%s?site=radio-t&block=%d", ts.URL, "user1", 1), nil) @@ -386,8 +396,10 @@ func TestAdmin_BlockedList(t *testing.T) { assert.Nil(t, err) assert.Equal(t, 2, len(users), "two users blocked") assert.Equal(t, "user1", users[0].ID) + assert.Equal(t, "user1 name", users[0].Name) assert.Equal(t, "user2", users[1].ID) - + assert.Equal(t, "user2 name", users[1].Name) + t.Logf("%+v", users) time.Sleep(50 * time.Millisecond) req, err = http.NewRequest("GET", ts.URL+"/api/v1/admin/blocked?site=radio-t", nil) diff --git a/backend/app/store/engine/bolt.go b/backend/app/store/engine/bolt.go index fe48d286..e4aa2567 100644 --- a/backend/app/store/engine/bolt.go +++ b/backend/app/store/engine/bolt.go @@ -378,7 +378,7 @@ func (b *BoltDB) Delete(req DeleteRequest) error { case req.Locator.URL != "" && req.CommentID != "": return b.deleteComment(bdb, req.Locator, req.CommentID, req.DeleteMode) case req.Locator.SiteID != "" && req.UserID != "" && req.CommentID == "": - return b.deleteUser(bdb, req.Locator.SiteID, req.UserID) + return b.deleteUser(bdb, req.Locator.SiteID, req.UserID, req.DeleteMode) case req.Locator.SiteID != "" && req.Locator.URL == "" && req.CommentID == "" && req.UserID == "": return b.deleteAll(bdb, req.Locator.SiteID) } @@ -673,7 +673,7 @@ func (b *BoltDB) deleteAll(bdb *bolt.DB, siteID string) error { // deleteUser removes all comments for given user. Everything will be market as deleted // and user name and userID will be changed to "deleted". Also removes from last and from user buckets. -func (b *BoltDB) deleteUser(bdb *bolt.DB, siteID string, userID string) error { +func (b *BoltDB) deleteUser(bdb *bolt.DB, siteID string, userID string, mode store.DeleteMode) error { bdb, err := b.db(siteID) if err != nil { return err @@ -717,24 +717,26 @@ func (b *BoltDB) deleteUser(bdb *bolt.DB, siteID string, userID string) error { // delete collected comments for _, ci := range comments { - if e := b.deleteComment(bdb, ci.locator, ci.commentID, store.HardDelete); e != nil { + if e := b.deleteComment(bdb, ci.locator, ci.commentID, mode); e != nil { return errors.Wrapf(err, "failed to delete comment %+v", ci) } } - // delete user bucket - err = bdb.Update(func(tx *bolt.Tx) error { - usersBkt := tx.Bucket([]byte(userBucketName)) - if usersBkt != nil { - if e := usersBkt.DeleteBucket([]byte(userID)); e != nil { - return errors.Wrapf(err, "failed to delete user bucket for %s", userID) + // delete user bucket in hard mode + if mode == store.HardDelete { + err = bdb.Update(func(tx *bolt.Tx) error { + usersBkt := tx.Bucket([]byte(userBucketName)) + if usersBkt != nil { + if e := usersBkt.DeleteBucket([]byte(userID)); e != nil { + return errors.Wrapf(err, "failed to delete user bucket for %s", userID) + } } - } - return nil - }) + return nil + }) - if err != nil { - return errors.Wrap(err, "can't delete user meta") + if err != nil { + return errors.Wrap(err, "can't delete user meta") + } } if len(comments) == 0 { diff --git a/backend/app/store/engine/bolt_test.go b/backend/app/store/engine/bolt_test.go index b373c5d6..502fcb8d 100644 --- a/backend/app/store/engine/bolt_test.go +++ b/backend/app/store/engine/bolt_test.go @@ -683,12 +683,12 @@ func TestBolt_DeleteAll(t *testing.T) { assert.EqualError(t, err, `site "bad" not found`) } -func TestBoltAdmin_DeleteUser(t *testing.T) { +func TestBoltAdmin_DeleteUserHard(t *testing.T) { b, teardown := prep(t) defer teardown() - err := b.Delete(DeleteRequest{Locator: store.Locator{SiteID: "radio-t"}, UserID: "user1"}) + err := b.Delete(DeleteRequest{Locator: store.Locator{SiteID: "radio-t"}, UserID: "user1", DeleteMode: store.HardDelete}) require.NoError(t, err) comments, err := b.Find(FindRequest{Locator: store.Locator{SiteID: "radio-t", URL: "https://radio-t.com"}, Sort: "time"}) @@ -712,6 +712,40 @@ func TestBoltAdmin_DeleteUser(t *testing.T) { assert.EqualError(t, err, `site "radio-t-bad" not found`) } +func TestBoltAdmin_DeleteUserSoft(t *testing.T) { + + b, teardown := prep(t) + defer teardown() + + err := b.Delete(DeleteRequest{Locator: store.Locator{SiteID: "radio-t"}, UserID: "user1", DeleteMode: store.SoftDelete}) + require.NoError(t, err) + + comments, err := b.Find(FindRequest{Locator: store.Locator{SiteID: "radio-t", URL: "https://radio-t.com"}, Sort: "time"}) + assert.NoError(t, err) + assert.Equal(t, 2, len(comments), "2 comments with deleted info") + assert.Equal(t, store.User{Name: "user name", ID: "user1", Picture: "", Admin: false, Blocked: false, IP: ""}, comments[0].User) + assert.Equal(t, store.User{Name: "user name", ID: "user1", Picture: "", Admin: false, Blocked: false, IP: ""}, comments[1].User) + + c, err := b.Count(FindRequest{Locator: store.Locator{SiteID: "radio-t", URL: "https://radio-t.com"}}) + assert.NoError(t, err) + assert.Equal(t, 0, c, "0 count") + + comments, err = b.Find(FindRequest{Locator: store.Locator{SiteID: "radio-t"}, UserID: "user1", Limit: 5}) + assert.NoError(t, err, "no comments for user user1 in store") + assert.Equal(t, 2, len(comments), "2 comments with deleted info") + assert.True(t, comments[0].Deleted) + assert.True(t, comments[1].Deleted) + assert.Equal(t, "", comments[0].Text) + assert.Equal(t, "", comments[1].Text) + + comments, err = b.Find(FindRequest{Locator: store.Locator{SiteID: "radio-t"}, Sort: "time"}) + assert.NoError(t, err) + assert.Equal(t, 0, len(comments), "nothing left") + + err = b.Delete(DeleteRequest{Locator: store.Locator{SiteID: "radio-t-bad"}, UserID: "user1"}) + assert.EqualError(t, err, `site "radio-t-bad" not found`) +} + func TestBoltDB_ref(t *testing.T) { b := BoltDB{} comment := store.Comment{ diff --git a/backend/app/store/service/service.go b/backend/app/store/service/service.go index 0087a877..7a0acc88 100644 --- a/backend/app/store/service/service.go +++ b/backend/app/store/service/service.go @@ -553,8 +553,8 @@ func (s *DataStore) Delete(locator store.Locator, commentID string, mode store.D } // DeleteUser removes all comments from user -func (s *DataStore) DeleteUser(siteID string, userID string) error { - req := engine.DeleteRequest{Locator: store.Locator{SiteID: siteID}, UserID: userID, DeleteMode: store.HardDelete} +func (s *DataStore) DeleteUser(siteID string, userID string, mode store.DeleteMode) error { + req := engine.DeleteRequest{Locator: store.Locator{SiteID: siteID}, UserID: userID, DeleteMode: mode} return s.Engine.Delete(req) }