revolt_database/models/server_members/
model.rs1use 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, Database, File,
7 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 pub struct Member {
21 #[serde(rename = "_id")]
23 pub id: MemberCompositeKey,
24
25 pub joined_at: Timestamp,
27
28 #[serde(skip_serializing_if = "Option::is_none")]
30 pub nickname: Option<String>,
31 #[serde(skip_serializing_if = "Option::is_none")]
33 pub pronouns: Option<String>,
34 #[serde(skip_serializing_if = "Option::is_none")]
36 pub avatar: Option<File>,
37
38 #[serde(skip_serializing_if = "Vec::is_empty", default)]
40 pub roles: Vec<String>,
41 #[serde(skip_serializing_if = "Option::is_none")]
43 pub timeout: Option<Timestamp>,
44
45 #[serde(skip_serializing_if = "is_true", default = "default_true")]
47 pub can_publish: bool,
48 #[serde(skip_serializing_if = "is_true", default = "default_true")]
50 pub can_receive: bool,
51 },
55 "PartialMember"
56);
57
58auto_derived!(
59 #[derive(Hash, Default)]
61 pub struct MemberCompositeKey {
62 pub server: String,
64 pub user: String,
66 }
67
68 pub enum FieldsMember {
70 Nickname,
71 Pronouns,
72 Avatar,
73 Roles,
74 Timeout,
75 CanReceive,
76 CanPublish,
77 JoinedAt,
78 VoiceChannel,
79 }
80
81 pub enum RemovalIntention {
83 Leave,
84 Kick,
85 Ban,
86 }
87);
88
89impl Default for Member {
90 fn default() -> Self {
91 Self {
92 id: Default::default(),
93 joined_at: Timestamp::now_utc(),
94 nickname: None,
95 pronouns: None,
96 avatar: None,
97 roles: vec![],
98 timeout: None,
99 can_publish: true,
100 can_receive: true,
101 }
102 }
103}
104
105#[allow(clippy::disallowed_methods)]
106impl Member {
107 pub async fn create(
109 db: &Database,
110 server: &Server,
111 user: &User,
112 channels: Option<Vec<Channel>>,
113 ) -> Result<(Member, Vec<Channel>)> {
114 if db.fetch_ban(&server.id, &user.id).await.is_ok() {
115 return Err(create_error!(Banned));
116 }
117
118 if db.fetch_member(&server.id, &user.id).await.is_ok() {
119 return Err(create_error!(AlreadyInServer));
120 }
121
122 let mut member = Member {
123 id: MemberCompositeKey {
124 server: server.id.to_string(),
125 user: user.id.to_string(),
126 },
127 ..Default::default()
128 };
129
130 if let Some(updated) = db.insert_or_merge_member(&member).await? {
131 member = updated;
132 }
133
134 let should_fetch = channels.is_none();
135 let mut channels = channels.unwrap_or_default();
136
137 if should_fetch {
138 let query = DatabasePermissionQuery::new(db, user).server(server);
139 let existing_channels = db.fetch_channels(&server.channels).await?;
140
141 for channel in existing_channels {
142 let mut channel_query = query.clone().channel(&channel);
143
144 if calculate_channel_permissions(&mut channel_query)
145 .await
146 .has_channel_permission(ChannelPermission::ViewChannel)
147 {
148 channels.push(channel);
149 }
150 }
151 }
152
153 let emojis = db.fetch_emoji_by_parent_id(&server.id).await?;
154
155 #[allow(unused_mut)]
156 let mut voice_states = Vec::new();
157
158 #[cfg(feature = "voice")]
159 for channel in &channels {
160 if let Ok(Some(voice_state)) = crate::voice::get_channel_voice_state(
161 &crate::voice::UserVoiceChannel::from_channel(channel),
162 )
163 .await
164 {
165 voice_states.push(voice_state)
166 }
167 }
168
169 EventV1::ServerMemberJoin {
170 id: server.id.clone(),
171 user: user.id.clone(),
172 member: member.clone().into(),
173 }
174 .p(server.id.clone())
175 .await;
176
177 EventV1::ServerCreate {
178 id: server.id.clone(),
179 server: server.clone().into(db).await,
180 channels: channels
181 .clone()
182 .into_iter()
183 .map(|channel| channel.into())
184 .collect(),
185 emojis: emojis.into_iter().map(|emoji| emoji.into()).collect(),
186 voice_states,
187 }
188 .private(user.id.clone())
189 .await;
190
191 if let Some(id) = server
192 .system_messages
193 .as_ref()
194 .and_then(|x| x.user_joined.as_ref())
195 {
196 SystemMessage::UserJoined {
197 id: user.id.clone(),
198 }
199 .into_message(id.to_string())
200 .send_without_notifications(db, None, None, false, false, false)
201 .await
202 .ok();
203 }
204
205 Ok((member, channels))
206 }
207
208 pub async fn update(
210 &mut self,
211 db: &Database,
212 partial: PartialMember,
213 remove: Vec<FieldsMember>,
214 ) -> Result<()> {
215 for field in &remove {
216 self.remove_field(field);
217 }
218
219 self.apply_options(partial.clone());
220
221 db.update_member(&self.id, &partial, remove.clone()).await?;
222
223 EventV1::ServerMemberUpdate {
224 id: self.id.clone().into(),
225 data: partial.into(),
226 clear: remove.into_iter().map(|field| field.into()).collect(),
227 }
228 .p(self.id.server.clone())
229 .await;
230
231 Ok(())
232 }
233
234 pub fn remove_field(&mut self, field: &FieldsMember) {
235 match field {
236 FieldsMember::JoinedAt => {}
237 FieldsMember::Avatar => self.avatar = None,
238 FieldsMember::Nickname => self.nickname = None,
239 FieldsMember::Pronouns => self.pronouns = None,
240 FieldsMember::Roles => self.roles.clear(),
241 FieldsMember::Timeout => self.timeout = None,
242 FieldsMember::CanReceive => self.can_receive = true,
243 FieldsMember::CanPublish => self.can_publish = true,
244 FieldsMember::VoiceChannel => {}
245 }
246 }
247
248 pub fn generate_diff(&self, partial: &PartialMember, remove: &[FieldsMember]) -> PartialMember {
250 let mut before = PartialMember::default();
251
252 generate_diff!(
253 self, before, partial, remove,
254 (
255 (FieldsMember::Nickname) nickname,
256 (FieldsMember::Avatar) avatar,
257 (FieldsMember::Timeout) timeout,
258 (FieldsMember::Pronouns) pronouns,
259 ((default) FieldsMember::Roles) roles,
260 ((default) FieldsMember::CanPublish) can_publish,
261 ((default) FieldsMember::CanReceive) can_receive,
262 )
263 );
264
265 before
266 }
267
268 pub fn get_ranking(&self, server: &Server) -> i64 {
270 let mut value = i64::MAX;
271 for role in &self.roles {
272 if let Some(role) = server.roles.get(role) {
273 if role.rank < value {
274 value = role.rank;
275 }
276 }
277 }
278
279 value
280 }
281
282 pub fn in_timeout(&self) -> bool {
284 if let Some(timeout) = self.timeout {
285 *timeout > *Timestamp::now_utc()
286 } else {
287 false
288 }
289 }
290
291 pub async fn remove(
293 &self,
294 db: &Database,
295 server: &Server,
296 intention: RemovalIntention,
297 silent: bool,
298 ) -> Result<()> {
299 db.soft_delete_member(&self.id).await?;
300
301 EventV1::ServerMemberLeave {
302 id: self.id.server.to_string(),
303 user: self.id.user.to_string(),
304 reason: intention.clone().into(),
305 }
306 .p(self.id.server.to_string())
307 .await;
308
309 if !silent {
310 if let Some(id) = server
311 .system_messages
312 .as_ref()
313 .and_then(|x| match intention {
314 RemovalIntention::Leave => x.user_left.as_ref(),
315 RemovalIntention::Kick => x.user_kicked.as_ref(),
316 RemovalIntention::Ban => x.user_banned.as_ref(),
317 })
318 {
319 match intention {
320 RemovalIntention::Leave => SystemMessage::UserLeft {
321 id: self.id.user.clone(),
322 },
323 RemovalIntention::Kick => SystemMessage::UserKicked {
324 id: self.id.user.clone(),
325 },
326 RemovalIntention::Ban => SystemMessage::UserBanned {
327 id: self.id.user.clone(),
328 },
329 }
330 .into_message(id.to_string())
331 .send_without_notifications(db, None, None, false, false, false)
333 .await
334 .ok();
335 }
336 }
337
338 Ok(())
339 }
340}
341
342#[cfg(test)]
343mod tests {
344 use iso8601_timestamp::{Duration, Timestamp};
345 use revolt_models::v0::DataCreateServer;
346
347 use crate::{Member, PartialMember, RemovalIntention, Server, User};
348
349 #[tokio::test]
350 async fn muted_member_rejoin() {
351 database_test!(|db| async move {
352 match db {
353 crate::Database::Reference(_) => return,
354 crate::Database::MongoDb(_) => (),
355 }
356 let owner = User::create(&db, "Server Owner".to_string(), None, None)
357 .await
358 .unwrap();
359
360 let kickable_user = User::create(&db, "Member".to_string(), None, None)
361 .await
362 .unwrap();
363
364 let server = Server::create(
365 &db,
366 DataCreateServer {
367 name: "Server".to_string(),
368 description: None,
369 nsfw: None,
370 },
371 &owner,
372 false,
373 )
374 .await
375 .unwrap()
376 .0;
377
378 Member::create(&db, &server, &owner, None).await.unwrap();
379 let mut kickable_member = Member::create(&db, &server, &kickable_user, None)
380 .await
381 .unwrap()
382 .0;
383
384 kickable_member
385 .update(
386 &db,
387 PartialMember {
388 timeout: Some(Timestamp::now_utc() + Duration::minutes(5)),
389 ..Default::default()
390 },
391 vec![],
392 )
393 .await
394 .unwrap();
395
396 assert!(kickable_member.in_timeout());
397
398 kickable_member
399 .remove(&db, &server, RemovalIntention::Kick, false)
400 .await
401 .unwrap();
402
403 let kickable_member = Member::create(&db, &server, &kickable_user, None)
404 .await
405 .unwrap()
406 .0;
407
408 assert!(kickable_member.in_timeout())
409 });
410 }
411}