revolt_database/models/servers/
model.rs

1use std::collections::{HashMap, HashSet};
2
3use revolt_models::v0::{self, DataCreateServerChannel};
4use revolt_permissions::{OverrideField, DEFAULT_PERMISSION_SERVER};
5use revolt_result::Result;
6use ulid::Ulid;
7
8use crate::{events::client::EventV1, Channel, Database, File, User};
9
10auto_derived_partial!(
11    /// Server
12    pub struct Server {
13        /// Unique Id
14        #[serde(rename = "_id")]
15        pub id: String,
16        /// User id of the owner
17        pub owner: String,
18
19        /// Name of the server
20        pub name: String,
21        /// Description for the server
22        #[serde(skip_serializing_if = "Option::is_none")]
23        pub description: Option<String>,
24
25        /// Channels within this server
26        // TODO: investigate if this is redundant and can be removed
27        pub channels: Vec<String>,
28        /// Categories for this server
29        #[serde(skip_serializing_if = "Option::is_none")]
30        pub categories: Option<Vec<Category>>,
31        /// Configuration for sending system event messages
32        #[serde(skip_serializing_if = "Option::is_none")]
33        pub system_messages: Option<SystemMessageChannels>,
34
35        /// Roles for this server
36        #[serde(
37            default = "HashMap::<String, Role>::new",
38            skip_serializing_if = "HashMap::<String, Role>::is_empty"
39        )]
40        pub roles: HashMap<String, Role>,
41        /// Default set of server and channel permissions
42        pub default_permissions: i64,
43
44        /// Icon attachment
45        #[serde(skip_serializing_if = "Option::is_none")]
46        pub icon: Option<File>,
47        /// Banner attachment
48        #[serde(skip_serializing_if = "Option::is_none")]
49        pub banner: Option<File>,
50
51        /// Bitfield of server flags
52        #[serde(skip_serializing_if = "Option::is_none")]
53        pub flags: Option<i32>,
54
55        /// Whether this server is flagged as not safe for work
56        #[serde(skip_serializing_if = "crate::if_false", default)]
57        pub nsfw: bool,
58        /// Whether to enable analytics
59        #[serde(skip_serializing_if = "crate::if_false", default)]
60        pub analytics: bool,
61        /// Whether this server should be publicly discoverable
62        #[serde(skip_serializing_if = "crate::if_false", default)]
63        pub discoverable: bool,
64    },
65    "PartialServer"
66);
67
68auto_derived_partial!(
69    /// Role
70    pub struct Role {
71        /// Unique Id
72        #[serde(rename = "_id")]
73        pub id: String,
74        /// Role name
75        pub name: String,
76        /// Permissions available to this role
77        pub permissions: OverrideField,
78        /// Colour used for this role
79        ///
80        /// This can be any valid CSS colour
81        #[serde(skip_serializing_if = "Option::is_none")]
82        pub colour: Option<String>,
83        /// Whether this role should be shown separately on the member sidebar
84        #[serde(skip_serializing_if = "crate::if_false", default)]
85        pub hoist: bool,
86        /// Ranking of this role
87        #[serde(default)]
88        pub rank: i64,
89    },
90    "PartialRole"
91);
92
93auto_derived!(
94    /// Channel category
95    pub struct Category {
96        /// Unique ID for this category
97        pub id: String,
98        /// Title for this category
99        pub title: String,
100        /// Channels in this category
101        pub channels: Vec<String>,
102    }
103
104    /// System message channel assignments
105    pub struct SystemMessageChannels {
106        /// ID of channel to send user join messages in
107        #[serde(skip_serializing_if = "Option::is_none")]
108        pub user_joined: Option<String>,
109        /// ID of channel to send user left messages in
110        #[serde(skip_serializing_if = "Option::is_none")]
111        pub user_left: Option<String>,
112        /// ID of channel to send user kicked messages in
113        #[serde(skip_serializing_if = "Option::is_none")]
114        pub user_kicked: Option<String>,
115        /// ID of channel to send user banned messages in
116        #[serde(skip_serializing_if = "Option::is_none")]
117        pub user_banned: Option<String>,
118    }
119
120    /// Optional fields on server object
121    pub enum FieldsServer {
122        Description,
123        Categories,
124        SystemMessages,
125        Icon,
126        Banner,
127    }
128
129    /// Optional fields on server object
130    pub enum FieldsRole {
131        Colour,
132    }
133);
134
135#[allow(clippy::disallowed_methods)]
136impl Server {
137    /// Create a server
138    pub async fn create(
139        db: &Database,
140        data: v0::DataCreateServer,
141        owner: &User,
142        create_default_channels: bool,
143    ) -> Result<(Server, Vec<Channel>)> {
144        let mut server = Server {
145            id: ulid::Ulid::new().to_string(),
146            owner: owner.id.to_string(),
147            name: data.name,
148            description: data.description,
149            channels: vec![],
150            nsfw: data.nsfw.unwrap_or(false),
151            default_permissions: *DEFAULT_PERMISSION_SERVER as i64,
152
153            analytics: false,
154            banner: None,
155            categories: None,
156            discoverable: false,
157            flags: None,
158            icon: None,
159            roles: HashMap::new(),
160            system_messages: None,
161        };
162
163        let channels: Vec<Channel> = if create_default_channels {
164            vec![
165                Channel::create_server_channel(
166                    db,
167                    &mut server,
168                    DataCreateServerChannel {
169                        channel_type: v0::LegacyServerChannelType::Text,
170                        name: "General".to_string(),
171                        ..Default::default()
172                    },
173                    false,
174                )
175                .await?,
176            ]
177        } else {
178            vec![]
179        };
180
181        server.channels = channels.iter().map(|c| c.id().to_string()).collect();
182        db.insert_server(&server).await?;
183        Ok((server, channels))
184    }
185
186    /// Update server data
187    pub async fn update(
188        &mut self,
189        db: &Database,
190        partial: PartialServer,
191        remove: Vec<FieldsServer>,
192    ) -> Result<()> {
193        for field in &remove {
194            self.remove_field(field);
195        }
196
197        self.apply_options(partial.clone());
198
199        db.update_server(&self.id, &partial, remove.clone()).await?;
200
201        EventV1::ServerUpdate {
202            id: self.id.clone(),
203            data: partial.into(),
204            clear: remove.into_iter().map(|v| v.into()).collect(),
205        }
206        .p(self.id.clone())
207        .await;
208
209        Ok(())
210    }
211
212    /// Delete a server
213    pub async fn delete(self, db: &Database) -> Result<()> {
214        EventV1::ServerDelete {
215            id: self.id.clone(),
216        }
217        .p(self.id.clone())
218        .await;
219
220        db.delete_server(&self.id).await
221    }
222
223    /// Remove a field from Server
224    pub fn remove_field(&mut self, field: &FieldsServer) {
225        match field {
226            FieldsServer::Description => self.description = None,
227            FieldsServer::Categories => self.categories = None,
228            FieldsServer::SystemMessages => self.system_messages = None,
229            FieldsServer::Icon => self.icon = None,
230            FieldsServer::Banner => self.banner = None,
231        }
232    }
233
234    /// Ordered roles list
235    pub fn ordered_roles(&self) -> Vec<(String, Role)> {
236        let mut ordered_roles = self.roles.clone().into_iter().collect::<Vec<_>>();
237        ordered_roles.sort_by(|(_, role_a), (_, role_b)| role_a.rank.cmp(&role_b.rank));
238        ordered_roles
239    }
240
241    /// Set role permission on a server
242    pub async fn set_role_permission(
243        &mut self,
244        db: &Database,
245        role_id: &str,
246        permissions: OverrideField,
247    ) -> Result<()> {
248        if let Some(role) = self.roles.get_mut(role_id) {
249            role.update(
250                db,
251                &self.id,
252                PartialRole {
253                    permissions: Some(permissions),
254                    ..Default::default()
255                },
256                vec![],
257            )
258            .await?;
259
260            Ok(())
261        } else {
262            Err(create_error!(NotFound))
263        }
264    }
265
266    /// Reorders the server's roles rankings
267    pub async fn set_role_ordering(&mut self, db: &Database, new_order: Vec<String>) -> Result<()> {
268        // New order must always contain every role
269        debug_assert_eq!(self.roles.len(), new_order.len());
270
271        // Set the role's ranks to the positions in the vec
272        for (rank, id) in new_order.iter().enumerate() {
273            self.roles.get_mut(id).unwrap().rank = rank as i64;
274        }
275
276        db.update_server(
277            &self.id,
278            &PartialServer {
279                roles: Some(self.roles.clone()),
280                ..Default::default()
281            },
282            Vec::new(),
283        )
284        .await?;
285
286        // Publish bulk update event
287        EventV1::ServerRoleRanksUpdate {
288            id: self.id.clone(),
289            ranks: new_order,
290        }
291        .p(self.id.clone())
292        .await;
293
294        Ok(())
295    }
296}
297
298impl Role {
299    /// Into optional struct
300    pub fn into_optional(self) -> PartialRole {
301        PartialRole {
302            id: Some(self.id),
303            name: Some(self.name),
304            permissions: Some(self.permissions),
305            colour: self.colour,
306            hoist: Some(self.hoist),
307            rank: Some(self.rank),
308        }
309    }
310
311    /// Create a role
312    pub async fn create(db: &Database, server: &Server, name: String) -> Result<Self> {
313        let role = Role {
314            id: Ulid::new().to_string(),
315            name,
316            // Rank of the new role should be below the lowest role
317            rank: server.roles.len() as i64,
318            colour: None,
319            hoist: false,
320            permissions: Default::default(),
321        };
322
323        db.insert_role(&server.id, &role).await?;
324
325        EventV1::ServerRoleUpdate {
326            id: server.id.clone(),
327            role_id: role.id.clone(),
328            data: role.clone().into_optional().into(),
329            clear: vec![],
330        }
331        .p(server.id.clone())
332        .await;
333
334        Ok(role)
335    }
336
337    /// Update server data
338    pub async fn update(
339        &mut self,
340        db: &Database,
341        server_id: &str,
342        partial: PartialRole,
343        remove: Vec<FieldsRole>,
344    ) -> Result<()> {
345        for field in &remove {
346            self.remove_field(field);
347        }
348
349        self.apply_options(partial.clone());
350
351        db.update_role(server_id, &self.id, &partial, remove.clone())
352            .await?;
353
354        EventV1::ServerRoleUpdate {
355            id: server_id.to_string(),
356            role_id: self.id.clone(),
357            data: partial.into(),
358            clear: remove.into_iter().map(Into::into).collect(),
359        }
360        .p(server_id.to_string())
361        .await;
362
363        Ok(())
364    }
365
366    /// Remove field from Role
367    pub fn remove_field(&mut self, field: &FieldsRole) {
368        match field {
369            FieldsRole::Colour => self.colour = None,
370        }
371    }
372
373    /// Delete a role
374    pub async fn delete(self, db: &Database, server_id: &str) -> Result<()> {
375        EventV1::ServerRoleDelete {
376            id: server_id.to_string(),
377            role_id: self.id.clone(),
378        }
379        .p(server_id.to_string())
380        .await;
381
382        db.delete_role(server_id, &self.id).await
383    }
384}
385
386impl SystemMessageChannels {
387    pub fn into_channel_ids(self) -> HashSet<String> {
388        let mut ids = HashSet::new();
389
390        if let Some(id) = self.user_joined {
391            ids.insert(id);
392        }
393
394        if let Some(id) = self.user_left {
395            ids.insert(id);
396        }
397
398        if let Some(id) = self.user_kicked {
399            ids.insert(id);
400        }
401
402        if let Some(id) = self.user_banned {
403            ids.insert(id);
404        }
405
406        ids
407    }
408}
409
410#[cfg(test)]
411mod tests {
412    use revolt_permissions::{calculate_server_permissions, ChannelPermission};
413
414    use crate::{fixture, util::permissions::DatabasePermissionQuery};
415
416    #[async_std::test]
417    async fn permissions() {
418        database_test!(|db| async move {
419            fixture!(db, "server_with_roles",
420                owner user 0
421                moderator user 1
422                user user 2
423                server server 4);
424
425            let mut query = DatabasePermissionQuery::new(&db, &owner).server(&server);
426            assert!(calculate_server_permissions(&mut query)
427                .await
428                .has_channel_permission(ChannelPermission::GrantAllSafe));
429
430            let mut query = DatabasePermissionQuery::new(&db, &moderator).server(&server);
431            assert!(calculate_server_permissions(&mut query)
432                .await
433                .has_channel_permission(ChannelPermission::BanMembers));
434
435            let mut query = DatabasePermissionQuery::new(&db, &user).server(&server);
436            assert!(!calculate_server_permissions(&mut query)
437                .await
438                .has_channel_permission(ChannelPermission::BanMembers));
439        });
440    }
441}