Add budget & transaction (de)serialization and some cmake improvements

Signed-off-by: William Brawner <me@wbrawner.com>
This commit is contained in:
William Brawner 2021-01-10 14:09:23 -07:00
parent dc7ba2719f
commit 774b90127a
30 changed files with 890 additions and 271 deletions

2
.gitignore vendored
View file

@ -1,3 +1,5 @@
build/ build/
.kdev4/ .kdev4/
*.kdev4 *.kdev4
deps/
tags

10
.vimrc Normal file
View file

@ -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 <F4> :!mkdir -p build <bar> cmake -B build <bar> make -C build<cr>
nnoremap <F4> :!build/PiHelper/pihelper localhost<cr>
nnoremap <F9> :!rm -rf build<cr>
let &path.="/Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX.sdk/usr/include,"

1
budget.json Normal file
View file

@ -0,0 +1 @@
{"id":"Gbhqm79yC8NereIONtq7W2Mf9MD2y1AU","name":"Monthly Budget","description":"Monthly expenses"}

View file

@ -0,0 +1,3 @@
#ifndef API_SERVICE_H
#define API_SERVICE_H
#endif

View file

@ -1,33 +1,28 @@
#ifndef BUDGET_H #ifndef BUDGET_H
#define BUDGET_H #define BUDGET_H
#include <string>
#include "hashable.h" #include "hashable.h"
#include "identifiable.h" #include "identifiable.h"
#include "serializable.h"
#include <string>
using namespace std; using namespace std;
class Budget: public Identifiable, public Hashable { class Budget : public Identifiable, public Hashable, public Serializable {
private: private:
string name; string id;
string description; string name;
string description;
public: public:
string getName() { Budget();
return this->name; Budget(std::string id, std::string name, std::string description);
} string getId() override;
string getName();
void setName(string name) { void setName(string name);
this->name = name; string getDescription();
} void setDescription(string description);
string hash() override;
string getDescription() { string serialize() override;
return this->description; static Budget deserialize(std::string data);
}
void setDescription(string description) {
this->description = description;
}
string hash() override;
}; };
#endif #endif

View file

