fix(users): preserve nullable rate limit updates

This commit is contained in:
RWDai
2026-05-07 19:16:49 +08:00
parent f3d9523502
commit a44667d2a9
5 changed files with 58 additions and 7 deletions

View File

@@ -882,6 +882,7 @@ impl UserReadRepository for InMemoryUserReadRepository {
allowed_api_formats: Option<Vec<String>>, allowed_api_formats: Option<Vec<String>>,
allowed_models_present: bool, allowed_models_present: bool,
allowed_models: Option<Vec<String>>, allowed_models: Option<Vec<String>>,
rate_limit_present: bool,
rate_limit: Option<i32>, rate_limit: Option<i32>,
is_active: Option<bool>, is_active: Option<bool>,
) -> Result<Option<StoredUserAuthRecord>, DataLayerError> { ) -> Result<Option<StoredUserAuthRecord>, DataLayerError> {
@@ -931,8 +932,8 @@ impl UserReadRepository for InMemoryUserReadRepository {
row.allowed_providers = updated.allowed_providers.clone(); row.allowed_providers = updated.allowed_providers.clone();
row.allowed_api_formats = updated.allowed_api_formats.clone(); row.allowed_api_formats = updated.allowed_api_formats.clone();
row.allowed_models = updated.allowed_models.clone(); row.allowed_models = updated.allowed_models.clone();
if let Some(rate_limit) = rate_limit { if rate_limit_present {
row.rate_limit = Some(rate_limit); row.rate_limit = rate_limit;
} }
row.is_active = updated.is_active; row.is_active = updated.is_active;
} }
@@ -1518,7 +1519,24 @@ mod tests {
None, None,
) )
.expect("auth user should build"); .expect("auth user should build");
let repository = InMemoryUserReadRepository::seed_auth_users(vec![user]); let export_user = StoredUserExportRow::new(
"user-1".to_string(),
Some("alice@example.com".to_string()),
true,
"alice".to_string(),
Some("old-hash".to_string()),
"user".to_string(),
"local".to_string(),
None,
None,
None,
Some(10),
None,
true,
)
.expect("export user should build");
let repository = InMemoryUserReadRepository::seed_auth_users(vec![user])
.with_export_users([export_user]);
let updated = repository let updated = repository
.update_local_auth_user_profile( .update_local_auth_user_profile(
@@ -1566,6 +1584,7 @@ mod tests {
None, None,
true, true,
Some(vec!["gpt-4.1".to_string()]), Some(vec!["gpt-4.1".to_string()]),
true,
Some(50), Some(50),
Some(false), Some(false),
) )
@@ -1583,6 +1602,31 @@ mod tests {
Some(vec!["gpt-4.1".to_string()]) Some(vec!["gpt-4.1".to_string()])
); );
assert!(!admin_updated.is_active); assert!(!admin_updated.is_active);
assert_eq!(
repository
.find_export_user_by_id("user-1")
.await
.expect("export lookup should succeed")
.expect("export row should exist")
.rate_limit,
Some(50)
);
repository
.update_local_auth_user_admin_fields(
"user-1", None, false, None, false, None, false, None, true, None, None,
)
.await
.expect("rate limit clear should succeed")
.expect("rate limit clear should return user");
assert_eq!(
repository
.find_export_user_by_id("user-1")
.await
.expect("export lookup should succeed")
.expect("export row should exist")
.rate_limit,
None
);
assert_eq!( assert_eq!(
repository repository
.update_user_model_capability_settings( .update_user_model_capability_settings(

View File

@@ -655,6 +655,7 @@ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)
allowed_api_formats: Option<Vec<String>>, allowed_api_formats: Option<Vec<String>>,
allowed_models_present: bool, allowed_models_present: bool,
allowed_models: Option<Vec<String>>, allowed_models: Option<Vec<String>>,
rate_limit_present: bool,
rate_limit: Option<i32>, rate_limit: Option<i32>,
is_active: Option<bool>, is_active: Option<bool>,
) -> Result<Option<StoredUserAuthRecord>, DataLayerError> { ) -> Result<Option<StoredUserAuthRecord>, DataLayerError> {
@@ -688,7 +689,7 @@ WHERE id = ?
allowed_models, allowed_models,
"users.allowed_models", "users.allowed_models",
)?) )?)
.bind(rate_limit.is_some()) .bind(rate_limit_present)
.bind(rate_limit) .bind(rate_limit)
.bind(is_active.is_some()) .bind(is_active.is_some())
.bind(is_active) .bind(is_active)

View File

