diff --git a/services/backup/src/database/backup_item.rs b/services/backup/src/database/backup_item.rs --- a/services/backup/src/database/backup_item.rs +++ b/services/backup/src/database/backup_item.rs @@ -125,3 +125,46 @@ }) } } + +/// Corresponds to the items in the [`crate::constants::BACKUP_TABLE_INDEX_USERID_CREATED`] +/// global index +#[derive(Clone, Debug)] +pub struct OrderedBackupItem { + pub user_id: String, + pub created: DateTime, + pub backup_id: String, + pub user_keys: BlobInfo, +} + +impl TryFrom> for OrderedBackupItem { + type Error = DBItemError; + + fn try_from( + mut value: HashMap, + ) -> Result { + let user_id = String::try_from_attr( + BACKUP_TABLE_FIELD_USER_ID, + value.remove(BACKUP_TABLE_FIELD_USER_ID), + )?; + let created = DateTime::::try_from_attr( + BACKUP_TABLE_FIELD_CREATED, + value.remove(BACKUP_TABLE_FIELD_CREATED), + )?; + let backup_id = String::try_from_attr( + BACKUP_TABLE_FIELD_BACKUP_ID, + value.remove(BACKUP_TABLE_FIELD_BACKUP_ID), + )?; + + let user_keys = BlobInfo::try_from_attr( + BACKUP_TABLE_FIELD_USER_KEYS, + value.remove(BACKUP_TABLE_FIELD_USER_KEYS), + )?; + + Ok(OrderedBackupItem { + user_id, + created, + backup_id, + user_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 @@ -18,7 +18,7 @@ }; use self::{ - backup_item::BackupItem, + backup_item::{BackupItem, OrderedBackupItem}, log_item::{parse_log_item, LogItem}, }; @@ -97,7 +97,7 @@ pub async fn find_last_backup_item( &self, user_id: &str, - ) -> Result, Error> { + ) -> Result, Error> { let response = self .client .query() 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 @@ -3,10 +3,11 @@ use actix_web::{ error::ErrorBadRequest, web::{self, Bytes}, - HttpResponse, + HttpResponse, Responder, }; use comm_services_lib::{ auth::UserIdentity, + backup::LatestBackupIDResponse, blob::{client::BlobServiceClient, types::BlobInfo}, http::multipart::{get_named_text_field, get_text_field}, }; @@ -207,3 +208,57 @@ .streaming(stream), ) } + +#[instrument(name = "get_latest_backup_id", skip_all, fields(username = %path.as_str()))] +pub async fn get_latest_backup_id( + path: web::Path, + db_client: web::Data, +) -> actix_web::Result { + let username = path.into_inner(); + // Treat username as user_id in the initial version + let user_id = username; + + let Some(backup_item) = db_client + .find_last_backup_item(&user_id) + .await + .map_err(BackupError::from)? else + { + return Err(BackupError::NoBackup.into()); + }; + + let response = LatestBackupIDResponse { + backup_id: backup_item.backup_id, + }; + + Ok(web::Json(response)) +} + +#[instrument(name = "download_latest_backup_keys", skip_all, fields(username = %path.as_str()))] +pub async fn download_latest_backup_keys( + path: web::Path, + db_client: web::Data, + blob_client: web::Data, +) -> actix_web::Result { + let username = path.into_inner(); + // Treat username as user_id in the initial version + let user_id = username; + + let Some(backup_item) = db_client + .find_last_backup_item(&user_id) + .await + .map_err(BackupError::from)? else + { + return Err(BackupError::NoBackup.into()); + }; + + let stream = blob_client + .get(&backup_item.user_keys.blob_hash) + .await + .map_err(BackupError::from)?; + + Ok( + HttpResponse::Ok() + .content_type("application/octet-stream") + .streaming(stream), + ) +} diff --git a/services/backup/src/http/mod.rs b/services/backup/src/http/mod.rs --- a/services/backup/src/http/mod.rs +++ b/services/backup/src/http/mod.rs @@ -32,6 +32,17 @@ )) .app_data(db.clone()) .app_data(blob.clone()) + .service( + // Services that don't require authetication + web::scope("/backups/latest") + .service( + web::resource("{username}/backup_id") + .route(web::get().to(handlers::backup::get_latest_backup_id)), + ) + .service(web::resource("{username}/user_keys").route( + web::get().to(handlers::backup::download_latest_backup_keys), + )), + ) .service( // Services requiring authetication web::scope("/backups") diff --git a/services/comm-services-lib/src/backup/mod.rs b/services/comm-services-lib/src/backup/mod.rs new file mode 100644 --- /dev/null +++ b/services/comm-services-lib/src/backup/mod.rs @@ -0,0 +1,7 @@ +use serde::{Deserialize, Serialize}; + +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct LatestBackupIDResponse { + #[serde(rename = "backupID")] + pub backup_id: String, +} diff --git a/services/comm-services-lib/src/lib.rs b/services/comm-services-lib/src/lib.rs --- a/services/comm-services-lib/src/lib.rs +++ b/services/comm-services-lib/src/lib.rs @@ -1,4 +1,5 @@ pub mod auth; +pub mod backup; pub mod blob; pub mod constants; pub mod database;