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