diff --git a/CMakeLists.txt b/CMakeLists.txt index 4d8e5a1..3754df3 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,6 +1,3 @@ -#SPDX-FileCopyrightText: 2023 Yury Gubich -#SPDX-License-Identifier: GPL-3.0-or-later - cmake_minimum_required(VERSION 3.5) project(pica VERSION 0.0.1 @@ -16,59 +13,28 @@ include(GNUInstallDirs) set(CMAKE_CXX_STANDARD 17) set(CMAKE_CXX_STANDARD_REQUIRED ON) set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} "${CMAKE_SOURCE_DIR}/cmake") - -if (NOT CMAKE_BUILD_TYPE) - set(CMAKE_BUILD_TYPE Debug) -endif() -message("Build type: ${CMAKE_BUILD_TYPE}") - -set(COMPILE_OPTIONS -fno-sized-deallocation) -if (CMAKE_BUILD_TYPE STREQUAL Release) - list(APPEND COMPILE_OPTIONS -O3) -elseif (CMAKE_BUILD_TYPE STREQUAL Debug) - list(APPEND COMPILE_OPTIONS -g) - list(APPEND COMPILE_OPTIONS -Wall) - list(APPEND COMPILE_OPTIONS -Wextra) -endif() - -set(COMPILE_OPTIONS_STRING "") -foreach(element IN LISTS COMPILE_OPTIONS) - if(NOT COMPILE_OPTIONS_STRING STREQUAL "") - set(COMPILE_OPTIONS_STRING "${COMPILE_OPTIONS_STRING} ") - endif() - set(COMPILE_OPTIONS_STRING "${COMPILE_OPTIONS_STRING}${element}") -endforeach() -message("Compile options: " ${COMPILE_OPTIONS_STRING}) +set(PICA_BIN_DIR ${CMAKE_CURRENT_BINARY_DIR}) find_package(nlohmann_json REQUIRED) find_package(FCGI REQUIRED) -find_package(Argon2 REQUIRED) -find_package(Threads REQUIRED) -add_executable(${PROJECT_NAME} main.cpp) -target_include_directories(${PROJECT_NAME} PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}) -target_include_directories(${PROJECT_NAME} PRIVATE ${CMAKE_CURRENT_BINARY_DIR}) -target_compile_options(${PROJECT_NAME} PRIVATE ${COMPILE_OPTIONS}) +add_executable(pica main.cpp) +target_include_directories(pica PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}) +target_include_directories(pica PRIVATE ${CMAKE_CURRENT_BINARY_DIR}) add_subdirectory(server) -add_subdirectory(handler) add_subdirectory(request) add_subdirectory(response) add_subdirectory(stream) add_subdirectory(database) add_subdirectory(utils) -add_subdirectory(taskmanager) configure_file(config.h.in config.h @ONLY) -configure_file(run.sh.in run.sh @ONLY) -execute_process(COMMAND chmod +x ${CMAKE_CURRENT_BINARY_DIR}/run.sh) -target_link_libraries(${PROJECT_NAME} PRIVATE +target_link_libraries(pica PRIVATE FCGI::FCGI FCGI::FCGI++ nlohmann_json::nlohmann_json - Argon2::Argon2 - Threads::Threads ) -install(TARGETS ${PROJECT_NAME} RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR}) +install(TARGETS pica RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR}) diff --git a/README.md b/README.md index 4c48ad8..e65dfcc 100644 --- a/README.md +++ b/README.md @@ -7,7 +7,6 @@ - fcgi - nlohmann_json - mariadb-client -- argon2 ### Building diff --git a/cmake/FindArgon2.cmake b/cmake/FindArgon2.cmake deleted file mode 100644 index 0f1fc81..0000000 --- a/cmake/FindArgon2.cmake +++ /dev/null @@ -1,29 +0,0 @@ -#SPDX-FileCopyrightText: 2023 Yury Gubich -#SPDX-License-Identifier: GPL-3.0-or-later - -find_library(Argon2_LIBRARIES argon2) -find_path(Argon2_INCLUDE_DIR argon2.h) - -if (Argon2_LIBRARIES AND Argon2_INCLUDE_DIR) - set(Argon2_FOUND TRUE) -endif() - -if (Argon2_FOUND) - add_library(Argon2::Argon2 SHARED IMPORTED) - set_target_properties(Argon2::Argon2 PROPERTIES - IMPORTED_LOCATION "${Argon2_LIBRARIES}" - INTERFACE_LINK_LIBRARIES "${Argon2_LIBRARIES}" - INTERFACE_INCLUDE_DIRECTORIES ${Argon2_INCLUDE_DIR} - ) - - if (NOT Argon2_FIND_QUIETLY) - message(STATUS "Found Argon2 includes: ${Argon2_INCLUDE_DIR}") - message(STATUS "Found Argon2 library: ${Argon2_LIBRARIES}") - endif () -else () - if (Argon2_FIND_REQUIRED) - message(FATAL_ERROR "Could NOT find Argon2 development files") - endif () -endif () - - diff --git a/cmake/FindFCGI.cmake b/cmake/FindFCGI.cmake index 77a04df..e3a3e88 100644 --- a/cmake/FindFCGI.cmake +++ b/cmake/FindFCGI.cmake @@ -1,6 +1,3 @@ -#SPDX-FileCopyrightText: 2023 Yury Gubich -#SPDX-License-Identifier: GPL-3.0-or-later - find_library(FCGI_LIBRARIES fcgi NAMES FCGI libfcgi) find_library(FCGI++_LIBRARIES fcgi++ NAMES FCGI++ libfcgi++) diff --git a/cmake/FindMariaDB.cmake b/cmake/FindMariaDB.cmake index f32d521..623de5f 100644 --- a/cmake/FindMariaDB.cmake +++ b/cmake/FindMariaDB.cmake @@ -1,6 +1,3 @@ -#SPDX-FileCopyrightText: 2023 Yury Gubich -#SPDX-License-Identifier: GPL-3.0-or-laterlater - find_library(MariaDB_CLIENT_LIBRARIES mysqlclient NAMES mariadbclient) find_path(MariaDB_INCLUDE_DIR mysql/mysql.h) diff --git a/config.h.in b/config.h.in index 624a6d9..703a9f7 100644 --- a/config.h.in +++ b/config.h.in @@ -1,6 +1,3 @@ -//SPDX-FileCopyrightText: 2023 Yury Gubich -//SPDX-License-Identifier: GPL-3.0-or-later - #pragma once #define FULL_DATA_DIR "@CMAKE_INSTALL_FULL_DATADIR@" @@ -10,4 +7,3 @@ #define BIN_DIR "@CMAKE_INSTALL_BINDIR@" #define PROJECT_NAME "@PROJECT_NAME@" -#define PROJECT_VERSION "@PROJECT_VERSION@" diff --git a/database/CMakeLists.txt b/database/CMakeLists.txt index 36e1ad5..8f5b864 100644 --- a/database/CMakeLists.txt +++ b/database/CMakeLists.txt @@ -1,22 +1,12 @@ -#SPDX-FileCopyrightText: 2023 Yury Gubich -#SPDX-License-Identifier: GPL-3.0-or-later - set(HEADERS - interface.h - exceptions.h - pool.h - resource.h + dbinterface.h ) set(SOURCES - interface.cpp - exceptions.cpp - pool.cpp - resource.cpp + dbinterface.cpp ) -target_sources(${PROJECT_NAME} PRIVATE ${SOURCES}) +target_sources(pica PRIVATE ${SOURCES}) add_subdirectory(mysql) add_subdirectory(migrations) -add_subdirectory(schema) diff --git a/database/dbinterface.cpp b/database/dbinterface.cpp new file mode 100644 index 0000000..de98e39 --- /dev/null +++ b/database/dbinterface.cpp @@ -0,0 +1,26 @@ +// SPDX-FileCopyrightText: 2023 Yury Gubich +// SPDX-License-Identifier: GPL-3.0-or-later + +#include "dbinterface.h" + +#include "mysql/mysql.h" + +DBInterface::DBInterface(Type type): + type(type), + state(State::disconnected) +{} + +DBInterface::~DBInterface() {} + +std::unique_ptr DBInterface::create(Type type) { + switch (type) { + case Type::mysql: + return std::make_unique(); + } + + throw std::runtime_error("Unexpected database type: " + std::to_string((uint8_t)type)); +} + +DBInterface::State DBInterface::currentState() const { + return state; +} diff --git a/database/dbinterface.h b/database/dbinterface.h new file mode 100644 index 0000000..839672d --- /dev/null +++ b/database/dbinterface.h @@ -0,0 +1,44 @@ +// SPDX-FileCopyrightText: 2023 Yury Gubich +// SPDX-License-Identifier: GPL-3.0-or-later + +#pragma once + +#include +#include +#include +#include + +class DBInterface { +public: + enum class Type { + mysql + }; + enum class State { + disconnected, + connecting, + connected + }; + static std::unique_ptr create(Type type); + + virtual ~DBInterface(); + + State currentState() const; + + const Type type; + +public: + virtual void connect(const std::string& path) = 0; + virtual void disconnect() = 0; + virtual void setDatabase(const std::string& newDatabase) = 0; + virtual void setCredentials(const std::string& login, const std::string& password) = 0; + + virtual void migrate(uint8_t targetVersion) = 0; + virtual uint8_t getVersion() = 0; + virtual void setVersion(uint8_t version) = 0; + +protected: + DBInterface(Type type); + +protected: + State state; +}; diff --git a/database/exceptions.cpp b/database/exceptions.cpp deleted file mode 100644 index b48ef51..0000000 --- a/database/exceptions.cpp +++ /dev/null @@ -1,24 +0,0 @@ -//SPDX-FileCopyrightText: 2023 Yury Gubich -//SPDX-License-Identifier: GPL-3.0-or-later - -#include "exceptions.h" - -DB::Duplicate::Duplicate(const std::string& text): - std::runtime_error(text) -{} - -DB::DuplicateLogin::DuplicateLogin(const std::string& text): - Duplicate(text) -{} - -DB::EmptyResult::EmptyResult(const std::string& text): - std::runtime_error(text) -{} - -DB::NoLogin::NoLogin(const std::string& text): - EmptyResult(text) -{} - -DB::NoSession::NoSession (const std::string& text): - EmptyResult(text) -{} diff --git a/database/exceptions.h b/database/exceptions.h deleted file mode 100644 index 81b4f7b..0000000 --- a/database/exceptions.h +++ /dev/null @@ -1,34 +0,0 @@ -//SPDX-FileCopyrightText: 2023 Yury Gubich -//SPDX-License-Identifier: GPL-3.0-or-later - -#pragma once - -#include -#include - -namespace DB { -class Duplicate : public std::runtime_error { -public: - explicit Duplicate(const std::string& text); -}; - -class DuplicateLogin : public Duplicate { -public: - explicit DuplicateLogin(const std::string& text); -}; - -class EmptyResult : public std::runtime_error { -public: - explicit EmptyResult(const std::string& text); -}; - -class NoLogin : public EmptyResult { -public: - explicit NoLogin(const std::string& text); -}; - -class NoSession : public EmptyResult { -public: - explicit NoSession(const std::string& text); -}; -} diff --git a/database/interface.cpp b/database/interface.cpp deleted file mode 100644 index ecf195e..0000000 --- a/database/interface.cpp +++ /dev/null @@ -1,26 +0,0 @@ -//SPDX-FileCopyrightText: 2023 Yury Gubich -//SPDX-License-Identifier: GPL-3.0-or-later - -#include "interface.h" - -#include "mysql/mysql.h" - -DB::Interface::Interface(Type type): - type(type), - state(State::disconnected) -{} - -DB::Interface::~Interface() {} - -std::unique_ptr DB::Interface::create(Type type) { - switch (type) { - case Type::mysql: - return std::make_unique(); - } - - throw std::runtime_error("Unexpected database type: " + std::to_string((uint8_t)type)); -} - -DB::Interface::State DB::Interface::currentState() const { - return state; -} diff --git a/database/interface.h b/database/interface.h deleted file mode 100644 index a0c2821..0000000 --- a/database/interface.h +++ /dev/null @@ -1,70 +0,0 @@ -// SPDX-FileCopyrightText: 2023 Yury Gubich -// SPDX-License-Identifier: GPL-3.0-or-later - -#pragma once - -#include -#include -#include -#include -#include - -#include "schema/session.h" -#include "schema/asset.h" -#include "schema/currency.h" -#include "schema/transaction.h" - -namespace DB { -class Interface { -public: - enum class Type { - mysql - }; - enum class State { - disconnected, - connecting, - connected - }; - static std::unique_ptr create(Type type); - - virtual ~Interface(); - - State currentState() const; - - const Type type; - -public: - virtual void connect(const std::string& path) = 0; - virtual void disconnect() = 0; - virtual void setDatabase(const std::string& newDatabase) = 0; - virtual void setCredentials(const std::string& login, const std::string& password) = 0; - - virtual void migrate(uint8_t targetVersion) = 0; - virtual uint8_t getVersion() = 0; - virtual void setVersion(uint8_t version) = 0; - - virtual uint32_t registerAccount(const std::string& login, const std::string& hash) = 0; - virtual std::string getAccountHash(const std::string& login) = 0; - - virtual Session createSession(const std::string& login, const std::string& access, const std::string& renew) = 0; - virtual Session findSession(const std::string& accessToken) = 0; - - virtual std::vector listAssets(uint32_t owner) = 0; - virtual Asset addAsset(const Asset& asset) = 0; - virtual void updateAsset(const Asset& asset) = 0; - virtual bool deleteAsset(uint32_t assetId, uint32_t actorId) = 0; - - virtual std::vector listUsedCurrencies(uint32_t owner) = 0; - - virtual DB::Transaction addTransaction(const DB::Transaction& transaction) = 0; - virtual std::vector listTransactions(uint32_t owner) = 0; - virtual void updateTransaction(const DB::Transaction& transaction) = 0; - virtual bool deleteTransaction(uint32_t id, uint32_t actorId) = 0; - -protected: - Interface(Type type); - -protected: - State state; -}; -} diff --git a/database/migrations/CMakeLists.txt b/database/migrations/CMakeLists.txt index d0842f0..e71b0a4 100644 --- a/database/migrations/CMakeLists.txt +++ b/database/migrations/CMakeLists.txt @@ -1,8 +1,5 @@ -#SPDX-FileCopyrightText: 2023 Yury Gubich -#SPDX-License-Identifier: GPL-3.0-or-laterater - set(MIGRATIONS migrations) -configure_file(m0.sql ${PROJECT_BINARY_DIR}/${CMAKE_INSTALL_DATADIR}/${MIGRATIONS}/m0.sql COPYONLY) +configure_file(m0.sql ${PICA_BIN_DIR}/${CMAKE_INSTALL_DATADIR}/${MIGRATIONS}/m0.sql COPYONLY) install( FILES diff --git a/database/migrations/m0.sql b/database/migrations/m0.sql index 0e6b2a5..951fa04 100644 --- a/database/migrations/m0.sql +++ b/database/migrations/m0.sql @@ -1,192 +1,6 @@ ---creating system table CREATE TABLE IF NOT EXISTS system ( `key` VARCHAR(32) PRIMARY KEY, `value` TEXT ); ---creating roles table -CREATE TABLE IF NOT EXISTS roles ( - `id` INTEGER UNSIGNED AUTO_INCREMENT PRIMARY KEY, - `name` VARCHAR(256) UNIQUE NOT NULL, - `color` INTEGER UNSIGNED DEFAULT 0 -); - ---creating accounts table -CREATE TABLE IF NOT EXISTS accounts ( - `id` INTEGER UNSIGNED AUTO_INCREMENT PRIMARY KEY, - `login` VARCHAR(256) UNIQUE NOT NULL, - `nick` VARCHAR(256), - `type` INTEGER UNSIGNED NOT NULL, - `password` VARCHAR(128), - `created` TIMESTAMP -); - ---creating role bindings table -CREATE TABLE IF NOT EXISTS roleBindings ( - `account` INTEGER UNSIGNED NOT NULL, - `role` INTEGER UNSIGNED NOT NULL, - - PRIMARY KEY (account, role), - FOREIGN KEY (account) REFERENCES accounts(id), - FOREIGN KEY (role) REFERENCES roles(id) -); - ---creating sessions table -CREATE TABLE IF NOT EXISTS sessions ( - `id` INTEGER UNSIGNED AUTO_INCREMENT PRIMARY KEY, - `owner` INTEGER UNSIGNED NOT NULL, - `started` TIMESTAMP, - `latest` TIMESTAMP, - `access` CHAR(32) NOT NULL UNIQUE, - `renew` CHAR(32), - `persist` BOOLEAN NOT NULL, - `device` TEXT, - - FOREIGN KEY (owner) REFERENCES accounts(id) -); - ---creating currencies table -CREATE TABLE IF NOT EXISTS currencies ( - `id` INTEGER UNSIGNED AUTO_INCREMENT PRIMARY KEY, - `code` VARCHAR(16) NOT NULL UNIQUE, - `title` VARCHAR(256), - `manual` BOOLEAN NOT NULL, - `created` TIMESTAMP, - `type` INTEGER UNSIGNED NOT NULL, - `value` DECIMAL (20, 5) NOT NULL, - `source` TEXT, - `description` TEXT, - `icon` VARCHAR(256), - - INDEX manual_idx (manual) -); - ---creating assets table -CREATE TABLE IF NOT EXISTS assets ( - `id` INTEGER UNSIGNED AUTO_INCREMENT PRIMARY KEY, - `owner` INTEGER UNSIGNED NOT NULL, - `currency` INTEGER UNSIGNED NOT NULL, - `title` VARCHAR(256), - `icon` VARCHAR(256), - `color` INTEGER UNSIGNED DEFAULT 0, - `balance` DECIMAL (20, 5) DEFAULT 0, - `type` INTEGER UNSIGNED NOT NULL, - `archived` BOOLEAN DEFAULT FALSE, - `created` TIMESTAMP, - - INDEX owner_idx (owner), - INDEX archived_idx (archived), - - FOREIGN KEY (owner) REFERENCES accounts(id), - FOREIGN KEY (currency) REFERENCES currencies(id) -); - ---creating parties table -CREATE TABLE IF NOT EXISTS parties ( - `id` INTEGER UNSIGNED AUTO_INCREMENT PRIMARY KEY, - `title` VARCHAR(256) NOT NULL UNIQUE -); - ---creating transactions table -CREATE TABLE IF NOT EXISTS transactions ( - `id` INTEGER UNSIGNED AUTO_INCREMENT PRIMARY KEY, - `initiator` INTEGER UNSIGNED NOT NULL, - `type` INTEGER UNSIGNED NOT NULL, - `asset` INTEGER UNSIGNED NOT NULL, - `parent` INTEGER UNSIGNED, - `value` DECIMAL (20, 5) NOT NULL, - `state` INTEGER UNSIGNED DEFAULT 0, - `modified` TIMESTAMP, - `performed` TIMESTAMP, - `party` INTEGER UNSIGNED, - `notes` TEXT, - - INDEX initiator_idx (initiator), - INDEX parent_idx (parent), - INDEX asset_idx (asset), - INDEX performed_idx (performed), - INDEX modified_idx (modified), - INDEX party_idx (party), - - FOREIGN KEY (initiator) REFERENCES accounts(id), - FOREIGN KEY (asset) REFERENCES assets(id), - FOREIGN KEY (parent) REFERENCES transactions(id), - FOREIGN KEY (party) REFERENCES parties(id) -); - ---creating trigger before insert accounts -CREATE TRIGGER before_insert_accounts -BEFORE INSERT ON accounts -FOR EACH ROW -BEGIN - SET NEW.created = UTC_TIMESTAMP(); -END; - ---creating trigger before insert sessions -CREATE TRIGGER before_insert_sessions -BEFORE INSERT ON sessions -FOR EACH ROW -BEGIN - SET NEW.started = UTC_TIMESTAMP(); - SET NEW.latest = UTC_TIMESTAMP(); -END; - ---creating trigger before insert currencies -CREATE TRIGGER before_insert_currencies -BEFORE INSERT ON currencies -FOR EACH ROW -BEGIN - IF NEW.created IS NULL THEN - SET NEW.created = UTC_TIMESTAMP(); - END IF; -END; - ---creating trigger before insert assets -CREATE TRIGGER before_insert_assets -BEFORE INSERT ON assets -FOR EACH ROW -BEGIN - IF NEW.created IS NULL THEN - SET NEW.created = UTC_TIMESTAMP(); - END IF; -END; - ---creating trigger before insert transactions -CREATE TRIGGER before_insert_transactions -BEFORE INSERT ON transactions -FOR EACH ROW -BEGIN - SET NEW.modified = UTC_TIMESTAMP(); - IF NEW.performed IS NULL THEN - SET NEW.performed = UTC_TIMESTAMP(); - END IF; -END; - ---creating trigger before update transactions -CREATE TRIGGER before_update_transactions -BEFORE UPDATE ON transactions -FOR EACH ROW -BEGIN - SET NEW.modified = UTC_TIMESTAMP(); -END; - ---creating default roles -INSERT IGNORE INTO -roles (`name`) -VALUES ('root'), - ('default'); - ---inserting initial version -INSERT IGNORE INTO -system (`key`, `value`) -VALUES ('version', '0'); - ---recording initial time -INSERT IGNORE INTO -system (`key`, `value`) -VALUES ('created', UTC_TIMESTAMP()); - ---creating default currencies -INSERT IGNORE INTO -currencies (`code`, `title`, `manual`, `description`, `type`, `value`, `icon`) -VALUES ('USD', 'United States Dollar', TRUE, 'Base currency', 1, 1, 'currency-usd'); +INSERT INTO system (`key`, `value`) VALUES ('version', '0'); diff --git a/database/mysql/CMakeLists.txt b/database/mysql/CMakeLists.txt index 7a77c4b..75dd5e1 100644 --- a/database/mysql/CMakeLists.txt +++ b/database/mysql/CMakeLists.txt @@ -1,20 +1,15 @@ -#SPDX-FileCopyrightText: 2023 Yury Gubich -#SPDX-License-Identifier: GPL-3.0-or-laterr - set(HEADERS mysql.h statement.h - transaction.h ) set(SOURCES mysql.cpp statement.cpp - transaction.cpp ) find_package(MariaDB REQUIRED) -target_sources(${PROJECT_NAME} PRIVATE ${SOURCES}) +target_sources(pica PRIVATE ${SOURCES}) -target_link_libraries(${PROJECT_NAME} PRIVATE MariaDB::client) +target_link_libraries(pica PRIVATE MariaDB::client) diff --git a/database/mysql/mysql.cpp b/database/mysql/mysql.cpp index ab8b004..6653e25 100644 --- a/database/mysql/mysql.cpp +++ b/database/mysql/mysql.cpp @@ -1,63 +1,27 @@ -//SPDX-FileCopyrightText: 2023 Yury Gubich -//SPDX-License-Identifier: GPL-3.0-or-later +// SPDX-FileCopyrightText: 2023 Yury Gubich +// SPDX-License-Identifier: GPL-3.0-or-later #include "mysql.h" #include #include -#include #include "mysqld_error.h" #include "statement.h" -#include "transaction.h" -#include "database/exceptions.h" -constexpr const char* versionQuery = "SELECT value FROM system WHERE `key` = 'version'"; constexpr const char* updateQuery = "UPDATE system SET `value` = ? WHERE `key` = 'version'"; -constexpr const char* registerQuery = "INSERT INTO accounts (`login`, `type`, `password`) VALUES (?, 1, ?)"; -constexpr const char* lastIdQuery = "SELECT LAST_INSERT_ID() AS id"; -constexpr const char* assignRoleQuery = "INSERT INTO roleBindings (`account`, `role`) SELECT ?, roles.id FROM roles WHERE roles.name = ?"; -constexpr const char* selectHash = "SELECT password FROM accounts where login = ?"; -constexpr const char* createSessionQuery = "INSERT INTO sessions (`owner`, `access`, `renew`, `persist`, `device`)" - " SELECT accounts.id, ?, ?, true, ? FROM accounts WHERE accounts.login = ?" - " RETURNING id, owner"; -constexpr const char* selectSession = "SELECT id, owner, access, renew FROM sessions where access = ?"; -constexpr const char* selectAssets = "SELECT id, owner, currency, title, icon, color, archived FROM assets where owner = ?"; -constexpr const char* insertAsset = "INSERT INTO assets (`owner`, `currency`, `title`, `icon`, `color`, `archived`, `type`)" - " VALUES (?, ?, ?, ?, ?, ?, 1)"; -constexpr const char* updateAssetQuery = "UPDATE assets SET `owner` = ?, `currency` = ?, `title` = ?, `icon` = ?, `color` = ?, `archived` = ?" - " WHERE `id` = ?"; -constexpr const char* removeAsset = "DELETE FROM assets where `id` = ? AND `owner` = ?"; -constexpr const char* selectUsedCurrencies = "SELECT DISTINCT c.id, c.code, c.title, c.manual, c.icon FROM currencies c" - " JOIN assets a ON c.id = a.currency" - " WHERE a.owner = ?"; -constexpr const char* addTransactionQuery = "INSERT INTO transactions" - " (`initiator`, `type`, `asset`, `parent`, `value`, `performed`)" - " VALUES (?, 1, ?, ?, ?, ?)"; -constexpr const char* updateTransactionQuery = "UPDATE transactions SET" - " `initiator` = ?, `type` = 1, `asset` = ?," - " `parent` = ?, `value` = ?, `performed` = ?" - " WHERE `id` = ?"; -constexpr const char* deleteTransactionQuery = "DELETE FROM transactions where (`id` = ? OR `parent` = ?) AND `initiator` = ?"; -constexpr const char* selectAllTransactions = "WITH RECURSIVE AllTransactions AS (" - " SELECT t.id, t.initiator, t.asset, t.parent, t.value, t.modified, t.performed t.notes FROM transactions t" - " JOIN assets a ON t.asset = a.id" - " WHERE a.owner = ?" - " UNION ALL" - - " SELECT t.id, t.initiator, t.asset, t.parent, t.value, t.modified, t.performed t.notes FROM transactions t" - " JOIN AllTransactions at ON t.id = at.parent)" - - " SELECT DISTINCT id, initiator, asset, parent, value, modified, performed notes FROM AllTransactions" - " ORDER BY performed" - " LIMIT 100 OFFSET 0;"; - static const std::filesystem::path buildSQLPath = "database"; -DB::MySQL::MySQL (): - Interface(Type::mysql), +struct ResDeleter { + void operator () (MYSQL_RES* res) { + mysql_free_result(res); + } +}; + +MySQL::MySQL(): + DBInterface(Type::mysql), connection(), login(), password(), @@ -66,11 +30,12 @@ DB::MySQL::MySQL (): mysql_init(&connection); } -DB::MySQL::~MySQL() { +MySQL::~MySQL() { mysql_close(&connection); } -void DB::MySQL::connect (const std::string& path) { + +void MySQL::connect(const std::string& path) { if (state != State::disconnected) return; @@ -92,7 +57,7 @@ void DB::MySQL::connect (const std::string& path) { state = State::connected; } -void DB::MySQL::setCredentials (const std::string& login, const std::string& password) { +void MySQL::setCredentials(const std::string& login, const std::string& password) { if (MySQL::login == login && MySQL::password == password) return; @@ -114,7 +79,7 @@ void DB::MySQL::setCredentials (const std::string& login, const std::string& pas throw std::runtime_error(std::string("Error changing credetials: ") + mysql_error(con)); } -void DB::MySQL::setDatabase (const std::string& database) { +void MySQL::setDatabase(const std::string& database) { if (MySQL::database == database) return; @@ -130,7 +95,7 @@ void DB::MySQL::setDatabase (const std::string& database) { throw std::runtime_error(std::string("Error changing db: ") + mysql_error(con)); } -void DB::MySQL::disconnect () { +void MySQL::disconnect() { if (state == State::disconnected) return; @@ -139,7 +104,7 @@ void DB::MySQL::disconnect () { mysql_init(con); //this is ridiculous! } -void DB::MySQL::executeFile (const std::filesystem::path& relativePath) { +void MySQL::executeFile(const std::filesystem::path& relativePath) { MYSQL* con = &connection; std::filesystem::path path = sharedPath() / relativePath; if (!std::filesystem::exists(path)) @@ -149,15 +114,9 @@ void DB::MySQL::executeFile (const std::filesystem::path& relativePath) { std::cout << "Executing file " << path << std::endl; std::ifstream inputFile(path); - std::string block, comment; - while (getBlock(inputFile, block, comment)) { - if (!comment.empty()) - std::cout << '\t' << comment << std::endl; - - if (block.empty()) - continue; - - int result = mysql_query(con, block.c_str()); + std::string query; + while (std::getline(inputFile, query, ';')) { + int result = mysql_query(con, query.c_str()); if (result != 0) { int errcode = mysql_errno(con); if (errcode == ER_EMPTY_QUERY) @@ -169,42 +128,9 @@ void DB::MySQL::executeFile (const std::filesystem::path& relativePath) { } } -bool DB::MySQL::getBlock(std::ifstream& file, std::string& block, std::string& name) { - if (file.eof()) - return false; - - block.clear(); - name.clear(); - - if (file.peek() == '-') { - file.get(); - if (file.peek() == '-') { - file.get(); - std::getline(file, name); - } else { - file.unget(); - } - } - std::string line; - while (!file.eof()) { - if (file.peek() == '-') - return true; - - if (!std::getline(file, line)) - break; - - if (!block.empty()) - block.append(1, '\n'); - - block += line; - } - - return !block.empty() || !name.empty(); -} - -uint8_t DB::MySQL::getVersion () { +uint8_t MySQL::getVersion() { MYSQL* con = &connection; - int result = mysql_query(con, versionQuery); + int result = mysql_query(con, "SELECT value FROM system WHERE `key` = 'version'"); if (result != 0) { unsigned int errcode = mysql_errno(con); @@ -225,284 +151,26 @@ uint8_t DB::MySQL::getVersion () { return 0; } -void DB::MySQL::setVersion (uint8_t version) { +void MySQL::setVersion(uint8_t version) { std::string strVersion = std::to_string(version); Statement statement(&connection, updateQuery); statement.bind(strVersion.data(), MYSQL_TYPE_VAR_STRING); statement.execute(); } -void DB::MySQL::migrate (uint8_t targetVersion) { +void MySQL::migrate(uint8_t targetVersion) { uint8_t currentVersion = getVersion(); while (currentVersion < targetVersion) { - if (currentVersion == 255) - throw std::runtime_error("Maximum possible database version reached"); - - uint8_t nextVersion = currentVersion + 1; std::string fileName = "migrations/m" + std::to_string(currentVersion) + ".sql"; std::cout << "Performing migration " << std::to_string(currentVersion) << " -> " - << std::to_string(nextVersion) + << std::to_string(++currentVersion) << std::endl; executeFile(fileName); - setVersion(nextVersion); - currentVersion = nextVersion; + setVersion(currentVersion); } std::cout << "Database is now on actual version " << std::to_string(targetVersion) << std::endl; } - -uint32_t DB::MySQL::registerAccount (const std::string& login, const std::string& hash) { - //TODO validate filed lengths! - MYSQL* con = &connection; - MySQL::Transaction txn(con); - - Statement addAcc(con, registerQuery); - - std::string l = login; //I hate copying just to please this horible API - std::string h = hash; - addAcc.bind(l.data(), MYSQL_TYPE_STRING); - addAcc.bind(h.data(), MYSQL_TYPE_STRING); - try { - addAcc.execute(); - } catch (const Duplicate& dup) { - throw DuplicateLogin(dup.what()); - } - - uint32_t id = lastInsertedId(); - static std::string defaultRole("default"); - - Statement addRole(con, assignRoleQuery); - addRole.bind(&id, MYSQL_TYPE_LONG, true); - addRole.bind(defaultRole.data(), MYSQL_TYPE_STRING); - addRole.execute(); - - txn.commit(); - return id; -} - -std::string DB::MySQL::getAccountHash (const std::string& login) { - std::string l = login; - MYSQL* con = &connection; - - Statement getHash(con, selectHash); - getHash.bind(l.data(), MYSQL_TYPE_STRING); - getHash.execute(); - - std::vector> result = getHash.fetchResult(); - if (result.empty()) - throw NoLogin("Couldn't find login " + l); - - if (result[0].empty()) - throw std::runtime_error("Error with the query \"selectHash\""); - - return std::any_cast(result[0][0]); -} - -DB::Session DB::MySQL::createSession (const std::string& login, const std::string& access, const std::string& renew) { - std::string l = login; - DB::Session res; - res.accessToken = access; - res.renewToken = renew; - static std::string testingDevice("Testing..."); - - MYSQL* con = &connection; - - Statement session(con, createSessionQuery); - session.bind(res.accessToken.data(), MYSQL_TYPE_STRING); - session.bind(res.renewToken.data(), MYSQL_TYPE_STRING); - session.bind(testingDevice.data(), MYSQL_TYPE_STRING); - session.bind(l.data(), MYSQL_TYPE_STRING); - session.execute(); - - std::vector> result = session.fetchResult(); - if (result.empty()) - throw std::runtime_error("Error returning ids after insertion in sessions table"); - - res.id = std::any_cast(result[0][0]); - res.owner = std::any_cast(result[0][1]); - - return res; -} - -uint32_t DB::MySQL::lastInsertedId () { - MYSQL* con = &connection; - int result = mysql_query(con, lastIdQuery); - - if (result != 0) - throw std::runtime_error(std::string("Error executing last inserted id: ") + mysql_error(con)); - - std::unique_ptr res(mysql_store_result(con)); - if (!res) - throw std::runtime_error(std::string("Querying last inserted id returned no result: ") + mysql_error(con)); - - MYSQL_ROW row = mysql_fetch_row(res.get()); - if (row) - return std::stoi(row[0]); - else - throw std::runtime_error(std::string("Querying last inserted id returned no rows")); -} -DB::Session DB::MySQL::findSession (const std::string& accessToken) { - std::string a = accessToken; - MYSQL* con = &connection; - - Statement session(con, selectSession); - session.bind(a.data(), MYSQL_TYPE_STRING); - session.execute(); - - std::vector> result = session.fetchResult(); - if (result.empty()) - throw NoSession("Couldn't find session with token " + a); - - return DB::Session(result[0]); -} - -std::vector DB::MySQL::listAssets (uint32_t owner) { - MYSQL* con = &connection; - - Statement st(con, selectAssets); - st.bind(&owner, MYSQL_TYPE_LONG, true); - st.execute(); - std::vector> res = st.fetchResult(); - - std::size_t size = res.size(); - std::vector result(size); - for (std::size_t i = 0; i < size; ++i) - result[i].parse(res[i]); - - return result; -} - -DB::Asset DB::MySQL::addAsset(const Asset& asset) { - MYSQL* con = &connection; - Asset result = asset; - - Statement add(con, insertAsset); - add.bind(&result.owner, MYSQL_TYPE_LONG, true); - add.bind(&result.currency, MYSQL_TYPE_LONG, true); - add.bind(result.title.data(), MYSQL_TYPE_STRING); - add.bind(result.icon.data(), MYSQL_TYPE_STRING); - add.bind(&result.color, MYSQL_TYPE_LONG, true); - add.bind(&result.archived, MYSQL_TYPE_TINY); - add.execute(); - - result.id = lastInsertedId(); - - return result; -} - -void DB::MySQL::updateAsset(const Asset& asset) { - MYSQL* con = &connection; - Asset result = asset; - - Statement update(con, updateAssetQuery); - update.bind(&result.owner, MYSQL_TYPE_LONG, true); - update.bind(&result.currency, MYSQL_TYPE_LONG, true); - update.bind(result.title.data(), MYSQL_TYPE_STRING); - update.bind(result.icon.data(), MYSQL_TYPE_STRING); - update.bind(&result.color, MYSQL_TYPE_LONG, true); - update.bind(&result.archived, MYSQL_TYPE_TINY); - update.bind(&result.id, MYSQL_TYPE_LONG, true); - update.execute(); -} - -bool DB::MySQL::deleteAsset(uint32_t assetId, uint32_t actorId) { - Statement del(&connection, removeAsset); - del.bind(&assetId, MYSQL_TYPE_LONG, true); - del.bind(&actorId, MYSQL_TYPE_LONG, true); - del.execute(); - - if (del.affectedRows() == 0) - return false; - - return true; -} - -std::vector DB::MySQL::listUsedCurrencies(uint32_t owner) { - Statement list(&connection, selectUsedCurrencies); - list.bind(&owner, MYSQL_TYPE_LONG, true); - list.execute(); - - std::vector> res = list.fetchResult(); - - std::size_t size = res.size(); - std::vector result(size); - for (std::size_t i = 0; i < size; ++i) - result[i].parse(res[i]); - - return result; -} - -DB::Transaction DB::MySQL::addTransaction(const DB::Transaction& transaction) { - MYSQL* con = &connection; - DB::Transaction result = transaction; - - std::string value = std::to_string(result.value); - - Statement add(con, addTransactionQuery); - add.bind(&result.initiator, MYSQL_TYPE_LONG, true); - add.bind(&result.asset, MYSQL_TYPE_LONG, true); - add.bind(&result.parent, MYSQL_TYPE_LONG, true); - add.bind(value.data(), MYSQL_TYPE_STRING); - add.bind(&result.performed, MYSQL_TYPE_LONG, true); - add.execute(); - - result.id = lastInsertedId(); - std::chrono::time_point currently = std::chrono::time_point_cast( - std::chrono::system_clock::now() - ); - result.modified = currently.time_since_epoch().count(); - //todo actual value which could have changed after insertion - - return result; -} - -void DB::MySQL::updateTransaction(const DB::Transaction& transaction) { - MYSQL* con = &connection; - DB::Transaction result = transaction; - - std::string value = std::to_string(result.value); - - Statement upd(con, updateTransactionQuery); - upd.bind(&result.initiator, MYSQL_TYPE_LONG, true); - upd.bind(&result.asset, MYSQL_TYPE_LONG, true); - upd.bind(&result.parent, MYSQL_TYPE_LONG, true); - upd.bind(value.data(), MYSQL_TYPE_STRING); - upd.bind(&result.performed, MYSQL_TYPE_LONG, true); - upd.bind(&result.id, MYSQL_TYPE_LONG, true); - upd.execute(); -} - -bool DB::MySQL::deleteTransaction(uint32_t id, uint32_t actorId) { - MYSQL* con = &connection; - - Statement del(con, deleteTransactionQuery); - del.bind(&id, MYSQL_TYPE_LONG, true); //for actual transactions - del.bind(&id, MYSQL_TYPE_LONG, true); //for potential children - del.bind(&actorId, MYSQL_TYPE_LONG, true); //for preventing unauthorized removal, but it needs to be improved - del.execute(); //need to think of a parent with no children transactions... - - if (del.affectedRows() == 0) - return false; - - return true; -} - -std::vector DB::MySQL::listTransactions(uint32_t owner) { - MYSQL* con = &connection; - - Statement get(con, selectAllTransactions); - get.bind(&owner, MYSQL_TYPE_LONG, true); - get.execute(); - - std::vector> res = get.fetchResult(); - std::size_t size = res.size(); - - std::vector result(size); - for (std::size_t i = 0; i < size; ++i) - result[i].parse(res[i]); - - return result; -} diff --git a/database/mysql/mysql.h b/database/mysql/mysql.h index a739fa6..37181a3 100644 --- a/database/mysql/mysql.h +++ b/database/mysql/mysql.h @@ -1,68 +1,37 @@ -//SPDX-FileCopyrightText: 2023 Yury Gubich -//SPDX-License-Identifier: GPL-3.0-or-later +// SPDX-FileCopyrightText: 2023 Yury Gubich +// SPDX-License-Identifier: GPL-3.0-or-later #pragma once #include #include -#include #include -#include "database/interface.h" +#include "database/dbinterface.h" #include "utils/helpers.h" -namespace DB { -class MySQL : public Interface { +class MySQL : public DBInterface { class Statement; - class Transaction; - public: - MySQL (); - ~MySQL () override; + MySQL(); + ~MySQL() override; - void connect (const std::string& path) override; - void disconnect () override; - void setCredentials (const std::string& login, const std::string& password) override; - void setDatabase (const std::string& database) override; + void connect(const std::string& path) override; + void disconnect() override; + void setCredentials(const std::string& login, const std::string& password) override; + void setDatabase(const std::string& database) override; - void migrate (uint8_t targetVersion) override; - uint8_t getVersion () override; - void setVersion (uint8_t version) override; - - uint32_t registerAccount (const std::string& login, const std::string& hash) override; - std::string getAccountHash (const std::string& login) override; - - Session createSession (const std::string& login, const std::string& access, const std::string& renew) override; - Session findSession (const std::string& accessToken) override; - - std::vector listAssets (uint32_t owner) override; - Asset addAsset (const Asset& asset) override; - void updateAsset (const Asset& asset) override; - bool deleteAsset(uint32_t assetId, uint32_t actorId) override; - - std::vector listUsedCurrencies(uint32_t owner) override; - - DB::Transaction addTransaction(const DB::Transaction& transaction) override; - std::vector listTransactions(uint32_t owner) override; - void updateTransaction(const DB::Transaction& transaction) override; - bool deleteTransaction(uint32_t id, uint32_t actorId) override; + void migrate(uint8_t targetVersion) override; + uint8_t getVersion() override; + void setVersion(uint8_t version) override; private: - void executeFile (const std::filesystem::path& relativePath); - bool getBlock (std::ifstream& file, std::string& block, std::string& name); - uint32_t lastInsertedId (); + void executeFile(const std::filesystem::path& relativePath); protected: MYSQL connection; std::string login; std::string password; std::string database; - -struct ResDeleter { - void operator () (MYSQL_RES* res) { - mysql_free_result(res); - } }; -}; -} diff --git a/database/mysql/statement.cpp b/database/mysql/statement.cpp index 7b6a93d..44c7592 100644 --- a/database/mysql/statement.cpp +++ b/database/mysql/statement.cpp @@ -1,24 +1,23 @@ -//SPDX-FileCopyrightText: 2023 Yury Gubich -//SPDX-License-Identifier: GPL-3.0-or-later +// SPDX-FileCopyrightText: 2023 Yury Gubich +// SPDX-License-Identifier: GPL-3.0-or-later #include "statement.h" -#include "mysqld_error.h" - -#include "database/exceptions.h" +#include static uint64_t TIME_LENGTH = sizeof(MYSQL_TIME); -DB::MySQL::Statement::Statement(MYSQL* connection, const char* statement): +MySQL::Statement::Statement(MYSQL* connection, const char* statement): stmt(mysql_stmt_init(connection)), - param() + param(), + lengths() { int result = mysql_stmt_prepare(stmt.get(), statement, strlen(statement)); if (result != 0) throw std::runtime_error(std::string("Error preparing statement: ") + mysql_stmt_error(stmt.get())); } -void DB::MySQL::Statement::bind(void* value, enum_field_types type, bool usigned) { +void MySQL::Statement::bind(void* value, enum_field_types type) { MYSQL_BIND& result = param.emplace_back(); std::memset(&result, 0, sizeof(result)); @@ -28,135 +27,24 @@ void DB::MySQL::Statement::bind(void* value, enum_field_types type, bool usigned switch (type) { case MYSQL_TYPE_STRING: case MYSQL_TYPE_VAR_STRING: - result.buffer_length = strlen(static_cast(value)); + result.length = &lengths.emplace_back(strlen(static_cast(value))); break; case MYSQL_TYPE_DATE: - result.buffer_length = TIME_LENGTH; - break; - case MYSQL_TYPE_LONG: - case MYSQL_TYPE_LONGLONG: - case MYSQL_TYPE_SHORT: - case MYSQL_TYPE_TINY: - result.is_unsigned = usigned; + result.length = &TIME_LENGTH; break; default: + lengths.pop_back(); throw std::runtime_error("Type: " + std::to_string(type) + " is not yet supported in bind"); break; } } -void DB::MySQL::Statement::execute() { - MYSQL_STMT* raw = stmt.get(); - int result = mysql_stmt_bind_param(raw, param.data()); +void MySQL::Statement::execute() { + int result = mysql_stmt_bind_param(stmt.get(), param.data()); if (result != 0) - throw std::runtime_error(std::string("Error binding statement: ") + mysql_stmt_error(raw)); + throw std::runtime_error(std::string("Error binding statement: ") + mysql_stmt_error(stmt.get())); - result = mysql_stmt_execute(raw); - if (result != 0) { - int errcode = mysql_stmt_errno(raw); - std::string text = mysql_stmt_error(raw); - switch (errcode) { - case ER_DUP_ENTRY: - throw Duplicate("Error executing statement: " + text); - default: - throw std::runtime_error("Error executing statement: " + text); - } - } -} - -std::vector> DB::MySQL::Statement::fetchResult() { - MYSQL_STMT* raw = stmt.get(); - if (mysql_stmt_store_result(raw) != 0) - throw std::runtime_error(std::string("Error fetching statement result: ") + mysql_stmt_error(raw)); //TODO not sure if it's valid here - - MYSQL_RES* meta = mysql_stmt_result_metadata(raw); - if (meta == nullptr) - throw std::runtime_error(std::string("Error fetching statement result: ") + mysql_stmt_error(raw)); //TODO not sure if it's valid here - - std::unique_ptr mt(meta); - unsigned int numColumns = mysql_num_fields(meta); - MYSQL_BIND bind[numColumns]; - std::memset(bind, 0, sizeof(bind)); - - std::vector line(numColumns); - std::vector lengths(numColumns); - for (unsigned int i = 0; i < numColumns; ++i) { - MYSQL_FIELD *field = mysql_fetch_field_direct(meta, i); - - switch (field->type) { - case MYSQL_TYPE_STRING: - case MYSQL_TYPE_VAR_STRING: - case MYSQL_TYPE_VARCHAR: { - line[i] = std::string(); - std::string& str = std::any_cast(line[i]); - str.resize(field->length); - bind[i].buffer = str.data(); - } break; - case MYSQL_TYPE_TINY: - line[i] = uint8_t{0}; - bind[i].buffer = &std::any_cast(line[i]); - break; - case MYSQL_TYPE_SHORT: - line[i] = uint16_t{0}; - bind[i].buffer = &std::any_cast(line[i]); - break; - case MYSQL_TYPE_LONG: - line[i] = uint32_t{0}; - bind[i].buffer = &std::any_cast(line[i]); - break; - case MYSQL_TYPE_LONGLONG: - line[i] = uint64_t{0}; - bind[i].buffer = &std::any_cast(line[i]); - break; - default: - throw std::runtime_error("Unsupported data fetching statement result " + std::to_string(field->type)); - } - bind[i].buffer_type = field->type; - bind[i].buffer_length = field->length; - bind[i].length = &lengths[i]; - if (field->flags & UNSIGNED_FLAG) - bind[i].is_unsigned = 1; - } - - if (mysql_stmt_bind_result(raw, bind) != 0) - throw std::runtime_error(std::string("Error binding on fetching statement result: ") + mysql_stmt_error(raw)); - - std::vector> result; - int rc; - while ((rc = mysql_stmt_fetch(raw)) == 0) { - std::vector& row = result.emplace_back(numColumns); - for (unsigned int i = 0; i < numColumns; ++i) { - switch (bind[i].buffer_type) { - case MYSQL_TYPE_STRING: - case MYSQL_TYPE_VAR_STRING: - case MYSQL_TYPE_VARCHAR: { - row[i] = std::string(std::any_cast(line[i]).data(), lengths[i]); - } break; - case MYSQL_TYPE_TINY: - row[i] = std::any_cast(line[i]); - break; - case MYSQL_TYPE_SHORT: - row[i] = std::any_cast(line[i]); - break; - case MYSQL_TYPE_LONG: - row[i] = std::any_cast(line[i]); - break; - case MYSQL_TYPE_LONGLONG: - row[i] = std::any_cast(line[i]); - break; - default: - throw std::runtime_error("Unsupported data fetching statement result " + std::to_string(bind[i].buffer_type)); - } - } - } - if (rc == 1) - throw std::runtime_error(std::string("Error occured fetching data ") + mysql_stmt_error(raw)); - else if (rc == MYSQL_DATA_TRUNCATED) - throw std::runtime_error("Data has been truncated"); - - return result; -} - -unsigned int DB::MySQL::Statement::affectedRows () { - return mysql_stmt_affected_rows(stmt.get()); + result = mysql_stmt_execute(stmt.get()); + if (result != 0) + throw std::runtime_error(std::string("Error executing statement: ") + mysql_stmt_error(stmt.get())); } diff --git a/database/mysql/statement.h b/database/mysql/statement.h index 7dee3c7..9cfc129 100644 --- a/database/mysql/statement.h +++ b/database/mysql/statement.h @@ -1,16 +1,13 @@ -//SPDX-FileCopyrightText: 2023 Yury Gubich -//SPDX-License-Identifier: GPL-3.0-or-later +// SPDX-FileCopyrightText: 2023 Yury Gubich +// SPDX-License-Identifier: GPL-3.0-or-later #pragma once -#include #include -#include -#include #include "mysql.h" -namespace DB { + class MySQL::Statement { struct STMTDeleter { void operator () (MYSQL_STMT* stmt) { @@ -20,13 +17,11 @@ class MySQL::Statement { public: Statement(MYSQL* connection, const char* statement); - void bind(void* value, enum_field_types type, bool usigned = false); + void bind(void* value, enum_field_types type); void execute(); - unsigned int affectedRows(); - std::vector> fetchResult(); private: std::unique_ptr stmt; std::vector param; + std::vector lengths; }; -} diff --git a/database/mysql/transaction.cpp b/database/mysql/transaction.cpp deleted file mode 100644 index e280fe6..0000000 --- a/database/mysql/transaction.cpp +++ /dev/null @@ -1,37 +0,0 @@ -//SPDX-FileCopyrightText: 2023 Yury Gubich -//SPDX-License-Identifier: GPL-3.0-or-later - -#include "transaction.h" - -DB::MySQL::Transaction::Transaction(MYSQL* connection): - con(connection), - opened(false) -{ - if (mysql_autocommit(con, 0) != 0) - throw std::runtime_error(std::string("Failed to start transaction") + mysql_error(con)); - - opened = true; -} - -DB::MySQL::Transaction::~Transaction() { - if (opened) - abort(); -} - -void DB::MySQL::Transaction::commit() { - if (mysql_commit(con) != 0) - throw std::runtime_error(std::string("Failed to commit transaction") + mysql_error(con)); - - opened = false; - if (mysql_autocommit(con, 1) != 0) - throw std::runtime_error(std::string("Failed to return autocommit") + mysql_error(con)); -} - -void DB::MySQL::Transaction::abort() { - opened = false; - if (mysql_rollback(con) != 0) - throw std::runtime_error(std::string("Failed to rollback transaction") + mysql_error(con)); - - if (mysql_autocommit(con, 1) != 0) - throw std::runtime_error(std::string("Failed to return autocommit") + mysql_error(con)); -} diff --git a/database/mysql/transaction.h b/database/mysql/transaction.h deleted file mode 100644 index aa972e6..0000000 --- a/database/mysql/transaction.h +++ /dev/null @@ -1,21 +0,0 @@ -//SPDX-FileCopyrightText: 2023 Yury Gubich -//SPDX-License-Identifier: GPL-3.0-or-later - -#pragma once - -#include "mysql.h" - -namespace DB { -class MySQL::Transaction { -public: - Transaction(MYSQL* connection); - ~Transaction(); - - void commit(); - void abort(); - -private: - MYSQL* con; - bool opened; -}; -} diff --git a/database/pool.cpp b/database/pool.cpp deleted file mode 100644 index 644fee1..0000000 --- a/database/pool.cpp +++ /dev/null @@ -1,57 +0,0 @@ -//SPDX-FileCopyrightText: 2023 Yury Gubich -//SPDX-License-Identifier: GPL-3.0-or-later - -#include "pool.h" - -DB::Pool::Pool (Private): - std::enable_shared_from_this(), - mutex(), - conditional(), - interfaces() -{} - -DB::Pool::~Pool () { -} - -std::shared_ptr DB::Pool::create () { - return std::make_shared(Private()); -} - -void DB::Pool::addInterfaces ( - Interface::Type type, - std::size_t amount, - const std::string & login, - const std::string & password, - const std::string & database, - const std::string& path -) { - std::unique_lock lock(mutex); - for (std::size_t i = 0; i < amount; ++i) { - const std::unique_ptr& ref = interfaces.emplace(Interface::create(type)); - ref->setCredentials(login, password); - ref->setDatabase(database); - ref->connect(path); - } - - lock.unlock(); - conditional.notify_all(); -} - -DB::Resource DB::Pool::request () { - std::unique_lock lock(mutex); - while (interfaces.empty()) - conditional.wait(lock); - - std::unique_ptr interface = std::move(interfaces.front()); - interfaces.pop(); - return Resource(std::move(interface), shared_from_this()); -} - -void DB::Pool::free (std::unique_ptr interface) { - std::unique_lock lock(mutex); - - interfaces.push(std::move(interface)); - - lock.unlock(); - conditional.notify_one(); -} diff --git a/database/pool.h b/database/pool.h deleted file mode 100644 index 8874f1f..0000000 --- a/database/pool.h +++ /dev/null @@ -1,47 +0,0 @@ -//SPDX-FileCopyrightText: 2023 Yury Gubich -//SPDX-License-Identifier: GPL-3.0-or-later - -#pragma once - -#include -#include -#include -#include -#include - -#include "interface.h" -#include "resource.h" - -namespace DB { -class Pool : public std::enable_shared_from_this { - struct Private {}; - friend class Resource; - - void free(std::unique_ptr interface); - -public: - Pool(Private); - Pool(const Pool&) = delete; - Pool(Pool&&) = delete; - ~Pool(); - Pool& operator = (const Pool&) = delete; - Pool& operator = (Pool&&) = delete; - - static std::shared_ptr create(); - Resource request(); - void addInterfaces( - Interface::Type type, - std::size_t amount, - const std::string& login, - const std::string& password, - const std::string& database, - const std::string& path - ); - -private: - std::mutex mutex; - std::condition_variable conditional; - std::queue> interfaces; - -}; -} diff --git a/database/resource.cpp b/database/resource.cpp deleted file mode 100644 index bfccae7..0000000 --- a/database/resource.cpp +++ /dev/null @@ -1,38 +0,0 @@ -//SPDX-FileCopyrightText: 2023 Yury Gubich -//SPDX-License-Identifier: GPL-3.0-or-later - -#include "resource.h" - -#include "pool.h" - -DB::Resource::Resource ( - std::unique_ptr interface, - std::weak_ptr parent -): - parent(parent), - interface(std::move(interface)) -{} - -DB::Resource::Resource(Resource&& other): - parent(other.parent), - interface(std::move(other.interface)) -{} - -DB::Resource::~Resource() { - if (!interface) - return; - - if (std::shared_ptr p = parent.lock()) - p->free(std::move(interface)); -} - -DB::Resource& DB::Resource::operator = (Resource&& other) { - parent = other.parent; - interface = std::move(other.interface); - - return *this; -} - -DB::Interface* DB::Resource::operator -> () { - return interface.get(); -} diff --git a/database/resource.h b/database/resource.h deleted file mode 100644 index 6e9d4fa..0000000 --- a/database/resource.h +++ /dev/null @@ -1,31 +0,0 @@ -//SPDX-FileCopyrightText: 2023 Yury Gubich -//SPDX-License-Identifier: GPL-3.0-or-later - -#pragma once - -#include - -#include "interface.h" - -namespace DB { -class Pool; - -class Resource { - friend class Pool; - Resource(std::unique_ptr interface, std::weak_ptr parent); - -public: - Resource(const Resource&) = delete; - Resource(Resource&& other); - ~Resource(); - - Resource& operator = (const Resource&) = delete; - Resource& operator = (Resource&& other); - - Interface* operator -> (); - -private: - std::weak_ptr parent; - std::unique_ptr interface; -}; -} diff --git a/database/schema/CMakeLists.txt b/database/schema/CMakeLists.txt deleted file mode 100644 index 776b4fb..0000000 --- a/database/schema/CMakeLists.txt +++ /dev/null @@ -1,18 +0,0 @@ -#SPDX-FileCopyrightText: 2023 Yury Gubich -#SPDX-License-Identifier: GPL-3.0-or-later - -set(HEADERS - session.h - asset.h - currency.h - transaction.h -) - -set(SOURCES - session.cpp - asset.cpp - currency.cpp - transaction.cpp -) - -target_sources(${PROJECT_NAME} PRIVATE ${SOURCES}) diff --git a/database/schema/asset.cpp b/database/schema/asset.cpp deleted file mode 100644 index 6187908..0000000 --- a/database/schema/asset.cpp +++ /dev/null @@ -1,48 +0,0 @@ -//SPDX-FileCopyrightText: 2024 Yury Gubich -//SPDX-License-Identifier: GPL-3.0-or-later - -#include "asset.h" - -DB::Asset::Asset (): - id(0), - owner(0), - currency(0), - title(), - icon(), - color(0), - archived(false) -{} - -DB::Asset::Asset (const std::vector& vec): - id(std::any_cast(vec[0])), - owner(std::any_cast(vec[1])), - currency(std::any_cast(vec[2])), - title(std::any_cast(vec[3])), - icon(std::any_cast(vec[4])), - color(std::any_cast(vec[5])), - archived(std::any_cast(vec[6])) -{} - -void DB::Asset::parse (const std::vector& vec) { - id = std::any_cast(vec[0]); - owner = std::any_cast(vec[1]); - currency = std::any_cast(vec[2]); - title = std::any_cast(vec[3]); - icon = std::any_cast(vec[4]); - color = std::any_cast(vec[5]); - archived = std::any_cast(vec[6]); -} - -nlohmann::json DB::Asset::toJSON () const { - nlohmann::json result = nlohmann::json::object(); - - result["id"] = id; - //result["owner"] = owner; - result["currency"] = currency; - result["title"] = title; - result["icon"] = icon; - result["color"] = color; - result["archived"] = archived; - - return result; -} diff --git a/database/schema/asset.h b/database/schema/asset.h deleted file mode 100644 index 83d513e..0000000 --- a/database/schema/asset.h +++ /dev/null @@ -1,33 +0,0 @@ -//SPDX-FileCopyrightText: 2024 Yury Gubich -//SPDX-License-Identifier: GPL-3.0-or-later - -#pragma once - -#include -#include -#include -#include - -#include - -namespace DB { -class Asset { -public: - Asset (); - Asset (const std::vector& vec); - - void parse (const std::vector& vec); - nlohmann::json toJSON () const; - -public: - uint32_t id; - uint32_t owner; - uint32_t currency; - std::string title; - std::string icon; - uint32_t color; - // `balance` DECIMAL (20, 5) DEFAULT 0, - // `type` INTEGER UNSIGNED NOT NULL, - bool archived; -}; -} diff --git a/database/schema/currency.cpp b/database/schema/currency.cpp deleted file mode 100644 index fb47480..0000000 --- a/database/schema/currency.cpp +++ /dev/null @@ -1,40 +0,0 @@ -//SPDX-FileCopyrightText: 2024 Yury Gubich -//SPDX-License-Identifier: GPL-3.0-or-later - -#include "currency.h" - -DB::Currency::Currency (): - id(0), - code(), - title(), - manual(false), - icon() -{} - -DB::Currency::Currency (const std::vector& vec): - id(std::any_cast(vec[0])), - code(std::any_cast(vec[1])), - title(std::any_cast(vec[2])), - manual(std::any_cast(vec[3])), - icon(std::any_cast(vec[4])) -{} - -void DB::Currency::parse (const std::vector& vec) { - id = std::any_cast(vec[0]); - code = std::any_cast(vec[1]); - title = std::any_cast(vec[2]); - manual = std::any_cast(vec[3]); - icon = std::any_cast(vec[4]); -} - -nlohmann::json DB::Currency::toJSON () const { - nlohmann::json result = nlohmann::json::object(); - - result["id"] = id; - result["code"] = code; - result["title"] = title; - result["manual"] = manual; - result["icon"] = icon; - - return result; -} diff --git a/database/schema/currency.h b/database/schema/currency.h deleted file mode 100644 index 0d88703..0000000 --- a/database/schema/currency.h +++ /dev/null @@ -1,35 +0,0 @@ -//SPDX-FileCopyrightText: 2024 Yury Gubich -//SPDX-License-Identifier: GPL-3.0-or-later - -#pragma once - -#include -#include -#include -#include - -#include - -namespace DB { -class Currency { -public: - Currency (); - Currency (const std::vector& vec); - - void parse (const std::vector& vec); - nlohmann::json toJSON () const; - -public: - uint32_t id; - std::string code; - std::string title; - bool manual; - // `added` TIMESTAMP DEFAULT UTC_TIMESTAMP(), - // `type` INTEGER UNSIGNED NOT NULL, - // `value` DECIMAL (20, 5) NOT NULL, - // `source` TEXT, - // `description` TEXT, - std::string icon; - -}; -} diff --git a/database/schema/session.cpp b/database/schema/session.cpp deleted file mode 100644 index 7bedaa5..0000000 --- a/database/schema/session.cpp +++ /dev/null @@ -1,18 +0,0 @@ -//SPDX-FileCopyrightText: 2024 Yury Gubich -//SPDX-License-Identifier: GPL-3.0-or-later - -#include "session.h" - -DB::Session::Session (): - id(), - owner(), - accessToken(), - renewToken() -{} - -DB::Session::Session (const std::vector& vec): - id(std::any_cast(vec[0])), - owner(std::any_cast(vec[1])), - accessToken(std::any_cast(vec[2])), - renewToken(std::any_cast(vec[3])) -{} diff --git a/database/schema/session.h b/database/schema/session.h deleted file mode 100644 index 55615c3..0000000 --- a/database/schema/session.h +++ /dev/null @@ -1,23 +0,0 @@ -//SPDX-FileCopyrightText: 2024 Yury Gubich -//SPDX-License-Identifier: GPL-3.0-or-later - -#pragma once - -#include -#include -#include -#include - -namespace DB { -class Session { -public: - Session (); - Session (const std::vector& vec); - -public: - unsigned int id; - unsigned int owner; - std::string accessToken; - std::string renewToken; -}; -} diff --git a/database/schema/transaction.cpp b/database/schema/transaction.cpp deleted file mode 100644 index 9d90429..0000000 --- a/database/schema/transaction.cpp +++ /dev/null @@ -1,50 +0,0 @@ -//SPDX-FileCopyrightText: 2024 Yury Gubich -//SPDX-License-Identifier: GPL-3.0-or-later - -#include "transaction.h" - -DB::Transaction::Transaction(): - id(0), - initiator(0), - asset(0), - parent(0), - value(0), - modified(0), - performed(0), - notes() -{} - -DB::Transaction::Transaction(const std::vector& vec): - id(std::any_cast(vec[0])), - initiator(std::any_cast(vec[1])), - asset(std::any_cast(vec[2])), - parent(std::any_cast(vec[3])), - value(std::any_cast(vec[4])), - modified(std::any_cast(vec[5])), - performed(std::any_cast(vec[6])), - notes() -{} - -void DB::Transaction::parse(const std::vector& vec) { - id = std::any_cast(vec[0]); - initiator = std::any_cast(vec[1]); - asset = std::any_cast(vec[2]); - parent = std::any_cast(vec[3]); - value = std::any_cast(vec[4]); - modified = std::any_cast(vec[5]); - performed = std::any_cast(vec[6]); -} - -nlohmann::json DB::Transaction::toJSON() const { - nlohmann::json result = nlohmann::json::object(); - - result["id"] = id; - result["initiator"] = initiator; - result["asset"] = asset; - result["parent"] = parent; - result["value"] = value; - result["modified"] = modified; - result["performed"] = performed; - - return result; -} diff --git a/database/schema/transaction.h b/database/schema/transaction.h deleted file mode 100644 index 754f0eb..0000000 --- a/database/schema/transaction.h +++ /dev/null @@ -1,35 +0,0 @@ -//SPDX-FileCopyrightText: 2024 Yury Gubich -//SPDX-License-Identifier: GPL-3.0-or-later - -#pragma once - -#include -#include -#include -#include - -#include - -namespace DB { -class Transaction { -public: - Transaction (); - Transaction (const std::vector& vec); - - void parse (const std::vector& vec); - nlohmann::json toJSON () const; - -public: - uint32_t id; - uint32_t initiator; - // `type` INTEGER UNSIGNED NOT NULL, - uint32_t asset; - uint32_t parent; - double value; - // `state` INTEGER UNSIGNED DEFAULT 0, - uint32_t modified; - uint32_t performed; - // `party` INTEGER UNSIGNED, - std::string notes; -}; -} \ No newline at end of file diff --git a/handler/CMakeLists.txt b/handler/CMakeLists.txt deleted file mode 100644 index 1971c0e..0000000 --- a/handler/CMakeLists.txt +++ /dev/null @@ -1,40 +0,0 @@ -#SPDX-FileCopyrightText: 2023 Yury Gubich -#SPDX-License-Identifier: GPL-3.0-or-later - -set(HEADERS - handler.h - info.h - env.h - register.h - login.h - poll.h - assets.h - addasset.h - deleteasset.h - updateasset.h - currencies.h - addtransaction.h - transactions.h - deletetransaction.h - updatetransaction.h -) - -set(SOURCES - handler.cpp - info.cpp - env.cpp - register.cpp - login.cpp - poll.cpp - assets.cpp - addasset.cpp - deleteasset.cpp - updateasset.cpp - currencies.cpp - addtransaction.cpp - transactions.cpp - deletetransaction.cpp - updatetransaction.cpp -) - -target_sources(${PROJECT_NAME} PRIVATE ${SOURCES}) diff --git a/handler/addasset.cpp b/handler/addasset.cpp deleted file mode 100644 index 99a7bf3..0000000 --- a/handler/addasset.cpp +++ /dev/null @@ -1,78 +0,0 @@ -//SPDX-FileCopyrightText: 2024 Yury Gubich -//SPDX-License-Identifier: GPL-3.0-or-later - -#include "addasset.h" - -#include - -#include "server/server.h" -#include "server/session.h" -#include "database/exceptions.h" - -Handler::AddAsset::AddAsset (const std::shared_ptr& server): - Handler("addAsset", Request::Method::post), - server(server) -{} - -void Handler::AddAsset::handle (Request& request) { - std::string access = request.getAuthorizationToken(); - if (access.empty()) - return error(request, Response::Status::unauthorized); - - if (access.size() != 32) - return error(request, Response::Status::badRequest); - - std::shared_ptr srv = server.lock(); - if (!srv) - return error(request, Response::Status::internalError); - - std::map form = request.getForm(); - std::map::const_iterator itr = form.find("currency"); - if (itr == form.end()) - return error(request, Response::Status::badRequest); - - DB::Asset asset; - asset.currency = std::stoul(itr->second); - //TODO validate the currency - - itr = form.find("title"); - if (itr == form.end()) - return error(request, Response::Status::badRequest); - - asset.title = itr->second; - - itr = form.find("icon"); - if (itr == form.end()) - return error(request, Response::Status::badRequest); - - asset.icon = itr->second; - - try { - itr = form.find("color"); - if (itr != form.end()) - asset.color = std::stoul(itr->second); - } catch (const std::exception& e) { - std::cerr << "Insignificant error parsing color during asset addition: " << e.what() << std::endl; - } - - try { - Session& session = srv->getSession(access); - - asset.owner = session.owner; - asset = srv->getDatabase()->addAsset(asset); - - Response& res = request.createResponse(Response::Status::ok); - res.send(); - - session.assetAdded(asset); - - } catch (const DB::NoSession& e) { - return error(request, Response::Status::unauthorized); - } catch (const std::exception& e) { - std::cerr << "Exception on " << path << ":\n\t" << e.what() << std::endl; - return error(request, Response::Status::internalError); - } catch (...) { - std::cerr << "Unknown exception on " << path << std::endl; - return error(request, Response::Status::internalError); - } -} diff --git a/handler/addasset.h b/handler/addasset.h deleted file mode 100644 index 8d4719e..0000000 --- a/handler/addasset.h +++ /dev/null @@ -1,20 +0,0 @@ -//SPDX-FileCopyrightText: 2024 Yury Gubich -//SPDX-License-Identifier: GPL-3.0-or-later - -#pragma once - -#include - -#include "handler.h" - -class Server; -namespace Handler { -class AddAsset : public Handler { -public: - AddAsset (const std::shared_ptr& server); - virtual void handle (Request& request) override; - -private: - std::weak_ptr server; -}; -} diff --git a/handler/addtransaction.cpp b/handler/addtransaction.cpp deleted file mode 100644 index 7c6ebee..0000000 --- a/handler/addtransaction.cpp +++ /dev/null @@ -1,78 +0,0 @@ -//SPDX-FileCopyrightText: 2024 Yury Gubich -//SPDX-License-Identifier: GPL-3.0-or-later - -#include "addtransaction.h" - -#include - -#include "server/server.h" -#include "server/session.h" -#include "database/exceptions.h" - -Handler::AddTransaction::AddTransaction (const std::shared_ptr& server): - Handler("addTransaction", Request::Method::post), - server(server) -{} - -void Handler::AddTransaction::handle (Request& request) { - std::string access = request.getAuthorizationToken(); - if (access.empty()) - return error(request, Response::Status::unauthorized); - - if (access.size() != 32) - return error(request, Response::Status::badRequest); - - std::shared_ptr srv = server.lock(); - if (!srv) - return error(request, Response::Status::internalError); - - std::map form = request.getForm(); - std::map::const_iterator itr = form.find("asset"); - if (itr == form.end()) - return error(request, Response::Status::badRequest); - - DB::Transaction txn; - txn.asset = std::stoul(itr->second); - //TODO validate the asset - - itr = form.find("value"); - if (itr == form.end()) - return error(request, Response::Status::badRequest); - - txn.value = std::stod(itr->second); - - itr = form.find("performed"); - if (itr == form.end()) - return error(request, Response::Status::badRequest); - - txn.performed = std::stoul(itr->second); - - itr = form.find("notes"); - if (itr != form.end()) - txn.notes = itr->second; - - itr = form.find("parent"); - if (itr != form.end()) - txn.parent = std::stoul(itr->second); - - try { - Session& session = srv->getSession(access); - - txn.initiator = session.owner; - txn = srv->getDatabase()->addTransaction(txn); - - Response& res = request.createResponse(Response::Status::ok); - res.send(); - - session.transactionAdded(txn); - - } catch (const DB::NoSession& e) { - return error(request, Response::Status::unauthorized); - } catch (const std::exception& e) { - std::cerr << "Exception on " << path << ":\n\t" << e.what() << std::endl; - return error(request, Response::Status::internalError); - } catch (...) { - std::cerr << "Unknown exception on " << path << std::endl; - return error(request, Response::Status::internalError); - } -} diff --git a/handler/addtransaction.h b/handler/addtransaction.h deleted file mode 100644 index b23e167..0000000 --- a/handler/addtransaction.h +++ /dev/null @@ -1,20 +0,0 @@ -//SPDX-FileCopyrightText: 2024 Yury Gubich -//SPDX-License-Identifier: GPL-3.0-or-later - -#pragma once - -#include - -#include "handler.h" - -class Server; -namespace Handler { -class AddTransaction : public Handler { -public: - AddTransaction (const std::shared_ptr& server); - virtual void handle (Request& request) override; - -private: - std::weak_ptr server; -}; -} diff --git a/handler/assets.cpp b/handler/assets.cpp deleted file mode 100644 index 1c89118..0000000 --- a/handler/assets.cpp +++ /dev/null @@ -1,51 +0,0 @@ -//SPDX-FileCopyrightText: 2024 Yury Gubich -//SPDX-License-Identifier: GPL-3.0-or-later - -#include "assets.h" - -#include "server/server.h" -#include "server/session.h" -#include "database/exceptions.h" - -Handler::Assets::Assets (const std::shared_ptr& server): - Handler("assets", Request::Method::get), - server(server) -{} - -void Handler::Assets::handle (Request& request) { - std::string access = request.getAuthorizationToken(); - if (access.empty()) - return error(request, Response::Status::unauthorized); - - if (access.size() != 32) - return error(request, Response::Status::badRequest); - - std::shared_ptr srv = server.lock(); - if (!srv) - return error(request, Response::Status::internalError); - - try { - Session& session = srv->getSession(access); - std::vector assets = srv->getDatabase()->listAssets(session.owner); - - nlohmann::json arr = nlohmann::json::array(); - for (const DB::Asset& asset : assets) - arr.push_back(asset.toJSON()); - - nlohmann::json body = nlohmann::json::object(); - body["assets"] = arr; - - Response& res = request.createResponse(Response::Status::ok); - res.setBody(body); - res.send(); - - } catch (const DB::NoSession& e) { - return error(request, Response::Status::unauthorized); - } catch (const std::exception& e) { - std::cerr << "Exception on " << path << ":\n\t" << e.what() << std::endl; - return error(request, Response::Status::internalError); - } catch (...) { - std::cerr << "Unknown exception on " << path << std::endl; - return error(request, Response::Status::internalError); - } -} diff --git a/handler/assets.h b/handler/assets.h deleted file mode 100644 index a1e19e8..0000000 --- a/handler/assets.h +++ /dev/null @@ -1,20 +0,0 @@ -//SPDX-FileCopyrightText: 2024 Yury Gubich -//SPDX-License-Identifier: GPL-3.0-or-later - -#pragma once - -#include - -#include "handler.h" - -class Server; -namespace Handler { -class Assets : public Handler::Handler { -public: - Assets (const std::shared_ptr& server); - void handle (Request& request) override; - -private: - std::weak_ptr server; -}; -} diff --git a/handler/currencies.cpp b/handler/currencies.cpp deleted file mode 100644 index 1c43c2a..0000000 --- a/handler/currencies.cpp +++ /dev/null @@ -1,51 +0,0 @@ -//SPDX-FileCopyrightText: 2024 Yury Gubich -//SPDX-License-Identifier: GPL-3.0-or-later - -#include "currencies.h" - -#include "server/server.h" -#include "server/session.h" -#include "database/exceptions.h" - -Handler::Currencies::Currencies (const std::shared_ptr& server): - Handler("currencies", Request::Method::get), - server(server) -{} - -void Handler::Currencies::handle (Request& request) { - std::string access = request.getAuthorizationToken(); - if (access.empty()) - return error(request, Response::Status::unauthorized); - - if (access.size() != 32) - return error(request, Response::Status::badRequest); - - std::shared_ptr srv = server.lock(); - if (!srv) - return error(request, Response::Status::internalError); - - try { - Session& session = srv->getSession(access); - std::vector cur = srv->getDatabase()->listUsedCurrencies(session.owner); - - nlohmann::json arr = nlohmann::json::array(); - for (const DB::Currency& c : cur) - arr.push_back(c.toJSON()); - - nlohmann::json body = nlohmann::json::object(); - body["currencies"] = arr; - - Response& res = request.createResponse(Response::Status::ok); - res.setBody(body); - res.send(); - - } catch (const DB::NoSession& e) { - return error(request, Response::Status::unauthorized); - } catch (const std::exception& e) { - std::cerr << "Exception on " << path << ":\n\t" << e.what() << std::endl; - return error(request, Response::Status::internalError); - } catch (...) { - std::cerr << "Unknown exception on " << path << std::endl; - return error(request, Response::Status::internalError); - } -} diff --git a/handler/currencies.h b/handler/currencies.h deleted file mode 100644 index 69c40c6..0000000 --- a/handler/currencies.h +++ /dev/null @@ -1,21 +0,0 @@ -//SPDX-FileCopyrightText: 2024 Yury Gubich -//SPDX-License-Identifier: GPL-3.0-or-later - -#pragma once - -#include - -#include "handler.h" - -class Server; -namespace Handler { -class Currencies : public Handler { -public: - Currencies(const std::shared_ptr& server); - - void handle (Request& request) override; - -private: - std::weak_ptr server; -}; -} diff --git a/handler/deleteasset.cpp b/handler/deleteasset.cpp deleted file mode 100644 index 9d62463..0000000 --- a/handler/deleteasset.cpp +++ /dev/null @@ -1,59 +0,0 @@ -//SPDX-FileCopyrightText: 2024 Yury Gubich -//SPDX-License-Identifier: GPL-3.0-or-later - -#include "deleteasset.h" - -#include "server/server.h" -#include "server/session.h" -#include "database/exceptions.h" - -Handler::DeleteAsset::DeleteAsset (const std::shared_ptr& server): - Handler("deleteAsset", Request::Method::post), - server(server) -{} - -void Handler::DeleteAsset::handle (Request& request) { - std::string access = request.getAuthorizationToken(); - if (access.empty()) - return error(request, Response::Status::unauthorized); - - if (access.size() != 32) - return error(request, Response::Status::badRequest); - - std::shared_ptr srv = server.lock(); - if (!srv) - return error(request, Response::Status::internalError); - - std::map form = request.getForm(); - std::map::const_iterator itr = form.find("id"); - if (itr == form.end()) - return error(request, Response::Status::badRequest); - - unsigned int assetId; - try { - assetId = std::stoul(itr->second); - } catch (const std::exception& e) { - return error(request, Response::Status::badRequest); - } - - try { - Session& session = srv->getSession(access); - bool success = srv->getDatabase()->deleteAsset(assetId, session.owner); - if (!success) - return error(request, Response::Status::forbidden); - - Response& res = request.createResponse(Response::Status::ok); - res.send(); - - session.assetRemoved(assetId); - - } catch (const DB::NoSession& e) { - return error(request, Response::Status::unauthorized); - } catch (const std::exception& e) { - std::cerr << "Exception on " << path << ":\n\t" << e.what() << std::endl; - return error(request, Response::Status::internalError); - } catch (...) { - std::cerr << "Unknown exception on " << path << std::endl; - return error(request, Response::Status::internalError); - } -} diff --git a/handler/deleteasset.h b/handler/deleteasset.h deleted file mode 100644 index f1c41ba..0000000 --- a/handler/deleteasset.h +++ /dev/null @@ -1,21 +0,0 @@ -//SPDX-FileCopyrightText: 2024 Yury Gubich -//SPDX-License-Identifier: GPL-3.0-or-later - -#pragma once - -#include - -#include "handler.h" - -class Server; -namespace Handler { -class DeleteAsset : public Handler { -public: - DeleteAsset (const std::shared_ptr& server); - - virtual void handle (Request& request) override; - -private: - std::weak_ptr server; -}; -} diff --git a/handler/deletetransaction.cpp b/handler/deletetransaction.cpp deleted file mode 100644 index 6d73529..0000000 --- a/handler/deletetransaction.cpp +++ /dev/null @@ -1,59 +0,0 @@ -//SPDX-FileCopyrightText: 2024 Yury Gubich -//SPDX-License-Identifier: GPL-3.0-or-later - -#include "deletetransaction.h" - -#include "server/server.h" -#include "server/session.h" -#include "database/exceptions.h" - -Handler::DeleteTransaction::DeleteTransaction (const std::shared_ptr& server): - Handler("deleteTransaction", Request::Method::post), - server(server) -{} - -void Handler::DeleteTransaction::handle (Request& request) { - std::string access = request.getAuthorizationToken(); - if (access.empty()) - return error(request, Response::Status::unauthorized); - - if (access.size() != 32) - return error(request, Response::Status::badRequest); - - std::shared_ptr srv = server.lock(); - if (!srv) - return error(request, Response::Status::internalError); - - std::map form = request.getForm(); - std::map::const_iterator itr = form.find("id"); - if (itr == form.end()) - return error(request, Response::Status::badRequest); - - unsigned int txnId; - try { - txnId = std::stoul(itr->second); - } catch (const std::exception& e) { - return error(request, Response::Status::badRequest); - } - - try { - Session& session = srv->getSession(access); - bool success = srv->getDatabase()->deleteTransaction(txnId, session.owner); - if (!success) - return error(request, Response::Status::forbidden); - - Response& res = request.createResponse(Response::Status::ok); - res.send(); - - session.transactionRemoved(txnId); - - } catch (const DB::NoSession& e) { - return error(request, Response::Status::unauthorized); - } catch (const std::exception& e) { - std::cerr << "Exception on " << path << ":\n\t" << e.what() << std::endl; - return error(request, Response::Status::internalError); - } catch (...) { - std::cerr << "Unknown exception on " << path << std::endl; - return error(request, Response::Status::internalError); - } -} diff --git a/handler/deletetransaction.h b/handler/deletetransaction.h deleted file mode 100644 index b1b6ab3..0000000 --- a/handler/deletetransaction.h +++ /dev/null @@ -1,21 +0,0 @@ -//SPDX-FileCopyrightText: 2024 Yury Gubich -//SPDX-License-Identifier: GPL-3.0-or-later - -#pragma once - -#include - -#include "handler.h" - -class Server; -namespace Handler { -class DeleteTransaction : public Handler { -public: - DeleteTransaction (const std::shared_ptr& server); - - virtual void handle (Request& request) override; - -private: - std::weak_ptr server; -}; -} diff --git a/handler/env.cpp b/handler/env.cpp deleted file mode 100644 index 5d4630c..0000000 --- a/handler/env.cpp +++ /dev/null @@ -1,17 +0,0 @@ -//SPDX-FileCopyrightText: 2023 Yury Gubich -//SPDX-License-Identifier: GPL-3.0-or-later - -#include "env.h" - -Handler::Env::Env(): - Handler("env", Request::Method::get) -{} - -void Handler::Env::handle(Request& request) { - nlohmann::json body = nlohmann::json::object(); - request.printEnvironment(body); - - Response& res = request.createResponse(); - res.setBody(body); - res.send(); -} diff --git a/handler/env.h b/handler/env.h deleted file mode 100644 index d452af1..0000000 --- a/handler/env.h +++ /dev/null @@ -1,16 +0,0 @@ -//SPDX-FileCopyrightText: 2023 Yury Gubich -//SPDX-License-Identifier: GPL-3.0-or-later - -#pragma once - -#include "handler.h" - -namespace Handler { - -class Env : public Handler { -public: - Env(); - void handle(Request& request) override; - -}; -} diff --git a/handler/handler.cpp b/handler/handler.cpp deleted file mode 100644 index 4623a06..0000000 --- a/handler/handler.cpp +++ /dev/null @@ -1,16 +0,0 @@ -//SPDX-FileCopyrightText: 2023 Yury Gubich -//SPDX-License-Identifier: GPL-3.0-or-later - -#include "handler.h" - -Handler::Handler::Handler(const std::string& path, Request::Method method): - path(path), - method(method) -{} - -Handler::Handler::~Handler() {} - -void Handler::Handler::error (Request& request, Response::Status status) { - Response& res = request.createResponse(status); - res.send(); -} diff --git a/handler/handler.h b/handler/handler.h deleted file mode 100644 index ee0df3c..0000000 --- a/handler/handler.h +++ /dev/null @@ -1,29 +0,0 @@ -//SPDX-FileCopyrightText: 2023 Yury Gubich -//SPDX-License-Identifier: GPL-3.0-or-later - -#pragma once - -#include -#include - -#include "request/request.h" -#include "response/response.h" - -namespace Handler { - -class Handler { -protected: - Handler(const std::string& path, Request::Method method); - -protected: - static void error (Request& request, Response::Status status); - -public: - virtual ~Handler(); - - virtual void handle(Request& request) = 0; - - const std::string path; - const Request::Method method; -}; -} diff --git a/handler/info.cpp b/handler/info.cpp deleted file mode 100644 index 408476c..0000000 --- a/handler/info.cpp +++ /dev/null @@ -1,18 +0,0 @@ -//SPDX-FileCopyrightText: 2023 Yury Gubich -//SPDX-License-Identifier: GPL-3.0-or-later - -#include "info.h" - -Handler::Info::Info(): - Handler("info", Request::Method::get) -{} - -void Handler::Info::handle(Request& request) { - Response& res = request.createResponse(); - nlohmann::json body = nlohmann::json::object(); - body["type"] = PROJECT_NAME; - body["version"] = PROJECT_VERSION; - - res.setBody(body); - res.send(); -} diff --git a/handler/info.h b/handler/info.h deleted file mode 100644 index da162b3..0000000 --- a/handler/info.h +++ /dev/null @@ -1,17 +0,0 @@ -//SPDX-FileCopyrightText: 2023 Yury Gubich -//SPDX-License-Identifier: GPL-3.0-or-later - -#pragma once - -#include "handler.h" -#include "config.h" - -namespace Handler { - -class Info : public Handler { -public: - Info(); - void handle(Request& request) override; -}; - -} diff --git a/handler/login.cpp b/handler/login.cpp deleted file mode 100644 index 9a891d9..0000000 --- a/handler/login.cpp +++ /dev/null @@ -1,79 +0,0 @@ -//SPDX-FileCopyrightText: 2023 Yury Gubich -//SPDX-License-Identifier: GPL-3.0-or-later - -#include "login.h" - -#include "server/server.h" -#include "database/exceptions.h" - -Handler::Login::Login(const std::shared_ptr& server): - Handler("login", Request::Method::post), - server(server) -{} - -void Handler::Login::handle(Request& request) { - std::map form = request.getForm(); - std::map::const_iterator itr = form.find("login"); - if (itr == form.end()) - return error(request, Result::noLogin, Response::Status::badRequest); - - const std::string& login = itr->second; - if (login.empty()) - return error(request, Result::emptyLogin, Response::Status::badRequest); - - itr = form.find("password"); - if (itr == form.end()) - return error(request, Result::noPassword, Response::Status::badRequest); - - const std::string& password = itr->second; - if (password.empty()) - return error(request, Result::emptyPassword, Response::Status::badRequest); - - std::shared_ptr srv = server.lock(); - if (!srv) - return error(request, Result::unknownError, Response::Status::internalError); - - bool success = false; - try { - success = srv->validatePassword(login, password); - } catch (const DB::NoLogin& e) { - std::cerr << "Exception on logging in:\n\t" << e.what() << std::endl; - return error(request, Result::wrongCredentials, Response::Status::badRequest); - } catch (const std::exception& e) { - std::cerr << "Exception on logging in:\n\t" << e.what() << std::endl; - return error(request, Result::unknownError, Response::Status::internalError); - } catch (...) { - std::cerr << "Unknown exception on ogging in" << std::endl; - return error(request, Result::unknownError, Response::Status::internalError); - } - if (!success) - return error(request, Result::wrongCredentials, Response::Status::badRequest); - - nlohmann::json body = nlohmann::json::object(); - body["result"] = Result::success; - - try { - Session& session = srv->openSession(login); - body["accessToken"] = session.getAccessToken(); - body["renewToken"] = session.getRenewToken(); - } catch (const std::exception& e) { - std::cerr << "Exception on opening a session:\n\t" << e.what() << std::endl; - return error(request, Result::unknownError, Response::Status::internalError); - } catch (...) { - std::cerr << "Unknown exception on opening a session" << std::endl; - return error(request, Result::unknownError, Response::Status::internalError); - } - - Response& res = request.createResponse(); - res.setBody(body); - res.send(); -} - -void Handler::Login::error(Request& request, Result result, Response::Status code) { - Response& res = request.createResponse(code); - nlohmann::json body = nlohmann::json::object(); - body["result"] = result; - - res.setBody(body); - res.send(); -} diff --git a/handler/login.h b/handler/login.h deleted file mode 100644 index a91e0f4..0000000 --- a/handler/login.h +++ /dev/null @@ -1,35 +0,0 @@ -//SPDX-FileCopyrightText: 2023 Yury Gubich -//SPDX-License-Identifier: GPL-3.0-or-later - -#pragma once - -#include - -#include "handler.h" - -class Server; -namespace Handler { - -class Login : public Handler { -public: - Login(const std::shared_ptr& server); - void handle(Request& request) override; - - enum class Result { - success, - noLogin, - emptyLogin, - noPassword, - emptyPassword, - wrongCredentials, - unknownError - }; - -private: - void error(Request& request, Result result, Response::Status code); - -private: - std::weak_ptr server; -}; -} - diff --git a/handler/poll.cpp b/handler/poll.cpp deleted file mode 100644 index be92a33..0000000 --- a/handler/poll.cpp +++ /dev/null @@ -1,51 +0,0 @@ -//SPDX-FileCopyrightText: 2023 Yury Gubich -//SPDX-License-Identifier: GPL-3.0-or-later - -#include "handler/poll.h" - -#include "response/response.h" -#include "server/server.h" -#include "request/redirect.h" -#include "database/exceptions.h" - -Handler::Poll::Poll (const std::shared_ptr& server): - Handler("poll", Request::Method::get), - server(server) -{} - -void Handler::Poll::handle (Request& request) { - std::string access = request.getAuthorizationToken(); - if (access.empty()) - return error(request, Result::tokenProblem, Response::Status::unauthorized); - - if (access.size() != 32) - return error(request, Result::tokenProblem, Response::Status::badRequest); - - std::shared_ptr srv = server.lock(); - if (!srv) - return error(request, Result::unknownError, Response::Status::internalError); - - try { - Session& session = srv->getSession(access); - throw Redirect(&session); - } catch (const Redirect& r) { - throw r; - } catch (const DB::NoSession& e) { - return error(request, Result::tokenProblem, Response::Status::unauthorized); - } catch (const std::exception& e) { - std::cerr << "Exception on " << path << ":\n\t" << e.what() << std::endl; - return error(request, Result::unknownError, Response::Status::internalError); - } catch (...) { - std::cerr << "Unknown exception on " << path << std::endl; - return error(request, Result::unknownError, Response::Status::internalError); - } -} - -void Handler::Poll::error(Request& request, Result result, Response::Status status) { - Response& res = request.createResponse(status); - nlohmann::json body = nlohmann::json::object(); - body["result"] = result; - - res.setBody(body); - res.send(); -} diff --git a/handler/poll.h b/handler/poll.h deleted file mode 100644 index e7a6cbd..0000000 --- a/handler/poll.h +++ /dev/null @@ -1,35 +0,0 @@ -//SPDX-FileCopyrightText: 2023 Yury Gubich -//SPDX-License-Identifier: GPL-3.0-or-later - -#pragma once - -#include - -#include "handler.h" -#include "request/request.h" -#include "response/response.h" - -class Server; -namespace Handler { - -class Poll : public Handler { -public: - Poll (const std::shared_ptr& server); - void handle (Request& request) override; - - enum class Result { - success, - tokenProblem, - replace, - timeout, - unknownError - }; - - static void error (Request& request, Result result, Response::Status status); - -private: - std::weak_ptr server; - -}; - -} diff --git a/handler/register.cpp b/handler/register.cpp deleted file mode 100644 index 1f2a651..0000000 --- a/handler/register.cpp +++ /dev/null @@ -1,68 +0,0 @@ -//SPDX-FileCopyrightText: 2023 Yury Gubich -//SPDX-License-Identifier: GPL-3.0-or-later - -#include "register.h" - -#include "server/server.h" -#include "database/exceptions.h" - -Handler::Register::Register(const std::shared_ptr& server): - Handler("register", Request::Method::post), - server(server) -{} - -void Handler::Register::handle(Request& request) { - std::map form = request.getForm(); - std::map::const_iterator itr = form.find("login"); - if (itr == form.end()) - return error(request, Result::noLogin, Response::Status::badRequest); - - const std::string& login = itr->second; - if (login.empty()) - return error(request, Result::emptyLogin, Response::Status::badRequest); - - //TODO login policies checkup - - itr = form.find("password"); - if (itr == form.end()) - return error(request, Result::noPassword, Response::Status::badRequest); - - const std::string& password = itr->second; - if (password.empty()) - return error(request, Result::emptyPassword, Response::Status::badRequest); - - //TODO password policies checkup - - std::shared_ptr srv = server.lock(); - if (!srv) - return error(request, Result::unknownError, Response::Status::internalError); - - try { - srv->registerAccount(login, password); - } catch (const DB::DuplicateLogin& e) { - std::cerr << "Exception on registration:\n\t" << e.what() << std::endl; - return error(request, Result::loginExists, Response::Status::conflict); - } catch (const std::exception& e) { - std::cerr << "Exception on registration:\n\t" << e.what() << std::endl; - return error(request, Result::unknownError, Response::Status::internalError); - } catch (...) { - std::cerr << "Unknown exception on registration" << std::endl; - return error(request, Result::unknownError, Response::Status::internalError); - } - - Response& res = request.createResponse(); - nlohmann::json body = nlohmann::json::object(); - body["result"] = Result::success; - - res.setBody(body); - res.send(); -} - -void Handler::Register::error(Request& request, Result result, Response::Status code) { - Response& res = request.createResponse(code); - nlohmann::json body = nlohmann::json::object(); - body["result"] = result; - - res.setBody(body); - res.send(); -} diff --git a/handler/register.h b/handler/register.h deleted file mode 100644 index 327556a..0000000 --- a/handler/register.h +++ /dev/null @@ -1,36 +0,0 @@ -//SPDX-FileCopyrightText: 2023 Yury Gubich -//SPDX-License-Identifier: GPL-3.0-or-later - -#pragma once - -#include - -#include "handler.h" - -class Server; -namespace Handler { - -class Register : public Handler { -public: - Register(const std::shared_ptr& server); - void handle(Request& request) override; - - enum class Result { - success, - noLogin, - emptyLogin, - loginExists, - loginPolicyViolation, - noPassword, - emptyPassword, - passwordPolicyViolation, - unknownError - }; - -private: - void error(Request& request, Result result, Response::Status code); - -private: - std::weak_ptr server; -}; -} diff --git a/handler/transactions.cpp b/handler/transactions.cpp deleted file mode 100644 index c7d574a..0000000 --- a/handler/transactions.cpp +++ /dev/null @@ -1,51 +0,0 @@ -//SPDX-FileCopyrightText: 2024 Yury Gubich -//SPDX-License-Identifier: GPL-3.0-or-later - -#include "transactions.h" - -#include "server/server.h" -#include "server/session.h" -#include "database/exceptions.h" - -Handler::Transactions::Transactions (const std::shared_ptr& server): - Handler("transactions", Request::Method::get), - server(server) -{} - -void Handler::Transactions::handle (Request& request) { - std::string access = request.getAuthorizationToken(); - if (access.empty()) - return error(request, Response::Status::unauthorized); - - if (access.size() != 32) - return error(request, Response::Status::badRequest); - - std::shared_ptr srv = server.lock(); - if (!srv) - return error(request, Response::Status::internalError); - - try { - Session& session = srv->getSession(access); - std::vector transactions = srv->getDatabase()->listTransactions(session.owner); - - nlohmann::json arr = nlohmann::json::array(); - for (const DB::Transaction& transaction : transactions) - arr.push_back(transaction.toJSON()); - - nlohmann::json body = nlohmann::json::object(); - body["transactions"] = arr; - - Response& res = request.createResponse(Response::Status::ok); - res.setBody(body); - res.send(); - - } catch (const DB::NoSession& e) { - return error(request, Response::Status::unauthorized); - } catch (const std::exception& e) { - std::cerr << "Exception on " << path << ":\n\t" << e.what() << std::endl; - return error(request, Response::Status::internalError); - } catch (...) { - std::cerr << "Unknown exception on " << path << std::endl; - return error(request, Response::Status::internalError); - } -} diff --git a/handler/transactions.h b/handler/transactions.h deleted file mode 100644 index 21c016f..0000000 --- a/handler/transactions.h +++ /dev/null @@ -1,20 +0,0 @@ -//SPDX-FileCopyrightText: 2024 Yury Gubich -//SPDX-License-Identifier: GPL-3.0-or-later - -#pragma once - -#include - -#include "handler.h" - -class Server; -namespace Handler { -class Transactions : public Handler::Handler { -public: - Transactions (const std::shared_ptr& server); - void handle (Request& request) override; - -private: - std::weak_ptr server; -}; -} diff --git a/handler/updateasset.cpp b/handler/updateasset.cpp deleted file mode 100644 index af06610..0000000 --- a/handler/updateasset.cpp +++ /dev/null @@ -1,84 +0,0 @@ -//SPDX-FileCopyrightText: 2024 Yury Gubich -//SPDX-License-Identifier: GPL-3.0-or-later - -#include "updateasset.h" - -#include - -#include "server/server.h" -#include "server/session.h" -#include "database/exceptions.h" - -Handler::UpdateAsset::UpdateAsset (const std::shared_ptr& server): - Handler("updateAsset", Request::Method::post), - server(server) -{} - -void Handler::UpdateAsset::handle (Request& request) { - std::string access = request.getAuthorizationToken(); - if (access.empty()) - return error(request, Response::Status::unauthorized); - - if (access.size() != 32) - return error(request, Response::Status::badRequest); - - std::shared_ptr srv = server.lock(); - if (!srv) - return error(request, Response::Status::internalError); - - std::map form = request.getForm(); - std::map::const_iterator itr = form.find("id"); - if (itr == form.end()) - return error(request, Response::Status::badRequest); - - DB::Asset asset; - asset.id = std::stoul(itr->second); - - itr = form.find("currency"); - if (itr == form.end()) - return error(request, Response::Status::badRequest); - - asset.currency = std::stoul(itr->second); - //TODO validate the currency - - itr = form.find("title"); - if (itr == form.end()) - return error(request, Response::Status::badRequest); - - asset.title = itr->second; - - itr = form.find("icon"); - if (itr == form.end()) - return error(request, Response::Status::badRequest); - - asset.icon = itr->second; - - try { - itr = form.find("color"); - if (itr != form.end()) - asset.color = std::stoul(itr->second); - } catch (const std::exception& e) { - std::cerr << "Insignificant error parsing color during asset addition: " << e.what() << std::endl; - } - - try { - Session& session = srv->getSession(access); - - asset.owner = session.owner; - srv->getDatabase()->updateAsset(asset); - - Response& res = request.createResponse(Response::Status::ok); - res.send(); - - session.assetChanged(asset); - - } catch (const DB::NoSession& e) { - return error(request, Response::Status::unauthorized); - } catch (const std::exception& e) { - std::cerr << "Exception on " << path << ":\n\t" << e.what() << std::endl; - return error(request, Response::Status::internalError); - } catch (...) { - std::cerr << "Unknown exception on " << path << std::endl; - return error(request, Response::Status::internalError); - } -} diff --git a/handler/updateasset.h b/handler/updateasset.h deleted file mode 100644 index 33ea6e4..0000000 --- a/handler/updateasset.h +++ /dev/null @@ -1,20 +0,0 @@ -//SPDX-FileCopyrightText: 2024 Yury Gubich -//SPDX-License-Identifier: GPL-3.0-or-later - -#pragma once - -#include - -#include "handler.h" - -class Server; -namespace Handler { -class UpdateAsset : public Handler { -public: - UpdateAsset (const std::shared_ptr& server); - virtual void handle (Request& request) override; - -private: - std::weak_ptr server; -}; -} diff --git a/handler/updatetransaction.cpp b/handler/updatetransaction.cpp deleted file mode 100644 index f27ef7e..0000000 --- a/handler/updatetransaction.cpp +++ /dev/null @@ -1,84 +0,0 @@ -//SPDX-FileCopyrightText: 2024 Yury Gubich -//SPDX-License-Identifier: GPL-3.0-or-later - -#include "updatetransaction.h" - -#include - -#include "server/server.h" -#include "server/session.h" -#include "database/exceptions.h" - -Handler::UpdateTransaction::UpdateTransaction (const std::shared_ptr& server): - Handler("updateTransaction", Request::Method::post), - server(server) -{} - -void Handler::UpdateTransaction::handle (Request& request) { - std::string access = request.getAuthorizationToken(); - if (access.empty()) - return error(request, Response::Status::unauthorized); - - if (access.size() != 32) - return error(request, Response::Status::badRequest); - - std::shared_ptr srv = server.lock(); - if (!srv) - return error(request, Response::Status::internalError); - - std::map form = request.getForm(); - std::map::const_iterator itr = form.find("id"); - if (itr == form.end()) - return error(request, Response::Status::badRequest); - - DB::Transaction txn; - txn.id = std::stoul(itr->second); - - itr = form.find("asset"); - if (itr == form.end()) - return error(request, Response::Status::badRequest); - - txn.asset = std::stoul(itr->second); - //TODO validate the asset - - itr = form.find("value"); - if (itr == form.end()) - return error(request, Response::Status::badRequest); - - txn.value = std::stod(itr->second); - - itr = form.find("performed"); - if (itr == form.end()) - return error(request, Response::Status::badRequest); - - txn.performed = std::stoul(itr->second); - - itr = form.find("notes"); - if (itr != form.end()) - txn.notes = itr->second; - - itr = form.find("parent"); - if (itr != form.end()) - txn.parent = std::stoul(itr->second); - - try { - Session& session = srv->getSession(access); - - txn.initiator = session.owner; - srv->getDatabase()->updateTransaction(txn); - - Response& res = request.createResponse(Response::Status::ok); - res.send(); - - session.transactionChanged(txn); - - } catch (const DB::NoSession& e) { - return error(request, Response::Status::unauthorized); - } catch (const std::exception& e) { - std::cerr << "Exception on " << path << ":\n\t" << e.what() << std::endl; - return error(request, Response::Status::internalError); - } catch (...) { - std::cerr << "Unknown exception on " << path << std::endl; - return error(request, Response::Status::internalError); - } -} diff --git a/handler/updatetransaction.h b/handler/updatetransaction.h deleted file mode 100644 index 3fc68dd..0000000 --- a/handler/updatetransaction.h +++ /dev/null @@ -1,20 +0,0 @@ -//SPDX-FileCopyrightText: 2024 Yury Gubich -//SPDX-License-Identifier: GPL-3.0-or-later - -#pragma once - -#include - -#include "handler.h" - -class Server; -namespace Handler { -class UpdateTransaction : public Handler { -public: - UpdateTransaction (const std::shared_ptr& server); - virtual void handle (Request& request) override; - -private: - std::weak_ptr server; -}; -} diff --git a/main.cpp b/main.cpp index c336bf6..8c37269 100644 --- a/main.cpp +++ b/main.cpp @@ -1,5 +1,5 @@ -//SPDX-FileCopyrightText: 2023 Yury Gubich -//SPDX-License-Identifier: GPL-3.0-or-later +// SPDX-FileCopyrightText: 2023 Yury Gubich +// SPDX-License-Identifier: GPL-3.0-or-later #include @@ -7,7 +7,6 @@ #include #include -#include #include "config.h" //autogenereted by cmake in the root of bindir #include "utils/helpers.h" @@ -35,6 +34,6 @@ int main(int argc, char** argv) { FCGX_Init(); - auto server = std::make_shared(); - server->run(sockfd); + Server server; + server.run(sockfd); } diff --git a/request/CMakeLists.txt b/request/CMakeLists.txt index 87e818d..eee9cda 100644 --- a/request/CMakeLists.txt +++ b/request/CMakeLists.txt @@ -1,16 +1,9 @@ -#SPDX-FileCopyrightText: 2023 Yury Gubich -#SPDX-License-Identifier: GPL-3.0-or-later - set(HEADERS request.h - redirect.h - - redirectable.h ) set(SOURCES request.cpp - redirect.cpp ) target_sources(pica PRIVATE ${SOURCES}) diff --git a/request/accepting.h b/request/accepting.h deleted file mode 100644 index f5d731c..0000000 --- a/request/accepting.h +++ /dev/null @@ -1,14 +0,0 @@ -//SPDX-FileCopyrightText: 2023 Yury Gubich -//SPDX-License-Identifier: GPL-3.0-or-later - -#pragma once - -#include - -#include "request/request.h" - -class Accepting { -public: - virtual ~Accepting() {}; - virtual void accept(std::unique_ptr request) = 0; -}; diff --git a/request/redirect.cpp b/request/redirect.cpp deleted file mode 100644 index e7fc326..0000000 --- a/request/redirect.cpp +++ /dev/null @@ -1,12 +0,0 @@ -//SPDX-FileCopyrightText: 2023 Yury Gubich -//SPDX-License-Identifier: GPL-3.0-or-later - -#include "redirect.h" - -Redirect::Redirect(Accepting* destination): - destination(destination) -{} - -const char* Redirect::what() const noexcept { - return "This is a redirect, should have beeh handled in router, but if you see it - something went terrebly wrong"; -} diff --git a/request/redirect.h b/request/redirect.h deleted file mode 100644 index e31d722..0000000 --- a/request/redirect.h +++ /dev/null @@ -1,16 +0,0 @@ -//SPDX-FileCopyrightText: 2023 Yury Gubich -//SPDX-License-Identifier: GPL-3.0-or-later - -#pragma once - -#include - -#include "accepting.h" - -class Redirect : std::exception { -public: - Redirect(Accepting* destination); - - Accepting* destination; - const char* what() const noexcept override; -}; diff --git a/request/request.cpp b/request/request.cpp index e6a055d..53fb88b 100644 --- a/request/request.cpp +++ b/request/request.cpp @@ -1,19 +1,13 @@ -//SPDX-FileCopyrightText: 2023 Yury Gubich -//SPDX-License-Identifier: GPL-3.0-or-later +// SPDX-FileCopyrightText: 2023 Yury Gubich +// SPDX-License-Identifier: GPL-3.0-or-later #include "request.h" -#include "response/response.h" +constexpr static const char* GET("GET"); constexpr static const char* REQUEST_METHOD("REQUEST_METHOD"); constexpr static const char* SCRIPT_FILENAME("SCRIPT_FILENAME"); constexpr static const char* SERVER_NAME("SERVER_NAME"); -constexpr static const char* CONTENT_TYPE("CONTENT_TYPE"); -constexpr static const char* CONTENT_LENGTH("CONTENT_LENGTH"); -constexpr static const char* AUTHORIZATION("HTTP_AUTHORIZATION"); -constexpr static const char* QUERY_STRING("QUERY_STRING"); - -constexpr static const char* urlEncoded("application/x-www-form-urlencoded"); // constexpr static const char* REQUEST_URI("REQUEST_URI"); // @@ -22,20 +16,9 @@ constexpr static const char* urlEncoded("application/x-www-form-urlencoded"); // // constexpr static const char* SCRIPT_NAME("SCRIPT_NAME"); -constexpr std::array< - std::pair, - static_cast(Request::Method::unknown) -> methods = {{ - {"GET", Request::Method::get}, - {"POST", Request::Method::post} -}}; - Request::Request (): state(State::initial), - raw(), - response(nullptr), - path(), - cachedMethod(Method::unknown) + raw() {} Request::~Request() { @@ -43,45 +26,22 @@ Request::~Request() { } void Request::terminate() { - switch (state) { - case State::terminated: - case State::initial: - break; + switch (state) { case State::accepted: - std::cout << "A termination of accepted request that was not responded to, it's probably an error" << std::endl; - FCGX_Finish_r(&raw); - break; - case State::responding: - std::cout << "A termination of responding request that was not actually sent, it's probably an error" << std::endl; - FCGX_Finish_r(&raw); - break; case State::responded: FCGX_Finish_r(&raw); break; + default: + break; } - - state = State::terminated; -} -std::string_view Request::methodName() const { - if (state == State::initial || state == State::terminated) - throw std::runtime_error("An attempt to read request method on not accepted request"); - - return FCGX_GetParam(REQUEST_METHOD, raw.envp); } -Request::Method Request::method() const { - if (cachedMethod != Method::unknown) - return cachedMethod; +bool Request::isGet() const { + if (state != State::accepted) + throw std::runtime_error("An attempt to read request type on a wrong request state"); - std::string_view method = methodName(); - for (const auto& pair : methods) { - if (pair.first == method) { - cachedMethod = pair.second; - return pair.second; - } - } - - return Request::Method::unknown; + std::string_view method(FCGX_GetParam(REQUEST_METHOD, raw.envp)); + return method == GET; } bool Request::wait(int socketDescriptor) { @@ -100,91 +60,38 @@ bool Request::wait(int socketDescriptor) { } OStream Request::getOutputStream() { + if (state != State::accepted) + throw std::runtime_error("An attempt to request output stream on a wrong request state"); + return OStream(raw.out); } OStream Request::getErrorStream() { + if (state != State::accepted) + throw std::runtime_error("An attempt to request error stream on a wrong request state"); + return OStream(raw.err); } -bool Request::active() const { - return state != State::initial && state != State::terminated; -} - -Response& Request::createResponse() { - if (state != State::accepted) - throw std::runtime_error("An attempt create response to the request in the wrong state"); - - response = std::unique_ptr(new Response(*this)); - state = State::responding; - - return *response.get(); -} - -Response& Request::createResponse(Response::Status status) { - if (state != State::accepted) - throw std::runtime_error("An attempt create response to the request in the wrong state"); - - response = std::unique_ptr(new Response(*this, status)); - state = State::responding; - - return *response.get(); -} - -uint16_t Request::responseCode() const { - if (state != State::responded) - throw std::runtime_error("An attempt create read response code on the wrong state"); - - return response->statusCode(); -} - -void Request::responseIsComplete() { - switch (state) { - case State::initial: - throw std::runtime_error("An attempt to mark the request as complete, but it wasn't even accepted yet"); - break; - case State::accepted: - throw std::runtime_error("An attempt to mark the request as complete, but it wasn't responded"); - break; - case State::responding: - state = State::responded; - std::cout << responseCode() << '\t' << methodName() << '\t' << path << std::endl; - break; - case State::responded: - throw std::runtime_error("An attempt to mark the request as a complete for the second time"); - break; - case State::terminated: - throw std::runtime_error("An attempt to mark the request as a complete on a terminated request"); - break; - } -} - -Request::State Request::currentState() const { - return state; -} - -void Request::readPath(const std::string& serverName) { +std::string Request::getPath(const std::string& serverName) const { if (state != State::accepted) throw std::runtime_error("An attempt to request path on a wrong request state"); - if (!path.empty()) - std::cout << "Request already has path \"" + path + "\", but it's being read again, probably an error"; - + std::string path; std::string_view scriptFileName(FCGX_GetParam(SCRIPT_FILENAME, raw.envp)); std::string::size_type snLocation = scriptFileName.find(serverName); if (snLocation != std::string::npos) { if (snLocation + serverName.size() < scriptFileName.size()) path = scriptFileName.substr(snLocation + serverName.size() + 1); + } if (!path.empty()) { while (path.back() == '/') path.erase(path.end() - 1); } -} -std::string Request::getPath() const { return path; } @@ -195,17 +102,14 @@ std::string Request::getServerName() const { return FCGX_GetParam(SERVER_NAME, raw.envp);; } -void Request::printEnvironment(std::ostream& out) const { - if (!active()) - throw std::runtime_error("An attempt to print environment of a request in a wrong state"); - +void Request::printEnvironment(std::ostream& out) { char **envp = raw.envp; for (int i = 0; envp[i] != nullptr; ++i) { out << envp[i] << "\n"; } } -void Request::printEnvironment(nlohmann::json& out) const { +void Request::printEnvironment(nlohmann::json& out) { if (!out.is_object()) return; @@ -217,75 +121,3 @@ void Request::printEnvironment(nlohmann::json& out) const { out[std::string(value.substr(0, pos))] = std::string(value.substr(pos + 1, value.size())); } } - -bool Request::isFormUrlEncoded() const { - if (!active()) - throw std::runtime_error("An attempt to read content type of a request in a wrong state"); - - std::string_view contentType(FCGX_GetParam(CONTENT_TYPE, raw.envp)); - if (!contentType.empty() && contentType.find(urlEncoded) != std::string_view::npos) { - return true; - } - - return false; -} - -unsigned int Request::contentLength() const { - if (!active()) - throw std::runtime_error("An attempt to read content length of a request in a wrong state"); - - char* cl = FCGX_GetParam(CONTENT_LENGTH, raw.envp); - if (cl != nullptr) - return atoi(cl); - else - return 0; -} - -std::map Request::getForm() const { - if (!active()) - throw std::runtime_error("An attempt to read form of a request in a wrong state"); - - switch (Request::method()) { - case Method::get: - return urlDecodeAndParse(FCGX_GetParam(QUERY_STRING, raw.envp)); - break; - case Method::post: - return getFormPOST(); - default: - return {}; - } -} - -std::map Request::getFormPOST () const { - std::map result; - std::string_view contentType(FCGX_GetParam(CONTENT_TYPE, raw.envp)); - if (contentType.empty()) - return result; - - if (contentType.find(urlEncoded) != std::string_view::npos) { - unsigned int length = contentLength(); - std::string postData(length, '\0'); - FCGX_GetStr(&postData[0], length, raw.in); - result = urlDecodeAndParse(postData); - } - - return result; -} - -std::string Request::getAuthorizationToken() const { - if (!active()) - throw std::runtime_error("An attempt to read authorization token of a request in a wrong state"); - - const char* auth = FCGX_GetParam(AUTHORIZATION, raw.envp); - if (auth == nullptr) - return std::string(); - - std::string result(auth); - if (result.find("Bearer") != 0) - return std::string(); - - result.erase(0, 6); - trim(result); - - return result; -} diff --git a/request/request.h b/request/request.h index 55f2650..2fc9a32 100644 --- a/request/request.h +++ b/request/request.h @@ -1,39 +1,24 @@ -//SPDX-FileCopyrightText: 2023 Yury Gubich -//SPDX-License-Identifier: GPL-3.0-or-later +// SPDX-FileCopyrightText: 2023 Yury Gubich +// SPDX-License-Identifier: GPL-3.0-or-later #pragma once #include #include #include -#include -#include -#include #include #include #include "stream/ostream.h" -#include "utils/formdecode.h" -#include "utils/helpers.h" -#include "response/response.h" class Request { - friend class Response; public: enum class State { initial, accepted, - responding, - responded, - terminated - }; - - enum class Method { - get, - post, - unknown + responded }; Request(); @@ -44,37 +29,18 @@ public: Request& operator = (Request&& other) = delete; bool wait(int socketDescriptor); - bool active() const; + void terminate(); + bool isGet() const; - Response& createResponse(); - Response& createResponse(Response::Status status); - - uint16_t responseCode() const; - Method method() const; - std::string_view methodName() const; - State currentState() const; - bool isFormUrlEncoded() const; - unsigned int contentLength() const; - std::map getForm() const; - - void readPath(const std::string& serverName); - std::string getPath() const; - std::string getServerName() const; - std::string getAuthorizationToken() const; - void printEnvironment(std::ostream& out) const; - void printEnvironment(nlohmann::json& out) const; - -private: OStream getOutputStream(); OStream getErrorStream(); - void responseIsComplete(); - void terminate(); - std::map getFormPOST() const; + + std::string getPath(const std::string& serverName) const; + std::string getServerName() const; + void printEnvironment(std::ostream& out); + void printEnvironment(nlohmann::json& out); private: State state; FCGX_Request raw; - std::unique_ptr response; - std::string path; - mutable Method cachedMethod; }; diff --git a/response/CMakeLists.txt b/response/CMakeLists.txt index 3df4e32..6b35c08 100644 --- a/response/CMakeLists.txt +++ b/response/CMakeLists.txt @@ -1,6 +1,3 @@ -#SPDX-FileCopyrightText: 2023 Yury Gubich -#SPDX-License-Identifier: GPL-3.0-or-later - set(HEADERS response.h ) @@ -9,4 +6,4 @@ set(SOURCES response.cpp ) -target_sources(${PROJECT_NAME} PRIVATE ${SOURCES}) +target_sources(pica PRIVATE ${SOURCES}) diff --git a/response/response.cpp b/response/response.cpp index 0b5416c..17be265 100644 --- a/response/response.cpp +++ b/response/response.cpp @@ -1,70 +1,45 @@ -//SPDX-FileCopyrightText: 2023 Yury Gubich -//SPDX-License-Identifier: GPL-3.0-or-later +// SPDX-FileCopyrightText: 2023 Yury Gubich +// SPDX-License-Identifier: GPL-3.0-or-later #include "response.h" -#include "request/request.h" - -constexpr std::array(Response::Status::__size)> statusCodes = { - 200, - 400, - 401, - 403, - 404, - 405, - 409, - 500 -}; - -constexpr std::array(Response::Status::__size)> statuses = { +constexpr std::array(Response::Status::__size)> statusCodes = { "Status: 200 OK", - "Status: 400 Bad Request", - "Status: 401 Unauthorized", - "Status: 403 Forbidden", "Status: 404 Not Found", "Status: 405 Method Not Allowed", - "Status: 409 Conflict", "Status: 500 Internal Error" }; constexpr std::array(Response::ContentType::__size)> contentTypes = { - "Content-Type: text/plain", - "Content-Type: application/json" + "Content-type: text/plain", + "Content-type: application/json" }; -Response::Response(Request& request): - request(request), +Response::Response(): status(Status::ok), type(ContentType::text), body() {} -Response::Response(Request& request, Status status): - request(request), +Response::Response(Status status): status(status), type(ContentType::text), body() {} -void Response::send() const { +void Response::replyTo(Request& request) const { // OStream out = status == Status::ok ? // request.getOutputStream() : // request.getErrorStream(); OStream out = request.getOutputStream(); - out << statuses[static_cast(status)] << "\r\n"; + out << statusCodes[static_cast(status)]; if (!body.empty()) - out << contentTypes[static_cast(type)] << "\r\n" - << "\r\n" + out << '\n' + << contentTypes[static_cast(type)] + << '\n' + << '\n' << body; - else - out << "\r\n"; - - request.responseIsComplete(); -} - -uint16_t Response::statusCode() const { - return statusCodes[static_cast(status)]; } void Response::setBody(const std::string& body) { diff --git a/response/response.h b/response/response.h index e305f51..7cf0d59 100644 --- a/response/response.h +++ b/response/response.h @@ -1,5 +1,5 @@ -//SPDX-FileCopyrightText: 2023 Yury Gubich -//SPDX-License-Identifier: GPL-3.0-or-later +// SPDX-FileCopyrightText: 2023 Yury Gubich +// SPDX-License-Identifier: GPL-3.0-or-later #pragma once @@ -9,21 +9,15 @@ #include +#include "request/request.h" #include "stream/ostream.h" -class Request; class Response { - friend class Request; - public: enum class Status { ok, - badRequest, - unauthorized, - forbidden, notFound, methodNotAllowed, - conflict, internalError, __size }; @@ -33,19 +27,14 @@ public: json, __size }; + Response(); + Response(Status status); - uint16_t statusCode() const; - - void send() const; + void replyTo(Request& request) const; void setBody(const std::string& body); void setBody(const nlohmann::json& body); private: - Response(Request& request); - Response(Request& request, Status status); - -private: - Request& request; Status status; ContentType type; std::string body; diff --git a/run.sh.in b/run.sh.in deleted file mode 100644 index 53b7203..0000000 --- a/run.sh.in +++ /dev/null @@ -1,28 +0,0 @@ -#!/bin/bash - -#SPDX-FileCopyrightText: 2023 Yury Gubich -#SPDX-License-Identifier: GPL-3.0-or-later - -start_service() { - if (systemctl is-active --quiet $1) then - echo "$1 is already running" - else - echo "$1 is not running, going to use sudo to start it" - if (sudo systemctl start $1) then - echo "$1 started" - else - exit - fi - fi -} - -if [ ! -d "/run/pica" ]; then - echo "required unix socket was not found, going to use sudo to create it" - sudo mkdir /run/pica - sudo chown $USER:$USER /run/pica -fi - -start_service "mariadb" -start_service "httpd" - -$(dirname "$0")/@PROJECT_NAME@ diff --git a/server/CMakeLists.txt b/server/CMakeLists.txt index 3ac7309..694b3a5 100644 --- a/server/CMakeLists.txt +++ b/server/CMakeLists.txt @@ -1,16 +1,11 @@ -#SPDX-FileCopyrightText: 2023 Yury Gubich -#SPDX-License-Identifier: GPL-3.0-or-later - set(HEADERS server.h router.h - session.h ) set(SOURCES server.cpp router.cpp - session.cpp ) -target_sources(${PROJECT_NAME} PRIVATE ${SOURCES}) +target_sources(pica PRIVATE ${SOURCES}) diff --git a/server/router.cpp b/server/router.cpp index 2079bc6..24bb402 100644 --- a/server/router.cpp +++ b/server/router.cpp @@ -1,77 +1,44 @@ -//SPDX-FileCopyrightText: 2023 Yury Gubich -//SPDX-License-Identifier: GPL-3.0-or-later +// SPDX-FileCopyrightText: 2023 Yury Gubich +// SPDX-License-Identifier: GPL-3.0-or-later #include "router.h" -#include "request/redirect.h" - Router::Router(): - get(), - post() -{} + table() +{ -void Router::addRoute(Handler handler) { - std::pair::const_iterator, bool> result; - switch (handler->method) { - case Request::Method::get: - result = get.emplace(handler->path, std::move(handler)); - break; - case Request::Method::post: - result = post.emplace(handler->path, std::move(handler)); - break; - default: - throw std::runtime_error("An attempt to register handler with unsupported method type: " + std::to_string((int)handler->method)); - } - - if (!result.second) - throw std::runtime_error("could'not add route " + handler->path + " to the routing table"); } -void Router::route(std::unique_ptr request) { - std::map::const_iterator itr, end; - switch (request->method()) { - case Request::Method::get: - itr = get.find(request->getPath()); - end = get.end(); - break; - case Request::Method::post: - itr = post.find(request->getPath()); - end = post.end(); - break; - default: - return handleMethodNotAllowed(std::move(request)); - } +void Router::addRoute(const std::string& path, const Handler& handler) { + auto result = table.emplace(path, handler); + if (!result.second) + std::cerr << "could'not add route " + path + " to the routing table"; +} - if (itr == end) - return handleNotFound(std::move(request)); +void Router::route(const std::string& path, std::unique_ptr request, Server* server) { + auto itr = table.find(path); + if (itr == table.end()) + return handleNotFound(path, std::move(request)); try { - itr->second->handle(*request.get()); - - if (request->currentState() != Request::State::responded) + bool result = itr->second(request.get(), server); + if (!result) handleInternalError(std::runtime_error("handler failed to handle the request"), std::move(request)); - } catch (const Redirect& redirect) { - redirect.destination->accept(std::move(request)); } catch (const std::exception& e) { handleInternalError(e, std::move(request)); } } -void Router::handleNotFound(std::unique_ptr request) { - Response& notFound = request->createResponse(Response::Status::notFound); - std::string path = request->getPath(); +void Router::handleNotFound(const std::string& path, std::unique_ptr request) { + Response notFound(Response::Status::notFound); notFound.setBody(std::string("Path \"") + path + "\" was not found"); - notFound.send(); + notFound.replyTo(*request.get()); + std::cerr << "Not found: " << path << std::endl; } void Router::handleInternalError(const std::exception& exception, std::unique_ptr request) { - Response& error = request->createResponse(Response::Status::internalError); + Response error(Response::Status::internalError); error.setBody(std::string(exception.what())); - error.send(); -} - -void Router::handleMethodNotAllowed(std::unique_ptr request) { - Response& error = request->createResponse(Response::Status::methodNotAllowed); - error.setBody(std::string("Method not allowed")); - error.send(); + error.replyTo(*request.get()); + std::cerr << "Internal error: " << exception.what() << std::endl; } diff --git a/server/router.h b/server/router.h index 1368075..c423282 100644 --- a/server/router.h +++ b/server/router.h @@ -1,5 +1,5 @@ -//SPDX-FileCopyrightText: 2023 Yury Gubich -//SPDX-License-Identifier: GPL-3.0-or-later +// SPDX-FileCopyrightText: 2023 Yury Gubich +// SPDX-License-Identifier: GPL-3.0-or-later #pragma once @@ -10,25 +10,23 @@ #include "request/request.h" #include "response/response.h" -#include "handler/handler.h" class Server; class Router { - using Handler = std::unique_ptr; public: + using Handler = std::function; + Router(); - void addRoute(Handler handler); - void route(std::unique_ptr request); + void addRoute(const std::string& path, const Handler& handler); + void route(const std::string& path, std::unique_ptr request, Server* server); private: - void handleNotFound(std::unique_ptr request); + void handleNotFound(const std::string& path, std::unique_ptr request); void handleInternalError(const std::exception& exception, std::unique_ptr request); - void handleMethodNotAllowed(std::unique_ptr request); private: - std::map get; - std::map post; + std::map table; }; diff --git a/server/server.cpp b/server/server.cpp index bb5191a..6d1b9a3 100644 --- a/server/server.cpp +++ b/server/server.cpp @@ -1,96 +1,45 @@ -//SPDX-FileCopyrightText: 2023 Yury Gubich -//SPDX-License-Identifier: GPL-3.0-or-later +// SPDX-FileCopyrightText: 2023 Yury Gubich +// SPDX-License-Identifier: GPL-3.0-or-later #include "server.h" -#include - -#include "database/exceptions.h" - -#include "handler/info.h" -#include "handler/env.h" -#include "handler/register.h" -#include "handler/login.h" -#include "handler/poll.h" -#include "handler/assets.h" -#include "handler/addasset.h" -#include "handler/deleteasset.h" -#include "handler/currencies.h" -#include "handler/updateasset.h" -#include "handler/addtransaction.h" -#include "handler/transactions.h" -#include "handler/deletetransaction.h" -#include "handler/updatetransaction.h" - -#include "taskmanager/route.h" - -constexpr const char* pepper = "well, not much of a secret, huh?"; -constexpr const char* dbLogin = "pica"; -constexpr const char* dbPassword = "pica"; -constexpr const char* dbName = "pica"; -constexpr const char* dbPath = "/run/mysqld/mysqld.sock"; -constexpr uint8_t dbConnectionsCount = 4; -constexpr uint32_t pollTimout = 10000; - -constexpr const char* randomChars = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"; -constexpr uint8_t saltSize = 16; -constexpr uint8_t hashSize = 32; -constexpr uint8_t hashParallel = 1; -constexpr uint8_t hashIterations = 2; -constexpr uint32_t hashMemoryCost = 65536; - constexpr uint8_t currentDbVesion = 1; -Server::Server (): - std::enable_shared_from_this(), +Server::Server(): terminating(false), requestCount(0), serverName(std::nullopt), - router(std::make_shared()), - pool(DB::Pool::create()), - taskManager(std::make_shared()), - scheduler(std::make_shared(taskManager)), - sessions() + router(), + db() { std::cout << "Startig pica..." << std::endl; + + db = DBInterface::create(DBInterface::Type::mysql); std::cout << "Database type: MySQL" << std::endl; - pool->addInterfaces( - DB::Interface::Type::mysql, - dbConnectionsCount, - dbLogin, - dbPassword, - dbName, - dbPath - ); - DB::Resource db = pool->request(); + db->setCredentials("pica", "pica"); + db->setDatabase("pica"); - db->migrate(currentDbVesion); + bool connected = false; + try { + db->connect("/run/mysqld/mysqld.sock"); + connected = true; + std::cout << "Successfully connected to the database" << std::endl; + + } catch (const std::runtime_error& e) { + std::cerr << "Couldn't connect to the database: " << e.what() << std::endl; + } + + if (connected) + db->migrate(currentDbVesion); + + router.addRoute("info", Server::info); + router.addRoute("env", Server::printEnvironment); } -Server::~Server () {} - -void Server::run (int socketDescriptor) { - std::shared_ptr srv = shared_from_this(); - - router->addRoute(std::make_unique()); - router->addRoute(std::make_unique()); - router->addRoute(std::make_unique(srv)); - router->addRoute(std::make_unique(srv)); - router->addRoute(std::make_unique(srv)); - router->addRoute(std::make_unique(srv)); - router->addRoute(std::make_unique(srv)); - router->addRoute(std::make_unique(srv)); - router->addRoute(std::make_unique(srv)); - router->addRoute(std::make_unique(srv)); - router->addRoute(std::make_unique(srv)); - router->addRoute(std::make_unique(srv)); - router->addRoute(std::make_unique(srv)); - router->addRoute(std::make_unique(srv)); - - taskManager->start(); - scheduler->start(); +Server::~Server() {} +void Server::run(int socketDescriptor) { while (!terminating) { std::unique_ptr request = std::make_unique(); bool result = request->wait(socketDescriptor); @@ -102,7 +51,7 @@ void Server::run (int socketDescriptor) { } } -void Server::handleRequest (std::unique_ptr request) { +void Server::handleRequest(std::unique_ptr request) { ++requestCount; if (!serverName) { try { @@ -110,115 +59,49 @@ void Server::handleRequest (std::unique_ptr request) { std::cout << "received server name " << serverName.value() << std::endl; } catch (...) { std::cerr << "failed to read server name" << std::endl; - Response& error = request->createResponse(Response::Status::internalError); - error.send(); + Response error(Response::Status::internalError); + error.replyTo(*request.get()); return; } } - request->readPath(serverName.value()); + if (!request->isGet()) { + static const Response methodNotAllowed(Response::Status::methodNotAllowed); + methodNotAllowed.replyTo(*request.get()); + return; + } - auto route = std::make_unique(router, std::move(request)); - taskManager->schedule(std::move(route)); -} - -std::string Server::generateRandomString (std::size_t length) { - std::random_device rd; - std::mt19937 gen(rd()); - std::uniform_int_distribution distribution(0, std::strlen(randomChars) - 1); - - std::string result(length, 0); - for (size_t i = 0; i < length; ++i) - result[i] = randomChars[distribution(gen)]; - - return result; -} - -unsigned int Server::registerAccount (const std::string& login, const std::string& password) { - std::size_t encSize = argon2_encodedlen( - hashIterations, hashMemoryCost, - hashParallel, saltSize, hashSize, Argon2_id - ); - - std::string hash(encSize, 0); - std::string salt = generateRandomString(saltSize); - std::string spiced = password + pepper; - - int result = argon2id_hash_encoded( - hashIterations, hashMemoryCost, hashParallel, - spiced.data(), spiced.size(), - salt.data(), saltSize, - hashSize, hash.data(), encSize - ); - - if (result != ARGON2_OK) - throw std::runtime_error(std::string("Hashing failed: ") + argon2_error_message(result)); - - DB::Resource db = pool->request(); - return db->registerAccount(login, hash); -} - -bool Server::validatePassword (const std::string& login, const std::string& password) { - DB::Resource db = pool->request(); - std::string hash = db->getAccountHash(login); - - std::string spiced = password + pepper; - int result = argon2id_verify(hash.data(), spiced.data(), spiced.size()); - - switch (result) { - case ARGON2_OK: - return true; - case ARGON2_VERIFY_MISMATCH: - return false; - default: - throw std::runtime_error(std::string("Failed to verify password: ") + argon2_error_message(result)); + try { + std::string path = request->getPath(serverName.value()); + router.route(path.data(), std::move(request), this); + } catch (const std::exception e) { + Response error(Response::Status::internalError); + error.setBody(std::string(e.what())); + error.replyTo(*request.get()); } } -Session& Server::openSession (const std::string& login) { - std::string accessToken, renewToken; - DB::Session s; - s.id = 0; - int counter = 10; - do { - try { - accessToken = generateRandomString(32); - renewToken = generateRandomString(32); - DB::Resource db = pool->request(); - s = db->createSession(login, accessToken, renewToken); - break; - } catch (const DB::Duplicate& e) { - std::cout << "Duplicate on creating session, trying again with different tokens"; - } - } while (--counter != 0); +bool Server::printEnvironment(Request* request, Server* server) { + (void)server; + nlohmann::json body = nlohmann::json::object(); + request->printEnvironment(body); - if (s.id == 0) - throw std::runtime_error("Couldn't create session, ran out of attempts"); + Response res; + res.setBody(body); + res.replyTo(*request); - std::unique_ptr& session = sessions[accessToken] - = std::make_unique(scheduler, s.id, s.owner, s.accessToken, s.renewToken, pollTimout); - return *session.get(); + return true; } -Session& Server::getSession (const std::string& accessToken) { - Sessions::const_iterator itr = sessions.find(accessToken); - if (itr != sessions.end()) - return *(itr->second); +bool Server::info(Request* request, Server* server) { + (void)server; + Response res; + nlohmann::json body = nlohmann::json::object(); + body["type"] = "Pica"; + body["version"] = "0.0.1"; - DB::Resource db = pool->request(); - DB::Session s = db->findSession(accessToken); - std::unique_ptr& session = sessions[accessToken] = std::make_unique( - scheduler, - s.id, - s.owner, - s.accessToken, - s.renewToken, - pollTimout - ); - return *session.get(); -} - - -DB::Resource Server::getDatabase () { - return pool->request(); + res.setBody(body); + res.replyTo(*request); + + return true; } diff --git a/server/server.h b/server/server.h index eaa9004..dae5b66 100644 --- a/server/server.h +++ b/server/server.h @@ -1,5 +1,5 @@ -//SPDX-FileCopyrightText: 2023 Yury Gubich -//SPDX-License-Identifier: GPL-3.0-or-later +// SPDX-FileCopyrightText: 2023 Yury Gubich +// SPDX-License-Identifier: GPL-3.0-or-later #pragma once @@ -10,50 +10,35 @@ #include #include #include -#include #include #include #include -#include +#include #include "request/request.h" #include "response/response.h" #include "router.h" -#include "session.h" -#include "database/pool.h" -#include "utils/helpers.h" -#include "config.h" -#include "taskmanager/manager.h" -#include "taskmanager/scheduler.h" +#include "database/dbinterface.h" -class Server : public std::enable_shared_from_this { +class Server { public: Server(); ~Server(); void run(int socketDescriptor); - unsigned int registerAccount(const std::string& login, const std::string& password); - bool validatePassword(const std::string& login, const std::string& password); - Session& openSession(const std::string& login); - Session& getSession(const std::string& accessToken); - DB::Resource getDatabase(); - private: void handleRequest(std::unique_ptr request); - static std::string generateRandomString(std::size_t length); + + static bool info(Request* request, Server* server); + static bool printEnvironment(Request* request, Server* server); private: - using Sessions = std::map>; - bool terminating; uint64_t requestCount; std::optional serverName; - std::shared_ptr router; - std::shared_ptr pool; - std::shared_ptr taskManager; - std::shared_ptr scheduler; - Sessions sessions; + Router router; + std::unique_ptr db; }; diff --git a/server/session.cpp b/server/session.cpp deleted file mode 100644 index ae01244..0000000 --- a/server/session.cpp +++ /dev/null @@ -1,218 +0,0 @@ -//SPDX-FileCopyrightText: 2023 Yury Gubich -//SPDX-License-Identifier: GPL-3.0-or-later - -#include "session.h" - -#include "handler/poll.h" - -Session::Session( - std::weak_ptr scheduler, - unsigned int id, - unsigned int owner, - const std::string& access, - const std::string& renew, - unsigned int timeout -): - id(id), - owner(owner), - scheduler(scheduler), - access(access), - renew(renew), - polling(nullptr), - timeoutId(TM::Scheduler::none), - timeout(timeout), - mtx(), - cache({ - {"system", { - {"invalidate", true} - }} - }) -{} - -Session::~Session () { - if (timeoutId != TM::Scheduler::none) { - if (std::shared_ptr sch = scheduler.lock()) - sch->cancel(timeoutId); - } -} - -std::string Session::getAccessToken() const { - return access; -} - -std::string Session::getRenewToken() const { - return renew; -} - -void Session::accept(std::unique_ptr request) { - std::lock_guard lock(mtx); - std::shared_ptr sch = scheduler.lock(); - if (polling) { - if (timeoutId != TM::Scheduler::none) { - if (sch) - sch->cancel(timeoutId); - - timeoutId = TM::Scheduler::none; - } - Handler::Poll::error(*polling.get(), Handler::Poll::Result::replace, Response::Status::ok); - polling.reset(); - } - - std::map form = request->getForm(); - auto clear = form.find("clearCache"); - if (clear != form.end() && clear->second == "all") - cache.clear(); - - if (!cache.empty()) - return sendUpdates(std::move(request)); - - if (!sch) { - std::cerr << "Was unable to schedule polling timeout, replying with an error" << std::endl; - Handler::Poll::error(*request.get(), Handler::Poll::Result::unknownError, Response::Status::internalError); - return; - } - - timeoutId = sch->schedule(std::bind(&Session::onTimeout, this), timeout); - polling = std::move(request); -} - -void Session::sendUpdates (std::unique_ptr request) { - Response& res = request->createResponse(Response::Status::ok); - nlohmann::json body = nlohmann::json::object(); - body["result"] = Handler::Poll::Result::success; - nlohmann::json& data = body["data"] = nlohmann::json::object(); - - for (const auto& category : cache) { - nlohmann::json& cat = data[category.first] = nlohmann::json::object(); - for (const auto& entry : category.second) - cat[entry.first] = entry.second; - } - - res.setBody(body); - res.send(); -} - -void Session::onTimeout () { - std::lock_guard lock(mtx); - timeoutId = TM::Scheduler::none; - Handler::Poll::error(*polling.get(), Handler::Poll::Result::timeout, Response::Status::ok); - polling.reset(); -} - -void Session::assetAdded (const DB::Asset& asset) { - std::lock_guard lock(mtx); - std::map& assets = cache["assets"]; - auto addedItr = assets.find("added"); - if (addedItr == assets.end()) - addedItr = assets.emplace("added", nlohmann::json::array()).first; - - addedItr->second.push_back(asset.toJSON()); - checkUpdates(); -} - -void Session::assetChanged (const DB::Asset& asset) { - std::lock_guard lock(mtx); - std::map& assets = cache["assets"]; - auto itr = assets.find("changed"); - if (itr == assets.end()) - itr = assets.emplace("changed", nlohmann::json::array()).first; - - removeByID(itr->second, asset.id); - itr->second.push_back(asset.toJSON()); - - checkUpdates(); -} - -void Session::assetRemoved (unsigned int assetId) { - std::lock_guard lock(mtx); - std::map& assets = cache["assets"]; - auto itr = assets.find("added"); - if (itr != assets.end()) - removeByID(itr->second, assetId); - else { - itr = assets.find("removed"); - if (itr == assets.end()) - itr = assets.emplace("removed", nlohmann::json::array()).first; - - itr->second.push_back(assetId); - } - - itr = assets.find("changed"); - if (itr != assets.end()) - removeByID(itr->second, assetId); - - checkUpdates(); -} - -void Session::transactionAdded(const DB::Transaction& txn) { - std::lock_guard lock(mtx); - std::map& txns = cache["transactions"]; - auto itr = txns.find("changed"); - if (itr == txns.end()) - itr = txns.emplace("changed", nlohmann::json::array()).first; - - removeByID(itr->second, txn.id); - itr->second.push_back(txn.toJSON()); - - checkUpdates(); -} - -void Session::transactionChanged(const DB::Transaction& txn) { - std::lock_guard lock(mtx); - std::map& txns = cache["transactions"]; - auto itr = txns.find("changed"); - if (itr == txns.end()) - itr = txns.emplace("changed", nlohmann::json::array()).first; - - removeByID(itr->second, txn.id); - itr->second.push_back(txn.toJSON()); - - checkUpdates(); -} - -void Session::transactionRemoved(unsigned int txnId) { - std::lock_guard lock(mtx); - std::map& txns = cache["transactions"]; - auto itr = txns.find("added"); - if (itr != txns.end()) - removeByID(itr->second, txnId); - else { - itr = txns.find("removed"); - if (itr == txns.end()) - itr = txns.emplace("removed", nlohmann::json::array()).first; - - itr->second.push_back(txnId); - } - - itr = txns.find("changed"); - if (itr != txns.end()) - removeByID(itr->second, txnId); - - checkUpdates(); -} - -void Session::removeByID(nlohmann::json& array, unsigned int id) { - array.erase( - std::remove_if( - array.begin(), - array.end(), - [id](const nlohmann::json& item) { - return item["id"].get() == id; - } - ), - array.end() - ); -} - -void Session::checkUpdates () { - std::shared_ptr sch = scheduler.lock(); - if (polling) { - if (timeoutId != TM::Scheduler::none) { - if (sch) - sch->cancel(timeoutId); - - timeoutId = TM::Scheduler::none; - } - sendUpdates(std::move(polling)); - } -} diff --git a/server/session.h b/server/session.h deleted file mode 100644 index a5329ac..0000000 --- a/server/session.h +++ /dev/null @@ -1,66 +0,0 @@ -//SPDX-FileCopyrightText: 2023 Yury Gubich -//SPDX-License-Identifier: GPL-3.0-or-later - -#pragma once - -#include -#include -#include - -#include - -#include "request/accepting.h" -#include "taskmanager/scheduler.h" - -#include "database/schema/asset.h" -#include "database/schema/transaction.h" - -class Session : public Accepting { -public: - Session ( - std::weak_ptr scheduler, - unsigned int id, - unsigned int owner, - const std::string& access, - const std::string& renew, - unsigned int timeout - ); - Session (const Session&) = delete; - Session (Session&& other) = delete; - ~Session (); - Session& operator = (const Session&) = delete; - Session& operator = (Session&& other) = delete; - - std::string getAccessToken () const; - std::string getRenewToken () const; - void accept (std::unique_ptr request) override; - - const unsigned int id; - const unsigned int owner; - - void assetAdded (const DB::Asset& asset); - void assetChanged (const DB::Asset& asset); - void assetRemoved (unsigned int assetId); - - void transactionAdded(const DB::Transaction& txn); - void transactionChanged(const DB::Transaction& txn); - void transactionRemoved(unsigned int txnId); - -private: - void onTimeout (); - void sendUpdates (std::unique_ptr request); - void checkUpdates (); - - void static removeByID (nlohmann::json& array, unsigned int id); - -private: - std::weak_ptr scheduler; - std::string access; - std::string renew; - std::unique_ptr polling; - TM::Record::ID timeoutId; - TM::Scheduler::Delay timeout; - std::mutex mtx; - - std::map> cache; -}; diff --git a/stream/CMakeLists.txt b/stream/CMakeLists.txt index 0d75e92..6eef2a8 100644 --- a/stream/CMakeLists.txt +++ b/stream/CMakeLists.txt @@ -1,6 +1,3 @@ -#SPDX-FileCopyrightText: 2023 Yury Gubich -#SPDX-License-Identifier: GPL-3.0-or-later - set(HEADERS stream.h ostream.h @@ -11,4 +8,4 @@ set(SOURCES ostream.cpp ) -target_sources(${PROJECT_NAME} PRIVATE ${SOURCES}) +target_sources(pica PRIVATE ${SOURCES}) diff --git a/stream/ostream.cpp b/stream/ostream.cpp index 31adf8a..8674355 100644 --- a/stream/ostream.cpp +++ b/stream/ostream.cpp @@ -1,5 +1,5 @@ -//SPDX-FileCopyrightText: 2023 Yury Gubich -//SPDX-License-Identifier: GPL-3.0-or-later +// SPDX-FileCopyrightText: 2023 Yury Gubich +// SPDX-License-Identifier: GPL-3.0-or-later #include "ostream.h" diff --git a/stream/ostream.h b/stream/ostream.h index e25fd4a..e08a936 100644 --- a/stream/ostream.h +++ b/stream/ostream.h @@ -1,5 +1,5 @@ -//SPDX-FileCopyrightText: 2023 Yury Gubich -//SPDX-License-Identifier: GPL-3.0-or-later +// SPDX-FileCopyrightText: 2023 Yury Gubich +// SPDX-License-Identifier: GPL-3.0-or-later #pragma once diff --git a/stream/stream.cpp b/stream/stream.cpp index f65203a..022dc07 100644 --- a/stream/stream.cpp +++ b/stream/stream.cpp @@ -1,5 +1,5 @@ -//SPDX-FileCopyrightText: 2023 Yury Gubich -//SPDX-License-Identifier: GPL-3.0-or-later +// SPDX-FileCopyrightText: 2023 Yury Gubich +// SPDX-License-Identifier: GPL-3.0-or-later #include "stream.h" diff --git a/stream/stream.h b/stream/stream.h index 697942a..07a263d 100644 --- a/stream/stream.h +++ b/stream/stream.h @@ -1,5 +1,5 @@ -//SPDX-FileCopyrightText: 2023 Yury Gubich -//SPDX-License-Identifier: GPL-3.0-or-later +// SPDX-FileCopyrightText: 2023 Yury Gubich +// SPDX-License-Identifier: GPL-3.0-or-later #pragma once diff --git a/taskmanager/CMakeLists.txt b/taskmanager/CMakeLists.txt deleted file mode 100644 index 70a8210..0000000 --- a/taskmanager/CMakeLists.txt +++ /dev/null @@ -1,22 +0,0 @@ -#SPDX-FileCopyrightText: 2023 Yury Gubich -#SPDX-License-Identifier: GPL-3.0-or-later - -set(HEADERS - manager.h - job.h - route.h - scheduler.h - function.h - record.h -) - -set(SOURCES - manager.cpp - job.cpp - route.cpp - scheduler.cpp - function.cpp - record.cpp -) - -target_sources(${PROJECT_NAME} PRIVATE ${SOURCES}) diff --git a/taskmanager/function.cpp b/taskmanager/function.cpp deleted file mode 100644 index bf6d01c..0000000 --- a/taskmanager/function.cpp +++ /dev/null @@ -1,12 +0,0 @@ -//SPDX-FileCopyrightText: 2024 Yury Gubich -//SPDX-License-Identifier: GPL-3.0-or-later - -#include "function.h" - -TM::Function::Function (const std::function& fn): - fn(fn) -{} - -void TM::Function::execute () { - fn(); -} diff --git a/taskmanager/function.h b/taskmanager/function.h deleted file mode 100644 index c6ca024..0000000 --- a/taskmanager/function.h +++ /dev/null @@ -1,20 +0,0 @@ -//SPDX-FileCopyrightText: 2024 Yury Gubich -//SPDX-License-Identifier: GPL-3.0-or-later - -#pragma once - -#include "functional" - -#include "job.h" - -namespace TM { -class Function : public Job { -public: - Function(const std::function& fn); - - void execute () override; - -private: - std::function fn; -}; -} diff --git a/taskmanager/job.cpp b/taskmanager/job.cpp deleted file mode 100644 index 5c74920..0000000 --- a/taskmanager/job.cpp +++ /dev/null @@ -1,9 +0,0 @@ -//SPDX-FileCopyrightText: 2023 Yury Gubich -//SPDX-License-Identifier: GPL-3.0-or-later - -#include "job.h" - -TM::Job::Job () -{} - -TM::Job::~Job () {} diff --git a/taskmanager/job.h b/taskmanager/job.h deleted file mode 100644 index 4895ced..0000000 --- a/taskmanager/job.h +++ /dev/null @@ -1,19 +0,0 @@ -//SPDX-FileCopyrightText: 2023 Yury Gubich -//SPDX-License-Identifier: GPL-3.0-or-later - -#pragma once - -namespace TM { -class Job { -public: - Job(); - Job(const Job& other) = delete; - Job(Job&& other) = delete; - virtual ~Job(); - - Job& operator = (const Job& other) = delete; - Job& operator = (Job&& other) = delete; - - virtual void execute() = 0; -}; -} diff --git a/taskmanager/manager.cpp b/taskmanager/manager.cpp deleted file mode 100644 index 1f86661..0000000 --- a/taskmanager/manager.cpp +++ /dev/null @@ -1,69 +0,0 @@ -//SPDX-FileCopyrightText: 2023 Yury Gubich -//SPDX-License-Identifier: GPL-3.0-or-later - -#include "manager.h" - -TM::Manager::Manager (): - terminating(false), - threads(), - queue(), - mtx(), - cond() -{} - -TM::Manager::~Manager () { - std::unique_lock lock(mtx); - if (threads.empty()) - return; - - lock.unlock(); - stop(); -} - -void TM::Manager::start () { - std::lock_guard lock(mtx); - - std::size_t amount = std::thread::hardware_concurrency(); - for (std::size_t i = 0; i < amount; ++i) - threads.emplace_back(std::thread(&Manager::loop, this)); -} - -void TM::Manager::stop () { - std::unique_lock lock(mtx); - - terminating = true; - - lock.unlock(); - cond.notify_all(); - for (std::thread& thread : threads) - thread.join(); - - lock.lock(); - threads.clear(); - terminating = false; -} - -void TM::Manager::loop () { - while (true) { - std::unique_lock lock(mtx); - while (!terminating && queue.empty()) - cond.wait(lock); - - if (terminating) - return; - - std::unique_ptr job = std::move(queue.front()); - queue.pop(); - lock.unlock(); - - job->execute(); - } -} - -void TM::Manager::schedule (std::unique_ptr job) { - std::unique_lock lock(mtx); - queue.emplace(std::move(job)); - - lock.unlock(); - cond.notify_one(); -} diff --git a/taskmanager/manager.h b/taskmanager/manager.h deleted file mode 100644 index 1f1aa3b..0000000 --- a/taskmanager/manager.h +++ /dev/null @@ -1,40 +0,0 @@ -//SPDX-FileCopyrightText: 2023 Yury Gubich -//SPDX-License-Identifier: GPL-3.0-or-later - -#pragma once - -#include -#include -#include -#include -#include -#include - -#include "job.h" - -namespace TM { -class Manager { -public: - Manager(); - Manager(const Manager&) = delete; - Manager(Manager&&) = delete; - ~Manager(); - - Manager& operator = (const Manager&) = delete; - Manager& operator = (Manager&&) = delete; - - void start(); - void stop(); - void schedule(std::unique_ptr job); - -private: - void loop(); - -private: - bool terminating; - std::vector threads; - std::queue> queue; - std::mutex mtx; - std::condition_variable cond; -}; -} diff --git a/taskmanager/record.cpp b/taskmanager/record.cpp deleted file mode 100644 index 77c8850..0000000 --- a/taskmanager/record.cpp +++ /dev/null @@ -1,18 +0,0 @@ -//SPDX-FileCopyrightText: 2024 Yury Gubich -//SPDX-License-Identifier: GPL-3.0-or-later - -#include "record.h" - -TM::Record::Record (ID id, const Task& task, Time time): - id(id), - task(task), - time(time) -{} - -bool TM::Record::operator < (const Record& other) const { - return time < other.time; -} - -bool TM::Record::operator > (const Record& other) const { - return time > other.time; -} diff --git a/taskmanager/record.h b/taskmanager/record.h deleted file mode 100644 index fe2b7c5..0000000 --- a/taskmanager/record.h +++ /dev/null @@ -1,26 +0,0 @@ -//SPDX-FileCopyrightText: 2024 Yury Gubich -//SPDX-License-Identifier: GPL-3.0-or-later - -#pragma once - -#include -#include -#include - -namespace TM { -class Record { -public: - using Time = std::chrono::time_point; - using Task = std::function; - using ID = uint64_t; - - Record(ID id, const Task& task, Time time); - - ID id; - Task task; - Time time; - - bool operator > (const Record& other) const; - bool operator < (const Record& other) const; -}; -} diff --git a/taskmanager/route.cpp b/taskmanager/route.cpp deleted file mode 100644 index f2d5af0..0000000 --- a/taskmanager/route.cpp +++ /dev/null @@ -1,13 +0,0 @@ -//SPDX-FileCopyrightText: 2023 Yury Gubich -//SPDX-License-Identifier: GPL-3.0-or-later - -#include "route.h" - -TM::Route::Route (std::shared_ptr router, std::unique_ptr request): - router(router), - request(std::move(request)) -{} - -void TM::Route::execute () { - router->route(std::move(request)); -} diff --git a/taskmanager/route.h b/taskmanager/route.h deleted file mode 100644 index 49d7c69..0000000 --- a/taskmanager/route.h +++ /dev/null @@ -1,23 +0,0 @@ -//SPDX-FileCopyrightText: 2023 Yury Gubich -//SPDX-License-Identifier: GPL-3.0-or-later - -#pragma once - -#include - -#include "job.h" -#include "server/router.h" -#include "request/request.h" - -namespace TM { -class Route : public Job { -public: - Route(std::shared_ptr router, std::unique_ptr request); - - void execute () override; - -private: - std::shared_ptr router; - std::unique_ptr request; -}; -} diff --git a/taskmanager/scheduler.cpp b/taskmanager/scheduler.cpp deleted file mode 100644 index fcae841..0000000 --- a/taskmanager/scheduler.cpp +++ /dev/null @@ -1,92 +0,0 @@ -//SPDX-FileCopyrightText: 2023 Yury Gubich -//SPDX-License-Identifier: GPL-3.0-or-later - -#include "scheduler.h" - -const TM::Record::ID TM::Scheduler::none = 0; - -TM::Scheduler::Scheduler (std::weak_ptr manager): - queue(), - scheduled(), - manager(manager), - mutex(), - cond(), - thread(nullptr), - running(false), - idCounter(TM::Scheduler::none) -{} - -TM::Scheduler::~Scheduler () { - stop(); -} - -void TM::Scheduler::start () { - std::unique_lock lock(mutex); - if (running) - return; - - running = true; - thread = std::make_unique(&Scheduler::loop, this); -} - -void TM::Scheduler::stop () { - std::unique_lock lock(mutex); - if (!running) - return; - - running = false; - - lock.unlock(); - cond.notify_all(); - thread->join(); - - lock.lock(); - thread.reset(); -} - -void TM::Scheduler::loop () { - while (running) { - std::unique_lock lock(mutex); - if (queue.empty()) { - cond.wait(lock); - continue; - } - - Time currentTime = std::chrono::steady_clock::now(); - while (!queue.empty()) { - Time nextScheduledTime = queue.top().time; - if (nextScheduledTime > currentTime) { - cond.wait_until(lock, nextScheduledTime); - break; - } - - Record record = queue.pop(); - std::size_t count = scheduled.erase(record.id); - if (count == 0) //it means this record has been cancelled, no need to execute it - continue; - - lock.unlock(); - std::shared_ptr mngr = manager.lock(); - if (mngr) - mngr->schedule(std::make_unique(record.task)); - - lock.lock(); - } - } -} - -TM::Record::ID TM::Scheduler::schedule (const Task& task, Delay delay) { - std::unique_lock lock(mutex); - Time time = std::chrono::steady_clock::now() + delay; - queue.emplace(++idCounter, task, time); - scheduled.emplace(idCounter); - - lock.unlock(); - cond.notify_one(); - - return idCounter; -} - -bool TM::Scheduler::cancel (Record::ID id) { - return scheduled.erase(id) != 0; //not to mess with the queue, here we just mark it as not scheduled -} //and when the time comes it will be just discarded diff --git a/taskmanager/scheduler.h b/taskmanager/scheduler.h deleted file mode 100644 index 6b3e609..0000000 --- a/taskmanager/scheduler.h +++ /dev/null @@ -1,52 +0,0 @@ -//SPDX-FileCopyrightText: 2023 Yury Gubich -//SPDX-License-Identifier: GPL-3.0-or-later - -#pragma once - -#include -#include -#include -#include -#include -#include - -#include "manager.h" -#include "function.h" -#include "record.h" -#include "utils/helpers.h" - -namespace TM { -class Scheduler { -public: - using Delay = std::chrono::milliseconds; - using Time = Record::Time; - using Task = Record::Task; - - Scheduler (std::weak_ptr manager); - ~Scheduler (); - - void start (); - void stop (); - Record::ID schedule (const Task& task, Delay delay); - bool cancel (Record::ID id); - - static const Record::ID none; - -private: - void loop (); - -private: - PriorityQueue< - Record, - std::vector, - std::greater<> - > queue; - std::set scheduled; - std::weak_ptr manager; - std::mutex mutex; - std::condition_variable cond; - std::unique_ptr thread; - bool running; - Record::ID idCounter; -}; -} diff --git a/utils/CMakeLists.txt b/utils/CMakeLists.txt index ac09c13..93d7c25 100644 --- a/utils/CMakeLists.txt +++ b/utils/CMakeLists.txt @@ -1,14 +1,9 @@ -#SPDX-FileCopyrightText: 2023 Yury Gubich -#SPDX-License-Identifier: GPL-3.0-or-later - set(HEADER helpers.h - formdecode.h ) set(SOURCES helpers.cpp - formdecode.cpp ) target_sources(${PROJECT_NAME} PRIVATE ${SOURCES}) diff --git a/utils/formdecode.cpp b/utils/formdecode.cpp deleted file mode 100644 index ed33203..0000000 --- a/utils/formdecode.cpp +++ /dev/null @@ -1,46 +0,0 @@ -//SPDX-FileCopyrightText: 2023 Yury Gubich -//SPDX-License-Identifier: GPL-3.0-or-later - -#include "formdecode.h" - -#include - -std::map urlDecodeAndParse(const std::string_view& encoded) { - std::map result; - - std::istringstream iss(std::string(encoded.begin(), encoded.end())); - std::string pair; - while (std::getline(iss, pair, '&')) { - size_t equalsPos = pair.find('='); - if (equalsPos != std::string::npos) - result.emplace( - urlDecode(pair.substr(0, equalsPos)), - urlDecode(pair.substr(equalsPos + 1)) - ); - } - - return result; -} - -std::string urlDecode(const std::string_view& encoded) { - std::ostringstream decoded; - - for (size_t i = 0; i < encoded.length(); ++i) { - if (encoded[i] == '%') { - if (i + 2 < encoded.length()) { - std::string hexStr(encoded.substr(i + 1, 2)); - char hexValue = static_cast(std::stoul(hexStr, nullptr, 16)); - decoded.put(hexValue); - i += 2; - } else { - decoded.put(encoded[i]); - } - } else if (encoded[i] == '+') { - decoded.put(' '); - } else { - decoded.put(encoded[i]); - } - } - - return decoded.str(); -} diff --git a/utils/formdecode.h b/utils/formdecode.h deleted file mode 100644 index ffbad3d..0000000 --- a/utils/formdecode.h +++ /dev/null @@ -1,11 +0,0 @@ -//SPDX-FileCopyrightText: 2023 Yury Gubich -//SPDX-License-Identifier: GPL-3.0-or-later - -#pragma once - -#include -#include -#include - -std::map urlDecodeAndParse(const std::string_view& encoded); -std::string urlDecode(const std::string_view& encoded); diff --git a/utils/helpers.cpp b/utils/helpers.cpp index 756cfa8..d94a401 100644 --- a/utils/helpers.cpp +++ b/utils/helpers.cpp @@ -1,20 +1,15 @@ -//SPDX-FileCopyrightText: 2023 Yury Gubich -//SPDX-License-Identifier: GPL-3.0-or-later +// SPDX-FileCopyrightText: 2023 Yury Gubich +// SPDX-License-Identifier: GPL-3.0-or-later #include "helpers.h" #include "iostream" -#include #include "config.h" static bool installed = false; static std::filesystem::path sPath; -bool isSpace(char ch){ - return std::isspace(static_cast(ch)); -} - void setAbsoluteSharedPath () { installed = true; sPath = FULL_DATA_DIR "/" PROJECT_NAME; // should be something like /usr/share/pica or /local/usr/share/pica @@ -36,12 +31,12 @@ void initPaths(const char* programPath) { if (cp.parent_path() == FULL_BIN_DIR) return setAbsoluteSharedPath(); - //std::cout << cp << std::endl; + std::cout << cp << std::endl; std::filesystem::path parent = cp.parent_path(); if (endsWith(parent.string(), BIN_DIR)) { //this is the case when the program is installed somewhere but not system root std::filesystem::path bin(BIN_DIR); //so it will read from something like ../share/pica/ relative to the binary for (const auto& hop : bin) { - UNUSED(hop); //I do this just to make as many ups as many members are in bin + (void)hop; //I do this just to make as many ups as many members are in bin parent = parent.parent_path(); } sPath = parent / DATA_DIR / PROJECT_NAME; @@ -64,40 +59,3 @@ bool endsWith(const std::string& string, const std::string& query) { return false; } } - -void ltrim(std::string& string) { - string.erase( - string.begin(), - std::find_if( - string.begin(), - string.end(), - std::not_fn(isSpace) - ) - ); -} - -void rtrim(std::string& string) { - string.erase( - std::find_if( - string.rbegin(), - string.rend(), - std::not_fn(isSpace) - ).base(), - string.end() - ); -} - -void trim(std::string& string) { - ltrim(string); - rtrim(string); -} - -std::string extract(std::string& string, std::string::size_type begin, std::string::size_type end) { - std::string result = string.substr(begin, end); - if (end == std::string::npos) - string.erase(begin, end); - else - string.erase(begin, result.length() + 1); - - return result; -} diff --git a/utils/helpers.h b/utils/helpers.h index 91a597d..d9fae3c 100644 --- a/utils/helpers.h +++ b/utils/helpers.h @@ -1,69 +1,12 @@ -//SPDX-FileCopyrightText: 2023 Yury Gubich -//SPDX-License-Identifier: GPL-3.0-or-later +// SPDX-FileCopyrightText: 2023 Yury Gubich +// SPDX-License-Identifier: GPL-3.0-or-later #pragma once #include #include -#include - -#define UNUSED(variable) (void)variable void initPaths(const char* programPath); const std::filesystem::path& sharedPath(); bool endsWith(const std::string& string, const std::string& query); -void ltrim(std::string& string); -void rtrim(std::string& string); -void trim(std::string& string); -std::string extract(std::string& string, std::string::size_type begin, std::string::size_type end); -template -struct FirstGreater { - bool operator () (const T& left, const T& right) { - return std::get<0>(left) > std::get<0>(right); - } -}; - -template > -class PriorityQueue { -public: - explicit PriorityQueue(const Compare& compare = Compare()): - container(), - compare(compare) - {} - - const Type& top () const { - return container.front(); - } - - bool empty () const { - return container.empty(); - } - - template - void emplace (Args&&... args) { - container.emplace_back(std::forward(args)...); - std::push_heap(container.begin(), container.end(), compare); - } - - void push (const Type& element) { - container.push_back(element); - std::push_heap(container.begin(), container.end(), compare); - } - - void push (Type&& element) { - container.push_back(std::move(element)); - std::push_heap(container.begin(), container.end(), compare); - } - - Type pop () { - std::pop_heap(container.begin(), container.end(), compare); - Type result = std::move(container.back()); - container.pop_back(); - return result; - } - -private: - Container container; - Compare compare; -};