code.gitea.io/gitea@v1.22.3/models/user/setting.go (about) 1 // Copyright 2021 The Gitea Authors. All rights reserved. 2 // SPDX-License-Identifier: MIT 3 4 package user 5 6 import ( 7 "context" 8 "fmt" 9 "strings" 10 11 "code.gitea.io/gitea/models/db" 12 "code.gitea.io/gitea/modules/cache" 13 setting_module "code.gitea.io/gitea/modules/setting" 14 "code.gitea.io/gitea/modules/util" 15 16 "xorm.io/builder" 17 ) 18 19 // Setting is a key value store of user settings 20 type Setting struct { 21 ID int64 `xorm:"pk autoincr"` 22 UserID int64 `xorm:"index unique(key_userid)"` // to load all of someone's settings 23 SettingKey string `xorm:"varchar(255) index unique(key_userid)"` // ensure key is always lowercase 24 SettingValue string `xorm:"text"` 25 } 26 27 // TableName sets the table name for the settings struct 28 func (s *Setting) TableName() string { 29 return "user_setting" 30 } 31 32 func init() { 33 db.RegisterModel(new(Setting)) 34 } 35 36 // ErrUserSettingIsNotExist represents an error that a setting is not exist with special key 37 type ErrUserSettingIsNotExist struct { 38 Key string 39 } 40 41 // Error implements error 42 func (err ErrUserSettingIsNotExist) Error() string { 43 return fmt.Sprintf("Setting[%s] is not exist", err.Key) 44 } 45 46 func (err ErrUserSettingIsNotExist) Unwrap() error { 47 return util.ErrNotExist 48 } 49 50 // IsErrUserSettingIsNotExist return true if err is ErrSettingIsNotExist 51 func IsErrUserSettingIsNotExist(err error) bool { 52 _, ok := err.(ErrUserSettingIsNotExist) 53 return ok 54 } 55 56 // genSettingCacheKey returns the cache key for some configuration 57 func genSettingCacheKey(userID int64, key string) string { 58 return fmt.Sprintf("user_%d.setting.%s", userID, key) 59 } 60 61 // GetSetting returns the setting value via the key 62 func GetSetting(ctx context.Context, uid int64, key string) (string, error) { 63 return cache.GetString(genSettingCacheKey(uid, key), func() (string, error) { 64 res, err := GetSettingNoCache(ctx, uid, key) 65 if err != nil { 66 return "", err 67 } 68 return res.SettingValue, nil 69 }) 70 } 71 72 // GetSettingNoCache returns specific setting without using the cache 73 func GetSettingNoCache(ctx context.Context, uid int64, key string) (*Setting, error) { 74 v, err := GetSettings(ctx, uid, []string{key}) 75 if err != nil { 76 return nil, err 77 } 78 if len(v) == 0 { 79 return nil, ErrUserSettingIsNotExist{key} 80 } 81 return v[key], nil 82 } 83 84 // GetSettings returns specific settings from user 85 func GetSettings(ctx context.Context, uid int64, keys []string) (map[string]*Setting, error) { 86 settings := make([]*Setting, 0, len(keys)) 87 if err := db.GetEngine(ctx). 88 Where("user_id=?", uid). 89 And(builder.In("setting_key", keys)). 90 Find(&settings); err != nil { 91 return nil, err 92 } 93 settingsMap := make(map[string]*Setting) 94 for _, s := range settings { 95 settingsMap[s.SettingKey] = s 96 } 97 return settingsMap, nil 98 } 99 100 // GetUserAllSettings returns all settings from user 101 func GetUserAllSettings(ctx context.Context, uid int64) (map[string]*Setting, error) { 102 settings := make([]*Setting, 0, 5) 103 if err := db.GetEngine(ctx). 104 Where("user_id=?", uid). 105 Find(&settings); err != nil { 106 return nil, err 107 } 108 settingsMap := make(map[string]*Setting) 109 for _, s := range settings { 110 settingsMap[s.SettingKey] = s 111 } 112 return settingsMap, nil 113 } 114 115 func validateUserSettingKey(key string) error { 116 if len(key) == 0 { 117 return fmt.Errorf("setting key must be set") 118 } 119 if strings.ToLower(key) != key { 120 return fmt.Errorf("setting key should be lowercase") 121 } 122 return nil 123 } 124 125 // GetUserSetting gets a specific setting for a user 126 func GetUserSetting(ctx context.Context, userID int64, key string, def ...string) (string, error) { 127 if err := validateUserSettingKey(key); err != nil { 128 return "", err 129 } 130 131 setting := &Setting{UserID: userID, SettingKey: key} 132 has, err := db.GetEngine(ctx).Get(setting) 133 if err != nil { 134 return "", err 135 } 136 if !has { 137 if len(def) == 1 { 138 return def[0], nil 139 } 140 return "", nil 141 } 142 return setting.SettingValue, nil 143 } 144 145 // DeleteUserSetting deletes a specific setting for a user 146 func DeleteUserSetting(ctx context.Context, userID int64, key string) error { 147 if err := validateUserSettingKey(key); err != nil { 148 return err 149 } 150 151 cache.Remove(genSettingCacheKey(userID, key)) 152 _, err := db.GetEngine(ctx).Delete(&Setting{UserID: userID, SettingKey: key}) 153 154 return err 155 } 156 157 // SetUserSetting updates a users' setting for a specific key 158 func SetUserSetting(ctx context.Context, userID int64, key, value string) error { 159 if err := validateUserSettingKey(key); err != nil { 160 return err 161 } 162 163 if err := upsertUserSettingValue(ctx, userID, key, value); err != nil { 164 return err 165 } 166 167 cc := cache.GetCache() 168 if cc != nil { 169 return cc.Put(genSettingCacheKey(userID, key), value, setting_module.CacheService.TTLSeconds()) 170 } 171 172 return nil 173 } 174 175 func upsertUserSettingValue(ctx context.Context, userID int64, key, value string) error { 176 return db.WithTx(ctx, func(ctx context.Context) error { 177 e := db.GetEngine(ctx) 178 179 // here we use a general method to do a safe upsert for different databases (and most transaction levels) 180 // 1. try to UPDATE the record and acquire the transaction write lock 181 // if UPDATE returns non-zero rows are changed, OK, the setting is saved correctly 182 // if UPDATE returns "0 rows changed", two possibilities: (a) record doesn't exist (b) value is not changed 183 // 2. do a SELECT to check if the row exists or not (we already have the transaction lock) 184 // 3. if the row doesn't exist, do an INSERT (we are still protected by the transaction lock, so it's safe) 185 // 186 // to optimize the SELECT in step 2, we can use an extra column like `revision=revision+1` 187 // to make sure the UPDATE always returns a non-zero value for existing (unchanged) records. 188 189 res, err := e.Exec("UPDATE user_setting SET setting_value=? WHERE setting_key=? AND user_id=?", value, key, userID) 190 if err != nil { 191 return err 192 } 193 rows, _ := res.RowsAffected() 194 if rows > 0 { 195 // the existing row is updated, so we can return 196 return nil 197 } 198 199 // in case the value isn't changed, update would return 0 rows changed, so we need this check 200 has, err := e.Exist(&Setting{UserID: userID, SettingKey: key}) 201 if err != nil { 202 return err 203 } 204 if has { 205 return nil 206 } 207 208 // if no existing row, insert a new row 209 _, err = e.Insert(&Setting{UserID: userID, SettingKey: key, SettingValue: value}) 210 return err 211 }) 212 }