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  }