From 524cc11b3fe1c096fbdeca0e276f2717f4e6af34 Mon Sep 17 00:00:00 2001
From: Johannes Loher <johannes.loher@fg4f.de>
Date: Fri, 10 Nov 2017 16:29:19 +0100
Subject: [PATCH] Added possibility to configure the application with a config
 file or with command line arguments. Currently only selection of the database
 system and options for those systems are available.

---
 source/calendarwebapp/authenticator.d |  10 ++-
 source/calendarwebapp/configuration.d | 125 +++++++++++++++++++++-----
 source/calendarwebapp/event.d         |  10 ++-
 3 files changed, 114 insertions(+), 31 deletions(-)

diff --git a/source/calendarwebapp/authenticator.d b/source/calendarwebapp/authenticator.d
index 09d8d56..84ed9cb 100644
--- a/source/calendarwebapp/authenticator.d
+++ b/source/calendarwebapp/authenticator.d
@@ -92,6 +92,7 @@ private:
 
     @Autowire MySQLPool pool;
     @Autowire PasswordHasher passwordHasher;
+    @Value("mysql.table.users") string usersTableName;
 
 public:
     Nullable!AuthInfo checkUser(string username, string password) @trusted
@@ -100,7 +101,8 @@ public:
         scope (exit)
             cn.close();
         auto prepared = cn.prepare(
-                "SELECT id, username, passwordHash, privilege FROM users WHERE username = ?");
+                "SELECT id, username, passwordHash, privilege FROM "
+                ~ usersTableName ~ " WHERE username = ?");
         prepared.setArg(0, username);
         auto result = prepared.query();
         /* checkHash should be called using vibe.core.concurrency.async to
@@ -123,7 +125,7 @@ public:
         scope (exit)
             cn.close;
         auto prepared = cn.prepare(
-                "INSERT INTO users (username, passwordHash, privilege) VALUES(?, ?, ?)");
+                "INSERT INTO " ~ usersTableName ~ " (username, passwordHash, privilege) VALUES(?, ?, ?)");
         prepared.setArgs(authInfo.username, authInfo.passwordHash, authInfo.privilege.to!uint);
         prepared.exec();
     }
@@ -136,7 +138,7 @@ public:
         auto cn = pool.lockConnection();
         scope (exit)
             cn.close;
-        auto prepared = cn.prepare("SELECT id, username, passwordHash, privilege FROM users");
+        auto prepared = cn.prepare("SELECT id, username, passwordHash, privilege FROM " ~ usersTableName ~ "");
         return prepared.querySet.map!(r => toAuthInfo(r)).inputRangeObject;
     }
 
@@ -145,7 +147,7 @@ public:
         auto cn = pool.lockConnection();
         scope (exit)
             cn.close;
-        auto prepared = cn.prepare("DELETE FROM users WHERE id = ?");
+        auto prepared = cn.prepare("DELETE FROM " ~ usersTableName ~ " WHERE id = ?");
         prepared.setArg(0, id.to!uint);
         prepared.exec();
     }
diff --git a/source/calendarwebapp/configuration.d b/source/calendarwebapp/configuration.d
index ddf38af..a010cc4 100644
--- a/source/calendarwebapp/configuration.d
+++ b/source/calendarwebapp/configuration.d
@@ -3,32 +3,50 @@ module calendarwebapp.configuration;
 import botan.rng.auto_rng : AutoSeededRNG;
 import botan.rng.rng : RandomNumberGenerator;
 
-import calendarwebapp.authenticator : Authenticator, MongoDBAuthenticator,
-    MySQLAuthenticator;
+import calendarwebapp.authenticator : Authenticator;
 import calendarwebapp.calendarwebapp : CalendarWebapp;
-import calendarwebapp.event : EventStore, MongoDBEventStore, MySQLEventStore;
+import calendarwebapp.event : EventStore;
 import calendarwebapp.passhash : BcryptPasswordHasher, PasswordHasher;
 
-import mysql : MySQLPool;
-
 import poodinis;
 
-import vibe.db.mongo.client : MongoClient;
+import vibe.core.log : logInfo;
 import vibe.db.mongo.collection : MongoCollection;
-import vibe.db.mongo.mongo : connectMongoDB;
 
 class Context : ApplicationContext
 {
 public:
     override void registerDependencies(shared(DependencyContainer) container)
     {
-        auto mongoClient = connectMongoDB("localhost");
-        auto pool = new MySQLPool("localhost", "username", "password", "CalendarWebapp");
-        container.register!MySQLPool.existingInstance(pool);
-        container.register!MongoClient.existingInstance(mongoClient);
-        container.register!(EventStore, MySQLEventStore);
-        container.register!(Authenticator, MySQLAuthenticator);
+        container.register!(ValueInjector!Arguments, AppArgumentsInjector);
+        auto arguments = container.resolve!(AppArgumentsInjector).get("");
+        final switch (arguments.database) with (DatabaseArgument)
+        {
+        case mongodb:
+            import vibe.db.mongo.client : MongoClient;
+            import vibe.db.mongo.mongo : connectMongoDB;
+            import calendarwebapp.authenticator : MongoDBAuthenticator;
+            import calendarwebapp.event : MongoDBEventStore;
 
+            auto mongoClient = connectMongoDB(arguments.mongodb.host);
+            container.register!MongoClient.existingInstance(mongoClient);
+            container.register!(EventStore, MongoDBEventStore!());
+            container.register!(Authenticator, MongoDBAuthenticator!());
+            logInfo("Using MongoDB as database system");
+            break;
+        case mysql:
+            import mysql : MySQLPool;
+            import calendarwebapp.authenticator : MySQLAuthenticator;
+            import calendarwebapp.event : MySQLEventStore;
+
+            auto pool = new MySQLPool(arguments.mysql.host, arguments.mysql.username,
+                    arguments.mysql.password, arguments.mysql.database);
+            container.register!MySQLPool.existingInstance(pool);
+            container.register!(EventStore, MySQLEventStore);
+            container.register!(Authenticator, MySQLAuthenticator);
+            logInfo("Using MySQL as database system");
+            break;
+        }
         container.register!(PasswordHasher, BcryptPasswordHasher);
         container.register!(RandomNumberGenerator, AutoSeededRNG);
         container.register!CalendarWebapp;
@@ -41,19 +59,18 @@ class StringInjector : ValueInjector!string
 {
 private:
     string[string] config;
+    @Value() Arguments arguments;
+    bool initialized = false;
 
 public:
-    this() const @safe pure nothrow
-    {
-        // dfmt off
-        config = ["Database name" :           "CalendarWebapp",
-                  "Users collection name":    "users",
-                  "Events collection name" :  "events"];
-        // dfmt on
-    }
 
-    override string get(string key) const @safe pure nothrow
+    override string get(string key) @safe nothrow
     {
+        if (!initialized)
+        {
+            config = ["MongoDB database name" : arguments.mongodb.database,
+                "mysql.table.users" : "users", "mysql.table.events" : "events"];
+        }
         return config[key];
     }
 }
@@ -61,8 +78,10 @@ public:
 class MongoCollectionInjector : ValueInjector!MongoCollection
 {
 private:
+    import vibe.db.mongo.client : MongoClient;
+
     @Autowire MongoClient mongoClient;
-    @Value("Database name")
+    @Value("MongoDB database name")
     string databaseName;
 
 public:
@@ -71,3 +90,63 @@ public:
         return mongoClient.getCollection(databaseName ~ "." ~ key);
     }
 }
+
+class AppArgumentsInjector : ValueInjector!Arguments
+{
+private:
+    Arguments arguments;
+public:
+
+    this()
+    {
+        import vibe.core.args : readOption;
+
+        readOption("database", &arguments.database, "The database system to use.");
+        readOption("mongodb.host", &arguments.mongodb.host,
+                "The host of the MongoDB instance to use.");
+        readOption("mongodb.database", &arguments.mongodb.database,
+                "The name of the MongoDB database to use.");
+        readOption("mysql.host", &arguments.mysql.host, "The host of the MySQL instance to use.");
+        readOption("mysql.username", &arguments.mysql.username,
+                "The username to use for logging into the MySQL instance.");
+        readOption("mysql.password", &arguments.mysql.password,
+                "The password to use for logging into the MySQL instance.");
+        readOption("mysql.database", &arguments.mysql.database,
+                "The name of the MySQL database to use.");
+    }
+
+    override Arguments get(string key) @safe
+    {
+        import std.exception : enforce;
+
+        enforce(key == "", "There is only one instance of Arguments, to inject it use @Value().");
+        return arguments;
+    }
+}
+
+enum DatabaseArgument
+{
+    mongodb,
+    mysql
+}
+
+struct MySQLArguments
+{
+    string host = "localhost";
+    string username = "username";
+    string password = "password";
+    string database = "CalendarWebapp";
+}
+
+struct MongoDBArguments
+{
+    string host = "localhost";
+    string database = "CalendarWebapp";
+}
+
+struct Arguments
+{
+    DatabaseArgument database = DatabaseArgument.mongodb;
+    MySQLArguments mysql;
+    MongoDBArguments mongodb;
+}
diff --git a/source/calendarwebapp/event.d b/source/calendarwebapp/event.d
index bde695d..587c89a 100644
--- a/source/calendarwebapp/event.d
+++ b/source/calendarwebapp/event.d
@@ -73,6 +73,8 @@ class MySQLEventStore : EventStore
 private:
     import mysql;
 
+    @Value("mysql.table.events") string eventsTableName;
+
 public:
     Event getEvent(string id)
     {
@@ -80,7 +82,7 @@ public:
         scope (exit)
             cn.close;
         auto prepared = cn.prepare(
-                "SELECT id begin end name description type shout FROM events WHERE id = ?");
+                "SELECT id begin end name description type shout FROM " ~ eventsTableName ~ " WHERE id = ?");
         prepared.setArg(0, id.to!uint);
         return toEvent(prepared.query.front);
     }
@@ -91,7 +93,7 @@ public:
         scope (exit)
             cn.close;
         auto prepared = cn.prepare(
-                "SELECT id, begin, end, name, description, type, shout FROM events");
+                "SELECT id, begin, end, name, description, type, shout FROM " ~ eventsTableName ~ "");
         return prepared.querySet.map!(r => toEvent(r)).inputRangeObject;
     }
 
@@ -101,7 +103,7 @@ public:
         scope (exit)
             cn.close;
         auto prepared = cn.prepare(
-                "INSERT INTO events (begin, end, name, description, type, shout) VALUES(?, ?, ?, ?, ?, ?)");
+                "INSERT INTO " ~ eventsTableName ~ " (begin, end, name, description, type, shout) VALUES(?, ?, ?, ?, ?, ?)");
         prepared.setArgs(event.begin, event.end, event.name, event.description,
                 event.type.to!uint, event.shout);
         prepared.exec();
@@ -119,7 +121,7 @@ public:
         auto cn = pool.lockConnection();
         scope (exit)
             cn.close;
-        auto prepared = cn.prepare("DELETE FROM events WHERE id = ?");
+        auto prepared = cn.prepare("DELETE FROM " ~ eventsTableName ~ " WHERE id = ?");
         prepared.setArg(0, id.to!uint);
         prepared.exec();
     }