From c6ba72d924bb0012f909fb242151cbbba560caab Mon Sep 17 00:00:00 2001
From: Paul Makles <paulmakles@gmail.com>
Date: Mon, 18 Jan 2021 20:07:42 +0000
Subject: [PATCH] Add message sending.

---
 src/routes/channels/message_send.rs | 57 +++++++++++++++++++++++++++++
 src/routes/channels/mod.rs          |  4 +-
 src/util/result.rs                  |  6 +++
 3 files changed, 66 insertions(+), 1 deletion(-)
 create mode 100644 src/routes/channels/message_send.rs

diff --git a/src/routes/channels/message_send.rs b/src/routes/channels/message_send.rs
new file mode 100644
index 0000000..472112d
--- /dev/null
+++ b/src/routes/channels/message_send.rs
@@ -0,0 +1,57 @@
+use crate::database::*;
+use crate::util::result::{Error, Result};
+
+use serde::{Serialize, Deserialize};
+use rocket_contrib::json::Json;
+use validator::Validate;
+use mongodb::bson::doc;
+use ulid::Ulid;
+
+#[derive(Validate, Serialize, Deserialize)]
+pub struct Data {
+    #[validate(length(min = 1, max = 2000))]
+    content: String,
+    // Maximum length of 36 allows both ULIDs and UUIDs.
+    #[validate(length(min = 1, max = 36))]
+    nonce: String,
+}
+
+#[post("/<target>/messages", data = "<message>")]
+pub async fn req(user: User, target: Ref, message: Json<Data>) -> Result<()> {
+    message.validate()
+        .map_err(|error| Error::FailedValidation { error })?;
+
+    let target = target.fetch_channel().await?;
+
+    let perm = permissions::channel::calculate(&user, &target).await;
+    if !perm.get_send_message() {
+        Err(Error::LabelMe)?
+    }
+
+    if get_collection("messages")
+        .find_one(
+            doc! {
+                "nonce": &message.nonce
+            },
+            None
+        )
+        .await
+        .map_err(|_| Error::DatabaseError { operation: "find_one", with: "message" })?
+        .is_some() {
+        Err(Error::AlreadySentMessage)?
+    }
+
+    Message {
+        id: Ulid::new().to_string(),
+        channel: target.id().to_string(),
+        author: user.id,
+
+        content: message.content.clone(),
+        nonce: Some(message.nonce.clone()),
+        edited: None,
+    }
+    .send()
+    .await?;
+
+    Ok(())
+}
diff --git a/src/routes/channels/mod.rs b/src/routes/channels/mod.rs
index 2f2d3b9..1b7135e 100644
--- a/src/routes/channels/mod.rs
+++ b/src/routes/channels/mod.rs
@@ -2,10 +2,12 @@ use rocket::Route;
 
 mod fetch_channel;
 mod delete_channel;
+mod message_send;
 
 pub fn routes() -> Vec<Route> {
     routes![
         fetch_channel::req,
-        delete_channel::req
+        delete_channel::req,
+        message_send::req
     ]
 }
diff --git a/src/util/result.rs b/src/util/result.rs
index e707f60..2afc546 100644
--- a/src/util/result.rs
+++ b/src/util/result.rs
@@ -31,6 +31,10 @@ pub enum Error {
     #[snafu(display("You have been blocked by this user."))]
     BlockedByOther,
 
+    // ? Channel related errors.
+    #[snafu(display("Already sent a message with this nonce."))]
+    AlreadySentMessage,
+
     // ? General errors.
     #[snafu(display("Failed to validate fields."))]
     FailedValidation { error: ValidationErrors },
@@ -62,6 +66,8 @@ impl<'r> Responder<'r, 'static> for Error {
             Error::Blocked => Status::Conflict,
             Error::BlockedByOther => Status::Forbidden,
 
+            Error::AlreadySentMessage => Status::Conflict,
+
             Error::FailedValidation { .. } => Status::UnprocessableEntity,
             Error::DatabaseError { .. } => Status::InternalServerError,
             Error::InternalError => Status::InternalServerError,
-- 
GitLab