use std::collections::{HashMap, HashSet};
use revolt_models::v0::{self, DataCreateServerChannel};
use revolt_permissions::{OverrideField, DEFAULT_PERMISSION_SERVER};
use revolt_result::Result;
use ulid::Ulid;
use crate::{events::client::EventV1, Channel, Database, File, User};
auto_derived_partial!(
pub struct Server {
#[serde(rename = "_id")]
pub id: String,
pub owner: String,
pub name: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub description: Option<String>,
pub channels: Vec<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub categories: Option<Vec<Category>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub system_messages: Option<SystemMessageChannels>,
#[serde(
default = "HashMap::<String, Role>::new",
skip_serializing_if = "HashMap::<String, Role>::is_empty"
)]
pub roles: HashMap<String, Role>,
pub default_permissions: i64,
#[serde(skip_serializing_if = "Option::is_none")]
pub icon: Option<File>,
#[serde(skip_serializing_if = "Option::is_none")]
pub banner: Option<File>,
#[serde(skip_serializing_if = "Option::is_none")]
pub flags: Option<i32>,
#[serde(skip_serializing_if = "crate::if_false", default)]
pub nsfw: bool,
#[serde(skip_serializing_if = "crate::if_false", default)]
pub analytics: bool,
#[serde(skip_serializing_if = "crate::if_false", default)]
pub discoverable: bool,
},
"PartialServer"
);
auto_derived_partial!(
pub struct Role {
pub name: String,
pub permissions: OverrideField,
#[serde(skip_serializing_if = "Option::is_none")]
pub colour: Option<String>,
#[serde(skip_serializing_if = "crate::if_false", default)]
pub hoist: bool,
#[serde(default)]
pub rank: i64,
},
"PartialRole"
);
auto_derived!(
pub struct Category {
pub id: String,
pub title: String,
pub channels: Vec<String>,
}
pub struct SystemMessageChannels {
#[serde(skip_serializing_if = "Option::is_none")]
pub user_joined: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub user_left: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub user_kicked: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub user_banned: Option<String>,
}
pub enum FieldsServer {
Description,
Categories,
SystemMessages,
Icon,
Banner,
}
pub enum FieldsRole {
Colour,
}
);
#[allow(clippy::disallowed_methods)]
impl Server {
pub async fn create(
db: &Database,
data: v0::DataCreateServer,
owner: &User,
create_default_channels: bool,
) -> Result<(Server, Vec<Channel>)> {
let mut server = Server {
id: ulid::Ulid::new().to_string(),
owner: owner.id.to_string(),
name: data.name,
description: data.description,
channels: vec![],
nsfw: data.nsfw.unwrap_or(false),
default_permissions: *DEFAULT_PERMISSION_SERVER as i64,
analytics: false,
banner: None,
categories: None,
discoverable: false,
flags: None,
icon: None,
roles: HashMap::new(),
system_messages: None,
};
let channels: Vec<Channel> = if create_default_channels {
vec![
Channel::create_server_channel(
db,
&mut server,
DataCreateServerChannel {
channel_type: v0::LegacyServerChannelType::Text,
name: "General".to_string(),
..Default::default()
},
false,
)
.await?,
]
} else {
vec![]
};
server.channels = channels.iter().map(|c| c.id()).collect();
db.insert_server(&server).await?;
Ok((server, channels))
}
pub async fn update<'a>(
&mut self,
db: &Database,
partial: PartialServer,
remove: Vec<FieldsServer>,
) -> Result<()> {
for field in &remove {
self.remove_field(field);
}
self.apply_options(partial.clone());
db.update_server(&self.id, &partial, remove.clone()).await?;
EventV1::ServerUpdate {
id: self.id.clone(),
data: partial.into(),
clear: remove.into_iter().map(|v| v.into()).collect(),
}
.p(self.id.clone())
.await;
Ok(())
}
pub async fn delete(self, db: &Database) -> Result<()> {
EventV1::ServerDelete {
id: self.id.clone(),
}
.p(self.id.clone())
.await;
db.delete_server(&self.id).await
}
pub fn remove_field(&mut self, field: &FieldsServer) {
match field {
FieldsServer::Description => self.description = None,
FieldsServer::Categories => self.categories = None,
FieldsServer::SystemMessages => self.system_messages = None,
FieldsServer::Icon => self.icon = None,
FieldsServer::Banner => self.banner = None,
}
}
pub async fn set_role_permission(
&mut self,
db: &Database,
role_id: &str,
permissions: OverrideField,
) -> Result<()> {
if let Some(role) = self.roles.get_mut(role_id) {
role.update(
db,
&self.id,
role_id,
PartialRole {
permissions: Some(permissions),
..Default::default()
},
vec![],
)
.await?;
Ok(())
} else {
Err(create_error!(NotFound))
}
}
}
impl Role {
pub fn into_optional(self) -> PartialRole {
PartialRole {
name: Some(self.name),
permissions: Some(self.permissions),
colour: self.colour,
hoist: Some(self.hoist),
rank: Some(self.rank),
}
}
pub async fn create(&self, db: &Database, server_id: &str) -> Result<String> {
let role_id = Ulid::new().to_string();
db.insert_role(server_id, &role_id, self).await?;
EventV1::ServerRoleUpdate {
id: server_id.to_string(),
role_id: role_id.to_string(),
data: self.clone().into_optional().into(),
clear: vec![],
}
.p(server_id.to_string())
.await;
Ok(role_id)
}
pub async fn update<'a>(
&mut self,
db: &Database,
server_id: &str,
role_id: &str,
partial: PartialRole,
remove: Vec<FieldsRole>,
) -> Result<()> {
for field in &remove {
self.remove_field(field);
}
self.apply_options(partial.clone());
db.update_role(server_id, role_id, &partial, remove.clone())
.await?;
EventV1::ServerRoleUpdate {
id: server_id.to_string(),
role_id: role_id.to_string(),
data: partial.into(),
clear: vec![],
}
.p(server_id.to_string())
.await;
Ok(())
}
pub fn remove_field(&mut self, field: &FieldsRole) {
match field {
FieldsRole::Colour => self.colour = None,
}
}
pub async fn delete(self, db: &Database, server_id: &str, role_id: &str) -> Result<()> {
EventV1::ServerRoleDelete {
id: server_id.to_string(),
role_id: role_id.to_string(),
}
.p(server_id.to_string())
.await;
db.delete_role(server_id, role_id).await
}
}
impl SystemMessageChannels {
pub fn into_channel_ids(self) -> HashSet<String> {
let mut ids = HashSet::new();
if let Some(id) = self.user_joined {
ids.insert(id);
}
if let Some(id) = self.user_left {
ids.insert(id);
}
if let Some(id) = self.user_kicked {
ids.insert(id);
}
if let Some(id) = self.user_banned {
ids.insert(id);
}
ids
}
}
#[cfg(test)]
mod tests {
use revolt_permissions::{calculate_server_permissions, ChannelPermission};
use crate::{fixture, util::permissions::DatabasePermissionQuery};
#[async_std::test]
async fn permissions() {
database_test!(|db| async move {
fixture!(db, "server_with_roles",
owner user 0
moderator user 1
user user 2
server server 4);
let mut query = DatabasePermissionQuery::new(&db, &owner).server(&server);
assert!(calculate_server_permissions(&mut query)
.await
.has_channel_permission(ChannelPermission::GrantAllSafe));
let mut query = DatabasePermissionQuery::new(&db, &moderator).server(&server);
assert!(calculate_server_permissions(&mut query)
.await
.has_channel_permission(ChannelPermission::BanMembers));
let mut query = DatabasePermissionQuery::new(&db, &user).server(&server);
assert!(!calculate_server_permissions(&mut query)
.await
.has_channel_permission(ChannelPermission::BanMembers));
});
}
}