api.spaceplanner.app

Spaceplanner API
git clone git://jacobedwards.org/api.spaceplanner.app
Log | Files | Refs

settings.go (4259B)


      1 package backend
      2 
      3 import (
      4 	"errors"
      5 	"database/sql"
      6 	"github.com/lib/pq"
      7 )
      8 
      9 type SettingDef struct {
     10 	Primitive string `json:"type"`
     11 	Validator func(interface{}) (bool, error)
     12 	DefaultValue interface{}
     13 	// min *int, max *int // for integer types, min and max value, for string, length
     14 	Memo string `json:"description"`
     15 }
     16 
     17 type rawSetting struct {
     18 	username *string
     19 	name *string
     20 	strval *string
     21 	numval *int
     22 	boolval *bool
     23 }
     24 
     25 func (e *Env) Settings() map[string]SettingDef {
     26 	return e.settingDefs
     27 }
     28 
     29 func (e *Env) UpdateUserSetting(tx *sql.Tx, username string, key string, value any) error {
     30 	m := make(map[string]any)
     31 	m[key] = value
     32 	return e.UpdateUserSettings(tx, username, m)
     33 }
     34 
     35 func (e *Env) UpdateUserSettings(tx *sql.Tx, username string, settings map[string]interface{}) error {
     36 	stmt, err := e.CacheTxStmt(tx, "set_user_settings", `INSERT INTO spaceplanner.user_settings VALUES ($1, $2, $3, $4, $5)
     37 		ON CONFLICT (username, name) DO UPDATE
     38 		SET (strval, numval, boolval) = (EXCLUDED.strval, EXCLUDED.numval, EXCLUDED.boolval)`)
     39 	if err != nil {
     40 		return err
     41 	}
     42 
     43 	invalid, err := e.splitInvalidSettings(settings)
     44 	if err != nil || len(invalid) > 0 {
     45 		// Helpful I know
     46 		return errors.New("Unable to validate all settings")
     47 	}
     48 
     49 	for name, setting := range settings {
     50 		r := toRawSetting(username, name, setting)
     51 		_, err = stmt.Exec(r.username, r.name, r.strval, r.numval, r.boolval)
     52 		if err != nil {
     53 			return err
     54 		}
     55 	}
     56 
     57 	return nil
     58 }
     59 
     60 func (e *Env) GetUserSettings(tx *sql.Tx, username string, names ...string) (map[string]interface{}, error) {
     61 	stmt, err := e.CacheTxStmt(tx, "get_user_settings", `SELECT * FROM spaceplanner.user_settings WHERE
     62 		user_settings.username = $1 AND
     63 		(ARRAY_LENGTH($2::varchar[], 1) IS NULL OR user_settings.name = ANY ($2))`)
     64 	if err != nil {
     65 		return nil, err
     66 	}
     67 
     68 	rows, err := stmt.Query(username, pq.Array(names))
     69 	if err != nil {
     70 		return nil, err
     71 	}
     72 
     73 	return collectSettings(rows)
     74 }
     75 
     76 func (e *Env) DeleteUserSettings(tx *sql.Tx, username string, names ...string) (map[string]interface{}, error) {
     77 	var deleted map[string]interface{}
     78 	stmt, err := e.CacheTxStmt(tx, "del_user_settings", `DELETE FROM spaceplanner.user_settings WHERE user_settings.username = $1 AND
     79 		(ARRAY_LENGTH($2::varchar[], 1) IS NULL OR user_settings.name = ANY ($2)) RETURNING *`)
     80 	if err != nil {
     81 		return deleted, err
     82 	}
     83 
     84 	rows, err := stmt.Query(username, pq.Array(names))
     85 	if err != nil {
     86 		return deleted, err
     87 	}
     88 	return collectSettings(rows)
     89 }
     90 
     91 func toRawSetting(username string, name string, setting interface{}) *rawSetting {
     92 	var b *bool
     93 	var i *int
     94 	var s *string
     95 
     96 	switch v := setting.(type) {
     97 	case string:
     98 		s = &v
     99 	case int:
    100 		i = &v
    101 	case bool:
    102 		b = &v
    103 	case nil:
    104 		;
    105 	default:
    106 		panic("Setting.Value: Unexpected type")
    107 	}
    108 
    109 	return &rawSetting{
    110 		username: &username,
    111 		name: &name,
    112 		strval: s,
    113 		numval: i,
    114 		boolval: b,
    115 	}
    116 }
    117 
    118 func collectSettings(rows *sql.Rows) (map[string]interface{}, error) {
    119 	settings := make(map[string]interface{})
    120 	for rows.Next() {
    121 		var setting interface{}
    122 		var raw rawSetting
    123 		err := rows.Scan(&raw.username, &raw.name, &raw.strval, &raw.numval, &raw.boolval)
    124 		if err != nil {
    125 			return settings, err
    126 		}
    127 
    128 		if raw.strval != nil {
    129 			setting = *raw.strval
    130 		} else if raw.numval != nil {
    131 			setting = *raw.numval
    132 		} else if raw.boolval != nil {
    133 			setting = *raw.boolval
    134 		} else {
    135 			return settings, errors.New("Database constraint error: no setting set")
    136 		}
    137 		settings[*raw.name] = setting
    138 	}
    139 
    140 	return settings, nil
    141 }
    142 
    143 func (e *Env) splitInvalidSettings(settings map[string]interface{}) (purged map[string]interface{}, err error) {
    144 	purged = make(map[string]interface{})
    145 	for k, v := range settings {
    146 		valid, err := e.validateSetting(k, v)
    147 		if err != nil {
    148 			return nil, err
    149 		}
    150 		if !valid {
    151 			purged[k] = v
    152 		}
    153 	}
    154 
    155         // Doing this afterwords incase validateSetting returns an
    156         // error so settings won't be altered on error
    157 	for k := range purged {
    158 		settings[k] = nil
    159 	}
    160 	return purged, nil
    161 }
    162 
    163 func (e *Env) validateSetting(name string, value interface{}) (bool, error) {
    164 	def, exists := e.settingDefs[name]
    165 	if !exists {
    166 		return false, errors.New("Setting does not exist")
    167 	}
    168 
    169 	if def.Validator == nil {
    170 		return true, nil
    171 	}
    172 	return def.Validator(value)
    173 }
    174