add comments per user

This commit is contained in:
Umputun
2017-12-26 15:56:18 -06:00
parent 728955f2ad
commit 195c83c34a
7 changed files with 109 additions and 8 deletions
+1
View File
@@ -98,6 +98,7 @@ Sort can be `time` or `score`. Supported sort order with prefix -/+, i.e. `-time
- `GET /api/v1/last/{max}` - get up to `{max}` last comments
- `GET /api/v1/id/{id}` - get comment by `id`
- `GET /api/v1/comments?user=id` - get comment by `user id`
- `GET /api/v1/count?url=post-url` - get comment's count for `{url}`
- `PUT /api/v1/vote/{id}?url=post-url&vote=1` - vote for comment. `vote`=1 will increase score, -1 decreases. _auth required_
- `DELETE /api/v1/admin/comment/{id}?url=post-url` - delete comment by `id`. _auth and admin required_
+1 -1
View File
@@ -35,7 +35,7 @@ func TestStore_MakeTree(t *testing.T) {
enc.SetIndent("", " ")
err := enc.Encode(res)
assert.Nil(t, err)
assert.Equal(t, expJSON, string(buf.Bytes()))
assert.Equal(t, expJSON, buf.String())
// t.Log(string(buf.Bytes()))
}
+18
View File
@@ -67,6 +67,7 @@ func (s *Server) Run() {
router.Route("/api/v1", func(rapi chi.Router) {
rapi.Get("/find", s.findCommentsCtrl)
rapi.Get("/id/{id}", s.commentByIDCtrl)
rapi.Get("/comments", s.findUserCommentsCtrl)
rapi.Get("/last/{max}", s.lastCommentsCtrl)
rapi.Get("/count", s.countCtrl)
@@ -219,6 +220,23 @@ func (s *Server) commentByIDCtrl(w http.ResponseWriter, r *http.Request) {
renderJSONWithHTML(w, r, comment)
}
// GET /comments?user=id
func (s *Server) findUserCommentsCtrl(w http.ResponseWriter, r *http.Request) {
userID := chi.URLParam(r, "user")
log.Printf("[DEBUG] get comments by userID %s", userID)
comment, err := s.Store.GetForUser(store.Locator{}, userID)
if err != nil {
log.Printf("[WARN] can't get comment, %s", err)
httpError(w, r, http.StatusBadRequest, err, "can't get comment by user id")
return
}
render.Status(r, http.StatusOK)
renderJSONWithHTML(w, r, comment)
}
// GET /user
func (s *Server) userInfoCtrl(w http.ResponseWriter, r *http.Request) {
user, err := auth.GetUserInfo(r)
+62 -2
View File
@@ -21,6 +21,7 @@ type BoltDB struct {
const (
lastBucketName = "last"
userBucketName = "users"
blocksBucketPrefix = "block-"
lastLimit = 1000
)
@@ -76,13 +77,34 @@ func (b *BoltDB) Create(comment Comment) (string, error) {
if e != nil {
return errors.Wrapf(e, "can't make bucket %s", lastBucketName)
}
rv := refFromComment(comment)
e = bucket.Put([]byte(rv.key), []byte(rv.value))
if e != nil {
return errors.Wrapf(e, "can't put reference %s to %s", rv.value, lastBucketName)
}
// add reference to commentID to "users" bucket
bucket, e = tx.CreateBucketIfNotExists([]byte(userBucketName))
if e != nil {
return errors.Wrapf(e, "can't make bucket %s", userBucketName)
}
userRefs := []string{} // holds current comment refs for the user
if data := bucket.Get([]byte(comment.User.ID)); data != nil {
if err := json.Unmarshal(data, &userRefs); err != nil {
return errors.Wrapf(e, "can't unmarshal comments for %s", comment.User.ID)
}
}
userRefs = append(userRefs, rv.value)
// serialize to json []byte for bolt and save
jdata, jerr = json.Marshal(&userRefs)
if jerr != nil {
return errors.Wrapf(jerr, "can't marshal comment ids for user %s, comment %s", comment.User.ID, comment.ID)
}
if err := bucket.Put([]byte(comment.User.ID), jdata); err != nil {
return errors.Wrapf(err, "failed to put user comment %s", comment.ID)
}
return nil
})
@@ -345,7 +367,7 @@ func (b BoltDB) List(locator Locator) (result []string, err error) {
err = b.View(func(tx *bolt.Tx) error {
return tx.ForEach(func(name []byte, _ *bolt.Bucket) error {
if string(name) != lastBucketName {
if string(name) != lastBucketName && string(name) != userBucketName {
result = append(result, string(name))
}
return nil
@@ -383,6 +405,44 @@ func (b *BoltDB) SetPin(locator Locator, commentID string, status bool) error {
})
}
// GetForUser extracts all comments for given site and given userID
// "users" bucket has pairs userID:[]commentID
func (b *BoltDB) GetForUser(locator Locator, userID string) (comments []Comment, err error) {
comments = []Comment{}
commentRefs := []string{}
err = b.View(func(tx *bolt.Tx) error {
userBucket := tx.Bucket([]byte(userBucketName))
if userBucket == nil {
return errors.Errorf("no bucket %s in store", userBucketName)
}
uData := userBucket.Get([]byte(userID))
if uData == nil {
return errors.Errorf("no comments for user %s in store", userID)
}
if e := json.Unmarshal(uData, &commentRefs); e != nil {
return errors.Wrap(e, "failed to unmarshal list of user's comments")
}
return nil
})
if err != nil {
return comments, err
}
for _, v := range commentRefs {
url, commentID, e := ref{value: v}.parseValue()
if e != nil {
return comments, errors.Wrapf(e, "can't parse reference %s", v)
}
if c, e := b.Get(Locator{URL: url, SiteID: locator.SiteID}, commentID); e == nil {
comments = append(comments, c)
}
}
return comments, err
}
func (b *BoltDB) bucketForBlock(locator Locator, userID string) []byte {
return []byte(fmt.Sprintf("%s%s", blocksBucketPrefix, locator.SiteID))
}
+23 -4
View File
@@ -165,6 +165,15 @@ func TestBoltDB_Pin(t *testing.T) {
assert.Equal(t, false, c.Pin)
}
func TestBoltDB_GetForUser(t *testing.T) {
defer os.Remove(testDb)
b := prep(t)
res, err := b.GetForUser(Locator{SiteID: "radio-t"}, "user1")
assert.Nil(t, err)
assert.Equal(t, 2, len(res))
}
// makes new boltdb, put two records
func prep(t *testing.T) *BoltDB {
os.Remove(testDb)
@@ -172,13 +181,23 @@ func prep(t *testing.T) *BoltDB {
b, err := NewBoltDB(testDb)
assert.Nil(t, err)
comment := Comment{Text: `some text, <a href="http://radio-t.com">link</a>`, Timestamp: time.Date(2017, 12, 20, 15, 18, 22, 0, time.Local),
Locator: Locator{URL: "https://radio-t.com", SiteID: "radio-t"}, User: User{ID: "user1", Name: "user name"}}
comment := Comment{
ID: "id-1",
Text: `some text, <a href="http://radio-t.com">link</a>`,
Timestamp: time.Date(2017, 12, 20, 15, 18, 22, 0, time.Local),
Locator: Locator{URL: "https://radio-t.com", SiteID: "radio-t"},
User: User{ID: "user1", Name: "user name"},
}
_, err = b.Create(comment)
assert.Nil(t, err)
comment = Comment{Text: "some text2", Timestamp: time.Date(2017, 12, 20, 15, 18, 23, 0, time.Local),
Locator: Locator{URL: "https://radio-t.com", SiteID: "radio-t"}, User: User{ID: "user1", Name: "user name"}}
comment = Comment{
ID: "id-2",
Text: "some text2",
Timestamp: time.Date(2017, 12, 20, 15, 18, 23, 0, time.Local),
Locator: Locator{URL: "https://radio-t.com", SiteID: "radio-t"},
User: User{ID: "user1", Name: "user name"},
}
_, err = b.Create(comment)
assert.Nil(t, err)
+1 -1
View File
@@ -61,10 +61,10 @@ type Interface interface {
Vote(locator Locator, commentID string, userID string, val bool) (Comment, error)
Count(locator Locator) (int, error)
List(locator Locator) ([]string, error)
GetForUser(locator Locator, userID string) ([]Comment, error)
SetBlock(locator Locator, userID string, status bool) error
IsBlocked(locator Locator, userID string) bool
SetPin(locator Locator, commentID string, status bool) error
}
+3
View File
@@ -36,5 +36,8 @@ GET http://remark.umputun.com/api/v1/user
### get comment by id
GET http://remark.umputun.com/api/v1/id/3665976683?url=https://radio-t.com/p/2017/12/16/podcast-576/
### get comment by user id
GET http://remark.umputun.com/api/v1/api/v1/comments?user=umputun
### get counts
GET http://remark.umputun.com/api/v1/count?url=https://radio-t.com/p/2017/12/16/podcast-576/