diff --git a/.gitignore b/.gitignore index 0898aed..141bb68 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,5 @@ build/ .kdev4/ *.kdev4 +deps/ +tags diff --git a/.vimrc b/.vimrc new file mode 100644 index 0000000..371c485 --- /dev/null +++ b/.vimrc @@ -0,0 +1,10 @@ +set colorcolumn=110 +highlight ColorColumn ctermbg=darkgray +augroup project + autocmd! + autocmd BufRead,BufNewFile *.h,*.c set filetype=c.doxygen +augroup END +nnoremap :!mkdir -p build cmake -B build make -C build +nnoremap :!build/PiHelper/pihelper localhost +nnoremap :!rm -rf build +let &path.="/Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX.sdk/usr/include," diff --git a/budget.json b/budget.json new file mode 100644 index 0000000..ab375dd --- /dev/null +++ b/budget.json @@ -0,0 +1 @@ +{"id":"Gbhqm79yC8NereIONtq7W2Mf9MD2y1AU","name":"Monthly Budget","description":"Monthly expenses"} diff --git a/include/twigs/api_service.h b/include/twigs/api_service.h new file mode 100644 index 0000000..e3bd250 --- /dev/null +++ b/include/twigs/api_service.h @@ -0,0 +1,3 @@ +#ifndef API_SERVICE_H +#define API_SERVICE_H +#endif diff --git a/include/twigs/budget.h b/include/twigs/budget.h index 80d86a5..4c39d5f 100644 --- a/include/twigs/budget.h +++ b/include/twigs/budget.h @@ -1,33 +1,28 @@ #ifndef BUDGET_H #define BUDGET_H -#include #include "hashable.h" #include "identifiable.h" +#include "serializable.h" +#include using namespace std; -class Budget: public Identifiable, public Hashable { - private: - string name; - string description; +class Budget : public Identifiable, public Hashable, public Serializable { +private: + string id; + string name; + string description; - public: - string getName() { - return this->name; - } - - void setName(string name) { - this->name = name; - } - - string getDescription() { - return this->description; - } - - void setDescription(string description) { - this->description = description; - } - - string hash() override; +public: + Budget(); + Budget(std::string id, std::string name, std::string description); + string getId() override; + string getName(); + void setName(string name); + string getDescription(); + void setDescription(string description); + string hash() override; + string serialize() override; + static Budget deserialize(std::string data); }; #endif diff --git a/include/twigs/category.h b/include/twigs/category.h index efca750..5fe3bf1 100644 --- a/include/twigs/category.h +++ b/include/twigs/category.h @@ -1,64 +1,38 @@ -#ifndef CATEGORY_H #define CATEGORY_H -#include +#ifndef CATEGORY_H #include "hashable.h" #include "identifiable.h" +#include "serializable.h" +#include -class Category: public Identifiable, public Hashable { - private: - std::string id; - std::string name; - std::string description; - long amount; - bool expense; - bool archived; - std::string budgetId; +class Category : public Identifiable, public Hashable, public Serializable { +private: + std::string id; + std::string name; + std::string description; + unsigned int amount; + bool expense; + bool archived; + std::string budgetId; - public: - std::string getId() { - return this->id; - } - - std::string getName() { - return this->name; - } - - void setName(std::string name) { - this->name = name; - } - - std::string getDescription() { - return this->description; - } - - void setDescription(std::string description) { - this->description = description; - } - - long getAmount() { - return this->amount; - } - - void setAmount(long amount) { - this->amount = amount; - } - - bool isExpense() { - return this->expense; - } - - void setExpense(bool expense) { - this->expense = expense; - } - - bool isArchived() { - return this->archived; - } - - void setArchived(bool archived) { - this->archived = archived; - } - - std::string hash() override; +public: + Category(); + Category(std::string id, std::string name, std::string budgetId, + std::string description = "", unsigned int amount = 0, + bool expense = true, bool archived = false); + std::string getId() override; + std::string getName(); + void setName(std::string name); + std::string getDescription(); + void setDescription(std::string description); + unsigned int getAmount(); + void setAmount(unsigned int amount); + bool isExpense(); + void setExpense(bool expense); + bool isArchived(); + void setArchived(bool archived); + std::string hash() override; + string serialize() override; + static Categpry deserialize(std::string data); }; #endif diff --git a/include/twigs/db_service.h b/include/twigs/db_service.h new file mode 100644 index 0000000..046b294 --- /dev/null +++ b/include/twigs/db_service.h @@ -0,0 +1,4 @@ +#ifndef DB_SERVICE_H +#define DB_SERVICE_H + +#endif diff --git a/include/twigs/identifiable.h b/include/twigs/identifiable.h index 9dcac11..3baab8e 100644 --- a/include/twigs/identifiable.h +++ b/include/twigs/identifiable.h @@ -3,15 +3,7 @@ #include class Identifiable { - private: - std::string id; - public: - std::string getId() { - return this->id; - } - void setId(std::string id) { - this->id = id; - } + virtual std::string getId() = 0; }; #endif diff --git a/include/twigs/permission.h b/include/twigs/permission.h index db16c48..892e18a 100644 --- a/include/twigs/permission.h +++ b/include/twigs/permission.h @@ -13,6 +13,7 @@ enum Permission { class UserPermission: public Identifiable { private: + std::string id; std::string userId; std::string budgetId; Permission permission; @@ -23,6 +24,8 @@ class UserPermission: public Identifiable { this->budgetId = budgetId; } + std::string getId() override; + std::string getUserId() { return this->userId; } diff --git a/include/twigs/serializable.h b/include/twigs/serializable.h new file mode 100644 index 0000000..20a5920 --- /dev/null +++ b/include/twigs/serializable.h @@ -0,0 +1,9 @@ +#ifndef SERIALIZABLE_H +#define SERIALIZABLE_H +#include + +class Serializable { +public: + virtual std::string serialize() = 0; +}; +#endif diff --git a/include/twigs/transaction.h b/include/twigs/transaction.h index 36d947a..9bfdc84 100644 --- a/include/twigs/transaction.h +++ b/include/twigs/transaction.h @@ -1,113 +1,51 @@ #ifndef TRANSACTION_H #define TRANSACTION_H -#include -#include -#include #include "hashable.h" #include "identifiable.h" +#include "serializable.h" +#include +#include +#include -using namespace std; +class Transaction : public Identifiable, public Hashable, public Serializable { +private: + std::string id; + std::string name; + std::string description; + tm date; + unsigned int amount; + bool expense; + std::string categoryId; + std::string budgetId; + std::string createdBy; -class Transaction: public Identifiable, public Hashable { - private: - string name; - string description; - tm date; - long amount; - bool expense; - bool archived; - string categoryId; - string budgetId; - - public: - Transaction() { - this->name = ""; - this->description = ""; - time_t now = time(0); - this->date = *gmtime(&now); - this->amount = 0; - this->expense = true; - this->archived = false; - } - - string getName() { - return this->name; - } - - void setName(string name) { - this->name = name; - } - - string getDescription() { - return this->description; - } - - void setDescription(string description) { - this->description = description; - } - - /** - * Returns the date as a string formatted as YYYY-MM-ddTHH:mm:ssZ - */ - string getDate() { - char date[25]; - snprintf( - date, - 21, - "%04d-%02d-%02dT%02d:%02d:%02dZ", - this->date.tm_year, - this->date.tm_mon + 1, - this->date.tm_mday, - this->date.tm_hour, - this->date.tm_min, - this->date.tm_sec - ); - return date; - } - - /** - * Sets the date using YYYY-MM-ddTHH:mm:ssZ format - */ - void setDate(string date) { - //TODO: Handle exceptions - int year = stoi(date.substr(0,4)); - int mon = stoi(date.substr(5, 2)); - int day = stoi(date.substr(8, 2)); - int hours = stoi(date.substr(11, 2)); - int min = stoi(date.substr(14, 2)); - int sec = stoi(date.substr(17, 2)); - this->date.tm_year = year; - this->date.tm_mon = mon - 1; - this->date.tm_mday = day; - this->date.tm_hour = hours; - this->date.tm_min = min; - this->date.tm_sec = sec; - } - - long getAmount() { - return this->amount; - } - - void setAmount(long amount) { - this->amount = amount; - } - - bool isExpense() { - return this->expense; - } - - void setExpense(bool expense) { - this->expense = expense; - } - - bool isArchived() { - return this->archived; - } - - void setArchived(bool archived) { - this->archived = archived; - } - - string hash() override; +public: + Transaction(); + Transaction(std::string id, std::string name, tm date, std::string createdBy, + unsigned int amount = 0, std::string description = "", + bool expense = true, std::string categoryId = "", + std::string budgetId = ""); + std::string getId() override; + std::string getName(); + void setName(std::string name); + std::string getDescription(); + void setDescription(std::string description); + tm getDate(); + void setDate(tm date); + unsigned int getAmount(); + void setAmount(unsigned int amount); + bool isExpense(); + void setExpense(bool expense); + bool isArchived(); + void setArchived(bool archived); + std::string getCategoryId(); + void setCategoryId(std::string categoryId); + std::string getBudgetId(); + void setBudgetId(std::string budgetId); + std::string getCreatedBy(); + void setCreatedBy(std::string createdBy); + std::string hash() override; + std::string serialize() override; + static Transaction deserialize(std::string data); }; #endif diff --git a/include/twigs/transaction_repository.h b/include/twigs/transaction_repository.h new file mode 100644 index 0000000..cfeeca6 --- /dev/null +++ b/include/twigs/transaction_repository.h @@ -0,0 +1,13 @@ +#define TRANSACTION_REPOSITORY_H +#ifndef TRANSACTION_REPOSITORY_H +#include "transaction.h" + +class TransactionRepository { +public: + Transaction findById(std::string id); + Transaction *findAll(std::string *budgetIds, std::string *categoryIds, + std::string *from, std::string *to, int count, int page); + Transaction save(Transaction transaction); + void remove(Transaction transaction); +}; +#endif diff --git a/include/twigs/user.h b/include/twigs/user.h index 511db5c..93f2db2 100644 --- a/include/twigs/user.h +++ b/include/twigs/user.h @@ -5,11 +5,13 @@ class User: public Identifiable { private: + std::string id; std::string name; std::string email; std::string avatar; public: + std::string getId() override; std::string getName(); void setName(std::string name); std::string getEmail(); diff --git a/include/twigs/utils.h b/include/twigs/utils.h new file mode 100644 index 0000000..63554ff --- /dev/null +++ b/include/twigs/utils.h @@ -0,0 +1,12 @@ +#ifndef UTILS_H +#define UTILS_H +#include + +namespace util { +std::string randomId(); +std::string hash(std::string data); +tm parseDate(std::string str); +std::string printDate(tm date); +} // namespace util + +#endif diff --git a/scripts/build-android b/scripts/build-android new file mode 100755 index 0000000..a764416 --- /dev/null +++ b/scripts/build-android @@ -0,0 +1,198 @@ +#!/usr/bin/env bash + +# Configure the following variables according to your needs + +NDK_VERSION="21.3.6528147" +OPENSSL_TAG_VERSION="OpenSSL_1_1_1g " +CURL_TAG_VERSION="master " +JSONCPP_TAG_VERSION="master" +ANDROID_ARCHS="arm arm64 x86 x86_64" +NDK=$HOME/Android/Sdk/ndk/$NDK_VERSION +MIN_SDK_VERSION=23 # Can't go any lower unfortunately. See the cURL docs for more info. +TARGET_SDK_VERSION=29 + +# Edit below at your own risk + +get_abi() { + case $1 in + "arm") + echo -n "armeabi-v7a" + ;; + "arm64") + echo -n "arm64-v8a" + ;; + "x86") + echo -n "x86" + ;; + "x86_64") + echo -n "x86_64" + ;; + esac +} + +make_openssl() { + ( + cd deps + test -d openssl || git clone --branch $OPENSSL_TAG_VERSION https://github.com/openssl/openssl.git + for ARCH in $ANDROID_ARCHS; do + ( + export PREFIX="$BUILD_DIR/android/$ARCH" + mkdir -p "$PREFIX" + cd openssl + git checkout $OPENSSL_TAG_VERSION + make clean + export ANDROID_NDK_HOME=$NDK + export ANDROID_NDK_ROOT=$NDK + export PATH=$ANDROID_NDK_HOME/toolchains/llvm/prebuilt/linux-x86_64/bin:$ANDROID_NDK_HOME/toolchains/arm-linux-androideabi-4.9/prebuilt/linux-x86_64/bin:$PATH + ./Configure \ + --prefix="$PREFIX" \ + -D__ANDROID_API__=$TARGET_SDK_VERSION \ + no-shared \ + android-$ARCH + make install_dev + ) + done + ) +} + +make_curl() { + ( + cd deps + test -d curl || git clone --branch $CURL_TAG_VERSION https://github.com/curl/curl.git + for ARCH in $ANDROID_ARCHS; do + ( + export PREFIX="$BUILD_DIR/android/$ARCH" + mkdir -p "$PREFIX" + cd curl + git checkout $CURL_TAG_VERSION + test -f configure || ./buildconf + export HOST_TAG=linux-x86_64 + export TOOLCHAIN=$NDK/toolchains/llvm/prebuilt/$HOST_TAG + case $ARCH in + "arm") + export CURL_ARCH="arm-linux-androideabi" + export CLANG="armv7a-linux-androideabi" + ;; + "arm64") + export CURL_ARCH="aarch64-linux-android" + export CLANG=$CURL_ARCH + ;; + "x86") + export CURL_ARCH="i686-linux-android" + export CLANG=$CURL_ARCH + ;; + "x86_64") + export CURL_ARCH="x86_64-linux-android" + export CLANG=$CURL_ARCH + ;; + esac + export AR=$TOOLCHAIN/bin/$CURL_ARCH-ar + export AS=$TOOLCHAIN/bin/$CURL_ARCH-as + export CC=$TOOLCHAIN/bin/${CLANG}${MIN_SDK_VERSION}-clang + export CXX=$TOOLCHAIN/bin/${CLANG}${MIN_SDK_VERSION}-clang++ + export LD=$TOOLCHAIN/bin/$CURL_ARCH-ld + export RANLIB=$TOOLCHAIN/bin/$CURL_ARCH-ranlib + export STRIP=$TOOLCHAIN/bin/$CURL_ARCH-strip + make clean + ./configure --prefix="$PREFIX" \ + --host $CURL_ARCH \ + --with-pic \ + --disable-shared \ + --with-ssl="$PREFIX" + make install + ) + done + ) +} + +make_jsoncpp() { + ( + cd deps + test -d jsoncpp || git clone --branch $JSONCPP_TAG_VERSION https://github.com/open-source-parsers/jsoncpp + for ARCH in $ANDROID_ARCHS; do + ( + export PREFIX="$BUILD_DIR/android/$ARCH" + mkdir -p "$PREFIX" + cd jsoncpp + git checkout $JSONCPP_TAG_VERSION + test -d $ARCH && rm -rf $ARCH + mkdir $ARCH + pushd $ARCH + export ABI="$(get_abi $ARCH)" + #TODO: CMake is deprecated, this should use meson/ninja + cmake \ + -DCMAKE_TOOLCHAIN_FILE=$NDK/build/cmake/android.toolchain.cmake \ + -DANDROID_ABI=$ABI \ + -DANDROID_NATIVE_API_LEVEL=$MIN_SDK_VERSION \ + -DCMAKE_INSTALL_PREFIX=$PREFIX \ + -DBUILD_OBJECT_LIBS=OFF \ + -DBUILD_SHARED_LIBS=OFF \ + -DJSONCPP_WITH_TESTS=OFF \ + -DBUILD_STATIC_LIBS=ON \ + .. + make + make install + ) + done + ) +} + +make_twigs() { + ( + for ARCH in $ANDROID_ARCHS; do + ( + export PREFIX="$BUILD_DIR/android/$ARCH" + export ABI="$(get_abi $ARCH)" + mkdir -p "$PREFIX" + cd "$BUILD_DIR" + test -d $ARCH && rm -rf $ARCH + mkdir $ARCH + pushd $ARCH + cmake \ + -DCMAKE_FIND_ROOT_PATH=$PREFIX \ + -DCMAKE_INCLUDE_PATH=$PREFIX/include \ + -DCMAKE_PREFIX_PATH=$PREFIX \ + -DCMAKE_TOOLCHAIN_FILE=$NDK/build/cmake/android.toolchain.cmake \ + -DANDROID_ABI=$ABI \ + -DANDROID_NATIVE_API_LEVEL=$MIN_SDK_VERSION \ + -DCMAKE_INSTALL_PREFIX=$PREFIX \ + -DBUILD_SHARED_LIBS=OFF \ + -DBUILD_TESTING=OFF \ + -DBUILD_STATIC_LIBS=ON \ + ../.. + make + make install + ) + done + ) +} + +package() { + for ARCH in $ANDROID_ARCHS; do + ( + export ABI="$(get_abi $ARCH)" + cd $BUILD_DIR/android + cp -r $ARCH/include . + mv $ARCH/lib/*.a $ARCH/lib/*.la $ARCH/ + rm -rf $ARCH/{bin,lib,include,share} + if [ "$ARCH" != "$ABI" ]; then + mv $ARCH $ABI + fi + ) + done +} + +( +if [ "$(dirname $0)" == "." ]; then + cd .. +fi +test -d deps || mkdir deps +export BUILD_DIR="$PWD/build" +test -d "$BUILD_DIR" || mkdir -p "$BUILD_DIR" +make_openssl +make_curl +make_jsoncpp +make_twigs +package +) + diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index e2f3330..7f76d93 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -1,7 +1,7 @@ add_subdirectory(libtwigs) -option(TWIGS_TESTS "Build tests for Twigs" ON) +option(BUILD_TESTING "Build tests for Twigs" ON) -if (TWIGS_TESTS) +if (BUILD_TESTING) add_subdirectory(test) endif() diff --git a/src/libtwigs/CMakeLists.txt b/src/libtwigs/CMakeLists.txt index 286a54b..a66f1ec 100644 --- a/src/libtwigs/CMakeLists.txt +++ b/src/libtwigs/CMakeLists.txt @@ -25,28 +25,41 @@ set(PUBLIC_HEADERS ${TWIGS_INCLUDE_DIR}/twigs/hashable.h ${TWIGS_INCLUDE_DIR}/twigs/identifiable.h ${TWIGS_INCLUDE_DIR}/twigs/permission.h + ${TWIGS_INCLUDE_DIR}/twigs/serializable.h ${TWIGS_INCLUDE_DIR}/twigs/transaction.h ${TWIGS_INCLUDE_DIR}/twigs/user.h + ${TWIGS_INCLUDE_DIR}/twigs/utils.h ) set(TWIGS_SOURCES budget.cpp #category.cpp transaction.cpp + utils.cpp #twigs.cpp #user.cpp ) -#if (NOT TARGET CURL) - #find_library( - #CURL - #NAMES curl libcurl - #PATHS /usr/local/lib /usr/local/lib64 /usr/lib /usr/lib64 /lib - #) - #if (NOT CURL) - #message(SEND_ERROR "Did not find curl") - #endif() -#endif() +if (NOT TARGET CURL) + find_library( + CURL + NAMES curl libcurl + PATHS /usr/local/lib /usr/local/lib64 /usr/lib /usr/lib64 /lib + ) + if (NOT CURL) + message(SEND_ERROR "Did not find curl") + endif() +endif() +if (NOT TARGET JSONCPP) + find_library( + JSONCPP + NAMES jsoncpp libjsoncpp + PATHS /usr/local/lib /usr/local/lib64 /usr/lib /usr/lib64 /lib + ) + if (NOT JSONCPP) + message(SEND_ERROR "Did not find jsoncpp") + endif() +endif() #if (NOT TARGET JSONC) #find_library( #JSONC @@ -57,31 +70,28 @@ set(TWIGS_SOURCES #message(SEND_ERROR "Did not find json-c") #endif() #endif() -#if (NOT TARGET CRYPTO) - #find_library( - #CRYPTO - #NAMES crypto libcrypto - #PATHS /usr/local/lib /usr/local/lib64 /usr/lib /usr/lib64 /lib - #) - #if (NOT CRYPTO) - #message(SEND_ERROR "Did not find OpenSSL") - #endif() -#endif() -#if (NOT TARGET OPENSSL) - #find_library( - #OPENSSL - #NAMES ssl libssl - #PATHS /usr/local/lib /usr/local/lib64 /usr/lib /usr/lib64 /lib - #) - #if (NOT OPENSSL) - #message(SEND_ERROR "Did not find OpenSSL") - #endif() -#endif() +if (NOT TARGET CRYPTO) + find_library( + CRYPTO + NAMES crypto libcrypto + PATHS /usr/local/lib /usr/local/lib64 /usr/lib /usr/lib64 /lib + ) + if (NOT CRYPTO) + message(SEND_ERROR "Did not find OpenSSL") + endif() +endif() +if (NOT TARGET OPENSSL) + find_library( + OPENSSL + NAMES ssl libssl + PATHS /usr/local/lib /usr/local/lib64 /usr/lib /usr/lib64 /lib + ) + if (NOT OPENSSL) + message(SEND_ERROR "Did not find OpenSSL") + endif() +endif() -option(TWIGS_STATIC "Build Twigs as a static library" ON) -option(TWIGS_SHARED "Build Twigs as a shared library" OFF) - -if (TWIGS_STATIC) +if (BUILD_SHARED_LIBS) add_library(libtwigs SHARED ${TWIGS_SOURCES} ${PUBLIC_HEADERS} @@ -93,12 +103,20 @@ else() ) endif() -target_include_directories(libtwigs PRIVATE +target_include_directories(libtwigs PUBLIC $ $ $ + SYSTEM ${CMAKE_INCLUDE_PATH} + SYSTEM /usr/local/include ) +target_link_libraries(libtwigs ${JSONCPP} + ${CURL} + ${CRYPTO} + ${OPENSSL} + ) + set_target_properties(libtwigs PROPERTIES OUTPUT_NAME "twigs" VERSION ${PROJECT_VERSION} @@ -106,12 +124,8 @@ set_target_properties(libtwigs PROPERTIES ) install(TARGETS libtwigs + RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR} LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR} + OBJECTS DESTINATION ${CMAKE_INSTALL_LIBDIR} ) - -#target_link_libraries(twigs ${CURL} - #${JSONC} - #${CRYPTO} - #${OPENSSL} - #) diff --git a/src/libtwigs/budget.cpp b/src/libtwigs/budget.cpp index d0f52de..e1568fe 100644 --- a/src/libtwigs/budget.cpp +++ b/src/libtwigs/budget.cpp @@ -1,6 +1,51 @@ -#include #include +#include +#include +#include -std::string Budget::hash() { - return ""; +Budget::Budget() : Budget(util::randomId(), "", "") {} + +Budget::Budget(std::string id, std::string name, std::string description) + : id(id), name(name), description(description) {} + +std::string Budget::getId() { return this->id; } + +string Budget::getName() { return this->name; } + +void Budget::setName(string name) { this->name = name; } + +string Budget::getDescription() { return this->description; } + +void Budget::setDescription(string description) { + this->description = description; } + +std::string Budget::serialize() { + Json::Value budget; + budget["id"] = this->getId(); + budget["name"] = this->getName(); + budget["description"] = this->getDescription(); + Json::StreamWriterBuilder builder; + builder["indentation"] = ""; + return Json::writeString(builder, budget); +} + +Budget Budget::deserialize(std::string data) { + Json::CharReaderBuilder builder; + Json::Value root; + if (!builder.newCharReader()->parse(&data[0], &data[0] + data.size(), &root, + NULL)) { + // TODO: Log errors + // std::cout << "Failed to create budget from JSON" << "\n"; + throw 5; + } + std::string id = root.get("id", "").asString(); + if (id == "") { + throw 5; + } + std::string name = root.get("name", "").asString(); + std::string description = root.get("description", "").asString(); + return Budget(id, name, description); +} + +string Budget::hash() { return util::hash(serialize()); } diff --git a/src/libtwigs/category.cpp b/src/libtwigs/category.cpp new file mode 100644 index 0000000..e8a4582 --- /dev/null +++ b/src/libtwigs/category.cpp @@ -0,0 +1,76 @@ +#include +#include + +Category::Category() : Category(util::randomId(), "", "") {} + +Category::Category(std::string id, std::string name, std::string budgetId, + std::string description, unsigned int amount, bool expense, + bool archived) + : id(id), name(name), budgetId(budgetId), description(description), + amount(amount), expense(expense), archived(archived) {} + +std::string Category::getId() { return this->id; } + +std::string Category::getName() { return this->name; } + +void Category::setName(std::string name) { this->name = name; } + +std::string Category::getDescription() { return this->description; } + +void Category::setDescription(std::string description) { + this->description = description; +} + +long Category::getAmount() { return this->amount; } + +void Category::setAmount(long amount) { this->amount = amount; } + +bool Category::isExpense() { return this->expense; } + +void Category::setExpense(bool expense) { this->expense = expense; } + +bool Category::isArchived() { return this->archived; } + +void Category::setArchived(bool archived) { this->archived = archived; } + +std::string Category::getBudgetId() { return this->budgetId; } + +void Category::setBudgetId(std::string budgetId) { this->budgetId = budgetId; } + +std::string Category::serialize() { + Json::Value category; + category["id"] = this->getId(); + category["name"] = this->getName(); + category["description"] = this->getDescription(); + category["amount"] = this->getAmount(); + category["expense"] = this->isExpense(); + category["archived"] = this->isArchived(); + category["budgetId"] = this->getBudgetId(); + Json::StreamWriterBuilder builder; + builder["indentation"] = ""; + return Json::writeString(builder, transaction); +} + +Category Category::deserialize(std::string data) { + Json::CharReaderBuilder builder; + Json::Value root; + if (!builder.newCharReader()->parse(&data[0], &data[0] + data.size(), &root, + NULL)) { + // TODO: Log errors + // std::cout << "Failed to create transaction from JSON" << "\n"; + throw 5; + } + std::string id = root.get("id", "").asString(); + if (id == "") { + throw 5; + } + std::string name = root.get("name", "").asString(); + std::string description = root.get("description", "").asString(); + long amount = root.get("amount", 0).asUInt(); + bool expense = root.get("expense", true).asBool(); + bool archived = root.get("archived", true).asBool(); + std::string budgetId = root.get("budgetId", "").asString(); + return Category(id, name, description); +} + +string Category::hash() { return util::hash(serialize()); } diff --git a/src/libtwigs/transaction.cpp b/src/libtwigs/transaction.cpp index 3156602..70d1534 100644 --- a/src/libtwigs/transaction.cpp +++ b/src/libtwigs/transaction.cpp @@ -1,8 +1,105 @@ +#include +#include #include +#include using namespace std; -string Transaction::hash() { - throw 5; -// return ""; +tm *now() { + time_t now = time(NULL); + return gmtime(&now); } + +Transaction::Transaction() : Transaction(util::randomId(), "", *now(), "") {} + +Transaction::Transaction(std::string id, std::string name, tm date, + std::string createdBy, unsigned int amount, + std::string description, bool expense, + std::string categoryId, std::string budgetId) + : id(id), name(name), description(description), date(date), amount(amount), + expense(expense), categoryId(categoryId), budgetId(budgetId), + createdBy(createdBy) {} + +string Transaction::getId() { return this->id; } + +string Transaction::getName() { return this->name; } + +void Transaction::setName(std::string name) { this->name = name; } + +string Transaction::getDescription() { return this->description; } + +void Transaction::setDescription(string description) { + this->description = description; +} + +tm Transaction::getDate() { return this->date; } + +void Transaction::setDate(tm date) { this->date = date; } + +unsigned int Transaction::getAmount() { return this->amount; } + +void Transaction::setAmount(unsigned int amount) { this->amount = amount; } + +bool Transaction::isExpense() { return this->expense; } + +void Transaction::setExpense(bool expense) { this->expense = expense; } + +string Transaction::getCategoryId() { return this->categoryId; } + +void Transaction::setCategoryId(string categoryId) { + this->categoryId = categoryId; +} + +string Transaction::getBudgetId() { return this->budgetId; } + +void Transaction::setBudgetId(string budgetId) { this->budgetId = budgetId; } + +string Transaction::getCreatedBy() { return this->createdBy; } + +void Transaction::setCreatedBy(string createdBy) { + this->createdBy = createdBy; +} + +std::string Transaction::serialize() { + Json::Value transaction; + transaction["id"] = this->getId(); + transaction["title"] = this->getName(); + transaction["description"] = this->getDescription(); + transaction["date"] = util::printDate(this->getDate()); + transaction["amount"] = this->getAmount(); + transaction["expense"] = this->isExpense(); + transaction["budgetId"] = this->getBudgetId(); + transaction["categoryId"] = this->getCategoryId(); + transaction["createdBy"] = this->getCreatedBy(); + Json::StreamWriterBuilder builder; + builder["indentation"] = ""; + return Json::writeString(builder, transaction); +} + +Transaction Transaction::deserialize(std::string data) { + Json::CharReaderBuilder builder; + Json::Value root; + if (!builder.newCharReader()->parse(&data[0], &data[0] + data.size(), &root, + NULL)) { + // TODO: Log errors + // std::cout << "Failed to create transaction from JSON" << "\n"; + throw 5; + } + std::string id = root.get("id", "").asString(); + if (id == "") { + throw 5; + } + std::string name = root.get("title", "").asString(); + std::string description = root.get("description", "").asString(); + std::string dateStr = root.get("date", "").asString(); + tm date = util::parseDate(dateStr); + long amount = root.get("amount", 0).asUInt(); + bool expense = root.get("expense", true).asBool(); + std::string budgetId = root.get("budgetId", "").asString(); + std::string categoryId = root.get("categoryId", "").asString(); + std::string createdBy = root.get("createdBy", "").asString(); + return Transaction(id, name, date, createdBy, amount, description, expense, + categoryId, budgetId); +} + +string Transaction::hash() { return util::hash(serialize()); } diff --git a/src/libtwigs/utils.cpp b/src/libtwigs/utils.cpp new file mode 100644 index 0000000..d0feed7 --- /dev/null +++ b/src/libtwigs/utils.cpp @@ -0,0 +1,61 @@ +#include +#include +#include + +namespace util { +std::string randomId() { + const std::string CHARACTERS = + "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"; + const int ID_LEN = 32; + + std::random_device rd; + std::mt19937 engine(rd()); + std::uniform_int_distribution<> distribution(0, CHARACTERS.size() - 1); + + std::string random_string; + + for (std::size_t i = 0; i < ID_LEN; ++i) { + random_string += CHARACTERS[distribution(engine)]; + } + + return random_string; +} + +tm parseDate(std::string str) { + static tm date; + // TODO: Handle exceptions + int year = stoi(str.substr(0, 4)); + int mon = stoi(str.substr(5, 2)); + int day = stoi(str.substr(8, 2)); + int hours = stoi(str.substr(11, 2)); + int min = stoi(str.substr(14, 2)); + int sec = stoi(str.substr(17, 2)); + date.tm_year = year; + date.tm_mon = mon - 1; + date.tm_mday = day; + date.tm_hour = hours; + date.tm_min = min; + date.tm_sec = sec; + return date; +} + +std::string printDate(tm date) { + static char str[25]; + snprintf(str, 21, "%04d-%02d-%02dT%02d:%02d:%02dZ", date.tm_year, + date.tm_mon + 1, date.tm_mday, date.tm_hour, date.tm_min, + date.tm_sec); + return str; +} + +std::string hash(std::string data) { + unsigned char bytes[SHA256_DIGEST_LENGTH]; + SHA256((unsigned char *) &data[0], data.size(), bytes); + char hash[(SHA256_DIGEST_LENGTH * 2) + 1]; + int i; + for(i = 0; i < SHA256_DIGEST_LENGTH; i++) { + sprintf(hash + (i * 2), "%02x", bytes[i]); + } + hash[SHA256_DIGEST_LENGTH * 2] = '\0'; + return std::string(hash, SHA256_DIGEST_LENGTH * 2); +} +} // namespace util diff --git a/src/test/CMakeLists.txt b/src/test/CMakeLists.txt index 7de2ddd..8d0620d 100644 --- a/src/test/CMakeLists.txt +++ b/src/test/CMakeLists.txt @@ -6,29 +6,27 @@ set(TEST_SOURCES budget_test.cpp transaction_test.h transaction_test.cpp + utils_test.h + utils_test.cpp ) find_library(CPPUNIT cppunit) -add_executable(twigs_test - ${TEST_SOURCES} - ${PUBLIC_HEADERS} -) +add_executable(twigs_test ${TEST_SOURCES}) target_link_libraries(twigs_test libtwigs ${CPPUNIT}) - -target_include_directories(twigs_test PRIVATE - $ - $ - $ -) - -message("CMAKE_INSTALL_INCLUDEDIR: ${CMAKE_INSTALL_INCLUDEDIR}") -message("CMAKE_CURRENT_LIST_DIR}/TWIGS_INCLUDE_DIR ${CMAKE_CURRENT_LIST_DIR}/${TWIGS_INCLUDE_DIR}") -message("PROJECT_BINARY_DIR/include/twigs: ${PROJECT_BINARY_DIR}/include/twigs}") - + set_target_properties(twigs_test PROPERTIES OUTPUT_NAME "twigstest" VERSION ${PROJECT_VERSION} SOVERSION ${PROJECT_VERSION_MAJOR} ) + +add_test(NAME twigs_test + COMMAND ${CMAKE_CROSSCOMPILING_EMULATOR} $ +) + +add_custom_command(TARGET twigs_test + POST_BUILD + COMMAND ${CMAKE_CROSSCOMPILING_EMULATOR} $ + ) diff --git a/src/test/budget_test.cpp b/src/test/budget_test.cpp index 4849c6b..50e6932 100644 --- a/src/test/budget_test.cpp +++ b/src/test/budget_test.cpp @@ -1,6 +1,41 @@ -#include #include "budget_test.h" +#include -void BudgetTest::parseDate() { - Budget budget; +void BudgetTest::serialize() { + const std::string json = + R"({"description":"Monthly expenses","id":"Gbhqm79yC8NereIONtq7W2Mf9MD2y1AU","name":"Monthly Budget"})"; + Budget b = Budget("Gbhqm79yC8NereIONtq7W2Mf9MD2y1AU", "Monthly Budget", + "Monthly expenses"); + CPPUNIT_ASSERT_EQUAL_MESSAGE("Budget serialization failed", json, + b.serialize()); +} + +void BudgetTest::deserialize() { + const std::string json = + R"({"description":"Monthly expenses","id":"Gbhqm79yC8NereIONtq7W2Mf9MD2y1AU","name":"Monthly Budget"})"; + Budget b = Budget::deserialize(json); + CPPUNIT_ASSERT_EQUAL_MESSAGE("Budget id parsing failed", + std::string("Gbhqm79yC8NereIONtq7W2Mf9MD2y1AU"), + b.getId()); + CPPUNIT_ASSERT_EQUAL_MESSAGE("Budget name parsing failed", + std::string("Monthly Budget"), b.getName()); + CPPUNIT_ASSERT_EQUAL_MESSAGE("Budget description parsing failed", + std::string("Monthly expenses"), + b.getDescription()); +} + +void BudgetTest::defaultIdsDifferent() { + Budget b1; + Budget b2; + CPPUNIT_ASSERT(b1.getId() != b2.getId()); +} + +void BudgetTest::hashBudget() { + Budget b = Budget("Gbhqm79yC8NereIONtq7W2Mf9MD2y1AU", "Monthly Budget", + "Monthly expenses"); + CPPUNIT_ASSERT_EQUAL_MESSAGE( + "Budget hash failed", + std::string( + "12fd354740ec016a4b50e2edd1e586107784fe7b88001d8d6ab7ba9ce38aa1d6"), + b.hash()); } diff --git a/src/test/budget_test.h b/src/test/budget_test.h index d2d8141..62e14de 100644 --- a/src/test/budget_test.h +++ b/src/test/budget_test.h @@ -7,10 +7,16 @@ class BudgetTest: public CppUnit::TestCase { public: - void parseDate(); - + void serialize(); + void deserialize(); + void defaultIdsDifferent(); + void hashBudget(); + CPPUNIT_TEST_SUITE( BudgetTest ); - CPPUNIT_TEST( parseDate ); + CPPUNIT_TEST( serialize ); + CPPUNIT_TEST( deserialize ); + CPPUNIT_TEST( defaultIdsDifferent ); + CPPUNIT_TEST( hashBudget ); CPPUNIT_TEST_SUITE_END(); }; diff --git a/src/test/main.cpp b/src/test/main.cpp index ed97c2a..1a0d2fe 100644 --- a/src/test/main.cpp +++ b/src/test/main.cpp @@ -1,12 +1,16 @@ #include #include "budget_test.h" #include "transaction_test.h" +#include "utils_test.h" +CPPUNIT_TEST_SUITE_REGISTRATION (UtilsTest); +CPPUNIT_TEST_SUITE_REGISTRATION (BudgetTest); CPPUNIT_TEST_SUITE_REGISTRATION (TransactionTest); int main() { BudgetTest budgetTest; TransactionTest transactionTest; + UtilsTest utilsTest; CppUnit::Test *test = CppUnit::TestFactoryRegistry::getRegistry().makeTest(); CppUnit::TextTestRunner runner; diff --git a/src/test/transaction_test.cpp b/src/test/transaction_test.cpp index 22e59cd..0625834 100644 --- a/src/test/transaction_test.cpp +++ b/src/test/transaction_test.cpp @@ -1,9 +1,84 @@ +#include "transaction_test.h" #include #include -#include "transaction_test.h" +#include +#include +#include -void TransactionTest::parseDate() { - Transaction transaction; - transaction.setDate("2020-01-31T23:59:59Z"); - CPPUNIT_ASSERT_EQUAL_MESSAGE("Transaction date parsing failed", std::string("2020-01-31T23:59:59Z"), transaction.getDate()); +void TransactionTest::serialize() { + const std::string json = + R"({"amount":2108,"budgetId":"Gbhqm79yC8NereIONtq7W2Mf9MD2y1AU","categoryId":"Q4R8QLhSksL2wi7XzmrqhL06qm2aZ2S4","createdBy":"1","date":"2021-01-08T13:06:00Z","description":"Books","expense":true,"id":"2snbSS8flrSH6OrvmSEmqyvaduOi2uc2","title":"Amazon"})"; + + tm date; + date.tm_year = 2021; + date.tm_mon = 0; + date.tm_mday = 8; + date.tm_hour = 13; + date.tm_min = 6; + date.tm_sec = 0; + Transaction t = + Transaction("2snbSS8flrSH6OrvmSEmqyvaduOi2uc2", "Amazon", date, "1", 2108, + "Books", true, "Q4R8QLhSksL2wi7XzmrqhL06qm2aZ2S4", + "Gbhqm79yC8NereIONtq7W2Mf9MD2y1AU"); + CPPUNIT_ASSERT_EQUAL_MESSAGE("Transaction serialization failed", json, + t.serialize()); +} + +void TransactionTest::deserialize() { + const std::string json = + R"({"id":"2snbSS8flrSH6OrvmSEmqyvaduOi2uc2","title":"Amazon","description":"Books","date":"2021-01-08T13:06:00Z","amount":2108,"expense":true,"budgetId":"Gbhqm79yC8NereIONtq7W2Mf9MD2y1AU","categoryId":"Q4R8QLhSksL2wi7XzmrqhL06qm2aZ2S4","createdBy":"1"})"; + Transaction t = Transaction::deserialize(json); + CPPUNIT_ASSERT_EQUAL_MESSAGE("Transaction id parsing failed", + std::string("2snbSS8flrSH6OrvmSEmqyvaduOi2uc2"), + t.getId()); + CPPUNIT_ASSERT_EQUAL_MESSAGE("Transaction name parsing failed", + std::string("Amazon"), t.getName()); + CPPUNIT_ASSERT_EQUAL_MESSAGE("Transaction description parsing failed", + std::string("Books"), t.getDescription()); + tm date = t.getDate(); + CPPUNIT_ASSERT_EQUAL_MESSAGE("Transaction date year parsing failed", 2021, + date.tm_year); + CPPUNIT_ASSERT_EQUAL_MESSAGE("Transaction date month parsing failed", 0, + date.tm_mon); + CPPUNIT_ASSERT_EQUAL_MESSAGE("Transaction date day parsing failed", 8, + date.tm_mday); + CPPUNIT_ASSERT_EQUAL_MESSAGE("Transaction date hour parsing failed", 13, + date.tm_hour); + CPPUNIT_ASSERT_EQUAL_MESSAGE("Transaction date min parsing failed", 6, + date.tm_min); + CPPUNIT_ASSERT_EQUAL_MESSAGE("Transaction date sec parsing failed", 0, + date.tm_sec); + CPPUNIT_ASSERT_EQUAL_MESSAGE("Transaction amount parsing failed", 2108U, + t.getAmount()); + CPPUNIT_ASSERT_MESSAGE("Transaction expense parsing failed", t.isExpense()); + CPPUNIT_ASSERT_EQUAL_MESSAGE("Transaction budgetId parsing failed", + std::string("Gbhqm79yC8NereIONtq7W2Mf9MD2y1AU"), + t.getBudgetId()); + CPPUNIT_ASSERT_EQUAL_MESSAGE("Transaction category parsing failed", + std::string("Q4R8QLhSksL2wi7XzmrqhL06qm2aZ2S4"), + t.getCategoryId()); + CPPUNIT_ASSERT_EQUAL_MESSAGE("Transaction createdBy parsing failed", + std::string("1"), t.getCreatedBy()); +} + +void TransactionTest::defaultIdsDifferent() { + Transaction t1; + Transaction t2; + CPPUNIT_ASSERT(t1.getId() != t2.getId()); +} + +void TransactionTest::hashTransaction() { + tm date; + date.tm_year = 2021; + date.tm_mon = 0; + date.tm_mday = 8; + date.tm_hour = 13; + date.tm_min = 6; + date.tm_sec = 0; + Transaction t = + Transaction("2snbSS8flrSH6OrvmSEmqyvaduOi2uc2", "Amazon", date, "1", 2108, + "Books", true, "Q4R8QLhSksL2wi7XzmrqhL06qm2aZ2S4", + "Gbhqm79yC8NereIONtq7W2Mf9MD2y1AU"); + CPPUNIT_ASSERT_EQUAL_MESSAGE("Transaction hash failed", + std::string("94140eb2923e905360cef1e7aca3d321da39f0608ea17eb944475d81b89a64cb"), t.hash()); } diff --git a/src/test/transaction_test.h b/src/test/transaction_test.h index b23f4d3..5c6e35b 100644 --- a/src/test/transaction_test.h +++ b/src/test/transaction_test.h @@ -7,10 +7,16 @@ class TransactionTest: public CppUnit::TestCase { public: - void parseDate(); + void serialize(); + void deserialize(); + void defaultIdsDifferent(); + void hashTransaction(); CPPUNIT_TEST_SUITE( TransactionTest ); - CPPUNIT_TEST( parseDate ); + CPPUNIT_TEST( serialize ); + CPPUNIT_TEST( deserialize ); + CPPUNIT_TEST( defaultIdsDifferent ); + CPPUNIT_TEST( hashTransaction ); CPPUNIT_TEST_SUITE_END(); }; diff --git a/src/test/utils_test.cpp b/src/test/utils_test.cpp new file mode 100644 index 0000000..0deab0b --- /dev/null +++ b/src/test/utils_test.cpp @@ -0,0 +1,24 @@ +#include "utils_test.h" +#include +#include + +void UtilsTest::parseDateTest() {} + +void UtilsTest::printDateTest() { + tm date; + date.tm_year = 2020; + date.tm_mon = 0; + date.tm_mday = 31; + date.tm_hour = 23; + date.tm_min = 59; + date.tm_sec = 59; + CPPUNIT_ASSERT_EQUAL_MESSAGE("Date printing failed", + std::string("2020-01-31T23:59:59Z"), + util::printDate(date)); +} + +void UtilsTest::hashTest() { + CPPUNIT_ASSERT_EQUAL_MESSAGE("Hashing failed", + std::string("9f86d081884c7d659a2feaa0c55ad015a3bf4f1b2b0b822cd15d6c15b0f00a08"), + util::hash("test")); +} diff --git a/src/test/utils_test.h b/src/test/utils_test.h new file mode 100644 index 0000000..1962048 --- /dev/null +++ b/src/test/utils_test.h @@ -0,0 +1,21 @@ +#ifndef UTILS_TEST_H_INCLUDED +#define UTILS_TEST_H_INCLUDED + +#include +#include +#include + +class UtilsTest: public CppUnit::TestCase { +public: + void parseDateTest(); + void printDateTest(); + void hashTest(); + + CPPUNIT_TEST_SUITE( UtilsTest ); + CPPUNIT_TEST( parseDateTest ); + CPPUNIT_TEST( printDateTest ); + CPPUNIT_TEST( hashTest ); + CPPUNIT_TEST_SUITE_END(); +}; + +#endif // UTILS_TEST_H_INCLUDED diff --git a/transaction.json b/transaction.json new file mode 100644 index 0000000..8eb9b8c --- /dev/null +++ b/transaction.json @@ -0,0 +1 @@ +{"amount":2108,"budgetId":"Gbhqm79yC8NereIONtq7W2Mf9MD2y1AU","categoryId":"Q4R8QLhSksL2wi7XzmrqhL06qm2aZ2S4","createdBy":"1","date":"2021-01-08T13:06:00Z","description":"Books","expense":true,"id":"2snbSS8flrSH6OrvmSEmqyvaduOi2uc2","title":"Amazon"}