@ -1,64 +1,38 @@
#ifndef CATEGORY_H
#define CATEGORY_H #define CATEGORY_H
#include <string> #ifndef CATEGORY_H
#include "hashable.h" #include "hashable.h"
#include "identifiable.h" #include "identifiable.h"
#include "serializable.h"
#include <string>
class Category: public Identifiable, public Hashable { class Category : public Identifiable, public Hashable, public Serializable {
private: private:
std::string id; std::string id;
std::string name; std::string name;
std::string description; std::string description;
long amount; unsigned int amount;
bool expense; bool expense;
bool archived; bool archived;
std::string budgetId; std::string budgetId;
public: public:
std::string getId() { Category();
return this->id; 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 getName() { std::string getId() override;
return this->name; std::string getName();
} void setName(std::string name);
std::string getDescription();
void setName(std::string name) { void setDescription(std::string description);
this->name = name; unsigned int getAmount();
} void setAmount(unsigned int amount);
bool isExpense();
std::string getDescription() { void setExpense(bool expense);
return this->description; bool isArchived();
} void setArchived(bool archived);
std::string hash() override;
void setDescription(std::string description) { string serialize() override;
this->description = description; static Categpry deserialize(std::string data);
}
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;
}; };
#endif #endif

View file

@ -0,0 +1,4 @@
#ifndef DB_SERVICE_H
#define DB_SERVICE_H
#endif

View file

@ -3,15 +3,7 @@
#include <string> #include <string>
class Identifiable { class Identifiable {
private:
std::string id;
public: public:
std::string getId() { virtual std::string getId() = 0;
return this->id;
}
void setId(std::string id) {
this->id = id;
}
}; };
#endif #endif

View file

@ -13,6 +13,7 @@ enum Permission {
class UserPermission: public Identifiable { class UserPermission: public Identifiable {
private: private:
std::string id;
std::string userId; std::string userId;
std::string budgetId; std::string budgetId;
Permission permission; Permission permission;
@ -23,6 +24,8 @@ class UserPermission: public Identifiable {
this->budgetId = budgetId; this->budgetId = budgetId;
} }
std::string getId() override;
std::string getUserId() { std::string getUserId() {
return this->userId; return this->userId;
} }

View file

@ -0,0 +1,9 @@
#ifndef SERIALIZABLE_H
#define SERIALIZABLE_H
#include <string>
class Serializable {
public:
virtual std::string serialize() = 0;
};
#endif

View file

@ -1,113 +1,51 @@
#ifndef TRANSACTION_H #ifndef TRANSACTION_H
#define TRANSACTION_H #define TRANSACTION_H
#include <string>
#include <stdio.h>
#include <time.h>
#include "hashable.h" #include "hashable.h"
#include "identifiable.h" #include "identifiable.h"
#include "serializable.h"
#include <stdio.h>
#include <string>
#include <time.h>
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 { public:
private: Transaction();
string name; Transaction(std::string id, std::string name, tm date, std::string createdBy,
string description; unsigned int amount = 0, std::string description = "",
tm date; bool expense = true, std::string categoryId = "",
long amount; std::string budgetId = "");
bool expense; std::string getId() override;
bool archived; std::string getName();
string categoryId; void setName(std::string name);
string budgetId; std::string getDescription();
void setDescription(std::string description);
public: tm getDate();
Transaction() { void setDate(tm date);
this->name = ""; unsigned int getAmount();
this->description = ""; void setAmount(unsigned int amount);
time_t now = time(0); bool isExpense();
this->date = *gmtime(&now); void setExpense(bool expense);
this->amount = 0; bool isArchived();
this->expense = true; void setArchived(bool archived);
this->archived = false; std::string getCategoryId();
} void setCategoryId(std::string categoryId);
std::string getBudgetId();
string getName() { void setBudgetId(std::string budgetId);
return this->name; std::string getCreatedBy();
} void setCreatedBy(std::string createdBy);
std::string hash() override;
void setName(string name) { std::string serialize() override;
this->name = name; static Transaction deserialize(std::string data);
}
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;
}; };
#endif #endif

View file

@ -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

View file

@ -5,11 +5,13 @@
class User: public Identifiable { class User: public Identifiable {
private: private:
std::string id;
std::string name; std::string name;
std::string email; std::string email;
std::string avatar; std::string avatar;
public: public:
std::string getId() override;
std::string getName(); std::string getName();
void setName(std::string name); void setName(std::string name);
std::string getEmail(); std::string getEmail();

12
include/twigs/utils.h Normal file
View file

@ -0,0 +1,12 @@
#ifndef UTILS_H
#define UTILS_H
#include <string>
namespace util {
std::string randomId();
std::string hash(std::string data);
tm parseDate(std::string str);
std::string printDate(tm date);
} // namespace util
#endif

198
scripts/build-android Executable file
View file

@ -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
)

View file

@ -1,7 +1,7 @@
add_subdirectory(libtwigs) 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) add_subdirectory(test)
endif() endif()

View file

@ -25,28 +25,41 @@ set(PUBLIC_HEADERS
${TWIGS_INCLUDE_DIR}/twigs/hashable.h ${TWIGS_INCLUDE_DIR}/twigs/hashable.h
${TWIGS_INCLUDE_DIR}/twigs/identifiable.h ${TWIGS_INCLUDE_DIR}/twigs/identifiable.h
${TWIGS_INCLUDE_DIR}/twigs/permission.h ${TWIGS_INCLUDE_DIR}/twigs/permission.h
${TWIGS_INCLUDE_DIR}/twigs/serializable.h
${TWIGS_INCLUDE_DIR}/twigs/transaction.h ${TWIGS_INCLUDE_DIR}/twigs/transaction.h
${TWIGS_INCLUDE_DIR}/twigs/user.h ${TWIGS_INCLUDE_DIR}/twigs/user.h
${TWIGS_INCLUDE_DIR}/twigs/utils.h
) )
set(TWIGS_SOURCES set(TWIGS_SOURCES
budget.cpp budget.cpp
#category.cpp #category.cpp
transaction.cpp transaction.cpp
utils.cpp
#twigs.cpp #twigs.cpp
#user.cpp #user.cpp
) )
#if (NOT TARGET CURL) if (NOT TARGET CURL)
#find_library( find_library(
#CURL CURL
#NAMES curl libcurl NAMES curl libcurl
#PATHS /usr/local/lib /usr/local/lib64 /usr/lib /usr/lib64 /lib PATHS /usr/local/lib /usr/local/lib64 /usr/lib /usr/lib64 /lib
#) )
#if (NOT CURL) if (NOT CURL)
#message(SEND_ERROR "Did not find curl") message(SEND_ERROR "Did not find curl")
#endif() endif()
#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) #if (NOT TARGET JSONC)
#find_library( #find_library(
#JSONC #JSONC
@ -57,31 +70,28 @@ set(TWIGS_SOURCES
#message(SEND_ERROR "Did not find json-c") #message(SEND_ERROR "Did not find json-c")
#endif() #endif()
#endif() #endif()
#if (NOT TARGET CRYPTO) if (NOT TARGET CRYPTO)
#find_library( find_library(
#CRYPTO CRYPTO
#NAMES crypto libcrypto NAMES crypto libcrypto
#PATHS /usr/local/lib /usr/local/lib64 /usr/lib /usr/lib64 /lib PATHS /usr/local/lib /usr/local/lib64 /usr/lib /usr/lib64 /lib
#) )
#if (NOT CRYPTO) if (NOT CRYPTO)
#message(SEND_ERROR "Did not find OpenSSL") message(SEND_ERROR "Did not find OpenSSL")
#endif() endif()
#endif() endif()
#if (NOT TARGET OPENSSL) if (NOT TARGET OPENSSL)
#find_library( find_library(
#OPENSSL OPENSSL
#NAMES ssl libssl NAMES ssl libssl
#PATHS /usr/local/lib /usr/local/lib64 /usr/lib /usr/lib64 /lib PATHS /usr/local/lib /usr/local/lib64 /usr/lib /usr/lib64 /lib
#) )
#if (NOT OPENSSL) if (NOT OPENSSL)
#message(SEND_ERROR "Did not find OpenSSL") message(SEND_ERROR "Did not find OpenSSL")
#endif() endif()
#endif() endif()
option(TWIGS_STATIC "Build Twigs as a static library" ON) if (BUILD_SHARED_LIBS)
option(TWIGS_SHARED "Build Twigs as a shared library" OFF)
if (TWIGS_STATIC)
add_library(libtwigs SHARED add_library(libtwigs SHARED
${TWIGS_SOURCES} ${TWIGS_SOURCES}
${PUBLIC_HEADERS} ${PUBLIC_HEADERS}
@ -93,12 +103,20 @@ else()
) )
endif() endif()
target_include_directories(libtwigs PRIVATE target_include_directories(libtwigs PUBLIC
$<INSTALL_INTERFACE:${CMAKE_INSTALL_INCLUDEDIR}> $<INSTALL_INTERFACE:${CMAKE_INSTALL_INCLUDEDIR}>
$<BUILD_INTERFACE:${CMAKE_CURRENT_LIST_DIR}/${TWIGS_INCLUDE_DIR}> $<BUILD_INTERFACE:${CMAKE_CURRENT_LIST_DIR}/${TWIGS_INCLUDE_DIR}>
$<BUILD_INTERFACE:${PROJECT_BINARY_DIR}/include/twigs> $<BUILD_INTERFACE:${PROJECT_BINARY_DIR}/include/twigs>
SYSTEM ${CMAKE_INCLUDE_PATH}
SYSTEM /usr/local/include
) )
target_link_libraries(libtwigs ${JSONCPP}
${CURL}
${CRYPTO}
${OPENSSL}
)
set_target_properties(libtwigs PROPERTIES set_target_properties(libtwigs PROPERTIES
OUTPUT_NAME "twigs" OUTPUT_NAME "twigs"
VERSION ${PROJECT_VERSION} VERSION ${PROJECT_VERSION}
@ -106,12 +124,8 @@ set_target_properties(libtwigs PROPERTIES
) )
install(TARGETS libtwigs install(TARGETS libtwigs
RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR}
LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR}
ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR} ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR}
OBJECTS DESTINATION ${CMAKE_INSTALL_LIBDIR}
) )
#target_link_libraries(twigs ${CURL}
#${JSONC}
#${CRYPTO}
#${OPENSSL}
#)

