From 6976f33db292933341c783299deefe136616dd9e Mon Sep 17 00:00:00 2001 From: Kenneth Hough Date: Fri, 12 Apr 2024 16:20:24 -0500 Subject: [PATCH] initial commit --- .github/ISSUE_TEMPLATE/bug_report.md | 38 ++++++++ .github/ISSUE_TEMPLATE/feature_request.md | 20 ++++ .github/pull_request_template.md | 44 +++++++++ .github/workflows/build.yml | 25 +++++ .vscode/settings.json | 53 +++++++++++ Makefile | 39 ++++++++ examples/main.cpp | 22 +++++ include/kyte/Client.hpp | 41 ++++++++ include/kyte/Util.hpp | 17 ++++ src/Client.cpp | 110 ++++++++++++++++++++++ src/Util.cpp | 65 +++++++++++++ 11 files changed, 474 insertions(+) create mode 100644 .github/ISSUE_TEMPLATE/bug_report.md create mode 100644 .github/ISSUE_TEMPLATE/feature_request.md create mode 100644 .github/pull_request_template.md create mode 100644 .github/workflows/build.yml create mode 100644 .vscode/settings.json create mode 100644 Makefile create mode 100644 examples/main.cpp create mode 100644 include/kyte/Client.hpp create mode 100644 include/kyte/Util.hpp create mode 100644 src/Client.cpp create mode 100644 src/Util.cpp diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md new file mode 100644 index 0000000..d6d5d3c --- /dev/null +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -0,0 +1,38 @@ +--- +name: Bug report +about: Create a report to help us improve +title: "[BUG]" +labels: '' +assignees: '' + +--- + +**Describe the bug** +A clear and concise description of what the bug is. + +**To Reproduce** +Steps to reproduce the behavior: +1. Go to '...' +2. Click on '....' +3. Scroll down to '....' +4. See error + +**Expected behavior** +A clear and concise description of what you expected to happen. + +**Screenshots** +If applicable, add screenshots to help explain your problem. + +**Desktop (please complete the following information):** + - OS: [e.g. iOS] + - Browser [e.g. chrome, safari] + - Version [e.g. 22] + +**Smartphone (please complete the following information):** + - Device: [e.g. iPhone6] + - OS: [e.g. iOS8.1] + - Browser [e.g. stock browser, safari] + - Version [e.g. 22] + +**Additional context** +Add any other context about the problem here. diff --git a/.github/ISSUE_TEMPLATE/feature_request.md b/.github/ISSUE_TEMPLATE/feature_request.md new file mode 100644 index 0000000..e0c0168 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/feature_request.md @@ -0,0 +1,20 @@ +--- +name: Feature request +about: Suggest an idea for this project +title: "[FEATURE]" +labels: '' +assignees: '' + +--- + +**Is your feature request related to a problem? Please describe.** +A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] + +**Describe the solution you'd like** +A clear and concise description of what you want to happen. + +**Describe alternatives you've considered** +A clear and concise description of any alternative solutions or features you've considered. + +**Additional context** +Add any other context or screenshots about the feature request here. diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md new file mode 100644 index 0000000..7b7c78b --- /dev/null +++ b/.github/pull_request_template.md @@ -0,0 +1,44 @@ +### Changes Proposed in This PR +- Clearly outline the changes you have made in this pull request. Provide enough detail for reviewers to understand what has been done and why. +- Include any new features, bug fixes, or enhancements. +- Specify how the changes impact the existing codebase. + +### Acceptance Criteria Scenarios +- List the scenarios that need to be tested to ensure the pull request meets the acceptance criteria. +- Describe the expected outcomes for each scenario. + +### Linked Issues +- Mention any related issues this pull request addresses or resolves. Include the issue numbers with hyperlinks. + +### Implementation Details +- Provide any necessary context or details that could help reviewers understand the implementation approach. +- Discuss any significant choices or trade-offs you made in the development process. + +### Dependencies +- List any dependencies that this pull request introduces or changes. +- Ensure all dependencies are documented and justified. + +### Testing Strategy +- Describe the testing approach. Mention the types of tests (unit, integration, end-to-end) that have been added or modified. +- Highlight any specific areas of the code that should be focused on during testing. + +### Screenshots/Video +- Include screenshots or video captures of new or updated UI/UX elements, if applicable. + +### Checklist Before Requesting Review +- [ ] Changes are complete and tested. +- [ ] Pull request targets the correct branch. +- [ ] Code is consistent with the existing codebase. +- [ ] Linter/static analysis passes. +- [ ] Existing tests pass. +- [ ] Documentation and changelog are updated. + +### Instructions for Reviewers +- Provide any specific instructions or areas of focus for the reviewers. +- Mention if there are any particular files or modules that require more attention. + +### Follow-Up Tasks (If Applicable) +- List any follow-up tasks or next steps that should be taken after the pull request is merged. + +### Additional Notes +- Include any other information that would be useful for the reviewers or maintainers. diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml new file mode 100644 index 0000000..a57e682 --- /dev/null +++ b/.github/workflows/build.yml @@ -0,0 +1,25 @@ +name: C++ Build Workflow + +on: [push, pull_request] + +jobs: + build: + name: Build Kyte C++ Library + runs-on: ubuntu-latest # You can specify other runners like windows-latest or macos-latest + + steps: + - name: Checkout code + uses: actions/checkout@v2 + + - name: Install dependencies + run: | + sudo apt-get update + sudo apt-get install libcurl4-openssl-dev libssl-dev -y # Ensure all necessary libraries are installed + + - name: Build project + run: | + make -C . # Use make in the root directory where the Makefile is located + + - name: Run tests + run: | + ./bin/kyte_client # Assuming executable is in the bin directory as per your Makefile setup diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..501dc70 --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,53 @@ +{ + "files.associations": { + "string": "cpp", + "bit": "cpp", + "cctype": "cpp", + "compare": "cpp", + "concepts": "cpp", + "cstddef": "cpp", + "cstdint": "cpp", + "cstdio": "cpp", + "cstdlib": "cpp", + "cstring": "cpp", + "cwchar": "cpp", + "exception": "cpp", + "initializer_list": "cpp", + "iosfwd": "cpp", + "limits": "cpp", + "new": "cpp", + "tuple": "cpp", + "type_traits": "cpp", + "utility": "cpp", + "xmemory": "cpp", + "xstddef": "cpp", + "xstring": "cpp", + "xtr1common": "cpp", + "xutility": "cpp", + "atomic": "cpp", + "bitset": "cpp", + "clocale": "cpp", + "cmath": "cpp", + "ctime": "cpp", + "ios": "cpp", + "istream": "cpp", + "iterator": "cpp", + "memory": "cpp", + "ostream": "cpp", + "sstream": "cpp", + "stdexcept": "cpp", + "streambuf": "cpp", + "system_error": "cpp", + "typeinfo": "cpp", + "vector": "cpp", + "xfacet": "cpp", + "xiosbase": "cpp", + "xlocale": "cpp", + "xlocinfo": "cpp", + "xlocnum": "cpp", + "iomanip": "cpp", + "iostream": "cpp", + "xlocmon": "cpp", + "xloctime": "cpp" + } +} \ No newline at end of file diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..62c24a3 --- /dev/null +++ b/Makefile @@ -0,0 +1,39 @@ +# Compiler settings - Can be customized. +CXX = g++ +CXXFLAGS = -Wall -Iinclude -std=c++11 -O2 +LDFLAGS = -lcurl -lssl -lcrypto + +# Build directories +SRC_DIR = src +INCLUDE_DIR = include +OBJ_DIR = obj +BIN_DIR = bin + +# Find all CPP files in SRC_DIR +SOURCES = $(wildcard $(SRC_DIR)/*.cpp) +# Replace .cpp from SOURCES with .o and prepend OBJ_DIR +OBJECTS = $(patsubst $(SRC_DIR)/%.cpp, $(OBJ_DIR)/%.o, $(SOURCES)) + +# Target executable +TARGET = $(BIN_DIR)/kyte_client + +all: build $(TARGET) + +build: + @mkdir -p $(BIN_DIR) + @mkdir -p $(OBJ_DIR) + +# Link the target with all objects files +$(TARGET): $(OBJECTS) + $(CXX) $(CXXFLAGS) $^ -o $@ $(LDFLAGS) + +# Compile each source file to an object +$(OBJ_DIR)/%.o: $(SRC_DIR)/%.cpp + $(CXX) $(CXXFLAGS) -c $< -o $@ + +# Cleanup +clean: + @rm -rf $(OBJ_DIR) $(BIN_DIR) + +# Phony targets +.PHONY: all build clean diff --git a/examples/main.cpp b/examples/main.cpp new file mode 100644 index 0000000..8bbd5f4 --- /dev/null +++ b/examples/main.cpp @@ -0,0 +1,22 @@ +// main.cpp +#include "../include/kyte/Client.hpp" +#include + +int main() { + // Initialize the client with sample credentials and endpoint + kyte::Client client("your_public_key", "your_private_key", "your_kyte_account", + "your_kyte_identifier", "https://api.flykyte.io"); + + // Perform a GET request to a sample model with optional field and value + std::string response = client.get("SampleModel", "SampleField", "SampleValue"); + + // Print the response to the console + std::cout << "Response from Kyte API: " << response << std::endl; + + // Optionally, demonstrate more requests, e.g., POST, PUT, DELETE + std::string postData = "{\"key\":\"value\"}"; + std::string postResponse = client.post("SampleModel", postData); + std::cout << "Response from POST to Kyte API: " << postResponse << std::endl; + + return 0; +} diff --git a/include/kyte/Client.hpp b/include/kyte/Client.hpp new file mode 100644 index 0000000..ee10d85 --- /dev/null +++ b/include/kyte/Client.hpp @@ -0,0 +1,41 @@ +// Client.hpp +#ifndef KYTE_CLIENT_HPP +#define KYTE_CLIENT_HPP + +#include +#include +#include "Util.hpp" + +namespace kyte { + +class Client { +private: + std::string public_key; + std::string private_key; + std::string kyte_account; + std::string kyte_identifier; + std::string kyte_endpoint; + std::string kyte_app_id; + std::string sessionToken; + std::string transactionToken; + + std::string getIdentity(const std::string& timestamp); + std::string getSignature(const std::string& epoch); + +public: + Client(const std::string& public_key, const std::string& private_key, const std::string& kyte_account, + const std::string& kyte_identifier, const std::string& kyte_endpoint, const std::string& kyte_app_id = ""); + + std::string request(const std::string& method, const std::string& model, const std::string& field = "", + const std::string& value = "", const std::string& data = ""); + + // Methods to expose high-level functionality like post, get, put, delete + std::string post(const std::string& model, const std::string& data); + std::string get(const std::string& model, const std::string& field = "", const std::string& value = ""); + std::string put(const std::string& model, const std::string& field, const std::string& value, const std::string& data); + std::string deleteRequest(const std::string& model, const std::string& field, const std::string& value); +}; + +} // namespace kyte + +#endif // KYTE_CLIENT_HPP diff --git a/include/kyte/Util.hpp b/include/kyte/Util.hpp new file mode 100644 index 0000000..86ad985 --- /dev/null +++ b/include/kyte/Util.hpp @@ -0,0 +1,17 @@ +// Util.hpp +#ifndef KYTE_UTIL_HPP +#define KYTE_UTIL_HPP + +#include + +namespace kyte { + + // Encode data using base64 + std::string base64_encode(const std::string& input); + + // URL encode a string + std::string url_encode(const std::string& input); + +} // namespace kyte + +#endif // KYTE_UTIL_HPP diff --git a/src/Client.cpp b/src/Client.cpp new file mode 100644 index 0000000..e94381c --- /dev/null +++ b/src/Client.cpp @@ -0,0 +1,110 @@ +// Client.cpp +#include "Client.hpp" +#include +#include +#include +#include +#include +#include +#include +#include + +namespace kyte { + + Client::Client(const std::string& public_key, const std::string& private_key, const std::string& kyte_account, + const std::string& kyte_identifier, const std::string& kyte_endpoint, const std::string& kyte_app_id) + : public_key(public_key), private_key(private_key), kyte_account(kyte_account), kyte_identifier(kyte_identifier), + kyte_endpoint(kyte_endpoint), kyte_app_id(kyte_app_id), sessionToken("0"), transactionToken("0") {} + + std::string Client::getIdentity(const std::string& timestamp) { + std::string identityStr = public_key + "%" + sessionToken + "%" + timestamp + "%" + kyte_account; + identityStr = base64_encode(identityStr); + return url_encode(identityStr); + } + + std::string Client::getSignature(const std::string &epoch) { + unsigned char result[SHA256_DIGEST_LENGTH]; + unsigned int result_len; + + HMAC(EVP_sha256(), reinterpret_cast(private_key.c_str()), private_key.length(), + reinterpret_cast(epoch.c_str()), epoch.length(), result, &result_len); + + std::stringstream hex_str; + hex_str << std::hex << std::setfill('0'); + for (unsigned int i = 0; i < result_len; i++) { + hex_str << std::setw(2) << static_cast(result[i]); + } + + return hex_str.str(); + } + + // Additional utility functions or include them from other files + static size_t WriteCallback(void *contents, size_t size, size_t nmemb, std::string *userp) { + userp->append((char*)contents, size * nmemb); + return size * nmemb; + } + + std::string Client::post(const std::string& model, const std::string& data) { + return this->request("POST", model, "", "", data); + } + + std::string Client::get(const std::string& model, const std::string& field, const std::string& value) { + return this->request("GET", model, field, value, ""); + } + + std::string Client::put(const std::string& model, const std::string& field, const std::string& value, const std::string& data) { + return this->request("PUT", model, field, value, data); + } + + std::string Client::deleteRequest(const std::string& model, const std::string& field, const std::string& value) { + return this->request("DELETE", model, field, value, ""); + } + + std::string Client::request(const std::string& method, const std::string& model, const std::string& field, + const std::string& value, const std::string& data) { + std::time_t now = std::time(nullptr); + std::string epoch = std::to_string(now); + std::string timestamp = std::asctime(std::localtime(&now)); + std::string signature = getSignature(epoch); + std::string identity = getIdentity(timestamp); + + std::string endpoint = kyte_endpoint + "/" + model; + if (!field.empty() && !value.empty()) { + endpoint += "/" + field + "/" + value; + } + + CURL* curl = curl_easy_init(); + std::string readBuffer; + if (curl) { + struct curl_slist *headers = NULL; + headers = curl_slist_append(headers, ("Content-Type: application/json").c_str()); + headers = curl_slist_append(headers, ("Accept: application/json").c_str()); + headers = curl_slist_append(headers, ("x-kyte-signature: " + signature).c_str()); + headers = curl_slist_append(headers, ("x-kyte-identity: " + identity).c_str()); + if (!kyte_app_id.empty()) { + headers = curl_slist_append(headers, ("x-kyte-appid: " + kyte_app_id).c_str()); + } + + curl_easy_setopt(curl, CURLOPT_HTTPHEADER, headers); + curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, WriteCallback); + curl_easy_setopt(curl, CURLOPT_WRITEDATA, &readBuffer); + curl_easy_setopt(curl, CURLOPT_URL, endpoint.c_str()); + curl_easy_setopt(curl, CURLOPT_CUSTOMREQUEST, method.c_str()); + + if (method == "POST" || method == "PUT") { + curl_easy_setopt(curl, CURLOPT_POSTFIELDS, data.c_str()); + } + + CURLcode res = curl_easy_perform(curl); + if (res != CURLE_OK) { + fprintf(stderr, "curl_easy_perform() failed: %s\n", curl_easy_strerror(res)); + } + + curl_slist_free_all(headers); + curl_easy_cleanup(curl); + } + + return readBuffer; + } + +} // namespace kyte diff --git a/src/Util.cpp b/src/Util.cpp new file mode 100644 index 0000000..0da3822 --- /dev/null +++ b/src/Util.cpp @@ -0,0 +1,65 @@ +// Util.cpp +#include "Util.hpp" +#include +#include + +namespace kyte { + + const std::string base64_chars = + "ABCDEFGHIJKLMNOPQRSTUVWXYZ" + "abcdefghijklmnopqrstuvwxyz" + "0123456789+/"; + + std::string base64_encode(const std::string& input) { + std::string ret; + int i = 0; + int j = 0; + unsigned char char_array_3[3]; + unsigned char char_array_4[4]; + + for (unsigned char c : input) { + char_array_3[i++] = c; + if (i == 3) { + char_array_4[0] = (char_array_3[0] & 0xfc) >> 2; + char_array_4[1] = ((char_array_3[0] & 0x03) << 4) + ((char_array_3[1] & 0xf0) >> 4); + char_array_4[2] = ((char_array_3[1] & 0x0f) << 2) + ((char_array_3[2] & 0xc0) >> 6); + char_array_4[3] = char_array_3[2] & 0x3f; + + for(i = 0; (i <4) ; i++) + ret += base64_chars[char_array_4[i]]; + i = 0; + } + } + + if (i) { + for(j = i; j < 3; j++) + char_array_3[j] = '\0'; + + char_array_4[0] = (char_array_3[0] & 0xfc) >> 2; + char_array_4[1] = ((char_array_3[0] & 0x03) << 4) + ((char_array_3[1] & 0xf0) >> 4); + char_array_4[2] = ((char_array_3[1] & 0x0f) << 2) + ((char_array_3[2] & 0xc0) >> 6); + + for (j = 0; (j < i + 1); j++) + ret += base64_chars[char_array_4[j]]; + + while((i++ < 3)) + ret += '='; + } + + return ret; + } + + std::string url_encode(const std::string& input) { + std::string output; + output.reserve(input.length()); + for (size_t i = 0; i < input.size(); ++i) { + if (isalnum(input[i]) || input[i] == '-' || input[i] == '_' || input[i] == '.' || input[i] == '~') { + output += input[i]; + } else { + output += '%' + std::to_string((unsigned char)input[i]); + } + } + return output; + } + +} // namespace kyte