From b2d22fce97b84b2ccbef4947040d919cf59723aa Mon Sep 17 00:00:00 2001 From: Umputun Date: Mon, 25 Dec 2017 21:12:04 -0600 Subject: [PATCH] add pin comment --- README.md | 20 +++++++++++--------- app/rest/admin.go | 15 +++++++++++++++ app/store/bolt.go | 29 +++++++++++++++++++++++++++++ app/store/bolt_test.go | 24 ++++++++++++++++++++++++ app/store/store.go | 3 +++ 5 files changed, 82 insertions(+), 9 deletions(-) diff --git a/README.md b/README.md index 415efcaf..14138e42 100644 --- a/README.md +++ b/README.md @@ -28,7 +28,7 @@ TBD ### Authorization - `GET /login/{provider}?from=http://url` - perform "social" login with one of supported providers and redirect to `url` -- `GET /logout` - logout +- `GET /logout` - logout - `GET /api/v1/user` - get user info, _auth required_ ```go @@ -49,14 +49,15 @@ _currently supported providers are `google` and `github`_ ```go type Comment struct { - ID string `json:"id"` // read only - ParentID string `json:"pid"` - Text string `json:"text"` - User User `json:"user"` // read only - Locator Locator `json:"locator"` - Score int `json:"score"` // read only - Votes map[string]bool `json:"votes"` // read only - Timestamp time.Time `json:"time"` // read only + ID string `json:"id"` // comment ID, read only + ParentID string `json:"pid"` // parent ID + Text string `json:"text"` // comment text + User User `json:"user"` // user info, read only + Locator Locator `json:"locator"` // post locator + Score int `json:"score"` // comment score, read only + Votes map[string]bool `json:"votes"` // comment votes, read only + Timestamp time.Time `json:"time"` // time stamp, read only + Pin bool `json:"pin"` // pinned status, read only } type Locator struct { @@ -90,3 +91,4 @@ Sort can be `time` or `score`. Supported sort order with prefix -/+, i.e. `-time - `DELETE /api/v1/admin/comment/{id}?url=post-url` - delete comment by `id`. _auth and admin required_ - `PUT /api/v1/admin/user/{userid}?site=side-id&block=1` - block or unblock user. _auth and admin required_ - `GET /api/v1/admin/export?site=side-id&block=1` - export all comments. _auth and admin required_ +- `PUT /api/v1/admin/pin/{id}?site=side-id&pin=1` - pin or unpin comment. _auth and admin required_ diff --git a/app/rest/admin.go b/app/rest/admin.go index dd2396ba..b46c7de2 100644 --- a/app/rest/admin.go +++ b/app/rest/admin.go @@ -24,6 +24,7 @@ func (a *admin) routes() chi.Router { router.Delete("/comment/{id}", a.deleteCommentCtrl) router.Put("/user/{userid}", a.setBlockCtrl) router.Get("/export", a.exportCtrl) + router.Put("/pin/{id}", a.setPinCtrl) return router } @@ -59,6 +60,20 @@ func (a *admin) setBlockCtrl(w http.ResponseWriter, r *http.Request) { render.JSON(w, r, JSON{"user_id": userID, "site_id": siteID, "block": blockStatus}) } +// PUT /pin/{id}?site=side-id&pin=1 +func (a *admin) setPinCtrl(w http.ResponseWriter, r *http.Request) { + commentID := chi.URLParam(r, "id") + siteID := r.URL.Query().Get("site") + pinStatus := r.URL.Query().Get("pin") == "1" + + if err := a.dataStore.SetPin(store.Locator{SiteID: siteID}, commentID, pinStatus); err != nil { + httpError(w, r, http.StatusBadRequest, err, "can't set pin status") + return + } + + render.JSON(w, r, JSON{"comment_id": commentID, "site_id": siteID, "pin": pinStatus}) +} + // GET /export?site=site-id func (a *admin) exportCtrl(w http.ResponseWriter, r *http.Request) { siteID := r.URL.Query().Get("site") diff --git a/app/store/bolt.go b/app/store/bolt.go index c1de2147..acbb4280 100644 --- a/app/store/bolt.go +++ b/app/store/bolt.go @@ -354,6 +354,35 @@ func (b BoltDB) List(locator Locator) (result []string, err error) { return result, err } +// SetPin pin/un-pin comment as special +func (b *BoltDB) SetPin(locator Locator, commentID string, status bool) error { + + comment, err := b.Get(locator, commentID) + if err != nil { + return err + } + + return b.Update(func(tx *bolt.Tx) error { + bucket := tx.Bucket([]byte(locator.URL)) + if bucket == nil { + return errors.Errorf("no bucket %s in store", locator.URL) + } + + comment.Pin = status + + // serialize comment to json []byte for bolt and save + jdata, jerr := json.Marshal(&comment) + if jerr != nil { + return errors.Wrap(jerr, "can't marshal comment") + } + if err := bucket.Put([]byte(comment.ID), jdata); err != nil { + return errors.Wrapf(err, "failed to put key %s", comment.ID) + } + + return nil + }) +} + func (b *BoltDB) bucketForBlock(locator Locator, userID string) []byte { return []byte(fmt.Sprintf("%s%s", blocksBucketPrefix, locator.SiteID)) } diff --git a/app/store/bolt_test.go b/app/store/bolt_test.go index 3f36d4e0..5856fa4f 100644 --- a/app/store/bolt_test.go +++ b/app/store/bolt_test.go @@ -141,6 +141,30 @@ func TestBoltDB_List(t *testing.T) { assert.Equal(t, []string{"https://radio-t.com", "https://radio-t.com/2"}, res) } +func TestBoltDB_Pin(t *testing.T) { + defer os.Remove(testDb) + b := prep(t) + + res, err := b.Last(Locator{URL: "https://radio-t.com"}, 0) + t.Logf("%+v", res[0]) + assert.Nil(t, err) + assert.Equal(t, 2, len(res)) + assert.Equal(t, false, res[0].Pin) + + err = b.SetPin(Locator{URL: "https://radio-t.com"}, res[0].ID, true) + assert.Nil(t, err) + + c, err := b.Get(Locator{URL: "https://radio-t.com"}, res[0].ID) + assert.Nil(t, err) + assert.Equal(t, true, c.Pin) + + err = b.SetPin(Locator{URL: "https://radio-t.com"}, res[0].ID, false) + assert.Nil(t, err) + c, err = b.Get(Locator{URL: "https://radio-t.com"}, res[0].ID) + assert.Nil(t, err) + assert.Equal(t, false, c.Pin) +} + // makes new boltdb, put two records func prep(t *testing.T) *BoltDB { os.Remove(testDb) diff --git a/app/store/store.go b/app/store/store.go index 4bb01d09..b3087a3e 100644 --- a/app/store/store.go +++ b/app/store/store.go @@ -24,6 +24,7 @@ type Comment struct { Score int `json:"score"` Votes map[string]bool `json:"votes"` Timestamp time.Time `json:"time"` + Pin bool `json:"pin"` } // Locator keeps site and url of the post @@ -63,6 +64,8 @@ type Interface interface { SetBlock(locator Locator, userID string, status bool) error IsBlocked(locator Locator, userID string) bool + + SetPin(locator Locator, commentID string, status bool) error } func makeCommentID() string {