diff --git a/.github/workflows/cmake.yml b/.github/workflows/cmake.yml index 7f621ef..c7607e6 100644 --- a/.github/workflows/cmake.yml +++ b/.github/workflows/cmake.yml @@ -5,7 +5,7 @@ on: branches: [ '*' ] # NOTE: replace/update with appropriate branch name(s) tags: [ '*' ] pull_request: - types: [closed] + types: [ closed ] branches: [ '*' ] # NOTE: replace/update with appropriate branch name(s) workflow_dispatch: inputs: @@ -107,16 +107,16 @@ jobs: fail-fast: false matrix: include: -# - os: ubuntu-22.04 -# c: clang-18 -# cxx: clang++-18 -# clang_ver: "18" -# clang_ver_full: "18.1.8" -# name: "MSan: Ubuntu 22.04 Clang 18" -# cmake_flags: "-DUSE_MSAN=ON" -# cmake_generator: Ninja -# # This env runs memory sanitizers -# runs_msan: true + # - os: ubuntu-22.04 + # c: clang-18 + # cxx: clang++-18 + # clang_ver: "18" + # clang_ver_full: "18.1.8" + # name: "MSan: Ubuntu 22.04 Clang 18" + # cmake_flags: "-DUSE_MSAN=ON" + # cmake_generator: Ninja + # # This env runs memory sanitizers + # runs_msan: true - os: ubuntu-22.04 c: gcc-12 @@ -154,16 +154,16 @@ jobs: # cmake_flags: # cmake_generator: Ninja -# - os: windows-2022 -# c: cl -# cxx: cl -# name: "ASan: Windows 2022 MSVC 19.41" -# cmake_flags: "-DUSE_ASAN=ON" -# # Ninja is not faster on MSVC because... MSVC -# # cmake_generator: "Ninja" -# # cmake_generator: "Ninja Multi-Config" -# # This env runs address sanitizers -# runs_asan: true + # - os: windows-2022 + # c: cl + # cxx: cl + # name: "ASan: Windows 2022 MSVC 19.41" + # cmake_flags: "-DUSE_ASAN=ON" + # # Ninja is not faster on MSVC because... MSVC + # # cmake_generator: "Ninja" + # # cmake_generator: "Ninja Multi-Config" + # # This env runs address sanitizers + # runs_asan: true - os: windows-2022 c: gcc @@ -280,12 +280,41 @@ jobs: - name: "Postgres setup" run: | export PGPASSWORD=pipeline_test_password - psql -h localhost -U pipeline_test_user -d pipeline_test_password_keeper -c "CREATE TABLE IF NOT EXISTS users ( - id SERIAL PRIMARY KEY, - username VARCHAR(50) UNIQUE NOT NULL, - passwordSalt VARCHAR(150) NOT NULL, - passwordHash VARCHAR(150) NOT NULL - );" + psql -h localhost -U pipeline_test_user -d pipeline_test_password_keeper -c " + CREATE TABLE IF NOT EXISTS users ( + id SERIAL PRIMARY KEY, + username VARCHAR(50) UNIQUE NOT NULL, + passwordSalt VARCHAR(150) NOT NULL, + passwordHash VARCHAR(150) NOT NULL + ); + + CREATE TABLE IF NOT EXISTS bankaccounts ( + id SERIAL REFERENCES users (id), + username VARCHAR(50) NOT NULL, + password VARCHAR(50) NOT NULL, + IBAN VARCHAR(50) NOT NULL, + bank VARCHAR(50) NOT NULL, + CONSTRAINT pk_bankaccount PRIMARY KEY (id, username, password, IBAN, bank) + ); + + CREATE TABLE IF NOT EXISTS emailaccounts ( + id SERIAL REFERENCES users (id), + username VARCHAR(50) NOT NULL, + password VARCHAR(50) NOT NULL, + emailAddress VARCHAR(50) NOT NULL, + mailProvider VARCHAR(50) NOT NULL, + CONSTRAINT pk_emailaccounts PRIMARY KEY (id, username, password, emailAddress, mailProvider) + ); + + CREATE TABLE IF NOT EXISTS socialmediaaccounts ( + id SERIAL REFERENCES users (id), + username VARCHAR(50) NOT NULL, + password VARCHAR(50) NOT NULL, + platform VARCHAR(50) NOT NULL, + profileUrl VARCHAR(50) NOT NULL, + CONSTRAINT pk_socialmediaaccounts PRIMARY KEY (id, username, password, platform, profileUrl) + ); + " - name: "Run tests" env: diff --git a/.idea/sqldialects.xml b/.idea/sqldialects.xml index 6c9a15b..bddfe6d 100644 --- a/.idea/sqldialects.xml +++ b/.idea/sqldialects.xml @@ -1,7 +1,8 @@ - + + \ No newline at end of file diff --git a/CMakeLists.txt b/CMakeLists.txt index c53fab3..da1a142 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -26,7 +26,7 @@ FetchContent_MakeAvailable(pqxx) find_package(PostgreSQL) add_subdirectory(src) -if(RUN_TESTS) +if (RUN_TESTS) include(CTest) enable_testing() add_subdirectory(tests) diff --git a/README.md b/README.md index 63d2b49..1368086 100644 --- a/README.md +++ b/README.md @@ -1,36 +1,51 @@ [ENG] + # Password-Keeper + ## Project Description + The Password Keeper application enables users to manage their passwords in a secure database. -It offers functionality to efficiently and safely store and organize passwords, as well as the option to generate new, customized passwords on demand. +It offers functionality to efficiently and safely store and organize passwords, as well as the option to generate new, +customized passwords on demand. ## Notes + 1. PostgreSQL Installation: - To build the project, you must first install [PostgreSQL](https://www.postgresql.org/download/) on your computer. 2. Running the Application Locally: - - To use the application locally, you need to install [Docker](https://docs.docker.com/engine/install/), use the command - `docker compose up` in the project's [infrastructure](./infrastructure) folder, and create a new file named `.env` in the src folder. - Fill this file with the details from [compose.yml](./infrastructure/compose.yaml) , following the format described in the .env.template file. + - To use the application locally, you need to install [Docker](https://docs.docker.com/engine/install/), use the + command + `docker compose up` in the project's [infrastructure](./infrastructure) folder, and create a new file named `.env` + in the src folder. + Fill this file with the details from [compose.yml](./infrastructure/compose.yaml) , following the format described + in the .env.template file. 3. Running Tests: - - To run the tests, add either the env.bat or env.sh file (the former for Windows, the latter for POSIX systems) - to the environment file settings of your toolchain (File | Settings | Build, Execution, Deployment | Toolchains) under the Environment File section. - Tests can be run either by executing the cmake-build-debug/tests/Password-Keeper-Test-Runner file or by running the ctest command in the cmake-build-debug folder. + - To run the tests, you have to compile them first. To do so you have to add the `-DRUN_TESTS=ON` argument when + compiling the app. + - Tests can be run either by executing the `cmake-build-debug/tests/Password-Keeper-Test-Runner` file or by running + the `ctest` command in the `cmake-build-debug` folder. ## CI/CD Pipeline -The CI/CD pipeline is configured in GitHub Actions and includes multiple stages to ensure code quality, compatibility, and functionality across various platforms. + +The CI/CD pipeline is configured in GitHub Actions and includes multiple stages to ensure code quality, compatibility, +and functionality across various platforms. 1. Code Quality Validation: - - The first two stages validate code correctness and ensure no warnings are generated (using Cppcheck and Clang-Tidy). + - The first two stages validate code correctness and ensure no warnings are generated (using Cppcheck and + Clang-Tidy). 2. Compatibility Testing Across Multiple Operating Systems: - - The next five stages ensure proper code execution on major operating systems (Linux, macOS, and Windows) and perform memory checks using MSAN, ASAN, and Valgrind to identify potential memory leaks and other memory-related issues. + - The next five stages ensure proper code execution on major operating systems (Linux, macOS, and Windows) and + perform memory checks using ASAN and Valgrind to identify potential memory leaks and other memory-related issues. 3. Functional Test Execution: - - The final stage runs functional tests to verify that the application behaves as expected. + - The final stage runs functional tests to verify that the application behaves as expected. -In all stages involving the execution of the application, a PostgreSQL service is included to ensure proper application functionality. +In all stages involving the execution of the application, a PostgreSQL service is included to ensure proper application +functionality. [RO] + # Password-Keeper ## Descriere proiect @@ -40,57 +55,42 @@ Aceasta oferă funcționalitatea de a stoca și organiza parolele într-un mod e genera parole noi, personalizate, la cerere. ## Mentiuni + 1. Instalare PostgreSQL: - - Pentru a construi proiectul trebuie sa instalati mai intai [PostgreSQL](https://www.postgresql.org/download/) pe - calculatorul dumneavoastra. + - Pentru a construi proiectul trebuie sa instalati mai intai [PostgreSQL](https://www.postgresql.org/download/) pe + calculatorul dumneavoastra. 2. Folosirea aplicatiei local: - - Pentru a folosi aplicatia local trebuie sa instalati [Docker](https://docs.docker.com/engine/install/),sa folositi comanda - `docker compose up` in folderul [infrastructure](./infrastructure) din proiect, sa create un nou fisier numit `.env` in - folderul [src](./src) si sa il completati cu datale din [compose.yml](./infrastructure/compose.yaml) + - Pentru a folosi aplicatia local trebuie sa instalati [Docker](https://docs.docker.com/engine/install/),sa folositi + comanda + `docker compose up` in folderul [infrastructure](./infrastructure) din proiect, sa create un nou fisier numit + `.env` in + folderul [src](./src) si sa il completati cu datale din [compose.yml](./infrastructure/compose.yaml) in modul prezentat in fisierul [.env.template](.env.template) 3. Rularea testelor: - - Pentru a rula testele trebuie sa adaugati unul dintre fisierele [env.bat](env.bat) sau [env.sh](env.sh) (primul pentru - Windows, al doilea pentru sisteme POSIX) in setarile toolchain-ului pe care il folositi (`File | Settings | Build, Execution, Deployment | Toolchains`) - la rubrica `Environment File`. Testele se ruleaza ori ruland executabilul `cmake-build-debug/tests/Password-Keeper-Test-Runner` ori ruland comanda ctest in folderul `cmake-build-debug`. + - Pentru a rula testele acestea trebuie compilate. Pentru a face acest lucru trebuie sa adaugati comanda + `-DRUN_TESTS=ON` atunci cand compilati. + - Testele se ruleaza ori ruland executabilul + `cmake-build-debug/tests/Password-Keeper-Test-Runner` ori ruland comanda ctest in folderul `cmake-build-debug`. ## Pipeline -Pipeline-ul CI/CD este configurat în [GitHub Actions](./.github/workflows/cmake.yml) și include mai multe etape pentru a asigura calitatea codului, compatibilitatea și funcționalitatea pe diferite platforme. + +Pipeline-ul CI/CD este configurat în [GitHub Actions](./.github/workflows/cmake.yml) și include mai multe etape pentru a +asigura calitatea codului, compatibilitatea și funcționalitatea pe diferite platforme. Verificarea calității codului: -Primele două etape validează corectitudinea codului și se asigură că nu generează warnings ( folosind `Cppcheck` si `Clang-Tidy` ). +Primele două etape validează corectitudinea codului și se asigură că nu generează warnings ( folosind `Cppcheck` si +`Clang-Tidy` ). Testarea compatibilității pe multiple sisteme de operare: -Următoarele cinci etape asigură rularea corectă a codului pe principalele sisteme de operare (Linux, macOS și Windows) și efectuează verificări de memorie folosind MSAN, ASAN și Valgrind, pentru a identifica posibile memory leaks și alte probleme legate de memorie. +Următoarele cinci etape asigură rularea corectă a codului pe principalele sisteme de operare (Linux, macOS și Windows) +și efectuează verificări de memorie folosind ASAN și Valgrind, pentru a identifica posibile memory leaks și alte +probleme legate de memorie. Rularea testelor funcționale: Ultima etapă rulează testele pentru a verifica dacă aplicația funcționează conform așteptărilor. -În toate etapele care implică rularea aplicatiei este inclus un serviciu PostgreSQL pentru a permite rularea corectă a aplicației. - -## Milestone #0 - -- [ ] Nume proiect (poate fi schimbat ulterior) -- [ ] Scurtă descriere a temei alese, ce v-ați propus să implementați - -## Milestone #1 - -#### Cerințe - -- [ ] definirea a minim **3-4 clase** folosind compunere cu clasele definite de voi -- [ ] constructori de inițializare cu parametri -- [ ] pentru o aceeași (singură) clasă: constructor de copiere, `operator=` de copiere, destructor -- [ ] `operator<<` pentru toate clasele pentru afișare (std::ostream) -- [ ] cât mai multe `const` (unde este cazul) -- [ ] implementarea a minim 3 funcții membru publice pentru funcționalități specifice temei alese, dintre care cel puțin - 1-2 funcții mai complexe - - nu doar citiri/afișări sau adăugat/șters elemente într-un/dintr-un vector -- [ ] scenariu de utilizare a claselor definite: - - preferabil sub formă de teste unitare, mai ales dacă vorbim de aplicații consolă - - crearea de obiecte și apelarea tuturor funcțiilor membru publice în main - - vor fi adăugate în fișierul `tastatura.txt` DOAR exemple de date de intrare de la tastatură (dacă există); dacă - aveți nevoie de date din fișiere, creați alte fișiere separat -- [ ] tag de `git`: de exemplu `v0.1` -- [ ] serviciu de integrare continuă (CI); exemplu: GitHub Actions +În toate etapele care implică rularea aplicatiei este inclus un serviciu PostgreSQL pentru a permite rularea corectă a +aplicației. ## Milestone #2 diff --git a/infrastructure/Dockerfile b/infrastructure/Dockerfile index aeeba00..8040e7b 100644 --- a/infrastructure/Dockerfile +++ b/infrastructure/Dockerfile @@ -4,4 +4,7 @@ ENV POSTGRES_DB=password_keeper ENV POSTGRES_USER=user ENV POSTGRES_PASSWORD=temp -COPY SQL_Scripts/users.sql /docker-entrypoint-initdb.d/ \ No newline at end of file +COPY SQL_Scripts/1_users.sql /docker-entrypoint-initdb.d/ +COPY SQL_Scripts/2_bankaccounts.sql /docker-entrypoint-initdb.d/ +COPY SQL_Scripts/3_emailaccounts.sql /docker-entrypoint-initdb.d/ +COPY SQL_Scripts/4_socialmediaaccounts.sql /docker-entrypoint-initdb.d/ \ No newline at end of file diff --git a/infrastructure/SQL_Scripts/1_users.sql b/infrastructure/SQL_Scripts/1_users.sql new file mode 100644 index 0000000..f46ffdb --- /dev/null +++ b/infrastructure/SQL_Scripts/1_users.sql @@ -0,0 +1,20 @@ +CREATE TABLE IF NOT EXISTS users +( + id + SERIAL + PRIMARY + KEY, + username + VARCHAR +( + 50 +) UNIQUE NOT NULL, + passwordSalt VARCHAR +( + 150 +) NOT NULL, + passwordHash VARCHAR +( + 150 +) NOT NULL + ); \ No newline at end of file diff --git a/infrastructure/SQL_Scripts/2_bankaccounts.sql b/infrastructure/SQL_Scripts/2_bankaccounts.sql new file mode 100644 index 0000000..0d969cd --- /dev/null +++ b/infrastructure/SQL_Scripts/2_bankaccounts.sql @@ -0,0 +1,9 @@ +CREATE TABLE IF NOT EXISTS bankaccounts +( + id SERIAL references users (id), + username VARCHAR(50) NOT NULL, + password varchar(50) NOT NULL, + IBAN varchar(50) NOT NULL, + bank varchar(50) NOT NULL, + constraint pk_bankaccount PRIMARY KEY (id, username, password, IBAN, bank) +); \ No newline at end of file diff --git a/infrastructure/SQL_Scripts/3_emailaccounts.sql b/infrastructure/SQL_Scripts/3_emailaccounts.sql new file mode 100644 index 0000000..8330187 --- /dev/null +++ b/infrastructure/SQL_Scripts/3_emailaccounts.sql @@ -0,0 +1,9 @@ +CREATE TABLE IF NOT EXISTS emailaccounts +( + id SERIAL references users (id), + username VARCHAR(50) NOT NULL, + password varchar(50) NOT NULL, + emailAddress varchar(50) NOT NULL, + mailProvider varchar(50) NOT NULL, + constraint pk_emailaccounts PRIMARY KEY (id, username, password, emailAddress, mailProvider) +); \ No newline at end of file diff --git a/infrastructure/SQL_Scripts/4_socialmediaaccounts.sql b/infrastructure/SQL_Scripts/4_socialmediaaccounts.sql new file mode 100644 index 0000000..a9b6911 --- /dev/null +++ b/infrastructure/SQL_Scripts/4_socialmediaaccounts.sql @@ -0,0 +1,9 @@ +CREATE TABLE IF NOT EXISTS socialmediaaccounts +( + id SERIAL references users (id), + username VARCHAR(50) NOT NULL, + password varchar(50) NOT NULL, + platform varchar(50) NOT NULL, + profileUrl varchar(50) NOT NULL, + constraint pk_socialmediaaccounts PRIMARY KEY (id, username, password, platform, profileUrl) +); \ No newline at end of file diff --git a/infrastructure/SQL_Scripts/users.sql b/infrastructure/SQL_Scripts/users.sql deleted file mode 100644 index 570ce88..0000000 --- a/infrastructure/SQL_Scripts/users.sql +++ /dev/null @@ -1,6 +0,0 @@ -CREATE TABLE IF NOT EXISTS users ( - id SERIAL PRIMARY KEY, - username VARCHAR(50) UNIQUE NOT NULL, - passwordSalt VARCHAR(150) NOT NULL, - passwordHash VARCHAR(150) NOT NULL -); \ No newline at end of file diff --git a/src/Accounts/Account.cpp b/src/Accounts/Account.cpp new file mode 100644 index 0000000..6125a4e --- /dev/null +++ b/src/Accounts/Account.cpp @@ -0,0 +1,23 @@ +#include + +#include +#include + +Account::Account(std::string username, std::string password) : username(std::move(username)), + password(std::move(password)) { +} + +std::string Account::getUsername() const { + return this->username; +} + +std::string Account::getPassword() const { + return this->password; +} + +void Account::show() const { + std::cout << "---------------------------------\n"; + std::cout << "Account details:\n"; + showAccountDetails(); + std::cout << "\n"; +} diff --git a/src/Accounts/Account.h b/src/Accounts/Account.h new file mode 100644 index 0000000..9dca5cd --- /dev/null +++ b/src/Accounts/Account.h @@ -0,0 +1,30 @@ +#ifndef ACCOUNT_H +#define ACCOUNT_H +#include +#include + +class Account { +protected: + std::string username; + std::string password; + + [[nodiscard]] virtual AccountType getAccountType() const = 0; + + virtual void showAccountDetails() const = 0; + +public: + Account(std::string, std::string); + + virtual void addAccount() = 0; + + virtual ~Account() = default; + + void show() const; + + [[nodiscard]] virtual std::string getUsername() const; + + [[nodiscard]] virtual std::string getPassword() const; +}; + + +#endif //ACCOUNT_H diff --git a/src/Accounts/AccountFactory.cpp b/src/Accounts/AccountFactory.cpp new file mode 100644 index 0000000..607cb4b --- /dev/null +++ b/src/Accounts/AccountFactory.cpp @@ -0,0 +1,27 @@ +#include +#include + +#include +#include +#include + +std::shared_ptr AccountFactory::accountFactory(const AccountType accountType, + std::map accountDetails) { + switch (accountType) { + case AccountType::BankAccountType : { + return std::make_shared(accountDetails["username"], accountDetails["password"], + accountDetails["IBAN"], accountDetails["bank"]); + } + case AccountType::EmailAccountType : { + return std::make_shared(accountDetails["username"], accountDetails["password"], + accountDetails["emailAddress"], accountDetails["mailProvider"]); + } + case AccountType::SocialMediaAccountType : { + return std::make_shared(accountDetails["username"], accountDetails["password"], + accountDetails["platform"], accountDetails["profileUrl"]); + } + default: { + throw std::invalid_argument("Invalid accountType"); + } + } +} diff --git a/src/Accounts/AccountFactory.h b/src/Accounts/AccountFactory.h new file mode 100644 index 0000000..91d166a --- /dev/null +++ b/src/Accounts/AccountFactory.h @@ -0,0 +1,16 @@ +#ifndef ACCOUNTFACTORY_H +#define ACCOUNTFACTORY_H + +#include +#include + +#include +#include + +class AccountFactory { +public: + static std::shared_ptr accountFactory(AccountType, std::map); +}; + + +#endif //ACCOUNTFACTORY_H diff --git a/src/Accounts/BankAccount.cpp b/src/Accounts/BankAccount.cpp new file mode 100644 index 0000000..5a804b6 --- /dev/null +++ b/src/Accounts/BankAccount.cpp @@ -0,0 +1,41 @@ +#include +#include + +#include +#include + +BankAccount::BankAccount(std::string username, std::string password, + std::string IBAN, std::string bank) + : Account(std::move(username), std::move(password)), IBAN(std::move(IBAN)), bank(std::move(bank)) { +} + +AccountType BankAccount::getAccountType() const { + return AccountType::BankAccountType; +} + +void BankAccount::addAccount() { + const Database &database = Database::getDatabaseInstance(); + const auto bankAccount = std::make_shared(*this); + database.addUserDefinedAccount(bankAccount, this->getAccountType()); +} + +std::string BankAccount::getIBAN() const { + return this->IBAN; +} + +std::string BankAccount::getBank() const { + return this->bank; +} + +bool BankAccount::operator==(const BankAccount &rhs) const { + return this->username == rhs.username && this->password == rhs.password + && this->IBAN == rhs.IBAN && this->bank == rhs.bank; +} + +void BankAccount::showAccountDetails() const { + std::cout << "Account Type: " << getAccountTypeString(this->getAccountType()) << "\n" + << "Username: " << this->username << "\n" + << "Password: " << this->password << "\n" + << "IBAN: " << this->IBAN << "\n" + << "Bank: " << this->bank; +} diff --git a/src/Accounts/BankAccount.h b/src/Accounts/BankAccount.h new file mode 100644 index 0000000..166efb1 --- /dev/null +++ b/src/Accounts/BankAccount.h @@ -0,0 +1,27 @@ +#ifndef BANKACCOUNT_H +#define BANKACCOUNT_H +#include +#include + +class BankAccount final : public Account { + std::string IBAN; + std::string bank; + + [[nodiscard]] AccountType getAccountType() const override; + + void showAccountDetails() const override; + +public: + BankAccount(std::string, std::string, std::string, std::string); + + void addAccount() override; + + [[nodiscard]] std::string getIBAN() const; + + [[nodiscard]] std::string getBank() const; + + bool operator==(const BankAccount &) const; +}; + + +#endif //BANKACCOUNT_H diff --git a/src/Accounts/EmailAccount.cpp b/src/Accounts/EmailAccount.cpp new file mode 100644 index 0000000..1716f0b --- /dev/null +++ b/src/Accounts/EmailAccount.cpp @@ -0,0 +1,42 @@ +#include +#include + +#include +#include + +AccountType EmailAccount::getAccountType() const { + return AccountType::EmailAccountType; +} + +EmailAccount::EmailAccount(std::string username, std::string password, + std::string emailAddress, std::string mailProvider) + : Account(std::move(username), std::move(password)), + emailAddress(std::move(emailAddress)), mailProvider(std::move(mailProvider)) { +} + +void EmailAccount::addAccount() { + const Database &database = Database::getDatabaseInstance(); + const auto emailAccount = std::make_shared(*this); + database.addUserDefinedAccount(emailAccount, this->getAccountType()); +} + +std::string EmailAccount::getEmailAddress() const { + return this->emailAddress; +} + +std::string EmailAccount::getMailProvider() const { + return this->mailProvider; +} + +bool EmailAccount::operator==(const EmailAccount &rhs) const { + return this->username == rhs.username && this->password == rhs.password && + this->emailAddress == rhs.emailAddress && this->mailProvider == rhs.mailProvider; +} + +void EmailAccount::showAccountDetails() const { + std::cout << "Account Type: " << getAccountTypeString(this->getAccountType()) << "\n" + << "Username: " << this->username << "\n" + << "Password: " << this->password << "\n" + << "Email Address: " << this->emailAddress << "\n" + << "Mail Provider: " << this->mailProvider; +} diff --git a/src/Accounts/EmailAccount.h b/src/Accounts/EmailAccount.h new file mode 100644 index 0000000..76af7d8 --- /dev/null +++ b/src/Accounts/EmailAccount.h @@ -0,0 +1,27 @@ +#ifndef EMAILACCOUNT_H +#define EMAILACCOUNT_H +#include +#include + +class EmailAccount final : public Account { + std::string emailAddress; + std::string mailProvider; + + [[nodiscard]] AccountType getAccountType() const override; + + void showAccountDetails() const override; + +public: + explicit EmailAccount(std::string, std::string, std::string, std::string); + + void addAccount() override; + + [[nodiscard]] std::string getEmailAddress() const; + + [[nodiscard]] std::string getMailProvider() const; + + bool operator==(const EmailAccount &) const; +}; + + +#endif //EMAILACCOUNT_H diff --git a/src/Accounts/SocialMediaAccount.cpp b/src/Accounts/SocialMediaAccount.cpp new file mode 100644 index 0000000..441b1db --- /dev/null +++ b/src/Accounts/SocialMediaAccount.cpp @@ -0,0 +1,42 @@ +#include +#include + +#include +#include + +AccountType SocialMediaAccount::getAccountType() const { + return AccountType::SocialMediaAccountType; +} + +SocialMediaAccount::SocialMediaAccount(std::string username, std::string password, + std::string platform, std::string profileUrl) + : Account(std::move(username), std::move(password)), + platform(std::move(platform)), profileUrl(std::move(profileUrl)) { +} + +void SocialMediaAccount::addAccount() { + const Database &database = Database::getDatabaseInstance(); + const auto emailAccount = std::make_shared(*this); + database.addUserDefinedAccount(emailAccount, this->getAccountType()); +} + +std::string SocialMediaAccount::getPlatform() const { + return this->platform; +} + +std::string SocialMediaAccount::getProfileUrl() const { + return this->profileUrl; +} + +bool SocialMediaAccount::operator==(const SocialMediaAccount &rhs) const { + return this->username == rhs.username && this->password == rhs.password && + this->platform == rhs.platform && this->profileUrl == rhs.profileUrl; +} + +void SocialMediaAccount::showAccountDetails() const { + std::cout << "Account Type: " << getAccountTypeString(this->getAccountType()) << "\n" + << "Username: " << this->username << "\n" + << "Password: " << this->password << "\n" + << "Platform: " << this->platform << "\n" + << "Profile URL: " << this->profileUrl; +} diff --git a/src/Accounts/SocialMediaAccount.h b/src/Accounts/SocialMediaAccount.h new file mode 100644 index 0000000..502260c --- /dev/null +++ b/src/Accounts/SocialMediaAccount.h @@ -0,0 +1,26 @@ +#ifndef SOCIALMEDIAACCOUNT_H +#define SOCIALMEDIAACCOUNT_H +#include +#include +class SocialMediaAccount final : public Account { + std::string platform; + std::string profileUrl; + + [[nodiscard]] AccountType getAccountType() const override; + + void showAccountDetails() const override; + +public: + SocialMediaAccount(std::string, std::string, std::string, std::string); + + void addAccount() override; + + [[nodiscard]] std::string getPlatform() const; + + [[nodiscard]] std::string getProfileUrl() const; + + bool operator==(const SocialMediaAccount&) const; +}; + + +#endif //SOCIALMEDIAACCOUNT_H diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index f7eb4ed..d08e8c0 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -11,6 +11,15 @@ add_library(${PROJECT_NAME}_lib Database/Auth.cpp Utils/EnvVarManager.cpp Logger/Logger.cpp + Exceptions/DatabaseExceptions/DatabaseExceptions.cpp + Exceptions/AccountExceptions/AccountExceptions.cpp + Accounts/Account.cpp + Accounts/BankAccount.cpp + Accounts/EmailAccount.cpp + Accounts/SocialMediaAccount.cpp + Accounts/AccountFactory.cpp + Accounts/AccountFactory.h + Utils/AddAccountCommandTemplate.cpp ) target_include_directories(${PROJECT_NAME}_lib SYSTEM PRIVATE @@ -27,6 +36,10 @@ target_include_directories(${PROJECT_NAME}_lib PUBLIC User Utils Logger + Exceptions + Exceptions/DatabaseExceptions + Exceptions/AccountExceptions + Accounts ) target_link_directories(${PROJECT_NAME}_lib PUBLIC ${pqxx_SOURCE_DIR}/lib) diff --git a/src/Database/Auth.cpp b/src/Database/Auth.cpp index 85fca37..585b4dd 100644 --- a/src/Database/Auth.cpp +++ b/src/Database/Auth.cpp @@ -12,6 +12,7 @@ User Auth::createAccount() const { auto currentUser = User(username, password); database.createAccount(currentUser); currentUser.setUserId(database.getCurrentUserId(currentUser.getUsername())); + User::setCurrentUserId(currentUser.getUserId()); return currentUser; } catch (pqxx::unique_violation&) { @@ -29,7 +30,7 @@ User Auth::login() const { hashedInputPassword.getPasswordHash() == storedPasswordHash) { auto currentUser = User(username, hashedInputPassword); currentUser.setUserId(database.getCurrentUserId(currentUser.getUsername())); - + User::setCurrentUserId(currentUser.getUserId()); return currentUser; } throw std::invalid_argument("Wrong password"); diff --git a/src/Database/Database.cpp b/src/Database/Database.cpp index 290af99..0ecd43b 100644 --- a/src/Database/Database.cpp +++ b/src/Database/Database.cpp @@ -1,18 +1,26 @@ -#include "Database.h" - +#include #include +#include +#include +#include +#include +#include +#include +#include +#include + std::string Database::connString; Database::Database() { try { connection = std::make_unique(connString); if (!connection->is_open()) { - throw std::runtime_error("Database exists but a connection couldn't be established"); + throw FailedToOpen("Database exists but a connection couldn't be established"); } - } catch (const std::exception &) { + } catch (const FailedToOpen& ) { connection = nullptr; - throw ; + throw; } } @@ -42,8 +50,7 @@ void Database::createAccount(const User ¤tUser) const { currentUser.getPasswordHash() ); work.commit(); - } - catch (const pqxx::unique_violation& e) { + } catch (const pqxx::unique_violation &e) { std::cerr << e.what() << std::endl; throw; } @@ -89,8 +96,7 @@ int Database::getNumberOfUsers() const { try { const auto queryResult = work.exec_params("SELECT COUNT(*) FROM users"); return queryResult[0][0].as(); - } - catch (const std::exception &e) { + } catch (const std::exception &e) { std::cerr << "Exception: " << e.what() << std::endl; throw; } @@ -105,11 +111,117 @@ User Database::getUserByUsername(std::string &username) const { const auto _username = queryResult[0][1].as(); const auto _passwordSalt = queryResult[0][2].as(); const auto _passwordHash = queryResult[0][3].as(); - return {_userId, _username, _passwordHash, - _passwordSalt}; - } - catch (const std::exception &e) { + return { + _userId, _username, _passwordHash, + _passwordSalt + }; + } catch (const std::exception &e) { std::cerr << "Exception: " << e.what() << std::endl; throw; } } + +void Database::addUserDefinedAccount(const std::shared_ptr &account, const AccountType &accountType) const { + pqxx::work work(*connection); + std::string query; + switch (accountType) { + case AccountType::BankAccountType: { + const auto bankAccount = dynamic_pointer_cast(account); + query = "INSERT INTO bankaccounts VALUES (" + + work.quote(User::getCurrentUserId()) + ", " + + work.quote(bankAccount->getUsername()) + ", " + + work.quote(bankAccount->getPassword()) + ", " + + work.quote(bankAccount->getIBAN()) + ", " + + work.quote(bankAccount->getBank()) + ");"; + break; + } + case AccountType::EmailAccountType: { + const auto emailAccount = dynamic_pointer_cast(account); + query = "INSERT INTO emailaccounts VALUES (" + + work.quote(User::getCurrentUserId()) + ", " + + work.quote(emailAccount->getUsername()) + ", " + + work.quote(emailAccount->getPassword()) + ", " + + work.quote(emailAccount->getEmailAddress()) + ", " + + work.quote(emailAccount->getMailProvider()) + ");"; + break; + } + case AccountType::SocialMediaAccountType: { + const auto socialMediaAccount = dynamic_pointer_cast(account); + query = "INSERT INTO socialmediaaccounts VALUES (" + + work.quote(User::getCurrentUserId()) + ", " + + work.quote(socialMediaAccount->getUsername()) + ", " + + work.quote(socialMediaAccount->getPassword()) + ", " + + work.quote(socialMediaAccount->getPlatform()) + ", " + + work.quote(socialMediaAccount->getProfileUrl()) + ");"; + break; + } + default: { + work.abort(); + throw AccountTypeExceptions("The addUserDefinedAccount method failed!"); + } + } + try { + work.exec_params(query); + work.commit(); + } catch ([[maybe_unused]] const pqxx::data_exception &e) { + throw FailedToCommit("The addUserDefinedAccount method failed!"); + } +} + +/// TODO Maybe lambda functions? +std::vector > Database::getAccountsByType(const AccountType &accountType) const { + pqxx::work work(*connection); + std::vector > accounts; + switch (accountType) { + case AccountType::BankAccountType: { + const auto queryResult = work.exec_params("Select * from bankaccounts where id=$1", + User::getCurrentUserId()); + for (const auto &row: queryResult) { + accounts.push_back(AccountFactory::accountFactory(AccountType::BankAccountType, { + {"username", row[1].as()}, + {"password", row[2].as()}, + {"IBAN", row[3].as()}, + {"bank", row[4].as()} + })); + } + break; + } + case AccountType::EmailAccountType: { + const auto queryResult = work.exec_params("Select * from emailaccounts where id=$1", + User::getCurrentUserId()); + for (const auto &row: queryResult) { + accounts.push_back(AccountFactory::accountFactory(AccountType::EmailAccountType, { + {"username", row[1].as()}, + {"password", row[2].as()}, + {"emailAddress", row[3].as()}, + {"mailProvider", row[4].as()} + })); + } + break; + } + case AccountType::SocialMediaAccountType: { + const auto queryResult = work.exec_params("Select * from socialmediaaccounts where id=$1", + User::getCurrentUserId()); + for (const auto &row: queryResult) { + accounts.push_back(AccountFactory::accountFactory(AccountType::SocialMediaAccountType, { + {"username", row[1].as()}, + {"password", row[2].as()}, + {"platform", row[3].as()}, + {"profileUrl", row[4].as()} + })); + } + break; + } + default: { + work.abort(); + throw AccountTypeExceptions("The getAccountsByType method failed!"); + } + } + return accounts; +} + +std::vector > Database::getAllAccounts() const { + return getAccountsByType(AccountType::BankAccountType) + + getAccountsByType(AccountType::EmailAccountType) + + getAccountsByType(AccountType::SocialMediaAccountType); +} diff --git a/src/Database/Database.h b/src/Database/Database.h index 17f648e..6a8fcc4 100644 --- a/src/Database/Database.h +++ b/src/Database/Database.h @@ -2,7 +2,9 @@ #define DATABASE_H #include -#include "../User/User.h" + +#include +#include class Database { std::unique_ptr connection; @@ -34,6 +36,12 @@ class Database { [[nodiscard]] int getNumberOfUsers() const; User getUserByUsername(std::string &username) const; + + void addUserDefinedAccount(const std::shared_ptr &account, const AccountType &) const; + + std::vector> getAccountsByType(const AccountType &accountType) const; + + std::vector> getAllAccounts() const; }; diff --git a/src/EnvironmentReader/EnvironmentReader.cpp b/src/EnvironmentReader/EnvironmentReader.cpp index 1ad02a6..f37f34e 100644 --- a/src/EnvironmentReader/EnvironmentReader.cpp +++ b/src/EnvironmentReader/EnvironmentReader.cpp @@ -1,11 +1,11 @@ -#include "EnvironmentReader.h" +#include + +#include #include -#include "../Database/Database.h" -#include "../Utils/EnvVarManager.h" -#include +#include #include -#include "../Logger/Logger.h" +#include EnvironmentReader::EnvironmentReader() { std::ifstream file(filePath); @@ -17,12 +17,12 @@ EnvironmentReader::EnvironmentReader() { if (delimiterPos != std::string::npos) { std::string key = line.substr(0, delimiterPos); std::string value = line.substr(delimiterPos + 1); - EnvVarManager::set(key,value); + EnvVarManager::set(key, value); } } } -EnvironmentReader& EnvironmentReader::getEnvReader() { +EnvironmentReader &EnvironmentReader::getEnvReader() { static EnvironmentReader envReader; return envReader; } @@ -31,29 +31,29 @@ std::string EnvironmentReader::getConnString() { auto &logger = Logger::getInstance(); if (EnvVarManager::get("DB_HOST").empty()) { logger.log(LogLevel::LOG_ERROR, "DB_HOST not set"); - throw std::invalid_argument("Environment variable DB_HOST is not set"); + throw EnvironmentVariableNotFound("Environment variable DB_HOST is not set"); } if (EnvVarManager::get("DB_NAME").empty()) { logger.log(LogLevel::LOG_ERROR, "DB_NAME not set"); - throw std::invalid_argument("Environment variable DB_NAME is not set"); + throw EnvironmentVariableNotFound("Environment variable DB_NAME is not set"); } if (EnvVarManager::get("DB_USER").empty()) { logger.log(LogLevel::LOG_ERROR, "DB_USER not set"); - throw std::invalid_argument("Environment variable DB_USER is not set"); + throw EnvironmentVariableNotFound("Environment variable DB_USER is not set"); } if (EnvVarManager::get("DB_PASSWORD").empty()) { logger.log(LogLevel::LOG_ERROR, "DB_PASSWORD not set"); - throw std::invalid_argument( - "Environment variable DB_PASSWORD is not set"); + throw EnvironmentVariableNotFound( + "Environment variable DB_PASSWORD is not set"); } if (EnvVarManager::get("DB_PORT").empty()) { logger.log(LogLevel::LOG_ERROR, "DB_PORT not set"); - throw std::invalid_argument("Environment variable DB_PORT is not set"); + throw EnvironmentVariableNotFound("Environment variable DB_PORT is not set"); } std::string connString = "dbname=" + EnvVarManager::get("DB_NAME") + " user=" + - EnvVarManager::get("DB_USER") + EnvVarManager::get("DB_USER") + " password=" + EnvVarManager::get("DB_PASSWORD") + " host=" + EnvVarManager::get("DB_HOST") + " port=" + EnvVarManager::get("DB_PORT"); logger.log(LogLevel::INFO, "Connection string set"); diff --git a/src/Exceptions/AccountExceptions/AccountExceptions.cpp b/src/Exceptions/AccountExceptions/AccountExceptions.cpp new file mode 100644 index 0000000..606cc03 --- /dev/null +++ b/src/Exceptions/AccountExceptions/AccountExceptions.cpp @@ -0,0 +1,12 @@ +#include + +AccountExceptions::AccountExceptions(std::string message) : message(std::move(message)) {} + +const char * AccountExceptions::what() const noexcept { + return message.c_str(); +} + +AccountTypeExceptions::AccountTypeExceptions(const std::string &message) + : AccountExceptions("The account type is incorrect!\n" + message) {} + + diff --git a/src/Exceptions/AccountExceptions/AccountExceptions.h b/src/Exceptions/AccountExceptions/AccountExceptions.h new file mode 100644 index 0000000..89665ad --- /dev/null +++ b/src/Exceptions/AccountExceptions/AccountExceptions.h @@ -0,0 +1,22 @@ +#ifndef ACCOUNTEXCEPTIONS_H +#define ACCOUNTEXCEPTIONS_H +#include + + +class AccountExceptions : public std::exception { +protected: + std::string message; + +public: + explicit AccountExceptions(std::string); + + const char *what() const noexcept override; +}; + +class AccountTypeExceptions final : public AccountExceptions { +public: + explicit AccountTypeExceptions(const std::string &); +}; + + +#endif //ACCOUNTEXCEPTIONS_H diff --git a/src/Exceptions/DatabaseExceptions/DatabaseExceptions.cpp b/src/Exceptions/DatabaseExceptions/DatabaseExceptions.cpp new file mode 100644 index 0000000..7cdac81 --- /dev/null +++ b/src/Exceptions/DatabaseExceptions/DatabaseExceptions.cpp @@ -0,0 +1,20 @@ +#include + +DatabaseExceptions::DatabaseExceptions(std::string message) : message(std::move(message)) { +} + +const char *DatabaseExceptions::what() const noexcept { + return message.c_str(); +} + +FailedToCommit::FailedToCommit(const std::string &message) : DatabaseExceptions( + std::move("Failure to commit work to database!\n" + message)) { +} + +FailedToOpen::FailedToOpen(const std::string &) + : DatabaseExceptions(std::move("Failed to open database!\n" + message)) { +} + +EnvironmentVariableNotFound::EnvironmentVariableNotFound(const std::string &) + : DatabaseExceptions(std::move("Environment variable not found!\n" + message)) { +} diff --git a/src/Exceptions/DatabaseExceptions/DatabaseExceptions.h b/src/Exceptions/DatabaseExceptions/DatabaseExceptions.h new file mode 100644 index 0000000..0df1190 --- /dev/null +++ b/src/Exceptions/DatabaseExceptions/DatabaseExceptions.h @@ -0,0 +1,33 @@ +#ifndef DATABASEEXCEPTIONS_H +#define DATABASEEXCEPTIONS_H + +#include +#include + + +class DatabaseExceptions : public std::exception { +protected: + std::string message; + +public: + explicit DatabaseExceptions(std::string); + + const char *what() const noexcept override; +}; + +class FailedToCommit final : public DatabaseExceptions { +public: + explicit FailedToCommit(const std::string &); +}; + +class FailedToOpen final : public DatabaseExceptions { +public: + explicit FailedToOpen(const std::string &); +}; + +class EnvironmentVariableNotFound final : public DatabaseExceptions { +public: + explicit EnvironmentVariableNotFound(const std::string &); +}; + +#endif //DATABASEEXCEPTIONS_H diff --git a/src/Logger/Logger.cpp b/src/Logger/Logger.cpp index 332e80a..8400698 100644 --- a/src/Logger/Logger.cpp +++ b/src/Logger/Logger.cpp @@ -1,4 +1,4 @@ -#include "Logger.h" +#include #include #include diff --git a/src/User/User.cpp b/src/User/User.cpp index 21e6f67..76f275c 100644 --- a/src/User/User.cpp +++ b/src/User/User.cpp @@ -1,9 +1,10 @@ #include "User.h" -#include #include -#include "../Logger/Logger.h" +#include + +int User::currentUserId = -1; std::ostream &operator<<(std::ostream &os, const User &user) { os << "UserId:" << user.userId << '\n' << @@ -47,3 +48,11 @@ void User::setUserId(const int id) { this->userId = id; } +void User::setCurrentUserId(const int currentUserId) { + User::currentUserId = currentUserId; +} + +int User::getCurrentUserId() { + return currentUserId; +} + diff --git a/src/User/User.h b/src/User/User.h index c489132..55acce8 100644 --- a/src/User/User.h +++ b/src/User/User.h @@ -2,13 +2,14 @@ #define USER_H #include -#include "../Utils/PasswordHash.h" +#include class User { std::string username; PasswordHash passwordHash; int userId; + static int currentUserId; public: User(std::string, const std::string&); User(std::string, PasswordHash); @@ -20,6 +21,9 @@ class User { [[nodiscard]] std::string getPasswordSalt() const; [[nodiscard]] int getUserId() const; void setUserId(int); + + static void setCurrentUserId(int); + static int getCurrentUserId(); }; diff --git a/src/Utils/AccountType.h b/src/Utils/AccountType.h new file mode 100644 index 0000000..bd70020 --- /dev/null +++ b/src/Utils/AccountType.h @@ -0,0 +1,25 @@ +#ifndef ACCOUNTTYPE_H +#define ACCOUNTTYPE_H + +#include + +enum class AccountType { + BankAccountType, + EmailAccountType, + SocialMediaAccountType +}; + +constexpr std::string_view getAccountTypeString(const AccountType& accountType) { + switch (accountType) { + case AccountType::BankAccountType: + return "Bank Account"; + case AccountType::EmailAccountType: + return "Email Account"; + case AccountType::SocialMediaAccountType: + return "Social Media Account"; + default: + return "Unknown Account Type"; + } +} + +#endif //ACCOUNTTYPE_H diff --git a/src/Utils/AddAccountCommandTemplate.cpp b/src/Utils/AddAccountCommandTemplate.cpp new file mode 100644 index 0000000..945c353 --- /dev/null +++ b/src/Utils/AddAccountCommandTemplate.cpp @@ -0,0 +1,71 @@ +#include + +#include + +void AddAccountCommandTemplate::addAccountCommand() { + std::cout << "Please enter the account details!\n"; + std::string username, password, IBAN, bank; + std::cout << "Username: "; + std::cin >> username; + std::cout << "Password: "; + std::cin >> password; + std::cout << "IBAN: "; + std::cin >> IBAN; + std::cout << "Bank: "; + std::cin >> bank; + const auto account = AccountFactory::accountFactory(AccountType::BankAccountType, + { + {"username", username}, + {"password", password}, + {"IBAN", IBAN}, + {"bank", bank} + }); + account->addAccount(); + std::cout << "Account added successfully!\n"; +} + +void AddAccountCommandTemplate::addAccountCommand() { + std::cout << "Please enter the account details!\n"; + std::string username, password, emailAddress, mailProvider; + std::cout << "Username: "; + std::cin >> username; + std::cout << "Password: "; + std::cin >> password; + std::cout << "Email Address: "; + std::cin >> emailAddress; + std::cout << "Mail Provider: "; + std::cin >> mailProvider; + const auto account = AccountFactory::accountFactory(AccountType::EmailAccountType, + { + {"username", username}, + {"password", password}, + {"emailAddress", emailAddress}, + {"mailProvider", mailProvider} + }); + account->addAccount(); + std::cout << "Account added successfully!\n"; +} + +void AddAccountCommandTemplate::addAccountCommand() { + std::cout << "Please enter the account details!\n"; + std::string username, password, platform, profileUrl; + std::cout << "Username: "; + std::cin >> username; + std::cout << "Password: "; + std::cin >> password; + std::cout << "Platform: "; + std::cin >> platform; + std::cout << "Profile URL: "; + std::cin >> profileUrl; + const auto account = AccountFactory::accountFactory(AccountType::SocialMediaAccountType, + { + {"username", username}, + {"password", password}, + {"platform", platform}, + {"profileUrl", profileUrl} + }); + account->addAccount(); + std::cout << "Account added successfully!\n"; +} + + diff --git a/src/Utils/AddAccountCommandTemplate.h b/src/Utils/AddAccountCommandTemplate.h new file mode 100644 index 0000000..28b8bb1 --- /dev/null +++ b/src/Utils/AddAccountCommandTemplate.h @@ -0,0 +1,32 @@ +#ifndef ADDACCOUNTCOMMANDTEMPLATE_H +#define ADDACCOUNTCOMMANDTEMPLATE_H + +#include + +template +class AddAccountCommandTemplate { +public: + virtual void addAccountCommand() = 0; + + virtual ~AddAccountCommandTemplate() = default; +}; + +template<> +class AddAccountCommandTemplate { +public: + static void addAccountCommand(); +}; + +template<> +class AddAccountCommandTemplate { +public: + static void addAccountCommand(); +}; + +template<> +class AddAccountCommandTemplate { +public: + static void addAccountCommand(); +}; + +#endif //ADDACCOUNTCOMMANDTEMPLATE_H diff --git a/src/Utils/EnvVarManager.cpp b/src/Utils/EnvVarManager.cpp index e89f054..b5dc189 100644 --- a/src/Utils/EnvVarManager.cpp +++ b/src/Utils/EnvVarManager.cpp @@ -1,4 +1,4 @@ -#include "EnvVarManager.h" +#include #include std::string EnvVarManager::get(const std::string & varKey) { return getEnv(varKey); @@ -32,8 +32,3 @@ int EnvVarManager::unsetEnv(const std::string & varKey) { return unsetenv(varKey.c_str()) == 0; #endif } - -std::ostream & operator<<(std::ostream & os, const EnvVarManager& envVarManager) { - os<<"EnvVarManager\n"< #include #include diff --git a/src/Utils/ShowAccountsCommand.h b/src/Utils/ShowAccountsCommand.h new file mode 100644 index 0000000..e395124 --- /dev/null +++ b/src/Utils/ShowAccountsCommand.h @@ -0,0 +1,24 @@ +#ifndef SHOWACCOUNTSCOMMAND_H +#define SHOWACCOUNTSCOMMAND_H + +#include +#include +#include +namespace ShowAccountsCommands { + template + void showAccountsCommand() { + for (const auto &database = Database::getDatabaseInstance(); + const auto &account: database.getAccountsByType(T)) { + account->show(); + } + std::cout << "---------------------------------\n"; + } ; + + inline void showAllAccounts() { + showAccountsCommand(); + showAccountsCommand(); + showAccountsCommand(); + } +} + +#endif //SHOWACCOUNTSCOMMAND_H diff --git a/src/Utils/VectorUtils.h b/src/Utils/VectorUtils.h new file mode 100644 index 0000000..0f72342 --- /dev/null +++ b/src/Utils/VectorUtils.h @@ -0,0 +1,15 @@ +#ifndef VECTORUTILS_H +#define VECTORUTILS_H + +#include + +template +std::vector operator+(const std::vector &lhs, const std::vector &rhs){ + std::vector result; + result.reserve(lhs.size() + rhs.size()); + result.insert(result.end(), lhs.begin(), lhs.end()); + result.insert(result.end(), rhs.begin(), rhs.end()); + return result; +} + +#endif //VECTORUTILS_H diff --git a/src/main.cpp b/src/main.cpp index 06a9835..4843965 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -1,10 +1,14 @@ +#include #include -#include "Database/Database.h" -#include "Database/Auth.h" -#include "EnvironmentReader/EnvironmentReader.h" -#include "Logger/Logger.h" -#include "Utils/EnvVarManager.h" +#include +#include +#include +#include + +#include +#include +#include void initializeDatabase() { auto &logger = Logger::getInstance(); @@ -14,9 +18,13 @@ void initializeDatabase() { Database::setConnString(connString); Database::getDatabaseInstance(); logger.log(LogLevel::INFO, "Connected to database"); - } catch (std::exception &e) { + } catch (EnvironmentVariableNotFound &e) { + logger.log(LogLevel::LOG_ERROR, "Error initializing database connection: " + std::string(e.what())); + throw; + } + catch (FailedToOpen &e) { logger.log(LogLevel::LOG_ERROR, "Error initializing database connection: " + std::string(e.what()) + '\n'); - throw std::runtime_error("Error initializing database connection" + std::string(e.what()) + '\n'); + throw; } } @@ -35,7 +43,7 @@ std::string promptCommand() { return command; } -std::tuple promptUserDetails() { +std::tuple promptUserInformation() { std::string username, password; std::cout << "Enter username: "; std::cin >> username; @@ -44,23 +52,172 @@ std::tuple promptUserDetails() { return {username, password}; } +User handleUserAuth(const std::string &command, const std::tuple &userInformation) { + const Auth auth = std::apply([](auto &&... args) { return Auth(args...); }, userInformation); + std::optional currentUser; + try { + if (command == "Login") currentUser = auth.login(); + else currentUser = auth.createAccount(); + } catch (std::exception &e) { + std::cout << e.what() << '\n'; + std::cout << "Invalid username or password.\n"; + throw; + } + return *currentUser; +} + +void addAccountCommand() { + auto &logger = Logger::getInstance(); + while (true) { + std::cout << "What type of account do you want to add? Choose the corresponding index!\n" + << "1. Bank Account\n" << "2. Email Account\n" << "3. Social Media Account\n" << "4. Exit\n"; + std::flush(std::cout); + std::string command; + std::cin >> command; + int commandIndex; + try { + commandIndex = std::stoi(command); + } catch ([[maybe_unused]] std::invalid_argument &e) { + std::cout << "Invalid command!\n"; + continue; + } + try { + switch (commandIndex) { + case 1: { + AddAccountCommandTemplate::addAccountCommand(); + break; + } + case 2: { + AddAccountCommandTemplate::addAccountCommand(); + break; + } + case 3: { + AddAccountCommandTemplate::addAccountCommand(); + break; + } + case 4: { + return; + } + default: + std::cout << "Invalid command!\n"; + } + logger.log(LogLevel::INFO, "Successfully added account!"); + } catch (FailedToCommit &e) { + logger.log(LogLevel::LOG_ERROR, e.what()); + std::cout << e.what() << '\n'; + } + catch (AccountTypeExceptions &e) { + logger.log(LogLevel::LOG_ERROR, e.what()); + std::cout << e.what() << '\n'; + } + catch (...) { + logger.log(LogLevel::LOG_ERROR, "Unknown error encountered!"); + std::cout << "Unknown error encountered!\n"; + } + } +} + +void showAccountsCommand() { + auto &logger = Logger::getInstance(); + while (true) { + std::cout << "What type of accounts do you want to show? Choose the corresponding index!\n" + << "1. Bank Account\n" << "2. Email Account\n" + << "3. Social Media Account\n" << "4. All accounts\n" << "5. Exit\n"; + std::flush(std::cout); + std::string command; + std::cin >> command; + int commandIndex; + try { + commandIndex = std::stoi(command); + } catch ([[maybe_unused]] std::invalid_argument &e) { + std::cout << "Invalid command!\n"; + continue; + } + try { + switch (commandIndex) { + case 1: { + ShowAccountsCommands::showAccountsCommand(); + break; + } + case 2: { + ShowAccountsCommands::showAccountsCommand(); + break; + } + case 3: { + ShowAccountsCommands::showAccountsCommand(); + break; + } + case 4: { + ShowAccountsCommands::showAllAccounts(); + break; + } + case 5: { + return; + } + default: + std::cout << "Invalid command!\n"; + } + logger.log(LogLevel::INFO, "Accounts showed successfully!"); + } catch (AccountTypeExceptions &e) { + logger.log(LogLevel::LOG_ERROR, e.what()); + std::cout << e.what() << '\n'; + } + catch (...) { + logger.log(LogLevel::LOG_ERROR, "Unknown error encountered!"); + std::cout << "Unknown error encountered!\n"; + } + } +} + +void userInteraction() { + while (true) { + std::cout << "Choose an option by inputting its index!\n" << "1. Add a new account\n" + << "2. Show accounts\n" << "3. Exit\n"; + std::string command = {}; + std::cin >> command; + int commandIndex; + try { + commandIndex = std::stoi(command); + } catch ([[maybe_unused]] std::invalid_argument &e) { + std::cout << "Invalid command!\n"; + continue; + } + switch (commandIndex) { + case 1: { + addAccountCommand(); + break; + } + case 2: { + showAccountsCommand(); + break; + } + case 3: { + return; + } + default: + std::cout << "Invalid command!\n"; + } + } +} + int main() { Logger::create("Logs"); auto &logger = Logger::getInstance(); logger.log(LogLevel::INFO, "Application started"); try { initializeDatabase(); - } - catch (...) { + } catch (...) { logger.log(LogLevel::LOG_ERROR, "Critical error encountered, application exiting"); return 1; } const std::string command = promptCommand(); - auto [username, password] = promptUserDetails(); - const Auth auth(username, password); - std::optional currentUser; - if(command == "Login") currentUser = auth.login(); - else currentUser = auth.createAccount(); - std::cout<<*currentUser; + while (true) { + try { + auto currentUser = handleUserAuth(command, promptUserInformation()); + break; + } catch (...) { + } + } + userInteraction(); return 0; } diff --git a/tastatura.txt b/tastatura.txt index 82c1663..04e39b6 100644 --- a/tastatura.txt +++ b/tastatura.txt @@ -1,3 +1,14 @@ Create account test -test \ No newline at end of file +test +1 +1 +test +test +test +test +4 +2 +1 +5 +3 \ No newline at end of file diff --git a/tests/AccountFactoryTest.cpp b/tests/AccountFactoryTest.cpp new file mode 100644 index 0000000..1884d17 --- /dev/null +++ b/tests/AccountFactoryTest.cpp @@ -0,0 +1,33 @@ +#include +#include +#include +#include +#include + +TEST(AccountFactoryTest, BankAccountCreationTest) { + const auto bankAccountFactory = + AccountFactory::accountFactory(AccountType::BankAccountType, + { + {"username", "sebi1"}, {"password", "1234"}, + {"IBAN", "123412412"}, {"bank", "bt"} + }); + const auto bankAccountManual = BankAccount("sebi1", "1234", "123412412", "bt"); + ASSERT_EQ(bankAccountManual, *std::dynamic_pointer_cast(bankAccountFactory)); +} + +TEST(AccountFactoryTest, EmailAccountCreationTest) { + const auto emailAccountFactory = AccountFactory::accountFactory(AccountType::EmailAccountType, + {{"username", "sebi1"},{"password","1234"}, + {"emailAddress", "123412412"}, {"mailProvider", "bt"}}); + const auto emailAccountManual = EmailAccount("sebi1", "1234", "123412412", "bt"); + ASSERT_EQ(emailAccountManual, *std::dynamic_pointer_cast(emailAccountFactory)); +} + +TEST(AccountFactoryTest, SocialMediaAccountCreationTest) { + const auto socialMediaAccountFactory = + AccountFactory::accountFactory(AccountType::SocialMediaAccountType, + {{"username", "sebi1"},{"password","1234"}, + {"platform", "123412412"}, {"profileUrl", "bt"}}); + const auto socialMediaAccountManual = SocialMediaAccount("sebi1", "1234", "123412412", "bt"); + ASSERT_EQ(socialMediaAccountManual, *std::dynamic_pointer_cast(socialMediaAccountFactory)); +} diff --git a/tests/AccountTestInsertShowLoggedIn.cpp b/tests/AccountTestInsertShowLoggedIn.cpp new file mode 100644 index 0000000..1af274d --- /dev/null +++ b/tests/AccountTestInsertShowLoggedIn.cpp @@ -0,0 +1,150 @@ +#include + +#include +#include +#include +#include +#include + +class AccountTestInsertShowLoggedIn : public ::testing::Test { +protected: + static void SetUpTestSuite() { + Logger::create("Logs"); + EnvironmentReader::getEnvReader(); + const std::string connString = EnvironmentReader::getConnString(); + Database::setConnString(connString); + const auto auth = Auth("test","test"); + const auto currentUser = auth.createAccount(); + } +}; + +TEST_F(AccountTestInsertShowLoggedIn, bankAccountInsert) { + const auto bankAccountFactory = + AccountFactory::accountFactory(AccountType::BankAccountType, + { + {"username", "sebi11"}, {"password", "1234"}, + {"IBAN", "123412412"}, {"bank", "bt"} + }); + ASSERT_NO_THROW(bankAccountFactory->addAccount()); +} + +TEST_F(AccountTestInsertShowLoggedIn, emailAccountInsert) { + const auto emailAccountFactory = AccountFactory::accountFactory(AccountType::EmailAccountType, + {{"username", "sebi1"},{"password","1234"}, + {"emailAddress", "123412412"}, {"mailProvider", "bt"}}); + ASSERT_NO_THROW(emailAccountFactory->addAccount()); +} + +TEST_F(AccountTestInsertShowLoggedIn, socialMediaAccountInsert) { + const auto socialMediaAccountFactory = + AccountFactory::accountFactory(AccountType::SocialMediaAccountType, + {{"username", "sebi1"},{"password","1234"}, + {"platform", "123412412"}, {"profileUrl", "bt"}}); + ASSERT_NO_THROW(socialMediaAccountFactory->addAccount()); +} + +TEST_F(AccountTestInsertShowLoggedIn, bankAccountShow) { + const auto &database = Database::getDatabaseInstance(); + const auto accounts = database.getAccountsByType(AccountType::BankAccountType); + std::streambuf* originalCoutBuffer = std::cout.rdbuf(); + const std::ostringstream capturedOutput; + std::cout.rdbuf(capturedOutput.rdbuf()); + for (const auto &account : accounts) { + account->show(); + } + std::cout.rdbuf(originalCoutBuffer); + const std::string captured = capturedOutput.str(); + const std::string expectedResult = +R"(--------------------------------- +Account details: +Account Type: Bank Account +Username: sebi11 +Password: 1234 +IBAN: 123412412 +Bank: bt +)"; + ASSERT_EQ(captured, expectedResult); +} + +TEST_F(AccountTestInsertShowLoggedIn, emailAccountShow) { + const auto &database = Database::getDatabaseInstance(); + const auto accounts = database.getAccountsByType(AccountType::EmailAccountType); + std::streambuf* originalCoutBuffer = std::cout.rdbuf(); + const std::ostringstream capturedOutput; + std::cout.rdbuf(capturedOutput.rdbuf()); + for (const auto &account : accounts) { + account->show(); + } + std::cout.rdbuf(originalCoutBuffer); + const std::string captured = capturedOutput.str(); + const std::string expectedResult = +R"(--------------------------------- +Account details: +Account Type: Email Account +Username: sebi1 +Password: 1234 +Email Address: 123412412 +Mail Provider: bt +)"; + ASSERT_EQ(captured, expectedResult); +} + +TEST_F(AccountTestInsertShowLoggedIn, socialMediaAccount) { + const auto &database = Database::getDatabaseInstance(); + const auto accounts = database.getAccountsByType(AccountType::SocialMediaAccountType); + std::streambuf* originalCoutBuffer = std::cout.rdbuf(); + const std::ostringstream capturedOutput; + std::cout.rdbuf(capturedOutput.rdbuf()); + for (const auto &account : accounts) { + account->show(); + } + std::cout.rdbuf(originalCoutBuffer); + const std::string captured = capturedOutput.str(); + const std::string expectedResult = +R"(--------------------------------- +Account details: +Account Type: Social Media Account +Username: sebi1 +Password: 1234 +Platform: 123412412 +Profile URL: bt +)"; + ASSERT_EQ(captured, expectedResult); +} + +TEST_F(AccountTestInsertShowLoggedIn, allAccounts) { + const auto &database = Database::getDatabaseInstance(); + const auto accounts = database.getAllAccounts(); + std::streambuf* originalCoutBuffer = std::cout.rdbuf(); + const std::ostringstream capturedOutput; + std::cout.rdbuf(capturedOutput.rdbuf()); + for (const auto &account : accounts) { + account->show(); + } + std::cout.rdbuf(originalCoutBuffer); + const std::string captured = capturedOutput.str(); + const std::string expectedResult = +R"(--------------------------------- +Account details: +Account Type: Bank Account +Username: sebi11 +Password: 1234 +IBAN: 123412412 +Bank: bt +--------------------------------- +Account details: +Account Type: Email Account +Username: sebi1 +Password: 1234 +Email Address: 123412412 +Mail Provider: bt +--------------------------------- +Account details: +Account Type: Social Media Account +Username: sebi1 +Password: 1234 +Platform: 123412412 +Profile URL: bt +)"; + ASSERT_EQ(captured, expectedResult); +} diff --git a/tests/AccountTestNotLoggedIn.cpp b/tests/AccountTestNotLoggedIn.cpp new file mode 100644 index 0000000..e3ebb7d --- /dev/null +++ b/tests/AccountTestNotLoggedIn.cpp @@ -0,0 +1,37 @@ +#include +#include +#include +#include + +class AccountTestNotLoggedIn : public ::testing::Test { +protected: + static void SetUpTestSuite() { + User::setCurrentUserId(-1); + } +}; + +TEST_F(AccountTestNotLoggedIn, noBankAccountCreatedWhenNotLoggedIn) { + const auto bankAccountFactory = + AccountFactory::accountFactory(AccountType::BankAccountType, + { + {"username", "sebi1"}, {"password", "1234"}, + {"IBAN", "123412412"}, {"bank", "bt"} + }); + ASSERT_THROW(bankAccountFactory->addAccount(), std::exception); +} + +TEST_F(AccountTestNotLoggedIn, noEmailAccountCreatedWhenNotLoggedIn) { + const auto emailAccountFactory = AccountFactory::accountFactory(AccountType::EmailAccountType, + {{"username", "sebi1"},{"password","1234"}, + {"emailAddress", "123412412"}, {"mailProvider", "bt"} + }); + ASSERT_THROW(emailAccountFactory->addAccount(), std::exception); +} + +TEST_F(AccountTestNotLoggedIn, noSocialMediaAccountCreatedWhenNotLoggedIn) { + const auto socialMediaAccountFactory = + AccountFactory::accountFactory(AccountType::SocialMediaAccountType, + {{"username", "sebi1"},{"password","1234"}, + {"platform", "123412412"}, {"profileUrl", "bt"}}); + ASSERT_THROW(socialMediaAccountFactory->addAccount(), std::exception); +} \ No newline at end of file diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 7987582..5010406 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -14,6 +14,10 @@ add_executable(${PROJECT_NAME} main.cpp HashingTest.cpp UserTest.cpp + AccountFactoryTest.cpp + VectorPlusTest.cpp + AccountTestInsertShowLoggedIn.cpp + AccountTestNotLoggedIn.cpp ) include(${CMAKE_HELPER_DIR}/CompilerFlags.cmake) diff --git a/tests/HashingTest.cpp b/tests/HashingTest.cpp index a38aa34..7e052ac 100644 --- a/tests/HashingTest.cpp +++ b/tests/HashingTest.cpp @@ -7,7 +7,8 @@ TEST(HashingTest, HashFunctionReturnsExpectedValue) { const auto password = "password"; const auto actualHashedPassword = digestpp::sha512().absorb(password).hexdigest(); - const auto expectedHashedPassword = "b109f3bbbc244eb82441917ed06d618b9008dd09b3befd1b5e07394c706a8bb980b1d7785e5976ec049b46df5f1326af5a2ea6d103fd07c95385ffab0cacbc86"; + const auto expectedHashedPassword = + "b109f3bbbc244eb82441917ed06d618b9008dd09b3befd1b5e07394c706a8bb980b1d7785e5976ec049b46df5f1326af5a2ea6d103fd07c95385ffab0cacbc86"; ASSERT_EQ(expectedHashedPassword, actualHashedPassword); } @@ -28,4 +29,4 @@ TEST(HashingTest, PasswordCanBeRehashedUsingSalt) { const PasswordHash passwordHash("password", salt); const PasswordHash passwordHash2("password", salt); ASSERT_EQ(passwordHash.getPasswordHash(), passwordHash2.getPasswordHash()); -} \ No newline at end of file +} diff --git a/tests/UserTest.cpp b/tests/UserTest.cpp index db75071..0a00b6f 100644 --- a/tests/UserTest.cpp +++ b/tests/UserTest.cpp @@ -18,7 +18,7 @@ class UserTest : public ::testing::Test { TEST_F(UserTest, AccountCreationIncreasesNumberOfAccounts) { const auto &database = Database::getDatabaseInstance(); const int numberOfUsersBefore = database.getNumberOfUsers(); - const Auth auth("AccountCreationIncreasesNumberOfAccounts","test"); + const Auth auth("AccountCreationIncreasesNumberOfAccounts", "test"); auto user = auth.createAccount(); const int numberOfUsersAfter = database.getNumberOfUsers(); ASSERT_EQ(numberOfUsersBefore+1, numberOfUsersAfter); @@ -27,7 +27,7 @@ TEST_F(UserTest, AccountCreationIncreasesNumberOfAccounts) { TEST_F(UserTest, NewAccountDetailsTest) { const auto &database = Database::getDatabaseInstance(); std::string username = "NewAccountDetailsTest"; - const Auth auth(username,"test"); + const Auth auth(username, "test"); const auto expectedUser = auth.createAccount(); const auto actualUser = database.getUserByUsername(username); ASSERT_EQ(expectedUser, actualUser); @@ -35,27 +35,27 @@ TEST_F(UserTest, NewAccountDetailsTest) { TEST_F(UserTest, NoDuplicateUsername) { const std::string username = "NoDuplicateUsername"; - const Auth auth(username,"test"); + const Auth auth(username, "test"); const auto expectedUser = auth.createAccount(); - const Auth auth1(username,"test2"); + const Auth auth1(username, "test2"); // ReSharper disable once CppNoDiscardExpression - ASSERT_THROW(auth1.createAccount(),pqxx::unique_violation); + ASSERT_THROW(auth1.createAccount(), pqxx::unique_violation); } TEST_F(UserTest, TestLoginValidCredentialsShouldSucceed) { const std::string username = "TestLoginValid"; - const Auth auth(username,"test"); + const Auth auth(username, "test"); const auto expectedUser = auth.createAccount(); - const Auth auth1(username,"test"); + const Auth auth1(username, "test"); const auto actualUser = auth1.login(); ASSERT_EQ(expectedUser, actualUser); } TEST_F(UserTest, TestLoginInvalidCredentialsShouldFail) { const std::string username = "LoginTestInvalid"; - const Auth auth(username,"test"); + const Auth auth(username, "test"); const auto expectedUser = auth.createAccount(); - const Auth auth1(username,"test1"); + const Auth auth1(username, "test1"); // ReSharper disable once CppNoDiscardExpression ASSERT_THROW(auth1.login(), std::exception); -} \ No newline at end of file +} diff --git a/tests/VectorPlusTest.cpp b/tests/VectorPlusTest.cpp new file mode 100644 index 0000000..cc5ad07 --- /dev/null +++ b/tests/VectorPlusTest.cpp @@ -0,0 +1,36 @@ +#include +#include +#include + +#include +#include + +TEST(VectorPlusTest, IntTest) { + const std::vector v = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10}; + const std::vector v1 = {11, 12, 13, 14, 15, 16, 17, 18, 19, 20}; + const std::vector result = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20}; + ASSERT_EQ(v+v1, result); +} + +TEST(VectorPlusTest, DoubleTest) { + const std::vector v = {1.0, 2.0, 3.0, 4.0}; + const std::vector v1 = {5.0, 6.0, 7.0, 8.0}; + const std::vector result = {1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0}; + ASSERT_EQ(v+v1, result); +} + +TEST(VectorPlusTest, AccountTest) { + const auto bankAccountFactory = + AccountFactory::accountFactory(AccountType::BankAccountType, + { + {"username", "sebi1"}, {"password", "1234"}, + {"IBAN", "123412412"}, {"bank", "bt"} + }); + const auto emailAccountFactory = AccountFactory::accountFactory(AccountType::EmailAccountType, + {{"username", "sebi1"},{"password","1234"}, + {"IBAN", "123412412"}, {"bank", "bt"}}); + const std::vector v = {bankAccountFactory}; + const std::vector v1 = {emailAccountFactory}; + const std::vector result = {bankAccountFactory, emailAccountFactory}; + ASSERT_EQ(v+v1, result); +} \ No newline at end of file