From 7aad88ad3ff1e16963219be1f292e49cdc87b3e2 Mon Sep 17 00:00:00 2001
From: Paul <paulmakles@gmail.com>
Date: Tue, 25 May 2021 17:23:47 +0100
Subject: [PATCH] =?UTF-8?q?Messages:=20Allow=20multiple=20attachments.=20(?=
 =?UTF-8?q?=E2=9A=A0=EF=B8=8F=20breaking=20change)?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

---
 set_version.sh                      |  2 +-
 src/database/entities/message.rs    | 17 ++++++----
 src/database/migrations/init.rs     |  8 +++++
 src/database/migrations/scripts.rs  | 50 +++++++++++++++++++++++++++--
 src/routes/channels/message_send.rs |  6 ++--
 src/version.rs                      |  2 +-
 6 files changed, 72 insertions(+), 13 deletions(-)

diff --git a/set_version.sh b/set_version.sh
index 44ddcfd..866f747 100755
--- a/set_version.sh
+++ b/set_version.sh
@@ -1,3 +1,3 @@
 #!/bin/bash
-export version=0.4.1-alpha.13
+export version=0.4.2-alpha.0
 echo "pub const VERSION: &str = \"${version}\";" > src/version.rs
diff --git a/src/database/entities/message.rs b/src/database/entities/message.rs
index ccd9954..177c530 100644
--- a/src/database/entities/message.rs
+++ b/src/database/entities/message.rs
@@ -54,7 +54,7 @@ pub struct Message {
 
     pub content: Content,
     #[serde(skip_serializing_if = "Option::is_none")]
-    pub attachment: Option<File>,
+    pub attachments: Option<Vec<File>>,
     #[serde(skip_serializing_if = "Option::is_none")]
     pub edited: Option<DateTime>,
     #[serde(skip_serializing_if = "Option::is_none")]
@@ -70,7 +70,7 @@ impl Message {
             author,
 
             content,
-            attachment: None,
+            attachments: None,
             edited: None,
             embeds: None,
         }
@@ -275,8 +275,10 @@ impl Message {
     }
 
     pub async fn delete(&self) -> Result<()> {
-        if let Some(attachment) = &self.attachment {
-            attachment.delete().await?;
+        if let Some(attachments) = &self.attachments {
+            for attachment in attachments {
+                attachment.delete().await?;
+            }
         }
 
         get_collection("messages")
@@ -299,11 +301,14 @@ impl Message {
         }
         .publish(channel);
 
-        if let Some(attachment) = &self.attachment {
+        if let Some(attachments) = &self.attachments {
+            let attachment_ids: Vec<String> = attachments.iter().map(|f| f.id.to_string()).collect();
             get_collection("attachments")
                 .update_one(
                     doc! {
-                        "_id": &attachment.id
+                        "_id": {
+                            "$in": attachment_ids
+                        }
                     },
                     doc! {
                         "$set": {
diff --git a/src/database/migrations/init.rs b/src/database/migrations/init.rs
index fa1ab83..d089c0d 100644
--- a/src/database/migrations/init.rs
+++ b/src/database/migrations/init.rs
@@ -37,6 +37,14 @@ pub async fn create_database() {
         .await
         .expect("Failed to create attachments collection.");
 
+    db.create_collection("channel_unreads", None)
+        .await
+        .expect("Failed to create channel_unreads collection.");
+
+    db.create_collection("user_settings", None)
+        .await
+        .expect("Failed to create user_settings collection.");
+
     db.create_collection(
         "pubsub",
         CreateCollectionOptions::builder()
diff --git a/src/database/migrations/scripts.rs b/src/database/migrations/scripts.rs
index d123ab2..e085959 100644
--- a/src/database/migrations/scripts.rs
+++ b/src/database/migrations/scripts.rs
@@ -1,8 +1,9 @@
 use crate::database::{get_collection, get_db};
 
 use log::info;
-use mongodb::bson::{doc, from_document};
+use futures::StreamExt;
 use serde::{Deserialize, Serialize};
+use mongodb::{bson::{doc, from_document}, options::FindOptions};
 
 #[derive(Serialize, Deserialize)]
 struct MigrationInfo {
@@ -10,7 +11,7 @@ struct MigrationInfo {
     revision: i32,
 }
 
-pub const LATEST_REVISION: i32 = 3;
+pub const LATEST_REVISION: i32 = 4;
 
 pub async fn migrate_database() {
     let migrations = get_collection("migrations");
@@ -87,6 +88,51 @@ pub async fn run_migrations(revision: i32) -> i32 {
             .expect("Failed to create servers collection.");
     }
 
+    if revision <= 3 {
+        info!("Running migration [revision 3 / 2021-05-25]: Support multiple file uploads, add channel_unreads and user_settings.");
+
+        let messages = get_collection("messages");
+        let mut cursor = messages.find(
+            doc! {
+                "attachment": {
+                    "$exists": 1
+                }
+            },
+            FindOptions::builder()
+                .projection(doc! {
+                    "_id": 1,
+                    "attachments": [ "$attachment" ]
+                })
+                .build()
+        )
+        .await
+        .expect("Failed to fetch messages.");
+
+        while let Some(result) = cursor.next().await {
+            let doc = result.unwrap();
+            let id = doc.get_str("_id").unwrap();
+            let attachments = doc.get_array("attachments").unwrap();
+
+            messages.update_one(
+                doc! { "_id": id },
+                doc! { "$unset": { "attachment": 1 }, "$set": { "attachments": attachments } },
+                None
+            )
+            .await
+            .unwrap();
+        }
+
+        get_db()
+            .create_collection("channel_unreads", None)
+            .await
+            .expect("Failed to create channel_unreads collection.");
+
+        get_db()
+            .create_collection("user_settings", None)
+            .await
+            .expect("Failed to create user_settings collection.");
+    }
+
     // Reminder to update LATEST_REVISION when adding new migrations.
     LATEST_REVISION
 }
diff --git a/src/routes/channels/message_send.rs b/src/routes/channels/message_send.rs
index 3f2e439..38c529d 100644
--- a/src/routes/channels/message_send.rs
+++ b/src/routes/channels/message_send.rs
@@ -58,8 +58,8 @@ pub async fn req(user: User, target: Ref, message: Json<Data>) -> Result<JsonVal
     }
 
     let id = Ulid::new().to_string();
-    let attachment = if let Some(attachment_id) = &message.attachment {
-        Some(File::find_and_use(attachment_id, "attachments", "message", &id).await?)
+    let attachments = if let Some(attachment_id) = &message.attachment {
+        Some(vec![ File::find_and_use(attachment_id, "attachments", "message", &id).await? ])
     } else {
         None
     };
@@ -70,7 +70,7 @@ pub async fn req(user: User, target: Ref, message: Json<Data>) -> Result<JsonVal
         author: user.id,
 
         content: Content::Text(message.content.clone()),
-        attachment,
+        attachments,
         nonce: Some(message.nonce.clone()),
         edited: None,
         embeds: None,
diff --git a/src/version.rs b/src/version.rs
index 3ea1721..cc9aa59 100644
--- a/src/version.rs
+++ b/src/version.rs
@@ -1 +1 @@
-pub const VERSION: &str = "0.4.1-alpha.13";
+pub const VERSION: &str = "0.4.2-alpha.0";
-- 
GitLab