diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json
index 5fbaf7b..bb6cda0 100644
--- a/.devcontainer/devcontainer.json
+++ b/.devcontainer/devcontainer.json
@@ -13,6 +13,12 @@
// "customizations": {},
// Uncomment to connect as root instead. More info: https://aka.ms/dev-containers-non-root.
"remoteUser": "root",
+ "runArgs": [
+ // This is necessary to avoid having to manually export environment variables from
+ // the .env file in every terminal session.
+ "--env-file",
+ "${localWorkspaceFolder}/.env"
+ ],
"customizations": {
"vscode": {
"extensions": [
diff --git a/.env.example b/.env.example
index efdf110..d672252 100644
--- a/.env.example
+++ b/.env.example
@@ -1,3 +1,8 @@
+API_HOST=tobemodified
+API_PORT=tobemodified
+API_LOG_LEVEL=tobemodified # Possible values: TRACE (0), DEBUG (1), INFO (2), WARN (3) or ERROR (4)
+API_NUMBER_OF_THREADS=tobemodified # Cannot be less than 1
+
POSTGRES_HOST=tobemodified
POSTGRES_PORT=tobemodified
POSTGRES_DB=tobemodified
diff --git a/CMakeLists.txt b/CMakeLists.txt
index 0064ae7..957a73d 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -5,6 +5,7 @@ project(${PROJECT_NAME} CXX)
file(GLOB_RECURSE sources CONFIGURE_DEPENDS
${CMAKE_SOURCE_DIR}/src/*/*.cpp
${CMAKE_SOURCE_DIR}/src/data/models/odb-gen/*.cxx
+ ${CMAKE_SOURCE_DIR}/src/utils/**/*.cpp
)
set(PROJECT_OBJECTS ${PROJECT_NAME}_lib)
diff --git a/README.md b/README.md
index 71bc614..0ec184c 100644
--- a/README.md
+++ b/README.md
@@ -16,6 +16,7 @@ For development purposes use a devcontainer named `developing`.
- Microsoft VS Code
- VS Code should also have the "Dev Containers" extension installed. To check it, open "View: Extensions" with `Ctrl + Shift + X` or as shown in the screenshot below:

+- Before running the container, create a .env file in the project root and specify the environment variables in it, just as you did in .env.example. Otherwise, running the devcontainer will result in an error.
- Make sure Docker daemon is running before opening the dev container (`Ctrl + Shift + P` -> "Reopen in container" or click here + "Reopen in container")

@@ -39,7 +40,7 @@ When dependencies are built, use the command `conan build . -pr:h profiles/to-do
#### Before launching web server:
- Run the database container via docker compose command `docker compose up -d` from workspace.
-- Import environment variables declared in the `.env` file while in the dev container and using the command `export $(grep -v '^#' .env | xargs)`. If the file containing the environment variables is named something other than `.env`, you should modify the command to specify the correct name.
+- Import the environment variables defined in the .env file in the project root. The environment variables were automatically exported from .env when the container was built. If the contents of the .env file have been modified after the container was built, run the command `export $(grep -v '^#' .env | xargs)`, but keep in mind that the environment variables will only be visible in the terminal session where the command was executed.
To launch the executable, click Launch in the CMake extension.

@@ -103,4 +104,4 @@ The alembic tool is used to work with migrations. To work with it, you need to m
Alternatively, you can use make targets.
- To create a migration, run the command `make create-migration name=`, where `name` is the name of the migration. You can also use `make create-migration`, in which case the migration will be named after the current date and time.
-- To apply the migrations, run the `make apply-migrations` command.
\ No newline at end of file
+- To apply the migrations, run the `make apply-migrations` command.
diff --git a/src/data/db_connection.cpp b/src/data/db_connection.cpp
index 38ec95a..d7ae6f4 100644
--- a/src/data/db_connection.cpp
+++ b/src/data/db_connection.cpp
@@ -1,4 +1,5 @@
#include "db_connection.h"
+#include "../utils/app-config/app-config.h"
#include
#include
@@ -14,15 +15,17 @@ std::shared_ptr DbConnection::get()
flag,
[]()
{
- const std::string user = std::getenv("POSTGRES_USER");
- const std::string password = std::getenv("POSTGRES_PASSWORD");
- const std::string db_name = std::getenv("POSTGRES_DB");
- const std::string host = std::getenv("POSTGRES_HOST");
- const std::string port = std::getenv("POSTGRES_PORT");
+ auto& config = AppConfig::GetInstance();
- std::string conninfo = "host=" + host + " port=" + port + " dbname=" + db_name + " user=" + user + " password=" + password;
+ const std::string host = config.getDatabaseHost();
+ const std::string port = config.getDatabasePort();
+ const std::string user = config.getDatabaseUser();
+ const std::string password = config.getDatabasePassword();
+ const std::string dbname = config.getDatabaseName();
- instance_ = std::shared_ptr(new odb::pgsql::database(conninfo));
+ std::string conn = "host=" + host + " port=" + port + " dbname=" + dbname + " user=" + user + " password=" + password;
+
+ instance_ = std::shared_ptr(new odb::pgsql::database(conn));
}
);
diff --git a/src/main.cpp b/src/main.cpp
index 1f7f9d8..009554b 100644
--- a/src/main.cpp
+++ b/src/main.cpp
@@ -1,9 +1,15 @@
+#include "utils/app-config/app-config.h"
#include
int main()
{
- drogon::app().addListener("127.0.0.1", 8080).setLogLevel(trantor::Logger::kInfo).setThreadNum(1);
+ // We need to set the logging level right away; otherwise, the default level
+ // will be used until .setLogLevel is called
+ drogon::app().setLogLevel(AppConfig::getApiLogLevel());
+ auto& config = AppConfig::GetInstance();
+
+ drogon::app().addListener(config.getApiHost(), config.getApiPort()).setThreadNum(config.getApiNumThreads());
drogon::app().run();
return 0;
diff --git a/src/utils/app-config/app-config.cpp b/src/utils/app-config/app-config.cpp
new file mode 100644
index 0000000..f8c48b0
--- /dev/null
+++ b/src/utils/app-config/app-config.cpp
@@ -0,0 +1,161 @@
+#include "app-config.h"
+
+AppConfig::AppConfig()
+{
+ setApiHost(getEnv("API_HOST", "0.0.0.0"));
+ setApiPort(getEnvInt("API_PORT", 80));
+ setApiNumThreads(getEnvInt("API_NUMBER_OF_THREADS", 1));
+ setDatabaseHost(getEnv("POSTGRES_HOST", "0.0.0.0"));
+ setDatabasePort(getEnv("POSTGRES_PORT", "5432"));
+ setDatabaseName(getEnv("POSTGRES_DB", "to-dos-api-cpp-db"));
+ setDatabaseUser(getEnv("POSTGRES_USER", "postgres"));
+ setDatabasePassword(getEnv("POSTGRES_PASSWORD", "password"));
+}
+
+AppConfig& AppConfig::GetInstance()
+{
+ static AppConfig instance;
+ return instance;
+}
+
+std::string AppConfig::getEnv(std::string name, std::string defaultValue)
+{
+ char* value = std::getenv(name.c_str());
+
+ if (value)
+ {
+ return std::string(value);
+ }
+ else
+ {
+ LOG_WARN << "Failed to get the value of the " << name << " environment variable; the default value will be used: " << defaultValue;
+ return defaultValue;
+ }
+}
+
+uint32_t AppConfig::getEnvInt(std::string name, uint32_t defaultValue)
+{
+ char* value = std::getenv(name.c_str());
+
+ if (!value)
+ return defaultValue;
+
+ try
+ {
+ auto result = std::stoi(value);
+ auto limit = std::numeric_limits::max();
+
+ if (result < 0 || result > limit)
+ throw std::overflow_error(
+ "error: an attempt to write a " + name + " value which is larger than what can be stored in a " + std::to_string(limit)
+ );
+
+ return static_cast(result);
+ }
+ catch (const std::exception& e)
+ {
+ LOG_ERROR << e.what();
+ return defaultValue;
+ }
+}
+
+trantor::Logger::LogLevel AppConfig::parseLogLevel(const std::string& level)
+{
+ // kTrace < kDebug < kInfo < kWarn < kError
+ if (level == "TRACE")
+ return trantor::Logger::kTrace;
+ else if (level == "DEBUG")
+ return trantor::Logger::kDebug;
+ else if (level == "INFO")
+ return trantor::Logger::kInfo;
+ else if (level == "WARN")
+ return trantor::Logger::kWarn;
+ else
+ return trantor::Logger::kError;
+}
+
+void AppConfig::setApiHost(std::string apiHost)
+{
+ apiHost_ = apiHost;
+ LOG_DEBUG << "Update value: apiHost_=" << apiHost_;
+};
+
+void AppConfig::setApiPort(uint32_t apiPort)
+{
+ apiPort_ = apiPort;
+ LOG_DEBUG << "Update value: apiPort_=" << apiPort_;
+};
+
+void AppConfig::setApiNumThreads(uint32_t apiNumThreads)
+{
+ uint32_t numberOfThreads = apiNumThreads;
+
+ if (numberOfThreads > 0)
+ {
+ apiNumThreads_ = numberOfThreads;
+ }
+ else
+ {
+ apiNumThreads_ = 1;
+ LOG_ERROR << "The number of allocated threads cannot be less than 1";
+ }
+
+ LOG_DEBUG << "Update value: apiNumThreads_=" << apiNumThreads_;
+};
+
+void AppConfig::setDatabaseHost(std::string databaseHost)
+{
+ databaseHost_ = databaseHost;
+ LOG_DEBUG << "Update value: databaseHost_=" << databaseHost_;
+};
+
+void AppConfig::setDatabasePort(std::string databasePort)
+{
+ databasePort_ = databasePort;
+ LOG_DEBUG << "Update value: databasePort_=" << databasePort_;
+};
+
+void AppConfig::setDatabaseName(std::string databaseName)
+{
+ databaseName_ = databaseName;
+ LOG_DEBUG << "Update value: databaseName_=" << databaseName_;
+};
+
+void AppConfig::setDatabaseUser(std::string databaseUser)
+{
+ databaseUser_ = databaseUser;
+ LOG_DEBUG << "Update value: databaseUser_=" << databaseUser_;
+};
+
+void AppConfig::setDatabasePassword(std::string databasePassword)
+{
+ databasePassword_ = databasePassword;
+ LOG_DEBUG << "Update value: databasePassword_=" << databasePassword_;
+};
+
+const std::string& AppConfig::getApiHost() const
+{ return apiHost_; }
+
+const uint32_t& AppConfig::getApiPort() const
+{ return apiPort_; }
+
+const uint32_t& AppConfig::getApiNumThreads() const
+{ return apiNumThreads_; }
+
+const trantor::Logger::LogLevel AppConfig::getApiLogLevel()
+{ return parseLogLevel(getEnv("API_LOG_LEVEL", "INFO")); }
+
+const std::string& AppConfig::getDatabaseHost() const
+{ return databaseHost_; }
+
+const std::string& AppConfig::getDatabasePort() const
+{ return databasePort_; }
+
+const std::string& AppConfig::getDatabaseName() const
+{ return databaseName_; }
+
+const std::string& AppConfig::getDatabaseUser() const
+{ return databaseUser_; }
+
+const std::string& AppConfig::getDatabasePassword() const
+{ return databasePassword_; }
\ No newline at end of file
diff --git a/src/utils/app-config/app-config.h b/src/utils/app-config/app-config.h
new file mode 100644
index 0000000..86fa4b3
--- /dev/null
+++ b/src/utils/app-config/app-config.h
@@ -0,0 +1,172 @@
+#pragma once
+
+#include
+#include
+#include
+#include
+#include
+
+class AppConfig
+{
+private:
+ /**
+ * @brief AppConfig class constructor
+ * @return void
+ */
+ AppConfig();
+
+ // No copying
+ AppConfig(const AppConfig&) = delete;
+ AppConfig& operator=(const AppConfig&) = delete;
+
+ std::string apiHost_;
+ uint32_t apiPort_;
+ uint32_t apiNumThreads_;
+
+ std::string databaseHost_;
+ std::string databasePort_;
+ std::string databaseName_;
+ std::string databaseUser_;
+ std::string databasePassword_;
+
+ /**
+ * @brief Setter for the `apiHost_` variable
+ * @param apiHost is host on which the API will be run
+ * @return void
+ */
+ void setApiHost(std::string apiHost);
+
+ /**
+ * @brief Setter for the `apiPort_` variable
+ * @param apiPort is port on which the API will be run
+ * @return void
+ */
+ void setApiPort(uint32_t apiPort);
+
+ /**
+ * @brief Setter for the `apiNumThreads_` variable
+ * @param apiNumThreads is number of threads on which the API will run
+ * @return void
+ */
+ void setApiNumThreads(uint32_t apiNumThreads);
+
+ /**
+ * @brief Setter for the `databaseHost_` variable
+ * @param databaseHost is the host that the API will use to connect to the database
+ * @return void
+ */
+ void setDatabaseHost(std::string databaseHost);
+
+ /**
+ * @brief Setter for the `databasePort_` variable
+ * @param databasePort is the port that the API will use to connect to the database
+ * @return void
+ */
+ void setDatabasePort(std::string databasePort);
+
+ /**
+ * @brief Setter for the `databaseName_` variable
+ * @param databaseName is the name that the API will use to connect to the database
+ * @return void
+ */
+ void setDatabaseName(std::string databaseName);
+
+ /**
+ * @brief Setter for the `databaseUser_` variable
+ * @param databaseUser is the user that the API will use to connect to the database
+ * @return void
+ */
+ void setDatabaseUser(std::string databaseUser);
+
+ /**
+ * @brief Setter for the `databasePassword_` variable
+ * @param databasePassword is the password that the API will use to connect to the database
+ * @return void
+ */
+ void setDatabasePassword(std::string databasePassword);
+
+public:
+ /**
+ * @brief Function to retrieve an instance of the AppConfig class
+ * @return an instance of the AppConfig class
+ */
+ static AppConfig& GetInstance();
+
+ /**
+ * @brief Function for retrieving the value of an environment variable
+ * @param name is the name of the environment variable
+ * @param defaultValue is the value that will be used if the environment variable doesn't exist
+ * @return std::string
+ */
+ static std::string getEnv(std::string name, std::string defaultValue);
+
+ /**
+ * @brief Function that retrieves a value from an environment variable and converts it to an integer type
+ * @param name is the name of the environment variable
+ * @param defaultValue is the value that will be used if the environment variable doesn't exist
+ * @return uint32_t
+ */
+ static uint32_t getEnvInt(std::string name, uint32_t defaultValue);
+
+ /**
+ * @brief Function for converting a string representing a logging level to the `trantor` type. Default is `trantor::Logger::kError`
+ * @param level is a string containing the logging level.
+ */
+ static trantor::Logger::LogLevel parseLogLevel(const std::string& level);
+
+ /**
+ * @brief Getter for the `apiHost_` variable
+ * @return const std::string
+ */
+ const std::string& getApiHost() const;
+
+ /**
+ * @brief Getter for the `apiPort_` variable
+ * @return const uint32_t
+ */
+ const uint32_t& getApiPort() const;
+
+ /**
+ * @brief Getter for the `apiNumThreads_` variable
+ * @return const uint32_t
+ */
+ const uint32_t& getApiNumThreads() const;
+
+ /**
+ * @brief Getter for the `apiLogLevel_` variable
+ * @note The method must be static so that the level can be set before the class is initialized;
+ * otherwise, logs from other levels will be displayed
+ * @return const trantor::Logger::LogLevel
+ */
+ static const trantor::Logger::LogLevel getApiLogLevel();
+
+ /**
+ * @brief Getter for the `databaseHost_` variable
+ * @return const std::string
+ */
+ const std::string& getDatabaseHost() const;
+
+ /**
+ * @brief Getter for the `databasePort_` variable
+ * @return const std::string
+ */
+ const std::string& getDatabasePort() const;
+
+ /**
+ * @brief Getter for the `databaseName_` variable
+ * @return const std::string
+ */
+ const std::string& getDatabaseName() const;
+
+ /**
+ * @brief Getter for the `databaseUser_` variable
+ * @return const std::string
+ */
+ const std::string& getDatabaseUser() const;
+
+ /**
+ * @brief Getter for the `databasePassword_` variable
+ * @return const std::string
+ */
+ const std::string& getDatabasePassword() const;
+};
\ No newline at end of file