diff --git a/backend/app/cmd/avatar.go b/backend/app/cmd/avatar.go index 5c7e9a92..e0075113 100644 --- a/backend/app/cmd/avatar.go +++ b/backend/app/cmd/avatar.go @@ -2,14 +2,12 @@ package cmd import ( "path" - "time" bolt "github.com/coreos/bbolt" log "github.com/go-pkgz/lgr" "github.com/pkg/errors" "github.com/go-pkgz/auth/avatar" - "github.com/go-pkgz/mongo" ) // AvatarCommand set of flags and command for avatar migration @@ -18,7 +16,7 @@ import ( type AvatarCommand struct { AvatarSrc AvatarGroup `group:"src" namespace:"src"` AvatarDst AvatarGroup `group:"dst" namespace:"dst"` - Mongo MongoGroup `group:"mongo" namespace:"mongo" env-namespace:"MONGO"` + // Mongo MongoGroup `group:"mongo" namespace:"mongo" env-namespace:"MONGO"` migrator AvatarMigrator CommonOpts @@ -78,13 +76,6 @@ func (ac *AvatarCommand) makeAvatarStore(gr AvatarGroup) (avatar.Store, error) { return nil, err } return avatar.NewLocalFS(gr.FS.Path), nil - case "mongo": - mgServer, err := ac.makeMongo() - if err != nil { - return nil, errors.Wrap(err, "failed to create mongo server") - } - conn := mongo.NewConnection(mgServer, ac.Mongo.DB, "") - return avatar.NewGridFS(conn), nil case "bolt": if err := makeDirs(path.Dir(gr.Bolt.File)); err != nil { return nil, err @@ -93,10 +84,3 @@ func (ac *AvatarCommand) makeAvatarStore(gr AvatarGroup) (avatar.Store, error) { } return nil, errors.Errorf("unsupported avatar store type %s", gr.Type) } - -func (ac *AvatarCommand) makeMongo() (result *mongo.Server, err error) { - if ac.Mongo.URL == "" { - return nil, errors.New("no mongo URL provided") - } - return mongo.NewServerWithURL(ac.Mongo.URL, 10*time.Second) -} diff --git a/backend/app/cmd/server.go b/backend/app/cmd/server.go index e5c46bb4..80293400 100644 --- a/backend/app/cmd/server.go +++ b/backend/app/cmd/server.go @@ -22,7 +22,6 @@ import ( "github.com/go-pkgz/auth/avatar" "github.com/go-pkgz/auth/provider" "github.com/go-pkgz/auth/token" - "github.com/go-pkgz/mongo" "github.com/go-pkgz/rest/cache" "github.com/umputun/remark/backend/app/migrator" @@ -41,7 +40,6 @@ type ServerCommand struct { Store StoreGroup `group:"store" namespace:"store" env-namespace:"STORE"` Avatar AvatarGroup `group:"avatar" namespace:"avatar" env-namespace:"AVATAR"` Cache CacheGroup `group:"cache" namespace:"cache" env-namespace:"CACHE"` - Mongo MongoGroup `group:"mongo" namespace:"mongo" env-namespace:"MONGO"` Admin AdminGroup `group:"admin" namespace:"admin" env-namespace:"ADMIN"` Notify NotifyGroup `group:"notify" namespace:"notify" env-namespace:"NOTIFY"` Image ImageGroup `group:"image" namespace:"image" env-namespace:"IMAGE"` @@ -89,11 +87,15 @@ type AuthGroup struct { // StoreGroup defines options group for store params type StoreGroup struct { - Type string `long:"type" env:"TYPE" description:"type of storage" choice:"bolt" choice:"mongo" default:"bolt"` + Type string `long:"type" env:"TYPE" description:"type of storage" choice:"bolt" choice:"remote" default:"bolt"` Bolt 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"` + Remote struct { + API string `long:"api" env:"API" description:"remote extension api url"` + TimeOut time.Duration `long:"timeout" env:"TIMEOUT" description:"http timeout"` + } `group:"remote" namespace:"remote" env-namespace:"REMOTE"` } // ImageGroup defines options group for store pictures @@ -134,12 +136,6 @@ type CacheGroup struct { } `group:"max" namespace:"max" env-namespace:"MAX"` } -// MongoGroup holds all mongo params, used by store, avatar and cache -type MongoGroup struct { - URL string `long:"url" env:"URL" description:"mongo url"` - DB string `long:"db" env:"DB" default:"remark42" description:"mongo database"` -} - // AdminGroup defines options group for admin params type AdminGroup struct { Type string `long:"type" env:"TYPE" description:"type of admin store" choice:"shared" choice:"mongo" default:"shared"` @@ -436,13 +432,13 @@ func (s *ServerCommand) makeAvatarStore() (avatar.Store, error) { return nil, err } return avatar.NewLocalFS(s.Avatar.FS.Path), nil - case "mongo": - mgServer, err := s.makeMongo() - if err != nil { - return nil, errors.Wrap(err, "failed to create mongo server") - } - conn := mongo.NewConnection(mgServer, s.Mongo.DB, "") - return avatar.NewGridFS(conn), nil + // case "mongo": + // mgServer, err := s.makeMongo() + // if err != nil { + // return nil, errors.Wrap(err, "failed to create mongo server") + // } + // conn := mongo.NewConnection(mgServer, s.Mongo.DB, "") + // return avatar.NewGridFS(conn), nil case "bolt": if err := makeDirs(path.Dir(s.Avatar.Bolt.File)); err != nil { return nil, err @@ -485,13 +481,13 @@ func (s *ServerCommand) makeAdminStore() (admin.Store, error) { } } return admin.NewStaticStore(s.SharedSecret, s.Admin.Shared.Admins, s.Admin.Shared.Email), nil - case "mongo": - mgServer, e := s.makeMongo() - if e != nil { - return nil, errors.Wrap(e, "failed to create mongo server") - } - conn := mongo.NewConnection(mgServer, s.Mongo.DB, "admin") - return admin.NewMongoStore(conn, s.SharedSecret), nil + // case "mongo": + // mgServer, e := s.makeMongo() + // if e != nil { + // return nil, errors.Wrap(e, "failed to create mongo server") + // } + // conn := mongo.NewConnection(mgServer, s.Mongo.DB, "admin") + // return admin.NewMongoStore(conn, s.SharedSecret), nil default: return nil, errors.Errorf("unsupported admin store type %s", s.Admin.Type) } @@ -517,13 +513,6 @@ func (s *ServerCommand) makeCache() (cache.LoadingCache, error) { return nil, errors.Errorf("unsupported cache type %s", s.Cache.Type) } -func (s *ServerCommand) makeMongo() (result *mongo.Server, err error) { - if s.Mongo.URL == "" { - return nil, errors.New("no mongo URL provided") - } - return mongo.NewServerWithURL(s.Mongo.URL, 10*time.Second) -} - func (s *ServerCommand) addAuthProviders(authenticator *auth.Service) { providers := 0 diff --git a/backend/app/cmd/server_test.go b/backend/app/cmd/server_test.go index 3f0a60c5..344225be 100644 --- a/backend/app/cmd/server_test.go +++ b/backend/app/cmd/server_test.go @@ -55,7 +55,9 @@ func TestServerApp(t *testing.T) { body, _ = ioutil.ReadAll(resp.Body) t.Log(string(body)) - assert.Equal(t, "admin@demo.remark42.com", app.dataService.AdminStore.Email(""), "default admin email") + email, err := app.dataService.AdminStore.Email("") + assert.NoError(t, err) + assert.Equal(t, "admin@demo.remark42.com", email, "default admin email") app.Wait() } diff --git a/backend/app/rest/api/rest.go b/backend/app/rest/api/rest.go index b5e1c307..30d037d7 100644 --- a/backend/app/rest/api/rest.go +++ b/backend/app/rest/api/rest.go @@ -377,6 +377,9 @@ func (s *Rest) updateLimiter() float64 { func (s *Rest) configCtrl(w http.ResponseWriter, r *http.Request) { siteID := r.URL.Query().Get("site") + admins, _ := s.DataService.AdminStore.Admins(siteID) + emails, _ := s.DataService.AdminStore.Email(siteID) + cnf := struct { Version string `json:"version"` EditDuration int `json:"edit_duration"` @@ -393,8 +396,8 @@ func (s *Rest) configCtrl(w http.ResponseWriter, r *http.Request) { Version: s.Version, EditDuration: int(s.DataService.EditDuration.Seconds()), MaxCommentSize: s.DataService.MaxCommentSize, - Admins: s.DataService.AdminStore.Admins(siteID), - AdminEmail: s.DataService.AdminStore.Email(siteID), + Admins: admins, + AdminEmail: emails, LowScore: s.ScoreThresholds.Low, CriticalScore: s.ScoreThresholds.Critical, PositiveScore: s.DataService.PositiveScore, diff --git a/backend/app/store/admin/admin.go b/backend/app/store/admin/admin.go index d0b50692..8a6fc669 100644 --- a/backend/app/store/admin/admin.go +++ b/backend/app/store/admin/admin.go @@ -10,25 +10,17 @@ import ( // Store defines interface returning admins info for given site type Store interface { Key() (key string, err error) - Admins(siteID string) (ids []string) - Email(siteID string) (email string) + Admins(siteID string) (ids []string, err error) + Email(siteID string) (email string, err error) } -// StaticStore implements keys.Store with a single, predefined key +// StaticStore implements keys.Store with a single set of admins and email for all sites type StaticStore struct { admins []string email string key string } -// Key returns static key for all sites, allows empty site -func (s *StaticStore) Key() (key string, err error) { - if s.key == "" { - return "", errors.New("empty key for static key store") - } - return s.key, nil -} - // NewStaticStore makes StaticStore instance with given key func NewStaticStore(key string, admins []string, email string) *StaticStore { log.Printf("[DEBUG] admin users %+v, email %s", admins, email) @@ -40,12 +32,20 @@ func NewStaticKeyStore(key string) *StaticStore { return &StaticStore{key: key, admins: []string{}, email: ""} } +// Key returns static key, same for all sites +func (s *StaticStore) Key() (key string, err error) { + if s.key == "" { + return "", errors.New("empty key for static key store") + } + return s.key, nil +} + // Admins returns static list of admin's ids, the same for all sites -func (s *StaticStore) Admins(string) (ids []string) { - return s.admins +func (s *StaticStore) Admins(string) (ids []string, err error) { + return s.admins, nil } // Email gets static email address -func (s *StaticStore) Email(string) (email string) { - return s.email +func (s *StaticStore) Email(string) (email string, err error) { + return s.email, nil } diff --git a/backend/app/store/admin/admin_test.go b/backend/app/store/admin/admin_test.go index 2d587f3a..6ce45178 100644 --- a/backend/app/store/admin/admin_test.go +++ b/backend/app/store/admin/admin_test.go @@ -3,10 +3,7 @@ package admin import ( "testing" - "github.com/globalsign/mgo" - "github.com/go-pkgz/mongo" "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" ) func TestStaticStore_Get(t *testing.T) { @@ -16,48 +13,11 @@ func TestStaticStore_Get(t *testing.T) { assert.NoError(t, err, "valid store") assert.Equal(t, "key123", k, "valid site") - a := ks.Admins("any") + a, err := ks.Admins("any") + assert.NoError(t, err) assert.Equal(t, []string{"123", "xyz"}, a) - email := ks.Email("blah") + email, err := ks.Email("blah") + assert.NoError(t, err) assert.Equal(t, "aa@example.com", email) } - -func TestMongoStore_Get(t *testing.T) { - conn, err := mongo.MakeTestConnection(t) - require.NoError(t, err) - var ms Store = NewMongoStore(conn, "secret") - - recs := []mongoRec{ - {"site1", []string{"i11", "i12"}, "e1"}, - {"site2", []string{"i21", "i22"}, "e2"}, - } - err = conn.WithCollection(func(coll *mgo.Collection) error { - if e1 := coll.Insert(recs[0]); e1 != nil { - return e1 - } - return coll.Insert(recs[1]) - }) - require.NoError(t, err) - - admins := ms.Admins("site1") - assert.Equal(t, []string{"i11", "i12"}, admins) - email := ms.Email("site1") - assert.Equal(t, "e1", email) - key, err := ms.Key() - assert.NoError(t, err) - assert.Equal(t, "secret", key) - - admins = ms.Admins("site2") - assert.Equal(t, []string{"i21", "i22"}, admins) - email = ms.Email("site2") - assert.Equal(t, "e2", email) - key, err = ms.Key() - assert.NoError(t, err) - assert.Equal(t, "secret", key) - - admins = ms.Admins("no-site-in-db") - assert.Equal(t, []string{}, admins) - email = ms.Email("no-site-in-db") - assert.Equal(t, "", email) -} diff --git a/backend/app/store/admin/mongo.go b/backend/app/store/admin/mongo.go deleted file mode 100644 index 1f19bc32..00000000 --- a/backend/app/store/admin/mongo.go +++ /dev/null @@ -1,56 +0,0 @@ -package admin - -import ( - "github.com/globalsign/mgo" - "github.com/globalsign/mgo/bson" - log "github.com/go-pkgz/lgr" - - "github.com/go-pkgz/mongo" -) - -// MongoStore implements admin.Store with mongo backend -type MongoStore struct { - connection *mongo.Connection - key string -} - -type mongoRec struct { - SiteID string `bson:"site"` - IDs []string `bson:"admin_ids"` - Email string `bson:"admin_email"` -} - -// NewMongoStore makes admin Store for mongo's connection -func NewMongoStore(conn *mongo.Connection, key string) *MongoStore { - log.Printf("[DEBUG] make mongo admin store with %+v", conn) - return &MongoStore{connection: conn, key: key} -} - -// Key executes find by siteID and returns substructure with secret key -func (m *MongoStore) Key() (key string, err error) { - return m.key, nil -} - -// Admins executes find by siteID and returns admins ids -func (m *MongoStore) Admins(siteID string) (ids []string) { - resp := mongoRec{} - err := m.connection.WithCollection(func(coll *mgo.Collection) error { - return coll.Find(bson.M{"site": siteID}).One(&resp) - }) - if err != nil { - return []string{} - } - return resp.IDs -} - -// Email executes find by siteID and returns admin's email -func (m *MongoStore) Email(siteID string) (email string) { - resp := mongoRec{} - err := m.connection.WithCollection(func(coll *mgo.Collection) error { - return coll.Find(bson.M{"site": siteID}).One(&resp) - }) - if err != nil { - return "" - } - return resp.Email -} diff --git a/backend/app/store/admin/remote.go b/backend/app/store/admin/remote.go new file mode 100644 index 00000000..1ba1805c --- /dev/null +++ b/backend/app/store/admin/remote.go @@ -0,0 +1,55 @@ +/* + * 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 admin + +import ( + "encoding/json" + + "github.com/umputun/remark/backend/app/store/remote" +) + +// Remote implements remote engine and delegates all Calls to remote http server +type Remote struct { + remote.Client +} + +// Key returns the key, same for all sites +func (r *Remote) Key() (key string, err error) { + resp, err := r.Call("admin.key") + if err != nil { + return "", err + } + + err = json.Unmarshal(*resp.Result, &key) + return key, err +} + +// Admins returns list of admin's ids for given site +func (r *Remote) Admins(siteID string) (ids []string, err error) { + resp, err := r.Call("admin.admins", siteID) + if err != nil { + return []string{}, err + } + + if err = json.Unmarshal(*resp.Result, &ids); err != nil { + return []string{}, err + } + return ids, nil +} + +// Email gets email address for given site +func (r *Remote) Email(siteID string) (email string, err error) { + resp, err := r.Call("admin.email", siteID) + if err != nil { + return "", err + } + + if err = json.Unmarshal(*resp.Result, &email); err != nil { + return "", err + } + return email, nil +} diff --git a/backend/app/store/admin/remote_test.go b/backend/app/store/admin/remote_test.go new file mode 100644 index 00000000..d1940f08 --- /dev/null +++ b/backend/app/store/admin/remote_test.go @@ -0,0 +1,74 @@ +/* + * 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 admin + +import ( + "fmt" + "io/ioutil" + "net/http" + "net/http/httptest" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + "github.com/umputun/remark/backend/app/store/remote" +) + +func TestRemote_Key(t *testing.T) { + ts := testServer(t, `{"method":"admin.key","params":null,"id":1}`, + `{"result":"12345","id":1}`) + defer ts.Close() + c := Remote{Client: remote.Client{API: ts.URL, Client: http.Client{}}} + + var a Store = &c + _ = a + + res, err := c.Key() + assert.NoError(t, err) + assert.Equal(t, "12345", res) + t.Logf("%v %T", res, res) +} + +func TestRemote_Admins(t *testing.T) { + ts := testServer(t, `{"method":"admin.admins","params":["site-1"],"id":1}`, + `{"result":["id1","id2"],"id":1}`) + defer ts.Close() + c := Remote{Client: remote.Client{API: ts.URL, Client: http.Client{}}} + + var a Store = &c + _ = a + + res, err := c.Admins("site-1") + assert.NoError(t, err) + assert.Equal(t, []string{"id1", "id2"}, res) + t.Logf("%v %T", res, res) +} + +func TestRemote_Email(t *testing.T) { + ts := testServer(t, `{"method":"admin.email","params":["site-1"],"id":1}`, + `{"result":"bbb@example.com","id":1}`) + defer ts.Close() + c := Remote{Client: remote.Client{API: ts.URL, Client: http.Client{}}} + + var a Store = &c + _ = a + + res, err := c.Email("site-1") + assert.NoError(t, err) + assert.Equal(t, "bbb@example.com", res) + t.Logf("%v %T", res, res) +} +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/store/engine/bolt.go b/backend/app/store/engine/bolt.go index 2fc7e02f..f94660c9 100644 --- a/backend/app/store/engine/bolt.go +++ b/backend/app/store/engine/bolt.go @@ -4,11 +4,11 @@ import ( "bytes" "encoding/json" "fmt" - "log" "strings" "time" bolt "github.com/coreos/bbolt" + log "github.com/go-pkgz/lgr" "github.com/hashicorp/go-multierror" "github.com/pkg/errors" diff --git a/backend/app/store/engine/remote_test.go b/backend/app/store/engine/remote_test.go index 965367ea..7ac33254 100644 --- a/backend/app/store/engine/remote_test.go +++ b/backend/app/store/engine/remote_test.go @@ -16,7 +16,7 @@ import ( "github.com/umputun/remark/backend/app/store/remote" ) -func TestClient_Create(t *testing.T) { +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() @@ -32,7 +32,7 @@ func TestClient_Create(t *testing.T) { t.Logf("%v %T", res, res) } -func TestClient_Get(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 := Remote{Client: remote.Client{API: ts.URL, Client: http.Client{}}} @@ -44,7 +44,7 @@ func TestClient_Get(t *testing.T) { t.Logf("%v %T", res, res) } -func TestClient_GetWithErrorResult(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 := Remote{Client: remote.Client{API: ts.URL, Client: http.Client{}}} @@ -54,7 +54,7 @@ func TestClient_GetWithErrorResult(t *testing.T) { assert.EqualError(t, err, "failed") } -func TestClient_GetWithErrorDecode(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 := Remote{Client: remote.Client{API: ts.URL, Client: http.Client{}}} @@ -64,7 +64,7 @@ func TestClient_GetWithErrorDecode(t *testing.T) { assert.EqualError(t, err, "failed to decode response for store.get: EOF") } -func TestClient_GetWithErrorRemote(t *testing.T) { +func TestRemote_GetWithErrorRemote(t *testing.T) { c := Remote{Client: remote.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"} @@ -73,7 +73,7 @@ func TestClient_GetWithErrorRemote(t *testing.T) { assert.True(t, strings.Contains(err.Error(), "remote call failed for store.get:"), err.Error()) } -func TestClient_FailedStatus(t *testing.T) { +func TestRemote_FailedStatus(t *testing.T) { ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { body, err := ioutil.ReadAll(r.Body) require.NoError(t, err) @@ -88,7 +88,7 @@ func TestClient_FailedStatus(t *testing.T) { assert.EqualError(t, err, "bad status 400 for store.get") } -func TestClient_Update(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 := Remote{Client: remote.Client{API: ts.URL, Client: http.Client{}}} @@ -99,7 +99,7 @@ func TestClient_Update(t *testing.T) { } -func TestClient_Find(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 := Remote{Client: remote.Client{API: ts.URL, Client: http.Client{}}} @@ -109,7 +109,7 @@ func TestClient_Find(t *testing.T) { assert.Equal(t, []store.Comment{{Text: "1"}, {Text: "2"}}, res) } -func TestClient_Info(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 := Remote{Client: remote.Client{API: ts.URL, Client: http.Client{}}} @@ -120,7 +120,7 @@ func TestClient_Info(t *testing.T) { assert.Equal(t, []store.PostInfo{{URL: "u1", Count: 22}, {URL: "u2", Count: 33}}, res) } -func TestClient_Flag(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 := Remote{Client: remote.Client{API: ts.URL, Client: http.Client{}}} @@ -130,7 +130,7 @@ func TestClient_Flag(t *testing.T) { assert.Equal(t, false, res) } -func TestClient_ListFlag(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 := Remote{Client: remote.Client{API: ts.URL, Client: http.Client{}}} @@ -139,7 +139,7 @@ func TestClient_ListFlag(t *testing.T) { assert.Equal(t, []interface{}{map[string]interface{}{"ID": "id1"}, map[string]interface{}{"ID": "id2"}}, res) } -func TestClient_Count(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 := Remote{Client: remote.Client{API: ts.URL, Client: http.Client{}}} @@ -149,7 +149,7 @@ func TestClient_Count(t *testing.T) { assert.Equal(t, 11, res) } -func TestClient_Delete(t *testing.T) { +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() @@ -159,7 +159,7 @@ func TestClient_Delete(t *testing.T) { assert.NoError(t, err) } -func TestClient_Close(t *testing.T) { +func TestRemote_Close(t *testing.T) { ts := testServer(t, `{"method":"store.close","params":null,"id":1}`, `{}`) defer ts.Close() c := Remote{Client: remote.Client{API: ts.URL, Client: http.Client{}}} diff --git a/backend/app/store/service/service.go b/backend/app/store/service/service.go index 376e92d4..5a537bc6 100644 --- a/backend/app/store/service/service.go +++ b/backend/app/store/service/service.go @@ -462,7 +462,11 @@ func (s *DataStore) ValidateComment(c *store.Comment) error { // IsAdmin checks if usesID in the list of admins func (s *DataStore) IsAdmin(siteID string, userID string) bool { - for _, a := range s.AdminStore.Admins(siteID) { + admins, err := s.AdminStore.Admins(siteID) + if err != nil { + return false + } + for _, a := range admins { if a == userID { return true }