add block/unblock rest and store

This commit is contained in:
Eugene
2017-12-22 20:48:18 -06:00
parent ef7c8fcba2
commit b526de7f51
6 changed files with 100 additions and 6 deletions
+2 -1
View File
@@ -61,4 +61,5 @@ type Locator struct {
- `GET /api/v1/id/{id}` - get comment by `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/comment/{id}?url=post-url` - delete comment by `id`. _auth and admin required_
- `DELETE /api/v1/comment/{id}?url=post-url` - delete comment by `id`. _auth and admin required_
- `PUT /user/{userid}?site=side-id&block=1` - block or unblock user. _auth and admin required_
+16
View File
@@ -18,6 +18,7 @@ func (m *moderator) routes() chi.Router {
router := chi.NewRouter()
router.Use(AdminOnly)
router.Delete("/comment/{id}", m.deleteCommentCtrl)
router.Put("/user/{userid}", m.setBlockCtrl)
return router
}
@@ -28,6 +29,7 @@ func (m *moderator) deleteCommentCtrl(w http.ResponseWriter, r *http.Request) {
if err != nil {
log.Printf("[WARN] bad id %s", chi.URLParam(r, "id"))
httpError(w, r, http.StatusBadRequest, err, "can't parse id")
return
}
log.Printf("[INFO] delete comment %d", id)
@@ -43,3 +45,17 @@ func (m *moderator) deleteCommentCtrl(w http.ResponseWriter, r *http.Request) {
render.Status(r, http.StatusOK)
render.JSON(w, r, JSON{"id": id, "url": url})
}
// PUT /user/{userid}?site=side-id&block=1
func (m *moderator) setBlockCtrl(w http.ResponseWriter, r *http.Request) {
userID := chi.URLParam(r, "userid")
siteID := r.URL.Query().Get("site")
blockStatus := r.URL.Query().Get("block") == "1"
if err := m.dataStore.SetBlock(store.Locator{SiteID: siteID}, userID, blockStatus); err != nil {
httpError(w, r, http.StatusBadRequest, err, "can't set blocking status")
return
}
render.JSON(w, r, JSON{"user_id": userID, "site_id": siteID, "block": blockStatus})
}
+9 -1
View File
@@ -1,6 +1,7 @@
package rest
import (
"errors"
"html/template"
"log"
"net/http"
@@ -98,7 +99,7 @@ func (s *Server) createCommentCtrl(w http.ResponseWriter, r *http.Request) {
}
user, err := GetUserInfo(r)
if err != nil {
if err != nil { // this not suppose to happen (handled by Auth), just dbl-check
httpError(w, r, http.StatusUnauthorized, err, "can't get user info")
return
}
@@ -114,6 +115,13 @@ func (s *Server) createCommentCtrl(w http.ResponseWriter, r *http.Request) {
log.Printf("[INFO] create comment %+v", comment)
// check if user blocked
if s.Store.IsBlocked(store.Locator{}, comment.User.ID) {
log.Printf("[WARN] user %s rejected (blocked)", err)
httpError(w, r, http.StatusForbidden, errors.New("rejected"), "user blocked")
return
}
id, err := s.Store.Create(comment)
if err != nil {
log.Printf("[WARN] can't save comment, %s", err)
+46 -1
View File
@@ -20,7 +20,10 @@ type BoltDB struct {
*bolt.DB
}
var lastBucketName = "last"
var (
lastBucketName = "last"
blocksBucketPrefix = "block-"
)
// NewBoltDB makes persistent boltdb-based store
func NewBoltDB(dbFile string) (*BoltDB, error) {
@@ -270,6 +273,48 @@ func (b *BoltDB) Count(locator Locator) (count int, err error) {
return count, err
}
// SetBlock blocks/unblocks user for given site
func (b *BoltDB) SetBlock(locator Locator, userID string, status bool) error {
blockBucketName := b.bucketForBlock(locator, userID)
return b.Update(func(tx *bolt.Tx) error {
bucket, e := tx.CreateBucketIfNotExists([]byte(blockBucketName))
if e != nil {
return errors.Errorf("no bucket %s in store", string(blockBucketName))
}
switch status {
case true:
if e := bucket.Put([]byte(userID), []byte(time.Now().Format(time.RFC3339))); e != nil {
return errors.Wrapf(e, "failed to put %s to %s", userID, string(blockBucketName))
}
case false:
if e := bucket.Delete([]byte(userID)); e != nil {
return errors.Wrapf(e, "failed to clean %s from %s", userID, string(blockBucketName))
}
}
return nil
})
}
// IsBlocked checks if user blocked
func (b *BoltDB) IsBlocked(locator Locator, userID string) (result bool) {
blockBucketName := b.bucketForBlock(locator, userID)
_ = b.View(func(tx *bolt.Tx) error {
result = false
bucket := tx.Bucket(blockBucketName)
if bucket != nil && bucket.Get([]byte(userID)) != nil {
result = true
}
return nil
})
return result
}
func (b *BoltDB) bucketForBlock(locator Locator, userID string) []byte {
return []byte(fmt.Sprintf("%s%s", blocksBucketPrefix, locator.SiteID))
}
func (b *BoltDB) keyFromComment(comment Comment) []byte {
return []byte(fmt.Sprintf("%22d", comment.ID))
}
+24 -3
View File
@@ -27,17 +27,22 @@ func TestBoltDB_Delete(t *testing.T) {
defer os.Remove(testDb)
b := prep(t)
res, err := b.Find(Request{Locator: Locator{URL: "https://radio-t.com"}})
loc := Locator{URL: "https://radio-t.com"}
res, err := b.Find(Request{Locator: loc})
assert.Nil(t, err)
assert.Equal(t, 2, len(res))
err = b.Delete(Locator{URL: "https://radio-t.com"}, res[0].ID)
err = b.Delete(loc, res[0].ID)
assert.Nil(t, err)
res, err = b.Find(Request{Locator: Locator{URL: "https://radio-t.com"}})
res, err = b.Find(Request{Locator: loc})
assert.Nil(t, err)
assert.Equal(t, 1, len(res))
assert.Equal(t, "some text2", res[0].Text)
comments, err := b.Last(loc, 10)
assert.Nil(t, err)
assert.Equal(t, 1, len(comments), "only 1 left in last")
}
func TestBoltDB_Get(t *testing.T) {
@@ -105,6 +110,22 @@ func TestBoltDB_Count(t *testing.T) {
assert.Equal(t, 2, c)
}
func TestBoltDB_BlockUser(t *testing.T) {
defer os.Remove(testDb)
b := prep(t)
assert.False(t, b.IsBlocked(Locator{SiteID: "site1"}, "user1"), "nothing blocked")
assert.NoError(t, b.SetBlock(Locator{SiteID: "site1"}, "user1", true))
assert.True(t, b.IsBlocked(Locator{SiteID: "site1"}, "user1"), "user1 blocked")
assert.False(t, b.IsBlocked(Locator{SiteID: "site1"}, "user2"), "user2 still unblocked")
assert.NoError(t, b.SetBlock(Locator{SiteID: "site1"}, "user1", false))
assert.False(t, b.IsBlocked(Locator{SiteID: "site1"}, "user1"), "user1 unblocked")
}
// makes new boltdb, put two records
func prep(t *testing.T) *BoltDB {
os.Remove(testDb)
+3
View File
@@ -47,4 +47,7 @@ type Interface interface {
Get(locator Locator, id int64) (Comment, error)
Vote(locator Locator, commentID int64, userID string, val bool) (Comment, error)
Count(locator Locator) (int, error)
SetBlock(locator Locator, userID string, status bool) error
IsBlocked(locator Locator, userID string) bool
}