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