package config import ( "encoding/json" "fmt" "sync" "time" "github.com/boltdb/bolt" "github.com/gorilla/mux" "github.com/rs/zerolog/log" ) var ( bucket = []byte("config") ) type configValue struct { Key string `json:"key"` Value interface{} `json:"value"` } func (value configValue) getBytes() ([]byte, error) { return json.Marshal(value) } func decodeConfig(in []byte) (configValue, error) { var out configValue err := json.Unmarshal(in, &out) return out, err } type Config struct { // I'm not completely sure if this mutex is necessary. // It is left here because I'm opening an closing the db // for every operation. sync.Mutex path string } func (c *Config) db() *bolt.DB { db, err := bolt.Open(c.path, 0666, &bolt.Options{ Timeout: 1 * time.Second, }) if err != nil { log.Fatal().Msgf("Could not open database: %s", err) } err = db.Update(func(tx *bolt.Tx) error { _, err := tx.CreateBucketIfNotExists(bucket) return err }) if err != nil { log.Fatal().Err(err).Msg("Could not open bucket") } return db } func (c *Config) getAll() (map[string]configValue, error) { c.Lock() defer c.Unlock() db := c.db() defer db.Close() vals := map[string]configValue{} err := db.View(func(tx *bolt.Tx) error { b := tx.Bucket(bucket) err := b.ForEach(func(k, v []byte) error { value, err := decodeConfig(v) if err != nil { return fmt.Errorf("could not convert value: %w", err) } vals[string(k)] = value return nil }) if err != nil { return fmt.Errorf("error in forEach: %w", err) } return nil }) return vals, err } func (c *Config) get(key string) (configValue, error) { c.Lock() defer c.Unlock() db := c.db() defer db.Close() var out configValue err := db.View(func(tx *bolt.Tx) error { b := tx.Bucket(bucket) value, err := decodeConfig(b.Get([]byte(key))) if err != nil { return err } out = value return nil }) if err != nil { return configValue{}, err } return out, nil } func (c *Config) set(key string, value configValue) error { c.Lock() defer c.Unlock() db := c.db() defer db.Close() err := db.Update(func(tx *bolt.Tx) error { b := tx.Bucket(bucket) v, err := value.getBytes() if err != nil { return err } err = b.Put([]byte(key), v) return err }) if err != nil { log.Error().Msgf("Error setting key: %s", err) } return err } func (c *Config) delete(key string) error { c.Lock() defer c.Unlock() db := c.db() defer db.Close() err := db.Update(func(tx *bolt.Tx) error { b := tx.Bucket(bucket) return b.Delete([]byte(key)) }) return err } // Public interface func New(r *mux.Router) *Config { c := &Config{ path: "lameralert.db", } c.setup(r) return c } func (c *Config) GetInt64(key string, defaultValue int64) int64 { v, err := c.get(key) if err != nil { return defaultValue } return int64(v.Value.(float64)) } func (c *Config) GetInt(key string, defaultValue int) int { v, err := c.get(key) if err != nil { return defaultValue } return int(v.Value.(float64)) } func (c *Config) GetStringSlice(key string, defaultValue []string) []string { v, err := c.get(key) if err != nil { log.Error().Msgf("GetStringSlice errored looking for %s: %s", key, err) return defaultValue } return interfaceSliceToStringSlice(v.Value.([]interface{})) } func interfaceSliceToStringSlice(in []interface{}) []string { out := []string{} for _, it := range in { out = append(out, it.(string)) } return out } func (c *Config) GetStringMap(key string, defaultValue map[string]string) map[string]string { v, err := c.get(key) if err != nil { return defaultValue } return v.Value.(map[string]string) } func (c *Config) GetString(key, defaultValue string) string { v, err := c.get(key) if err != nil { return defaultValue } return v.Value.(string) } func (c *Config) Set(key string, value interface{}) error { return c.set(key, configValue{key, value}) }