forked from ponzu-cms/ponzu
/
addon.go
245 lines (200 loc) · 5.85 KB
/
addon.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
package addon
import (
"encoding/json"
"fmt"
"net/url"
"strings"
"github.com/ponzu-cms/ponzu/system/db"
"github.com/ponzu-cms/ponzu/system/item"
"github.com/tidwall/sjson"
)
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 constructs a new addon and registers it with the system. Meta is a
// addon.Meta and fn is a closure returning a pointer to your own addon type
func Register(m Meta, fn func() interface{}) Addon {
// get or create the reverse DNS identifier
if m.PonzuAddonReverseDNS == "" {
revDNS, err := reverseDNS(m)
if err != nil {
panic(err)
}
m.PonzuAddonReverseDNS = revDNS
}
Types[m.PonzuAddonReverseDNS] = fn
a := Addon{Meta: m}
err := register(a)
if err != nil {
panic(err)
}
return a
}
// register sets up the system to use the Addon by:
// 1. Validating the Addon struct
// 2. Saving it to the __addons bucket in DB with id/key = addon_reverse_dns
func register(a Addon) error {
if a.PonzuAddonName == "" {
return fmt.Errorf(`Addon must have valid Meta struct embedded: missing %s field.`, "PonzuAddonName")
}
if a.PonzuAddonAuthor == "" {
return fmt.Errorf(`Addon must have valid Meta struct embedded: missing %s field.`, "PonzuAddonAuthor")
}
if a.PonzuAddonAuthorURL == "" {
return fmt.Errorf(`Addon must have valid Meta struct embedded: missing %s field.`, "PonzuAddonAuthorURL")
}
if a.PonzuAddonVersion == "" {
return fmt.Errorf(`Addon must have valid Meta struct embedded: missing %s field.`, "PonzuAddonVersion")
}
if _, ok := Types[a.PonzuAddonReverseDNS]; !ok {
return fmt.Errorf(`Addon "%s" has no record in the addons.Types map`, a.PonzuAddonName)
}
// 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, fmt.Sprintf("%v", v))
}
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
kind, ok := Types[a.PonzuAddonReverseDNS]
if !ok {
return fmt.Errorf("Error: no addon to set with id: %s", a.PonzuAddonReverseDNS)
}
err = db.SetAddon(vals, kind())
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
}
// KeyFromMeta creates a unique string identifier for an addon based on its url and name
func KeyFromMeta(meta Meta) (string, error) {
return reverseDNS(meta)
}
func setStatus(key, status string) error {
a, err := db.Addon(key)
if err != nil {
return err
}
a, err = sjson.SetBytes(a, "addon_status", status)
if err != nil {
return err
}
kind, ok := Types[key]
if !ok {
return fmt.Errorf("Error: no addon to set with id: %s", key)
}
// convert json => map[string]interface{} => url.Values
var kv map[string]interface{}
err = json.Unmarshal(a, &kv)
if err != nil {
return err
}
vals := make(url.Values)
for k, v := range kv {
switch v.(type) {
case []string:
s := v.([]string)
for i := range s {
if i == 0 {
vals.Set(k, s[i])
}
vals.Add(k, s[i])
}
default:
vals.Set(k, fmt.Sprintf("%v", v))
}
}
err = db.SetAddon(vals, kind())
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, 0, len(parts))
for i := len(parts) - 1; i >= 0; i-- {
strap = append(strap, parts[i])
}
return strings.Join(append(strap, name), "."), nil
}
// String returns the addon name and overrides the item String() method in
// item.Identifiable interface
func (a *Addon) String() string {
return a.PonzuAddonName
}