View file

@ -1,6 +1,51 @@
#include <twigs/budget.h>
#include <string> #include <string>
#include <twigs/budget.h>
#include <twigs/utils.h>
#include <json/json.h>
std::string Budget::hash() { Budget::Budget() : Budget(util::randomId(), "", "") {}
return "";
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()); }

76
src/libtwigs/category.cpp Normal file
View file

@ -0,0 +1,76 @@
#include <twigs/category.h>
#include <twigs/utils.h>
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()); }

View file

@ -1,8 +1,105 @@
#include <json/json.h>
#include <twigs/serializable.h>
#include <twigs/transaction.h> #include <twigs/transaction.h>
#include <twigs/utils.h>
using namespace std; using namespace std;
string Transaction::hash() { tm *now() {
throw 5; time_t now = time(NULL);
// return ""; 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()); }

61
src/libtwigs/utils.cpp Normal file
View file

@ -0,0 +1,61 @@
#include <random>
#include <twigs/utils.h>
#include <openssl/sha.h>
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

View file

@ -6,29 +6,27 @@ set(TEST_SOURCES
budget_test.cpp budget_test.cpp
transaction_test.h transaction_test.h
transaction_test.cpp transaction_test.cpp
utils_test.h
utils_test.cpp
) )
find_library(CPPUNIT cppunit) find_library(CPPUNIT cppunit)
add_executable(twigs_test add_executable(twigs_test ${TEST_SOURCES})
${TEST_SOURCES}
${PUBLIC_HEADERS}
)
target_link_libraries(twigs_test libtwigs ${CPPUNIT}) target_link_libraries(twigs_test libtwigs ${CPPUNIT})
target_include_directories(twigs_test PRIVATE
$<INSTALL_INTERFACE:${CMAKE_INSTALL_INCLUDEDIR}>
$<BUILD_INTERFACE:${CMAKE_CURRENT_LIST_DIR}/${TWIGS_INCLUDE_DIR}>
$<BUILD_INTERFACE:${PROJECT_BINARY_DIR}/include/twigs>
)
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 set_target_properties(twigs_test PROPERTIES
OUTPUT_NAME "twigstest" OUTPUT_NAME "twigstest"
VERSION ${PROJECT_VERSION} VERSION ${PROJECT_VERSION}
SOVERSION ${PROJECT_VERSION_MAJOR} SOVERSION ${PROJECT_VERSION_MAJOR}
) )
add_test(NAME twigs_test
COMMAND ${CMAKE_CROSSCOMPILING_EMULATOR} $<TARGET_FILE:twigs_test>
)
add_custom_command(TARGET twigs_test
POST_BUILD
COMMAND ${CMAKE_CROSSCOMPILING_EMULATOR} $<TARGET_FILE:twigs_test>
)

