Admin edit (#997)
* allow admin edits without restrictions * lint: err reassignment * lint: suppress false positive * add admin-edit to readme * expose admin_edit to config controller
This commit is contained in:
@@ -189,6 +189,7 @@ _this is the recommended way to run remark42_
|
||||
| restricted-words | RESTRICTED_WORDS | | words banned in comments (can use `*`), _multi_ |
|
||||
| restricted-names | RESTRICTED_NAMES | | names prohibited to use by the user, _multi_ |
|
||||
| edit-time | EDIT_TIME | `5m` | edit window |
|
||||
| admin-edit | ADMIN_EDIT | `false` | unlimited edit for admins |
|
||||
| read-age | READONLY_AGE | | read-only age of comments, days |
|
||||
| image-proxy.http2https | IMAGE_PROXY_HTTP2HTTPS | `false` | enable http->https proxy for images |
|
||||
| image-proxy.cache-external | IMAGE_PROXY_CACHE_EXTERNAL | `false` | enable caching external images to current image storage |
|
||||
|
||||
@@ -68,6 +68,7 @@ type ServerCommand struct {
|
||||
PositiveScore bool `long:"positive-score" env:"POSITIVE_SCORE" description:"enable positive score only"`
|
||||
ReadOnlyAge int `long:"read-age" env:"READONLY_AGE" default:"0" description:"read-only age of comments, days"`
|
||||
EditDuration time.Duration `long:"edit-time" env:"EDIT_TIME" default:"5m" description:"edit window"`
|
||||
AdminEdit bool `long:"admin-edit" env:"ADMIN_EDIT" description:"unlimited edit for admins"`
|
||||
Port int `long:"port" env:"REMARK_PORT" default:"8080" description:"port"`
|
||||
WebRoot string `long:"web-root" env:"REMARK_WEB_ROOT" default:"./web" description:"web root directory"`
|
||||
UpdateLimit float64 `long:"update-limit" env:"UPDATE_LIMIT" default:"0.5" description:"updates/sec limit"`
|
||||
@@ -355,6 +356,7 @@ func (s *ServerCommand) newServerApp() (*serverApp, error) {
|
||||
dataService := &service.DataStore{
|
||||
Engine: storeEngine,
|
||||
EditDuration: s.EditDuration,
|
||||
AdminEdits: s.AdminEdit,
|
||||
AdminStore: adminStore,
|
||||
MaxCommentSize: s.MaxCommentSize,
|
||||
MaxVotes: s.MaxVotes,
|
||||
|
||||
@@ -404,6 +404,7 @@ func (s *Rest) configCtrl(w http.ResponseWriter, r *http.Request) {
|
||||
cnf := struct {
|
||||
Version string `json:"version"`
|
||||
EditDuration int `json:"edit_duration"`
|
||||
AdminEdit bool `json:"admin_edit"`
|
||||
MaxCommentSize int `json:"max_comment_size"`
|
||||
Admins []string `json:"admins"`
|
||||
AdminEmail string `json:"admin_email"`
|
||||
@@ -421,6 +422,7 @@ func (s *Rest) configCtrl(w http.ResponseWriter, r *http.Request) {
|
||||
}{
|
||||
Version: s.Version,
|
||||
EditDuration: int(s.DataService.EditDuration.Seconds()),
|
||||
AdminEdit: s.DataService.AdminEdits,
|
||||
MaxCommentSize: s.DataService.MaxCommentSize,
|
||||
Admins: admins,
|
||||
AdminEmail: emails,
|
||||
|
||||
@@ -166,6 +166,7 @@ func (s *private) updateCommentCtrl(w http.ResponseWriter, r *http.Request) {
|
||||
Orig: edit.Text,
|
||||
Summary: edit.Summary,
|
||||
Delete: edit.Delete,
|
||||
Admin: user.Admin,
|
||||
}
|
||||
|
||||
res, err := s.dataService.EditComment(locator, id, editReq)
|
||||
|
||||
@@ -550,6 +550,7 @@ func TestRest_Config(t *testing.T) {
|
||||
assert.Equal(t, 10.0, j["readonly_age"])
|
||||
assert.Equal(t, 10000.0, j["max_image_size"])
|
||||
assert.Equal(t, true, j["emoji_enabled"].(bool))
|
||||
assert.Equal(t, false, j["admin_edit"].(bool))
|
||||
}
|
||||
|
||||
func TestRest_Info(t *testing.T) {
|
||||
|
||||
@@ -36,6 +36,7 @@ type DataStore struct {
|
||||
TitleExtractor *TitleExtractor
|
||||
RestrictedWordsMatcher *RestrictedWordsMatcher
|
||||
ImageService *image.Service
|
||||
AdminEdits bool // allow admin unlimited edits
|
||||
|
||||
// granular locks
|
||||
scopedLocks struct {
|
||||
@@ -433,22 +434,35 @@ type EditRequest struct {
|
||||
Orig string
|
||||
Summary string
|
||||
Delete bool
|
||||
Admin bool
|
||||
}
|
||||
|
||||
// EditComment to edit text and update Edit info
|
||||
func (s *DataStore) EditComment(locator store.Locator, commentID string, req EditRequest) (comment store.Comment, err error) {
|
||||
comment, err = s.Engine.Get(engine.GetRequest{Locator: locator, CommentID: commentID})
|
||||
if err != nil {
|
||||
|
||||
editAllowed := func(comment store.Comment) error {
|
||||
if req.Admin && s.AdminEdits {
|
||||
return nil
|
||||
}
|
||||
|
||||
// edit allowed in editDuration window only
|
||||
if s.EditDuration > 0 && time.Now().After(comment.Timestamp.Add(s.EditDuration)) {
|
||||
return errors.Errorf("too late to edit %s", commentID)
|
||||
}
|
||||
|
||||
// edit rejected on replayed threads
|
||||
if s.HasReplies(comment) {
|
||||
return errors.Errorf("parent comment with reply can't be edited, %s", commentID)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
if comment, err = s.Engine.Get(engine.GetRequest{Locator: locator, CommentID: commentID}); err != nil {
|
||||
return comment, err
|
||||
}
|
||||
|
||||
// edit allowed in editDuration window only
|
||||
if s.EditDuration > 0 && time.Now().After(comment.Timestamp.Add(s.EditDuration)) {
|
||||
return comment, errors.Errorf("too late to edit %s", commentID)
|
||||
}
|
||||
|
||||
if s.HasReplies(comment) {
|
||||
return comment, errors.Errorf("parent comment with reply can't be edited, %s", commentID)
|
||||
if err = editAllowed(comment); err != nil { //nolint gocritic
|
||||
return comment, err
|
||||
}
|
||||
|
||||
if req.Delete { // delete request
|
||||
@@ -466,10 +480,7 @@ func (s *DataStore) EditComment(locator store.Locator, commentID string, req Edi
|
||||
|
||||
comment.Text = req.Text
|
||||
comment.Orig = req.Orig
|
||||
comment.Edit = &store.Edit{
|
||||
Timestamp: time.Now(),
|
||||
Summary: req.Summary,
|
||||
}
|
||||
comment.Edit = &store.Edit{Timestamp: time.Now(), Summary: req.Summary}
|
||||
comment.Locator = locator
|
||||
comment.Sanitize()
|
||||
|
||||
|
||||
@@ -742,6 +742,29 @@ func TestService_EditCommentReplyFailed(t *testing.T) {
|
||||
assert.EqualError(t, err, "parent comment with reply can't be edited, id-1")
|
||||
}
|
||||
|
||||
func TestService_EditCommentAdmin(t *testing.T) {
|
||||
eng, teardown := prepStoreEngine(t)
|
||||
defer teardown()
|
||||
b := DataStore{Engine: eng, EditDuration: 100 * time.Millisecond,
|
||||
AdminStore: admin.NewStaticKeyStore("secret 123"), AdminEdits: true}
|
||||
|
||||
res, err := b.Last("radio-t", 0, time.Time{}, store.User{})
|
||||
t.Logf("%+v", res[0])
|
||||
assert.NoError(t, err)
|
||||
require.Equal(t, 2, len(res))
|
||||
assert.Nil(t, res[0].Edit)
|
||||
|
||||
time.Sleep(time.Second)
|
||||
|
||||
_, err = b.EditComment(store.Locator{URL: "https://radio-t.com", SiteID: "radio-t"}, res[0].ID,
|
||||
EditRequest{Orig: "yyy", Text: "xxx", Summary: "my edit", Admin: true})
|
||||
assert.NoError(t, err)
|
||||
|
||||
_, err = b.EditComment(store.Locator{URL: "https://radio-t.com", SiteID: "radio-t"}, res[0].ID,
|
||||
EditRequest{Orig: "yyy", Text: "xxx", Summary: "my edit", Admin: false})
|
||||
assert.Error(t, err)
|
||||
}
|
||||
|
||||
func TestService_ValidateComment(t *testing.T) {
|
||||
|
||||
b := DataStore{MaxCommentSize: 2000, AdminStore: admin.NewStaticKeyStore("secret 123")}
|
||||
|
||||
Reference in New Issue
Block a user