diff --git a/include/bank.h b/include/bank.h index 9f28efb..497d3ab 100644 --- a/include/bank.h +++ b/include/bank.h @@ -3,10 +3,13 @@ #include #include #include +#include #include "error_responses.hpp" #include "parallel-hashmap/parallel_hashmap/phmap.h" #include "user.h" +using BankResponse = std::pair; + class Bank { private: @@ -34,35 +37,32 @@ private: */ std::shared_mutex send_funds_l; + void ChangesMade() noexcept; //called after making changes + void ChangesSaved() noexcept; //called after saving public: std::string admin_pass; - void ChangesMade() noexcept; //called after making changes - void ChangesSaved() noexcept; //called after saving bool GetChangeState() noexcept; + BankResponse GetBal(const std::string &name) const noexcept; + BankResponse GetLogs(const std::string &name) noexcept; + BankResponse SendFunds(const std::string &a_name, const std::string &b_name, uint32_t amount) noexcept; + bool VerifyPassword(const std::string &name, const std::string &attempt) const noexcept; //internally used + int_fast8_t AddUser(const std::string &name, const std::string &init_pass) noexcept; int_fast8_t AdminAddUser(const std::string &attempt, std::string &&name, uint32_t init_bal, std::string &&init_pass) noexcept; int_fast8_t DelUser(const std::string &name, const std::string &attempt) noexcept; int_fast8_t AdminDelUser(const std::string &name, const std::string &attempt) noexcept; - int_fast8_t SendFunds(const std::string &a_name, const std::string &b_name, uint32_t amount, const std::string &attempt) noexcept; - - int_fast8_t Contains(const std::string &name) const noexcept; + bool Contains(const std::string &name) const noexcept; //done int_fast8_t AdminVerifyPass(const std::string &attempt) noexcept; int_fast8_t SetBal(const std::string &name, const std::string &attempt, uint32_t amount) noexcept; - int_fast64_t GetBal(const std::string &name) const noexcept; - int_fast8_t VerifyPassword(const std::string &name, const std::string &attempt) const noexcept; int_fast8_t ChangePassword(const std::string &name, const std::string &attempt, std::string &&new_pass) noexcept; - Json::Value GetLogs(const std::string &name, const std::string &attempt) noexcept; - void Save(); - - //NOT THREAD SAFE void Load(); }; diff --git a/include/bank_api.h b/include/bank_api.h index 6c4176e..b95db67 100644 --- a/include/bank_api.h +++ b/include/bank_api.h @@ -1,15 +1,10 @@ #pragma once #include -#include "bank.h" +#include "user_filter.h" using namespace drogon; #define req_args const HttpRequestPtr &req, std::function &&callback -#define JSON(V) callback(HttpResponse::newHttpJsonResponse(JsonCast(V))); -#define PASS_HEADER req->getHeader("Password") //temporary -#define GEN_BODY \ - const auto temp_req = req->getJsonObject(); \ - const auto body = temp_req ? *temp_req : Json::Value(); namespace v1 { @@ -19,6 +14,11 @@ namespace v1 public: api(Bank &b); + void GetBal(req_args) const; + void GetLog(req_args, const std::string &name); + void SendFunds(req_args, const std::string name) const; + void VerifyPassword(req_args) const; + void Help(req_args) const; void Ping(req_args) const; void Close(req_args) const; @@ -26,22 +26,18 @@ namespace v1 void AdminAddUser(req_args, std::string &&name, uint32_t init_bal) const; void DelUser(req_args, const std::string &name) const; void AdminDelUser(req_args, const std::string &name) const; - void SendFunds(req_args, const std::string name, const std::string to, uint32_t amount) const; void ChangePassword(req_args, const std::string &name) const; void Contains(req_args, const std::string &name) const; - void GetBal(req_args, const std::string &name) const; - void VerifyPassword(req_args, const std::string &name) const; void SetBal(req_args, const std::string &name, uint32_t amount) const; void AdminVerifyPass(req_args); - void GetLog(req_args, const std::string &name); METHOD_LIST_BEGIN //Usage - METHOD_ADD(api::GetBal, "/{name}/bal", Get, Options); - METHOD_ADD(api::GetLog, "/{name}/log", Get, Options); - METHOD_ADD(api::SendFunds, "/{name}/send/{to}?amount={amount}", Post, Options); - METHOD_ADD(api::VerifyPassword, "/{name}/pass/verify", Get, Options); + METHOD_ADD(api::GetBal, "/user/bal", Get, Options); //done + METHOD_ADD(api::GetLog, "/user/log", Get, Options, "UserFilter"); //snapshot not implemented + METHOD_ADD(api::SendFunds, "/user/transfer", Post, Options, "UserFilter"); //responses incomplete + METHOD_ADD(api::VerifyPassword, "/{name}/pass/verify", Get, Options); //done //Meta Usage METHOD_ADD(api::ChangePassword, "/{name}/pass/change", Patch, Options); diff --git a/src/bank.cpp b/src/bank.cpp index e1933cc..be4fdf7 100644 --- a/src/bank.cpp +++ b/src/bank.cpp @@ -1,22 +1,24 @@ #include "bank.h" +using namespace drogon; + void Bank::ChangesMade() noexcept { - if constexpr (conservative_disk_save) + if constexpr (CONSERVATIVE_DISK_SAVE) { return change_flag.store(1, std::memory_order_release); } } void Bank::ChangesSaved() noexcept { - if constexpr (conservative_disk_save) + if constexpr (CONSERVATIVE_DISK_SAVE) { - return change_flag.store(1, std::memory_order_release); + return change_flag.store(0, std::memory_order_release); } } bool Bank::GetChangeState() noexcept { - if constexpr (conservative_disk_save) + if constexpr (CONSERVATIVE_DISK_SAVE) { return change_flag.load(std::memory_order_acquire); } @@ -26,6 +28,112 @@ bool Bank::GetChangeState() noexcept } } +BankResponse Bank::GetBal(const std::string &name) const noexcept +{ + BankResponse res = {k404NotFound, "User not found"}; + users.if_contains(name, [&res](const User &u) { + res = {k200OK, u.balance}; + }); + return res; +} +BankResponse Bank::GetLogs(const std::string &name) noexcept +{ + BankResponse res{k404NotFound, "User not found"}; + users.if_contains(name, [&res](const User &u) { + Json::Value temp; + for (uint32_t i = u.log.data.size(); i > 0; --i) + { + temp[i - 1]["to"] = u.log.data[u.log.data.size() - i].to; + temp[i - 1]["from"] = u.log.data[u.log.data.size() - i].from; + temp[i - 1]["amount"] = (Json::UInt)u.log.data[u.log.data.size() - i].amount; + temp[i - 1]["time"] = (Json::UInt64)u.log.data[u.log.data.size() - i].time; + } + res = {HttpStatusCode::k200OK, std::move(temp)}; + }); + return res; +} +BankResponse Bank::SendFunds(const std::string &a_name, const std::string &b_name, uint32_t amount) noexcept +{ + //cant send money to self, from self or amount is 0 + if (a_name == b_name) + { + return {k400BadRequest, "Sender and Reciever names cannot match"}; + } + + //cant send 0 + if (!amount) + { + return {k400BadRequest, "Amount being sent cannot be 0"}; + } + + //as first modify_if checks a_name and grabs unique lock + if (!Contains(b_name)) + { + return {k404NotFound, "Reciever does not exist"}; + } + + BankResponse state = {k404NotFound, "Sender does not exist"}; + if constexpr (max_log_size > 0) + { + Transaction temp(a_name, b_name, amount); + std::shared_lock lock{send_funds_l}; + users.modify_if(a_name, [this, &temp, &state, amount](User &a) { + //if A can afford it and A's password matches attempt + if (a.balance < amount) + { + state = {ErrorResponse::InsufficientFunds, "Sender has insufficient funds"}; + } + else + { + a.balance -= amount; + a.log.AddTrans(Transaction(temp)); + state = {HttpStatusCode::k200OK, "Transfer successful!"}; + } + }); + if (state.first == HttpStatusCode::k200OK) + { + users.modify_if(b_name, [&a_name, &b_name, &temp, amount](User &b) { + b.balance += amount; + b.log.AddTrans(std::move(temp)); + }); + ChangesMade(); + } + return state; + } + else + { + std::shared_lock lock{send_funds_l}; + users.modify_if(a_name, [this, &state, amount](User &a) { + //if A can afford it and A's password matches attempt + if (a.balance < amount) + { + state = {ErrorResponse::InsufficientFunds, "Sender has insufficient funds"}; + } + else + { + a.balance -= amount; + state = {k200OK, "Transfer successful!"}; + } + }); + if (state.first == k200OK) + { + users.modify_if(b_name, [&a_name, &b_name, amount](User &b) { + b.balance += amount; + }); + ChangesMade(); + } + return state; + } +} +bool Bank::VerifyPassword(const std::string &name, const std::string &attempt) const noexcept +{ + bool res = false; + users.if_contains(name, [&res, &attempt](const User &u) { + res = (u.password == XXH3_64bits(attempt.data(), attempt.size())); + }); + return res; +} + int_fast8_t Bank::AddUser(const std::string &name, const std::string &init_pass) noexcept { if (name.size() > max_name_size) @@ -64,7 +172,7 @@ int_fast8_t Bank::DelUser(const std::string &name, const std::string &attempt) n { std::shared_lock lock{size_l}; bool state = false; -#if return_on_del +#if RETURN_ON_DEL uint32_t bal; if (users.erase_if(name, [this, &bal, &name, &state, &attempt](User &u) { bal = u.balance; @@ -74,7 +182,7 @@ int_fast8_t Bank::DelUser(const std::string &name, const std::string &attempt) n return state = (XXH3_64bits(attempt.data(), attempt.size()) == u.password); })) { - if constexpr (return_on_del) + if constexpr (RETURN_ON_DEL) { return (state) ? true : ErrorResponse::WrongPassword; } @@ -82,7 +190,7 @@ int_fast8_t Bank::DelUser(const std::string &name, const std::string &attempt) n { if (state) { -#if return_on_del +#if RETURN_ON_DEL users.modify_if(return_account, [&bal](User &u) { u.balance += bal; }); @@ -104,7 +212,7 @@ int_fast8_t Bank::AdminDelUser(const std::string &name, const std::string &attem { std::shared_lock lock{size_l}; bool state = false; -#if return_on_del +#if RETURN_ON_DEL uint32_t bal; if (users.erase_if(name, [this, &bal, &name, &state, &attempt](User &u) { bal = u.balance; @@ -116,7 +224,7 @@ int_fast8_t Bank::AdminDelUser(const std::string &name, const std::string &attem { if (state) { -#if return_on_del +#if RETURN_ON_DEL users.modify_if(return_account, [&bal](User &u) { u.balance += bal; }); @@ -134,84 +242,9 @@ int_fast8_t Bank::AdminDelUser(const std::string &name, const std::string &attem } } -int_fast8_t Bank::SendFunds(const std::string &a_name, const std::string &b_name, uint32_t amount, const std::string &attempt) noexcept +bool Bank::Contains(const std::string &name) const noexcept { - //cant send money to self, from self or amount is 0 - if (a_name == b_name || !amount) - { - return ErrorResponse::InvalidRequest; - } - //as first modify_if checks a_name and grabs unique lock - if (!Contains(b_name)) - { - return ErrorResponse::UserNotFound; - } - - int_fast8_t state = false; - if constexpr (max_log_size > 0) - { - Transaction temp(a_name, b_name, amount); - std::shared_lock lock{send_funds_l}; - users.modify_if(a_name, [&temp, &state, amount, &attempt](User &a) { - //if A can afford it and A's password matches attempt - if (a.balance < amount) - { - state = ErrorResponse::InsufficientFunds; - } - else if (a.password != XXH3_64bits(attempt.data(), attempt.size())) - { - - state = ErrorResponse::WrongPassword; - } - else - { - a.balance -= amount; - a.log.AddTrans(Transaction(temp)); - state = true; - } - }); - if (state > 0) - { - users.modify_if(b_name, [&a_name, &b_name, &temp, amount](User &b) { - b.balance += amount; - b.log.AddTrans(std::move(temp)); - }); - } - return state; - } - else - { - std::shared_lock lock{send_funds_l}; - users.modify_if(a_name, [&state, amount, &attempt](User &a) { - //if A can afford it and A's password matches attempt - if (a.balance < amount) - { - state = ErrorResponse::InsufficientFunds; - } - else if (a.password != XXH3_64bits(attempt.data(), attempt.size())) - { - - state = ErrorResponse::WrongPassword; - } - else - { - a.balance -= amount; - state = true; - } - }); - if (state > 0) - { - users.modify_if(b_name, [&a_name, &b_name, amount](User &b) { - b.balance += amount; - }); - } - return state; - } -} - -int_fast8_t Bank::Contains(const std::string &name) const noexcept -{ - return (users.contains(name)) ? true : ErrorResponse::UserNotFound; + return users.contains(name); } int_fast8_t Bank::AdminVerifyPass(const std::string &attempt) noexcept { @@ -231,23 +264,7 @@ int_fast8_t Bank::SetBal(const std::string &name, const std::string &attempt, ui ? true : ErrorResponse::UserNotFound; } -int_fast64_t Bank::GetBal(const std::string &name) const noexcept -{ - int_fast64_t res = ErrorResponse::UserNotFound; - users.if_contains(name, [&res](const User &u) { - res = u.balance; - }); - return res; -} -int_fast8_t Bank::VerifyPassword(const std::string &name, const std::string &attempt) const noexcept -{ - int_fast8_t res = ErrorResponse::UserNotFound; - users.if_contains(name, [&res, &attempt](const User &u) { - res = (u.password == XXH3_64bits(attempt.data(), attempt.size())) ? true : ErrorResponse::WrongPassword; - }); - return res; -} int_fast8_t Bank::ChangePassword(const std::string &name, const std::string &attempt, std::string &&new_pass) noexcept { int_fast8_t res = ErrorResponse::UserNotFound; @@ -265,36 +282,10 @@ int_fast8_t Bank::ChangePassword(const std::string &name, const std::string &att return res; } -Json::Value Bank::GetLogs(const std::string &name, const std::string &attempt) noexcept -{ - Json::Value res; - if (!users.if_contains(name, [&res, &attempt](const User &u) { - if (u.password != XXH3_64bits(attempt.data(), attempt.size())) - { - res = ErrorResponse::WrongPassword; - } - else - { - for (uint32_t i = u.log.data.size(); i > 0; --i) - { - res[i - 1]["to"] = u.log.data[u.log.data.size() - i].to; - res[i - 1]["from"] = u.log.data[u.log.data.size() - i].from; - res[i - 1]["amount"] = (Json::UInt)u.log.data[u.log.data.size() - i].amount; - res[i - 1]["time"] = (Json::UInt64)u.log.data[u.log.data.size() - i].time; - } - } - })) - { - return ErrorResponse::UserNotFound; - } - return res; -} - void Bank::Save() { if (GetChangeState()) { - //std::cout << " Saving to disk...\n"; Json::Value temp; //loading info into json temp @@ -320,6 +311,7 @@ void Bank::Save() writer->write(temp, &user_save); user_save.close(); } + ChangesSaved(); } } diff --git a/src/bank_api.cpp b/src/bank_api.cpp index d7caa5a..f5e2787 100644 --- a/src/bank_api.cpp +++ b/src/bank_api.cpp @@ -1,5 +1,19 @@ #include "bank_api.h" +#define JSON(V) callback(HttpResponse::newHttpJsonResponse(JsonCast(V))); //temporary +#define PASS_HEADER req->getHeader("Password") //temporary + +#define GEN_BODY \ + const auto temp_req = req->getJsonObject(); \ + const auto body = temp_req ? *temp_req : Json::Value(); + +#define RESPONSE_PARSE(R) \ + auto resp = HttpResponse::newHttpJsonResponse(JsonCast(R.second)); \ + resp->setStatusCode(R.first); \ + callback(resp); + +#define NAME_PARAM req->getParameter("name") + namespace v1 { template @@ -71,9 +85,10 @@ namespace v1 { JSON(bank.AdminDelUser(name, PASS_HEADER)); } - void api::SendFunds(req_args, const std::string name, const std::string to, uint32_t amount) const + void api::SendFunds(req_args, const std::string name) const { - JSON(bank.SendFunds(name, to, amount, PASS_HEADER)); + GEN_BODY + RESPONSE_PARSE(bank.SendFunds(NAME_PARAM, body["to"].asCString(), body["amount"].asUInt())); } void api::ChangePassword(req_args, const std::string &name) const { @@ -83,13 +98,13 @@ namespace v1 { JSON(bank.Contains(name)); } - void api::GetBal(req_args, const std::string &name) const + void api::GetBal(req_args) const { - JSON(bank.GetBal(name)); + RESPONSE_PARSE(bank.GetBal(NAME_PARAM)); } - void api::VerifyPassword(req_args, const std::string &name) const + void api::VerifyPassword(req_args) const { - JSON(bank.VerifyPassword(name, PASS_HEADER)); + RESPONSE_PARSE(BankResponse(k200OK, true)); } void api::SetBal(req_args, const std::string &name, uint32_t amount) const { @@ -103,11 +118,12 @@ namespace v1 { if constexpr (max_log_size > 0) { - JSON(bank.GetLogs(name, PASS_HEADER)); + RESPONSE_PARSE(bank.GetLogs(NAME_PARAM)); } else { auto resp = HttpResponse::newHttpJsonResponse("Logs are Disabled"); + resp->setStatusCode(k404NotFound); resp->setExpiredTime(0); //cached forever callback(resp); }