eliminate multiple calls to counter in paginated user comments
This commit is contained in:
@@ -444,11 +444,16 @@ 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, 0)
|
||||
comments, e := s.DataService.User(siteID, userID, limit, 0)
|
||||
if e != nil {
|
||||
return nil, e
|
||||
}
|
||||
comments = s.adminService.alterComments(comments, r)
|
||||
|
||||
count, e := s.DataService.UserCount(siteID, userID)
|
||||
if e != nil {
|
||||
return nil, e
|
||||
}
|
||||
resp.Comments, resp.Count = comments, count
|
||||
return encodeJSONWithHTML(resp)
|
||||
})
|
||||
|
||||
@@ -294,14 +294,14 @@ 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, userID string, limit, skip int) (comments []store.Comment, totalComments int, err error) {
|
||||
func (b *BoltDB) User(siteID, userID string, limit, skip int) (comments []store.Comment, err error) {
|
||||
|
||||
comments = []store.Comment{}
|
||||
commentRefs := []string{}
|
||||
|
||||
bdb, err := b.db(siteID)
|
||||
if err != nil {
|
||||
return nil, 0, err
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if limit == 0 || limit > userLimit {
|
||||
@@ -317,37 +317,56 @@ func (b *BoltDB) User(siteID, userID string, limit, skip int) (comments []store.
|
||||
}
|
||||
|
||||
c := userIDBkt.Cursor()
|
||||
totalComments = 0
|
||||
skipComments := 0
|
||||
for k, v := c.Last(); k != nil; k, v = c.Prev() {
|
||||
totalComments++
|
||||
if len(commentRefs) >= limit {
|
||||
break
|
||||
}
|
||||
if skip > 0 && skipComments < skip {
|
||||
skipComments++
|
||||
continue
|
||||
}
|
||||
if len(commentRefs) < limit {
|
||||
commentRefs = append(commentRefs, string(v))
|
||||
}
|
||||
commentRefs = append(commentRefs, string(v))
|
||||
}
|
||||
return nil
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
return comments, totalComments, err
|
||||
return comments, err
|
||||
}
|
||||
|
||||
// retrieve comments for refs
|
||||
for _, v := range commentRefs {
|
||||
url, commentID, e := b.parseRef([]byte(v))
|
||||
if e != nil {
|
||||
return comments, totalComments, errors.Wrapf(e, "can't parse reference %s", v)
|
||||
return comments, errors.Wrapf(e, "can't parse reference %s", v)
|
||||
}
|
||||
if c, e := b.Get(store.Locator{SiteID: siteID, URL: url}, commentID); e == nil {
|
||||
comments = append(comments, c)
|
||||
}
|
||||
}
|
||||
|
||||
return comments, totalComments, err
|
||||
return comments, err
|
||||
}
|
||||
|
||||
// UserCount returns number of comments for user TODO: this can be slow, but userIDBkt just refs
|
||||
func (b *BoltDB) UserCount(siteID, userID string) (int, error) {
|
||||
bdb, err := b.db(siteID)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
count := 0
|
||||
err = bdb.View(func(tx *bolt.Tx) error {
|
||||
usersBkt := tx.Bucket([]byte(userBucketName))
|
||||
userIDBkt := usersBkt.Bucket([]byte(userID))
|
||||
if userIDBkt == nil {
|
||||
return errors.Errorf("no comments for user %s in store", userID)
|
||||
}
|
||||
stats := userIDBkt.Stats()
|
||||
count = stats.KeyN
|
||||
return nil
|
||||
})
|
||||
return count, err
|
||||
}
|
||||
|
||||
// Get returns comment for locator.URL and commentID string
|
||||
|
||||
@@ -225,28 +225,25 @@ func TestBoltDB_GetForUser(t *testing.T) {
|
||||
defer os.Remove(testDb)
|
||||
b := prep(t)
|
||||
|
||||
res, count, err := b.User("radio-t", "user1", 5, 0)
|
||||
res, 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, 0)
|
||||
res, 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")
|
||||
|
||||
res, count, err = b.User("radio-t", "user1", 1, 1)
|
||||
res, 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, <a href="http://radio-t.com">link</a>`, res[0].Text, "second comment")
|
||||
|
||||
_, _, err = b.User("bad", "user1", 1, 0)
|
||||
_, err = b.User("bad", "user1", 1, 0)
|
||||
assert.EqualError(t, err, `site "bad" not found`)
|
||||
|
||||
_, _, err = b.User("radio-t", "userZ", 1, 0)
|
||||
_, err = b.User("radio-t", "userZ", 1, 0)
|
||||
assert.EqualError(t, err, `no comments for user userZ in store`)
|
||||
}
|
||||
|
||||
@@ -271,36 +268,46 @@ func TestBoltDB_GetForUserPagination(t *testing.T) {
|
||||
}
|
||||
|
||||
// seek 0, 5 comments
|
||||
res, count, err := b.User("radio-t", "user1", 5, 0)
|
||||
res, 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)
|
||||
res, 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)
|
||||
res, 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)
|
||||
res, 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_GetForUserCounter(t *testing.T) {
|
||||
defer os.Remove(testDb)
|
||||
b := prep(t)
|
||||
count, err := b.UserCount("radio-t", "user1")
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, 2, count)
|
||||
|
||||
_, err = b.UserCount("bad", "user1")
|
||||
assert.EqualError(t, err, `site "bad" not found`)
|
||||
|
||||
_, err = b.UserCount("radio-t", "userZ")
|
||||
assert.EqualError(t, err, `no comments for user userZ in store`)
|
||||
}
|
||||
|
||||
func TestBoltDB_Ref(t *testing.T) {
|
||||
b := BoltDB{}
|
||||
comment := store.Comment{
|
||||
|
||||
@@ -206,7 +206,7 @@ func (b *BoltDB) Blocked(siteID string) (users []store.BlockedUser, err error) {
|
||||
|
||||
// get user name from comment user section
|
||||
userName := ""
|
||||
userComments, _, e := b.User(siteID, string(k), 1, 0)
|
||||
userComments, e := b.User(siteID, string(k), 1, 0)
|
||||
if e == nil && len(userComments) > 0 {
|
||||
userName = userComments[0].User.Name
|
||||
}
|
||||
|
||||
@@ -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, 0)
|
||||
_, 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)
|
||||
|
||||
@@ -27,15 +27,16 @@ type UserRequest struct {
|
||||
|
||||
// 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, 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
|
||||
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, error) // comments by user, sorted by time
|
||||
UserCount(siteID, userID string) (int, error) // comments count by user
|
||||
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
|
||||
|
||||
Reference in New Issue
Block a user