From 837b2ef719fd2fd7cdc5979b9e9276e52b964841 Mon Sep 17 00:00:00 2001 From: Umputun Date: Fri, 1 Jun 2018 22:17:57 -0500 Subject: [PATCH] find user comments to support pagination --- .dockerignore | 1 - Dockerfile | 5 +- app/rest/api/rest.go | 2 +- app/store/engine/bolt_accessor.go | 7 ++- app/store/engine/bolt_accessor_test.go | 66 ++++++++++++++++++++++++-- app/store/engine/bolt_admin.go | 4 +- app/store/engine/bolt_admin_test.go | 2 +- app/store/engine/engine.go | 26 ++++++---- 8 files changed, 93 insertions(+), 20 deletions(-) diff --git a/.dockerignore b/.dockerignore index 9e51d29c..827ec1d2 100644 --- a/.dockerignore +++ b/.dockerignore @@ -5,7 +5,6 @@ /web/public/ /.vscode/ /.idea/ -#/.git/ # source files docker-compose.yml diff --git a/Dockerfile b/Dockerfile index 0db480f3..fb2ef73a 100644 --- a/Dockerfile +++ b/Dockerfile @@ -31,7 +31,10 @@ RUN if [ -z "$COVERALLS_TOKEN" ] ; then \ echo coverall not enabled ; \ else goveralls -coverprofile=.cover/cover.out -service=travis-ci -repotoken $COVERALLS_TOKEN; fi -RUN go build -o remark -ldflags "-X main.revision=$(git rev-parse --abbrev-ref HEAD)-$(git describe --abbrev=7 --always --tags)-$(date +%Y%m%d-%H:%M:%S) -s -w" ./app +RUN \ + version=$(git rev-parse --abbrev-ref HEAD)-$(git describe --abbrev=7 --always --tags)-$(date +%Y%m%d-%H:%M:%S) && \ + echo "version $version" && \ + go build -o remark -ldflags "-X main.revision=${version} -s -w" ./app FROM node:9.4-alpine as build-frontend diff --git a/app/rest/api/rest.go b/app/rest/api/rest.go index d2816e29..1866db56 100644 --- a/app/rest/api/rest.go +++ b/app/rest/api/rest.go @@ -444,7 +444,7 @@ func (s *Rest) findUserCommentsCtrl(w http.ResponseWriter, r *http.Request) { log.Printf("[DEBUG] get comments for userID %s, %s", userID, siteID) data, err := s.Cache.Get(cache.Key(cache.URLKey(r), userID, siteID), func() ([]byte, error) { - comments, count, e := s.DataService.User(siteID, userID, limit) + comments, count, e := s.DataService.User(siteID, userID, limit, 0) if e != nil { return nil, e } diff --git a/app/store/engine/bolt_accessor.go b/app/store/engine/bolt_accessor.go index b1a94fde..e7673ade 100644 --- a/app/store/engine/bolt_accessor.go +++ b/app/store/engine/bolt_accessor.go @@ -294,7 +294,7 @@ func (b *BoltDB) Info(locator store.Locator, readOnlyAge int) (store.PostInfo, e // User extracts all comments for given site and given userID // "users" bucket has sub-bucket for each userID, and keeps it as ts:ref -func (b *BoltDB) User(siteID string, userID string, limit int) (comments []store.Comment, totalComments int, err error) { +func (b *BoltDB) User(siteID, userID string, limit, skip int) (comments []store.Comment, totalComments int, err error) { comments = []store.Comment{} commentRefs := []string{} @@ -318,8 +318,13 @@ func (b *BoltDB) User(siteID string, userID string, limit int) (comments []store c := userIDBkt.Cursor() totalComments = 0 + skipComments := 0 for k, v := c.Last(); k != nil; k, v = c.Prev() { totalComments++ + if skip > 0 && skipComments < skip { + skipComments++ + continue + } if len(commentRefs) < limit { commentRefs = append(commentRefs, string(v)) } diff --git a/app/store/engine/bolt_accessor_test.go b/app/store/engine/bolt_accessor_test.go index 473ae39e..6cfea02f 100644 --- a/app/store/engine/bolt_accessor_test.go +++ b/app/store/engine/bolt_accessor_test.go @@ -1,6 +1,7 @@ package engine import ( + "fmt" "os" "testing" "time" @@ -224,25 +225,82 @@ func TestBoltDB_GetForUser(t *testing.T) { defer os.Remove(testDb) b := prep(t) - res, count, err := b.User("radio-t", "user1", 5) + res, count, err := b.User("radio-t", "user1", 5, 0) assert.Nil(t, err) assert.Equal(t, 2, len(res)) assert.Equal(t, 2, count) assert.Equal(t, "some text2", res[0].Text, "sorted by -time") - res, count, err = b.User("radio-t", "user1", 1) + res, count, err = b.User("radio-t", "user1", 1, 0) assert.Nil(t, err) assert.Equal(t, 1, len(res), "allow 1 comment") assert.Equal(t, 2, count) assert.Equal(t, "some text2", res[0].Text, "sorted by -time") - _, _, err = b.User("bad", "user1", 1) + res, count, err = b.User("radio-t", "user1", 1, 1) + assert.Nil(t, err) + assert.Equal(t, 1, len(res), "allow 1 comment") + assert.Equal(t, 2, count) + assert.Equal(t, `some text, link`, res[0].Text, "second comment") + + _, _, err = b.User("bad", "user1", 1, 0) assert.EqualError(t, err, `site "bad" not found`) - _, _, err = b.User("radio-t", "userZ", 1) + _, _, err = b.User("radio-t", "userZ", 1, 0) assert.EqualError(t, err, `no comments for user userZ in store`) } +func TestBoltDB_GetForUserPagination(t *testing.T) { + os.Remove(testDb) + b, err := NewBoltDB(bolt.Options{}, BoltSite{FileName: testDb, SiteID: "radio-t"}) + require.Nil(t, err) + defer os.Remove(testDb) + + c := store.Comment{ + Locator: store.Locator{URL: "https://radio-t.com", SiteID: "radio-t"}, + User: store.User{ID: "user1", Name: "user name"}, + } + + // write 50 comments + for i := 0; i < 50; i++ { + c.ID = fmt.Sprintf("id-%d", i) + c.Text = fmt.Sprintf("text #%d", i) + c.Timestamp = time.Date(2017, 12, 20, 15, 18, i, 0, time.Local) + _, err = b.Create(c) + require.Nil(t, err) + } + + // seek 0, 5 comments + res, count, err := b.User("radio-t", "user1", 5, 0) + assert.Nil(t, err) + assert.Equal(t, 50, count) + assert.Equal(t, 5, len(res)) + assert.Equal(t, "id-49", res[0].ID) + assert.Equal(t, "id-45", res[4].ID) + + // seek 10, 3 comments + res, count, err = b.User("radio-t", "user1", 3, 10) + assert.Nil(t, err) + assert.Equal(t, 50, count) + assert.Equal(t, 3, len(res)) + assert.Equal(t, "id-39", res[0].ID) + assert.Equal(t, "id-37", res[2].ID) + + // seek 45, ask 10 comments + res, count, err = b.User("radio-t", "user1", 10, 45) + assert.Nil(t, err) + assert.Equal(t, 50, count) + assert.Equal(t, 5, len(res)) + assert.Equal(t, "id-4", res[0].ID) + assert.Equal(t, "id-0", res[4].ID) + + // seek 55, ask 10 comments + res, count, err = b.User("radio-t", "user1", 10, 55) + assert.Nil(t, err) + assert.Equal(t, 50, count) + assert.Equal(t, 0, len(res)) +} + func TestBoltDB_Ref(t *testing.T) { b := BoltDB{} comment := store.Comment{ diff --git a/app/store/engine/bolt_admin.go b/app/store/engine/bolt_admin.go index aa3481fc..e8a25a17 100644 --- a/app/store/engine/bolt_admin.go +++ b/app/store/engine/bolt_admin.go @@ -206,10 +206,10 @@ func (b *BoltDB) Blocked(siteID string) (users []store.BlockedUser, err error) { // get user name from comment user section userName := "" - if userComments, _, e := b.User(siteID, string(k), 1); e == nil && len(userComments) > 0 { + userComments, _, e := b.User(siteID, string(k), 1, 0) + if e == nil && len(userComments) > 0 { userName = userComments[0].User.Name } - users = append(users, store.BlockedUser{ID: string(k), Name: userName, Timestamp: ts}) return nil }) diff --git a/app/store/engine/bolt_admin_test.go b/app/store/engine/bolt_admin_test.go index 94e64d94..1fe9a98e 100644 --- a/app/store/engine/bolt_admin_test.go +++ b/app/store/engine/bolt_admin_test.go @@ -109,7 +109,7 @@ func TestBoltAdmin_DeleteUser(t *testing.T) { assert.Nil(t, err) assert.Equal(t, 0, c, "0 count") - _, _, err = b.User("radio-t", "user1", 5) + _, _, err = b.User("radio-t", "user1", 5, 0) assert.EqualError(t, err, "no comments for user user1 in store") comments, err := b.Last("radio-t", 10) diff --git a/app/store/engine/engine.go b/app/store/engine/engine.go index 73c94e0d..05e4ebb6 100644 --- a/app/store/engine/engine.go +++ b/app/store/engine/engine.go @@ -17,17 +17,25 @@ type Interface interface { Admin } +// UserRequest is the request send to get comments by user +type UserRequest struct { + SiteID string + UserID string + Limit int + Skip int +} + // Accessor defines all usual access ops avail for regular user type Accessor interface { - Create(comment store.Comment) (commentID string, err error) // create new comment, avoid dups by id - Get(locator store.Locator, commentID string) (store.Comment, error) // get comment by id - Put(locator store.Locator, comment store.Comment) error // update comment, mutable parts only - Find(locator store.Locator, sort string) ([]store.Comment, error) // find comments for locator - Last(siteID string, limit int) ([]store.Comment, error) // last comments for given site, sorted by time - User(siteID string, userID string, limit int) ([]store.Comment, int, error) // comments by user, sorted by time - Count(locator store.Locator) (int, error) // number of comments for the post - List(siteID string, limit int, skip int) ([]store.PostInfo, error) // list of commented posts - Info(locator store.Locator, readonlyAge int) (store.PostInfo, error) // get post info + Create(comment store.Comment) (commentID string, err error) // create new comment, avoid dups by id + Get(locator store.Locator, commentID string) (store.Comment, error) // get comment by id + Put(locator store.Locator, comment store.Comment) error // update comment, mutable parts only + Find(locator store.Locator, sort string) ([]store.Comment, error) // find comments for locator + Last(siteID string, limit int) ([]store.Comment, error) // last comments for given site, sorted by time + User(siteID, userID string, limit, skip int) ([]store.Comment, int, error) // comments by user, sorted by time + Count(locator store.Locator) (int, error) // number of comments for the post + List(siteID string, limit int, skip int) ([]store.PostInfo, error) // list of commented posts + Info(locator store.Locator, readonlyAge int) (store.PostInfo, error) // get post info } // Admin defines all store ops avail for admin only