From e68b9d00a6e05b3a941f63ffb696f91e554ac5ec Mon Sep 17 00:00:00 2001 From: Daniel Baumann Date: Fri, 18 Oct 2024 20:33:49 +0200 Subject: Adding upstream version 9.0.3. Signed-off-by: Daniel Baumann --- models/user/setting.go | 212 +++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 212 insertions(+) create mode 100644 models/user/setting.go (limited to 'models/user/setting.go') diff --git a/models/user/setting.go b/models/user/setting.go new file mode 100644 index 0000000..b4af0e5 --- /dev/null +++ b/models/user/setting.go @@ -0,0 +1,212 @@ +// Copyright 2021 The Gitea Authors. All rights reserved. +// SPDX-License-Identifier: MIT + +package user + +import ( + "context" + "fmt" + "strings" + + "code.gitea.io/gitea/models/db" + "code.gitea.io/gitea/modules/cache" + setting_module "code.gitea.io/gitea/modules/setting" + "code.gitea.io/gitea/modules/util" + + "xorm.io/builder" +) + +// Setting is a key value store of user settings +type Setting struct { + ID int64 `xorm:"pk autoincr"` + UserID int64 `xorm:"index unique(key_userid)"` // to load all of someone's settings + SettingKey string `xorm:"varchar(255) index unique(key_userid)"` // ensure key is always lowercase + SettingValue string `xorm:"text"` +} + +// TableName sets the table name for the settings struct +func (s *Setting) TableName() string { + return "user_setting" +} + +func init() { + db.RegisterModel(new(Setting)) +} + +// ErrUserSettingIsNotExist represents an error that a setting is not exist with special key +type ErrUserSettingIsNotExist struct { + Key string +} + +// Error implements error +func (err ErrUserSettingIsNotExist) Error() string { + return fmt.Sprintf("Setting[%s] is not exist", err.Key) +} + +func (err ErrUserSettingIsNotExist) Unwrap() error { + return util.ErrNotExist +} + +// IsErrUserSettingIsNotExist return true if err is ErrSettingIsNotExist +func IsErrUserSettingIsNotExist(err error) bool { + _, ok := err.(ErrUserSettingIsNotExist) + return ok +} + +// genSettingCacheKey returns the cache key for some configuration +func genSettingCacheKey(userID int64, key string) string { + return fmt.Sprintf("user_%d.setting.%s", userID, key) +} + +// GetSetting returns the setting value via the key +func GetSetting(ctx context.Context, uid int64, key string) (string, error) { + return cache.GetString(genSettingCacheKey(uid, key), func() (string, error) { + res, err := GetSettingNoCache(ctx, uid, key) + if err != nil { + return "", err + } + return res.SettingValue, nil + }) +} + +// GetSettingNoCache returns specific setting without using the cache +func GetSettingNoCache(ctx context.Context, uid int64, key string) (*Setting, error) { + v, err := GetSettings(ctx, uid, []string{key}) + if err != nil { + return nil, err + } + if len(v) == 0 { + return nil, ErrUserSettingIsNotExist{key} + } + return v[key], nil +} + +// GetSettings returns specific settings from user +func GetSettings(ctx context.Context, uid int64, keys []string) (map[string]*Setting, error) { + settings := make([]*Setting, 0, len(keys)) + if err := db.GetEngine(ctx). + Where("user_id=?", uid). + And(builder.In("setting_key", keys)). + Find(&settings); err != nil { + return nil, err + } + settingsMap := make(map[string]*Setting) + for _, s := range settings { + settingsMap[s.SettingKey] = s + } + return settingsMap, nil +} + +// GetUserAllSettings returns all settings from user +func GetUserAllSettings(ctx context.Context, uid int64) (map[string]*Setting, error) { + settings := make([]*Setting, 0, 5) + if err := db.GetEngine(ctx). + Where("user_id=?", uid). + Find(&settings); err != nil { + return nil, err + } + settingsMap := make(map[string]*Setting) + for _, s := range settings { + settingsMap[s.SettingKey] = s + } + return settingsMap, nil +} + +func validateUserSettingKey(key string) error { + if len(key) == 0 { + return fmt.Errorf("setting key must be set") + } + if strings.ToLower(key) != key { + return fmt.Errorf("setting key should be lowercase") + } + return nil +} + +// GetUserSetting gets a specific setting for a user +func GetUserSetting(ctx context.Context, userID int64, key string, def ...string) (string, error) { + if err := validateUserSettingKey(key); err != nil { + return "", err + } + + setting := &Setting{UserID: userID, SettingKey: key} + has, err := db.GetEngine(ctx).Get(setting) + if err != nil { + return "", err + } + if !has { + if len(def) == 1 { + return def[0], nil + } + return "", nil + } + return setting.SettingValue, nil +} + +// DeleteUserSetting deletes a specific setting for a user +func DeleteUserSetting(ctx context.Context, userID int64, key string) error { + if err := validateUserSettingKey(key); err != nil { + return err + } + + cache.Remove(genSettingCacheKey(userID, key)) + _, err := db.GetEngine(ctx).Delete(&Setting{UserID: userID, SettingKey: key}) + + return err +} + +// SetUserSetting updates a users' setting for a specific key +func SetUserSetting(ctx context.Context, userID int64, key, value string) error { + if err := validateUserSettingKey(key); err != nil { + return err + } + + if err := upsertUserSettingValue(ctx, userID, key, value); err != nil { + return err + } + + cc := cache.GetCache() + if cc != nil { + return cc.Put(genSettingCacheKey(userID, key), value, setting_module.CacheService.TTLSeconds()) + } + + return nil +} + +func upsertUserSettingValue(ctx context.Context, userID int64, key, value string) error { + return db.WithTx(ctx, func(ctx context.Context) error { + e := db.GetEngine(ctx) + + // here we use a general method to do a safe upsert for different databases (and most transaction levels) + // 1. try to UPDATE the record and acquire the transaction write lock + // if UPDATE returns non-zero rows are changed, OK, the setting is saved correctly + // if UPDATE returns "0 rows changed", two possibilities: (a) record doesn't exist (b) value is not changed + // 2. do a SELECT to check if the row exists or not (we already have the transaction lock) + // 3. if the row doesn't exist, do an INSERT (we are still protected by the transaction lock, so it's safe) + // + // to optimize the SELECT in step 2, we can use an extra column like `revision=revision+1` + // to make sure the UPDATE always returns a non-zero value for existing (unchanged) records. + + res, err := e.Exec("UPDATE user_setting SET setting_value=? WHERE setting_key=? AND user_id=?", value, key, userID) + if err != nil { + return err + } + rows, _ := res.RowsAffected() + if rows > 0 { + // the existing row is updated, so we can return + return nil + } + + // in case the value isn't changed, update would return 0 rows changed, so we need this check + has, err := e.Exist(&Setting{UserID: userID, SettingKey: key}) + if err != nil { + return err + } + if has { + return nil + } + + // if no existing row, insert a new row + _, err = e.Insert(&Setting{UserID: userID, SettingKey: key, SettingValue: value}) + return err + }) +} -- cgit v1.2.3