View file

@ -1,6 +1,41 @@
#include <cppunit/extensions/HelperMacros.h>
#include "budget_test.h" #include "budget_test.h"
#include <cppunit/extensions/HelperMacros.h>
void BudgetTest::parseDate() { void BudgetTest::serialize() {
Budget budget; 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());
} }

View file

@ -7,10 +7,16 @@
class BudgetTest: public CppUnit::TestCase { class BudgetTest: public CppUnit::TestCase {
public: public:
void parseDate(); void serialize();
void deserialize();
void defaultIdsDifferent();
void hashBudget();
CPPUNIT_TEST_SUITE( BudgetTest ); CPPUNIT_TEST_SUITE( BudgetTest );
CPPUNIT_TEST( parseDate ); CPPUNIT_TEST( serialize );
CPPUNIT_TEST( deserialize );
CPPUNIT_TEST( defaultIdsDifferent );
CPPUNIT_TEST( hashBudget );
CPPUNIT_TEST_SUITE_END(); CPPUNIT_TEST_SUITE_END();
}; };

View file

@ -1,12 +1,16 @@
#include <cppunit/TextTestRunner.h> #include <cppunit/TextTestRunner.h>
#include "budget_test.h" #include "budget_test.h"
#include "transaction_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); CPPUNIT_TEST_SUITE_REGISTRATION (TransactionTest);
int main() { int main() {
BudgetTest budgetTest; BudgetTest budgetTest;
TransactionTest transactionTest; TransactionTest transactionTest;
UtilsTest utilsTest;
CppUnit::Test *test = CppUnit::TestFactoryRegistry::getRegistry().makeTest(); CppUnit::Test *test = CppUnit::TestFactoryRegistry::getRegistry().makeTest();
CppUnit::TextTestRunner runner; CppUnit::TextTestRunner runner;

View file

@ -1,9 +1,84 @@
#include "transaction_test.h"
#include <cppunit/TestCase.h> #include <cppunit/TestCase.h>
#include <cppunit/extensions/HelperMacros.h> #include <cppunit/extensions/HelperMacros.h>
#include "transaction_test.h" #include <iostream>
#include <time.h>
#include <twigs/utils.h>
void TransactionTest::parseDate() { void TransactionTest::serialize() {
Transaction transaction; const std::string json =
transaction.setDate("2020-01-31T23:59:59Z"); R"({"amount":2108,"budgetId":"Gbhqm79yC8NereIONtq7W2Mf9MD2y1AU","categoryId":"Q4R8QLhSksL2wi7XzmrqhL06qm2aZ2S4","createdBy":"1","date":"2021-01-08T13:06:00Z","description":"Books","expense":true,"id":"2snbSS8flrSH6OrvmSEmqyvaduOi2uc2","title":"Amazon"})";
CPPUNIT_ASSERT_EQUAL_MESSAGE("Transaction date parsing failed", std::string("2020-01-31T23:59:59Z"), transaction.getDate());
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());
} }

View file

@ -7,10 +7,16 @@
class TransactionTest: public CppUnit::TestCase { class TransactionTest: public CppUnit::TestCase {
public: public:
void parseDate(); void serialize();
void deserialize();
void defaultIdsDifferent();
void hashTransaction();
CPPUNIT_TEST_SUITE( TransactionTest ); CPPUNIT_TEST_SUITE( TransactionTest );
CPPUNIT_TEST( parseDate ); CPPUNIT_TEST( serialize );
CPPUNIT_TEST( deserialize );
CPPUNIT_TEST( defaultIdsDifferent );
CPPUNIT_TEST( hashTransaction );
CPPUNIT_TEST_SUITE_END(); CPPUNIT_TEST_SUITE_END();
}; };

24
src/test/utils_test.cpp Normal file
View file

@ -0,0 +1,24 @@
#include "utils_test.h"
#include <cppunit/TestCase.h>
#include <cppunit/extensions/HelperMacros.h>
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"));
}

21
src/test/utils_test.h Normal file
View file

@ -0,0 +1,21 @@
#ifndef UTILS_TEST_H_INCLUDED
#define UTILS_TEST_H_INCLUDED
#include <cppunit/TestCase.h>
#include <cppunit/extensions/HelperMacros.h>
#include <twigs/utils.h>
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

1
transaction.json Normal file
View file

@ -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"}