diff --git a/src/database/channel.rs b/src/database/channel.rs index e60856b83db8af5a69a76dc428392118ec01c232..a49d0f0d6e940a001160b0786403213f2f74784d 100644 --- a/src/database/channel.rs +++ b/src/database/channel.rs @@ -11,5 +11,11 @@ pub struct Channel { // for Direct Messages pub recipients: Option<Vec<String>>, - pub active: Option<bool>, + pub active: Option<bool>, + + // for Guilds + pub name: Option<String>, + + // for Guilds and Group DMs + pub description: Option<String>, } diff --git a/src/database/guild.rs b/src/database/guild.rs new file mode 100644 index 0000000000000000000000000000000000000000..a3ffa0e0f1c39fd5a6e7284511d7c7e21484ecdc --- /dev/null +++ b/src/database/guild.rs @@ -0,0 +1,27 @@ +use serde::{ Deserialize, Serialize }; + +#[derive(Serialize, Deserialize, Debug)] +pub struct Member { + pub id: String, +} + +#[derive(Serialize, Deserialize, Debug)] +pub struct Invite { + pub id: String, + pub custom: bool, + pub channel: String, +} + +#[derive(Serialize, Deserialize, Debug)] +pub struct Guild { + #[serde(rename = "_id")] + pub id: String, + // pub nonce: String, used internally + pub name: String, + pub description: String, + pub owner: String, + + pub channels: Vec<String>, + pub members: Vec<Member>, + pub invites: Vec<Invite>, +} diff --git a/src/database/message.rs b/src/database/message.rs index 9411b82e88c7993622b5745366a5722284f9d3fc..5bb6992b8e1e74cfb16e615e7fc4629c379d7d39 100644 --- a/src/database/message.rs +++ b/src/database/message.rs @@ -1,13 +1,22 @@ use serde::{ Deserialize, Serialize }; use bson::{ UtcDateTime }; +#[derive(Serialize, Deserialize, Debug)] +pub struct PreviousEntry { + pub content: String, + pub time: UtcDateTime, +} + #[derive(Serialize, Deserialize, Debug)] pub struct Message { #[serde(rename = "_id")] pub id: String, + // pub nonce: String, used internally pub channel: String, pub author: String, pub content: String, - pub edited: Option<UtcDateTime>, + pub edited: Option<UtcDateTime>, + + pub previous_content: Option<Vec<PreviousEntry>> } diff --git a/src/database/mod.rs b/src/database/mod.rs index fb1d14cfcc07c750fcb06521a88eb8b60be46f01..d425bd1c8e55e6495f004773e871c8693678e6b7 100644 --- a/src/database/mod.rs +++ b/src/database/mod.rs @@ -27,3 +27,4 @@ pub fn get_collection(collection: &str) -> Collection { pub mod user; pub mod channel; pub mod message; +pub mod guild; diff --git a/src/email.rs b/src/email.rs index b03861153f23aa0fdb0c616c88ef870426db4b6c..6b218588417f8f96d31aa5fc072a7349abf4c579 100644 --- a/src/email.rs +++ b/src/email.rs @@ -24,25 +24,19 @@ fn public_uri() -> String { pub fn send_verification_email(email: String, code: String) -> bool { let url = format!("{}/api/account/verify/{}", public_uri(), code); - match send_email( + send_email( email, "Verify your email!".to_string(), - format!("Verify your email here: {}", url).to_string(), - format!("<a href=\"{}\">Click to verify your email!</a>", url).to_string() - ) { - Ok(_) => true, - Err(_) => false, - } + format!("Verify your email here: {}", url), + format!("<a href=\"{}\">Click to verify your email!</a>", url) + ).is_ok() } pub fn send_welcome_email(email: String, username: String) -> bool { - match send_email( + send_email( email, "Welcome to REVOLT!".to_string(), - format!("Welcome, {}! You can now use REVOLT.", username.clone()).to_string(), - format!("<b>Welcome, {}!</b><br/>You can now use REVOLT.<br/><a href=\"{}\">Go to REVOLT</a>", username.clone(), public_uri()).to_string() - ) { - Ok(_) => true, - Err(_) => false, - } + format!("Welcome, {}! You can now use REVOLT.", username.clone()), + format!("<b>Welcome, {}!</b><br/>You can now use REVOLT.<br/><a href=\"{}\">Go to REVOLT</a>", username.clone(), public_uri()) + ).is_ok() } diff --git a/src/guards/guild.rs b/src/guards/guild.rs new file mode 100644 index 0000000000000000000000000000000000000000..daa277d5ced8aefd760ab31e77571414f47e1c99 --- /dev/null +++ b/src/guards/guild.rs @@ -0,0 +1,38 @@ +use rocket::http::{ RawStr }; +use rocket::request::{ FromParam }; +use bson::{ bson, doc, from_bson }; + +use crate::database; + +use database::channel::Channel; +use database::message::Message; + +impl<'r> FromParam<'r> for Channel { + type Error = &'r RawStr; + + fn from_param(param: &'r RawStr) -> Result<Self, Self::Error> { + let col = database::get_db().collection("channels"); + let result = col.find_one(doc! { "_id": param.to_string() }, None).unwrap(); + + if let Some(channel) = result { + Ok(from_bson(bson::Bson::Document(channel)).expect("Failed to unwrap channel.")) + } else { + Err(param) + } + } +} + +impl<'r> FromParam<'r> for Message { + type Error = &'r RawStr; + + fn from_param(param: &'r RawStr) -> Result<Self, Self::Error> { + let col = database::get_db().collection("messages"); + let result = col.find_one(doc! { "_id": param.to_string() }, None).unwrap(); + + if let Some(message) = result { + Ok(from_bson(bson::Bson::Document(message)).expect("Failed to unwrap message.")) + } else { + Err(param) + } + } +} diff --git a/src/routes/channel.rs b/src/routes/channel.rs index 151cf3640f14a954acaf48a40c5b6178af5506c1..f518cc1a56b0a279d252f47f01cca5e2aa7553b4 100644 --- a/src/routes/channel.rs +++ b/src/routes/channel.rs @@ -134,6 +134,7 @@ pub fn messages(user: User, target: Channel) -> Option<JsonValue> { #[derive(Serialize, Deserialize)] pub struct SendMessage { content: String, + nonce: String, } /// send a message to a channel @@ -143,51 +144,63 @@ pub fn send_message(user: User, target: Channel, message: Json<SendMessage>) -> return None } + let content: String = message.content.chars().take(2000).collect(); + let nonce: String = message.nonce.chars().take(32).collect(); + let col = database::get_collection("messages"); + if let Some(_) = col.find_one(doc! { "nonce": nonce.clone() }, None).unwrap() { + return Some( + json!({ + "success": false, + "error": "Message already sent!" + }) + ) + } + let id = Ulid::new().to_string(); - Some(match col.insert_one( + Some(if col.insert_one( doc! { "_id": id.clone(), + "nonce": nonce.clone(), "channel": target.id.clone(), "author": user.id.clone(), - "content": message.content.clone(), + "content": content.clone(), }, None - ) { - Ok(_) => { - if target.channel_type == ChannelType::DM as u8 { - let col = database::get_collection("channels"); - col.update_one( - doc! { "_id": target.id.clone() }, - doc! { "$set": { "active": true } }, - None - ).unwrap(); - } - - websocket::queue_message( - get_recipients(&target), - json!({ - "type": "message", - "data": { - "id": id.clone(), - "channel": target.id, - "author": user.id, - "content": message.content.clone(), - }, - }).to_string() - ); - - json!({ - "success": true, - "id": id - }) - }, - Err(_) => - json!({ - "success": false, - "error": "Failed database query." - }) - }) + ).is_ok() { + if target.channel_type == ChannelType::DM as u8 { + let col = database::get_collection("channels"); + col.update_one( + doc! { "_id": target.id.clone() }, + doc! { "$set": { "active": true } }, + None + ).unwrap(); + } + + websocket::queue_message( + get_recipients(&target), + json!({ + "type": "message", + "data": { + "id": id.clone(), + "nonce": nonce, + "channel": target.id, + "author": user.id, + "content": content, + }, + }).to_string() + ); + + json!({ + "success": true, + "id": id + }) + } else { + json!({ + "success": false, + "error": "Failed database query." + }) + }) } /// get a message @@ -195,14 +208,31 @@ pub fn send_message(user: User, target: Channel, message: Json<SendMessage>) -> pub fn get_message(user: User, target: Channel, message: Message) -> Option<JsonValue> { if !has_permission(&user, &target) { return None - } + } + + let prev = + // ! CHECK IF USER HAS PERMISSION TO VIEW EDITS OF MESSAGES + if let Some(previous) = message.previous_content { + let mut entries = vec![]; + for entry in previous { + entries.push(json!({ + "content": entry.content, + "time": entry.time.timestamp(), + })); + } + + Some(entries) + } else { + None + }; Some( json!({ "id": message.id, "author": message.author, "content": message.content, - "edited": if let Some(t) = message.edited { Some(t.timestamp()) } else { None } + "edited": if let Some(t) = message.edited { Some(t.timestamp()) } else { None }, + "previous_content": prev, }) ) } @@ -214,7 +244,7 @@ pub struct EditMessage { /// edit a message #[patch("/<target>/messages/<message>", data = "<edit>")] -pub fn edit_message(user: User, target: Channel, message: Message, edit: Json<SendMessage>) -> Option<JsonValue> { +pub fn edit_message(user: User, target: Channel, message: Message, edit: Json<EditMessage>) -> Option<JsonValue> { if !has_permission(&user, &target) { return None } @@ -228,6 +258,13 @@ pub fn edit_message(user: User, target: Channel, message: Message, edit: Json<Se } else { let col = database::get_collection("messages"); + let time = + if let Some(edited) = message.edited { + edited.0 + } else { + Ulid::from_string(&message.id).unwrap().datetime() + }; + let edited = Utc::now(); match col.update_one( doc! { "_id": message.id.clone() }, @@ -235,7 +272,13 @@ pub fn edit_message(user: User, target: Channel, message: Message, edit: Json<Se "$set": { "content": edit.content.clone(), "edited": UtcDatetime(edited.clone()) - } + }, + "$push": { + "previous_content": { + "content": message.content, + "time": time, + } + }, }, None ) { diff --git a/src/routes/guild.rs b/src/routes/guild.rs new file mode 100644 index 0000000000000000000000000000000000000000..8bf22db84b8f188970bfae1f2595c7bc075d708c --- /dev/null +++ b/src/routes/guild.rs @@ -0,0 +1,84 @@ +use crate::database::{ self, user::User }; + +use bson::{ bson, doc }; +use rocket_contrib::json::{ JsonValue, Json }; +use serde::{ Serialize, Deserialize }; +use ulid::Ulid; + +use super::channel::ChannelType; + +#[derive(Serialize, Deserialize)] +pub struct CreateGuild { + name: String, + description: Option<String>, + nonce: String, +} + +/// send a message to a channel +#[post("/create", data = "<info>")] +pub fn create_guild(user: User, info: Json<CreateGuild>) -> JsonValue { + if !user.email_verification.verified { + return json!({ + "success": false, + "error": "Email not verified!", + }); + } + + let name: String = info.name.chars().take(32).collect(); + let description: String = info.description.clone().unwrap_or("No description.".to_string()).chars().take(255).collect(); + let nonce: String = info.nonce.chars().take(32).collect(); + + let channels = database::get_collection("channels"); + let col = database::get_collection("guilds"); + if let Some(_) = col.find_one(doc! { "nonce": nonce.clone() }, None).unwrap() { + return json!({ + "success": false, + "error": "Guild already created!" + }) + } + + let channel_id = Ulid::new().to_string(); + if let Err(_) = channels.insert_one( + doc! { + "_id": channel_id.clone(), + "channel_type": ChannelType::GUILDCHANNEL as u32, + "name": "general", + }, + None) { + return json!({ + "success": false, + "error": "Failed to create guild channel." + }) + } + + let id = Ulid::new().to_string(); + if col.insert_one( + doc! { + "_id": id.clone(), + "nonce": nonce, + "name": name, + "description": description, + "owner": user.id.clone(), + "channels": [ + channel_id.clone() + ], + "members": [ + user.id + ], + "invites": [], + }, + None + ).is_ok() { + json!({ + "success": true, + "id": id, + }) + } else { + channels.delete_one(doc! { "_id": channel_id }, None).expect("Failed to delete the channel we just made."); + + json!({ + "success": false, + "error": "Failed to create guild." + }) + } +} diff --git a/src/routes/mod.rs b/src/routes/mod.rs index bdedabb317987f70816cc3a2baff67cb85abd04a..1865b089da3b2b73d9c4a502a5566b48d223d425 100644 --- a/src/routes/mod.rs +++ b/src/routes/mod.rs @@ -4,6 +4,7 @@ pub mod root; pub mod account; pub mod user; pub mod channel; +pub mod guild; pub fn mount(rocket: Rocket) -> Rocket { rocket @@ -11,4 +12,5 @@ pub fn mount(rocket: Rocket) -> Rocket { .mount("/api/account", routes![ account::create, account::verify_email, account::resend_email, account::login, account::token ]) .mount("/api/users", routes![ user::me, user::user, user::lookup, user::dms, user::dm, user::get_friends, user::get_friend, user::add_friend, user::remove_friend ]) .mount("/api/channels", routes![ channel::channel, channel::delete, channel::messages, channel::get_message, channel::send_message, channel::edit_message, channel::delete_message ]) + .mount("/api/guild", routes![ guild::create_guild ]) } diff --git a/src/routes/root.rs b/src/routes/root.rs index 12f7ca0c751ea17e6a6d28faa597139a3da7d85d..112f05cc5d92b5cf3605feae2471da70f9545931 100644 --- a/src/routes/root.rs +++ b/src/routes/root.rs @@ -1,5 +1,5 @@ use rocket_contrib::json::{ JsonValue }; -use bson::{ bson, doc }; +use bson::{ doc }; /// root #[get("/")] diff --git a/src/routes/user.rs b/src/routes/user.rs index 43d8890f16825667d005ada961b86bc25b52323d..2f9d89abcb6ba32311f5ad18fa5e2b0d6a6f6e44 100644 --- a/src/routes/user.rs +++ b/src/routes/user.rs @@ -124,6 +124,7 @@ pub fn dm(user: User, target: User) -> JsonValue { ).expect("Failed insert query."); json!({ + "success": true, "id": id.to_string() }) }