From f0d205dee79a6e8e91ac03f07729c8ed4a918611 Mon Sep 17 00:00:00 2001 From: blue Date: Mon, 11 Dec 2023 20:29:55 -0300 Subject: [PATCH 01/27] just some thoughts --- CMakeLists.txt | 35 +++++++++++++++++++---- config.h.in | 1 + database/CMakeLists.txt | 2 +- database/migrations/CMakeLists.txt | 2 +- database/migrations/m0.sql | 30 +++++++++++++++++++ database/mysql/CMakeLists.txt | 4 +-- database/mysql/mysql.cpp | 46 ++++++++++++++++++++++++++++-- database/mysql/mysql.h | 2 ++ response/CMakeLists.txt | 2 +- server/CMakeLists.txt | 2 +- server/server.cpp | 24 +++++----------- server/server.h | 2 ++ stream/CMakeLists.txt | 2 +- utils/helpers.cpp | 45 ++++++++++++++++++++++++++++- utils/helpers.h | 7 ++++- 15 files changed, 172 insertions(+), 34 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 3754df3..1d90451 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -13,14 +13,37 @@ include(GNUInstallDirs) set(CMAKE_CXX_STANDARD 17) set(CMAKE_CXX_STANDARD_REQUIRED ON) set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} "${CMAKE_SOURCE_DIR}/cmake") -set(PICA_BIN_DIR ${CMAKE_CURRENT_BINARY_DIR}) + +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}) find_package(nlohmann_json REQUIRED) find_package(FCGI REQUIRED) -add_executable(pica main.cpp) -target_include_directories(pica PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}) -target_include_directories(pica PRIVATE ${CMAKE_CURRENT_BINARY_DIR}) +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_subdirectory(server) add_subdirectory(request) @@ -31,10 +54,10 @@ add_subdirectory(utils) configure_file(config.h.in config.h @ONLY) -target_link_libraries(pica PRIVATE +target_link_libraries(${PROJECT_NAME} PRIVATE FCGI::FCGI FCGI::FCGI++ nlohmann_json::nlohmann_json ) -install(TARGETS pica RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR}) +install(TARGETS ${PROJECT_NAME} RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR}) diff --git a/config.h.in b/config.h.in index 703a9f7..07ee1ea 100644 --- a/config.h.in +++ b/config.h.in @@ -7,3 +7,4 @@ #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 8f5b864..42941d6 100644 --- a/database/CMakeLists.txt +++ b/database/CMakeLists.txt @@ -6,7 +6,7 @@ set(SOURCES dbinterface.cpp ) -target_sources(pica PRIVATE ${SOURCES}) +target_sources(${PROJECT_NAME} PRIVATE ${SOURCES}) add_subdirectory(mysql) add_subdirectory(migrations) diff --git a/database/migrations/CMakeLists.txt b/database/migrations/CMakeLists.txt index e71b0a4..9091bba 100644 --- a/database/migrations/CMakeLists.txt +++ b/database/migrations/CMakeLists.txt @@ -1,5 +1,5 @@ set(MIGRATIONS migrations) -configure_file(m0.sql ${PICA_BIN_DIR}/${CMAKE_INSTALL_DATADIR}/${MIGRATIONS}/m0.sql COPYONLY) +configure_file(m0.sql ${PROJECT_BINARY_DIR}/${CMAKE_INSTALL_DATADIR}/${MIGRATIONS}/m0.sql COPYONLY) install( FILES diff --git a/database/migrations/m0.sql b/database/migrations/m0.sql index 951fa04..53b7028 100644 --- a/database/migrations/m0.sql +++ b/database/migrations/m0.sql @@ -1,6 +1,36 @@ +--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(64), + `salt` VARCHAR(32), + `role` INTEGER UNSIGNED NOT NULL, + `created` TIMESTAMP DEFAULT UTC_TIMESTAMP(), + + FOREIGN KEY (role) REFERENCES roles(id) +); + +--creating defailt roles +INSERT IGNORE INTO roles (`name`) +VALUES ('root'); + +--inserting initial version INSERT INTO system (`key`, `value`) VALUES ('version', '0'); + +--recording initial time +INSERT INTO system (`key`, `value`) VALUES ('created', UTC_TIMESTAMP()); diff --git a/database/mysql/CMakeLists.txt b/database/mysql/CMakeLists.txt index 75dd5e1..64d1920 100644 --- a/database/mysql/CMakeLists.txt +++ b/database/mysql/CMakeLists.txt @@ -10,6 +10,6 @@ set(SOURCES find_package(MariaDB REQUIRED) -target_sources(pica PRIVATE ${SOURCES}) +target_sources(${PROJECT_NAME} PRIVATE ${SOURCES}) -target_link_libraries(pica PRIVATE MariaDB::client) +target_link_libraries(${PROJECT_NAME} PRIVATE MariaDB::client) diff --git a/database/mysql/mysql.cpp b/database/mysql/mysql.cpp index 6653e25..0532f7c 100644 --- a/database/mysql/mysql.cpp +++ b/database/mysql/mysql.cpp @@ -116,6 +116,14 @@ void MySQL::executeFile(const std::filesystem::path& relativePath) { std::ifstream inputFile(path); std::string query; while (std::getline(inputFile, query, ';')) { + std::optional comment = getComment(query); + while (comment) { + std::cout << '\t' << comment.value() << std::endl; + comment = getComment(query); + } + if (query.empty()) + continue; + int result = mysql_query(con, query.c_str()); if (result != 0) { int errcode = mysql_errno(con); @@ -162,15 +170,49 @@ 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(++currentVersion) + << std::to_string(nextVersion) << std::endl; executeFile(fileName); - setVersion(currentVersion); + setVersion(nextVersion); + currentVersion = nextVersion; } std::cout << "Database is now on actual version " << std::to_string(targetVersion) << std::endl; } + +std::optional MySQL::getComment(std::string& string) { + ltrim(string); + if (string.length() < 2) + return std::nullopt; + + if (string[0] == '-') { + if (string[1] == '-') { + string.erase(0, 2); + std::string::size_type eol = string.find('\n'); + return extract(string, 0, eol); + } + } else if (string[0] == '/') { + if (string[1] == '*') { + string.erase(0, 2); + std::string::size_type end = 0; + do { + end = string.find(end, '*'); + } while (end != std::string::npos && end < string.size() - 1 && string[end + 1] == '/'); + if (end < string.size() - 1) + end = std::string::npos; + + return extract(string, 0, end); + } + } + + return std::nullopt; +} + diff --git a/database/mysql/mysql.h b/database/mysql/mysql.h index 37181a3..007c985 100644 --- a/database/mysql/mysql.h +++ b/database/mysql/mysql.h @@ -5,6 +5,7 @@ #include #include +#include #include @@ -28,6 +29,7 @@ public: private: void executeFile(const std::filesystem::path& relativePath); + static std::optional getComment(std::string& string); protected: MYSQL connection; diff --git a/response/CMakeLists.txt b/response/CMakeLists.txt index 6b35c08..e4c5093 100644 --- a/response/CMakeLists.txt +++ b/response/CMakeLists.txt @@ -6,4 +6,4 @@ set(SOURCES response.cpp ) -target_sources(pica PRIVATE ${SOURCES}) +target_sources(${PROJECT_NAME} PRIVATE ${SOURCES}) diff --git a/server/CMakeLists.txt b/server/CMakeLists.txt index 694b3a5..0e38cbe 100644 --- a/server/CMakeLists.txt +++ b/server/CMakeLists.txt @@ -8,4 +8,4 @@ set(SOURCES router.cpp ) -target_sources(pica PRIVATE ${SOURCES}) +target_sources(${PROJECT_NAME} PRIVATE ${SOURCES}) diff --git a/server/server.cpp b/server/server.cpp index 6d1b9a3..f08b408 100644 --- a/server/server.cpp +++ b/server/server.cpp @@ -20,18 +20,8 @@ Server::Server(): db->setCredentials("pica", "pica"); db->setDatabase("pica"); - 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); + db->connect("/run/mysqld/mysqld.sock"); + db->migrate(currentDbVesion); router.addRoute("info", Server::info); router.addRoute("env", Server::printEnvironment); @@ -74,7 +64,7 @@ void Server::handleRequest(std::unique_ptr request) { try { std::string path = request->getPath(serverName.value()); router.route(path.data(), std::move(request), this); - } catch (const std::exception e) { + } catch (const std::exception& e) { Response error(Response::Status::internalError); error.setBody(std::string(e.what())); error.replyTo(*request.get()); @@ -82,7 +72,7 @@ void Server::handleRequest(std::unique_ptr request) { } bool Server::printEnvironment(Request* request, Server* server) { - (void)server; + UNUSED(server); nlohmann::json body = nlohmann::json::object(); request->printEnvironment(body); @@ -94,11 +84,11 @@ bool Server::printEnvironment(Request* request, Server* server) { } bool Server::info(Request* request, Server* server) { - (void)server; + UNUSED(server); Response res; nlohmann::json body = nlohmann::json::object(); - body["type"] = "Pica"; - body["version"] = "0.0.1"; + body["type"] = PROJECT_NAME; + body["version"] = PROJECT_VERSION; res.setBody(body); res.replyTo(*request); diff --git a/server/server.h b/server/server.h index dae5b66..f13df21 100644 --- a/server/server.h +++ b/server/server.h @@ -21,6 +21,8 @@ #include "response/response.h" #include "router.h" #include "database/dbinterface.h" +#include "utils/helpers.h" +#include "config.h" class Server { public: diff --git a/stream/CMakeLists.txt b/stream/CMakeLists.txt index 6eef2a8..3fef9b0 100644 --- a/stream/CMakeLists.txt +++ b/stream/CMakeLists.txt @@ -8,4 +8,4 @@ set(SOURCES ostream.cpp ) -target_sources(pica PRIVATE ${SOURCES}) +target_sources(${PROJECT_NAME} PRIVATE ${SOURCES}) diff --git a/utils/helpers.cpp b/utils/helpers.cpp index d94a401..c1b93e4 100644 --- a/utils/helpers.cpp +++ b/utils/helpers.cpp @@ -4,12 +4,18 @@ #include "helpers.h" #include "iostream" +#include +#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,7 +42,7 @@ void initPaths(const char* programPath) { 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) { - (void)hop; //I do this just to make as many ups as many members are in bin + UNUSED(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; @@ -59,3 +65,40 @@ 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 d9fae3c..34fcd03 100644 --- a/utils/helpers.h +++ b/utils/helpers.h @@ -6,7 +6,12 @@ #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); From 3fe6d254486f15bf60a99f378b79514e97abf8f7 Mon Sep 17 00:00:00 2001 From: blue Date: Wed, 13 Dec 2023 17:33:11 -0300 Subject: [PATCH 02/27] a bit better way to treah handlers --- CMakeLists.txt | 1 + handler/CMakeLists.txt | 13 +++++++ handler/env.cpp | 17 +++++++++ handler/env.h | 16 ++++++++ handler/handler.cpp | 11 ++++++ handler/handler.h | 26 +++++++++++++ handler/info.cpp | 18 +++++++++ handler/info.h | 17 +++++++++ request/request.cpp | 83 +++++++++++++++++++++++++++++++++++------- request/request.h | 23 ++++++++++-- response/response.cpp | 12 ++++-- response/response.h | 7 ++-- server/router.cpp | 73 +++++++++++++++++++++++++++---------- server/router.h | 14 ++++--- server/server.cpp | 52 +++++--------------------- server/server.h | 3 -- 16 files changed, 291 insertions(+), 95 deletions(-) create mode 100644 handler/CMakeLists.txt create mode 100644 handler/env.cpp create mode 100644 handler/env.h create mode 100644 handler/handler.cpp create mode 100644 handler/handler.h create mode 100644 handler/info.cpp create mode 100644 handler/info.h diff --git a/CMakeLists.txt b/CMakeLists.txt index 1d90451..bca2822 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -46,6 +46,7 @@ target_include_directories(${PROJECT_NAME} PRIVATE ${CMAKE_CURRENT_BINARY_DIR}) target_compile_options(${PROJECT_NAME} PRIVATE ${COMPILE_OPTIONS}) add_subdirectory(server) +add_subdirectory(handler) add_subdirectory(request) add_subdirectory(response) add_subdirectory(stream) diff --git a/handler/CMakeLists.txt b/handler/CMakeLists.txt new file mode 100644 index 0000000..a3dd084 --- /dev/null +++ b/handler/CMakeLists.txt @@ -0,0 +1,13 @@ +set(HEADERS + handler.h + info.h + env.h +) + +set(SOURCES + handler.cpp + info.cpp + env.cpp +) + +target_sources(${PROJECT_NAME} PRIVATE ${SOURCES}) diff --git a/handler/env.cpp b/handler/env.cpp new file mode 100644 index 0000000..a615e17 --- /dev/null +++ b/handler/env.cpp @@ -0,0 +1,17 @@ +// 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); + res.setBody(body); + res.send(); +} diff --git a/handler/env.h b/handler/env.h new file mode 100644 index 0000000..b4a5ea4 --- /dev/null +++ b/handler/env.h @@ -0,0 +1,16 @@ +// SPDX-FileCopyrightText: 2023 Yury Gubich +// SPDX-License-Identifier: GPL-3.0-or-later + +#pragma once + +#include "handler.h" + +namespace Handler { + +class Env : public Handler::Handler { +public: + Env(); + virtual void handle(Request& request); + +}; +} diff --git a/handler/handler.cpp b/handler/handler.cpp new file mode 100644 index 0000000..e0c39c1 --- /dev/null +++ b/handler/handler.cpp @@ -0,0 +1,11 @@ +// 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() {} diff --git a/handler/handler.h b/handler/handler.h new file mode 100644 index 0000000..4e682c4 --- /dev/null +++ b/handler/handler.h @@ -0,0 +1,26 @@ +// 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); + +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 new file mode 100644 index 0000000..ba2dfec --- /dev/null +++ b/handler/info.cpp @@ -0,0 +1,18 @@ +// 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); + 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 new file mode 100644 index 0000000..b1adc12 --- /dev/null +++ b/handler/info.h @@ -0,0 +1,17 @@ +// 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/request/request.cpp b/request/request.cpp index 53fb88b..48e084b 100644 --- a/request/request.cpp +++ b/request/request.cpp @@ -3,6 +3,8 @@ #include "request.h" +#include "response/response.h" + constexpr static const char* GET("GET"); constexpr static const char* REQUEST_METHOD("REQUEST_METHOD"); @@ -16,9 +18,18 @@ constexpr static const char* SERVER_NAME("SERVER_NAME"); // // 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() + raw(), + response(nullptr) {} Request::~Request() { @@ -36,12 +47,17 @@ void Request::terminate() { } } -bool Request::isGet() const { - if (state != State::accepted) - throw std::runtime_error("An attempt to read request type on a wrong request state"); +Request::Method Request::method() const { + if (state == State::initial) + throw std::runtime_error("An attempt to read request method on not accepted request"); std::string_view method(FCGX_GetParam(REQUEST_METHOD, raw.envp)); - return method == GET; + for (const auto& pair : methods) { + if (pair.first == method) + return pair.second; + } + + return Request::Method::unknown; } bool Request::wait(int socketDescriptor) { @@ -59,20 +75,61 @@ bool Request::wait(int socketDescriptor) { return result; } -OStream Request::getOutputStream() { - if (state != State::accepted) - throw std::runtime_error("An attempt to request output stream on a wrong request state"); - +OStream Request::getOutputStream(const Response* response) { + validateResponse(response); 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"); - +OStream Request::getErrorStream(const Response* response) { + validateResponse(response); return OStream(raw.err); } +void Request::responseIsComplete(const Response* response) { + 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: + if (Request::response != response) + throw std::runtime_error("An attempt to mark the request as complete by the different response who actually started responding"); + + Request::response = nullptr; + state = State::responded; + break; + case State::responded: + throw std::runtime_error("An attempt to mark the request as a complete for the second time"); + break; + } +} + +void Request::validateResponse(const Response* response) { + switch (state) { + case State::initial: + throw std::runtime_error("An attempt to request stream while the request wasn't even accepted yet"); + break; + case State::accepted: + Request::response = response; + state = State::responding; + break; + case State::responding: + if (Request::response != response) + throw std::runtime_error("Error handling a request: first time one response started replying, then another continued"); + + break; + case State::responded: + throw std::runtime_error("An attempt to request stream on a request that was already done responding"); + break; + } +} + +Request::State Request::currentState() const { + return state; +} + 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"); diff --git a/request/request.h b/request/request.h index 2fc9a32..9871106 100644 --- a/request/request.h +++ b/request/request.h @@ -6,6 +6,8 @@ #include #include #include +#include +#include #include @@ -13,14 +15,22 @@ #include "stream/ostream.h" +class Response; class Request { public: enum class State { initial, accepted, + responding, responded }; + enum class Method { + get, + post, + unknown + }; + Request(); ~Request(); Request(const Request& other) = delete; @@ -30,17 +40,24 @@ public: bool wait(int socketDescriptor); void terminate(); - bool isGet() const; - OStream getOutputStream(); - OStream getErrorStream(); + Method method() const; + State currentState() const; + + OStream getOutputStream(const Response* response); + OStream getErrorStream(const Response* response); + void responseIsComplete(const Response* response); std::string getPath(const std::string& serverName) const; std::string getServerName() const; void printEnvironment(std::ostream& out); void printEnvironment(nlohmann::json& out); +private: + void validateResponse(const Response* response); + private: State state; FCGX_Request raw; + const Response* response; }; diff --git a/response/response.cpp b/response/response.cpp index 17be265..7d3d394 100644 --- a/response/response.cpp +++ b/response/response.cpp @@ -15,23 +15,25 @@ constexpr std::array(Response::ContentTyp "Content-type: application/json" }; -Response::Response(): +Response::Response(Request& request): + request(request), status(Status::ok), type(ContentType::text), body() {} -Response::Response(Status status): +Response::Response(Request& request, Status status): + request(request), status(status), type(ContentType::text), body() {} -void Response::replyTo(Request& request) const { +void Response::send() const { // OStream out = status == Status::ok ? // request.getOutputStream() : // request.getErrorStream(); - OStream out = request.getOutputStream(); + OStream out = request.getOutputStream(this); out << statusCodes[static_cast(status)]; if (!body.empty()) @@ -40,6 +42,8 @@ void Response::replyTo(Request& request) const { << '\n' << '\n' << body; + + request.responseIsComplete(this); } void Response::setBody(const std::string& body) { diff --git a/response/response.h b/response/response.h index 7cf0d59..946ecbb 100644 --- a/response/response.h +++ b/response/response.h @@ -27,14 +27,15 @@ public: json, __size }; - Response(); - Response(Status status); + Response(Request& request); + Response(Request& request, Status status); - void replyTo(Request& request) const; + void send() const; void setBody(const std::string& body); void setBody(const nlohmann::json& body); private: + Request& request; Status status; ContentType type; std::string body; diff --git a/server/router.cpp b/server/router.cpp index 24bb402..75fe696 100644 --- a/server/router.cpp +++ b/server/router.cpp @@ -4,41 +4,74 @@ #include "router.h" Router::Router(): - table() -{ + get(), + post() +{} -} +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)); + } -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"; + throw std::runtime_error("could'not add route " + handler->path + " to the routing table"); } -void Router::route(const std::string& path, std::unique_ptr request, Server* server) { - auto itr = table.find(path); - if (itr == table.end()) +void Router::route(const std::string& path, std::unique_ptr request) { + std::map::const_iterator itr, end; + switch (request->method()) { + case Request::Method::get: + itr = get.find(path); + end = get.end(); + break; + case Request::Method::post: + itr = post.find(path); + end = post.end(); + break; + default: + return handleMethodNotAllowed(path, std::move(request)); + } + + if (itr == end) return handleNotFound(path, std::move(request)); try { - bool result = itr->second(request.get(), server); - if (!result) - handleInternalError(std::runtime_error("handler failed to handle the request"), std::move(request)); + itr->second->handle(*request.get()); + + if (request->currentState() != Request::State::responded) + handleInternalError(path, std::runtime_error("handler failed to handle the request"), std::move(request)); + else + std::cout << "Success:\t" << path << std::endl; } catch (const std::exception& e) { - handleInternalError(e, std::move(request)); + handleInternalError(path, e, std::move(request)); } } void Router::handleNotFound(const std::string& path, std::unique_ptr request) { - Response notFound(Response::Status::notFound); + Response notFound(*request.get(), Response::Status::notFound); notFound.setBody(std::string("Path \"") + path + "\" was not found"); - notFound.replyTo(*request.get()); - std::cerr << "Not found: " << path << std::endl; + notFound.send(); + std::cerr << "Not found:\t" << path << std::endl; } -void Router::handleInternalError(const std::exception& exception, std::unique_ptr request) { - Response error(Response::Status::internalError); +void Router::handleInternalError(const std::string& path, const std::exception& exception, std::unique_ptr request) { + Response error(*request.get(), Response::Status::internalError); error.setBody(std::string(exception.what())); - error.replyTo(*request.get()); - std::cerr << "Internal error: " << exception.what() << std::endl; + error.send(); + std::cerr << "Internal error:\t" << path << "\n\t" << exception.what() << std::endl; +} + +void Router::handleMethodNotAllowed(const std::string& path, std::unique_ptr request) { + Response error(*request.get(), Response::Status::methodNotAllowed); + error.setBody(std::string("Method not allowed")); + error.send(); + std::cerr << "Method not allowed:\t" << path << std::endl; } diff --git a/server/router.h b/server/router.h index c423282..95fa8db 100644 --- a/server/router.h +++ b/server/router.h @@ -10,23 +10,25 @@ #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(const std::string& path, const Handler& handler); - void route(const std::string& path, std::unique_ptr request, Server* server); + void addRoute(Handler handler); + void route(const std::string& path, std::unique_ptr request); private: void handleNotFound(const std::string& path, std::unique_ptr request); - void handleInternalError(const std::exception& exception, std::unique_ptr request); + void handleInternalError(const std::string& path, const std::exception& exception, std::unique_ptr request); + void handleMethodNotAllowed(const std::string& path, std::unique_ptr request); private: - std::map table; + std::map get; + std::map post; }; diff --git a/server/server.cpp b/server/server.cpp index f08b408..744c27e 100644 --- a/server/server.cpp +++ b/server/server.cpp @@ -3,6 +3,9 @@ #include "server.h" +#include "handler/info.h" +#include "handler/env.h" + constexpr uint8_t currentDbVesion = 1; Server::Server(): @@ -23,8 +26,8 @@ Server::Server(): db->connect("/run/mysqld/mysqld.sock"); db->migrate(currentDbVesion); - router.addRoute("info", Server::info); - router.addRoute("env", Server::printEnvironment); + router.addRoute(std::make_unique()); + router.addRoute(std::make_unique()); } Server::~Server() {} @@ -49,49 +52,12 @@ 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(Response::Status::internalError); - error.replyTo(*request.get()); + Response error(*request.get(), Response::Status::internalError); + error.send(); return; } } - if (!request->isGet()) { - static const Response methodNotAllowed(Response::Status::methodNotAllowed); - methodNotAllowed.replyTo(*request.get()); - return; - } - - 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()); - } -} - -bool Server::printEnvironment(Request* request, Server* server) { - UNUSED(server); - nlohmann::json body = nlohmann::json::object(); - request->printEnvironment(body); - - Response res; - res.setBody(body); - res.replyTo(*request); - - return true; -} - -bool Server::info(Request* request, Server* server) { - UNUSED(server); - Response res; - nlohmann::json body = nlohmann::json::object(); - body["type"] = PROJECT_NAME; - body["version"] = PROJECT_VERSION; - - res.setBody(body); - res.replyTo(*request); - - return true; + std::string path = request->getPath(serverName.value()); + router.route(path.data(), std::move(request)); } diff --git a/server/server.h b/server/server.h index f13df21..13d17d0 100644 --- a/server/server.h +++ b/server/server.h @@ -34,9 +34,6 @@ public: private: void handleRequest(std::unique_ptr request); - static bool info(Request* request, Server* server); - static bool printEnvironment(Request* request, Server* server); - private: bool terminating; uint64_t requestCount; From 0c50cfa639a1c17046fbcd804f854904092dbaec Mon Sep 17 00:00:00 2001 From: blue Date: Thu, 14 Dec 2023 19:17:28 -0300 Subject: [PATCH 03/27] some thinking around passing the form --- handler/CMakeLists.txt | 2 ++ handler/register.cpp | 23 +++++++++++++++++++++ handler/register.h | 16 +++++++++++++++ request/request.cpp | 42 ++++++++++++++++++++++++++++++++++++++ request/request.h | 5 +++++ server/server.cpp | 2 ++ utils/CMakeLists.txt | 2 ++ utils/formdecode.cpp | 46 ++++++++++++++++++++++++++++++++++++++++++ utils/formdecode.h | 11 ++++++++++ 9 files changed, 149 insertions(+) create mode 100644 handler/register.cpp create mode 100644 handler/register.h create mode 100644 utils/formdecode.cpp create mode 100644 utils/formdecode.h diff --git a/handler/CMakeLists.txt b/handler/CMakeLists.txt index a3dd084..1f58f4f 100644 --- a/handler/CMakeLists.txt +++ b/handler/CMakeLists.txt @@ -2,12 +2,14 @@ set(HEADERS handler.h info.h env.h + register.h ) set(SOURCES handler.cpp info.cpp env.cpp + register.cpp ) target_sources(${PROJECT_NAME} PRIVATE ${SOURCES}) diff --git a/handler/register.cpp b/handler/register.cpp new file mode 100644 index 0000000..4928627 --- /dev/null +++ b/handler/register.cpp @@ -0,0 +1,23 @@ +// SPDX-FileCopyrightText: 2023 Yury Gubich +// SPDX-License-Identifier: GPL-3.0-or-later + +#include "register.h" + +Handler::Register::Register(): + Handler("register", Request::Method::post) +{} + +void Handler::Register::handle(Request& request) { + std::map form = request.getForm(); + + std::cout << "Received form:" << std::endl; + for (const auto& pair : form) + std::cout << '\t' << pair.first << ": " << pair.second << std::endl; + + Response res(request); + nlohmann::json body = nlohmann::json::object(); + body["result"] = "ok"; + + res.setBody(body); + res.send(); +} diff --git a/handler/register.h b/handler/register.h new file mode 100644 index 0000000..047dde3 --- /dev/null +++ b/handler/register.h @@ -0,0 +1,16 @@ +// SPDX-FileCopyrightText: 2023 Yury Gubich +// SPDX-License-Identifier: GPL-3.0-or-later + +#pragma once + +#include "handler.h" + +namespace Handler { + +class Register : public Handler::Handler { +public: + Register(); + virtual void handle(Request& request); + +}; +} diff --git a/request/request.cpp b/request/request.cpp index 48e084b..2859e00 100644 --- a/request/request.cpp +++ b/request/request.cpp @@ -10,6 +10,10 @@ 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* urlEncoded("application/x-www-form-urlencoded"); // constexpr static const char* REQUEST_URI("REQUEST_URI"); // @@ -178,3 +182,41 @@ void Request::printEnvironment(nlohmann::json& out) { out[std::string(value.substr(0, pos))] = std::string(value.substr(pos + 1, value.size())); } } + +bool Request::isFormUrlEncoded() const { + if (state == State::initial) + throw std::runtime_error("An attempt to read request content type on not accepted request"); + + 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 (state == State::initial) + throw std::runtime_error("An attempt to read request content length on not accepted request"); + + return atoi(FCGX_GetParam(CONTENT_LENGTH, raw.envp)); +} + +std::map Request::getForm() const { + if (state == State::initial) + throw std::runtime_error("An attempt to read form on not accepted request"); + + std::map result; + std::string_view contentType(FCGX_GetParam(CONTENT_TYPE, raw.envp)); + if (contentType.empty()) + return result; + + unsigned int length = contentLength(); + if (contentType.find(urlEncoded) != std::string_view::npos) { + std::string postData(length, '\0'); + FCGX_GetStr(&postData[0], length, raw.in); + result = urlDecodeAndParse(postData); + } + + return result; +} diff --git a/request/request.h b/request/request.h index 9871106..e28e283 100644 --- a/request/request.h +++ b/request/request.h @@ -6,6 +6,7 @@ #include #include #include +#include #include #include @@ -14,6 +15,7 @@ #include #include "stream/ostream.h" +#include "utils/formdecode.h" class Response; class Request { @@ -43,6 +45,9 @@ public: Method method() const; State currentState() const; + bool isFormUrlEncoded() const; + unsigned int contentLength() const; + std::map getForm() const; OStream getOutputStream(const Response* response); OStream getErrorStream(const Response* response); diff --git a/server/server.cpp b/server/server.cpp index 744c27e..dad6a3a 100644 --- a/server/server.cpp +++ b/server/server.cpp @@ -5,6 +5,7 @@ #include "handler/info.h" #include "handler/env.h" +#include "handler/register.h" constexpr uint8_t currentDbVesion = 1; @@ -28,6 +29,7 @@ Server::Server(): router.addRoute(std::make_unique()); router.addRoute(std::make_unique()); + router.addRoute(std::make_unique()); } Server::~Server() {} diff --git a/utils/CMakeLists.txt b/utils/CMakeLists.txt index 93d7c25..4834150 100644 --- a/utils/CMakeLists.txt +++ b/utils/CMakeLists.txt @@ -1,9 +1,11 @@ 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 new file mode 100644 index 0000000..8dbd14d --- /dev/null +++ b/utils/formdecode.cpp @@ -0,0 +1,46 @@ +// 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 new file mode 100644 index 0000000..5f4ec61 --- /dev/null +++ b/utils/formdecode.h @@ -0,0 +1,11 @@ +// 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); From 99a9fd507ea57b303f6e02f2965ca7c623807665 Mon Sep 17 00:00:00 2001 From: blue Date: Wed, 20 Dec 2023 19:42:13 -0300 Subject: [PATCH 04/27] first what so ever registration --- CMakeLists.txt | 2 ++ README.md | 1 + cmake/FindArgon2.cmake | 26 +++++++++++++++++ database/dbinterface.h | 2 ++ database/migrations/m0.sql | 29 +++++++++++++++---- database/mysql/CMakeLists.txt | 2 ++ database/mysql/mysql.cpp | 53 ++++++++++++++++++++++++++++++++-- database/mysql/mysql.h | 4 +++ database/mysql/statement.cpp | 16 ++++++---- database/mysql/statement.h | 4 +-- database/mysql/transaction.cpp | 34 ++++++++++++++++++++++ database/mysql/transaction.h | 16 ++++++++++ handler/register.cpp | 49 +++++++++++++++++++++++++++---- handler/register.h | 20 ++++++++++++- server/router.cpp | 1 + server/server.cpp | 46 ++++++++++++++++++++++++++++- server/server.h | 5 +++- 17 files changed, 285 insertions(+), 25 deletions(-) create mode 100644 cmake/FindArgon2.cmake create mode 100644 database/mysql/transaction.cpp create mode 100644 database/mysql/transaction.h diff --git a/CMakeLists.txt b/CMakeLists.txt index bca2822..183ff6d 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -39,6 +39,7 @@ message("Compile options: " ${COMPILE_OPTIONS_STRING}) find_package(nlohmann_json REQUIRED) find_package(FCGI REQUIRED) +find_package(Argon2 REQUIRED) add_executable(${PROJECT_NAME} main.cpp) target_include_directories(${PROJECT_NAME} PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}) @@ -59,6 +60,7 @@ target_link_libraries(${PROJECT_NAME} PRIVATE FCGI::FCGI FCGI::FCGI++ nlohmann_json::nlohmann_json + Argon2::Argon2 ) install(TARGETS ${PROJECT_NAME} RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR}) diff --git a/README.md b/README.md index e65dfcc..4c48ad8 100644 --- a/README.md +++ b/README.md @@ -7,6 +7,7 @@ - fcgi - nlohmann_json - mariadb-client +- argon2 ### Building diff --git a/cmake/FindArgon2.cmake b/cmake/FindArgon2.cmake new file mode 100644 index 0000000..75d70a6 --- /dev/null +++ b/cmake/FindArgon2.cmake @@ -0,0 +1,26 @@ +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/database/dbinterface.h b/database/dbinterface.h index 839672d..ea03191 100644 --- a/database/dbinterface.h +++ b/database/dbinterface.h @@ -36,6 +36,8 @@ public: virtual uint8_t getVersion() = 0; virtual void setVersion(uint8_t version) = 0; + virtual unsigned int registerAccount(const std::string& login, const std::string& hash) = 0; + protected: DBInterface(Type type); diff --git a/database/migrations/m0.sql b/database/migrations/m0.sql index 53b7028..e52e9ef 100644 --- a/database/migrations/m0.sql +++ b/database/migrations/m0.sql @@ -17,17 +17,36 @@ CREATE TABLE IF NOT EXISTS accounts ( `login` VARCHAR(256) UNIQUE NOT NULL, `nick` VARCHAR(256), `type` INTEGER UNSIGNED NOT NULL, - `password` VARCHAR(64), - `salt` VARCHAR(32), - `role` INTEGER UNSIGNED NOT NULL, - `created` TIMESTAMP DEFAULT UTC_TIMESTAMP(), + `password` VARCHAR(128), + `created` TIMESTAMP DEFAULT UTC_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 sessings table +CREATE TABLE IF NOT EXISTS sessions ( + `id` INTEGER AUTO_INCREMENT PRIMARY KEY, + `owner` INTEGER UNSIGNED NOT NULL, + `started` TIMESTAMP DEFAULT UTC_TIMESTAMP(), + `latest` TIMESTAMP DEFAULT UTC_TIMESTAMP(), + `salt` CHAR(16), + `persist` BOOLEAN NOT NULL, + + FOREIGN KEY (owner) REFERENCES accounts(id) +); + --creating defailt roles INSERT IGNORE INTO roles (`name`) -VALUES ('root'); +VALUES ('root'), + ('default'); --inserting initial version INSERT INTO system (`key`, `value`) VALUES ('version', '0'); diff --git a/database/mysql/CMakeLists.txt b/database/mysql/CMakeLists.txt index 64d1920..b1cec73 100644 --- a/database/mysql/CMakeLists.txt +++ b/database/mysql/CMakeLists.txt @@ -1,11 +1,13 @@ set(HEADERS mysql.h statement.h + transaction.h ) set(SOURCES mysql.cpp statement.cpp + transaction.cpp ) find_package(MariaDB REQUIRED) diff --git a/database/mysql/mysql.cpp b/database/mysql/mysql.cpp index 0532f7c..5d781f7 100644 --- a/database/mysql/mysql.cpp +++ b/database/mysql/mysql.cpp @@ -9,8 +9,13 @@ #include "mysqld_error.h" #include "statement.h" +#include "transaction.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 = ?"; static const std::filesystem::path buildSQLPath = "database"; @@ -34,7 +39,6 @@ MySQL::~MySQL() { mysql_close(&connection); } - void MySQL::connect(const std::string& path) { if (state != State::disconnected) return; @@ -138,7 +142,7 @@ void MySQL::executeFile(const std::filesystem::path& relativePath) { uint8_t MySQL::getVersion() { MYSQL* con = &connection; - int result = mysql_query(con, "SELECT value FROM system WHERE `key` = 'version'"); + int result = mysql_query(con, versionQuery); if (result != 0) { unsigned int errcode = mysql_errno(con); @@ -216,3 +220,48 @@ std::optional MySQL::getComment(std::string& string) { return std::nullopt; } +unsigned int 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); + addAcc.execute(); + + unsigned int 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; +} + +unsigned int 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")); +} + + + diff --git a/database/mysql/mysql.h b/database/mysql/mysql.h index 007c985..4246e51 100644 --- a/database/mysql/mysql.h +++ b/database/mysql/mysql.h @@ -14,6 +14,7 @@ class MySQL : public DBInterface { class Statement; + class Transaction; public: MySQL(); ~MySQL() override; @@ -27,9 +28,12 @@ public: uint8_t getVersion() override; void setVersion(uint8_t version) override; + unsigned int registerAccount(const std::string& login, const std::string& hash) override; + private: void executeFile(const std::filesystem::path& relativePath); static std::optional getComment(std::string& string); + unsigned int lastInsertedId(); protected: MYSQL connection; diff --git a/database/mysql/statement.cpp b/database/mysql/statement.cpp index 44c7592..5cbf482 100644 --- a/database/mysql/statement.cpp +++ b/database/mysql/statement.cpp @@ -9,15 +9,14 @@ static uint64_t TIME_LENGTH = sizeof(MYSQL_TIME); MySQL::Statement::Statement(MYSQL* connection, const char* statement): stmt(mysql_stmt_init(connection)), - param(), - lengths() + param() { 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 MySQL::Statement::bind(void* value, enum_field_types type) { +void MySQL::Statement::bind(void* value, enum_field_types type, bool usigned) { MYSQL_BIND& result = param.emplace_back(); std::memset(&result, 0, sizeof(result)); @@ -27,13 +26,18 @@ void MySQL::Statement::bind(void* value, enum_field_types type) { switch (type) { case MYSQL_TYPE_STRING: case MYSQL_TYPE_VAR_STRING: - result.length = &lengths.emplace_back(strlen(static_cast(value))); + result.buffer_length = strlen(static_cast(value)); break; case MYSQL_TYPE_DATE: - result.length = &TIME_LENGTH; + 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; break; default: - lengths.pop_back(); throw std::runtime_error("Type: " + std::to_string(type) + " is not yet supported in bind"); break; } diff --git a/database/mysql/statement.h b/database/mysql/statement.h index 9cfc129..3dca291 100644 --- a/database/mysql/statement.h +++ b/database/mysql/statement.h @@ -7,7 +7,6 @@ #include "mysql.h" - class MySQL::Statement { struct STMTDeleter { void operator () (MYSQL_STMT* stmt) { @@ -17,11 +16,10 @@ class MySQL::Statement { public: Statement(MYSQL* connection, const char* statement); - void bind(void* value, enum_field_types type); + void bind(void* value, enum_field_types type, bool usigned = false); void execute(); private: std::unique_ptr stmt; std::vector param; - std::vector lengths; }; diff --git a/database/mysql/transaction.cpp b/database/mysql/transaction.cpp new file mode 100644 index 0000000..ff921cc --- /dev/null +++ b/database/mysql/transaction.cpp @@ -0,0 +1,34 @@ +#include "transaction.h" + +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; +} + +MySQL::Transaction::~Transaction() { + if (opened) + abort(); +} + +void 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 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 new file mode 100644 index 0000000..87e6713 --- /dev/null +++ b/database/mysql/transaction.h @@ -0,0 +1,16 @@ +#pragma once + +#include "mysql.h" + +class MySQL::Transaction { +public: + Transaction(MYSQL* connection); + ~Transaction(); + + void commit(); + void abort(); + +private: + MYSQL* con; + bool opened; +}; diff --git a/handler/register.cpp b/handler/register.cpp index 4928627..c36e8d4 100644 --- a/handler/register.cpp +++ b/handler/register.cpp @@ -3,20 +3,57 @@ #include "register.h" -Handler::Register::Register(): - Handler("register", Request::Method::post) +#include "server/server.h" + +Handler::Register::Register(Server* 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); - std::cout << "Received form:" << std::endl; - for (const auto& pair : form) - std::cout << '\t' << pair.first << ": " << pair.second << std::endl; + const std::string& login = itr->second; + if (login.empty()) + return error(request, Result::emptyLogin); + + //TODO login policies checkup + + itr = form.find("password"); + if (itr == form.end()) + return error(request, Result::noPassword); + + const std::string& password = itr->second; + if (password.empty()) + return error(request, Result::emptyPassword); + + //TODO password policies checkup + + try { + server->registerAccount(login, password); + } catch (const std::exception& e) { + std::cerr << "Exception on registration:\n\t" << e.what() << std::endl; + return error(request, Result::unknownError); + } catch (...) { + std::cerr << "Unknown exception on registration" << std::endl; + return error(request, Result::unknownError); + } Response res(request); nlohmann::json body = nlohmann::json::object(); - body["result"] = "ok"; + body["result"] = Result::success; + + res.setBody(body); + res.send(); +} + +void Handler::Register::error(Request& request, Result result) { + Response res(request); + nlohmann::json body = nlohmann::json::object(); + body["result"] = result; res.setBody(body); res.send(); diff --git a/handler/register.h b/handler/register.h index 047dde3..6aedb2c 100644 --- a/handler/register.h +++ b/handler/register.h @@ -5,12 +5,30 @@ #include "handler.h" +class Server; namespace Handler { class Register : public Handler::Handler { public: - Register(); + Register(Server* server); virtual void handle(Request& request); + enum class Result { + success, + noLogin, + emptyLogin, + loginExists, + loginPolicyViolation, + noPassword, + emptyPassword, + passwordPolicyViolation, + unknownError + }; + +private: + void error(Request& request, Result result); + +private: + Server* server; }; } diff --git a/server/router.cpp b/server/router.cpp index 75fe696..1c7b10d 100644 --- a/server/router.cpp +++ b/server/router.cpp @@ -44,6 +44,7 @@ void Router::route(const std::string& path, std::unique_ptr request) { return handleNotFound(path, std::move(request)); try { + std::cout << "Handling " << path << "..." << std::endl; itr->second->handle(*request.get()); if (request->currentState() != Request::State::responded) diff --git a/server/server.cpp b/server/server.cpp index dad6a3a..6f02de1 100644 --- a/server/server.cpp +++ b/server/server.cpp @@ -3,11 +3,20 @@ #include "server.h" +#include + #include "handler/info.h" #include "handler/env.h" #include "handler/register.h" +constexpr const char* pepper = "well, not much of a secret, huh?"; constexpr uint8_t currentDbVesion = 1; +constexpr const char* randomChars = "0123456789abcdef"; +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; Server::Server(): terminating(false), @@ -29,7 +38,7 @@ Server::Server(): router.addRoute(std::make_unique()); router.addRoute(std::make_unique()); - router.addRoute(std::make_unique()); + router.addRoute(std::make_unique(this)); } Server::~Server() {} @@ -63,3 +72,38 @@ void Server::handleRequest(std::unique_ptr request) { std::string path = request->getPath(serverName.value()); router.route(path.data(), std::move(request)); } + +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)); + + 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)); + + return db->registerAccount(login, hash); +} diff --git a/server/server.h b/server/server.h index 13d17d0..5a5fb1b 100644 --- a/server/server.h +++ b/server/server.h @@ -15,7 +15,7 @@ #include #include -#include +#include #include "request/request.h" #include "response/response.h" @@ -31,8 +31,11 @@ public: void run(int socketDescriptor); + unsigned int registerAccount(const std::string& login, const std::string& password); + private: void handleRequest(std::unique_ptr request); + static std::string generateRandomString(std::size_t length); private: bool terminating; From 534c28222655ac653796bbc3253b2b3f98cacea9 Mon Sep 17 00:00:00 2001 From: blue Date: Fri, 22 Dec 2023 20:25:20 -0300 Subject: [PATCH 05/27] password hash cheching --- database/CMakeLists.txt | 2 + database/dbinterface.h | 6 +++ database/exceptions.cpp | 20 ++++++++++ database/exceptions.h | 26 +++++++++++++ database/mysql/mysql.cpp | 32 +++++++++++---- database/mysql/mysql.h | 9 +++++ database/mysql/statement.cpp | 71 +++++++++++++++++++++++++++++++--- database/mysql/statement.h | 1 + database/mysql/transaction.cpp | 3 ++ database/mysql/transaction.h | 3 ++ handler/CMakeLists.txt | 2 + handler/env.cpp | 2 +- handler/env.h | 4 +- handler/info.cpp | 2 +- handler/login.cpp | 65 +++++++++++++++++++++++++++++++ handler/login.h | 32 +++++++++++++++ handler/register.cpp | 24 +++++++----- handler/register.h | 6 +-- request/request.cpp | 69 +++++++++++++++++---------------- request/request.h | 18 +++++---- response/response.cpp | 27 +++++++++++-- response/response.h | 15 +++++-- server/router.cpp | 14 +++---- server/server.cpp | 20 +++++++++- server/server.h | 1 + 25 files changed, 390 insertions(+), 84 deletions(-) create mode 100644 database/exceptions.cpp create mode 100644 database/exceptions.h create mode 100644 handler/login.cpp create mode 100644 handler/login.h diff --git a/database/CMakeLists.txt b/database/CMakeLists.txt index 42941d6..a0adbc9 100644 --- a/database/CMakeLists.txt +++ b/database/CMakeLists.txt @@ -1,9 +1,11 @@ set(HEADERS dbinterface.h + exceptions.h ) set(SOURCES dbinterface.cpp + exceptions.cpp ) target_sources(${PROJECT_NAME} PRIVATE ${SOURCES}) diff --git a/database/dbinterface.h b/database/dbinterface.h index ea03191..5f018e8 100644 --- a/database/dbinterface.h +++ b/database/dbinterface.h @@ -26,6 +26,11 @@ public: const Type type; + class Duplicate; + class DuplicateLogin; + class EmptyResult; + class NoLogin; + public: virtual void connect(const std::string& path) = 0; virtual void disconnect() = 0; @@ -37,6 +42,7 @@ public: virtual void setVersion(uint8_t version) = 0; virtual unsigned int registerAccount(const std::string& login, const std::string& hash) = 0; + virtual std::string getAccountHash(const std::string& login) = 0; protected: DBInterface(Type type); diff --git a/database/exceptions.cpp b/database/exceptions.cpp new file mode 100644 index 0000000..5d555e2 --- /dev/null +++ b/database/exceptions.cpp @@ -0,0 +1,20 @@ +// SPDX-FileCopyrightText: 2023 Yury Gubich +// SPDX-License-Identifier: GPL-3.0-or-later + +#include "exceptions.h" + +DBInterface::Duplicate::Duplicate(const std::string& text): + std::runtime_error(text) +{} + +DBInterface::DuplicateLogin::DuplicateLogin(const std::string& text): + Duplicate(text) +{} + +DBInterface::EmptyResult::EmptyResult(const std::string& text): + std::runtime_error(text) +{} + +DBInterface::NoLogin::NoLogin(const std::string& text): + EmptyResult(text) +{} diff --git a/database/exceptions.h b/database/exceptions.h new file mode 100644 index 0000000..8d447e2 --- /dev/null +++ b/database/exceptions.h @@ -0,0 +1,26 @@ +// SPDX-FileCopyrightText: 2023 Yury Gubich +// SPDX-License-Identifier: GPL-3.0-or-later + +#pragma once + +#include "dbinterface.h" + +class DBInterface::Duplicate : public std::runtime_error { +public: + explicit Duplicate(const std::string& text); +}; + +class DBInterface::DuplicateLogin : public DBInterface::Duplicate { +public: + explicit DuplicateLogin(const std::string& text); +}; + +class DBInterface::EmptyResult : public std::runtime_error { +public: + explicit EmptyResult(const std::string& text); +}; + +class DBInterface::NoLogin : public DBInterface::EmptyResult { +public: + explicit NoLogin(const std::string& text); +}; diff --git a/database/mysql/mysql.cpp b/database/mysql/mysql.cpp index 5d781f7..1e40cf4 100644 --- a/database/mysql/mysql.cpp +++ b/database/mysql/mysql.cpp @@ -10,21 +10,17 @@ #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 = ?"; static const std::filesystem::path buildSQLPath = "database"; -struct ResDeleter { - void operator () (MYSQL_RES* res) { - mysql_free_result(res); - } -}; - MySQL::MySQL(): DBInterface(Type::mysql), connection(), @@ -231,7 +227,11 @@ unsigned int MySQL::registerAccount(const std::string& login, const std::string& std::string h = hash; addAcc.bind(l.data(), MYSQL_TYPE_STRING); addAcc.bind(h.data(), MYSQL_TYPE_STRING); - addAcc.execute(); + try { + addAcc.execute(); + } catch (const Duplicate& dup) { + throw DuplicateLogin(dup.what()); + } unsigned int id = lastInsertedId(); static std::string defaultRole("default"); @@ -245,6 +245,24 @@ unsigned int MySQL::registerAccount(const std::string& login, const std::string& return id; } +std::string 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 result[0][0]; +} + unsigned int MySQL::lastInsertedId() { MYSQL* con = &connection; int result = mysql_query(con, lastIdQuery); diff --git a/database/mysql/mysql.h b/database/mysql/mysql.h index 4246e51..75e865d 100644 --- a/database/mysql/mysql.h +++ b/database/mysql/mysql.h @@ -15,6 +15,8 @@ class MySQL : public DBInterface { class Statement; class Transaction; + + public: MySQL(); ~MySQL() override; @@ -29,6 +31,7 @@ public: void setVersion(uint8_t version) override; unsigned int registerAccount(const std::string& login, const std::string& hash) override; + std::string getAccountHash(const std::string& login) override; private: void executeFile(const std::filesystem::path& relativePath); @@ -40,4 +43,10 @@ protected: 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 5cbf482..acfb0e6 100644 --- a/database/mysql/statement.cpp +++ b/database/mysql/statement.cpp @@ -5,6 +5,10 @@ #include +#include "mysqld_error.h" + +#include "database/exceptions.h" + static uint64_t TIME_LENGTH = sizeof(MYSQL_TIME); MySQL::Statement::Statement(MYSQL* connection, const char* statement): @@ -44,11 +48,68 @@ void MySQL::Statement::bind(void* value, enum_field_types type, bool usigned) { } void MySQL::Statement::execute() { - int result = mysql_stmt_bind_param(stmt.get(), param.data()); + MYSQL_STMT* raw = stmt.get(); + int result = mysql_stmt_bind_param(raw, param.data()); if (result != 0) - throw std::runtime_error(std::string("Error binding statement: ") + mysql_stmt_error(stmt.get())); + throw std::runtime_error(std::string("Error binding statement: ") + mysql_stmt_error(raw)); - result = mysql_stmt_execute(stmt.get()); - if (result != 0) - throw std::runtime_error(std::string("Error executing 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> 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]; + 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: + break; + default: + throw std::runtime_error("Unsupported data fetching statement result " + std::to_string(field->type)); + } + line[i].resize(field->length); + bind[i].buffer_type = field->type; + bind[i].buffer = line[i].data(); + bind[i].buffer_length = field->length; + bind[i].length = &lengths[i]; + } + + 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; + while (mysql_stmt_fetch(raw) == 0) { + std::vector& row = result.emplace_back(numColumns); + for (unsigned int i = 0; i < numColumns; ++i) + row[i] = std::string(line[i].data(), lengths[i]); + } + + return result; +} + diff --git a/database/mysql/statement.h b/database/mysql/statement.h index 3dca291..3209155 100644 --- a/database/mysql/statement.h +++ b/database/mysql/statement.h @@ -18,6 +18,7 @@ public: void bind(void* value, enum_field_types type, bool usigned = false); void execute(); + std::vector> fetchResult(); private: std::unique_ptr stmt; diff --git a/database/mysql/transaction.cpp b/database/mysql/transaction.cpp index ff921cc..1a9b92c 100644 --- a/database/mysql/transaction.cpp +++ b/database/mysql/transaction.cpp @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: 2023 Yury Gubich +// SPDX-License-Identifier: GPL-3.0-or-later + #include "transaction.h" MySQL::Transaction::Transaction(MYSQL* connection): diff --git a/database/mysql/transaction.h b/database/mysql/transaction.h index 87e6713..5a6a15c 100644 --- a/database/mysql/transaction.h +++ b/database/mysql/transaction.h @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: 2023 Yury Gubich +// SPDX-License-Identifier: GPL-3.0-or-later + #pragma once #include "mysql.h" diff --git a/handler/CMakeLists.txt b/handler/CMakeLists.txt index 1f58f4f..07b134a 100644 --- a/handler/CMakeLists.txt +++ b/handler/CMakeLists.txt @@ -3,6 +3,7 @@ set(HEADERS info.h env.h register.h + login.h ) set(SOURCES @@ -10,6 +11,7 @@ set(SOURCES info.cpp env.cpp register.cpp + login.cpp ) target_sources(${PROJECT_NAME} PRIVATE ${SOURCES}) diff --git a/handler/env.cpp b/handler/env.cpp index a615e17..08910c2 100644 --- a/handler/env.cpp +++ b/handler/env.cpp @@ -11,7 +11,7 @@ void Handler::Env::handle(Request& request) { nlohmann::json body = nlohmann::json::object(); request.printEnvironment(body); - Response res(request); + Response& res = request.createResponse(); res.setBody(body); res.send(); } diff --git a/handler/env.h b/handler/env.h index b4a5ea4..cb361f0 100644 --- a/handler/env.h +++ b/handler/env.h @@ -7,10 +7,10 @@ namespace Handler { -class Env : public Handler::Handler { +class Env : public Handler { public: Env(); - virtual void handle(Request& request); + void handle(Request& request) override; }; } diff --git a/handler/info.cpp b/handler/info.cpp index ba2dfec..3a65cd7 100644 --- a/handler/info.cpp +++ b/handler/info.cpp @@ -8,7 +8,7 @@ Handler::Info::Info(): {} void Handler::Info::handle(Request& request) { - Response res(request); + Response& res = request.createResponse(); nlohmann::json body = nlohmann::json::object(); body["type"] = PROJECT_NAME; body["version"] = PROJECT_VERSION; diff --git a/handler/login.cpp b/handler/login.cpp new file mode 100644 index 0000000..f09ecec --- /dev/null +++ b/handler/login.cpp @@ -0,0 +1,65 @@ +// 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(Server* 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); + + bool success = false; + try { + success = server->validatePassword(login, password); + } catch (const DBInterface::NoLogin& e) { + std::cerr << "Exception on registration:\n\t" << e.what() << std::endl; + return error(request, Result::noLogin, Response::Status::badRequest); //can send unauthed instead, to exclude login spoofing + } 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); + } + if (!success) + return error(request, Result::noLogin, Response::Status::badRequest); + + //TODO opening the session + + Response& res = request.createResponse(); + nlohmann::json body = nlohmann::json::object(); + body["result"] = Result::success; + + 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 new file mode 100644 index 0000000..ef0986e --- /dev/null +++ b/handler/login.h @@ -0,0 +1,32 @@ +// SPDX-FileCopyrightText: 2023 Yury Gubich +// SPDX-License-Identifier: GPL-3.0-or-later + +#pragma once + +#include "handler.h" + +class Server; +namespace Handler { + +class Login : public Handler { +public: + Login(Server* server); + void handle(Request& request) override; + + enum class Result { + success, + noLogin, + emptyLogin, + noPassword, + emptyPassword, + unknownError + }; + +private: + void error(Request& request, Result result, Response::Status code); + +private: + Server* server; +}; +} + diff --git a/handler/register.cpp b/handler/register.cpp index c36e8d4..57fefb9 100644 --- a/handler/register.cpp +++ b/handler/register.cpp @@ -4,6 +4,7 @@ #include "register.h" #include "server/server.h" +#include "database/exceptions.h" Handler::Register::Register(Server* server): Handler("register", Request::Method::post), @@ -14,35 +15,38 @@ 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); + return error(request, Result::noLogin, Response::Status::badRequest); const std::string& login = itr->second; if (login.empty()) - return error(request, Result::emptyLogin); + 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); + return error(request, Result::noPassword, Response::Status::badRequest); const std::string& password = itr->second; if (password.empty()) - return error(request, Result::emptyPassword); + return error(request, Result::emptyPassword, Response::Status::badRequest); //TODO password policies checkup try { server->registerAccount(login, password); + } catch (const DBInterface::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); - } catch (...) { + return error(request, Result::unknownError, Response::Status::internalError); + } catch (...) { std::cerr << "Unknown exception on registration" << std::endl; - return error(request, Result::unknownError); + return error(request, Result::unknownError, Response::Status::internalError); } - Response res(request); + Response& res = request.createResponse(); nlohmann::json body = nlohmann::json::object(); body["result"] = Result::success; @@ -50,8 +54,8 @@ void Handler::Register::handle(Request& request) { res.send(); } -void Handler::Register::error(Request& request, Result result) { - Response res(request); +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; diff --git a/handler/register.h b/handler/register.h index 6aedb2c..6e36655 100644 --- a/handler/register.h +++ b/handler/register.h @@ -8,10 +8,10 @@ class Server; namespace Handler { -class Register : public Handler::Handler { +class Register : public Handler { public: Register(Server* server); - virtual void handle(Request& request); + void handle(Request& request) override; enum class Result { success, @@ -26,7 +26,7 @@ public: }; private: - void error(Request& request, Result result); + void error(Request& request, Result result, Response::Status code); private: Server* server; diff --git a/request/request.cpp b/request/request.cpp index 2859e00..a3e8701 100644 --- a/request/request.cpp +++ b/request/request.cpp @@ -5,8 +5,6 @@ #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"); @@ -51,11 +49,15 @@ void Request::terminate() { } } -Request::Method Request::method() const { +std::string_view Request::methodName() const { if (state == State::initial) throw std::runtime_error("An attempt to read request method on not accepted request"); - std::string_view method(FCGX_GetParam(REQUEST_METHOD, raw.envp)); + return FCGX_GetParam(REQUEST_METHOD, raw.envp); +} + +Request::Method Request::method() const { + std::string_view method = methodName(); for (const auto& pair : methods) { if (pair.first == method) return pair.second; @@ -79,17 +81,42 @@ bool Request::wait(int socketDescriptor) { return result; } -OStream Request::getOutputStream(const Response* response) { - validateResponse(response); +OStream Request::getOutputStream() { return OStream(raw.out); } -OStream Request::getErrorStream(const Response* response) { - validateResponse(response); +OStream Request::getErrorStream() { return OStream(raw.err); } -void Request::responseIsComplete(const Response* response) { +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"); @@ -98,10 +125,6 @@ void Request::responseIsComplete(const Response* response) { throw std::runtime_error("An attempt to mark the request as complete, but it wasn't responded"); break; case State::responding: - if (Request::response != response) - throw std::runtime_error("An attempt to mark the request as complete by the different response who actually started responding"); - - Request::response = nullptr; state = State::responded; break; case State::responded: @@ -110,26 +133,6 @@ void Request::responseIsComplete(const Response* response) { } } -void Request::validateResponse(const Response* response) { - switch (state) { - case State::initial: - throw std::runtime_error("An attempt to request stream while the request wasn't even accepted yet"); - break; - case State::accepted: - Request::response = response; - state = State::responding; - break; - case State::responding: - if (Request::response != response) - throw std::runtime_error("Error handling a request: first time one response started replying, then another continued"); - - break; - case State::responded: - throw std::runtime_error("An attempt to request stream on a request that was already done responding"); - break; - } -} - Request::State Request::currentState() const { return state; } diff --git a/request/request.h b/request/request.h index e28e283..dc2ca3c 100644 --- a/request/request.h +++ b/request/request.h @@ -16,9 +16,10 @@ #include "stream/ostream.h" #include "utils/formdecode.h" +#include "response/response.h" -class Response; class Request { + friend class Response; public: enum class State { initial, @@ -43,26 +44,29 @@ public: bool wait(int socketDescriptor); void terminate(); + 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; - OStream getOutputStream(const Response* response); - OStream getErrorStream(const Response* response); - void responseIsComplete(const Response* response); - std::string getPath(const std::string& serverName) const; std::string getServerName() const; void printEnvironment(std::ostream& out); void printEnvironment(nlohmann::json& out); private: - void validateResponse(const Response* response); + OStream getOutputStream(); + OStream getErrorStream(); + void responseIsComplete(); private: State state; FCGX_Request raw; - const Response* response; + std::unique_ptr response; }; diff --git a/response/response.cpp b/response/response.cpp index 7d3d394..e9a483a 100644 --- a/response/response.cpp +++ b/response/response.cpp @@ -3,10 +3,25 @@ #include "response.h" -constexpr std::array(Response::Status::__size)> statusCodes = { +#include "request/request.h" + +constexpr std::array(Response::Status::__size)> statusCodes = { + 200, + 400, + 401, + 404, + 405, + 409, + 500 +}; + +constexpr std::array(Response::Status::__size)> statuses = { "Status: 200 OK", + "Status: 400 Bad Request", + "Status: 401 Unauthorized", "Status: 404 Not Found", "Status: 405 Method Not Allowed", + "Status: 409 Conflict", "Status: 500 Internal Error" }; @@ -33,9 +48,9 @@ void Response::send() const { // OStream out = status == Status::ok ? // request.getOutputStream() : // request.getErrorStream(); - OStream out = request.getOutputStream(this); + OStream out = request.getOutputStream(); - out << statusCodes[static_cast(status)]; + out << statuses[static_cast(status)]; if (!body.empty()) out << '\n' << contentTypes[static_cast(type)] @@ -43,7 +58,11 @@ void Response::send() const { << '\n' << body; - request.responseIsComplete(this); + 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 946ecbb..bb18c5a 100644 --- a/response/response.h +++ b/response/response.h @@ -9,15 +9,20 @@ #include -#include "request/request.h" #include "stream/ostream.h" +class Request; class Response { + friend class Request; + public: enum class Status { ok, + badRequest, + unauthorized, notFound, methodNotAllowed, + conflict, internalError, __size }; @@ -27,13 +32,17 @@ public: json, __size }; - Response(Request& request); - Response(Request& request, Status status); + + uint16_t statusCode() const; void send() 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; diff --git a/server/router.cpp b/server/router.cpp index 1c7b10d..93c459c 100644 --- a/server/router.cpp +++ b/server/router.cpp @@ -50,29 +50,29 @@ void Router::route(const std::string& path, std::unique_ptr request) { if (request->currentState() != Request::State::responded) handleInternalError(path, std::runtime_error("handler failed to handle the request"), std::move(request)); else - std::cout << "Success:\t" << path << std::endl; + std::cout << request->responseCode() << '\t' << request->methodName() << '\t' << path << std::endl; } catch (const std::exception& e) { handleInternalError(path, e, std::move(request)); } } void Router::handleNotFound(const std::string& path, std::unique_ptr request) { - Response notFound(*request.get(), Response::Status::notFound); + Response& notFound = request->createResponse(Response::Status::notFound); notFound.setBody(std::string("Path \"") + path + "\" was not found"); notFound.send(); - std::cerr << "Not found:\t" << path << std::endl; + std::cerr << notFound.statusCode() << '\t' << request->methodName() << '\t' << path << std::endl; } void Router::handleInternalError(const std::string& path, const std::exception& exception, std::unique_ptr request) { - Response error(*request.get(), Response::Status::internalError); + Response& error = request->createResponse(Response::Status::internalError); error.setBody(std::string(exception.what())); error.send(); - std::cerr << "Internal error:\t" << path << "\n\t" << exception.what() << std::endl; + std::cerr << error.statusCode() << '\t' << request->methodName() << '\t' << path << std::endl; } void Router::handleMethodNotAllowed(const std::string& path, std::unique_ptr request) { - Response error(*request.get(), Response::Status::methodNotAllowed); + Response& error = request->createResponse(Response::Status::methodNotAllowed); error.setBody(std::string("Method not allowed")); error.send(); - std::cerr << "Method not allowed:\t" << path << std::endl; + std::cerr << error.statusCode() << '\t' << request->methodName() << '\t' << path << std::endl; } diff --git a/server/server.cpp b/server/server.cpp index 6f02de1..a605012 100644 --- a/server/server.cpp +++ b/server/server.cpp @@ -8,6 +8,7 @@ #include "handler/info.h" #include "handler/env.h" #include "handler/register.h" +#include "handler/login.h" constexpr const char* pepper = "well, not much of a secret, huh?"; constexpr uint8_t currentDbVesion = 1; @@ -39,6 +40,7 @@ Server::Server(): router.addRoute(std::make_unique()); router.addRoute(std::make_unique()); router.addRoute(std::make_unique(this)); + router.addRoute(std::make_unique(this)); } Server::~Server() {} @@ -63,7 +65,7 @@ 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.get(), Response::Status::internalError); + Response& error = request->createResponse(Response::Status::internalError); error.send(); return; } @@ -107,3 +109,19 @@ unsigned int Server::registerAccount(const std::string& login, const std::string return db->registerAccount(login, hash); } + +bool Server::validatePassword(const std::string& login, const std::string& password) { + 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)); + } +} diff --git a/server/server.h b/server/server.h index 5a5fb1b..4291ee9 100644 --- a/server/server.h +++ b/server/server.h @@ -32,6 +32,7 @@ public: 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); private: void handleRequest(std::unique_ptr request); From 4b87b560acfe24717e5d5b28eccb2b88d10e4a08 Mon Sep 17 00:00:00 2001 From: blue Date: Sat, 23 Dec 2023 17:23:38 -0300 Subject: [PATCH 06/27] session creation --- database/dbinterface.h | 1 + database/migrations/m0.sql | 4 ++- database/mysql/mysql.cpp | 22 +++++++++++-- database/mysql/mysql.h | 1 + database/mysql/statement.cpp | 61 +++++++++++++++++++++++++++++------- database/mysql/statement.h | 5 ++- handler/login.cpp | 22 +++++++++---- server/CMakeLists.txt | 2 ++ server/server.cpp | 16 ++++++++-- server/server.h | 6 ++++ server/session.cpp | 18 +++++++++++ server/session.h | 19 +++++++++++ 12 files changed, 152 insertions(+), 25 deletions(-) create mode 100644 server/session.cpp create mode 100644 server/session.h diff --git a/database/dbinterface.h b/database/dbinterface.h index 5f018e8..e2c0ebe 100644 --- a/database/dbinterface.h +++ b/database/dbinterface.h @@ -43,6 +43,7 @@ public: virtual unsigned int registerAccount(const std::string& login, const std::string& hash) = 0; virtual std::string getAccountHash(const std::string& login) = 0; + virtual unsigned int createSession(const std::string& login, const std::string& access, const std::string& renew) = 0; protected: DBInterface(Type type); diff --git a/database/migrations/m0.sql b/database/migrations/m0.sql index e52e9ef..b6e9316 100644 --- a/database/migrations/m0.sql +++ b/database/migrations/m0.sql @@ -37,8 +37,10 @@ CREATE TABLE IF NOT EXISTS sessions ( `owner` INTEGER UNSIGNED NOT NULL, `started` TIMESTAMP DEFAULT UTC_TIMESTAMP(), `latest` TIMESTAMP DEFAULT UTC_TIMESTAMP(), - `salt` CHAR(16), + `access` CHAR(32), + `renew` CHAR(32), `persist` BOOLEAN NOT NULL, + `device` TEXT, FOREIGN KEY (owner) REFERENCES accounts(id) ); diff --git a/database/mysql/mysql.cpp b/database/mysql/mysql.cpp index 1e40cf4..c101220 100644 --- a/database/mysql/mysql.cpp +++ b/database/mysql/mysql.cpp @@ -18,6 +18,8 @@ constexpr const char* registerQuery = "INSERT INTO accounts (`login`, `type`, `p 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 = ?"; static const std::filesystem::path buildSQLPath = "database"; @@ -253,14 +255,30 @@ std::string MySQL::getAccountHash(const std::string& login) { getHash.bind(l.data(), MYSQL_TYPE_STRING); getHash.execute(); - std::vector> result = getHash.fetchResult(); + 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 result[0][0]; + return std::any_cast(result[0][0]); +} + +unsigned int MySQL::createSession(const std::string& login, const std::string& access, const std::string& renew) { + std::string l = login, a = access, r = renew; + static std::string testingDevice("Testing..."); + + MYSQL* con = &connection; + + Statement session(con, createSessionQuery); + session.bind(a.data(), MYSQL_TYPE_STRING); + session.bind(r.data(), MYSQL_TYPE_STRING); + session.bind(testingDevice.data(), MYSQL_TYPE_STRING); + session.bind(l.data(), MYSQL_TYPE_STRING); + session.execute(); + + return lastInsertedId(); } unsigned int MySQL::lastInsertedId() { diff --git a/database/mysql/mysql.h b/database/mysql/mysql.h index 75e865d..8344ce2 100644 --- a/database/mysql/mysql.h +++ b/database/mysql/mysql.h @@ -32,6 +32,7 @@ public: unsigned int registerAccount(const std::string& login, const std::string& hash) override; std::string getAccountHash(const std::string& login) override; + unsigned int createSession(const std::string& login, const std::string& access, const std::string& renew) override; private: void executeFile(const std::filesystem::path& relativePath); diff --git a/database/mysql/statement.cpp b/database/mysql/statement.cpp index acfb0e6..9a1fee9 100644 --- a/database/mysql/statement.cpp +++ b/database/mysql/statement.cpp @@ -3,8 +3,6 @@ #include "statement.h" -#include - #include "mysqld_error.h" #include "database/exceptions.h" @@ -66,7 +64,7 @@ void MySQL::Statement::execute() { } } -std::vector> MySQL::Statement::fetchResult() { +std::vector> 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 @@ -78,9 +76,9 @@ std::vector> MySQL::Statement::fetchResult() { std::unique_ptr mt(meta); unsigned int numColumns = mysql_num_fields(meta); MYSQL_BIND bind[numColumns]; - memset(bind, 0, sizeof(bind)); + std::memset(bind, 0, sizeof(bind)); - std::vector line(numColumns); + 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); @@ -88,14 +86,32 @@ std::vector> MySQL::Statement::fetchResult() { switch (field->type) { case MYSQL_TYPE_STRING: case MYSQL_TYPE_VAR_STRING: - case MYSQL_TYPE_VARCHAR: + 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)); } - line[i].resize(field->length); bind[i].buffer_type = field->type; - bind[i].buffer = line[i].data(); bind[i].buffer_length = field->length; bind[i].length = &lengths[i]; } @@ -103,11 +119,32 @@ std::vector> MySQL::Statement::fetchResult() { 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; + std::vector> result; while (mysql_stmt_fetch(raw) == 0) { - std::vector& row = result.emplace_back(numColumns); - for (unsigned int i = 0; i < numColumns; ++i) - row[i] = std::string(line[i].data(), lengths[i]); + 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)); + } + } } return result; diff --git a/database/mysql/statement.h b/database/mysql/statement.h index 3209155..369f0fa 100644 --- a/database/mysql/statement.h +++ b/database/mysql/statement.h @@ -3,7 +3,10 @@ #pragma once +#include #include +#include +#include #include "mysql.h" @@ -18,7 +21,7 @@ public: void bind(void* value, enum_field_types type, bool usigned = false); void execute(); - std::vector> fetchResult(); + std::vector> fetchResult(); private: std::unique_ptr stmt; diff --git a/handler/login.cpp b/handler/login.cpp index f09ecec..2937a77 100644 --- a/handler/login.cpp +++ b/handler/login.cpp @@ -33,24 +33,34 @@ void Handler::Login::handle(Request& request) { try { success = server->validatePassword(login, password); } catch (const DBInterface::NoLogin& e) { - std::cerr << "Exception on registration:\n\t" << e.what() << std::endl; + std::cerr << "Exception on logging in:\n\t" << e.what() << std::endl; return error(request, Result::noLogin, Response::Status::badRequest); //can send unauthed instead, to exclude login spoofing } catch (const std::exception& e) { - std::cerr << "Exception on registration:\n\t" << e.what() << std::endl; + 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 registration" << std::endl; + std::cerr << "Unknown exception on ogging in" << std::endl; return error(request, Result::unknownError, Response::Status::internalError); } if (!success) return error(request, Result::noLogin, Response::Status::badRequest); - //TODO opening the session - - Response& res = request.createResponse(); nlohmann::json body = nlohmann::json::object(); body["result"] = Result::success; + try { + Session& session = server->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(); } diff --git a/server/CMakeLists.txt b/server/CMakeLists.txt index 0e38cbe..9a1b2d8 100644 --- a/server/CMakeLists.txt +++ b/server/CMakeLists.txt @@ -1,11 +1,13 @@ set(HEADERS server.h router.h + session.h ) set(SOURCES server.cpp router.cpp + session.cpp ) target_sources(${PROJECT_NAME} PRIVATE ${SOURCES}) diff --git a/server/server.cpp b/server/server.cpp index a605012..cfb65ea 100644 --- a/server/server.cpp +++ b/server/server.cpp @@ -12,7 +12,7 @@ constexpr const char* pepper = "well, not much of a secret, huh?"; constexpr uint8_t currentDbVesion = 1; -constexpr const char* randomChars = "0123456789abcdef"; +constexpr const char* randomChars = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"; constexpr uint8_t saltSize = 16; constexpr uint8_t hashSize = 32; constexpr uint8_t hashParallel = 1; @@ -24,7 +24,8 @@ Server::Server(): requestCount(0), serverName(std::nullopt), router(), - db() + db(), + sessions() { std::cout << "Startig pica..." << std::endl; @@ -78,7 +79,7 @@ void Server::handleRequest(std::unique_ptr request) { 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)); + std::uniform_int_distribution distribution(0, std::strlen(randomChars) - 1); std::string result(length, 0); for (size_t i = 0; i < length; ++i) @@ -125,3 +126,12 @@ bool Server::validatePassword(const std::string& login, const std::string& passw throw std::runtime_error(std::string("Failed to verify password: ") + argon2_error_message(result)); } } + +Session& Server::openSession(const std::string& login) { + std::string accessToken = generateRandomString(32); + std::string renewToken = generateRandomString(32); + unsigned int sessionId = db->createSession(login, accessToken, renewToken); + + std::unique_ptr& session = sessions[accessToken] = std::make_unique(sessionId, accessToken, renewToken); + return *session.get(); +} diff --git a/server/server.h b/server/server.h index 4291ee9..5e5b321 100644 --- a/server/server.h +++ b/server/server.h @@ -10,6 +10,7 @@ #include #include #include +#include #include #include @@ -20,6 +21,7 @@ #include "request/request.h" #include "response/response.h" #include "router.h" +#include "session.h" #include "database/dbinterface.h" #include "utils/helpers.h" #include "config.h" @@ -33,15 +35,19 @@ public: 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); private: void handleRequest(std::unique_ptr request); static std::string generateRandomString(std::size_t length); private: + using Sessions = std::map>; + bool terminating; uint64_t requestCount; std::optional serverName; Router router; std::unique_ptr db; + Sessions sessions; }; diff --git a/server/session.cpp b/server/session.cpp new file mode 100644 index 0000000..2e203be --- /dev/null +++ b/server/session.cpp @@ -0,0 +1,18 @@ +// SPDX-FileCopyrightText: 2023 Yury Gubich +// SPDX-License-Identifier: GPL-3.0-or-later + +#include "session.h" + +Session::Session(unsigned int id, const std::string& access, const std::string& renew): + id(id), + access(access), + renew(renew) +{} + +std::string Session::getAccessToken() const { + return access; +} + +std::string Session::getRenewToken() const { + return renew; +} diff --git a/server/session.h b/server/session.h new file mode 100644 index 0000000..6d23636 --- /dev/null +++ b/server/session.h @@ -0,0 +1,19 @@ +// SPDX-FileCopyrightText: 2023 Yury Gubich +// SPDX-License-Identifier: GPL-3.0-or-later + +#pragma once + +#include + +class Session { +public: + Session(unsigned int id, const std::string& access, const std::string& renew); + + std::string getAccessToken() const; + std::string getRenewToken() const; + +private: + unsigned int id; + std::string access; + std::string renew; +}; From 59c1ffd02775ee6a05c03d5f4924b44df954c0c3 Mon Sep 17 00:00:00 2001 From: blue Date: Thu, 28 Dec 2023 17:26:08 -0300 Subject: [PATCH 07/27] Some thoughts about poll --- CMakeLists.txt | 2 + database/migrations/m0.sql | 2 +- handler/login.cpp | 4 +- handler/login.h | 1 + handler/poll.cpp | 44 ++++++++++++++++++++++ handler/poll.h | 32 ++++++++++++++++ request/CMakeLists.txt | 1 + request/accepting.h | 13 +++++++ request/redirect.cpp | 12 ++++++ request/redirect.h | 16 ++++++++ request/request.cpp | 75 ++++++++++++++++++++++++++++++-------- request/request.h | 12 ++++-- run.sh.in | 21 +++++++++++ server/router.cpp | 32 ++++++++-------- server/router.h | 8 ++-- server/server.cpp | 29 ++++++++++++--- server/server.h | 1 + server/session.cpp | 18 ++++++++- server/session.h | 6 ++- 19 files changed, 280 insertions(+), 49 deletions(-) create mode 100644 handler/poll.cpp create mode 100644 handler/poll.h create mode 100644 request/accepting.h create mode 100644 request/redirect.cpp create mode 100644 request/redirect.h create mode 100644 run.sh.in diff --git a/CMakeLists.txt b/CMakeLists.txt index 183ff6d..dba39c4 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -55,6 +55,8 @@ add_subdirectory(database) add_subdirectory(utils) 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 FCGI::FCGI diff --git a/database/migrations/m0.sql b/database/migrations/m0.sql index b6e9316..c691187 100644 --- a/database/migrations/m0.sql +++ b/database/migrations/m0.sql @@ -37,7 +37,7 @@ CREATE TABLE IF NOT EXISTS sessions ( `owner` INTEGER UNSIGNED NOT NULL, `started` TIMESTAMP DEFAULT UTC_TIMESTAMP(), `latest` TIMESTAMP DEFAULT UTC_TIMESTAMP(), - `access` CHAR(32), + `access` CHAR(32) NOT NULL UNIQUE, `renew` CHAR(32), `persist` BOOLEAN NOT NULL, `device` TEXT, diff --git a/handler/login.cpp b/handler/login.cpp index 2937a77..c72cdd2 100644 --- a/handler/login.cpp +++ b/handler/login.cpp @@ -34,7 +34,7 @@ void Handler::Login::handle(Request& request) { success = server->validatePassword(login, password); } catch (const DBInterface::NoLogin& e) { std::cerr << "Exception on logging in:\n\t" << e.what() << std::endl; - return error(request, Result::noLogin, Response::Status::badRequest); //can send unauthed instead, to exclude login spoofing + 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); @@ -43,7 +43,7 @@ void Handler::Login::handle(Request& request) { return error(request, Result::unknownError, Response::Status::internalError); } if (!success) - return error(request, Result::noLogin, Response::Status::badRequest); + return error(request, Result::wrongCredentials, Response::Status::badRequest); nlohmann::json body = nlohmann::json::object(); body["result"] = Result::success; diff --git a/handler/login.h b/handler/login.h index ef0986e..d128f40 100644 --- a/handler/login.h +++ b/handler/login.h @@ -19,6 +19,7 @@ public: emptyLogin, noPassword, emptyPassword, + wrongCredentials, unknownError }; diff --git a/handler/poll.cpp b/handler/poll.cpp new file mode 100644 index 0000000..a6a2e0c --- /dev/null +++ b/handler/poll.cpp @@ -0,0 +1,44 @@ +// 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" + +Handler::Poll::Poll (Server* server): + Handler("login", 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); + + try { + Session& session = server->getSession(access); + throw Redirect(&session); + } catch (const Redirect& r) { + throw r; + } catch (const std::exception& e) { + std::cerr << "Exception on poll:\n\t" << e.what() << std::endl; + return error(request, Result::unknownError, Response::Status::internalError); + } catch (...) { + std::cerr << "Unknown exception on poll" << std::endl; + return error(request, Result::unknownError, Response::Status::internalError); + } +} + +void Handler::Poll::error(Request& request, Result result, Response::Status status) const { + Response& res = request.createResponse(status); + nlohmann::json body = nlohmann::json::object(); + body["result"] = result; + + res.setBody(body); + res.send(); +} \ No newline at end of file diff --git a/handler/poll.h b/handler/poll.h new file mode 100644 index 0000000..a7b4362 --- /dev/null +++ b/handler/poll.h @@ -0,0 +1,32 @@ +// SPDX-FileCopyrightText: 2023 Yury Gubich +// SPDX-License-Identifier: GPL-3.0-or-later + +#pragma once + +#include "handler.h" +#include "request/request.h" +#include "response/response.h" + +class Server; +namespace Handler { + +class Poll : public Handler { +public: + Poll(Server* server); + void handle(Request& request) override; + + enum class Result { + success, + tokenProblem, + replace, + timeout, + unknownError + }; + + void error(Request& request, Result result, Response::Status status) const; +private: + Server* server; + +}; + +} \ No newline at end of file diff --git a/request/CMakeLists.txt b/request/CMakeLists.txt index eee9cda..bca1871 100644 --- a/request/CMakeLists.txt +++ b/request/CMakeLists.txt @@ -1,5 +1,6 @@ set(HEADERS request.h + redirectable.h ) set(SOURCES diff --git a/request/accepting.h b/request/accepting.h new file mode 100644 index 0000000..d1b28b8 --- /dev/null +++ b/request/accepting.h @@ -0,0 +1,13 @@ +// SPDX-FileCopyrightText: 2023 Yury Gubich +// SPDX-License-Identifier: GPL-3.0-or-later + +#pragma once + +#include + +#include "request/request.h" + +class Accepting { +public: + virtual void accept(std::unique_ptr request) = 0; +}; \ No newline at end of file diff --git a/request/redirect.cpp b/request/redirect.cpp new file mode 100644 index 0000000..04dac58 --- /dev/null +++ b/request/redirect.cpp @@ -0,0 +1,12 @@ +// 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"; +} \ No newline at end of file diff --git a/request/redirect.h b/request/redirect.h new file mode 100644 index 0000000..ce54f79 --- /dev/null +++ b/request/redirect.h @@ -0,0 +1,16 @@ +// 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; +}; \ No newline at end of file diff --git a/request/request.cpp b/request/request.cpp index a3e8701..4b239c9 100644 --- a/request/request.cpp +++ b/request/request.cpp @@ -10,6 +10,7 @@ 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* urlEncoded("application/x-www-form-urlencoded"); @@ -31,7 +32,8 @@ constexpr std::array< Request::Request (): state(State::initial), raw(), - response(nullptr) + response(nullptr), + path() {} Request::~Request() { @@ -39,25 +41,34 @@ Request::~Request() { } void Request::terminate() { - switch (state) { + switch (state) { + case State::terminated: + case State::initial: + break; 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) + 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 { - std::string_view method = methodName(); + std::string_view method = methodName(); for (const auto& pair : methods) { if (pair.first == method) return pair.second; @@ -89,6 +100,10 @@ OStream Request::getErrorStream() { 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"); @@ -125,11 +140,15 @@ void Request::responseIsComplete() { throw std::runtime_error("An attempt to mark the request as complete, but it wasn't responded"); break; case State::responding: + std::cout << responseCode() << '\t' << methodName() << '\t' << path << std::endl; state = State::responded; 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; } } @@ -137,25 +156,28 @@ Request::State Request::currentState() const { return state; } -std::string Request::getPath(const std::string& serverName) const { +void Request::readPath(const std::string& serverName) { if (state != State::accepted) throw std::runtime_error("An attempt to request path on a wrong request state"); - std::string path; + if (!path.empty()) + std::cout << "Request already has path \"" + path + "\", but it's being read again, probably an error"; + 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; } @@ -167,6 +189,9 @@ std::string Request::getServerName() const { } void Request::printEnvironment(std::ostream& out) { + if (!active()) + throw std::runtime_error("An attempt to print environment of a request in a wrong state"); + char **envp = raw.envp; for (int i = 0; envp[i] != nullptr; ++i) { out << envp[i] << "\n"; @@ -187,8 +212,8 @@ void Request::printEnvironment(nlohmann::json& out) { } bool Request::isFormUrlEncoded() const { - if (state == State::initial) - throw std::runtime_error("An attempt to read request content type on not accepted request"); + 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) { @@ -199,15 +224,15 @@ bool Request::isFormUrlEncoded() const { } unsigned int Request::contentLength() const { - if (state == State::initial) - throw std::runtime_error("An attempt to read request content length on not accepted request"); + if (!active()) + throw std::runtime_error("An attempt to read content length of a request in a wrong state"); return atoi(FCGX_GetParam(CONTENT_LENGTH, raw.envp)); } std::map Request::getForm() const { - if (state == State::initial) - throw std::runtime_error("An attempt to read form on not accepted request"); + if (!active()) + throw std::runtime_error("An attempt to read form of a request in a wrong state"); std::map result; std::string_view contentType(FCGX_GetParam(CONTENT_TYPE, raw.envp)); @@ -223,3 +248,21 @@ std::map Request::getForm() const { 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; +} \ No newline at end of file diff --git a/request/request.h b/request/request.h index dc2ca3c..d4bacd2 100644 --- a/request/request.h +++ b/request/request.h @@ -16,6 +16,7 @@ #include "stream/ostream.h" #include "utils/formdecode.h" +#include "utils/helpers.h" #include "response/response.h" class Request { @@ -25,7 +26,8 @@ public: initial, accepted, responding, - responded + responded, + terminated }; enum class Method { @@ -42,7 +44,7 @@ public: Request& operator = (Request&& other) = delete; bool wait(int socketDescriptor); - void terminate(); + bool active() const; Response& createResponse(); Response& createResponse(Response::Status status); @@ -55,8 +57,10 @@ public: unsigned int contentLength() const; std::map getForm() const; - std::string getPath(const std::string& serverName) const; + void readPath(const std::string& serverName); + std::string getPath() const; std::string getServerName() const; + std::string getAuthorizationToken() const; void printEnvironment(std::ostream& out); void printEnvironment(nlohmann::json& out); @@ -64,9 +68,11 @@ private: OStream getOutputStream(); OStream getErrorStream(); void responseIsComplete(); + void terminate(); private: State state; FCGX_Request raw; std::unique_ptr response; + std::string path; }; diff --git a/run.sh.in b/run.sh.in new file mode 100644 index 0000000..ab3070f --- /dev/null +++ b/run.sh.in @@ -0,0 +1,21 @@ +#!/bin/bash + +start_service() { + if systemctl is-active --quiet $1 + then + echo "$1 is already running" + else + sudo systemctl start $1 + echo "$1 started" + fi +} + +if [ ! -d "/run/pica" ]; then + sudo mkdir /run/pica + sudo chown $USER:$USER /run/pica +fi + +start_service "mariadb" +start_service "httpd" + +./@PROJECT_NAME@ \ No newline at end of file diff --git a/server/router.cpp b/server/router.cpp index 93c459c..2a1a63c 100644 --- a/server/router.cpp +++ b/server/router.cpp @@ -3,6 +3,8 @@ #include "router.h" +#include "request/redirect.h" + Router::Router(): get(), post() @@ -25,54 +27,54 @@ void Router::addRoute(Handler handler) { throw std::runtime_error("could'not add route " + handler->path + " to the routing table"); } -void Router::route(const std::string& path, std::unique_ptr request) { +void Router::route(std::unique_ptr request) { std::map::const_iterator itr, end; switch (request->method()) { case Request::Method::get: - itr = get.find(path); + itr = get.find(request->getPath()); end = get.end(); break; case Request::Method::post: - itr = post.find(path); + itr = post.find(request->getPath()); end = post.end(); break; default: - return handleMethodNotAllowed(path, std::move(request)); + return handleMethodNotAllowed(std::move(request)); } if (itr == end) - return handleNotFound(path, std::move(request)); + return handleNotFound(std::move(request)); try { - std::cout << "Handling " << path << "..." << std::endl; itr->second->handle(*request.get()); if (request->currentState() != Request::State::responded) - handleInternalError(path, std::runtime_error("handler failed to handle the request"), std::move(request)); - else - std::cout << request->responseCode() << '\t' << request->methodName() << '\t' << path << std::endl; + 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(path, e, std::move(request)); + handleInternalError(e, std::move(request)); } } -void Router::handleNotFound(const std::string& path, std::unique_ptr request) { +void Router::handleNotFound(std::unique_ptr request) { Response& notFound = request->createResponse(Response::Status::notFound); + std::string path = request->getPath(); notFound.setBody(std::string("Path \"") + path + "\" was not found"); notFound.send(); std::cerr << notFound.statusCode() << '\t' << request->methodName() << '\t' << path << std::endl; } -void Router::handleInternalError(const std::string& path, const std::exception& exception, std::unique_ptr request) { +void Router::handleInternalError(const std::exception& exception, std::unique_ptr request) { Response& error = request->createResponse(Response::Status::internalError); error.setBody(std::string(exception.what())); error.send(); - std::cerr << error.statusCode() << '\t' << request->methodName() << '\t' << path << std::endl; + std::cerr << error.statusCode() << '\t' << request->methodName() << '\t' << request->getPath() << std::endl; } -void Router::handleMethodNotAllowed(const std::string& path, std::unique_ptr request) { +void Router::handleMethodNotAllowed(std::unique_ptr request) { Response& error = request->createResponse(Response::Status::methodNotAllowed); error.setBody(std::string("Method not allowed")); error.send(); - std::cerr << error.statusCode() << '\t' << request->methodName() << '\t' << path << std::endl; + std::cerr << error.statusCode() << '\t' << request->methodName() << '\t' << request->getPath() << std::endl; } diff --git a/server/router.h b/server/router.h index 95fa8db..83c2fea 100644 --- a/server/router.h +++ b/server/router.h @@ -20,12 +20,12 @@ public: Router(); void addRoute(Handler handler); - void route(const std::string& path, std::unique_ptr request); + void route(std::unique_ptr request); private: - void handleNotFound(const std::string& path, std::unique_ptr request); - void handleInternalError(const std::string& path, const std::exception& exception, std::unique_ptr request); - void handleMethodNotAllowed(const std::string& path, std::unique_ptr request); + void handleNotFound(std::unique_ptr request); + void handleInternalError(const std::exception& exception, std::unique_ptr request); + void handleMethodNotAllowed(std::unique_ptr request); private: std::map get; diff --git a/server/server.cpp b/server/server.cpp index cfb65ea..4bc8ad6 100644 --- a/server/server.cpp +++ b/server/server.cpp @@ -5,13 +5,15 @@ #include +#include "database/exceptions.h" + #include "handler/info.h" #include "handler/env.h" #include "handler/register.h" #include "handler/login.h" constexpr const char* pepper = "well, not much of a secret, huh?"; -constexpr uint8_t currentDbVesion = 1; + constexpr const char* randomChars = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"; constexpr uint8_t saltSize = 16; constexpr uint8_t hashSize = 32; @@ -19,6 +21,8 @@ constexpr uint8_t hashParallel = 1; constexpr uint8_t hashIterations = 2; constexpr uint32_t hashMemoryCost = 65536; +constexpr uint8_t currentDbVesion = 1; + Server::Server(): terminating(false), requestCount(0), @@ -72,8 +76,8 @@ void Server::handleRequest(std::unique_ptr request) { } } - std::string path = request->getPath(serverName.value()); - router.route(path.data(), std::move(request)); + request->readPath(serverName.value()); + router.route(std::move(request)); } std::string Server::generateRandomString(std::size_t length) { @@ -128,9 +132,22 @@ bool Server::validatePassword(const std::string& login, const std::string& passw } Session& Server::openSession(const std::string& login) { - std::string accessToken = generateRandomString(32); - std::string renewToken = generateRandomString(32); - unsigned int sessionId = db->createSession(login, accessToken, renewToken); + std::string accessToken, renewToken; + unsigned int sessionId = 0; + int counter = 10; + do { + try { + accessToken = generateRandomString(32); + renewToken = generateRandomString(32); + sessionId = db->createSession(login, accessToken, renewToken); + break; + } catch (const DBInterface::Duplicate& e) { + std::cout << "Duplicate on creating session, trying again with different tokens"; + } + } while (--counter != 0); + + if (sessionId == 0) + throw std::runtime_error("Couldn't create session, ran out of attempts"); std::unique_ptr& session = sessions[accessToken] = std::make_unique(sessionId, accessToken, renewToken); return *session.get(); diff --git a/server/server.h b/server/server.h index 5e5b321..9c3e187 100644 --- a/server/server.h +++ b/server/server.h @@ -36,6 +36,7 @@ public: 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); private: void handleRequest(std::unique_ptr request); diff --git a/server/session.cpp b/server/session.cpp index 2e203be..43de593 100644 --- a/server/session.cpp +++ b/server/session.cpp @@ -3,10 +3,13 @@ #include "session.h" +#include "handler/poll.h" + Session::Session(unsigned int id, const std::string& access, const std::string& renew): id(id), access(access), - renew(renew) + renew(renew), + polling(nullptr) {} std::string Session::getAccessToken() const { @@ -16,3 +19,16 @@ std::string Session::getAccessToken() const { std::string Session::getRenewToken() const { return renew; } + +void Session::accept(std::unique_ptr request) { + if (polling) { + Response& res = request->createResponse(Response::Status::ok); + nlohmann::json body = nlohmann::json::object(); + body["result"] = Handler::Poll::Result::replace; + + res.setBody(body); + res.send(); + } + + polling = std::move(request); +} \ No newline at end of file diff --git a/server/session.h b/server/session.h index 6d23636..b5b459e 100644 --- a/server/session.h +++ b/server/session.h @@ -5,15 +5,19 @@ #include -class Session { +#include "request/accepting.h" + +class Session : public Accepting { public: Session(unsigned int id, const std::string& access, const std::string& renew); std::string getAccessToken() const; std::string getRenewToken() const; + void accept(std::unique_ptr request) override; private: unsigned int id; std::string access; std::string renew; + std::unique_ptr polling; }; From fe2fbb9ad08b6ccfa14c25a13dbd80bf2397e2b4 Mon Sep 17 00:00:00 2001 From: blue Date: Fri, 29 Dec 2023 14:40:00 -0300 Subject: [PATCH 08/27] Database Pool --- CMakeLists.txt | 2 + database/CMakeLists.txt | 8 ++- database/exceptions.cpp | 8 +-- database/exceptions.h | 13 +++-- database/{dbinterface.cpp => interface.cpp} | 10 ++-- database/{dbinterface.h => interface.h} | 15 +++--- database/mysql/mysql.cpp | 32 ++++++------ database/mysql/mysql.h | 6 ++- database/mysql/statement.cpp | 8 +-- database/mysql/statement.h | 2 + database/mysql/transaction.cpp | 8 +-- database/mysql/transaction.h | 2 + database/pool.cpp | 57 +++++++++++++++++++++ database/pool.h | 47 +++++++++++++++++ database/resource.cpp | 38 ++++++++++++++ database/resource.h | 31 +++++++++++ handler/login.cpp | 2 +- handler/register.cpp | 2 +- request/CMakeLists.txt | 3 ++ request/accepting.h | 3 +- server/server.cpp | 26 +++++++--- server/server.h | 4 +- server/session.h | 4 ++ 23 files changed, 268 insertions(+), 63 deletions(-) rename database/{dbinterface.cpp => interface.cpp} (64%) rename database/{dbinterface.h => interface.h} (83%) create mode 100644 database/pool.cpp create mode 100644 database/pool.h create mode 100644 database/resource.cpp create mode 100644 database/resource.h diff --git a/CMakeLists.txt b/CMakeLists.txt index dba39c4..2eac80e 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -40,6 +40,7 @@ message("Compile options: " ${COMPILE_OPTIONS_STRING}) 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}) @@ -63,6 +64,7 @@ target_link_libraries(${PROJECT_NAME} PRIVATE FCGI::FCGI++ nlohmann_json::nlohmann_json Argon2::Argon2 + Threads::Threads ) install(TARGETS ${PROJECT_NAME} RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR}) diff --git a/database/CMakeLists.txt b/database/CMakeLists.txt index a0adbc9..d903715 100644 --- a/database/CMakeLists.txt +++ b/database/CMakeLists.txt @@ -1,11 +1,15 @@ set(HEADERS - dbinterface.h + interface.h exceptions.h + pool.h + resource.h ) set(SOURCES - dbinterface.cpp + interface.cpp exceptions.cpp + pool.cpp + resource.cpp ) target_sources(${PROJECT_NAME} PRIVATE ${SOURCES}) diff --git a/database/exceptions.cpp b/database/exceptions.cpp index 5d555e2..d76ff93 100644 --- a/database/exceptions.cpp +++ b/database/exceptions.cpp @@ -3,18 +3,18 @@ #include "exceptions.h" -DBInterface::Duplicate::Duplicate(const std::string& text): +DB::Duplicate::Duplicate(const std::string& text): std::runtime_error(text) {} -DBInterface::DuplicateLogin::DuplicateLogin(const std::string& text): +DB::DuplicateLogin::DuplicateLogin(const std::string& text): Duplicate(text) {} -DBInterface::EmptyResult::EmptyResult(const std::string& text): +DB::EmptyResult::EmptyResult(const std::string& text): std::runtime_error(text) {} -DBInterface::NoLogin::NoLogin(const std::string& text): +DB::NoLogin::NoLogin(const std::string& text): EmptyResult(text) {} diff --git a/database/exceptions.h b/database/exceptions.h index 8d447e2..8b677d8 100644 --- a/database/exceptions.h +++ b/database/exceptions.h @@ -3,24 +3,27 @@ #pragma once -#include "dbinterface.h" +#include +#include -class DBInterface::Duplicate : public std::runtime_error { +namespace DB { +class Duplicate : public std::runtime_error { public: explicit Duplicate(const std::string& text); }; -class DBInterface::DuplicateLogin : public DBInterface::Duplicate { +class DuplicateLogin : public Duplicate { public: explicit DuplicateLogin(const std::string& text); }; -class DBInterface::EmptyResult : public std::runtime_error { +class EmptyResult : public std::runtime_error { public: explicit EmptyResult(const std::string& text); }; -class DBInterface::NoLogin : public DBInterface::EmptyResult { +class NoLogin : public EmptyResult { public: explicit NoLogin(const std::string& text); }; +} diff --git a/database/dbinterface.cpp b/database/interface.cpp similarity index 64% rename from database/dbinterface.cpp rename to database/interface.cpp index de98e39..563296c 100644 --- a/database/dbinterface.cpp +++ b/database/interface.cpp @@ -1,18 +1,18 @@ // SPDX-FileCopyrightText: 2023 Yury Gubich // SPDX-License-Identifier: GPL-3.0-or-later -#include "dbinterface.h" +#include "interface.h" #include "mysql/mysql.h" -DBInterface::DBInterface(Type type): +DB::Interface::Interface(Type type): type(type), state(State::disconnected) {} -DBInterface::~DBInterface() {} +DB::Interface::~Interface() {} -std::unique_ptr DBInterface::create(Type type) { +std::unique_ptr DB::Interface::create(Type type) { switch (type) { case Type::mysql: return std::make_unique(); @@ -21,6 +21,6 @@ std::unique_ptr DBInterface::create(Type type) { throw std::runtime_error("Unexpected database type: " + std::to_string((uint8_t)type)); } -DBInterface::State DBInterface::currentState() const { +DB::Interface::State DB::Interface::currentState() const { return state; } diff --git a/database/dbinterface.h b/database/interface.h similarity index 83% rename from database/dbinterface.h rename to database/interface.h index e2c0ebe..7b6e204 100644 --- a/database/dbinterface.h +++ b/database/interface.h @@ -8,7 +8,8 @@ #include #include -class DBInterface { +namespace DB { +class Interface { public: enum class Type { mysql @@ -18,19 +19,14 @@ public: connecting, connected }; - static std::unique_ptr create(Type type); + static std::unique_ptr create(Type type); - virtual ~DBInterface(); + virtual ~Interface(); State currentState() const; const Type type; - class Duplicate; - class DuplicateLogin; - class EmptyResult; - class NoLogin; - public: virtual void connect(const std::string& path) = 0; virtual void disconnect() = 0; @@ -46,8 +42,9 @@ public: virtual unsigned int createSession(const std::string& login, const std::string& access, const std::string& renew) = 0; protected: - DBInterface(Type type); + Interface(Type type); protected: State state; }; +} diff --git a/database/mysql/mysql.cpp b/database/mysql/mysql.cpp index c101220..b436b03 100644 --- a/database/mysql/mysql.cpp +++ b/database/mysql/mysql.cpp @@ -23,8 +23,8 @@ constexpr const char* createSessionQuery = "INSERT INTO sessions (`owner`, `acce static const std::filesystem::path buildSQLPath = "database"; -MySQL::MySQL(): - DBInterface(Type::mysql), +DB::MySQL::MySQL(): + Interface(Type::mysql), connection(), login(), password(), @@ -33,11 +33,11 @@ MySQL::MySQL(): mysql_init(&connection); } -MySQL::~MySQL() { +DB::MySQL::~MySQL() { mysql_close(&connection); } -void MySQL::connect(const std::string& path) { +void DB::MySQL::connect(const std::string& path) { if (state != State::disconnected) return; @@ -59,7 +59,7 @@ void MySQL::connect(const std::string& path) { state = State::connected; } -void MySQL::setCredentials(const std::string& login, const std::string& password) { +void DB::MySQL::setCredentials(const std::string& login, const std::string& password) { if (MySQL::login == login && MySQL::password == password) return; @@ -81,7 +81,7 @@ void MySQL::setCredentials(const std::string& login, const std::string& password throw std::runtime_error(std::string("Error changing credetials: ") + mysql_error(con)); } -void MySQL::setDatabase(const std::string& database) { +void DB::MySQL::setDatabase(const std::string& database) { if (MySQL::database == database) return; @@ -97,7 +97,7 @@ void MySQL::setDatabase(const std::string& database) { throw std::runtime_error(std::string("Error changing db: ") + mysql_error(con)); } -void MySQL::disconnect() { +void DB::MySQL::disconnect() { if (state == State::disconnected) return; @@ -106,7 +106,7 @@ void MySQL::disconnect() { mysql_init(con); //this is ridiculous! } -void MySQL::executeFile(const std::filesystem::path& relativePath) { +void DB::MySQL::executeFile(const std::filesystem::path& relativePath) { MYSQL* con = &connection; std::filesystem::path path = sharedPath() / relativePath; if (!std::filesystem::exists(path)) @@ -138,7 +138,7 @@ void MySQL::executeFile(const std::filesystem::path& relativePath) { } } -uint8_t MySQL::getVersion() { +uint8_t DB::MySQL::getVersion() { MYSQL* con = &connection; int result = mysql_query(con, versionQuery); @@ -161,14 +161,14 @@ uint8_t MySQL::getVersion() { return 0; } -void MySQL::setVersion(uint8_t version) { +void DB::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 MySQL::migrate(uint8_t targetVersion) { +void DB::MySQL::migrate(uint8_t targetVersion) { uint8_t currentVersion = getVersion(); while (currentVersion < targetVersion) { @@ -190,7 +190,7 @@ void MySQL::migrate(uint8_t targetVersion) { std::cout << "Database is now on actual version " << std::to_string(targetVersion) << std::endl; } -std::optional MySQL::getComment(std::string& string) { +std::optional DB::MySQL::getComment(std::string& string) { ltrim(string); if (string.length() < 2) return std::nullopt; @@ -218,7 +218,7 @@ std::optional MySQL::getComment(std::string& string) { return std::nullopt; } -unsigned int MySQL::registerAccount(const std::string& login, const std::string& hash) { +unsigned int DB::MySQL::registerAccount(const std::string& login, const std::string& hash) { //TODO validate filed lengths! MYSQL* con = &connection; MySQL::Transaction txn(con); @@ -247,7 +247,7 @@ unsigned int MySQL::registerAccount(const std::string& login, const std::string& return id; } -std::string MySQL::getAccountHash(const std::string& login) { +std::string DB::MySQL::getAccountHash(const std::string& login) { std::string l = login; MYSQL* con = &connection; @@ -265,7 +265,7 @@ std::string MySQL::getAccountHash(const std::string& login) { return std::any_cast(result[0][0]); } -unsigned int MySQL::createSession(const std::string& login, const std::string& access, const std::string& renew) { +unsigned int DB::MySQL::createSession(const std::string& login, const std::string& access, const std::string& renew) { std::string l = login, a = access, r = renew; static std::string testingDevice("Testing..."); @@ -281,7 +281,7 @@ unsigned int MySQL::createSession(const std::string& login, const std::string& a return lastInsertedId(); } -unsigned int MySQL::lastInsertedId() { +unsigned int DB::MySQL::lastInsertedId() { MYSQL* con = &connection; int result = mysql_query(con, lastIdQuery); diff --git a/database/mysql/mysql.h b/database/mysql/mysql.h index 8344ce2..7edc2ae 100644 --- a/database/mysql/mysql.h +++ b/database/mysql/mysql.h @@ -9,10 +9,11 @@ #include -#include "database/dbinterface.h" +#include "database/interface.h" #include "utils/helpers.h" -class MySQL : public DBInterface { +namespace DB { +class MySQL : public Interface { class Statement; class Transaction; @@ -51,3 +52,4 @@ struct ResDeleter { } }; }; +} diff --git a/database/mysql/statement.cpp b/database/mysql/statement.cpp index 9a1fee9..3e8a841 100644 --- a/database/mysql/statement.cpp +++ b/database/mysql/statement.cpp @@ -9,7 +9,7 @@ static uint64_t TIME_LENGTH = sizeof(MYSQL_TIME); -MySQL::Statement::Statement(MYSQL* connection, const char* statement): +DB::MySQL::Statement::Statement(MYSQL* connection, const char* statement): stmt(mysql_stmt_init(connection)), param() { @@ -18,7 +18,7 @@ MySQL::Statement::Statement(MYSQL* connection, const char* statement): throw std::runtime_error(std::string("Error preparing statement: ") + mysql_stmt_error(stmt.get())); } -void MySQL::Statement::bind(void* value, enum_field_types type, bool usigned) { +void DB::MySQL::Statement::bind(void* value, enum_field_types type, bool usigned) { MYSQL_BIND& result = param.emplace_back(); std::memset(&result, 0, sizeof(result)); @@ -45,7 +45,7 @@ void MySQL::Statement::bind(void* value, enum_field_types type, bool usigned) { } } -void MySQL::Statement::execute() { +void DB::MySQL::Statement::execute() { MYSQL_STMT* raw = stmt.get(); int result = mysql_stmt_bind_param(raw, param.data()); if (result != 0) @@ -64,7 +64,7 @@ void MySQL::Statement::execute() { } } -std::vector> MySQL::Statement::fetchResult() { +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 diff --git a/database/mysql/statement.h b/database/mysql/statement.h index 369f0fa..68d9563 100644 --- a/database/mysql/statement.h +++ b/database/mysql/statement.h @@ -10,6 +10,7 @@ #include "mysql.h" +namespace DB { class MySQL::Statement { struct STMTDeleter { void operator () (MYSQL_STMT* stmt) { @@ -27,3 +28,4 @@ private: std::unique_ptr stmt; std::vector param; }; +} diff --git a/database/mysql/transaction.cpp b/database/mysql/transaction.cpp index 1a9b92c..24cda9f 100644 --- a/database/mysql/transaction.cpp +++ b/database/mysql/transaction.cpp @@ -3,7 +3,7 @@ #include "transaction.h" -MySQL::Transaction::Transaction(MYSQL* connection): +DB::MySQL::Transaction::Transaction(MYSQL* connection): con(connection), opened(false) { @@ -13,12 +13,12 @@ MySQL::Transaction::Transaction(MYSQL* connection): opened = true; } -MySQL::Transaction::~Transaction() { +DB::MySQL::Transaction::~Transaction() { if (opened) abort(); } -void MySQL::Transaction::commit() { +void DB::MySQL::Transaction::commit() { if (mysql_commit(con) != 0) throw std::runtime_error(std::string("Failed to commit transaction") + mysql_error(con)); @@ -27,7 +27,7 @@ void MySQL::Transaction::commit() { throw std::runtime_error(std::string("Failed to return autocommit") + mysql_error(con)); } -void MySQL::Transaction::abort() { +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)); diff --git a/database/mysql/transaction.h b/database/mysql/transaction.h index 5a6a15c..b4b97d3 100644 --- a/database/mysql/transaction.h +++ b/database/mysql/transaction.h @@ -5,6 +5,7 @@ #include "mysql.h" +namespace DB { class MySQL::Transaction { public: Transaction(MYSQL* connection); @@ -17,3 +18,4 @@ private: MYSQL* con; bool opened; }; +} diff --git a/database/pool.cpp b/database/pool.cpp new file mode 100644 index 0000000..53c08f7 --- /dev/null +++ b/database/pool.cpp @@ -0,0 +1,57 @@ +// 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 new file mode 100644 index 0000000..1040781 --- /dev/null +++ b/database/pool.h @@ -0,0 +1,47 @@ +// 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 new file mode 100644 index 0000000..4021a51 --- /dev/null +++ b/database/resource.cpp @@ -0,0 +1,38 @@ +// 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 new file mode 100644 index 0000000..d437efd --- /dev/null +++ b/database/resource.h @@ -0,0 +1,31 @@ +// 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/handler/login.cpp b/handler/login.cpp index c72cdd2..4cd1ef9 100644 --- a/handler/login.cpp +++ b/handler/login.cpp @@ -32,7 +32,7 @@ void Handler::Login::handle(Request& request) { bool success = false; try { success = server->validatePassword(login, password); - } catch (const DBInterface::NoLogin& e) { + } 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) { diff --git a/handler/register.cpp b/handler/register.cpp index 57fefb9..ee485c8 100644 --- a/handler/register.cpp +++ b/handler/register.cpp @@ -35,7 +35,7 @@ void Handler::Register::handle(Request& request) { try { server->registerAccount(login, password); - } catch (const DBInterface::DuplicateLogin& e) { + } 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) { diff --git a/request/CMakeLists.txt b/request/CMakeLists.txt index bca1871..19e567c 100644 --- a/request/CMakeLists.txt +++ b/request/CMakeLists.txt @@ -1,10 +1,13 @@ 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 index d1b28b8..de7b626 100644 --- a/request/accepting.h +++ b/request/accepting.h @@ -9,5 +9,6 @@ class Accepting { public: + virtual ~Accepting() {}; virtual void accept(std::unique_ptr request) = 0; -}; \ No newline at end of file +}; diff --git a/server/server.cpp b/server/server.cpp index 4bc8ad6..4bf05d4 100644 --- a/server/server.cpp +++ b/server/server.cpp @@ -13,6 +13,11 @@ #include "handler/login.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 const char* randomChars = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"; constexpr uint8_t saltSize = 16; @@ -28,18 +33,22 @@ Server::Server(): requestCount(0), serverName(std::nullopt), router(), - db(), + pool(DB::Pool::create()), sessions() { 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->setCredentials("pica", "pica"); - db->setDatabase("pica"); + DB::Resource db = pool->request(); - db->connect("/run/mysqld/mysqld.sock"); db->migrate(currentDbVesion); router.addRoute(std::make_unique()); @@ -112,10 +121,12 @@ unsigned int Server::registerAccount(const std::string& login, const std::string 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; @@ -139,9 +150,10 @@ Session& Server::openSession(const std::string& login) { try { accessToken = generateRandomString(32); renewToken = generateRandomString(32); + DB::Resource db = pool->request(); sessionId = db->createSession(login, accessToken, renewToken); break; - } catch (const DBInterface::Duplicate& e) { + } catch (const DB::Duplicate& e) { std::cout << "Duplicate on creating session, trying again with different tokens"; } } while (--counter != 0); diff --git a/server/server.h b/server/server.h index 9c3e187..9d3319c 100644 --- a/server/server.h +++ b/server/server.h @@ -22,7 +22,7 @@ #include "response/response.h" #include "router.h" #include "session.h" -#include "database/dbinterface.h" +#include "database/pool.h" #include "utils/helpers.h" #include "config.h" @@ -49,6 +49,6 @@ private: uint64_t requestCount; std::optional serverName; Router router; - std::unique_ptr db; + std::shared_ptr pool; Sessions sessions; }; diff --git a/server/session.h b/server/session.h index b5b459e..910070f 100644 --- a/server/session.h +++ b/server/session.h @@ -10,6 +10,10 @@ class Session : public Accepting { public: Session(unsigned int id, const std::string& access, const std::string& renew); + Session(const Session&) = delete; + Session(Session&& other); + Session& operator = (const Session&) = delete; + Session& operator = (Session&& other); std::string getAccessToken() const; std::string getRenewToken() const; From f1a2006b4bf35f0dda9214f8830ec510b6c5302f Mon Sep 17 00:00:00 2001 From: blue Date: Sat, 30 Dec 2023 19:42:11 -0300 Subject: [PATCH 09/27] task manager, license formatting --- CMakeLists.txt | 4 ++ cmake/FindArgon2.cmake | 3 ++ cmake/FindFCGI.cmake | 3 ++ cmake/FindMariaDB.cmake | 3 ++ config.h.in | 3 ++ database/CMakeLists.txt | 3 ++ database/exceptions.cpp | 4 +- database/exceptions.h | 4 +- database/interface.cpp | 4 +- database/migrations/CMakeLists.txt | 3 ++ database/mysql/CMakeLists.txt | 3 ++ database/mysql/mysql.cpp | 4 +- database/mysql/mysql.h | 4 +- database/mysql/statement.cpp | 4 +- database/mysql/statement.h | 4 +- database/mysql/transaction.cpp | 4 +- database/mysql/transaction.h | 4 +- database/pool.cpp | 4 +- database/pool.h | 4 +- database/resource.cpp | 4 +- database/resource.h | 4 +- handler/CMakeLists.txt | 3 ++ handler/env.cpp | 4 +- handler/env.h | 4 +- handler/handler.cpp | 4 +- handler/handler.h | 4 +- handler/info.cpp | 4 +- handler/info.h | 4 +- handler/login.cpp | 14 +++--- handler/login.h | 10 +++-- handler/poll.cpp | 6 +-- handler/poll.h | 6 +-- handler/register.cpp | 12 ++++-- handler/register.h | 10 +++-- main.cpp | 9 ++-- request/CMakeLists.txt | 3 ++ request/accepting.h | 4 +- request/redirect.cpp | 6 +-- request/redirect.h | 6 +-- request/request.cpp | 8 ++-- request/request.h | 4 +- response/CMakeLists.txt | 3 ++ response/response.cpp | 4 +- response/response.h | 4 +- run.sh.in | 5 ++- server/CMakeLists.txt | 3 ++ server/router.cpp | 4 +- server/router.h | 4 +- server/server.cpp | 26 +++++++---- server/server.h | 10 +++-- server/session.cpp | 6 +-- server/session.h | 4 +- stream/CMakeLists.txt | 3 ++ stream/ostream.cpp | 4 +- stream/ostream.h | 4 +- stream/stream.cpp | 4 +- stream/stream.h | 4 +- taskmanager/CMakeLists.txt | 16 +++++++ taskmanager/job.cpp | 9 ++++ taskmanager/job.h | 19 ++++++++ taskmanager/manager.cpp | 69 ++++++++++++++++++++++++++++++ taskmanager/manager.h | 40 +++++++++++++++++ taskmanager/route.cpp | 13 ++++++ taskmanager/route.h | 23 ++++++++++ utils/CMakeLists.txt | 3 ++ utils/formdecode.cpp | 4 +- utils/formdecode.h | 4 +- utils/helpers.cpp | 4 +- utils/helpers.h | 4 +- 69 files changed, 380 insertions(+), 122 deletions(-) create mode 100644 taskmanager/CMakeLists.txt create mode 100644 taskmanager/job.cpp create mode 100644 taskmanager/job.h create mode 100644 taskmanager/manager.cpp create mode 100644 taskmanager/manager.h create mode 100644 taskmanager/route.cpp create mode 100644 taskmanager/route.h diff --git a/CMakeLists.txt b/CMakeLists.txt index 2eac80e..4d8e5a1 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,3 +1,6 @@ +#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 @@ -54,6 +57,7 @@ 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) diff --git a/cmake/FindArgon2.cmake b/cmake/FindArgon2.cmake index 75d70a6..0f1fc81 100644 --- a/cmake/FindArgon2.cmake +++ b/cmake/FindArgon2.cmake @@ -1,3 +1,6 @@ +#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) diff --git a/cmake/FindFCGI.cmake b/cmake/FindFCGI.cmake index e3a3e88..77a04df 100644 --- a/cmake/FindFCGI.cmake +++ b/cmake/FindFCGI.cmake @@ -1,3 +1,6 @@ +#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 623de5f..f32d521 100644 --- a/cmake/FindMariaDB.cmake +++ b/cmake/FindMariaDB.cmake @@ -1,3 +1,6 @@ +#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 07ee1ea..624a6d9 100644 --- a/config.h.in +++ b/config.h.in @@ -1,3 +1,6 @@ +//SPDX-FileCopyrightText: 2023 Yury Gubich +//SPDX-License-Identifier: GPL-3.0-or-later + #pragma once #define FULL_DATA_DIR "@CMAKE_INSTALL_FULL_DATADIR@" diff --git a/database/CMakeLists.txt b/database/CMakeLists.txt index d903715..42e02ef 100644 --- a/database/CMakeLists.txt +++ b/database/CMakeLists.txt @@ -1,3 +1,6 @@ +#SPDX-FileCopyrightText: 2023 Yury Gubich +#SPDX-License-Identifier: GPL-3.0-or-later + set(HEADERS interface.h exceptions.h diff --git a/database/exceptions.cpp b/database/exceptions.cpp index d76ff93..5ba8058 100644 --- a/database/exceptions.cpp +++ b/database/exceptions.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 "exceptions.h" diff --git a/database/exceptions.h b/database/exceptions.h index 8b677d8..19da281 100644 --- a/database/exceptions.h +++ b/database/exceptions.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/database/interface.cpp b/database/interface.cpp index 563296c..ecf195e 100644 --- a/database/interface.cpp +++ b/database/interface.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 "interface.h" diff --git a/database/migrations/CMakeLists.txt b/database/migrations/CMakeLists.txt index 9091bba..d0842f0 100644 --- a/database/migrations/CMakeLists.txt +++ b/database/migrations/CMakeLists.txt @@ -1,3 +1,6 @@ +#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) diff --git a/database/mysql/CMakeLists.txt b/database/mysql/CMakeLists.txt index b1cec73..7a77c4b 100644 --- a/database/mysql/CMakeLists.txt +++ b/database/mysql/CMakeLists.txt @@ -1,3 +1,6 @@ +#SPDX-FileCopyrightText: 2023 Yury Gubich +#SPDX-License-Identifier: GPL-3.0-or-laterr + set(HEADERS mysql.h statement.h diff --git a/database/mysql/mysql.cpp b/database/mysql/mysql.cpp index b436b03..151fa9b 100644 --- a/database/mysql/mysql.cpp +++ b/database/mysql/mysql.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 "mysql.h" diff --git a/database/mysql/mysql.h b/database/mysql/mysql.h index 7edc2ae..7bd9775 100644 --- a/database/mysql/mysql.h +++ b/database/mysql/mysql.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/database/mysql/statement.cpp b/database/mysql/statement.cpp index 3e8a841..edd90bd 100644 --- a/database/mysql/statement.cpp +++ b/database/mysql/statement.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 "statement.h" diff --git a/database/mysql/statement.h b/database/mysql/statement.h index 68d9563..4f013f5 100644 --- a/database/mysql/statement.h +++ b/database/mysql/statement.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/database/mysql/transaction.cpp b/database/mysql/transaction.cpp index 24cda9f..e280fe6 100644 --- a/database/mysql/transaction.cpp +++ b/database/mysql/transaction.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 "transaction.h" diff --git a/database/mysql/transaction.h b/database/mysql/transaction.h index b4b97d3..aa972e6 100644 --- a/database/mysql/transaction.h +++ b/database/mysql/transaction.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/database/pool.cpp b/database/pool.cpp index 53c08f7..644fee1 100644 --- a/database/pool.cpp +++ b/database/pool.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 "pool.h" diff --git a/database/pool.h b/database/pool.h index 1040781..8874f1f 100644 --- a/database/pool.h +++ b/database/pool.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/database/resource.cpp b/database/resource.cpp index 4021a51..bfccae7 100644 --- a/database/resource.cpp +++ b/database/resource.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 "resource.h" diff --git a/database/resource.h b/database/resource.h index d437efd..6e9d4fa 100644 --- a/database/resource.h +++ b/database/resource.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/handler/CMakeLists.txt b/handler/CMakeLists.txt index 07b134a..536d1d0 100644 --- a/handler/CMakeLists.txt +++ b/handler/CMakeLists.txt @@ -1,3 +1,6 @@ +#SPDX-FileCopyrightText: 2023 Yury Gubich +#SPDX-License-Identifier: GPL-3.0-or-later + set(HEADERS handler.h info.h diff --git a/handler/env.cpp b/handler/env.cpp index 08910c2..5d4630c 100644 --- a/handler/env.cpp +++ b/handler/env.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 "env.h" diff --git a/handler/env.h b/handler/env.h index cb361f0..d452af1 100644 --- a/handler/env.h +++ b/handler/env.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/handler/handler.cpp b/handler/handler.cpp index e0c39c1..49b8572 100644 --- a/handler/handler.cpp +++ b/handler/handler.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 "handler.h" diff --git a/handler/handler.h b/handler/handler.h index 4e682c4..1ba643b 100644 --- a/handler/handler.h +++ b/handler/handler.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/handler/info.cpp b/handler/info.cpp index 3a65cd7..408476c 100644 --- a/handler/info.cpp +++ b/handler/info.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 "info.h" diff --git a/handler/info.h b/handler/info.h index b1adc12..da162b3 100644 --- a/handler/info.h +++ b/handler/info.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/handler/login.cpp b/handler/login.cpp index 4cd1ef9..e8c32a1 100644 --- a/handler/login.cpp +++ b/handler/login.cpp @@ -1,12 +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 #include "login.h" #include "server/server.h" #include "database/exceptions.h" -Handler::Login::Login(Server* server): +Handler::Login::Login(std::weak_ptr server): Handler("login", Request::Method::post), server(server) {} @@ -29,9 +29,13 @@ void Handler::Login::handle(Request& request) { 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 = server->validatePassword(login, password); + 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); @@ -49,7 +53,7 @@ void Handler::Login::handle(Request& request) { body["result"] = Result::success; try { - Session& session = server->openSession(login); + Session& session = srv->openSession(login); body["accessToken"] = session.getAccessToken(); body["renewToken"] = session.getRenewToken(); } catch (const std::exception& e) { diff --git a/handler/login.h b/handler/login.h index d128f40..47f0456 100644 --- a/handler/login.h +++ b/handler/login.h @@ -1,8 +1,10 @@ -// 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 "handler.h" class Server; @@ -10,7 +12,7 @@ namespace Handler { class Login : public Handler { public: - Login(Server* server); + Login(std::weak_ptr server); void handle(Request& request) override; enum class Result { @@ -27,7 +29,7 @@ private: void error(Request& request, Result result, Response::Status code); private: - Server* server; + std::weak_ptr server; }; } diff --git a/handler/poll.cpp b/handler/poll.cpp index a6a2e0c..5fb2c3b 100644 --- a/handler/poll.cpp +++ b/handler/poll.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 "handler/poll.h" @@ -41,4 +41,4 @@ void Handler::Poll::error(Request& request, Result result, Response::Status stat res.setBody(body); res.send(); -} \ No newline at end of file +} diff --git a/handler/poll.h b/handler/poll.h index a7b4362..656a159 100644 --- a/handler/poll.h +++ b/handler/poll.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 @@ -29,4 +29,4 @@ private: }; -} \ No newline at end of file +} diff --git a/handler/register.cpp b/handler/register.cpp index ee485c8..203e376 100644 --- a/handler/register.cpp +++ b/handler/register.cpp @@ -1,12 +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 #include "register.h" #include "server/server.h" #include "database/exceptions.h" -Handler::Register::Register(Server* server): +Handler::Register::Register(std::weak_ptr server): Handler("register", Request::Method::post), server(server) {} @@ -33,8 +33,12 @@ void Handler::Register::handle(Request& request) { //TODO password policies checkup + std::shared_ptr srv = server.lock(); + if (!srv) + return error(request, Result::unknownError, Response::Status::internalError); + try { - server->registerAccount(login, password); + 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); diff --git a/handler/register.h b/handler/register.h index 6e36655..39fe000 100644 --- a/handler/register.h +++ b/handler/register.h @@ -1,8 +1,10 @@ -// 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 "handler.h" class Server; @@ -10,7 +12,7 @@ namespace Handler { class Register : public Handler { public: - Register(Server* server); + Register(std::weak_ptr server); void handle(Request& request) override; enum class Result { @@ -29,6 +31,6 @@ private: void error(Request& request, Result result, Response::Status code); private: - Server* server; + std::weak_ptr server; }; } diff --git a/main.cpp b/main.cpp index 8c37269..c336bf6 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,6 +7,7 @@ #include #include +#include #include "config.h" //autogenereted by cmake in the root of bindir #include "utils/helpers.h" @@ -34,6 +35,6 @@ int main(int argc, char** argv) { FCGX_Init(); - Server server; - server.run(sockfd); + auto server = std::make_shared(); + server->run(sockfd); } diff --git a/request/CMakeLists.txt b/request/CMakeLists.txt index 19e567c..87e818d 100644 --- a/request/CMakeLists.txt +++ b/request/CMakeLists.txt @@ -1,3 +1,6 @@ +#SPDX-FileCopyrightText: 2023 Yury Gubich +#SPDX-License-Identifier: GPL-3.0-or-later + set(HEADERS request.h redirect.h diff --git a/request/accepting.h b/request/accepting.h index de7b626..f5d731c 100644 --- a/request/accepting.h +++ b/request/accepting.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/request/redirect.cpp b/request/redirect.cpp index 04dac58..e7fc326 100644 --- a/request/redirect.cpp +++ b/request/redirect.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 "redirect.h" @@ -9,4 +9,4 @@ Redirect::Redirect(Accepting* 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"; -} \ No newline at end of file +} diff --git a/request/redirect.h b/request/redirect.h index ce54f79..e31d722 100644 --- a/request/redirect.h +++ b/request/redirect.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 @@ -13,4 +13,4 @@ public: Accepting* destination; const char* what() const noexcept override; -}; \ No newline at end of file +}; diff --git a/request/request.cpp b/request/request.cpp index 4b239c9..3ee662f 100644 --- a/request/request.cpp +++ b/request/request.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 "request.h" @@ -140,8 +140,8 @@ void Request::responseIsComplete() { throw std::runtime_error("An attempt to mark the request as complete, but it wasn't responded"); break; case State::responding: - std::cout << responseCode() << '\t' << methodName() << '\t' << path << std::endl; 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"); @@ -265,4 +265,4 @@ std::string Request::getAuthorizationToken() const { trim(result); return result; -} \ No newline at end of file +} diff --git a/request/request.h b/request/request.h index d4bacd2..dae582f 100644 --- a/request/request.h +++ b/request/request.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/response/CMakeLists.txt b/response/CMakeLists.txt index e4c5093..3df4e32 100644 --- a/response/CMakeLists.txt +++ b/response/CMakeLists.txt @@ -1,3 +1,6 @@ +#SPDX-FileCopyrightText: 2023 Yury Gubich +#SPDX-License-Identifier: GPL-3.0-or-later + set(HEADERS response.h ) diff --git a/response/response.cpp b/response/response.cpp index e9a483a..7a4e4cb 100644 --- a/response/response.cpp +++ b/response/response.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 "response.h" diff --git a/response/response.h b/response/response.h index bb18c5a..6fbce08 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 diff --git a/run.sh.in b/run.sh.in index ab3070f..919bcad 100644 --- a/run.sh.in +++ b/run.sh.in @@ -1,5 +1,8 @@ #!/bin/bash +#SPDX-FileCopyrightText: 2023 Yury Gubich +#SPDX-License-Identifier: GPL-3.0-or-later + start_service() { if systemctl is-active --quiet $1 then @@ -18,4 +21,4 @@ fi start_service "mariadb" start_service "httpd" -./@PROJECT_NAME@ \ No newline at end of file +./@PROJECT_NAME@ diff --git a/server/CMakeLists.txt b/server/CMakeLists.txt index 9a1b2d8..3ac7309 100644 --- a/server/CMakeLists.txt +++ b/server/CMakeLists.txt @@ -1,3 +1,6 @@ +#SPDX-FileCopyrightText: 2023 Yury Gubich +#SPDX-License-Identifier: GPL-3.0-or-later + set(HEADERS server.h router.h diff --git a/server/router.cpp b/server/router.cpp index 2a1a63c..d64b875 100644 --- a/server/router.cpp +++ b/server/router.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 "router.h" diff --git a/server/router.h b/server/router.h index 83c2fea..1368075 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 diff --git a/server/server.cpp b/server/server.cpp index 4bf05d4..dbb99f1 100644 --- a/server/server.cpp +++ b/server/server.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 "server.h" @@ -12,6 +12,8 @@ #include "handler/register.h" #include "handler/login.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"; @@ -29,11 +31,13 @@ constexpr uint32_t hashMemoryCost = 65536; constexpr uint8_t currentDbVesion = 1; Server::Server(): + std::enable_shared_from_this(), terminating(false), requestCount(0), serverName(std::nullopt), - router(), + router(std::make_shared()), pool(DB::Pool::create()), + taskManager(std::make_shared()), sessions() { std::cout << "Startig pica..." << std::endl; @@ -50,16 +54,18 @@ Server::Server(): DB::Resource db = pool->request(); db->migrate(currentDbVesion); - - router.addRoute(std::make_unique()); - router.addRoute(std::make_unique()); - router.addRoute(std::make_unique(this)); - router.addRoute(std::make_unique(this)); } Server::~Server() {} void Server::run(int socketDescriptor) { + router->addRoute(std::make_unique()); + router->addRoute(std::make_unique()); + router->addRoute(std::make_unique(shared_from_this())); + router->addRoute(std::make_unique(shared_from_this())); + + taskManager->start(); + while (!terminating) { std::unique_ptr request = std::make_unique(); bool result = request->wait(socketDescriptor); @@ -86,7 +92,9 @@ void Server::handleRequest(std::unique_ptr request) { } request->readPath(serverName.value()); - router.route(std::move(request)); + + auto route = std::make_unique(router, std::move(request)); + taskManager->schedule(std::move(route)); } std::string Server::generateRandomString(std::size_t length) { diff --git a/server/server.h b/server/server.h index 9d3319c..72f7fe6 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 @@ -25,8 +25,9 @@ #include "database/pool.h" #include "utils/helpers.h" #include "config.h" +#include "taskmanager/manager.h" -class Server { +class Server : public std::enable_shared_from_this { public: Server(); ~Server(); @@ -48,7 +49,8 @@ private: bool terminating; uint64_t requestCount; std::optional serverName; - Router router; + std::shared_ptr router; std::shared_ptr pool; + std::shared_ptr taskManager; Sessions sessions; }; diff --git a/server/session.cpp b/server/session.cpp index 43de593..fb7d91b 100644 --- a/server/session.cpp +++ b/server/session.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 "session.h" @@ -31,4 +31,4 @@ void Session::accept(std::unique_ptr request) { } polling = std::move(request); -} \ No newline at end of file +} diff --git a/server/session.h b/server/session.h index 910070f..0027562 100644 --- a/server/session.h +++ b/server/session.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/CMakeLists.txt b/stream/CMakeLists.txt index 3fef9b0..0d75e92 100644 --- a/stream/CMakeLists.txt +++ b/stream/CMakeLists.txt @@ -1,3 +1,6 @@ +#SPDX-FileCopyrightText: 2023 Yury Gubich +#SPDX-License-Identifier: GPL-3.0-or-later + set(HEADERS stream.h ostream.h diff --git a/stream/ostream.cpp b/stream/ostream.cpp index 8674355..31adf8a 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 e08a936..e25fd4a 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 022dc07..f65203a 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 07a263d..697942a 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 new file mode 100644 index 0000000..2149fd4 --- /dev/null +++ b/taskmanager/CMakeLists.txt @@ -0,0 +1,16 @@ +#SPDX-FileCopyrightText: 2023 Yury Gubich +#SPDX-License-Identifier: GPL-3.0-or-later + +set(HEADERS + manager.h + job.h + route.h +) + +set(SOURCES + manager.cpp + job.cpp + route.cpp +) + +target_sources(${PROJECT_NAME} PRIVATE ${SOURCES}) diff --git a/taskmanager/job.cpp b/taskmanager/job.cpp new file mode 100644 index 0000000..5c74920 --- /dev/null +++ b/taskmanager/job.cpp @@ -0,0 +1,9 @@ +//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 new file mode 100644 index 0000000..4895ced --- /dev/null +++ b/taskmanager/job.h @@ -0,0 +1,19 @@ +//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 new file mode 100644 index 0000000..1f86661 --- /dev/null +++ b/taskmanager/manager.cpp @@ -0,0 +1,69 @@ +//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 new file mode 100644 index 0000000..1f1aa3b --- /dev/null +++ b/taskmanager/manager.h @@ -0,0 +1,40 @@ +//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/route.cpp b/taskmanager/route.cpp new file mode 100644 index 0000000..f2d5af0 --- /dev/null +++ b/taskmanager/route.cpp @@ -0,0 +1,13 @@ +//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 new file mode 100644 index 0000000..49d7c69 --- /dev/null +++ b/taskmanager/route.h @@ -0,0 +1,23 @@ +//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/utils/CMakeLists.txt b/utils/CMakeLists.txt index 4834150..ac09c13 100644 --- a/utils/CMakeLists.txt +++ b/utils/CMakeLists.txt @@ -1,3 +1,6 @@ +#SPDX-FileCopyrightText: 2023 Yury Gubich +#SPDX-License-Identifier: GPL-3.0-or-later + set(HEADER helpers.h formdecode.h diff --git a/utils/formdecode.cpp b/utils/formdecode.cpp index 8dbd14d..ed33203 100644 --- a/utils/formdecode.cpp +++ b/utils/formdecode.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 "formdecode.h" diff --git a/utils/formdecode.h b/utils/formdecode.h index 5f4ec61..ffbad3d 100644 --- a/utils/formdecode.h +++ b/utils/formdecode.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/utils/helpers.cpp b/utils/helpers.cpp index c1b93e4..5e2aed2 100644 --- a/utils/helpers.cpp +++ b/utils/helpers.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 "helpers.h" diff --git a/utils/helpers.h b/utils/helpers.h index 34fcd03..2c00151 100644 --- a/utils/helpers.h +++ b/utils/helpers.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 From 26114aad5f9d95e0b945ca0bb862729ae6ee2766 Mon Sep 17 00:00:00 2001 From: blue Date: Sun, 31 Dec 2023 14:10:04 -0300 Subject: [PATCH 10/27] beginning of the scheduler --- server/server.cpp | 23 ++++++++++++ server/server.h | 2 + taskmanager/CMakeLists.txt | 2 + taskmanager/scheduler.cpp | 75 ++++++++++++++++++++++++++++++++++++++ taskmanager/scheduler.h | 48 ++++++++++++++++++++++++ utils/helpers.h | 7 ++++ 6 files changed, 157 insertions(+) create mode 100644 taskmanager/scheduler.cpp create mode 100644 taskmanager/scheduler.h diff --git a/server/server.cpp b/server/server.cpp index dbb99f1..c4a2f60 100644 --- a/server/server.cpp +++ b/server/server.cpp @@ -38,6 +38,7 @@ Server::Server(): router(std::make_shared()), pool(DB::Pool::create()), taskManager(std::make_shared()), + scheduler(std::make_shared(taskManager)), sessions() { std::cout << "Startig pica..." << std::endl; @@ -65,6 +66,28 @@ void Server::run(int socketDescriptor) { router->addRoute(std::make_unique(shared_from_this())); taskManager->start(); + scheduler->start(); + + scheduler->schedule([]() { + std::cout << "5000" << std::endl; + }, TM::Scheduler::Delay(5000)); + scheduler->schedule([]() { + std::cout << "2000" << std::endl; + }, TM::Scheduler::Delay(2000)); + scheduler->schedule([]() { + std::cout << "6000" << std::endl; + }, TM::Scheduler::Delay(6000)); + + std::this_thread::sleep_for(std::chrono::milliseconds(500)); + + scheduler->schedule([]() { + std::cout << "2000 + 500" << std::endl; + }, TM::Scheduler::Delay(2000)); + std::this_thread::sleep_for(std::chrono::milliseconds(100)); + + scheduler->schedule([]() { + std::cout << "1000 + 600" << std::endl; + }, TM::Scheduler::Delay(1000)); while (!terminating) { std::unique_ptr request = std::make_unique(); diff --git a/server/server.h b/server/server.h index 72f7fe6..4402cfb 100644 --- a/server/server.h +++ b/server/server.h @@ -26,6 +26,7 @@ #include "utils/helpers.h" #include "config.h" #include "taskmanager/manager.h" +#include "taskmanager/scheduler.h" class Server : public std::enable_shared_from_this { public: @@ -52,5 +53,6 @@ private: std::shared_ptr router; std::shared_ptr pool; std::shared_ptr taskManager; + std::shared_ptr scheduler; Sessions sessions; }; diff --git a/taskmanager/CMakeLists.txt b/taskmanager/CMakeLists.txt index 2149fd4..7a8c2c3 100644 --- a/taskmanager/CMakeLists.txt +++ b/taskmanager/CMakeLists.txt @@ -5,12 +5,14 @@ set(HEADERS manager.h job.h route.h + scheduler.h ) set(SOURCES manager.cpp job.cpp route.cpp + scheduler.cpp ) target_sources(${PROJECT_NAME} PRIVATE ${SOURCES}) diff --git a/taskmanager/scheduler.cpp b/taskmanager/scheduler.cpp new file mode 100644 index 0000000..45a8dc0 --- /dev/null +++ b/taskmanager/scheduler.cpp @@ -0,0 +1,75 @@ +//SPDX-FileCopyrightText: 2023 Yury Gubich +//SPDX-License-Identifier: GPL-3.0-or-later + +#include "scheduler.h" + +TM::Scheduler::Scheduler (std::weak_ptr manager): + manager(manager), + queue(), + mutex(), + cond(), + thread(nullptr), + running(false) +{} + +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().first; + if (nextScheduledTime > currentTime) { + cond.wait_until(lock, nextScheduledTime); + break; + } + + Task task = queue.top().second; + queue.pop(); + lock.unlock(); + task(); + lock.lock(); + } + } +} + +void TM::Scheduler::schedule (Task task, Delay delay) { + std::unique_lock lock(mutex); + Time time = std::chrono::steady_clock::now() + delay; + queue.emplace(time, task); + + lock.unlock(); + cond.notify_one(); +} diff --git a/taskmanager/scheduler.h b/taskmanager/scheduler.h new file mode 100644 index 0000000..40a0093 --- /dev/null +++ b/taskmanager/scheduler.h @@ -0,0 +1,48 @@ +//SPDX-FileCopyrightText: 2023 Yury Gubich +//SPDX-License-Identifier: GPL-3.0-or-later + +#pragma once + +#include +#include +#include +#include +#include +#include +#include + +#include "manager.h" +#include "utils/helpers.h" + +namespace TM { +class Scheduler { +public: + using Delay = std::chrono::milliseconds; + using Task = std::function; + + Scheduler (std::weak_ptr manager); + ~Scheduler (); + + void start(); + void stop(); + void schedule(Task task, Delay delay); + +private: + void loop(); + +private: + using Time = std::chrono::time_point; + using Record = std::pair; + + std::weak_ptr manager; + std::priority_queue< + Record, + std::vector, + FirstGreater + > queue; + std::mutex mutex; + std::condition_variable cond; + std::unique_ptr thread; + bool running; +}; +} diff --git a/utils/helpers.h b/utils/helpers.h index 2c00151..7991346 100644 --- a/utils/helpers.h +++ b/utils/helpers.h @@ -15,3 +15,10 @@ 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 () (T left, T right) { + return std::get<0>(left) > std::get<0>(right); + } +}; From 544db92b6eedf29533a609f602b81930562f44f6 Mon Sep 17 00:00:00 2001 From: blue Date: Tue, 2 Jan 2024 22:11:56 -0300 Subject: [PATCH 11/27] some thoughts about scheduling --- handler/poll.cpp | 2 +- handler/poll.h | 2 +- server/session.cpp | 24 +++++++++++++------ server/session.h | 12 +++++++++- taskmanager/CMakeLists.txt | 2 ++ taskmanager/function.cpp | 12 ++++++++++ taskmanager/function.h | 20 ++++++++++++++++ taskmanager/scheduler.cpp | 14 +++++++----- taskmanager/scheduler.h | 10 ++++---- utils/helpers.cpp | 1 - utils/helpers.h | 47 +++++++++++++++++++++++++++++++++++++- 11 files changed, 123 insertions(+), 23 deletions(-) create mode 100644 taskmanager/function.cpp create mode 100644 taskmanager/function.h diff --git a/handler/poll.cpp b/handler/poll.cpp index 5fb2c3b..c0e4d82 100644 --- a/handler/poll.cpp +++ b/handler/poll.cpp @@ -34,7 +34,7 @@ void Handler::Poll::handle (Request& request) { } } -void Handler::Poll::error(Request& request, Result result, Response::Status status) const { +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; diff --git a/handler/poll.h b/handler/poll.h index 656a159..580256e 100644 --- a/handler/poll.h +++ b/handler/poll.h @@ -23,7 +23,7 @@ public: unknownError }; - void error(Request& request, Result result, Response::Status status) const; + static void error(Request& request, Result result, Response::Status status); private: Server* server; diff --git a/server/session.cpp b/server/session.cpp index fb7d91b..efafd89 100644 --- a/server/session.cpp +++ b/server/session.cpp @@ -5,7 +5,13 @@ #include "handler/poll.h" -Session::Session(unsigned int id, const std::string& access, const std::string& renew): +Session::Session( + std::weak_ptr scheduler, + unsigned int id, + const std::string& access, + const std::string& renew +): + scheduler(scheduler), id(id), access(access), renew(renew), @@ -22,13 +28,17 @@ std::string Session::getRenewToken() const { void Session::accept(std::unique_ptr request) { if (polling) { - Response& res = request->createResponse(Response::Status::ok); - nlohmann::json body = nlohmann::json::object(); - body["result"] = Handler::Poll::Result::replace; - - res.setBody(body); - res.send(); + Handler::Poll::error(*request.get(), Handler::Poll::Result::replace, Response::Status::ok); + //TODO unschedule } + std::shared_ptr sch = scheduler.lock(); + 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; + } + sch->schedule(std::bind(&Session::onTimeout, this), TM::Scheduler::Delay(5000)); + polling = std::move(request); } diff --git a/server/session.h b/server/session.h index 0027562..14c43e9 100644 --- a/server/session.h +++ b/server/session.h @@ -6,10 +6,16 @@ #include #include "request/accepting.h" +#include "taskmanager/scheduler.h" class Session : public Accepting { public: - Session(unsigned int id, const std::string& access, const std::string& renew); + Session( + std::weak_ptr scheduler, + unsigned int id, + const std::string& access, + const std::string& renew + ); Session(const Session&) = delete; Session(Session&& other); Session& operator = (const Session&) = delete; @@ -20,6 +26,10 @@ public: void accept(std::unique_ptr request) override; private: + void onTimeout(); + +private: + std::weak_ptr scheduler; unsigned int id; std::string access; std::string renew; diff --git a/taskmanager/CMakeLists.txt b/taskmanager/CMakeLists.txt index 7a8c2c3..f45b98f 100644 --- a/taskmanager/CMakeLists.txt +++ b/taskmanager/CMakeLists.txt @@ -6,6 +6,7 @@ set(HEADERS job.h route.h scheduler.h + function.h ) set(SOURCES @@ -13,6 +14,7 @@ set(SOURCES job.cpp route.cpp scheduler.cpp + function.cpp ) target_sources(${PROJECT_NAME} PRIVATE ${SOURCES}) diff --git a/taskmanager/function.cpp b/taskmanager/function.cpp new file mode 100644 index 0000000..bf6d01c --- /dev/null +++ b/taskmanager/function.cpp @@ -0,0 +1,12 @@ +//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 new file mode 100644 index 0000000..c6ca024 --- /dev/null +++ b/taskmanager/function.h @@ -0,0 +1,20 @@ +//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/scheduler.cpp b/taskmanager/scheduler.cpp index 45a8dc0..db781e3 100644 --- a/taskmanager/scheduler.cpp +++ b/taskmanager/scheduler.cpp @@ -4,8 +4,8 @@ #include "scheduler.h" TM::Scheduler::Scheduler (std::weak_ptr manager): - manager(manager), queue(), + manager(manager), mutex(), cond(), thread(nullptr), @@ -56,19 +56,21 @@ void TM::Scheduler::loop () { break; } - Task task = queue.top().second; - queue.pop(); + Record task = queue.pop(); lock.unlock(); - task(); + std::shared_ptr mngr = manager.lock(); + if (mngr) + mngr->schedule(std::move(task.second)); + lock.lock(); } } } -void TM::Scheduler::schedule (Task task, Delay delay) { +void TM::Scheduler::schedule (const std::function& task, Delay delay) { std::unique_lock lock(mutex); Time time = std::chrono::steady_clock::now() + delay; - queue.emplace(time, task); + queue.emplace(time, std::make_unique(task)); lock.unlock(); cond.notify_one(); diff --git a/taskmanager/scheduler.h b/taskmanager/scheduler.h index 40a0093..a952858 100644 --- a/taskmanager/scheduler.h +++ b/taskmanager/scheduler.h @@ -5,41 +5,41 @@ #include #include -#include #include #include #include #include #include "manager.h" +#include "function.h" #include "utils/helpers.h" namespace TM { class Scheduler { public: using Delay = std::chrono::milliseconds; - using Task = std::function; Scheduler (std::weak_ptr manager); ~Scheduler (); void start(); void stop(); - void schedule(Task task, Delay delay); + void schedule(const std::function& task, Delay delay); private: void loop(); private: + using Task = std::unique_ptr; using Time = std::chrono::time_point; using Record = std::pair; - std::weak_ptr manager; - std::priority_queue< + PriorityQueue< Record, std::vector, FirstGreater > queue; + std::weak_ptr manager; std::mutex mutex; std::condition_variable cond; std::unique_ptr thread; diff --git a/utils/helpers.cpp b/utils/helpers.cpp index 5e2aed2..1af821e 100644 --- a/utils/helpers.cpp +++ b/utils/helpers.cpp @@ -4,7 +4,6 @@ #include "helpers.h" #include "iostream" -#include #include #include "config.h" diff --git a/utils/helpers.h b/utils/helpers.h index 7991346..91a597d 100644 --- a/utils/helpers.h +++ b/utils/helpers.h @@ -5,6 +5,7 @@ #include #include +#include #define UNUSED(variable) (void)variable @@ -18,7 +19,51 @@ std::string extract(std::string& string, std::string::size_type begin, std::stri template struct FirstGreater { - bool operator () (T left, T right) { + 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; +}; From 5d765958e543cd96a7744bec1eae87287d2f5837 Mon Sep 17 00:00:00 2001 From: blue Date: Wed, 3 Jan 2024 19:20:01 -0300 Subject: [PATCH 12/27] scheduler canceling, sessiion query, didn't test yet! --- database/exceptions.cpp | 4 ++++ database/exceptions.h | 5 +++++ database/interface.h | 8 ++++++++ database/mysql/mysql.cpp | 21 ++++++++++++++++++++- database/mysql/mysql.h | 1 + handler/CMakeLists.txt | 2 ++ handler/poll.cpp | 3 +++ server/server.cpp | 18 +++++++++++++++++- server/session.cpp | 26 +++++++++++++++++++++++--- server/session.h | 2 ++ taskmanager/CMakeLists.txt | 2 ++ taskmanager/record.cpp | 18 ++++++++++++++++++ taskmanager/record.h | 26 ++++++++++++++++++++++++++ taskmanager/scheduler.cpp | 27 +++++++++++++++++++++------ taskmanager/scheduler.h | 24 ++++++++++++++---------- 15 files changed, 166 insertions(+), 21 deletions(-) create mode 100644 taskmanager/record.cpp create mode 100644 taskmanager/record.h diff --git a/database/exceptions.cpp b/database/exceptions.cpp index 5ba8058..b48ef51 100644 --- a/database/exceptions.cpp +++ b/database/exceptions.cpp @@ -18,3 +18,7 @@ DB::EmptyResult::EmptyResult(const std::string& 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 index 19da281..81b4f7b 100644 --- a/database/exceptions.h +++ b/database/exceptions.h @@ -26,4 +26,9 @@ 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.h b/database/interface.h index 7b6e204..4d0e651 100644 --- a/database/interface.h +++ b/database/interface.h @@ -9,6 +9,13 @@ #include namespace DB { +struct Session { + unsigned int id; + unsigned int owner; + std::string accessToken; + std::string renewToken; +}; + class Interface { public: enum class Type { @@ -40,6 +47,7 @@ public: virtual unsigned int registerAccount(const std::string& login, const std::string& hash) = 0; virtual std::string getAccountHash(const std::string& login) = 0; virtual unsigned int createSession(const std::string& login, const std::string& access, const std::string& renew) = 0; + virtual Session findSession(const std::string& accessToken) = 0; protected: Interface(Type type); diff --git a/database/mysql/mysql.cpp b/database/mysql/mysql.cpp index 151fa9b..33f2f75 100644 --- a/database/mysql/mysql.cpp +++ b/database/mysql/mysql.cpp @@ -20,6 +20,7 @@ constexpr const char* assignRoleQuery = "INSERT INTO roleBindings (`account`, `r 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 = ?"; +constexpr const char* selectSession = "SELECT id, owner, renew FROM sessions where access = ?"; static const std::filesystem::path buildSQLPath = "database"; @@ -298,6 +299,24 @@ unsigned int DB::MySQL::lastInsertedId() { 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); + + std::vector> result = session.fetchResult(); + if (result.empty()) + throw NoSession("Couldn't find session with token " + a); + + DB::Session res; + res.id = std::any_cast(result[0][0]); + res.owner = std::any_cast(result[0][1]); + res.renewToken = std::any_cast(result[0][2]); + res.accessToken = a; + + return res; +} diff --git a/database/mysql/mysql.h b/database/mysql/mysql.h index 7bd9775..517ed1c 100644 --- a/database/mysql/mysql.h +++ b/database/mysql/mysql.h @@ -34,6 +34,7 @@ public: unsigned int registerAccount(const std::string& login, const std::string& hash) override; std::string getAccountHash(const std::string& login) override; unsigned int createSession(const std::string& login, const std::string& access, const std::string& renew) override; + Session findSession(const std::string& accessToken) override; private: void executeFile(const std::filesystem::path& relativePath); diff --git a/handler/CMakeLists.txt b/handler/CMakeLists.txt index 536d1d0..e6c3995 100644 --- a/handler/CMakeLists.txt +++ b/handler/CMakeLists.txt @@ -7,6 +7,7 @@ set(HEADERS env.h register.h login.h + poll.h ) set(SOURCES @@ -15,6 +16,7 @@ set(SOURCES env.cpp register.cpp login.cpp + poll.cpp ) target_sources(${PROJECT_NAME} PRIVATE ${SOURCES}) diff --git a/handler/poll.cpp b/handler/poll.cpp index c0e4d82..0ffd138 100644 --- a/handler/poll.cpp +++ b/handler/poll.cpp @@ -6,6 +6,7 @@ #include "response/response.h" #include "server/server.h" #include "request/redirect.h" +#include "database/exceptions.h" Handler::Poll::Poll (Server* server): Handler("login", Request::Method::get), @@ -25,6 +26,8 @@ void Handler::Poll::handle (Request& request) { 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 poll:\n\t" << e.what() << std::endl; return error(request, Result::unknownError, Response::Status::internalError); diff --git a/server/server.cpp b/server/server.cpp index c4a2f60..bc258ed 100644 --- a/server/server.cpp +++ b/server/server.cpp @@ -192,6 +192,22 @@ Session& Server::openSession(const std::string& login) { if (sessionId == 0) throw std::runtime_error("Couldn't create session, ran out of attempts"); - std::unique_ptr& session = sessions[accessToken] = std::make_unique(sessionId, accessToken, renewToken); + std::unique_ptr& session = sessions[accessToken] = std::make_unique(scheduler, sessionId, accessToken, renewToken); + return *session.get(); +} + +Session& Server::getSession (const std::string& accessToken) { + Sessions::const_iterator itr = sessions.find(accessToken); + if (itr != sessions.end()) + return *(itr->second); + + DB::Resource db = pool->request(); + DB::Session s = db->findSession(accessToken); + std::unique_ptr& session = sessions[accessToken] = std::make_unique( + scheduler, + s.id, + s.accessToken, + s.renewToken + ); return *session.get(); } diff --git a/server/session.cpp b/server/session.cpp index efafd89..6a1aeda 100644 --- a/server/session.cpp +++ b/server/session.cpp @@ -15,9 +15,17 @@ Session::Session( id(id), access(access), renew(renew), - polling(nullptr) + polling(nullptr), + timeoutId(TM::Scheduler::none) {} +Session::~Session () { + if (timeoutId != TM::Scheduler::none) { + if (std::shared_ptr sch = scheduler.lock()) + sch->cancel(timeoutId); + } +} + std::string Session::getAccessToken() const { return access; } @@ -27,18 +35,30 @@ std::string Session::getRenewToken() const { } void Session::accept(std::unique_ptr request) { + std::shared_ptr sch = scheduler.lock(); if (polling) { Handler::Poll::error(*request.get(), Handler::Poll::Result::replace, Response::Status::ok); + if (timeoutId != TM::Scheduler::none) { + if (sch) + sch->cancel(timeoutId); + + timeoutId = TM::Scheduler::none; + } //TODO unschedule } - std::shared_ptr sch = scheduler.lock(); 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; } - sch->schedule(std::bind(&Session::onTimeout, this), TM::Scheduler::Delay(5000)); + timeoutId = sch->schedule(std::bind(&Session::onTimeout, this), TM::Scheduler::Delay(5000)); polling = std::move(request); } + +void Session::onTimeout () { + timeoutId = TM::Scheduler::none; + Handler::Poll::error(*polling.get(), Handler::Poll::Result::timeout, Response::Status::ok); + polling.reset(); +} diff --git a/server/session.h b/server/session.h index 14c43e9..7581352 100644 --- a/server/session.h +++ b/server/session.h @@ -18,6 +18,7 @@ public: ); Session(const Session&) = delete; Session(Session&& other); + ~Session(); Session& operator = (const Session&) = delete; Session& operator = (Session&& other); @@ -34,4 +35,5 @@ private: std::string access; std::string renew; std::unique_ptr polling; + TM::Record::ID timeoutId; }; diff --git a/taskmanager/CMakeLists.txt b/taskmanager/CMakeLists.txt index f45b98f..70a8210 100644 --- a/taskmanager/CMakeLists.txt +++ b/taskmanager/CMakeLists.txt @@ -7,6 +7,7 @@ set(HEADERS route.h scheduler.h function.h + record.h ) set(SOURCES @@ -15,6 +16,7 @@ set(SOURCES route.cpp scheduler.cpp function.cpp + record.cpp ) target_sources(${PROJECT_NAME} PRIVATE ${SOURCES}) diff --git a/taskmanager/record.cpp b/taskmanager/record.cpp new file mode 100644 index 0000000..77c8850 --- /dev/null +++ b/taskmanager/record.cpp @@ -0,0 +1,18 @@ +//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 new file mode 100644 index 0000000..fe2b7c5 --- /dev/null +++ b/taskmanager/record.h @@ -0,0 +1,26 @@ +//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/scheduler.cpp b/taskmanager/scheduler.cpp index db781e3..fcae841 100644 --- a/taskmanager/scheduler.cpp +++ b/taskmanager/scheduler.cpp @@ -3,13 +3,17 @@ #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) + running(false), + idCounter(TM::Scheduler::none) {} TM::Scheduler::~Scheduler () { @@ -50,28 +54,39 @@ void TM::Scheduler::loop () { Time currentTime = std::chrono::steady_clock::now(); while (!queue.empty()) { - Time nextScheduledTime = queue.top().first; + Time nextScheduledTime = queue.top().time; if (nextScheduledTime > currentTime) { cond.wait_until(lock, nextScheduledTime); break; } - Record task = queue.pop(); + 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::move(task.second)); + mngr->schedule(std::make_unique(record.task)); lock.lock(); } } } -void TM::Scheduler::schedule (const std::function& task, Delay delay) { +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(time, std::make_unique(task)); + 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 index a952858..6b3e609 100644 --- a/taskmanager/scheduler.h +++ b/taskmanager/scheduler.h @@ -6,43 +6,47 @@ #include #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(); - void schedule(const std::function& task, Delay delay); + 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(); + void loop (); private: - using Task = std::unique_ptr; - using Time = std::chrono::time_point; - using Record = std::pair; - PriorityQueue< Record, std::vector, - FirstGreater + 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; }; } From a1ab1339e35137f495635000343905463f38d08a Mon Sep 17 00:00:00 2001 From: blue Date: Tue, 9 Jan 2024 14:02:56 -0300 Subject: [PATCH 13/27] testing and debugging --- database/mysql/mysql.cpp | 1 + database/mysql/statement.cpp | 8 ++++---- handler/poll.cpp | 12 ++++++++---- handler/poll.h | 6 ++++-- request/request.cpp | 4 ++-- request/request.h | 4 ++-- server/router.cpp | 3 --- server/server.cpp | 30 +++++++----------------------- server/session.cpp | 13 +++++++------ server/session.h | 4 +++- 10 files changed, 38 insertions(+), 47 deletions(-) diff --git a/database/mysql/mysql.cpp b/database/mysql/mysql.cpp index 33f2f75..8449dc5 100644 --- a/database/mysql/mysql.cpp +++ b/database/mysql/mysql.cpp @@ -305,6 +305,7 @@ DB::Session DB::MySQL::findSession(const std::string& accessToken) { Statement session(con, selectSession); session.bind(a.data(), MYSQL_TYPE_STRING); + session.execute(); std::vector> result = session.fetchResult(); if (result.empty()) diff --git a/database/mysql/statement.cpp b/database/mysql/statement.cpp index edd90bd..f8edd76 100644 --- a/database/mysql/statement.cpp +++ b/database/mysql/statement.cpp @@ -94,19 +94,19 @@ std::vector> DB::MySQL::Statement::fetchResult() { } break; case MYSQL_TYPE_TINY: line[i] = uint8_t{0}; - bind[i].buffer = &std::any_cast(line[i]); + 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]); + 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]); + 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]); + bind[i].buffer = &std::any_cast(line[i]); break; default: throw std::runtime_error("Unsupported data fetching statement result " + std::to_string(field->type)); diff --git a/handler/poll.cpp b/handler/poll.cpp index 0ffd138..1aa36d3 100644 --- a/handler/poll.cpp +++ b/handler/poll.cpp @@ -8,8 +8,8 @@ #include "request/redirect.h" #include "database/exceptions.h" -Handler::Poll::Poll (Server* server): - Handler("login", Request::Method::get), +Handler::Poll::Poll (std::weak_ptr server): + Handler("poll", Request::Method::get), server(server) {} @@ -20,9 +20,13 @@ void Handler::Poll::handle (Request& request) { 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 = server->getSession(access); + Session& session = srv->getSession(access); throw Redirect(&session); } catch (const Redirect& r) { throw r; diff --git a/handler/poll.h b/handler/poll.h index 580256e..f9c43c8 100644 --- a/handler/poll.h +++ b/handler/poll.h @@ -3,6 +3,8 @@ #pragma once +#include + #include "handler.h" #include "request/request.h" #include "response/response.h" @@ -12,7 +14,7 @@ namespace Handler { class Poll : public Handler { public: - Poll(Server* server); + Poll(std::weak_ptr server); void handle(Request& request) override; enum class Result { @@ -25,7 +27,7 @@ public: static void error(Request& request, Result result, Response::Status status); private: - Server* server; + std::weak_ptr server; }; diff --git a/request/request.cpp b/request/request.cpp index 3ee662f..eedbc72 100644 --- a/request/request.cpp +++ b/request/request.cpp @@ -188,7 +188,7 @@ std::string Request::getServerName() const { return FCGX_GetParam(SERVER_NAME, raw.envp);; } -void Request::printEnvironment(std::ostream& out) { +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"); @@ -198,7 +198,7 @@ void Request::printEnvironment(std::ostream& out) { } } -void Request::printEnvironment(nlohmann::json& out) { +void Request::printEnvironment(nlohmann::json& out) const { if (!out.is_object()) return; diff --git a/request/request.h b/request/request.h index dae582f..1cef22c 100644 --- a/request/request.h +++ b/request/request.h @@ -61,8 +61,8 @@ public: std::string getPath() const; std::string getServerName() const; std::string getAuthorizationToken() const; - void printEnvironment(std::ostream& out); - void printEnvironment(nlohmann::json& out); + void printEnvironment(std::ostream& out) const; + void printEnvironment(nlohmann::json& out) const; private: OStream getOutputStream(); diff --git a/server/router.cpp b/server/router.cpp index d64b875..2079bc6 100644 --- a/server/router.cpp +++ b/server/router.cpp @@ -62,19 +62,16 @@ void Router::handleNotFound(std::unique_ptr request) { std::string path = request->getPath(); notFound.setBody(std::string("Path \"") + path + "\" was not found"); notFound.send(); - std::cerr << notFound.statusCode() << '\t' << request->methodName() << '\t' << path << std::endl; } void Router::handleInternalError(const std::exception& exception, std::unique_ptr request) { Response& error = request->createResponse(Response::Status::internalError); error.setBody(std::string(exception.what())); error.send(); - std::cerr << error.statusCode() << '\t' << request->methodName() << '\t' << request->getPath() << std::endl; } void Router::handleMethodNotAllowed(std::unique_ptr request) { Response& error = request->createResponse(Response::Status::methodNotAllowed); error.setBody(std::string("Method not allowed")); error.send(); - std::cerr << error.statusCode() << '\t' << request->methodName() << '\t' << request->getPath() << std::endl; } diff --git a/server/server.cpp b/server/server.cpp index bc258ed..01c4cbb 100644 --- a/server/server.cpp +++ b/server/server.cpp @@ -11,6 +11,7 @@ #include "handler/env.h" #include "handler/register.h" #include "handler/login.h" +#include "handler/poll.h" #include "taskmanager/route.h" @@ -20,6 +21,7 @@ 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; @@ -64,31 +66,11 @@ void Server::run(int socketDescriptor) { router->addRoute(std::make_unique()); router->addRoute(std::make_unique(shared_from_this())); router->addRoute(std::make_unique(shared_from_this())); + router->addRoute(std::make_unique(shared_from_this())); taskManager->start(); scheduler->start(); - scheduler->schedule([]() { - std::cout << "5000" << std::endl; - }, TM::Scheduler::Delay(5000)); - scheduler->schedule([]() { - std::cout << "2000" << std::endl; - }, TM::Scheduler::Delay(2000)); - scheduler->schedule([]() { - std::cout << "6000" << std::endl; - }, TM::Scheduler::Delay(6000)); - - std::this_thread::sleep_for(std::chrono::milliseconds(500)); - - scheduler->schedule([]() { - std::cout << "2000 + 500" << std::endl; - }, TM::Scheduler::Delay(2000)); - std::this_thread::sleep_for(std::chrono::milliseconds(100)); - - scheduler->schedule([]() { - std::cout << "1000 + 600" << std::endl; - }, TM::Scheduler::Delay(1000)); - while (!terminating) { std::unique_ptr request = std::make_unique(); bool result = request->wait(socketDescriptor); @@ -192,7 +174,8 @@ Session& Server::openSession(const std::string& login) { if (sessionId == 0) throw std::runtime_error("Couldn't create session, ran out of attempts"); - std::unique_ptr& session = sessions[accessToken] = std::make_unique(scheduler, sessionId, accessToken, renewToken); + std::unique_ptr& session = sessions[accessToken] + = std::make_unique(scheduler, sessionId, accessToken, renewToken, pollTimout); return *session.get(); } @@ -207,7 +190,8 @@ Session& Server::getSession (const std::string& accessToken) { scheduler, s.id, s.accessToken, - s.renewToken + s.renewToken, + pollTimout ); return *session.get(); } diff --git a/server/session.cpp b/server/session.cpp index 6a1aeda..fe77381 100644 --- a/server/session.cpp +++ b/server/session.cpp @@ -9,14 +9,16 @@ Session::Session( std::weak_ptr scheduler, unsigned int id, const std::string& access, - const std::string& renew + const std::string& renew, + unsigned int timeout ): scheduler(scheduler), id(id), access(access), renew(renew), polling(nullptr), - timeoutId(TM::Scheduler::none) + timeoutId(TM::Scheduler::none), + timeout(timeout) {} Session::~Session () { @@ -37,14 +39,14 @@ std::string Session::getRenewToken() const { void Session::accept(std::unique_ptr request) { std::shared_ptr sch = scheduler.lock(); if (polling) { - Handler::Poll::error(*request.get(), Handler::Poll::Result::replace, Response::Status::ok); if (timeoutId != TM::Scheduler::none) { if (sch) sch->cancel(timeoutId); timeoutId = TM::Scheduler::none; } - //TODO unschedule + Handler::Poll::error(*polling.get(), Handler::Poll::Result::replace, Response::Status::ok); + polling.reset(); } if (!sch) { @@ -52,8 +54,7 @@ void Session::accept(std::unique_ptr request) { Handler::Poll::error(*request.get(), Handler::Poll::Result::unknownError, Response::Status::internalError); return; } - timeoutId = sch->schedule(std::bind(&Session::onTimeout, this), TM::Scheduler::Delay(5000)); - + timeoutId = sch->schedule(std::bind(&Session::onTimeout, this), timeout); polling = std::move(request); } diff --git a/server/session.h b/server/session.h index 7581352..55f84cb 100644 --- a/server/session.h +++ b/server/session.h @@ -14,7 +14,8 @@ public: std::weak_ptr scheduler, unsigned int id, const std::string& access, - const std::string& renew + const std::string& renew, + unsigned int timeout ); Session(const Session&) = delete; Session(Session&& other); @@ -36,4 +37,5 @@ private: std::string renew; std::unique_ptr polling; TM::Record::ID timeoutId; + TM::Scheduler::Delay timeout; }; From d33ec5def8e00dbc88f96c405a2f3609888c9258 Mon Sep 17 00:00:00 2001 From: blue Date: Thu, 11 Jan 2024 18:33:46 -0300 Subject: [PATCH 14/27] some ideas about database structure, began assets fetching request --- database/interface.h | 18 +++++++- database/migrations/m0.sql | 85 ++++++++++++++++++++++++++++++++++++-- database/mysql/mysql.cpp | 47 ++++++++++++++++++--- database/mysql/mysql.h | 33 ++++++++------- handler/CMakeLists.txt | 2 + handler/listassets.cpp | 45 ++++++++++++++++++++ handler/listassets.h | 21 ++++++++++ handler/poll.h | 9 ++-- run.sh.in | 11 +++-- server/server.cpp | 10 +++-- server/session.cpp | 4 +- server/session.h | 9 ++-- utils/helpers.cpp | 2 +- 13 files changed, 251 insertions(+), 45 deletions(-) create mode 100644 handler/listassets.cpp create mode 100644 handler/listassets.h diff --git a/database/interface.h b/database/interface.h index 4d0e651..b2cc592 100644 --- a/database/interface.h +++ b/database/interface.h @@ -6,7 +6,8 @@ #include #include #include -#include +#include +#include namespace DB { struct Session { @@ -16,6 +17,18 @@ struct Session { std::string renewToken; }; +struct Asset { + unsigned int id; + unsigned int owner; + unsigned int currency; + std::string title; + std::string icon; + // `color` INTEGER UNSIGNED DEFAULT 0, + // `balance` DECIMAL (20, 5) DEFAULT 0, + // `type` INTEGER UNSIGNED NOT NULL, + bool archived; +}; + class Interface { public: enum class Type { @@ -46,8 +59,9 @@ public: virtual unsigned int registerAccount(const std::string& login, const std::string& hash) = 0; virtual std::string getAccountHash(const std::string& login) = 0; - virtual unsigned int createSession(const std::string& login, const std::string& access, const std::string& renew) = 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(unsigned int owner) = 0; protected: Interface(Type type); diff --git a/database/migrations/m0.sql b/database/migrations/m0.sql index c691187..72f14e0 100644 --- a/database/migrations/m0.sql +++ b/database/migrations/m0.sql @@ -33,7 +33,7 @@ CREATE TABLE IF NOT EXISTS roleBindings ( --creating sessings table CREATE TABLE IF NOT EXISTS sessions ( - `id` INTEGER AUTO_INCREMENT PRIMARY KEY, + `id` INTEGER UNSIGNED AUTO_INCREMENT PRIMARY KEY, `owner` INTEGER UNSIGNED NOT NULL, `started` TIMESTAMP DEFAULT UTC_TIMESTAMP(), `latest` TIMESTAMP DEFAULT UTC_TIMESTAMP(), @@ -45,13 +45,90 @@ CREATE TABLE IF NOT EXISTS sessions ( 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, + `added` TIMESTAMP DEFAULT UTC_TIMESTAMP(), + `type` INTEGER UNSIGNED NOT NULL, + `value` DECIMAL (20, 5) NOT NULL, + `source` TEXT, + `description` TEXT, + + INDEX manual_idx (manual) +); + +--creating assests 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, + + 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 DEFAULT UTC_TIMESTAMP(), + `performed` TIMESTAMP DEFAULT UTC_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 defailt roles -INSERT IGNORE INTO roles (`name`) +INSERT IGNORE INTO +roles (`name`) VALUES ('root'), ('default'); --inserting initial version -INSERT INTO system (`key`, `value`) VALUES ('version', '0'); +INSERT IGNORE INTO +system (`key`, `value`) +VALUES ('version', '0'); --recording initial time -INSERT INTO system (`key`, `value`) VALUES ('created', UTC_TIMESTAMP()); +INSERT IGNORE INTO +system (`key`, `value`) +VALUES ('created', UTC_TIMESTAMP()); + +--creating default currencies +INSERT IGNORE INTO +currencies (`code`, `title`, `manual`, `description`, `type`, `value`) +VALUES ('USD', 'United States Dollar', TRUE, 'Base currency', 0, 1); diff --git a/database/mysql/mysql.cpp b/database/mysql/mysql.cpp index 8449dc5..e6b3067 100644 --- a/database/mysql/mysql.cpp +++ b/database/mysql/mysql.cpp @@ -19,8 +19,10 @@ 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 = ?"; + " SELECT accounts.id, ?, ?, true, ? FROM accounts WHERE accounts.login = ?" + " RETURNING id, owner"; constexpr const char* selectSession = "SELECT id, owner, renew FROM sessions where access = ?"; +constexpr const char* selectAssets = "SELECT id, owner, currency, title, icon, archived FROM assets where owner = ?"; static const std::filesystem::path buildSQLPath = "database"; @@ -266,20 +268,30 @@ std::string DB::MySQL::getAccountHash(const std::string& login) { return std::any_cast(result[0][0]); } -unsigned int DB::MySQL::createSession(const std::string& login, const std::string& access, const std::string& renew) { - std::string l = login, a = access, r = renew; +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(a.data(), MYSQL_TYPE_STRING); - session.bind(r.data(), MYSQL_TYPE_STRING); + 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(); - return lastInsertedId(); + 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; } unsigned int DB::MySQL::lastInsertedId() { @@ -320,4 +332,27 @@ DB::Session DB::MySQL::findSession(const std::string& accessToken) { return res; } +std::vector DB::MySQL::listAssets(unsigned int owner) { + MYSQL* con = &connection; + + Statement st(con, selectSession); + 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) { + const std::vector& proto = res[i]; + DB::Asset& asset = result[i]; + asset.id = std::any_cast(proto[0]); + asset.owner = std::any_cast(proto[1]); + asset.currency = std::any_cast(proto[2]); + asset.title = std::any_cast(proto[3]); + asset.icon = std::any_cast(proto[4]); + asset.archived = std::any_cast(proto[5]); //TODO + } + + return result; +} diff --git a/database/mysql/mysql.h b/database/mysql/mysql.h index 517ed1c..a768b44 100644 --- a/database/mysql/mysql.h +++ b/database/mysql/mysql.h @@ -19,27 +19,28 @@ class MySQL : public Interface { 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; + void migrate (uint8_t targetVersion) override; + uint8_t getVersion () override; + void setVersion (uint8_t version) override; - unsigned int registerAccount(const std::string& login, const std::string& hash) override; - std::string getAccountHash(const std::string& login) override; - unsigned int createSession(const std::string& login, const std::string& access, const std::string& renew) override; - Session findSession(const std::string& accessToken) override; + unsigned int 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 (unsigned int owner) override; private: - void executeFile(const std::filesystem::path& relativePath); - static std::optional getComment(std::string& string); - unsigned int lastInsertedId(); + void executeFile (const std::filesystem::path& relativePath); + static std::optional getComment (std::string& string); + unsigned int lastInsertedId (); protected: MYSQL connection; diff --git a/handler/CMakeLists.txt b/handler/CMakeLists.txt index e6c3995..2449784 100644 --- a/handler/CMakeLists.txt +++ b/handler/CMakeLists.txt @@ -8,6 +8,7 @@ set(HEADERS register.h login.h poll.h + listassets.h ) set(SOURCES @@ -17,6 +18,7 @@ set(SOURCES register.cpp login.cpp poll.cpp + listassets.cpp ) target_sources(${PROJECT_NAME} PRIVATE ${SOURCES}) diff --git a/handler/listassets.cpp b/handler/listassets.cpp new file mode 100644 index 0000000..b8a7ab8 --- /dev/null +++ b/handler/listassets.cpp @@ -0,0 +1,45 @@ +//SPDX-FileCopyrightText: 2024 Yury Gubich +//SPDX-License-Identifier: GPL-3.0-or-later + +#include "listassets.h" + +#include "server/server.h" +#include "server/session.h" +#include "database/exceptions.h" + +Handler::ListAssets::ListAssets (std::weak_ptr server): + Handler("listAssets", Request::Method::get), + server(server) +{} + +void Handler::ListAssets::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); + + + } catch (const DB::NoSession& e) { + return error(request, Response::Status::unauthorized); + } catch (const std::exception& e) { + std::cerr << "Exception on poll:\n\t" << e.what() << std::endl; + return error(request, Response::Status::internalError); + } catch (...) { + std::cerr << "Unknown exception on poll" << std::endl; + return error(request, Response::Status::internalError); + } +} + +void Handler::ListAssets::error (Request& request, Response::Status status) { + Response& res = request.createResponse(status); + res.send(); +} diff --git a/handler/listassets.h b/handler/listassets.h new file mode 100644 index 0000000..0d57ed1 --- /dev/null +++ b/handler/listassets.h @@ -0,0 +1,21 @@ +//SPDX-FileCopyrightText: 2024 Yury Gubich +//SPDX-License-Identifier: GPL-3.0-or-later + +#pragma once + +#include + +#include "handler.h" + +class Server; +namespace Handler { +class ListAssets : public Handler::Handler { +public: + ListAssets (std::weak_ptr server); + void handle (Request& request) override; + + static void error (Request& request, Response::Status status); +private: + std::weak_ptr server; +}; +} diff --git a/handler/poll.h b/handler/poll.h index f9c43c8..a11db7a 100644 --- a/handler/poll.h +++ b/handler/poll.h @@ -14,8 +14,8 @@ namespace Handler { class Poll : public Handler { public: - Poll(std::weak_ptr server); - void handle(Request& request) override; + Poll (std::weak_ptr server); + void handle (Request& request) override; enum class Result { success, @@ -24,8 +24,9 @@ public: timeout, unknownError }; - - static void error(Request& request, Result result, Response::Status status); + + static void error (Request& request, Result result, Response::Status status); + private: std::weak_ptr server; diff --git a/run.sh.in b/run.sh.in index 919bcad..7ee8ac3 100644 --- a/run.sh.in +++ b/run.sh.in @@ -4,12 +4,15 @@ #SPDX-License-Identifier: GPL-3.0-or-later start_service() { - if systemctl is-active --quiet $1 - then + if (systemctl is-active --quiet $1) then echo "$1 is already running" else - sudo systemctl start $1 - echo "$1 started" + echo "$1 is not running, going to use \"sudo systemctl start $1\"" + if (sudo systemctl start $1) then + echo "$1 started" + else + exit + fi fi } diff --git a/server/server.cpp b/server/server.cpp index 01c4cbb..089f374 100644 --- a/server/server.cpp +++ b/server/server.cpp @@ -157,25 +157,26 @@ bool Server::validatePassword(const std::string& login, const std::string& passw Session& Server::openSession(const std::string& login) { std::string accessToken, renewToken; - unsigned int sessionId = 0; + DB::Session s; + s.id = 0; int counter = 10; do { try { accessToken = generateRandomString(32); renewToken = generateRandomString(32); DB::Resource db = pool->request(); - sessionId = db->createSession(login, accessToken, renewToken); + 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); - if (sessionId == 0) + if (s.id == 0) throw std::runtime_error("Couldn't create session, ran out of attempts"); std::unique_ptr& session = sessions[accessToken] - = std::make_unique(scheduler, sessionId, accessToken, renewToken, pollTimout); + = std::make_unique(scheduler, s.id, s.owner, s.accessToken, s.renewToken, pollTimout); return *session.get(); } @@ -189,6 +190,7 @@ Session& Server::getSession (const std::string& accessToken) { std::unique_ptr& session = sessions[accessToken] = std::make_unique( scheduler, s.id, + s.owner, s.accessToken, s.renewToken, pollTimout diff --git a/server/session.cpp b/server/session.cpp index fe77381..a5bd4ce 100644 --- a/server/session.cpp +++ b/server/session.cpp @@ -8,12 +8,14 @@ Session::Session( std::weak_ptr scheduler, unsigned int id, + unsigned int owner, const std::string& access, const std::string& renew, unsigned int timeout ): - scheduler(scheduler), id(id), + owner(owner), + scheduler(scheduler), access(access), renew(renew), polling(nullptr), diff --git a/server/session.h b/server/session.h index 55f84cb..9ba2899 100644 --- a/server/session.h +++ b/server/session.h @@ -13,26 +13,29 @@ 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); + Session(Session&& other) = delete; ~Session(); Session& operator = (const Session&) = delete; - Session& operator = (Session&& other); + 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; + private: void onTimeout(); private: std::weak_ptr scheduler; - unsigned int id; std::string access; std::string renew; std::unique_ptr polling; diff --git a/utils/helpers.cpp b/utils/helpers.cpp index 1af821e..756cfa8 100644 --- a/utils/helpers.cpp +++ b/utils/helpers.cpp @@ -36,7 +36,7 @@ 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 From 4df8d4319e9b3b72b995258f905ac9b3bb30b44a Mon Sep 17 00:00:00 2001 From: blue Date: Fri, 12 Jan 2024 20:39:41 -0300 Subject: [PATCH 15/27] schema directory for all datastructures of database, add-list assets requests, not tested --- database/CMakeLists.txt | 1 + database/interface.h | 23 ++-------- database/mysql/mysql.cpp | 74 ++++++++++++++++---------------- database/mysql/mysql.h | 1 + database/schema/CMakeLists.txt | 14 +++++++ database/schema/asset.cpp | 44 +++++++++++++++++++ database/schema/asset.h | 33 +++++++++++++++ database/schema/session.cpp | 18 ++++++++ database/schema/session.h | 23 ++++++++++ handler/CMakeLists.txt | 2 + handler/addasset.cpp | 77 ++++++++++++++++++++++++++++++++++ handler/addasset.h | 21 ++++++++++ handler/listassets.cpp | 11 +++++ server/server.cpp | 25 +++++++---- server/server.h | 1 + 15 files changed, 306 insertions(+), 62 deletions(-) create mode 100644 database/schema/CMakeLists.txt create mode 100644 database/schema/asset.cpp create mode 100644 database/schema/asset.h create mode 100644 database/schema/session.cpp create mode 100644 database/schema/session.h create mode 100644 handler/addasset.cpp create mode 100644 handler/addasset.h diff --git a/database/CMakeLists.txt b/database/CMakeLists.txt index 42e02ef..36e1ad5 100644 --- a/database/CMakeLists.txt +++ b/database/CMakeLists.txt @@ -19,3 +19,4 @@ target_sources(${PROJECT_NAME} PRIVATE ${SOURCES}) add_subdirectory(mysql) add_subdirectory(migrations) +add_subdirectory(schema) diff --git a/database/interface.h b/database/interface.h index b2cc592..e96f97b 100644 --- a/database/interface.h +++ b/database/interface.h @@ -9,26 +9,10 @@ #include #include +#include "schema/session.h" +#include "schema/asset.h" + namespace DB { -struct Session { - unsigned int id; - unsigned int owner; - std::string accessToken; - std::string renewToken; -}; - -struct Asset { - unsigned int id; - unsigned int owner; - unsigned int currency; - std::string title; - std::string icon; - // `color` INTEGER UNSIGNED DEFAULT 0, - // `balance` DECIMAL (20, 5) DEFAULT 0, - // `type` INTEGER UNSIGNED NOT NULL, - bool archived; -}; - class Interface { public: enum class Type { @@ -62,6 +46,7 @@ public: 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(unsigned int owner) = 0; + virtual Asset addAsset(const Asset& asset) = 0; protected: Interface(Type type); diff --git a/database/mysql/mysql.cpp b/database/mysql/mysql.cpp index e6b3067..6a807fb 100644 --- a/database/mysql/mysql.cpp +++ b/database/mysql/mysql.cpp @@ -21,12 +21,14 @@ 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, renew FROM sessions where access = ?"; +constexpr const char* selectSession = "SELECT id, owner, access, renew FROM sessions where access = ?"; constexpr const char* selectAssets = "SELECT id, owner, currency, title, icon, archived FROM assets where owner = ?"; +constexpr const char* insertAsset = "INSERT INTO assets (`owner`, `currency`, `title`, `icon`, `archived`)" + " VALUES (?, ?, ?, ?, ?)"; static const std::filesystem::path buildSQLPath = "database"; -DB::MySQL::MySQL(): +DB::MySQL::MySQL (): Interface(Type::mysql), connection(), login(), @@ -40,7 +42,7 @@ DB::MySQL::~MySQL() { mysql_close(&connection); } -void DB::MySQL::connect(const std::string& path) { +void DB::MySQL::connect (const std::string& path) { if (state != State::disconnected) return; @@ -62,7 +64,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 DB::MySQL::setCredentials (const std::string& login, const std::string& password) { if (MySQL::login == login && MySQL::password == password) return; @@ -84,7 +86,7 @@ void DB::MySQL::setCredentials(const std::string& login, const std::string& pass throw std::runtime_error(std::string("Error changing credetials: ") + mysql_error(con)); } -void DB::MySQL::setDatabase(const std::string& database) { +void DB::MySQL::setDatabase (const std::string& database) { if (MySQL::database == database) return; @@ -100,7 +102,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 DB::MySQL::disconnect () { if (state == State::disconnected) return; @@ -109,7 +111,7 @@ void DB::MySQL::disconnect() { mysql_init(con); //this is ridiculous! } -void DB::MySQL::executeFile(const std::filesystem::path& relativePath) { +void DB::MySQL::executeFile (const std::filesystem::path& relativePath) { MYSQL* con = &connection; std::filesystem::path path = sharedPath() / relativePath; if (!std::filesystem::exists(path)) @@ -141,7 +143,7 @@ void DB::MySQL::executeFile(const std::filesystem::path& relativePath) { } } -uint8_t DB::MySQL::getVersion() { +uint8_t DB::MySQL::getVersion () { MYSQL* con = &connection; int result = mysql_query(con, versionQuery); @@ -164,14 +166,14 @@ uint8_t DB::MySQL::getVersion() { return 0; } -void DB::MySQL::setVersion(uint8_t version) { +void DB::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 DB::MySQL::migrate (uint8_t targetVersion) { uint8_t currentVersion = getVersion(); while (currentVersion < targetVersion) { @@ -193,7 +195,7 @@ void DB::MySQL::migrate(uint8_t targetVersion) { std::cout << "Database is now on actual version " << std::to_string(targetVersion) << std::endl; } -std::optional DB::MySQL::getComment(std::string& string) { +std::optional DB::MySQL::getComment (std::string& string) { ltrim(string); if (string.length() < 2) return std::nullopt; @@ -221,7 +223,7 @@ std::optional DB::MySQL::getComment(std::string& string) { return std::nullopt; } -unsigned int DB::MySQL::registerAccount(const std::string& login, const std::string& hash) { +unsigned int DB::MySQL::registerAccount (const std::string& login, const std::string& hash) { //TODO validate filed lengths! MYSQL* con = &connection; MySQL::Transaction txn(con); @@ -250,7 +252,7 @@ unsigned int DB::MySQL::registerAccount(const std::string& login, const std::str return id; } -std::string DB::MySQL::getAccountHash(const std::string& login) { +std::string DB::MySQL::getAccountHash (const std::string& login) { std::string l = login; MYSQL* con = &connection; @@ -268,7 +270,7 @@ std::string DB::MySQL::getAccountHash(const std::string& login) { return std::any_cast(result[0][0]); } -DB::Session DB::MySQL::createSession(const std::string& login, const std::string& access, const std::string& renew) { +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; @@ -294,7 +296,7 @@ DB::Session DB::MySQL::createSession(const std::string& login, const std::string return res; } -unsigned int DB::MySQL::lastInsertedId() { +unsigned int DB::MySQL::lastInsertedId () { MYSQL* con = &connection; int result = mysql_query(con, lastIdQuery); @@ -311,7 +313,7 @@ unsigned int DB::MySQL::lastInsertedId() { else throw std::runtime_error(std::string("Querying last inserted id returned no rows")); } -DB::Session DB::MySQL::findSession(const std::string& accessToken) { +DB::Session DB::MySQL::findSession (const std::string& accessToken) { std::string a = accessToken; MYSQL* con = &connection; @@ -323,36 +325,38 @@ DB::Session DB::MySQL::findSession(const std::string& accessToken) { if (result.empty()) throw NoSession("Couldn't find session with token " + a); - DB::Session res; - res.id = std::any_cast(result[0][0]); - res.owner = std::any_cast(result[0][1]); - res.renewToken = std::any_cast(result[0][2]); - res.accessToken = a; - - return res; + return DB::Session(result[0]); } -std::vector DB::MySQL::listAssets(unsigned int owner) { +std::vector DB::MySQL::listAssets (unsigned int owner) { MYSQL* con = &connection; - Statement st(con, selectSession); + 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) { - const std::vector& proto = res[i]; - DB::Asset& asset = result[i]; - asset.id = std::any_cast(proto[0]); - asset.owner = std::any_cast(proto[1]); - asset.currency = std::any_cast(proto[2]); - asset.title = std::any_cast(proto[3]); - asset.icon = std::any_cast(proto[4]); - asset.archived = std::any_cast(proto[5]); //TODO - } + 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 session(con, insertAsset); + session.bind(&result.owner, MYSQL_TYPE_LONG, true); + session.bind(&result.currency, MYSQL_TYPE_LONG, true); + session.bind(result.title.data(), MYSQL_TYPE_STRING); + session.bind(result.icon.data(), MYSQL_TYPE_STRING); + session.bind(&result.archived, MYSQL_TYPE_TINY); + session.execute(); + + result.id = lastInsertedId(); + + return asset; +} diff --git a/database/mysql/mysql.h b/database/mysql/mysql.h index a768b44..e30d309 100644 --- a/database/mysql/mysql.h +++ b/database/mysql/mysql.h @@ -36,6 +36,7 @@ public: 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 (unsigned int owner) override; + Asset addAsset (const Asset& asset) override; private: void executeFile (const std::filesystem::path& relativePath); diff --git a/database/schema/CMakeLists.txt b/database/schema/CMakeLists.txt new file mode 100644 index 0000000..b3538da --- /dev/null +++ b/database/schema/CMakeLists.txt @@ -0,0 +1,14 @@ +#SPDX-FileCopyrightText: 2023 Yury Gubich +#SPDX-License-Identifier: GPL-3.0-or-later + +set(HEADERS + session.h + asset.h +) + +set(SOURCES + session.cpp + asset.cpp +) + +target_sources(${PROJECT_NAME} PRIVATE ${SOURCES}) diff --git a/database/schema/asset.cpp b/database/schema/asset.cpp new file mode 100644 index 0000000..5d496e3 --- /dev/null +++ b/database/schema/asset.cpp @@ -0,0 +1,44 @@ +//SPDX-FileCopyrightText: 2024 Yury Gubich +//SPDX-License-Identifier: GPL-3.0-or-later + +#include "asset.h" + +DB::Asset::Asset (): + id(), + owner(), + currency(), + title(), + icon(), + archived() +{} + +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])), + archived(std::any_cast(vec[5])) +{} + +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]); + archived = std::any_cast(vec[5]); +} + +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["archived"] = archived; + + return result; +} diff --git a/database/schema/asset.h b/database/schema/asset.h new file mode 100644 index 0000000..005f308 --- /dev/null +++ b/database/schema/asset.h @@ -0,0 +1,33 @@ +//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: + unsigned int id; + unsigned int owner; + unsigned int currency; + std::string title; + std::string icon; + // `color` INTEGER UNSIGNED DEFAULT 0, + // `balance` DECIMAL (20, 5) DEFAULT 0, + // `type` INTEGER UNSIGNED NOT NULL, + bool archived; +}; +} diff --git a/database/schema/session.cpp b/database/schema/session.cpp new file mode 100644 index 0000000..7bedaa5 --- /dev/null +++ b/database/schema/session.cpp @@ -0,0 +1,18 @@ +//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 new file mode 100644 index 0000000..55615c3 --- /dev/null +++ b/database/schema/session.h @@ -0,0 +1,23 @@ +//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/handler/CMakeLists.txt b/handler/CMakeLists.txt index 2449784..6791d53 100644 --- a/handler/CMakeLists.txt +++ b/handler/CMakeLists.txt @@ -9,6 +9,7 @@ set(HEADERS login.h poll.h listassets.h + addasset.h ) set(SOURCES @@ -19,6 +20,7 @@ set(SOURCES login.cpp poll.cpp listassets.cpp + addasset.cpp ) target_sources(${PROJECT_NAME} PRIVATE ${SOURCES}) diff --git a/handler/addasset.cpp b/handler/addasset.cpp new file mode 100644 index 0000000..fd63886 --- /dev/null +++ b/handler/addasset.cpp @@ -0,0 +1,77 @@ +//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 (std::weak_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::stoi(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 { + Session& session = srv->getSession(access); + + asset.owner = session.owner; + asset = srv->getDatabase()->addAsset(asset); + + nlohmann::json body = nlohmann::json::object(); + body["asset"] = asset.toJSON(); + + 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 poll:\n\t" << e.what() << std::endl; + return error(request, Response::Status::internalError); + } catch (...) { + std::cerr << "Unknown exception on poll" << std::endl; + return error(request, Response::Status::internalError); + } +} + +void Handler::AddAsset::error (Request& request, Response::Status status) { + Response& res = request.createResponse(status); + res.send(); +} diff --git a/handler/addasset.h b/handler/addasset.h new file mode 100644 index 0000000..5ebcbeb --- /dev/null +++ b/handler/addasset.h @@ -0,0 +1,21 @@ +//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 (std::weak_ptr server); + virtual void handle (Request& request) override; + + static void error (Request& request, Response::Status status); +private: + std::weak_ptr server; +}; +} diff --git a/handler/listassets.cpp b/handler/listassets.cpp index b8a7ab8..ed0af09 100644 --- a/handler/listassets.cpp +++ b/handler/listassets.cpp @@ -26,7 +26,18 @@ void Handler::ListAssets::handle (Request& request) { 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); diff --git a/server/server.cpp b/server/server.cpp index 089f374..fb93424 100644 --- a/server/server.cpp +++ b/server/server.cpp @@ -12,6 +12,8 @@ #include "handler/register.h" #include "handler/login.h" #include "handler/poll.h" +#include "handler/listassets.h" +#include "handler/addasset.h" #include "taskmanager/route.h" @@ -32,7 +34,7 @@ constexpr uint32_t hashMemoryCost = 65536; constexpr uint8_t currentDbVesion = 1; -Server::Server(): +Server::Server (): std::enable_shared_from_this(), terminating(false), requestCount(0), @@ -59,14 +61,16 @@ Server::Server(): db->migrate(currentDbVesion); } -Server::~Server() {} +Server::~Server () {} -void Server::run(int socketDescriptor) { +void Server::run (int socketDescriptor) { router->addRoute(std::make_unique()); router->addRoute(std::make_unique()); router->addRoute(std::make_unique(shared_from_this())); router->addRoute(std::make_unique(shared_from_this())); router->addRoute(std::make_unique(shared_from_this())); + router->addRoute(std::make_unique(shared_from_this())); + router->addRoute(std::make_unique(shared_from_this())); taskManager->start(); scheduler->start(); @@ -82,7 +86,7 @@ void Server::run(int socketDescriptor) { } } -void Server::handleRequest(std::unique_ptr request) { +void Server::handleRequest (std::unique_ptr request) { ++requestCount; if (!serverName) { try { @@ -102,7 +106,7 @@ void Server::handleRequest(std::unique_ptr request) { taskManager->schedule(std::move(route)); } -std::string Server::generateRandomString(std::size_t length) { +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); @@ -114,7 +118,7 @@ std::string Server::generateRandomString(std::size_t length) { return result; } -unsigned int Server::registerAccount(const std::string& login, const std::string& password) { +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 @@ -138,7 +142,7 @@ unsigned int Server::registerAccount(const std::string& login, const std::string return db->registerAccount(login, hash); } -bool Server::validatePassword(const std::string& login, const std::string& password) { +bool Server::validatePassword (const std::string& login, const std::string& password) { DB::Resource db = pool->request(); std::string hash = db->getAccountHash(login); @@ -155,7 +159,7 @@ bool Server::validatePassword(const std::string& login, const std::string& passw } } -Session& Server::openSession(const std::string& login) { +Session& Server::openSession (const std::string& login) { std::string accessToken, renewToken; DB::Session s; s.id = 0; @@ -197,3 +201,8 @@ Session& Server::getSession (const std::string& accessToken) { ); return *session.get(); } + + +DB::Resource Server::getDatabase () { + return pool->request(); +} diff --git a/server/server.h b/server/server.h index 4402cfb..eaa9004 100644 --- a/server/server.h +++ b/server/server.h @@ -39,6 +39,7 @@ public: 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); From 2e01fe8d67a72940b80511f4367dca87de6b3961 Mon Sep 17 00:00:00 2001 From: blue Date: Sat, 13 Jan 2024 20:57:42 -0300 Subject: [PATCH 16/27] some ideas of updates delivery --- handler/addasset.cpp | 6 ++--- request/request.cpp | 31 +++++++++++++++++++++---- request/request.h | 2 ++ run.sh.in | 3 ++- server/session.cpp | 55 +++++++++++++++++++++++++++++++++++++++++++- server/session.h | 29 ++++++++++++++++------- 6 files changed, 108 insertions(+), 18 deletions(-) diff --git a/handler/addasset.cpp b/handler/addasset.cpp index fd63886..adc5e09 100644 --- a/handler/addasset.cpp +++ b/handler/addasset.cpp @@ -53,13 +53,11 @@ void Handler::AddAsset::handle (Request& request) { asset.owner = session.owner; asset = srv->getDatabase()->addAsset(asset); - nlohmann::json body = nlohmann::json::object(); - body["asset"] = asset.toJSON(); - Response& res = request.createResponse(Response::Status::ok); - res.setBody(body); res.send(); + session.assetAdded(asset); + } catch (const DB::NoSession& e) { return error(request, Response::Status::unauthorized); } catch (const std::exception& e) { diff --git a/request/request.cpp b/request/request.cpp index eedbc72..e6a055d 100644 --- a/request/request.cpp +++ b/request/request.cpp @@ -11,6 +11,7 @@ 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"); @@ -33,7 +34,8 @@ Request::Request (): state(State::initial), raw(), response(nullptr), - path() + path(), + cachedMethod(Method::unknown) {} Request::~Request() { @@ -68,10 +70,15 @@ std::string_view Request::methodName() const { } Request::Method Request::method() const { + if (cachedMethod != Method::unknown) + return cachedMethod; + std::string_view method = methodName(); for (const auto& pair : methods) { - if (pair.first == method) + if (pair.first == method) { + cachedMethod = pair.second; return pair.second; + } } return Request::Method::unknown; @@ -227,20 +234,36 @@ unsigned int Request::contentLength() const { if (!active()) throw std::runtime_error("An attempt to read content length of a request in a wrong state"); - return atoi(FCGX_GetParam(CONTENT_LENGTH, raw.envp)); + 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; - unsigned int length = contentLength(); 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); diff --git a/request/request.h b/request/request.h index 1cef22c..55f2650 100644 --- a/request/request.h +++ b/request/request.h @@ -69,10 +69,12 @@ private: OStream getErrorStream(); void responseIsComplete(); void terminate(); + std::map getFormPOST() const; private: State state; FCGX_Request raw; std::unique_ptr response; std::string path; + mutable Method cachedMethod; }; diff --git a/run.sh.in b/run.sh.in index 7ee8ac3..be5f016 100644 --- a/run.sh.in +++ b/run.sh.in @@ -7,7 +7,7 @@ start_service() { if (systemctl is-active --quiet $1) then echo "$1 is already running" else - echo "$1 is not running, going to use \"sudo systemctl start $1\"" + echo "$1 is not running, going to use sudo to start it" if (sudo systemctl start $1) then echo "$1 started" else @@ -17,6 +17,7 @@ start_service() { } if [ ! -d "/run/pica" ]; then + echo "reuired unix socket was not found, going to use sudo to create it" sudo mkdir /run/pica sudo chown $USER:$USER /run/pica fi diff --git a/server/session.cpp b/server/session.cpp index a5bd4ce..ef13c2a 100644 --- a/server/session.cpp +++ b/server/session.cpp @@ -20,7 +20,9 @@ Session::Session( renew(renew), polling(nullptr), timeoutId(TM::Scheduler::none), - timeout(timeout) + timeout(timeout), + mtx(), + cache() {} Session::~Session () { @@ -39,6 +41,7 @@ std::string Session::getRenewToken() const { } 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) { @@ -51,17 +54,67 @@ void Session::accept(std::unique_ptr request) { 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); + auto 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::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 index 9ba2899..04bfc6e 100644 --- a/server/session.h +++ b/server/session.h @@ -4,13 +4,19 @@ #pragma once #include +#include +#include + +#include #include "request/accepting.h" #include "taskmanager/scheduler.h" +#include "database/schema/asset.h" + class Session : public Accepting { public: - Session( + Session ( std::weak_ptr scheduler, unsigned int id, unsigned int owner, @@ -18,21 +24,25 @@ public: const std::string& renew, unsigned int timeout ); - Session(const Session&) = delete; - Session(Session&& other) = delete; - ~Session(); + 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; + 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); + private: - void onTimeout(); + void onTimeout (); + void sendUpdates (std::unique_ptr request); + void checkUpdates (); private: std::weak_ptr scheduler; @@ -41,4 +51,7 @@ private: std::unique_ptr polling; TM::Record::ID timeoutId; TM::Scheduler::Delay timeout; + std::mutex mtx; + + std::map> cache; }; From 19d786631a21cf4045778e80c0ffaba141d3f5ae Mon Sep 17 00:00:00 2001 From: blue Date: Wed, 17 Jan 2024 18:56:53 -0300 Subject: [PATCH 17/27] some debug --- database/mysql/mysql.cpp | 4 ++-- response/response.cpp | 14 +++++++------- server/session.cpp | 6 +++++- 3 files changed, 14 insertions(+), 10 deletions(-) diff --git a/database/mysql/mysql.cpp b/database/mysql/mysql.cpp index 6a807fb..062f972 100644 --- a/database/mysql/mysql.cpp +++ b/database/mysql/mysql.cpp @@ -23,8 +23,8 @@ constexpr const char* createSessionQuery = "INSERT INTO sessions (`owner`, `acce " 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, archived FROM assets where owner = ?"; -constexpr const char* insertAsset = "INSERT INTO assets (`owner`, `currency`, `title`, `icon`, `archived`)" - " VALUES (?, ?, ?, ?, ?)"; +constexpr const char* insertAsset = "INSERT INTO assets (`owner`, `currency`, `title`, `icon`, `archived`, `type`)" + " VALUES (?, ?, ?, ?, ?, 1)"; static const std::filesystem::path buildSQLPath = "database"; diff --git a/response/response.cpp b/response/response.cpp index 7a4e4cb..1db90f0 100644 --- a/response/response.cpp +++ b/response/response.cpp @@ -26,8 +26,8 @@ constexpr std::array(Response::Status::__ }; 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): @@ -50,13 +50,13 @@ void Response::send() const { // request.getErrorStream(); OStream out = request.getOutputStream(); - out << statuses[static_cast(status)]; + out << statuses[static_cast(status)] << "\r\n"; if (!body.empty()) - out << '\n' - << contentTypes[static_cast(type)] - << '\n' - << '\n' + out << contentTypes[static_cast(type)] << "\r\n" + << "\r\n" << body; + else + out << "\r\n"; request.responseIsComplete(); } diff --git a/server/session.cpp b/server/session.cpp index ef13c2a..4cd0c60 100644 --- a/server/session.cpp +++ b/server/session.cpp @@ -22,7 +22,11 @@ Session::Session( timeoutId(TM::Scheduler::none), timeout(timeout), mtx(), - cache() + cache({ + {"system", { + {"invalidate", true} + }} + }) {} Session::~Session () { From a2c2c2a88346a261bece60ef41bdf33d3fc2d6b4 Mon Sep 17 00:00:00 2001 From: blue Date: Sun, 21 Jan 2024 16:23:48 -0300 Subject: [PATCH 18/27] debug, deleting assets --- database/interface.h | 1 + database/migrations/m0.sql | 2 ++ database/mysql/mysql.cpp | 31 ++++++++++++++----- database/mysql/mysql.h | 1 + database/mysql/statement.cpp | 3 ++ database/mysql/statement.h | 1 + handler/CMakeLists.txt | 2 ++ handler/addasset.cpp | 5 --- handler/addasset.h | 1 - handler/deleteasset.cpp | 59 ++++++++++++++++++++++++++++++++++++ handler/deleteasset.h | 21 +++++++++++++ handler/handler.cpp | 5 +++ handler/handler.h | 3 ++ handler/listassets.cpp | 5 --- handler/listassets.h | 1 - response/response.cpp | 2 ++ response/response.h | 1 + server/server.cpp | 2 ++ server/session.cpp | 13 +++++++- server/session.h | 1 + 20 files changed, 139 insertions(+), 21 deletions(-) create mode 100644 handler/deleteasset.cpp create mode 100644 handler/deleteasset.h diff --git a/database/interface.h b/database/interface.h index e96f97b..73f2cc0 100644 --- a/database/interface.h +++ b/database/interface.h @@ -47,6 +47,7 @@ public: virtual Session findSession(const std::string& accessToken) = 0; virtual std::vector listAssets(unsigned int owner) = 0; virtual Asset addAsset(const Asset& asset) = 0; + virtual bool deleteAsset(unsigned int assetId, unsigned int actorId) = 0; protected: Interface(Type type); diff --git a/database/migrations/m0.sql b/database/migrations/m0.sql index 72f14e0..b4bcaac 100644 --- a/database/migrations/m0.sql +++ b/database/migrations/m0.sql @@ -56,6 +56,7 @@ CREATE TABLE IF NOT EXISTS currencies ( `value` DECIMAL (20, 5) NOT NULL, `source` TEXT, `description` TEXT, + `icon` VARCHAR(256), INDEX manual_idx (manual) ); @@ -71,6 +72,7 @@ CREATE TABLE IF NOT EXISTS assets ( `balance` DECIMAL (20, 5) DEFAULT 0, `type` INTEGER UNSIGNED NOT NULL, `archived` BOOLEAN DEFAULT FALSE, + `created` TIMESTAMP DEFAULT UTC_TIMESTAMP(), INDEX owner_idx (owner), INDEX archived_idx (archived), diff --git a/database/mysql/mysql.cpp b/database/mysql/mysql.cpp index 062f972..632be60 100644 --- a/database/mysql/mysql.cpp +++ b/database/mysql/mysql.cpp @@ -25,6 +25,7 @@ constexpr const char* selectSession = "SELECT id, owner, access, renew FROM sess constexpr const char* selectAssets = "SELECT id, owner, currency, title, icon, archived FROM assets where owner = ?"; constexpr const char* insertAsset = "INSERT INTO assets (`owner`, `currency`, `title`, `icon`, `archived`, `type`)" " VALUES (?, ?, ?, ?, ?, 1)"; +constexpr const char* removeAsset = "DELETE FROM assets where `id` = ? AND `owner` = ?"; static const std::filesystem::path buildSQLPath = "database"; @@ -348,15 +349,29 @@ DB::Asset DB::MySQL::addAsset(const Asset& asset) { MYSQL* con = &connection; Asset result = asset; - Statement session(con, insertAsset); - session.bind(&result.owner, MYSQL_TYPE_LONG, true); - session.bind(&result.currency, MYSQL_TYPE_LONG, true); - session.bind(result.title.data(), MYSQL_TYPE_STRING); - session.bind(result.icon.data(), MYSQL_TYPE_STRING); - session.bind(&result.archived, MYSQL_TYPE_TINY); - session.execute(); + 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.archived, MYSQL_TYPE_TINY); + add.execute(); result.id = lastInsertedId(); - return asset; + return result; +} + +bool DB::MySQL::deleteAsset(unsigned int assetId, unsigned int actorId) { + MYSQL* con = &connection; + + Statement del(con, 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; } diff --git a/database/mysql/mysql.h b/database/mysql/mysql.h index e30d309..7b8abeb 100644 --- a/database/mysql/mysql.h +++ b/database/mysql/mysql.h @@ -37,6 +37,7 @@ public: Session findSession (const std::string& accessToken) override; std::vector listAssets (unsigned int owner) override; Asset addAsset (const Asset& asset) override; + bool deleteAsset(unsigned int assetId, unsigned int actorId) override; private: void executeFile (const std::filesystem::path& relativePath); diff --git a/database/mysql/statement.cpp b/database/mysql/statement.cpp index f8edd76..d770ce4 100644 --- a/database/mysql/statement.cpp +++ b/database/mysql/statement.cpp @@ -150,3 +150,6 @@ std::vector> DB::MySQL::Statement::fetchResult() { return result; } +unsigned int DB::MySQL::Statement::affectedRows () { + return mysql_stmt_affected_rows(stmt.get()); +} diff --git a/database/mysql/statement.h b/database/mysql/statement.h index 4f013f5..7dee3c7 100644 --- a/database/mysql/statement.h +++ b/database/mysql/statement.h @@ -22,6 +22,7 @@ public: void bind(void* value, enum_field_types type, bool usigned = false); void execute(); + unsigned int affectedRows(); std::vector> fetchResult(); private: diff --git a/handler/CMakeLists.txt b/handler/CMakeLists.txt index 6791d53..09de5a6 100644 --- a/handler/CMakeLists.txt +++ b/handler/CMakeLists.txt @@ -10,6 +10,7 @@ set(HEADERS poll.h listassets.h addasset.h + deleteasset.cpp ) set(SOURCES @@ -21,6 +22,7 @@ set(SOURCES poll.cpp listassets.cpp addasset.cpp + deleteasset.cpp ) target_sources(${PROJECT_NAME} PRIVATE ${SOURCES}) diff --git a/handler/addasset.cpp b/handler/addasset.cpp index adc5e09..8934025 100644 --- a/handler/addasset.cpp +++ b/handler/addasset.cpp @@ -68,8 +68,3 @@ void Handler::AddAsset::handle (Request& request) { return error(request, Response::Status::internalError); } } - -void Handler::AddAsset::error (Request& request, Response::Status status) { - Response& res = request.createResponse(status); - res.send(); -} diff --git a/handler/addasset.h b/handler/addasset.h index 5ebcbeb..f653a0a 100644 --- a/handler/addasset.h +++ b/handler/addasset.h @@ -14,7 +14,6 @@ public: AddAsset (std::weak_ptr server); virtual void handle (Request& request) override; - static void error (Request& request, Response::Status status); private: std::weak_ptr server; }; diff --git a/handler/deleteasset.cpp b/handler/deleteasset.cpp new file mode 100644 index 0000000..f224e49 --- /dev/null +++ b/handler/deleteasset.cpp @@ -0,0 +1,59 @@ +//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 (std::weak_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 poll:\n\t" << e.what() << std::endl; + return error(request, Response::Status::internalError); + } catch (...) { + std::cerr << "Unknown exception on poll" << std::endl; + return error(request, Response::Status::internalError); + } +} diff --git a/handler/deleteasset.h b/handler/deleteasset.h new file mode 100644 index 0000000..4c05561 --- /dev/null +++ b/handler/deleteasset.h @@ -0,0 +1,21 @@ +//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 (std::weak_ptr server); + + virtual void handle (Request& request) override; + +private: + std::weak_ptr server; +}; +} diff --git a/handler/handler.cpp b/handler/handler.cpp index 49b8572..4623a06 100644 --- a/handler/handler.cpp +++ b/handler/handler.cpp @@ -9,3 +9,8 @@ Handler::Handler::Handler(const std::string& path, Request::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 index 1ba643b..ee0df3c 100644 --- a/handler/handler.h +++ b/handler/handler.h @@ -15,6 +15,9 @@ class Handler { protected: Handler(const std::string& path, Request::Method method); +protected: + static void error (Request& request, Response::Status status); + public: virtual ~Handler(); diff --git a/handler/listassets.cpp b/handler/listassets.cpp index ed0af09..f9b5a32 100644 --- a/handler/listassets.cpp +++ b/handler/listassets.cpp @@ -49,8 +49,3 @@ void Handler::ListAssets::handle (Request& request) { return error(request, Response::Status::internalError); } } - -void Handler::ListAssets::error (Request& request, Response::Status status) { - Response& res = request.createResponse(status); - res.send(); -} diff --git a/handler/listassets.h b/handler/listassets.h index 0d57ed1..983092f 100644 --- a/handler/listassets.h +++ b/handler/listassets.h @@ -14,7 +14,6 @@ public: ListAssets (std::weak_ptr server); void handle (Request& request) override; - static void error (Request& request, Response::Status status); private: std::weak_ptr server; }; diff --git a/response/response.cpp b/response/response.cpp index 1db90f0..0b5416c 100644 --- a/response/response.cpp +++ b/response/response.cpp @@ -9,6 +9,7 @@ constexpr std::array(Response::Status::__size)> s 200, 400, 401, + 403, 404, 405, 409, @@ -19,6 +20,7 @@ constexpr std::array(Response::Status::__ "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", diff --git a/response/response.h b/response/response.h index 6fbce08..e305f51 100644 --- a/response/response.h +++ b/response/response.h @@ -20,6 +20,7 @@ public: ok, badRequest, unauthorized, + forbidden, notFound, methodNotAllowed, conflict, diff --git a/server/server.cpp b/server/server.cpp index fb93424..84c4e1a 100644 --- a/server/server.cpp +++ b/server/server.cpp @@ -14,6 +14,7 @@ #include "handler/poll.h" #include "handler/listassets.h" #include "handler/addasset.h" +#include "handler/deleteasset.h" #include "taskmanager/route.h" @@ -71,6 +72,7 @@ void Server::run (int socketDescriptor) { router->addRoute(std::make_unique(shared_from_this())); router->addRoute(std::make_unique(shared_from_this())); router->addRoute(std::make_unique(shared_from_this())); + router->addRoute(std::make_unique(shared_from_this())); taskManager->start(); scheduler->start(); diff --git a/server/session.cpp b/server/session.cpp index 4cd0c60..3e29d14 100644 --- a/server/session.cpp +++ b/server/session.cpp @@ -101,7 +101,7 @@ void Session::onTimeout () { void Session::assetAdded (const DB::Asset& asset) { std::lock_guard lock(mtx); - auto assets = cache["assets"]; + std::map& assets = cache["assets"]; auto addedItr = assets.find("added"); if (addedItr == assets.end()) addedItr = assets.emplace("added", nlohmann::json::array()).first; @@ -110,6 +110,17 @@ void Session::assetAdded (const DB::Asset& asset) { checkUpdates(); } +void Session::assetRemoved (unsigned int assetId) { + std::lock_guard lock(mtx); + std::map& assets = cache["assets"]; + auto addedItr = assets.find("removed"); + if (addedItr == assets.end()) + addedItr = assets.emplace("removed", nlohmann::json::array()).first; + + addedItr->second.push_back(assetId); + checkUpdates(); +} + void Session::checkUpdates () { std::shared_ptr sch = scheduler.lock(); if (polling) { diff --git a/server/session.h b/server/session.h index 04bfc6e..75c55ca 100644 --- a/server/session.h +++ b/server/session.h @@ -38,6 +38,7 @@ public: const unsigned int owner; void assetAdded (const DB::Asset& asset); + void assetRemoved (unsigned int assetId); private: void onTimeout (); From db37abacd22651b19ab0ae86f7a1ac418a073581 Mon Sep 17 00:00:00 2001 From: blue Date: Mon, 22 Jan 2024 15:21:55 -0300 Subject: [PATCH 19/27] currencies request --- database/interface.h | 8 ++++-- database/mysql/mysql.cpp | 43 +++++++++++++++++++--------- database/mysql/mysql.h | 10 +++---- database/schema/CMakeLists.txt | 2 ++ database/schema/asset.cpp | 28 +++++++++++-------- database/schema/asset.h | 8 +++--- database/schema/currency.cpp | 40 ++++++++++++++++++++++++++ database/schema/currency.h | 35 +++++++++++++++++++++++ handler/CMakeLists.txt | 4 ++- handler/addasset.cpp | 12 ++++++-- handler/addasset.h | 2 +- handler/deleteasset.cpp | 2 +- handler/deleteasset.h | 2 +- handler/listassets.cpp | 2 +- handler/listassets.h | 2 +- handler/login.cpp | 2 +- handler/login.h | 2 +- handler/mycurrencies.cpp | 51 ++++++++++++++++++++++++++++++++++ handler/mycurrencies.h | 21 ++++++++++++++ handler/poll.cpp | 2 +- handler/poll.h | 2 +- handler/register.cpp | 2 +- handler/register.h | 2 +- server/server.cpp | 16 +++++++---- 24 files changed, 243 insertions(+), 57 deletions(-) create mode 100644 database/schema/currency.cpp create mode 100644 database/schema/currency.h create mode 100644 handler/mycurrencies.cpp create mode 100644 handler/mycurrencies.h diff --git a/database/interface.h b/database/interface.h index 73f2cc0..5173234 100644 --- a/database/interface.h +++ b/database/interface.h @@ -11,6 +11,7 @@ #include "schema/session.h" #include "schema/asset.h" +#include "schema/currency.h" namespace DB { class Interface { @@ -41,13 +42,14 @@ public: virtual uint8_t getVersion() = 0; virtual void setVersion(uint8_t version) = 0; - virtual unsigned int registerAccount(const std::string& login, const std::string& hash) = 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(unsigned int owner) = 0; + virtual std::vector listAssets(uint32_t owner) = 0; virtual Asset addAsset(const Asset& asset) = 0; - virtual bool deleteAsset(unsigned int assetId, unsigned int actorId) = 0; + virtual bool deleteAsset(uint32_t assetId, uint32_t actorId) = 0; + virtual std::vector listUsedCurrencies(uint32_t owner) = 0; protected: Interface(Type type); diff --git a/database/mysql/mysql.cpp b/database/mysql/mysql.cpp index 632be60..ea76b75 100644 --- a/database/mysql/mysql.cpp +++ b/database/mysql/mysql.cpp @@ -22,10 +22,13 @@ constexpr const char* createSessionQuery = "INSERT INTO sessions (`owner`, `acce " 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, archived FROM assets where owner = ?"; -constexpr const char* insertAsset = "INSERT INTO assets (`owner`, `currency`, `title`, `icon`, `archived`, `type`)" - " VALUES (?, ?, ?, ?, ?, 1)"; +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* removeAsset = "DELETE FROM assets where `id` = ? AND `owner` = ?"; +constexpr const char* selectUsedCurrencies = "SELECT c.id, c.code, c.title, c.manual, c.icon FROM currencies c" + "JOIN assets a ON c.id = a.currency" + "WHERE a.owner = ?"; static const std::filesystem::path buildSQLPath = "database"; @@ -224,7 +227,7 @@ std::optional DB::MySQL::getComment (std::string& string) { return std::nullopt; } -unsigned int DB::MySQL::registerAccount (const std::string& login, const std::string& hash) { +uint32_t DB::MySQL::registerAccount (const std::string& login, const std::string& hash) { //TODO validate filed lengths! MYSQL* con = &connection; MySQL::Transaction txn(con); @@ -241,7 +244,7 @@ unsigned int DB::MySQL::registerAccount (const std::string& login, const std::st throw DuplicateLogin(dup.what()); } - unsigned int id = lastInsertedId(); + uint32_t id = lastInsertedId(); static std::string defaultRole("default"); Statement addRole(con, assignRoleQuery); @@ -291,13 +294,13 @@ DB::Session DB::MySQL::createSession (const std::string& login, const std::strin 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]); + res.id = std::any_cast(result[0][0]); + res.owner = std::any_cast(result[0][1]); return res; } -unsigned int DB::MySQL::lastInsertedId () { +uint32_t DB::MySQL::lastInsertedId () { MYSQL* con = &connection; int result = mysql_query(con, lastIdQuery); @@ -329,7 +332,7 @@ DB::Session DB::MySQL::findSession (const std::string& accessToken) { return DB::Session(result[0]); } -std::vector DB::MySQL::listAssets (unsigned int owner) { +std::vector DB::MySQL::listAssets (uint32_t owner) { MYSQL* con = &connection; Statement st(con, selectAssets); @@ -354,6 +357,7 @@ DB::Asset DB::MySQL::addAsset(const Asset& asset) { 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(); @@ -362,10 +366,8 @@ DB::Asset DB::MySQL::addAsset(const Asset& asset) { return result; } -bool DB::MySQL::deleteAsset(unsigned int assetId, unsigned int actorId) { - MYSQL* con = &connection; - - Statement del(con, removeAsset); +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(); @@ -375,3 +377,18 @@ bool DB::MySQL::deleteAsset(unsigned int assetId, unsigned int actorId) { return true; } + +std::vector DB::MySQL::listUsedCurrencies(uint32_t owner) { + Statement list(&connection, removeAsset); + 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; +} diff --git a/database/mysql/mysql.h b/database/mysql/mysql.h index 7b8abeb..81bdcf5 100644 --- a/database/mysql/mysql.h +++ b/database/mysql/mysql.h @@ -17,7 +17,6 @@ class MySQL : public Interface { class Statement; class Transaction; - public: MySQL (); ~MySQL () override; @@ -31,18 +30,19 @@ public: uint8_t getVersion () override; void setVersion (uint8_t version) override; - unsigned int registerAccount (const std::string& login, const std::string& hash) 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 (unsigned int owner) override; + std::vector listAssets (uint32_t owner) override; Asset addAsset (const Asset& asset) override; - bool deleteAsset(unsigned int assetId, unsigned int actorId) override; + bool deleteAsset(uint32_t assetId, uint32_t actorId) override; + std::vector listUsedCurrencies(uint32_t owner) override; private: void executeFile (const std::filesystem::path& relativePath); static std::optional getComment (std::string& string); - unsigned int lastInsertedId (); + uint32_t lastInsertedId (); protected: MYSQL connection; diff --git a/database/schema/CMakeLists.txt b/database/schema/CMakeLists.txt index b3538da..94706fd 100644 --- a/database/schema/CMakeLists.txt +++ b/database/schema/CMakeLists.txt @@ -4,11 +4,13 @@ set(HEADERS session.h asset.h + currency.h ) set(SOURCES session.cpp asset.cpp + currency.cpp ) target_sources(${PROJECT_NAME} PRIVATE ${SOURCES}) diff --git a/database/schema/asset.cpp b/database/schema/asset.cpp index 5d496e3..d1ff7af 100644 --- a/database/schema/asset.cpp +++ b/database/schema/asset.cpp @@ -4,30 +4,33 @@ #include "asset.h" DB::Asset::Asset (): - id(), - owner(), - currency(), + id(0), + owner(0), + currency(0), title(), icon(), - archived() + 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])), + 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])), - archived(std::any_cast(vec[5])) + 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]); + 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]); - archived = std::any_cast(vec[5]); + color = std::any_cast(vec[5]); + archived = std::any_cast(vec[6]); } nlohmann::json DB::Asset::toJSON () const { @@ -38,6 +41,7 @@ nlohmann::json DB::Asset::toJSON () const { //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 index 005f308..83d513e 100644 --- a/database/schema/asset.h +++ b/database/schema/asset.h @@ -20,12 +20,12 @@ public: nlohmann::json toJSON () const; public: - unsigned int id; - unsigned int owner; - unsigned int currency; + uint32_t id; + uint32_t owner; + uint32_t currency; std::string title; std::string icon; - // `color` INTEGER UNSIGNED DEFAULT 0, + 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 new file mode 100644 index 0000000..fb47480 --- /dev/null +++ b/database/schema/currency.cpp @@ -0,0 +1,40 @@ +//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 new file mode 100644 index 0000000..0d88703 --- /dev/null +++ b/database/schema/currency.h @@ -0,0 +1,35 @@ +//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/handler/CMakeLists.txt b/handler/CMakeLists.txt index 09de5a6..1d3296b 100644 --- a/handler/CMakeLists.txt +++ b/handler/CMakeLists.txt @@ -10,7 +10,8 @@ set(HEADERS poll.h listassets.h addasset.h - deleteasset.cpp + deleteasset.h + mycurrencies.h ) set(SOURCES @@ -23,6 +24,7 @@ set(SOURCES listassets.cpp addasset.cpp deleteasset.cpp + mycurrencies.cpp ) target_sources(${PROJECT_NAME} PRIVATE ${SOURCES}) diff --git a/handler/addasset.cpp b/handler/addasset.cpp index 8934025..69e5539 100644 --- a/handler/addasset.cpp +++ b/handler/addasset.cpp @@ -9,7 +9,7 @@ #include "server/session.h" #include "database/exceptions.h" -Handler::AddAsset::AddAsset (std::weak_ptr server): +Handler::AddAsset::AddAsset (const std::shared_ptr& server): Handler("addAsset", Request::Method::post), server(server) {} @@ -32,7 +32,7 @@ void Handler::AddAsset::handle (Request& request) { return error(request, Response::Status::badRequest); DB::Asset asset; - asset.currency = std::stoi(itr->second); + asset.currency = std::stoul(itr->second); //TODO validate the currency itr = form.find("title"); @@ -47,6 +47,14 @@ void Handler::AddAsset::handle (Request& request) { 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); diff --git a/handler/addasset.h b/handler/addasset.h index f653a0a..8d4719e 100644 --- a/handler/addasset.h +++ b/handler/addasset.h @@ -11,7 +11,7 @@ class Server; namespace Handler { class AddAsset : public Handler { public: - AddAsset (std::weak_ptr server); + AddAsset (const std::shared_ptr& server); virtual void handle (Request& request) override; private: diff --git a/handler/deleteasset.cpp b/handler/deleteasset.cpp index f224e49..0d35ba5 100644 --- a/handler/deleteasset.cpp +++ b/handler/deleteasset.cpp @@ -7,7 +7,7 @@ #include "server/session.h" #include "database/exceptions.h" -Handler::DeleteAsset::DeleteAsset (std::weak_ptr server): +Handler::DeleteAsset::DeleteAsset (const std::shared_ptr& server): Handler("deleteAsset", Request::Method::post), server(server) {} diff --git a/handler/deleteasset.h b/handler/deleteasset.h index 4c05561..f1c41ba 100644 --- a/handler/deleteasset.h +++ b/handler/deleteasset.h @@ -11,7 +11,7 @@ class Server; namespace Handler { class DeleteAsset : public Handler { public: - DeleteAsset (std::weak_ptr server); + DeleteAsset (const std::shared_ptr& server); virtual void handle (Request& request) override; diff --git a/handler/listassets.cpp b/handler/listassets.cpp index f9b5a32..de5ace8 100644 --- a/handler/listassets.cpp +++ b/handler/listassets.cpp @@ -7,7 +7,7 @@ #include "server/session.h" #include "database/exceptions.h" -Handler::ListAssets::ListAssets (std::weak_ptr server): +Handler::ListAssets::ListAssets (const std::shared_ptr& server): Handler("listAssets", Request::Method::get), server(server) {} diff --git a/handler/listassets.h b/handler/listassets.h index 983092f..c806112 100644 --- a/handler/listassets.h +++ b/handler/listassets.h @@ -11,7 +11,7 @@ class Server; namespace Handler { class ListAssets : public Handler::Handler { public: - ListAssets (std::weak_ptr server); + ListAssets (const std::shared_ptr& server); void handle (Request& request) override; private: diff --git a/handler/login.cpp b/handler/login.cpp index e8c32a1..9a891d9 100644 --- a/handler/login.cpp +++ b/handler/login.cpp @@ -6,7 +6,7 @@ #include "server/server.h" #include "database/exceptions.h" -Handler::Login::Login(std::weak_ptr server): +Handler::Login::Login(const std::shared_ptr& server): Handler("login", Request::Method::post), server(server) {} diff --git a/handler/login.h b/handler/login.h index 47f0456..a91e0f4 100644 --- a/handler/login.h +++ b/handler/login.h @@ -12,7 +12,7 @@ namespace Handler { class Login : public Handler { public: - Login(std::weak_ptr server); + Login(const std::shared_ptr& server); void handle(Request& request) override; enum class Result { diff --git a/handler/mycurrencies.cpp b/handler/mycurrencies.cpp new file mode 100644 index 0000000..eba9e1b --- /dev/null +++ b/handler/mycurrencies.cpp @@ -0,0 +1,51 @@ +//SPDX-FileCopyrightText: 2024 Yury Gubich +//SPDX-License-Identifier: GPL-3.0-or-later + +#include "mycurrencies.h" + +#include "server/server.h" +#include "server/session.h" +#include "database/exceptions.h" + +Handler::MyCurrencies::MyCurrencies (const std::shared_ptr& server): + Handler("myCurrencies", Request::Method::get), + server(server) +{} + +void Handler::MyCurrencies::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 poll:\n\t" << e.what() << std::endl; + return error(request, Response::Status::internalError); + } catch (...) { + std::cerr << "Unknown exception on poll" << std::endl; + return error(request, Response::Status::internalError); + } +} diff --git a/handler/mycurrencies.h b/handler/mycurrencies.h new file mode 100644 index 0000000..021f9db --- /dev/null +++ b/handler/mycurrencies.h @@ -0,0 +1,21 @@ +//SPDX-FileCopyrightText: 2024 Yury Gubich +//SPDX-License-Identifier: GPL-3.0-or-later + +#pragma once + +#include + +#include "handler.h" + +class Server; +namespace Handler { +class MyCurrencies : public Handler { +public: + MyCurrencies(const std::shared_ptr& server); + + void handle (Request& request) override; + +private: + std::weak_ptr server; +}; +} diff --git a/handler/poll.cpp b/handler/poll.cpp index 1aa36d3..055e0fd 100644 --- a/handler/poll.cpp +++ b/handler/poll.cpp @@ -8,7 +8,7 @@ #include "request/redirect.h" #include "database/exceptions.h" -Handler::Poll::Poll (std::weak_ptr server): +Handler::Poll::Poll (const std::shared_ptr& server): Handler("poll", Request::Method::get), server(server) {} diff --git a/handler/poll.h b/handler/poll.h index a11db7a..e7a6cbd 100644 --- a/handler/poll.h +++ b/handler/poll.h @@ -14,7 +14,7 @@ namespace Handler { class Poll : public Handler { public: - Poll (std::weak_ptr server); + Poll (const std::shared_ptr& server); void handle (Request& request) override; enum class Result { diff --git a/handler/register.cpp b/handler/register.cpp index 203e376..1f2a651 100644 --- a/handler/register.cpp +++ b/handler/register.cpp @@ -6,7 +6,7 @@ #include "server/server.h" #include "database/exceptions.h" -Handler::Register::Register(std::weak_ptr server): +Handler::Register::Register(const std::shared_ptr& server): Handler("register", Request::Method::post), server(server) {} diff --git a/handler/register.h b/handler/register.h index 39fe000..327556a 100644 --- a/handler/register.h +++ b/handler/register.h @@ -12,7 +12,7 @@ namespace Handler { class Register : public Handler { public: - Register(std::weak_ptr server); + Register(const std::shared_ptr& server); void handle(Request& request) override; enum class Result { diff --git a/server/server.cpp b/server/server.cpp index 84c4e1a..8bb9d1e 100644 --- a/server/server.cpp +++ b/server/server.cpp @@ -15,6 +15,7 @@ #include "handler/listassets.h" #include "handler/addasset.h" #include "handler/deleteasset.h" +#include "handler/mycurrencies.h" #include "taskmanager/route.h" @@ -65,14 +66,17 @@ Server::Server (): 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(shared_from_this())); - router->addRoute(std::make_unique(shared_from_this())); - router->addRoute(std::make_unique(shared_from_this())); - router->addRoute(std::make_unique(shared_from_this())); - router->addRoute(std::make_unique(shared_from_this())); - router->addRoute(std::make_unique(shared_from_this())); + 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(); From 07003c2fe64237fa8865694d0edcee5b6a0be2ff Mon Sep 17 00:00:00 2001 From: blue Date: Thu, 28 Mar 2024 20:20:21 -0300 Subject: [PATCH 20/27] Currencies request debug, sign now is handled in statement results --- database/migrations/m0.sql | 4 ++-- database/mysql/mysql.cpp | 6 +++--- database/mysql/statement.cpp | 9 ++++++++- database/schema/asset.cpp | 2 +- handler/addasset.cpp | 4 ++-- handler/deleteasset.cpp | 4 ++-- handler/listassets.cpp | 4 ++-- handler/mycurrencies.cpp | 4 ++-- handler/poll.cpp | 4 ++-- 9 files changed, 24 insertions(+), 17 deletions(-) diff --git a/database/migrations/m0.sql b/database/migrations/m0.sql index b4bcaac..0695568 100644 --- a/database/migrations/m0.sql +++ b/database/migrations/m0.sql @@ -132,5 +132,5 @@ VALUES ('created', UTC_TIMESTAMP()); --creating default currencies INSERT IGNORE INTO -currencies (`code`, `title`, `manual`, `description`, `type`, `value`) -VALUES ('USD', 'United States Dollar', TRUE, 'Base currency', 0, 1); +currencies (`code`, `title`, `manual`, `description`, `type`, `value`, `icon`) +VALUES ('USD', 'United States Dollar', TRUE, 'Base currency', 1, 1, 'currency-usd'); diff --git a/database/mysql/mysql.cpp b/database/mysql/mysql.cpp index ea76b75..be6695f 100644 --- a/database/mysql/mysql.cpp +++ b/database/mysql/mysql.cpp @@ -27,8 +27,8 @@ constexpr const char* insertAsset = "INSERT INTO assets (`owner`, `currency`, `t " VALUES (?, ?, ?, ?, ?, ?, 1)"; constexpr const char* removeAsset = "DELETE FROM assets where `id` = ? AND `owner` = ?"; constexpr const char* selectUsedCurrencies = "SELECT c.id, c.code, c.title, c.manual, c.icon FROM currencies c" - "JOIN assets a ON c.id = a.currency" - "WHERE a.owner = ?"; + " JOIN assets a ON c.id = a.currency" + " WHERE a.owner = ?"; static const std::filesystem::path buildSQLPath = "database"; @@ -379,7 +379,7 @@ bool DB::MySQL::deleteAsset(uint32_t assetId, uint32_t actorId) { } std::vector DB::MySQL::listUsedCurrencies(uint32_t owner) { - Statement list(&connection, removeAsset); + Statement list(&connection, selectUsedCurrencies); list.bind(&owner, MYSQL_TYPE_LONG, true); list.execute(); diff --git a/database/mysql/statement.cpp b/database/mysql/statement.cpp index d770ce4..7b6a93d 100644 --- a/database/mysql/statement.cpp +++ b/database/mysql/statement.cpp @@ -114,13 +114,16 @@ std::vector> DB::MySQL::Statement::fetchResult() { 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; - while (mysql_stmt_fetch(raw) == 0) { + 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) { @@ -146,6 +149,10 @@ std::vector> DB::MySQL::Statement::fetchResult() { } } } + 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; } diff --git a/database/schema/asset.cpp b/database/schema/asset.cpp index d1ff7af..6187908 100644 --- a/database/schema/asset.cpp +++ b/database/schema/asset.cpp @@ -38,7 +38,7 @@ nlohmann::json DB::Asset::toJSON () const { result["id"] = id; //result["owner"] = owner; - //result["currency"] = currency; + result["currency"] = currency; result["title"] = title; result["icon"] = icon; result["color"] = color; diff --git a/handler/addasset.cpp b/handler/addasset.cpp index 69e5539..99a7bf3 100644 --- a/handler/addasset.cpp +++ b/handler/addasset.cpp @@ -69,10 +69,10 @@ void Handler::AddAsset::handle (Request& request) { } catch (const DB::NoSession& e) { return error(request, Response::Status::unauthorized); } catch (const std::exception& e) { - std::cerr << "Exception on poll:\n\t" << e.what() << std::endl; + std::cerr << "Exception on " << path << ":\n\t" << e.what() << std::endl; return error(request, Response::Status::internalError); } catch (...) { - std::cerr << "Unknown exception on poll" << std::endl; + std::cerr << "Unknown exception on " << path << std::endl; return error(request, Response::Status::internalError); } } diff --git a/handler/deleteasset.cpp b/handler/deleteasset.cpp index 0d35ba5..9d62463 100644 --- a/handler/deleteasset.cpp +++ b/handler/deleteasset.cpp @@ -50,10 +50,10 @@ void Handler::DeleteAsset::handle (Request& request) { } catch (const DB::NoSession& e) { return error(request, Response::Status::unauthorized); } catch (const std::exception& e) { - std::cerr << "Exception on poll:\n\t" << e.what() << std::endl; + std::cerr << "Exception on " << path << ":\n\t" << e.what() << std::endl; return error(request, Response::Status::internalError); } catch (...) { - std::cerr << "Unknown exception on poll" << std::endl; + std::cerr << "Unknown exception on " << path << std::endl; return error(request, Response::Status::internalError); } } diff --git a/handler/listassets.cpp b/handler/listassets.cpp index de5ace8..21a2825 100644 --- a/handler/listassets.cpp +++ b/handler/listassets.cpp @@ -42,10 +42,10 @@ void Handler::ListAssets::handle (Request& request) { } catch (const DB::NoSession& e) { return error(request, Response::Status::unauthorized); } catch (const std::exception& e) { - std::cerr << "Exception on poll:\n\t" << e.what() << std::endl; + std::cerr << "Exception on " << path << ":\n\t" << e.what() << std::endl; return error(request, Response::Status::internalError); } catch (...) { - std::cerr << "Unknown exception on poll" << std::endl; + std::cerr << "Unknown exception on " << path << std::endl; return error(request, Response::Status::internalError); } } diff --git a/handler/mycurrencies.cpp b/handler/mycurrencies.cpp index eba9e1b..549eef1 100644 --- a/handler/mycurrencies.cpp +++ b/handler/mycurrencies.cpp @@ -42,10 +42,10 @@ void Handler::MyCurrencies::handle (Request& request) { } catch (const DB::NoSession& e) { return error(request, Response::Status::unauthorized); } catch (const std::exception& e) { - std::cerr << "Exception on poll:\n\t" << e.what() << std::endl; + std::cerr << "Exception on " << path << ":\n\t" << e.what() << std::endl; return error(request, Response::Status::internalError); } catch (...) { - std::cerr << "Unknown exception on poll" << std::endl; + std::cerr << "Unknown exception on " << path << std::endl; return error(request, Response::Status::internalError); } } diff --git a/handler/poll.cpp b/handler/poll.cpp index 055e0fd..be92a33 100644 --- a/handler/poll.cpp +++ b/handler/poll.cpp @@ -33,10 +33,10 @@ void Handler::Poll::handle (Request& request) { } catch (const DB::NoSession& e) { return error(request, Result::tokenProblem, Response::Status::unauthorized); } catch (const std::exception& e) { - std::cerr << "Exception on poll:\n\t" << e.what() << std::endl; + 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 poll" << std::endl; + std::cerr << "Unknown exception on " << path << std::endl; return error(request, Result::unknownError, Response::Status::internalError); } } From a9f46b2ab0b61912e81beed41e9076396ae9bc53 Mon Sep 17 00:00:00 2001 From: blue Date: Fri, 29 Mar 2024 18:57:45 -0300 Subject: [PATCH 21/27] renamed methods --- handler/CMakeLists.txt | 8 ++-- handler/{listassets.cpp => assets.cpp} | 8 ++-- handler/assets.h | 20 ++++++++++ handler/currencies.cpp | 51 ++++++++++++++++++++++++++ handler/currencies.h | 21 +++++++++++ handler/listassets.h | 20 ---------- handler/mycurrencies.cpp | 51 -------------------------- handler/mycurrencies.h | 21 ----------- run.sh.in | 2 +- server/server.cpp | 8 ++-- 10 files changed, 105 insertions(+), 105 deletions(-) rename handler/{listassets.cpp => assets.cpp} (88%) create mode 100644 handler/assets.h create mode 100644 handler/currencies.cpp create mode 100644 handler/currencies.h diff --git a/handler/CMakeLists.txt b/handler/CMakeLists.txt index 1d3296b..a660a29 100644 --- a/handler/CMakeLists.txt +++ b/handler/CMakeLists.txt @@ -8,10 +8,10 @@ set(HEADERS register.h login.h poll.h - listassets.h + assets.h addasset.h deleteasset.h - mycurrencies.h + currencies.h ) set(SOURCES @@ -21,10 +21,10 @@ set(SOURCES register.cpp login.cpp poll.cpp - listassets.cpp + assets.cpp addasset.cpp deleteasset.cpp - mycurrencies.cpp + currencies.cpp ) target_sources(${PROJECT_NAME} PRIVATE ${SOURCES}) diff --git a/handler/listassets.cpp b/handler/assets.cpp similarity index 88% rename from handler/listassets.cpp rename to handler/assets.cpp index 21a2825..1c89118 100644 --- a/handler/listassets.cpp +++ b/handler/assets.cpp @@ -1,18 +1,18 @@ //SPDX-FileCopyrightText: 2024 Yury Gubich //SPDX-License-Identifier: GPL-3.0-or-later -#include "listassets.h" +#include "assets.h" #include "server/server.h" #include "server/session.h" #include "database/exceptions.h" -Handler::ListAssets::ListAssets (const std::shared_ptr& server): - Handler("listAssets", Request::Method::get), +Handler::Assets::Assets (const std::shared_ptr& server): + Handler("assets", Request::Method::get), server(server) {} -void Handler::ListAssets::handle (Request& request) { +void Handler::Assets::handle (Request& request) { std::string access = request.getAuthorizationToken(); if (access.empty()) return error(request, Response::Status::unauthorized); diff --git a/handler/assets.h b/handler/assets.h new file mode 100644 index 0000000..a1e19e8 --- /dev/null +++ b/handler/assets.h @@ -0,0 +1,20 @@ +//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 new file mode 100644 index 0000000..1c43c2a --- /dev/null +++ b/handler/currencies.cpp @@ -0,0 +1,51 @@ +//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 new file mode 100644 index 0000000..69c40c6 --- /dev/null +++ b/handler/currencies.h @@ -0,0 +1,21 @@ +//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/listassets.h b/handler/listassets.h index c806112..e69de29 100644 --- a/handler/listassets.h +++ b/handler/listassets.h @@ -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 ListAssets : public Handler::Handler { -public: - ListAssets (const std::shared_ptr& server); - void handle (Request& request) override; - -private: - std::weak_ptr server; -}; -} diff --git a/handler/mycurrencies.cpp b/handler/mycurrencies.cpp index 549eef1..e69de29 100644 --- a/handler/mycurrencies.cpp +++ b/handler/mycurrencies.cpp @@ -1,51 +0,0 @@ -//SPDX-FileCopyrightText: 2024 Yury Gubich -//SPDX-License-Identifier: GPL-3.0-or-later - -#include "mycurrencies.h" - -#include "server/server.h" -#include "server/session.h" -#include "database/exceptions.h" - -Handler::MyCurrencies::MyCurrencies (const std::shared_ptr& server): - Handler("myCurrencies", Request::Method::get), - server(server) -{} - -void Handler::MyCurrencies::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/mycurrencies.h b/handler/mycurrencies.h index 021f9db..e69de29 100644 --- a/handler/mycurrencies.h +++ b/handler/mycurrencies.h @@ -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 MyCurrencies : public Handler { -public: - MyCurrencies(const std::shared_ptr& server); - - void handle (Request& request) override; - -private: - std::weak_ptr server; -}; -} diff --git a/run.sh.in b/run.sh.in index be5f016..d6eecee 100644 --- a/run.sh.in +++ b/run.sh.in @@ -17,7 +17,7 @@ start_service() { } if [ ! -d "/run/pica" ]; then - echo "reuired unix socket was not found, going to use sudo to create it" + 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 diff --git a/server/server.cpp b/server/server.cpp index 8bb9d1e..5964e5e 100644 --- a/server/server.cpp +++ b/server/server.cpp @@ -12,10 +12,10 @@ #include "handler/register.h" #include "handler/login.h" #include "handler/poll.h" -#include "handler/listassets.h" +#include "handler/assets.h" #include "handler/addasset.h" #include "handler/deleteasset.h" -#include "handler/mycurrencies.h" +#include "handler/currencies.h" #include "taskmanager/route.h" @@ -73,10 +73,10 @@ void Server::run (int socketDescriptor) { 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(); From 4914a467e5cb6b7e1cc48ac22549bcc1da413864 Mon Sep 17 00:00:00 2001 From: blue Date: Sun, 7 Apr 2024 20:03:10 -0300 Subject: [PATCH 22/27] Small fixes, update asset method --- database/interface.h | 1 + database/mysql/mysql.cpp | 19 ++++++++- database/mysql/mysql.h | 1 + handler/CMakeLists.txt | 2 + handler/listassets.h | 0 handler/mycurrencies.cpp | 0 handler/mycurrencies.h | 0 handler/updateasset.cpp | 84 ++++++++++++++++++++++++++++++++++++++++ handler/updateasset.h | 20 ++++++++++ run.sh.in | 2 +- server/server.cpp | 2 + server/session.cpp | 44 +++++++++++++++++++-- server/session.h | 3 ++ 13 files changed, 172 insertions(+), 6 deletions(-) delete mode 100644 handler/listassets.h delete mode 100644 handler/mycurrencies.cpp delete mode 100644 handler/mycurrencies.h create mode 100644 handler/updateasset.cpp create mode 100644 handler/updateasset.h diff --git a/database/interface.h b/database/interface.h index 5173234..9c5c0c4 100644 --- a/database/interface.h +++ b/database/interface.h @@ -48,6 +48,7 @@ public: 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; diff --git a/database/mysql/mysql.cpp b/database/mysql/mysql.cpp index be6695f..2ae8770 100644 --- a/database/mysql/mysql.cpp +++ b/database/mysql/mysql.cpp @@ -25,8 +25,10 @@ constexpr const char* selectSession = "SELECT id, owner, access, renew FROM sess 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 c.id, c.code, c.title, c.manual, c.icon FROM currencies c" +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 = ?"; @@ -366,6 +368,21 @@ DB::Asset DB::MySQL::addAsset(const Asset& asset) { 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); diff --git a/database/mysql/mysql.h b/database/mysql/mysql.h index 81bdcf5..bd703bb 100644 --- a/database/mysql/mysql.h +++ b/database/mysql/mysql.h @@ -36,6 +36,7 @@ public: 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; diff --git a/handler/CMakeLists.txt b/handler/CMakeLists.txt index a660a29..587a51c 100644 --- a/handler/CMakeLists.txt +++ b/handler/CMakeLists.txt @@ -11,6 +11,7 @@ set(HEADERS assets.h addasset.h deleteasset.h + updateasset.h currencies.h ) @@ -24,6 +25,7 @@ set(SOURCES assets.cpp addasset.cpp deleteasset.cpp + updateasset.cpp currencies.cpp ) diff --git a/handler/listassets.h b/handler/listassets.h deleted file mode 100644 index e69de29..0000000 diff --git a/handler/mycurrencies.cpp b/handler/mycurrencies.cpp deleted file mode 100644 index e69de29..0000000 diff --git a/handler/mycurrencies.h b/handler/mycurrencies.h deleted file mode 100644 index e69de29..0000000 diff --git a/handler/updateasset.cpp b/handler/updateasset.cpp new file mode 100644 index 0000000..af06610 --- /dev/null +++ b/handler/updateasset.cpp @@ -0,0 +1,84 @@ +//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 new file mode 100644 index 0000000..33ea6e4 --- /dev/null +++ b/handler/updateasset.h @@ -0,0 +1,20 @@ +//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/run.sh.in b/run.sh.in index d6eecee..53b7203 100644 --- a/run.sh.in +++ b/run.sh.in @@ -25,4 +25,4 @@ fi start_service "mariadb" start_service "httpd" -./@PROJECT_NAME@ +$(dirname "$0")/@PROJECT_NAME@ diff --git a/server/server.cpp b/server/server.cpp index 5964e5e..ae8a962 100644 --- a/server/server.cpp +++ b/server/server.cpp @@ -16,6 +16,7 @@ #include "handler/addasset.h" #include "handler/deleteasset.h" #include "handler/currencies.h" +#include "handler/updateasset.h" #include "taskmanager/route.h" @@ -77,6 +78,7 @@ void Server::run (int socketDescriptor) { 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(); diff --git a/server/session.cpp b/server/session.cpp index 3e29d14..c9c38e5 100644 --- a/server/session.cpp +++ b/server/session.cpp @@ -110,17 +110,53 @@ void Session::assetAdded (const DB::Asset& asset) { 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 addedItr = assets.find("removed"); - if (addedItr == assets.end()) - addedItr = assets.emplace("removed", nlohmann::json::array()).first; + 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); - addedItr->second.push_back(assetId); 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) { diff --git a/server/session.h b/server/session.h index 75c55ca..b82ad3a 100644 --- a/server/session.h +++ b/server/session.h @@ -38,6 +38,7 @@ public: const unsigned int owner; void assetAdded (const DB::Asset& asset); + void assetChanged (const DB::Asset& asset); void assetRemoved (unsigned int assetId); private: @@ -45,6 +46,8 @@ private: 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; From 973deaefd980f00d28983227672f403198acfe83 Mon Sep 17 00:00:00 2001 From: blue Date: Wed, 10 Apr 2024 20:09:45 -0300 Subject: [PATCH 23/27] First ideas over transaction --- database/interface.h | 8 ++++++ database/mysql/mysql.cpp | 31 ++++++++++++++++++++ database/mysql/mysql.h | 7 +++++ database/schema/CMakeLists.txt | 2 ++ database/schema/transaction.cpp | 50 +++++++++++++++++++++++++++++++++ database/schema/transaction.h | 35 +++++++++++++++++++++++ 6 files changed, 133 insertions(+) create mode 100644 database/schema/transaction.cpp create mode 100644 database/schema/transaction.h diff --git a/database/interface.h b/database/interface.h index 9c5c0c4..097474d 100644 --- a/database/interface.h +++ b/database/interface.h @@ -12,6 +12,7 @@ #include "schema/session.h" #include "schema/asset.h" #include "schema/currency.h" +#include "schema/transaction.h" namespace DB { class Interface { @@ -44,14 +45,21 @@ public: 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 void updateTransaction(const DB::Transaction& transaction) = 0; + virtual std::vector listTransactions(uint32_t owner) = 0; + protected: Interface(Type type); diff --git a/database/mysql/mysql.cpp b/database/mysql/mysql.cpp index 2ae8770..296d37b 100644 --- a/database/mysql/mysql.cpp +++ b/database/mysql/mysql.cpp @@ -31,6 +31,10 @@ constexpr const char* removeAsset = "DELETE FROM assets where `id` = ? AND `owne 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, ?, ?, ?, ?)"; + static const std::filesystem::path buildSQLPath = "database"; @@ -409,3 +413,30 @@ std::vector DB::MySQL::listUsedCurrencies(uint32_t owner) { 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(); + //todo retreive timestamp and actual value which could have changed after insertion + + return result; +} + +void DB::MySQL::updateTransaction(const DB::Transaction& transaction) { +} + +std::vector DB::MySQL::listTransactions(uint32_t owner) { + return std::vector(); +} diff --git a/database/mysql/mysql.h b/database/mysql/mysql.h index bd703bb..273a9e8 100644 --- a/database/mysql/mysql.h +++ b/database/mysql/mysql.h @@ -32,14 +32,21 @@ public: 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; + void updateTransaction(const DB::Transaction& transaction) override; + std::vector listTransactions(uint32_t owner) override; + private: void executeFile (const std::filesystem::path& relativePath); static std::optional getComment (std::string& string); diff --git a/database/schema/CMakeLists.txt b/database/schema/CMakeLists.txt index 94706fd..776b4fb 100644 --- a/database/schema/CMakeLists.txt +++ b/database/schema/CMakeLists.txt @@ -5,12 +5,14 @@ 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/transaction.cpp b/database/schema/transaction.cpp new file mode 100644 index 0000000..9d90429 --- /dev/null +++ b/database/schema/transaction.cpp @@ -0,0 +1,50 @@ +//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 new file mode 100644 index 0000000..754f0eb --- /dev/null +++ b/database/schema/transaction.h @@ -0,0 +1,35 @@ +//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 From c2d4bf5ccb71aa4d802f07a9bd37d24234cc2660 Mon Sep 17 00:00:00 2001 From: blue Date: Sun, 14 Apr 2024 21:16:36 -0300 Subject: [PATCH 24/27] Changed database structure from default UTC_TIMESTAMP() which are not supposed to be supported to triggers update transaction database method --- database/migrations/m0.sql | 76 ++++++++++++++++++++++++---- database/mysql/mysql.cpp | 101 +++++++++++++++++++++++-------------- database/mysql/mysql.h | 2 +- 3 files changed, 130 insertions(+), 49 deletions(-) diff --git a/database/migrations/m0.sql b/database/migrations/m0.sql index 0695568..0e6b2a5 100644 --- a/database/migrations/m0.sql +++ b/database/migrations/m0.sql @@ -18,7 +18,7 @@ CREATE TABLE IF NOT EXISTS accounts ( `nick` VARCHAR(256), `type` INTEGER UNSIGNED NOT NULL, `password` VARCHAR(128), - `created` TIMESTAMP DEFAULT UTC_TIMESTAMP() + `created` TIMESTAMP ); --creating role bindings table @@ -31,12 +31,12 @@ CREATE TABLE IF NOT EXISTS roleBindings ( FOREIGN KEY (role) REFERENCES roles(id) ); ---creating sessings table +--creating sessions table CREATE TABLE IF NOT EXISTS sessions ( `id` INTEGER UNSIGNED AUTO_INCREMENT PRIMARY KEY, `owner` INTEGER UNSIGNED NOT NULL, - `started` TIMESTAMP DEFAULT UTC_TIMESTAMP(), - `latest` TIMESTAMP DEFAULT UTC_TIMESTAMP(), + `started` TIMESTAMP, + `latest` TIMESTAMP, `access` CHAR(32) NOT NULL UNIQUE, `renew` CHAR(32), `persist` BOOLEAN NOT NULL, @@ -51,7 +51,7 @@ CREATE TABLE IF NOT EXISTS currencies ( `code` VARCHAR(16) NOT NULL UNIQUE, `title` VARCHAR(256), `manual` BOOLEAN NOT NULL, - `added` TIMESTAMP DEFAULT UTC_TIMESTAMP(), + `created` TIMESTAMP, `type` INTEGER UNSIGNED NOT NULL, `value` DECIMAL (20, 5) NOT NULL, `source` TEXT, @@ -61,7 +61,7 @@ CREATE TABLE IF NOT EXISTS currencies ( INDEX manual_idx (manual) ); ---creating assests table +--creating assets table CREATE TABLE IF NOT EXISTS assets ( `id` INTEGER UNSIGNED AUTO_INCREMENT PRIMARY KEY, `owner` INTEGER UNSIGNED NOT NULL, @@ -72,7 +72,7 @@ CREATE TABLE IF NOT EXISTS assets ( `balance` DECIMAL (20, 5) DEFAULT 0, `type` INTEGER UNSIGNED NOT NULL, `archived` BOOLEAN DEFAULT FALSE, - `created` TIMESTAMP DEFAULT UTC_TIMESTAMP(), + `created` TIMESTAMP, INDEX owner_idx (owner), INDEX archived_idx (archived), @@ -96,8 +96,8 @@ CREATE TABLE IF NOT EXISTS transactions ( `parent` INTEGER UNSIGNED, `value` DECIMAL (20, 5) NOT NULL, `state` INTEGER UNSIGNED DEFAULT 0, - `modified` TIMESTAMP DEFAULT UTC_TIMESTAMP(), - `performed` TIMESTAMP DEFAULT UTC_TIMESTAMP(), + `modified` TIMESTAMP, + `performed` TIMESTAMP, `party` INTEGER UNSIGNED, `notes` TEXT, @@ -114,7 +114,63 @@ CREATE TABLE IF NOT EXISTS transactions ( FOREIGN KEY (party) REFERENCES parties(id) ); ---creating defailt roles +--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'), diff --git a/database/mysql/mysql.cpp b/database/mysql/mysql.cpp index 296d37b..d6fb7e3 100644 --- a/database/mysql/mysql.cpp +++ b/database/mysql/mysql.cpp @@ -5,6 +5,7 @@ #include #include +#include #include "mysqld_error.h" @@ -34,6 +35,10 @@ constexpr const char* selectUsedCurrencies = "SELECT DISTINCT c.id, c.code, c.ti 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` = ?"; static const std::filesystem::path buildSQLPath = "database"; @@ -131,17 +136,15 @@ void DB::MySQL::executeFile (const std::filesystem::path& relativePath) { std::cout << "Executing file " << path << std::endl; std::ifstream inputFile(path); - std::string query; - while (std::getline(inputFile, query, ';')) { - std::optional comment = getComment(query); - while (comment) { - std::cout << '\t' << comment.value() << std::endl; - comment = getComment(query); - } - if (query.empty()) + 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, query.c_str()); + int result = mysql_query(con, block.c_str()); if (result != 0) { int errcode = mysql_errno(con); if (errcode == ER_EMPTY_QUERY) @@ -153,6 +156,39 @@ 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 () { MYSQL* con = &connection; int result = mysql_query(con, versionQuery); @@ -205,34 +241,6 @@ void DB::MySQL::migrate (uint8_t targetVersion) { std::cout << "Database is now on actual version " << std::to_string(targetVersion) << std::endl; } -std::optional DB::MySQL::getComment (std::string& string) { - ltrim(string); - if (string.length() < 2) - return std::nullopt; - - if (string[0] == '-') { - if (string[1] == '-') { - string.erase(0, 2); - std::string::size_type eol = string.find('\n'); - return extract(string, 0, eol); - } - } else if (string[0] == '/') { - if (string[1] == '*') { - string.erase(0, 2); - std::string::size_type end = 0; - do { - end = string.find(end, '*'); - } while (end != std::string::npos && end < string.size() - 1 && string[end + 1] == '/'); - if (end < string.size() - 1) - end = std::string::npos; - - return extract(string, 0, end); - } - } - - return std::nullopt; -} - uint32_t DB::MySQL::registerAccount (const std::string& login, const std::string& hash) { //TODO validate filed lengths! MYSQL* con = &connection; @@ -429,12 +437,29 @@ DB::Transaction DB::MySQL::addTransaction(const DB::Transaction& transaction) { add.execute(); result.id = lastInsertedId(); - //todo retreive timestamp and actual value which could have changed after insertion + 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(); } std::vector DB::MySQL::listTransactions(uint32_t owner) { diff --git a/database/mysql/mysql.h b/database/mysql/mysql.h index 273a9e8..2bc92e4 100644 --- a/database/mysql/mysql.h +++ b/database/mysql/mysql.h @@ -49,7 +49,7 @@ public: private: void executeFile (const std::filesystem::path& relativePath); - static std::optional getComment (std::string& string); + bool getBlock (std::ifstream& file, std::string& block, std::string& name); uint32_t lastInsertedId (); protected: From e1d5b6c76c379125eddeefead936e130f749f7b4 Mon Sep 17 00:00:00 2001 From: blue Date: Mon, 15 Apr 2024 19:13:22 -0300 Subject: [PATCH 25/27] Add transaction handler, delete transaction and list transactions queries, session methods --- database/interface.h | 3 +- database/mysql/mysql.cpp | 39 ++++++++++++++++++- database/mysql/mysql.h | 3 +- handler/CMakeLists.txt | 2 + handler/addtransaction.cpp | 78 ++++++++++++++++++++++++++++++++++++++ handler/addtransaction.h | 20 ++++++++++ server/server.cpp | 2 + server/session.cpp | 47 +++++++++++++++++++++++ server/session.h | 5 +++ 9 files changed, 195 insertions(+), 4 deletions(-) create mode 100644 handler/addtransaction.cpp create mode 100644 handler/addtransaction.h diff --git a/database/interface.h b/database/interface.h index 097474d..1f1aee2 100644 --- a/database/interface.h +++ b/database/interface.h @@ -57,8 +57,9 @@ public: virtual std::vector listUsedCurrencies(uint32_t owner) = 0; virtual DB::Transaction addTransaction(const DB::Transaction& transaction) = 0; - virtual void updateTransaction(const DB::Transaction& transaction) = 0; virtual std::vector listTransactions(uint32_t owner) = 0; + virtual void updateTransaction(const DB::Transaction& transaction) = 0; + virtual void deleteTransaction(uint32_t id) = 0; protected: Interface(Type type); diff --git a/database/mysql/mysql.cpp b/database/mysql/mysql.cpp index d6fb7e3..4707352 100644 --- a/database/mysql/mysql.cpp +++ b/database/mysql/mysql.cpp @@ -39,6 +39,19 @@ 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` = ?"; +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"; @@ -462,6 +475,28 @@ void DB::MySQL::updateTransaction(const DB::Transaction& transaction) { upd.execute(); } -std::vector DB::MySQL::listTransactions(uint32_t owner) { - return std::vector(); +void DB::MySQL::deleteTransaction(uint32_t id) { + 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.execute(); //need to think of a parent with no children transactions... +} + +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 2bc92e4..d7962df 100644 --- a/database/mysql/mysql.h +++ b/database/mysql/mysql.h @@ -44,8 +44,9 @@ public: std::vector listUsedCurrencies(uint32_t owner) override; DB::Transaction addTransaction(const DB::Transaction& transaction) override; - void updateTransaction(const DB::Transaction& transaction) override; std::vector listTransactions(uint32_t owner) override; + void updateTransaction(const DB::Transaction& transaction) override; + void deleteTransaction(uint32_t id) override; private: void executeFile (const std::filesystem::path& relativePath); diff --git a/handler/CMakeLists.txt b/handler/CMakeLists.txt index 587a51c..91bd69f 100644 --- a/handler/CMakeLists.txt +++ b/handler/CMakeLists.txt @@ -13,6 +13,7 @@ set(HEADERS deleteasset.h updateasset.h currencies.h + addtransaction.h ) set(SOURCES @@ -27,6 +28,7 @@ set(SOURCES deleteasset.cpp updateasset.cpp currencies.cpp + addtransaction.cpp ) target_sources(${PROJECT_NAME} PRIVATE ${SOURCES}) diff --git a/handler/addtransaction.cpp b/handler/addtransaction.cpp new file mode 100644 index 0000000..7c6ebee --- /dev/null +++ b/handler/addtransaction.cpp @@ -0,0 +1,78 @@ +//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 new file mode 100644 index 0000000..b23e167 --- /dev/null +++ b/handler/addtransaction.h @@ -0,0 +1,20 @@ +//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/server/server.cpp b/server/server.cpp index ae8a962..cfbe9ef 100644 --- a/server/server.cpp +++ b/server/server.cpp @@ -17,6 +17,7 @@ #include "handler/deleteasset.h" #include "handler/currencies.h" #include "handler/updateasset.h" +#include "handler/addtransaction.h" #include "taskmanager/route.h" @@ -79,6 +80,7 @@ void Server::run (int socketDescriptor) { 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(); diff --git a/server/session.cpp b/server/session.cpp index c9c38e5..ae01244 100644 --- a/server/session.cpp +++ b/server/session.cpp @@ -144,6 +144,53 @@ void Session::assetRemoved (unsigned int 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( diff --git a/server/session.h b/server/session.h index b82ad3a..a5329ac 100644 --- a/server/session.h +++ b/server/session.h @@ -13,6 +13,7 @@ #include "taskmanager/scheduler.h" #include "database/schema/asset.h" +#include "database/schema/transaction.h" class Session : public Accepting { public: @@ -41,6 +42,10 @@ public: 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); From 7c4adaf45043212adb96f25170c7fd228c5bf9ac Mon Sep 17 00:00:00 2001 From: blue Date: Wed, 17 Apr 2024 18:37:15 -0300 Subject: [PATCH 26/27] update delete and list transaction requests --- database/interface.h | 2 +- database/mysql/mysql.cpp | 10 ++++- database/mysql/mysql.h | 2 +- handler/CMakeLists.txt | 6 +++ handler/deletetransaction.cpp | 59 ++++++++++++++++++++++++ handler/deletetransaction.h | 21 +++++++++ handler/transactions.cpp | 51 +++++++++++++++++++++ handler/transactions.h | 20 +++++++++ handler/updatetransaction.cpp | 84 +++++++++++++++++++++++++++++++++++ handler/updatetransaction.h | 20 +++++++++ server/server.cpp | 6 +++ 11 files changed, 277 insertions(+), 4 deletions(-) create mode 100644 handler/deletetransaction.cpp create mode 100644 handler/deletetransaction.h create mode 100644 handler/transactions.cpp create mode 100644 handler/transactions.h create mode 100644 handler/updatetransaction.cpp create mode 100644 handler/updatetransaction.h diff --git a/database/interface.h b/database/interface.h index 1f1aee2..a0c2821 100644 --- a/database/interface.h +++ b/database/interface.h @@ -59,7 +59,7 @@ public: 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 void deleteTransaction(uint32_t id) = 0; + virtual bool deleteTransaction(uint32_t id, uint32_t actorId) = 0; protected: Interface(Type type); diff --git a/database/mysql/mysql.cpp b/database/mysql/mysql.cpp index 4707352..ab8b004 100644 --- a/database/mysql/mysql.cpp +++ b/database/mysql/mysql.cpp @@ -39,7 +39,7 @@ 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` = ?"; +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" @@ -475,13 +475,19 @@ void DB::MySQL::updateTransaction(const DB::Transaction& transaction) { upd.execute(); } -void DB::MySQL::deleteTransaction(uint32_t id) { +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) { diff --git a/database/mysql/mysql.h b/database/mysql/mysql.h index d7962df..a739fa6 100644 --- a/database/mysql/mysql.h +++ b/database/mysql/mysql.h @@ -46,7 +46,7 @@ public: DB::Transaction addTransaction(const DB::Transaction& transaction) override; std::vector listTransactions(uint32_t owner) override; void updateTransaction(const DB::Transaction& transaction) override; - void deleteTransaction(uint32_t id) override; + bool deleteTransaction(uint32_t id, uint32_t actorId) override; private: void executeFile (const std::filesystem::path& relativePath); diff --git a/handler/CMakeLists.txt b/handler/CMakeLists.txt index 91bd69f..1971c0e 100644 --- a/handler/CMakeLists.txt +++ b/handler/CMakeLists.txt @@ -14,6 +14,9 @@ set(HEADERS updateasset.h currencies.h addtransaction.h + transactions.h + deletetransaction.h + updatetransaction.h ) set(SOURCES @@ -29,6 +32,9 @@ set(SOURCES updateasset.cpp currencies.cpp addtransaction.cpp + transactions.cpp + deletetransaction.cpp + updatetransaction.cpp ) target_sources(${PROJECT_NAME} PRIVATE ${SOURCES}) diff --git a/handler/deletetransaction.cpp b/handler/deletetransaction.cpp new file mode 100644 index 0000000..6d73529 --- /dev/null +++ b/handler/deletetransaction.cpp @@ -0,0 +1,59 @@ +//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 new file mode 100644 index 0000000..b1b6ab3 --- /dev/null +++ b/handler/deletetransaction.h @@ -0,0 +1,21 @@ +//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/transactions.cpp b/handler/transactions.cpp new file mode 100644 index 0000000..c7d574a --- /dev/null +++ b/handler/transactions.cpp @@ -0,0 +1,51 @@ +//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 new file mode 100644 index 0000000..21c016f --- /dev/null +++ b/handler/transactions.h @@ -0,0 +1,20 @@ +//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/updatetransaction.cpp b/handler/updatetransaction.cpp new file mode 100644 index 0000000..f27ef7e --- /dev/null +++ b/handler/updatetransaction.cpp @@ -0,0 +1,84 @@ +//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 new file mode 100644 index 0000000..3fc68dd --- /dev/null +++ b/handler/updatetransaction.h @@ -0,0 +1,20 @@ +//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/server/server.cpp b/server/server.cpp index cfbe9ef..bb5191a 100644 --- a/server/server.cpp +++ b/server/server.cpp @@ -18,6 +18,9 @@ #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" @@ -81,6 +84,9 @@ void Server::run (int socketDescriptor) { 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(); From ccb5f00f69980270103e0f4f9d2724b8dda7feb3 Mon Sep 17 00:00:00 2001 From: "m.podoprelov" Date: Fri, 8 Dec 2023 19:36:16 +0300 Subject: [PATCH 27/27] add default.conf --- default.conf | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) create mode 100644 default.conf diff --git a/default.conf b/default.conf new file mode 100644 index 0000000..30c2c0d --- /dev/null +++ b/default.conf @@ -0,0 +1,22 @@ +# This is a default autogenerated Pica config +# The syntax is +# Only the first occasion of the valid key value pair is taken to consideration +# +# Use '#' sign for comments +# +# All printed comented out values are default values +# +# You should have one of this files as ~/.config/pica.conf, +# if you have started mlc at least once. +# This is going to be used every time you launch mlc, +# so, edit it if you wish to change default behaviour. +# +# Alternatively, you can launch `pica -c customConfig.conf` +# if you wish to override ~/.config/pica.conf file + +# Parallel tasks +# Defines how many threads are going to be started in parralel +# Allowed values are [0, 1, 2, 3 ...] etc +# If it's set to 0 - amount of threads is going to be +# as high as your processor can effectively handle +#parallel 0