From e84a155ef5ee3d8003de96affd71ade83c6e5220 Mon Sep 17 00:00:00 2001 From: Umputun Date: Wed, 31 Jul 2019 18:34:32 -0500 Subject: [PATCH] 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 -}