github.com/rpdict/ponzu@v0.10.1-0.20190226054626-477f29d6bf5e/system/db/config.go (about)

     1  package db
     2  
     3  import (
     4  	"bytes"
     5  	"encoding/json"
     6  	"fmt"
     7  	"net/url"
     8  	"strings"
     9  	"sync"
    10  
    11  	"github.com/rpdict/ponzu/system/admin/config"
    12  
    13  	"github.com/boltdb/bolt"
    14  	"github.com/gorilla/schema"
    15  )
    16  
    17  const (
    18  	// DefaultMaxAge provides a 2592000 second (30-day) cache max-age setting
    19  	DefaultMaxAge = int64(60 * 60 * 24 * 30)
    20  )
    21  
    22  var mu = &sync.Mutex{}
    23  var configCache map[string]interface{}
    24  
    25  func init() {
    26  	configCache = make(map[string]interface{})
    27  }
    28  
    29  // SetConfig sets key:value pairs in the db for configuration settings
    30  func SetConfig(data url.Values) error {
    31  	var j []byte
    32  	err := store.Update(func(tx *bolt.Tx) error {
    33  		b := tx.Bucket([]byte("__config"))
    34  
    35  		// check for any multi-value fields (ex. checkbox fields)
    36  		// and correctly format for db storage. Essentially, we need
    37  		// fieldX.0: value1, fieldX.1: value2 => fieldX: []string{value1, value2}
    38  		fieldOrderValue := make(map[string]map[string][]string)
    39  		for k, v := range data {
    40  			if strings.Contains(k, ".") {
    41  				fo := strings.Split(k, ".")
    42  
    43  				// put the order and the field value into map
    44  				field := string(fo[0])
    45  				order := string(fo[1])
    46  				if len(fieldOrderValue[field]) == 0 {
    47  					fieldOrderValue[field] = make(map[string][]string)
    48  				}
    49  
    50  				// orderValue is 0:[?type=Thing&id=1]
    51  				orderValue := fieldOrderValue[field]
    52  				orderValue[order] = v
    53  				fieldOrderValue[field] = orderValue
    54  
    55  				// discard the post form value with name.N
    56  				data.Del(k)
    57  			}
    58  
    59  		}
    60  
    61  		// add/set the key & value to the post form in order
    62  		for f, ov := range fieldOrderValue {
    63  			for i := 0; i < len(ov); i++ {
    64  				position := fmt.Sprintf("%d", i)
    65  				fieldValue := ov[position]
    66  
    67  				if data.Get(f) == "" {
    68  					for i, fv := range fieldValue {
    69  						if i == 0 {
    70  							data.Set(f, fv)
    71  						} else {
    72  							data.Add(f, fv)
    73  						}
    74  					}
    75  				} else {
    76  					for _, fv := range fieldValue {
    77  						data.Add(f, fv)
    78  					}
    79  				}
    80  			}
    81  		}
    82  
    83  		cfg := &config.Config{}
    84  		dec := schema.NewDecoder()
    85  		dec.SetAliasTag("json")     // allows simpler struct tagging when creating a content type
    86  		dec.IgnoreUnknownKeys(true) // will skip over form values submitted, but not in struct
    87  		err := dec.Decode(cfg, data)
    88  		if err != nil {
    89  			return err
    90  		}
    91  
    92  		// check for "invalidate" value to reset the Etag
    93  		if len(cfg.CacheInvalidate) > 0 && cfg.CacheInvalidate[0] == "invalidate" {
    94  			cfg.Etag = NewEtag()
    95  			cfg.CacheInvalidate = []string{}
    96  		}
    97  
    98  		j, err = json.Marshal(cfg)
    99  		if err != nil {
   100  			return err
   101  		}
   102  
   103  		err = b.Put([]byte("settings"), j)
   104  		if err != nil {
   105  			return err
   106  		}
   107  
   108  		return nil
   109  	})
   110  	if err != nil {
   111  		return err
   112  	}
   113  
   114  	// convert json => map[string]interface{}
   115  	var kv map[string]interface{}
   116  	err = json.Unmarshal(j, &kv)
   117  	if err != nil {
   118  		return err
   119  	}
   120  
   121  	mu.Lock()
   122  	configCache = kv
   123  	mu.Unlock()
   124  
   125  	return nil
   126  }
   127  
   128  // Config gets the value of a key in the configuration from the db
   129  func Config(key string) ([]byte, error) {
   130  	kv := make(map[string]interface{})
   131  
   132  	cfg, err := ConfigAll()
   133  	if err != nil {
   134  		return nil, err
   135  	}
   136  
   137  	if len(cfg) < 1 {
   138  		return nil, nil
   139  	}
   140  
   141  	err = json.Unmarshal(cfg, &kv)
   142  	if err != nil {
   143  		return nil, err
   144  	}
   145  
   146  	return []byte(kv[key].(string)), nil
   147  }
   148  
   149  // ConfigAll gets the configuration from the db
   150  func ConfigAll() ([]byte, error) {
   151  	val := &bytes.Buffer{}
   152  	err := store.View(func(tx *bolt.Tx) error {
   153  		b := tx.Bucket([]byte("__config"))
   154  		if b == nil {
   155  			return fmt.Errorf("Error finding bucket: %s", "__config")
   156  		}
   157  		_, err := val.Write(b.Get([]byte("settings")))
   158  		if err != nil {
   159  			return err
   160  		}
   161  
   162  		return nil
   163  	})
   164  	if err != nil {
   165  		return nil, err
   166  	}
   167  
   168  	return val.Bytes(), nil
   169  }
   170  
   171  // PutConfig updates a single k/v in the config
   172  func PutConfig(key string, value interface{}) error {
   173  	kv := make(map[string]interface{})
   174  
   175  	c, err := ConfigAll()
   176  	if err != nil {
   177  		return err
   178  	}
   179  
   180  	if c == nil {
   181  		c, err = emptyConfig()
   182  		if err != nil {
   183  			return err
   184  		}
   185  	}
   186  
   187  	err = json.Unmarshal(c, &kv)
   188  	if err != nil {
   189  		return err
   190  	}
   191  
   192  	// set k/v from params to decoded map
   193  	kv[key] = value
   194  
   195  	data := make(url.Values)
   196  	for k, v := range kv {
   197  		switch v.(type) {
   198  		case string:
   199  			data.Set(k, v.(string))
   200  
   201  		case []string:
   202  			vv := v.([]string)
   203  			for i := range vv {
   204  				data.Add(k, vv[i])
   205  			}
   206  
   207  		default:
   208  			data.Set(k, fmt.Sprintf("%v", v))
   209  		}
   210  	}
   211  
   212  	err = SetConfig(data)
   213  	if err != nil {
   214  		return err
   215  	}
   216  
   217  	return nil
   218  }
   219  
   220  // ConfigCache is a in-memory cache of the Configs for quicker lookups
   221  // 'key' is the JSON tag associated with the config field
   222  func ConfigCache(key string) interface{} {
   223  	mu.Lock()
   224  	val := configCache[key]
   225  	mu.Unlock()
   226  
   227  	return val
   228  }
   229  
   230  // LoadCacheConfig loads the config into a cache to be accessed by ConfigCache()
   231  func LoadCacheConfig() error {
   232  	c, err := ConfigAll()
   233  	if err != nil {
   234  		return err
   235  	}
   236  
   237  	if c == nil {
   238  		c, err = emptyConfig()
   239  		if err != nil {
   240  			return err
   241  		}
   242  	}
   243  
   244  	// convert json => map[string]interface{}
   245  	var kv map[string]interface{}
   246  	err = json.Unmarshal(c, &kv)
   247  	if err != nil {
   248  		return err
   249  	}
   250  
   251  	mu.Lock()
   252  	configCache = kv
   253  	mu.Unlock()
   254  
   255  	return nil
   256  }
   257  
   258  func emptyConfig() ([]byte, error) {
   259  	cfg := &config.Config{}
   260  
   261  	data, err := json.Marshal(cfg)
   262  	if err != nil {
   263  		return nil, err
   264  	}
   265  
   266  	return data, nil
   267  }