From 49593a3327df428c09570ebfc04684abdcba27bb Mon Sep 17 00:00:00 2001 From: Umputun Date: Sat, 29 Jun 2019 21:17:34 -0500 Subject: [PATCH 1/9] wip: memory rpc example --- backend/_example/rpc/main.go | 22 + backend/_example/rpc/plugin/admin.go | 58 ++ backend/_example/rpc/plugin/admin_test.go | 52 ++ backend/_example/rpc/plugin/engine.go | 436 +++++++++++++ backend/_example/rpc/plugin/engine_test.go | 725 +++++++++++++++++++++ backend/_example/rpc/server/store.go | 230 +++++++ 6 files changed, 1523 insertions(+) create mode 100644 backend/_example/rpc/main.go create mode 100644 backend/_example/rpc/plugin/admin.go create mode 100644 backend/_example/rpc/plugin/admin_test.go create mode 100644 backend/_example/rpc/plugin/engine.go create mode 100644 backend/_example/rpc/plugin/engine_test.go create mode 100644 backend/_example/rpc/server/store.go diff --git a/backend/_example/rpc/main.go b/backend/_example/rpc/main.go new file mode 100644 index 00000000..de668734 --- /dev/null +++ b/backend/_example/rpc/main.go @@ -0,0 +1,22 @@ +/* + * Copyright 2019 Umputun. All rights reserved. + * Use of this source code is governed by a MIT-style + * license that can be found in the LICENSE file. + */ + +package main + +// Opts with all cli commands and flags +var opts struct { + API string `long:"api" env:"API" default:"/" description:"api root url"` + Port int `long:"port" env:"PORT" default:"8080" description:"rpc server port"` + AuthUser string `long:"auth-user" env:"AUTH_USER" default:"" description:"auth user name"` + AuthPasswd string `long:"auth-passwd" env:"AUTH_PASSWD" default:"" description:"auth password"` + Secret string `long:"secret" env:"SECRET" required:"true" description:"secret key"` + Dbg bool `long:"dbg" env:"DEBUG" description:"debug mode"` +} + + +func main() { + +} diff --git a/backend/_example/rpc/plugin/admin.go b/backend/_example/rpc/plugin/admin.go new file mode 100644 index 00000000..6f457199 --- /dev/null +++ b/backend/_example/rpc/plugin/admin.go @@ -0,0 +1,58 @@ +/* + * Copyright 2019 Umputun. All rights reserved. + * Use of this source code is governed by a MIT-style + * license that can be found in the LICENSE file. + */ + +package plugin + +import ( + log "github.com/go-pkgz/lgr" + "github.com/pkg/errors" +) + +// MemAdminStore implements admin.Store with mongo backend +type MemAdminStore struct { + data map[string]AdminRec + key string +} + +// AdminRec is a records per site with all admin info in +type AdminRec struct { + SiteID string `bson:"site"` + IDs []string `bson:"ids"` + Email string `bson:"email"` +} + +const mongoAdmin = "admin" + +// NewMemAdminStore makes admin Store in memory +func NewMemAdminStore(key string) *MemAdminStore { + log.Print("[DEBUG] make memory admin store") + return &MemAdminStore{data: map[string]AdminRec{}, key: key} +} + +// Key executes find by siteID and returns substructure with secret key +func (m *MemAdminStore) Key() (key string, err error) { + return m.key, nil +} + +// Admins executes find by siteID and returns admins ids +func (m *MemAdminStore) Admins(siteID string) (ids []string, err error) { + resp, ok := m.data[siteID] + if !ok { + return nil, errors.Errorf("site %s not found", siteID) + } + + return resp.IDs, nil +} + +// Email executes find by siteID and returns admin's email +func (m *MemAdminStore) Email(siteID string) (email string, err error) { + resp, ok := m.data[siteID] + if !ok { + return "", errors.Errorf("site %s not found", siteID) + } + + return resp.Email, nil +} diff --git a/backend/_example/rpc/plugin/admin_test.go b/backend/_example/rpc/plugin/admin_test.go new file mode 100644 index 00000000..1fcb46e8 --- /dev/null +++ b/backend/_example/rpc/plugin/admin_test.go @@ -0,0 +1,52 @@ +/* + * Copyright 2019 Umputun. All rights reserved. + * Use of this source code is governed by a MIT-style + * license that can be found in the LICENSE file. + */ + +package plugin + +import ( + "testing" + + "github.com/stretchr/testify/assert" + + "github.com/umputun/remark/backend/app/store/admin" +) + +func TestMongoStore_Get(t *testing.T) { + + adm := NewMemAdminStore("secret") + var ms admin.Store = adm + + adm.data = map[string]AdminRec{ + "site1": {"site1", []string{"i11", "i12"}, "e1"}, + "site2": {"site2", []string{"i21", "i22"}, "e2"}, + } + + admins, err := ms.Admins("site1") + assert.NoError(t, err) + assert.Equal(t, []string{"i11", "i12"}, admins) + email, err := ms.Email("site1") + assert.NoError(t, err) + assert.Equal(t, "e1", email) + key, err := ms.Key() + assert.NoError(t, err) + assert.Equal(t, "secret", key) + + admins, err = ms.Admins("site2") + assert.NoError(t, err) + assert.Equal(t, []string{"i21", "i22"}, admins) + email, err = ms.Email("site2") + assert.NoError(t, err) + assert.Equal(t, "e2", email) + key, err = ms.Key() + assert.NoError(t, err) + assert.Equal(t, "secret", key) + + admins, err = ms.Admins("no-site-in-db") + assert.EqualError(t, err, "site no-site-in-db not found") + + email, err = ms.Email("no-site-in-db") + assert.EqualError(t, err, "site no-site-in-db not found") +} diff --git a/backend/_example/rpc/plugin/engine.go b/backend/_example/rpc/plugin/engine.go new file mode 100644 index 00000000..ed892ded --- /dev/null +++ b/backend/_example/rpc/plugin/engine.go @@ -0,0 +1,436 @@ +/* + * Copyright 2019 Umputun. All rights reserved. + * Use of this source code is governed by a MIT-style + * license that can be found in the LICENSE file. + */ + +package plugin + +import ( + "log" + "sort" + "sync" + "time" + + "github.com/pkg/errors" + + "github.com/umputun/remark/backend/app/store/engine" + + "github.com/umputun/remark/backend/app/store" +) + +const lastLimit = 1000 + +// MemEngine implements in-memory engine interface +type MemEngine struct { + posts map[string][]store.Comment // key is siteID + metaUsers map[string]metaUser // key is userID + metaPosts map[store.Locator]metaPost // key is post's locator + sync.RWMutex +} + +type metaPost struct { + PostURL string + SiteID string + ReadOnly bool +} + +type metaUser struct { + UserID string + SiteID string + Verified bool + Blocked bool + BlockedUntil time.Time +} + +// NewMemEngine makes in-memory engine. +func NewMemEngine() *MemEngine { + + result := &MemEngine{ + posts: map[string][]store.Comment{}, + metaUsers: map[string]metaUser{}, + metaPosts: map[store.Locator]metaPost{}, + } + return result +} + +// Create new comment, write can be buffered and delayed. +func (m *MemEngine) Create(comment store.Comment) (commentID string, err error) { + + if ro, e := m.Flag(engine.FlagRequest{Flag: engine.ReadOnly, Locator: comment.Locator}); e == nil && ro { + return "", errors.Errorf("post %s is read-only", comment.Locator.URL) + } + + m.Lock() + defer m.Unlock() + comments := m.posts[comment.Locator.SiteID] + for _, c := range comments { + if c.ID == comment.ID { + return "", errors.New("dup key") + } + } + comments = append(comments, comment) + m.posts[comment.Locator.SiteID] = comments + return comment.ID, nil +} + +// Find returns all comments for post and sorts results +func (m *MemEngine) Find(req engine.FindRequest) (comments []store.Comment, err error) { + m.RLock() + defer m.RUnlock() + + comments = []store.Comment{} + + if req.Sort == "" { + req.Sort = "time" + } + + switch { + + case req.Locator.SiteID != "" && req.Locator.URL != "": // find comments for site and url + comments = m.match(m.posts[req.Locator.SiteID], func(c store.Comment) bool { + return c.Locator == req.Locator + }) + + case req.Locator.SiteID != "" && req.Locator.URL == "" && req.UserID == "": // find last comments for site + if req.Limit > lastLimit || req.Limit == 0 { + req.Limit = lastLimit + } + if req.Since.IsZero() { + req.Since = time.Date(2000, 1, 1, 0, 0, 0, 0, time.UTC) + } + + comments = m.match(m.posts[req.Locator.SiteID], func(c store.Comment) bool { + return !c.Deleted && c.Timestamp.After(req.Since) + }) + comments = engine.SortComments(comments, "-time") + if len(comments) > req.Limit { + comments = comments[:req.Limit] + } + return comments, nil + + case req.Locator.SiteID != "" && req.UserID != "": // find comments for user + comments = m.match(m.posts[req.Locator.SiteID], func(c store.Comment) bool { + return c.User.ID == req.UserID + }) + } + comments = engine.SortComments(comments, req.Sort) + if req.Skip > 0 && req.Skip > len(comments) { + return []store.Comment{}, nil + } + if req.Skip > 0 && req.Skip < len(comments) { + comments = comments[req.Skip:] + } + + if req.Limit > 0 && req.Limit < len(comments) { + comments = comments[:req.Limit] + } + + return comments, err +} + +// Get returns comment for locator.URL and commentID string +func (m *MemEngine) Get(req engine.GetRequest) (comment store.Comment, err error) { + m.RLock() + defer m.RUnlock() + return m.get(req.Locator, req.CommentID) +} + +// Update updates comment for locator.URL with mutable part of comment +func (m *MemEngine) Update(comment store.Comment) error { + m.Lock() + defer m.Unlock() + return m.updateComment(comment) +} + +// Count returns number of comments for post or user +func (m *MemEngine) Count(req engine.FindRequest) (count int, err error) { + m.RLock() + defer m.RUnlock() + + switch { + case req.Locator.URL != "": // comment's count for post + comments := m.match(m.posts[req.Locator.SiteID], func(c store.Comment) bool { + return c.Locator == req.Locator && !c.Deleted + }) + return len(comments), nil + case req.UserID != "": + comments := m.match(m.posts[req.Locator.SiteID], func(c store.Comment) bool { + return c.User.ID == req.UserID && !c.Deleted + }) + return len(comments), nil + default: + return 0, errors.Errorf("invalid count request %+v", req) + } +} + +// Info get post(s) meta info +func (m *MemEngine) Info(req engine.InfoRequest) (res []store.PostInfo, err error) { + m.RLock() + defer m.RUnlock() + res = []store.PostInfo{} + + if req.Locator.URL != "" { // post info + comments := m.match(m.posts[req.Locator.SiteID], func(c store.Comment) bool { + return c.Locator == req.Locator + }) + if len(comments) == 0 { + return nil, errors.New("not found") + } + info := store.PostInfo{ + URL: req.Locator.URL, + Count: len(comments), + ReadOnly: false, + FirstTS: comments[0].Timestamp.UTC(), + LastTS: comments[len(comments)-1].Timestamp.UTC(), + } + // set read-only from age and manual bucket + info.ReadOnly = req.ReadOnlyAge > 0 && !info.FirstTS.IsZero() && + info.FirstTS.AddDate(0, 0, req.ReadOnlyAge).Before(time.Now()) + if !info.ReadOnly { + v := m.checkFlag(engine.FlagRequest{Flag: engine.ReadOnly, Locator: req.Locator}) + info.ReadOnly = v + } + return []store.PostInfo{info}, nil + } + + if req.Locator.URL == "" && req.Locator.SiteID != "" { // site info (list) + if req.Limit <= 0 { + req.Limit = 1000 + } + if req.Skip < 0 { + req.Skip = 0 + } + + infoAll := map[store.Locator]store.PostInfo{} + for _, c := range m.posts[req.Locator.SiteID] { + var info store.PostInfo + var ok bool + if info, ok = infoAll[c.Locator]; !ok { + info = store.PostInfo{URL: c.Locator.URL, FirstTS: c.Timestamp.UTC()} + } + info.Count++ + info.LastTS = c.Timestamp.UTC() + infoAll[c.Locator] = info + } + + n := 0 + for _, v := range infoAll { + n++ + if len(res) >= req.Limit { + break + } + if req.Skip > 0 && n <= req.Skip { + continue + } + res = append(res, v) + } + sort.Slice(res, func(i, j int) bool { + return res[i].URL > res[j].URL + }) + return res, nil + } + + return nil, errors.Errorf("invalid info request %+v", req) +} + +// Flag sets and gets flag values +func (m *MemEngine) Flag(req engine.FlagRequest) (val bool, err error) { + m.Lock() + defer m.Unlock() + + if req.Update == engine.FlagNonSet { // read flag value, no update requested + return m.checkFlag(req), nil + } + // write flag value + return m.setFlag(req) +} + +// ListFlags get list of flagged keys, like blocked & verified user +// works for full locator (post flags) or with userID +func (m *MemEngine) ListFlags(req engine.FlagRequest) (res []interface{}, err error) { + m.RLock() + defer m.RUnlock() + + res = []interface{}{} + + switch req.Flag { + case engine.Verified: + for _, u := range m.metaUsers { + if u.SiteID == req.Locator.SiteID { + res = append(res, u.UserID) + } + } + return res, nil + + case engine.Blocked: + log.Printf("%+v", m.metaUsers) + for _, u := range m.metaUsers { + if u.SiteID == req.Locator.SiteID && u.Blocked && u.BlockedUntil.After(time.Now()) { + res = append(res, store.BlockedUser{ID: u.UserID, Until: u.BlockedUntil}) + } + } + return res, nil + } + + return nil, errors.Errorf("flag %s not listable", req.Flag) +} + +// Delete post(s) by id or by userID +func (m *MemEngine) Delete(req engine.DeleteRequest) error { + + m.Lock() + defer m.Unlock() + + switch { + case req.Locator.URL != "" && req.CommentID != "": // delete comment + return m.deleteComment(req.Locator, req.CommentID, req.DeleteMode) + + case req.Locator.SiteID != "" && req.UserID != "" && req.CommentID == "": // delete user + comments := m.match(m.posts[req.Locator.SiteID], func(c store.Comment) bool { + return c.User.ID == req.UserID && !c.Deleted + }) + for _, c := range comments { + if e := m.deleteComment(c.Locator, c.ID, req.DeleteMode); e != nil { + return e + } + } + return nil + + case req.Locator.SiteID != "" && req.Locator.URL == "" && req.CommentID == "" && req.UserID == "": // delete site + if _, ok := m.posts[req.Locator.SiteID]; !ok { + return errors.New("not found") + } + m.posts[req.Locator.SiteID] = []store.Comment{} + return nil + } + + return errors.Errorf("invalid delete request %+v", req) +} + +func (m *MemEngine) deleteComment(loc store.Locator, id string, mode store.DeleteMode) error { + + comments := m.match(m.posts[loc.SiteID], func(c store.Comment) bool { + return c.Locator == loc && c.ID == id + }) + if len(comments) == 0 { + return errors.New("not found") + } + + comments[0].SetDeleted(mode) + return m.updateComment(comments[0]) +} + +// Close store +func (m *MemEngine) Close() error { + return nil +} + +func (m *MemEngine) checkFlag(req engine.FlagRequest) (val bool) { + switch req.Flag { + case engine.Blocked: + if meta, ok := m.metaUsers[req.UserID]; ok { + if meta.SiteID != req.Locator.SiteID { + return false + } + return meta.Blocked && meta.BlockedUntil.After(time.Now()) + } + case engine.Verified: + if meta, ok := m.metaUsers[req.UserID]; ok { + if meta.SiteID != req.Locator.SiteID { + return false + } + return meta.Verified + } + case engine.ReadOnly: + if meta, ok := m.metaPosts[req.Locator]; ok { + return meta.ReadOnly + } + } + return false +} + +func (m *MemEngine) setFlag(req engine.FlagRequest) (res bool, err error) { + + status := false + if req.Update == engine.FlagTrue { + status = true + } + + switch req.Flag { + + case engine.Blocked: + until := time.Time{} + if status { + until = time.Now().AddDate(100, 0, 0) // permanent is 100years + if req.TTL > 0 { + until = time.Now().Add(req.TTL) + } + } + meta := m.metaUsers[req.UserID] + meta = metaUser{ + UserID: req.UserID, + SiteID: req.Locator.SiteID, + Blocked: status, + BlockedUntil: until, + } + m.metaUsers[req.UserID] = meta + + case engine.Verified: + meta := m.metaUsers[req.UserID] + meta = metaUser{ + UserID: req.UserID, + SiteID: req.Locator.SiteID, + Verified: status, + } + m.metaUsers[req.UserID] = meta + + case engine.ReadOnly: + info, ok := m.metaPosts[req.Locator] + if !ok { + info.SiteID = req.Locator.SiteID + info.PostURL = req.Locator.URL + } + info.ReadOnly = status + m.metaPosts[req.Locator] = info + } + return status, errors.Wrapf(err, "failed to set flag %+v", req) +} + +func (m *MemEngine) get(loc store.Locator, commentID string) (store.Comment, error) { + comments := m.match(m.posts[loc.SiteID], func(c store.Comment) bool { + return c.Locator == loc && c.ID == commentID + }) + if len(comments) == 0 { + return store.Comment{}, errors.New("not found") + } + return comments[0], nil +} + +func (m *MemEngine) updateComment(comment store.Comment) error { + comments := m.posts[comment.Locator.SiteID] + for i, c := range comments { + if c.ID == comment.ID && c.Locator == comment.Locator { + c.Text = comment.Text + c.Orig = comment.Orig + c.Score = comment.Score + c.Votes = comment.Votes + c.Pin = comment.Pin + c.Deleted = comment.Deleted + c.User = comment.User + comments[i] = c + m.posts[comment.Locator.SiteID] = comments + return nil + } + } + return errors.New("not found") +} + +func (m *MemEngine) match(comments []store.Comment, fn func(c store.Comment) bool) (res []store.Comment) { + for _, c := range comments { + if fn(c) { + res = append(res, c) + } + } + return res +} diff --git a/backend/_example/rpc/plugin/engine_test.go b/backend/_example/rpc/plugin/engine_test.go new file mode 100644 index 00000000..6ff3a6ee --- /dev/null +++ b/backend/_example/rpc/plugin/engine_test.go @@ -0,0 +1,725 @@ +/* + * Copyright 2019 Umputun. All rights reserved. + * Use of this source code is governed by a MIT-style + * license that can be found in the LICENSE file. + */ + +package plugin + +import ( + "fmt" + "strings" + "testing" + "time" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + "github.com/umputun/remark/backend/app/store" + "github.com/umputun/remark/backend/app/store/engine" +) + +func TestMem_CreateAndFind(t *testing.T) { + m := prepMem(t) // adds two comments + + req := engine.FindRequest{Locator: store.Locator{URL: "https://radio-t.com", SiteID: "radio-t"}, Sort: "time"} + res, err := m.Find(req) + assert.NoError(t, err) + require.Equal(t, 2, len(res)) + assert.Equal(t, `some text, link`, res[0].Text) + assert.Equal(t, "user1", res[0].User.ID) + t.Log(res[0].ID) + + _, err = m.Create(store.Comment{ID: res[0].ID, Locator: store.Locator{URL: "https://radio-t.com", SiteID: "radio-t"}}) + require.NotNil(t, err) + assert.True(t, strings.Contains(err.Error(), "dup key"), err.Error()) + + id, err := m.Create(store.Comment{ID: "id-3", Locator: store.Locator{URL: "https://radio-t2.com", SiteID: "radio-t2"}}) + require.NoError(t, err) + assert.Equal(t, "id-3", id) + req = engine.FindRequest{Locator: store.Locator{URL: "https://radio-t2.com", SiteID: "radio-t2"}, Sort: "time"} + res, err = m.Find(req) + assert.NoError(t, err) + require.Equal(t, 1, len(res)) +} + +func TestMem_CreateFailedReadOnly(t *testing.T) { + b := prepMem(t) + comment := store.Comment{ + ID: "id-ro", + Text: `some text, link`, + Timestamp: time.Date(2017, 12, 20, 15, 18, 22, 0, time.Local), + Locator: store.Locator{URL: "https://radio-t.com/ro", SiteID: "radio-t"}, + User: store.User{ID: "user1", Name: "user name"}, + } + + flagReq := engine.FlagRequest{Locator: comment.Locator, Flag: engine.ReadOnly, Update: engine.FlagTrue} + v, err := b.Flag(flagReq) + require.NoError(t, err) + assert.Equal(t, true, v) + + _, err = b.Create(comment) + assert.NotNil(t, err) + assert.Equal(t, "post https://radio-t.com/ro is read-only", err.Error()) + + flagReq = engine.FlagRequest{Locator: comment.Locator, Flag: engine.ReadOnly, Update: engine.FlagFalse} + v, err = b.Flag(flagReq) + require.NoError(t, err) + assert.Equal(t, false, v) + + _, err = b.Create(comment) + assert.NoError(t, err) +} + +func TestMem_Get(t *testing.T) { + b := prepMem(t) + req := engine.FindRequest{Locator: store.Locator{URL: "https://radio-t.com", SiteID: "radio-t"}, Sort: "time"} + res, err := b.Find(req) + assert.NoError(t, err) + require.Equal(t, 2, len(res), "2 records initially") + + comment, err := b.Get(getReq(store.Locator{URL: "https://radio-t.com", SiteID: "radio-t"}, res[1].ID)) + assert.NoError(t, err) + assert.Equal(t, "some text2", comment.Text) + + _, err = b.Get(getReq(store.Locator{URL: "https://radio-t.com", SiteID: "radio-t"}, "1234567")) + assert.EqualError(t, err, `not found`) + + _, err = b.Get(getReq(store.Locator{URL: "https://radio-t.com", SiteID: "bad"}, res[1].ID)) + assert.EqualError(t, err, `not found`) +} + +func TestMem_Update(t *testing.T) { + b := prepMem(t) + req := engine.FindRequest{Locator: store.Locator{URL: "https://radio-t.com", SiteID: "radio-t"}, Sort: "time"} + res, err := b.Find(req) + assert.NoError(t, err) + require.Equal(t, 2, len(res), "2 records initially") + + comment := res[0] + comment.Text = "abc 123" + comment.Score = 100 + err = b.Update(comment) + assert.NoError(t, err) + + comment, err = b.Get(getReq(store.Locator{URL: "https://radio-t.com", SiteID: "radio-t"}, res[0].ID)) + assert.NoError(t, err) + assert.Equal(t, "abc 123", comment.Text) + assert.Equal(t, res[0].ID, comment.ID) + assert.Equal(t, 100, comment.Score) + + comment.Locator.SiteID = "bad" + err = b.Update(comment) + assert.EqualError(t, err, `not found`) + + comment.Locator.SiteID = "https://radio-t.com" + comment.Locator.URL = "https://radio-t.com-bad" + err = b.Update(comment) + assert.EqualError(t, err, `not found`) +} + +func TestMem_FindLast(t *testing.T) { + b := prepMem(t) + req := engine.FindRequest{Locator: store.Locator{SiteID: "radio-t"}, Sort: "-time"} + res, err := b.Find(req) + assert.NoError(t, err) + require.Equal(t, 2, len(res)) + assert.Equal(t, "some text2", res[0].Text) + + req.Limit = 1 + res, err = b.Find(req) + assert.NoError(t, err) + require.Equal(t, 1, len(res)) + assert.Equal(t, "some text2", res[0].Text) + + req.Locator.SiteID = "bad" + res, err = b.Find(req) + assert.NoError(t, err) + assert.Equal(t, 0, len(res)) +} + +func TestMem_FindLastSince(t *testing.T) { + b := prepMem(t) + ts := time.Date(2017, 12, 20, 15, 18, 21, 0, time.Local) + req := engine.FindRequest{Locator: store.Locator{SiteID: "radio-t"}, Sort: "-time", Since: ts} + res, err := b.Find(req) + assert.NoError(t, err) + require.Equal(t, 2, len(res)) + assert.Equal(t, "some text2", res[0].Text) + + req.Since = time.Date(2017, 12, 20, 15, 18, 22, 0, time.Local) + res, err = b.Find(req) + assert.NoError(t, err) + require.Equal(t, 1, len(res)) + assert.Equal(t, "some text2", res[0].Text) + + req.Since = time.Date(2017, 12, 20, 16, 18, 22, 0, time.Local) + res, err = b.Find(req) + assert.NoError(t, err) + assert.Equal(t, 0, len(res)) +} + +func TestMem_FindForUser(t *testing.T) { + b := prepMem(t) + req := engine.FindRequest{Locator: store.Locator{SiteID: "radio-t"}, Sort: "-time", UserID: "user1", Limit: 5} + res, err := b.Find(req) + assert.NoError(t, err) + require.Equal(t, 2, len(res)) + assert.Equal(t, "some text2", res[0].Text, "sorted by -time") + + req = engine.FindRequest{Locator: store.Locator{SiteID: "radio-t"}, Sort: "-time", UserID: "user1", Limit: 1} + res, err = b.Find(req) + assert.NoError(t, err) + require.Equal(t, 1, len(res), "allow 1 comment") + assert.Equal(t, "some text2", res[0].Text, "sorted by -time") + + req = engine.FindRequest{Locator: store.Locator{SiteID: "radio-t"}, Sort: "-time", UserID: "user1", Limit: 1, Skip: 1} + res, err = b.Find(req) + assert.NoError(t, err) + require.Equal(t, 1, len(res), "allow 1 comment") + assert.Equal(t, `some text, link`, res[0].Text, "second comment") + + req = engine.FindRequest{Locator: store.Locator{SiteID: "bad"}, Sort: "-time", UserID: "user1", Limit: 1, Skip: 1} + res, err = b.Find(req) + assert.NoError(t, err) + assert.Equal(t, 0, len(res), "no comments") + + req = engine.FindRequest{Locator: store.Locator{SiteID: "radio-t"}, Sort: "-time", UserID: "userZ", Limit: 1, Skip: 1} + res, err = b.Find(req) + assert.NoError(t, err) + assert.Equal(t, 0, len(res), "no comments") +} + +func TestMem_FindForUserPagination(t *testing.T) { + b := NewMemEngine() + + c := store.Comment{ + Locator: store.Locator{URL: "https://radio-t.com", SiteID: "radio-t"}, + User: store.User{ID: "user1", Name: "user name"}, + } + + // write 200 comments + for i := 0; i < 200; i++ { + c.ID = fmt.Sprintf("idd-%d", i) + c.Text = fmt.Sprintf("text #%d", i) + c.Timestamp = time.Date(2017, 12, 20, 15, 18, i, 0, time.Local) + _, err := b.Create(c) + require.Nil(t, err) + } + + // get all comments + req := engine.FindRequest{Locator: store.Locator{SiteID: "radio-t"}, Sort: "-time", UserID: "user1"} + res, err := b.Find(req) + assert.NoError(t, err) + require.Equal(t, 200, len(res)) + assert.Equal(t, "idd-199", res[0].ID) + + // seek 0, 5 comments + req.Limit = 5 + res, err = b.Find(req) + assert.NoError(t, err) + require.Equal(t, 5, len(res)) + assert.Equal(t, "idd-199", res[0].ID) + assert.Equal(t, "idd-195", res[4].ID) + + // seek 10, 3 comments + req.Skip, req.Limit = 10, 3 + res, err = b.Find(req) + assert.NoError(t, err) + require.Equal(t, 3, len(res)) + assert.Equal(t, "idd-189", res[0].ID) + assert.Equal(t, "idd-187", res[2].ID) + + // seek 195, ask 10 comments + req.Skip, req.Limit = 195, 10 + res, err = b.Find(req) + assert.NoError(t, err) + require.Equal(t, 5, len(res)) + assert.Equal(t, "idd-4", res[0].ID) + assert.Equal(t, "idd-0", res[4].ID) + + // seek 255, ask 10 comments + req.Skip, req.Limit = 255, 10 + res, err = b.Find(req) + assert.NoError(t, err) + assert.Equal(t, 0, len(res)) +} + +func TestMem_CountPost(t *testing.T) { + b := prepMem(t) + req := engine.FindRequest{Locator: store.Locator{URL: "https://radio-t.com", SiteID: "radio-t"}} + c, err := b.Count(req) + assert.NoError(t, err) + require.Equal(t, 2, c) + + req = engine.FindRequest{Locator: store.Locator{URL: "https://radio-t.com-xxx", SiteID: "radio-t"}} + c, err = b.Count(req) + assert.NoError(t, err) + assert.Equal(t, 0, c) + + req = engine.FindRequest{Locator: store.Locator{URL: "https://radio-t.com", SiteID: "bad"}} + c, err = b.Count(req) + assert.NoError(t, err) + assert.Equal(t, 0, c) +} + +func TestMem_CountUser(t *testing.T) { + b := prepMem(t) + req := engine.FindRequest{Locator: store.Locator{SiteID: "radio-t"}, UserID: "user1"} + c, err := b.Count(req) + assert.NoError(t, err) + require.Equal(t, 2, c) + + req = engine.FindRequest{Locator: store.Locator{SiteID: "bad"}, UserID: "user1"} + c, err = b.Count(req) + assert.NoError(t, err) + assert.Equal(t, 0, c) + + req = engine.FindRequest{Locator: store.Locator{SiteID: "radio-t"}, UserID: "userZ"} + c, err = b.Count(req) + assert.NoError(t, err) + assert.Equal(t, 0, c) +} + +func TestMem_InfoPost(t *testing.T) { + b := prepMem(t) + ts := func(min int) time.Time { return time.Date(2017, 12, 20, 15, 18, min, 0, time.Local).In(time.UTC) } + + // add one more for https://radio-t.com/2 + comment := store.Comment{ + ID: "12345", + Text: `some text, link`, + Timestamp: time.Date(2017, 12, 20, 15, 18, 24, 0, time.Local), + Locator: store.Locator{URL: "https://radio-t.com/2", SiteID: "radio-t"}, + User: store.User{ID: "user1", Name: "user name"}, + } + _, err := b.Create(comment) + assert.NoError(t, err) + + req := engine.InfoRequest{Locator: store.Locator{URL: "https://radio-t.com/2", SiteID: "radio-t"}, ReadOnlyAge: 0} + r, err := b.Info(req) + require.NoError(t, err) + assert.Equal(t, []store.PostInfo{{URL: "https://radio-t.com/2", Count: 1, FirstTS: ts(24), LastTS: ts(24)}}, r) + + req = engine.InfoRequest{Locator: store.Locator{URL: "https://radio-t.com/2", SiteID: "radio-t"}, ReadOnlyAge: 10} + r, err = b.Info(req) + require.NoError(t, err) + assert.Equal(t, []store.PostInfo{{URL: "https://radio-t.com/2", Count: 1, + FirstTS: ts(24), LastTS: ts(24), ReadOnly: true}}, r) + + req = engine.InfoRequest{Locator: store.Locator{URL: "https://radio-t.com", SiteID: "radio-t"}, ReadOnlyAge: 0} + r, err = b.Info(req) + require.NoError(t, err) + assert.Equal(t, []store.PostInfo{{URL: "https://radio-t.com", Count: 2, FirstTS: ts(22), LastTS: ts(23)}}, r) + + req = engine.InfoRequest{Locator: store.Locator{URL: "https://radio-t.com/error", SiteID: "radio-t"}, ReadOnlyAge: 0} + _, err = b.Info(req) + require.NotNil(t, err) + + req = engine.InfoRequest{Locator: store.Locator{URL: "https://radio-t.com", SiteID: "radio-t-error"}, ReadOnlyAge: 0} + _, err = b.Info(req) + require.NotNil(t, err) + + fr := engine.FlagRequest{Flag: engine.ReadOnly, + Locator: store.Locator{URL: "https://radio-t.com/2", SiteID: "radio-t"}, Update: engine.FlagTrue} + _, err = b.Flag(fr) + require.NoError(t, err) + req = engine.InfoRequest{Locator: store.Locator{URL: "https://radio-t.com/2", SiteID: "radio-t"}, ReadOnlyAge: 0} + r, err = b.Info(req) + require.NoError(t, err) + assert.Equal(t, []store.PostInfo{{URL: "https://radio-t.com/2", Count: 1, FirstTS: ts(24), LastTS: ts(24), + ReadOnly: true}}, r) +} + +func TestMem_InfoList(t *testing.T) { + b := prepMem(t) + // add one more for https://radio-t.com/2 + comment := store.Comment{ + ID: "12345", + Text: `some text, link`, + Timestamp: time.Date(2017, 12, 20, 15, 18, 22, 0, time.Local), + Locator: store.Locator{URL: "https://radio-t.com/2", SiteID: "radio-t"}, + User: store.User{ID: "user1", Name: "user name"}, + } + _, err := b.Create(comment) + assert.NoError(t, err) + + ts := func(sec int) time.Time { return time.Date(2017, 12, 20, 15, 18, sec, 0, time.Local).In(time.UTC) } + + req := engine.InfoRequest{Locator: store.Locator{SiteID: "radio-t"}} + res, err := b.Info(req) + assert.NoError(t, err) + assert.EqualValues(t, []store.PostInfo{{URL: "https://radio-t.com/2", Count: 1, FirstTS: ts(22), LastTS: ts(22)}, + {URL: "https://radio-t.com", Count: 2, FirstTS: ts(22), LastTS: ts(23)}}, res) + + req = engine.InfoRequest{Locator: store.Locator{SiteID: "radio-t"}, Limit: -1, Skip: -1} + res, err = b.Info(req) + assert.NoError(t, err) + assert.EqualValues(t, []store.PostInfo{{URL: "https://radio-t.com/2", Count: 1, FirstTS: ts(22), LastTS: ts(22)}, + {URL: "https://radio-t.com", Count: 2, FirstTS: ts(22), LastTS: ts(23)}}, res) + + req = engine.InfoRequest{Locator: store.Locator{SiteID: "radio-t"}, Limit: 1} + res, err = b.Info(req) + assert.NoError(t, err) + assert.Equal(t, []store.PostInfo{{URL: "https://radio-t.com/2", Count: 1, FirstTS: ts(22), LastTS: ts(22)}}, res) + + req = engine.InfoRequest{Locator: store.Locator{SiteID: "radio-t"}, Limit: 1, Skip: 1} + res, err = b.Info(req) + assert.NoError(t, err) + assert.Equal(t, []store.PostInfo{{URL: "https://radio-t.com", Count: 2, FirstTS: ts(22), LastTS: ts(23)}}, res) + + req = engine.InfoRequest{Locator: store.Locator{SiteID: "bad"}, Limit: 1, Skip: 1} + res, err = b.Info(req) + assert.NoError(t, err) + assert.Equal(t, []store.PostInfo{}, res) +} + +func TestMem_FlagBlockedUser(t *testing.T) { + + b := prepMem(t) + req := engine.FlagRequest{Locator: store.Locator{SiteID: "radio-t"}, UserID: "user1"} + val, err := b.Flag(req) + assert.NoError(t, err) + assert.False(t, val, "nothing blocked yet") + + req = engine.FlagRequest{Flag: engine.Blocked, Locator: store.Locator{SiteID: "radio-t"}, UserID: "user1", + Update: engine.FlagTrue} + _, err = b.Flag(req) + assert.NoError(t, err) + val, err = b.Flag(engine.FlagRequest{Flag: engine.Blocked, Locator: store.Locator{SiteID: "radio-t"}, UserID: "user1"}) + assert.NoError(t, err) + assert.True(t, val, "user1 blocked") + + req = engine.FlagRequest{Flag: engine.Blocked, Locator: store.Locator{SiteID: "radio-t"}, UserID: "user1", + Update: engine.FlagTrue} + _, err = b.Flag(req) + assert.NoError(t, err) + val, err = b.Flag(engine.FlagRequest{Flag: engine.Blocked, Locator: store.Locator{SiteID: "radio-t"}, UserID: "user1"}) + assert.NoError(t, err) + assert.True(t, val, "user1 still blocked") + + req = engine.FlagRequest{Flag: engine.Blocked, Locator: store.Locator{SiteID: "radio-t"}, UserID: "user1", + Update: engine.FlagFalse} + _, err = b.Flag(req) + assert.NoError(t, err) + val, err = b.Flag(engine.FlagRequest{Flag: engine.Blocked, Locator: store.Locator{SiteID: "radio-t"}, UserID: "user1"}) + assert.NoError(t, err) + assert.False(t, val, "user1 unblocked") +} + +func TestMem_FlagReadOnlyPost(t *testing.T) { + + b := prepMem(t) + req := engine.FlagRequest{Locator: store.Locator{SiteID: "radio-t", URL: "url-1"}, Flag: engine.ReadOnly} + val, err := b.Flag(req) + assert.NoError(t, err) + assert.False(t, val, "nothing ro") + + req = engine.FlagRequest{Locator: store.Locator{SiteID: "radio-t", URL: "url-1"}, Flag: engine.ReadOnly, + Update: engine.FlagTrue} + val, err = b.Flag(req) + assert.NoError(t, err) + req = engine.FlagRequest{Locator: store.Locator{SiteID: "radio-t", URL: "url-1"}, Flag: engine.ReadOnly} + val, err = b.Flag(req) + assert.NoError(t, err) + assert.True(t, val, "url-1 ro") + + req = engine.FlagRequest{Locator: store.Locator{SiteID: "radio-t", URL: "url-2"}, Flag: engine.ReadOnly} + val, err = b.Flag(req) + assert.NoError(t, err) + assert.False(t, val, "url-2 still writable") + + req = engine.FlagRequest{Locator: store.Locator{SiteID: "radio-t", URL: "url-1"}, Flag: engine.ReadOnly, + Update: engine.FlagFalse} + _, err = b.Flag(req) + assert.NoError(t, err) + req = engine.FlagRequest{Locator: store.Locator{SiteID: "radio-t", URL: "url-1"}, Flag: engine.ReadOnly} + val, err = b.Flag(req) + assert.NoError(t, err) + assert.False(t, val, "url-1 writable") +} + +func TestMem_FlagVerified(t *testing.T) { + + b := prepMem(t) + isVerified := func(site, user string) bool { + req := engine.FlagRequest{Flag: engine.Verified, Locator: store.Locator{SiteID: site}, UserID: user} + v, err := b.Flag(req) + require.NoError(t, err) + return v + } + + setVerified := func(site, user string, status engine.FlagStatus) error { + req := engine.FlagRequest{Flag: engine.Verified, Locator: store.Locator{SiteID: site}, UserID: user, Update: status} + _, err := b.Flag(req) + return err + } + + assert.False(t, isVerified("radio-t", "u1"), "nothing verified") + + assert.NoError(t, setVerified("radio-t", "u1", engine.FlagTrue)) + assert.True(t, isVerified("radio-t", "u1"), "u1 verified") + + assert.False(t, isVerified("radio-t", "u2"), "u2 still not verified") + assert.NoError(t, setVerified("radio-t", "u1", engine.FlagFalse)) + assert.False(t, isVerified("radio-t", "u1"), "u1 not verified anymore") + + assert.NoError(t, setVerified("bad", "u1", engine.FlagTrue)) + assert.NoError(t, setVerified("radio-t", "u1xyz", engine.FlagFalse)) + + assert.False(t, isVerified("radio-t-bad", "u1"), "nothing verified on wrong site") + + assert.NoError(t, setVerified("radio-t", "u1", engine.FlagTrue)) + assert.NoError(t, setVerified("radio-t", "u2", engine.FlagTrue)) + assert.NoError(t, setVerified("radio-t", "u3", engine.FlagFalse)) +} + +func TestMem_FlagListVerified(t *testing.T) { + + b := prepMem(t) + toIDs := func(inp []interface{}) (res []string) { + res = make([]string, len(inp)) + for i, v := range inp { + vv, ok := v.(string) + require.True(t, ok) + res[i] = vv + } + return res + } + + setVerified := func(site, user string, status engine.FlagStatus) error { + req := engine.FlagRequest{Flag: engine.Verified, Locator: store.Locator{SiteID: site}, UserID: user, Update: status} + _, err := b.Flag(req) + return err + } + + ids, err := b.ListFlags(engine.FlagRequest{Flag: engine.Verified, Locator: store.Locator{SiteID: "radio-t"}}) + assert.NoError(t, err) + assert.Equal(t, []string{}, toIDs(ids), "verified list empty") + + assert.NoError(t, setVerified("radio-t", "u1", engine.FlagTrue)) + assert.NoError(t, setVerified("radio-t", "u2", engine.FlagTrue)) + ids, err = b.ListFlags(engine.FlagRequest{Flag: engine.Verified, Locator: store.Locator{SiteID: "radio-t"}}) + assert.NoError(t, err) + assert.EqualValues(t, []string{"u1", "u2"}, toIDs(ids), "verified 2 ids") + + ids, err = b.ListFlags(engine.FlagRequest{Flag: engine.Verified, Locator: store.Locator{SiteID: "radio-t-bad"}}) + assert.NoError(t, err) + assert.Equal(t, 0, len(ids)) +} + +func TestMem_FlagListBlocked(t *testing.T) { + + b := prepMem(t) + setBlocked := func(site, user string, status engine.FlagStatus, ttl time.Duration) error { + req := engine.FlagRequest{Flag: engine.Blocked, Locator: store.Locator{SiteID: site}, UserID: user, Update: status, + TTL: ttl} + _, err := b.Flag(req) + return err + } + + toBlocked := func(inp []interface{}) (res []store.BlockedUser) { + res = make([]store.BlockedUser, len(inp)) + for i, v := range inp { + vv, ok := v.(store.BlockedUser) + require.True(t, ok) + res[i] = vv + } + return res + } + assert.NoError(t, setBlocked("radio-t", "user1", engine.FlagTrue, 0)) + assert.NoError(t, setBlocked("radio-t", "user2", engine.FlagTrue, 50*time.Millisecond)) + assert.NoError(t, setBlocked("radio-t", "user3", engine.FlagFalse, 0)) + + vv, err := b.ListFlags(engine.FlagRequest{Flag: engine.Blocked, Locator: store.Locator{SiteID: "radio-t"}}) + assert.NoError(t, err) + + blockedList := toBlocked(vv) + require.Equal(t, 2, len(blockedList), b.metaUsers) + assert.Equal(t, "user1", blockedList[0].ID) + assert.Equal(t, "user2", blockedList[1].ID) + t.Logf("%+v", blockedList) + + // check block expiration + time.Sleep(50 * time.Millisecond) + vv, err = b.ListFlags(engine.FlagRequest{Flag: engine.Blocked, Locator: store.Locator{SiteID: "radio-t"}}) + assert.NoError(t, err) + blockedList = toBlocked(vv) + require.Equal(t, 1, len(blockedList)) + assert.Equal(t, "user1", blockedList[0].ID) + + vv, err = b.ListFlags(engine.FlagRequest{Flag: engine.Blocked, Locator: store.Locator{SiteID: "bad"}}) + assert.NoError(t, err) + assert.Equal(t, 0, len(vv)) +} + +func TestMem_DeleteComment(t *testing.T) { + + b := prepMem(t) + reqReq := engine.FindRequest{Locator: store.Locator{URL: "https://radio-t.com", SiteID: "radio-t"}, Sort: "time"} + res, err := b.Find(reqReq) + assert.NoError(t, err) + require.Equal(t, 2, len(res), "initially 2 comments") + + count, err := b.Count(reqReq) + require.NoError(t, err) + require.Equal(t, 2, count, "count=2 initially") + + delReq := engine.DeleteRequest{Locator: store.Locator{URL: "https://radio-t.com", SiteID: "radio-t"}, + CommentID: res[0].ID, DeleteMode: store.SoftDelete} + + err = b.Delete(delReq) + assert.NoError(t, err) + + res, err = b.Find(reqReq) + assert.NoError(t, err) + require.Equal(t, 2, len(res)) + assert.Equal(t, "", res[0].Text) + assert.True(t, res[0].Deleted, "marked deleted") + assert.Equal(t, store.User{Name: "user name", ID: "user1", Picture: "", Admin: false, Blocked: false, IP: ""}, res[0].User) + + assert.Equal(t, "some text2", res[1].Text) + assert.False(t, res[1].Deleted) + + comments, err := b.Find(engine.FindRequest{Locator: store.Locator{SiteID: "radio-t"}, Limit: 10}) + assert.NoError(t, err) + assert.Equal(t, 1, len(comments), "1 in last, 1 removed") + + count, err = b.Count(reqReq) + require.NoError(t, err) + assert.Equal(t, 1, count) + + delReq.CommentID = "123456" + err = b.Delete(delReq) + assert.NotNil(t, err) + + delReq.Locator.SiteID = "bad" + delReq.CommentID = res[0].ID + err = b.Delete(delReq) + assert.EqualError(t, err, `not found`) + + delReq.Locator = store.Locator{URL: "https://radio-t.com/bad", SiteID: "radio-t"} + err = b.Delete(delReq) + assert.EqualError(t, err, `not found`) +} + +func TestMem_DeleteHard(t *testing.T) { + + b := prepMem(t) + reqReq := engine.FindRequest{Locator: store.Locator{URL: "https://radio-t.com", SiteID: "radio-t"}, Sort: "time"} + res, err := b.Find(reqReq) + assert.NoError(t, err) + assert.Equal(t, 2, len(res), "initially 2 comments") + + delReq := engine.DeleteRequest{Locator: store.Locator{URL: "https://radio-t.com", SiteID: "radio-t"}, + CommentID: res[0].ID, DeleteMode: store.HardDelete} + err = b.Delete(delReq) + assert.NoError(t, err) + + res, err = b.Find(reqReq) + assert.NoError(t, err) + require.Equal(t, 2, len(res)) + assert.Equal(t, "", res[0].Text) + assert.True(t, res[0].Deleted, "marked deleted") + assert.Equal(t, store.User{Name: "deleted", ID: "deleted", Picture: "", Admin: false, Blocked: false, IP: ""}, res[0].User) +} + +func TestMem_DeleteAll(t *testing.T) { + b := prepMem(t) + delReq := engine.DeleteRequest{Locator: store.Locator{SiteID: "radio-t"}} + err := b.Delete(delReq) + assert.NoError(t, err) + + comments, err := b.Find(engine.FindRequest{Locator: store.Locator{SiteID: "radio-t"}, Limit: 10}) + assert.NoError(t, err) + assert.Equal(t, 0, len(comments), "nothing left") +} + +func TestBoltAdmin_DeleteUserHard(t *testing.T) { + b := prepMem(t) + err := b.Delete(engine.DeleteRequest{Locator: store.Locator{SiteID: "radio-t"}, UserID: "user1", + DeleteMode: store.HardDelete}) + require.NoError(t, err) + + comments, err := b.Find(engine.FindRequest{Locator: store.Locator{SiteID: "radio-t", URL: "https://radio-t.com"}, + Sort: "time"}) + assert.NoError(t, err) + require.Equal(t, 2, len(comments), "2 comments with deleted info") + assert.Equal(t, store.User{Name: "deleted", ID: "deleted", Picture: "", Admin: false, Blocked: false, IP: ""}, comments[0].User) + assert.Equal(t, store.User{Name: "deleted", ID: "deleted", Picture: "", Admin: false, Blocked: false, IP: ""}, comments[1].User) + + c, err := b.Count(engine.FindRequest{Locator: store.Locator{SiteID: "radio-t", URL: "https://radio-t.com"}}) + assert.NoError(t, err) + assert.Equal(t, 0, c, "0 count") + + _, err = b.Find(engine.FindRequest{Locator: store.Locator{SiteID: "radio-t"}, UserID: "user1", Limit: 5}) + assert.NoError(t, err, "no comments for user user1 in store") + + comments, err = b.Find(engine.FindRequest{Locator: store.Locator{SiteID: "radio-t"}, Sort: "time"}) + assert.NoError(t, err) + assert.Equal(t, 0, len(comments), "nothing left") +} + +func TestBoltAdmin_DeleteUserSoft(t *testing.T) { + + b := prepMem(t) + err := b.Delete(engine.DeleteRequest{Locator: store.Locator{SiteID: "radio-t"}, UserID: "user1", + DeleteMode: store.SoftDelete}) + require.NoError(t, err) + + comments, err := b.Find(engine.FindRequest{Locator: store.Locator{SiteID: "radio-t", URL: "https://radio-t.com"}, + Sort: "time"}) + assert.NoError(t, err) + require.Equal(t, 2, len(comments), "2 comments with deleted info") + assert.Equal(t, store.User{Name: "user name", ID: "user1", Picture: "", Admin: false, Blocked: false, IP: ""}, comments[0].User) + assert.Equal(t, store.User{Name: "user name", ID: "user1", Picture: "", Admin: false, Blocked: false, IP: ""}, comments[1].User) + + c, err := b.Count(engine.FindRequest{Locator: store.Locator{SiteID: "radio-t", URL: "https://radio-t.com"}}) + assert.NoError(t, err) + assert.Equal(t, 0, c, "0 count") + + comments, err = b.Find(engine.FindRequest{Locator: store.Locator{SiteID: "radio-t"}, UserID: "user1", Limit: 5}) + assert.NoError(t, err, "no comments for user user1 in store") + require.Equal(t, 2, len(comments), "2 comments with deleted info") + assert.True(t, comments[0].Deleted) + assert.True(t, comments[1].Deleted) + assert.Equal(t, "", comments[0].Text) + assert.Equal(t, "", comments[1].Text) + + comments, err = b.Find(engine.FindRequest{Locator: store.Locator{SiteID: "radio-t"}, Sort: "time"}) + assert.NoError(t, err) + assert.Equal(t, 0, len(comments), "nothing left") +} + +func prepMem(t *testing.T) *MemEngine { + + m := NewMemEngine() + + comment := store.Comment{ + ID: "id-1", + Text: `some text, link`, + Timestamp: time.Date(2017, 12, 20, 15, 18, 22, 0, time.Local), + Locator: store.Locator{URL: "https://radio-t.com", SiteID: "radio-t"}, + User: store.User{ID: "user1", Name: "user name"}, + } + _, err := m.Create(comment) + require.NoError(t, err) + + comment = store.Comment{ + ID: "id-2", + Text: "some text2", + Timestamp: time.Date(2017, 12, 20, 15, 18, 23, 0, time.Local), + Locator: store.Locator{URL: "https://radio-t.com", SiteID: "radio-t"}, + User: store.User{ID: "user1", Name: "user name"}, + } + _, err = m.Create(comment) + require.NoError(t, err) + return m +} + +func getReq(locator store.Locator, commentID string) engine.GetRequest { + return engine.GetRequest{ + Locator: locator, + CommentID: commentID, + } +} diff --git a/backend/_example/rpc/server/store.go b/backend/_example/rpc/server/store.go new file mode 100644 index 00000000..30f029a6 --- /dev/null +++ b/backend/_example/rpc/server/store.go @@ -0,0 +1,230 @@ +package server + +import ( + "encoding/json" + + "github.com/umputun/remark/backend/app/rpc" + "github.com/umputun/remark/backend/app/store" + "github.com/umputun/remark/backend/app/store/admin" + "github.com/umputun/remark/backend/app/store/engine" +) + +// Store handler wraps both engine and remote server and implements all handlers +// Note: this file can be used as-is in any custom rpc plugin +type Store struct { + *rpc.Server + eng engine.Interface + adm admin.Store +} + +// NewStore makes Store instance and register handlers +func NewStore(e engine.Interface, a admin.Store, r *rpc.Server) *Store { + res := &Store{eng: e, adm: a, Server: r} + res.addHandlers() + return res +} + + +func (s *Store) addHandlers() { + // data store handlers + s.Group("store", rpc.HandlersGroup{ + "create": s.createHndl, + "find": s.findHndl, + "get": s.getHndl, + "update": s.updateHndl, + "count": s.countHndl, + "info": s.infoHndl, + "flag": s.flagHndl, + "list_flags": s.listFlagsHndl, + "delete": s.deleteHndl, + "close": s.closeHndl, + }) + + // admin store handlers + s.Group("admin", rpc.HandlersGroup{ + "key": s.admKeyHndl, + "admins": s.admAdminsHndl, + "email": s.admEmailHndl, + }) +} + +func (s *Store) createHndl(id uint64, params json.RawMessage) (rr rpc.Response) { + comment := store.Comment{} + if err := json.Unmarshal(params, &comment); err != nil { + return rpc.Response{Error: err.Error()} + } + commentID, err := s.eng.Create(comment) + if rr, err = s.EncodeResponse(id, commentID, err); err != nil { + return rpc.Response{Error: err.Error()} + + } + return rr +} + +// Find comments +func (s *Store) findHndl(id uint64, params json.RawMessage) (rr rpc.Response) { + req := engine.FindRequest{} + if err := json.Unmarshal(params, &req); err != nil { + return rpc.Response{Error: err.Error()} + } + resp, err := s.eng.Find(req) + if rr, err = s.EncodeResponse(id, resp, err); err != nil { + return rpc.Response{Error: err.Error()} + } + return rr +} + +// Get comment +func (s *Store) getHndl(id uint64, params json.RawMessage) (rr rpc.Response) { + req := engine.GetRequest{} + if err := json.Unmarshal(params, &req); err != nil { + return rpc.Response{Error: err.Error()} + } + comment, err := s.eng.Get(req) + if rr, err = s.EncodeResponse(id, comment, err); err != nil { + return rpc.Response{Error: err.Error()} + } + return rr +} + +// Update comment +func (s *Store) updateHndl(id uint64, params json.RawMessage) (rr rpc.Response) { + comment := store.Comment{} + if err := json.Unmarshal(params, &comment); err != nil { + return rpc.Response{Error: err.Error()} + } + err := s.eng.Update(comment) + if rr, err = s.EncodeResponse(id, nil, err); err != nil { + return rpc.Response{Error: err.Error()} + } + return rr +} + +// counts for site and users +func (s *Store) countHndl(id uint64, params json.RawMessage) (rr rpc.Response) { + req := engine.FindRequest{} + if err := json.Unmarshal(params, &req); err != nil { + return rpc.Response{Error: err.Error()} + } + count, err := s.eng.Count(req) + if rr, err = s.EncodeResponse(id, count, err); err != nil { + return rpc.Response{Error: err.Error()} + } + return rr +} + +// info get post meta info +func (s *Store) infoHndl(id uint64, params json.RawMessage) (rr rpc.Response) { + req := engine.InfoRequest{} + if err := json.Unmarshal(params, &req); err != nil { + return rpc.Response{Error: err.Error()} + } + info, err := s.eng.Info(req) + if rr, err = s.EncodeResponse(id, info, err); err != nil { + return rpc.Response{Error: err.Error()} + } + return rr +} + +// flagHndl get and sets flag value +func (s *Store) flagHndl(id uint64, params json.RawMessage) (rr rpc.Response) { + req := engine.FlagRequest{} + if err := json.Unmarshal(params, &req); err != nil { + return rpc.Response{Error: err.Error()} + } + status, err := s.eng.Flag(req) + if rr, err = s.EncodeResponse(id, status, err); err != nil { + return rpc.Response{Error: err.Error()} + } + return rr +} + +// listFlagsHndl list flags for given request +func (s *Store) listFlagsHndl(id uint64, params json.RawMessage) (rr rpc.Response) { + req := engine.FlagRequest{} + if err := json.Unmarshal(params, &req); err != nil { + return rpc.Response{Error: err.Error()} + } + flags, err := s.eng.ListFlags(req) + if rr, err = s.EncodeResponse(id, flags, err); err != nil { + return rpc.Response{Error: err.Error()} + } + return rr +} + +// deleteHndl remove comment(s) +func (s *Store) deleteHndl(id uint64, params json.RawMessage) (rr rpc.Response) { + req := engine.DeleteRequest{} + if err := json.Unmarshal(params, &req); err != nil { + return rpc.Response{Error: err.Error()} + } + err := s.eng.Delete(req) + if rr, err = s.EncodeResponse(id, nil, err); err != nil { + return rpc.Response{Error: err.Error()} + } + return rr +} + +// close store +func (s *Store) closeHndl(id uint64, _ json.RawMessage) (rr rpc.Response) { + if err := s.eng.Close(); err != nil { + return rpc.Response{Error: err.Error()} + } + return rpc.Response{} +} + +// get admin key +func (s *Store) admKeyHndl(id uint64, _ json.RawMessage) (rr rpc.Response) { + key, err := s.adm.Key() + if err != nil { + return rpc.Response{Error: err.Error()} + } + if rr, err = s.EncodeResponse(id, key, err); err != nil { + return rpc.Response{Error: err.Error()} + } + return rr +} + +// get admins list +func (s *Store) admAdminsHndl(id uint64, params json.RawMessage) (rr rpc.Response) { + args := []interface{}{} + if err := json.Unmarshal(params, &args); err != nil { + return rpc.Response{Error: err.Error()} + } + siteID, ok := args[0].(string) + if !ok { + return rpc.Response{Error: "incompatible argument"} + } + + admins, err := s.adm.Admins(siteID) + if err != nil { + return rpc.Response{Error: err.Error()} + } + + if rr, err = s.EncodeResponse(id, admins, err); err != nil { + return rpc.Response{Error: err.Error()} + } + return rr +} + +// get admin email +func (s *Store) admEmailHndl(id uint64, params json.RawMessage) (rr rpc.Response) { + args := []interface{}{} + if err := json.Unmarshal(params, &args); err != nil { + return rpc.Response{Error: err.Error()} + } + siteID, ok := args[0].(string) + if !ok { + return rpc.Response{Error: "incompatible argument"} + } + + email, err := s.adm.Email(siteID) + if err != nil { + return rpc.Response{Error: err.Error()} + } + + if rr, err = s.EncodeResponse(id, email, err); err != nil { + return rpc.Response{Error: err.Error()} + } + return rr +} From e84a155ef5ee3d8003de96affd71ade83c6e5220 Mon Sep 17 00:00:00 2001 From: Umputun Date: Wed, 31 Jul 2019 18:34:32 -0500 Subject: [PATCH 2/9] switch example to go-pkgz/jrpc --- .gitignore | 1 + backend/_example/memory_store/README.md | 4 + .../plugin => memory_store/accessor}/admin.go | 29 +- .../accessor}/admin_test.go | 4 +- .../accessor/data.go} | 71 ++-- .../accessor/data_test.go} | 10 +- backend/_example/memory_store/go.mod | 16 + backend/_example/memory_store/go.sum | 192 +++++++++++ backend/_example/memory_store/main.go | 72 ++++ backend/_example/memory_store/server/rpc.go | 191 +++++++++++ .../_example/memory_store/server/rpc_test.go | 320 ++++++++++++++++++ backend/_example/rpc/main.go | 22 -- backend/_example/rpc/server/store.go | 230 ------------- 13 files changed, 858 insertions(+), 304 deletions(-) create mode 100644 backend/_example/memory_store/README.md rename backend/_example/{rpc/plugin => memory_store/accessor}/admin.go (58%) rename backend/_example/{rpc/plugin => memory_store/accessor}/admin_test.go (95%) rename backend/_example/{rpc/plugin/engine.go => memory_store/accessor/data.go} (84%) rename backend/_example/{rpc/plugin/engine_test.go => memory_store/accessor/data_test.go} (99%) create mode 100644 backend/_example/memory_store/go.mod create mode 100644 backend/_example/memory_store/go.sum create mode 100644 backend/_example/memory_store/main.go create mode 100644 backend/_example/memory_store/server/rpc.go create mode 100644 backend/_example/memory_store/server/rpc_test.go delete mode 100644 backend/_example/rpc/main.go delete mode 100644 backend/_example/rpc/server/store.go diff --git a/.gitignore b/.gitignore index f6821858..ed8c2e56 100644 --- a/.gitignore +++ b/.gitignore @@ -20,3 +20,4 @@ remark42 /bin/ /backend/var/ compose-private-backend.yml +/backend/_example/*/vendor diff --git a/backend/_example/memory_store/README.md b/backend/_example/memory_store/README.md new file mode 100644 index 00000000..51797e1f --- /dev/null +++ b/backend/_example/memory_store/README.md @@ -0,0 +1,4 @@ +# sample store implementation + +`memory_store` illustrates how to make a storage plugin for remark42. + diff --git a/backend/_example/rpc/plugin/admin.go b/backend/_example/memory_store/accessor/admin.go similarity index 58% rename from backend/_example/rpc/plugin/admin.go rename to backend/_example/memory_store/accessor/admin.go index 6f457199..e82ef519 100644 --- a/backend/_example/rpc/plugin/admin.go +++ b/backend/_example/memory_store/accessor/admin.go @@ -4,41 +4,39 @@ * license that can be found in the LICENSE file. */ -package plugin +package accessor import ( log "github.com/go-pkgz/lgr" "github.com/pkg/errors" ) -// MemAdminStore implements admin.Store with mongo backend -type MemAdminStore struct { - data map[string]AdminRec +// MemAdmin implements admin.Store with memory backend +type MemAdmin struct { + data map[string]AdminRec // admin info per site key string } // AdminRec is a records per site with all admin info in type AdminRec struct { SiteID string `bson:"site"` - IDs []string `bson:"ids"` - Email string `bson:"email"` + IDs []string `bson:"ids"` // admin ids + Email string `bson:"email"` // admin email } -const mongoAdmin = "admin" - // NewMemAdminStore makes admin Store in memory -func NewMemAdminStore(key string) *MemAdminStore { +func NewMemAdminStore(key string) *MemAdmin { log.Print("[DEBUG] make memory admin store") - return &MemAdminStore{data: map[string]AdminRec{}, key: key} + return &MemAdmin{data: map[string]AdminRec{}, key: key} } // Key executes find by siteID and returns substructure with secret key -func (m *MemAdminStore) Key() (key string, err error) { +func (m *MemAdmin) Key() (key string, err error) { return m.key, nil } // Admins executes find by siteID and returns admins ids -func (m *MemAdminStore) Admins(siteID string) (ids []string, err error) { +func (m *MemAdmin) Admins(siteID string) (ids []string, err error) { resp, ok := m.data[siteID] if !ok { return nil, errors.Errorf("site %s not found", siteID) @@ -48,7 +46,7 @@ func (m *MemAdminStore) Admins(siteID string) (ids []string, err error) { } // Email executes find by siteID and returns admin's email -func (m *MemAdminStore) Email(siteID string) (email string, err error) { +func (m *MemAdmin) Email(siteID string) (email string, err error) { resp, ok := m.data[siteID] if !ok { return "", errors.Errorf("site %s not found", siteID) @@ -56,3 +54,8 @@ func (m *MemAdminStore) Email(siteID string) (email string, err error) { return resp.Email, nil } + +// Set admin data for siteID +func (m *MemAdmin) Set(siteID string, arec AdminRec) { + m.data[siteID] = arec +} diff --git a/backend/_example/rpc/plugin/admin_test.go b/backend/_example/memory_store/accessor/admin_test.go similarity index 95% rename from backend/_example/rpc/plugin/admin_test.go rename to backend/_example/memory_store/accessor/admin_test.go index 1fcb46e8..1fe18688 100644 --- a/backend/_example/rpc/plugin/admin_test.go +++ b/backend/_example/memory_store/accessor/admin_test.go @@ -4,7 +4,7 @@ * license that can be found in the LICENSE file. */ -package plugin +package accessor import ( "testing" @@ -14,7 +14,7 @@ import ( "github.com/umputun/remark/backend/app/store/admin" ) -func TestMongoStore_Get(t *testing.T) { +func TestMemAdmin_Get(t *testing.T) { adm := NewMemAdminStore("secret") var ms admin.Store = adm diff --git a/backend/_example/rpc/plugin/engine.go b/backend/_example/memory_store/accessor/data.go similarity index 84% rename from backend/_example/rpc/plugin/engine.go rename to backend/_example/memory_store/accessor/data.go index ed892ded..b3f596c5 100644 --- a/backend/_example/rpc/plugin/engine.go +++ b/backend/_example/memory_store/accessor/data.go @@ -4,7 +4,7 @@ * license that can be found in the LICENSE file. */ -package plugin +package accessor import ( "log" @@ -21,8 +21,8 @@ import ( const lastLimit = 1000 -// MemEngine implements in-memory engine interface -type MemEngine struct { +// MemData implements in-memory data store +type MemData struct { posts map[string][]store.Comment // key is siteID metaUsers map[string]metaUser // key is userID metaPosts map[store.Locator]metaPost // key is post's locator @@ -43,10 +43,10 @@ type metaUser struct { BlockedUntil time.Time } -// NewMemEngine makes in-memory engine. -func NewMemEngine() *MemEngine { +// NewMemData makes in-memory engine. +func NewMemData() *MemData { - result := &MemEngine{ + result := &MemData{ posts: map[string][]store.Comment{}, metaUsers: map[string]metaUser{}, metaPosts: map[store.Locator]metaPost{}, @@ -54,8 +54,8 @@ func NewMemEngine() *MemEngine { return result } -// Create new comment, write can be buffered and delayed. -func (m *MemEngine) Create(comment store.Comment) (commentID string, err error) { +// Create new comment +func (m *MemData) Create(comment store.Comment) (commentID string, err error) { if ro, e := m.Flag(engine.FlagRequest{Flag: engine.ReadOnly, Locator: comment.Locator}); e == nil && ro { return "", errors.Errorf("post %s is read-only", comment.Locator.URL) @@ -64,7 +64,7 @@ func (m *MemEngine) Create(comment store.Comment) (commentID string, err error) m.Lock() defer m.Unlock() comments := m.posts[comment.Locator.SiteID] - for _, c := range comments { + for _, c := range comments { // don't allow duplicated IDs if c.ID == comment.ID { return "", errors.New("dup key") } @@ -75,7 +75,7 @@ func (m *MemEngine) Create(comment store.Comment) (commentID string, err error) } // Find returns all comments for post and sorts results -func (m *MemEngine) Find(req engine.FindRequest) (comments []store.Comment, err error) { +func (m *MemData) Find(req engine.FindRequest) (comments []store.Comment, err error) { m.RLock() defer m.RUnlock() @@ -89,7 +89,7 @@ func (m *MemEngine) Find(req engine.FindRequest) (comments []store.Comment, err case req.Locator.SiteID != "" && req.Locator.URL != "": // find comments for site and url comments = m.match(m.posts[req.Locator.SiteID], func(c store.Comment) bool { - return c.Locator == req.Locator + return c.Locator == req.Locator && c.Timestamp.After(req.Since) }) case req.Locator.SiteID != "" && req.Locator.URL == "" && req.UserID == "": // find last comments for site @@ -114,6 +114,7 @@ func (m *MemEngine) Find(req engine.FindRequest) (comments []store.Comment, err return c.User.ID == req.UserID }) } + comments = engine.SortComments(comments, req.Sort) if req.Skip > 0 && req.Skip > len(comments) { return []store.Comment{}, nil @@ -130,21 +131,21 @@ func (m *MemEngine) Find(req engine.FindRequest) (comments []store.Comment, err } // Get returns comment for locator.URL and commentID string -func (m *MemEngine) Get(req engine.GetRequest) (comment store.Comment, err error) { +func (m *MemData) Get(req engine.GetRequest) (comment store.Comment, err error) { m.RLock() defer m.RUnlock() return m.get(req.Locator, req.CommentID) } // Update updates comment for locator.URL with mutable part of comment -func (m *MemEngine) Update(comment store.Comment) error { +func (m *MemData) Update(comment store.Comment) error { m.Lock() defer m.Unlock() return m.updateComment(comment) } // Count returns number of comments for post or user -func (m *MemEngine) Count(req engine.FindRequest) (count int, err error) { +func (m *MemData) Count(req engine.FindRequest) (count int, err error) { m.RLock() defer m.RUnlock() @@ -165,7 +166,7 @@ func (m *MemEngine) Count(req engine.FindRequest) (count int, err error) { } // Info get post(s) meta info -func (m *MemEngine) Info(req engine.InfoRequest) (res []store.PostInfo, err error) { +func (m *MemData) Info(req engine.InfoRequest) (res []store.PostInfo, err error) { m.RLock() defer m.RUnlock() res = []store.PostInfo{} @@ -214,20 +215,23 @@ func (m *MemEngine) Info(req engine.InfoRequest) (res []store.PostInfo, err erro infoAll[c.Locator] = info } - n := 0 for _, v := range infoAll { - n++ - if len(res) >= req.Limit { - break - } - if req.Skip > 0 && n <= req.Skip { - continue - } res = append(res, v) } sort.Slice(res, func(i, j int) bool { return res[i].URL > res[j].URL }) + + if req.Skip > 0 { + if req.Skip >= len(res) { + return []store.PostInfo{}, nil + } + res = res[req.Skip:] + } + + if req.Limit > 0 && req.Limit < len(res) { + res = res[:req.Limit] + } return res, nil } @@ -235,7 +239,7 @@ func (m *MemEngine) Info(req engine.InfoRequest) (res []store.PostInfo, err erro } // Flag sets and gets flag values -func (m *MemEngine) Flag(req engine.FlagRequest) (val bool, err error) { +func (m *MemData) Flag(req engine.FlagRequest) (val bool, err error) { m.Lock() defer m.Unlock() @@ -248,7 +252,7 @@ func (m *MemEngine) Flag(req engine.FlagRequest) (val bool, err error) { // ListFlags get list of flagged keys, like blocked & verified user // works for full locator (post flags) or with userID -func (m *MemEngine) ListFlags(req engine.FlagRequest) (res []interface{}, err error) { +func (m *MemData) ListFlags(req engine.FlagRequest) (res []interface{}, err error) { m.RLock() defer m.RUnlock() @@ -277,7 +281,7 @@ func (m *MemEngine) ListFlags(req engine.FlagRequest) (res []interface{}, err er } // Delete post(s) by id or by userID -func (m *MemEngine) Delete(req engine.DeleteRequest) error { +func (m *MemData) Delete(req engine.DeleteRequest) error { m.Lock() defer m.Unlock() @@ -308,7 +312,7 @@ func (m *MemEngine) Delete(req engine.DeleteRequest) error { return errors.Errorf("invalid delete request %+v", req) } -func (m *MemEngine) deleteComment(loc store.Locator, id string, mode store.DeleteMode) error { +func (m *MemData) deleteComment(loc store.Locator, id string, mode store.DeleteMode) error { comments := m.match(m.posts[loc.SiteID], func(c store.Comment) bool { return c.Locator == loc && c.ID == id @@ -322,11 +326,11 @@ func (m *MemEngine) deleteComment(loc store.Locator, id string, mode store.Delet } // Close store -func (m *MemEngine) Close() error { +func (m *MemData) Close() error { return nil } -func (m *MemEngine) checkFlag(req engine.FlagRequest) (val bool) { +func (m *MemData) checkFlag(req engine.FlagRequest) (val bool) { switch req.Flag { case engine.Blocked: if meta, ok := m.metaUsers[req.UserID]; ok { @@ -350,7 +354,7 @@ func (m *MemEngine) checkFlag(req engine.FlagRequest) (val bool) { return false } -func (m *MemEngine) setFlag(req engine.FlagRequest) (res bool, err error) { +func (m *MemData) setFlag(req engine.FlagRequest) (res bool, err error) { status := false if req.Update == engine.FlagTrue { @@ -397,7 +401,7 @@ func (m *MemEngine) setFlag(req engine.FlagRequest) (res bool, err error) { return status, errors.Wrapf(err, "failed to set flag %+v", req) } -func (m *MemEngine) get(loc store.Locator, commentID string) (store.Comment, error) { +func (m *MemData) get(loc store.Locator, commentID string) (store.Comment, error) { comments := m.match(m.posts[loc.SiteID], func(c store.Comment) bool { return c.Locator == loc && c.ID == commentID }) @@ -407,7 +411,7 @@ func (m *MemEngine) get(loc store.Locator, commentID string) (store.Comment, err return comments[0], nil } -func (m *MemEngine) updateComment(comment store.Comment) error { +func (m *MemData) updateComment(comment store.Comment) error { comments := m.posts[comment.Locator.SiteID] for i, c := range comments { if c.ID == comment.ID && c.Locator == comment.Locator { @@ -426,7 +430,8 @@ func (m *MemEngine) updateComment(comment store.Comment) error { return errors.New("not found") } -func (m *MemEngine) match(comments []store.Comment, fn func(c store.Comment) bool) (res []store.Comment) { +func (m *MemData) match(comments []store.Comment, fn func(c store.Comment) bool) (res []store.Comment) { + res = []store.Comment{} for _, c := range comments { if fn(c) { res = append(res, c) diff --git a/backend/_example/rpc/plugin/engine_test.go b/backend/_example/memory_store/accessor/data_test.go similarity index 99% rename from backend/_example/rpc/plugin/engine_test.go rename to backend/_example/memory_store/accessor/data_test.go index 6ff3a6ee..e95b15bf 100644 --- a/backend/_example/rpc/plugin/engine_test.go +++ b/backend/_example/memory_store/accessor/data_test.go @@ -4,10 +4,11 @@ * license that can be found in the LICENSE file. */ -package plugin +package accessor import ( "fmt" + "sort" "strings" "testing" "time" @@ -191,7 +192,7 @@ func TestMem_FindForUser(t *testing.T) { } func TestMem_FindForUserPagination(t *testing.T) { - b := NewMemEngine() + b := NewMemData() c := store.Comment{ Locator: store.Locator{URL: "https://radio-t.com", SiteID: "radio-t"}, @@ -484,6 +485,7 @@ func TestMem_FlagListVerified(t *testing.T) { require.True(t, ok) res[i] = vv } + sort.Strings(res) return res } @@ -691,9 +693,9 @@ func TestBoltAdmin_DeleteUserSoft(t *testing.T) { assert.Equal(t, 0, len(comments), "nothing left") } -func prepMem(t *testing.T) *MemEngine { +func prepMem(t *testing.T) *MemData { - m := NewMemEngine() + m := NewMemData() comment := store.Comment{ ID: "id-1", diff --git a/backend/_example/memory_store/go.mod b/backend/_example/memory_store/go.mod new file mode 100644 index 00000000..e95d925c --- /dev/null +++ b/backend/_example/memory_store/go.mod @@ -0,0 +1,16 @@ +module github.com/umputun/remark/backend/_example/memory_store + +go 1.12 + +require ( + github.com/go-pkgz/jrpc v0.1.0 + github.com/go-pkgz/lgr v0.6.3 + github.com/jessevdk/go-flags v0.0.0-20180331124232-1c38ed7ad0cc + github.com/pkg/errors v0.8.1 + github.com/stretchr/testify v1.3.0 + github.com/umputun/remark/backend v1.4.0 +) + +replace github.com/umputun/remark/backend => ../../ + +replace gopkg.in/russross/blackfriday.v2 => github.com/russross/blackfriday/v2 v2.0.1 diff --git a/backend/_example/memory_store/go.sum b/backend/_example/memory_store/go.sum new file mode 100644 index 00000000..6958cdc4 --- /dev/null +++ b/backend/_example/memory_store/go.sum @@ -0,0 +1,192 @@ +cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= +cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= +cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU= +cloud.google.com/go v0.40.0/go.mod h1:Tk58MuI9rbLMKlAjeO/bDnteAx7tX2gJIXw4T5Jwlro= +cloud.google.com/go v0.41.0/go.mod h1:OauMR7DV8fzvZIl2qg6rkaIhD/vmgk4iwEw/h6ercmg= +github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= +github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= +github.com/PuerkitoBio/goquery v1.5.0 h1:uGvmFXOA73IKluu/F84Xd1tt/z07GYm8X49XKHP7EJk= +github.com/PuerkitoBio/goquery v1.5.0/go.mod h1:qD2PgZ9lccMbQlc7eEOjaeRlFQON7xY8kdmcsrnKqMg= +github.com/andybalholm/cascadia v1.0.0 h1:hOCXnnZ5A+3eVDX8pvgl4kofXv2ELss0bKcqRySc45o= +github.com/andybalholm/cascadia v1.0.0/go.mod h1:GsXiBklL0woXo1j/WYWtSYYC4ouU9PqHO0sqidkEA4Y= +github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= +github.com/coreos/bbolt v1.3.3 h1:n6AiVyVRKQFNb6mJlwESEvvLoDyiTzXX7ORAUlkeBdY= +github.com/coreos/bbolt v1.3.3/go.mod h1:iRUV2dpdMOn7Bo10OQBFzIJO9kkE559Wcmn+qkEiiKk= +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/dgrijalva/jwt-go v3.2.0+incompatible h1:7qlOGliEKZXTDg6OTjfoBKDXWrumCAMpl/TFQ4/5kLM= +github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ= +github.com/didip/tollbooth v4.0.0+incompatible h1:ayQZYuF5QOxx3NdYRNuRVFLv9/2b64JtSUlewb+0TMo= +github.com/didip/tollbooth v4.0.0+incompatible/go.mod h1:A9b0665CE6l1KmzpDws2++elm/CsuWBMa5Jv4WY0PEY= +github.com/didip/tollbooth v4.0.2+incompatible h1:fVSa33JzSz0hoh2NxpwZtksAzAgd7zjmGO20HCZtF4M= +github.com/didip/tollbooth v4.0.2+incompatible/go.mod h1:A9b0665CE6l1KmzpDws2++elm/CsuWBMa5Jv4WY0PEY= +github.com/didip/tollbooth_chi v0.0.0-20170928041846-6ab5f3083f3d h1:vs5Nf6IE0N/PwGJ8//zRed4gpCdcr99K2HzX7RuLOQ8= +github.com/didip/tollbooth_chi v0.0.0-20170928041846-6ab5f3083f3d/go.mod h1:YWyIfq3y4ArRfWZ9XksmuusP+7Mad+T0iFZ0kv0XG/M= +github.com/globalsign/mgo v0.0.0-20180615134936-113d3961e731/go.mod h1:xkRDCp4j0OGD1HRkm4kmhM+pmpv3AKq5SU7GMg4oO/Q= +github.com/globalsign/mgo v0.0.0-20181015135952-eeefdecb41b8/go.mod h1:xkRDCp4j0OGD1HRkm4kmhM+pmpv3AKq5SU7GMg4oO/Q= +github.com/go-chi/chi v4.0.2+incompatible h1:maB6vn6FqCxrpz4FqWdh4+lwpyZIQS7YEAUcHlgXVRs= +github.com/go-chi/chi v4.0.2+incompatible/go.mod h1:eB3wogJHnLi3x/kFX2A+IbTBlXxmMeXJVKy9tTv1XzQ= +github.com/go-chi/cors v1.0.0/go.mod h1:K2Yje0VW/SJzxiyMYu6iPQYa7hMjQX2i/F491VChg1I= +github.com/go-chi/render v1.0.1 h1:4/5tis2cKaNdnv9zFLfXzcquC9HbeZgCnxGnKrltBS8= +github.com/go-chi/render v1.0.1/go.mod h1:pq4Rr7HbnsdaeHagklXub+p6Wd16Af5l9koip1OvJns= +github.com/go-pkgz/auth v0.7.2 h1:+LvAgqwQtYuWphpZE8qLtspVd65+VgreJkFLsKrtmmk= +github.com/go-pkgz/auth v0.7.2/go.mod h1:ibOpZYISiaOvAHe2bsKj2s3v4AkMam2WxxIFn+zhulo= +github.com/go-pkgz/jrpc v0.1.0 h1:hNg/IyfEqJcSWOKkuHw0ZwcuGc9TDp7QZREsD2ycmiM= +github.com/go-pkgz/jrpc v0.1.0/go.mod h1:JxZsvoBklA50DNhELVJnJ567Rt+KrMH9rR3u515wvE8= +github.com/go-pkgz/lcw v0.3.1/go.mod h1:k+PY1CkCMTLXILtFoJOyK65Qqi9rkoTYunFH1vE/C0I= +github.com/go-pkgz/lgr v0.2.2/go.mod h1:hBM1NM/SoYdlrykgdgJWGrZ/TM/XaZIjRbJfx7NkMm8= +github.com/go-pkgz/lgr v0.6.2/go.mod h1:hBM1NM/SoYdlrykgdgJWGrZ/TM/XaZIjRbJfx7NkMm8= +github.com/go-pkgz/lgr v0.6.3 h1:n9pGk2paBV8w/Y/FVEq5MkwDmP33dnUPKbY4CyyygwM= +github.com/go-pkgz/lgr v0.6.3/go.mod h1:hBM1NM/SoYdlrykgdgJWGrZ/TM/XaZIjRbJfx7NkMm8= +github.com/go-pkgz/mongo v1.1.2/go.mod h1:0NkWnzpiUxoL5fYZuttCtJrpC67oNDidfYxcdPqHTf0= +github.com/go-pkgz/repeater v1.1.2/go.mod h1:QfNR/a+xqjs+f9wSxWqOQlw9aQhmKlUaSwXCiZ+Ko2w= +github.com/go-pkgz/rest v1.4.1 h1:DmaVLPH2O7yLehrWOW0uz01d2mVHz9fBR/iuTiPRzaw= +github.com/go-pkgz/rest v1.4.1/go.mod h1:COazNj35u3RXAgQNBr6neR599tYP3URiOpsu9p0rOtk= +github.com/go-pkgz/syncs v1.1.1/go.mod h1:bt9lxWRRJ9vOCMGc8Big8ttjYHLKP88ofj1y38UlaHE= +github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= +github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= +github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= +github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y= +github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= +github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= +github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= +github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= +github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= +github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= +github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= +github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk= +github.com/gorilla/feeds v1.1.1/go.mod h1:Nk0jZrvPFZX1OBe5NPiddPw7CfwF6Q9eqzaBbaightA= +github.com/hashicorp/errwrap v1.0.0 h1:hLrqtEDnRye3+sgx6z4qVLNuviH3MR5aQ0ykNJa/UYA= +github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= +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/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= +github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= +github.com/jessevdk/go-flags v0.0.0-20180331124232-1c38ed7ad0cc h1:0L2sGkaj6MWuV1BfXsrLJ/+XA8RzKKVsYlLVXNkK1Lw= +github.com/jessevdk/go-flags v0.0.0-20180331124232-1c38ed7ad0cc/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI= +github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= +github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= +github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= +github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= +github.com/kyokomi/emoji v2.1.0+incompatible/go.mod h1:mZ6aGCD7yk8j6QY6KICwnZ2pxoszVseX1DNoGtU2tBA= +github.com/microcosm-cc/bluemonday v1.0.2 h1:5lPfLTTAvAbtS0VqT+94yOtFnGfUWYyx0+iToC3Os3s= +github.com/microcosm-cc/bluemonday v1.0.2/go.mod h1:iVP4YcDBq+n/5fb23BhYFvIMq/leAFZyRl6bYmGDlGc= +github.com/nullrocks/identicon v0.0.0-20180626043057-7875f45b0022/go.mod h1:x4NsS+uc7ecH/Cbm9xKQ6XzmJM57rWTkjywjfB2yQ18= +github.com/patrickmn/go-cache v2.1.0+incompatible h1:HRMgzkcYKYpi3C8ajMPV8OFXaaRUnok+kx1WdO15EQc= +github.com/patrickmn/go-cache v2.1.0+incompatible/go.mod h1:3Qf8kWWT7OJRJbdiICTKqZju1ZixQ/KpMGzzAfe6+WQ= +github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I= +github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/rakyll/statik v0.1.6/go.mod h1:OEi9wJV/fMUAGx1eNjq75DKDsJVuEv1U0oYdX6GX8Zs= +github.com/rs/xid v1.2.1/go.mod h1:+uKXf+4Djp6Md1KODXJxgGQPKngRmWyn10oCKFzNHOQ= +github.com/russross/blackfriday/v2 v2.0.1 h1:lPqVAte+HuHNfhJ/0LC98ESWRz8afy9tM/0RK8m9o+Q= +github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= +github.com/shurcooL/sanitized_anchor_name v1.0.0 h1:PdmoCO6wvbs+7yrJyMORt4/BmY5IYyJwS/kOiWx8mHo= +github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +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= +go.etcd.io/bbolt v1.3.3 h1:MUGmc65QhB3pIlaQ5bB4LwqSj6GIonVJXpZiaKNyaKk= +go.etcd.io/bbolt v1.3.3/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU= +go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU= +go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20190701094942-4def268fd1a4/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= +golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= +golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= +golang.org/x/image v0.0.0-20190523035834-f03afa92d3ff/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= +golang.org/x/image v0.0.0-20190703141733-d6a02ce849c9/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= +golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= +golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= +golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= +golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/lint v0.0.0-20190409202823-959b441ac422/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE= +golang.org/x/net v0.0.0-20180218175443-cbe0f9307d01/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20181220203305-927f97764cc3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= +golang.org/x/net v0.0.0-20190611141213-3f473d35a33a/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20190628185345-da137c7871d7 h1:rTIdg5QFRR7XCaK4LCjBiPbx8j4DQRpdYMnGn/bJUEU= +golang.org/x/net v0.0.0-20190628185345-da137c7871d7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20190724013045-ca1201d0de80 h1:Ao/3l156eZf2AW5wK8a7/smtodRU+gha3+BeqJ69lRk= +golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= +golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= +golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= +golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190610200419-93c9922d18ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190626221950-04f50cda93cb h1:fgwFCsaw9buMuxNd6+DQfAuSFqbNiQZpcgJQAgJsK6k= +golang.org/x/sys v0.0.0-20190626221950-04f50cda93cb/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.2 h1:tW2bmiBqwgJj/UpqtC8EpXEZVYOwU0yG4iWbprSVAcs= +golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= +golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/time v0.0.0-20190308202827-9d24e82272b4 h1:SvFZT6jyqRaOeXpc5h/JSfZenJ2O330aBsf7JfSUXmQ= +golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= +golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= +golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= +golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= +golang.org/x/tools v0.0.0-20190624190245-7f2218787638/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= +google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE= +google.golang.org/api v0.6.0/go.mod h1:btoxGiFvQNVUZQ8W08zLtrVS08CNpINPEfxXxgJL1Q4= +google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M= +google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= +google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= +google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= +google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0= +google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= +google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/genproto v0.0.0-20190502173448-54afdca5d873/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/genproto v0.0.0-20190530194941-fb225487d101/go.mod h1:z3L6/3dTEVtUr6QSP8miRzeRqwQOioJ9I66odjN4I7s= +google.golang.org/genproto v0.0.0-20190626174449-989357319d63/go.mod h1:z3L6/3dTEVtUr6QSP8miRzeRqwQOioJ9I66odjN4I7s= +google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= +google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38= +google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= +gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8= diff --git a/backend/_example/memory_store/main.go b/backend/_example/memory_store/main.go new file mode 100644 index 00000000..250faa22 --- /dev/null +++ b/backend/_example/memory_store/main.go @@ -0,0 +1,72 @@ +/* + * Copyright 2019 Umputun. All rights reserved. + * Use of this source code is governed by a MIT-style + * license that can be found in the LICENSE file. + */ + +package main + +import ( + "fmt" + "os" + + log "github.com/go-pkgz/lgr" + "github.com/jessevdk/go-flags" + + "github.com/umputun/remark/backend/app/rpc" + + "github.com/umputun/remark/backend/_example/memory_store/accessor" + "github.com/umputun/remark/backend/_example/memory_store/server" +) + +// opts with all cli commands and flags +var opts struct { + API string `long:"api" env:"API" default:"/" description:"api root url"` + Port int `long:"port" env:"PORT" default:"8080" description:"rpc server port"` + AuthUser string `long:"auth-user" env:"AUTH_USER" default:"" description:"rpc auth user name"` + AuthPasswd string `long:"auth-passwd" env:"AUTH_PASSWD" default:"" description:"rpc auth password"` + + Secret string `long:"secret" env:"SECRET" required:"true" description:"secret key"` + Dbg bool `long:"dbg" env:"DEBUG" description:"debug mode"` +} + +var revision = "unknown" + +func main() { + fmt.Printf("remark42-memory module %s\n", revision) + + if _, err := flags.Parse(&opts); err != nil { + os.Exit(2) + } + setupLog(opts.Dbg) + + dataStore := accessor.NewMemData() + adminStore := accessor.NewMemAdminStore(opts.Secret) + + rpcServer := rpc.Server{ + API: opts.API, + AuthUser: opts.AuthUser, + AuthPasswd: opts.AuthPasswd, + Version: revision, + AppName: "remark42-memory", + } + srv := server.NewRPC(dataStore, adminStore, &rpcServer) + + admRec := accessor.AdminRec{ + SiteID: "example", + IDs: []string{"id1", "id2"}, + Email: "admin@example.com", + } + adminStore.Set("example", admRec) + + err := srv.Run(opts.Port) + log.Printf("[ERROR] server failed or terminated, %+v", err) +} + +func setupLog(dbg bool) { + if dbg { + log.Setup(log.Debug, log.CallerFile, log.CallerFunc, log.Msec, log.LevelBraces) + return + } + log.Setup(log.Msec, log.LevelBraces) +} diff --git a/backend/_example/memory_store/server/rpc.go b/backend/_example/memory_store/server/rpc.go new file mode 100644 index 00000000..c9ea42d2 --- /dev/null +++ b/backend/_example/memory_store/server/rpc.go @@ -0,0 +1,191 @@ +package server + +import ( + "encoding/json" + + "github.com/go-pkgz/jrpc" + + "github.com/umputun/remark/backend/app/store" + "github.com/umputun/remark/backend/app/store/admin" + "github.com/umputun/remark/backend/app/store/engine" +) + +// RPC handler wraps both engine and remote server and implements all handlers +// Note: this file can be used as-is in any custom jrpc plugin +type RPC struct { + *jrpc.Server + eng engine.Interface + adm admin.Store +} + +// NewRPC makes RPC instance and register handlers +func NewRPC(e engine.Interface, a admin.Store, r *jrpc.Server) *RPC { + res := &RPC{eng: e, adm: a, Server: r} + res.addHandlers() + return res +} + +func (s *RPC) addHandlers() { + // data store handlers + s.Group("store", jrpc.HandlersGroup{ + "create": s.createHndl, + "find": s.findHndl, + "get": s.getHndl, + "update": s.updateHndl, + "count": s.countHndl, + "info": s.infoHndl, + "flag": s.flagHndl, + "list_flags": s.listFlagsHndl, + "delete": s.deleteHndl, + "close": s.closeHndl, + }) + + // admin store handlers + s.Group("admin", jrpc.HandlersGroup{ + "key": s.admKeyHndl, + "admins": s.admAdminsHndl, + "email": s.admEmailHndl, + }) +} + +func (s *RPC) createHndl(id uint64, params json.RawMessage) (rr jrpc.Response) { + comment := store.Comment{} + if err := json.Unmarshal(params, &comment); err != nil { + return jrpc.Response{Error: err.Error()} + } + commentID, err := s.eng.Create(comment) + return jrpc.EncodeResponse(id, commentID, err) +} + +// Find comments +func (s *RPC) findHndl(id uint64, params json.RawMessage) (rr jrpc.Response) { + req := engine.FindRequest{} + if err := json.Unmarshal(params, &req); err != nil { + return jrpc.Response{Error: err.Error()} + } + resp, err := s.eng.Find(req) + return jrpc.EncodeResponse(id, resp, err) +} + +// Get comment +func (s *RPC) getHndl(id uint64, params json.RawMessage) (rr jrpc.Response) { + req := engine.GetRequest{} + if err := json.Unmarshal(params, &req); err != nil { + return jrpc.Response{Error: err.Error()} + } + comment, err := s.eng.Get(req) + return jrpc.EncodeResponse(id, comment, err) +} + +// Update comment +func (s *RPC) updateHndl(id uint64, params json.RawMessage) (rr jrpc.Response) { + comment := store.Comment{} + if err := json.Unmarshal(params, &comment); err != nil { + return jrpc.Response{Error: err.Error()} + } + err := s.eng.Update(comment) + return jrpc.EncodeResponse(id, nil, err) +} + +// counts for site and users +func (s *RPC) countHndl(id uint64, params json.RawMessage) (rr jrpc.Response) { + req := engine.FindRequest{} + if err := json.Unmarshal(params, &req); err != nil { + return jrpc.Response{Error: err.Error()} + } + count, err := s.eng.Count(req) + return jrpc.EncodeResponse(id, count, err) +} + +// info get post meta info +func (s *RPC) infoHndl(id uint64, params json.RawMessage) (rr jrpc.Response) { + req := engine.InfoRequest{} + if err := json.Unmarshal(params, &req); err != nil { + return jrpc.Response{Error: err.Error()} + } + info, err := s.eng.Info(req) + return jrpc.EncodeResponse(id, info, err) +} + +// flagHndl get and sets flag value +func (s *RPC) flagHndl(id uint64, params json.RawMessage) (rr jrpc.Response) { + req := engine.FlagRequest{} + if err := json.Unmarshal(params, &req); err != nil { + return jrpc.Response{Error: err.Error()} + } + status, err := s.eng.Flag(req) + return jrpc.EncodeResponse(id, status, err) +} + +// listFlagsHndl list flags for given request +func (s *RPC) listFlagsHndl(id uint64, params json.RawMessage) (rr jrpc.Response) { + req := engine.FlagRequest{} + if err := json.Unmarshal(params, &req); err != nil { + return jrpc.Response{Error: err.Error()} + } + flags, err := s.eng.ListFlags(req) + return jrpc.EncodeResponse(id, flags, err) +} + +// deleteHndl remove comment(s) +func (s *RPC) deleteHndl(id uint64, params json.RawMessage) (rr jrpc.Response) { + req := engine.DeleteRequest{} + if err := json.Unmarshal(params, &req); err != nil { + return jrpc.Response{Error: err.Error()} + } + err := s.eng.Delete(req) + return jrpc.EncodeResponse(id, nil, err) +} + +// close store +func (s *RPC) closeHndl(id uint64, _ json.RawMessage) (rr jrpc.Response) { + if err := s.eng.Close(); err != nil { + return jrpc.Response{Error: err.Error()} + } + return jrpc.Response{} +} + +// get admin key +func (s *RPC) admKeyHndl(id uint64, _ json.RawMessage) (rr jrpc.Response) { + key, err := s.adm.Key() + if err != nil { + return jrpc.Response{Error: err.Error()} + } + return jrpc.EncodeResponse(id, key, err) +} + +// get admins list +func (s *RPC) admAdminsHndl(id uint64, params json.RawMessage) (rr jrpc.Response) { + args := []interface{}{} + if err := json.Unmarshal(params, &args); err != nil { + return jrpc.Response{Error: err.Error()} + } + siteID, ok := args[0].(string) + if !ok { + return jrpc.Response{Error: "incompatible argument"} + } + + admins, err := s.adm.Admins(siteID) + if err != nil { + return jrpc.Response{Error: err.Error()} + } + return jrpc.EncodeResponse(id, admins, err) +} + +// get admin email +func (s *RPC) admEmailHndl(id uint64, params json.RawMessage) (rr jrpc.Response) { + args := []interface{}{} + if err := json.Unmarshal(params, &args); err != nil { + return jrpc.Response{Error: err.Error()} + } + siteID, ok := args[0].(string) + if !ok { + return jrpc.Response{Error: "incompatible argument"} + } + + email, err := s.adm.Email(siteID) + if err != nil { + return jrpc.Response{Error: err.Error()} + } + return jrpc.EncodeResponse(id, email, err) +} diff --git a/backend/_example/memory_store/server/rpc_test.go b/backend/_example/memory_store/server/rpc_test.go new file mode 100644 index 00000000..583c3eb9 --- /dev/null +++ b/backend/_example/memory_store/server/rpc_test.go @@ -0,0 +1,320 @@ +/* + * Copyright 2019 Umputun. All rights reserved. + * Use of this source code is governed by a MIT-style + * license that can be found in the LICENSE file. + */ + +package server + +import ( + "fmt" + "math/rand" + "net/http" + "testing" + "time" + + "github.com/go-pkgz/jrpc" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + "github.com/umputun/remark/backend/app/rpc" + "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/_example/memory_store/accessor" +) + +func TestRPC_createHndl(t *testing.T) { + _, port, teardown := prepTestStore(t) + defer teardown() + api := fmt.Sprintf("http://localhost:%d/test", port) + + re := engine.RPC{Client: rpc.Client{API: api, Client: http.Client{Timeout: 1 * time.Second}}} + id, err := re.Create(store.Comment{ID: "123456", Locator: store.Locator{SiteID: "test-site", URL: "http://example.com/post1"}, + Text: "text 123", User: store.User{ID: "u1", Name: "user1"}}) + assert.NoError(t, err) + assert.Equal(t, "123456", id) +} + +func TestRPC_findHndl(t *testing.T) { + _, port, teardown := prepTestStore(t) + defer teardown() + api := fmt.Sprintf("http://localhost:%d/test", port) + + re := engine.RPC{Client: rpc.Client{API: api, Client: http.Client{Timeout: 1 * time.Second}}} + findReq := engine.FindRequest{Locator: store.Locator{SiteID: "test-site", URL: "http://example.com/post1"}} + comments, err := re.Find(findReq) + require.NoError(t, err) + assert.Equal(t, 0, len(comments)) + + c := store.Comment{ID: "123456", Locator: store.Locator{SiteID: "test-site", URL: "http://example.com/post1"}, + Text: "text 123", User: store.User{ID: "u1", Name: "user1"}} + id, err := re.Create(c) + assert.NoError(t, err) + assert.Equal(t, "123456", id) + + comments, err = re.Find(findReq) + require.NoError(t, err) + assert.Equal(t, 1, len(comments)) + assert.Equal(t, c, comments[0]) +} + +func TestRPC_getHndl(t *testing.T) { + _, port, teardown := prepTestStore(t) + defer teardown() + api := fmt.Sprintf("http://localhost:%d/test", port) + + re := engine.RPC{Client: rpc.Client{API: api, Client: http.Client{Timeout: 1 * time.Second}}} + req := engine.GetRequest{ + Locator: store.Locator{SiteID: "test-site", URL: "http://example.com/post1"}, + CommentID: "123456", + } + + _, err := re.Get(req) + assert.EqualError(t, err, "not found") + + c := store.Comment{ID: "123456", Locator: store.Locator{SiteID: "test-site", URL: "http://example.com/post1"}, + Text: "text 123", User: store.User{ID: "u1", Name: "user1"}} + _, err = re.Create(c) + assert.NoError(t, err) + + comment, err := re.Get(req) + assert.NoError(t, err) + assert.Equal(t, c, comment) +} + +func TestRPC_updateHndl(t *testing.T) { + _, port, teardown := prepTestStore(t) + defer teardown() + api := fmt.Sprintf("http://localhost:%d/test", port) + + re := engine.RPC{Client: rpc.Client{API: api, Client: http.Client{Timeout: 1 * time.Second}}} + + c := store.Comment{ID: "123456", Locator: store.Locator{SiteID: "test-site", URL: "http://example.com/post1"}, + Text: "text 123", User: store.User{ID: "u1", Name: "user1"}} + err := re.Update(c) + assert.EqualError(t, err, "not found") + + _, err = re.Create(c) + assert.NoError(t, err) + + c.Text = "updates" + err = re.Update(c) + assert.NoError(t, err) + + req := engine.GetRequest{ + Locator: store.Locator{SiteID: "test-site", URL: "http://example.com/post1"}, + CommentID: "123456", + } + comment, err := re.Get(req) + assert.NoError(t, err) + assert.Equal(t, c, comment) +} + +func TestRPC_countHndl(t *testing.T) { + _, port, teardown := prepTestStore(t) + defer teardown() + api := fmt.Sprintf("http://localhost:%d/test", port) + + re := engine.RPC{Client: rpc.Client{API: api, Client: http.Client{Timeout: 1 * time.Second}}} + findReq := engine.FindRequest{Locator: store.Locator{SiteID: "test-site", URL: "http://example.com/post1"}} + count, err := re.Count(findReq) + require.NoError(t, err) + assert.Equal(t, 0, count) + + c := store.Comment{ID: "123456", Locator: store.Locator{SiteID: "test-site", URL: "http://example.com/post1"}, + Text: "text 123", User: store.User{ID: "u1", Name: "user1"}} + id, err := re.Create(c) + assert.NoError(t, err) + assert.Equal(t, "123456", id) + + count, err = re.Count(findReq) + require.NoError(t, err) + assert.Equal(t, 1, count) +} + +func TestRPC_infoHndl(t *testing.T) { + _, port, teardown := prepTestStore(t) + defer teardown() + api := fmt.Sprintf("http://localhost:%d/test", port) + + re := engine.RPC{Client: rpc.Client{API: api, Client: http.Client{Timeout: 1 * time.Second}}} + + c := store.Comment{ID: "123456", Locator: store.Locator{SiteID: "test-site", URL: "http://example.com/post1"}, + Text: "text 123", User: store.User{ID: "u1", Name: "user1"}} + id, err := re.Create(c) + assert.NoError(t, err) + assert.Equal(t, "123456", id) + + infoReq := engine.InfoRequest{Locator: store.Locator{SiteID: "test-site", URL: "http://example.com/post1"}} + info, err := re.Info(infoReq) + require.NoError(t, err) + assert.Equal(t, 1, len(info)) + i := info[0] + assert.Equal(t, store.PostInfo{URL: "http://example.com/post1", Count: 1}, i) +} + +func TestRPC_flagHndl(t *testing.T) { + _, port, teardown := prepTestStore(t) + defer teardown() + api := fmt.Sprintf("http://localhost:%d/test", port) + + re := engine.RPC{Client: rpc.Client{API: api, Client: http.Client{Timeout: 1 * time.Second}}} + + c := store.Comment{ID: "123456", Locator: store.Locator{SiteID: "test-site", URL: "http://example.com/post1"}, + Text: "text 123", User: store.User{ID: "u1", Name: "user1"}} + id, err := re.Create(c) + assert.NoError(t, err) + assert.Equal(t, "123456", id) + + flagReq := engine.FlagRequest{ + Flag: engine.Verified, + Locator: store.Locator{ + SiteID: "test-site", + }, + UserID: "u1", + } + status, err := re.Flag(flagReq) + require.NoError(t, err) + assert.Equal(t, false, status) + + flagReq.Update = engine.FlagTrue + status, err = re.Flag(flagReq) + require.NoError(t, err) + assert.Equal(t, true, status) + + flagReq.Update = engine.FlagNonSet + status, err = re.Flag(flagReq) + require.NoError(t, err) + assert.Equal(t, true, status) +} + +func TestRPC_listFlagsHndl(t *testing.T) { + _, port, teardown := prepTestStore(t) + defer teardown() + api := fmt.Sprintf("http://localhost:%d/test", port) + + re := engine.RPC{Client: rpc.Client{API: api, Client: http.Client{Timeout: 1 * time.Second}}} + + c := store.Comment{ID: "123456", Locator: store.Locator{SiteID: "test-site", URL: "http://example.com/post1"}, + Text: "text 123", User: store.User{ID: "u1", Name: "user1"}} + id, err := re.Create(c) + assert.NoError(t, err) + assert.Equal(t, "123456", id) + + flagReq := engine.FlagRequest{ + Flag: engine.Verified, + UserID: "u1", + Locator: store.Locator{ + SiteID: "test-site", + }, + } + flags, err := re.ListFlags(flagReq) + require.NoError(t, err) + assert.Equal(t, []interface{}{}, flags) + + flagReq.Update = engine.FlagTrue + status, err := re.Flag(flagReq) + require.NoError(t, err) + assert.Equal(t, true, status) + + flags, err = re.ListFlags(flagReq) + require.NoError(t, err) + assert.Equal(t, []interface{}{"u1"}, flags) +} + +func TestRPC_deleteHndl(t *testing.T) { + _, port, teardown := prepTestStore(t) + defer teardown() + api := fmt.Sprintf("http://localhost:%d/test", port) + + re := engine.RPC{Client: rpc.Client{API: api, Client: http.Client{Timeout: 1 * time.Second}}} + req := engine.DeleteRequest{ + Locator: store.Locator{SiteID: "test-site", URL: "http://example.com/post1"}, + CommentID: "123456", + } + + err := re.Delete(req) + assert.EqualError(t, err, "not found") + + c := store.Comment{ID: "123456", Locator: store.Locator{SiteID: "test-site", URL: "http://example.com/post1"}, + Text: "text 123", User: store.User{ID: "u1", Name: "user1"}} + _, err = re.Create(c) + assert.NoError(t, err) + + err = re.Delete(req) + assert.NoError(t, err) +} + +func TestRPC_closeHndl(t *testing.T) { + _, port, teardown := prepTestStore(t) + defer teardown() + api := fmt.Sprintf("http://localhost:%d/test", port) + + re := engine.RPC{Client: rpc.Client{API: api, Client: http.Client{Timeout: 1 * time.Second}}} + err := re.Close() + assert.NoError(t, err) +} +func TestRPC_admKeyHndl(t *testing.T) { + _, port, teardown := prepTestStore(t) + defer teardown() + api := fmt.Sprintf("http://localhost:%d/test", port) + + ra := admin.RPC{Client: rpc.Client{API: api, Client: http.Client{Timeout: 1 * time.Second}}} + key, err := ra.Key() + assert.NoError(t, err) + assert.Equal(t, "secret", key) +} + +func TestRPC_admAdminsHndl(t *testing.T) { + _, port, teardown := prepTestStore(t) + defer teardown() + api := fmt.Sprintf("http://localhost:%d/test", port) + + ra := admin.RPC{Client: rpc.Client{API: api, Client: http.Client{Timeout: 1 * time.Second}}} + _, err := ra.Admins("bad site") + assert.EqualError(t, err, "site bad site not found") + + admins, err := ra.Admins("test-site") + assert.NoError(t, err) + assert.Equal(t, []string{"id1", "id2"}, admins) +} + +func TestRPC_admEmailHndl(t *testing.T) { + _, port, teardown := prepTestStore(t) + defer teardown() + api := fmt.Sprintf("http://localhost:%d/test", port) + + ra := admin.RPC{Client: rpc.Client{API: api, Client: http.Client{Timeout: 1 * time.Second}}} + _, err := ra.Admins("bad site") + assert.EqualError(t, err, "site bad site not found") + + email, err := ra.Email("test-site") + assert.NoError(t, err) + assert.Equal(t, "admin@example.com", email) +} + +func prepTestStore(t *testing.T) (s *RPC, port int, teardown func()) { + port = 40000 + int(rand.Int31n(10000)) + + mg := accessor.NewMemData() + adm := accessor.NewMemAdminStore("secret") + s = NewRPC(mg, adm, &jrpc.Server{API: "/test", Logger: jrpc.NoOpLogger}) + + admRec := accessor.AdminRec{ + SiteID: "test-site", + IDs: []string{"id1", "id2"}, + Email: "admin@example.com", + } + adm.Set("test-site", admRec) + + go func() { + t.Log(s.Run(port)) + }() + time.Sleep(time.Millisecond * 10) + + return s, port, func() { + require.NoError(t, s.Shutdown()) + } +} diff --git a/backend/_example/rpc/main.go b/backend/_example/rpc/main.go deleted file mode 100644 index de668734..00000000 --- a/backend/_example/rpc/main.go +++ /dev/null @@ -1,22 +0,0 @@ -/* - * Copyright 2019 Umputun. All rights reserved. - * Use of this source code is governed by a MIT-style - * license that can be found in the LICENSE file. - */ - -package main - -// Opts with all cli commands and flags -var opts struct { - API string `long:"api" env:"API" default:"/" description:"api root url"` - Port int `long:"port" env:"PORT" default:"8080" description:"rpc server port"` - AuthUser string `long:"auth-user" env:"AUTH_USER" default:"" description:"auth user name"` - AuthPasswd string `long:"auth-passwd" env:"AUTH_PASSWD" default:"" description:"auth password"` - Secret string `long:"secret" env:"SECRET" required:"true" description:"secret key"` - Dbg bool `long:"dbg" env:"DEBUG" description:"debug mode"` -} - - -func main() { - -} diff --git a/backend/_example/rpc/server/store.go b/backend/_example/rpc/server/store.go deleted file mode 100644 index 30f029a6..00000000 --- a/backend/_example/rpc/server/store.go +++ /dev/null @@ -1,230 +0,0 @@ -package server - -import ( - "encoding/json" - - "github.com/umputun/remark/backend/app/rpc" - "github.com/umputun/remark/backend/app/store" - "github.com/umputun/remark/backend/app/store/admin" - "github.com/umputun/remark/backend/app/store/engine" -) - -// Store handler wraps both engine and remote server and implements all handlers -// Note: this file can be used as-is in any custom rpc plugin -type Store struct { - *rpc.Server - eng engine.Interface - adm admin.Store -} - -// NewStore makes Store instance and register handlers -func NewStore(e engine.Interface, a admin.Store, r *rpc.Server) *Store { - res := &Store{eng: e, adm: a, Server: r} - res.addHandlers() - return res -} - - -func (s *Store) addHandlers() { - // data store handlers - s.Group("store", rpc.HandlersGroup{ - "create": s.createHndl, - "find": s.findHndl, - "get": s.getHndl, - "update": s.updateHndl, - "count": s.countHndl, - "info": s.infoHndl, - "flag": s.flagHndl, - "list_flags": s.listFlagsHndl, - "delete": s.deleteHndl, - "close": s.closeHndl, - }) - - // admin store handlers - s.Group("admin", rpc.HandlersGroup{ - "key": s.admKeyHndl, - "admins": s.admAdminsHndl, - "email": s.admEmailHndl, - }) -} - -func (s *Store) createHndl(id uint64, params json.RawMessage) (rr rpc.Response) { - comment := store.Comment{} - if err := json.Unmarshal(params, &comment); err != nil { - return rpc.Response{Error: err.Error()} - } - commentID, err := s.eng.Create(comment) - if rr, err = s.EncodeResponse(id, commentID, err); err != nil { - return rpc.Response{Error: err.Error()} - - } - return rr -} - -// Find comments -func (s *Store) findHndl(id uint64, params json.RawMessage) (rr rpc.Response) { - req := engine.FindRequest{} - if err := json.Unmarshal(params, &req); err != nil { - return rpc.Response{Error: err.Error()} - } - resp, err := s.eng.Find(req) - if rr, err = s.EncodeResponse(id, resp, err); err != nil { - return rpc.Response{Error: err.Error()} - } - return rr -} - -// Get comment -func (s *Store) getHndl(id uint64, params json.RawMessage) (rr rpc.Response) { - req := engine.GetRequest{} - if err := json.Unmarshal(params, &req); err != nil { - return rpc.Response{Error: err.Error()} - } - comment, err := s.eng.Get(req) - if rr, err = s.EncodeResponse(id, comment, err); err != nil { - return rpc.Response{Error: err.Error()} - } - return rr -} - -// Update comment -func (s *Store) updateHndl(id uint64, params json.RawMessage) (rr rpc.Response) { - comment := store.Comment{} - if err := json.Unmarshal(params, &comment); err != nil { - return rpc.Response{Error: err.Error()} - } - err := s.eng.Update(comment) - if rr, err = s.EncodeResponse(id, nil, err); err != nil { - return rpc.Response{Error: err.Error()} - } - return rr -} - -// counts for site and users -func (s *Store) countHndl(id uint64, params json.RawMessage) (rr rpc.Response) { - req := engine.FindRequest{} - if err := json.Unmarshal(params, &req); err != nil { - return rpc.Response{Error: err.Error()} - } - count, err := s.eng.Count(req) - if rr, err = s.EncodeResponse(id, count, err); err != nil { - return rpc.Response{Error: err.Error()} - } - return rr -} - -// info get post meta info -func (s *Store) infoHndl(id uint64, params json.RawMessage) (rr rpc.Response) { - req := engine.InfoRequest{} - if err := json.Unmarshal(params, &req); err != nil { - return rpc.Response{Error: err.Error()} - } - info, err := s.eng.Info(req) - if rr, err = s.EncodeResponse(id, info, err); err != nil { - return rpc.Response{Error: err.Error()} - } - return rr -} - -// flagHndl get and sets flag value -func (s *Store) flagHndl(id uint64, params json.RawMessage) (rr rpc.Response) { - req := engine.FlagRequest{} - if err := json.Unmarshal(params, &req); err != nil { - return rpc.Response{Error: err.Error()} - } - status, err := s.eng.Flag(req) - if rr, err = s.EncodeResponse(id, status, err); err != nil { - return rpc.Response{Error: err.Error()} - } - return rr -} - -// listFlagsHndl list flags for given request -func (s *Store) listFlagsHndl(id uint64, params json.RawMessage) (rr rpc.Response) { - req := engine.FlagRequest{} - if err := json.Unmarshal(params, &req); err != nil { - return rpc.Response{Error: err.Error()} - } - flags, err := s.eng.ListFlags(req) - if rr, err = s.EncodeResponse(id, flags, err); err != nil { - return rpc.Response{Error: err.Error()} - } - return rr -} - -// deleteHndl remove comment(s) -func (s *Store) deleteHndl(id uint64, params json.RawMessage) (rr rpc.Response) { - req := engine.DeleteRequest{} - if err := json.Unmarshal(params, &req); err != nil { - return rpc.Response{Error: err.Error()} - } - err := s.eng.Delete(req) - if rr, err = s.EncodeResponse(id, nil, err); err != nil { - return rpc.Response{Error: err.Error()} - } - return rr -} - -// close store -func (s *Store) closeHndl(id uint64, _ json.RawMessage) (rr rpc.Response) { - if err := s.eng.Close(); err != nil { - return rpc.Response{Error: err.Error()} - } - return rpc.Response{} -} - -// get admin key -func (s *Store) admKeyHndl(id uint64, _ json.RawMessage) (rr rpc.Response) { - key, err := s.adm.Key() - if err != nil { - return rpc.Response{Error: err.Error()} - } - if rr, err = s.EncodeResponse(id, key, err); err != nil { - return rpc.Response{Error: err.Error()} - } - return rr -} - -// get admins list -func (s *Store) admAdminsHndl(id uint64, params json.RawMessage) (rr rpc.Response) { - args := []interface{}{} - if err := json.Unmarshal(params, &args); err != nil { - return rpc.Response{Error: err.Error()} - } - siteID, ok := args[0].(string) - if !ok { - return rpc.Response{Error: "incompatible argument"} - } - - admins, err := s.adm.Admins(siteID) - if err != nil { - return rpc.Response{Error: err.Error()} - } - - if rr, err = s.EncodeResponse(id, admins, err); err != nil { - return rpc.Response{Error: err.Error()} - } - return rr -} - -// get admin email -func (s *Store) admEmailHndl(id uint64, params json.RawMessage) (rr rpc.Response) { - args := []interface{}{} - if err := json.Unmarshal(params, &args); err != nil { - return rpc.Response{Error: err.Error()} - } - siteID, ok := args[0].(string) - if !ok { - return rpc.Response{Error: "incompatible argument"} - } - - email, err := s.adm.Email(siteID) - if err != nil { - return rpc.Response{Error: err.Error()} - } - - if rr, err = s.EncodeResponse(id, email, err); err != nil { - return rpc.Response{Error: err.Error()} - } - return rr -} From 3c21cba9c1eca96e416256be1410be9bec252a8c Mon Sep 17 00:00:00 2001 From: Umputun Date: Wed, 31 Jul 2019 18:38:53 -0500 Subject: [PATCH 3/9] switch core to go-pkgz/jrpc --- backend/app/rpc/client_test.go | 97 ------- backend/app/rpc/remote.go | 23 -- backend/app/rpc/server_test.go | 245 ------------------ backend/app/store/engine/remote.go | 5 +- backend/app/store/engine/remote_test.go | 30 +-- backend/go.mod | 5 +- backend/go.sum | 7 + .../github.com/didip/tollbooth/README.md | 8 +- .../github.com/didip/tollbooth/tollbooth.go | 32 ++- .../vendor/github.com/go-pkgz/jrpc/.gitignore | 12 + .../github.com/go-pkgz/jrpc/.golangci.yml | 70 +++++ .../github.com/go-pkgz/jrpc/.travis.yml | 21 ++ .../vendor/github.com/go-pkgz/jrpc/LICENSE | 21 ++ .../vendor/github.com/go-pkgz/jrpc/README.md | 99 +++++++ .../github.com/go-pkgz/jrpc}/client.go | 21 +- backend/vendor/github.com/go-pkgz/jrpc/go.mod | 16 ++ backend/vendor/github.com/go-pkgz/jrpc/go.sum | 32 +++ .../vendor/github.com/go-pkgz/jrpc/jrpc.go | 40 +++ .../github.com/go-pkgz/jrpc}/server.go | 82 +++--- backend/vendor/modules.txt | 6 +- 20 files changed, 420 insertions(+), 452 deletions(-) delete mode 100644 backend/app/rpc/client_test.go delete mode 100644 backend/app/rpc/remote.go delete mode 100644 backend/app/rpc/server_test.go create mode 100644 backend/vendor/github.com/go-pkgz/jrpc/.gitignore create mode 100644 backend/vendor/github.com/go-pkgz/jrpc/.golangci.yml create mode 100644 backend/vendor/github.com/go-pkgz/jrpc/.travis.yml create mode 100644 backend/vendor/github.com/go-pkgz/jrpc/LICENSE create mode 100644 backend/vendor/github.com/go-pkgz/jrpc/README.md rename backend/{app/rpc => vendor/github.com/go-pkgz/jrpc}/client.go (65%) create mode 100644 backend/vendor/github.com/go-pkgz/jrpc/go.mod create mode 100644 backend/vendor/github.com/go-pkgz/jrpc/go.sum create mode 100644 backend/vendor/github.com/go-pkgz/jrpc/jrpc.go rename backend/{app/rpc => vendor/github.com/go-pkgz/jrpc}/server.go (58%) diff --git a/backend/app/rpc/client_test.go b/backend/app/rpc/client_test.go deleted file mode 100644 index 12b718ce..00000000 --- a/backend/app/rpc/client_test.go +++ /dev/null @@ -1,97 +0,0 @@ -package rpc - -import ( - "encoding/json" - "fmt" - "io/ioutil" - "net/http" - "net/http/httptest" - "testing" - "time" - - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" -) - -func TestClient_Call(t *testing.T) { - ts := testServer(t, `{"method":"test","params":[123,"abc"],"id":1}`, `{"result":"12345"}`) - defer ts.Close() - c := Client{API: ts.URL, Client: http.Client{}} - resp, err := c.Call("test", 123, "abc") - assert.NoError(t, err) - res := "" - err = json.Unmarshal(*resp.Result, &res) - assert.NoError(t, err) - assert.Equal(t, "12345", res) - t.Logf("%v %T", res, res) -} - -func TestClient_CallWithObject(t *testing.T) { - ts := testServer(t, `{"method":"test","params":{"F1":123,"F2":"abc","F3":"2019-06-09T23:03:55Z"},"id":1}`, `{"result":"12345"}`) - defer ts.Close() - c := Client{API: ts.URL, Client: http.Client{}} - obj := struct { - F1 int - F2 string - F3 time.Time - }{ - F1: 123, - F2: "abc", - F3: time.Date(2019, 6, 9, 23, 3, 55, 0, time.UTC), - } - - resp, err := c.Call("test", obj) - assert.NoError(t, err) - res := "" - err = json.Unmarshal(*resp.Result, &res) - assert.NoError(t, err) - assert.Equal(t, "12345", res) - t.Logf("%v %T", res, res) -} - -func TestClient_CallWithNoParams(t *testing.T) { - ts := testServer(t, `{"method":"test","id":1}`, `{"result":"12345"}`) - defer ts.Close() - c := Client{API: ts.URL, Client: http.Client{}} - resp, err := c.Call("test") - assert.NoError(t, err) - res := "" - err = json.Unmarshal(*resp.Result, &res) - assert.NoError(t, err) - assert.Equal(t, "12345", res) - t.Logf("%v %T", res, res) -} - -func TestClient_CallError(t *testing.T) { - ts := testServer(t, `{"method":"test","params":[123,"abc"],"id":1}`, `{"error":"some error"}`) - defer ts.Close() - c := Client{API: ts.URL, Client: http.Client{}} - _, err := c.Call("test", 123, "abc") - assert.EqualError(t, err, "some error") -} - -func TestClient_CallBadResponse(t *testing.T) { - ts := testServer(t, `{"method":"test","params":[123,"abc"],"id":1}`, `{"result":"12345 invalid}`) - defer ts.Close() - c := Client{API: ts.URL, Client: http.Client{}} - _, err := c.Call("test", 123, "abc") - assert.NotNil(t, err) -} - -func TestClient_CallBadRemote(t *testing.T) { - ts := testServer(t, `{"method":"test","params":[123,"abc"],"id":1}`, `{"result":"12345"}`) - defer ts.Close() - c := Client{API: "http://127.0.0.2", Client: http.Client{Timeout: 10 * time.Millisecond}} - _, err := c.Call("test", 123) - assert.NotNil(t, err) -} - -func testServer(t *testing.T, req, resp string) *httptest.Server { - return httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - body, err := ioutil.ReadAll(r.Body) - require.NoError(t, err) - assert.Equal(t, req, string(body)) - t.Logf("req: %s", string(body)) - fmt.Fprintf(w, resp) - })) -} diff --git a/backend/app/rpc/remote.go b/backend/app/rpc/remote.go deleted file mode 100644 index 48d8ac50..00000000 --- a/backend/app/rpc/remote.go +++ /dev/null @@ -1,23 +0,0 @@ -// Package rpc implements client ans server for RPC-like communication with remote storage. -// The protocol is somewhat simplified version of json-rpc with a single POST call sending -// Request json (method name and the list of parameters) and receiving back json Response with "result" json -// and error string -package rpc - -import ( - "encoding/json" -) - -// Request encloses method name and all params -type Request struct { - Method string `json:"method"` - Params interface{} `json:"params,omitempty"` - ID uint64 `json:"id"` -} - -// Response encloses result and error received from remote server -type Response struct { - Result *json.RawMessage `json:"result,omitempty"` - Error string `json:"error,omitempty"` - ID uint64 `json:"id"` -} diff --git a/backend/app/rpc/server_test.go b/backend/app/rpc/server_test.go deleted file mode 100644 index c39fe51a..00000000 --- a/backend/app/rpc/server_test.go +++ /dev/null @@ -1,245 +0,0 @@ -package rpc - -import ( - "bytes" - "encoding/json" - "io/ioutil" - "net/http" - "net/http/httptest" - "testing" - "time" - - "github.com/pkg/errors" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" -) - -func TestServerPrimitiveTypes(t *testing.T) { - s := Server{API: "/v1/cmd"} - - type respData struct { - Res1 string - Res2 bool - } - - s.Add("test", func(id uint64, params json.RawMessage) Response { - args := []interface{}{} - if err := json.Unmarshal(params, &args); err != nil { - return Response{Error: err.Error()} - } - t.Logf("%+v", args) - - assert.Equal(t, 3, len(args)) - assert.Equal(t, "blah", args[0].(string)) - assert.Equal(t, 42., args[1].(float64)) - assert.Equal(t, true, args[2].(bool)) - - r, err := s.EncodeResponse(id, respData{"res blah", true}, nil) - assert.NoError(t, err) - return r - }) - - go func() { _ = s.Run(9091) }() - defer func() { assert.NoError(t, s.Shutdown()) }() - time.Sleep(10 * time.Millisecond) - - // check with direct http call - clientReq := Request{Method: "test", Params: []interface{}{"blah", 42, true}, ID: 123} - b := bytes.Buffer{} - require.NoError(t, json.NewEncoder(&b).Encode(clientReq)) - resp, err := http.Post("http://127.0.0.1:9091/v1/cmd", "application/json", &b) - require.NoError(t, err) - defer resp.Body.Close() - assert.Equal(t, 200, resp.StatusCode) - data, err := ioutil.ReadAll(resp.Body) - assert.NoError(t, err) - assert.Equal(t, `{"result":{"Res1":"res blah","Res2":true},"id":123}`+"\n", string(data)) - - // check with client call - c := Client{API: "http://127.0.0.1:9091/v1/cmd", Client: http.Client{}} - r, err := c.Call("test", "blah", 42, true) - assert.NoError(t, err) - assert.Equal(t, "", r.Error) - - res := respData{} - err = json.Unmarshal(*r.Result, &res) - assert.NoError(t, err) - assert.Equal(t, respData{Res1: "res blah", Res2: true}, res) - assert.Equal(t, uint64(1), r.ID) -} - -func TestServerWithObject(t *testing.T) { - s := Server{API: "/v1/cmd"} - - type respData struct { - Res1 string - Res2 bool - } - - type reqData struct { - Time time.Time - F1 string - F2 time.Duration - } - - s.Add("test", func(id uint64, params json.RawMessage) Response { - arg := reqData{} - if err := json.Unmarshal(params, &arg); err != nil { - return Response{Error: err.Error()} - } - t.Logf("%+v", arg) - - r, err := s.EncodeResponse(id, respData{"res blah", true}, nil) - assert.NoError(t, err) - return r - }) - - go func() { _ = s.Run(9091) }() - defer func() { assert.NoError(t, s.Shutdown()) }() - time.Sleep(10 * time.Millisecond) - - c := Client{API: "http://127.0.0.1:9091/v1/cmd", Client: http.Client{}} - r, err := c.Call("test", reqData{Time: time.Now(), F1: "sawert", F2: time.Minute}) - assert.NoError(t, err) - assert.Equal(t, "", r.Error) - - res := respData{} - err = json.Unmarshal(*r.Result, &res) - assert.NoError(t, err) - assert.Equal(t, respData{Res1: "res blah", Res2: true}, res) -} - -func TestServerMethodNotImplemented(t *testing.T) { - s := Server{} - ts := httptest.NewServer(http.HandlerFunc(s.handler)) - defer ts.Close() - s.Add("test", func(id uint64, params json.RawMessage) Response { - return Response{} - }) - - r := Request{Method: "blah"} - buf := bytes.Buffer{} - assert.NoError(t, json.NewEncoder(&buf).Encode(r)) - resp, err := http.Post(ts.URL, "application/json", &buf) - require.NoError(t, err) - assert.Equal(t, http.StatusNotImplemented, resp.StatusCode) - - assert.EqualError(t, s.Shutdown(), "http server is not running") -} - -func TestServerWithAuth(t *testing.T) { - s := Server{API: "/v1/cmd", AuthUser: "user", AuthPasswd: "passwd"} - - s.Add("test", func(id uint64, params json.RawMessage) Response { - args := []interface{}{} - if err := json.Unmarshal(params, &args); err != nil { - return Response{Error: err.Error()} - } - t.Logf("%+v", args) - - assert.Equal(t, 3, len(args)) - assert.Equal(t, "blah", args[0].(string)) - assert.Equal(t, 42., args[1].(float64)) - assert.Equal(t, true, args[2].(bool)) - - r, err := s.EncodeResponse(id, "res blah", nil) - assert.NoError(t, err) - return r - }) - - go func() { _ = s.Run(9091) }() - time.Sleep(10 * time.Millisecond) - defer func() { assert.NoError(t, s.Shutdown()) }() - - c := Client{API: "http://127.0.0.1:9091/v1/cmd", Client: http.Client{}, AuthUser: "user", AuthPasswd: "passwd"} - r, err := c.Call("test", "blah", 42, true) - assert.NoError(t, err) - assert.Equal(t, "", r.Error) - val := "" - err = json.Unmarshal(*r.Result, &val) - assert.NoError(t, err) - assert.Equal(t, "res blah", val) - - c = Client{API: "http://127.0.0.1:9091/v1/cmd", Client: http.Client{}} - _, err = c.Call("test", "blah", 42, true) - assert.EqualError(t, err, "bad status 401 Unauthorized for test") -} - -func TestServerErrReturn(t *testing.T) { - s := Server{API: "/v1/cmd", AuthUser: "user", AuthPasswd: "passwd"} - - s.Add("test", func(id uint64, params json.RawMessage) Response { - args := []interface{}{} - if err := json.Unmarshal(params, &args); err != nil { - return Response{Error: err.Error()} - } - t.Logf("%+v", args) - - assert.Equal(t, 3, len(args)) - assert.Equal(t, "blah", args[0].(string)) - assert.Equal(t, 42., args[1].(float64)) - assert.Equal(t, true, args[2].(bool)) - - r, err := s.EncodeResponse(id, "res blah", errors.New("some error")) - assert.NoError(t, err) - return r - }) - - go func() { _ = s.Run(9091) }() - defer func() { assert.NoError(t, s.Shutdown()) }() - time.Sleep(10 * time.Millisecond) - - c := Client{API: "http://127.0.0.1:9091/v1/cmd", Client: http.Client{}, AuthUser: "user", AuthPasswd: "passwd"} - _, err := c.Call("test", "blah", 42, true) - assert.EqualError(t, err, "some error") -} - -func TestServerGroup(t *testing.T) { - s := Server{API: "/v1/cmd"} - s.Group("pre", HandlersGroup{ - "fn1": func(id uint64, params json.RawMessage) Response { - return Response{} - }, - "fn2": func(id uint64, params json.RawMessage) Response { - return Response{} - }, - }) - go func() { _ = s.Run(9091) }() - defer func() { assert.NoError(t, s.Shutdown()) }() - time.Sleep(10 * time.Millisecond) - - c := Client{API: "http://127.0.0.1:9091/v1/cmd", Client: http.Client{}} - _, err := c.Call("fn1") - assert.EqualError(t, err, "bad status 501 Not Implemented for fn1") - - _, err = c.Call("pre.fn1") - assert.NoError(t, err) - _, err = c.Call("pre.fn2") - assert.NoError(t, err) -} - -func TestServerAddLate(t *testing.T) { - s := Server{API: "/v1/cmd"} - s.Add("fn1", func(id uint64, params json.RawMessage) Response { - return Response{} - }) - go func() { _ = s.Run(9091) }() - defer func() { assert.NoError(t, s.Shutdown()) }() - time.Sleep(10 * time.Millisecond) - - // too late, ignored after run - s.Add("fn2", func(id uint64, params json.RawMessage) Response { - return Response{} - }) - - c := Client{API: "http://127.0.0.1:9091/v1/cmd", Client: http.Client{}} - _, err := c.Call("fn1") - assert.NoError(t, err) - _, err = c.Call("fn2") - assert.EqualError(t, err, "bad status 501 Not Implemented for fn2") -} - -func TestServerNoHandlers(t *testing.T) { - s := Server{API: "/v1/cmd", AuthUser: "user", AuthPasswd: "passwd"} - assert.EqualError(t, s.Run(9091), "nothing mapped for dispatch, Add has to be called prior to Run") -} diff --git a/backend/app/store/engine/remote.go b/backend/app/store/engine/remote.go index bb084295..186ff9f0 100644 --- a/backend/app/store/engine/remote.go +++ b/backend/app/store/engine/remote.go @@ -3,13 +3,14 @@ package engine import ( "encoding/json" - "github.com/umputun/remark/backend/app/rpc" + "github.com/go-pkgz/jrpc" + "github.com/umputun/remark/backend/app/store" ) // RPC implements remote engine and delegates all Calls to remote http server type RPC struct { - rpc.Client + jrpc.Client } // Create comment and return ID diff --git a/backend/app/store/engine/remote_test.go b/backend/app/store/engine/remote_test.go index 160f6d29..b97b637c 100644 --- a/backend/app/store/engine/remote_test.go +++ b/backend/app/store/engine/remote_test.go @@ -9,10 +9,10 @@ import ( "testing" "time" + "github.com/go-pkgz/jrpc" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" - "github.com/umputun/remark/backend/app/rpc" "github.com/umputun/remark/backend/app/store" ) @@ -20,7 +20,7 @@ func TestRemote_Create(t *testing.T) { ts := testServer(t, `{"method":"store.create","params":{"id":"123","pid":"","text":"msg","user":{"name":"","id":"","picture":"","admin":false},"locator":{"site":"site","url":"http://example.com/url"},"score":0,"vote":0,"time":"0001-01-01T00:00:00Z"},"id":1}`, `{"result":"12345","id":1}`) defer ts.Close() - c := RPC{Client: rpc.Client{API: ts.URL, Client: http.Client{}}} + c := RPC{Client: jrpc.Client{API: ts.URL, Client: http.Client{}}} var eng Interface = &c _ = eng @@ -35,7 +35,7 @@ func TestRemote_Create(t *testing.T) { func TestRemote_Get(t *testing.T) { ts := testServer(t, `{"method":"store.get","params":{"locator":{"url":"http://example.com/url"},"comment_id":"site"},"id":1}`, `{"result":{"id":"123","pid":"","text":"msg","delete":true}}`) defer ts.Close() - c := RPC{Client: rpc.Client{API: ts.URL, Client: http.Client{}}} + c := RPC{Client: jrpc.Client{API: ts.URL, Client: http.Client{}}} req := GetRequest{Locator: store.Locator{URL: "http://example.com/url"}, CommentID: "site"} res, err := c.Get(req) @@ -47,7 +47,7 @@ func TestRemote_Get(t *testing.T) { func TestRemote_GetWithErrorResult(t *testing.T) { ts := testServer(t, `{"method":"store.get","params":{"locator":{"url":"http://example.com/url"},"comment_id":"site"},"id":1}`, `{"error":"failed"}`) defer ts.Close() - c := RPC{Client: rpc.Client{API: ts.URL, Client: http.Client{}}} + c := RPC{Client: jrpc.Client{API: ts.URL, Client: http.Client{}}} req := GetRequest{Locator: store.Locator{URL: "http://example.com/url"}, CommentID: "site"} _, err := c.Get(req) @@ -57,7 +57,7 @@ func TestRemote_GetWithErrorResult(t *testing.T) { func TestRemote_GetWithErrorDecode(t *testing.T) { ts := testServer(t, `{"method":"store.get","params":{"locator":{"url":"http://example.com/url"},"comment_id":"site"},"id":1}`, ``) defer ts.Close() - c := RPC{Client: rpc.Client{API: ts.URL, Client: http.Client{}}} + c := RPC{Client: jrpc.Client{API: ts.URL, Client: http.Client{}}} req := GetRequest{Locator: store.Locator{URL: "http://example.com/url"}, CommentID: "site"} _, err := c.Get(req) @@ -65,7 +65,7 @@ func TestRemote_GetWithErrorDecode(t *testing.T) { } func TestRemote_GetWithErrorRemote(t *testing.T) { - c := RPC{Client: rpc.Client{API: "http://127.0.0.2", Client: http.Client{Timeout: 10 * time.Millisecond}}} + c := RPC{Client: jrpc.Client{API: "http://127.0.0.2", Client: http.Client{Timeout: 10 * time.Millisecond}}} req := GetRequest{Locator: store.Locator{URL: "http://example.com/url"}, CommentID: "site"} _, err := c.Get(req) @@ -81,7 +81,7 @@ func TestRemote_FailedStatus(t *testing.T) { w.WriteHeader(400) })) defer ts.Close() - c := RPC{Client: rpc.Client{API: ts.URL, Client: http.Client{}}} + c := RPC{Client: jrpc.Client{API: ts.URL, Client: http.Client{}}} req := GetRequest{Locator: store.Locator{URL: "http://example.com/url"}, CommentID: "site"} _, err := c.Get(req) @@ -91,7 +91,7 @@ func TestRemote_FailedStatus(t *testing.T) { func TestRemote_Update(t *testing.T) { ts := testServer(t, `{"method":"store.update","params":{"id":"123","pid":"","text":"msg","user":{"name":"","id":"","picture":"","admin":false},"locator":{"site":"site123","url":"http://example.com/url"},"score":0,"vote":0,"time":"0001-01-01T00:00:00Z"},"id":1}`, `{}`) defer ts.Close() - c := RPC{Client: rpc.Client{API: ts.URL, Client: http.Client{}}} + c := RPC{Client: jrpc.Client{API: ts.URL, Client: http.Client{}}} err := c.Update(store.Comment{ID: "123", Locator: store.Locator{URL: "http://example.com/url", SiteID: "site123"}, Text: "msg"}) @@ -102,7 +102,7 @@ func TestRemote_Update(t *testing.T) { func TestRemote_Find(t *testing.T) { ts := testServer(t, `{"method":"store.find","params":{"locator":{"url":"http://example.com/url"},"sort":"-time","since":"0001-01-01T00:00:00Z","limit":10},"id":1}`, `{"result":[{"text":"1"},{"text":"2"}]}`) defer ts.Close() - c := RPC{Client: rpc.Client{API: ts.URL, Client: http.Client{}}} + c := RPC{Client: jrpc.Client{API: ts.URL, Client: http.Client{}}} res, err := c.Find(FindRequest{Locator: store.Locator{URL: "http://example.com/url"}, Sort: "-time", Limit: 10}) assert.NoError(t, err) @@ -112,7 +112,7 @@ func TestRemote_Find(t *testing.T) { func TestRemote_Info(t *testing.T) { ts := testServer(t, `{"method":"store.info","params":{"locator":{"url":"http://example.com/url"},"limit":10,"skip":5,"ro_age":10},"id":1}`, `{"result":[{"url":"u1","count":22},{"url":"u2","count":33}]}`) defer ts.Close() - c := RPC{Client: rpc.Client{API: ts.URL, Client: http.Client{}}} + c := RPC{Client: jrpc.Client{API: ts.URL, Client: http.Client{}}} res, err := c.Info(InfoRequest{Locator: store.Locator{URL: "http://example.com/url"}, Limit: 10, Skip: 5, ReadOnlyAge: 10}) @@ -123,7 +123,7 @@ func TestRemote_Info(t *testing.T) { func TestRemote_Flag(t *testing.T) { ts := testServer(t, `{"method":"store.flag","params":{"flag":"verified","locator":{"url":"http://example.com/url"}},"id":1}`, `{"result":false}`) defer ts.Close() - c := RPC{Client: rpc.Client{API: ts.URL, Client: http.Client{}}} + c := RPC{Client: jrpc.Client{API: ts.URL, Client: http.Client{}}} res, err := c.Flag(FlagRequest{Locator: store.Locator{URL: "http://example.com/url"}, Flag: Verified}) assert.NoError(t, err) @@ -133,7 +133,7 @@ func TestRemote_Flag(t *testing.T) { func TestRemote_ListFlag(t *testing.T) { ts := testServer(t, `{"method":"store.list_flags","params":{"flag":"blocked","locator":{"site":"site_id","url":""}},"id":1}`, `{"result":[{"ID":"id1"},{"ID":"id2"}]}`) defer ts.Close() - c := RPC{Client: rpc.Client{API: ts.URL, Client: http.Client{}}} + c := RPC{Client: jrpc.Client{API: ts.URL, Client: http.Client{}}} res, err := c.ListFlags(FlagRequest{Locator: store.Locator{SiteID: "site_id"}, Flag: Blocked}) assert.NoError(t, err) assert.Equal(t, []interface{}{map[string]interface{}{"ID": "id1"}, map[string]interface{}{"ID": "id2"}}, res) @@ -142,7 +142,7 @@ func TestRemote_ListFlag(t *testing.T) { func TestRemote_Count(t *testing.T) { ts := testServer(t, `{"method":"store.count","params":{"locator":{"url":"http://example.com/url"},"since":"0001-01-01T00:00:00Z"},"id":1}`, `{"result":11}`) defer ts.Close() - c := RPC{Client: rpc.Client{API: ts.URL, Client: http.Client{}}} + c := RPC{Client: jrpc.Client{API: ts.URL, Client: http.Client{}}} res, err := c.Count(FindRequest{Locator: store.Locator{URL: "http://example.com/url"}}) assert.NoError(t, err) @@ -153,7 +153,7 @@ func TestRemote_Delete(t *testing.T) { ts := testServer(t, `{"method":"store.delete","params":{"locator":{"url":"http://example.com/url"},"del_mode":0},"id":1}`, `{}`) defer ts.Close() - c := RPC{Client: rpc.Client{API: ts.URL, Client: http.Client{}}} + c := RPC{Client: jrpc.Client{API: ts.URL, Client: http.Client{}}} err := c.Delete(DeleteRequest{Locator: store.Locator{URL: "http://example.com/url"}}) assert.NoError(t, err) @@ -162,7 +162,7 @@ func TestRemote_Delete(t *testing.T) { func TestRemote_Close(t *testing.T) { ts := testServer(t, `{"method":"store.close","id":1}`, `{}`) defer ts.Close() - c := RPC{Client: rpc.Client{API: ts.URL, Client: http.Client{}}} + c := RPC{Client: jrpc.Client{API: ts.URL, Client: http.Client{}}} err := c.Close() assert.NoError(t, err) } diff --git a/backend/go.mod b/backend/go.mod index f8fdac22..4b78ea1f 100644 --- a/backend/go.mod +++ b/backend/go.mod @@ -9,12 +9,13 @@ require ( github.com/PuerkitoBio/goquery v1.5.0 github.com/coreos/bbolt v1.3.3 github.com/dgrijalva/jwt-go v3.2.0+incompatible - github.com/didip/tollbooth v4.0.0+incompatible + github.com/didip/tollbooth v4.0.2+incompatible github.com/didip/tollbooth_chi v0.0.0-20170928041846-6ab5f3083f3d github.com/go-chi/chi v4.0.2+incompatible github.com/go-chi/cors v1.0.0 github.com/go-chi/render v1.0.1 github.com/go-pkgz/auth v0.7.2 + github.com/go-pkgz/jrpc v0.1.0 github.com/go-pkgz/lcw v0.3.1 github.com/go-pkgz/lgr v0.6.3 github.com/go-pkgz/repeater v1.1.2 @@ -35,7 +36,7 @@ require ( github.com/stretchr/testify v1.3.0 golang.org/x/crypto v0.0.0-20190701094942-4def268fd1a4 golang.org/x/image v0.0.0-20190703141733-d6a02ce849c9 - golang.org/x/net v0.0.0-20190628185345-da137c7871d7 + golang.org/x/net v0.0.0-20190724013045-ca1201d0de80 golang.org/x/sys v0.0.0-20190626221950-04f50cda93cb // indirect gopkg.in/russross/blackfriday.v2 v2.0.1 ) diff --git a/backend/go.sum b/backend/go.sum index 56a7f941..2936f18e 100644 --- a/backend/go.sum +++ b/backend/go.sum @@ -21,6 +21,8 @@ github.com/dgrijalva/jwt-go v3.2.0+incompatible h1:7qlOGliEKZXTDg6OTjfoBKDXWrumC github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ= github.com/didip/tollbooth v4.0.0+incompatible h1:ayQZYuF5QOxx3NdYRNuRVFLv9/2b64JtSUlewb+0TMo= github.com/didip/tollbooth v4.0.0+incompatible/go.mod h1:A9b0665CE6l1KmzpDws2++elm/CsuWBMa5Jv4WY0PEY= +github.com/didip/tollbooth v4.0.2+incompatible h1:fVSa33JzSz0hoh2NxpwZtksAzAgd7zjmGO20HCZtF4M= +github.com/didip/tollbooth v4.0.2+incompatible/go.mod h1:A9b0665CE6l1KmzpDws2++elm/CsuWBMa5Jv4WY0PEY= github.com/didip/tollbooth_chi v0.0.0-20170928041846-6ab5f3083f3d h1:vs5Nf6IE0N/PwGJ8//zRed4gpCdcr99K2HzX7RuLOQ8= github.com/didip/tollbooth_chi v0.0.0-20170928041846-6ab5f3083f3d/go.mod h1:YWyIfq3y4ArRfWZ9XksmuusP+7Mad+T0iFZ0kv0XG/M= github.com/globalsign/mgo v0.0.0-20180615134936-113d3961e731/go.mod h1:xkRDCp4j0OGD1HRkm4kmhM+pmpv3AKq5SU7GMg4oO/Q= @@ -34,6 +36,8 @@ github.com/go-chi/render v1.0.1 h1:4/5tis2cKaNdnv9zFLfXzcquC9HbeZgCnxGnKrltBS8= github.com/go-chi/render v1.0.1/go.mod h1:pq4Rr7HbnsdaeHagklXub+p6Wd16Af5l9koip1OvJns= github.com/go-pkgz/auth v0.7.2 h1:+LvAgqwQtYuWphpZE8qLtspVd65+VgreJkFLsKrtmmk= github.com/go-pkgz/auth v0.7.2/go.mod h1:ibOpZYISiaOvAHe2bsKj2s3v4AkMam2WxxIFn+zhulo= +github.com/go-pkgz/jrpc v0.1.0 h1:hNg/IyfEqJcSWOKkuHw0ZwcuGc9TDp7QZREsD2ycmiM= +github.com/go-pkgz/jrpc v0.1.0/go.mod h1:JxZsvoBklA50DNhELVJnJ567Rt+KrMH9rR3u515wvE8= github.com/go-pkgz/lcw v0.3.1 h1:PhfB0xNUawLMlx5rXvOTIc7d5LMrr1GM9vIzmG96aUI= github.com/go-pkgz/lcw v0.3.1/go.mod h1:k+PY1CkCMTLXILtFoJOyK65Qqi9rkoTYunFH1vE/C0I= github.com/go-pkgz/lgr v0.2.2/go.mod h1:hBM1NM/SoYdlrykgdgJWGrZ/TM/XaZIjRbJfx7NkMm8= @@ -115,6 +119,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.4.0 h1:rJf4ndpvRDS7tOrtIpVkacItTxF5pVpXvDqtb1dX40M= go.etcd.io/bbolt v1.3.3 h1:MUGmc65QhB3pIlaQ5bB4LwqSj6GIonVJXpZiaKNyaKk= go.etcd.io/bbolt v1.3.3/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU= go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU= @@ -153,6 +158,8 @@ golang.org/x/net v0.0.0-20190611141213-3f473d35a33a/go.mod h1:z5CRVTTTmAJ677TzLL golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190628185345-da137c7871d7 h1:rTIdg5QFRR7XCaK4LCjBiPbx8j4DQRpdYMnGn/bJUEU= golang.org/x/net v0.0.0-20190628185345-da137c7871d7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20190724013045-ca1201d0de80 h1:Ao/3l156eZf2AW5wK8a7/smtodRU+gha3+BeqJ69lRk= +golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45 h1:SVwTIAaPC2U/AvvLNZ2a7OVsmBpC8L5BlwK1whH3hm0= diff --git a/backend/vendor/github.com/didip/tollbooth/README.md b/backend/vendor/github.com/didip/tollbooth/README.md index d1971b84..f6145879 100644 --- a/backend/vendor/github.com/didip/tollbooth/README.md +++ b/backend/vendor/github.com/didip/tollbooth/README.md @@ -7,9 +7,7 @@ This is a generic middleware to rate-limit HTTP requests. **NOTE 1:** This library is considered finished. -**NOTE 2:** In the coming weeks, I will be removing thirdparty modules and moving them to their own dedicated repos. - -**NOTE 3:** Major version changes are backward-incompatible. `v2.0.0` streamlines the ugliness of the old API. +**NOTE 2:** Major version changes are backward-incompatible. `v2.0.0` streamlines the ugliness of the old API. ## Versions @@ -21,7 +19,7 @@ This is a generic middleware to rate-limit HTTP requests. **v3.x.x:** Apparently we have been using golang.org/x/time/rate incorrectly. See issue #48. It always limit X number per 1 second. The time duration is not changeable, so it does not make sense to pass TTL to tollbooth. -## Five Minutes Tutorial +## Five Minute Tutorial ```go package main @@ -161,4 +159,6 @@ Sometimes, other frameworks require a little bit of shim to use Tollbooth. These * [Stopwatch](https://github.com/didip/stopwatch): A small library to measure latency of things. Useful if you want to report latency data to Graphite. +* [LaborUnion](https://github.com/didip/laborunion): A dynamic worker pool library. + * [Gomet](https://github.com/didip/gomet): Simple HTTP client & server long poll library for Go. Useful for receiving live updates without needing Websocket. diff --git a/backend/vendor/github.com/didip/tollbooth/tollbooth.go b/backend/vendor/github.com/didip/tollbooth/tollbooth.go index c023db26..b4f7b3e8 100644 --- a/backend/vendor/github.com/didip/tollbooth/tollbooth.go +++ b/backend/vendor/github.com/didip/tollbooth/tollbooth.go @@ -6,10 +6,11 @@ import ( "strings" "fmt" + "math" + "github.com/didip/tollbooth/errors" "github.com/didip/tollbooth/libstring" "github.com/didip/tollbooth/limiter" - "math" ) // setResponseHeaders configures X-Rate-Limit-Limit and X-Rate-Limit-Duration @@ -58,18 +59,21 @@ func BuildKeys(lmt *limiter.Limiter, r *http.Request) [][]string { if libstring.StringInSlice(lmtMethods, r.Method) { for headerKey, headerValues := range lmtHeaders { if (headerValues == nil || len(headerValues) <= 0) && r.Header.Get(headerKey) != "" { - // If header values are empty, rate-limit all request with headerKey. + // If header values are empty, rate-limit all request containing headerKey. username, _, ok := r.BasicAuth() if ok && libstring.StringInSlice(lmtBasicAuthUsers, username) { - sliceKeys = append(sliceKeys, []string{remoteIP, path, r.Method, headerKey, username}) + sliceKeys = append(sliceKeys, []string{remoteIP, path, r.Method, headerKey, r.Header.Get(headerKey), username}) } } else if len(headerValues) > 0 && r.Header.Get(headerKey) != "" { // If header values are not empty, rate-limit all request with headerKey and headerValues. for _, headerValue := range headerValues { - username, _, ok := r.BasicAuth() - if ok && libstring.StringInSlice(lmtBasicAuthUsers, username) { - sliceKeys = append(sliceKeys, []string{remoteIP, path, r.Method, headerKey, headerValue, username}) + if r.Header.Get(headerKey) == headerValue { + username, _, ok := r.BasicAuth() + if ok && libstring.StringInSlice(lmtBasicAuthUsers, username) { + sliceKeys = append(sliceKeys, []string{remoteIP, path, r.Method, headerKey, headerValue, username}) + } + break } } } @@ -82,12 +86,15 @@ func BuildKeys(lmt *limiter.Limiter, r *http.Request) [][]string { for headerKey, headerValues := range lmtHeaders { if (headerValues == nil || len(headerValues) <= 0) && r.Header.Get(headerKey) != "" { // If header values are empty, rate-limit all request with headerKey. - sliceKeys = append(sliceKeys, []string{remoteIP, path, r.Method, headerKey}) + sliceKeys = append(sliceKeys, []string{remoteIP, path, r.Method, headerKey, r.Header.Get(headerKey)}) } else if len(headerValues) > 0 && r.Header.Get(headerKey) != "" { - // If header values are not empty, rate-limit all request with headerKey and headerValues. + // We are only limiting if request's header value is defined inside `headerValues`. for _, headerValue := range headerValues { - sliceKeys = append(sliceKeys, []string{remoteIP, path, r.Method, headerKey, headerValue}) + if r.Header.Get(headerKey) == headerValue { + sliceKeys = append(sliceKeys, []string{remoteIP, path, r.Method, headerKey, headerValue}) + break + } } } } @@ -113,12 +120,15 @@ func BuildKeys(lmt *limiter.Limiter, r *http.Request) [][]string { for headerKey, headerValues := range lmtHeaders { if (headerValues == nil || len(headerValues) <= 0) && r.Header.Get(headerKey) != "" { // If header values are empty, rate-limit all request with headerKey. - sliceKeys = append(sliceKeys, []string{remoteIP, path, headerKey}) + sliceKeys = append(sliceKeys, []string{remoteIP, path, headerKey, r.Header.Get(headerKey)}) } else if len(headerValues) > 0 && r.Header.Get(headerKey) != "" { // If header values are not empty, rate-limit all request with headerKey and headerValues. for _, headerValue := range headerValues { - sliceKeys = append(sliceKeys, []string{remoteIP, path, headerKey, headerValue}) + if r.Header.Get(headerKey) == headerValue { + sliceKeys = append(sliceKeys, []string{remoteIP, path, headerKey, headerValue}) + break + } } } } diff --git a/backend/vendor/github.com/go-pkgz/jrpc/.gitignore b/backend/vendor/github.com/go-pkgz/jrpc/.gitignore new file mode 100644 index 00000000..f1c181ec --- /dev/null +++ b/backend/vendor/github.com/go-pkgz/jrpc/.gitignore @@ -0,0 +1,12 @@ +# Binaries for programs and plugins +*.exe +*.exe~ +*.dll +*.so +*.dylib + +# Test binary, build with `go test -c` +*.test + +# Output of the go coverage tool, specifically when used with LiteIDE +*.out diff --git a/backend/vendor/github.com/go-pkgz/jrpc/.golangci.yml b/backend/vendor/github.com/go-pkgz/jrpc/.golangci.yml new file mode 100644 index 00000000..bb83e9ae --- /dev/null +++ b/backend/vendor/github.com/go-pkgz/jrpc/.golangci.yml @@ -0,0 +1,70 @@ +linters-settings: + govet: + check-shadowing: true + golint: + min-confidence: 0 + gocyclo: + min-complexity: 15 + maligned: + suggest-new: true + dupl: + threshold: 100 + goconst: + min-len: 2 + min-occurrences: 2 + misspell: + locale: US + lll: + line-length: 140 + gocritic: + enabled-tags: + - performance + - style + - experimental + disabled-checks: + - wrapperFunc + +linters: + disable-all: true + enable: + - megacheck + - golint + - govet + - unconvert + - megacheck + - structcheck + - gas + - gocyclo + - dupl + - misspell + - unparam + - varcheck + - deadcode + - typecheck + - ineffassign + - varcheck + - stylecheck + - gochecknoinits + - scopelint + - gocritic + - golint + - nakedret + - gosimple + - prealloc + fast: false + + +run: +# modules-download-mode: vendor + skip-dirs: + - vendor + tests: true + +issues: + exclude-rules: + - text: "weak cryptographic primitive" + linters: + - gosec + +service: + golangci-lint-version: 1.17.x \ No newline at end of file diff --git a/backend/vendor/github.com/go-pkgz/jrpc/.travis.yml b/backend/vendor/github.com/go-pkgz/jrpc/.travis.yml new file mode 100644 index 00000000..0f0c5bdc --- /dev/null +++ b/backend/vendor/github.com/go-pkgz/jrpc/.travis.yml @@ -0,0 +1,21 @@ +language: go + +go: + - "1.12.x" + +install: true + +before_install: + - export TZ=America/Chicago + - curl -sfL https://install.goreleaser.com/github.com/golangci/golangci-lint.sh | sh -s -- -b $(go env GOPATH)/bin v1.17.1 + - go get github.com/mattn/goveralls + - export PATH=$(pwd)/bin:$PATH + +script: + - GO111MODULE=on go get ./... + - GO111MODULE=on go mod vendor + - GO111MODULE=on go test -v -mod=vendor -covermode=count -coverprofile=profile.cov ./... || travis_terminate 1; + - GO111MODULE=on go test -v -covermode=count -coverprofile=profile.cov ./... || travis_terminate 1; + - golangci-lint run -v || travis_terminate 1; + - $GOPATH/bin/goveralls -coverprofile=profile.cov -service=travis-ci + diff --git a/backend/vendor/github.com/go-pkgz/jrpc/LICENSE b/backend/vendor/github.com/go-pkgz/jrpc/LICENSE new file mode 100644 index 00000000..ac540250 --- /dev/null +++ b/backend/vendor/github.com/go-pkgz/jrpc/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2019 Umputun + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/backend/vendor/github.com/go-pkgz/jrpc/README.md b/backend/vendor/github.com/go-pkgz/jrpc/README.md new file mode 100644 index 00000000..a3e36c9b --- /dev/null +++ b/backend/vendor/github.com/go-pkgz/jrpc/README.md @@ -0,0 +1,99 @@ +# jrpc - rpc with json [![Build Status](https://travis-ci.org/go-pkgz/jrpc.svg?branch=master)](https://travis-ci.org/go-pkgz/jrpc) [![Go Report Card](https://goreportcard.com/badge/github.com/go-pkgz/jrpc)](https://goreportcard.com/report/github.com/go-pkgz/jrpc) [![Coverage Status](https://coveralls.io/repos/github/go-pkgz/jrpc/badge.svg?branch=master)](https://coveralls.io/github/go-pkgz/jrpc?branch=master) [![godoc](https://godoc.org/github.com/go-pkgz/jrpc?status.svg)](https://godoc.org/github.com/go-pkgz/jrpc) + +jrpc library provides client and server for RPC-like communication over HTTP with json encoded messages. +The protocol is a somewhat simplified version of json-rpc with a single POST call sending Request json +(method name and the list of parameters) moreover, receiving json Response with result data and an error string. + +## Usage + +### Plugin (server) + +```go +// Server wraps jrpc.Server and adds synced map to store data +type Puglin struct { + *jrpc.Server +} + +// create plugin (jrpc server) +plugin := jrpcServer{ + Server: &jrpc.Server{ + API: "/command", // base url for rpc calls + AuthUser: "user", // basic auth user name + AuthPasswd: "password", // basic auth password + AppName: "jrpc-example", // plugin name for headers + Logger: logger, + }, +} + +plugin.Add("mycommand", func(id uint64, params json.RawMessage) Response { + return jrpc.EncodeResponse(id, "hello, it works", nil) +}) +``` + +### Application (client) + +```go +// Client makes jrpc.Client and invoke remote call +rpcClient := jrpc.Client{ + API: "http://127.0.0.1:8080/command", + Client: http.Client{}, + AuthUser: "user", + AuthPasswd: "password", +} + +resp, err := rpcClient.Call("mycommand") +var message string +if err = json.Unmarshal(*resp.Result, &message); err != nil { + panic(err) +} +``` + +*for functional examples for both plugin and application see [_example](https://github.com/go-pkgz/jrpc/tree/master/_example)* + +## Technical details + + * `jrpc.Server` runs on user-defined port as a regular http server + * Server accepts a single POST request on user-defined url with [Request](https://github.com/go-pkgz/jrpc/blob/master/jrpc.go#L12) sent as json payload +
request details and an example: + + ```go + type Request struct { + Method string `json:"method"` + Params interface{} `json:"params,omitempty"` + ID uint64 `json:"id"` + } + ``` + example: + + ```json + { + "method":"test", + "params":[123,"abc"], + "id":1 + } + ``` +
+* Params can be a struct, primitive type or slice of values, even with different types. +* Server defines `ServerFn` handler function to react on a POST request. The handler provided by the user. +* Communication between the server and the caller can be protected with basic auth. +* [Client](https://github.com/go-pkgz/jrpc/blob/master/client.go) provides a single method `Call` and return `Response` + +
response details: + + ```go + // Response encloses result and error received from remote server + type Response struct { + Result *json.RawMessage `json:"result,omitempty"` + Error string `json:"error,omitempty"` + ID uint64 `json:"id"` + } + ``` +
+* User should encode and decode json payloads on the application level, see provided [examples](https://github.com/go-pkgz/jrpc/tree/master/_example) +* `jrpc.Server` doesn't support https internally (yet). If used on exposed or non-private networks, should be proxied with something providing https termination (nginx and others). + +## Status + +The code was extracted from [remark42](https://github.com/umputun/remark) and still under development. Until v1.x released the + API & protocol may change. + \ No newline at end of file diff --git a/backend/app/rpc/client.go b/backend/vendor/github.com/go-pkgz/jrpc/client.go similarity index 65% rename from backend/app/rpc/client.go rename to backend/vendor/github.com/go-pkgz/jrpc/client.go index 5cef2a16..b9a40573 100644 --- a/backend/app/rpc/client.go +++ b/backend/vendor/github.com/go-pkgz/jrpc/client.go @@ -1,26 +1,29 @@ -package rpc +package jrpc import ( "bytes" "encoding/json" "net/http" - "reflect" "sync/atomic" "github.com/pkg/errors" ) // Client implements remote engine and delegates all calls to remote http server +// if AuthUser and AuthPasswd defined will be used for basic auth in each call to server type Client struct { - API string - Client http.Client - AuthUser string - AuthPasswd string + API string // URL to jrpc server with entrypoint, i.e. http://127.0.0.1:8080/command + Client http.Client // http client injected by user + AuthUser string // basic auth user name, should match Server.AuthUser, optional + AuthPasswd string // basic auth password, should match Server.AuthPasswd, optional - id uint64 + id uint64 // used with atomic to populate unique id to Request.ID } -// Call remote server with given method and arguments +// Call remote server with given method and arguments. +// Empty args will be ignored, single arg will be marshaled as-us and multiple args marshaled as []interface{}. +// Returns Response and error. Note: Response has it's own Error field, but that onw controlled by server. +// Returned error represent client-level errors, like failed http call, failed marshaling and so on. func (r *Client) Call(method string, args ...interface{}) (*Response, error) { var b []byte @@ -32,7 +35,7 @@ func (r *Client) Call(method string, args ...interface{}) (*Response, error) { if err != nil { return nil, errors.Wrapf(err, "marshaling failed for %s", method) } - case len(args) == 1 && reflect.TypeOf(args[0]).Kind() == reflect.Struct: + case len(args) == 1: b, err = json.Marshal(Request{Method: method, Params: args[0], ID: atomic.AddUint64(&r.id, 1)}) if err != nil { return nil, errors.Wrapf(err, "marshaling failed for %s", method) diff --git a/backend/vendor/github.com/go-pkgz/jrpc/go.mod b/backend/vendor/github.com/go-pkgz/jrpc/go.mod new file mode 100644 index 00000000..1e3fdb2a --- /dev/null +++ b/backend/vendor/github.com/go-pkgz/jrpc/go.mod @@ -0,0 +1,16 @@ +module github.com/go-pkgz/jrpc + +go 1.12 + +require ( + github.com/didip/tollbooth v4.0.2+incompatible + github.com/didip/tollbooth_chi v0.0.0-20170928041846-6ab5f3083f3d + github.com/go-chi/chi v4.0.2+incompatible + github.com/go-chi/render v1.0.1 + github.com/go-pkgz/rest v1.4.1 + github.com/patrickmn/go-cache v2.1.0+incompatible // indirect + github.com/pkg/errors v0.8.1 + github.com/stretchr/testify v1.3.0 + golang.org/x/net v0.0.0-20190724013045-ca1201d0de80 // indirect + golang.org/x/time v0.0.0-20190308202827-9d24e82272b4 // indirect +) diff --git a/backend/vendor/github.com/go-pkgz/jrpc/go.sum b/backend/vendor/github.com/go-pkgz/jrpc/go.sum new file mode 100644 index 00000000..9a20171c --- /dev/null +++ b/backend/vendor/github.com/go-pkgz/jrpc/go.sum @@ -0,0 +1,32 @@ +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/didip/tollbooth v4.0.2+incompatible h1:fVSa33JzSz0hoh2NxpwZtksAzAgd7zjmGO20HCZtF4M= +github.com/didip/tollbooth v4.0.2+incompatible/go.mod h1:A9b0665CE6l1KmzpDws2++elm/CsuWBMa5Jv4WY0PEY= +github.com/didip/tollbooth_chi v0.0.0-20170928041846-6ab5f3083f3d h1:vs5Nf6IE0N/PwGJ8//zRed4gpCdcr99K2HzX7RuLOQ8= +github.com/didip/tollbooth_chi v0.0.0-20170928041846-6ab5f3083f3d/go.mod h1:YWyIfq3y4ArRfWZ9XksmuusP+7Mad+T0iFZ0kv0XG/M= +github.com/go-chi/chi v4.0.2+incompatible h1:maB6vn6FqCxrpz4FqWdh4+lwpyZIQS7YEAUcHlgXVRs= +github.com/go-chi/chi v4.0.2+incompatible/go.mod h1:eB3wogJHnLi3x/kFX2A+IbTBlXxmMeXJVKy9tTv1XzQ= +github.com/go-chi/render v1.0.1 h1:4/5tis2cKaNdnv9zFLfXzcquC9HbeZgCnxGnKrltBS8= +github.com/go-chi/render v1.0.1/go.mod h1:pq4Rr7HbnsdaeHagklXub+p6Wd16Af5l9koip1OvJns= +github.com/go-pkgz/rest v1.4.1 h1:DmaVLPH2O7yLehrWOW0uz01d2mVHz9fBR/iuTiPRzaw= +github.com/go-pkgz/rest v1.4.1/go.mod h1:COazNj35u3RXAgQNBr6neR599tYP3URiOpsu9p0rOtk= +github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= +github.com/patrickmn/go-cache v2.1.0+incompatible h1:HRMgzkcYKYpi3C8ajMPV8OFXaaRUnok+kx1WdO15EQc= +github.com/patrickmn/go-cache v2.1.0+incompatible/go.mod h1:3Qf8kWWT7OJRJbdiICTKqZju1ZixQ/KpMGzzAfe6+WQ= +github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I= +github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v1.3.0 h1:TivCn/peBQ7UY8ooIcPgZFpTNSz0Q2U6UrFlUfqbe0Q= +github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/net v0.0.0-20190724013045-ca1201d0de80 h1:Ao/3l156eZf2AW5wK8a7/smtodRU+gha3+BeqJ69lRk= +golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/time v0.0.0-20190308202827-9d24e82272b4 h1:SvFZT6jyqRaOeXpc5h/JSfZenJ2O330aBsf7JfSUXmQ= +golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= diff --git a/backend/vendor/github.com/go-pkgz/jrpc/jrpc.go b/backend/vendor/github.com/go-pkgz/jrpc/jrpc.go new file mode 100644 index 00000000..86063690 --- /dev/null +++ b/backend/vendor/github.com/go-pkgz/jrpc/jrpc.go @@ -0,0 +1,40 @@ +// Package jrpc implements client and server for RPC-like communication over HTTP with json encoded messages. +// The protocol is somewhat simplified version of json-rpc with a single POST call sending Request json +// (method name and the list of parameters) and receiving back json Response with "result" json +// and error string +package jrpc + +import ( + "encoding/json" +) + +// Request encloses method name and all params +type Request struct { + Method string `json:"method"` // method (function) name + Params interface{} `json:"params,omitempty"` // function arguments + ID uint64 `json:"id"` // unique call id +} + +// Response encloses result and error received from remote server +type Response struct { + Result *json.RawMessage `json:"result,omitempty"` // response json + Error string `json:"error,omitempty"` // optional remote (server side / plugin side) error + ID uint64 `json:"id"` // unique call id, echoed Request.ID to allow calls tracing +} + +// EncodeResponse convert anything (type interface{}) and incoming error (if any) to Response +func EncodeResponse(id uint64, resp interface{}, e error) Response { + v, err := json.Marshal(&resp) + if err != nil { + return Response{Error: err.Error()} + } + if e != nil { + return Response{ID: id, Result: nil, Error: e.Error()} // pass input error + } + raw := json.RawMessage{} + if err := raw.UnmarshalJSON(v); err != nil { + return Response{Error: err.Error()} + } + + return Response{ID: id, Result: &raw} +} diff --git a/backend/app/rpc/server.go b/backend/vendor/github.com/go-pkgz/jrpc/server.go similarity index 58% rename from backend/app/rpc/server.go rename to backend/vendor/github.com/go-pkgz/jrpc/server.go index 55d95866..a52e1434 100644 --- a/backend/app/rpc/server.go +++ b/backend/vendor/github.com/go-pkgz/jrpc/server.go @@ -1,4 +1,4 @@ -package rpc +package jrpc import ( "context" @@ -13,21 +13,19 @@ import ( "github.com/go-chi/chi" "github.com/go-chi/chi/middleware" "github.com/go-chi/render" - log "github.com/go-pkgz/lgr" - R "github.com/go-pkgz/rest" + "github.com/go-pkgz/rest" "github.com/go-pkgz/rest/logger" "github.com/pkg/errors" - - "github.com/umputun/remark/backend/app/rest" ) // Server is json-rpc server with an optional basic auth type Server struct { - API string - AuthUser string - AuthPasswd string - Version string - AppName string + API string // url path, i.e. "/command" or "/rpc" etc. + AuthUser string // basic auth user name, should match Client.AuthUser, optional + AuthPasswd string // basic auth password, should match Client.AuthPasswd, optional + Version string // server version, injected from main and used for informational headers only + AppName string // plugin name, injected from main and used for informational headers only + Logger L // logger, if nil will default to NoOpLogger funcs struct { m map[string]ServerFn @@ -40,26 +38,26 @@ type Server struct { } } -// Encoder is a function to encode call's result to Response -type Encoder func(id uint64, resp interface{}, e error) (Response, error) - -// ServerFn handler registered for each method with Add -// Implementations provided by consumer and define response logic. +// ServerFn handler registered for each method with Add or Group. +// Implementations provided by consumer and defines response logic. type ServerFn func(id uint64, params json.RawMessage) Response // Run http server on given port func (s *Server) Run(port int) error { + if s.Logger == nil { + s.Logger = NoOpLogger + } if s.AuthUser == "" || s.AuthPasswd == "" { - log.Print("[WARN] extension server runs without auth") + s.Logger.Logf("[WARN] extension server runs without auth") } if s.funcs.m == nil && len(s.funcs.m) == 0 { return errors.Errorf("nothing mapped for dispatch, Add has to be called prior to Run") } router := chi.NewRouter() - router.Use(middleware.Throttle(1000), middleware.RealIP, R.Recoverer(log.Default())) - router.Use(R.AppInfo(s.AppName, "umputun", s.Version), R.Ping) - logInfoWithBody := logger.New(logger.Log(log.Default()), logger.WithBody, logger.Prefix("[INFO]")).Handler + router.Use(middleware.Throttle(1000), middleware.RealIP, rest.Recoverer(s.Logger)) + router.Use(rest.AppInfo(s.AppName, "umputun", s.Version), rest.Ping) + logInfoWithBody := logger.New(logger.Log(s.Logger), logger.WithBody, logger.Prefix("[INFO]")).Handler router.Use(middleware.Timeout(5 * time.Second)) router.Use(logInfoWithBody, tollbooth_chi.LimitHandler(tollbooth.NewLimiter(1000, nil)), middleware.NoCache) router.Use(s.basicAuth) @@ -76,26 +74,10 @@ func (s *Server) Run(port int) error { } s.httpServer.Unlock() - log.Printf("[INFO] listen on %d", port) + s.Logger.Logf("[INFO] listen on %d", port) return s.httpServer.ListenAndServe() } -// EncodeResponse convert anything to Response -func (s *Server) EncodeResponse(id uint64, resp interface{}, e error) (Response, error) { - v, err := json.Marshal(&resp) - if err != nil { - return Response{}, err - } - if e != nil { - return Response{ID: id, Result: nil, Error: e.Error()}, nil - } - raw := json.RawMessage{} - if err = raw.UnmarshalJSON(v); err != nil { - return Response{}, err - } - return Response{ID: id, Result: &raw}, nil -} - // Shutdown http server func (s *Server) Shutdown() error { s.httpServer.Lock() @@ -108,12 +90,12 @@ func (s *Server) Shutdown() error { return s.httpServer.Shutdown(ctx) } -// Add method handler +// Add method handler. Handler will be called on matching method (Request.Method) func (s *Server) Add(method string, fn ServerFn) { s.httpServer.Lock() defer s.httpServer.Unlock() if s.httpServer.Server != nil { - log.Printf("[WARN] ignored method %s, can't be added to activated server", method) + s.Logger.Logf("[WARN] ignored method %s, can't be added to activated server", method) return } @@ -122,19 +104,20 @@ func (s *Server) Add(method string, fn ServerFn) { }) s.funcs.m[method] = fn - log.Printf("[INFO] add handler for %s", method) + s.Logger.Logf("[INFO] add handler for %s", method) } // HandlersGroup alias for map of handlers type HandlersGroup map[string]ServerFn -// Group of handlers with common prefix +// Group of handlers with common prefix, match on group.method func (s *Server) Group(prefix string, m HandlersGroup) { for k, v := range m { s.Add(prefix+"."+k, v) } } +// handler is http handler multiplexing calls by req.Method func (s *Server) handler(w http.ResponseWriter, r *http.Request) { req := struct { ID uint64 `json:"id"` @@ -143,12 +126,12 @@ func (s *Server) handler(w http.ResponseWriter, r *http.Request) { }{} if err := json.NewDecoder(r.Body).Decode(&req); err != nil { - rest.SendErrorJSON(w, r, http.StatusBadRequest, err, req.Method, 0) + rest.SendErrorJSON(w, r, s.Logger, http.StatusBadRequest, err, req.Method) return } fn, ok := s.funcs.m[req.Method] if !ok { - rest.SendErrorJSON(w, r, http.StatusNotImplemented, errors.New("unsupported method"), req.Method, 0) + rest.SendErrorJSON(w, r, s.Logger, http.StatusNotImplemented, errors.New("unsupported method"), req.Method) return } @@ -160,6 +143,7 @@ func (s *Server) handler(w http.ResponseWriter, r *http.Request) { render.JSON(w, r, fn(req.ID, params)) } +// basicAuth middleware. enabled only if both AuthUser and AuthPasswd defined. func (s *Server) basicAuth(h http.Handler) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { @@ -177,3 +161,17 @@ func (s *Server) basicAuth(h http.Handler) http.Handler { h.ServeHTTP(w, r) }) } + +// L defined logger interface used for an optional rest logging +type L interface { + Logf(format string, args ...interface{}) +} + +// LoggerFunc type is an adapter to allow the use of ordinary functions as Logger. +type LoggerFunc func(format string, args ...interface{}) + +// Logf calls f(id) +func (f LoggerFunc) Logf(format string, args ...interface{}) { f(format, args...) } + +// NoOpLogger logger does nothing +var NoOpLogger = LoggerFunc(func(format string, args ...interface{}) {}) diff --git a/backend/vendor/modules.txt b/backend/vendor/modules.txt index 582f708d..f501d4fb 100644 --- a/backend/vendor/modules.txt +++ b/backend/vendor/modules.txt @@ -10,7 +10,7 @@ github.com/coreos/bbolt github.com/davecgh/go-spew/spew # github.com/dgrijalva/jwt-go v3.2.0+incompatible github.com/dgrijalva/jwt-go -# github.com/didip/tollbooth v4.0.0+incompatible +# github.com/didip/tollbooth v4.0.2+incompatible github.com/didip/tollbooth github.com/didip/tollbooth/errors github.com/didip/tollbooth/libstring @@ -38,6 +38,8 @@ github.com/go-pkgz/auth/provider/sender github.com/go-pkgz/auth/token github.com/go-pkgz/auth/logger github.com/go-pkgz/auth/middleware +# github.com/go-pkgz/jrpc v0.1.0 +github.com/go-pkgz/jrpc # github.com/go-pkgz/lcw v0.3.1 github.com/go-pkgz/lcw # github.com/go-pkgz/lgr v0.6.3 @@ -98,7 +100,7 @@ golang.org/x/crypto/acme # golang.org/x/image v0.0.0-20190703141733-d6a02ce849c9 golang.org/x/image/draw golang.org/x/image/math/f64 -# golang.org/x/net v0.0.0-20190628185345-da137c7871d7 +# golang.org/x/net v0.0.0-20190724013045-ca1201d0de80 golang.org/x/net/html golang.org/x/net/idna golang.org/x/net/html/atom From 9ab18dfe1af9d996ec819af64118c961a02ece7f Mon Sep 17 00:00:00 2001 From: Umputun Date: Wed, 31 Jul 2019 18:47:28 -0500 Subject: [PATCH 4/9] fix zero time check --- backend/_example/memory_store/accessor/data.go | 2 +- backend/_example/memory_store/main.go | 5 ++--- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/backend/_example/memory_store/accessor/data.go b/backend/_example/memory_store/accessor/data.go index b3f596c5..0773f09f 100644 --- a/backend/_example/memory_store/accessor/data.go +++ b/backend/_example/memory_store/accessor/data.go @@ -89,7 +89,7 @@ func (m *MemData) Find(req engine.FindRequest) (comments []store.Comment, err er case req.Locator.SiteID != "" && req.Locator.URL != "": // find comments for site and url comments = m.match(m.posts[req.Locator.SiteID], func(c store.Comment) bool { - return c.Locator == req.Locator && c.Timestamp.After(req.Since) + return c.Locator == req.Locator && (req.Since.IsZero() || c.Timestamp.After(req.Since)) }) case req.Locator.SiteID != "" && req.Locator.URL == "" && req.UserID == "": // find last comments for site diff --git a/backend/_example/memory_store/main.go b/backend/_example/memory_store/main.go index 250faa22..8c8e55ff 100644 --- a/backend/_example/memory_store/main.go +++ b/backend/_example/memory_store/main.go @@ -10,11 +10,10 @@ import ( "fmt" "os" + "github.com/go-pkgz/jrpc" log "github.com/go-pkgz/lgr" "github.com/jessevdk/go-flags" - "github.com/umputun/remark/backend/app/rpc" - "github.com/umputun/remark/backend/_example/memory_store/accessor" "github.com/umputun/remark/backend/_example/memory_store/server" ) @@ -43,7 +42,7 @@ func main() { dataStore := accessor.NewMemData() adminStore := accessor.NewMemAdminStore(opts.Secret) - rpcServer := rpc.Server{ + rpcServer := jrpc.Server{ API: opts.API, AuthUser: opts.AuthUser, AuthPasswd: opts.AuthPasswd, From 3d883ba4bba3be2eda3e610329213a5bf0fbd1a8 Mon Sep 17 00:00:00 2001 From: Umputun Date: Wed, 31 Jul 2019 22:49:50 -0500 Subject: [PATCH 5/9] clean rpc leftovers --- backend/_example/memory_store/README.md | 2 +- backend/app/cmd/server.go | 6 +++--- backend/app/store/admin/remote.go | 4 ++-- backend/app/store/admin/remote_test.go | 13 ++++++------- 4 files changed, 12 insertions(+), 13 deletions(-) diff --git a/backend/_example/memory_store/README.md b/backend/_example/memory_store/README.md index 51797e1f..8005cf9f 100644 --- a/backend/_example/memory_store/README.md +++ b/backend/_example/memory_store/README.md @@ -1,4 +1,4 @@ # sample store implementation -`memory_store` illustrates how to make a storage plugin for remark42. +`memory_store` illustrates how to make a custom storage plugin for remark42. diff --git a/backend/app/cmd/server.go b/backend/app/cmd/server.go index 5ff65abe..5d57e2cc 100644 --- a/backend/app/cmd/server.go +++ b/backend/app/cmd/server.go @@ -14,6 +14,7 @@ import ( "time" bolt "github.com/coreos/bbolt" + "github.com/go-pkgz/jrpc" log "github.com/go-pkgz/lgr" "github.com/kyokomi/emoji" authcache "github.com/patrickmn/go-cache" @@ -30,7 +31,6 @@ import ( "github.com/umputun/remark/backend/app/notify" "github.com/umputun/remark/backend/app/rest/api" "github.com/umputun/remark/backend/app/rest/proxy" - "github.com/umputun/remark/backend/app/rpc" "github.com/umputun/remark/backend/app/store" "github.com/umputun/remark/backend/app/store/admin" "github.com/umputun/remark/backend/app/store/engine" @@ -438,7 +438,7 @@ func (s *ServerCommand) makeDataStore() (result engine.Interface, err error) { } result, err = engine.NewBoltDB(bolt.Options{Timeout: s.Store.Bolt.Timeout}, sites...) case "rpc": - r := &engine.RPC{Client: rpc.Client{ + r := &engine.RPC{Client: jrpc.Client{ API: s.Store.RPC.API, Client: http.Client{Timeout: s.Store.RPC.TimeOut}, AuthUser: s.Store.RPC.AuthUser, @@ -503,7 +503,7 @@ func (s *ServerCommand) makeAdminStore() (admin.Store, error) { } return admin.NewStaticStore(s.SharedSecret, s.Admin.Shared.Admins, s.Admin.Shared.Email), nil case "rpc": - r := &admin.RPC{Client: rpc.Client{ + r := &admin.RPC{Client: jrpc.Client{ API: s.Admin.RPC.API, Client: http.Client{Timeout: s.Admin.RPC.TimeOut}, AuthUser: s.Admin.RPC.AuthUser, diff --git a/backend/app/store/admin/remote.go b/backend/app/store/admin/remote.go index c25b84ee..08a9fccd 100644 --- a/backend/app/store/admin/remote.go +++ b/backend/app/store/admin/remote.go @@ -9,12 +9,12 @@ package admin import ( "encoding/json" - "github.com/umputun/remark/backend/app/rpc" + "github.com/go-pkgz/jrpc" ) // RPC implements remote engine and delegates all Calls to remote http server type RPC struct { - rpc.Client + jrpc.Client } // Key returns the key, same for all sites diff --git a/backend/app/store/admin/remote_test.go b/backend/app/store/admin/remote_test.go index b385c591..ebf4bb38 100644 --- a/backend/app/store/admin/remote_test.go +++ b/backend/app/store/admin/remote_test.go @@ -13,17 +13,16 @@ import ( "net/http/httptest" "testing" + "github.com/go-pkgz/jrpc" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" - - "github.com/umputun/remark/backend/app/rpc" ) func TestRemote_Key(t *testing.T) { ts := testServer(t, `{"method":"admin.key","id":1}`, `{"result":"12345","id":1}`) defer ts.Close() - c := RPC{Client: rpc.Client{API: ts.URL, Client: http.Client{}}} + c := RPC{Client: jrpc.Client{API: ts.URL, Client: http.Client{}}} var a Store = &c _ = a @@ -35,10 +34,10 @@ func TestRemote_Key(t *testing.T) { } func TestRemote_Admins(t *testing.T) { - ts := testServer(t, `{"method":"admin.admins","params":["site-1"],"id":1}`, + ts := testServer(t, `{"method":"admin.admins","params":"site-1","id":1}`, `{"result":["id1","id2"],"id":1}`) defer ts.Close() - c := RPC{Client: rpc.Client{API: ts.URL, Client: http.Client{}}} + c := RPC{Client: jrpc.Client{API: ts.URL, Client: http.Client{}}} var a Store = &c _ = a @@ -50,10 +49,10 @@ func TestRemote_Admins(t *testing.T) { } func TestRemote_Email(t *testing.T) { - ts := testServer(t, `{"method":"admin.email","params":["site-1"],"id":1}`, + ts := testServer(t, `{"method":"admin.email","params":"site-1","id":1}`, `{"result":"bbb@example.com","id":1}`) defer ts.Close() - c := RPC{Client: rpc.Client{API: ts.URL, Client: http.Client{}}} + c := RPC{Client: jrpc.Client{API: ts.URL, Client: http.Client{}}} var a Store = &c _ = a From da671c005c2436adb8fbd214528bb02aa6e98537 Mon Sep 17 00:00:00 2001 From: Umputun Date: Wed, 31 Jul 2019 23:24:48 -0500 Subject: [PATCH 6/9] prep compose and Dockerfile --- backend/_example/memory_store/Dockerfile | 20 +++++ .../_example/memory_store/docker-compose.yml | 33 ++++++++ backend/_example/memory_store/go.mod | 33 ++++++-- backend/_example/memory_store/go.sum | 83 ++++++++++++++----- backend/_example/memory_store/main.go | 4 +- 5 files changed, 143 insertions(+), 30 deletions(-) create mode 100644 backend/_example/memory_store/Dockerfile create mode 100644 backend/_example/memory_store/docker-compose.yml diff --git a/backend/_example/memory_store/Dockerfile b/backend/_example/memory_store/Dockerfile new file mode 100644 index 00000000..ab68a329 --- /dev/null +++ b/backend/_example/memory_store/Dockerfile @@ -0,0 +1,20 @@ +FROM umputun/baseimage:buildgo-latest as build-backend + +ADD . /build/memory_store +WORKDIR /build/memory_store + + +RUN go build -o memory_store -ldflags "-X main.revision=0.0.0 -s -w" + + +FROM umputun/baseimage:app-latest + +WORKDIR /srv +COPY --from=build-backend /build/memory_store/memory_store /srv/memory_store +RUN chown -R app:app /srv + +EXPOSE 8080 +HEALTHCHECK --interval=30s --timeout=3s CMD curl --fail http://localhost:8080/ping || exit 1 +USER app + +CMD ["/srv/memory_store"] diff --git a/backend/_example/memory_store/docker-compose.yml b/backend/_example/memory_store/docker-compose.yml new file mode 100644 index 00000000..6a5576b6 --- /dev/null +++ b/backend/_example/memory_store/docker-compose.yml @@ -0,0 +1,33 @@ +version: '2' + +services: + remark: + image: umputun/remark42:dev + container_name: "remark42" + hostname: "remark42" + restart: always + + logging: + driver: json-file + options: + max-size: "10m" + max-file: "5" + + ports: + - "8080:8080" + + environment: + - REMARK_URL + - SECRET + - STORE_BOLT_PATH=/srv/var/db + - BACKUP_PATH=/srv/var/backup + - DEBUG=true + - EMOJI=true + - AUTH_ANON=true + - AUTH_DEV=true + - + volumes: + - ./var:/srv/var + + mem_storage: + build: . diff --git a/backend/_example/memory_store/go.mod b/backend/_example/memory_store/go.mod index e95d925c..a8c6269a 100644 --- a/backend/_example/memory_store/go.mod +++ b/backend/_example/memory_store/go.mod @@ -1,16 +1,37 @@ -module github.com/umputun/remark/backend/_example/memory_store +module github.com/umputun/remark/memory_store go 1.12 require ( + cloud.google.com/go v0.43.0 // indirect + github.com/PuerkitoBio/goquery v1.5.0 // indirect + github.com/go-pkgz/auth v0.7.2 // indirect github.com/go-pkgz/jrpc v0.1.0 + github.com/go-pkgz/lcw v0.3.1 // indirect github.com/go-pkgz/lgr v0.6.3 - github.com/jessevdk/go-flags v0.0.0-20180331124232-1c38ed7ad0cc + github.com/go-pkgz/repeater v1.1.3 // indirect + github.com/go-pkgz/syncs v1.1.1 // indirect + github.com/google/pprof v0.0.0-20190723021845-34ac40c74b70 // indirect + github.com/google/uuid v1.1.1 // indirect + github.com/gorilla/feeds v1.1.1 // indirect + github.com/hashicorp/go-multierror v1.0.0 // indirect + github.com/hashicorp/golang-lru v0.5.3 // indirect + github.com/jessevdk/go-flags v1.4.0 + github.com/kr/pty v1.1.8 // indirect + github.com/microcosm-cc/bluemonday v1.0.2 // indirect github.com/pkg/errors v0.8.1 - github.com/stretchr/testify v1.3.0 - github.com/umputun/remark/backend v1.4.0 + github.com/rakyll/statik v0.1.6 // indirect + github.com/stretchr/objx v0.2.0 // indirect + github.com/umputun/remark/backend v1.3.0 + golang.org/x/crypto v0.0.0-20190701094942-4def268fd1a4 // indirect + golang.org/x/exp v0.0.0-20190731235908-ec7cb31e5a56 // indirect + golang.org/x/image v0.0.0-20190729225735-1bd0cf576493 // indirect + golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028 // indirect + golang.org/x/sys v0.0.0-20190801041406-cbf593c0f2f3 // indirect + golang.org/x/tools v0.0.0-20190731214159-1e85ed8060aa // indirect + google.golang.org/grpc v1.22.1 // indirect + gopkg.in/russross/blackfriday.v2 v2.0.1 // indirect + honnef.co/go/tools v0.0.1-2019.2.2 // indirect ) -replace github.com/umputun/remark/backend => ../../ - replace gopkg.in/russross/blackfriday.v2 => github.com/russross/blackfriday/v2 v2.0.1 diff --git a/backend/_example/memory_store/go.sum b/backend/_example/memory_store/go.sum index 6958cdc4..c08f6639 100644 --- a/backend/_example/memory_store/go.sum +++ b/backend/_example/memory_store/go.sum @@ -2,47 +2,58 @@ cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMT cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU= cloud.google.com/go v0.40.0/go.mod h1:Tk58MuI9rbLMKlAjeO/bDnteAx7tX2gJIXw4T5Jwlro= -cloud.google.com/go v0.41.0/go.mod h1:OauMR7DV8fzvZIl2qg6rkaIhD/vmgk4iwEw/h6ercmg= +cloud.google.com/go v0.43.0/go.mod h1:BOSR3VbTLkk6FDC/TcffxP4NF/FFBGA5ku+jvKOP7pg= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= -github.com/PuerkitoBio/goquery v1.5.0 h1:uGvmFXOA73IKluu/F84Xd1tt/z07GYm8X49XKHP7EJk= +github.com/PuerkitoBio/goquery v1.4.0 h1:13fV4AYmaSopdNp8KWDUlLyU5INklBkYk0tsTfxRO2U= +github.com/PuerkitoBio/goquery v1.4.0/go.mod h1:T9ezsOHcCrDCgA8aF1Cqr3sSYbO/xgdy8/R/XiIMAhA= github.com/PuerkitoBio/goquery v1.5.0/go.mod h1:qD2PgZ9lccMbQlc7eEOjaeRlFQON7xY8kdmcsrnKqMg= github.com/andybalholm/cascadia v1.0.0 h1:hOCXnnZ5A+3eVDX8pvgl4kofXv2ELss0bKcqRySc45o= github.com/andybalholm/cascadia v1.0.0/go.mod h1:GsXiBklL0woXo1j/WYWtSYYC4ouU9PqHO0sqidkEA4Y= +github.com/boltdb/bolt v1.3.1/go.mod h1:clJnj/oiGkjum5o1McbSZDSLxVThjynRyGBgiAx27Ps= github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= -github.com/coreos/bbolt v1.3.3 h1:n6AiVyVRKQFNb6mJlwESEvvLoDyiTzXX7ORAUlkeBdY= +github.com/coreos/bbolt v1.3.0 h1:HIgH5xUWXT914HCI671AxuTTqjj64UOFr7pHn48LUTI= +github.com/coreos/bbolt v1.3.0/go.mod h1:iRUV2dpdMOn7Bo10OQBFzIJO9kkE559Wcmn+qkEiiKk= github.com/coreos/bbolt v1.3.3/go.mod h1:iRUV2dpdMOn7Bo10OQBFzIJO9kkE559Wcmn+qkEiiKk= +github.com/creack/pty v1.1.7/go.mod h1:lj5s0c3V2DBrqTV7llrYr5NG6My20zk30Fl46Y7DoTY= 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/dgrijalva/jwt-go v3.2.0+incompatible h1:7qlOGliEKZXTDg6OTjfoBKDXWrumCAMpl/TFQ4/5kLM= github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ= -github.com/didip/tollbooth v4.0.0+incompatible h1:ayQZYuF5QOxx3NdYRNuRVFLv9/2b64JtSUlewb+0TMo= github.com/didip/tollbooth v4.0.0+incompatible/go.mod h1:A9b0665CE6l1KmzpDws2++elm/CsuWBMa5Jv4WY0PEY= github.com/didip/tollbooth v4.0.2+incompatible h1:fVSa33JzSz0hoh2NxpwZtksAzAgd7zjmGO20HCZtF4M= github.com/didip/tollbooth v4.0.2+incompatible/go.mod h1:A9b0665CE6l1KmzpDws2++elm/CsuWBMa5Jv4WY0PEY= github.com/didip/tollbooth_chi v0.0.0-20170928041846-6ab5f3083f3d h1:vs5Nf6IE0N/PwGJ8//zRed4gpCdcr99K2HzX7RuLOQ8= github.com/didip/tollbooth_chi v0.0.0-20170928041846-6ab5f3083f3d/go.mod h1:YWyIfq3y4ArRfWZ9XksmuusP+7Mad+T0iFZ0kv0XG/M= github.com/globalsign/mgo v0.0.0-20180615134936-113d3961e731/go.mod h1:xkRDCp4j0OGD1HRkm4kmhM+pmpv3AKq5SU7GMg4oO/Q= +github.com/globalsign/mgo v0.0.0-20181015135952-eeefdecb41b8 h1:DujepqpGd1hyOd7aW59XpK7Qymp8iy83xq74fLr21is= github.com/globalsign/mgo v0.0.0-20181015135952-eeefdecb41b8/go.mod h1:xkRDCp4j0OGD1HRkm4kmhM+pmpv3AKq5SU7GMg4oO/Q= github.com/go-chi/chi v4.0.2+incompatible h1:maB6vn6FqCxrpz4FqWdh4+lwpyZIQS7YEAUcHlgXVRs= github.com/go-chi/chi v4.0.2+incompatible/go.mod h1:eB3wogJHnLi3x/kFX2A+IbTBlXxmMeXJVKy9tTv1XzQ= github.com/go-chi/cors v1.0.0/go.mod h1:K2Yje0VW/SJzxiyMYu6iPQYa7hMjQX2i/F491VChg1I= +github.com/go-chi/render v1.0.0/go.mod h1:pq4Rr7HbnsdaeHagklXub+p6Wd16Af5l9koip1OvJns= github.com/go-chi/render v1.0.1 h1:4/5tis2cKaNdnv9zFLfXzcquC9HbeZgCnxGnKrltBS8= github.com/go-chi/render v1.0.1/go.mod h1:pq4Rr7HbnsdaeHagklXub+p6Wd16Af5l9koip1OvJns= -github.com/go-pkgz/auth v0.7.2 h1:+LvAgqwQtYuWphpZE8qLtspVd65+VgreJkFLsKrtmmk= +github.com/go-pkgz/auth v0.5.0/go.mod h1:CWtB8dHmOv+TfF3MUzKwk/YwTLepC2TaDL05A+pFVBM= github.com/go-pkgz/auth v0.7.2/go.mod h1:ibOpZYISiaOvAHe2bsKj2s3v4AkMam2WxxIFn+zhulo= github.com/go-pkgz/jrpc v0.1.0 h1:hNg/IyfEqJcSWOKkuHw0ZwcuGc9TDp7QZREsD2ycmiM= github.com/go-pkgz/jrpc v0.1.0/go.mod h1:JxZsvoBklA50DNhELVJnJ567Rt+KrMH9rR3u515wvE8= +github.com/go-pkgz/lcw v0.2.0/go.mod h1:k+PY1CkCMTLXILtFoJOyK65Qqi9rkoTYunFH1vE/C0I= github.com/go-pkgz/lcw v0.3.1/go.mod h1:k+PY1CkCMTLXILtFoJOyK65Qqi9rkoTYunFH1vE/C0I= github.com/go-pkgz/lgr v0.2.2/go.mod h1:hBM1NM/SoYdlrykgdgJWGrZ/TM/XaZIjRbJfx7NkMm8= github.com/go-pkgz/lgr v0.6.2/go.mod h1:hBM1NM/SoYdlrykgdgJWGrZ/TM/XaZIjRbJfx7NkMm8= github.com/go-pkgz/lgr v0.6.3 h1:n9pGk2paBV8w/Y/FVEq5MkwDmP33dnUPKbY4CyyygwM= github.com/go-pkgz/lgr v0.6.3/go.mod h1:hBM1NM/SoYdlrykgdgJWGrZ/TM/XaZIjRbJfx7NkMm8= +github.com/go-pkgz/mongo v1.0.0/go.mod h1:R9si/F2aJsjz4MUxhzuppIHY8yLV3YCeuCpgcI50cu4= +github.com/go-pkgz/mongo v1.1.2 h1:2Vqn3CWQJkkx4gxxDiQUitAW2FN/CH26lKHkipmpKcc= github.com/go-pkgz/mongo v1.1.2/go.mod h1:0NkWnzpiUxoL5fYZuttCtJrpC67oNDidfYxcdPqHTf0= -github.com/go-pkgz/repeater v1.1.2/go.mod h1:QfNR/a+xqjs+f9wSxWqOQlw9aQhmKlUaSwXCiZ+Ko2w= +github.com/go-pkgz/repeater v1.1.1/go.mod h1:QfNR/a+xqjs+f9wSxWqOQlw9aQhmKlUaSwXCiZ+Ko2w= +github.com/go-pkgz/repeater v1.1.3/go.mod h1:hVTavuO5x3Gxnu8zW7d6sQBfAneKV8X2FjU48kGfpKw= +github.com/go-pkgz/rest v1.2.0/go.mod h1:COazNj35u3RXAgQNBr6neR599tYP3URiOpsu9p0rOtk= +github.com/go-pkgz/rest v1.4.0/go.mod h1:COazNj35u3RXAgQNBr6neR599tYP3URiOpsu9p0rOtk= github.com/go-pkgz/rest v1.4.1 h1:DmaVLPH2O7yLehrWOW0uz01d2mVHz9fBR/iuTiPRzaw= github.com/go-pkgz/rest v1.4.1/go.mod h1:COazNj35u3RXAgQNBr6neR599tYP3URiOpsu9p0rOtk= +github.com/go-pkgz/syncs v1.1.0/go.mod h1:bt9lxWRRJ9vOCMGc8Big8ttjYHLKP88ofj1y38UlaHE= github.com/go-pkgz/syncs v1.1.1/go.mod h1:bt9lxWRRJ9vOCMGc8Big8ttjYHLKP88ofj1y38UlaHE= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= @@ -50,6 +61,7 @@ github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfb github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y= github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= @@ -57,24 +69,33 @@ github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMyw github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= +github.com/google/pprof v0.0.0-20190723021845-34ac40c74b70/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= +github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= +github.com/google/uuid v0.0.0-20161128191214-064e2069ce9c/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk= +github.com/gorilla/feeds v1.1.0/go.mod h1:Nk0jZrvPFZX1OBe5NPiddPw7CfwF6Q9eqzaBbaightA= github.com/gorilla/feeds v1.1.1/go.mod h1:Nk0jZrvPFZX1OBe5NPiddPw7CfwF6Q9eqzaBbaightA= github.com/hashicorp/errwrap v1.0.0 h1:hLrqtEDnRye3+sgx6z4qVLNuviH3MR5aQ0ykNJa/UYA= github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= -github.com/hashicorp/go-multierror v1.0.0 h1:iVjPR7a6H0tWELX5NxNe7bYopibicUzc7uPribsnS6o= +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/go.mod h1:dHtQlpGsu+cZNNAkkCN/P3hoUDHhCYQXV3UM06sGGrk= github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= -github.com/jessevdk/go-flags v0.0.0-20180331124232-1c38ed7ad0cc h1:0L2sGkaj6MWuV1BfXsrLJ/+XA8RzKKVsYlLVXNkK1Lw= +github.com/hashicorp/golang-lru v0.5.3/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uGBxy+E8yxSoD4= github.com/jessevdk/go-flags v0.0.0-20180331124232-1c38ed7ad0cc/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI= +github.com/jessevdk/go-flags v1.4.0 h1:4IU2WS7AumrZ/40jfhf4QVDMsQwqA7VEHozFRrGARJA= +github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI= github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= +github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= +github.com/kr/pty v1.1.8/go.mod h1:O1sed60cT9XZ5uDucP5qwvh+TE3NnUj51EiZO/lmSfw= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= -github.com/kyokomi/emoji v2.1.0+incompatible/go.mod h1:mZ6aGCD7yk8j6QY6KICwnZ2pxoszVseX1DNoGtU2tBA= -github.com/microcosm-cc/bluemonday v1.0.2 h1:5lPfLTTAvAbtS0VqT+94yOtFnGfUWYyx0+iToC3Os3s= +github.com/microcosm-cc/bluemonday v0.0.0-20171222152607-542fd4642604 h1:BbG6VMVavjbhIsD7Hoscfz+wExp1hY+pmk+7Agc4J74= +github.com/microcosm-cc/bluemonday v0.0.0-20171222152607-542fd4642604/go.mod h1:hsXNsILzKxV+sX77C5b8FSuKF00vh2OMYv+xgHpAMF4= github.com/microcosm-cc/bluemonday v1.0.2/go.mod h1:iVP4YcDBq+n/5fb23BhYFvIMq/leAFZyRl6bYmGDlGc= github.com/nullrocks/identicon v0.0.0-20180626043057-7875f45b0022/go.mod h1:x4NsS+uc7ecH/Cbm9xKQ6XzmJM57rWTkjywjfB2yQ18= github.com/patrickmn/go-cache v2.1.0+incompatible h1:HRMgzkcYKYpi3C8ajMPV8OFXaaRUnok+kx1WdO15EQc= @@ -84,55 +105,65 @@ github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I= github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/rakyll/statik v0.1.3/go.mod h1:OEi9wJV/fMUAGx1eNjq75DKDsJVuEv1U0oYdX6GX8Zs= github.com/rakyll/statik v0.1.6/go.mod h1:OEi9wJV/fMUAGx1eNjq75DKDsJVuEv1U0oYdX6GX8Zs= +github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= github.com/rs/xid v1.2.1/go.mod h1:+uKXf+4Djp6Md1KODXJxgGQPKngRmWyn10oCKFzNHOQ= -github.com/russross/blackfriday/v2 v2.0.1 h1:lPqVAte+HuHNfhJ/0LC98ESWRz8afy9tM/0RK8m9o+Q= github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/shurcooL/sanitized_anchor_name v1.0.0 h1:PdmoCO6wvbs+7yrJyMORt4/BmY5IYyJwS/kOiWx8mHo= github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= -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= -go.etcd.io/bbolt v1.3.3 h1:MUGmc65QhB3pIlaQ5bB4LwqSj6GIonVJXpZiaKNyaKk= +github.com/umputun/remark v1.4.0 h1:rJf4ndpvRDS7tOrtIpVkacItTxF5pVpXvDqtb1dX40M= +github.com/umputun/remark/backend v1.3.0 h1:zm5lVbVv8fXvAPFUVzQ7TCvk07q8POG1ezIq6fpiQHE= +github.com/umputun/remark/backend v1.3.0/go.mod h1:Wja+FhIuI/XkCHzRmyXDY1q1YbqptNF9GY8p/HfKiAU= go.etcd.io/bbolt v1.3.3/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU= go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU= go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20190701094942-4def268fd1a4/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= +golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= +golang.org/x/exp v0.0.0-20190731235908-ec7cb31e5a56/go.mod h1:JhuoJpWY28nO4Vef9tZUw9qufEGTyX1+7lmHxV5q5G4= +golang.org/x/image v0.0.0-20181116024801-cd38e8056d9b/go.mod h1:ux5Hcp/YLpHSI86hEcLt0YII63i6oz57MZXIpbrjZUs= golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= golang.org/x/image v0.0.0-20190523035834-f03afa92d3ff/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= -golang.org/x/image v0.0.0-20190703141733-d6a02ce849c9/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= +golang.org/x/image v0.0.0-20190729225735-1bd0cf576493/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= golang.org/x/lint v0.0.0-20190409202823-959b441ac422/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE= +golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o= +golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc= +golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY= golang.org/x/net v0.0.0-20180218175443-cbe0f9307d01/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20181220203305-927f97764cc3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190107210223-45ffb0cd1ba0/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190415214537-1da14a5a36f2/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= golang.org/x/net v0.0.0-20190611141213-3f473d35a33a/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20190628185345-da137c7871d7 h1:rTIdg5QFRR7XCaK4LCjBiPbx8j4DQRpdYMnGn/bJUEU= -golang.org/x/net v0.0.0-20190628185345-da137c7871d7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190724013045-ca1201d0de80 h1:Ao/3l156eZf2AW5wK8a7/smtodRU+gha3+BeqJ69lRk= golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= +golang.org/x/oauth2 v0.0.0-20181203162652-d668ce993890/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= @@ -141,6 +172,7 @@ golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJ golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190109145017-48ac38b7c8cb/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -149,11 +181,9 @@ golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190610200419-93c9922d18ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190626221950-04f50cda93cb h1:fgwFCsaw9buMuxNd6+DQfAuSFqbNiQZpcgJQAgJsK6k= -golang.org/x/sys v0.0.0-20190626221950-04f50cda93cb/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190801041406-cbf593c0f2f3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= -golang.org/x/text v0.3.2 h1:tW2bmiBqwgJj/UpqtC8EpXEZVYOwU0yG4iWbprSVAcs= golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20190308202827-9d24e82272b4 h1:SvFZT6jyqRaOeXpc5h/JSfZenJ2O330aBsf7JfSUXmQ= @@ -166,8 +196,11 @@ golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3 golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= +golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= -golang.org/x/tools v0.0.0-20190624190245-7f2218787638/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= +golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= +golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= +golang.org/x/tools v0.0.0-20190731214159-1e85ed8060aa/go.mod h1:jcCCGcm9btYwXyDqrUWc6MKQKKGJCWEQ3AfLSRIbEuI= google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE= google.golang.org/api v0.6.0/go.mod h1:btoxGiFvQNVUZQ8W08zLtrVS08CNpINPEfxXxgJL1Q4= google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M= @@ -181,12 +214,18 @@ google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRn google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= google.golang.org/genproto v0.0.0-20190502173448-54afdca5d873/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= google.golang.org/genproto v0.0.0-20190530194941-fb225487d101/go.mod h1:z3L6/3dTEVtUr6QSP8miRzeRqwQOioJ9I66odjN4I7s= -google.golang.org/genproto v0.0.0-20190626174449-989357319d63/go.mod h1:z3L6/3dTEVtUr6QSP8miRzeRqwQOioJ9I66odjN4I7s= +google.golang.org/genproto v0.0.0-20190716160619-c506a9f90610/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38= google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= +google.golang.org/grpc v1.22.1/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= +gopkg.in/russross/blackfriday.v2 v2.0.0 h1:+FlnIV8DSQnT7NZ43hcVKcdJdzZoeCmJj4Ql8gq5keA= +gopkg.in/russross/blackfriday.v2 v2.0.0/go.mod h1:6sSBNz/GtOm/pJTuh5UmBK2ZHfmnxGbl2NZg1UliSOI= honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.1-2019.2.2/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg= rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8= diff --git a/backend/_example/memory_store/main.go b/backend/_example/memory_store/main.go index 8c8e55ff..5b3b55a7 100644 --- a/backend/_example/memory_store/main.go +++ b/backend/_example/memory_store/main.go @@ -14,8 +14,8 @@ import ( log "github.com/go-pkgz/lgr" "github.com/jessevdk/go-flags" - "github.com/umputun/remark/backend/_example/memory_store/accessor" - "github.com/umputun/remark/backend/_example/memory_store/server" + "github.com/umputun/remark/memory_store/accessor" + "github.com/umputun/remark/memory_store/server" ) // opts with all cli commands and flags From 17baa0cff60ab21ffc3baefc6e26464770df6538 Mon Sep 17 00:00:00 2001 From: Umputun Date: Thu, 1 Aug 2019 01:49:09 -0500 Subject: [PATCH 7/9] fix admin.admins call --- backend/_example/memory_store/accessor/admin.go | 2 +- backend/app/cmd/server.go | 2 +- backend/app/store/service/service.go | 1 + backend/nginx-proxy-example.conf | 16 ---------------- 4 files changed, 3 insertions(+), 18 deletions(-) diff --git a/backend/_example/memory_store/accessor/admin.go b/backend/_example/memory_store/accessor/admin.go index e82ef519..6e375dfd 100644 --- a/backend/_example/memory_store/accessor/admin.go +++ b/backend/_example/memory_store/accessor/admin.go @@ -41,7 +41,7 @@ func (m *MemAdmin) Admins(siteID string) (ids []string, err error) { if !ok { return nil, errors.Errorf("site %s not found", siteID) } - + log.Printf("[DEBUG] admins for %s, %+v", siteID, resp.IDs) return resp.IDs, nil } diff --git a/backend/app/cmd/server.go b/backend/app/cmd/server.go index 5d57e2cc..ffdc5092 100644 --- a/backend/app/cmd/server.go +++ b/backend/app/cmd/server.go @@ -108,7 +108,7 @@ type StoreGroup struct { Path string `long:"path" env:"PATH" default:"./var" description:"parent dir for bolt files"` Timeout time.Duration `long:"timeout" env:"TIMEOUT" default:"30s" description:"bolt timeout"` } `group:"bolt" namespace:"bolt" env-namespace:"BOLT"` - RPC RPCGroup `group:"rpc" namespace:"rpc" env-namespace:"TPC"` + RPC RPCGroup `group:"rpc" namespace:"rpc" env-namespace:"RPC"` } // ImageGroup defines options group for store pictures diff --git a/backend/app/store/service/service.go b/backend/app/store/service/service.go index 937cbd3c..820b37fe 100644 --- a/backend/app/store/service/service.go +++ b/backend/app/store/service/service.go @@ -469,6 +469,7 @@ func (s *DataStore) ValidateComment(c *store.Comment) error { func (s *DataStore) IsAdmin(siteID string, userID string) bool { admins, err := s.AdminStore.Admins(siteID) if err != nil { + log.Printf("[WARN] can't get admins for %s, %v", siteID, err) return false } for _, a := range admins { diff --git a/backend/nginx-proxy-example.conf b/backend/nginx-proxy-example.conf index fabd0ae7..32d96ec3 100644 --- a/backend/nginx-proxy-example.conf +++ b/backend/nginx-proxy-example.conf @@ -14,22 +14,6 @@ server { limit_conn perip 10; location / { - - #CORS - add_header 'Access-Control-Allow-Origin' '*'; - add_header 'Access-Control-Allow-Methods' 'GET, PUT, POST, DELETE, OPTIONS'; - add_header Access-Control-Allow-Headers "Content-Type, Authorization, X-Requested-With"; - if ($request_method = 'OPTIONS') { - add_header 'Access-Control-Allow-Origin' '*'; - add_header 'Access-Control-Allow-Methods' 'GET, PUT, POST, DELETE, OPTIONS'; - add_header 'Access-Control-Max-Age' 86400; - add_header 'Access-Control-Allow-Credentials' 'true'; - add_header 'Access-Control-Allow-Headers' 'Authorization,DNT,X-Mx-ReqToken,Keep-Alive,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type'; - add_header 'Content-Length' 0; - add_header 'Content-Type' 'text/plain; charset=UTF-8'; - return 204; - } - proxy_redirect off; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; From 796c4f45ebbea152874b3220d5c12d6125e8b2ba Mon Sep 17 00:00:00 2001 From: Umputun Date: Thu, 1 Aug 2019 01:49:38 -0500 Subject: [PATCH 8/9] fix rpc deps --- backend/_example/memory_store/go.mod | 28 ++------ backend/_example/memory_store/go.sum | 72 +++++-------------- backend/_example/memory_store/server/rpc.go | 18 ++--- .../_example/memory_store/server/rpc_test.go | 30 ++++---- 4 files changed, 39 insertions(+), 109 deletions(-) diff --git a/backend/_example/memory_store/go.mod b/backend/_example/memory_store/go.mod index a8c6269a..ddabb0b5 100644 --- a/backend/_example/memory_store/go.mod +++ b/backend/_example/memory_store/go.mod @@ -3,35 +3,15 @@ module github.com/umputun/remark/memory_store go 1.12 require ( - cloud.google.com/go v0.43.0 // indirect - github.com/PuerkitoBio/goquery v1.5.0 // indirect - github.com/go-pkgz/auth v0.7.2 // indirect github.com/go-pkgz/jrpc v0.1.0 - github.com/go-pkgz/lcw v0.3.1 // indirect github.com/go-pkgz/lgr v0.6.3 - github.com/go-pkgz/repeater v1.1.3 // indirect - github.com/go-pkgz/syncs v1.1.1 // indirect - github.com/google/pprof v0.0.0-20190723021845-34ac40c74b70 // indirect - github.com/google/uuid v1.1.1 // indirect - github.com/gorilla/feeds v1.1.1 // indirect - github.com/hashicorp/go-multierror v1.0.0 // indirect - github.com/hashicorp/golang-lru v0.5.3 // indirect github.com/jessevdk/go-flags v1.4.0 - github.com/kr/pty v1.1.8 // indirect - github.com/microcosm-cc/bluemonday v1.0.2 // indirect github.com/pkg/errors v0.8.1 - github.com/rakyll/statik v0.1.6 // indirect - github.com/stretchr/objx v0.2.0 // indirect - github.com/umputun/remark/backend v1.3.0 - golang.org/x/crypto v0.0.0-20190701094942-4def268fd1a4 // indirect - golang.org/x/exp v0.0.0-20190731235908-ec7cb31e5a56 // indirect - golang.org/x/image v0.0.0-20190729225735-1bd0cf576493 // indirect - golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028 // indirect + github.com/stretchr/testify v1.3.0 + github.com/umputun/remark/backend v1.4.0 golang.org/x/sys v0.0.0-20190801041406-cbf593c0f2f3 // indirect - golang.org/x/tools v0.0.0-20190731214159-1e85ed8060aa // indirect - google.golang.org/grpc v1.22.1 // indirect - gopkg.in/russross/blackfriday.v2 v2.0.1 // indirect - honnef.co/go/tools v0.0.1-2019.2.2 // indirect ) +replace github.com/umputun/remark/backend => ../../ + replace gopkg.in/russross/blackfriday.v2 => github.com/russross/blackfriday/v2 v2.0.1 diff --git a/backend/_example/memory_store/go.sum b/backend/_example/memory_store/go.sum index c08f6639..ab5ff9e5 100644 --- a/backend/_example/memory_store/go.sum +++ b/backend/_example/memory_store/go.sum @@ -2,25 +2,20 @@ cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMT cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU= cloud.google.com/go v0.40.0/go.mod h1:Tk58MuI9rbLMKlAjeO/bDnteAx7tX2gJIXw4T5Jwlro= -cloud.google.com/go v0.43.0/go.mod h1:BOSR3VbTLkk6FDC/TcffxP4NF/FFBGA5ku+jvKOP7pg= +cloud.google.com/go v0.41.0/go.mod h1:OauMR7DV8fzvZIl2qg6rkaIhD/vmgk4iwEw/h6ercmg= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= -github.com/PuerkitoBio/goquery v1.4.0 h1:13fV4AYmaSopdNp8KWDUlLyU5INklBkYk0tsTfxRO2U= -github.com/PuerkitoBio/goquery v1.4.0/go.mod h1:T9ezsOHcCrDCgA8aF1Cqr3sSYbO/xgdy8/R/XiIMAhA= +github.com/PuerkitoBio/goquery v1.5.0 h1:uGvmFXOA73IKluu/F84Xd1tt/z07GYm8X49XKHP7EJk= github.com/PuerkitoBio/goquery v1.5.0/go.mod h1:qD2PgZ9lccMbQlc7eEOjaeRlFQON7xY8kdmcsrnKqMg= github.com/andybalholm/cascadia v1.0.0 h1:hOCXnnZ5A+3eVDX8pvgl4kofXv2ELss0bKcqRySc45o= github.com/andybalholm/cascadia v1.0.0/go.mod h1:GsXiBklL0woXo1j/WYWtSYYC4ouU9PqHO0sqidkEA4Y= -github.com/boltdb/bolt v1.3.1/go.mod h1:clJnj/oiGkjum5o1McbSZDSLxVThjynRyGBgiAx27Ps= github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= -github.com/coreos/bbolt v1.3.0 h1:HIgH5xUWXT914HCI671AxuTTqjj64UOFr7pHn48LUTI= -github.com/coreos/bbolt v1.3.0/go.mod h1:iRUV2dpdMOn7Bo10OQBFzIJO9kkE559Wcmn+qkEiiKk= +github.com/coreos/bbolt v1.3.3 h1:n6AiVyVRKQFNb6mJlwESEvvLoDyiTzXX7ORAUlkeBdY= github.com/coreos/bbolt v1.3.3/go.mod h1:iRUV2dpdMOn7Bo10OQBFzIJO9kkE559Wcmn+qkEiiKk= -github.com/creack/pty v1.1.7/go.mod h1:lj5s0c3V2DBrqTV7llrYr5NG6My20zk30Fl46Y7DoTY= 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/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ= -github.com/didip/tollbooth v4.0.0+incompatible/go.mod h1:A9b0665CE6l1KmzpDws2++elm/CsuWBMa5Jv4WY0PEY= github.com/didip/tollbooth v4.0.2+incompatible h1:fVSa33JzSz0hoh2NxpwZtksAzAgd7zjmGO20HCZtF4M= github.com/didip/tollbooth v4.0.2+incompatible/go.mod h1:A9b0665CE6l1KmzpDws2++elm/CsuWBMa5Jv4WY0PEY= github.com/didip/tollbooth_chi v0.0.0-20170928041846-6ab5f3083f3d h1:vs5Nf6IE0N/PwGJ8//zRed4gpCdcr99K2HzX7RuLOQ8= @@ -31,29 +26,21 @@ github.com/globalsign/mgo v0.0.0-20181015135952-eeefdecb41b8/go.mod h1:xkRDCp4j0 github.com/go-chi/chi v4.0.2+incompatible h1:maB6vn6FqCxrpz4FqWdh4+lwpyZIQS7YEAUcHlgXVRs= github.com/go-chi/chi v4.0.2+incompatible/go.mod h1:eB3wogJHnLi3x/kFX2A+IbTBlXxmMeXJVKy9tTv1XzQ= github.com/go-chi/cors v1.0.0/go.mod h1:K2Yje0VW/SJzxiyMYu6iPQYa7hMjQX2i/F491VChg1I= -github.com/go-chi/render v1.0.0/go.mod h1:pq4Rr7HbnsdaeHagklXub+p6Wd16Af5l9koip1OvJns= github.com/go-chi/render v1.0.1 h1:4/5tis2cKaNdnv9zFLfXzcquC9HbeZgCnxGnKrltBS8= github.com/go-chi/render v1.0.1/go.mod h1:pq4Rr7HbnsdaeHagklXub+p6Wd16Af5l9koip1OvJns= -github.com/go-pkgz/auth v0.5.0/go.mod h1:CWtB8dHmOv+TfF3MUzKwk/YwTLepC2TaDL05A+pFVBM= github.com/go-pkgz/auth v0.7.2/go.mod h1:ibOpZYISiaOvAHe2bsKj2s3v4AkMam2WxxIFn+zhulo= github.com/go-pkgz/jrpc v0.1.0 h1:hNg/IyfEqJcSWOKkuHw0ZwcuGc9TDp7QZREsD2ycmiM= github.com/go-pkgz/jrpc v0.1.0/go.mod h1:JxZsvoBklA50DNhELVJnJ567Rt+KrMH9rR3u515wvE8= -github.com/go-pkgz/lcw v0.2.0/go.mod h1:k+PY1CkCMTLXILtFoJOyK65Qqi9rkoTYunFH1vE/C0I= github.com/go-pkgz/lcw v0.3.1/go.mod h1:k+PY1CkCMTLXILtFoJOyK65Qqi9rkoTYunFH1vE/C0I= github.com/go-pkgz/lgr v0.2.2/go.mod h1:hBM1NM/SoYdlrykgdgJWGrZ/TM/XaZIjRbJfx7NkMm8= github.com/go-pkgz/lgr v0.6.2/go.mod h1:hBM1NM/SoYdlrykgdgJWGrZ/TM/XaZIjRbJfx7NkMm8= github.com/go-pkgz/lgr v0.6.3 h1:n9pGk2paBV8w/Y/FVEq5MkwDmP33dnUPKbY4CyyygwM= github.com/go-pkgz/lgr v0.6.3/go.mod h1:hBM1NM/SoYdlrykgdgJWGrZ/TM/XaZIjRbJfx7NkMm8= -github.com/go-pkgz/mongo v1.0.0/go.mod h1:R9si/F2aJsjz4MUxhzuppIHY8yLV3YCeuCpgcI50cu4= github.com/go-pkgz/mongo v1.1.2 h1:2Vqn3CWQJkkx4gxxDiQUitAW2FN/CH26lKHkipmpKcc= github.com/go-pkgz/mongo v1.1.2/go.mod h1:0NkWnzpiUxoL5fYZuttCtJrpC67oNDidfYxcdPqHTf0= -github.com/go-pkgz/repeater v1.1.1/go.mod h1:QfNR/a+xqjs+f9wSxWqOQlw9aQhmKlUaSwXCiZ+Ko2w= -github.com/go-pkgz/repeater v1.1.3/go.mod h1:hVTavuO5x3Gxnu8zW7d6sQBfAneKV8X2FjU48kGfpKw= -github.com/go-pkgz/rest v1.2.0/go.mod h1:COazNj35u3RXAgQNBr6neR599tYP3URiOpsu9p0rOtk= -github.com/go-pkgz/rest v1.4.0/go.mod h1:COazNj35u3RXAgQNBr6neR599tYP3URiOpsu9p0rOtk= +github.com/go-pkgz/repeater v1.1.2/go.mod h1:QfNR/a+xqjs+f9wSxWqOQlw9aQhmKlUaSwXCiZ+Ko2w= github.com/go-pkgz/rest v1.4.1 h1:DmaVLPH2O7yLehrWOW0uz01d2mVHz9fBR/iuTiPRzaw= github.com/go-pkgz/rest v1.4.1/go.mod h1:COazNj35u3RXAgQNBr6neR599tYP3URiOpsu9p0rOtk= -github.com/go-pkgz/syncs v1.1.0/go.mod h1:bt9lxWRRJ9vOCMGc8Big8ttjYHLKP88ofj1y38UlaHE= github.com/go-pkgz/syncs v1.1.1/go.mod h1:bt9lxWRRJ9vOCMGc8Big8ttjYHLKP88ofj1y38UlaHE= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= @@ -61,7 +48,6 @@ github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfb github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y= github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= -github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= @@ -69,33 +55,25 @@ github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMyw github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= -github.com/google/pprof v0.0.0-20190723021845-34ac40c74b70/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= -github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= -github.com/google/uuid v0.0.0-20161128191214-064e2069ce9c/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk= -github.com/gorilla/feeds v1.1.0/go.mod h1:Nk0jZrvPFZX1OBe5NPiddPw7CfwF6Q9eqzaBbaightA= github.com/gorilla/feeds v1.1.1/go.mod h1:Nk0jZrvPFZX1OBe5NPiddPw7CfwF6Q9eqzaBbaightA= github.com/hashicorp/errwrap v1.0.0 h1:hLrqtEDnRye3+sgx6z4qVLNuviH3MR5aQ0ykNJa/UYA= 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/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= -github.com/hashicorp/golang-lru v0.5.3/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uGBxy+E8yxSoD4= github.com/jessevdk/go-flags v0.0.0-20180331124232-1c38ed7ad0cc/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI= github.com/jessevdk/go-flags v1.4.0 h1:4IU2WS7AumrZ/40jfhf4QVDMsQwqA7VEHozFRrGARJA= github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI= github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= -github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= -github.com/kr/pty v1.1.8/go.mod h1:O1sed60cT9XZ5uDucP5qwvh+TE3NnUj51EiZO/lmSfw= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= -github.com/microcosm-cc/bluemonday v0.0.0-20171222152607-542fd4642604 h1:BbG6VMVavjbhIsD7Hoscfz+wExp1hY+pmk+7Agc4J74= -github.com/microcosm-cc/bluemonday v0.0.0-20171222152607-542fd4642604/go.mod h1:hsXNsILzKxV+sX77C5b8FSuKF00vh2OMYv+xgHpAMF4= +github.com/kyokomi/emoji v2.1.0+incompatible/go.mod h1:mZ6aGCD7yk8j6QY6KICwnZ2pxoszVseX1DNoGtU2tBA= +github.com/microcosm-cc/bluemonday v1.0.2 h1:5lPfLTTAvAbtS0VqT+94yOtFnGfUWYyx0+iToC3Os3s= github.com/microcosm-cc/bluemonday v1.0.2/go.mod h1:iVP4YcDBq+n/5fb23BhYFvIMq/leAFZyRl6bYmGDlGc= github.com/nullrocks/identicon v0.0.0-20180626043057-7875f45b0022/go.mod h1:x4NsS+uc7ecH/Cbm9xKQ6XzmJM57rWTkjywjfB2yQ18= github.com/patrickmn/go-cache v2.1.0+incompatible h1:HRMgzkcYKYpi3C8ajMPV8OFXaaRUnok+kx1WdO15EQc= @@ -105,56 +83,46 @@ github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I= github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/rakyll/statik v0.1.3/go.mod h1:OEi9wJV/fMUAGx1eNjq75DKDsJVuEv1U0oYdX6GX8Zs= github.com/rakyll/statik v0.1.6/go.mod h1:OEi9wJV/fMUAGx1eNjq75DKDsJVuEv1U0oYdX6GX8Zs= -github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= github.com/rs/xid v1.2.1/go.mod h1:+uKXf+4Djp6Md1KODXJxgGQPKngRmWyn10oCKFzNHOQ= +github.com/russross/blackfriday/v2 v2.0.1 h1:lPqVAte+HuHNfhJ/0LC98ESWRz8afy9tM/0RK8m9o+Q= github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/shurcooL/sanitized_anchor_name v1.0.0 h1:PdmoCO6wvbs+7yrJyMORt4/BmY5IYyJwS/kOiWx8mHo= github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +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.4.0 h1:rJf4ndpvRDS7tOrtIpVkacItTxF5pVpXvDqtb1dX40M= -github.com/umputun/remark/backend v1.3.0 h1:zm5lVbVv8fXvAPFUVzQ7TCvk07q8POG1ezIq6fpiQHE= -github.com/umputun/remark/backend v1.3.0/go.mod h1:Wja+FhIuI/XkCHzRmyXDY1q1YbqptNF9GY8p/HfKiAU= +go.etcd.io/bbolt v1.3.3 h1:MUGmc65QhB3pIlaQ5bB4LwqSj6GIonVJXpZiaKNyaKk= go.etcd.io/bbolt v1.3.3/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU= go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU= go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= -golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20190701094942-4def268fd1a4/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= -golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= -golang.org/x/exp v0.0.0-20190731235908-ec7cb31e5a56/go.mod h1:JhuoJpWY28nO4Vef9tZUw9qufEGTyX1+7lmHxV5q5G4= -golang.org/x/image v0.0.0-20181116024801-cd38e8056d9b/go.mod h1:ux5Hcp/YLpHSI86hEcLt0YII63i6oz57MZXIpbrjZUs= golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= golang.org/x/image v0.0.0-20190523035834-f03afa92d3ff/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= -golang.org/x/image v0.0.0-20190729225735-1bd0cf576493/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= +golang.org/x/image v0.0.0-20190703141733-d6a02ce849c9/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= golang.org/x/lint v0.0.0-20190409202823-959b441ac422/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE= -golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o= -golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc= -golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY= golang.org/x/net v0.0.0-20180218175443-cbe0f9307d01/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20181220203305-927f97764cc3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20190107210223-45ffb0cd1ba0/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= -golang.org/x/net v0.0.0-20190415214537-1da14a5a36f2/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= @@ -163,7 +131,6 @@ golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLL golang.org/x/net v0.0.0-20190724013045-ca1201d0de80 h1:Ao/3l156eZf2AW5wK8a7/smtodRU+gha3+BeqJ69lRk= golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= -golang.org/x/oauth2 v0.0.0-20181203162652-d668ce993890/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= @@ -172,7 +139,6 @@ golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJ golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20190109145017-48ac38b7c8cb/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -181,9 +147,12 @@ golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190610200419-93c9922d18ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190626221950-04f50cda93cb/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190801041406-cbf593c0f2f3 h1:4y9KwBHBgBNwDbtu44R5o1fdOCQUEXhbk/P4A9WmJq0= golang.org/x/sys v0.0.0-20190801041406-cbf593c0f2f3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.2 h1:tW2bmiBqwgJj/UpqtC8EpXEZVYOwU0yG4iWbprSVAcs= golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20190308202827-9d24e82272b4 h1:SvFZT6jyqRaOeXpc5h/JSfZenJ2O330aBsf7JfSUXmQ= @@ -196,11 +165,8 @@ golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3 golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= -golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= -golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= -golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= -golang.org/x/tools v0.0.0-20190731214159-1e85ed8060aa/go.mod h1:jcCCGcm9btYwXyDqrUWc6MKQKKGJCWEQ3AfLSRIbEuI= +golang.org/x/tools v0.0.0-20190624190245-7f2218787638/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE= google.golang.org/api v0.6.0/go.mod h1:btoxGiFvQNVUZQ8W08zLtrVS08CNpINPEfxXxgJL1Q4= google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M= @@ -214,18 +180,12 @@ google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRn google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= google.golang.org/genproto v0.0.0-20190502173448-54afdca5d873/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= google.golang.org/genproto v0.0.0-20190530194941-fb225487d101/go.mod h1:z3L6/3dTEVtUr6QSP8miRzeRqwQOioJ9I66odjN4I7s= -google.golang.org/genproto v0.0.0-20190716160619-c506a9f90610/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= +google.golang.org/genproto v0.0.0-20190626174449-989357319d63/go.mod h1:z3L6/3dTEVtUr6QSP8miRzeRqwQOioJ9I66odjN4I7s= google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38= google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= -google.golang.org/grpc v1.22.1/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= -gopkg.in/russross/blackfriday.v2 v2.0.0 h1:+FlnIV8DSQnT7NZ43hcVKcdJdzZoeCmJj4Ql8gq5keA= -gopkg.in/russross/blackfriday.v2 v2.0.0/go.mod h1:6sSBNz/GtOm/pJTuh5UmBK2ZHfmnxGbl2NZg1UliSOI= honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= -honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= -honnef.co/go/tools v0.0.1-2019.2.2/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg= rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8= diff --git a/backend/_example/memory_store/server/rpc.go b/backend/_example/memory_store/server/rpc.go index c9ea42d2..47bcc824 100644 --- a/backend/_example/memory_store/server/rpc.go +++ b/backend/_example/memory_store/server/rpc.go @@ -10,7 +10,7 @@ import ( "github.com/umputun/remark/backend/app/store/engine" ) -// RPC handler wraps both engine and remote server and implements all handlers +// RPC handler wraps both engine and remote server and implements all handlers for data store and admin store // Note: this file can be used as-is in any custom jrpc plugin type RPC struct { *jrpc.Server @@ -156,14 +156,10 @@ func (s *RPC) admKeyHndl(id uint64, _ json.RawMessage) (rr jrpc.Response) { // get admins list func (s *RPC) admAdminsHndl(id uint64, params json.RawMessage) (rr jrpc.Response) { - args := []interface{}{} - if err := json.Unmarshal(params, &args); err != nil { + var siteID string + if err := json.Unmarshal(params, &siteID); err != nil { return jrpc.Response{Error: err.Error()} } - siteID, ok := args[0].(string) - if !ok { - return jrpc.Response{Error: "incompatible argument"} - } admins, err := s.adm.Admins(siteID) if err != nil { @@ -174,14 +170,10 @@ func (s *RPC) admAdminsHndl(id uint64, params json.RawMessage) (rr jrpc.Response // get admin email func (s *RPC) admEmailHndl(id uint64, params json.RawMessage) (rr jrpc.Response) { - args := []interface{}{} - if err := json.Unmarshal(params, &args); err != nil { + var siteID string + if err := json.Unmarshal(params, &siteID); err != nil { return jrpc.Response{Error: err.Error()} } - siteID, ok := args[0].(string) - if !ok { - return jrpc.Response{Error: "incompatible argument"} - } email, err := s.adm.Email(siteID) if err != nil { diff --git a/backend/_example/memory_store/server/rpc_test.go b/backend/_example/memory_store/server/rpc_test.go index 583c3eb9..30f3942c 100644 --- a/backend/_example/memory_store/server/rpc_test.go +++ b/backend/_example/memory_store/server/rpc_test.go @@ -17,12 +17,10 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" - "github.com/umputun/remark/backend/app/rpc" "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/_example/memory_store/accessor" + "github.com/umputun/remark/memory_store/accessor" ) func TestRPC_createHndl(t *testing.T) { @@ -30,7 +28,7 @@ func TestRPC_createHndl(t *testing.T) { defer teardown() api := fmt.Sprintf("http://localhost:%d/test", port) - re := engine.RPC{Client: rpc.Client{API: api, Client: http.Client{Timeout: 1 * time.Second}}} + re := engine.RPC{Client: jrpc.Client{API: api, Client: http.Client{Timeout: 1 * time.Second}}} id, err := re.Create(store.Comment{ID: "123456", Locator: store.Locator{SiteID: "test-site", URL: "http://example.com/post1"}, Text: "text 123", User: store.User{ID: "u1", Name: "user1"}}) assert.NoError(t, err) @@ -42,7 +40,7 @@ func TestRPC_findHndl(t *testing.T) { defer teardown() api := fmt.Sprintf("http://localhost:%d/test", port) - re := engine.RPC{Client: rpc.Client{API: api, Client: http.Client{Timeout: 1 * time.Second}}} + re := engine.RPC{Client: jrpc.Client{API: api, Client: http.Client{Timeout: 1 * time.Second}}} findReq := engine.FindRequest{Locator: store.Locator{SiteID: "test-site", URL: "http://example.com/post1"}} comments, err := re.Find(findReq) require.NoError(t, err) @@ -65,7 +63,7 @@ func TestRPC_getHndl(t *testing.T) { defer teardown() api := fmt.Sprintf("http://localhost:%d/test", port) - re := engine.RPC{Client: rpc.Client{API: api, Client: http.Client{Timeout: 1 * time.Second}}} + re := engine.RPC{Client: jrpc.Client{API: api, Client: http.Client{Timeout: 1 * time.Second}}} req := engine.GetRequest{ Locator: store.Locator{SiteID: "test-site", URL: "http://example.com/post1"}, CommentID: "123456", @@ -89,7 +87,7 @@ func TestRPC_updateHndl(t *testing.T) { defer teardown() api := fmt.Sprintf("http://localhost:%d/test", port) - re := engine.RPC{Client: rpc.Client{API: api, Client: http.Client{Timeout: 1 * time.Second}}} + re := engine.RPC{Client: jrpc.Client{API: api, Client: http.Client{Timeout: 1 * time.Second}}} c := store.Comment{ID: "123456", Locator: store.Locator{SiteID: "test-site", URL: "http://example.com/post1"}, Text: "text 123", User: store.User{ID: "u1", Name: "user1"}} @@ -117,7 +115,7 @@ func TestRPC_countHndl(t *testing.T) { defer teardown() api := fmt.Sprintf("http://localhost:%d/test", port) - re := engine.RPC{Client: rpc.Client{API: api, Client: http.Client{Timeout: 1 * time.Second}}} + re := engine.RPC{Client: jrpc.Client{API: api, Client: http.Client{Timeout: 1 * time.Second}}} findReq := engine.FindRequest{Locator: store.Locator{SiteID: "test-site", URL: "http://example.com/post1"}} count, err := re.Count(findReq) require.NoError(t, err) @@ -139,7 +137,7 @@ func TestRPC_infoHndl(t *testing.T) { defer teardown() api := fmt.Sprintf("http://localhost:%d/test", port) - re := engine.RPC{Client: rpc.Client{API: api, Client: http.Client{Timeout: 1 * time.Second}}} + re := engine.RPC{Client: jrpc.Client{API: api, Client: http.Client{Timeout: 1 * time.Second}}} c := store.Comment{ID: "123456", Locator: store.Locator{SiteID: "test-site", URL: "http://example.com/post1"}, Text: "text 123", User: store.User{ID: "u1", Name: "user1"}} @@ -160,7 +158,7 @@ func TestRPC_flagHndl(t *testing.T) { defer teardown() api := fmt.Sprintf("http://localhost:%d/test", port) - re := engine.RPC{Client: rpc.Client{API: api, Client: http.Client{Timeout: 1 * time.Second}}} + re := engine.RPC{Client: jrpc.Client{API: api, Client: http.Client{Timeout: 1 * time.Second}}} c := store.Comment{ID: "123456", Locator: store.Locator{SiteID: "test-site", URL: "http://example.com/post1"}, Text: "text 123", User: store.User{ID: "u1", Name: "user1"}} @@ -195,7 +193,7 @@ func TestRPC_listFlagsHndl(t *testing.T) { defer teardown() api := fmt.Sprintf("http://localhost:%d/test", port) - re := engine.RPC{Client: rpc.Client{API: api, Client: http.Client{Timeout: 1 * time.Second}}} + re := engine.RPC{Client: jrpc.Client{API: api, Client: http.Client{Timeout: 1 * time.Second}}} c := store.Comment{ID: "123456", Locator: store.Locator{SiteID: "test-site", URL: "http://example.com/post1"}, Text: "text 123", User: store.User{ID: "u1", Name: "user1"}} @@ -229,7 +227,7 @@ func TestRPC_deleteHndl(t *testing.T) { defer teardown() api := fmt.Sprintf("http://localhost:%d/test", port) - re := engine.RPC{Client: rpc.Client{API: api, Client: http.Client{Timeout: 1 * time.Second}}} + re := engine.RPC{Client: jrpc.Client{API: api, Client: http.Client{Timeout: 1 * time.Second}}} req := engine.DeleteRequest{ Locator: store.Locator{SiteID: "test-site", URL: "http://example.com/post1"}, CommentID: "123456", @@ -252,7 +250,7 @@ func TestRPC_closeHndl(t *testing.T) { defer teardown() api := fmt.Sprintf("http://localhost:%d/test", port) - re := engine.RPC{Client: rpc.Client{API: api, Client: http.Client{Timeout: 1 * time.Second}}} + re := engine.RPC{Client: jrpc.Client{API: api, Client: http.Client{Timeout: 1 * time.Second}}} err := re.Close() assert.NoError(t, err) } @@ -261,7 +259,7 @@ func TestRPC_admKeyHndl(t *testing.T) { defer teardown() api := fmt.Sprintf("http://localhost:%d/test", port) - ra := admin.RPC{Client: rpc.Client{API: api, Client: http.Client{Timeout: 1 * time.Second}}} + ra := admin.RPC{Client: jrpc.Client{API: api, Client: http.Client{Timeout: 1 * time.Second}}} key, err := ra.Key() assert.NoError(t, err) assert.Equal(t, "secret", key) @@ -272,7 +270,7 @@ func TestRPC_admAdminsHndl(t *testing.T) { defer teardown() api := fmt.Sprintf("http://localhost:%d/test", port) - ra := admin.RPC{Client: rpc.Client{API: api, Client: http.Client{Timeout: 1 * time.Second}}} + ra := admin.RPC{Client: jrpc.Client{API: api, Client: http.Client{Timeout: 1 * time.Second}}} _, err := ra.Admins("bad site") assert.EqualError(t, err, "site bad site not found") @@ -286,7 +284,7 @@ func TestRPC_admEmailHndl(t *testing.T) { defer teardown() api := fmt.Sprintf("http://localhost:%d/test", port) - ra := admin.RPC{Client: rpc.Client{API: api, Client: http.Client{Timeout: 1 * time.Second}}} + ra := admin.RPC{Client: jrpc.Client{API: api, Client: http.Client{Timeout: 1 * time.Second}}} _, err := ra.Admins("bad site") assert.EqualError(t, err, "site bad site not found") From 5b173a05df387c81ccbf116e03814fb691ffd0ce Mon Sep 17 00:00:00 2001 From: Umputun Date: Thu, 1 Aug 2019 01:49:58 -0500 Subject: [PATCH 9/9] add dockerfile and compose --- backend/_example/memory_store/Dockerfile | 10 ++-- backend/_example/memory_store/README.md | 9 ++++ .../memory_store/compose-dev-memstore.yml | 47 +++++++++++++++++++ .../_example/memory_store/docker-compose.yml | 33 ------------- backend/_example/memory_store/main.go | 8 ++-- 5 files changed, 67 insertions(+), 40 deletions(-) create mode 100644 backend/_example/memory_store/compose-dev-memstore.yml delete mode 100644 backend/_example/memory_store/docker-compose.yml diff --git a/backend/_example/memory_store/Dockerfile b/backend/_example/memory_store/Dockerfile index ab68a329..2eda2e85 100644 --- a/backend/_example/memory_store/Dockerfile +++ b/backend/_example/memory_store/Dockerfile @@ -1,16 +1,18 @@ FROM umputun/baseimage:buildgo-latest as build-backend -ADD . /build/memory_store -WORKDIR /build/memory_store +#ADD . /build/memory_store +#WORKDIR /build/memory_store +ADD backend /build/backend +WORKDIR /build/backend/_example/memory_store -RUN go build -o memory_store -ldflags "-X main.revision=0.0.0 -s -w" +RUN go build -o /build/bin/memory_store -ldflags "-X main.revision=0.0.0 -s -w" FROM umputun/baseimage:app-latest WORKDIR /srv -COPY --from=build-backend /build/memory_store/memory_store /srv/memory_store +COPY --from=build-backend /build/bin/memory_store /srv/memory_store RUN chown -R app:app /srv EXPOSE 8080 diff --git a/backend/_example/memory_store/README.md b/backend/_example/memory_store/README.md index 8005cf9f..475703a4 100644 --- a/backend/_example/memory_store/README.md +++ b/backend/_example/memory_store/README.md @@ -2,3 +2,12 @@ `memory_store` illustrates how to make a custom storage plugin for remark42. +In order to run remark42 with memory_store copy provided `compose-dev-memstore.yml` to the root directory and run: + +1. docker-compose -f compose-dev-memstore.yml build +1. docker-compose -f compose-dev-memstore.yml up + +As usual, demo site will run on http://127.0.0.1:8080/web/ + +note: in order to work with the latest (current) version of master `go.mod` uses replacement directive for the backend package +. In real-life usage `replace github.com/umputun/remark/backend => ../../` should not be used. \ No newline at end of file diff --git a/backend/_example/memory_store/compose-dev-memstore.yml b/backend/_example/memory_store/compose-dev-memstore.yml new file mode 100644 index 00000000..3392ecae --- /dev/null +++ b/backend/_example/memory_store/compose-dev-memstore.yml @@ -0,0 +1,47 @@ +# compose file demonstrating custom storage use. The memory_store (see backend/_example/memory_store) starts +# in a separate container and remark42 communicates to mem_store.r42 via STORE_RPC_API url + +version: '2' + +services: + + remark42: + build: + context: . + dockerfile: Dockerfile + args: + - SKIP_BACKEND_TEST=true + - SKIP_FRONTEND_TEST=true + image: umputun/remark42:dev + container_name: "remark42" + hostname: "remark42" + restart: always + + ports: + - "8080:8080" # primary rest server + - "8084:8084" # local oauth2 server + + environment: + - REMARK_URL=http://127.0.0.1:8080 + - SECRET=123456 + - BACKUP_PATH=/srv/var/backup + - DEBUG=true + - EMOJI=true + - AUTH_ANON=true + - AUTH_DEV=true + - ADMIN_TYPE=rpc + - ADMIN_RPC_API=http://mem_store.r42:8080/cmd + - STORE_TYPE=rpc + - STORE_RPC_API=http://mem_store.r42:8080/cmd + + mem_store.r42: + image: umputun/mem_store.r42 + build: + context: . + dockerfile: backend/_example/memory_store/Dockerfile + container_name: "mem_store.r42" + hostname: "mem_store.r42" + environment: + - API=/cmd + - SECRET=123456 + - DEBUG=true diff --git a/backend/_example/memory_store/docker-compose.yml b/backend/_example/memory_store/docker-compose.yml deleted file mode 100644 index 6a5576b6..00000000 --- a/backend/_example/memory_store/docker-compose.yml +++ /dev/null @@ -1,33 +0,0 @@ -version: '2' - -services: - remark: - image: umputun/remark42:dev - container_name: "remark42" - hostname: "remark42" - restart: always - - logging: - driver: json-file - options: - max-size: "10m" - max-file: "5" - - ports: - - "8080:8080" - - environment: - - REMARK_URL - - SECRET - - STORE_BOLT_PATH=/srv/var/db - - BACKUP_PATH=/srv/var/backup - - DEBUG=true - - EMOJI=true - - AUTH_ANON=true - - AUTH_DEV=true - - - volumes: - - ./var:/srv/var - - mem_storage: - build: . diff --git a/backend/_example/memory_store/main.go b/backend/_example/memory_store/main.go index 5b3b55a7..07f2e85f 100644 --- a/backend/_example/memory_store/main.go +++ b/backend/_example/memory_store/main.go @@ -48,15 +48,17 @@ func main() { AuthPasswd: opts.AuthPasswd, Version: revision, AppName: "remark42-memory", + Logger: log.Default(), } + srv := server.NewRPC(dataStore, adminStore, &rpcServer) admRec := accessor.AdminRec{ - SiteID: "example", - IDs: []string{"id1", "id2"}, + SiteID: "remark", + IDs: []string{"dev_user"}, Email: "admin@example.com", } - adminStore.Set("example", admRec) + adminStore.Set("remark", admRec) err := srv.Run(opts.Port) log.Printf("[ERROR] server failed or terminated, %+v", err)