2021-11-22 12:47:23 +03:00
// Copyright 2021 The Gitea Authors. All rights reserved.
2022-11-27 21:20:29 +03:00
// SPDX-License-Identifier: MIT
2021-11-22 12:47:23 +03:00
package user
import (
"context"
"fmt"
"strings"
"code.gitea.io/gitea/models/db"
2022-11-10 09:43:53 +03:00
"code.gitea.io/gitea/modules/cache"
2023-01-21 17:31:11 +03:00
setting_module "code.gitea.io/gitea/modules/setting"
2023-05-02 19:31:35 +03:00
"code.gitea.io/gitea/modules/util"
2021-11-22 12:47:23 +03:00
"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 ) )
}
2022-10-17 02:29:26 +03:00
// 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 )
}
2023-05-02 19:31:35 +03:00
func ( err ErrUserSettingIsNotExist ) Unwrap ( ) error {
return util . ErrNotExist
}
2022-10-17 02:29:26 +03:00
// IsErrUserSettingIsNotExist return true if err is ErrSettingIsNotExist
func IsErrUserSettingIsNotExist ( err error ) bool {
_ , ok := err . ( ErrUserSettingIsNotExist )
return ok
}
2022-11-10 09:43:53 +03:00
// 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
2023-09-15 09:13:19 +03:00
func GetSetting ( ctx context . Context , uid int64 , key string ) ( string , error ) {
2023-01-01 19:06:52 +03:00
return cache . GetString ( genSettingCacheKey ( uid , key ) , func ( ) ( string , error ) {
2023-09-15 09:13:19 +03:00
res , err := GetSettingNoCache ( ctx , uid , key )
2022-11-10 09:43:53 +03:00
if err != nil {
2023-01-01 19:06:52 +03:00
return "" , err
2022-11-10 09:43:53 +03:00
}
2023-01-01 19:06:52 +03:00
return res . SettingValue , nil
2022-11-10 09:43:53 +03:00
} )
}
// GetSettingNoCache returns specific setting without using the cache
2023-09-15 09:13:19 +03:00
func GetSettingNoCache ( ctx context . Context , uid int64 , key string ) ( * Setting , error ) {
v , err := GetSettings ( ctx , uid , [ ] string { key } )
2022-10-17 02:29:26 +03:00
if err != nil {
return nil , err
}
if len ( v ) == 0 {
return nil , ErrUserSettingIsNotExist { key }
}
return v [ key ] , nil
}
2022-11-10 09:43:53 +03:00
// GetSettings returns specific settings from user
2023-09-15 09:13:19 +03:00
func GetSettings ( ctx context . Context , uid int64 , keys [ ] string ) ( map [ string ] * Setting , error ) {
2021-11-22 12:47:23 +03:00
settings := make ( [ ] * Setting , 0 , len ( keys ) )
2023-09-15 09:13:19 +03:00
if err := db . GetEngine ( ctx ) .
2021-11-22 12:47:23 +03:00
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
2023-09-15 09:13:19 +03:00
func GetUserAllSettings ( ctx context . Context , uid int64 ) ( map [ string ] * Setting , error ) {
2021-11-22 12:47:23 +03:00
settings := make ( [ ] * Setting , 0 , 5 )
2023-09-15 09:13:19 +03:00
if err := db . GetEngine ( ctx ) .
2021-11-22 12:47:23 +03:00
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
}
2022-01-21 20:59:26 +03:00
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
2023-09-15 09:13:19 +03:00
func GetUserSetting ( ctx context . Context , userID int64 , key string , def ... string ) ( string , error ) {
2022-01-21 20:59:26 +03:00
if err := validateUserSettingKey ( key ) ; err != nil {
return "" , err
}
2022-11-10 09:43:53 +03:00
2022-01-21 20:59:26 +03:00
setting := & Setting { UserID : userID , SettingKey : key }
2023-09-15 09:13:19 +03:00
has , err := db . GetEngine ( ctx ) . Get ( setting )
2022-01-21 20:59:26 +03:00
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
2023-09-15 09:13:19 +03:00
func DeleteUserSetting ( ctx context . Context , userID int64 , key string ) error {
2022-01-21 20:59:26 +03:00
if err := validateUserSettingKey ( key ) ; err != nil {
return err
}
2022-11-10 09:43:53 +03:00
cache . Remove ( genSettingCacheKey ( userID , key ) )
2023-09-15 09:13:19 +03:00
_ , err := db . GetEngine ( ctx ) . Delete ( & Setting { UserID : userID , SettingKey : key } )
2022-11-10 09:43:53 +03:00
2021-11-22 12:47:23 +03:00
return err
}
2022-01-21 20:59:26 +03:00
// SetUserSetting updates a users' setting for a specific key
2023-09-15 09:13:19 +03:00
func SetUserSetting ( ctx context . Context , userID int64 , key , value string ) error {
2022-01-21 20:59:26 +03:00
if err := validateUserSettingKey ( key ) ; err != nil {
return err
2021-11-22 12:47:23 +03:00
}
2022-11-10 09:43:53 +03:00
2023-09-15 09:13:19 +03:00
if err := upsertUserSettingValue ( ctx , userID , key , value ) ; err != nil {
2023-01-21 17:31:11 +03:00
return err
}
2022-11-10 09:43:53 +03:00
2023-01-21 17:31:11 +03:00
cc := cache . GetCache ( )
if cc != nil {
return cc . Put ( genSettingCacheKey ( userID , key ) , value , setting_module . CacheService . TTLSeconds ( ) )
}
return nil
2021-11-22 12:47:23 +03:00
}
2023-09-15 09:13:19 +03:00
func upsertUserSettingValue ( ctx context . Context , userID int64 , key , value string ) error {
return db . WithTx ( ctx , func ( ctx context . Context ) error {
2021-11-22 12:47:23 +03:00
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
} )
}