From 57f81e018cebfb33f23cd43775758798e2e530d1 Mon Sep 17 00:00:00 2001
From: Paul <paulmakles@gmail.com>
Date: Wed, 30 Jun 2021 20:09:42 +0100
Subject: [PATCH] Servers: Create or delete roles. Members: Assign / un-assign
 multiple roles. Servers: Routes to set permissions.

---
 src/routes/servers/member_edit.rs             | 21 +++++-
 src/routes/servers/mod.rs                     |  6 +-
 src/routes/servers/permissions_set.rs         | 69 +++++++++++++++++++
 src/routes/servers/permissions_set_default.rs | 65 +++++++++++++++++
 src/routes/servers/roles_create.rs            |  2 +-
 src/routes/servers/roles_delete.rs            | 41 +++++++++--
 6 files changed, 197 insertions(+), 7 deletions(-)
 create mode 100644 src/routes/servers/permissions_set.rs
 create mode 100644 src/routes/servers/permissions_set_default.rs

diff --git a/src/routes/servers/member_edit.rs b/src/routes/servers/member_edit.rs
index 2b34dd5..79b342a 100644
--- a/src/routes/servers/member_edit.rs
+++ b/src/routes/servers/member_edit.rs
@@ -1,3 +1,5 @@
+use std::collections::HashSet;
+
 use crate::notifications::events::ClientboundNotification;
 use crate::util::result::{Error, Result};
 use crate::{database::*, notifications::events::RemoveMemberField};
@@ -12,6 +14,7 @@ pub struct Data {
     #[validate(length(min = 1, max = 32))]
     nickname: Option<String>,
     avatar: Option<String>,
+    roles: Option<Vec<String>>,
     remove: Option<RemoveMemberField>,
 }
 
@@ -21,7 +24,7 @@ pub async fn req(user: User, server: Ref, target: String, data: Json<Data>) -> R
     data.validate()
         .map_err(|error| Error::FailedValidation { error })?;
 
