From a8eb4032807691dce629aeefed7f24869a53be11 Mon Sep 17 00:00:00 2001
From: Paul Makles <paulmakles@gmail.com>
Date: Tue, 11 Aug 2020 21:08:01 +0200
Subject: [PATCH] Logging + database migrations system.

---
 Cargo.lock                         | 41 ++++++++++++++++++++---
 Cargo.toml                         |  2 ++
 src/database/guild.rs              |  1 +
 src/database/migrations/init.rs    | 39 ++++++++++++++++++++++
 src/database/migrations/mod.rs     | 19 +++++++++++
 src/database/migrations/scripts.rs | 53 ++++++++++++++++++++++++++++++
 src/database/mod.rs                |  9 ++---
 src/main.rs                        |  1 +
 src/notifications/ws.rs            |  4 +--
 9 files changed, 157 insertions(+), 12 deletions(-)
 create mode 100644 src/database/migrations/init.rs
 create mode 100644 src/database/migrations/mod.rs
 create mode 100644 src/database/migrations/scripts.rs

diff --git a/Cargo.lock b/Cargo.lock
index 6e85b9e..842ac0d 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -509,6 +509,19 @@ dependencies = [
  "syn 1.0.37",
 ]
 
+[[package]]
+name = "env_logger"
+version = "0.7.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "44533bbbb3bb3c1fa17d9f2e4e38bbbaf8396ba82193c4cb1b6445d711445d36"
+dependencies = [
+ "atty",
+ "humantime",
+ "log 0.4.11",
+ "regex",
+ "termcolor",
+]
+
 [[package]]
 name = "err-derive"
 version = "0.2.4"
@@ -888,6 +901,15 @@ version = "1.3.4"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "cd179ae861f0c2e53da70d892f5f3029f9594be0c41dc5269cd371691b1dc2f9"
 
+[[package]]
+name = "humantime"
+version = "1.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "df004cfca50ef23c36850aaaa59ad52cc70d0e90243c3c7737a4dd32dc7a3c4f"
+dependencies = [
+ "quick-error",
+]
+
 [[package]]
 name = "hyper"
 version = "0.10.16"
