From 05902461122d78897feef14c92126c1af597f02c Mon Sep 17 00:00:00 2001 From: Umputun Date: Thu, 16 May 2019 20:49:07 -0500 Subject: [PATCH] move comment alter functionality from rest and admin to store service --- backend/app/migrator/disqus_test.go | 2 +- backend/app/migrator/migrator.go | 4 +- backend/app/migrator/migrator_test.go | 7 +- backend/app/migrator/native.go | 2 +- backend/app/migrator/native_test.go | 13 +- backend/app/migrator/wordpress_test.go | 2 +- backend/app/notify/notify.go | 4 +- backend/app/notify/notify_test.go | 2 +- backend/app/rest/api/admin.go | 37 +---- backend/app/rest/api/admin_test.go | 4 +- backend/app/rest/api/rest.go | 35 ----- backend/app/rest/api/rest_private.go | 6 +- backend/app/rest/api/rest_private_test.go | 2 +- backend/app/rest/api/rest_public.go | 22 ++- backend/app/rest/api/rss.go | 13 +- backend/app/rest/user.go | 8 ++ backend/app/store/engine/engine.go | 2 +- backend/app/store/service/service.go | 132 ++++++++++++++---- backend/app/store/service/service_test.go | 74 ++++++---- backend/go.mod | 3 +- backend/go.sum | 3 + .../vendor/github.com/go-pkgz/mongo/go.sum | 1 + .../github.com/hashicorp/golang-lru/go.mod | 2 + 23 files changed, 213 insertions(+), 167 deletions(-) diff --git a/backend/app/migrator/disqus_test.go b/backend/app/migrator/disqus_test.go index 3f3db451..5388f9b3 100644 --- a/backend/app/migrator/disqus_test.go +++ b/backend/app/migrator/disqus_test.go @@ -26,7 +26,7 @@ func TestDisqus_Import(t *testing.T) { assert.Nil(t, err) assert.Equal(t, 4, size) - last, err := dataStore.Last("test", 10, time.Time{}) + last, err := dataStore.Last("test", 10, time.Time{}, adminUser) assert.Nil(t, err) assert.Equal(t, 4, len(last), "4 comments imported") diff --git a/backend/app/migrator/migrator.go b/backend/app/migrator/migrator.go index a63d56c3..93ba4b53 100644 --- a/backend/app/migrator/migrator.go +++ b/backend/app/migrator/migrator.go @@ -27,7 +27,7 @@ type Exporter interface { // Store defines minimal interface needed to export and import comments type Store interface { Create(comment store.Comment) (commentID string, err error) - Find(locator store.Locator, sort string) ([]store.Comment, error) + Find(locator store.Locator, sort string, user store.User) ([]store.Comment, error) List(siteID string, limit int, skip int) ([]store.PostInfo, error) DeleteAll(siteID string) error Metas(siteID string) (umetas []service.UserMetaData, pmetas []service.PostMetaData, err error) @@ -42,6 +42,8 @@ type ImportParams struct { SiteID string } +var adminUser = store.User{Admin: true} + // ImportComments imports from given provider format and saves to store func ImportComments(p ImportParams) (int, error) { log.Printf("[INFO] import from %s (%s) to %s", p.InputFile, p.Provider, p.SiteID) diff --git a/backend/app/migrator/migrator_test.go b/backend/app/migrator/migrator_test.go index c004eb1c..4b1dae2e 100644 --- a/backend/app/migrator/migrator_test.go +++ b/backend/app/migrator/migrator_test.go @@ -10,6 +10,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/umputun/remark/backend/app/store" "github.com/umputun/remark/backend/app/store/admin" "github.com/umputun/remark/backend/app/store/engine" "github.com/umputun/remark/backend/app/store/service" @@ -36,7 +37,7 @@ func TestMigrator_ImportDisqus(t *testing.T) { assert.Nil(t, err) assert.Equal(t, 4, size) - last, err := dataStore.Last("test", 10, time.Time{}) + last, err := dataStore.Last("test", 10, time.Time{}, store.User{}) assert.Nil(t, err) assert.Equal(t, 4, len(last), "4 comments imported") } @@ -62,7 +63,7 @@ func TestMigrator_ImportWordPress(t *testing.T) { assert.Nil(t, err) assert.Equal(t, 3, size) - last, err := dataStore.Last("test", 10, time.Time{}) + last, err := dataStore.Last("test", 10, time.Time{}, store.User{}) assert.Nil(t, err) assert.Equal(t, 3, len(last), "3 comments imported") } @@ -92,7 +93,7 @@ func TestMigrator_ImportNative(t *testing.T) { assert.Nil(t, err) assert.Equal(t, 2, size) - last, err := dataStore.Last("radio-t", 10, time.Time{}) + last, err := dataStore.Last("radio-t", 10, time.Time{}, store.User{}) assert.Nil(t, err) assert.Equal(t, 2, len(last), "2 comments imported") } diff --git a/backend/app/migrator/native.go b/backend/app/migrator/native.go index 40db2a17..6084dd24 100644 --- a/backend/app/migrator/native.go +++ b/backend/app/migrator/native.go @@ -49,7 +49,7 @@ func (n *Native) Export(w io.Writer, siteID string) (size int, err error) { commentsCount := 0 for i := len(topics) - 1; i >= 0; i-- { // topics from List sorted in opposite direction topic := topics[i] - comments, e := n.DataStore.Find(store.Locator{SiteID: siteID, URL: topic.URL}, "time") + comments, e := n.DataStore.Find(store.Locator{SiteID: siteID, URL: topic.URL}, "time", adminUser) if e != nil { return commentsCount, e } diff --git a/backend/app/migrator/native_test.go b/backend/app/migrator/native_test.go index 7a0acbd4..ed0b0c97 100644 --- a/backend/app/migrator/native_test.go +++ b/backend/app/migrator/native_test.go @@ -77,12 +77,13 @@ func TestNative_Import(t *testing.T) { {"id":"f863bd79-fec6-4a75-b308-61fe5dd02aa1","pid":"1234","text":"some text2","user":{"name":"user name","id":"user2","picture":"","ip":"293ec5b0cf154855258824ec7fac5dc63d176915","admin":false},"locator":{"site":"radio-t","url":"https://radio-t.com/2"},"score":0,"votes":{},"time":"2017-12-20T15:18:23-06:00"}` b := prep(t) // write some recs - r := Native{DataStore: &service.DataStore{Interface: b, AdminStore: admin.NewStaticStore("12345", []string{}, "")}} + b.AdminStore = admin.NewStaticStore("12345", []string{}, "") + r := Native{DataStore: b} size, err := r.Import(strings.NewReader(inp), "radio-t") assert.Nil(t, err) assert.Equal(t, 2, size) - comments, err := b.Last("radio-t", 10, time.Time{}) + comments, err := b.Last("radio-t", 10, time.Time{}, store.User{}) assert.Nil(t, err) assert.Equal(t, 2, len(comments)) assert.Equal(t, "f863bd79-fec6-4a75-b308-61fe5dd02aa1", comments[0].ID) @@ -106,7 +107,8 @@ func TestNative_ImportWrongVersion(t *testing.T) { {"id":"f863bd79-fec6-4a75-b308-61fe5dd02aa1","pid":"1234","text":"some text2","user":{"name":"user name","id":"user2","picture":"","ip":"293ec5b0cf154855258824ec7fac5dc63d176915","admin":false},"locator":{"site":"radio-t","url":"https://radio-t.com/2"},"score":0,"votes":{},"time":"2017-12-20T15:18:23-06:00"}` b := prep(t) // write some recs - r := Native{DataStore: &service.DataStore{Interface: b, AdminStore: admin.NewStaticStore("12345", []string{}, "")}} + b.AdminStore = admin.NewStaticStore("12345", []string{}, "") + r := Native{DataStore: b} size, err := r.Import(strings.NewReader(inp), "radio-t") assert.EqualError(t, err, "unexpected import file version 2") assert.Equal(t, 0, size) @@ -126,11 +128,12 @@ func TestNative_ImportManyWithError(t *testing.T) { buf.WriteString("{}\n") b := prep(t) // write some recs - r := Native{DataStore: &service.DataStore{Interface: b, AdminStore: admin.NewStaticStore("12345", []string{}, "")}} + b.AdminStore = admin.NewStaticStore("12345", []string{}, "") + r := Native{DataStore: b} n, err := r.Import(buf, "radio-t") assert.EqualError(t, err, "failed to save 2 comments") assert.Equal(t, 1200, n) - comments, err := b.Find(store.Locator{SiteID: "radio-t", URL: "https://radio-t.com"}, "time") + comments, err := b.Find(store.Locator{SiteID: "radio-t", URL: "https://radio-t.com"}, "time", store.User{}) assert.Nil(t, err) assert.Equal(t, 1200, len(comments)) } diff --git a/backend/app/migrator/wordpress_test.go b/backend/app/migrator/wordpress_test.go index 001fbffd..86a99b93 100644 --- a/backend/app/migrator/wordpress_test.go +++ b/backend/app/migrator/wordpress_test.go @@ -27,7 +27,7 @@ func TestWordPress_Import(t *testing.T) { assert.Nil(t, err) assert.Equal(t, 3, size) - last, err := dataStore.Last(siteID, 10, time.Time{}) + last, err := dataStore.Last(siteID, 10, time.Time{}, adminUser) assert.Nil(t, err) assert.Equal(t, 3, len(last), "3 comments imported") diff --git a/backend/app/notify/notify.go b/backend/app/notify/notify.go index 886fabad..7157bdb6 100644 --- a/backend/app/notify/notify.go +++ b/backend/app/notify/notify.go @@ -31,7 +31,7 @@ type Destination interface { // Store defines the minimal interface accessing stored comments used by notifier type Store interface { - Get(locator store.Locator, id string) (store.Comment, error) + Get(locator store.Locator, id string, user store.User) (store.Comment, error) } type request struct { @@ -69,7 +69,7 @@ func (s *Service) Submit(comment store.Comment) { } parentComment := store.Comment{} if s.dataService != nil { - if p, err := s.dataService.Get(comment.Locator, comment.ParentID); err == nil { + if p, err := s.dataService.Get(comment.Locator, comment.ParentID, store.User{}); err == nil { parentComment = p } } diff --git a/backend/app/notify/notify_test.go b/backend/app/notify/notify_test.go index b3ca33c5..2dfbc632 100644 --- a/backend/app/notify/notify_test.go +++ b/backend/app/notify/notify_test.go @@ -146,7 +146,7 @@ func (m *mockDest) String() string { return fmt.Sprintf("mock id=%d, closed=%v", type mockStore struct{ data map[string]store.Comment } -func (m *mockStore) Get(_ store.Locator, id string) (store.Comment, error) { +func (m *mockStore) Get(_ store.Locator, id string, user store.User) (store.Comment, error) { res, ok := m.data[id] if !ok { return store.Comment{}, errors.New("no such id") diff --git a/backend/app/rest/api/admin.go b/backend/app/rest/api/admin.go index 2bd96113..59b3b979 100644 --- a/backend/app/rest/api/admin.go +++ b/backend/app/rest/api/admin.go @@ -67,7 +67,7 @@ func (a *admin) getUserInfoCtrl(w http.ResponseWriter, r *http.Request) { siteID := r.URL.Query().Get("site") log.Printf("[INFO] get user info for %s, site %s", userID, siteID) - ucomments, err := a.dataService.User(siteID, userID, 1, 0) + ucomments, err := a.dataService.User(siteID, userID, 1, 0, rest.GetUserOrEmpty(r)) if err != nil || len(ucomments) == 0 { rest.SendErrorJSON(w, r, http.StatusBadRequest, err, "can't get user info", rest.ErrInternal) return @@ -222,38 +222,3 @@ func (a *admin) setPinCtrl(w http.ResponseWriter, r *http.Request) { func (a *admin) checkBlocked(siteID string, user store.User) bool { return a.dataService.IsBlocked(siteID, user.ID) } - -// post-processes comments, hides text of all comments for blocked users, -// resets score and votes too. Also hides sensitive info for non-admin users -func (a *admin) alterComments(comments []store.Comment, r *http.Request) (res []store.Comment) { - res = make([]store.Comment, len(comments)) - - user, err := rest.GetUserInfo(r) - isAdmin := err == nil && user.Admin - - for i, c := range comments { - - blocked := a.dataService.IsBlocked(c.Locator.SiteID, c.User.ID) - // process blocked users - if blocked { - if !isAdmin { // reset comment to deleted for non-admins - c.SetDeleted(store.SoftDelete) - } - c.User.Blocked = true - c.Deleted = true - } - - // set verified status retroactively - if !blocked { - c.User.Verified = a.dataService.IsVerified(c.Locator.SiteID, c.User.ID) - } - - // hide info from non-admins - if !isAdmin { - c.User.IP = "" - } - - res[i] = c - } - return res -} diff --git a/backend/app/rest/api/admin_test.go b/backend/app/rest/api/admin_test.go index d5295de1..f3bb2ab6 100644 --- a/backend/app/rest/api/admin_test.go +++ b/backend/app/rest/api/admin_test.go @@ -618,7 +618,7 @@ func TestAdmin_DeleteMeRequest(t *testing.T) { _, err = srv.DataService.Create(c2) assert.Nil(t, err) - comments, err := srv.DataService.User("radio-t", "user1", 0, 0) + comments, err := srv.DataService.User("radio-t", "user1", 0, 0, store.User{}) assert.Nil(t, err) assert.Equal(t, 1, len(comments), "a comment for user1") @@ -655,7 +655,7 @@ func TestAdmin_DeleteMeRequest(t *testing.T) { require.NoError(t, err) assert.Equal(t, 200, resp.StatusCode) - _, err = srv.DataService.User("radio-t", "user1", 0, 0) + _, err = srv.DataService.User("radio-t", "user1", 0, 0, store.User{}) assert.EqualError(t, err, "no comments for user user1 in store") } diff --git a/backend/app/rest/api/rest.go b/backend/app/rest/api/rest.go index 946cb6bd..58c2243c 100644 --- a/backend/app/rest/api/rest.go +++ b/backend/app/rest/api/rest.go @@ -307,41 +307,6 @@ func (s *Rest) routes() chi.Router { return router } -func (s *Rest) alterComments(comments []store.Comment, r *http.Request) (res []store.Comment) { - - res = s.adminService.alterComments(comments, r) // apply admin's alteration - - // prepare vote info for client view - vote := func(c store.Comment, r *http.Request) store.Comment { - - c.Vote = 0 // default is "none" (not voted) - - user, err := rest.GetUserInfo(r) - if err != nil { - c.Votes = nil // hide voters list and don't set Vote for non-authed user - return c - } - - if v, ok := c.Votes[user.ID]; ok { - if v { - c.Vote = 1 - } else { - c.Vote = -1 - } - } - - c.Votes = nil // hide voters list - return c - } - - for i, c := range res { - c = vote(c, r) - res[i] = c - } - - return res -} - // updateLimiter returns UpdateLimiter if set, or 10 if not func (s *Rest) updateLimiter() float64 { lmt := 10.0 diff --git a/backend/app/rest/api/rest_private.go b/backend/app/rest/api/rest_private.go index 787ffbd5..58045611 100644 --- a/backend/app/rest/api/rest_private.go +++ b/backend/app/rest/api/rest_private.go @@ -67,7 +67,7 @@ func (s *Rest) createCommentCtrl(w http.ResponseWriter, r *http.Request) { } // DataService modifies comment - finalComment, err := s.DataService.Get(comment.Locator, id) + finalComment, err := s.DataService.Get(comment.Locator, id, rest.GetUserOrEmpty(r)) if err != nil { rest.SendErrorJSON(w, r, http.StatusInternalServerError, err, "can't load created comment", rest.ErrInternal) return @@ -107,7 +107,7 @@ func (s *Rest) updateCommentCtrl(w http.ResponseWriter, r *http.Request) { var currComment store.Comment var err error - if currComment, err = s.DataService.Get(locator, id); err != nil { + if currComment, err = s.DataService.Get(locator, id, rest.GetUserOrEmpty(r)); err != nil { rest.SendErrorJSON(w, r, http.StatusBadRequest, err, "can't find comment", rest.ErrCommentNotFound) return } @@ -213,7 +213,7 @@ func (s *Rest) userAllDataCtrl(w http.ResponseWriter, r *http.Request) { // get comments in 100 in each paginated request for i := 0; i < 100; i++ { - comments, errUser := s.DataService.User(siteID, user.ID, 100, i*100) + comments, errUser := s.DataService.User(siteID, user.ID, 100, i*100, rest.GetUserOrEmpty(r)) if errUser != nil { rest.SendErrorJSON(w, r, http.StatusInternalServerError, errUser, "can't get user comments", rest.ErrInternal) return diff --git a/backend/app/rest/api/rest_private_test.go b/backend/app/rest/api/rest_private_test.go index 2f71abb9..20be01e3 100644 --- a/backend/app/rest/api/rest_private_test.go +++ b/backend/app/rest/api/rest_private_test.go @@ -63,7 +63,7 @@ func TestRest_CreateOldPost(t *testing.T) { _, err := srv.DataService.Create(old) assert.Nil(t, err) - comments, err := srv.DataService.Find(store.Locator{SiteID: "radio-t", URL: "https://radio-t.com/blah1"}, "time") + comments, err := srv.DataService.Find(store.Locator{SiteID: "radio-t", URL: "https://radio-t.com/blah1"}, "time", store.User{}) assert.Nil(t, err) assert.Equal(t, 1, len(comments)) diff --git a/backend/app/rest/api/rest_public.go b/backend/app/rest/api/rest_public.go index 2a795514..5fd1f003 100644 --- a/backend/app/rest/api/rest_public.go +++ b/backend/app/rest/api/rest_public.go @@ -34,15 +34,14 @@ func (s *Rest) findCommentsCtrl(w http.ResponseWriter, r *http.Request) { key := cache.NewKey(locator.SiteID).ID(URLKeyWithUser(r)).Scopes(locator.SiteID, locator.URL) data, err := s.Cache.Get(key, func() ([]byte, error) { - comments, e := s.DataService.Find(locator, sort) + comments, e := s.DataService.Find(locator, sort, rest.GetUserOrEmpty(r)) if e != nil { comments = []store.Comment{} // error should clear comments and continue for post info } - maskedComments := s.alterComments(comments, r) var b []byte switch r.URL.Query().Get("format") { case "tree": - tree := service.MakeTree(maskedComments, sort, s.ReadOnlyAge) + tree := service.MakeTree(comments, sort, s.ReadOnlyAge) if tree.Nodes == nil { // eliminate json nil serialization tree.Nodes = []*service.Node{} } @@ -51,7 +50,7 @@ func (s *Rest) findCommentsCtrl(w http.ResponseWriter, r *http.Request) { } b, e = encodeJSONWithHTML(tree) default: - withInfo := commentsWithInfo{Comments: maskedComments} + withInfo := commentsWithInfo{Comments: comments} if info, ee := s.DataService.Info(locator, s.ReadOnlyAge); ee == nil { withInfo.Info = info } @@ -132,9 +131,9 @@ func (s *Rest) lastCommentsCtrl(w http.ResponseWriter, r *http.Request) { sinceTime := time.Time{} since := r.URL.Query().Get("since") if since != "" { - unixTS, err := strconv.ParseInt(since, 10, 64) - if err != nil { - rest.SendErrorJSON(w, r, http.StatusBadRequest, err, "can't translate since parameter", rest.ErrDecode) + unixTS, e := strconv.ParseInt(since, 10, 64) + if e != nil { + rest.SendErrorJSON(w, r, http.StatusBadRequest, e, "can't translate since parameter", rest.ErrDecode) return } sinceTime = time.Unix(unixTS/1000, 1000000*(unixTS%1000)) // since param in msec timestamp @@ -142,11 +141,10 @@ func (s *Rest) lastCommentsCtrl(w http.ResponseWriter, r *http.Request) { key := cache.NewKey(siteID).ID(URLKey(r)).Scopes(lastCommentsScope) data, err := s.Cache.Get(key, func() ([]byte, error) { - comments, e := s.DataService.Last(siteID, limit, sinceTime) + comments, e := s.DataService.Last(siteID, limit, sinceTime, rest.GetUserOrEmpty(r)) if e != nil { return nil, e } - comments = s.alterComments(comments, r) // filter deleted from last comments view. Blocked marked as deleted and will sneak in without filterDeleted := filterComments(comments, func(c store.Comment) bool { return !c.Deleted }) return encodeJSONWithHTML(filterDeleted) @@ -171,12 +169,11 @@ func (s *Rest) commentByIDCtrl(w http.ResponseWriter, r *http.Request) { log.Printf("[DEBUG] get comments by id %s, %s %s", id, siteID, url) - comment, err := s.DataService.Get(store.Locator{SiteID: siteID, URL: url}, id) + comment, err := s.DataService.Get(store.Locator{SiteID: siteID, URL: url}, id, rest.GetUserOrEmpty(r)) if err != nil { rest.SendErrorJSON(w, r, http.StatusBadRequest, err, "can't get comment by id", rest.ErrCommentNotFound) return } - comment = s.alterComments([]store.Comment{comment}, r)[0] render.Status(r, http.StatusOK) if err = R.RenderJSONWithHTML(w, r, comment); err != nil { @@ -204,11 +201,10 @@ func (s *Rest) findUserCommentsCtrl(w http.ResponseWriter, r *http.Request) { key := cache.NewKey(siteID).ID(URLKeyWithUser(r)).Scopes(userID, siteID) data, err := s.Cache.Get(key, func() ([]byte, error) { - comments, e := s.DataService.User(siteID, userID, limit, 0) + comments, e := s.DataService.User(siteID, userID, limit, 0, rest.GetUserOrEmpty(r)) if e != nil { return nil, e } - comments = s.alterComments(comments, r) comments = filterComments(comments, func(c store.Comment) bool { return !c.Deleted }) count, e := s.DataService.UserCount(siteID, userID) if e != nil { diff --git a/backend/app/rest/api/rss.go b/backend/app/rest/api/rss.go index 62d51b67..47ec8edf 100644 --- a/backend/app/rest/api/rss.go +++ b/backend/app/rest/api/rss.go @@ -28,11 +28,10 @@ func (s *Rest) rssPostCommentsCtrl(w http.ResponseWriter, r *http.Request) { key := cache.NewKey(locator.SiteID).ID(URLKey(r)).Scopes(locator.SiteID, locator.URL) data, err := s.Cache.Get(key, func() ([]byte, error) { - comments, e := s.DataService.Find(locator, "-time") + comments, e := s.DataService.Find(locator, "-time", rest.GetUserOrEmpty(r)) if e != nil { return nil, e } - comments = s.alterComments(comments, r) rss, e := s.toRssFeed(locator.URL, comments, "post comments for "+r.URL.Query().Get("url")) if e != nil { return nil, e @@ -60,11 +59,10 @@ func (s *Rest) rssSiteCommentsCtrl(w http.ResponseWriter, r *http.Request) { key := cache.NewKey(siteID).ID(URLKey(r)).Scopes(siteID, lastCommentsScope) data, err := s.Cache.Get(key, func() ([]byte, error) { - comments, e := s.DataService.Last(siteID, maxRssItems, time.Time{}) + comments, e := s.DataService.Last(siteID, maxRssItems, time.Time{}, rest.GetUserOrEmpty(r)) if e != nil { return nil, e } - comments = s.alterComments(comments, r) rss, e := s.toRssFeed(r.URL.Query().Get("site"), comments, "site comment for "+siteID) if e != nil { @@ -94,11 +92,10 @@ func (s *Rest) rssRepliesCtrl(w http.ResponseWriter, r *http.Request) { userName := "" key := cache.NewKey(siteID).ID(URLKey(r)).Scopes(siteID, lastCommentsScope) data, err := s.Cache.Get(key, func() (res []byte, e error) { - comments, e := s.DataService.Last(siteID, maxLastCommentsReply, time.Time{}) + comments, e := s.DataService.Last(siteID, maxLastCommentsReply, time.Time{}, rest.GetUserOrEmpty(r)) if e != nil { return nil, errors.Wrap(e, "can't get last comments") } - comments = s.alterComments(comments, r) replies := []store.Comment{} for _, c := range comments { if len(replies) > maxRssItems || c.Timestamp.Add(maxReplyDuration).Before(time.Now()) { @@ -109,7 +106,7 @@ func (s *Rest) rssRepliesCtrl(w http.ResponseWriter, r *http.Request) { } if c.ParentID != "" && !c.Deleted && c.User.ID != userID { // not interested in replies to yourself var pc store.Comment - if pc, e = s.DataService.Get(c.Locator, c.ParentID); e != nil { + if pc, e = s.DataService.Get(c.Locator, c.ParentID, rest.GetUserOrEmpty(r)); e != nil { return nil, errors.Wrap(e, "can't get parent comment") } if pc.User.ID == userID { @@ -166,7 +163,7 @@ func (s *Rest) toRssFeed(url string, comments []store.Comment, description strin } if c.ParentID != "" { // add indication to parent comment - parentComment, err := s.DataService.Get(c.Locator, c.ParentID) + parentComment, err := s.DataService.Get(c.Locator, c.ParentID, store.User{}) if err == nil { f.Title = fmt.Sprintf("%s > %s", c.User.Name, parentComment.User.Name) } else { diff --git a/backend/app/rest/user.go b/backend/app/rest/user.go index 7727e137..6ae05fd0 100644 --- a/backend/app/rest/user.go +++ b/backend/app/rest/user.go @@ -36,7 +36,15 @@ func GetUserInfo(r *http.Request) (user store.User, err error) { Verified: u.BoolAttr("verified"), Blocked: u.BoolAttr("blocked"), }, nil +} +// GetUserOrEmpty attempts to get user info from request and returns empty object if failed +func GetUserOrEmpty(r *http.Request) store.User { + user, err := GetUserInfo(r) + if err != nil { + return store.User{} + } + return user } // SetUserInfo sets user into request context diff --git a/backend/app/store/engine/engine.go b/backend/app/store/engine/engine.go index dbbdd654..3fcecb8f 100644 --- a/backend/app/store/engine/engine.go +++ b/backend/app/store/engine/engine.go @@ -10,7 +10,7 @@ import ( "github.com/umputun/remark/backend/app/store" ) -//go:generate sh -c "mockery -inpkg -name Interface -print > file.tmp && mv file.tmp engine_mock.go" +//go:generate sh -c "mockery -inpkg -name Interface -print > /tmp/engine-mock.tmp && mv /tmp/engine-mock.tmp engine_mock.go" // Interface combines all store interfaces type Interface interface { diff --git a/backend/app/store/service/service.go b/backend/app/store/service/service.go index 0d6ee63f..9999ffd8 100644 --- a/backend/app/store/service/service.go +++ b/backend/app/store/service/service.go @@ -1,3 +1,6 @@ +// Package service wraps engine interfaces with common logic unrelated to any particular engine implementation. +// All consumers should be using service.DataStore and not the naked engine! + package service import ( @@ -96,12 +99,48 @@ func (s *DataStore) Create(comment store.Comment) (commentID string, err error) return s.Interface.Create(comment) } +// Find wraps engine's Find call and alter results if needed +func (s *DataStore) Find(locator store.Locator, sort string, user store.User) ([]store.Comment, error) { + comments, err := s.Interface.Find(locator, sort) + if err != nil { + return comments, err + } + + changedSort := false + // set votes controversy for comments added prior to #274 + for i, c := range comments { + if c.Controversy == 0 && len(c.Votes) > 0 { + c.Controversy = s.controversy(s.upsAndDowns(c)) + if !changedSort && strings.Contains(sort, "controversy") { // trigger sort change + changedSort = true + } + } + comments[i] = s.alterComment(c, user) + } + + // resort commits if altered + if changedSort { + comments = engine.SortComments(comments, sort) + } + + return comments, nil +} + +// Get comment by ID +func (s *DataStore) Get(locator store.Locator, commentID string, user store.User) (store.Comment, error) { + c, err := s.Interface.Get(locator, commentID) + if err != nil { + return store.Comment{}, err + } + return s.alterComment(c, user), nil +} + // submitImages initiated delayed commit of all images from the comment uploaded to remark42 func (s *DataStore) submitImages(comment store.Comment) { s.ImageService.Submit(func() []string { c := comment - cc, err := s.Get(c.Locator, c.ID) // this can be called after last edit, we have to retrieve fresh comment + cc, err := s.Interface.Get(c.Locator, c.ID) // this can be called after last edit, we have to retrieve fresh comment if err != nil { log.Printf("[WARN] can't get comment's %s text for image extraction, %v", c.ID, err) return nil @@ -143,7 +182,7 @@ func (s *DataStore) prepareNewComment(comment store.Comment) (store.Comment, err // SetPin pin/un-pin comment as special func (s *DataStore) SetPin(locator store.Locator, commentID string, status bool) error { - comment, err := s.Get(locator, commentID) + comment, err := s.Interface.Get(locator, commentID) if err != nil { return err } @@ -158,7 +197,7 @@ func (s *DataStore) Vote(locator store.Locator, commentID string, userID string, cLock.Lock() // prevents race on voting defer cLock.Unlock() - comment, err = s.Get(locator, commentID) + comment, err = s.Interface.Get(locator, commentID) if err != nil { return comment, err } @@ -246,7 +285,7 @@ type EditRequest struct { // EditComment to edit text and update Edit info func (s *DataStore) EditComment(locator store.Locator, commentID string, req EditRequest) (comment store.Comment, err error) { - comment, err = s.Get(locator, commentID) + comment, err = s.Interface.Get(locator, commentID) if err != nil { return comment, err } @@ -295,7 +334,7 @@ func (s *DataStore) HasReplies(comment store.Comment) bool { return true } - comments, err := s.Last(comment.Locator.SiteID, maxLastCommentsReply, time.Time{}) + comments, err := s.Interface.Last(comment.Locator.SiteID, maxLastCommentsReply, time.Time{}) if err != nil { log.Printf("[WARN] can't get last comments for reply check, %v", err) return false @@ -318,7 +357,7 @@ func (s *DataStore) SetTitle(locator store.Locator, commentID string) (comment s return comment, errors.New("no title extractor") } - comment, err = s.Get(locator, commentID) + comment, err = s.Interface.Get(locator, commentID) if err != nil { return comment, err } @@ -452,30 +491,22 @@ func (s *DataStore) SetMetas(siteID string, umetas []UserMetaData, pmetas []Post return errs.ErrorOrNil() } -// Find wraps engine's Find call and alter results if needed -func (s *DataStore) Find(locator store.Locator, sort string) ([]store.Comment, error) { - comments, err := s.Interface.Find(locator, sort) +// User gets comment for given userID on siteID +func (s *DataStore) User(siteID, userID string, limit, skip int, user store.User) ([]store.Comment, error) { + comments, err := s.Interface.User(siteID, userID, limit, skip) if err != nil { return comments, err } + return s.alterComments(comments, user), nil +} - changedSort := false - // set votes controversy for comments added prior to #274 - for i, c := range comments { - if c.Controversy == 0 && len(c.Votes) > 0 { - comments[i].Controversy = s.controversy(s.upsAndDowns(c)) - if !changedSort && strings.Contains(sort, "controversy") { // trigger sort change - changedSort = true - } - } +// Last gets last comments for site, cross-post. Limited by count and optional since ts +func (s *DataStore) Last(siteID string, limit int, since time.Time, user store.User) ([]store.Comment, error) { + comments, err := s.Interface.Last(siteID, limit, since) + if err != nil { + return comments, err } - - // resort commits if altered - if changedSort { - comments = engine.SortComments(comments, sort) - } - - return comments, nil + return s.alterComments(comments, user), nil } func (s *DataStore) upsAndDowns(c store.Comment) (ups, downs int) { @@ -503,3 +534,54 @@ func (s *DataStore) getScopedLocks(id string) (lock sync.Locker) { return lock } + +func (s *DataStore) alterComments(cc []store.Comment, user store.User) (res []store.Comment) { + res = make([]store.Comment, len(cc)) + for i, c := range cc { + res[i] = s.alterComment(c, user) + } + return res +} + +func (s *DataStore) alterComment(c store.Comment, user store.User) (res store.Comment) { + + blocked := s.IsBlocked(c.Locator.SiteID, c.User.ID) + // process blocked users + if blocked { + if !user.Admin { // reset comment to deleted for non-admins + c.SetDeleted(store.SoftDelete) + } + c.User.Blocked = true + c.Deleted = true + } + + // set verified status retroactively + if !blocked { + c.User.Verified = s.IsVerified(c.Locator.SiteID, c.User.ID) + } + + // hide info from non-admins + if !user.Admin { + c.User.IP = "" + } + + c = s.prepVotes(c, user) + return c +} + +// prepare vote info for client view +func (s *DataStore) prepVotes(c store.Comment, user store.User) store.Comment { + + c.Vote = 0 // default is "none" (not voted) + + if v, ok := c.Votes[user.ID]; ok { + if v { + c.Vote = 1 + } else { + c.Vote = -1 + } + } + + c.Votes = nil // hide voters list + return c +} diff --git a/backend/app/store/service/service_test.go b/backend/app/store/service/service_test.go index 52f8d041..d6948df0 100644 --- a/backend/app/store/service/service_test.go +++ b/backend/app/store/service/service_test.go @@ -6,6 +6,7 @@ import ( "net/http" "net/http/httptest" "os" + "strconv" "strings" "sync" "sync/atomic" @@ -18,11 +19,11 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/mock" "github.com/stretchr/testify/require" - "github.com/umputun/remark/backend/app/store/image" "github.com/umputun/remark/backend/app/store" "github.com/umputun/remark/backend/app/store/admin" "github.com/umputun/remark/backend/app/store/engine" + "github.com/umputun/remark/backend/app/store/image" ) var testDb = "/tmp/test-remark.db" @@ -40,7 +41,7 @@ func TestService_CreateFromEmpty(t *testing.T) { assert.NoError(t, err) assert.True(t, id != "", id) - res, err := b.Get(store.Locator{URL: "https://radio-t.com", SiteID: "radio-t"}, id) + res, err := b.Interface.Get(store.Locator{URL: "https://radio-t.com", SiteID: "radio-t"}, id) assert.NoError(t, err) t.Logf("%+v", res) assert.Equal(t, "text", res.Text) @@ -66,7 +67,7 @@ func TestService_CreateFromPartial(t *testing.T) { assert.NoError(t, err) assert.True(t, id != "", id) - res, err := b.Get(store.Locator{URL: "https://radio-t.com", SiteID: "radio-t"}, id) + res, err := b.Interface.Get(store.Locator{URL: "https://radio-t.com", SiteID: "radio-t"}, id) assert.NoError(t, err) t.Logf("%+v", res) assert.Equal(t, "text", res.Text) @@ -94,7 +95,7 @@ func TestService_CreateFromPartialWithTitle(t *testing.T) { assert.NoError(t, err) assert.True(t, id != "", id) - res, err := b.Get(store.Locator{URL: "https://radio-t.com/p/2018/12/29/podcast-630/", SiteID: "radio-t"}, id) + res, err := b.Interface.Get(store.Locator{URL: "https://radio-t.com/p/2018/12/29/podcast-630/", SiteID: "radio-t"}, id) assert.NoError(t, err) t.Logf("%+v", res) assert.Equal(t, "Радио-Т 630 — Радио-Т Подкаст", res.PostTitle) @@ -102,7 +103,7 @@ func TestService_CreateFromPartialWithTitle(t *testing.T) { comment.PostTitle = "post blah" id, err = b.Create(comment) assert.NoError(t, err) - res, err = b.Get(store.Locator{URL: "https://radio-t.com/p/2018/12/29/podcast-630/", SiteID: "radio-t"}, id) + res, err = b.Interface.Get(store.Locator{URL: "https://radio-t.com/p/2018/12/29/podcast-630/", SiteID: "radio-t"}, id) assert.NoError(t, err) t.Logf("%+v", res) assert.Equal(t, "post blah", res.PostTitle, "keep comment title") @@ -145,7 +146,7 @@ func TestService_SetTitle(t *testing.T) { assert.NoError(t, err) assert.True(t, id != "", id) - res, err := b.Get(store.Locator{URL: tss.URL + "/post1", SiteID: "radio-t"}, id) + res, err := b.Interface.Get(store.Locator{URL: tss.URL + "/post1", SiteID: "radio-t"}, id) assert.NoError(t, err) t.Logf("%+v", res) assert.Equal(t, "", res.PostTitle) @@ -174,7 +175,7 @@ func TestService_Vote(t *testing.T) { _, err := b.Create(comment) assert.NoError(t, err) - res, err := b.Last("radio-t", 0, time.Time{}) + res, err := b.Interface.Last("radio-t", 0, time.Time{}) t.Logf("%+v", res[0]) assert.Nil(t, err) assert.Equal(t, 3, len(res)) @@ -195,7 +196,7 @@ func TestService_Vote(t *testing.T) { assert.NotNil(t, err, "double-voting rejected") assert.True(t, strings.HasPrefix(err.Error(), "user user1 already voted")) - res, err = b.Last("radio-t", 0, time.Time{}) + res, err = b.Interface.Last("radio-t", 0, time.Time{}) assert.Nil(t, err) assert.Equal(t, 3, len(res)) assert.Equal(t, 1, res[0].Score) @@ -204,7 +205,7 @@ func TestService_Vote(t *testing.T) { _, err = b.Vote(store.Locator{URL: "https://radio-t.com", SiteID: "radio-t"}, res[0].ID, "user1", false) assert.Nil(t, err, "vote reset") - res, err = b.Last("radio-t", 0, time.Time{}) + res, err = b.Interface.Last("radio-t", 0, time.Time{}) assert.Nil(t, err) assert.Equal(t, 3, len(res)) assert.Equal(t, 0, res[0].Score) @@ -250,7 +251,7 @@ func TestService_VoteAggressive(t *testing.T) { _, err := b.Create(comment) assert.NoError(t, err) - res, err := b.Last("radio-t", 0, time.Time{}) + res, err := b.Interface.Last("radio-t", 0, time.Time{}) require.Nil(t, err) t.Logf("%+v", res[0]) assert.Equal(t, 3, len(res)) @@ -271,7 +272,7 @@ func TestService_VoteAggressive(t *testing.T) { }() } wg.Wait() - res, err = b.Last("radio-t", 0, time.Time{}) + res, err = b.Interface.Last("radio-t", 0, time.Time{}) require.NoError(t, err) t.Logf("%+v", res[0]) @@ -290,7 +291,7 @@ func TestService_VoteAggressive(t *testing.T) { }() } wg.Wait() - res, err = b.Last("radio-t", 0, time.Time{}) + res, err = b.Interface.Last("radio-t", 0, time.Time{}) require.NoError(t, err) assert.Equal(t, 3, len(res)) t.Logf("%+v %d", res[0], res[0].Score) @@ -309,7 +310,7 @@ func TestService_VoteConcurrent(t *testing.T) { } _, err := b.Create(comment) assert.NoError(t, err) - res, err := b.Last("radio-t", 0, time.Time{}) + res, err := b.Interface.Last("radio-t", 0, time.Time{}) require.Nil(t, err) // concurrent vote +1 as multiple users for the same comment @@ -324,7 +325,7 @@ func TestService_VoteConcurrent(t *testing.T) { }() } wg.Wait() - res, err = b.Last("radio-t", 0, time.Time{}) + res, err = b.Interface.Last("radio-t", 0, time.Time{}) require.NoError(t, err) assert.Equal(t, 100, res[0].Score, "should have 100 score") assert.Equal(t, 100, len(res[0].Votes), "should have 100 votes") @@ -370,7 +371,7 @@ func TestService_VoteControversy(t *testing.T) { assert.InDelta(t, 1.73, c.Controversy, 0.01) // check if stored - res, err := b.Last("radio-t", 0, time.Time{}) + res, err := b.Interface.Last("radio-t", 0, time.Time{}) require.NoError(t, err) assert.Equal(t, 1, res[0].Score, "should have 1 score") assert.InDelta(t, 1.73, res[0].Controversy, 0.01) @@ -404,7 +405,7 @@ func TestService_Pin(t *testing.T) { defer teardown(t) b := DataStore{Interface: prepStoreEngine(t), AdminStore: admin.NewStaticKeyStore("secret 123")} - res, err := b.Last("radio-t", 0, time.Time{}) + res, err := b.Interface.Last("radio-t", 0, time.Time{}) t.Logf("%+v", res[0]) assert.Nil(t, err) assert.Equal(t, 2, len(res)) @@ -413,13 +414,13 @@ func TestService_Pin(t *testing.T) { err = b.SetPin(store.Locator{URL: "https://radio-t.com", SiteID: "radio-t"}, res[0].ID, true) assert.Nil(t, err) - c, err := b.Get(store.Locator{URL: "https://radio-t.com", SiteID: "radio-t"}, res[0].ID) + c, err := b.Interface.Get(store.Locator{URL: "https://radio-t.com", SiteID: "radio-t"}, res[0].ID) assert.Nil(t, err) assert.Equal(t, true, c.Pin) err = b.SetPin(store.Locator{URL: "https://radio-t.com", SiteID: "radio-t"}, res[0].ID, false) assert.Nil(t, err) - c, err = b.Get(store.Locator{URL: "https://radio-t.com", SiteID: "radio-t"}, res[0].ID) + c, err = b.Interface.Get(store.Locator{URL: "https://radio-t.com", SiteID: "radio-t"}, res[0].ID) assert.Nil(t, err) assert.Equal(t, false, c.Pin) } @@ -428,7 +429,7 @@ func TestService_EditComment(t *testing.T) { defer teardown(t) b := DataStore{Interface: prepStoreEngine(t), AdminStore: admin.NewStaticKeyStore("secret 123")} - res, err := b.Last("radio-t", 0, time.Time{}) + res, err := b.Interface.Last("radio-t", 0, time.Time{}) t.Logf("%+v", res[0]) assert.Nil(t, err) assert.Equal(t, 2, len(res)) @@ -441,7 +442,7 @@ func TestService_EditComment(t *testing.T) { assert.Equal(t, "xxx", comment.Text) assert.Equal(t, "yyy", comment.Orig) - c, err := b.Get(store.Locator{URL: "https://radio-t.com", SiteID: "radio-t"}, res[0].ID) + c, err := b.Interface.Get(store.Locator{URL: "https://radio-t.com", SiteID: "radio-t"}, res[0].ID) assert.Nil(t, err) assert.Equal(t, "my edit", c.Edit.Summary) assert.Equal(t, "xxx", c.Text) @@ -455,7 +456,7 @@ func TestService_DeleteComment(t *testing.T) { defer teardown(t) b := DataStore{Interface: prepStoreEngine(t), AdminStore: admin.NewStaticKeyStore("secret 123")} - res, err := b.Last("radio-t", 0, time.Time{}) + res, err := b.Interface.Last("radio-t", 0, time.Time{}) t.Logf("%+v", res[0]) assert.Nil(t, err) assert.Equal(t, 2, len(res)) @@ -464,7 +465,7 @@ func TestService_DeleteComment(t *testing.T) { _, err = b.EditComment(store.Locator{URL: "https://radio-t.com", SiteID: "radio-t"}, res[0].ID, EditRequest{Delete: true}) assert.Nil(t, err) - c, err := b.Get(store.Locator{URL: "https://radio-t.com", SiteID: "radio-t"}, res[0].ID) + c, err := b.Interface.Get(store.Locator{URL: "https://radio-t.com", SiteID: "radio-t"}, res[0].ID) assert.Nil(t, err) assert.True(t, c.Deleted) t.Logf("%+v", c) @@ -474,7 +475,7 @@ func TestService_EditCommentDurationFailed(t *testing.T) { defer teardown(t) b := DataStore{Interface: prepStoreEngine(t), EditDuration: 100 * time.Millisecond, AdminStore: admin.NewStaticKeyStore("secret 123")} - res, err := b.Last("radio-t", 0, time.Time{}) + res, err := b.Interface.Last("radio-t", 0, time.Time{}) t.Logf("%+v", res[0]) assert.Nil(t, err) assert.Equal(t, 2, len(res)) @@ -491,7 +492,7 @@ func TestService_EditCommentReplyFailed(t *testing.T) { defer teardown(t) b := DataStore{Interface: prepStoreEngine(t), AdminStore: admin.NewStaticKeyStore("secret 123")} - res, err := b.Last("radio-t", 0, time.Time{}) + res, err := b.Interface.Last("radio-t", 0, time.Time{}) t.Logf("%+v", res[1]) assert.Nil(t, err) assert.Equal(t, 2, len(res)) @@ -534,6 +535,7 @@ func TestService_ValidateComment(t *testing.T) { assert.Nil(t, e, "check #%d", n) continue } + require.NotNil(t, e) assert.EqualError(t, tt.err, e.Error(), "check #%d", n) } } @@ -669,7 +671,7 @@ func TestService_Find(t *testing.T) { b := DataStore{Interface: prepStoreEngine(t), EditDuration: 100 * time.Millisecond, AdminStore: admin.NewStaticStore("secret 123", []string{"user2"}, "user@email.com")} - res, err := b.Find(store.Locator{URL: "https://radio-t.com", SiteID: "radio-t"}, "time") + res, err := b.Find(store.Locator{URL: "https://radio-t.com", SiteID: "radio-t"}, "time", store.User{}) require.NoError(t, err) assert.Equal(t, 2, len(res)) @@ -687,7 +689,7 @@ func TestService_Find(t *testing.T) { assert.Nil(t, err) // make sure Controversy altered - res, err = b.Find(store.Locator{URL: "https://radio-t.com", SiteID: "radio-t"}, "-controversy") + res, err = b.Find(store.Locator{URL: "https://radio-t.com", SiteID: "radio-t"}, "-controversy", store.User{}) require.NoError(t, err) assert.Equal(t, 3, len(res)) assert.Equal(t, "123456", res[0].ID) @@ -722,6 +724,26 @@ func TestService_submitImages(t *testing.T) { time.Sleep(250 * time.Millisecond) } +func TestService_alterComment(t *testing.T) { + defer teardown(t) + svc := DataStore{Interface: prepStoreEngine(t), AdminStore: admin.NewStaticKeyStore("secret 123")} + + tbl := []struct { + c store.Comment + u store.User + r store.Comment + }{ + {store.Comment{}, store.User{}, store.Comment{}}, + {store.Comment{}, store.User{}, store.Comment{}}, + } + for i, tt := range tbl { + t.Run(strconv.Itoa(i), func(t *testing.T) { + r := svc.alterComment(tt.c, tt.u) + assert.Equal(t, tt.r, r) + }) + } +} + // makes new boltdb, put two records func prepStoreEngine(t *testing.T) engine.Interface { _ = os.Remove(testDb) diff --git a/backend/go.mod b/backend/go.mod index 7b424bc5..5d3d8cbc 100644 --- a/backend/go.mod +++ b/backend/go.mod @@ -24,8 +24,7 @@ require ( github.com/golang/protobuf v1.3.1 // indirect github.com/google/uuid v0.0.0-20161128191214-064e2069ce9c github.com/gorilla/feeds v1.1.0 - github.com/hashicorp/errwrap v1.0.0 // indirect - github.com/hashicorp/go-multierror v0.0.0-20171204182908-b7773ae21874 + github.com/hashicorp/go-multierror v1.0.0 github.com/hashicorp/golang-lru v0.5.1 // indirect github.com/jessevdk/go-flags v0.0.0-20180331124232-1c38ed7ad0cc github.com/microcosm-cc/bluemonday v0.0.0-20171222152607-542fd4642604 diff --git a/backend/go.sum b/backend/go.sum index 72602d2e..20045e07 100644 --- a/backend/go.sum +++ b/backend/go.sum @@ -71,6 +71,8 @@ github.com/hashicorp/errwrap v1.0.0 h1:hLrqtEDnRye3+sgx6z4qVLNuviH3MR5aQ0ykNJa/U github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= github.com/hashicorp/go-multierror v0.0.0-20171204182908-b7773ae21874 h1:em+tTnzgU7N22woTBMcSJAOW7tRHAkK597W+MD/CpK8= github.com/hashicorp/go-multierror v0.0.0-20171204182908-b7773ae21874/go.mod h1:JMRHfdO9jKNzS/+BTlxCjKNQHg/jZAft8U7LloJvN7I= +github.com/hashicorp/go-multierror v1.0.0 h1:iVjPR7a6H0tWELX5NxNe7bYopibicUzc7uPribsnS6o= +github.com/hashicorp/go-multierror v1.0.0/go.mod h1:dHtQlpGsu+cZNNAkkCN/P3hoUDHhCYQXV3UM06sGGrk= github.com/hashicorp/golang-lru v0.5.0 h1:CL2msUPvZTLb5O648aiLNJw3hnBxN2+1Jq8rCOH9wdo= github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= github.com/hashicorp/golang-lru v0.5.1 h1:0hERBMJE1eitiLkihrMvRVBYAkpHzc/J3QdDN+dAcgU= @@ -107,6 +109,7 @@ github.com/stretchr/objx v0.2.0 h1:Hbg2NidpLE8veEBkEZTL3CvlkUIVzuU9jDplZO54c48= github.com/stretchr/objx v0.2.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE= github.com/stretchr/testify v1.3.0 h1:TivCn/peBQ7UY8ooIcPgZFpTNSz0Q2U6UrFlUfqbe0Q= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= +github.com/umputun/remark v1.3.2 h1:m0PvvY7GEWSIjfQeqddvglJVswxUoSBPi4RrsI3sJz8= go.etcd.io/bbolt v1.3.2 h1:Z/90sZLPOeCy2PwprqkFa25PdkusRzaj9P8zm/KNyvk= go.etcd.io/bbolt v1.3.2/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU= go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU= diff --git a/backend/vendor/github.com/go-pkgz/mongo/go.sum b/backend/vendor/github.com/go-pkgz/mongo/go.sum index 66ac0631..21efc324 100644 --- a/backend/vendor/github.com/go-pkgz/mongo/go.sum +++ b/backend/vendor/github.com/go-pkgz/mongo/go.sum @@ -1,5 +1,6 @@ github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/globalsign/mgo v0.0.0-20180615134936-113d3961e731 h1:y7wyeiA6T+TT+HGC9DYypvLkUeg99N4rqHMzn2MmjYk= github.com/globalsign/mgo v0.0.0-20180615134936-113d3961e731/go.mod h1:xkRDCp4j0OGD1HRkm4kmhM+pmpv3AKq5SU7GMg4oO/Q= diff --git a/backend/vendor/github.com/hashicorp/golang-lru/go.mod b/backend/vendor/github.com/hashicorp/golang-lru/go.mod index 824cb97e..8ad8826b 100644 --- a/backend/vendor/github.com/hashicorp/golang-lru/go.mod +++ b/backend/vendor/github.com/hashicorp/golang-lru/go.mod @@ -1 +1,3 @@ module github.com/hashicorp/golang-lru + +go 1.12