From d33ec5def8e00dbc88f96c405a2f3609888c9258 Mon Sep 17 00:00:00 2001 From: blue Date: Thu, 11 Jan 2024 18:33:46 -0300 Subject: [PATCH] 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