add block/unblock rest and store
This commit is contained in:
@@ -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_
|
||||
|
||||
@@ -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
@@ -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
@@ -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
@@ -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)
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user