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,
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 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
49 },
53 "PartialMember"
54);
55
56auto_derived!(
57 #[derive(Hash, Default)]
59 pub struct MemberCompositeKey {
60 pub server: String,
62 pub user: String,
64 }
65
66 pub enum FieldsMember {
68 Nickname,
69 Avatar,
70 Roles,
71 Timeout,
72 CanReceive,
73 CanPublish,
74 JoinedAt,
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(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 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 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 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 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 .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}