xmtp_mls/groups/lifecycle/
restored.rs1use super::*;
4use xmtp_db::ConnectionExt;
5use xmtp_proto::xmtp::device_sync::group_backup::GroupSave;
6
7impl<Context: XmtpSharedContext> MlsGroup<Context> {
8 pub fn restore_from_archive(context: &Context, save: &GroupSave) -> Result<bool, GroupError> {
11 let group_id = GroupId::try_from(save.id.as_slice())?;
12 state_write(context.mls_storage(), |tx| {
13 if tx.storage().db().find_group(&group_id)?.is_some() {
14 return Ok::<_, GroupError>(Continue(merge_activity(
15 &tx.storage().db(),
16 &group_id,
17 save.last_message_ns,
18 )?));
19 }
20 let conversation_type: ConversationType = save.conversation_type().try_into()?;
21 let pair = if conversation_type == ConversationType::Dm {
22 Some(crate::groups::parse_canonical_dm_id(save.dm_id.as_deref())?)
23 } else {
24 None
25 };
26 let attributes = save
27 .mutable_metadata
28 .as_ref()
29 .map(|metadata| &metadata.attributes);
30 let attribute = |name| {
31 attributes
32 .and_then(|attributes| attributes.get(name))
33 .cloned()
34 };
35 let opts = GroupMetadataOptions {
36 name: attribute("group_name"),
37 image_url_square: attribute("group_image_url_square"),
38 description: attribute("description"),
39 app_data: attribute("app_data"),
40 message_disappearing_settings: match (
41 save.message_disappear_from_ns,
42 save.message_disappear_in_ns,
43 ) {
44 (Some(from_ns), Some(in_ns)) => {
45 Some(MessageDisappearingSettings::new(from_ns, in_ns))
46 }
47 _ => None,
48 },
49 };
50 let kind = match &pair {
51 Some(pair) => InitialGroupKind::RestoredDm {
52 member_one_inbox_id: &pair.member_one_inbox_id,
53 member_two_inbox_id: &pair.member_two_inbox_id,
54 },
55 None => InitialGroupKind::Group {
56 conversation_type,
57 oneshot_message: None,
58 },
59 };
60 let policy = if pair.is_some() {
61 PolicySet::new_dm()
62 } else {
63 PolicySet::default()
64 };
65 let dictionary = initial_dictionary(
68 kind,
69 &policy
70 .to_proto()
71 .map_err(group_permissions::GroupMutablePermissionsError::from)
72 .map_err(MetadataPermissionsError::from)?,
73 &opts,
74 context.inbox_id(),
75 None,
76 &context
77 .server_configuration()
78 .configuration()
79 .application_components,
80 )
81 .map_err(app_data::migration::BootstrapSynthesisError::from)?;
82 let config = build_group_config(dictionary)?;
83 if let Some(pair) = &pair {
84 Self::insert_dm_row(
85 context,
86 tx,
87 GroupMembershipState::Restored,
88 pair,
89 &opts,
90 NewGroupId::Placeholder(group_id),
91 &config,
92 false,
93 )?;
94 } else {
95 Self::insert_group_row(
96 context,
97 tx,
98 NewGroupId::Placeholder(group_id),
99 GroupMembershipState::Restored,
100 conversation_type,
101 &opts,
102 &config,
103 false,
104 )?;
105 }
106 let storage = tx.storage();
107 let db = storage.db();
108 use xmtp_db::diesel::prelude::*;
109 use xmtp_db::schema::groups::dsl;
110 db.raw_query(|conn| {
111 xmtp_db::diesel::update(dsl::groups.find(&group_id))
112 .set((
113 dsl::created_at_ns.eq(save.created_at_ns),
114 dsl::added_by_inbox_id.eq(&save.added_by_inbox_id),
115 dsl::message_disappear_from_ns.eq(save.message_disappear_from_ns),
116 dsl::message_disappear_in_ns.eq(save.message_disappear_in_ns),
117 dsl::should_publish_commit_log.eq(false),
118 ))
119 .execute(conn)
120 })?;
121 merge_activity(&db, &group_id, save.last_message_ns)?;
122 Ok(Continue(true))
123 })
124 .map(TransactionOutcome::into_continued)
125 }
126}
127
128fn merge_activity(
129 db: &impl DbQuery,
130 group_id: &GroupId,
131 timestamp: Option<i64>,
132) -> Result<bool, StorageError> {
133 let Some(timestamp) = timestamp else {
134 return Ok(false);
135 };
136 use xmtp_db::diesel::prelude::*;
137 use xmtp_db::schema::groups::dsl;
138 Ok(db.raw_query(|conn| {
139 xmtp_db::diesel::update(dsl::groups.find(group_id))
140 .filter(
141 dsl::last_message_ns
142 .is_null()
143 .or(dsl::last_message_ns.lt(timestamp)),
144 )
145 .set(dsl::last_message_ns.eq(Some(timestamp)))
146 .execute(conn)
147 })? > 0)
148}