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