@@ -1107,6 +1107,7 @@ WHERE id = $1
allowed_api_formats: Option<Vec<String>>, allowed_api_formats: Option<Vec<String>>,
allowed_models_present: bool, allowed_models_present: bool,
allowed_models: Option<Vec<String>>, allowed_models: Option<Vec<String>>,
rate_limit_present: bool,
rate_limit: Option<i32>, rate_limit: Option<i32>,
is_active: Option<bool>, is_active: Option<bool>,
) -> Result<Option<StoredUserAuthRecord>, DataLayerError> { ) -> Result<Option<StoredUserAuthRecord>, DataLayerError> {
@@ -1130,7 +1131,7 @@ SET role = CASE
ELSE allowed_models ELSE allowed_models
END, END,
rate_limit = CASE rate_limit = CASE
WHEN $10::BOOLEAN AND $11 IS NOT NULL THEN $11 WHEN $10::BOOLEAN THEN $11
ELSE rate_limit ELSE rate_limit
END, END,
is_active = CASE is_active = CASE
@@ -1150,7 +1151,7 @@ WHERE id = $1
.bind(allowed_api_formats.map(serde_json::Value::from)) .bind(allowed_api_formats.map(serde_json::Value::from))
.bind(allowed_models_present) .bind(allowed_models_present)
.bind(allowed_models.map(serde_json::Value::from)) .bind(allowed_models.map(serde_json::Value::from))
.bind(rate_limit.is_some()) .bind(rate_limit_present)
.bind(rate_limit) .bind(rate_limit)
.bind(is_active.is_some()) .bind(is_active.is_some())
.bind(is_active) .bind(is_active)
@@ -1882,6 +1883,7 @@ impl UserReadRepository for SqlxUserReadRepository {
allowed_api_formats: Option<Vec<String>>, allowed_api_formats: Option<Vec<String>>,
allowed_models_present: bool, allowed_models_present: bool,
allowed_models: Option<Vec<String>>, allowed_models: Option<Vec<String>>,
rate_limit_present: bool,
rate_limit: Option<i32>, rate_limit: Option<i32>,
is_active: Option<bool>, is_active: Option<bool>,
) -> Result<Option<StoredUserAuthRecord>, DataLayerError> { ) -> Result<Option<StoredUserAuthRecord>, DataLayerError> {
@@ -1894,6 +1896,7 @@ impl UserReadRepository for SqlxUserReadRepository {
allowed_api_formats, allowed_api_formats,
allowed_models_present, allowed_models_present,
allowed_models, allowed_models,
rate_limit_present,
rate_limit, rate_limit,
is_active, is_active,
) )

View File

@@ -655,6 +655,7 @@ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)
allowed_api_formats: Option<Vec<String>>, allowed_api_formats: Option<Vec<String>>,
allowed_models_present: bool, allowed_models_present: bool,
allowed_models: Option<Vec<String>>, allowed_models: Option<Vec<String>>,
rate_limit_present: bool,
rate_limit: Option<i32>, rate_limit: Option<i32>,
is_active: Option<bool>, is_active: Option<bool>,
) -> Result<Option<StoredUserAuthRecord>, DataLayerError> { ) -> Result<Option<StoredUserAuthRecord>, DataLayerError> {
@@ -688,7 +689,7 @@ WHERE id = ?
allowed_models, allowed_models,
"users.allowed_models", "users.allowed_models",
)?) )?)
.bind(rate_limit.is_some()) .bind(rate_limit_present)
.bind(rate_limit) .bind(rate_limit)
.bind(is_active.is_some()) .bind(is_active.is_some())
.bind(is_active) .bind(is_active)
@@ -1638,6 +1639,7 @@ INSERT INTO users (
Some(vec!["responses".to_string()]), Some(vec!["responses".to_string()]),
true, true,
Some(vec!["gpt-4.1-mini".to_string()]), Some(vec!["gpt-4.1-mini".to_string()]),
true,
Some(5), Some(5),
Some(false), Some(false),
) )

View File

@@ -596,6 +596,7 @@ pub trait UserReadRepository: Send + Sync {
allowed_api_formats: Option<Vec<String>>, allowed_api_formats: Option<Vec<String>>,
allowed_models_present: bool, allowed_models_present: bool,
allowed_models: Option<Vec<String>>, allowed_models: Option<Vec<String>>,
rate_limit_present: bool,
rate_limit: Option<i32>, rate_limit: Option<i32>,
is_active: Option<bool>, is_active: Option<bool>,
) -> Result<Option<StoredUserAuthRecord>, crate::DataLayerError>; ) -> Result<Option<StoredUserAuthRecord>, crate::DataLayerError>;