diff --git a/native/native_rust_library/src/backup.rs b/native/native_rust_library/src/backup.rs --- a/native/native_rust_library/src/backup.rs +++ b/native/native_rust_library/src/backup.rs @@ -258,6 +258,7 @@ user_id, siwe_backup_msg, keyserver_device_id, + .. } = result; let siwe_backup_data = match siwe_backup_msg { @@ -380,19 +381,9 @@ Err(err) => return Err(err.into()), }; - let LatestBackupInfoResponse { - backup_id, - user_id, - siwe_backup_msg, - keyserver_device_id, - } = serde_json::from_slice(&backup_info_response)?; - - Ok(Some(LatestBackupInfoResponse { - backup_id, - user_id, - siwe_backup_msg, - keyserver_device_id, - })) + let response: LatestBackupInfoResponse = + serde_json::from_slice(&backup_info_response)?; + Ok(Some(response)) } async fn download_backup_keys( diff --git a/services/backup/src/database/mod.rs b/services/backup/src/database/mod.rs --- a/services/backup/src/database/mod.rs +++ b/services/backup/src/database/mod.rs @@ -13,7 +13,7 @@ types::{AttributeValue, DeleteRequest, ReturnValue, WriteRequest}, }; use comm_lib::{ - blob::client::BlobServiceClient, + blob::{client::BlobServiceClient, types::BlobInfo}, database::{ self, batch_operations::ExponentialBackoffConfig, parse_int_attribute, AttributeMap, Error, @@ -330,6 +330,33 @@ Ok(items) } + pub async fn get_blob_infos_and_size_for_logs( + &self, + user_id: &str, + backup_id: &str, + ) -> Result<(Vec, u64), Error> { + let mut blob_infos = Vec::new(); + let mut logs_ddb_size = 0u64; + + let log_items = self + .fetch_all_log_items_for_backup(user_id, backup_id) + .await?; + + use comm_lib::database::blob::BlobOrDBContent as LogContent; + for log in log_items { + match log.content { + LogContent::Blob(blob_info) => blob_infos.push(blob_info), + LogContent::Database(db_content) => { + logs_ddb_size += db_content.len() as u64; + } + } + + blob_infos.extend(log.attachments); + } + + Ok((blob_infos, logs_ddb_size)) + } + pub async fn remove_log_items_for_backup( &self, user_id: &str, diff --git a/services/backup/src/http/handlers/backup.rs b/services/backup/src/http/handlers/backup.rs --- a/services/backup/src/http/handlers/backup.rs +++ b/services/backup/src/http/handlers/backup.rs @@ -6,7 +6,10 @@ use comm_lib::{ auth::{AuthorizationCredential, UserIdentity}, backup::LatestBackupInfoResponse, - blob::{client::BlobServiceClient, types::BlobInfo}, + blob::{ + client::BlobServiceClient, + types::{http::BlobSizesRequest, BlobInfo}, + }, http::{ auth_service::Authenticated, multipart::{get_named_text_field, get_text_field}, @@ -430,6 +433,7 @@ path: web::Path, db_client: web::Data, auth_service: comm_lib::auth::AuthService, + blob_client: Authenticated, ) -> actix_web::Result { let user_identifier = path.into_inner(); let user_id = find_user_id(&user_identifier).await?; @@ -445,11 +449,22 @@ let keyserver_device_id = find_keyserver_device_for_user(&user_id, &auth_service).await?; + let total_backup_size = get_total_backup_size( + &user_id, + &backup_item.backup_id, + &auth_service, + &db_client, + &blob_client, + ) + .await?; + let response = LatestBackupInfoResponse { backup_id: backup_item.backup_id, user_id, siwe_backup_msg: backup_item.siwe_backup_msg, keyserver_device_id, + total_backup_size, + creation_timestamp: backup_item.created.to_rfc3339(), }; Ok(web::Json(response)) @@ -563,3 +578,41 @@ Ok((item, revokes)) } + +async fn get_total_backup_size( + user_id: &str, + backup_id: &str, + auth_service: &comm_lib::auth::AuthService, + db_client: &DatabaseClient, + blob_client: &BlobServiceClient, +) -> Result { + let backup_item = db_client + .find_backup_item(user_id, backup_id) + .await? + .ok_or(BackupError::NoBackup)?; + + // gather blob infos for backup item + let mut blob_infos = backup_item.attachments; + blob_infos.push(backup_item.user_keys); + blob_infos.extend(backup_item.user_data); + + // gather blob infos and DDB size for logs + let (log_blob_infos, ddb_logs_size) = db_client + .get_blob_infos_and_size_for_logs(user_id, &backup_item.backup_id) + .await?; + blob_infos.extend(log_blob_infos); + + // we have to re-auth blob client with s2s token because the sizes endpoint is service-only + let credential = auth_service.get_services_token().await?; + let blob_client = blob_client.with_authentication(credential.into()); + + // query Blob Service for blobs size + let blob_hashes = blob_infos.into_iter().map(|it| it.blob_hash).collect(); + let total_blobs_size = blob_client + .fetch_blob_sizes(BlobSizesRequest { blob_hashes }) + .await? + .total_size(); + + let total_backup_size = total_blobs_size + ddb_logs_size; + Ok(total_backup_size) +} diff --git a/shared/comm-lib/src/backup/mod.rs b/shared/comm-lib/src/backup/mod.rs --- a/shared/comm-lib/src/backup/mod.rs +++ b/shared/comm-lib/src/backup/mod.rs @@ -14,6 +14,9 @@ pub siwe_backup_msg: Option, #[serde(rename = "keyserverDeviceID")] pub keyserver_device_id: Option, + // ISO 8601 / RFC 3339 DateTime string + pub creation_timestamp: String, + pub total_backup_size: u64, } #[derive(Debug, Clone, Serialize, Deserialize)]