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 avatar: Option<File>,
34
35 #[serde(skip_serializing_if = "Vec::is_empty", default)]
37 pub roles: Vec<String>,
38 #[serde(skip_serializing_if = "Option::is_none")]
40 pub timeout: Option<Timestamp>,
41
42 #[serde(skip_serializing_if = "is_true", default = "default_true")]
44 pub can_publish: bool,
45 #[serde(skip_serializing_if = "is_true", default = "default_true")]
47 pub can_receive: bool,
48 },
52 "PartialMember"
53);
54
55auto_derived!(
56 #[derive(Hash, Default)]
58 pub struct MemberCompositeKey {
59 pub server: String,
61 pub user: String,
63 }
64
65 pub enum FieldsMember {
67 Nickname,
68 Avatar,
69 Roles,
70 Timeout,
71 CanReceive,
72 CanPublish,
73 JoinedAt,
74 VoiceChannel,
75 }
76
77 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 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(
156 &crate::voice::UserVoiceChannel::from_channel(channel),
157 )
158 .await
159 {
160 voice_states.push(voice_state)
161 }
162 }
163
164 EventV1::ServerMemberJoin {
165 id: server.id.clone(),
166 user: user.id.clone(),
167 member: member.clone().into(),
168 }
169 .p(server.id.clone())
170 .await;
171
172 EventV1::ServerCreate {
173 id: server.id.clone(),
174 server: server.clone().into(),
175 channels: channels
176 .clone()
177 .into_iter()
178 .map(|channel| channel.into())
179 .collect(),
180 emojis: emojis.into_iter().map(|emoji| emoji.into()).collect(),
181 voice_states,
182 }
183 .private(user.id.clone())
184 .await;
185
186 if let Some(id) = server
187 .system_messages
188 .as_ref()
189 .and_then(|x| x.user_joined.as_ref())
190 {
191 SystemMessage::UserJoined {
192 id: user.id.clone(),
193 }
194 .into_message(id.to_string())
195 .send_without_notifications(db, None, None, false, false, false)
196 .await
197 .ok();
198 }
199
200 Ok((member, channels))
201 }
202
203 pub async fn update(
205 &mut self,
206 db: &Database,
207 partial: PartialMember,
208 remove: Vec<FieldsMember>,
209 ) -> Result<()> {
210 for field in &remove {
211 self.remove_field(field);
212 }
213
214 self.apply_options(partial.clone());
215
216 db.update_member(&self.id, &partial, remove.clone()).await?;
217
218 EventV1::ServerMemberUpdate {
219 id: self.id.clone().into(),
220 data: partial.into(),
221 clear: remove.into_iter().map(|field| field.into()).collect(),
222 }
223 .p(self.id.server.clone())
224 .await;
225
226 Ok(())
227 }
228
229 pub fn remove_field(&mut self, field: &FieldsMember) {
230 match field {
231 FieldsMember::JoinedAt => {}
232 FieldsMember::Avatar => self.avatar = None,
233 FieldsMember::Nickname => self.nickname = None,
234 FieldsMember::Roles => self.roles.clear(),
235 FieldsMember::Timeout => self.timeout = None,
236 FieldsMember::CanReceive => self.can_receive = true,
237 FieldsMember::CanPublish => self.can_publish = true,
238 FieldsMember::VoiceChannel => {}
239 }
240 }
241
242 pub fn get_ranking(&self, server: &Server) -> i64 {
244 let mut value = i64::MAX;
245 for role in &self.roles {
246 if let Some(role) = server.roles.get(role) {
247 if role.rank < value {
248 value = role.rank;
249 }
250 }
251 }
252
253 value
254 }
255
256 pub fn in_timeout(&self) -> bool {
258 if let Some(timeout) = self.timeout {
259 *timeout > *Timestamp::now_utc()
260 } else {
261 false
262 }
263 }
264
265 pub async fn remove(
267 self,
268 db: &Database,
269 server: &Server,
270 intention: RemovalIntention,
271 silent: bool,
272 ) -> Result<()> {
273 db.soft_delete_member(&self.id).await?;
274
275 EventV1::ServerMemberLeave {
276 id: self.id.server.to_string(),
277 user: self.id.user.to_string(),
278 reason: intention.clone().into(),
279 }
280 .p(self.id.server.to_string())
281 .await;
282
283 if !silent {
284 if let Some(id) = server
285 .system_messages
286 .as_ref()
287 .and_then(|x| match intention {
288 RemovalIntention::Leave => x.user_left.as_ref(),
289 RemovalIntention::Kick => x.user_kicked.as_ref(),
290 RemovalIntention::Ban => x.user_banned.as_ref(),
291 })
292 {
293 match intention {
294 RemovalIntention::Leave => SystemMessage::UserLeft { id: self.id.user },
295 RemovalIntention::Kick => SystemMessage::UserKicked { id: self.id.user },
296 RemovalIntention::Ban => SystemMessage::UserBanned { id: self.id.user },
297 }
298 .into_message(id.to_string())
299 .send_without_notifications(db, None, None, false, false, false)
301 .await
302 .ok();
303 }
304 }
305
306 Ok(())
307 }
308}
309
310#[cfg(test)]
311mod tests {
312 use iso8601_timestamp::{Duration, Timestamp};
313 use revolt_models::v0::DataCreateServer;
314
315 use crate::{Member, PartialMember, RemovalIntention, Server, User};
316
317 #[async_std::test]
318 async fn muted_member_rejoin() {
319 database_test!(|db| async move {
320 match db {
321 crate::Database::Reference(_) => return,
322 crate::Database::MongoDb(_) => (),
323 }
324 let owner = User::create(&db, "Server Owner".to_string(), None, None)
325 .await
326 .unwrap();
327
328 let kickable_user = User::create(&db, "Member".to_string(), None, None)
329 .await
330 .unwrap();
331
332 let server = Server::create(
333 &db,
334 DataCreateServer {
335 name: "Server".to_string(),
336 description: None,
337 nsfw: None,
338 },
339 &owner,
340 false,
341 )
342 .await
343 .unwrap()
344 .0;
345
346 Member::create(&db, &server, &owner, None).await.unwrap();
347 let mut kickable_member = Member::create(&db, &server, &kickable_user, None)
348 .await
349 .unwrap()
350 .0;
351
352 kickable_member
353 .update(
354 &db,
355 PartialMember {
356 timeout: Some(Timestamp::now_utc() + Duration::minutes(5)),
357 ..Default::default()
358 },
359 vec![],
360 )
361 .await
362 .unwrap();
363
364 assert!(kickable_member.in_timeout());
365
366 kickable_member
367 .remove(&db, &server, RemovalIntention::Kick, false)
368 .await
369 .unwrap();
370
371 let kickable_member = Member::create(&db, &server, &kickable_user, None)
372 .await
373 .unwrap()
374 .0;
375
376 assert!(kickable_member.in_timeout())
377 });
378 }
379}