From a7ea29d8210fafd5fdfbe9f55a6d59e52c4f0807 Mon Sep 17 00:00:00 2001
From: Paul Makles <paulmakles@gmail.com>
Date: Tue, 16 Feb 2021 15:25:33 +0000
Subject: [PATCH] Add a way to send messages with attachments.

---
 .dockerignore                       |  1 +
 src/database/entities/autumn.rs     | 21 +++++++++++
 src/database/entities/message.rs    |  3 ++
 src/database/entities/mod.rs        |  2 ++
 src/routes/channels/message_send.rs | 54 +++++++++++++++++++++++++++--
 src/routes/root.rs                  |  8 +++--
 src/util/result.rs                  |  3 ++
 src/util/variables.rs               |  7 ++--
 8 files changed, 92 insertions(+), 7 deletions(-)
 create mode 100644 src/database/entities/autumn.rs

diff --git a/.dockerignore b/.dockerignore
index 796603f..8f7866a 100644
--- a/.dockerignore
+++ b/.dockerignore
@@ -1,2 +1,3 @@
 target
+.mongo
 .env
\ No newline at end of file
diff --git a/src/database/entities/autumn.rs b/src/database/entities/autumn.rs
new file mode 100644
index 0000000..7ae1102
--- /dev/null
+++ b/src/database/entities/autumn.rs
@@ -0,0 +1,21 @@
+use serde::{Serialize, Deserialize};
+
+#[derive(Serialize, Deserialize, Debug, Clone)]
+#[serde(tag = "type")]
+enum Metadata {
+    File,
+    Image { width: isize, height: isize },
+    Video { width: isize, height: isize },
+    Audio,
+}
+
+#[derive(Serialize, Deserialize, Debug, Clone)]
+pub struct File {
+    #[serde(rename = "_id")]
+    pub id: String,
+    filename: String,
+    metadata: Metadata,
+    content_type: String,
+
+    message_id: Option<String>
+}
diff --git a/src/database/entities/message.rs b/src/database/entities/message.rs
index 8594f40..ee26c40 100644
--- a/src/database/entities/message.rs
+++ b/src/database/entities/message.rs
@@ -19,6 +19,8 @@ pub struct Message {
 
     pub content: String,
     #[serde(skip_serializing_if = "Option::is_none")]
+    pub attachment: Option<File>,
+    #[serde(skip_serializing_if = "Option::is_none")]
     pub edited: Option<DateTime>,
 }
 
@@ -31,6 +33,7 @@ impl Message {
             author,
 
             content,
+            attachment: None,
             edited: None,
         }
     }
diff --git a/src/database/entities/mod.rs b/src/database/entities/mod.rs
index d64512a..864ee9c 100644
--- a/src/database/entities/mod.rs
+++ b/src/database/entities/mod.rs
@@ -2,8 +2,10 @@ mod channel;
 mod guild;
 mod message;
 mod user;
+mod autumn;
 
 pub use channel::*;
 pub use guild::*;
 pub use message::*;
 pub use user::*;
+pub use autumn::*;
diff --git a/src/routes/channels/message_send.rs b/src/routes/channels/message_send.rs
index 1a7df55..66c9e94 100644
--- a/src/routes/channels/message_send.rs
+++ b/src/routes/channels/message_send.rs
@@ -1,7 +1,7 @@
 use crate::database::*;
 use crate::util::result::{Error, Result};
 
-use mongodb::bson::doc;
+use mongodb::{bson::{doc, from_document}, options::FindOneOptions};
 use rocket_contrib::json::{Json, JsonValue};
 use serde::{Deserialize, Serialize};
 use ulid::Ulid;
@@ -14,6 +14,8 @@ pub struct Data {
     // Maximum length of 36 allows both ULIDs and UUIDs.
     #[validate(length(min = 1, max = 36))]
     nonce: String,
+    #[validate(length(min = 1, max = 128))]
+    attachment: Option<String>,
 }
 
 #[post("/<target>/messages", data = "<message>")]
@@ -37,7 +39,9 @@ pub async fn req(user: User, target: Ref, message: Json<Data>) -> Result<JsonVal
             doc! {
                 "nonce": &message.nonce
             },
