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();