From b8a961a9363a1808864c5f970d6ce0a9bc10e4aa Mon Sep 17 00:00:00 2001 From: qaz741wsd856 Date: Mon, 1 Dec 2025 10:30:50 +0000 Subject: [PATCH] feat: optimize bulk cipher restoration --- src/handlers/ciphers.rs | 39 +++++++++++++++++++++------------------ src/models/cipher.rs | 4 ++-- 2 files changed, 23 insertions(+), 20 deletions(-) diff --git a/src/handlers/ciphers.rs b/src/handlers/ciphers.rs index 7239813..e1c5717 100644 --- a/src/handlers/ciphers.rs +++ b/src/handlers/ciphers.rs @@ -331,8 +331,17 @@ pub async fn restore_ciphers_bulk( let now = Utc::now().format("%Y-%m-%dT%H:%M:%S%.3fZ").to_string(); let batch_size = get_batch_size(&env); let ids = payload.ids; - let mut update_statements: Vec = Vec::with_capacity(ids.len()); + if ids.is_empty() { + return Ok(Json(BulkRestoreResponse { + data: vec![], + object: "list".to_string(), + continuation_token: None, + })); + } + + // Batch UPDATE operations + let mut update_statements: Vec = Vec::with_capacity(ids.len()); for id in ids.iter() { let stmt = query!( &db, @@ -345,26 +354,20 @@ pub async fn restore_ciphers_bulk( update_statements.push(stmt); } - db::execute_in_batches(&db, update_statements, batch_size).await?; - let mut restored_ciphers = Vec::with_capacity(ids.len()); + // Batch SELECT using json_each() - avoid N+1 query problem + let ids_json = serde_json::to_string(&ids).map_err(|_| AppError::Internal)?; - for id in ids { - let cipher_db: Option = query!( - &db, - "SELECT * FROM ciphers WHERE id = ?1 AND user_id = ?2", - id, - claims.sub - ) - .map_err(|_| AppError::Database)? - .first(None) - .await?; - - if let Some(cipher) = cipher_db { - restored_ciphers.push(cipher.into()); - } - } + let restored_ciphers: Vec = db + .prepare("SELECT * FROM ciphers WHERE user_id = ?1 AND id IN (SELECT value FROM json_each(?2))") + .bind(&[claims.sub.clone().into(), ids_json.into()])? + .all() + .await? + .results::()? + .into_iter() + .map(|cipher| cipher.into()) + .collect(); db::touch_user_updated_at(&db, &claims.sub).await?; diff --git a/src/models/cipher.rs b/src/models/cipher.rs index b7317d8..0b7dd2d 100644 --- a/src/models/cipher.rs +++ b/src/models/cipher.rs @@ -155,7 +155,7 @@ impl Into for CipherDBModel { deleted_at: self.deleted_at, created_at: self.created_at, updated_at: self.updated_at, - object: "default_object".to_string(), + object: "cipherDetails".to_string(), organization_use_totp: false, edit: true, view_password: true, @@ -264,7 +264,7 @@ impl Serialize for Cipher { } fn default_object() -> String { - "cipher".to_string() + "cipherDetails".to_string() } fn default_true() -> bool {