summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--system/addon/addon.go170
-rw-r--r--system/addon/manager.go89
-rw-r--r--system/admin/admin.go1
-rw-r--r--system/admin/handlers.go474
-rw-r--r--system/admin/server.go3
-rw-r--r--system/admin/upload/upload.go2
-rw-r--r--system/api/external.go7
-rw-r--r--system/api/handlers.go2
-rw-r--r--system/db/addon.go155
-rw-r--r--system/db/init.go2
10 files changed, 886 insertions, 19 deletions
diff --git a/system/addon/addon.go b/system/addon/addon.go
new file mode 100644
index 0000000..bf1a717
--- /dev/null
+++ b/system/addon/addon.go
@@ -0,0 +1,170 @@
+package addon
+
+import (
+ "encoding/json"
+ "fmt"
+ "net/url"
+ "strings"
+
+ "github.com/ponzu-cms/ponzu/system/db"
+ "github.com/ponzu-cms/ponzu/system/item"
+)
+
+var (
+ // Types is a record of addons, like content types, of addon_reverse_dns:interface{}
+ Types = make(map[string]func() interface{})
+)
+
+const (
+ // StatusEnabled defines string status for Addon enabled state
+ StatusEnabled = "enabled"
+ // StatusDisabled defines string status for Addon disabled state
+ StatusDisabled = "disabled"
+)
+
+// Meta contains the basic information about the addon
+type Meta struct {
+ PonzuAddonName string `json:"addon_name"`
+ PonzuAddonAuthor string `json:"addon_author"`
+ PonzuAddonAuthorURL string `json:"addon_author_url"`
+ PonzuAddonVersion string `json:"addon_version"`
+ PonzuAddonReverseDNS string `json:"addon_reverse_dns"`
+ PonzuAddonStatus string `json:"addon_status"`
+}
+
+// Addon contains information about a provided addon to the system
+type Addon struct {
+ item.Item
+ Meta
+}
+
+// Register sets up the system to use the Addon by:
+// 1. Adding Meta to the Addon struct
+// 2. Saving it to the __addons bucket in DB with id/key = addon_reverse_dns
+// 3. Checking that the Addon parent type was added to Types (likely via its init())
+func Register(meta Meta, addon Addon) error {
+ a := Addon{Meta: meta}
+
+ // get or create the reverse DNS identifier
+ if a.PonzuAddonReverseDNS == "" {
+ revDNS, err := reverseDNS(meta)
+ if err != nil {
+ return err
+ }
+
+ a.PonzuAddonReverseDNS = revDNS
+ }
+
+ if _, ok := Types[a.PonzuAddonReverseDNS]; !ok {
+ panic(`Addon "` + a.PonzuAddonName + `" has no record in the addons.Types map`)
+ }
+
+ // check if addon is already registered in db as addon_reverse_dns
+ if db.AddonExists(a.PonzuAddonReverseDNS) {
+ return nil
+ }
+
+ // convert a.Item into usable data, Item{} => []byte(json) => map[string]interface{}
+ kv := make(map[string]interface{})
+
+ data, err := json.Marshal(a.Item)
+ if err != nil {
+ return err
+ }
+
+ err = json.Unmarshal(data, &kv)
+ if err != nil {
+ return err
+ }
+
+ // save new addon to db
+ vals := make(url.Values)
+ for k, v := range kv {
+ vals.Set(k, v.(string))
+ }
+
+ vals.Set("addon_name", a.PonzuAddonName)
+ vals.Set("addon_author", a.PonzuAddonAuthor)
+ vals.Set("addon_author_url", a.PonzuAddonAuthorURL)
+ vals.Set("addon_version", a.PonzuAddonVersion)
+ vals.Set("addon_reverse_dns", a.PonzuAddonReverseDNS)
+ vals.Set("addon_status", StatusDisabled)
+
+ // db.SetAddon is like SetContent, but rather than the key being an int64 ID,
+ // we need it to be a string based on the addon_reverse_dns
+ err = db.SetAddon(vals)
+ if err != nil {
+ return err
+ }
+
+ return nil
+}
+
+// Deregister removes an addon from the system. `key` is the addon_reverse_dns
+func Deregister(key string) error {
+ err := db.DeleteAddon(key)
+ if err != nil {
+ return err
+ }
+
+ delete(Types, key)
+ return nil
+}
+
+// Enable sets the addon status to `enabled`. `key` is the addon_reverse_dns
+func Enable(key string) error {
+ err := setStatus(key, StatusEnabled)
+ if err != nil {
+ return err
+ }
+
+ return nil
+}
+
+// Disable sets the addon status to `disabled`. `key` is the addon_reverse_dns
+func Disable(key string) error {
+ err := setStatus(key, StatusDisabled)
+ if err != nil {
+ return err
+ }
+
+ return nil
+}
+
+func setStatus(key, status string) error {
+ a, err := db.Addon(key)
+ if err != nil {
+ return err
+ }
+
+ a.Set("addon_status", status)
+
+ err = db.SetAddon(a)
+ if err != nil {
+ return err
+ }
+
+ return nil
+}
+
+func reverseDNS(meta Meta) (string, error) {
+ u, err := url.Parse(meta.PonzuAddonAuthorURL)
+ if err != nil {
+ return "", nil
+ }
+
+ if u.Host == "" {
+ return "", fmt.Errorf(`Error parsing Addon Author URL: %s. Ensure URL is formatted as "scheme://hostname/path?query" (path & query optional)`, meta.PonzuAddonAuthorURL)
+ }
+
+ name := strings.Replace(meta.PonzuAddonName, " ", "", -1)
+
+ // reverse the host name parts, split on '.', ex. bosssauce.it => it.bosssauce
+ parts := strings.Split(u.Host, ".")
+ strap := make([]string, len(parts), len(parts))
+ for i := len(parts) - 1; i >= 0; i-- {
+ strap = append(strap, parts[i])
+ }
+
+ return strings.Join(append(strap, name), "."), nil
+}
diff --git a/system/addon/manager.go b/system/addon/manager.go
new file mode 100644
index 0000000..1be09c0
--- /dev/null
+++ b/system/addon/manager.go
@@ -0,0 +1,89 @@
+package addon
+
+import (
+ "bytes"
+ "fmt"
+ "html/template"
+ "net/url"
+
+ "encoding/json"
+
+ "github.com/ponzu-cms/ponzu/management/editor"
+)
+
+const defaultInput = `<input type="hidden" name="%s" value="%s"/>`
+
+const managerHTML = `
+<div class="card editor">
+ <form method="post" action="/admin/addon" enctype="multipart/form-data">
+ {{ .DefaultInputs }}
+ {{ .Editor }}
+ </form>
+</div>
+`
+
+type manager struct {
+ DefaultInputs template.HTML
+ Editor template.HTML
+}
+
+// Manage ...
+func Manage(data url.Values, reverseDNS string) ([]byte, error) {
+ a, ok := Types[reverseDNS]
+ if !ok {
+ return nil, fmt.Errorf("Addon has not been added to addon.Types map")
+ }
+
+ at := a()
+
+ // convert data => json => at{}
+ j, err := json.Marshal(data)
+ if err != nil {
+ return nil, err
+ }
+
+ err = json.Unmarshal(j, &at)
+ if err != nil {
+ return nil, err
+ }
+
+ e, ok := at.(editor.Editable)
+ if !ok {
+ return nil, fmt.Errorf("Addon is not editable - must implement editor.Editable: %T", at)
+ }
+
+ v, err := e.MarshalEditor()
+ if err != nil {
+ return nil, fmt.Errorf("Couldn't marshal editor for addon: %s", err.Error())
+ }
+
+ inputs := &bytes.Buffer{}
+ fields := []string{
+ "addon_name",
+ "addon_author",
+ "addon_author_url",
+ "addon_version",
+ "addon_reverse_dns",
+ "addon_status",
+ }
+
+ for _, f := range fields {
+ input := fmt.Sprintf(defaultInput, f, data.Get(f))
+ _, err := inputs.WriteString(input)
+ if err != nil {
+ return nil, fmt.Errorf("Failed to write input for addon view: %s", f)
+ }
+ }
+
+ m := manager{
+ DefaultInputs: template.HTML(inputs.Bytes()),
+ Editor: template.HTML(v),
+ }
+
+ // execute html template into buffer for func return val
+ buf := &bytes.Buffer{}
+ tmpl := template.Must(template.New("manager").Parse(managerHTML))
+ tmpl.Execute(buf, m)
+
+ return buf.Bytes(), nil
+}
diff --git a/system/admin/admin.go b/system/admin/admin.go
index e3ae2d6..db1f2ce 100644
--- a/system/admin/admin.go
+++ b/system/admin/admin.go
@@ -65,6 +65,7 @@ var mainAdminHTML = `
<div class="row collection-item">
<li><a class="col s12" href="/admin/configure"><i class="tiny left material-icons">settings</i>Configuration</a></li>
<li><a class="col s12" href="/admin/configure/users"><i class="tiny left material-icons">supervisor_account</i>Users</a></li>
+ <li><a class="col s12" href="/admin/addons"><i class="tiny left material-icons">supervisor_account</i>Addons</a></li>
</div>
</ul>
</div>
diff --git a/system/admin/handlers.go b/system/admin/handlers.go
index c5c4a4c..cccfa54 100644
--- a/system/admin/handlers.go
+++ b/system/admin/handlers.go
@@ -2,17 +2,20 @@ package admin
import (
"bytes"
+ "context"
"encoding/base64"
"encoding/json"
"fmt"
"log"
"net/http"
+ "net/url"
"strconv"
"strings"
"time"
"github.com/ponzu-cms/ponzu/management/editor"
"github.com/ponzu-cms/ponzu/management/manager"
+ "github.com/ponzu-cms/ponzu/system/addon"
"github.com/ponzu-cms/ponzu/system/admin/config"
"github.com/ponzu-cms/ponzu/system/admin/upload"
"github.com/ponzu-cms/ponzu/system/admin/user"
@@ -263,6 +266,17 @@ func configUsersEditHandler(res http.ResponseWriter, req *http.Request) {
switch req.Method {
case http.MethodPost:
err := req.ParseMultipartForm(1024 * 1024 * 4) // maxMemory 4MB
+ if err != nil {
+ log.Println(err)
+ res.WriteHeader(http.StatusInternalServerError)
+ errView, err := Error500()
+ if err != nil {
+ return
+ }
+
+ res.Write(errView)
+ return
+ }
// check if user to be edited is current user
j, err := db.CurrentUser(req)
@@ -385,6 +399,17 @@ func configUsersDeleteHandler(res http.ResponseWriter, req *http.Request) {
switch req.Method {
case http.MethodPost:
err := req.ParseMultipartForm(1024 * 1024 * 4) // maxMemory 4MB
+ if err != nil {
+ log.Println(err)
+ res.WriteHeader(http.StatusInternalServerError)
+ errView, err := Error500()
+ if err != nil {
+ return
+ }
+
+ res.Write(errView)
+ return
+ }
// do not allow current user to delete themselves
j, err := db.CurrentUser(req)
@@ -567,8 +592,9 @@ func forgotPasswordHandler(res http.ResponseWriter, req *http.Request) {
case http.MethodPost:
err := req.ParseMultipartForm(1024 * 1024 * 4) // maxMemory 4MB
if err != nil {
- res.WriteHeader(http.StatusBadRequest)
- errView, err := Error400()
+ log.Println(err)
+ res.WriteHeader(http.StatusInternalServerError)
+ errView, err := Error500()
if err != nil {
return
}
@@ -939,12 +965,37 @@ func contentsHandler(res http.ResponseWriter, req *http.Request) {
log.Println("Error unmarshal json into", t, err, string(posts[i]))
post := `<li class="col s12">Error decoding data. Possible file corruption.</li>`
- b.Write([]byte(post))
+ _, err := b.Write([]byte(post))
+ if err != nil {
+ log.Println(err)
+
+ res.WriteHeader(http.StatusInternalServerError)
+ errView, err := Error500()
+ if err != nil {
+ log.Println(err)
+ }
+
+ res.Write(errView)
+ return
+ }
+
continue
}
post := adminPostListItem(p, t, status)
- b.Write(post)
+ _, err = b.Write(post)
+ if err != nil {
+ log.Println(err)
+
+ res.WriteHeader(http.StatusInternalServerError)
+ errView, err := Error500()
+ if err != nil {
+ log.Println(err)
+ }
+
+ res.Write(errView)
+ return
+ }
}
case "pending":
@@ -964,12 +1015,36 @@ func contentsHandler(res http.ResponseWriter, req *http.Request) {
log.Println("Error unmarshal json into", t, err, string(posts[i]))
post := `<li class="col s12">Error decoding data. Possible file corruption.</li>`
- b.Write([]byte(post))
+ _, err := b.Write([]byte(post))
+ if err != nil {
+ log.Println(err)
+
+ res.WriteHeader(http.StatusInternalServerError)
+ errView, err := Error500()
+ if err != nil {
+ log.Println(err)
+ }
+
+ res.Write(errView)
+ return
+ }
continue
}
post := adminPostListItem(p, t, status)
- b.Write(post)
+ _, err = b.Write(post)
+ if err != nil {
+ log.Println(err)
+
+ res.WriteHeader(http.StatusInternalServerError)
+ errView, err := Error500()
+ if err != nil {
+ log.Println(err)
+ }
+
+ res.Write(errView)
+ return
+ }
}
}
@@ -982,18 +1057,54 @@ func contentsHandler(res http.ResponseWriter, req *http.Request) {
log.Println("Error unmarshal json into", t, err, string(posts[i]))
post := `<li class="col s12">Error decoding data. Possible file corruption.</li>`
- b.Write([]byte(post))
+ _, err := b.Write([]byte(post))
+ if err != nil {
+ log.Println(err)
+
+ res.WriteHeader(http.StatusInternalServerError)
+ errView, err := Error500()
+ if err != nil {
+ log.Println(err)
+ }
+
+ res.Write(errView)
+ return
+ }
continue
}
post := adminPostListItem(p, t, status)
- b.Write(post)
+ _, err = b.Write(post)
+ if err != nil {
+ log.Println(err)
+
+ res.WriteHeader(http.StatusInternalServerError)
+ errView, err := Error500()
+ if err != nil {
+ log.Println(err)
+ }
+
+ res.Write(errView)
+ return
+ }
}
}
html += `<ul class="posts row">`
- b.Write([]byte(`</ul>`))
+ _, err = b.Write([]byte(`</ul>`))
+ if err != nil {
+ log.Println(err)
+
+ res.WriteHeader(http.StatusInternalServerError)
+ errView, err := Error500()
+ if err != nil {
+ log.Println(err)
+ }
+
+ res.Write(errView)
+ return
+ }
statusDisabled := "disabled"
prevStatus := ""
@@ -1042,7 +1153,19 @@ func contentsHandler(res http.ResponseWriter, req *http.Request) {
`
}
- b.Write([]byte(pagination + `</div></div>`))
+ _, err = b.Write([]byte(pagination + `</div></div>`))
+ if err != nil {
+ log.Println(err)
+
+ res.WriteHeader(http.StatusInternalServerError)
+ errView, err := Error500()
+ if err != nil {
+ log.Println(err)
+ }
+
+ res.Write(errView)
+ return
+ }
script := `
<script>
@@ -1269,6 +1392,10 @@ func approveContentHandler(res http.ResponseWriter, req *http.Request) {
return
}
+ // set the target in the context so user can get saved value from db in hook
+ ctx := context.WithValue(req.Context(), "target", fmt.Sprintf("%s:%d", t, id))
+ req = req.WithContext(ctx)
+
err = hook.AfterSave(req)
if err != nil {
log.Println("Error running AfterSave hook in approveContentHandler for:", t, err)
@@ -1358,6 +1485,7 @@ func editHandler(res http.ResponseWriter, req *http.Request) {
log.Println("Content type", t, "doesn't implement item.Identifiable")
return
}
+
item.SetItemID(-1)
}
@@ -1388,8 +1516,8 @@ func editHandler(res http.ResponseWriter, req *http.Request) {
err := req.ParseMultipartForm(1024 * 1024 * 4) // maxMemory 4MB
if err != nil {
log.Println(err)
- res.WriteHeader(http.StatusBadRequest)
- errView, err := Error405()
+ res.WriteHeader(http.StatusInternalServerError)
+ errView, err := Error500()
if err != nil {
return
}
@@ -1509,6 +1637,10 @@ func editHandler(res http.ResponseWriter, req *http.Request) {
return
}
+ // set the target in the context so user can get saved value from db in hook
+ ctx := context.WithValue(req.Context(), "target", fmt.Sprintf("%s:%d", t, id))
+ req = req.WithContext(ctx)
+
err = hook.AfterSave(req)
if err != nil {
log.Println(err)
@@ -1549,6 +1681,12 @@ func deleteHandler(res http.ResponseWriter, req *http.Request) {
if err != nil {
log.Println(err)
res.WriteHeader(http.StatusInternalServerError)
+ errView, err := Error500()
+ if err != nil {
+ return
+ }
+
+ res.Write(errView)
return
}
@@ -1729,15 +1867,51 @@ func searchHandler(res http.ResponseWriter, req *http.Request) {
log.Println("Error unmarshal search result json into", t, err, posts[i])
post := `<li class="col s12">Error decoding data. Possible file corruption.</li>`
- b.Write([]byte(post))
+ _, err = b.Write([]byte(post))
+ if err != nil {
+ log.Println(err)
+
+ res.WriteHeader(http.StatusInternalServerError)
+ errView, err := Error500()
+ if err != nil {
+ log.Println(err)
+ }
+
+ res.Write(errView)
+ return
+ }
continue
}
post := adminPostListItem(p, t, status)
- b.Write([]byte(post))
+ _, err = b.Write([]byte(post))
+ if err != nil {
+ log.Println(err)
+
+ res.WriteHeader(http.StatusInternalServerError)
+ errView, err := Error500()
+ if err != nil {
+ log.Println(err)
+ }
+
+ res.Write(errView)
+ return
+ }
}
- b.Write([]byte(`</ul></div></div>`))
+ _, err := b.WriteString(`</ul></div></div>`)
+ if err != nil {
+ log.Println(err)
+
+ res.WriteHeader(http.StatusInternalServerError)
+ errView, err := Error500()
+ if err != nil {
+ log.Println(err)
+ }
+
+ res.Write(errView)
+ return
+ }
btn := `<div class="col s3"><a href="/admin/edit?type=` + t + `" class="btn new-post waves-effect waves-light">New ` + t + `</a></div></div>`
html = html + b.String() + btn
@@ -1752,3 +1926,273 @@ func searchHandler(res http.ResponseWriter, req *http.Request) {
res.Header().Set("Content-Type", "text/html")
res.Write(adminView)
}
+
+func addonsHandler(res http.ResponseWriter, req *http.Request) {
+ switch req.Method {
+ case http.MethodGet:
+ all := db.AddonAll()
+ list := &bytes.Buffer{}
+
+ for i := range all {
+ v := adminAddonListItem(all[i])
+ _, err := list.Write(v)
+ if err != nil {
+ log.Println("Error writing bytes to addon list view:", err)
+ res.WriteHeader(http.StatusInternalServerError)
+ errView, err := Error500()
+ if err != nil {
+ log.Println(err)
+ return
+ }
+
+ res.Write(errView)
+ }
+ }
+
+ html := &bytes.Buffer{}
+ open := `<div class="col s9 card">
+ <div class="card-content">
+ <div class="row">
+ <div class="card-title col s7">Addons</div>
+ </div>
+ <ul class="posts row">`
+
+ _, err := html.WriteString(open)
+ if err != nil {
+ log.Println("Error writing open html to addon html view:", err)
+ res.WriteHeader(http.StatusInternalServerError)
+ errView, err := Error500()
+ if err != nil {
+ log.Println(err)
+ return
+ }
+
+ res.Write(errView)
+ }
+
+ _, err = html.Write(list.Bytes())
+ if err != nil {
+ log.Println("Error writing list bytes to addon html view:", err)
+ res.WriteHeader(http.StatusInternalServerError)
+ errView, err := Error500()
+ if err != nil {
+ log.Println(err)
+ return
+ }
+
+ res.Write(errView)
+ }
+
+ _, err = html.WriteString(`</ul></div></div>`)
+ if err != nil {
+ log.Println("Error writing close html to addon html view:", err)
+ res.WriteHeader(http.StatusInternalServerError)
+ errView, err := Error500()
+ if err != nil {
+ log.Println(err)
+ return
+ }
+
+ res.Write(errView)
+ }
+
+ view, err := Admin(html.Bytes())
+ if err != nil {
+ log.Println("Error writing addon html to admin view:", err)
+ res.WriteHeader(http.StatusInternalServerError)
+ errView, err := Error500()
+ if err != nil {
+ log.Println(err)
+ return
+ }
+
+ res.Write(errView)
+ }
+
+ res.Write(view)
+
+ case http.MethodPost:
+ err := req.ParseMultipartForm(1024 * 1024 * 4) // maxMemory 4MB
+ if err != nil {
+ log.Println(err)
+ res.WriteHeader(http.StatusInternalServerError)
+ errView, err := Error500()
+ if err != nil {
+ return
+ }
+
+ res.Write(errView)
+ return
+ }
+
+ id := req.PostFormValue("id")
+ action := req.PostFormValue("action")
+
+ _, err = db.Addon(id)
+ if err == db.ErrNoAddonExists {
+ log.Println(err)
+ res.WriteHeader(http.StatusNotFound)
+ errView, err := Error404()
+ if err != nil {
+ return
+ }
+
+ res.Write(errView)
+ return
+ }
+ if err != nil {
+ log.Println(err)
+ res.WriteHeader(http.StatusInternalServerError)
+ errView, err := Error500()
+ if err != nil {
+ return
+ }
+
+ res.Write(errView)
+ return
+ }
+
+ switch action {
+ case "enable":
+ err := addon.Enable(id)
+ if err != nil {
+ log.Println(err)
+ res.WriteHeader(http.StatusInternalServerError)
+ errView, err := Error500()
+ if err != nil {
+ return
+ }
+
+ res.Write(errView)
+ return
+ }
+ case "disable":
+ err := addon.Disable(id)
+ if err != nil {
+ log.Println(err)
+ res.WriteHeader(http.StatusInternalServerError)
+ errView, err := Error500()
+ if err != nil {
+ return
+ }
+
+ res.Write(errView)
+ return
+ }
+ default:
+ res.WriteHeader(http.StatusBadRequest)
+ errView, err := Error405()
+ if err != nil {
+ return
+ }
+
+ res.Write(errView)
+ return
+ }
+
+ default:
+ res.WriteHeader(http.StatusBadRequest)
+ errView, err := Error405()
+ if err != nil {
+ log.Println(err)
+ return
+ }
+
+ res.Write(errView)
+ }
+}
+
+func addonHandler(res http.ResponseWriter, req *http.Request) {
+ switch req.Method {
+ case http.MethodGet:
+ id := req.FormValue("id")
+
+ data, err := db.Addon(id)
+ if err != nil {
+ log.Println(err)
+ res.WriteHeader(http.StatusInternalServerError)
+ errView, err := Error500()
+ if err != nil {
+ return
+ }
+
+ res.Write(errView)
+ return
+ }
+
+ _, ok := addon.Types[id]
+ if !ok {
+ log.Println("Addon: ", id, "is not found in addon.Types map")
+ res.WriteHeader(http.StatusNotFound)
+ errView, err := Error404()
+ if err != nil {
+ return
+ }
+
+ res.Write(errView)
+ return
+ }
+
+ m, err := addon.Manage(data, id)
+ if err != nil {
+ log.Println(err)
+ res.WriteHeader(http.StatusInternalServerError)
+ errView, err := Error500()
+ if err != nil {
+ return
+ }
+
+ res.Write(errView)
+ return
+ }
+
+ addonView, err := Admin(m)
+ if err != nil {
+ log.Println(err)
+ res.WriteHeader(http.StatusInternalServerError)
+ return
+ }
+
+ res.Header().Set("Content-Type", "text/html")
+ res.Write(addonView)
+
+ case http.MethodPost:
+ default:
+ res.WriteHeader(http.StatusBadRequest)
+ errView, err := Error405()
+ if err != nil {
+ log.Println(err)
+ return
+ }
+
+ res.Write(errView)
+ }
+}
+
+func adminAddonListItem(data url.Values) []byte {
+ var action string
+ var buttonClass string
+ status := data.Get("addon_status")
+ if status != addon.StatusEnabled {
+ action = "Enable"
+ buttonClass = "green"
+ } else {
+ action = "Disable"
+ buttonClass = "red"
+ }
+
+ id := data.Get("addon_reverse_dns")
+ name := data.Get("addon_name")
+ a := `
+ <li class="col s12">
+ <a href="/admin/addon?id=` + id + `" alt="Configure '` + name + `'">` + name + `</a>
+
+ <form enctype="multipart/form-data" class="quick-` + strings.ToLower(action) + `-addon __ponzu right" action="/admin/addons" method="post">
+ <button class="btn waves-effect waves-effect-light ` + buttonClass + `">` + action + `</button>
+ <input type="hidden" name="id" value="` + id + `" />
+ <input type="hidden" name="action" value="` + action + `" />
+ </form>
+ </li>`
+
+ return []byte(a)
+}
diff --git a/system/admin/server.go b/system/admin/server.go
index 155892a..f2bf244 100644
--- a/system/admin/server.go
+++ b/system/admin/server.go
@@ -23,6 +23,9 @@ func Run() {
http.HandleFunc("/admin/recover", forgotPasswordHandler)
http.HandleFunc("/admin/recover/key", recoveryKeyHandler)
+ http.HandleFunc("/admin/addons", user.Auth(addonsHandler))
+ http.HandleFunc("/admin/addon", user.Auth(addonHandler))
+
http.HandleFunc("/admin/configure", user.Auth(configHandler))
http.HandleFunc("/admin/configure/users", user.Auth(configUsersHandler))
http.HandleFunc("/admin/configure/users/edit", user.Auth(configUsersEditHandler))
diff --git a/system/admin/upload/upload.go b/system/admin/upload/upload.go
index 323f371..486f55c 100644
--- a/system/admin/upload/upload.go
+++ b/system/admin/upload/upload.go
@@ -14,7 +14,7 @@ import (
func StoreFiles(req *http.Request) (map[string]string, error) {
err := req.ParseMultipartForm(1024 * 1024 * 4) // maxMemory 4MB
if err != nil {
- return nil, fmt.Errorf("%s", err)
+ return nil, err
}
ts := req.FormValue("timestamp") // timestamp in milliseconds since unix epoch
diff --git a/system/api/external.go b/system/api/external.go
index 302b7c9..662fc07 100644
--- a/system/api/external.go
+++ b/system/api/external.go
@@ -1,6 +1,7 @@
package api
import (
+ "context"
"fmt"
"log"
"net/http"
@@ -136,13 +137,17 @@ func externalContentHandler(res http.ResponseWriter, req *http.Request) {
spec = "__pending"
}
- _, err = db.SetContent(t+spec+":-1", req.PostForm)
+ id, err := db.SetContent(t+spec+":-1", req.PostForm)
if err != nil {
log.Println("[External] error:", err)
res.WriteHeader(http.StatusInternalServerError)
return
}
+ // set the target in the context so user can get saved value from db in hook
+ ctx := context.WithValue(req.Context(), "target", fmt.Sprintf("%s:%d", t, id))
+ req = req.WithContext(ctx)
+
err = hook.AfterSave(req)
if err != nil {
log.Println("[External] error:", err)
diff --git a/system/api/handlers.go b/system/api/handlers.go
index 869640f..1bc4fbb 100644
--- a/system/api/handlers.go
+++ b/system/api/handlers.go
@@ -178,7 +178,7 @@ func hide(it interface{}, res http.ResponseWriter, req *http.Request) bool {
// check if should be hidden
if h, ok := it.(item.Hideable); ok {
err := h.Hide(req)
- if err != nil && err == item.ErrAllowHiddenItem {
+ if err == item.ErrAllowHiddenItem {
return false
}
diff --git a/system/db/addon.go b/system/db/addon.go
new file mode 100644
index 0000000..da83b7f
--- /dev/null
+++ b/system/db/addon.go
@@ -0,0 +1,155 @@
+package db
+
+import (
+ "bytes"
+ "errors"
+ "fmt"
+ "log"
+ "net/url"
+
+ "github.com/boltdb/bolt"
+)
+
+var (
+ // ErrNoAddonExists indicates that there was not addon found in the db
+ ErrNoAddonExists = errors.New("No addon exists.")
+)
+
+// Addon looks for an addon by its addon_reverse_dns as the key and returns
+// the url.Values representation of an addon
+func Addon(key string) (url.Values, error) {
+ buf := &bytes.Buffer{}
+
+ err := store.View(func(tx *bolt.Tx) error {
+ b := tx.Bucket([]byte("__addons"))
+
+ val := b.Get([]byte(key))
+
+ if val == nil {
+ return ErrNoAddonExists
+ }
+
+ _, err := buf.Write(val)
+ if err != nil {
+ return err
+ }
+
+ return nil
+ })
+ if err != nil {
+ return nil, err
+ }
+
+ data, err := url.ParseQuery(buf.String())
+ if err != nil {
+ return nil, err
+ }
+
+ return data, nil
+}
+
+// SetAddon stores the values of an addon into the __addons bucket with a the
+// addon_reverse_dns field used as the key
+func SetAddon(data url.Values) error {
+ // we don't know the structure of the addon type from a addon developer, so
+ // encoding to json before it's stored in the db is difficult. Instead, we
+ // can just encode the url.Values to a query string using the Encode() method.
+ // The downside is that we will have to parse the values out of the query
+ // string when loading it from the db
+ v := data.Encode()
+
+ err := store.Update(func(tx *bolt.Tx) error {
+ b := tx.Bucket([]byte("__addons"))
+ k := data.Get("addon_reverse_dns")
+ if k == "" {
+ name := data.Get("addon_name")
+ return fmt.Errorf(`Addon "%s" has no identifier to use as key.`, name)
+ }
+
+ err := b.Put([]byte(k), []byte(v))
+ if err != nil {
+ return err
+ }
+
+ return nil
+ })
+ if err != nil {
+ return err
+ }
+
+ return nil
+}
+
+// AddonAll returns all registered addons as a [][]byte
+func AddonAll() []url.Values {
+ var all []url.Values
+ buf := &bytes.Buffer{}
+
+ err := store.View(func(tx *bolt.Tx) error {
+ b := tx.Bucket([]byte("__addons"))
+ err := b.ForEach(func(k, v []byte) error {
+ _, err := buf.Write(v)
+ if err != nil {
+ return err
+ }
+
+ data, err := url.ParseQuery(buf.String())
+ if err != nil {
+ return err
+ }
+
+ all = append(all, data)
+ return nil
+ })
+ if err != nil {
+ return err
+ }
+
+ return nil
+ })
+ if err != nil {
+ log.Println("Error finding addons in db with db.AddonAll:", err)
+ return nil
+ }
+
+ return all
+}
+
+// DeleteAddon removes an addon from the db by its key, the addon_reverse_dns
+func DeleteAddon(key string) error {
+ err := store.Update(func(tx *bolt.Tx) error {
+ b := tx.Bucket([]byte("__addons"))
+
+ if err := b.Delete([]byte(key)); err != nil {
+ return err
+ }
+
+ return nil
+ })
+ if err != nil {
+ return err
+ }
+
+ return nil
+}
+
+// AddonExists checks if there is an existing addon stored. The key is an the
+// value at addon_reverse_dns
+func AddonExists(key string) bool {
+ var exists bool
+ err := store.View(func(tx *bolt.Tx) error {
+ b := tx.Bucket([]byte("__addons"))
+ if b.Get([]byte(key)) == nil {
+ return nil
+ }
+
+ exists = true
+ return nil
+ })
+ if err != nil {
+ log.Println("Error checking existence of addon with key:", key, "-", err)
+ return false
+ }
+
+ return exists
+}
diff --git a/system/db/init.go b/system/db/init.go
index bf93bc6..6bc4753 100644
--- a/system/db/init.go
+++ b/system/db/init.go
@@ -45,7 +45,7 @@ func Init() {
}
// init db with other buckets as needed
- buckets := []string{"__config", "__users", "__contentIndex"}
+ buckets := []string{"__config", "__users", "__contentIndex", "__addons"}
for _, name := range buckets {
_, err := tx.CreateBucketIfNotExists([]byte(name))
if err != nil {