Merge branch 'master' of github.com:umputun/remark
This commit is contained in:
@@ -9,6 +9,7 @@ package accessor
|
||||
import (
|
||||
log "github.com/go-pkgz/lgr"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/umputun/remark/backend/app/store/admin"
|
||||
)
|
||||
|
||||
// MemAdmin implements admin.Store with memory backend
|
||||
@@ -19,10 +20,11 @@ type MemAdmin struct {
|
||||
|
||||
// AdminRec is a records per site with all admin info in
|
||||
type AdminRec struct {
|
||||
SiteID string
|
||||
IDs []string // admin ids
|
||||
Email string // admin email
|
||||
Enabled bool // site enabled
|
||||
SiteID string
|
||||
IDs []string // admin ids
|
||||
Email string // admin email
|
||||
Enabled bool // site enabled
|
||||
CountCreated int64 // number of created posts
|
||||
}
|
||||
|
||||
// NewMemAdminStore makes admin Store in memory
|
||||
@@ -65,6 +67,19 @@ func (m *MemAdmin) Enabled(siteID string) (ok bool, err error) {
|
||||
return resp.Enabled, nil
|
||||
}
|
||||
|
||||
// OnEvent reacts on events from updates, created, delete and vote
|
||||
func (m *MemAdmin) OnEvent(siteID string, ev admin.EventType) error {
|
||||
resp, ok := m.data[siteID]
|
||||
if !ok {
|
||||
return errors.Errorf("site %s not found", siteID)
|
||||
}
|
||||
if ev == admin.EvCreate {
|
||||
resp.CountCreated += 1 // not a good idea, just for demo
|
||||
m.data[siteID] = resp
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Set admin data for siteID
|
||||
func (m *MemAdmin) Set(siteID string, arec AdminRec) {
|
||||
m.data[siteID] = arec
|
||||
|
||||
@@ -20,9 +20,9 @@ func TestMemAdmin_Get(t *testing.T) {
|
||||
var ms admin.Store = adm
|
||||
|
||||
adm.data = map[string]AdminRec{
|
||||
"site1": {"site1", []string{"i11", "i12"}, "e1", true},
|
||||
"site2": {"site2", []string{"i21", "i22"}, "e2", true},
|
||||
"site3": {"site3", []string{"i21", "i22"}, "e3", false},
|
||||
"site1": {"site1", []string{"i11", "i12"}, "e1", true, 0},
|
||||
"site2": {"site2", []string{"i21", "i22"}, "e2", true, 0},
|
||||
"site3": {"site3", []string{"i21", "i22"}, "e3", false, 0},
|
||||
}
|
||||
|
||||
admins, err := ms.Admins("site1")
|
||||
|
||||
@@ -46,6 +46,7 @@ func (s *RPC) addHandlers() {
|
||||
"admins": s.admAdminsHndl,
|
||||
"email": s.admEmailHndl,
|
||||
"enabled": s.admEnabledHndl,
|
||||
"event": s.admEventHndl,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -196,3 +197,25 @@ func (s *RPC) admEnabledHndl(id uint64, params json.RawMessage) (rr jrpc.Respons
|
||||
}
|
||||
return jrpc.EncodeResponse(id, ok, err)
|
||||
}
|
||||
|
||||
// onEvent returns nothing, callback to OnEvent
|
||||
func (s *RPC) admEventHndl(id uint64, params json.RawMessage) (rr jrpc.Response) {
|
||||
var siteID string
|
||||
ps := []interface{}{}
|
||||
if err := json.Unmarshal(params, &ps); err != nil {
|
||||
return jrpc.Response{Error: err.Error()}
|
||||
}
|
||||
siteID, ok := ps[0].(string)
|
||||
if !ok {
|
||||
return jrpc.Response{Error: "wrong siteID type"}
|
||||
}
|
||||
evType, ok := ps[1].(float64)
|
||||
if !ok {
|
||||
return jrpc.Response{Error: "wrong event type"}
|
||||
}
|
||||
err := s.adm.OnEvent(siteID, admin.EventType(evType))
|
||||
if err != nil {
|
||||
return jrpc.Response{Error: err.Error()}
|
||||
}
|
||||
return jrpc.EncodeResponse(id, nil, err)
|
||||
}
|
||||
|
||||
@@ -311,6 +311,20 @@ func TestRPC_admEnabledHndl(t *testing.T) {
|
||||
assert.Equal(t, false, ok)
|
||||
}
|
||||
|
||||
|
||||
func TestRPC_admEventHndl(t *testing.T) {
|
||||
_, port, teardown := prepTestStore(t)
|
||||
defer teardown()
|
||||
api := fmt.Sprintf("http://localhost:%d/test", port)
|
||||
|
||||
ra := admin.RPC{Client: jrpc.Client{API: api, Client: http.Client{Timeout: 1 * time.Second}}}
|
||||
err := ra.OnEvent("bad site", admin.EvCreate)
|
||||
assert.EqualError(t, err, "site bad site not found")
|
||||
|
||||
err = ra.OnEvent("test-site", admin.EvCreate)
|
||||
assert.NoError(t, err)
|
||||
}
|
||||
|
||||
func prepTestStore(t *testing.T) (s *RPC, port int, teardown func()) {
|
||||
port = 40000 + int(rand.Int31n(10000))
|
||||
|
||||
|
||||
@@ -388,13 +388,12 @@ func TestServerAuthHooks(t *testing.T) {
|
||||
defer resp.Body.Close()
|
||||
assert.Equal(t, http.StatusCreated, resp.StatusCode, "non-blocked user able to post")
|
||||
|
||||
time.Sleep(1200 * time.Millisecond) // prevent limiter to be triggered
|
||||
// add comment with no-aud claim
|
||||
claimsNoAud := claims
|
||||
claims.Audience = ""
|
||||
claimsNoAud.Audience = ""
|
||||
tkNoAud, err := tkService.Token(claimsNoAud)
|
||||
require.NoError(t, err)
|
||||
t.Log(tkNoAud)
|
||||
t.Logf("no-aud claims: %s", tkNoAud)
|
||||
req, err = http.NewRequest("POST", fmt.Sprintf("http://localhost:%d/api/v1/comment", port),
|
||||
strings.NewReader(`{"text": "test 123", "locator":{"url": "https://radio-t.com/p/2018/12/29/podcast-631/",
|
||||
"site": "remark"}}`))
|
||||
@@ -403,7 +402,9 @@ func TestServerAuthHooks(t *testing.T) {
|
||||
resp, err = client.Do(req)
|
||||
require.NoError(t, err)
|
||||
defer resp.Body.Close()
|
||||
assert.Equal(t, http.StatusUnauthorized, resp.StatusCode, "user without aud claim rejected")
|
||||
body, err := ioutil.ReadAll(resp.Body)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, http.StatusUnauthorized, resp.StatusCode, "user without aud claim rejected, \n"+tkNoAud+"\n"+string(body))
|
||||
|
||||
// block user dev as admin
|
||||
req, e := http.NewRequest(http.MethodPut,
|
||||
@@ -426,7 +427,10 @@ func TestServerAuthHooks(t *testing.T) {
|
||||
resp, err = client.Do(req)
|
||||
require.Nil(t, err)
|
||||
defer resp.Body.Close()
|
||||
assert.Equal(t, http.StatusUnauthorized, resp.StatusCode, "blocked user can't post")
|
||||
body, err = ioutil.ReadAll(resp.Body)
|
||||
require.NoError(t, err)
|
||||
assert.True(t, resp.StatusCode == http.StatusForbidden || resp.StatusCode == http.StatusUnauthorized,
|
||||
"blocked user can't post, \n"+tk+"\n"+string(body))
|
||||
|
||||
app.Wait()
|
||||
}
|
||||
|
||||
@@ -14,8 +14,20 @@ type Store interface {
|
||||
Admins(siteID string) (ids []string, err error)
|
||||
Email(siteID string) (email string, err error)
|
||||
Enabled(siteID string) (ok bool, err error)
|
||||
OnEvent(siteID string, et EventType) error
|
||||
}
|
||||
|
||||
// EventType indicates type of the event
|
||||
type EventType int
|
||||
|
||||
// enum of all event types
|
||||
const (
|
||||
EvCreate EventType = iota
|
||||
EvDelete
|
||||
EvUpdate
|
||||
EvVote
|
||||
)
|
||||
|
||||
// StaticStore implements keys.Store with a single set of admins and email for all sites
|
||||
type StaticStore struct {
|
||||
admins []string
|
||||
@@ -65,3 +77,6 @@ func (s *StaticStore) Enabled(site string) (ok bool, err error) {
|
||||
}
|
||||
return false, nil
|
||||
}
|
||||
|
||||
// OnEvent doesn nothing for StaticStore
|
||||
func (s *StaticStore) OnEvent(siteID string, et EventType) error { return nil }
|
||||
|
||||
@@ -66,3 +66,12 @@ func (r *RPC) Enabled(siteID string) (ok bool, err error) {
|
||||
}
|
||||
return ok, nil
|
||||
}
|
||||
|
||||
// OnEvent reacts (register) events about data modification
|
||||
func (r *RPC) OnEvent(siteID string, et EventType) error {
|
||||
_, err := r.Call("admin.event", siteID, et)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -78,6 +78,18 @@ func TestRemote_Enables(t *testing.T) {
|
||||
t.Logf("%v %T", res, res)
|
||||
}
|
||||
|
||||
func TestRemote_OnEvent(t *testing.T) {
|
||||
ts := testServer(t, `{"method":"admin.event","params":["site-1",2],"id":1}`, `{"id":1}`)
|
||||
defer ts.Close()
|
||||
c := RPC{Client: jrpc.Client{API: ts.URL, Client: http.Client{}}}
|
||||
|
||||
var a Store = &c
|
||||
_ = a
|
||||
|
||||
err := c.OnEvent("site-1", EvUpdate)
|
||||
assert.NoError(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)
|
||||
|
||||
@@ -102,6 +102,10 @@ func (s *DataStore) Create(comment store.Comment) (commentID string, err error)
|
||||
}()
|
||||
|
||||
s.submitImages(comment)
|
||||
if e := s.AdminStore.OnEvent(comment.Locator.SiteID, admin.EvCreate); e != nil {
|
||||
log.Printf("[WARN] failed to send create event, %s", e)
|
||||
}
|
||||
|
||||
return s.Engine.Create(comment)
|
||||
}
|
||||
|
||||
@@ -306,6 +310,10 @@ func (s *DataStore) Vote(req VoteReq) (comment store.Comment, err error) {
|
||||
}
|
||||
}
|
||||
|
||||
if e := s.AdminStore.OnEvent(comment.Locator.SiteID, admin.EvVote); e != nil {
|
||||
log.Printf("[WARN] failed to send vote event, %s", e)
|
||||
}
|
||||
|
||||
comment.Controversy = s.controversy(s.upsAndDowns(comment))
|
||||
comment.Locator = req.Locator
|
||||
return comment, s.Engine.Update(comment)
|
||||
@@ -368,6 +376,9 @@ func (s *DataStore) EditComment(locator store.Locator, commentID string, req Edi
|
||||
}
|
||||
|
||||
if req.Delete { // delete request
|
||||
if e := s.AdminStore.OnEvent(comment.Locator.SiteID, admin.EvDelete); e != nil {
|
||||
log.Printf("[WARN] failed to send delete event, %s", e)
|
||||
}
|
||||
comment.Deleted = true
|
||||
delReq := engine.DeleteRequest{Locator: locator, CommentID: commentID, DeleteMode: store.SoftDelete}
|
||||
return comment, s.Engine.Delete(delReq)
|
||||
@@ -386,6 +397,10 @@ func (s *DataStore) EditComment(locator store.Locator, commentID string, req Edi
|
||||
comment.Locator = locator
|
||||
comment.Sanitize()
|
||||
|
||||
if e := s.AdminStore.OnEvent(comment.Locator.SiteID, admin.EvUpdate); e != nil {
|
||||
log.Printf("[WARN] failed to send update event, %s", e)
|
||||
}
|
||||
|
||||
err = s.Engine.Update(comment)
|
||||
return comment, err
|
||||
}
|
||||
@@ -608,6 +623,9 @@ func (s *DataStore) Info(locator store.Locator, readonlyAge int) (store.PostInfo
|
||||
|
||||
// Delete comment by id
|
||||
func (s *DataStore) Delete(locator store.Locator, commentID string, mode store.DeleteMode) error {
|
||||
if e := s.AdminStore.OnEvent(locator.SiteID, admin.EvDelete); e != nil {
|
||||
log.Printf("[WARN] failed to send delete event, %s", e)
|
||||
}
|
||||
req := engine.DeleteRequest{Locator: locator, CommentID: commentID, DeleteMode: mode}
|
||||
return s.Engine.Delete(req)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user