revolt_database/models/server_members/
model.rs

1use iso8601_timestamp::Timestamp;
2use revolt_permissions::{calculate_channel_permissions, ChannelPermission};
3use revolt_result::{create_error, Result};
4
5use crate::{
6    events::client::EventV1, util::permissions::DatabasePermissionQuery, Channel,
7    Database, File, Server, SystemMessage, User,
8};
9
10fn default_true() -> bool {
11    true
12}
13
14fn is_true(x: &bool) -> bool {
15    *x
16}
17
18auto_derived_partial!(
19    /// Server Member
20    pub struct Member {
21        /// Unique member id
22        #[serde(rename = "_id")]
23        pub id: MemberCompositeKey,
24
25        /// Time at which this user joined the server
26        pub joined_at: Timestamp,
27
28        /// Member's nickname
29        #[serde(skip_serializing_if = "Option::is_none")]
30        pub nickname: Option<String>,
31        /// Avatar attachment
32        #[serde(skip_serializing_if = "Option::is_none")]
33        pub avatar: Option<File>,
34
35        /// Member's roles
36        #[serde(skip_serializing_if = "Vec::is_empty", default)]
37        pub roles: Vec<String>,
38        /// Timestamp this member is timed out until
39        #[serde(skip_serializing_if = "Option::is_none")]
40        pub timeout: Option<Timestamp>,
41
42        /// Whether the member is server-wide voice muted
43        #[serde(skip_serializing_if = "is_true", default = "default_true")]
44        pub can_publish: bool,
45        /// Whether the member is server-wide voice deafened
46        #[serde(skip_serializing_if = "is_true", default = "default_true")]
47        pub can_receive: bool,
48        
49        // This value only exists in the database, not the models.
50        // If it is not-None, the database layer should return None to member fetching queries.
51        // pub pending_deletion_at: Option<Timestamp>
52    },
53    "PartialMember"
54);
55
56auto_derived!(
57    /// Composite primary key consisting of server and user id
58    #[derive(Hash, Default)]
59    pub struct MemberCompositeKey {
60        /// Server Id
61        pub server: String,
62        /// User Id
63        pub user: String,
64    }
65
66    /// Optional fields on server member object
67    pub enum FieldsMember {
68        Nickname,
69        Avatar,
70        Roles,
71        Timeout,
72        CanReceive,
73        CanPublish,
74        JoinedAt,
75    }
76
77    /// Member removal intention
78    pub enum RemovalIntention {
79        Leave,
80        Kick,
81        Ban,
82    }
83);
84
85impl Default for Member {
86    fn default() -> Self {
87        Self {
88            id: Default::default(),
89            joined_at: Timestamp::now_utc(),
90            nickname: None,
91            avatar: None,
92            roles: vec![],
93            timeout: None,
94            can_publish: true,
95            can_receive: true,
96        }
97    }
98}
99
100#[allow(clippy::disallowed_methods)]
101impl Member {
102    /// Create a new member in a server
103    pub async fn create(
104        db: &Database,
105        server: &Server,
106        user: &User,
107        channels: Option<Vec<Channel>>,
108    ) -> Result<(Member, Vec<Channel>)> {
109        if db.fetch_ban(&server.id, &user.id).await.is_ok() {
110            return Err(create_error!(Banned));
111        }
112
113        if db.fetch_member(&server.id, &user.id).await.is_ok() {
114            return Err(create_error!(AlreadyInServer));
115        }
116
117        let mut member = Member {
118            id: MemberCompositeKey {
119                server: server.id.to_string(),
120                user: user.id.to_string(),
121            },
122            ..Default::default()
123        };
124
125        if let Some(updated) = db.insert_or_merge_member(&member).await? {
126            member = updated;
127        }
128
129        let should_fetch = channels.is_none();
130        let mut channels = channels.unwrap_or_default();
131
132        if should_fetch {
133            let query = DatabasePermissionQuery::new(db, user).server(server);
134            let existing_channels = db.fetch_channels(&server.channels).await?;
135
136            for channel in existing_channels {
137                let mut channel_query = query.clone().channel(&channel);
138
139                if calculate_channel_permissions(&mut channel_query)
140                    .await
141                    .has_channel_permission(ChannelPermission::ViewChannel)
142                {
143                    channels.push(channel);
144                }
145            }
146        }
147
148        let emojis = db.fetch_emoji_by_parent_id(&server.id).await?;
149
150        #[allow(unused_mut)]
151        let mut voice_states = Vec::new();
152
153        #[cfg(feature = "voice")]
154        for channel in &channels {
155            if let Ok(Some(voice_state)) = crate::voice::get_channel_voice_state(channel).await {
156                voice_states.push(voice_state)
157            }
158        }
159
160        EventV1::ServerMemberJoin {
161            id: server.id.clone(),
162            user: user.id.clone(),
163            member: member.clone().into(),
164        }
165        .p(server.id.clone())
166        .await;
167
168        EventV1::ServerCreate {
169            id: server.id.clone(),
170            server: server.clone().into(),
171            channels: channels
172                .clone()
173                .into_iter()
174                .map(|channel| channel.into())
175                .collect(),
176            emojis: emojis.into_iter().map(|emoji| emoji.into()).collect(),
177            voice_states
178        }
179        .private(user.id.clone())
180        .await;
181
182        if let Some(id) = server
183            .system_messages
184            .as_ref()
185            .and_then(|x| x.user_joined.as_ref())
186        {
187            SystemMessage::UserJoined {
188                id: user.id.clone(),
189            }
190            .into_message(id.to_string())
191            .send_without_notifications(db, None, None, false, false, false)
192            .await
193            .ok();
194        }
195
196        Ok((member, channels))
197    }
198
199    /// Update member data
200    pub async fn update(
201        &mut self,
202        db: &Database,
203        partial: PartialMember,
204        remove: Vec<FieldsMember>,
205    ) -> Result<()> {
206        for field in &remove {
207            self.remove_field(field);
208        }
209
210        self.apply_options(partial.clone());
211
212        db.update_member(&self.id, &partial, remove.clone()).await?;
213
214        EventV1::ServerMemberUpdate {
215            id: self.id.clone().into(),
216            data: partial.into(),
217            clear: remove.into_iter().map(|field| field.into()).collect(),
218        }
219        .p(self.id.server.clone())
220        .await;
221
222        Ok(())
223    }
224
225    pub fn remove_field(&mut self, field: &FieldsMember) {
226        match field {
227            FieldsMember::JoinedAt => (),
228            FieldsMember::Avatar => self.avatar = None,
229            FieldsMember::Nickname => self.nickname = None,
230            FieldsMember::Roles => self.roles.clear(),
231            FieldsMember::Timeout => self.timeout = None,
232            FieldsMember::CanReceive => self.can_receive = true,
233            FieldsMember::CanPublish => self.can_publish = true,
234        }
235    }
236
237    /// Get this user's current ranking
238    pub fn get_ranking(&self, server: &Server) -> i64 {
239        let mut value = i64::MAX;
240        for role in &self.roles {
241            if let Some(role) = server.roles.get(role) {
242                if role.rank < value {
243                    value = role.rank;
244                }
245            }
246        }
247
248        value
249    }
250
251    /// Check whether this member is in timeout
252    pub fn in_timeout(&self) -> bool {
253        if let Some(timeout) = self.timeout {
254            *timeout > *Timestamp::now_utc()
255        } else {
256            false
257        }
258    }
259
260    /// Remove member from server
261    pub async fn remove(
262        self,
263        db: &Database,
264        server: &Server,
265        intention: RemovalIntention,
266        silent: bool,
267    ) -> Result<()> {
268        db.soft_delete_member(&self.id).await?;
269
270        EventV1::ServerMemberLeave {
271            id: self.id.server.to_string(),
272            user: self.id.user.to_string(),
273            reason: intention.clone().into(),
274        }
275        .p(self.id.server.to_string())
276        .await;
277
278        if !silent {
279            if let Some(id) = server
280                .system_messages
281                .as_ref()
282                .and_then(|x| match intention {
283                    RemovalIntention::Leave => x.user_left.as_ref(),
284                    RemovalIntention::Kick => x.user_kicked.as_ref(),
285                    RemovalIntention::Ban => x.user_banned.as_ref(),
286                })
287            {
288                match intention {
289                    RemovalIntention::Leave => SystemMessage::UserLeft { id: self.id.user },
290                    RemovalIntention::Kick => SystemMessage::UserKicked { id: self.id.user },
291                    RemovalIntention::Ban => SystemMessage::UserBanned { id: self.id.user },
292                }
293                .into_message(id.to_string())
294                // TODO: support notifications here in the future?
295                .send_without_notifications(db, None, None, false, false, false)
296                .await
297                .ok();
298            }
299        }
300
301        Ok(())
302    }
303}
304
305#[cfg(test)]
306mod tests {
307    use iso8601_timestamp::{Duration, Timestamp};
308    use revolt_models::v0::DataCreateServer;
309
310    use crate::{Member, PartialMember, RemovalIntention, Server, User};
311
312    #[async_std::test]
313    async fn muted_member_rejoin() {
314        database_test!(|db| async move {
315            match db {
316                crate::Database::Reference(_) => return,
317                crate::Database::MongoDb(_) => (),
318            }
319            let owner = User::create(&db, "Server Owner".to_string(), None, None)
320                .await
321                .unwrap();
322
323            let kickable_user = User::create(&db, "Member".to_string(), None, None)
324                .await
325                .unwrap();
326
327            let server = Server::create(
328                &db,
329                DataCreateServer {
330                    name: "Server".to_string(),
331                    description: None,
332                    nsfw: None,
333                },
334                &owner,
335                false,
336            )
337            .await
338            .unwrap()
339            .0;
340
341            Member::create(&db, &server, &owner, None).await.unwrap();
342            let mut kickable_member = Member::create(&db, &server, &kickable_user, None)
343                .await
344                .unwrap()
345                .0;
346
347            kickable_member
348                .update(
349                    &db,
350                    PartialMember {
351                        timeout: Some(Timestamp::now_utc() + Duration::minutes(5)),
352                        ..Default::default()
353                    },
354                    vec![],
355                )
356                .await
357                .unwrap();
358
359            assert!(kickable_member.in_timeout());
360
361            kickable_member
362                .remove(&db, &server, RemovalIntention::Kick, false)
363                .await
364                .unwrap();
365
366            let kickable_member = Member::create(&db, &server, &kickable_user, None)
367                .await
368                .unwrap()
369                .0;
370
371            assert!(kickable_member.in_timeout())
372        });
373    }
374}