summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorSteve Manuel <nilslice@gmail.com>2016-11-08 16:48:12 -0800
committerSteve Manuel <nilslice@gmail.com>2016-11-08 16:48:12 -0800
commitdfa5e33f99f8727d36420981156c1ba8400b17b8 (patch)
tree7404d8d2eb804770c058ffbfffc9ade99ece3a23
parente9e9ea47d8bcbdbcf91a2ad822e107d8561a9822 (diff)
adding before/after hooks to actions: save, delete, approve and reject
-rw-r--r--content/item.go39
-rw-r--r--management/editor/editor.go22
-rw-r--r--system/admin/handlers.go215
3 files changed, 264 insertions, 12 deletions
diff --git a/content/item.go b/content/item.go
index d099936..046ed04 100644
--- a/content/item.go
+++ b/content/item.go
@@ -1,5 +1,7 @@
package content
+import "net/http"
+
// Item should only be embedded into content type structs.
type Item struct {
ID int `json:"id"`
@@ -33,6 +35,26 @@ func (i *Item) SetItemID(id int) {
i.ID = id
}
+// BeforeSave is a no-op to ensure structs which embed Item implement Hookable
+func (i *Item) BeforeSave(req *http.Request) error {
+ return nil
+}
+
+// AfterSave is a no-op to ensure structs which embed Item implement Hookable
+func (i *Item) AfterSave(req *http.Request) error {
+ return nil
+}
+
+// BeforeDelete is a no-op to ensure structs which embed Item implement Hookable
+func (i *Item) BeforeDelete(req *http.Request) error {
+ return nil
+}
+
+// AfterDelete is a no-op to ensure structs which embed Item implement Hookable
+func (i *Item) AfterDelete(req *http.Request) error {
+ return nil
+}
+
// Sluggable makes a struct locatable by URL with it's own path
// As an Item implementing Sluggable, slugs may overlap. If this is an issue,
// make your content struct (or one which imbeds Item) implement Sluggable
@@ -48,3 +70,20 @@ type Sluggable interface {
type Identifiable interface {
SetItemID(int)
}
+
+// Hookable provides our user with an easy way to intercept or add functionality
+// to the different lifecycles/events a struct may encounter. Item implements
+// Hookable with no-ops so our user can override only whichever ones necessary.
+type Hookable interface {
+ BeforeSave(req *http.Request) error
+ AfterSave(req *http.Request) error
+
+ BeforeDelete(req *http.Request) error
+ AfterDelete(req *http.Request) error
+
+ BeforeApprove(req *http.Request) error
+ AfterApprove(req *http.Request) error
+
+ BeforeReject(req *http.Request) error
+ AfterReject(req *http.Request) error
+}
diff --git a/management/editor/editor.go b/management/editor/editor.go
index f76197a..73fe400 100644
--- a/management/editor/editor.go
+++ b/management/editor/editor.go
@@ -109,9 +109,10 @@ func Form(post Editable, fields ...Field) ([]byte, error) {
<div class="row external post-controls">
<div class="col s12 input-field">
+ <button class="right waves-effect waves-light btn gray reject-post" type="submit">Reject</button>
<button class="right waves-effect waves-light btn blue approve-post" type="submit">Approve</button>
</div>
- <label class="approve-details right-align col s12">This content is pending approval. By clicking 'Approve', it will be immediately published.</label>
+ <label class="approve-details right-align col s12">g approval. By clicking 'Approve', it will be immediately published.</label>
</div>
<script>
@@ -119,18 +120,18 @@ func Form(post Editable, fields ...Field) ([]byte, error) {
var form = $('form'),
save = form.find('button.save-post'),
del = form.find('button.delete-post'),
- approve = form.find('.post-controls.external'),
+ external = form.find('.post-controls.external'),
id = form.find('input[name=id]');
// hide if this is a new post, or a non-post editor page
if (id.val() === '-1' || form.attr('action') !== '/admin/edit') {
del.hide();
- approve.hide();
+ external.hide();
}
// hide approval if not on a pending content item
if (getParam('status') !== 'pending') {
- approve.hide();
+ external.hide();
}
save.on('click', function(e) {
@@ -155,7 +156,7 @@ func Form(post Editable, fields ...Field) ([]byte, error) {
}
});
- approve.find('button').on('click', function(e) {
+ external.find('button.approve-post').on('click', function(e) {
e.preventDefault();
var action = form.attr('action');
action = action + '/approve';
@@ -163,6 +164,17 @@ func Form(post Editable, fields ...Field) ([]byte, error) {
form.submit();
});
+
+ external.find('button.reject-post').on('click', function(e) {
+ e.preventDefault();
+ var action = form.attr('action');
+ action = action + '/delete?reject=true';
+ form.attr('action', action);
+
+ if (confirm("[Ponzu] Please confirm:\n\nAre you sure you want to reject this post?\nDoing so will delete it, and cannot be undone.")) {
+ form.submit();
+ }
+ });
});
</script>
`
diff --git a/system/admin/handlers.go b/system/admin/handlers.go
index e653d29..65d846d 100644
--- a/system/admin/handlers.go
+++ b/system/admin/handlers.go
@@ -822,6 +822,20 @@ func approvePostHandler(res http.ResponseWriter, req *http.Request) {
post := content.Types[t]()
+ // run hooks
+ hook, ok := post.(content.Hookable)
+ if !ok {
+ log.Println("Type", t, "does not implement content.Hookable or embed content.Item.")
+ res.WriteHeader(http.StatusBadRequest)
+ errView, err := Error400()
+ if err != nil {
+ return
+ }
+
+ res.Write(errView)
+ return
+ }
+
// check if we have a Mergeable
m, ok := post.(api.Mergeable)
if !ok {
@@ -851,6 +865,18 @@ func approvePostHandler(res http.ResponseWriter, req *http.Request) {
return
}
+ err = hook.BeforeApprove(req)
+ if err != nil {
+ res.WriteHeader(http.StatusInternalServerError)
+ errView, err := Error500()
+ if err != nil {
+ return
+ }
+
+ res.Write(errView)
+ return
+ }
+
// call its Approve method
err = m.Approve(req)
if err != nil {
@@ -864,6 +890,30 @@ func approvePostHandler(res http.ResponseWriter, req *http.Request) {
return
}
+ err = hook.AfterApprove(req)
+ if err != nil {
+ res.WriteHeader(http.StatusInternalServerError)
+ errView, err := Error500()
+ if err != nil {
+ return
+ }
+
+ res.Write(errView)
+ return
+ }
+
+ err = hook.BeforeSave(req)
+ if err != nil {
+ res.WriteHeader(http.StatusInternalServerError)
+ errView, err := Error500()
+ if err != nil {
+ return
+ }
+
+ res.Write(errView)
+ return
+ }
+
// Store the content in the bucket t
id, err := db.SetContent(t+":-1", req.Form)
if err != nil {
@@ -877,6 +927,18 @@ func approvePostHandler(res http.ResponseWriter, req *http.Request) {
return
}
+ err = hook.AfterSave(req)
+ if err != nil {
+ res.WriteHeader(http.StatusInternalServerError)
+ errView, err := Error500()
+ if err != nil {
+ return
+ }
+
+ res.Write(errView)
+ return
+ }
+
// redirect to the new approved content's editor
redir := req.URL.Scheme + req.URL.Host + strings.TrimSuffix(req.URL.Path, "/approve")
redir += fmt.Sprintf("?type=%s&id=%d", t, id)
@@ -1040,6 +1102,50 @@ func editHandler(res http.ResponseWriter, req *http.Request) {
req.PostForm.Del(discardKey)
}
+ if strings.Contains(t, "_") {
+ t = strings.Split(t, "_")[0]
+ }
+
+ p, ok := content.Types[t]
+ if !ok {
+ log.Println("Type", t, "is not a content type. Cannot edit or save.")
+ res.WriteHeader(http.StatusBadRequest)
+ errView, err := Error400()
+ if err != nil {
+ return
+ }
+
+ res.Write(errView)
+ return
+ }
+
+ post := p()
+ hook, ok := post.(content.Hookable)
+ if !ok {
+ log.Println("Type", t, "does not implement content.Hookable or embed content.Item.")
+ res.WriteHeader(http.StatusBadRequest)
+ errView, err := Error400()
+ if err != nil {
+ return
+ }
+
+ res.Write(errView)
+ return
+ }
+
+ err = hook.BeforeSave(req)
+ if err != nil {
+ log.Println(err)
+ res.WriteHeader(http.StatusInternalServerError)
+ errView, err := Error500()
+ if err != nil {
+ return
+ }
+
+ res.Write(errView)
+ return
+ }
+
id, err := db.SetContent(t+":"+cid, req.PostForm)
if err != nil {
log.Println(err)
@@ -1053,13 +1159,23 @@ func editHandler(res http.ResponseWriter, req *http.Request) {
return
}
+ err = hook.AfterSave(req)
+ if err != nil {
+ log.Println(err)
+ res.WriteHeader(http.StatusInternalServerError)
+ errView, err := Error500()
+ if err != nil {
+ return
+ }
+
+ res.Write(errView)
+ return
+ }
+
scheme := req.URL.Scheme
host := req.URL.Host
path := req.URL.Path
sid := fmt.Sprintf("%d", id)
- if strings.Contains(t, "_") {
- t = strings.Split(t, "_")[0]
- }
redir := scheme + host + path + "?type=" + t + "&id=" + sid
if req.URL.Query().Get("status") == "pending" {
@@ -1088,12 +1204,75 @@ func deleteHandler(res http.ResponseWriter, req *http.Request) {
id := req.FormValue("id")
t := req.FormValue("type")
+ ct := t
if id == "" || t == "" {
res.WriteHeader(http.StatusBadRequest)
return
}
+ // catch specifier suffix from delete form value
+ if strings.Contains(t, "_") {
+ spec := strings.Split(t, "_")
+ ct = spec[0]
+ }
+
+ p, ok := content.Types[ct]
+ if !ok {
+ log.Println("Type", t, "does not implement content.Hookable or embed content.Item.")
+ res.WriteHeader(http.StatusBadRequest)
+ errView, err := Error400()
+ if err != nil {
+ return
+ }
+
+ res.Write(errView)
+ return
+ }
+
+ post := p()
+ hook, ok := post.(content.Hookable)
+ if !ok {
+ log.Println("Type", t, "does not implement content.Hookable or embed content.Item.")
+ res.WriteHeader(http.StatusBadRequest)
+ errView, err := Error400()
+ if err != nil {
+ return
+ }
+
+ res.Write(errView)
+ return
+ }
+
+ reject := req.URL.Query().Get("reject")
+ if reject == "true" {
+ err = hook.BeforeReject(req)
+ if err != nil {
+ log.Println(err)
+ res.WriteHeader(http.StatusInternalServerError)
+ errView, err := Error500()
+ if err != nil {
+ return
+ }
+
+ res.Write(errView)
+ return
+ }
+ }
+
+ err = hook.BeforeDelete(req)
+ if err != nil {
+ log.Println(err)
+ res.WriteHeader(http.StatusInternalServerError)
+ errView, err := Error500()
+ if err != nil {
+ return
+ }
+
+ res.Write(errView)
+ return
+ }
+
err = db.DeleteContent(t + ":" + id)
if err != nil {
log.Println(err)
@@ -1101,10 +1280,32 @@ func deleteHandler(res http.ResponseWriter, req *http.Request) {
return
}
- // catch specifier suffix from delete form value
- if strings.Contains(t, "_") {
- spec := strings.Split(t, "_")
- t = spec[0]
+ err = hook.AfterDelete(req)
+ if err != nil {
+ log.Println(err)
+ res.WriteHeader(http.StatusInternalServerError)
+ errView, err := Error500()
+ if err != nil {
+ return
+ }
+
+ res.Write(errView)
+ return
+ }
+
+ if reject == "true" {
+ err = hook.AfterReject(req)
+ if err != nil {
+ log.Println(err)
+ res.WriteHeader(http.StatusInternalServerError)
+ errView, err := Error500()
+ if err != nil {
+ return
+ }
+
+ res.Write(errView)
+ return
+ }
}
redir := strings.TrimSuffix(req.URL.Scheme+req.URL.Host+req.URL.Path, "/edit/delete")