diff --git a/src/database/entities/channel.rs b/src/database/entities/channel.rs index 3919dc18..985e45f2 100644 --- a/src/database/entities/channel.rs +++ b/src/database/entities/channel.rs @@ -42,7 +42,8 @@ pub enum Channel { name: String, owner: String, - description: String, + #[serde(skip_serializing_if = "Option::is_none")] + description: Option, recipients: Vec, #[serde(skip_serializing_if = "Option::is_none")] @@ -58,10 +59,11 @@ pub enum Channel { nonce: Option, name: String, + #[serde(skip_serializing_if = "Option::is_none")] description: Option, #[serde(skip_serializing_if = "Option::is_none")] icon: Option, - } + }, } impl Channel { diff --git a/src/notifications/events.rs b/src/notifications/events.rs index a40035e0..3082b273 100644 --- a/src/notifications/events.rs +++ b/src/notifications/events.rs @@ -1,12 +1,15 @@ use futures::StreamExt; use hive_pubsub::PubSub; +use mongodb::bson::{doc, Document}; use rauth::auth::Session; -use mongodb::bson::{Document, doc}; -use serde::{Deserialize, Serialize}; use rocket_contrib::json::JsonValue; +use serde::{Deserialize, Serialize}; use super::hive::{get_hive, subscribe_if_exists}; -use crate::{database::*, util::result::{Error, Result}}; +use crate::{ + database::*, + util::result::{Error, Result}, +}; #[derive(Serialize, Deserialize, Debug)] #[serde(tag = "error")] diff --git a/src/notifications/subscriptions.rs b/src/notifications/subscriptions.rs index fc770f72..86a54cb5 100644 --- a/src/notifications/subscriptions.rs +++ b/src/notifications/subscriptions.rs @@ -1,9 +1,11 @@ use crate::database::*; +use crate::util::result::Error; use super::hive::get_hive; use futures::StreamExt; use hive_pubsub::PubSub; use mongodb::bson::doc; +use mongodb::bson::Document; use mongodb::options::FindOptions; pub async fn generate_subscriptions(user: &User) -> Result<(), String> { @@ -16,6 +18,44 @@ pub async fn generate_subscriptions(user: &User) -> Result<(), String> { } } + let server_ids = user + .fetch_server_ids() + .await + .map_err(|_| "Failed to fetch memberships.".to_string())?; + + let channel_ids = get_collection("servers") + .find( + doc! { + "_id": { + "$in": &server_ids + } + }, + None, + ) + .await + .map_err(|_| "Failed to fetch servers.".to_string())? + .filter_map(async move |s| s.ok()) + .collect::>() + .await + .into_iter() + .filter_map(|x| { + x.get_array("channels").ok().map(|v| { + v.into_iter() + .filter_map(|x| x.as_str().map(|x| x.to_string())) + .collect::>() + }) + }) + .flatten() + .collect::>(); + + for id in server_ids { + hive.subscribe(user.id.clone(), id)?; + } + + for id in channel_ids { + hive.subscribe(user.id.clone(), id)?; + } + let mut cursor = get_collection("channels") .find( doc! { @@ -45,14 +85,5 @@ pub async fn generate_subscriptions(user: &User) -> Result<(), String> { } } - let server_ids = user - .fetch_server_ids() - .await - .map_err(|_| "Failed to fetch memberships.".to_string())?; - - for id in server_ids { - hive.subscribe(user.id.clone(), id)?; - } - Ok(()) } diff --git a/src/routes/channels/delete_channel.rs b/src/routes/channels/delete_channel.rs index 1885048c..0fc7f7b7 100644 --- a/src/routes/channels/delete_channel.rs +++ b/src/routes/channels/delete_channel.rs @@ -108,6 +108,12 @@ pub async fn req(user: User, target: Ref) -> Result<()> { Ok(()) } - Channel::TextChannel { .. } => unimplemented!() + Channel::TextChannel { .. } => { + if perm.get_manage_channel() { + target.delete().await + } else { + Err(Error::MissingPermission) + } + } } } diff --git a/src/routes/channels/group_create.rs b/src/routes/channels/group_create.rs index ab637414..22c68fc0 100644 --- a/src/routes/channels/group_create.rs +++ b/src/routes/channels/group_create.rs @@ -24,6 +24,7 @@ pub struct Data { #[post("/create", data = "")] pub async fn req(user: User, info: Json) -> Result { + let info = info.into_inner(); info.validate() .map_err(|error| Error::FailedValidation { error })?; @@ -62,12 +63,9 @@ pub async fn req(user: User, info: Json) -> Result { let id = Ulid::new().to_string(); let channel = Channel::Group { id, - nonce: Some(info.nonce.clone()), - name: info.name.clone(), - description: info - .description - .clone() - .unwrap_or_else(|| "A group.".to_string()), + nonce: Some(info.nonce), + name: info.name, + description: info.description, owner: user.id, recipients: set.into_iter().collect::>(), icon: None, diff --git a/src/routes/servers/channel_create.rs b/src/routes/servers/channel_create.rs new file mode 100644 index 00000000..5023d301 --- /dev/null +++ b/src/routes/servers/channel_create.rs @@ -0,0 +1,85 @@ +use crate::database::*; +use crate::util::result::{Error, Result}; + +use mongodb::bson::doc; +use rocket_contrib::json::{Json, JsonValue}; +use serde::{Deserialize, Serialize}; +use ulid::Ulid; +use validator::Validate; + +#[derive(Validate, Serialize, Deserialize)] +pub struct Data { + #[validate(length(min = 1, max = 32))] + name: String, + #[validate(length(min = 0, max = 1024))] + description: Option, + // Maximum length of 36 allows both ULIDs and UUIDs. + #[validate(length(min = 1, max = 36))] + nonce: String, +} + +#[post("//channels", data = "")] +pub async fn req(user: User, target: Ref, info: Json) -> Result { + let info = info.into_inner(); + info.validate() + .map_err(|error| Error::FailedValidation { error })?; + + let target = target.fetch_server().await?; + let perm = permissions::PermissionCalculator::new(&user) + .with_server(&target) + .for_server() + .await?; + + if !perm.get_manage_server() { + Err(Error::MissingPermission)? + } + + if get_collection("channels") + .find_one( + doc! { + "nonce": &info.nonce + }, + None, + ) + .await + .map_err(|_| Error::DatabaseError { + operation: "find_one", + with: "channel", + })? + .is_some() + { + Err(Error::DuplicateNonce)? + } + + let id = Ulid::new().to_string(); + let channel = Channel::TextChannel { + id: id.clone(), + server: target.id.clone(), + nonce: Some(info.nonce), + + name: info.name, + description: info.description, + icon: None, + }; + + channel.clone().publish().await?; + get_collection("servers") + .update_one( + doc! { + "_id": target.id + }, + doc! { + "$addToSet": { + "channels": id + } + }, + None + ) + .await + .map_err(|_| Error::DatabaseError { + operation: "update_one", + with: "server", + })?; + + Ok(json!(channel)) +} diff --git a/src/routes/servers/mod.rs b/src/routes/servers/mod.rs index a6d64cd7..2b8d5c8b 100644 --- a/src/routes/servers/mod.rs +++ b/src/routes/servers/mod.rs @@ -4,6 +4,13 @@ mod server_create; mod server_delete; mod server_edit; +mod channel_create; + pub fn routes() -> Vec { - routes![server_create::req, server_delete::req, server_edit::req] + routes![ + server_create::req, + server_delete::req, + server_edit::req, + channel_create::req + ] } diff --git a/src/routes/servers/server_create.rs b/src/routes/servers/server_create.rs index b299ae4c..1ba9907c 100644 --- a/src/routes/servers/server_create.rs +++ b/src/routes/servers/server_create.rs @@ -40,34 +40,13 @@ pub async fn req(user: User, info: Json) -> Result { let id = Ulid::new().to_string(); let cid = Ulid::new().to_string(); - - Channel::TextChannel { - id: cid.clone(), - server: id.clone(), - nonce: Some(info.nonce.clone()), - name: "general".to_string(), - description: None, - icon: None, - }.publish().await?; - - let server = Server { - id: id.clone(), - nonce: Some(info.nonce.clone()), - owner: user.id.clone(), - - name: info.name.clone(), - channels: vec![ cid ], - - icon: None, - banner: None, - }; get_collection("server_members") .insert_one( doc! { "_id": { - "server": id, - "user": user.id + "server": &id, + "user": &user.id } }, None, @@ -78,6 +57,29 @@ pub async fn req(user: User, info: Json) -> Result { with: "server_members", })?; + Channel::TextChannel { + id: cid.clone(), + server: id.clone(), + nonce: Some(info.nonce.clone()), + name: "general".to_string(), + description: None, + icon: None, + } + .publish() + .await?; + + let server = Server { + id: id.clone(), + nonce: Some(info.nonce.clone()), + owner: user.id.clone(), + + name: info.name.clone(), + channels: vec![cid], + + icon: None, + banner: None, + }; + server.clone().publish().await?; Ok(json!(server))