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