-    if data.nickname.is_none() && data.avatar.is_none() && data.remove.is_none() {
+    if data.nickname.is_none() && data.avatar.is_none() && data.roles.is_none() && data.remove.is_none() {
         return Ok(());
     }
 
@@ -33,6 +36,10 @@ pub async fn req(user: User, server: Ref, target: String, data: Json<Data>) -> R
         .for_server()
         .await?;
 
+    if data.roles.is_some() && !perm.get_manage_roles() {
+        return Err(Error::MissingPermission);
+    }
+
     if target.id.user == user.id {
         if (data.nickname.is_some() && !perm.get_change_nickname())
             || (data.avatar.is_some() && !perm.get_change_avatar())
@@ -97,6 +104,18 @@ pub async fn req(user: User, server: Ref, target: String, data: Json<Data>) -> R
         remove_avatar = true;
     }
 
+    if let Some(role_ids) = &data.roles {
+        let mut ids = HashSet::new();
+
+        for role in role_ids {
+            if server.roles.contains_key(role) {
+                ids.insert(role.clone());
+            }
+        }
+
+        set.insert("roles", ids.into_iter().collect::<Vec<String>>());
+    }
+
     let mut operations = doc! {};
     if set.len() > 0 {
         operations.insert("$set", &set);
diff --git a/src/routes/servers/mod.rs b/src/routes/servers/mod.rs
index 2f03c35..15968de 100644
--- a/src/routes/servers/mod.rs
+++ b/src/routes/servers/mod.rs
@@ -20,6 +20,8 @@ mod invites_fetch;
 
 mod roles_create;
 mod roles_delete;
+mod permissions_set;
+mod permissions_set_default;
 
 pub fn routes() -> Vec<Route> {
     routes![
@@ -37,6 +39,8 @@ pub fn routes() -> Vec<Route> {
         ban_list::req,
         invites_fetch::req,
         roles_create::req,
-        roles_delete::req
+        roles_delete::req,
+        permissions_set::req,
+        permissions_set_default::req
     ]
 }
diff --git a/src/routes/servers/permissions_set.rs b/src/routes/servers/permissions_set.rs
new file mode 100644
index 0000000..3e23a06
--- /dev/null
+++ b/src/routes/servers/permissions_set.rs
@@ -0,0 +1,69 @@
+use mongodb::bson::doc;
+use rocket_contrib::json::Json;
+use serde::{Serialize, Deserialize};
+
+use crate::database::*;
+use crate::database::permissions::channel::ChannelPermission;
+use crate::database::permissions::server::ServerPermission;
+use crate::notifications::events::ClientboundNotification;
+use crate::util::result::{Error, Result};
+
+#[derive(Serialize, Deserialize)]
+pub struct Data {
+    server: u32,
+    channel: u32
+}
+
+#[put("/<target>/permissions/<role_id>", data = "<data>")]
+pub async fn req(user: User, target: Ref, role_id: String, data: Json<Data>) -> Result<()> {
+    let target = target.fetch_server().await?;
+
+    let perm = permissions::PermissionCalculator::new(&user)
+        .with_server(&target)
+        .for_server()
+        .await?;
+
+    if !perm.get_manage_roles() {
+        return Err(Error::MissingPermission);
+    }
+
+    if !target.roles.contains_key(&role_id) {
+        return Err(Error::NotFound);
+    }
+
+    let server_permissions: u32 = ServerPermission::View as u32 | data.server;
+    let channel_permissions: u32 = ChannelPermission::View as u32 | data.channel;
+    
+    get_collection("servers")
+        .update_one(
+            doc! { "_id": &target.id },
+            doc! {
+                "$set": {
+                    "roles.".to_owned() + &role_id + &".permissions": [
+                        server_permissions as i32,
+                        channel_permissions as i32
+                    ]
+                }
+            },
+            None
+        )
+        .await
+        .map_err(|_| Error::DatabaseError {
+            operation: "update_one",
+            with: "server"
+        })?;
+    
+    ClientboundNotification::ServerRoleUpdate {
+        id: target.id.clone(),
+        role_id,
+        data: json!({
+            "permissions": [
+                server_permissions as i32,
+                channel_permissions as i32
+            ]
+        })
+    }
+    .publish(target.id);
+
+    Ok(())
+}
diff --git a/src/routes/servers/permissions_set_default.rs b/src/routes/servers/permissions_set_default.rs
new file mode 100644
index 0000000..d379092
--- /dev/null
+++ b/src/routes/servers/permissions_set_default.rs
@@ -0,0 +1,65 @@
+use mongodb::bson::doc;
+use rocket_contrib::json::Json;
+use serde::{Serialize, Deserialize};
+
+use crate::database::*;
+use crate::database::permissions::channel::ChannelPermission;
+use crate::database::permissions::server::ServerPermission;
+use crate::notifications::events::ClientboundNotification;
+use crate::util::result::{Error, Result};
+
+#[derive(Serialize, Deserialize)]
+pub struct Data {
+    server: u32,
+    channel: u32
+}
+
+#[put("/<target>/permissions/default", data = "<data>", rank = 1)]
+pub async fn req(user: User, target: Ref, data: Json<Data>) -> Result<()> {
+    let target = target.fetch_server().await?;
+
+    let perm = permissions::PermissionCalculator::new(&user)
+        .with_server(&target)
+        .for_server()
+        .await?;
+
+    if !perm.get_manage_roles() {
+        return Err(Error::MissingPermission);
+    }
+
+    let server_permissions: u32 = ServerPermission::View as u32 | data.server;
+    let channel_permissions: u32 = ChannelPermission::View as u32 | data.channel;
+    
+    get_collection("servers")
+        .update_one(
+            doc! { "_id": &target.id },
+            doc! {
+                "$set": {
+                    "default_permissions": [
+                        server_permissions as i32,
+                        channel_permissions as i32
+                    ]
+                }
+            },
+            None
+        )
+        .await
+        .map_err(|_| Error::DatabaseError {
+            operation: "update_one",
+            with: "server"
+        })?;
+    
+    ClientboundNotification::ServerUpdate {
+        id: target.id.clone(),
+        data: json!({
+            "default_permissions": [
+                server_permissions as i32,
+                channel_permissions as i32
+            ]
+        }),
+        clear: None
+    }
+    .publish(target.id);
+
+    Ok(())
+}
diff --git a/src/routes/servers/roles_create.rs b/src/routes/servers/roles_create.rs
index 6453642..e55887c 100644
--- a/src/routes/servers/roles_create.rs
+++ b/src/routes/servers/roles_create.rs
@@ -40,7 +40,7 @@ pub async fn req(user: User, target: Ref, data: Json<Data>) -> Result<JsonValue>
     get_collection("servers")
         .update_one(
             doc! {
-                "_id": &id
+                "_id": &target.id
             },
             doc! {
                 "$set": {
diff --git a/src/routes/servers/roles_delete.rs b/src/routes/servers/roles_delete.rs
index baab78a..09c7b2c 100644
--- a/src/routes/servers/roles_delete.rs
+++ b/src/routes/servers/roles_delete.rs
@@ -20,7 +20,7 @@ pub async fn req(user: User, target: Ref, role_id: String) -> Result<()> {
     get_collection("servers")
         .update_one(
             doc! {
-                "_id": &role_id
+                "_id": &target.id
             },
             doc! {
                 "$unset": {
@@ -34,9 +34,42 @@ pub async fn req(user: User, target: Ref, role_id: String) -> Result<()> {
             operation: "update_one",
             with: "servers"
         })?;
-    
-    // remove role from members
-    // remove role from channels
+
+    get_collection("channels")
+        .update_one(
+            doc! {
+                "server": &target.id
+            },
+            doc! {
+                "$unset": {
+                    "role_permissions.".to_owned() + &role_id: 1
+                }
+            },
+            None
+        )
+        .await
+        .map_err(|_| Error::DatabaseError {
+            operation: "update_one",
+            with: "channels"
+        })?;
+
+    get_collection("server_members")
+        .update_many(
+            doc! {
+                "_id.server": &target.id
+            },
+            doc! {
+                "$pull": {
+                    "roles": &role_id
+                }
+            },
+            None
+        )
+        .await
+        .map_err(|_| Error::DatabaseError {
+            operation: "update_many",
+            with: "server_members"
+        })?;
     
     ClientboundNotification::ServerRoleDelete {
         id: target.id.clone(),
-- 
GitLab