diff options
author | Steve Manuel <nilslice@gmail.com> | 2016-11-08 16:48:12 -0800 |
---|---|---|
committer | Steve Manuel <nilslice@gmail.com> | 2016-11-08 16:48:12 -0800 |
commit | dfa5e33f99f8727d36420981156c1ba8400b17b8 (patch) | |
tree | 7404d8d2eb804770c058ffbfffc9ade99ece3a23 | |
parent | e9e9ea47d8bcbdbcf91a2ad822e107d8561a9822 (diff) |
adding before/after hooks to actions: save, delete, approve and reject
-rw-r--r-- | content/item.go | 39 | ||||
-rw-r--r-- | management/editor/editor.go | 22 | ||||
-rw-r--r-- | system/admin/handlers.go | 215 |
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") |