Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
440beb3303 | ||
|
|
f7de26ef4b | ||
|
|
6250f2e52e | ||
|
|
1cc09a33f6 | ||
|
|
5a4e84550d | ||
|
|
8f0b6a6a59 | ||
|
|
f4a616e2de | ||
|
|
1475060555 | ||
|
|
1761995482 | ||
|
|
7aeec9535f | ||
|
|
32503395ea | ||
|
|
e82a8f9824 |
@@ -266,7 +266,7 @@ Admins/moderators should be defined in `docker-compose.yml` as a list of user ID
|
||||
|
||||
```
|
||||
environment:
|
||||
- ADMIN=github_ef0f706a79cc24b17bbbb374cd234a691a034128,github_dae9983158e9e5e127ef2b87a411ef13c891e9e5
|
||||
- ADMIN_SHARED_ID=github_ef0f706a79cc24b17bbbb374cd234a691a034128,github_dae9983158e9e5e127ef2b87a411ef13c891e9e5
|
||||
```
|
||||
|
||||
To get user id just login and click on your username or any other user you want to promote to admins.
|
||||
|
||||
Generated
+6
-6
@@ -112,7 +112,7 @@
|
||||
version = "v1.0.0"
|
||||
|
||||
[[projects]]
|
||||
digest = "1:d31c72099b9e195785933d11441bc92cce9b2c421f8fca712a7eaf36a8e55a47"
|
||||
digest = "1:38940db39a8ee330d3e7f68ab4c298575462f5986ae2bb0cafa86f095e9d32ad"
|
||||
name = "github.com/go-pkgz/auth"
|
||||
packages = [
|
||||
".",
|
||||
@@ -123,16 +123,16 @@
|
||||
"token",
|
||||
]
|
||||
pruneopts = "UT"
|
||||
revision = "92256b685196d26a346dfbedea12898fe589b0eb"
|
||||
version = "v0.4.0"
|
||||
revision = "7597c083287c33ba5362687a7815a032fd92c418"
|
||||
version = "v0.4.1"
|
||||
|
||||
[[projects]]
|
||||
digest = "1:1933dabfb0e07548ed9684fdef857cc6f2413bbbb74ba838a7c1f77407b8fd90"
|
||||
digest = "1:7b1f422f560b103f435f8501f854f238f1ce00d0f8146e76eacdfa71f2c8d8c0"
|
||||
name = "github.com/go-pkgz/lgr"
|
||||
packages = ["."]
|
||||
pruneopts = "UT"
|
||||
revision = "8f56fccc1a15dfe9c7f72e049fc87cc378205b97"
|
||||
version = "v0.2.2"
|
||||
revision = "dbd940fec9650260d15aa071cab658d0502ed92f"
|
||||
version = "v0.3.2"
|
||||
|
||||
[[projects]]
|
||||
digest = "1:c509e3f646c48148f2239fa66d5cd62785dfd057b5d2b46972e45b60049c19ed"
|
||||
|
||||
+1
-1
@@ -65,7 +65,7 @@ func setupLog(dbg bool) {
|
||||
log.Setup(log.Debug, log.CallerFile, log.Msec, log.LevelBraces)
|
||||
return
|
||||
}
|
||||
log.Setup(log.Msec, log.LevelBraces)
|
||||
log.Setup(log.Msec, log.LevelBraces, log.CallerPkg, log.CallerIgnore("logger"))
|
||||
}
|
||||
|
||||
// getDump reads runtime stack and returns as a string
|
||||
|
||||
@@ -58,7 +58,7 @@ func (a *admin) deleteCommentCtrl(w http.ResponseWriter, r *http.Request) {
|
||||
rest.SendErrorJSON(w, r, http.StatusInternalServerError, err, "can't delete comment")
|
||||
return
|
||||
}
|
||||
a.cache.Flush(cache.Flusher(locator.SiteID).Scopes(locator.URL, lastCommentsScope))
|
||||
a.cache.Flush(cache.Flusher(locator.SiteID).Scopes(locator.SiteID, locator.URL, lastCommentsScope))
|
||||
render.Status(r, http.StatusOK)
|
||||
render.JSON(w, r, R.JSON{"id": id, "locator": locator})
|
||||
}
|
||||
|
||||
@@ -16,12 +16,13 @@ import (
|
||||
jwt "github.com/dgrijalva/jwt-go"
|
||||
"github.com/go-pkgz/auth/token"
|
||||
R "github.com/go-pkgz/rest"
|
||||
"github.com/umputun/remark/backend/app/store/service"
|
||||
"github.com/go-pkgz/rest/cache"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"github.com/umputun/remark/backend/app/store"
|
||||
"github.com/umputun/remark/backend/app/store/service"
|
||||
)
|
||||
|
||||
func TestAdmin_Delete(t *testing.T) {
|
||||
@@ -36,12 +37,33 @@ func TestAdmin_Delete(t *testing.T) {
|
||||
id1 := addComment(t, c1, ts)
|
||||
addComment(t, c2, ts)
|
||||
|
||||
// check last comments
|
||||
res, code := get(t, ts.URL+"/api/v1/last/2?site=radio-t")
|
||||
assert.Equal(t, 200, code)
|
||||
comments := []store.Comment{}
|
||||
err := json.Unmarshal([]byte(res), &comments)
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, 2, len(comments), "should have 2 comments")
|
||||
|
||||
// check multi count
|
||||
resp, err := post(t, ts.URL+"/api/v1/counts?site=radio-t", `["https://radio-t.com/blah","https://radio-t.com/blah2"]`)
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, http.StatusOK, resp.StatusCode)
|
||||
bb, err := ioutil.ReadAll(resp.Body)
|
||||
assert.Nil(t, err)
|
||||
j := []store.PostInfo{}
|
||||
err = json.Unmarshal(bb, &j)
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, []store.PostInfo([]store.PostInfo{{URL: "https://radio-t.com/blah", Count: 2},
|
||||
{URL: "https://radio-t.com/blah2", Count: 0}}), j)
|
||||
|
||||
// delete a comment
|
||||
client := http.Client{}
|
||||
req, err := http.NewRequest(http.MethodDelete,
|
||||
fmt.Sprintf("%s/api/v1/admin/comment/%s?site=radio-t&url=https://radio-t.com/blah", ts.URL, id1), nil)
|
||||
assert.Nil(t, err)
|
||||
req.SetBasicAuth("admin", "password")
|
||||
resp, err := client.Do(req)
|
||||
resp, err = client.Do(req)
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, 200, resp.StatusCode)
|
||||
|
||||
@@ -52,6 +74,35 @@ func TestAdmin_Delete(t *testing.T) {
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, "", cr.Text)
|
||||
assert.True(t, cr.Deleted)
|
||||
|
||||
// check last comments updated
|
||||
res, code = get(t, ts.URL+"/api/v1/last/2?site=radio-t")
|
||||
assert.Equal(t, 200, code)
|
||||
comments = []store.Comment{}
|
||||
err = json.Unmarshal([]byte(res), &comments)
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, 1, len(comments), "should have 1 comments")
|
||||
|
||||
// check count updated
|
||||
res, code = get(t, ts.URL+"/api/v1/count?site=radio-t&url=https://radio-t.com/blah")
|
||||
assert.Equal(t, 200, code)
|
||||
b := map[string]interface{}{}
|
||||
err = json.Unmarshal([]byte(res), &b)
|
||||
assert.Nil(t, err)
|
||||
t.Logf("%#v", b)
|
||||
assert.Equal(t, 1.0, b["count"], "should report 1 comments")
|
||||
|
||||
// check multi count updated
|
||||
resp, err = post(t, ts.URL+"/api/v1/counts?site=radio-t", `["https://radio-t.com/blah","https://radio-t.com/blah2"]`)
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, http.StatusOK, resp.StatusCode)
|
||||
bb, err = ioutil.ReadAll(resp.Body)
|
||||
assert.Nil(t, err)
|
||||
j = []store.PostInfo{}
|
||||
err = json.Unmarshal(bb, &j)
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, []store.PostInfo([]store.PostInfo{{URL: "https://radio-t.com/blah", Count: 1},
|
||||
{URL: "https://radio-t.com/blah2", Count: 0}}), j)
|
||||
}
|
||||
|
||||
func TestAdmin_Title(t *testing.T) {
|
||||
@@ -262,6 +313,7 @@ func TestAdmin_Block(t *testing.T) {
|
||||
assert.Equal(t, "", comments.Comments[0].Text)
|
||||
assert.True(t, comments.Comments[0].Deleted)
|
||||
|
||||
srv.Cache = &cache.Nop{} // TODO: with lru cache it won't be refreshed and invalidated for long time
|
||||
time.Sleep(50 * time.Millisecond)
|
||||
res, code = get(t, ts.URL+"/api/v1/find?site=radio-t&url=https://radio-t.com/blah&sort=+time")
|
||||
assert.Equal(t, 200, code)
|
||||
@@ -392,6 +444,33 @@ func TestAdmin_ReadOnly(t *testing.T) {
|
||||
assert.Equal(t, http.StatusCreated, resp.StatusCode)
|
||||
}
|
||||
|
||||
func TestAdmin_ReadOnlyNoComments(t *testing.T) {
|
||||
ts, srv, teardown := startupT(t)
|
||||
defer teardown()
|
||||
|
||||
client := http.Client{}
|
||||
|
||||
// set post to read-only
|
||||
req, err := http.NewRequest(http.MethodPut,
|
||||
fmt.Sprintf("%s/api/v1/admin/readonly?site=radio-t&url=https://radio-t.com/blah&ro=1", ts.URL), nil)
|
||||
assert.Nil(t, err)
|
||||
req.SetBasicAuth("admin", "password")
|
||||
resp, err := client.Do(req)
|
||||
require.Nil(t, err)
|
||||
assert.Equal(t, 200, resp.StatusCode)
|
||||
_, err = srv.DataService.Info(store.Locator{SiteID: "radio-t", URL: "https://radio-t.com/blah"}, 0)
|
||||
assert.NotNil(t, err)
|
||||
|
||||
res, code := get(t, ts.URL+"/api/v1/find?site=radio-t&url=https://radio-t.com/blah&format=tree")
|
||||
assert.Equal(t, 200, code)
|
||||
comments := commentsWithInfo{}
|
||||
err = json.Unmarshal([]byte(res), &comments)
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, 0, len(comments.Comments), "should have 0 comments")
|
||||
assert.True(t, comments.Info.ReadOnly)
|
||||
t.Logf("%+v", comments)
|
||||
}
|
||||
|
||||
func TestAdmin_ReadOnlyWithAge(t *testing.T) {
|
||||
ts, srv, teardown := startupT(t)
|
||||
defer teardown()
|
||||
|
||||
@@ -326,7 +326,8 @@ func encodeJSONWithHTML(v interface{}) ([]byte, error) {
|
||||
return buf.Bytes(), nil
|
||||
}
|
||||
|
||||
func filterComments(comments []store.Comment, fn func(c store.Comment) bool) (filtered []store.Comment) {
|
||||
func filterComments(comments []store.Comment, fn func(c store.Comment) bool) []store.Comment {
|
||||
filtered := []store.Comment{}
|
||||
for _, c := range comments {
|
||||
if fn(c) {
|
||||
filtered = append(filtered, c)
|
||||
|
||||
@@ -134,7 +134,7 @@ func (s *Rest) updateCommentCtrl(w http.ResponseWriter, r *http.Request) {
|
||||
return
|
||||
}
|
||||
|
||||
s.Cache.Flush(cache.Flusher(locator.SiteID).Scopes(locator.URL, lastCommentsScope, user.ID))
|
||||
s.Cache.Flush(cache.Flusher(locator.SiteID).Scopes(locator.SiteID, locator.URL, lastCommentsScope, user.ID))
|
||||
render.JSON(w, r, res)
|
||||
}
|
||||
|
||||
|
||||
@@ -216,21 +216,34 @@ func TestRest_UpdateDelete(t *testing.T) {
|
||||
Locator: store.Locator{SiteID: "radio-t", URL: "https://radio-t.com/blah1"}}
|
||||
id := addComment(t, c1, ts)
|
||||
|
||||
// check multi count updated
|
||||
resp, err := post(t, ts.URL+"/api/v1/counts?site=radio-t", `["https://radio-t.com/blah1","https://radio-t.com/blah2"]`)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, http.StatusOK, resp.StatusCode)
|
||||
bb, err := ioutil.ReadAll(resp.Body)
|
||||
require.NoError(t, err)
|
||||
j := []store.PostInfo{}
|
||||
err = json.Unmarshal(bb, &j)
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, []store.PostInfo([]store.PostInfo{{URL: "https://radio-t.com/blah1", Count: 1},
|
||||
{URL: "https://radio-t.com/blah2", Count: 0}}), j)
|
||||
|
||||
// delete a comment
|
||||
client := http.Client{}
|
||||
req, err := http.NewRequest(http.MethodPut, ts.URL+"/api/v1/comment/"+id+"?site=radio-t&url=https://radio-t.com/blah1",
|
||||
strings.NewReader(`{"delete": true, "summary":"removed by user"}`))
|
||||
assert.Nil(t, err)
|
||||
require.NoError(t, err)
|
||||
req.Header.Add("X-JWT", devToken)
|
||||
b, err := client.Do(req)
|
||||
assert.Nil(t, err)
|
||||
require.NoError(t, err)
|
||||
body, err := ioutil.ReadAll(b.Body)
|
||||
assert.Nil(t, err)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, 200, b.StatusCode, string(body))
|
||||
|
||||
// comments returned by update
|
||||
c2 := store.Comment{}
|
||||
err = json.Unmarshal(body, &c2)
|
||||
assert.Nil(t, err)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, id, c2.ID)
|
||||
assert.True(t, c2.Deleted)
|
||||
|
||||
@@ -243,6 +256,18 @@ func TestRest_UpdateDelete(t *testing.T) {
|
||||
assert.Equal(t, "", c3.Text)
|
||||
assert.Equal(t, "", c3.Orig)
|
||||
assert.True(t, c3.Deleted)
|
||||
|
||||
// check multi count updated
|
||||
resp, err = post(t, ts.URL+"/api/v1/counts?site=radio-t", `["https://radio-t.com/blah1","https://radio-t.com/blah2"]`)
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, http.StatusOK, resp.StatusCode)
|
||||
bb, err = ioutil.ReadAll(resp.Body)
|
||||
assert.Nil(t, err)
|
||||
j = []store.PostInfo{}
|
||||
err = json.Unmarshal(bb, &j)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, []store.PostInfo([]store.PostInfo{{URL: "https://radio-t.com/blah1", Count: 0},
|
||||
{URL: "https://radio-t.com/blah2", Count: 0}}), j)
|
||||
}
|
||||
|
||||
func TestRest_UpdateNotOwner(t *testing.T) {
|
||||
|
||||
@@ -31,13 +31,16 @@ func (s *Rest) findCommentsCtrl(w http.ResponseWriter, r *http.Request) {
|
||||
data, err := s.Cache.Get(key, func() ([]byte, error) {
|
||||
comments, e := s.DataService.Find(locator, sort)
|
||||
if e != nil {
|
||||
return nil, e
|
||||
comments = []store.Comment{} // error should clear comments and continue for post info
|
||||
}
|
||||
maskedComments := s.adminService.alterComments(comments, r)
|
||||
var b []byte
|
||||
switch r.URL.Query().Get("format") {
|
||||
case "tree":
|
||||
tree := rest.MakeTree(maskedComments, sort, s.ReadOnlyAge)
|
||||
if tree.Nodes == nil { // eliminate json nil serialization
|
||||
tree.Nodes = []*rest.Node{}
|
||||
}
|
||||
if s.DataService.IsReadOnly(locator) {
|
||||
tree.Info.ReadOnly = true
|
||||
}
|
||||
|
||||
@@ -10,6 +10,7 @@ import (
|
||||
"time"
|
||||
|
||||
R "github.com/go-pkgz/rest"
|
||||
"github.com/go-pkgz/rest/cache"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
@@ -69,8 +70,12 @@ func TestRest_Find(t *testing.T) {
|
||||
ts, _, teardown := startupT(t)
|
||||
defer teardown()
|
||||
|
||||
_, code := get(t, ts.URL+"/api/v1/find?site=radio-t&url=https://radio-t.com/blah1")
|
||||
assert.Equal(t, 400, code, "nothing in")
|
||||
res, code := get(t, ts.URL+"/api/v1/find?site=radio-t&url=https://radio-t.com/blah1")
|
||||
assert.Equal(t, 200, code)
|
||||
comments := commentsWithInfo{}
|
||||
err := json.Unmarshal([]byte(res), &comments)
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, 0, len(comments.Comments), "should have 0 comments")
|
||||
|
||||
c1 := store.Comment{Text: "test test #1", ParentID: "",
|
||||
Locator: store.Locator{SiteID: "radio-t", URL: "https://radio-t.com/blah1"}}
|
||||
@@ -83,10 +88,10 @@ func TestRest_Find(t *testing.T) {
|
||||
assert.NotEqual(t, id1, id2)
|
||||
|
||||
// get sorted by +time
|
||||
res, code := get(t, ts.URL+"/api/v1/find?site=radio-t&url=https://radio-t.com/blah1&sort=+time")
|
||||
res, code = get(t, ts.URL+"/api/v1/find?site=radio-t&url=https://radio-t.com/blah1&sort=+time")
|
||||
assert.Equal(t, 200, code)
|
||||
comments := commentsWithInfo{}
|
||||
err := json.Unmarshal([]byte(res), &comments)
|
||||
comments = commentsWithInfo{}
|
||||
err = json.Unmarshal([]byte(res), &comments)
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, 2, len(comments.Comments), "should have 2 comments")
|
||||
assert.Equal(t, id1, comments.Comments[0].ID)
|
||||
@@ -194,6 +199,10 @@ func TestRest_Last(t *testing.T) {
|
||||
ts, srv, teardown := startupT(t)
|
||||
defer teardown()
|
||||
|
||||
res, code := get(t, ts.URL+"/api/v1/last/2?site=radio-t")
|
||||
assert.Equal(t, 200, code)
|
||||
assert.Equal(t, "[]\n", res, "empty last should return empty list")
|
||||
|
||||
c1 := store.Comment{Text: "test test #1", ParentID: "p1",
|
||||
Locator: store.Locator{SiteID: "radio-t", URL: "https://radio-t.com/blah1"}}
|
||||
c2 := store.Comment{Text: "test test #2", ParentID: "p1",
|
||||
@@ -204,7 +213,7 @@ func TestRest_Last(t *testing.T) {
|
||||
id1 := addComment(t, c1, ts)
|
||||
id2 := addComment(t, c2, ts)
|
||||
|
||||
res, code := get(t, ts.URL+"/api/v1/last/2?site=radio-t")
|
||||
res, code = get(t, ts.URL+"/api/v1/last/2?site=radio-t")
|
||||
assert.Equal(t, 200, code)
|
||||
comments := []store.Comment{}
|
||||
err := json.Unmarshal([]byte(res), &comments)
|
||||
@@ -227,6 +236,7 @@ func TestRest_Last(t *testing.T) {
|
||||
|
||||
err = srv.DataService.Delete(store.Locator{SiteID: "radio-t", URL: "https://radio-t.com/blah1"}, id1, store.SoftDelete)
|
||||
assert.Nil(t, err)
|
||||
srv.Cache.Flush(cache.FlusherRequest{})
|
||||
res, code = get(t, ts.URL+"/api/v1/last/5?site=radio-t")
|
||||
assert.Equal(t, 200, code)
|
||||
err = json.Unmarshal([]byte(res), &comments)
|
||||
|
||||
@@ -179,6 +179,9 @@ func startupT(t *testing.T) (ts *httptest.Server, srv *Rest, teardown func()) {
|
||||
b, err := engine.NewBoltDB(bolt.Options{}, engine.BoltSite{FileName: testDb, SiteID: "radio-t"})
|
||||
require.Nil(t, err)
|
||||
|
||||
memCache, err := cache.NewMemoryCache()
|
||||
assert.NoError(t, err)
|
||||
|
||||
adminStore := adminstore.NewStaticStore("123456", []string{"a1", "a2"}, "admin@remark-42.com")
|
||||
restrictedWordsMatcher := service.NewRestrictedWordsMatcher(service.StaticRestrictedWordsLister{Words: []string{"duck"}})
|
||||
|
||||
@@ -198,7 +201,7 @@ func startupT(t *testing.T) (ts *httptest.Server, srv *Rest, teardown func()) {
|
||||
SecretReader: token.SecretFunc(func() (string, error) { return "secret", nil }),
|
||||
AvatarStore: avatar.NewLocalFS("/tmp/ava-remark42"),
|
||||
}),
|
||||
Cache: &cache.Nop{},
|
||||
Cache: memCache,
|
||||
WebRoot: "/tmp",
|
||||
RemarkURL: "https://demo.remark42.com",
|
||||
|
||||
|
||||
@@ -111,8 +111,8 @@ func (t *Tree) proc(comments []store.Comment, node *Node, rd *recurData, parentI
|
||||
}
|
||||
|
||||
// filter returns comments for parentID
|
||||
func (t *Tree) filter(comments []store.Comment, fn func(comment store.Comment) bool) (f []store.Comment) {
|
||||
|
||||
func (t *Tree) filter(comments []store.Comment, fn func(comment store.Comment) bool) []store.Comment {
|
||||
f := []store.Comment{}
|
||||
for _, c := range comments {
|
||||
if fn(c) {
|
||||
f = append(f, c)
|
||||
|
||||
@@ -170,6 +170,8 @@ func (b *BoltDB) Find(locator store.Locator, sortFld string) (comments []store.C
|
||||
// Last returns up to max last comments for given siteID
|
||||
func (b *BoltDB) Last(siteID string, max int) (comments []store.Comment, err error) {
|
||||
|
||||
comments = []store.Comment{}
|
||||
|
||||
if max > lastLimit || max == 0 {
|
||||
max = lastLimit
|
||||
}
|
||||
|
||||
@@ -20,6 +20,10 @@ func TestBoltAdmin_Delete(t *testing.T) {
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, 2, len(res), "initially 2 comments")
|
||||
|
||||
count, err := b.Count(loc)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, 2, count, "count=2 initially")
|
||||
|
||||
err = b.Delete(loc, res[0].ID, store.SoftDelete)
|
||||
assert.Nil(t, err)
|
||||
|
||||
@@ -37,6 +41,10 @@ func TestBoltAdmin_Delete(t *testing.T) {
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, 1, len(comments), "1 in last, 1 removed")
|
||||
|
||||
count, err = b.Count(loc)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, 1, count)
|
||||
|
||||
err = b.Delete(loc, "123456", store.SoftDelete)
|
||||
assert.NotNil(t, err)
|
||||
|
||||
|
||||
+1
-1
@@ -21,7 +21,7 @@ This library provides "social login" with Github, Google, Facebook and Yandex as
|
||||
|
||||
## Install
|
||||
|
||||
`go install github.com/go-pkgz/auth`
|
||||
`go get -u github.com/go-pkgz/auth`
|
||||
|
||||
## Usage
|
||||
|
||||
|
||||
+1
-1
@@ -202,7 +202,7 @@ func (s *Service) AddProvider(name string, cid string, csecret string) {
|
||||
case "facebook":
|
||||
s.providers = append(s.providers, provider.NewService(provider.NewFacebook(p)))
|
||||
case "yandex":
|
||||
s.providers = append(s.providers, provider.NewService(provider.NewFacebook(p)))
|
||||
s.providers = append(s.providers, provider.NewService(provider.NewYandex(p)))
|
||||
case "dev":
|
||||
s.providers = append(s.providers, provider.NewService(provider.NewDev(p)))
|
||||
default:
|
||||
|
||||
+17
@@ -73,6 +73,23 @@ func (u *User) IsAdmin() bool {
|
||||
return u.BoolAttr(adminAttr)
|
||||
}
|
||||
|
||||
// SliceAttr gets slice attribute
|
||||
func (u *User) SliceAttr(key string) []string {
|
||||
r, ok := u.Attributes[key].([]string)
|
||||
if !ok {
|
||||
return []string{}
|
||||
}
|
||||
return r
|
||||
}
|
||||
|
||||
// SetSliceAttr sets boolean attribute
|
||||
func (u *User) SetSliceAttr(key string, val []string) {
|
||||
if u.Attributes == nil {
|
||||
u.Attributes = map[string]interface{}{}
|
||||
}
|
||||
u.Attributes[key] = val
|
||||
}
|
||||
|
||||
// HashID tries to hash val with hash.Hash and fallback to crc if needed
|
||||
func HashID(h hash.Hash, val string) string {
|
||||
|
||||
|
||||
+4
-3
@@ -18,7 +18,7 @@ output looks like this:
|
||||
2018/01/07 13:02:34.015 DEBUG {svc/handler.go:155 h.MyFunc2} some less important err message, file is too small`
|
||||
```
|
||||
|
||||
_Without `lgr.CallerFile` it will drop `{caller}` part_
|
||||
_Without `lgr.Caller*` it will drop `{caller}` part_
|
||||
|
||||
## details
|
||||
|
||||
@@ -33,8 +33,9 @@ _Without `lgr.CallerFile` it will drop `{caller}` part_
|
||||
`lgr.New` call accepts functional options:
|
||||
|
||||
- `lgr.Debug` - turn debug mode on. This allows messages with "DEBUG" level (filtered overwise)
|
||||
- `lgr.CallerFile` - adds the caller file info each message
|
||||
- `lgr.CallerFunc` - adds the caller function info each message
|
||||
- `lgr.CallerFile` - adds the caller file info
|
||||
- `lgr.CallerFunc` - adds the caller function info
|
||||
- `lgr.CallerPkg` - adds the caller package
|
||||
- `lgr.LevelBraces` - wraps levels with "[" and "]"
|
||||
- `lgr.Msec` - adds milliseconds to timestamp
|
||||
- `lgr.Out(io.Writer)` - sets the output writer, default `os.Stdout`
|
||||
|
||||
+46
-12
@@ -4,6 +4,7 @@ import (
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"path"
|
||||
"runtime"
|
||||
"strings"
|
||||
"sync"
|
||||
@@ -14,15 +15,18 @@ var levels = []string{"DEBUG", "INFO", "WARN", "ERROR", "PANIC", "FATAL"}
|
||||
|
||||
// Logger provided simple logger with basic support of levels. Thread safe
|
||||
type Logger struct {
|
||||
stdout, stderr io.Writer
|
||||
dbg bool
|
||||
lock sync.Mutex
|
||||
callerFile, callerFunc bool
|
||||
now nowFn
|
||||
fatal panicFn
|
||||
skipCallers int
|
||||
levelBraces bool
|
||||
msec bool
|
||||
stdout, stderr io.Writer
|
||||
dbg bool
|
||||
lock sync.Mutex
|
||||
callerFile bool
|
||||
callerFunc bool
|
||||
callerPkg bool
|
||||
ignoredPkgCallers []string
|
||||
now nowFn
|
||||
fatal panicFn
|
||||
skipCallers int
|
||||
levelBraces bool
|
||||
msec bool
|
||||
}
|
||||
|
||||
type nowFn func() time.Time
|
||||
@@ -67,15 +71,16 @@ func (l *Logger) Logf(format string, args ...interface{}) {
|
||||
bld.WriteString(l.formatLevel(lv))
|
||||
bld.WriteString(" ")
|
||||
|
||||
if l.dbg && (l.callerFile || l.callerFunc) {
|
||||
if l.callerFile || l.callerFunc || l.callerPkg {
|
||||
if pc, file, line, ok := runtime.Caller(l.skipCallers); ok {
|
||||
|
||||
funcName := ""
|
||||
funcName, fileInfo := "", ""
|
||||
|
||||
if l.callerFunc {
|
||||
funcNameElems := strings.Split(runtime.FuncForPC(pc).Name(), "/")
|
||||
funcName = funcNameElems[len(funcNameElems)-1]
|
||||
}
|
||||
fileInfo := ""
|
||||
|
||||
if l.callerFile {
|
||||
fnameElems := strings.Split(file, "/")
|
||||
fileInfo = fmt.Sprintf("%s:%d", strings.Join(fnameElems[len(fnameElems)-2:], "/"), line)
|
||||
@@ -83,6 +88,14 @@ func (l *Logger) Logf(format string, args ...interface{}) {
|
||||
fileInfo += " "
|
||||
}
|
||||
}
|
||||
// callerPkg only if no other callers
|
||||
if l.callerPkg && !l.callerFile && !l.callerFunc {
|
||||
file = l.ignoreCaller(file)
|
||||
_, fileInfo = path.Split(path.Dir(file))
|
||||
if l.callerFunc {
|
||||
fileInfo += " "
|
||||
}
|
||||
}
|
||||
srcFileInfo := fmt.Sprintf("{%s%s} ", fileInfo, funcName)
|
||||
bld.WriteString(srcFileInfo)
|
||||
}
|
||||
@@ -108,6 +121,15 @@ func (l *Logger) Logf(format string, args ...interface{}) {
|
||||
l.lock.Unlock()
|
||||
}
|
||||
|
||||
func (l *Logger) ignoreCaller(p string) string {
|
||||
for _, s := range l.ignoredPkgCallers {
|
||||
if strings.Contains(p, "/"+s+"/") {
|
||||
return strings.Replace(p, "/"+s, "", 1)
|
||||
}
|
||||
}
|
||||
return p
|
||||
}
|
||||
|
||||
func (l *Logger) formatLevel(lv string) string {
|
||||
|
||||
brace := func(b string) string {
|
||||
@@ -183,6 +205,18 @@ func CallerFunc(l *Logger) {
|
||||
l.callerFunc = true
|
||||
}
|
||||
|
||||
// CallerPkg adds caller's package name
|
||||
func CallerPkg(l *Logger) {
|
||||
l.callerPkg = true
|
||||
}
|
||||
|
||||
// CallerIgnore sets packages skipped from logging caller
|
||||
func CallerIgnore(ignores ...string) Option {
|
||||
return func(l *Logger) {
|
||||
l.ignoredPkgCallers = ignores
|
||||
}
|
||||
}
|
||||
|
||||
// LevelBraces adds [] to level
|
||||
func LevelBraces(l *Logger) {
|
||||
l.levelBraces = true
|
||||
|
||||
+28
-5
@@ -24,6 +24,15 @@
|
||||
.document_theme_dark a {
|
||||
color: #5e5eff;
|
||||
}
|
||||
|
||||
.widget {
|
||||
margin-top: 1rem;
|
||||
}
|
||||
|
||||
.widget__comments-frame {
|
||||
border: 1px dashed #aaa;
|
||||
min-width: 35rem;
|
||||
}
|
||||
</style>
|
||||
|
||||
<script>
|
||||
@@ -46,11 +55,18 @@
|
||||
<div class="container">
|
||||
<h1>Demo page</h1>
|
||||
<p>To install widgets on your website, follow the <a href="https://github.com/umputun/remark#setup-on-your-website">instructions</a>.</p>
|
||||
<p>See also</p>
|
||||
<ul>
|
||||
<li><a href="/web/last-comments.html">Last comments widget</a></li>
|
||||
<li><a href="/web/counter.html">Counter widget</a></li>
|
||||
</ul>
|
||||
<div class="widgets">
|
||||
<div class="widget widgets__widget ">
|
||||
<a class="widget__link" href="/web/last-comments.html">Last comments widget page</a><br/>
|
||||
<iframe class="widget__frame widget__comments-frame" src="/web/last-comments.html" frameborder="0"></iframe>
|
||||
</div>
|
||||
<div class="widget widgets__widget">
|
||||
<a class="widget__link" href="/web/counter.html">Counter widget page</a><br/>
|
||||
<div class="widget__frame widget__counter-frame">
|
||||
Comments count: <span class="remark42__counter"></span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<p><Button onclick="toggleTheme()">Toggle theme</Button></p>
|
||||
<div id="remark42"></div>
|
||||
</div>
|
||||
@@ -67,6 +83,13 @@
|
||||
s.type = 'text/javascript';
|
||||
(d.head || d.body).appendChild(s);
|
||||
})();
|
||||
|
||||
(function() {
|
||||
var d = document, s = d.createElement('script');
|
||||
s.src = '/web/counter.js';
|
||||
s.type = 'text/javascript';
|
||||
(d.head || d.body).appendChild(s);
|
||||
})();
|
||||
</script>
|
||||
<noscript>
|
||||
Please enable JavaScript to view the comments powered by Remark.
|
||||
|
||||
Reference in New Issue
Block a user