mirror of
https://github.com/Expand-sys/CCash
synced 2025-12-17 00:22:14 +11:00
Merge remote-tracking branch 'upstream/main' into autodeploy
This commit is contained in:
commit
58d5d3e28b
18 changed files with 423 additions and 231 deletions
8
.dockerignore
Normal file
8
.dockerignore
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
/build
|
||||
/config.json
|
||||
/users.json
|
||||
/help.md
|
||||
/services.md
|
||||
/APIs.md
|
||||
/README.md
|
||||
/benchmarking.cpp
|
||||
31
.github/workflows/deploy.yaml
vendored
Normal file
31
.github/workflows/deploy.yaml
vendored
Normal file
|
|
@ -0,0 +1,31 @@
|
|||
name: Publish Staging
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
jobs:
|
||||
release:
|
||||
name: Push Docker image to GitHub Packages
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
packages: write
|
||||
contents: read
|
||||
steps:
|
||||
- name: Checkout the repo
|
||||
uses: actions/checkout@v2
|
||||
- name: Login to GitHub Docker Registry
|
||||
uses: docker/login-action@v1
|
||||
with:
|
||||
registry: docker.pkg.github.com
|
||||
username: ${{ github.actor }}
|
||||
password: ${{ secrets.GITHUB_TOKEN }}
|
||||
- name: Format repository
|
||||
run: |
|
||||
echo IMAGE_REPOSITORY=$(echo ${{ github.repository }} | tr '[:upper:]' '[:lower:]') >> $GITHUB_ENV
|
||||
- name: Build container image
|
||||
uses: docker/build-push-action@v2
|
||||
with:
|
||||
push: true
|
||||
tags: |
|
||||
docker.pkg.github.com/${{ env.IMAGE_REPOSITORY }}/ccash:${{ github.sha }}
|
||||
docker.pkg.github.com/${{ env.IMAGE_REPOSITORY }}/ccash:latest
|
||||
2
.gitignore
vendored
2
.gitignore
vendored
|
|
@ -1,4 +1,4 @@
|
|||
.vscode
|
||||
build
|
||||
config.json
|
||||
users.json
|
||||
users.json
|
||||
|
|
|
|||
16
Dockerfile
Normal file
16
Dockerfile
Normal file
|
|
@ -0,0 +1,16 @@
|
|||
FROM debian:latest
|
||||
|
||||
WORKDIR /ccash
|
||||
|
||||
RUN apt update && apt -y install build-essential g++ cmake protobuf-compiler libjsoncpp-dev uuid-dev openssl libssl-dev zlib1g-dev
|
||||
|
||||
COPY . .
|
||||
|
||||
RUN mkdir build
|
||||
|
||||
WORKDIR /ccash/build
|
||||
|
||||
RUN cmake ..
|
||||
RUN make -j$(nprov)
|
||||
|
||||
CMD ["/ccash/build/bank", "$CCASH_ADMIN_PASSWORD", "$CCASH_SAVE_FREQUENCY", "$CCASH_THREAD_COUNT"]
|
||||
37
README.md
37
README.md
|
|
@ -16,7 +16,11 @@ CCash solves these issues and adds a level of abstraction, the main philosophy o
|
|||
|
||||
drogon depedencies (varies by OS/distro)
|
||||
```
|
||||
# Debian
|
||||
sudo apt install libjsoncpp-dev uuid-dev openssl libssl-dev zlib1g-dev
|
||||
|
||||
# macOS
|
||||
brew install jsoncpp ossp-uuid openssl zlib
|
||||
```
|
||||
|
||||
building the project
|
||||
|
|
@ -44,7 +48,12 @@ sudo ./bank <admin password> <saving frequency in minutes> <threads>
|
|||
|
||||
## Connected Services
|
||||
|
||||
Go to [here](docs/help.md) to see the API's endpoints. Using the Bank's API allows (you/others) to (make/use) connected services that utilize the bank, a couple ideas can be found [here](docs/services.md)
|
||||
Using the Bank's API allows (you/others) to (make/use) connected services that utilize the bank, a couple ideas can be found [here](docs/services.md)
|
||||
|
||||
## Developing for
|
||||
as a dev check out
|
||||
* [APIs](https://github.com/EntireTwix/CCash/blob/main/docs/APIs.md)
|
||||
* [endpoints](https://github.com/EntireTwix/CCash/blob/main/docs/help.md)
|
||||
|
||||
## FAQ
|
||||
**Q:** how is money initially injected into the economy
|
||||
|
|
@ -54,12 +63,13 @@ Go to [here](docs/help.md) to see the API's endpoints. Using the Bank's API allo
|
|||
## [Contributions](https://github.com/EntireTwix/CCash/graphs/contributors)
|
||||
Thank you to the contributors
|
||||
|
||||
| Name | Work |
|
||||
| :------------------------------------------ | ----------------------------------------------------------------- |
|
||||
| [Expand](https://github.com/Expand-sys) | Frontend |
|
||||
| [React](https://github.com/Reactified) | CC {API, Shops, and ATM, Logo} |
|
||||
| [Doggo](https://github.com/FearlessDoggo21) | Logs loading/adding Optimized, Python API, convention suggestions |
|
||||
|
||||
| Name | Project Work | Connected Service Work |
|
||||
| :------------------------------------------ | ----------------------------------------------------------------------- | ---------------------- |
|
||||
| [Expand](https://github.com/Expand-sys) | Slight docker changes | Frontend |
|
||||
| [React](https://github.com/Reactified) | CC API, Logo | CC Shop, CC ATM. |
|
||||
| [Doggo](https://github.com/FearlessDoggo21) | Logs loading/adding Optimized, HTTP convention suggestions, Python API | `N/A` |
|
||||
| [Luke](https://github.com/LukeeeeBennett) | JS API, Docker, Slight Doc edits | `N/A` |
|
||||
| [Jolly](https://github.com/STBoyden) | Slight Doc edits | `N/A` |
|
||||
|
||||
## Features
|
||||
|
||||
|
|
@ -70,12 +80,18 @@ Thank you to the contributors
|
|||
- **multi-threaded**
|
||||
- **parallel hashmaps** a far [superior](https://greg7mdp.github.io/parallel-hashmap/) HashMap implementation to the STD, that also benefits from multi-threaded
|
||||
- **Drogon** is a very fast [web framework](https://www.techempower.com/benchmarks/#section=data-r20&hw=ph&test=composite)
|
||||
- **Lightweight**, anecodotally I experienced 0.0% idle, <1% CPU usage on average, 7% at peak, 1000 requests in 0.85s
|
||||
|
||||
- **xxHash** for the hashing of passwords, it is very fast: [graph](https://user-images.githubusercontent.com/750081/61976089-aedeab00-af9f-11e9-9239-e5375d6c080f.png)
|
||||
- **Lightweight**, anecodotally I experienced (on my laptop's i7 6700K, 8 threads):
|
||||
- memory usage of 8.5 MB (with 0 users)
|
||||
- 0.0% CPU usage idle
|
||||
- <1% CPU on average
|
||||
- 1000 requests in parallel completed in 0.85s which spiked CPU usage to 7%
|
||||
|
||||
### Safety
|
||||
|
||||
- **Tamper Proof** relative to an in-game implementation
|
||||
- **Auto-Saving** and Saves on close
|
||||
- All passwords are **Hashed**
|
||||
- **HTTPS** (OpenSSL)
|
||||
|
||||
### Accessibility
|
||||
|
|
@ -84,6 +100,9 @@ Thank you to the contributors
|
|||
- able to be used millions of blocks away, across dimensions, servers, **vanilla or modded**.
|
||||
- **Logging** of all transactions, configurable in [consts.hpp](include/consts.hpp)
|
||||
|
||||
### Other
|
||||
- **return balance on deletion**, configurable in [consts.hpp](include/consts.hpp)
|
||||
|
||||
## Dependencies
|
||||
|
||||
- [Parallel HashMap](https://github.com/greg7mdp/parallel-hashmap/tree/master)
|
||||
|
|
|
|||
87
benchmarking.cpp
Normal file
87
benchmarking.cpp
Normal file
|
|
@ -0,0 +1,87 @@
|
|||
#include <iostream>
|
||||
#include <chrono>
|
||||
#include <thread>
|
||||
#include <sys/types.h>
|
||||
#include <unistd.h>
|
||||
#include "bank_f.h"
|
||||
|
||||
#include <signal.h>
|
||||
#include <stdlib.h>
|
||||
#include <stdio.h>
|
||||
#include <unistd.h>
|
||||
|
||||
using namespace std::chrono;
|
||||
using namespace drogon;
|
||||
|
||||
static Bank bank;
|
||||
|
||||
#include <ctime>
|
||||
#include <ratio>
|
||||
#include <chrono>
|
||||
|
||||
#define time_func_a(f, a, x) \
|
||||
{ \
|
||||
using namespace std::chrono; \
|
||||
uint32_t timer = 0; \
|
||||
for (int i = 0; i < x; ++i) \
|
||||
{ \
|
||||
auto t1 = high_resolution_clock::now().time_since_epoch(); \
|
||||
f; \
|
||||
auto t2 = high_resolution_clock::now().time_since_epoch(); \
|
||||
a; \
|
||||
timer += std::chrono::duration_cast<std::chrono::nanoseconds>((t2 - t1)).count(); \
|
||||
} \
|
||||
std::cout << timer / x << '\n'; \
|
||||
}
|
||||
|
||||
#define time_func(f, x) \
|
||||
{ \
|
||||
using namespace std::chrono; \
|
||||
uint32_t timer = 0; \
|
||||
for (int i = 0; i < x; ++i) \
|
||||
{ \
|
||||
auto t1 = high_resolution_clock::now().time_since_epoch(); \
|
||||
f; \
|
||||
auto t2 = high_resolution_clock::now().time_since_epoch(); \
|
||||
timer += std::chrono::duration_cast<std::chrono::nanoseconds>((t2 - t1)).count(); \
|
||||
} \
|
||||
std::cout << timer / x << '\n'; \
|
||||
}
|
||||
|
||||
#define Op_a(v, name, x, a) \
|
||||
{ \
|
||||
std::cout << name; \
|
||||
time_func_a(v, a, x); \
|
||||
}
|
||||
|
||||
#define Op(v, name, x) \
|
||||
{ \
|
||||
std::cout << name; \
|
||||
time_func(v, x); \
|
||||
}
|
||||
|
||||
int main(int argc, char **argv)
|
||||
{
|
||||
bank.AddUser("twix", "root");
|
||||
bank.AddUser("jolly", "root");
|
||||
bank.admin_pass = "root";
|
||||
Op_a(bank.AddUser("", ""), "add user: ", 1000000, bank.DelUser("", ""));
|
||||
Op_a(bank.AdminAddUser("root", "", 0, ""), "admin add user: ", 1000000, bank.DelUser("", ""));
|
||||
Op(bank.SetBal("twix", "root", 1000000), "set bal: ", 1000000);
|
||||
Op(bank.SendFunds("twix", "jolly", 1, "root"), "send funds: ", 1000000);
|
||||
|
||||
bank.AddUser("", "");
|
||||
Op_a(bank.DelUser("", ""), "del user: ", 1000000, bank.AddUser("", ""));
|
||||
Op_a(bank.AdminDelUser("", "root"), "admin del user: ", 1000000, bank.AddUser("", ""));
|
||||
bank.DelUser("", "");
|
||||
|
||||
Op(bank.Contains("twix"), "contains: ", 1000000);
|
||||
Op(bank.AdminVerifyPass("root"), "admin verify pass: ", 1000000);
|
||||
Op(bank.GetBal("twix"), "get bal: ", 1000000);
|
||||
Op(bank.VerifyPassword("twix", "root"), "verify pass: ", 1000000);
|
||||
Op(bank.ChangePassword("twix", "root", "root"), "change pass: ", 1000000);
|
||||
Op(bank.GetLogs("twix", "root"), "get logs: ", 10000);
|
||||
Op(bank.Save(), "saving: ", 1);
|
||||
|
||||
return 0;
|
||||
}
|
||||
13
docs/APIs.md
13
docs/APIs.md
|
|
@ -1,2 +1,11 @@
|
|||
[CC API](https://github.com/Reactified/rpm/blob/main/packages/ccash-api/api.lua)
|
||||
[Python API](https://github.com/fearlessdoggo21/ccashpythonclient)
|
||||
# Language Specific APIs
|
||||
|
||||
## Complete
|
||||
* [JS API](https://github.com/LukeeeeBennett/ccash-client-js)
|
||||
* [ComputerCraft (Lua) API](https://github.com/Reactified/rpm/blob/main/packages/ccash-api/api.lua)
|
||||
* [Python API](https://github.com/fearlessdoggo21/ccashpythonclient)
|
||||
|
||||
## In Dev
|
||||
* [C API]()
|
||||
* [CS API](https://github.com/Soverclysm/CCash-dotnet-api)
|
||||
* [Rust API](https://git.stboyden.com/STBoyden/ccash-rs)
|
||||
|
|
|
|||
61
docs/help.md
61
docs/help.md
|
|
@ -1,44 +1,45 @@
|
|||
# Error Responses
|
||||
|
||||
| # | meaning |
|
||||
| --- | ------------------ |
|
||||
| -1 | UserNotFound |
|
||||
| -2 | WrongPassword |
|
||||
| -3 | InvalidRequest |
|
||||
| -5 | NameTooLong |
|
||||
| -6 | UserAlreadyExists |
|
||||
| -7 | InsufficientFunds |
|
||||
| # | meaning |
|
||||
| --- | ----------------- |
|
||||
| -1 | UserNotFound |
|
||||
| -2 | WrongPassword |
|
||||
| -3 | InvalidRequest |
|
||||
| -4 | NameTooLong |
|
||||
| -5 | UserAlreadyExists |
|
||||
| -6 | InsufficientFunds |
|
||||
|
||||
# Things of Note
|
||||
* all endpoints respond with **JSON** file type
|
||||
* "**A**" denotes requiring Authentication in the form of a header titled "**Password**"
|
||||
|
||||
# Usage
|
||||
| Name | Path | Method | A | Description |
|
||||
| :------------: | :-------------------------------- | :----: | :---: | --------------------------------------------------------------------------------------------------------------------------- |
|
||||
| GetBal | /{name}/bal | GET | false | returns the balance of a given user `{name}` |
|
||||
| GetLog | /{name}/log | GET | true | returns a list of last `n` number of transactions (a configurable amount) of a given user `{name}` |
|
||||
| SendFunds | /{name}/send/{to}/amount={amount} | POST | true | sends `{amount}` from user `{name}` to user `{to}` |
|
||||
| VerifyPassword | /{name}/pass/verify | GET | true | returns `true` or `false` depending on if the supplied user `{name}`'s password matches the password supplied in the header |
|
||||
| Name | Path | Method | A | Description |
|
||||
| :------------: | :------------------------------------- | :----: | :---: | ------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| GetBal | BankF/{name}/bal | GET | false | returns the balance of a given user `{name}` |
|
||||
| GetLog | BankF/{name}/log | GET | true | returns a list of last `n` number of transactions (a configurable amount when the program is compiled) of a given user `{name}` |
|
||||
| SendFunds | BankF/{name}/send/{to}?amount={amount} | POST | true | sends `{amount}` from user `{name}` to user `{to}` |
|
||||
| VerifyPassword | BankF/{name}/pass/verify | GET | true | returns `1` if the supplied user `{name}`'s password matches the password supplied in the header |
|
||||
|
||||
# Meta Usage
|
||||
| Name | Path | Method | A | Description |
|
||||
| :------------: | :-------------------------------- | :----: | :---: | ---------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| ChangePassword | /{name}/pass/change | PATCH | true | if the password supplied in the header matches the user `{name}`'s password, the user's password is changed to the one given in the body |
|
||||
| SetBal | /admin/{name}/bal/amount={amount} | PATCH | true | sets the balance of a give user `{name}` if the supplied password matches the admin password |
|
||||
| Name | Path | Method | A | Description |
|
||||
| :------------: | :------------------------------------- | :----: | :---: | ---------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| ChangePassword | BankF/{name}/pass/change | PATCH | true | if the password supplied in the header matches the user `{name}`'s password, the user's password is changed to the one given in the body |
|
||||
| SetBal | BankF/admin/{name}/bal?amount={amount} | PATCH | true | sets the balance of a give user `{name}` if the supplied password matches the admin password |
|
||||
|
||||
# System Usage
|
||||
| Name | Path | Method | A | Description |
|
||||
| :-------------: | :--------------- | :----: | :---: | -------------------------------------------------------------------------------------------------------- |
|
||||
| Help | /help | GET | false | the page you're looking at right now! |
|
||||
| Close | /admin/close | POST | true | saves and then closes the program if the supplied password matches the admin password |
|
||||
| Contains | /contains/{name} | GET | false | returns `true` or `false` depending on if the supplied user `{name}` exists |
|
||||
| AdminVerifyPass | /admin/verify | GET | true | returns `true` or `false` depending on if the password supplied in the header matches the admin password |
|
||||
| Name | Path | Method | A | Description |
|
||||
| :-------------: | :-------------------- | :----: | :---: | ------------------------------------------------------------------------------------- |
|
||||
| Help | BankF/help | GET | false | the page you're looking at right now! |
|
||||
| Ping | BankF/ping | GET | false | for pinging the server to see if its online |
|
||||
| Close | BankF/admin/close | POST | true | saves and then closes the program if the supplied password matches the admin password |
|
||||
| Contains | BankF/contains/{name} | GET | false | returns `1` if the supplied user `{name}` exists |
|
||||
| AdminVerifyPass | BankF/admin/verify | GET | true | returns `1` if the password supplied in the header matches the admin password |
|
||||
|
||||
# User Management
|
||||
| Name | Path | Method | A | Description |
|
||||
| :----------: | :------------------------------------- | :----: | :---: | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| AddUser | /user/{name} | POST | true | registers a user with the name `{name}`, balance of 0 and a password of the password supplied in the header |
|
||||
| AdminAddUser | /admin/user/{name}?init_bal={init_bal} | POST | true | if the password supplied in the header matches the admin password, then it registers a user with the name `{name}`, balance of `init_bal` and a password supplied by the body of the request |
|
||||
| DelUser | /user/{name} | DELETE | true | if the password supplied in the header matches the user `{name}`'s password, then the user is deleted |
|
||||
| AdminDelUser | /admin/user/{name} | DELETE | true | if the password supplied in the header matches the admin password, then the user is deleted |
|
||||
| Name | Path | Method | A | Description |
|
||||
| :----------: | :------------------------------------------ | :----: | :---: | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| AddUser | BankF/user/{name} | POST | true | registers a user with the name `{name}`, balance of 0 and a password of the password supplied in the header |
|
||||
| AdminAddUser | BankF/admin/user/{name}?init_bal={init_bal} | POST | true | if the password supplied in the header matches the admin password, then it registers a user with the name `{name}`, balance of `init_bal` and a password supplied by the body of the request |
|
||||
| DelUser | BankF/user/{name} | DELETE | true | if the password supplied in the header matches the user `{name}`'s password, then the user is deleted |
|
||||
| AdminDelUser | BankF/admin/user/{name} | DELETE | true | if the password supplied in the header matches the admin password, then the user is deleted |
|
||||
|
|
|
|||
|
|
@ -10,7 +10,7 @@
|
|||
- [CC Shop](https://github.com/Reactified/rpm/tree/main/packages/ccash-shop)
|
||||

|
||||

|
||||
- [CC ATM](https://github.com/Reactified/misc/tree/main/lua/ccash-bank) an ATM for economies allowing for an initial exchange to start up
|
||||
- [CC Reverse ATM](https://github.com/Reactified/misc/tree/main/lua/ccash-bank) an ATM for economies allowing for an initial exchange to start up
|
||||

|
||||
|
||||
### In-Dev:
|
||||
|
|
@ -24,3 +24,6 @@
|
|||
- Shipping
|
||||
- High-level bank operations such as loans
|
||||
- Some trust based system for transactions similiar to Paypal
|
||||
|
||||
- a better version of one of these existing ideas
|
||||
- something completely different
|
||||
|
|
|
|||
|
|
@ -33,28 +33,28 @@ private:
|
|||
public:
|
||||
std::string admin_pass;
|
||||
|
||||
int_fast8_t AddUser(const std::string &name, const std::string &init_pass);
|
||||
int_fast8_t AdminAddUser(const std::string &attempt, std::string &&name, uint32_t init_bal, std::string &&init_pass);
|
||||
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);
|
||||
int_fast8_t AdminDelUser(const std::string &name, const std::string &attempt);
|
||||
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);
|
||||
int_fast8_t SendFunds(const std::string &a_name, const std::string &b_name, uint32_t amount, const std::string &attempt) noexcept;
|
||||
|
||||
bool Contains(const std::string &name) const;
|
||||
bool AdminVerifyPass(const std::string &attempt);
|
||||
int_fast8_t Contains(const std::string &name) const noexcept;
|
||||
int_fast8_t AdminVerifyPass(const std::string &attempt) noexcept;
|
||||
|
||||
int_fast8_t SetBal(const std::string &name, const std::string &attempt, uint32_t amount);
|
||||
int_fast64_t GetBal(const std::string &name) const;
|
||||
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;
|
||||
int_fast8_t ChangePassword(const std::string &name, const std::string &attempt, std::string &&new_pass);
|
||||
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);
|
||||
Json::Value GetLogs(const std::string &name, const std::string &attempt) noexcept;
|
||||
|
||||
void Save();
|
||||
|
||||
//NOT THREAD SAFE, BY NO MEANS SHOULD THIS BE CALLED WHILE RECEIEVING REQUESTS
|
||||
//NOT THREAD SAFE
|
||||
void Load();
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -13,6 +13,7 @@ class BankF : public HttpController<BankF, false>
|
|||
public:
|
||||
BankF(Bank *b);
|
||||
void Help(req_args) const;
|
||||
void Ping(req_args) const;
|
||||
void Close(req_args) const;
|
||||
void AddUser(req_args, const std::string &name) const;
|
||||
void AdminAddUser(req_args, std::string &&name, uint32_t init_bal) const;
|
||||
|
|
@ -32,15 +33,16 @@ public:
|
|||
//Usage
|
||||
METHOD_ADD(BankF::GetBal, "/{name}/bal", Get, Options);
|
||||
METHOD_ADD(BankF::GetLog, "/{name}/log", Get, Options);
|
||||
METHOD_ADD(BankF::SendFunds, "/{name}/send/{to}/amount={amount}", Post, Options);
|
||||
METHOD_ADD(BankF::SendFunds, "/{name}/send/{to}?amount={amount}", Post, Options);
|
||||
METHOD_ADD(BankF::VerifyPassword, "/{name}/pass/verify", Get, Options);
|
||||
|
||||
//Meta Usage
|
||||
METHOD_ADD(BankF::ChangePassword, "/{name}/pass/change", Patch, Options);
|
||||
METHOD_ADD(BankF::SetBal, "/admin/{name}/bal/amount={amount}", Patch, Options);
|
||||
METHOD_ADD(BankF::SetBal, "/admin/{name}/bal?amount={amount}", Patch, Options);
|
||||
|
||||
//System Usage
|
||||
METHOD_ADD(BankF::Help, "/help", Get, Options);
|
||||
METHOD_ADD(BankF::Ping, "/ping", Get, Options);
|
||||
METHOD_ADD(BankF::Close, "/admin/close", Post, Options);
|
||||
METHOD_ADD(BankF::Contains, "/contains/{name}", Get, Options);
|
||||
METHOD_ADD(BankF::AdminVerifyPass, "/admin/verify", Get, Options);
|
||||
|
|
|
|||
|
|
@ -3,4 +3,12 @@
|
|||
// Setting both values to 0 does not compile logging
|
||||
constexpr unsigned max_log_size = 100;
|
||||
constexpr unsigned pre_log_size = 10;
|
||||
constexpr unsigned max_name_size = 50;
|
||||
|
||||
constexpr unsigned max_name_size = 50;
|
||||
|
||||
constexpr const char *users_location = "../users.json";
|
||||
constexpr const char *config_location = "../config.json";
|
||||
|
||||
//returns money to an account on deletion
|
||||
constexpr bool return_on_del = false;
|
||||
constexpr const char *return_account = "";
|
||||
|
|
@ -4,8 +4,7 @@ enum ErrorResponse
|
|||
UserNotFound = -1,
|
||||
WrongPassword = -2,
|
||||
InvalidRequest = -3,
|
||||
WrongAdminPassword = -4,
|
||||
NameTooLong = -5,
|
||||
UserAlreadyExists = -6,
|
||||
InsufficientFunds = -7,
|
||||
NameTooLong = -4,
|
||||
UserAlreadyExists = -5,
|
||||
InsufficientFunds = -6,
|
||||
};
|
||||
4
main.cpp
4
main.cpp
|
|
@ -55,7 +55,7 @@ int main(int argc, char **argv)
|
|||
bank.admin_pass = argv[1];
|
||||
|
||||
//Auto Saving
|
||||
const unsigned long saving_freq = std::stoul(argv[2]);
|
||||
const unsigned long saving_freq = std::stoul(std::string(argv[2]));
|
||||
if (saving_freq) //if saving frequency is 0 then auto saving is turned off
|
||||
{
|
||||
std::thread([saving_freq]() {
|
||||
|
|
@ -73,7 +73,7 @@ int main(int argc, char **argv)
|
|||
[](const drogon::HttpRequestPtr &req, const drogon::HttpResponsePtr &resp) {
|
||||
resp->addHeader("Access-Control-Allow-Origin", "*");
|
||||
});
|
||||
app().loadConfigFile("../config.json").registerController(API).setThreadNum(std::stoul(argv[3])).run();
|
||||
app().loadConfigFile(config_location).registerController(API).setThreadNum(std::stoul(std::string(argv[3]))).run();
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
|
|
|||
293
src/bank.cpp
293
src/bank.cpp
|
|
@ -1,25 +1,28 @@
|
|||
#include "bank.h"
|
||||
|
||||
int_fast8_t Bank::AddUser(const std::string &name, const std::string &init_pass)
|
||||
int_fast8_t Bank::AddUser(const std::string &name, const std::string &init_pass) noexcept
|
||||
{
|
||||
if (name.size() > max_name_size)
|
||||
{
|
||||
return ErrorResponse::NameTooLong;
|
||||
}
|
||||
for (char c : name)
|
||||
{
|
||||
std::shared_lock<std::shared_mutex> lock{size_l};
|
||||
if (users.try_emplace_l(
|
||||
name, [](User &) {}, init_pass))
|
||||
if (c == ' ')
|
||||
{
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
return ErrorResponse::UserAlreadyExists;
|
||||
return ErrorResponse::InvalidRequest;
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
std::shared_lock<std::shared_mutex> lock{size_l};
|
||||
return (users.try_emplace_l(
|
||||
name, [](User &) {}, init_pass))
|
||||
? true
|
||||
: ErrorResponse::UserAlreadyExists;
|
||||
}
|
||||
}
|
||||
int_fast8_t Bank::AdminAddUser(const std::string &attempt, std::string &&name, uint32_t init_bal, std::string &&init_pass)
|
||||
int_fast8_t Bank::AdminAddUser(const std::string &attempt, std::string &&name, uint32_t init_bal, std::string &&init_pass) noexcept
|
||||
{
|
||||
if (name.size() > max_name_size)
|
||||
{
|
||||
|
|
@ -27,161 +30,158 @@ int_fast8_t Bank::AdminAddUser(const std::string &attempt, std::string &&name, u
|
|||
}
|
||||
if (admin_pass != attempt)
|
||||
{
|
||||
return ErrorResponse::WrongAdminPassword;
|
||||
}
|
||||
{
|
||||
std::shared_lock<std::shared_mutex> lock{size_l};
|
||||
if (users.try_emplace_l(
|
||||
name, [](User &) {}, std::move(init_pass)))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
return ErrorResponse::UserAlreadyExists;
|
||||
}
|
||||
return ErrorResponse::WrongPassword;
|
||||
}
|
||||
|
||||
std::shared_lock<std::shared_mutex> lock{size_l};
|
||||
return (users.try_emplace_l(
|
||||
name, [](User &) {}, init_bal, std::move(init_pass)))
|
||||
? true
|
||||
: ErrorResponse::UserAlreadyExists;
|
||||
}
|
||||
int_fast8_t Bank::DelUser(const std::string &name, const std::string &attempt)
|
||||
int_fast8_t Bank::DelUser(const std::string &name, const std::string &attempt) noexcept
|
||||
{
|
||||
std::shared_lock<std::shared_mutex> lock{size_l};
|
||||
bool state = false;
|
||||
if (!users.erase_if(name, [&state, &attempt](User &u) { return state = (XXH3_64bits(attempt.data(), attempt.size()) == u.password); }))
|
||||
if (users.erase_if(name, [this, &name, &state, &attempt](User &u) {
|
||||
if constexpr (return_on_del)
|
||||
{
|
||||
if (SendFunds(name, return_account, u.balance, attempt) == 1)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return state = (XXH3_64bits(attempt.data(), attempt.size()) == u.password);
|
||||
}))
|
||||
{
|
||||
return ErrorResponse::UserNotFound;
|
||||
return (state) ? true : ErrorResponse::WrongPassword;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (state)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
return ErrorResponse::WrongAdminPassword;
|
||||
}
|
||||
return ErrorResponse::UserNotFound;
|
||||
}
|
||||
}
|
||||
int_fast8_t Bank::AdminDelUser(const std::string &name, const std::string &attempt)
|
||||
int_fast8_t Bank::AdminDelUser(const std::string &name, const std::string &attempt) noexcept
|
||||
{
|
||||
std::shared_lock<std::shared_mutex> lock{size_l};
|
||||
bool state = false;
|
||||
if (!users.erase_if(name, [this, &state, &attempt](const User &) { return state = (admin_pass == attempt); }))
|
||||
if (users.erase_if(name, [this, &name, &state, &attempt](const User &u) {
|
||||
if constexpr (return_on_del)
|
||||
{
|
||||
if (SendFunds(name, return_account, u.balance, attempt) == 1)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return state = (XXH3_64bits(attempt.data(), attempt.size()) == u.password);
|
||||
}))
|
||||
{
|
||||
return ErrorResponse::UserNotFound;
|
||||
return (state) ? true : ErrorResponse::WrongPassword;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (state)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
return ErrorResponse::WrongAdminPassword;
|
||||
}
|
||||
return ErrorResponse::UserNotFound;
|
||||
}
|
||||
}
|
||||
|
||||
int_fast8_t Bank::SendFunds(const std::string &a_name, const std::string &b_name, uint32_t amount, const std::string &attempt)
|
||||
int_fast8_t Bank::SendFunds(const std::string &a_name, const std::string &b_name, uint32_t amount, const std::string &attempt) noexcept
|
||||
{
|
||||
//cant send money to self, from self or amount is 0
|
||||
if (a_name == b_name || !amount)
|
||||
{
|
||||
return ErrorResponse::InvalidRequest;
|
||||
}
|
||||
|
||||
int_fast8_t state = false;
|
||||
{
|
||||
std::shared_lock<std::shared_mutex> lock{send_funds_l}; //because SendFunds requires 3 locking operations
|
||||
if (users.modify_if(a_name, [&state, amount, &attempt](User &a) {
|
||||
//if A exists, A can afford it, and A's password matches
|
||||
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;
|
||||
}
|
||||
}
|
||||
}))
|
||||
{
|
||||
return ErrorResponse::UserNotFound;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (state)
|
||||
{
|
||||
//if B doesnt exist
|
||||
if (users.modify_if(b_name, [amount](User &b) {
|
||||
b.balance += amount;
|
||||
}))
|
||||
{
|
||||
if constexpr (max_log_size)
|
||||
{
|
||||
Transaction temp(a_name, b_name, amount);
|
||||
Transaction temp2 = temp;
|
||||
users.modify_if(a_name, [&temp](User &a) {
|
||||
a.log.AddTrans(std::move(temp));
|
||||
});
|
||||
users.modify_if(b_name, [&temp2](User &b) {
|
||||
b.log.AddTrans(std::move(temp2));
|
||||
});
|
||||
}
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
//attempt to refund if A exist
|
||||
users.modify_if(a_name, [amount](User &a) {
|
||||
a.balance += amount;
|
||||
});
|
||||
return ErrorResponse::UserNotFound; //because had to refund transaction
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
return state;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool Bank::Contains(const std::string &name) const
|
||||
{
|
||||
return users.contains(name);
|
||||
}
|
||||
bool Bank::AdminVerifyPass(const std::string &attempt)
|
||||
{
|
||||
return (admin_pass == attempt);
|
||||
}
|
||||
|
||||
int_fast8_t Bank::SetBal(const std::string &name, const std::string &attempt, uint32_t amount)
|
||||
{
|
||||
if (admin_pass != attempt)
|
||||
{
|
||||
return ErrorResponse::WrongAdminPassword;
|
||||
}
|
||||
if (users.modify_if(name, [amount](User &u) {
|
||||
u.balance = amount;
|
||||
}))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
else
|
||||
//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<std::shared_mutex> 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<std::shared_mutex> 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_fast64_t Bank::GetBal(const std::string &name) const
|
||||
|
||||
int_fast8_t Bank::Contains(const std::string &name) const noexcept
|
||||
{
|
||||
return (users.contains(name)) ? true : ErrorResponse::UserNotFound;
|
||||
}
|
||||
int_fast8_t Bank::AdminVerifyPass(const std::string &attempt) noexcept
|
||||
{
|
||||
return (admin_pass == attempt) ? true : ErrorResponse::WrongPassword;
|
||||
}
|
||||
|
||||
int_fast8_t Bank::SetBal(const std::string &name, const std::string &attempt, uint32_t amount) noexcept
|
||||
{
|
||||
if (admin_pass != attempt)
|
||||
{
|
||||
return ErrorResponse::WrongPassword;
|
||||
}
|
||||
|
||||
return (users.modify_if(name, [amount](User &u) {
|
||||
u.balance = amount;
|
||||
}))
|
||||
? 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) {
|
||||
|
|
@ -190,15 +190,15 @@ int_fast64_t Bank::GetBal(const std::string &name) const
|
|||
return res;
|
||||
}
|
||||
|
||||
int_fast8_t Bank::VerifyPassword(const std::string &name, const std::string &attempt) const
|
||||
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());
|
||||
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)
|
||||
int_fast8_t Bank::ChangePassword(const std::string &name, const std::string &attempt, std::string &&new_pass) noexcept
|
||||
{
|
||||
int_fast8_t res = ErrorResponse::UserNotFound;
|
||||
users.modify_if(name, [&res, &attempt, &new_pass](User &u) {
|
||||
|
|
@ -208,13 +208,14 @@ int_fast8_t Bank::ChangePassword(const std::string &name, const std::string &att
|
|||
}
|
||||
else
|
||||
{
|
||||
res = true;
|
||||
u.password = XXH3_64bits(new_pass.data(), new_pass.size());
|
||||
}
|
||||
});
|
||||
return res;
|
||||
}
|
||||
|
||||
Json::Value Bank::GetLogs(const std::string &name, const std::string &attempt)
|
||||
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) {
|
||||
|
|
@ -224,15 +225,13 @@ Json::Value Bank::GetLogs(const std::string &name, const std::string &attempt)
|
|||
}
|
||||
else
|
||||
{
|
||||
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[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;
|
||||
}
|
||||
res = std::move(temp);
|
||||
}
|
||||
}))
|
||||
{
|
||||
|
|
@ -262,7 +261,7 @@ void Bank::Save()
|
|||
}
|
||||
else
|
||||
{
|
||||
std::ofstream user_save("../users.json");
|
||||
std::ofstream user_save(users_location);
|
||||
Json::StreamWriterBuilder builder;
|
||||
const std::unique_ptr<Json::StreamWriter> writer(builder.newStreamWriter());
|
||||
writer->write(temp, &user_save);
|
||||
|
|
@ -276,7 +275,7 @@ void Bank::Load()
|
|||
Json::CharReaderBuilder builder;
|
||||
|
||||
Json::Value temp;
|
||||
std::ifstream user_save("../users.json");
|
||||
std::ifstream user_save(users_location);
|
||||
builder["collectComments"] = true;
|
||||
JSONCPP_STRING errs;
|
||||
if (!parseFromStream(builder, user_save, &temp, &errs))
|
||||
|
|
@ -290,7 +289,7 @@ void Bank::Load()
|
|||
user_save.close();
|
||||
for (const auto &u : temp.getMemberNames())
|
||||
{
|
||||
if constexpr (max_log_size)
|
||||
if constexpr (max_log_size > 0)
|
||||
{
|
||||
users.try_emplace(u, temp[u]["balance"].asUInt(), std::move(temp[u]["password"].asUInt64()), temp[u]["log"]);
|
||||
}
|
||||
|
|
|
|||
File diff suppressed because one or more lines are too long
|
|
@ -29,11 +29,11 @@ User::User(uint32_t init_bal, uint64_t init_pass, const Json::Value &log_j) : ba
|
|||
log.data.reserve(std::min(pre_log_size * ((log_j.size() / pre_log_size) + 1), max_log_size));
|
||||
for (uint32_t i = (log_j.size() - max_log_size) * (log_j.size() > max_log_size); i < log_j.size(); i++)
|
||||
{
|
||||
log.data.push_back(std::move(Transaction(
|
||||
log.data.push_back(Transaction(
|
||||
log_j[i]["from"].asCString(),
|
||||
log_j[i]["to"].asCString(),
|
||||
log_j[i]["amount"].asUInt(),
|
||||
log_j[i]["time"].asUInt64())));
|
||||
log_j[i]["time"].asUInt64()));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -43,7 +43,7 @@ Json::Value User::Serialize() const
|
|||
Json::Value res;
|
||||
res["balance"] = (Json::UInt)balance;
|
||||
res["password"] = (Json::UInt64)password;
|
||||
if constexpr (max_log_size)
|
||||
if constexpr (max_log_size > 0)
|
||||
{
|
||||
res["log"] = log.Serialize();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,2 +1,8 @@
|
|||
{
|
||||
"" :
|
||||
{
|
||||
"balance" : 0,
|
||||
"log" : null,
|
||||
"password" : 0
|
||||
},
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue