diff --git a/src/handlers/attachments.rs b/src/handlers/attachments.rs index 668f7db..495bafd 100644 --- a/src/handlers/attachments.rs +++ b/src/handlers/attachments.rs @@ -443,16 +443,33 @@ pub async fn hydrate_ciphers_attachments( } }; - let mut map = load_attachment_map_json(db, body, path).await?; + let map = load_attachment_map_json(db, body, path).await?; + apply_attachment_map(ciphers, map); - for cipher in ciphers.iter_mut() { - if let Some(list) = map.remove(&cipher.id) { - if !list.is_empty() { - cipher.attachments = Some(list); - } - } + Ok(()) +} + +/// Batch hydrate attachments for all ciphers belonging to a user without serializing ids. +pub async fn hydrate_ciphers_attachments_for_user( + db: &D1Database, + env: &Env, + ciphers: &mut [Cipher], + user_id: &str, +) -> Result<(), AppError> { + if ciphers.is_empty() { + return Ok(()); } + if !attachments_enabled(env) { + for cipher in ciphers.iter_mut() { + cipher.attachments = None; + } + return Ok(()); + } + + let map = load_attachment_map_for_user(db, user_id).await?; + apply_attachment_map(ciphers, map); + Ok(()) } @@ -627,6 +644,32 @@ async fn load_attachment_map_json( .results() .map_err(|_| AppError::Database)?; + Ok(build_attachment_map(attachments)) +} + +async fn load_attachment_map_for_user( + db: &D1Database, + user_id: &str, +) -> Result>, AppError> { + let attachments: Vec = db + .prepare( + "SELECT a.* FROM attachments a \ + JOIN ciphers c ON a.cipher_id = c.id \ + WHERE c.user_id = ?1", + ) + .bind(&[user_id.into()])? + .all() + .await + .map_err(|_| AppError::Database)? + .results() + .map_err(|_| AppError::Database)?; + + Ok(build_attachment_map(attachments)) +} + +fn build_attachment_map( + attachments: Vec, +) -> HashMap> { let mut map: HashMap> = HashMap::new(); for attachment in attachments { @@ -636,7 +679,17 @@ async fn load_attachment_map_json( .push(attachment.to_response(None)); } - Ok(map) + map +} + +fn apply_attachment_map(ciphers: &mut [Cipher], mut map: HashMap>) { + for cipher in ciphers.iter_mut() { + if let Some(list) = map.remove(&cipher.id) { + if !list.is_empty() { + cipher.attachments = Some(list); + } + } + } } async fn upload_to_r2( diff --git a/src/handlers/ciphers.rs b/src/handlers/ciphers.rs index 7ef4f16..e7e4c66 100644 --- a/src/handlers/ciphers.rs +++ b/src/handlers/ciphers.rs @@ -233,7 +233,13 @@ pub async fn list_ciphers( let mut ciphers: Vec = ciphers_db.into_iter().map(|c| c.into()).collect(); - attachments::hydrate_ciphers_attachments(&db, env.as_ref(), &mut ciphers, None, None).await?; + attachments::hydrate_ciphers_attachments_for_user( + &db, + env.as_ref(), + &mut ciphers, + &claims.sub, + ) + .await?; Ok(Json(CipherListResponse { data: ciphers, diff --git a/src/handlers/sync.rs b/src/handlers/sync.rs index 27fc07c..a0771f3 100644 --- a/src/handlers/sync.rs +++ b/src/handlers/sync.rs @@ -65,7 +65,8 @@ pub async fn get_sync_data( .collect::>(); let mut ciphers = ciphers; - attachments::hydrate_ciphers_attachments(&db, env.as_ref(), &mut ciphers, None, None).await?; + attachments::hydrate_ciphers_attachments_for_user(&db, env.as_ref(), &mut ciphers, &user_id) + .await?; let profile = Profile::from_user(user)?;