@@ -1891,8 +1913,10 @@ dependencies = [
  "bitfield",
  "chrono",
  "dotenv",
+ "env_logger",
  "hashbrown 0.7.2",
  "lazy_static",
+ "log 0.4.11",
  "lru",
  "mongodb",
  "num_enum",
@@ -2423,6 +2447,15 @@ dependencies = [
  "winapi 0.3.9",
 ]
 
+[[package]]
+name = "termcolor"
+version = "1.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bb6bfa289a4d7c5766392812c0a1f4c1ba45afa1ad47803c11e1f407d846d75f"
+dependencies = [
+ "winapi-util",
+]
+
 [[package]]
 name = "thiserror"
 version = "1.0.20"
@@ -2586,9 +2619,9 @@ checksum = "e987b6bf443f4b5b3b6f38704195592cca41c5bb7aedd3c3693c7081f8289860"
 
 [[package]]
 name = "tracing"
-version = "0.1.18"
+version = "0.1.19"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "f0aae59226cf195d8e74d4b34beae1859257efb4e5fed3f147d2dc2c7d372178"
+checksum = "6d79ca061b032d6ce30c660fded31189ca0b9922bf483cd70759f13a2d86786c"
 dependencies = [
  "cfg-if",
  "log 0.4.11",
@@ -2597,9 +2630,9 @@ dependencies = [
 
 [[package]]
 name = "tracing-core"
-version = "0.1.12"
+version = "0.1.14"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "b2734b5a028fa697686f16c6d18c2c6a3c7e41513f9a213abb6754c4acb3c8d7"
+checksum = "db63662723c316b43ca36d833707cc93dff82a02ba3d7e354f342682cc8b3545"
 dependencies = [
  "lazy_static",
 ]
diff --git a/Cargo.toml b/Cargo.toml
index 849b553..0a7a76a 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -28,3 +28,5 @@ rocket_cors = "0.5.2"
 bitfield = "0.13.2"
 lru = "0.5.3"
 lazy_static = "1.4.0"
+log = "0.4.11"
+env_logger = "0.7.1"
diff --git a/src/database/guild.rs b/src/database/guild.rs
index 68cc5c5..73a2698 100644
--- a/src/database/guild.rs
+++ b/src/database/guild.rs
@@ -42,6 +42,7 @@ pub struct Guild {
     pub description: String,
     pub owner: String,
 
+    // ? FIXME: ADD: pub channels: Vec<Channel>,
     pub invites: Vec<Invite>,
     pub bans: Vec<Ban>,
 
diff --git a/src/database/migrations/init.rs b/src/database/migrations/init.rs
new file mode 100644
index 0000000..4616760
--- /dev/null
+++ b/src/database/migrations/init.rs
@@ -0,0 +1,39 @@
+use super::super::get_db;
+use super::scripts::LATEST_REVISION;
+
+use mongodb::options::CreateCollectionOptions;
+use mongodb::bson::doc;
+use log::info;
+
+pub fn create_database() {
+    info!("Creating database.");
+    let db = get_db();
+
+    db.create_collection("users", None).expect("Failed to create users collection.");
+    db.create_collection("channels", None).expect("Failed to create channels collection.");
+    db.create_collection("guilds", None).expect("Failed to create guilds collection.");
+    db.create_collection("members", None).expect("Failed to create members collection.");
+    db.create_collection("messages", None).expect("Failed to create messages collection.");
+    db.create_collection("migrations", None).expect("Failed to create migrations collection.");
+
+    db.create_collection(
+        "pubsub",
+        CreateCollectionOptions::builder()
+            .capped(true)
+            .size(1_000_000)
+            .build()
+    )
+    .expect("Failed to create pubsub collection.");
+
+    db.collection("migrations")
+        .insert_one(
+            doc! {
+                "_id": 0,
+                "revision": LATEST_REVISION
+            },
+            None
+        )
+        .expect("Failed to save migration info.");
+    
+    info!("Created database.");
+}
diff --git a/src/database/migrations/mod.rs b/src/database/migrations/mod.rs
new file mode 100644
index 0000000..6e188b0
--- /dev/null
+++ b/src/database/migrations/mod.rs
@@ -0,0 +1,19 @@
+use super::get_connection;
+
+pub mod init;
+pub mod scripts;
+
+pub fn run_migrations() {
+    let client = get_connection();
+
+    let list = client.list_database_names(
+        None,
+        None
+    ).expect("Failed to fetch database names.");
+
+    if list.iter().position(|x| x == "revolt").is_none() {
+        init::create_database();
+    } else {
+        scripts::migrate_database();
+    }
+}
diff --git a/src/database/migrations/scripts.rs b/src/database/migrations/scripts.rs
new file mode 100644
index 0000000..72291af
--- /dev/null
+++ b/src/database/migrations/scripts.rs
@@ -0,0 +1,53 @@
+use super::super::get_collection;
+
+use serde::{Serialize, Deserialize};
+use mongodb::bson::{Bson, from_bson, doc};
+use log::info;
+
+#[derive(Serialize, Deserialize)]
+struct MigrationInfo {
+    _id: i32,
+    revision: i32
+}
+
+pub const LATEST_REVISION: i32 = 1;
+
+pub fn migrate_database() {
+    let migrations = get_collection("migrations");
+    let data = migrations.find_one(None, None)
+        .expect("Failed to fetch migration data.");
+    
+    if let Some(doc) = data {
+        let info: MigrationInfo = from_bson(Bson::Document(doc))
+            .expect("Failed to read migration information.");
+        
+        let revision = run_migrations(info.revision);
+
+        migrations.update_one(
+            doc! {
+                "_id": info._id
+            },
+            doc! {
+                "$set": {
+                    "revision": revision
+                }
+            },
+            None
+        ).expect("Failed to commit migration information.");
+
+        info!("Migration complete. Currently at revision {}.", revision);
+    } else {
+        panic!("Database was configured incorrectly, possibly because initalization failed.")
+    }
+}
+
+pub fn run_migrations(revision: i32) -> i32 {
+    info!("Starting database migration.");
+
+    if revision <= 0 {
+        info!("Running migration [revision 0]: Test migration system.");
+    }
+
+    // Reminder to update LATEST_REVISION when adding new migrations.
+    LATEST_REVISION
+}
diff --git a/src/database/mod.rs b/src/database/mod.rs
index 49d688b..0c5c534 100644
--- a/src/database/mod.rs
+++ b/src/database/mod.rs
@@ -10,13 +10,8 @@ pub fn connect() {
         Client::with_uri_str(&env::var("DB_URI").expect("DB_URI not in environment variables!"))
             .expect("Failed to init db connection.");
 
-    client
-        .database("revolt")
-        .collection("migrations")
-        .find(doc! {}, None)
-        .expect("Failed to get migration data from database.");
-
     DBCONN.set(client).unwrap();
+    migrations::run_migrations();
 }
 
 pub fn get_connection() -> &'static Client {
@@ -31,6 +26,8 @@ pub fn get_collection(collection: &str) -> Collection {
     get_db().collection(collection)
 }
 
+pub mod migrations;
+
 pub mod channel;
 pub mod guild;
 pub mod message;
diff --git a/src/main.rs b/src/main.rs
index b220a02..72161bc 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -21,6 +21,7 @@ use std::thread;
 
 fn main() {
     dotenv::dotenv().ok();
+    env_logger::init();
     database::connect();
     notifications::start_worker();
 
diff --git a/src/notifications/ws.rs b/src/notifications/ws.rs
index 0d3f343..55ae2f5 100644
--- a/src/notifications/ws.rs
+++ b/src/notifications/ws.rs
@@ -38,7 +38,6 @@ impl Handler for Server {
                                 StateResult::Success(user_id) => {
                                     let user = crate::database::user::fetch_user(&user_id).unwrap().unwrap();
                                     
-                                    self.user_id = Some(user_id);
                                     self.sender.send(
                                         json!({
                                             "type": "authenticate",
@@ -47,12 +46,13 @@ impl Handler for Server {
                                         .to_string(),
                                     )?;
                                     
+                                    self.user_id = Some(user_id);
                                     self.sender.send(
                                         json!({
                                             "type": "ready",
                                             "data": {
                                                 // ! FIXME: rewrite
-                                                "user": user
+                                                "user": user,
                                             }
                                         })
                                         .to_string(),
-- 
GitLab