-            None,
+            FindOneOptions::builder()
+                .projection(doc! { "_id": 1 })
+                .build()
         )
         .await
         .map_err(|_| Error::DatabaseError {
@@ -49,12 +53,56 @@ pub async fn req(user: User, target: Ref, message: Json<Data>) -> Result<JsonVal
         Err(Error::DuplicateNonce)?
     }
 
+    let id = Ulid::new().to_string();
+    let attachments = get_collection("attachments");
+    let attachment = if let Some(attachment_id) = &message.attachment {
+        if let Some(doc) = attachments
+            .find_one(
+                doc! {
+                    "_id": attachment_id,
+                    "message_id": {
+                        "$exists": false
+                    }
+                },
+                None
+            )
+            .await
+            .map_err(|_| Error::DatabaseError {
+                operation: "find_one",
+                with: "attachment",
+            })? {
+            let attachment = from_document::<File>(doc)
+                .map_err(|_| Error::DatabaseError { operation: "from_document", with: "attachment" })?;
+
+            attachments.update_one(
+                doc! {
+                    "_id": &attachment.id
+                },
+                doc! {
+                    "$set": {
+                        "message_id": &id
+                    }
+                },
+                None
+            )
+            .await
+            .map_err(|_| Error::DatabaseError { operation: "update_one", with: "attachment" })?;
+
+            Some(attachment)
+        } else {
+            return Err(Error::UnknownAttachment)
+        }
+    } else {
+        None
+    };
+
     let msg = Message {
-        id: Ulid::new().to_string(),
+        id,
         channel: target.id().to_string(),
         author: user.id,
 
         content: message.content.clone(),
+        attachment,
         nonce: Some(message.nonce.clone()),
         edited: None,
     };
diff --git a/src/routes/root.rs b/src/routes/root.rs
index 70ff4a5..9242caf 100644
--- a/src/routes/root.rs
+++ b/src/routes/root.rs
@@ -1,5 +1,5 @@
 use crate::util::variables::{
-    DISABLE_REGISTRATION, EXTERNAL_WS_URL, HCAPTCHA_SITEKEY, INVITE_ONLY, USE_EMAIL, USE_HCAPTCHA,
+    DISABLE_REGISTRATION, EXTERNAL_WS_URL, HCAPTCHA_SITEKEY, INVITE_ONLY, USE_EMAIL, USE_HCAPTCHA, USE_AUTUMN, AUTUMN_URL
 };
 
 use mongodb::bson::doc;
@@ -16,7 +16,11 @@ pub async fn root() -> JsonValue {
                 "key": HCAPTCHA_SITEKEY.to_string()
             },
             "email": *USE_EMAIL,
-            "invite_only": *INVITE_ONLY
+            "invite_only": *INVITE_ONLY,
+            "autumn": {
+                "enabled": *USE_AUTUMN,
+                "url": *AUTUMN_URL
+            }
         },
         "ws": *EXTERNAL_WS_URL,
     })
diff --git a/src/util/result.rs b/src/util/result.rs
index 4a54803..e69c48a 100644
--- a/src/util/result.rs
+++ b/src/util/result.rs
@@ -36,6 +36,8 @@ pub enum Error {
     // ? Channel related errors.
     #[snafu(display("This channel does not exist!"))]
     UnknownChannel,
+    #[snafu(display("Attachment does not exist!"))]
+    UnknownAttachment,
     #[snafu(display("Cannot edit someone else's message."))]
     CannotEditMessage,
     #[snafu(display("Cannot remove yourself from a group, use delete channel instead."))]
@@ -84,6 +86,7 @@ impl<'r> Responder<'r, 'static> for Error {
             Error::NotFriends => Status::Forbidden,
 
             Error::UnknownChannel => Status::NotFound,
+            Error::UnknownAttachment => Status::BadRequest,
             Error::CannotEditMessage => Status::Forbidden,
             Error::CannotRemoveYourself => Status::BadRequest,
             Error::GroupTooLarge { .. } => Status::Forbidden,
diff --git a/src/util/variables.rs b/src/util/variables.rs
index 00f3d02..17a5084 100644
--- a/src/util/variables.rs
+++ b/src/util/variables.rs
@@ -7,18 +7,20 @@ lazy_static! {
     // Application Settings
     pub static ref MONGO_URI: String =
         env::var("REVOLT_MONGO_URI").expect("Missing REVOLT_MONGO_URI environment variable.");
+    pub static ref WS_HOST: String =
+            env::var("REVOLT_WS_HOST").unwrap_or_else(|_| "0.0.0.0:9000".to_string());
     pub static ref PUBLIC_URL: String =
         env::var("REVOLT_PUBLIC_URL").expect("Missing REVOLT_PUBLIC_URL environment variable.");
     pub static ref APP_URL: String =
         env::var("REVOLT_APP_URL").unwrap_or_else(|_| "https://app.revolt.chat".to_string());
     pub static ref EXTERNAL_WS_URL: String =
         env::var("REVOLT_EXTERNAL_WS_URL").expect("Missing REVOLT_EXTERNAL_WS_URL environment variable.");
+    pub static ref AUTUMN_URL: String =
+        env::var("AUTUMN_PUBLIC_URL").unwrap_or_else(|_| "https://example.com".to_string());
     pub static ref HCAPTCHA_KEY: String =
         env::var("REVOLT_HCAPTCHA_KEY").unwrap_or_else(|_| "0x0000000000000000000000000000000000000000".to_string());
     pub static ref HCAPTCHA_SITEKEY: String =
         env::var("REVOLT_HCAPTCHA_SITEKEY").unwrap_or_else(|_| "10000000-ffff-ffff-ffff-000000000001".to_string());
-    pub static ref WS_HOST: String =
-        env::var("REVOLT_WS_HOST").unwrap_or_else(|_| "0.0.0.0:9000".to_string());
 
     // Application Flags
     pub static ref DISABLE_REGISTRATION: bool = env::var("REVOLT_DISABLE_REGISTRATION").map_or(false, |v| v == "1");
@@ -32,6 +34,7 @@ lazy_static! {
     );
     pub static ref USE_HCAPTCHA: bool = env::var("REVOLT_HCAPTCHA_KEY").is_ok();
     pub static ref USE_PROMETHEUS: bool = env::var("REVOLT_ENABLE_PROMETHEUS").map_or(false, |v| v == "1");
+    pub static ref USE_AUTUMN: bool = env::var("AUTUMN_PUBLIC_URL").is_ok();
 
     // SMTP Settings
     pub static ref SMTP_HOST: String =
-- 
GitLab