diff --git a/services/identity/src/database/device_list.rs b/services/identity/src/database/device_list.rs --- a/services/identity/src/database/device_list.rs +++ b/services/identity/src/database/device_list.rs @@ -241,6 +241,20 @@ } } +impl From> + for protos::auth::UserDevicesPlatformDetails +{ + fn from(devices_map: HashMap) -> Self { + let devices_platform_details = devices_map + .into_iter() + .map(|(device_id, platform_details)| (device_id, platform_details.into())) + .collect(); + Self { + devices_platform_details, + } + } +} + // helper structs for converting to/from attribute values for sort key (a.k.a itemID) pub struct DeviceIDAttribute(pub String); struct DeviceListKeyAttribute(DateTime); @@ -333,37 +347,7 @@ .and_then(Prekey::try_from)?; let login_time: DateTime = attrs.take_attr(ATTR_LOGIN_TIME)?; - - // New schema contains PlatformDetails attribute while legacy schema - // contains "deviceType" and "codeVersion" top-level attributes - let platform_details = match attrs - .take_attr::>(ATTR_PLATFORM_DETAILS)? - { - Some(platform_details) => platform_details, - None => { - let raw_device_type: String = attrs.take_attr(OLD_ATTR_DEVICE_TYPE)?; - let device_type = DeviceType::from_str_name(&raw_device_type) - .ok_or_else(|| { - DBItemError::new( - OLD_ATTR_DEVICE_TYPE.to_string(), - raw_device_type.into(), - DBItemAttributeError::InvalidValue, - ) - })?; - let code_version = attrs - .remove(OLD_ATTR_CODE_VERSION) - .and_then(|attr| attr.as_n().ok().cloned()) - .and_then(|val| val.parse::().ok()) - .unwrap_or_default(); - - PlatformDetails { - device_type, - code_version, - state_version: None, - major_desktop_version: None, - } - } - }; + let platform_details = take_platform_details(&mut attrs)?; Ok(Self { user_id, @@ -1153,6 +1137,66 @@ Ok(device_lists) } + /// Gets [`PlatformDetails`] for multiple users. + /// Takes iterable collection of tuples `(UserID, DeviceID)`. + /// Returns nested map: `Map>`. + #[tracing::instrument(skip_all)] + pub async fn get_devices_platform_details( + &self, + user_device_ids: impl IntoIterator, + ) -> Result>, Error> { + let primary_keys = user_device_ids + .into_iter() + .map(|(user_id, device_id)| { + AttributeMap::from([ + ( + devices_table::ATTR_USER_ID.to_string(), + AttributeValue::S(user_id), + ), + ( + devices_table::ATTR_ITEM_ID.to_string(), + DeviceIDAttribute(device_id).into(), + ), + ]) + }) + .collect::>(); + let projection_expression = Some( + [ + devices_table::ATTR_USER_ID, + devices_table::ATTR_ITEM_ID, + devices_table::ATTR_PLATFORM_DETAILS, + // we need these for legacy devices without ATTR_PLATFORM_DETAILS + devices_table::OLD_ATTR_DEVICE_TYPE, + devices_table::OLD_ATTR_CODE_VERSION, + ] + .join(", "), + ); + + let fetched_results = comm_lib::database::batch_operations::batch_get( + &self.client, + devices_table::NAME, + primary_keys, + projection_expression, + Default::default(), + ) + .await?; + + let mut users_devices_platform_details = HashMap::new(); + for mut item in fetched_results { + let user_id: String = item.take_attr(devices_table::ATTR_USER_ID)?; + let device_id: DeviceIDAttribute = + item.remove(devices_table::ATTR_ITEM_ID).try_into()?; + let platform_details = take_platform_details(&mut item)?; + + let user_devices = users_devices_platform_details + .entry(user_id) + .or_insert_with(HashMap::new); + user_devices.insert(device_id.into_inner(), platform_details); + } + + Ok(users_devices_platform_details) + } + /// Adds device data to devices table. If the device already exists, its /// data is overwritten. This does not update the device list; the device ID /// should already be present in the device list. @@ -1800,6 +1844,44 @@ .consistent_read(true) } +fn take_platform_details( + device_attrs: &mut AttributeMap, +) -> Result { + let platform_details_attr: Option = + device_attrs.take_attr::>(ATTR_PLATFORM_DETAILS)?; + + // New schema contains PlatformDetails attribute while legacy schema + // contains "deviceType" and "codeVersion" top-level attributes + let platform_details = match platform_details_attr { + Some(platform_details) => platform_details, + None => { + let raw_device_type: String = + device_attrs.take_attr(OLD_ATTR_DEVICE_TYPE)?; + let device_type = DeviceType::from_str_name(&raw_device_type) + .ok_or_else(|| { + DBItemError::new( + OLD_ATTR_DEVICE_TYPE.to_string(), + raw_device_type.into(), + DBItemAttributeError::InvalidValue, + ) + })?; + let code_version = device_attrs + .remove(OLD_ATTR_CODE_VERSION) + .and_then(|attr| attr.as_n().ok().cloned()) + .and_then(|val| val.parse::().ok()) + .unwrap_or_default(); + + PlatformDetails { + device_type, + code_version, + state_version: None, + major_desktop_version: None, + } + } + }; + Ok(platform_details) +} + /// [`transact_update_devicelist()`] closure result struct UpdateOperationInfo { /// (optional) transactional DDB operation to be performed