From f9303ae0c32292b930e3063a4adb738b0f8b9487 Mon Sep 17 00:00:00 2001 From: iven Date: Tue, 7 Apr 2026 22:24:19 +0800 Subject: [PATCH] fix(saas): SQL type cast fixes for E2E relay flow MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - key_pool.rs: cast cooldown_until to timestamptz for comparison with NOW() - key_pool.rs: cast request_count to bigint (INT4→INT8) for sqlx decoding - service.rs: cast cooldown_until to timestamptz in quota sort query - scheduler.rs: cast last_seen_at to timestamptz in device cleanup - totp.rs: use DateTime instead of rfc3339 string for updated_at --- crates/zclaw-saas/src/auth/totp.rs | 4 ++-- crates/zclaw-saas/src/relay/key_pool.rs | 6 +++--- crates/zclaw-saas/src/relay/service.rs | 2 +- crates/zclaw-saas/src/scheduler.rs | 2 +- 4 files changed, 7 insertions(+), 7 deletions(-) diff --git a/crates/zclaw-saas/src/auth/totp.rs b/crates/zclaw-saas/src/auth/totp.rs index 2425257..4414a08 100644 --- a/crates/zclaw-saas/src/auth/totp.rs +++ b/crates/zclaw-saas/src/auth/totp.rs @@ -226,7 +226,7 @@ pub async fn verify_totp( encrypt_totp_secret(&secret, &enc_key)? }; - let now_ts = chrono::Utc::now().to_rfc3339(); + let now_ts = chrono::Utc::now(); sqlx::query("UPDATE accounts SET totp_enabled = true, totp_secret = $1, updated_at = $2 WHERE id = $3") .bind(&final_secret) .bind(&now_ts) @@ -260,7 +260,7 @@ pub async fn disable_totp( } // 清除 TOTP - let now = chrono::Utc::now().to_rfc3339(); + let now = chrono::Utc::now(); sqlx::query("UPDATE accounts SET totp_enabled = false, totp_secret = NULL, updated_at = $1 WHERE id = $2") .bind(&now) .bind(&ctx.account_id) diff --git a/crates/zclaw-saas/src/relay/key_pool.rs b/crates/zclaw-saas/src/relay/key_pool.rs index 05d1135..63e6da3 100644 --- a/crates/zclaw-saas/src/relay/key_pool.rs +++ b/crates/zclaw-saas/src/relay/key_pool.rs @@ -45,11 +45,11 @@ pub async fn select_best_key(db: &PgPool, provider_id: &str, enc_key: &[u8; 32]) let rows: Vec<(String, String, i32, Option, Option, Option, Option)> = sqlx::query_as( "SELECT pk.id, pk.key_value, pk.priority, pk.max_rpm, pk.max_tpm, - uw.request_count, uw.token_count + uw.request_count::bigint, uw.token_count FROM provider_keys pk LEFT JOIN key_usage_window uw ON pk.id = uw.key_id AND uw.window_minute = $1 WHERE pk.provider_id = $2 AND pk.is_active = TRUE - AND (pk.cooldown_until IS NULL OR pk.cooldown_until <= $3) + AND (pk.cooldown_until IS NULL OR pk.cooldown_until::timestamptz <= $3) ORDER BY pk.priority ASC, pk.last_used_at ASC NULLS FIRST" ).bind(¤t_minute).bind(provider_id).bind(&now).fetch_all(db).await?; @@ -95,7 +95,7 @@ pub async fn select_best_key(db: &PgPool, provider_id: &str, enc_key: &[u8; 32]) // 检查是否有冷却中的 Key,返回预计等待时间 let cooldown_row: Option<(String,)> = sqlx::query_as( "SELECT cooldown_until::TEXT FROM provider_keys - WHERE provider_id = $1 AND is_active = TRUE AND cooldown_until IS NOT NULL AND cooldown_until > $2 + WHERE provider_id = $1 AND is_active = TRUE AND cooldown_until IS NOT NULL AND cooldown_until::timestamptz > $2 ORDER BY cooldown_until ASC LIMIT 1" ).bind(provider_id).bind(&now).fetch_optional(db).await?; diff --git a/crates/zclaw-saas/src/relay/service.rs b/crates/zclaw-saas/src/relay/service.rs index 9af46ec..c2e51bb 100644 --- a/crates/zclaw-saas/src/relay/service.rs +++ b/crates/zclaw-saas/src/relay/service.rs @@ -653,7 +653,7 @@ pub async fn sort_candidates_by_quota( AND uw.window_minute = to_char(date_trunc('minute', NOW()), 'YYYY-MM-DDTHH24:MI') WHERE pk.provider_id = ANY($1) AND pk.is_active = TRUE - AND (pk.cooldown_until IS NULL OR pk.cooldown_until <= NOW()) + AND (pk.cooldown_until IS NULL OR pk.cooldown_until::timestamptz <= NOW()) GROUP BY pk.provider_id "#, ) diff --git a/crates/zclaw-saas/src/scheduler.rs b/crates/zclaw-saas/src/scheduler.rs index b9328bf..17184eb 100644 --- a/crates/zclaw-saas/src/scheduler.rs +++ b/crates/zclaw-saas/src/scheduler.rs @@ -89,7 +89,7 @@ pub fn start_db_cleanup_tasks(db: PgPool) { loop { interval.tick().await; match sqlx::query( - "DELETE FROM devices WHERE last_seen_at < $1" + "DELETE FROM devices WHERE last_seen_at::timestamptz < $1" ) .bind({ let cutoff = (chrono::Utc::now() - chrono::Duration::days(90));