diff --git a/CMakeLists.txt b/CMakeLists.txt index 16bfdba..e6f8c10 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -16,6 +16,9 @@ include(CMakeDependentOption) cmake_dependent_option(NEROSHOP_USE_QT "Build neroshop with Qt" ON "NEROSHOP_BUILD_GUI" OFF) option(UUID_SYSTEM_GENERATOR "Enable operating system uuid generator" OFF) option(UUID_TIME_GENERATOR "Enable experimental time-based uuid generator" OFF) +cmake_dependent_option(NEROSHOP_USE_SYSTEM_SOCKETS "Build neroshop with SYSTEM_SOCKETS" ON "NOT NEROSHOP_USE_LIBZMQ;NOT NEROSHOP_USE_LIBUV" OFF) +cmake_dependent_option(NEROSHOP_USE_LIBZMQ "Build neroshop with LibZMQ" ON "NOT NEROSHOP_USE_LIBUV;NOT NEROSHOP_USE_SYSTEM_SOCKETS" OFF) +cmake_dependent_option(NEROSHOP_USE_LIBUV "Build neroshop with LIBUV" ON "NOT NEROSHOP_USE_LIBZMQ;NOT NEROSHOP_USE_SYSTEM_SOCKETS" OFF) list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/cmake" "${CMAKE_CURRENT_SOURCE_DIR}/external/monero-cpp/external/monero-project/cmake") ###################################### @@ -43,7 +46,7 @@ if(NOT WIN32) set(Magenta "${Esc}[35m") set(Cyan "${Esc}[36m") set(White "${Esc}[37m") - set(BoldRed "${Esc}[1;31m") + set(BoldRed "${Esc}[1;91m") set(BoldGreen "${Esc}[1;32m") set(BoldYellow "${Esc}[1;33m") set(BoldBlue "${Esc}[1;34m") @@ -352,19 +355,6 @@ if(NOT CURL_FOUND) set(CMAKE_CXX_LINK_EXECUTABLE "${CMAKE_CXX_LINK_EXECUTABLE} -lldap -llber -lnghttp2 -lpsl -lidn2 -lbrotlidec -lzstd -lrtmp") # for Arch (Manjaro) endif() -###################################### -# libuv -find_package(LibUV) -if(LIBUV_FOUND) - message(STATUS "Using LibUV: ${LibUV_LIBRARIES} (v${LibUV_VERSION})") - include_directories(${LibUV_INCLUDE_DIRS}) - set(libuv_src ${LibUV_LIBRARIES}) -endif() -if(NOT LIBUV_FOUND) - include_directories(${CMAKE_CURRENT_SOURCE_DIR}/external/libuv/include ${CMAKE_CURRENT_SOURCE_DIR}/external/libuv/src) - set(libuv_src ${CMAKE_CURRENT_SOURCE_DIR}/external/libuv/.libs/libuv.a) # In case user chooses to build libuv themselves instead of installing it on the system -endif() - ###################################### # willemt/raft include_directories(${CMAKE_CURRENT_SOURCE_DIR}/external/raft/include) @@ -390,7 +380,7 @@ link_directories(${CMAKE_CURRENT_SOURCE_DIR}/build) # target_link_directories on ###################################### # neroshop core source files -set(neroshop_core_src src/core/buyer.cpp src/core/cart.cpp src/core/catalog.cpp src/core/client.cpp src/core/config.cpp src/core/currency_converter.cpp src/core/database.cpp src/core/encryptor.cpp src/core/item.cpp src/core/order.cpp src/core/process.cpp src/core/script.cpp src/core/seller.cpp src/core/server.cpp src/core/user.cpp src/core/validator.cpp src/core/wallet.cpp) +set(neroshop_core_src src/core/buyer.cpp src/core/cart.cpp src/core/catalog.cpp src/core/client.cpp src/core/config.cpp src/core/currency_converter.cpp src/core/database.cpp src/core/encryptor.cpp src/core/item.cpp src/core/order.cpp src/core/process.cpp src/core/script.cpp src/core/seller.cpp src/core/server.cpp src/core/user.cpp src/core/validator.cpp src/core/wallet.cpp src/core/database/sqlite.cpp) ###################################### # neroshop tests @@ -403,9 +393,9 @@ endif() ###################################### # neroshop-daemon set(daemon_executable "neromon") -add_executable(${daemon_executable} src/daemon/main.cpp src/core/server.cpp)#target_link_libraries(daemon ${curl_src} ${OPENSSL_LIBRARIES}) # curl requires both openssl(used in monero) and zlib(used in dokun-ui) +add_executable(${daemon_executable} src/daemon/main.cpp src/core/server.cpp src/core/database/sqlite.cpp src/core/database.cpp src/core/rpc.cpp)#target_link_libraries(daemon ${curl_src} ${OPENSSL_LIBRARIES}) # curl requires both openssl(used in monero) and zlib(used in dokun-ui) target_include_directories(${daemon_executable} PRIVATE #[[${CMAKE_CURRENT_SOURCE_DIR}/src/]]) -target_link_libraries(${daemon_executable} ${libuv_src} ${raft_src})#set(CMAKE_CXX_LINK_EXECUTABLE "${CMAKE_CXX_LINK_EXECUTABLE} -ldl") # fixes undefined reference to symbol 'dlsym@@GLIBC_2.2.5' error +target_link_libraries(${daemon_executable} ${sqlite_src} ${raft_src})#set(CMAKE_CXX_LINK_EXECUTABLE "${CMAKE_CXX_LINK_EXECUTABLE} -ldl") # fixes undefined reference to symbol 'dlsym@@GLIBC_2.2.5' error ###################################### # neroshop-console @@ -414,7 +404,7 @@ if(NEROSHOP_BUILD_CLI) add_executable(${neroshop_console} src/console/main.cpp ${neroshop_core_src}) target_compile_definitions(${neroshop_console} PRIVATE NEROSHOP_DEBUG) target_include_directories(${neroshop_console} PRIVATE #[[${CMAKE_CURRENT_SOURCE_DIR}/src/]]) - target_link_libraries(${neroshop_console} ${monero_cpp_src} ${sqlite_src} ${qr_code_generator_src} ${raft_src} ${libuv_src} ${curl_src} ${monero_src} ${lua_src} ${linenoise_src}) + target_link_libraries(${neroshop_console} ${monero_cpp_src} ${sqlite_src} ${qr_code_generator_src} ${raft_src} ${curl_src} ${monero_src} ${lua_src} ${linenoise_src}) message(STATUS "${BoldMagenta}NEROSHOP_BUILD_CLI option set to ON${ColourReset}") endif() @@ -427,12 +417,79 @@ if(NEROSHOP_BUILD_GUI) target_sources(${neroshop_executable} PRIVATE ${neroshop_res} src/gui/main.cpp src/gui/script_controller.cpp src/gui/wallet_controller.cpp src/gui/user_controller.cpp src/gui/image_loader.cpp src/gui/image_provider.cpp src/gui/wallet_qr_provider.cpp src/gui/currency_exchange_rates_provider.cpp src/gui/backend.cpp ${neroshop_core_src}) target_compile_definitions(${neroshop_executable} PRIVATE NEROSHOP_DEBUG)#set_target_properties(${neroshop_executable} PROPERTIES COMPILE_DEFINITIONS "NEROSHOP_DEBUG") target_include_directories(${neroshop_executable} PRIVATE #[[${CMAKE_CURRENT_SOURCE_DIR}/src/]]) - target_link_libraries(${neroshop_executable} ${monero_cpp_src} ${sqlite_src} ${qr_code_generator_src} ${raft_src} ${libuv_src} ${monero_src} ${lua_src}) + target_link_libraries(${neroshop_executable} ${monero_cpp_src} ${sqlite_src} ${qr_code_generator_src} ${raft_src} ${monero_src} ${lua_src}) message(STATUS "${BoldMagenta}NEROSHOP_BUILD_GUI option set to ON${ColourReset}") target_compile_definitions(${neroshop_executable} PRIVATE NEROSHOP_BUILD_GUI) endif() ###################################### +if(NOT NEROSHOP_USE_LIBZMQ AND NOT NEROSHOP_USE_LIBUV) + message(STATUS "${BoldWhite}Using SYSTEM SOCKETS${ColourReset}") + if(NEROSHOP_BUILD_GUI) + target_compile_definitions(${neroshop_executable} PRIVATE NEROSHOP_USE_SYSTEM_SOCKETS) + endif() + if(NEROSHOP_BUILD_CLI) + target_compile_definitions(${neroshop_console} PRIVATE NEROSHOP_USE_SYSTEM_SOCKETS) + endif() + target_compile_definitions(${daemon_executable} PRIVATE NEROSHOP_USE_SYSTEM_SOCKETS) +endif() + +###################################### +# libuv +find_package(LibUV) +if(LIBUV_FOUND) + if(NEROSHOP_USE_LIBUV) + message(STATUS "${BoldGreen}Using LibUV: ${LibUV_LIBRARIES} (v${LibUV_VERSION})${ColourReset}") + #include_directories(${LibUV_INCLUDE_DIRS}) + if(NEROSHOP_BUILD_GUI) + target_compile_definitions(${neroshop_executable} PRIVATE NEROSHOP_USE_LIBUV) + target_link_libraries(${neroshop_executable} ${LibUV_LIBRARIES}) + endif() + if(NEROSHOP_BUILD_CLI) + target_compile_definitions(${neroshop_console} PRIVATE NEROSHOP_USE_LIBUV) + target_link_libraries(${neroshop_console} ${LibUV_LIBRARIES}) + endif() + target_compile_definitions(${daemon_executable} PRIVATE NEROSHOP_USE_LIBUV) + target_link_libraries(${daemon_executable} ${LibUV_LIBRARIES}) + endif() +endif() +if(NOT LIBUV_FOUND) + if(NEROSHOP_USE_LIBUV) + include_directories(${CMAKE_CURRENT_SOURCE_DIR}/external/libuv/include ${CMAKE_CURRENT_SOURCE_DIR}/external/libuv/src) + if(NEROSHOP_BUILD_GUI) + target_compile_definitions(${neroshop_executable} PRIVATE NEROSHOP_USE_LIBUV) + target_link_libraries(${neroshop_executable} ${CMAKE_CURRENT_SOURCE_DIR}/external/libuv/.libs/libuv.a) + endif() + if(NEROSHOP_BUILD_CLI) + target_compile_definitions(${neroshop_console} PRIVATE NEROSHOP_USE_LIBUV) + target_link_libraries(${neroshop_console} ${CMAKE_CURRENT_SOURCE_DIR}/external/libuv/.libs/libuv.a) + endif() + target_compile_definitions(${daemon_executable} PRIVATE NEROSHOP_USE_LIBUV) + target_link_libraries(${daemon_executable} ${CMAKE_CURRENT_SOURCE_DIR}/external/libuv/.libs/libuv.a) # In case user chooses to build libuv themselves instead of installing it on the system + endif() +endif() + +###################################### +# libzmq +find_package(ZeroMQ) +if(ZeroMQ_FOUND) + if(NEROSHOP_USE_LIBZMQ) + message(STATUS "${BoldRed}Using LIBZMQ: ${ZeroMQ_LIBRARY} (v${ZeroMQ_VERSION})${ColourReset}") + if(NEROSHOP_BUILD_GUI) + target_compile_definitions(${neroshop_executable} PRIVATE NEROSHOP_USE_LIBZMQ) + target_link_libraries(${neroshop_executable} ${ZeroMQ_LIBRARIES}) + endif() + if(NEROSHOP_BUILD_CLI) + target_compile_definitions(${neroshop_console} PRIVATE NEROSHOP_USE_LIBZMQ) + target_link_libraries(${neroshop_console} ${ZeroMQ_LIBRARIES}) + endif() + target_compile_definitions(${daemon_executable} PRIVATE NEROSHOP_USE_LIBZMQ) + target_link_libraries(${daemon_executable} ${ZeroMQ_LIBRARIES}) + endif() +endif() + +###################################### +# libbcrypt if(DEFINED NEROSHOP_USE_LIBBCRYPT) # -DNEROSHOP_USE_LIBBCRYPT=1 message(STATUS "Using libbcrypt: ${bcrypt_src}") if(NEROSHOP_BUILD_GUI) @@ -445,6 +502,7 @@ if(DEFINED NEROSHOP_USE_LIBBCRYPT) # -DNEROSHOP_USE_LIBBCRYPT=1 endif() endif() +###################################### if (UUID_TIME_GENERATOR) # -DUUID_TIME_GENERATOR=1 if(NEROSHOP_BUILD_GUI) target_compile_definitions(${neroshop_executable} PRIVATE UUID_TIME_GENERATOR) @@ -454,6 +512,7 @@ if (UUID_TIME_GENERATOR) # -DUUID_TIME_GENERATOR=1 endif() endif() +###################################### if(WIN32) set(winsock2_src ws2_32.lib) # -lglfw3 -lgdi32 -lopengl32 -limm32 if(NEROSHOP_BUILD_GUI) @@ -465,6 +524,7 @@ if(WIN32) target_link_libraries(${daemon_executable} ${winsock2_src}) endif() +###################################### if(${CMAKE_SYSTEM_NAME} MATCHES "Linux") find_package(LibUUID) # optional if(UUID_SYSTEM_GENERATOR) # -DUUID_SYSTEM_GENERATOR=1 @@ -479,6 +539,8 @@ if(${CMAKE_SYSTEM_NAME} MATCHES "Linux") target_compile_definitions(${neroshop_console} PRIVATE UUID_SYSTEM_GENERATOR) # uuids::uuid_system_generator target_link_libraries(${neroshop_console} ${LibUUID_LIBRARY}) endif() + target_compile_definitions(${daemon_executable} PRIVATE UUID_SYSTEM_GENERATOR) + target_link_libraries(${daemon_executable} ${LibUUID_LIBRARY}) endif() endif() @@ -492,6 +554,7 @@ if(${CMAKE_SYSTEM_NAME} MATCHES "Linux") target_link_libraries(${daemon_executable} ${posix_src}) endif() +###################################### if(${CMAKE_SYSTEM_NAME} MATCHES "Darwin") find_library(CFLIB CoreFoundation) # optional if(UUID_SYSTEM_GENERATOR) # -DUUID_SYSTEM_GENERATOR=1 @@ -508,6 +571,8 @@ if(${CMAKE_SYSTEM_NAME} MATCHES "Darwin") endif() endif() +###################################### +# qt5 if(Qt5_FOUND) if(NEROSHOP_BUILD_GUI AND NEROSHOP_USE_QT) # Some Linux distros require Qt::Widgets due to the lack of a native platform file dialog implementation diff --git a/README.md b/README.md index 8277eb7..df8786d 100644 --- a/README.md +++ b/README.md @@ -84,7 +84,7 @@ The name _neroshop_ is a combination of the words _nero_, which is Italian for _ | [curl](https://github.com/curl/curl) | ? | currency conversion | :o: | | [openssl](https://github.com/openssl/openssl) | 1.1.1 | for curl, sha256 sum and message encryption | :heavy_check_mark: | | [Qt](https://www.qt.io/) | 5.12.8 | graphical user interface | :heavy_check_mark: | -| [libuv](https://github.com/libuv/libuv) | ? | networking and child process | :heavy_check_mark: | +| [libuv](https://github.com/libuv/libuv) | ? | networking and child process | :grey_question: | | [raft](https://github.com/willemt/raft) | ? | consensus mechanism | :heavy_check_mark: | | [stduuid](https://github.com/mariusbancila/stduuid) | ? | unique id generation | :o: | | [linenoise](https://github.com/antirez/linenoise) | ? | command line interface | :heavy_check_mark: :white_square_button: | @@ -216,7 +216,7 @@ See [CONTRIBUTING.md](CONTRIBUTING.md) ## License -This project is licensed under the [GNU General Public License (GPLv3)](LICENSE) +This project is licensed under the [GNU General Public License v3.0 (GPLv3)](LICENSE) ## Donations diff --git a/cmake/FindZeroMQ.cmake b/cmake/FindZeroMQ.cmake new file mode 100644 index 0000000..fb46301 --- /dev/null +++ b/cmake/FindZeroMQ.cmake @@ -0,0 +1,139 @@ +##============================================================================= +## +## Copyright (c) Kitware, Inc. +## All rights reserved. +## See LICENSE.txt for details. +## +## This software is distributed WITHOUT ANY WARRANTY; without even +## the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR +## PURPOSE. See the above copyright notice for more information. +## +##============================================================================= +# - Try to find ZeroMQ headers and libraries +# +# Usage of this module as follows: +# +# find_package(ZeroMQ) +# +# Variables used by this module, they can change the default behaviour and need +# to be set before calling find_package: +# +# ZeroMQ_ROOT_DIR Set this variable to the root installation of +# ZeroMQ if the module has problems finding +# the proper installation path. +# +# Variables defined by this module: +# +# ZeroMQ_FOUND System has ZeroMQ libs/headers +# ZeroMQ_LIBRARIES The ZeroMQ libraries +# ZeroMQ_INCLUDE_DIR The location of ZeroMQ headers +# ZeroMQ_VERSION The version of ZeroMQ + +find_path(ZeroMQ_ROOT_DIR + NAMES include/zmq.h + ) + +if(MSVC) + #add in all the names it can have on windows + if(CMAKE_GENERATOR_TOOLSET MATCHES "v140" OR MSVC14) + set(_zmq_TOOLSET "-v140") + elseif(CMAKE_GENERATOR_TOOLSET MATCHES "v120" OR MSVC12) + set(_zmq_TOOLSET "-v120") + elseif(CMAKE_GENERATOR_TOOLSET MATCHES "v110_xp") + set(_zmq_TOOLSET "-v110_xp") + elseif(CMAKE_GENERATOR_TOOLSET MATCHES "v110" OR MSVC11) + set(_zmq_TOOLSET "-v110") + elseif(CMAKE_GENERATOR_TOOLSET MATCHES "v100" OR MSVC10) + set(_zmq_TOOLSET "-v100") + elseif(CMAKE_GENERATOR_TOOLSET MATCHES "v90" OR MSVC90) + set(_zmq_TOOLSET "-v90") + endif() + + set(_zmq_versions + "4_1_5" "4_1_4" "4_1_3" "4_1_2" "4_1_1" "4_1_0" + "4_0_8" "4_0_7" "4_0_6" "4_0_5" "4_0_4" "4_0_3" "4_0_2" "4_0_1" "4_0_0" + "3_2_5" "3_2_4" "3_2_3" "3_2_2" "3_2_1" "3_2_0" "3_1_0") + + set(_zmq_release_names) + set(_zmq_debug_names) + foreach( ver ${_zmq_versions}) + list(APPEND _zmq_release_names "libzmq${_zmq_TOOLSET}-mt-${ver}") + endforeach() + foreach( ver ${_zmq_versions}) + list(APPEND _zmq_debug_names "libzmq${_zmq_TOOLSET}-mt-gd-${ver}") + endforeach() + + #now try to find the release and debug version + find_library(ZeroMQ_LIBRARY_RELEASE + NAMES ${_zmq_release_names} zmq libzmq + HINTS ${ZeroMQ_ROOT_DIR}/bin + ${ZeroMQ_ROOT_DIR}/lib + ) + + find_library(ZeroMQ_LIBRARY_DEBUG + NAMES ${_zmq_debug_names} zmq libzmq + HINTS ${ZeroMQ_ROOT_DIR}/bin + ${ZeroMQ_ROOT_DIR}/lib + ) + + if(ZeroMQ_LIBRARY_RELEASE AND ZeroMQ_LIBRARY_DEBUG) + set(ZeroMQ_LIBRARY + debug ${ZeroMQ_LIBRARY_DEBUG} + optimized ${ZeroMQ_LIBRARY_RELEASE} + ) + elseif(ZeroMQ_LIBRARY_RELEASE) + set(ZeroMQ_LIBRARY ${ZeroMQ_LIBRARY_RELEASE}) + elseif(ZeroMQ_LIBRARY_DEBUG) + set(ZeroMQ_LIBRARY ${ZeroMQ_LIBRARY_DEBUG}) + endif() + +else() + find_library(ZeroMQ_LIBRARY + NAMES zmq libzmq + HINTS ${ZeroMQ_ROOT_DIR}/lib + ) +endif() + +find_path(ZeroMQ_INCLUDE_DIR + NAMES zmq.h + HINTS ${ZeroMQ_ROOT_DIR}/include + ) + +function(extract_version_value value_name file_name value) + file(STRINGS ${file_name} val REGEX "${value_name} .") + string(FIND ${val} " " last REVERSE) + string(SUBSTRING ${val} ${last} -1 val) + string(STRIP ${val} val) + set(${value} ${val} PARENT_SCOPE) +endfunction(extract_version_value) + +extract_version_value("ZMQ_VERSION_MAJOR" ${ZeroMQ_INCLUDE_DIR}/zmq.h MAJOR) +extract_version_value("ZMQ_VERSION_MINOR" ${ZeroMQ_INCLUDE_DIR}/zmq.h MINOR) +extract_version_value("ZMQ_VERSION_PATCH" ${ZeroMQ_INCLUDE_DIR}/zmq.h PATCH) + +set(ZeroMQ_VER "${MAJOR}.${MINOR}.${PATCH}") + +#We are using the 2.8.10 signature of find_package_handle_standard_args, +#as that is the version that ParaView 5.1 && VTK 6/7 ship, and inject +#into the CMake module path. This allows our FindModule to work with +#projects that include VTK/ParaView before searching for Remus +include(FindPackageHandleStandardArgs) +find_package_handle_standard_args( + ZeroMQ + REQUIRED_VARS ZeroMQ_LIBRARY ZeroMQ_INCLUDE_DIR + VERSION_VAR ZeroMQ_VER + ) + +set(ZeroMQ_FOUND ${ZEROMQ_FOUND}) +set(ZeroMQ_INCLUDE_DIRS ${ZeroMQ_INCLUDE_DIR}) +set(ZeroMQ_LIBRARIES ${ZeroMQ_LIBRARY}) +set(ZeroMQ_VERSION ${ZeroMQ_VER}) + +mark_as_advanced( + ZeroMQ_ROOT_DIR + ZeroMQ_LIBRARY + ZeroMQ_LIBRARY_DEBUG + ZeroMQ_LIBRARY_RELEASE + ZeroMQ_INCLUDE_DIR + ZeroMQ_VERSION + ) diff --git a/fonts/FontAwesome/FontAwesome.qml b/fonts/FontAwesome/FontAwesome.qml index df65638..6ddd85d 100644 --- a/fonts/FontAwesome/FontAwesome.qml +++ b/fonts/FontAwesome/FontAwesome.qml @@ -36,6 +36,7 @@ Object { property string angleLeft: "\uf104" property string angleRight: "\uf105" property string cog: "\uf013" + property string coin: "\uf85c" property string coins: "\uf51e" property string lock: "\uf023" property string lockOpen: "\uf3c1" @@ -55,6 +56,38 @@ Object { property string cloud: "\uf0c2" property string house: "\uf015" property string ellipsis: "\uf141" + property string cartShopping: "\uf07a" + property string shop: "\uf54f" + property string store: "\uf54e" + property string tag: "\uf02b" + property string tags: "\uf02c" + property string truck: "\uf0d1" + property string dolly: "\uf472" + property string personDolly: "\uf4d0" // use this icon for item pickup option + property string paperPlane: "\uf1d8" + property string ship: "\uf21a" + property string shield: "\uf132" + property string shieldHalf: "\uf3ed" + property string bolt: "\uf0e7" + property string trash: "\uf1f8" + property string inbox: "\uf01c" + property string volume: "\uf6a8" + property string barcode: "\uf02a" + property string barcodeRead: "\uf464" + //property string plus: "\u2b" + property string pen: "\uf304" + property string penToSquare: "\uf044" + property string droplet: "\uf043" + property string fire: "\uf06d" + property string globe: "\uf0ac" + property string bug: "\uf188" + property string database: "\uf1c0" + property string doorOpen: "\uf52b" + property string mailbox: "\uf813" + property string wallet: "\uf555" + property string desktop: "\uf390" + property string robot: "\uf544" + property string handshake: "\uf2b5" ////property string terminal: "" ////property string upload: "\u" ////property string ?: "\u" diff --git a/images/cart.png b/images/cart.png index 0a17e27..46c2a46 100644 Binary files a/images/cart.png and b/images/cart.png differ diff --git a/qml/components/CatalogGrid.qml b/qml/components/CatalogGrid.qml index 5edab21..28702df 100644 --- a/qml/components/CatalogGrid.qml +++ b/qml/components/CatalogGrid.qml @@ -39,7 +39,7 @@ GridView { visible: true width: catalogGrid.cellWidth-catalogGrid.spacing height: catalogGrid.cellHeight-catalogGrid.spacing - color: (NeroshopComponents.Style.darkTheme) ? (NeroshopComponents.Style.themeName == "PurpleDust" ? "#17171c" : "#1d1d1d") : "#c9c9cd"//"#e6e6e6"//"#f0f0f0" + color: (NeroshopComponents.Style.darkTheme) ? (NeroshopComponents.Style.themeName == "PurpleDust" ? "#17171c" : "#181a1b") : "#c9c9cd"//"#e6e6e6"//"#f0f0f0" border.color: (NeroshopComponents.Style.darkTheme) ? "#404040" : "#4d4d4d"////(NeroshopComponents.Style.darkTheme) ? "#ffffff" : "#000000" border.width: 0 radius: 5 diff --git a/qml/components/CatalogList.qml b/qml/components/CatalogList.qml index 66bc379..d447da5 100644 --- a/qml/components/CatalogList.qml +++ b/qml/components/CatalogList.qml @@ -23,7 +23,7 @@ ListView { delegate: Rectangle { id: productBox width: catalogList.boxWidth; height: catalogList.boxHeight // The height of each individual model item/ list element - color: (NeroshopComponents.Style.darkTheme) ? (NeroshopComponents.Style.themeName == "PurpleDust" ? "#17171c" : "#1d1d1d") : "#c9c9cd"//"#f0f0f0" + color: (NeroshopComponents.Style.darkTheme) ? (NeroshopComponents.Style.themeName == "PurpleDust" ? "#17171c" : "#181a1b") : "#c9c9cd"//"#f0f0f0" border.color: (NeroshopComponents.Style.darkTheme) ? "#404040" : "#4d4d4d"////(NeroshopComponents.Style.darkTheme) ? "#ffffff" : "#000000" border.width: 0 radius: 5 diff --git a/qml/components/NodeList.qml b/qml/components/NodeList.qml index bff2e08..bab8344 100644 --- a/qml/components/NodeList.qml +++ b/qml/components/NodeList.qml @@ -2,7 +2,8 @@ import QtQuick 2.12 import QtQuick.Controls 2.12 import QtQuick.Layouts 1.12 -//import FontAwesome 1.0 +import FontAwesome 1.0 + import "." as NeroshopComponents Item { @@ -60,20 +61,9 @@ Item { Layout.fillHeight: true ScrollBar.vertical: ScrollBar { } model: { - // Get monero nodes from settings.lua - /*let network_type = Wallet.getNetworkTypeString() - return Script.getTableStrings("neroshop.monero.nodes." + network_type)*/ - // Get monero nodes from https://monero.fail/health.json - ////return Backend.getMoneroNodeList() - // Get only stagenet nodes (for now) - let stagenet_nodes = [] - const monero_node_list = Backend.getMoneroNodeList() - for(let i = 0; i < monero_node_list.length; i++) { - if(monero_node_list[i].address.includes("38081") || monero_node_list[i].address.includes("38089")) { - stagenet_nodes.push(monero_node_list[i]) - } - } - return stagenet_nodes; + // Get all monero nodes from https://monero.fail/health.json + const monero_node_list = Backend.getNodeList("monero") + return monero_node_list } delegate: Item { width: listView.width @@ -100,16 +90,18 @@ Item { Label { id: nodeStatusLabel - text: status ? "✅" : "❌" - color: status ? "#698b22" : "#dd4b4b" + text: (typeof modelData === "string") ? qsTr("\uf1ce") : (status ? qsTr("\uf14a") : qsTr("\uf00d"))//"✅" : "❌" + color: (typeof modelData === "string") ? "royalblue" : (status ? "#698b22" : "#dd4b4b") + font.bold: true + font.family: FontAwesome.fontFamily Layout.maximumWidth: 25 - property bool status: modelData.available + property bool status: (typeof modelData === "string") ? false : modelData.available } Label { id: nodeAddressLabel Layout.fillWidth: true - text: modelData.address//"node.neroshop.org:38081"//:18081" + text: (typeof modelData === "string") ? modelData : modelData.address//"node.neroshop.org:38081"//:18081" color: delegateRow.parent.ListView.isCurrentItem ? "#471d00" : ((NeroshopComponents.Style.darkTheme) ? "#ffffff" : "#000000") font.bold: delegateRow.parent.ListView.isCurrentItem ? true : false elide: Label.ElideRight @@ -119,7 +111,7 @@ Item { id: nodeHeightLabel Layout.minimumWidth: heightTitle.width//50 Layout.maximumWidth: heightTitle.width//50 - text: modelData.last_height + text: (typeof modelData === "string") ? "- -" : modelData.last_height color: nodeAddressLabel.color font.bold: nodeAddressLabel.font.bold elide: Label.ElideRight diff --git a/qml/components/PaginationBar.qml b/qml/components/PaginationBar.qml index f278f6b..6903d50 100644 --- a/qml/components/PaginationBar.qml +++ b/qml/components/PaginationBar.qml @@ -18,6 +18,7 @@ Row {//RowLayout { property real buttonRadius: 5 property real radius: buttonRadius property bool showDirectionalIcons: false + width: childrenRect.width; height: childrenRect.height Button { id: backButton @@ -35,6 +36,11 @@ Row {//RowLayout { horizontalAlignment: Text.AlignHCenter verticalAlignment: Text.AlignVCenter } + MouseArea { + anchors.fill: parent + onPressed: mouse.accepted = false + cursorShape: !backButton.disabled ? Qt.PointingHandCursor : Qt.ArrowCursor + } } TextField { @@ -71,6 +77,11 @@ Row {//RowLayout { verticalAlignment: Text.AlignVCenter //font.family: "Font Awesome 6 Free"//FontAwesome.fontFamily//FontAwesome.fontFamilySolid //font.weight: Font.Bold - } + } + MouseArea { + anchors.fill: parent + onPressed: mouse.accepted = false + cursorShape: !forwardButton.disabled ? Qt.PointingHandCursor : Qt.ArrowCursor + } } } diff --git a/qml/components/SettingsDialog.qml b/qml/components/SettingsDialog.qml index 933e8a4..60fe834 100644 --- a/qml/components/SettingsDialog.qml +++ b/qml/components/SettingsDialog.qml @@ -22,6 +22,7 @@ Popup { // General tab properties property alias theme: themeBox property alias currency: currencyBox + property alias hideHomepageButton: hideHomepageButtonSwitch.checked // Wallet settings property alias balanceDisplay: balanceDisplayBox.currentIndex//property alias balanceDisplay: balanceDisplayBox.currentText property alias balanceAmountPrecision: balancePrecisionBox.currentText @@ -151,7 +152,12 @@ Popup { verticalAlignment: Text.AlignVCenter font.bold: true//(parent.checked) ? true : false //font.family: FontAwesome.fontFamily - } + } + MouseArea { + anchors.fill: parent + onPressed: mouse.accepted = false + cursorShape: !parent.checked ? Qt.PointingHandCursor : Qt.ArrowCursor + } } TabButton { @@ -176,6 +182,11 @@ Popup { font.bold: true //font.family: FontAwesome.fontFamily } + MouseArea { + anchors.fill: parent + onPressed: mouse.accepted = false + cursorShape: !parent.checked ? Qt.PointingHandCursor : Qt.ArrowCursor + } } } /*Rectangle { @@ -255,33 +266,33 @@ Popup { //Layout.alignment: Qt.AlignLeft; Layout.leftMargin: 0 } - NeroshopComponents.ComboBox { - id: currencyBox - anchors.right: parent.right//Layout.alignment: Qt.AlignRight; Layout.rightMargin: 0 - width: settingsStack.comboBoxWidth - currentIndex: model.indexOf(Script.getString("neroshop.generalsettings.currency").toUpperCase()) - displayText: currentText - ////property string lastCurrencySet: (Script.getString("neroshop.generalsettings.currency")) ? Script.getString("neroshop.generalsettings.currency") : "USD" - //editable: true; selectTextByMouse: true - model: Backend.getCurrencyList() - //implicitContentWidthPolicy: ComboBox.WidestText//ComboBox.ContentItemImplicitWidth - onAccepted: { - if (find(editText) === -1) - model.append({text: editText}) - } + NeroshopComponents.ComboBox { + id: currencyBox + anchors.right: parent.right//Layout.alignment: Qt.AlignRight; Layout.rightMargin: 0 + width: settingsStack.comboBoxWidth + currentIndex: model.indexOf(Script.getString("neroshop.generalsettings.currency").toUpperCase()) + displayText: currentText + ////property string lastCurrencySet: (Script.getString("neroshop.generalsettings.currency")) ? Script.getString("neroshop.generalsettings.currency") : "USD" + //editable: true; selectTextByMouse: true + model: Backend.getCurrencyList() + //implicitContentWidthPolicy: ComboBox.WidestText//ComboBox.ContentItemImplicitWidth + onAccepted: { + if (find(editText) === -1) + model.append({text: editText}) + } - onActivated: { - displayText = currentText - priceDisplayText.currency = displayText - ////lastCurrencySet = currentText + onActivated: { + displayText = currentText + priceDisplayText.currency = displayText + ////lastCurrencySet = currentText + } + indicatorWidth: settingsStack.comboBoxButtonWidth + indicatorDoNotPassBorder: settingsStack.comboBoxNestedButton + color: "#f2f2f2"//(NeroshopComponents.Style.darkTheme) ? "#101010" : "#f0f0f0" + //textColor: (NeroshopComponents.Style.darkTheme) ? "#ffffff" : "#000000" } - indicatorWidth: settingsStack.comboBoxButtonWidth - indicatorDoNotPassBorder: settingsStack.comboBoxNestedButton - color: "#f2f2f2"//(NeroshopComponents.Style.darkTheme) ? "#101010" : "#f0f0f0" - //textColor: (NeroshopComponents.Style.darkTheme) ? "#ffffff" : "#000000" } - } - // Price API + // Price API - TODO /*Item { Layout.fillWidth: true Layout.preferredHeight: childrenRect.height @@ -295,7 +306,7 @@ Popup { id: priceApiBox anchors.right: parent.right width: settingsStack.comboBoxWidth; indicatorWidth: settingsStack.comboBoxButtonWidth - model: ["CoinGecko", "CoinMarketCap"] + //model: ["CoinGecko", "CoinMarketCap"] Component.onCompleted: currentIndex = find("CoinGecko") indicatorDoNotPassBorder: settingsStack.comboBoxNestedButton color: "#f2f2f2" @@ -327,55 +338,73 @@ Popup { } ColumnLayout { - id: themeColumn + id: appColumn width: parent.width; height: childrenRect.height //spacing: 200 // spacing between Row items Item { Layout.fillWidth: true Layout.preferredHeight: childrenRect.height - Text { - text: qsTr("Theme:") - color: (NeroshopComponents.Style.darkTheme) ? "#ffffff" : "#000000" - anchors.verticalCenter: themeBox.verticalCenter//Layout.alignment: Qt.AlignLeft; Layout.leftMargin: 0 - } - NeroshopComponents.ComboBox { - id: themeBox - anchors.right: parent.right//Layout.alignment: Qt.AlignRight; Layout.rightMargin: 0 - width: settingsStack.comboBoxWidth - currentIndex: model.indexOf(NeroshopComponents.Style.themeName)//Component.onCompleted: currentIndex = model.indexOf(NeroshopComponents.Style.themeName) // Set the initial currentIndex to the index in the array containing themeName string - displayText: currentText - property string lastUsedDarkTheme: (Script.getBoolean("neroshop.generalsettings.application.theme.dark")) ? Script.getString("neroshop.generalsettings.application.theme.name") : "DefaultDark" - property string lastUsedLightTheme: (!Script.getBoolean("neroshop.generalsettings.application.theme.dark")) ? Script.getString("neroshop.generalsettings.application.theme.name") : "DefaultLight" - model: ["DefaultDark", "DefaultLight", "PurpleDust"] - onActivated: { - if(currentText == "PurpleDust") { - NeroshopComponents.Style.darkTheme = true - lastUsedDarkTheme = currentText - } - if(currentText == "DefaultDark") { - NeroshopComponents.Style.darkTheme = true - lastUsedDarkTheme = currentText - } - if(currentText == "DefaultLight") { - NeroshopComponents.Style.darkTheme = false - lastUsedLightTheme = currentText - } - displayText = currentText - NeroshopComponents.Style.themeName = displayText // update the actual theme (name) - themeSwitcher.checked = !NeroshopComponents.Style.darkTheme // update the theme switch - // NOTE: on app launch, the theme will ALWAYS be reset back to its default unless you change the theme settings in your configuration file - //todo: change theme in configuration file too - console.log("Theme set to", currentText) + Text { + text: qsTr("Theme:") + color: (NeroshopComponents.Style.darkTheme) ? "#ffffff" : "#000000" + anchors.verticalCenter: themeBox.verticalCenter//Layout.alignment: Qt.AlignLeft; Layout.leftMargin: 0 } - indicatorWidth: settingsStack.comboBoxButtonWidth - indicatorDoNotPassBorder: settingsStack.comboBoxNestedButton - color: "#f2f2f2" - } // ComboBox + NeroshopComponents.ComboBox { + id: themeBox + anchors.right: parent.right//Layout.alignment: Qt.AlignRight; Layout.rightMargin: 0 + width: settingsStack.comboBoxWidth + currentIndex: model.indexOf(NeroshopComponents.Style.themeName)//Component.onCompleted: currentIndex = model.indexOf(NeroshopComponents.Style.themeName) // Set the initial currentIndex to the index in the array containing themeName string + displayText: currentText + property string lastUsedDarkTheme: (Script.getBoolean("neroshop.generalsettings.application.theme.dark")) ? Script.getString("neroshop.generalsettings.application.theme.name") : "DefaultDark" + property string lastUsedLightTheme: (!Script.getBoolean("neroshop.generalsettings.application.theme.dark")) ? Script.getString("neroshop.generalsettings.application.theme.name") : "DefaultLight" + model: ["DefaultDark", "DefaultLight", "PurpleDust"] + onActivated: { + if(currentText == "PurpleDust") { + NeroshopComponents.Style.darkTheme = true + lastUsedDarkTheme = currentText + } + if(currentText == "DefaultDark") { + NeroshopComponents.Style.darkTheme = true + lastUsedDarkTheme = currentText + } + if(currentText == "DefaultLight") { + NeroshopComponents.Style.darkTheme = false + lastUsedLightTheme = currentText + } + displayText = currentText + NeroshopComponents.Style.themeName = displayText // update the actual theme (name) + themeSwitcher.checked = !NeroshopComponents.Style.darkTheme // update the theme switch + // NOTE: on app launch, the theme will ALWAYS be reset back to its default unless you change the theme settings in your configuration file + //todo: change theme in configuration file too + console.log("Theme set to", currentText) + } + indicatorWidth: settingsStack.comboBoxButtonWidth + indicatorDoNotPassBorder: settingsStack.comboBoxNestedButton + color: "#f2f2f2" + } // ComboBox } - // Window + // Hide homepage button + Item { + Layout.fillWidth: true + Layout.preferredHeight: childrenRect.height + Text { + anchors.verticalCenter: hideHomepageButtonSwitch.verticalCenter + text: qsTr("Hide homepage button:") + color: NeroshopComponents.Style.darkTheme ? "#ffffff" : "#000000" + } + + NeroshopComponents.Switch { + id: hideHomepageButtonSwitch + anchors.right: parent.right; anchors.rightMargin: 5 + //width: settingsStack.comboBoxWidth + checked: true + radius: 13 + backgroundCheckedColor: "#605185" + } + } } // RowLayout2 } // GroupBox2 - GroupBox { + GroupBox { //Layout.row: 2 Layout.alignment: Qt.AlignHCenter Layout.preferredWidth: settingsStack.contentBoxWidth @@ -500,7 +529,7 @@ Popup { NeroshopComponents.Switch { id: showCurrencySignSwitch anchors.right: parent.right; anchors.rightMargin: 5 - width: settingsStack.comboBoxWidth + //width: settingsStack.comboBoxWidth checked: false radius: 13 backgroundCheckedColor: "#605185" @@ -570,7 +599,7 @@ Popup { NeroshopComponents.Switch { id: hideProductDetailsSwitch anchors.right: parent.right; anchors.rightMargin: 5 - width: settingsStack.comboBoxWidth + //width: settingsStack.comboBoxWidth checked: false radius: 13 backgroundCheckedColor: "#605185" @@ -610,7 +639,7 @@ Popup { NeroshopComponents.Switch { id: gridDetailsAlignCenterSwitch anchors.right: parent.right; anchors.rightMargin: 5 - width: settingsStack.comboBoxWidth + //width: settingsStack.comboBoxWidth checked: false radius: 13 backgroundCheckedColor: "#605185" diff --git a/qml/components/Style.qml b/qml/components/Style.qml index 3a9823e..9482391 100644 --- a/qml/components/Style.qml +++ b/qml/components/Style.qml @@ -37,7 +37,7 @@ QtObject { tertiaryColor = "#393947"//"#4f4f63"//<= tint } else { //"DefaultDark" - primaryColor = "#1a1a1a"//"#202020"////"#141414" + primaryColor = "#131415"//"#1a1a1a"//"#202020"////"#141414" secondaryColor = "#202020"//"#2e2e2e"////"#1a1a1a" tertiaryColor = "#595959" } diff --git a/qml/components/ViewToggle.qml b/qml/components/ViewToggle.qml index ca4115f..02f29b4 100644 --- a/qml/components/ViewToggle.qml +++ b/qml/components/ViewToggle.qml @@ -11,56 +11,55 @@ Item { property int currentIndex: viewButtonGroup.checkedButton.buttonIndex width: childrenRect.width; height: childrenRect.height -ButtonGroup { - id: viewButtonGroup - buttons: column.children - exclusive: true // only one button in the group can be checked at any given time - onClicked: { - console.log("Switched to", button.text) - button.checked = true + ButtonGroup { + id: viewButtonGroup + buttons: column.children + exclusive: true // only one button in the group can be checked at any given time + onClicked: { + console.log("Switched to", button.text) + button.checked = true + } } -} -Row { - id: column - spacing: 2 - - Button { - checked: true - text: qsTr("Grid view") - ButtonGroup.group: viewButtonGroup // attaches a button to a button group - property int buttonIndex: 0 - display: AbstractButton.IconOnly - icon.source: "qrc:/images/grid.png" - icon.color: !this.checked ? "#39304f" : "#ffffff"// icon color is set automatically unless we set it ourselves, which we do here - background: Rectangle { - radius: viewToggle.radius - color: parent.checked ? "#39304f" : "#e0e0e0"//"#353637" : "#e0e0e0"// rgb(53, 54, 55), rgb(224, 224, 224); - } - MouseArea { - anchors.fill: parent - onPressed: mouse.accepted = false - cursorShape: !parent.checked ? Qt.PointingHandCursor : Qt.ArrowCursor - } - } + Row { + id: column + spacing: 2 - Button { - text: qsTr("List view") - ButtonGroup.group: viewButtonGroup - property int buttonIndex: 1 - display: AbstractButton.IconOnly - icon.source: "qrc:/images/list.png" - icon.color: !this.checked ? "#39304f" : "#ffffff" - background: Rectangle { - radius: viewToggle.radius - color: parent.checked ? "#39304f" : "#e0e0e0"//"#353637" : "#e0e0e0" + Button { + checked: true + text: qsTr("Grid view") + ButtonGroup.group: viewButtonGroup // attaches a button to a button group + property int buttonIndex: 0 + display: AbstractButton.IconOnly + icon.source: "qrc:/images/grid.png" + icon.color: !this.checked ? "#39304f" : "#ffffff"// icon color is set automatically unless we set it ourselves, which we do here + background: Rectangle { + radius: viewToggle.radius + color: parent.checked ? "#39304f" : "#e0e0e0"//"#353637" : "#e0e0e0"// rgb(53, 54, 55), rgb(224, 224, 224); + } + MouseArea { + anchors.fill: parent + onPressed: mouse.accepted = false + cursorShape: !parent.checked ? Qt.PointingHandCursor : Qt.ArrowCursor + } } - MouseArea { - anchors.fill: parent - onPressed: mouse.accepted = false - cursorShape: !parent.checked ? Qt.PointingHandCursor : Qt.ArrowCursor + + Button { + text: qsTr("List view") + ButtonGroup.group: viewButtonGroup + property int buttonIndex: 1 + display: AbstractButton.IconOnly + icon.source: "qrc:/images/list.png" + icon.color: !this.checked ? "#39304f" : "#ffffff" + background: Rectangle { + radius: viewToggle.radius + color: parent.checked ? "#39304f" : "#e0e0e0"//"#353637" : "#e0e0e0" + } + MouseArea { + anchors.fill: parent + onPressed: mouse.accepted = false + cursorShape: !parent.checked ? Qt.PointingHandCursor : Qt.ArrowCursor + } } } } - -} diff --git a/qml/main.qml b/qml/main.qml index f056573..b9c9c5e 100644 --- a/qml/main.qml +++ b/qml/main.qml @@ -32,7 +32,7 @@ ApplicationWindow { Button {//Image { id: neroshopLogoImageButton - visible: true + visible: !settingsDialog.hideHomepageButton property real iconSize: 30 icon.source: (NeroshopComponents.Style.darkTheme) ? "qrc:/images/appicons/Vector_Illustrator Files/LogoLight.svg" : "qrc:/images/appicons/Vector_Illustrator Files/LogoDark.svg" icon.color: icon.color diff --git a/qml/pages/HomePage.qml b/qml/pages/HomePage.qml index fb5288e..93d1708 100644 --- a/qml/pages/HomePage.qml +++ b/qml/pages/HomePage.qml @@ -153,6 +153,7 @@ Page { Layout.preferredWidth: childrenRect.width Layout.preferredHeight: childrenRect.height Layout.maximumWidth: scrollView.width + visible: itemsRepeater.count > 0 Column { spacing: 10 diff --git a/src/console/main.cpp b/src/console/main.cpp index 53e60ff..c262e60 100644 --- a/src/console/main.cpp +++ b/src/console/main.cpp @@ -6,8 +6,9 @@ using namespace neroshop; // linenoise #include -int main() { - +int main(int argc, char** argv) { + // todo: bind command names to functions + //------------------------- if(!neroshop::create_config()) { neroshop::load_config(); } @@ -26,7 +27,6 @@ int main() { std::cout << "failed to get nodes in the config file\nCheck your config file in ~/.config/neroshop" << std::endl; } //------------------------- - // todo: bind command names to functions char * line = NULL; while((line = linenoise("neroshop-console> ")) != NULL) { // Do something with the string @@ -93,10 +93,10 @@ int main() { }*/ //------------------------- - //neroshop::Server server; - //server.bind("exit", [](void) { ::system("exit"); }); + /*neroshop::Server server; + server.bind("exit", [](void) { ::system("exit"); });*/ - //client.call("exit" /*args ...*/); + //neroshop::Client client;client.call(server, "exit" /*args ...*/); //------------------------- return 0; } diff --git a/src/core/cart.cpp b/src/core/cart.cpp index 7aa23c3..9a00700 100644 --- a/src/core/cart.cpp +++ b/src/core/cart.cpp @@ -18,7 +18,7 @@ neroshop::Cart::~Cart() { // normal //////////////////// void neroshop::Cart::load(const std::string& user_id) { - neroshop::db::Sqlite3 * database = neroshop::db::Sqlite3::get_database(); + neroshop::db::Sqlite3 * database = neroshop::get_database(); if(!database) throw std::runtime_error("database is NULL"); // Set the cart's id this->id = database->get_text_params("SELECT uuid FROM cart WHERE user_id = $1", { user_id }); @@ -81,7 +81,7 @@ void neroshop::Cart::load(const std::string& user_id) { } //////////////////// void neroshop::Cart::add(const std::string& user_id, const std::string& product_id, int quantity) { - neroshop::db::Sqlite3 * database = neroshop::db::Sqlite3::get_database(); + neroshop::db::Sqlite3 * database = neroshop::get_database(); if(!database) throw std::runtime_error("database is NULL"); if(quantity < 1) return; std::string item_name = database->get_text_params("SELECT name FROM products WHERE uuid = $1", { product_id }); // temporary @@ -134,7 +134,7 @@ void neroshop::Cart::add(const std::string& user_id, const neroshop::Item& item, } //////////////////// void neroshop::Cart::remove(const std::string& user_id, const std::string& product_id, int quantity) { - neroshop::db::Sqlite3 * database = neroshop::db::Sqlite3::get_database(); + neroshop::db::Sqlite3 * database = neroshop::get_database(); if(!database) throw std::runtime_error("database is NULL"); } @@ -144,7 +144,7 @@ void neroshop::Cart::remove(const std::string& user_id, const neroshop::Item& it } //////////////////// void neroshop::Cart::empty(const std::string& user_id) { - neroshop::db::Sqlite3 * database = neroshop::db::Sqlite3::get_database(); + neroshop::db::Sqlite3 * database = neroshop::get_database(); if(!database) throw std::runtime_error("database is NULL"); std::string cart_id = database->get_text_params("SELECT uuid FROM cart WHERE user_id = $1", { user_id }); database->execute_params("DELETE FROM cart_item WHERE cart_id = $1;", { cart_id }); diff --git a/src/core/client.cpp b/src/core/client.cpp index 8350097..52fedd4 100644 --- a/src/core/client.cpp +++ b/src/core/client.cpp @@ -9,7 +9,7 @@ neroshop::Client::~Client() { } //////////////////// void neroshop::Client::create() { - #if defined(__gnu_linux__) + #if defined(__gnu_linux__) && defined(NEROSHOP_USE_SYSTEM_SOCKETS) if(socket) return; // socket must be null before a new one can be created (if socket is not null then it means it was never closed) socket = ::socket(AF_INET, SOCK_STREAM, 0); if(socket < 0) { @@ -19,7 +19,7 @@ void neroshop::Client::create() { } //////////////////// bool neroshop::Client::connect(unsigned int port, std::string address) { - #if defined(__gnu_linux__) + #if defined(__gnu_linux__) && defined(NEROSHOP_USE_SYSTEM_SOCKETS) struct hostent * host = gethostbyname(address.c_str()); if(host == nullptr) { std::cerr << "No host to connect to" << std::endl; @@ -42,7 +42,7 @@ bool neroshop::Client::connect(unsigned int port, std::string address) { } //////////////////// void neroshop::Client::write(const std::string& text) { - #if defined(__gnu_linux__) + #if defined(__gnu_linux__) && defined(NEROSHOP_USE_SYSTEM_SOCKETS) ssize_t write_result = ::write(socket, text.c_str(), text.length()); if(write_result < 0) { // -1 = error std::cerr << "Could not write to server" << std::endl; @@ -52,7 +52,7 @@ void neroshop::Client::write(const std::string& text) { //////////////////// std::string neroshop::Client::read() { - #if defined(__gnu_linux__) + #if defined(__gnu_linux__) && defined(NEROSHOP_USE_SYSTEM_SOCKETS) memset(buffer, 0, 256); // clear buffer (fills buffer with 0's) before reading into buffer//bzero(buffer, 256); // bzero is deprecated ssize_t read_result = ::read(socket, buffer, 255); if(read_result < 0) { @@ -60,10 +60,11 @@ std::string neroshop::Client::read() } return static_cast(buffer); #endif + return ""; } //////////////////// void neroshop::Client::close() { - #if defined(__gnu_linux__) + #if defined(__gnu_linux__) && defined(NEROSHOP_USE_SYSTEM_SOCKETS) if(socket == 0) return; ::close(socket); socket = 0; @@ -71,7 +72,7 @@ void neroshop::Client::close() { } //////////////////// void neroshop::Client::shutdown() { - #if defined(__gnu_linux__) + #if defined(__gnu_linux__) && defined(NEROSHOP_USE_SYSTEM_SOCKETS) ::shutdown(socket, SHUT_RDWR); // SHUT_RD, SHUT_WR, SHUT_RDWR #endif } diff --git a/src/core/client.hpp b/src/core/client.hpp index e608076..27f2355 100644 --- a/src/core/client.hpp +++ b/src/core/client.hpp @@ -1,18 +1,27 @@ #ifndef CLIENT_HPP_NEROSHOP #define CLIENT_HPP_NEROSHOP +#if defined(__gnu_linux__) && defined(NEROSHOP_USE_SYSTEM_SOCKETS) +#include +#include +#include +#include +#include +#endif + +#if defined(NEROSHOP_USE_LIBZMQ) +#include +#endif + +#if defined(NEROSHOP_USE_LIBUV) #include -//#include -#include // std::unique_ptr -#include // memset -#if defined(__gnu_linux__) -#include // ::close #endif -#include "debug.hpp" -#include "server.hpp" // not sure if server should even be included in client ┐(´•_•`)┌ +#include // std::unique_ptr +#include // memset #include "debug.hpp" +#include "server.hpp" // temporary namespace neroshop { class Client { @@ -38,26 +47,23 @@ public: //! \param name The name of the functor. // decltype (auto) detects the function's return type and allows us to return any return type template - decltype (auto) call(const Server& server/*<= temporary*/, const std::string& name, Args&&... args) { // todo: rename to request or nah? + decltype (auto) call(const Server& server, const std::string& name, Args&&... args) { // todo: rename to request or nah? + std::cout << "calling " << name << "\n"; // Print the arguments //(std::cout << ... << args); // Get number of arguments static const size_t arg_count = sizeof...(Args); + std::cout << "arg count: " << arg_count << "\n"; // Check if function is not nullptr before calling it // Call function (this will work even if a function has zero args :D) - return const_cast(server).function_list[name](std::forward(args)...); + return const_cast(server).functions[name](std::forward(args)...); } // For functions without a return value template void call(const std::string& name, Args&&... args) { // todo: rename to request or nah? - } + } private: - #if defined(NEROSHOP_USE_LIBUV) - uv_tcp_t handle;//uv_tcp_t client; - //uv_udp_t handle; //(for receiving) - //uv_udp_send_t request; //(for sending) - #endif - #if defined(__gnu_linux__) + #if defined(__gnu_linux__) && defined(NEROSHOP_USE_SYSTEM_SOCKETS) int socket; char buffer[256]; #endif diff --git a/src/core/config.cpp b/src/core/config.cpp index 6ca064c..1f69f90 100644 --- a/src/core/config.cpp +++ b/src/core/config.cpp @@ -67,24 +67,18 @@ neroshop = { "node.xmr.ru:18081" }, stagenet = { - "stagenet.community.rino.io:38081", - "xmr-lux.boldsuck.org:38081", - "node2.monerodevs.org:38089", - --"plowsof3t5hogddwabaeiyrno25efmzfxyro2vligremt7sxpsclfaid.onion:38089", - "stagenet.xmr-tw.org:38081", - --"stagenet.xmr.ditatompel.com", - "xmr-lux.boldsuck.org:38081", - --"plowsoffjexmxalw73tkjmf422gq6575fc7vicuu4javzn2ynnte6tyd.onion:38089", - "node.monerodevs.org:38089", + "http://node.monerodevs.org:38089", + "http://node2.monerodevs.org:38089", + "http://stagenet.community.rino.io:38081", + "http://stagenet.xmr-tw.org:38081", + "http://xmr-lux.boldsuck.org:38081", + "https://xmr-lux.boldsuck.org:38081", }, testnet = { - "testnet.community.rino.io:28081", - "node2.monerodevs.org:28089", - --"plowsof3t5hogddwabaeiyrno25efmzfxyro2vligremt7sxpsclfaid.onion:28089", - "testnet.xmr-tw.org:28081", - --"plowsoffjexmxalw73tkjmf422gq6575fc7vicuu4javzn2ynnte6tyd.onion:28089", - --"testnet.xmr.ditatompel.com", - "node.monerodevs.org:28089", + "http://node.monerodevs.org:28089", + "http://node2.monerodevs.org:28089", + "http://testnet.community.rino.io:28081", + "http://testnet.xmr-tw.org:28081", } } } diff --git a/src/core/currency_converter.hpp b/src/core/currency_converter.hpp index 85d84ce..45efe6a 100644 --- a/src/core/currency_converter.hpp +++ b/src/core/currency_converter.hpp @@ -39,6 +39,9 @@ enum class Currency { RUB, PHP, INR, + // Metals + XAG, + XAU, // Crypto XMR, BTC, @@ -203,6 +206,8 @@ static std::map> C //{ "VUV", { neroshop::Currency::, "Vanuatu Vatu" } }, //{ "WST", { neroshop::Currency::, "Samoa Tala" } }, //{ "XAF", { neroshop::Currency::, "Communauté Financière Africaine (BEAC) CFA Franc BEAC" } }, + { "XAG", { neroshop::Currency::XAG, "Silver", 2 } }, + { "XAU", { neroshop::Currency::XAU, "Gold", 2 } }, //{ "XCD", { neroshop::Currency::, "East Caribbean Dollar" } }, //{ "XDR", { neroshop::Currency::, "International Monetary Fund (IMF) Special Drawing Rights" } }, //{ "XOF", { neroshop::Currency::, "Communauté Financière Africaine (BCEAO) Franc" } }, diff --git a/src/core/database.cpp b/src/core/database.cpp index d57ff35..482dc65 100644 --- a/src/core/database.cpp +++ b/src/core/database.cpp @@ -1,446 +1,8 @@ #include "database.hpp" - -neroshop::db::Sqlite3::Sqlite3() : handle(nullptr), opened(false) {} -//////////////////// -neroshop::db::Sqlite3::Sqlite3(const std::string& filename) : Sqlite3() -{ - if(!open(filename)) { - throw std::runtime_error(std::string("sqlite3_open: ") + std::string(sqlite3_errmsg(handle))); - } -} -//////////////////// -neroshop::db::Sqlite3::~Sqlite3() { - close(); -} -//////////////////// -// SQLite database should only need to be opened once per application session and closed once when the application is terminated -bool neroshop::db::Sqlite3::open(const std::string& filename) -{ - if(opened) { - neroshop::print("database is already opened", 2); - return true; - } - if(sqlite3_open(filename.c_str(), &handle) != SQLITE_OK) { - close(); - return false; - } - // Enable Write-Ahead Log. This will prevent the database from being locked - if(get_text("PRAGMA journal_mode;") != "wal") { - execute("PRAGMA journal_mode = WAL;"); // requires version 3.7.0 (2010-07-21) - } - // Enable Foreign keys - if(get_integer("PRAGMA foreign_keys;") != 1) { - execute("PRAGMA foreign_keys = ON;"); // requires version 3.6.19 (2009-10-14) - } - opened = true; - return true; -} -//////////////////// -void neroshop::db::Sqlite3::close() { - if(!handle) { - return; - } - sqlite3_close(handle); - handle = nullptr; - opened = false; - neroshop::print("database is now closed"); -} -//////////////////// -void neroshop::db::Sqlite3::execute(const std::string& command) -{ - if(!handle) throw std::runtime_error("database is not connected"); - char * error_message = 0; - int result = sqlite3_exec(handle, command.c_str(), neroshop::db::Sqlite3::callback, 0, &error_message); - if (result != SQLITE_OK) { - neroshop::print("sqlite3_exec: " + std::string(error_message), 1); - sqlite3_free(error_message); - } -} -//////////////////// -void neroshop::db::Sqlite3::execute_params(const std::string& command, const std::vector& args) { - if(!handle) throw std::runtime_error("database is not connected"); - // Prepare statement - sqlite3_stmt * statement = nullptr; - int result = sqlite3_prepare_v2(handle, command.c_str(), -1, &statement, nullptr); - if(result != SQLITE_OK) { - neroshop::print("sqlite3_prepare_v2: " + std::string(sqlite3_errmsg(handle)), 1); - // Since we don't prepare a statement here, there is no need to finalise it - return; - } - // Bind user-defined parameter arguments - for(int i = 0; i < args.size()/*sqlite3_bind_parameter_count(statement)*/; i++) { - result = sqlite3_bind_text(statement, i + 1, args[i].c_str(), args[i].length(), SQLITE_STATIC); - if(result != SQLITE_OK) { - neroshop::print("sqlite3_bind_*: " + std::string(sqlite3_errmsg(handle)), 1); - sqlite3_finalize(statement); - return; - } - } - // Evaluate the statement - result = sqlite3_step(statement); - if(result != SQLITE_DONE) { - neroshop::print("sqlite3_step: " + std::string(sqlite3_errmsg(handle)), 1); - sqlite3_finalize(statement); - return; - } - // Finalize (destroy) the prepared statement - result = sqlite3_finalize(statement); - if(result != SQLITE_OK) { - neroshop::print("sqlite3_finalize: " + std::string(sqlite3_errmsg(handle)), 1); - sqlite3_finalize(statement); - return; - } - // We are not returning anything, just setting executing queries -} -//////////////////// -//////////////////// -//////////////////// -std::string neroshop::db::Sqlite3::get_sqlite_version() { - return sqlite3_libversion(); -} -//////////////////// -sqlite3 * neroshop::db::Sqlite3::get_handle() const { - return handle; -} -//////////////////// -neroshop::db::Sqlite3 * neroshop::db::Sqlite3::get_database() { +neroshop::db::Sqlite3 * neroshop::get_database() { std::string database_path = NEROSHOP_DEFAULT_DATABASE_PATH; std::string database_file = "data.sqlite3"; static neroshop::db::Sqlite3 database_obj { database_file };////static neroshop::db::Sqlite3 database_obj { database_path + "/" + database_file }; return &database_obj; } -//////////////////// -void * neroshop::db::Sqlite3::get_blob(const std::string& command) { - if(!handle) throw std::runtime_error("database is not connected"); - sqlite3_stmt * statement = nullptr; - int result = sqlite3_prepare_v2(handle, command.c_str(), -1, &statement, nullptr); - if(result != SQLITE_OK) { - neroshop::print("sqlite3_prepare_v2: " + std::string(sqlite3_errmsg(handle)), 1); - return nullptr; - } - result = sqlite3_step(statement); - if (result != SQLITE_ROW) { - neroshop::print("sqlite3_step: " + std::string(sqlite3_errmsg(handle)), 1); - sqlite3_finalize(statement); - return nullptr; - } - int column_type = sqlite3_column_type(statement, 0); - if(column_type == SQLITE_NULL) { - sqlite3_finalize(statement); - return nullptr; - } - if(column_type != SQLITE_BLOB) { // NULL is the only other acceptable return type - neroshop::print("sqlite3_column_type: invalid column return type\ncommand: " + command, 1); - sqlite3_finalize(statement); - return nullptr; - } - void * blob = const_cast(sqlite3_column_blob(statement, 0));//reinterpret_cast(sqlite3_column_text16(stmt, 0)); // utf-16 - sqlite3_finalize(statement); - return blob; -} -//////////////////// -void * neroshop::db::Sqlite3::get_blob_params(const std::string& command, const std::vector& args) { - if(!handle) throw std::runtime_error("database is not connected"); - sqlite3_stmt * statement = nullptr; - int result = sqlite3_prepare_v2(handle, command.c_str(), -1, &statement, nullptr); - if(result != SQLITE_OK) { - neroshop::print("sqlite3_prepare_v2: " + std::string(sqlite3_errmsg(handle)), 1); - return nullptr; - } - // Bind user-defined parameter arguments - for(int i = 0; i < args.size()/*sqlite3_bind_parameter_count(statement)*/; i++) { - result = sqlite3_bind_text(statement, i + 1, args[i].c_str(), args[i].length(), SQLITE_STATIC); - if(result != SQLITE_OK) { - neroshop::print("sqlite3_bind_*: " + std::string(sqlite3_errmsg(handle)), 1); - sqlite3_finalize(statement); - return nullptr; - } - } - sqlite3_step(statement); // Don't check for error or it'll keep saying: "another row available" or "no more rows available" - int column_type = sqlite3_column_type(statement, 0); - if(column_type == SQLITE_NULL) { - sqlite3_finalize(statement); - return nullptr; - } - if(column_type != SQLITE_BLOB) { - neroshop::print("sqlite3_column_type: invalid column return type\ncommand: " + command, 1); - sqlite3_finalize(statement); - return nullptr; - } - void * blob = const_cast(sqlite3_column_blob(statement, 0));//reinterpret_cast(sqlite3_column_text16(stmt, 0)); // utf-16 - sqlite3_finalize(statement); - return blob; -} -//////////////////// -std::string neroshop::db::Sqlite3::get_text(const std::string& command) {//const { - if(!handle) throw std::runtime_error("database is not connected"); - sqlite3_stmt * stmt = nullptr; - int result = sqlite3_prepare_v2(handle, command.c_str(), -1, &stmt, nullptr); - if(result != SQLITE_OK) { - neroshop::print("sqlite3_prepare_v2: " + std::string(sqlite3_errmsg(handle)), 1); - return ""; - } - result = sqlite3_step(stmt); - if (result != SQLITE_ROW) { - neroshop::print("sqlite3_step: " + std::string(sqlite3_errmsg(handle)), 1); - sqlite3_finalize(stmt); - return ""; - } - int column_type = sqlite3_column_type(stmt, 0); - if(column_type == SQLITE_NULL) { - sqlite3_finalize(stmt); - return ""; - } - if(column_type != SQLITE_TEXT) { - neroshop::print("sqlite3_column_type: invalid column return type\ncommand: " + command, 1); - sqlite3_finalize(stmt); - return ""; - } - std::string text = reinterpret_cast(sqlite3_column_text(stmt, 0));//reinterpret_cast(sqlite3_column_text16(stmt, 0)); // utf-16 - sqlite3_finalize(stmt); - return text; -} -//////////////////// -std::string neroshop::db::Sqlite3::get_text_params(const std::string& command, const std::vector& args) {//const { - if(!handle) throw std::runtime_error("database is not connected"); - // Prepare statement - sqlite3_stmt * statement = nullptr; - int result = sqlite3_prepare_v2(handle, command.c_str(), -1, &statement, nullptr); - if(result != SQLITE_OK) { - neroshop::print("sqlite3_prepare_v2: " + std::string(sqlite3_errmsg(handle)), 1); - return ""; - } - // Bind user-defined parameter arguments - for(int i = 0; i < args.size()/*sqlite3_bind_parameter_count(statement)*/; i++) { - result = sqlite3_bind_text(statement, i + 1, args[i].c_str(), args[i].length(), SQLITE_STATIC); - if(result != SQLITE_OK) { - neroshop::print("sqlite3_bind_*: " + std::string(sqlite3_errmsg(handle)), 1); - sqlite3_finalize(statement); - return ""; - } - } - // Evaluate statement - sqlite3_step(statement); // Don't check for error or it'll keep saying: "another row available" or "no more rows available" - // Check the type of the statement's return value - int column_type = sqlite3_column_type(statement, 0); - if(column_type == SQLITE_NULL) { - sqlite3_finalize(statement); - return ""; - } - if(column_type != SQLITE_TEXT) { - neroshop::print("sqlite3_column_type: invalid column return type\ncommand: " + command, 1); - sqlite3_finalize(statement); - return ""; - } - // Finalize (destroy) the prepared statement - std::string text = reinterpret_cast(sqlite3_column_text(statement, 0));//reinterpret_cast(sqlite3_column_text16(stmt, 0)); // utf-16 - sqlite3_finalize(statement); - return text; -} -//////////////////// -int neroshop::db::Sqlite3::get_integer(const std::string& command) { - if(!handle) throw std::runtime_error("database is not connected"); - sqlite3_stmt * statement = nullptr; - int result = sqlite3_prepare_v2(handle, command.c_str(), -1, &statement, nullptr); - if(result != SQLITE_OK) { - neroshop::print("sqlite3_prepare_v2: " + std::string(sqlite3_errmsg(handle)), 1); - return 0; - } - result = sqlite3_step(statement); - if (result != SQLITE_ROW) { - neroshop::print("sqlite3_step: " + std::string(sqlite3_errmsg(handle)), 1); - sqlite3_finalize(statement); - return 0; - } - int column_type = sqlite3_column_type(statement, 0); - if(column_type == SQLITE_NULL) { - sqlite3_finalize(statement); - return 0; - } - if(column_type != SQLITE_INTEGER) { - neroshop::print("sqlite3_column_type: invalid column return type\ncommand: " + command, 1); - sqlite3_finalize(statement); - return 0; - } - int number = sqlite3_column_int64(statement, 0);//sqlite3_column_int(statement, 0); - sqlite3_finalize(statement); - return number; -} -//////////////////// -int neroshop::db::Sqlite3::get_integer_params(const std::string& command, const std::vector& args) { - if(!handle) throw std::runtime_error("database is not connected"); - // Prepare statement - sqlite3_stmt * statement = nullptr; - int result = sqlite3_prepare_v2(handle, command.c_str(), -1, &statement, nullptr); - if(result != SQLITE_OK) { - neroshop::print("sqlite3_prepare_v2: " + std::string(sqlite3_errmsg(handle)), 1); - return 0; - } - // Bind user-defined parameter arguments - for(int i = 0; i < args.size()/*sqlite3_bind_parameter_count(statement)*/; i++) { - result = sqlite3_bind_text(statement, i + 1, args[i].c_str(), args[i].length(), SQLITE_STATIC); - if(result != SQLITE_OK) { - neroshop::print("sqlite3_bind_*: " + std::string(sqlite3_errmsg(handle)), 1); - sqlite3_finalize(statement); - return 0; - } - } - // Evaluate statement - sqlite3_step(statement); // Don't check for error or it'll keep saying: "another row available" or "no more rows available" - // Check the type of the statement's return value - int column_type = sqlite3_column_type(statement, 0); - if(column_type == SQLITE_NULL) { - sqlite3_finalize(statement); - return 0; - } - if(column_type != SQLITE_INTEGER) { - neroshop::print("sqlite3_column_type: invalid column return type\ncommand: " + command, 1); - sqlite3_finalize(statement); - return 0; - } - // Finalize (destroy) the prepared statement - int number = sqlite3_column_int64(statement, 0);//sqlite3_column_int(statement, 0); - sqlite3_finalize(statement); - return number; -} -//////////////////// -double neroshop::db::Sqlite3::get_real(const std::string& command) { - if(!handle) throw std::runtime_error("database is not connected"); - sqlite3_stmt * stmt = nullptr; - int result = sqlite3_prepare_v2(handle, command.c_str(), -1, &stmt, nullptr); - if(result != SQLITE_OK) { - neroshop::print("sqlite3_prepare_v2: " + std::string(sqlite3_errmsg(handle)), 1); - return 0.0; - } - result = sqlite3_step(stmt); - if (result != SQLITE_ROW) { - neroshop::print("sqlite3_step: " + std::string(sqlite3_errmsg(handle)), 1); - sqlite3_finalize(stmt); - return 0.0; - } - int column_type = sqlite3_column_type(stmt, 0); - if(column_type == SQLITE_NULL) { - sqlite3_finalize(stmt); - return 0.0; - } - if(column_type != SQLITE_FLOAT) { - neroshop::print("sqlite3_column_type: invalid column return type\ncommand: " + command, 1); - sqlite3_finalize(stmt); - return 0.0; - } - double number = sqlite3_column_double(stmt, 0); - sqlite3_finalize(stmt); - return number; -} -//////////////////// -double neroshop::db::Sqlite3::get_real_params(const std::string& command, const std::vector& args) { - if(!handle) throw std::runtime_error("database is not connected"); - // Prepare statement - sqlite3_stmt * statement = nullptr; - int result = sqlite3_prepare_v2(handle, command.c_str(), -1, &statement, nullptr); - if(result != SQLITE_OK) { - neroshop::print("sqlite3_prepare_v2: " + std::string(sqlite3_errmsg(handle)), 1); - return 0.0; - } - // Bind user-defined parameter arguments - for(int i = 0; i < args.size()/*sqlite3_bind_parameter_count(statement)*/; i++) { - result = sqlite3_bind_text(statement, i + 1, args[i].c_str(), args[i].length(), SQLITE_STATIC); - if(result != SQLITE_OK) { - neroshop::print("sqlite3_bind_*: " + std::string(sqlite3_errmsg(handle)), 1); - sqlite3_finalize(statement); - return 0.0; - } - } - // Evaluate statement - sqlite3_step(statement); // Don't check for error or it'll keep saying: "another row available" or "no more rows available" - // Check the type of the statement's return value - int column_type = sqlite3_column_type(statement, 0); - if(column_type == SQLITE_NULL) { - sqlite3_finalize(statement); - return 0.0; - } - if(column_type != SQLITE_FLOAT) { - neroshop::print("sqlite3_column_type: invalid column return type\ncommand: " + command, 1); - sqlite3_finalize(statement); - return 0.0; - } - // Finalize (destroy) the prepared statement - double number = sqlite3_column_double(statement, 0); - sqlite3_finalize(statement); - return number; -} -//////////////////// -std::vector neroshop::db::Sqlite3::get_rows(const std::string& command) { - if(!handle) throw std::runtime_error("database is not connected"); - sqlite3_stmt * stmt = nullptr; - std::vector row_values = {}; - // Prepare (compile) statement - if(sqlite3_prepare_v2(handle, command.c_str(), -1, &stmt, nullptr) != SQLITE_OK) { - neroshop::print("sqlite3_prepare_v2: " + std::string(sqlite3_errmsg(handle)), 1); - return {}; - } - // Check whether the prepared statement returns no data (for example an UPDATE) - // "SELECT name FROM users ORDER BY id;" = 1 column - // "SELECT name, age FROM users ORDER BY id;" = 2 columns - // "SELECT * FROM users ORDER BY id;" = all column(s) including the id - if(sqlite3_column_count(stmt) == 0) { - neroshop::print("No data found. Be sure to use an appropriate SELECT statement", 1); - return {}; - } - // Get all table values row by row instead of column by column - int result = 0; - while((result = sqlite3_step(stmt)) == SQLITE_ROW) { - for(int i = 0; i < sqlite3_column_count(stmt); i++) { - std::string column_value = (sqlite3_column_text(stmt, i) == nullptr) ? "" : reinterpret_cast(sqlite3_column_text(stmt, i));////if(sqlite3_column_text(stmt, i) == nullptr) {throw std::runtime_error("column is NULL");} - row_values.push_back(column_value); //std::cout << sqlite3_column_text(stmt, i) << std::endl;//std::cout << sqlite3_column_name(stmt, i) << std::endl; - // To get a specific column use: if(i == 0) {} or any number - // or call sqlite3_column_name for each column - } - } - - if(result != SQLITE_DONE) { - neroshop::print("sqlite3_step: " + std::string(sqlite3_errmsg(handle)), 1); - } - - sqlite3_finalize(stmt); - - return row_values; -} -//////////////////// -//////////////////// -//////////////////// -//////////////////// -bool neroshop::db::Sqlite3::is_open() const { - return (opened == true); -} -//////////////////// -bool neroshop::db::Sqlite3::table_exists(const std::string& table_name) { - std::string command = "SELECT count(*) FROM sqlite_master WHERE type = 'table' AND name = $1;"; - return get_integer_params(command, { table_name }); -} -//////////////////// -/*bool neroshop::db::Sqlite3::rowid_exists(const std::string& table_name, int rowid) { - int rowid = database->get_integer_params("SELECT id FROM $1 WHERE id = $2", { table_name, rowid }); - return (rowid != 0); -}*/ -//////////////////// -//////////////////// -//////////////////// -int neroshop::db::Sqlite3::callback(void *not_used, int argc, char **argv, char **az_col_name) -{ - int i; - for(i = 0; i < argc; i++) { - std::cout << SQLITE3_TAG << az_col_name[i] << " = " << (argv[i] ? argv[i] : "NULL") << std::endl; // printf("%s = %s\n", azcolname[i], argv[i] ? argv[i] : "nullptr"); - } - std::cout << std::endl; - return 0; -} -//////////////////// -//////////////////// -//////////////////// -//////////////////// -//////////////////// -//////////////////// -//////////////////// -//////////////////// diff --git a/src/core/database.hpp b/src/core/database.hpp index bfef6c7..a13c8a4 100644 --- a/src/core/database.hpp +++ b/src/core/database.hpp @@ -18,4 +18,9 @@ #include "database/sqlite.hpp" +namespace neroshop { + +extern db::Sqlite3 * get_database(); + +} #endif diff --git a/src/core/database/sqlite.cpp b/src/core/database/sqlite.cpp new file mode 100644 index 0000000..595ed4b --- /dev/null +++ b/src/core/database/sqlite.cpp @@ -0,0 +1,439 @@ +#include "sqlite.hpp" + + +neroshop::db::Sqlite3::Sqlite3() : handle(nullptr), opened(false) {} +//////////////////// +neroshop::db::Sqlite3::Sqlite3(const std::string& filename) : Sqlite3() +{ + if(!open(filename)) { + throw std::runtime_error(std::string("sqlite3_open: ") + std::string(sqlite3_errmsg(handle))); + } +} +//////////////////// +neroshop::db::Sqlite3::~Sqlite3() { + close(); +} +//////////////////// +// SQLite database should only need to be opened once per application session and closed once when the application is terminated +bool neroshop::db::Sqlite3::open(const std::string& filename) +{ + if(opened) { + neroshop::print("database is already opened", 2); + return true; + } + if(sqlite3_open(filename.c_str(), &handle) != SQLITE_OK) { + close(); + return false; + } + // Enable Write-Ahead Log. This will prevent the database from being locked + if(get_text("PRAGMA journal_mode;") != "wal") { + execute("PRAGMA journal_mode = WAL;"); // requires version 3.7.0 (2010-07-21) + } + // Enable Foreign keys + if(get_integer("PRAGMA foreign_keys;") != 1) { + execute("PRAGMA foreign_keys = ON;"); // requires version 3.6.19 (2009-10-14) + } + opened = true; + return true; +} +//////////////////// +void neroshop::db::Sqlite3::close() { + if(!handle) { + return; + } + sqlite3_close(handle); + handle = nullptr; + opened = false; + neroshop::print("database is now closed"); +} +//////////////////// +void neroshop::db::Sqlite3::execute(const std::string& command) +{ + if(!handle) throw std::runtime_error("database is not connected"); + char * error_message = 0; + int result = sqlite3_exec(handle, command.c_str(), neroshop::db::Sqlite3::callback, 0, &error_message); + if (result != SQLITE_OK) { + neroshop::print("sqlite3_exec: " + std::string(error_message), 1); + sqlite3_free(error_message); + } +} +//////////////////// +void neroshop::db::Sqlite3::execute_params(const std::string& command, const std::vector& args) { + if(!handle) throw std::runtime_error("database is not connected"); + // Prepare statement + sqlite3_stmt * statement = nullptr; + int result = sqlite3_prepare_v2(handle, command.c_str(), -1, &statement, nullptr); + if(result != SQLITE_OK) { + neroshop::print("sqlite3_prepare_v2: " + std::string(sqlite3_errmsg(handle)), 1); + // Since we don't prepare a statement here, there is no need to finalise it + return; + } + // Bind user-defined parameter arguments + for(int i = 0; i < args.size()/*sqlite3_bind_parameter_count(statement)*/; i++) { + result = sqlite3_bind_text(statement, i + 1, args[i].c_str(), args[i].length(), SQLITE_STATIC); + if(result != SQLITE_OK) { + neroshop::print("sqlite3_bind_*: " + std::string(sqlite3_errmsg(handle)), 1); + sqlite3_finalize(statement); + return; + } + } + // Evaluate the statement + result = sqlite3_step(statement); + if(result != SQLITE_DONE) { + neroshop::print("sqlite3_step: " + std::string(sqlite3_errmsg(handle)), 1); + sqlite3_finalize(statement); + return; + } + // Finalize (destroy) the prepared statement + result = sqlite3_finalize(statement); + if(result != SQLITE_OK) { + neroshop::print("sqlite3_finalize: " + std::string(sqlite3_errmsg(handle)), 1); + sqlite3_finalize(statement); + return; + } + // We are not returning anything, just setting executing queries +} +//////////////////// +//////////////////// +//////////////////// +std::string neroshop::db::Sqlite3::get_sqlite_version() { + return sqlite3_libversion(); +} +//////////////////// +sqlite3 * neroshop::db::Sqlite3::get_handle() const { + return handle; +} +//////////////////// +void * neroshop::db::Sqlite3::get_blob(const std::string& command) { + if(!handle) throw std::runtime_error("database is not connected"); + sqlite3_stmt * statement = nullptr; + int result = sqlite3_prepare_v2(handle, command.c_str(), -1, &statement, nullptr); + if(result != SQLITE_OK) { + neroshop::print("sqlite3_prepare_v2: " + std::string(sqlite3_errmsg(handle)), 1); + return nullptr; + } + result = sqlite3_step(statement); + if (result != SQLITE_ROW) { + neroshop::print("sqlite3_step: " + std::string(sqlite3_errmsg(handle)), 1); + sqlite3_finalize(statement); + return nullptr; + } + int column_type = sqlite3_column_type(statement, 0); + if(column_type == SQLITE_NULL) { + sqlite3_finalize(statement); + return nullptr; + } + if(column_type != SQLITE_BLOB) { // NULL is the only other acceptable return type + neroshop::print("sqlite3_column_type: invalid column return type\ncommand: " + command, 1); + sqlite3_finalize(statement); + return nullptr; + } + void * blob = const_cast(sqlite3_column_blob(statement, 0));//reinterpret_cast(sqlite3_column_text16(stmt, 0)); // utf-16 + sqlite3_finalize(statement); + return blob; +} +//////////////////// +void * neroshop::db::Sqlite3::get_blob_params(const std::string& command, const std::vector& args) { + if(!handle) throw std::runtime_error("database is not connected"); + sqlite3_stmt * statement = nullptr; + int result = sqlite3_prepare_v2(handle, command.c_str(), -1, &statement, nullptr); + if(result != SQLITE_OK) { + neroshop::print("sqlite3_prepare_v2: " + std::string(sqlite3_errmsg(handle)), 1); + return nullptr; + } + // Bind user-defined parameter arguments + for(int i = 0; i < args.size()/*sqlite3_bind_parameter_count(statement)*/; i++) { + result = sqlite3_bind_text(statement, i + 1, args[i].c_str(), args[i].length(), SQLITE_STATIC); + if(result != SQLITE_OK) { + neroshop::print("sqlite3_bind_*: " + std::string(sqlite3_errmsg(handle)), 1); + sqlite3_finalize(statement); + return nullptr; + } + } + sqlite3_step(statement); // Don't check for error or it'll keep saying: "another row available" or "no more rows available" + int column_type = sqlite3_column_type(statement, 0); + if(column_type == SQLITE_NULL) { + sqlite3_finalize(statement); + return nullptr; + } + if(column_type != SQLITE_BLOB) { + neroshop::print("sqlite3_column_type: invalid column return type\ncommand: " + command, 1); + sqlite3_finalize(statement); + return nullptr; + } + void * blob = const_cast(sqlite3_column_blob(statement, 0));//reinterpret_cast(sqlite3_column_text16(stmt, 0)); // utf-16 + sqlite3_finalize(statement); + return blob; +} +//////////////////// +std::string neroshop::db::Sqlite3::get_text(const std::string& command) {//const { + if(!handle) throw std::runtime_error("database is not connected"); + sqlite3_stmt * stmt = nullptr; + int result = sqlite3_prepare_v2(handle, command.c_str(), -1, &stmt, nullptr); + if(result != SQLITE_OK) { + neroshop::print("sqlite3_prepare_v2: " + std::string(sqlite3_errmsg(handle)), 1); + return ""; + } + result = sqlite3_step(stmt); + if (result != SQLITE_ROW) { + neroshop::print("sqlite3_step: " + std::string(sqlite3_errmsg(handle)), 1); + sqlite3_finalize(stmt); + return ""; + } + int column_type = sqlite3_column_type(stmt, 0); + if(column_type == SQLITE_NULL) { + sqlite3_finalize(stmt); + return ""; + } + if(column_type != SQLITE_TEXT) { + neroshop::print("sqlite3_column_type: invalid column return type\ncommand: " + command, 1); + sqlite3_finalize(stmt); + return ""; + } + std::string text = reinterpret_cast(sqlite3_column_text(stmt, 0));//reinterpret_cast(sqlite3_column_text16(stmt, 0)); // utf-16 + sqlite3_finalize(stmt); + return text; +} +//////////////////// +std::string neroshop::db::Sqlite3::get_text_params(const std::string& command, const std::vector& args) {//const { + if(!handle) throw std::runtime_error("database is not connected"); + // Prepare statement + sqlite3_stmt * statement = nullptr; + int result = sqlite3_prepare_v2(handle, command.c_str(), -1, &statement, nullptr); + if(result != SQLITE_OK) { + neroshop::print("sqlite3_prepare_v2: " + std::string(sqlite3_errmsg(handle)), 1); + return ""; + } + // Bind user-defined parameter arguments + for(int i = 0; i < args.size()/*sqlite3_bind_parameter_count(statement)*/; i++) { + result = sqlite3_bind_text(statement, i + 1, args[i].c_str(), args[i].length(), SQLITE_STATIC); + if(result != SQLITE_OK) { + neroshop::print("sqlite3_bind_*: " + std::string(sqlite3_errmsg(handle)), 1); + sqlite3_finalize(statement); + return ""; + } + } + // Evaluate statement + sqlite3_step(statement); // Don't check for error or it'll keep saying: "another row available" or "no more rows available" + // Check the type of the statement's return value + int column_type = sqlite3_column_type(statement, 0); + if(column_type == SQLITE_NULL) { + sqlite3_finalize(statement); + return ""; + } + if(column_type != SQLITE_TEXT) { + neroshop::print("sqlite3_column_type: invalid column return type\ncommand: " + command, 1); + sqlite3_finalize(statement); + return ""; + } + // Finalize (destroy) the prepared statement + std::string text = reinterpret_cast(sqlite3_column_text(statement, 0));//reinterpret_cast(sqlite3_column_text16(stmt, 0)); // utf-16 + sqlite3_finalize(statement); + return text; +} +//////////////////// +int neroshop::db::Sqlite3::get_integer(const std::string& command) { + if(!handle) throw std::runtime_error("database is not connected"); + sqlite3_stmt * statement = nullptr; + int result = sqlite3_prepare_v2(handle, command.c_str(), -1, &statement, nullptr); + if(result != SQLITE_OK) { + neroshop::print("sqlite3_prepare_v2: " + std::string(sqlite3_errmsg(handle)), 1); + return 0; + } + result = sqlite3_step(statement); + if (result != SQLITE_ROW) { + neroshop::print("sqlite3_step: " + std::string(sqlite3_errmsg(handle)), 1); + sqlite3_finalize(statement); + return 0; + } + int column_type = sqlite3_column_type(statement, 0); + if(column_type == SQLITE_NULL) { + sqlite3_finalize(statement); + return 0; + } + if(column_type != SQLITE_INTEGER) { + neroshop::print("sqlite3_column_type: invalid column return type\ncommand: " + command, 1); + sqlite3_finalize(statement); + return 0; + } + int number = sqlite3_column_int64(statement, 0);//sqlite3_column_int(statement, 0); + sqlite3_finalize(statement); + return number; +} +//////////////////// +int neroshop::db::Sqlite3::get_integer_params(const std::string& command, const std::vector& args) { + if(!handle) throw std::runtime_error("database is not connected"); + // Prepare statement + sqlite3_stmt * statement = nullptr; + int result = sqlite3_prepare_v2(handle, command.c_str(), -1, &statement, nullptr); + if(result != SQLITE_OK) { + neroshop::print("sqlite3_prepare_v2: " + std::string(sqlite3_errmsg(handle)), 1); + return 0; + } + // Bind user-defined parameter arguments + for(int i = 0; i < args.size()/*sqlite3_bind_parameter_count(statement)*/; i++) { + result = sqlite3_bind_text(statement, i + 1, args[i].c_str(), args[i].length(), SQLITE_STATIC); + if(result != SQLITE_OK) { + neroshop::print("sqlite3_bind_*: " + std::string(sqlite3_errmsg(handle)), 1); + sqlite3_finalize(statement); + return 0; + } + } + // Evaluate statement + sqlite3_step(statement); // Don't check for error or it'll keep saying: "another row available" or "no more rows available" + // Check the type of the statement's return value + int column_type = sqlite3_column_type(statement, 0); + if(column_type == SQLITE_NULL) { + sqlite3_finalize(statement); + return 0; + } + if(column_type != SQLITE_INTEGER) { + neroshop::print("sqlite3_column_type: invalid column return type\ncommand: " + command, 1); + sqlite3_finalize(statement); + return 0; + } + // Finalize (destroy) the prepared statement + int number = sqlite3_column_int64(statement, 0);//sqlite3_column_int(statement, 0); + sqlite3_finalize(statement); + return number; +} +//////////////////// +double neroshop::db::Sqlite3::get_real(const std::string& command) { + if(!handle) throw std::runtime_error("database is not connected"); + sqlite3_stmt * stmt = nullptr; + int result = sqlite3_prepare_v2(handle, command.c_str(), -1, &stmt, nullptr); + if(result != SQLITE_OK) { + neroshop::print("sqlite3_prepare_v2: " + std::string(sqlite3_errmsg(handle)), 1); + return 0.0; + } + result = sqlite3_step(stmt); + if (result != SQLITE_ROW) { + neroshop::print("sqlite3_step: " + std::string(sqlite3_errmsg(handle)), 1); + sqlite3_finalize(stmt); + return 0.0; + } + int column_type = sqlite3_column_type(stmt, 0); + if(column_type == SQLITE_NULL) { + sqlite3_finalize(stmt); + return 0.0; + } + if(column_type != SQLITE_FLOAT) { + neroshop::print("sqlite3_column_type: invalid column return type\ncommand: " + command, 1); + sqlite3_finalize(stmt); + return 0.0; + } + double number = sqlite3_column_double(stmt, 0); + sqlite3_finalize(stmt); + return number; +} +//////////////////// +double neroshop::db::Sqlite3::get_real_params(const std::string& command, const std::vector& args) { + if(!handle) throw std::runtime_error("database is not connected"); + // Prepare statement + sqlite3_stmt * statement = nullptr; + int result = sqlite3_prepare_v2(handle, command.c_str(), -1, &statement, nullptr); + if(result != SQLITE_OK) { + neroshop::print("sqlite3_prepare_v2: " + std::string(sqlite3_errmsg(handle)), 1); + return 0.0; + } + // Bind user-defined parameter arguments + for(int i = 0; i < args.size()/*sqlite3_bind_parameter_count(statement)*/; i++) { + result = sqlite3_bind_text(statement, i + 1, args[i].c_str(), args[i].length(), SQLITE_STATIC); + if(result != SQLITE_OK) { + neroshop::print("sqlite3_bind_*: " + std::string(sqlite3_errmsg(handle)), 1); + sqlite3_finalize(statement); + return 0.0; + } + } + // Evaluate statement + sqlite3_step(statement); // Don't check for error or it'll keep saying: "another row available" or "no more rows available" + // Check the type of the statement's return value + int column_type = sqlite3_column_type(statement, 0); + if(column_type == SQLITE_NULL) { + sqlite3_finalize(statement); + return 0.0; + } + if(column_type != SQLITE_FLOAT) { + neroshop::print("sqlite3_column_type: invalid column return type\ncommand: " + command, 1); + sqlite3_finalize(statement); + return 0.0; + } + // Finalize (destroy) the prepared statement + double number = sqlite3_column_double(statement, 0); + sqlite3_finalize(statement); + return number; +} +//////////////////// +std::vector neroshop::db::Sqlite3::get_rows(const std::string& command) { + if(!handle) throw std::runtime_error("database is not connected"); + sqlite3_stmt * stmt = nullptr; + std::vector row_values = {}; + // Prepare (compile) statement + if(sqlite3_prepare_v2(handle, command.c_str(), -1, &stmt, nullptr) != SQLITE_OK) { + neroshop::print("sqlite3_prepare_v2: " + std::string(sqlite3_errmsg(handle)), 1); + return {}; + } + // Check whether the prepared statement returns no data (for example an UPDATE) + // "SELECT name FROM users ORDER BY id;" = 1 column + // "SELECT name, age FROM users ORDER BY id;" = 2 columns + // "SELECT * FROM users ORDER BY id;" = all column(s) including the id + if(sqlite3_column_count(stmt) == 0) { + neroshop::print("No data found. Be sure to use an appropriate SELECT statement", 1); + return {}; + } + // Get all table values row by row instead of column by column + int result = 0; + while((result = sqlite3_step(stmt)) == SQLITE_ROW) { + for(int i = 0; i < sqlite3_column_count(stmt); i++) { + std::string column_value = (sqlite3_column_text(stmt, i) == nullptr) ? "" : reinterpret_cast(sqlite3_column_text(stmt, i));////if(sqlite3_column_text(stmt, i) == nullptr) {throw std::runtime_error("column is NULL");} + row_values.push_back(column_value); //std::cout << sqlite3_column_text(stmt, i) << std::endl;//std::cout << sqlite3_column_name(stmt, i) << std::endl; + // To get a specific column use: if(i == 0) {} or any number + // or call sqlite3_column_name for each column + } + } + + if(result != SQLITE_DONE) { + neroshop::print("sqlite3_step: " + std::string(sqlite3_errmsg(handle)), 1); + } + + sqlite3_finalize(stmt); + + return row_values; +} +//////////////////// +//////////////////// +//////////////////// +//////////////////// +bool neroshop::db::Sqlite3::is_open() const { + return (opened == true); +} +//////////////////// +bool neroshop::db::Sqlite3::table_exists(const std::string& table_name) { + std::string command = "SELECT count(*) FROM sqlite_master WHERE type = 'table' AND name = $1;"; + return get_integer_params(command, { table_name }); +} +//////////////////// +/*bool neroshop::db::Sqlite3::rowid_exists(const std::string& table_name, int rowid) { + int rowid = database->get_integer_params("SELECT id FROM $1 WHERE id = $2", { table_name, rowid }); + return (rowid != 0); +}*/ +//////////////////// +//////////////////// +//////////////////// +int neroshop::db::Sqlite3::callback(void *not_used, int argc, char **argv, char **az_col_name) +{ + int i; + for(i = 0; i < argc; i++) { + std::cout << SQLITE3_TAG << az_col_name[i] << " = " << (argv[i] ? argv[i] : "NULL") << std::endl; // printf("%s = %s\n", azcolname[i], argv[i] ? argv[i] : "nullptr"); + } + std::cout << std::endl; + return 0; +} +//////////////////// +//////////////////// +//////////////////// +//////////////////// +//////////////////// +//////////////////// +//////////////////// +//////////////////// diff --git a/src/core/database/sqlite.hpp b/src/core/database/sqlite.hpp index c006b64..c621286 100644 --- a/src/core/database/sqlite.hpp +++ b/src/core/database/sqlite.hpp @@ -18,7 +18,9 @@ #include "../debug.hpp" namespace neroshop { + namespace db { + class Sqlite3 { public: Sqlite3(); @@ -50,10 +52,8 @@ private: bool opened; static int callback(void *not_used, int argc, char **argv, char **az_col_name); }; + } + } -/* - // Would it be more appropriate if the server handles the database queries instead of the client? - // The client would send the server a request to execute a SQLite query. This sounds good. -*/ #endif diff --git a/src/core/debug.hpp b/src/core/debug.hpp index 820836e..697bf5e 100644 --- a/src/core/debug.hpp +++ b/src/core/debug.hpp @@ -11,14 +11,42 @@ #define NEROSHOP_LOG_FILE "log.txt" #include +#include +#include +#include +#include // std::put_time namespace neroshop { - inline void print(const std::string& text, int code = 0) { // 0=normal, 1=error, 2=warning, 3=success, - if(code == 0) std::cout << "\033[1;35;49m" << "[neroshop]: " << "\033[1;37;49m" << text << "\033[0m" << std::endl; - if(code == 1) std::cout << "\033[1;35;49m" << "[neroshop]: " << "\033[1;91;49m" << text << "\033[0m" << std::endl; - if(code == 2) std::cout << "\033[1;35;49m" << "[neroshop]: " << "\033[1;33;49m" << text << "\033[0m" << std::endl; - if(code == 3) std::cout << "\033[1;35;49m" << "[neroshop]: " << "\033[1;32;49m" << text << "\033[0m" << std::endl; - if(code == 4) std::cout << "\033[1;35;49m" << "[neroshop]: " << "\033[1;34;49m" << text << "\033[0m" << std::endl; + enum log_priority { + trace, error, warn, info + }; + static void logger(log_priority priority, const std::string& message) { + std::ofstream file(std::string(NEROSHOP_LOG_FILE).c_str(), std::ios_base::app); + + auto now = std::chrono::system_clock::now(); + auto in_time_t = std::chrono::system_clock::to_time_t(now); // current time + std::stringstream ss; + ss << std::put_time(std::localtime(&in_time_t), std::string("[%Y-%m-%d %H:%M:%S %p]").c_str()); + + switch (priority) { + case trace: file << ss.str() << "[Trace]: "; break; + //case debug: file << ss.str() << "[Debug:] "; break; + case info: file << ss.str() << "[Info:] "; break; + case warn: file << ss.str() << "[Warn]: "; break; + case error: file << ss.str() << "[Error]: "; break; + //case critical: file << ss.str() << "[Critical]: "; break; + } + file << message << "\n"; + file.close(); + } + inline void print(const std::string& text, int code = 0, bool log_msg = true) { // 0=normal, 1=error, 2=warning, 3=success, + log_priority verbosity; + if(code == 0) { std::cout << "\033[1;35;49m" << "[neroshop]: " << "\033[1;37;49m" << text << "\033[0m\n"; verbosity = log_priority::trace; } + if(code == 1) { std::cout << "\033[1;35;49m" << "[neroshop]: " << "\033[1;91;49m" << text << "\033[0m\n"; verbosity = log_priority::error; } + if(code == 2) { std::cout << "\033[1;35;49m" << "[neroshop]: " << "\033[1;33;49m" << text << "\033[0m\n"; verbosity = log_priority::warn; } + if(code == 3) { std::cout << "\033[1;35;49m" << "[neroshop]: " << "\033[1;32;49m" << text << "\033[0m\n"; verbosity = log_priority::info; } + if(code == 4) { std::cout << "\033[1;35;49m" << "[neroshop]: " << "\033[1;34;49m" << text << "\033[0m\n"; verbosity = log_priority::info; } + if(log_msg) logger(verbosity, text); } inline void io_write(const std::string& text) {// like print but without a newline std::cout << "\033[1;35;49m" << "[neroshop]: " << "\033[1;37;49m" << text << "\033[0m"; diff --git a/src/core/item.cpp b/src/core/item.cpp index 5a05fd3..0932005 100644 --- a/src/core/item.cpp +++ b/src/core/item.cpp @@ -32,7 +32,7 @@ neroshop::Item::~Item() { } //////////////////// void neroshop::Item::register_item(const std::string& name, const std::string& description, double price, double weight, double length, double width, double height, const std::string& condition, const std::string& product_code) { - db::Sqlite3 * database = db::Sqlite3::get_database(); + db::Sqlite3 * database = neroshop::get_database(); // if item is already registered, then exit function if(!database->table_exists("products")) { neroshop::print("register_item: table \"products\" is missing", 1); diff --git a/src/core/price_api/coinmarketcap.cpp b/src/core/price_api/coinmarketcap.cpp index ba91acc..d209006 100644 --- a/src/core/price_api/coinmarketcap.cpp +++ b/src/core/price_api/coinmarketcap.cpp @@ -35,6 +35,8 @@ const std::map CURRENCY_TO_ID{ {neroshop::Currency::PHP, "2803"}, {neroshop::Currency::INR, "2796"}, //{neroshop::Currency::}, + //{neroshop::Currency::XAG, ""}, + //{neroshop::Currency::XAU, ""}, {neroshop::Currency::BTC, "1"}, {neroshop::Currency::ETH, "1027"}, {neroshop::Currency::LTC, "2"}, diff --git a/src/core/price_api/cointelegraph.cpp b/src/core/price_api/cointelegraph.cpp index 5b8bddf..cb026b3 100644 --- a/src/core/price_api/cointelegraph.cpp +++ b/src/core/price_api/cointelegraph.cpp @@ -28,6 +28,9 @@ const std::map CURRENCY_TO_ID{ {neroshop::Currency::MXN, "MXN"}, {neroshop::Currency::NZD, "NZD"}, {neroshop::Currency::SEK, "SEK"}, + + {neroshop::Currency::XAG, "XAG"}, + {neroshop::Currency::XAU, "XAU"}, }; const std::map CRYPTO_TO_ID{ diff --git a/src/core/rpc.cpp b/src/core/rpc.cpp new file mode 100644 index 0000000..52349af --- /dev/null +++ b/src/core/rpc.cpp @@ -0,0 +1,140 @@ +#include "rpc.hpp" + +std::string neroshop::rpc::translate(const std::string& sql) { + std::string request = ""; + #if defined(NEROSHOP_USE_QT) + QJsonObject request_object; // JSON-RPC Request object + + request_object.insert(QString("jsonrpc"), QJsonValue("2.0")); + request_object.insert(QString("method"), QJsonValue("query"));////QJsonValue(QString::fromStdString(method_type))); + QJsonObject params_object;////QJsonArray params_array; // can be an array or object + params_object.insert(QString("sql"), QJsonValue(QString::fromStdString(sql)));////params_array.insert(params_array.size(), QJsonValue("arg1")); // based on the number of parameter args + request_object.insert(QString("params"), QJsonValue(params_object)); + // Generate random number for id (id can be either a string or an integer or null which is not recommended) + std::random_device rd; // obtain a random number from hardware + std::mt19937 gen(rd()); // seed the generator + std::uniform_int_distribution<> distr(1, 9); // define the range + std::string random_id; + for(int n = 0; n < 10; ++n) { + int random_integer = distr(gen); // generate numbers + random_id = random_id + std::to_string(random_integer); // append 10 different numbers to generate a unique id + } + request_object.insert(QString("id"), QJsonValue(QString::fromStdString(random_id))); // https://stackoverflow.com/questions/2210791/json-rpc-what-is-the-id-for + // Convert JSON to string then display it (for debugging purposes) + QJsonDocument json_doc(request_object); + QString json_str = json_doc.toJson();////(QJsonDocument::Compact); // https://doc.qt.io/qt-6/qjsondocument.html#JsonFormat-enum + request = json_str.toStdString(); + #else + nlohmann::json json; + json["jsonrpc"] = "2.0"; + json["method"] = "query";////get_method(sql); + json["params"]["sql"] = sql; + // Generate random number for id (id can be either a string or an integer or null which is not recommended) + std::random_device rd; // obtain a random number from hardware + std::mt19937 gen(rd()); // seed the generator + std::uniform_int_distribution<> distr(1, 9); // define the range + std::string random_id; + for(int n = 0; n < 10; ++n) { + int random_integer = distr(gen); // generate numbers + random_id = random_id + std::to_string(random_integer); // append 10 different numbers to generate a unique id + } + json["id"] = random_id; + // Dump JSON to string + request = json.dump(4);////json.dump(); + #endif + #ifdef NEROSHOP_DEBUG + std::cout << "\"" << sql << "\" has been translated to: \n" << request << std::endl; + #endif + return request; +} + +std::string neroshop::rpc::translate(const std::string& sql, const std::vector& args) { + std::string request = ""; + #if defined(NEROSHOP_USE_QT) + QJsonObject request_object; // JSON-RPC Request object + + request_object.insert(QString("jsonrpc"), QJsonValue("2.0")); + request_object.insert(QString("method"), QJsonValue("query")); + QJsonObject params_object; + params_object.insert(QString("sql"), QJsonValue(QString::fromStdString(sql))); // eg. "SELECT * FROM products WHERE condition = $1;" + params_object.insert(QString("count"), static_cast(args.size())); + for(int index = 0; index < args.size(); index++) { + std::string arg_key = /*"" + */std::to_string(index + 1); + std::string arg_value = args[index]; + params_object.insert(QString::fromStdString(arg_key), QJsonValue(QString::fromStdString(arg_value))); + std::cout << "" << (index + 1) << "= " << arg_value << "\n"; + } + request_object.insert(QString("params"), QJsonValue(params_object)); + // Generate random number for id (id can be either a string or an integer or null which is not recommended) + std::random_device rd; + std::mt19937 gen(rd()); + std::uniform_int_distribution<> distr(1, 9); + std::string random_id; + for(int n = 0; n < 10; ++n) { + int random_integer = distr(gen); + random_id = random_id + std::to_string(random_integer); + } + request_object.insert(QString("id"), QJsonValue(QString::fromStdString(random_id))); + // Convert JSON to string + QJsonDocument json_doc(request_object); + QString json_str = json_doc.toJson(); + request = json_str.toStdString(); + #else + nlohmann::json json; + json["jsonrpc"] = "2.0"; + json["method"] = "query";////get_method(sql); + json["params"]["sql"] = sql; + json["params"]["count"] = args.size(); + for(int index = 0; index < args.size(); index++) { + std::string arg_key = std::to_string(index + 1); + std::string arg_value = args[index]; + json["params"][arg_key] = arg_value; + } + // Generate random number for id (id can be either a string or an integer or null which is not recommended) + std::random_device rd; + std::mt19937 gen(rd()); + std::uniform_int_distribution<> distr(1, 9); + std::string random_id; + for(int n = 0; n < 10; ++n) { + int random_integer = distr(gen); + random_id = random_id + std::to_string(random_integer); + } + json["id"] = random_id; + // Dump JSON to string + request = json.dump(4);////json.dump(); + #endif + #ifdef NEROSHOP_DEBUG + std::cout << "\"" << sql << "\" has been translated to: \n" << request << std::endl; + #endif + return request; +} + +void neroshop::rpc::request(const std::string& json) { // TODO: this function should return a json_rpc response from the server + // Get the json which is basically a translated sqlite query or a translate method string + args + // Passing the data onto the server + ////zmq_send (requester, request.c_str(), request.size(), 0); + // The server will then execute the data + // Lastly, the server will return any results +} // Usage: neroshop::rpc::request(neroshop::rpc::translate("SELECT * FROM users;")); + +void request_batch(const std::vector& json_batch) { +} + +void neroshop::rpc::respond(const std::string& json) { + std::string response = ""; + #if defined(NEROSHOP_USE_QT) + #endif +} + +void neroshop::rpc::respond_batch(const std::vector& json_batch) { +} + +std::string neroshop::rpc::get_method(const std::string& sql) { + std::string first_word = sql.substr(0, sql.find_first_of(" ")); + if(is_valid_method(first_word)) return first_word; + return ""; +} + +bool neroshop::rpc::is_valid_method(const std::string& method) { + return ((std::find(sql_methods.begin(), sql_methods.end(), method) != sql_methods.end()) || (std::find(methods.begin(), methods.end(), method) != methods.end())); +} diff --git a/src/core/rpc.hpp b/src/core/rpc.hpp new file mode 100644 index 0000000..d68a977 --- /dev/null +++ b/src/core/rpc.hpp @@ -0,0 +1,48 @@ +#pragma once + +#if defined(NEROSHOP_USE_QT) +#include +#include +#include +#include +#else +#include +#endif + +#include +#include +// uncomment to disable assert() +// #define NDEBUG +////#include +// neroshop JSON-RPC API // TODO: translate method names like get_product_ratings (along with the args) into json request strings via command line +namespace neroshop { + +namespace rpc { + static std::vector methods = {};//static std::unordered_map> methods_cmd + static std::vector sql_methods = { // TODO: rename to query_type? + "SELECT", // extracts data from a database + "UPDATE", // updates data in a database + "DELETE", // deletes data from a database + "INSERT", // "INSERT INTO" - inserts new data into a database + "CREATE", // "CREATE DATABASE" - creates a new database // "CREATE TABLE" - creates a new table // "CREATE INDEX" - creates an index (search key) + "ALTER", // "ALTER DATABASE" - modifies a database // "ALTER TABLE" - modifies a table + "DROP", // "DROP TABLE" - deletes a table // "DROP INDEX" - deletes an index + }; + static std::string get_method(const std::string& sql); + static bool is_valid_method(const std::string& method); + static std::string translate(const std::string& sql); // converts an sqlite query to a json_rpc request message + static std::string translate(const std::string& sql, const std::vector& args); + template + static std::string translate_cmd(const std::string& method, Args&&... args) { // converts a method (string) and its argument(s) into a json_rpc response message via the command line + // accessing the number of args: static const size_t arg_count = sizeof...(Args); + //if(method == "get_whatever") { ... + // accessing the arguments: functions[name](std::forward(args)...); + return ""; // TEMPORARY + } + static void request(const std::string& json); // for client - to send requests + static void request_batch(const std::vector& json_batch); // sends a batch of json_rpc requests to the server and expects a batch of json_rpc responses + static void respond(const std::string& json); // for server - to respond to requests + static void respond_batch(const std::vector& json_batch); +} + +} diff --git a/src/core/seller.cpp b/src/core/seller.cpp index d411884..9532dff 100644 --- a/src/core/seller.cpp +++ b/src/core/seller.cpp @@ -26,7 +26,7 @@ neroshop::Seller::~Seller() { //////////////////// void neroshop::Seller::list_item(const std::string& product_id, unsigned int quantity, double price, const std::string& currency, const std::string& condition, const std::string& location) { - neroshop::db::Sqlite3 * database = neroshop::db::Sqlite3::get_database(); + neroshop::db::Sqlite3 * database = neroshop::get_database(); if(!database) throw std::runtime_error("database is NULL"); std::string listing_uuid = database->get_text_params("INSERT INTO listings (uuid, product_id, seller_id, quantity, price, currency, condition, location, date) " // date should always be last @@ -342,7 +342,7 @@ unsigned int neroshop::Seller::get_total_ratings() const { } //////////////////// unsigned int neroshop::Seller::get_reputation() const { - neroshop::db::Sqlite3 * database = neroshop::db::Sqlite3::get_database(); + neroshop::db::Sqlite3 * database = neroshop::get_database(); if(!database) throw std::runtime_error("database is NULL"); // Get seller reputation as percentage unsigned int ratings_count = database->get_integer_params("SELECT COUNT(*) FROM seller_ratings WHERE seller_id = $1", { get_id() }); @@ -467,7 +467,7 @@ std::vector neroshop::Seller::get_pending_customer_orders() { // getters - sales and statistics-related stuff //////////////////// unsigned int neroshop::Seller::get_products_count() const { - neroshop::db::Sqlite3 * database = neroshop::db::Sqlite3::get_database(); + neroshop::db::Sqlite3 * database = neroshop::get_database(); if(!database) throw std::runtime_error("database is NULL"); int products_listed = database->get_integer_params("SELECT COUNT(product_id) FROM listings WHERE seller_id = $1;", { get_id() }); @@ -591,7 +591,7 @@ bool neroshop::Seller::has_wallet_synced() const { // callbacks //////////////////// neroshop::User * neroshop::Seller::on_login(const neroshop::Wallet& wallet) { // assumes user data already exists in database - /*neroshop::db::Sqlite3 * database = neroshop::db::Sqlite3::get_database(); + /*neroshop::db::Sqlite3 * database = neroshop::get_database(); if(!database) throw std::runtime_error("database is NULL");*/ //std::string user_id = database->get_text_params("SELECT monero_address FROM users WHERE name = $1", { username }); std::string monero_address = wallet.get_monero_wallet()->get_primary_address(); diff --git a/src/core/server.cpp b/src/core/server.cpp index 0173bc3..5d2a3da 100644 --- a/src/core/server.cpp +++ b/src/core/server.cpp @@ -13,7 +13,7 @@ neroshop::Server::Server() neroshop::print("uv_tcp_init error: " + std::string(uv_strerror(result)), 1); } #endif - #if defined(__gnu_linux__) + #if defined(__gnu_linux__) && defined(NEROSHOP_USE_SYSTEM_SOCKETS) socket = ::socket(AF_INET, SOCK_STREAM, 0); if (socket < 0) { std::cerr << "Could not create socket" << std::endl; @@ -25,22 +25,6 @@ neroshop::Server::Server() std::cerr << "Could not set socket options" << std::endl; } #endif - // Create a new raft server - /*int server_count = 1; - void * connection_user_data = nullptr; // pointer to userdata - // Generate random number to be used for node_id - std::random_device rd; - std::mt19937 mt(rd()); - std::uniform_real_distribution dist(0, 100); - int node_id = static_cast(dist(mt)); // SHOULD be random - bool peer_is_self = true; - - raft = raft_new(); - - for(int i = 0; i < server_count; ++i) { - raft_add_node(static_cast(raft), connection_user_data, node_id, peer_is_self); - std::cout << "raft node has been added\n"; - }*/ } //////////////////// neroshop::Server::~Server() { @@ -66,7 +50,7 @@ bool neroshop::Server::bind(unsigned int port) neroshop::print("uv_tcp_bind error: " + std::string(uv_strerror(result)), 1); } #endif - #if defined(__gnu_linux__) + #if defined(__gnu_linux__) && defined(NEROSHOP_USE_SYSTEM_SOCKETS) struct sockaddr_in server_addr; memset(&server_addr, 0, sizeof(struct sockaddr_in));//bzero((char *) &server_addr, sizeof(server_addr)); server_addr.sin_port = htons(port); @@ -91,7 +75,7 @@ bool neroshop::Server::listen() return false; } #endif - #if defined(__gnu_linux__) + #if defined(__gnu_linux__) && defined(NEROSHOP_USE_SYSTEM_SOCKETS) if(::listen(socket, SOMAXCONN) < 0) {//SOMAXCONN=4096 // number of requests to listen to at a time std::cerr << "Cannot listen for a connection" << std::endl; shutdown(); @@ -103,7 +87,7 @@ bool neroshop::Server::listen() } //////////////////// bool neroshop::Server::accept() { - #if defined(__gnu_linux__) + #if defined(__gnu_linux__) && defined(NEROSHOP_USE_SYSTEM_SOCKETS) struct sockaddr_in client_addr; socklen_t clilen = sizeof(client_addr); /*int */client_socket = ::accept(socket, (struct sockaddr *) &client_addr, &clilen); @@ -126,7 +110,7 @@ bool neroshop::Server::accept_all() } //////////////////// void neroshop::Server::write(const std::string& message) { - #if defined(__gnu_linux__) + #if defined(__gnu_linux__) && defined(NEROSHOP_USE_SYSTEM_SOCKETS) ssize_t write_result = ::write(client_socket, message.c_str(), message.length()/*buffer, strlen(buffer)*/); if(write_result < 0) { // -1 = error std::cout << "Server unable to write to client" << std::endl; @@ -136,7 +120,7 @@ void neroshop::Server::write(const std::string& message) { //////////////////// std::string neroshop::Server::read() // receive data { - #if defined(__gnu_linux__) + #if defined(__gnu_linux__) && defined(NEROSHOP_USE_SYSTEM_SOCKETS) memset(buffer, 0, 256); // clear buffer (fills buffer with 0's) before reading into buffer ssize_t read_result = ::read(client_socket, buffer, 255);//::read(client_socket, (void *)buffer_new.c_str(), buffer_new.length()); // https://stackoverflow.com/questions/10105591/is-it-possible-to-use-an-stdstring-for-read // #include if(read_result < 0) { // -1 = error @@ -159,75 +143,18 @@ std::string neroshop::Server::read() // receive data } //////////////////// void neroshop::Server::close() { - #if defined(__gnu_linux__) + #if defined(__gnu_linux__) && defined(NEROSHOP_USE_SYSTEM_SOCKETS) ::close(socket); #endif } //////////////////// void neroshop::Server::shutdown() { - #if defined(__gnu_linux__) + #if defined(__gnu_linux__) && defined(NEROSHOP_USE_SYSTEM_SOCKETS) ::shutdown(socket, SHUT_RDWR); // SHUT_RD, SHUT_WR, SHUT_RDWR #endif } //////////////////// //////////////////// -//////////////////// -#if defined(NEROSHOP_USE_LIBUV) -void neroshop::Server::on_new_connection(uv_stream_t *server, int status) { - if (status < 0) { - neroshop::print("New connection error: " + std::string(uv_strerror(status)), 1); - ////uv_close((uv_handle_t*) client, NULL); // close tcp connection - // error! - return; - } - - uv_tcp_t *client = (uv_tcp_t*) malloc(sizeof(uv_tcp_t)); - uv_tcp_init(uv_default_loop()/*loop*/, client); - if (uv_accept(server, (uv_stream_t*) client) == 0) { // success - uv_read_start((uv_stream_t*) client, alloc_buffer, echo_read);//std::cout << "Got a connection from a peer/client!\n";//neroshop::print(std::string("\033[0;37mReceived a connection from ") + "" + ":\033[0;36m" + ""); - } - else { - uv_close((uv_handle_t*) client, NULL); - } -} -//////////////////// -void neroshop::Server::alloc_buffer(uv_handle_t *handle, size_t suggested_size, uv_buf_t *buf) { - buf->base = (char *)malloc(suggested_size); - buf->len = suggested_size; - //memset(buf->base, 0, buf->len); -} -//////////////////// -void neroshop::Server::echo_read(uv_stream_t *client, ssize_t nread, const uv_buf_t *buf) { - if(nread < 0) {//== -1) { - if(nread != UV_EOF) neroshop::print("Read error: " + std::string(uv_err_name(nread)), 1); - uv_close((uv_handle_t*)client, NULL); - return; - } - - std::cout << "server_read_message_from_client: " << buf->base << std::endl; - - // Write what the server read from the client (works so far) - //if (nread > 0) { - uv_write_t *write_req = (uv_write_t*)malloc(sizeof(uv_write_t)); - write_req->data = (void*)buf->base; - const_cast(buf)->len = nread; - uv_write(write_req, client, buf, 1, echo_write); - //return; - //} - - ////free(buf->base); -} -//////////////////// -void neroshop::Server::echo_write(uv_write_t *req, int status) { - if(status == -1) { - neroshop::print("Write error: " + std::string(uv_strerror(status))); - } - char *buffer_base = (char*) req->data; - free(buffer_base); - free(req); -} -#endif -//////////////////// //template void neroshop::Server::bind(const std::string& name, F func) {//F response) { // if client requests a string e.g "LOGIN" then server will respond with login() //response(); diff --git a/src/core/server.hpp b/src/core/server.hpp index 89b271a..82681e2 100644 --- a/src/core/server.hpp +++ b/src/core/server.hpp @@ -1,26 +1,34 @@ #ifndef SERVER_HPP_NEROSHOP #define SERVER_HPP_NEROSHOP -//#if defined(NEROSHOP_USE_LIBUV) +#if defined(__gnu_linux__) && defined(NEROSHOP_USE_SYSTEM_SOCKETS) +#include +#include +#include +#include +#include +#endif + +#if defined(NEROSHOP_USE_LIBZMQ) +#include +#endif + +#if defined(NEROSHOP_USE_LIBUV) #include -//#endif +#endif + +#include extern "C" { #include } #include // std::unordered_map #include // std::function -//#if defined(__cplusplus) && (__cplusplus >= 201703L) #include // std::any (C++17) -//#endif #include // std::runtime_error #include // memset #include // std::random_device -#if defined(__gnu_linux__) -#include // ::close -#endif - #include "database.hpp" #include "debug.hpp" @@ -58,7 +66,7 @@ public: //if constexpr (std::is_same>::value) { // std::cout << "F is a valid function\n"; //} - function_list[name] = functor; + functions[name] = functor; } //! \brief Unbinds a functor binded to a name. @@ -67,30 +75,25 @@ public: //! //! \param name The name of the functor. void unbind(std::string const &name) { - function_list[name] = nullptr; // todo: remove element from unordered_map c++ the proper way + functions.erase(functions.find(name));//functions[name] = nullptr; // todo: remove element from unordered_map c++ the proper way } + + std::unordered_map get_functions() { + return functions; + } //private: - #if defined(NEROSHOP_USE_LIBUV) - uv_tcp_t * handle_tcp; - uv_udp_t * handle_udp; - // callbacks (libuv) - static void on_new_connection(uv_stream_t *server, int status); - static void alloc_buffer(uv_handle_t *handle, size_t suggested_size, uv_buf_t *buf); - static void echo_read(uv_stream_t *client, ssize_t nread, const uv_buf_t *buf); - static void echo_write(uv_write_t *req, int status); - #endif - #if defined(__gnu_linux__) + #if defined(__gnu_linux__) && defined(NEROSHOP_USE_SYSTEM_SOCKETS) int socket; char buffer[256]; int client_socket; #endif - void * raft; + raft_server_t* raft; // functors //#if defined(__cplusplus) && (__cplusplus < 201703L) - //std::unordered_map/*adaptor_type*/> function_list; + //std::unordered_map/*adaptor_type*/> functions; //#endif //#if defined(__cplusplus) && (__cplusplus >= 201703L) - std::unordered_map function_list; + std::unordered_map functions; //#endif // ?? /*template< class R, class... Args > diff --git a/src/core/user.cpp b/src/core/user.cpp index 436f74b..e8a0ce6 100644 --- a/src/core/user.cpp +++ b/src/core/user.cpp @@ -22,7 +22,7 @@ neroshop::User::~User() //////////////////// // buyers can only rate seller they have purchased from!! void neroshop::User::rate_seller(const std::string& seller_id, int score, const std::string& comments, const std::string& signature) { // perfected 99.9%!! - neroshop::db::Sqlite3 * database = neroshop::db::Sqlite3::get_database(); + neroshop::db::Sqlite3 * database = neroshop::get_database(); if(!database) throw std::runtime_error("database is NULL"); // seller_id cannot be 0 (0 = invalid id) if(seller_id.empty()) return; @@ -73,7 +73,7 @@ void neroshop::User::rate_seller(const std::string& seller_id, int score, const //////////////////// //////////////////// void neroshop::User::rate_item(const std::string& product_id, int stars, const std::string& comments, const std::string& signature) { // perfected 99%!!! - neroshop::db::Sqlite3 * database = neroshop::db::Sqlite3::get_database(); + neroshop::db::Sqlite3 * database = neroshop::get_database(); if(!database) throw std::runtime_error("database is NULL"); // If item is not registered if(product_id.empty()) return; // exit function @@ -169,7 +169,7 @@ void neroshop::User::convert() { //////////////////// void neroshop::User::delete_account() { if(!is_logged()) {neroshop::print("You are not logged in", 2);return;} // must be logged in to delete your account - neroshop::db::Sqlite3 * database = neroshop::db::Sqlite3::get_database(); + neroshop::db::Sqlite3 * database = neroshop::get_database(); if(!database) throw std::runtime_error("database is NULL"); ////database->execute("BEGIN;"); // not necessary unless doing multiple operations ////database->execute("SAVEPOINT before_account_deletion_savepoint;");//ROLLBACK TO before_account_deletion_savepoint; @@ -366,7 +366,7 @@ void neroshop::User::load_favorites() { // avatar-related stuff here //////////////////// void neroshop::User::upload_avatar(const std::string& filename) { - neroshop::db::Sqlite3 * database = neroshop::db::Sqlite3::get_database(); + neroshop::db::Sqlite3 * database = neroshop::get_database(); if(!database) throw std::runtime_error("database is NULL"); //---------------------------- //unsigned int user_id = 1; @@ -450,7 +450,7 @@ void neroshop::User::upload_avatar(const std::string& filename) { } //////////////////// bool neroshop::User::export_avatar() { - neroshop::db::Sqlite3 * database = neroshop::db::Sqlite3::get_database(); + neroshop::db::Sqlite3 * database = neroshop::get_database(); if(!database) throw std::runtime_error("database is NULL"); //---------------------------- // Check if avatar column exists first and that its not null @@ -811,7 +811,7 @@ bool neroshop::User::has_email() const { } //////////////////// bool neroshop::User::has_avatar() const { - neroshop::db::Sqlite3 * database = neroshop::db::Sqlite3::get_database(); + neroshop::db::Sqlite3 * database = neroshop::get_database(); if(!database) throw std::runtime_error("database is NULL"); // If id is zero (this means the user does not exist) if(this->id.empty()) return false; diff --git a/src/core/wallet.cpp b/src/core/wallet.cpp index 3521874..186e800 100644 --- a/src/core/wallet.cpp +++ b/src/core/wallet.cpp @@ -195,6 +195,7 @@ std::string neroshop::Wallet::upload(bool open, std::string password) { // opens void neroshop::Wallet::transfer(const std::string& address, double amount) { if(!monero_wallet_obj.get()) throw std::runtime_error("monero_wallet_full is not opened"); if(!monero_wallet_obj.get()->is_synced()) throw std::runtime_error("wallet is not synced with a daemon"); + std::packaged_task transfer_task([this, address, amount]() -> void { // Convert monero to piconero double piconero = 0.000000000001; uint64_t monero_to_piconero = amount / piconero; //std::cout << neroshop::string::precision(amount, 12) << " xmr to piconero: " << monero_to_piconero << "\n"; @@ -231,6 +232,12 @@ void neroshop::Wallet::transfer(const std::string& address, double amount) { //uint64_t deducted_amount = (monero_to_piconero + fee); std::string tx_hash = monero_wallet_obj->relay_tx(*sent_tx); // recipient receives notification within 5 seconds std::cout << "Tx hash: " << tx_hash << "\n"; + }); + + std::future future_result = transfer_task.get_future(); + // move the task (function) to a separate thread to prevent blocking of the main thread + std::thread worker(std::move(transfer_task)); + worker.detach(); // join may block but detach won't//void transfer_result = future_result.get(); } //////////////////// std::string neroshop::Wallet::sign_message(const std::string& message, monero_message_signature_type signature_type) const { diff --git a/src/daemon/main.cpp b/src/daemon/main.cpp index 40f9123..9427496 100644 --- a/src/daemon/main.cpp +++ b/src/daemon/main.cpp @@ -1,22 +1,13 @@ -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include #include // neroshop #include "../core/server.hpp" +#include "../core/database.hpp" #include "../core/debug.hpp" -//#include "db2.hpp" // daemon should handle database server requests from the client ?? + #define NEROMON_TAG "\033[1;95m[neromon]:\033[0m " +// Daemon will handle database server requests from the client + using namespace neroshop; Server * server; @@ -28,98 +19,6 @@ void close_server() { std::cout << NEROMON_TAG "\033[1;91mdisconnected\033[0m" << std::endl; } //////////////////// -#define DEFAULT_PORT 7000 -#define DEFAULT_BACKLOG 128 -//////////////////// -// Alloc callback (uv_alloc_cb) -// suggested_size で渡された領域を確保 -void alloc_buffer(uv_handle_t *handle, size_t suggested_size, uv_buf_t *buf) { - buf->base = (char *)malloc(suggested_size); - buf->len = suggested_size; - ////memset(buf->base, 0, buf->len); -} -//////////////////// -void on_client_read(uv_stream_t *stream, ssize_t nread, const uv_buf_t *buf); -void on_client_write(uv_write_t *req, int status); -//////////////////// -// Read callback (uv_read_cb) -void on_client_read(uv_stream_t *stream, ssize_t nread, const uv_buf_t *buf) { - if(nread < 0) {//== -1) { - neroshop::print("error on_client_read", 1); - uv_close((uv_handle_t*)stream, NULL); - return; - } - // クライアントから受け取ったデータ - // データは buf->base に格納されている。 - //if(nread > 0) { - std::cout << "server_read_message_from_client: " << buf->base << std::endl; // there is data available - // ファイルを開く (buf がファイルの場合) - ////int mode = 0; - ////uv_fs_open(uv_default_loop(), &open_req, filename, O_RDONLY, mode, on_file_open); - //} - // Write what the server read from the client (works) - uv_write_t *write_req = (uv_write_t*)malloc(sizeof(uv_write_t)); - write_req->data = (void*)buf->base; - const_cast(buf)->len = nread; - uv_write(write_req, stream, buf, 1, on_client_write); -} -//////////////////// -// Write callback (uv_write_cb) -void on_client_write(uv_write_t *req, int status) { // change to on_write_end - if(status == -1) { - neroshop::print("error on_client_write", 1); - return; - } - char *buffer_base = (char*) req->data; - free(buffer_base); - free(req); -} -//////////////////// -// Shutdown callback -void shutdown_cb(uv_shutdown_t *req, int status) { -} -//////////////////// -uv_loop_t * loop; -// Connection callback - received an incoming connection -void on_new_connection(uv_stream_t *server, int status) { - if (status < 0) { - neroshop::print("New connection error: " + std::string(uv_strerror(status)), 1); - ////uv_close((uv_handle_t*) client, NULL); // close tcp connection - // error! - return; - } - - // クライアントを保持するためのメモリを確保 - uv_tcp_t *client = (uv_tcp_t*) malloc(sizeof(uv_tcp_t)); - // loop への登録 - uv_tcp_init(loop, client); - - // accept the connection - if (uv_accept(server, (uv_stream_t*) client) == 0) { // success - //---------------------------------------- - /*std::string message = "Server: Welcome to the server";//NEROMON_TAG "\033[1;32mconnected\033[0m"; - // write to client - char buffer[100]; - uv_buf_t buf = uv_buf_init(buffer, sizeof(buffer)); - - buf.len = message.length(); - buf.base = const_cast(message.c_str()); - - uv_write_t write_req; - - // writes - int buf_count = 1; // number of times we write - // To write to the client - uv_write(&write_req, (uv_stream_t*)client, &buf, buf_count, on_client_write);*/ - //---------------------------------------- - // reading from client - uv_read_start((uv_stream_t*) client, alloc_buffer, on_client_read);//std::cout << "Got a connection from a peer/client!\n";//neroshop::print(std::string("\033[0;37mReceived a connection from ") + "" + ":\033[0;36m" + ""); // daemons cannot use std::cout so this is useless - } - else { - uv_close((uv_handle_t*) client, NULL); - } - // then continue reading for any client messages -} /////////////////// void do_heartbeat() { @@ -131,130 +30,33 @@ void do_heartbeat() //new_client.join(); server->write(NEROMON_TAG "\033[1;32mconnected\033[0m"); // write to client once } //else exit(0); - std::cout << server->read() << std::flush << std::endl; + std::cout << server->read() << std::endl; } // For security purposes, we don't allow any arguments to be passed into the daemon int main(void) { - - // Fork the current process. The parent process continues with a process ID - // greater than 0. A process ID lower than 0 indicates a failure in either - // process. - pid_t pid = fork(); - if (pid > 0) exit(EXIT_SUCCESS); else if (pid < 0) exit(EXIT_FAILURE); - - // TODO: I think we should log to ~/.config/neroshop/ - - // The parent process has now terminated, and the forked child process will - // continue (the pid of the child process was 0). Since the child process is - // a daemon, the umask needs to be set so files and logs can be written. - umask(0); - - // Open system logs for the child process - openlog("neromon", LOG_NOWAIT | LOG_PID, LOG_USER); - syslog(LOG_NOTICE, "Successfully started neromon"); - - // Generate a session ID for the child process and ensure it is valid. - if(setsid() < 0) { - // Log failure and exit - syslog(LOG_ERR, "Could not generate session ID for child process"); - // If a new session ID could not be generated, we must terminate the child - // process or it will be orphaned. - exit(EXIT_FAILURE); - } - - // Change the current working directory to a directory guaranteed to exist - if (chdir("/") < 0) { - // Log failure and exit - syslog(LOG_ERR, "Could not change working directory to /"); - // If our guaranteed directory does not exist, terminate the child process - // to ensure the daemon has not been hijacked. - exit(EXIT_FAILURE); - } -//--- - // A daemon cannot use the terminal, so close standard file descriptors for security reasons - close(STDIN_FILENO); - close(STDOUT_FILENO); - close(STDERR_FILENO); - // Daemon-specific intialization should go here - const int SLEEP_INTERVAL = 1; - ////////////////////////////////////////////////// // Start server std::atexit(close_server); server = new Server(); - int server_port = 1234;//(std::stoi(port)); + int server_port = 40441;//1234;//(std::stoi(port)); if(server->bind(server_port)) { - // Daemon cannot write to stdin, so we must use the Server::write function - //std::cout << std::endl << NEROMON_TAG "\033[1;97mbound to port " + std::to_string(server_port) << "\033[0m" << std::endl; - server->write(NEROMON_TAG "\033[1;97mbound to port " + std::to_string(server_port) + "\033[0m"); + server->write(NEROMON_TAG "\033[1;97mbound to port " + std::to_string(server_port) + "\033[0m\n"); } server->listen(); // listens for any incoming connection + const int SLEEP_INTERVAL = 1; // Enter daemon loop - while(true) { - // Execute daemon heartbeat - do_heartbeat(); - // Sleep for a period of time - sleep(SLEEP_INTERVAL); - } - ///////////////////////////////////////////////////////////////// - //loop = uv_default_loop(); - ///////////////////////////////////////////////////////////////// - /*uv_pipe_t server_pipe; - uv_pipe_init(loop, &server_pipe, 1); // 1 = ipc - - ////signal(SIGINT, remove_sock); - int resultp = 0; - if((resultp = uv_pipe_bind(&server_pipe, "echo.sock"))) { - fprintf(stderr, "Bind error %s\n", uv_err_name(resultp)); - return 1; - } - if((resultp = uv_listen((uv_stream_t*) &server_pipe, 128, on_new_connection))) { - fprintf(stderr, "Listen error %s\n", uv_err_name(resultp)); - return 2; - }*/ - - //uv_read_start((uv_stream_t*)&server_pipe, alloc_buffer, on_new_connection); - ///////////////////////////////////////////////////////////////// - // Start server (libuv) - /*uv_tcp_t server; - int result = uv_tcp_init(loop, &server); - if(result != 0) { - neroshop::print("uv_tcp_init error: " + std::string(uv_strerror(result)), 1); - } - - struct sockaddr_in bind_addr; - std::string ipv4_default = "0.0.0.0"; - std::string ipv6_default = "::/0"; // ::/0 is the IPv6 equivalent of 0.0.0.0/0 (IPv4) - std::string ipv4_localhost = "127.0.0.1"; - std::string ipv6_localhost = "::1"; // ::1/128 is the IPv6 equivalent of 127.0.0.1/8 (IPv4) - - int port = DEFAULT_PORT;//1234; - result = uv_ip4_addr(ipv4_localhost.c_str(), port, &bind_addr); - if(result != 0) { - neroshop::print("uv_ip4_addr error: " + std::string(uv_strerror(result)), 1); - } - // bind server to a port - result = uv_tcp_bind(&server, (const struct sockaddr *)&bind_addr, 0); - if(result != 0) { - neroshop::print("uv_tcp_bind error: " + std::string(uv_strerror(result)), 1); - } - std::cout << std::endl << NEROMON_TAG "\033[1;97mbound to port " + std::to_string(port) << "\033[0m" << std::endl; - // listen for any incoming connections - if((result = uv_listen((uv_stream_t*)&server, DEFAULT_BACKLOG, on_new_connection)) != 0) { - neroshop::print("uv_listen error: " + std::string(uv_strerror(result)), 1); - return 1; - }*/ - // Simplified (wrapped) version - /*Server * server = new Server(); // uv_tcp_init called here - server->bind(DEFAULT_PORT);//(1234); - server->listen();*/ - - - - //return uv_run(loop, UV_RUN_DEFAULT); - + while(true) { + // Execute daemon heartbeat + do_heartbeat(); + // Sleep for a period of time + #ifdef _WIN32 + Sleep(SLEEP_INTERVAL); + #else + sleep(SLEEP_INTERVAL); + #endif + } return 0; } diff --git a/src/gui/backend.cpp b/src/gui/backend.cpp index e672888..acdb4f3 100644 --- a/src/gui/backend.cpp +++ b/src/gui/backend.cpp @@ -3,6 +3,7 @@ #include #include #include +#include #include #include @@ -53,7 +54,7 @@ bool neroshop::Backend::isSupportedCurrency(const QString& currency) const { void neroshop::Backend::initializeDatabase() { std::cout << "sqlite3 v" << db::Sqlite3::get_sqlite_version() << std::endl; - db::Sqlite3 * database = db::Sqlite3::get_database(); + db::Sqlite3 * database = neroshop::get_database(); database->execute("BEGIN;"); //------------------------- // Todo: Make monero_address the primary key and remove id. Also, replace all foreign key references from id to monero_address @@ -99,7 +100,7 @@ void neroshop::Backend::initializeDatabase() { database->execute("ALTER TABLE listings ADD COLUMN condition TEXT;"); // item condition database->execute("ALTER TABLE listings ADD COLUMN location TEXT;"); //database->execute("ALTER TABLE listings ADD COLUMN last_updated ?datatype;"); - database->execute("ALTER TABLE listings ADD COLUMN date TEXT;"); // date when first listed // will use ISO8601 string format as follows: YYYY-MM-DD HH:MM:SS.SSS + database->execute("ALTER TABLE listings ADD COLUMN date TEXT DEFAULT CURRENT_TIMESTAMP;"); // date when first listed // will use ISO8601 string format as follows: YYYY-MM-DD HH:MM:SS.SSS //database->execute(""); // For most recent listings: "SELECT * FROM listings ORDER BY date DESC;" } @@ -198,31 +199,31 @@ void neroshop::Backend::initializeDatabase() { database->execute("ALTER TABLE subcategories ADD COLUMN description TEXT;"); // categories types int category_id = 0; - category_id = database->get_integer("INSERT INTO categories (name, description, thumbnail) VALUES ('Food & Beverages', 'Grocery', 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAAABmJLR0QA/wD/AP+gvaeTAAAAx0lEQVRIie2UMQ7CMBAE7yAlTaCFP/Al+EDgW7SIgm9AhygpIySoSDU0ETJW7JwRKYLYJvJlz+ONY4v8lSJgSbsWXg8AofHAY4wN67B4Xvo9wMTQY/EEAf3/RLkVAMwiv+ctBEjZg2vEs2+sApXhoD1qbx54XwLTUIKzIcGpfo5EpHTqdxHZiMhcVS+hBGtDgsLrebsaogIyYBeZfAtkHwMcyAo4OhMfgAIYNvjTAKnNbR5/k7+uzgGZX2iK69ZUVVMAnSfov56KfCz1jwSA8gAAAABJRU5ErkJggg==') RETURNING id;"); + category_id = database->get_integer("INSERT INTO categories (name, description, thumbnail) VALUES ('Food & Beverages', 'Grocery', 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAEAAAABACAYAAACqaXHeAAAACXBIWXMAAAsTAAALEwEAmpwYAAACZUlEQVR4nO2aPWgUQRSABy0iokRIdWoTBJtoIVqEgD+NINHCZiWFprCykYDa2MUmimBjCrWyCAouBCwEC7GwUUyTwlILrSL4V0RBRf3CoMKRm73d2503vovvg+lm3sz72Jmdt3fOGYZhGIZhdAEYR47limvIQoML+uaBrnmVeYIAJ5FlQLuAKWEBLe0CpoUFjGgXMCssYL92AXeEBRzXLuChsIDT2gU8FxZwQbuAV8ICZrQL+Cgs4KZaAcB64KewgFyzgCHkeaxZwE75/FnULGBUPn/eaBYwLp8/nzULOEUaBrQKmCINLa0CLpGGEa0CrpOGjooQGP6TuG/XCsZlgfYs0O+e1kqwsCIEJojHrNZKsLAiBA4Tj4t1BSyQho6KENgbMf5YXQEvScNMwRkQg7fAuroCPpCGW4G5t0SKfb5W8h5/SyMNt90qgM0R4voneIOrC/CJNHR8EwAGG8ZcBnbXTt4DvCYNV90qgG0N4r0HDrqmAPOk4URg7kM1Yz0BdjRO3gOcQ55fwHYXvgj9qBjjC3AfOOZiArSAb8ICHnWZ358DY8CVgrFHgF3AxqiJtwPMCQs46kropRiKDrBV8Mvwg4pr+HcCPMCkQPLvvFzXDwI8/kYVMXl/v9jnKqJCgAc4C3xtmLz/pWmP6wE1Ajz+dlXw0aGM78ANYJPrEVUC/gIcAO4CSyXv+BfA5dC7vq8FtAOcKRAw5CLQDwIyyQWaAOwJyGwLCG6xxtgZgB2C2Zo+BPn9P4G8S3saWmDJmLyfBGQI0HR+2azbMAHYE5D971tgtOxAq9Oazi+btWEYhlvbrADFelwIpcui8wAAAABJRU5ErkJggg==') RETURNING id;"); category_id = database->get_integer("INSERT INTO categories (name, description, thumbnail) VALUES ('Electronics', '', 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAEAAAABACAYAAACqaXHeAAAABmJLR0QA/wD/AP+gvaeTAAACO0lEQVR4nO2bTU7DMBCF3yDgFGVBWSNY0ntQDsJBABXBHWgP0u6Q+FkCC3qKIvpYpEShHbeJk9gpmW8VWZN4ZuJxZmwHMNqNVPUgkh0AvZUOREaKbA9AZ6l5KiJjRbavdDcWkamvrrVAsk8Fh+xQER06ZDU0p3ixU9WDthVzQGwFYtN6BxiGYbSa0qkwyRMAfQCHAPZKa5SPLwAfAB5E5DlQn38huUvyjuTcka6GYE7yluRuDAfcRTR8mYGvHV4hwGTYP/reXwNzAKc+4eCbCZ6jOcYDiR3nPjemscMC9TyArk9nNXOkNXLDekJ28ugB0Gpy7U2Hmu2LsO9o12y6ADACrBgyB5gDYisQm3QSXMz2Tfq0VYKIrLXJRkDAvqYAVtb9HWj7BrUQ0gFjEbnII8hkj6Cytf91WAj8XpA8A3C5LJD3rTUV6jtOVyIyAf6GwAECDbvAaDaNAEwACwFzQOsdkJ0DPrEoEf8Zmk2fvxfZVHiCpE7+V2z6ilkIBOyr5/gmq7K1apIhpAM6aGCe0foQSB3AAoectgnHRko6Em0ExFYgNuaA2ArEJvsZHCN/JvhVgy5lmTnaNZvSpblsKjxF/lrgPb9ewXjTGh17mym+ITBCsiXdFObwLOS8HCAiTwDufe6tiVsReQnaI5MjMgOS31Uf9yjAN8kbljgiU8UhqWMkOX4X7i3qqpkhmYceROQ1UJ+GYRiGE9pPU9uJOSC2ArFpvQOqXBYvsp5wjdXqzfUr7Np63jDK8QNj5L+yJWBCUgAAAABJRU5ErkJggg==') RETURNING id;"); - category_id = database->get_integer("INSERT INTO categories (name, description, thumbnail) VALUES ('Home, Furniture & Appliances', 'Domestic Goods;Furniture;Home Appliances', 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAAABmJLR0QA/wD/AP+gvaeTAAABEklEQVRIie2UMU4CQRSG/0dgo0fAwoQYW0NDLLwAFVraeB6xNqGwgGsQDmDiIYgBZfUGmih+FuwGd5zAzkCMBX+yxbx9872Z/72MtNO/E5AAN0BKec2ALpCUKdANALu6dnnmKZBKqkca8GJmB+sKEAlfAM0KzIonZ7YB/8kN+AqcSkoj4a1fUeACeAWmwFkWu4po8GW2tw08A2Ogo2yRawLUsi9kTFOg6tk3rUiq/bjQoaSOmX1IGgTY0zezT0nnKk5gIqDnnGYYZn3B7pHDuhVw4gS/gKMIeAOYO6xm/vMhwO+yupeWY3oXa8sKLZnAPvC4xdOPgT3Xw2MWTXrfAPwGDFnXwzy7rBer8n1PxU5/q2+5j5kIl7IVAwAAAABJRU5ErkJggg==') RETURNING id;"); + category_id = database->get_integer("INSERT INTO categories (name, description, thumbnail) VALUES ('Home, Furniture & Appliances', 'Domestic Goods;Furniture;Home Appliances', 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAEAAAABACAYAAACqaXHeAAAACXBIWXMAAAsTAAALEwEAmpwYAAACoUlEQVR4nO2Yy2oUQRSGKwkqibeFF0SjuPC+EQQvuPJGEEEGldmKlxAUl6KQjQhufAEvoIIuTGBwo75BXAgRdRtcqJgH0BiiRoVPjvSQoadrpipOV1W39UHv6tR/6lD9V9VRKhKJRCKRSCQSmRdAH7APqABVR18l0exVvgC2AqPADP4Q7RFgi+vFDwKzhIPkct7V4s8SLkMutv0s4SK5bc6zAKOEz+M83X6G8JEc+/IowF6Kw548ClChOFTyKECV4lCNBeg0xB3ACf5zD+gGrgI/CZdvwAWgq+MFqAOcDLQIcv4fUS4AhgiPU51YWC9wEDgKbGgz9inh8LBNrpuAY8B+YHHWgEXATWAqNfEYsEMz6TbgN/6R33Fji9vry9T4L8ANYGGjuT1vIfAZ2KkRqOGfEU1uh9u8XWQHd8vAMwYiE8CCDJFD+OeA5lf+aBB7Wga/MBQ6lyHUBbzDHxNZR15yFJowJoOnDQe/0my1K/jjsianN4bxUzL4u4XgrgyxVcAP3COaKzPy2W0xx7QEvLUIuBtQp0hnfvcs5hiXgGsWAV+BJZrjpub4a2p6AMssfmlhWIL6Lc/zQRUowEWLdfwC1tYDn1kEjqtAAV5brONJY+Bx7GgyQ99Ymp8w0BjcY3hxqHNbBYal+b3/ewtsBLhuaYZLVSCIMSc5mTKcNUl/II+bvJkzvzRtHkVlYc780szDDIvIQKsC9FiaYdFoNr80lmZYNJrNT2OGITY9O/F4WqNMAO5TPm4ZLV4AVgCfKNe/v1zZAGwvSRE+SFfYavF1gNXAo4JekOTC8yCraWINsB64JA2R5C0+mSE46bAX0Er/TvIsXvfPC9ehaYfXlCN86yvfCfjWV74T8K2vfCfgW1/5TsC3fiQSiUQiEVUK/gB5u4ODDC6IPAAAAABJRU5ErkJggg==') RETURNING id;"); category_id = database->get_integer("INSERT INTO categories (name, description, thumbnail) VALUES ('Patio & Garden', '', 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAEAAAABACAYAAACqaXHeAAAABmJLR0QA/wD/AP+gvaeTAAAD20lEQVR4nO2aT6gVVRzHv7+UMk3RAjErynTRoiBCqAdtcmEiUiFkqxbRrnbiQiLquVKTstxIGOTGzQvJlegiwUUlRJYP+iMYQaWP0pSnkvi093Ex8+h6PWfuPTPnzNyb84EL78098/v9vt8798y5vzNSS0tLS0tLy+2KNV1AJ8AcSaslPSfpKUmPSrpX0lxJFyVNSBqXdEzSUTP7tqFS4wIsB3YDk4TxPfAGcHfTGkoBLAB2AtcChXfzK7C+aT1BACuBXyoK72YMmNe0tp4ALwH/RBY/w3HgwX5rqX0SBF6UtF/SrIJhVyV9Iem4pL8knZW0UNmkOJK/is4/KelZMzsXo+ZokF32RZ/8b8BrwPwecRYD7wDnC2J9zSBNjsB84JSn2GlgKzA3MOYS4ECBCe+n0hMMsMNT5BTwaoW4BmzzxL4OPB1TR9kiH86FunglUo6PPPG/jBG/anG7PMXtjphjNvCVJ88zsfKUKewu3JPVBJEnKeBJ4F9Hrn0x84QWtdbzqbyVKN/njlzngaLbZjqA9xwFTQGLEuV7wWP4Stf4O1IU0YUr8biZXUiU74ik647jzrtBHQYscxw7liqZmV2W9LPjraWu8XUY8Ijj2Juey7QMZ4DNXfH/cORc4iouyADgceAg8BPwIXBPyPmJuF/SVuDljmNTjnGzXSc7D7ogu5celrQgP/RY/lrTb4zErJX0Wf6367fERddJfV0BwIhuFj8TcEtAganp7AOscLzvnHR7XgG5+EO6VfzzZpZsMisLsEzSQ463TrjGFxrgES9JYwHiN/Q5riozE58v3zdB0YAHKP69PVqp3AQAd5L1B7tx3RZ7Btvcxy1ooEwANnnqDF92dxkwCawHLoea0IeJsTiBu9t0lYAeYWfhS8m+ApNkc4GAdz3JvSYkkRrGB8HiO4ofIRef/z8POO1J5DShNpluzgH3lTbAI+j1goTbBsiAabIOdFyAWcB4QeLRrvFNccuHEdOEVf0mr0Opg4+BtHsewKEeRYw2YMBMez39hg/wBFnbuYjR5JL/429gXXLhXSbsqVFgL8bKaKjaEHlb0qWKMRqlkgFm9qek8guNASBGS2y7pN8jxGmEygaY2RUNVmMkiFhN0U8lfRcpVq1EMcDMpiVtihGrbqK1xc3siLK+4VARe19go9y7MgNLVAPM7EdJe2PGTE2KnaGhWhxFN2DYFkep9ga3y70/N3AkMSBfHA1Ux9hHyt3hoVgcJTNgWBZHSZ8PyBdHEylzdHC6zEl1PCCxq4YcVyR9UubEWh6WBjYoe44gxQMVZyTtMbMfEsRuaWlp+X9zA8EbEeUfOYl4AAAAAElFTkSuQmCC') RETURNING id;"); - category_id = database->get_integer("INSERT INTO categories (name, description, thumbnail) VALUES ('Digital Goods', '', 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAAABmJLR0QA/wD/AP+gvaeTAAAAoklEQVRIie2TUQ6CMBAFV2N6CG8i90P0ni7XGH/Wn1oeS0piTJivLm2YBDpmB1shiHUBnsAMODABpT7XmrOCB9/c9xR4jDdgiLXvKVh8SVZwXjV2clnZn83sCgxmdopn3mWsPsPU+Mljfa41ZwUlJA68gJHea/r/oOtNl60Eqt502Uqg6k2XrQSLt2PL3oeflKzq7S8bXW+6bCVQ9abLPkjzBiW5R8M25vEUAAAAAElFTkSuQmCC') RETURNING id;"); + category_id = database->get_integer("INSERT INTO categories (name, description, thumbnail) VALUES ('Digital Goods', '', 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAEAAAABACAYAAACqaXHeAAAACXBIWXMAAAsTAAALEwEAmpwYAAADS0lEQVR4nO2au2sUURSHJ7Exia9SRURJRA2oENFNQLcUFXQ3ypYSNBZaqcFCUCSinfgfmMZkjaQSQTvFJ9qorYiFj8JnhEACRtFPDrmRzc25szNj2Mks94Ot5t7fnPkx9+ycc28QeDwej8fj8VQBKKEQMr4BKAIjwHtgEvgEPAT6gMUhc2WOzYhjrEYpSNMAYC3wjHC+AN11ZwCw0TxcFP4Ax+vGAKAFeEM8fgH5ejHgHMl4ITkj0wYwlfQ+OF7zIeAIcBEYcwTdmXUD2h3B9Fs6HcBPZdz5rBuwxxHMckXrsTJuIOsGlMKuW1qhD+gNwL8BI34JzMbngGCeJ8GCI5gWReuOMm4o60kw5whmRsEDLANGlXGXs25AEzCuDJESuBtYAmwB7juCPpBpAwTzyZuEb5VLJcsGtAE/EhhwwtLJpgECcCjmw98EGoN6MUAAeiK+CdeAhYFF5g0QgFZgUEmMv4EnwN7AwXw1YJUxYcYvwjz5d9gO7Ad2atWhMqdLuVeXY+ysmCTWpM/p8Xg8sTBJrtMUQ3lgxX9orTQaBVNbNEWYk04SBNYBZWBC6QI/BfbF0CqYnSSZW8mE+SttC5lbm7/BSoDDZq+vGmXtQ8d6e4Yj6MhHVc+8MICpr7w4SO2/QNFpBG7F1DqaqgFMvfZJip1TitbpBDpy79Y0DSiTjFGr3F0EfE+oNZiKAUCzkvCmGx5F0/DYDNxzBHWwWl0B3AU2Ga2i0bYZr/x3qKUBOcfNita4pabBEdbyuqJc/ypzLS3pJGlsS8OAwhw2PbWldFvRke12jUIaBpS0O6W99eUN0PFvQOCXwByDzwHsjnEAQnp+Nlcrrg8o1x85qkONXWnkgHbHzS5Y47aak19hR2D6letybKbD0pIzRRob0jCgwZz41LgO9AKXQg5B5aymp8aY0egNqRLfWifKamOAAJwlGc+toKUSfJlQ68y0ThoGNAOvYwYsy2GHopV3LJUwXtldopoaIMj6Az5HDFg2QY4FDuSYrNIFciGF0XpFo7YGCMAaR6a3A/73ze7CFDzVDJVjdasd82tvQEVSlN2eG8A70yL7aPb/T0rNH0REjs5L0wR4YIybNMluuFpvMTUDPB6Px+PxBFnnL5kjkrLcntRPAAAAAElFTkSuQmCC') RETURNING id;"); category_id = database->get_integer("INSERT INTO categories (name, description, thumbnail) VALUES ('Services', 'Non-product services, Freelancing, etc.', 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAEAAAABACAYAAACqaXHeAAAABmJLR0QA/wD/AP+gvaeTAAADjUlEQVR4nO2aS6hNURjH/+t61c2juCjKsy5loCiuiTKiFGUiTEQxo0gGCteEiYmZPGbUjQkhRhQDEiZInYG8out6XFeKe/kZ7H1qO/fsx7l7rb3Ocfdvck77nG+t7/+tb639rbW3VFJSUjKKMb4dkCRgk6T1kiqSThljfnh2qTiALfzLZd8+FQpwrSYAQ8Ak3345B5gHHAR6Gc5pYLVvH50ATAPOA4N1hNfyAOjy7XMugA6gM/y+AnifQXiUQeBAaD8DWOhXUQMAe4CfoZDnwECD4qM8BP6E368DE3zrS4Qg1X/lEJzGDht+ttloJIapksY5bH+GjUacBcAYU5F0N+PfK5KuSHog6XeG/w9Iav56AZgC3ElI4wFgZ41NJ/AkwaYfWO5LU8MANxudx8Bs4HOMzRAwpWgdIwZ4FyOkkmJ3PCFwq2z553IRrNIRc/15it2zhN+mj9CXYRQRgO8x19NEzEz4bWCEvhQPQQETN5c7Y2zagEcJU2CuLf+KyIC4VB8jqQeYE70ItEk6KWlZjN0XSR/suecQoDthFKt8DRe8bcD+lJGvch+Y6FtfIsDMMM1dsduGny6nQLuCNHfFZBuNuCyFX0q6HrlEziaj9p8k9eRsT5LjQ1GCLetWBbe0G5I2Sjo2gqbuStouaZ2kSZIuGmPe2PKzUIANZD8UGQROAON9+20VoB3Yy7+HHFHeAqeABb59dQ71d4vWSt00iiiE0qg9M3hhjPnoxRMfhNPhAsE+/yGw1LdPLQ2wBLgC9AGvgLNA3I70/wLoIiita3kKtNezsV4HhPf+TkmzZKFaM8Zcythvl6RbCX3uMsacyetPkgNrgMvkO/sfRsa+u8I1JInT9WzHWhA+S9I5BVVa4WQY+Sp9LjpfTlC0uOJnSv+rMow8BMXWStvis6RdXmLPDckuHuBQK4oH6LYg/miriq8Aw+Y1nsU30nkeHgHzixCfuQ4geBhxU/nv7b2S7qn+M8D3km5LumqM+ZOj/25jjL3RbzDyaSxy3H/Tp/3iZhKfOAWwl/ZRPirYAmd5DC5JazP239Rp75oj1oSPNvFxJ0K/lD1FfXLUGFO3WMoNQZ0f95JCM2A37VssCO7FR4LQbOvB4cLEN1kQXgObbWtzUQp/k7Qv/LTBkKQ3kh7XlseFQ/qa0E+rv9ycRkIQ/n/xVeoEYfSIrxIJwugTX4Xg7mDtpcWSkpKSkoL5C2IZ98cgmOE6AAAAAElFTkSuQmCC') RETURNING id;"); category_id = database->get_integer("INSERT INTO categories (name, description, thumbnail) VALUES ('Books', '', 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAEAAAABACAYAAACqaXHeAAAABmJLR0QA/wD/AP+gvaeTAAACCklEQVR4nO2bPWsUURhGz00iWlgYFAtbQRFUgiCI2NjZJgEbSZlOsNTKP2EnNmkCaZLCzkL8HYpg4QeSEAJiEVEeC7dY1M3c3XnnPpPde6plZ/flzNll7sDehcpskyZ5k6Ql4CpwHjgRahTPTkrp7aiDC7lTJCVgDXgKXAwQK8U7oF0ASaeADeB+kFRvmMt83Qum8OQhI4CkZeBBARcLOd+AJ51bGDkygKQLwM1CLhaavgGXmHCpPC40BVgsYmGkKcBUf/owxo1QBu+Bg8B5TSwA18hfykcOieAbcDml9CtoXhaStoHlNjNa1RvisPTJD/jedkBUgGNLDeAWcFMDBM1x3S+09o9aBs9Keg7sB83LYR6413ZI5I3QeuCsYtRrgFvATQ3gFnBTA7gF3NQAbgE3NYBbwE0N4BZwUwO4BdzUAG4BNzWAW8BNDeAWcFMDuAXc1ABuATc1gFvAzcwHaPPb4E/+7A3qOz8mfqekFf3LlqTbkuYDJfvJfwI8dDsV5a8AL90+XTDORXCzMwsjTQEOhx7vdSnioinAx6HH17sU6SWS5iR9HlwDPkg67XYqjqTHQxfCV5LOuJ0iadzeJukk8Aa4NXjqC/AMeA3sdqcWxteU0sg9xVn7+ySdA3aAO1FWBVlNKW2POpi1DKaU9oC7wCPgU5BYLxh7h+fgH6Q3gCtMwV9nK7POb7zRpspIHJDQAAAAAElFTkSuQmCC') RETURNING id;"); category_id = database->get_integer("INSERT INTO categories (name, description, thumbnail) VALUES ('Movies & TV Shows', '', 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAEAAAABACAYAAACqaXHeAAAABmJLR0QA/wD/AP+gvaeTAAAC7ElEQVR4nO2az2+MQRjHv09pohHclBIkUhc/Dhqk6iYSN0LqVxxxpPEPkHB0QURE4w/g5CROIkSJgyoJ4kaiKRJpKyG0/Ti8u0mzZrbv7nR3urvzOT7vPLPf7zM7M/vujJRINAyAAT2xdUShYP4GMAUci62nrswyX2QKOB5bVy6AbuBSQL4Bt/ifP8Ch+dQ67wAngYmC4DNV5PvMFxkBFtdCexBAB3C1ROwvYFsFfZR+7Ut5D3TV0kfVAIMe0W+Ajhz5c438O2BVPbxUBdAJjHrE35wjt3FHfjbAfmDGY8K5jTWN+SLAFY+RH8CGkrbNZV6SgHZgyGPoBdBeaNd85osAG4Fxj7HLTW2+CHDCY24aeFDG/MJe7SsBuFPGaLSRt5ziV0g6LemgpG5JK2spKoAxSR8l3Zd028zGg3sEjgLfKxy9hcA3oD/U/Dn8e3kjMAOcLefROwWAvZIeSloUVMX4TEvaZ2aPXA+dBQDaJA1L2lpDYfXktaTtZjZT+sBXgD2SnjgeTUq6JumtssrWg9XKFuAtgf30mdmzXC2Bi4759BfYESiiKoAlZG+SIVxw9d3m+cz1jthzM3s5f7byY2a/JQ0GdrPOFfQVYKkjNhooIJQvrqA5kHTP0XSZK99XgJYhFSC2gNi0fAEW3l/KFQLcdYR78+Y3fAEkBb3wtPwUSAWILSA2zbAGuH719Upamye54QtgZkdKY4WdIdfi2PJTIBUgtoDYtHwBGn4RBAjJb/lvQCpAbAGxSQWILSA2qQCxBcTGV4BJRyz2NZU1gfkTrqCvAJ8dsV3AzkARVUF2ufJUYDefXEHf4WifpKeORz8lXZc0ovodjnYpOxzdHNjPbjMbKg2WOx5/JSn33d4FzrCkHtfxuHMKFBoOqH6jXEumJQ24zEtldoHCjYrzkoJeNiKDMvOPq+8B+skuHDUaX4HDc/nLe01uubKF6ICkTZI6q65obRmT9EHZNblBM3NufYlEIpFIJBKS9A+F5c3zqqADtAAAAABJRU5ErkJggg==') RETURNING id;"); category_id = database->get_integer("INSERT INTO categories (name, description, thumbnail) VALUES ('Music & Vinyl', 'Musical instruments', 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAEAAAABACAYAAACqaXHeAAAABmJLR0QA/wD/AP+gvaeTAAAFHUlEQVR4nO2bXYhVVRTHfzuiJBuNMc2ERDJ0irEilF4sDaIPeopAjaCnihF6k0CUEqLGeQoVfEh7CsqH8CnNqBCCCgqJzI9yCMf8wjKFGY0+EH897Gtex3POPV/3XsH5w8DM3mevtf777LP2WmvvgQlMYAITuI4ROqVInQH0A/OB2UAvcCsgcB44AxwDhoG9IYQznbKtLVBvUJeqm9T9FsPFxpgN6hK1Yy+qMtRe9Q3114KkszCirlF7u80vFeoUdUgdq5H4eIypg+qUbvO9Aurz6sk2Eh+PE+qybvNG7VE/7CDx8diu3tYt8vPV4S6Sv4RD6txOk1+o/tZl4s04pT5Uhkvh7UVdBOwm7uHXEsaAx0II3xcZVGgC1HuAr4A7iozrIE4Di0MIw3kH3JD3QbUH2En3ye8EhlL6pgMfN2ytF+q2bn7kDexQb27YM6BeSHluW93kV3SMYjr+J99k10DG8/XECcYI70RHKKbjKvJN9q1PGXPCHBFjHh+wBphVdOJqRi8wdXxjY1IWpIyZBayupNWY2LQzti+CEbWvmbxxZWRhzBYJ1I0t5uBVoG6POgrsAg4AxxttdwEPAE9k6JsD7FIfbsjYDjzTQlcPsBJ4u7CVxnz+SI1v8LD6gnpThs6p6ir1aIacb2z95psxYpl6grGYURc2m0E8Qfdk9d0a9S8pMwGbalK+qrDyyzasrcmGDWWUFy1jJWFzWfJNdmypwY79afITvw1jAfNUWn9OjAB9IYR/K8hAnQz8RHSUpcUA05MKrWlxQD/VK8avVyUPEEL4E9hYVQxwf1JH2gTMr6hwFPioooxmvAecqyijL6kxbQLmVFT2aau3ry5QNzZ++rOeDSGMAp9VtGl2UmNaIFS16rovq1NdAHwHTGo0vaIuCiGkOivgB+C5CjZdFUpD+gqoGv2dbNH/MpfJ0/j9pYoyWyGRU+6CSEG0cqDmbCsisxTSJmCsotxW2eNW4O+mv/8iOros3FnJonj+eBXSfMBoRWUPZnWGEPYbi6uXlv3WEMKBFjITt7ECyM9JXVkx8hpTE51OGRhT36pp+UCS7LRP4OeKNvfQ2qkVwXKqO+ZDSY1pofA0Yom5iuM5BtzbiORKQ51END5xH88rBpgZQvh9fEfiCmjEzAcrKIQYu79TUQbAJqqRBziYRB6yt8EvKiqFGOCsLTtYXU2MGapidxnlSyo6nWZsMWZ1eXVPUrfWqH9pmQkIxnJSXuwwlqvScNRY7krdHYze/kXrvV1yRE1d6ZlOzrh838oxXzuJcfpU4Fuyk6lzxMRmL9FRBqK/6Aeeov4i7LoQwpulRpqvLH7FoYXaZ7GV0060LIvnmYTBDAWJJzbqDLM/h05hsBW/PMnQEOmZ2L4Qwj8J7aPA2Ryy24mTwPpaJKnLMmZ5YNyzeU5sOoEVtZBvIpZ2PH7BxiR47ZDPfTyeO9Q1XjrYA8xLeWSIeFDZ6riq3RgGFoYQctUQi16RuRv4GphZwrBO4A/gkRBC7mSuUEUohHCY+IarFkzagXPA00XIQ4mSWOMW1mKq1+jqxFngyRDCno5pVOcaLyl2G4fUNL/U9knoUT/oIvn31e7fVzTGCZ28R3RcXd5t3lfAeJlq0PZeqTmvrlNv6TbfVBgTqDXWmxD9or6m3l63vW37VxTjtZRHgWeBx4H7CugT+BH4HPgE+DKEcLEddnbyn6amES9CzSPm/9O4fOH6PHErO0qM5PaFEE53yrYJTGACE7hu8R+yRA2BiW4OUQAAAABJRU5ErkJggg==') RETURNING id;"); - category_id = database->get_integer("INSERT INTO categories (name, description, thumbnail) VALUES ('Apparel', 'Clothing, Shoes and Accessories; Jewelry and Watches; Fashion', 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAB4AAAAeCAYAAAA7MK6iAAAABmJLR0QA/wD/AP+gvaeTAAAA2UlEQVRIie2SsQrCMBRFX/wBEb9CF/0YoYO6qrMODhZ09Dv003TQyQ5uRo7LK1RpJTXaweZAoEm494RHRQK1BegDe+AI3HHnrpkd0CsrXQC2hKwIC8xdpZs3RVegk5Pp6l0RGx9pygkYAW1dY+DskMuXO0p92b5K4wqkKbGIiFFxIiJNp5/An8QY02roZioitgKpFZHZ0wkwAG6Zkay/MNZl5tsCw9znABFwAVa690I7Yu2Msi7zbi5p+FOMMYX9jaKLXxPEQRzE/y8+enQffMSTD+UHzQZqzAOhndP5TQOzlgAAAABJRU5ErkJggg==') RETURNING id;"); + category_id = database->get_integer("INSERT INTO categories (name, description, thumbnail) VALUES ('Apparel', 'Clothing, Shoes and Accessories; Jewelry and Watches; Fashion', 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADwAAAA8CAYAAAA6/NlyAAAACXBIWXMAAAsTAAALEwEAmpwYAAABhklEQVR4nO3av0rDUBSA8dBF7DP4B1wERxF8kD6HQlEonZrRVxDBTaFdXH0NDQ5uuluJ0k30kwsJ1JDatMk9p17Pbyq0cO7HTW5LSRQZY4wx5s8BtoFj4BZ4BCb4M8lmuFlHwJZk6CZwDnyg5xMYATu+Yzued3JRbi0dX7F94IvV49bUbzo2XnIxD8D6AnPWgLslZ51px+YugFaFOS3gknpi7djcFdD+ZU4buKYZsXZs7gk4AfbcZZ5FutenwDPNirVjNVS7pwOJrRZNWLG58migS7i6ZcFjwjUuC04IV1IWfACkhCd1bbPu4/3ALu0UOJx3UleJHqCvVzu2YvQg+4yqbA292rFzogdT76uaWkevdmzhIEuy8B/fZSgr+Q3xCtzPPKDqWqVgEViwLNth31DmPbDIgoVF0lBmwb5hOyzL+44WCfdZcCQN22FZtsO+ocx7YJEFC4uk8Q+D3xV7U43gG8XgoUbwbvbXqLQXYEM8eOqpPPdE3JtAqJsxVIs1xhhjTFTXN9+cDWs9QZG1AAAAAElFTkSuQmCC') RETURNING id;"); category_id = database->get_integer("INSERT INTO categories (name, description, thumbnail) VALUES ('Pets', 'Domesticated Animals', 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAEAAAABACAYAAACqaXHeAAAABmJLR0QA/wD/AP+gvaeTAAAEcUlEQVR4nO2a3YsVZRzHv4/HsjfbLLctaMNqgzapC7Oydwgh0kuTqLvQiqD6A4reichujW6KbmqXrbQwNuyii7rpwlXXtDKLCs1Aio1W18yV/XQxM3Ac5zzzO8955pyF5gPD4Tzzez9nnteRampqampqamoiAThgAzAJnAD2AI8BC9qwsQB4PNU9AXwDbARclbFHAdhEMe9ZigA0gJEWNjZ1I4dggHtaBJ7xjMHGcx79OeDubuQSBDBeUoDjwBUe/UHgnxIbn3YzJzPAecBsSfAAL3hsvGTQPwmc283cTAC3GoIH+M5j4wejjZWx4jb3zAYuNcpdB1ycb0zbrjXa6DdHVULMAlhtOUnDBe3D6T0LC41ypcQswJ9tyJ7xD5C0tA39qTZkvcQswPeSMMqeKmibM+oiqWU/0i7RCuCcm5J0wCj+R0HbEaPufufcX0bZUmL+AyRpzCBzUtK+gvZ9kmYj+egNJBOZ4yVD2DaPfkcTqUpJk3seGAXeAG5pIfesJ4E54C6Pj6CpNLAqjWkkjXEwVt6ZgzXA0YKA3gHOzsk2SBY+Rbxm8GVeTAGLgHcLZKeB+2MlP9gi+YwPyS1TSZazGzl9SfyI0V+2nM6WwrvT7/nkHbDVE9dRYjwunl+kmQ0dO2o/rkcNcb0ew9G3BkeHgGizM0NMC4HfDHHtjeGsrFfPWN2m3QHgvvQaaFN3tTGmY2W2LPMA6wztTosQsAz4SNJhSdvT6zDwAXCl0Zd1U6Q0dksBDhqdlQ49wB2SJiStk9RoutWQtF7SBHBbDF8ppbFbCvCl0ZnXFnCDpHFJl3jE+iV9BizvxFcTX5UJWAy9Jdsi5+dWN0h2cMYk9RnsXCRpDDgnxFezWyWxdw6wzdDh3OTRf9nYaTXzosfeSoP+1ijJpw6XAj95nG336F4OzAQU4Bie0QH43KN7APA9akFFGCI5oMizC2i5RQW8GpB8xiseu/0ks8Q8k8A11rzaOmkhmew8IOnetOlrSSPOuX9byPcpeV6LdoAsTEm62jn3dwv7iyQ9LCkbOb6QtMU5V7Th0n0Ie/bztOwL5jUkz/50hAJMA5dVFWfsHSFJEnCWpFFJiyOYWyxpC7ll97wGeDPCL59nc6/zKoVkM2RzBclnvE0XV51tAfQBn1SYfMbHJKPL/AFYCxzsQvIZvwPrep23gCXA+11MPM8IsKRXyS8n2QnqNYeA60PzCHrnBrhA0m5JQ6GOI/OjpBXOudIdoDyh84CHNH+Sl5Jj9QdDFEMLsCpQr0osO0lnEFqARrlI1wmKKbQAewL1qmQyRCm0ExxQ0vHEmOvHYFrSkHOu6NjdS9A/wDl3RNLTIboV8VRI8h0DPInt1biqmAWe6HriuSLcDkz0IPkd2M4QqofkpHY9yf5g1exMfUV5cTr629fAsJJ9ujWSblTnr7SdUjLqjEsadc7t79DeaVT6+jnJlPlmSSskXZVeyySdL+nC9FOSZpT05DOSfpH0a/q5U9IO59xMlXHW1NTU1NTU/D/5D8Q7H3ts84ozAAAAAElFTkSuQmCC') RETURNING id;"); category_id = database->get_integer("INSERT INTO categories (name, description, thumbnail) VALUES ('Toys & Games', '', 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAEAAAABACAYAAACqaXHeAAAABmJLR0QA/wD/AP+gvaeTAAAEAklEQVR4nO2aS2hdRRyHv0lrsb7ahQk2ol1119YWQaUgCq31QYUq1ErBR0QFBSm4EcWF4EY3KiotIhUfuyAUBB/YjeJCSx+SpO1CUYwLH6W2xi5iG5PPxbmhye3MfZx7bk6Se7/NJTP/md9/frlnZu6ZgS5dunTpYELehuo64PHKn++GEI7ViH0Q2AL8DLwVQjibV3deoK5Tx73AuLo2EfuCszmoLp3rnAtFfdOLeSMR+1skdtNc55yiJ2e7ayJluyMDFVgVib02p27h5DVg0ZDXgKkWdSdbbF8YeQ0YbkFTYKSF9uWjXlmZzZtlSn2p7Pxn0so+YClwM9BfKboFeDYS+kDlcxIYCSH8mFdzXqPuSKwC85qOXwUaegTUFcATwHZgDdCXU+8U8BPwDbAf+DaEML+/JepO9VSOCa8RflCfUpeXPc4o6u7KzN1uflHvK3u8s1A3q//NweBn8rm6puyxo/aow3M8+GnOqtvLNuDWkgY/zZT6ipp7n9IoqWVwc7uF6xCA54B96pJ2CqUMWN1O0SYYAPbbxlUiZcDl7RLMwb3AZ+pV7eh8oewEbwcOqLGXKy2xUAwAuAkYUrcV2elCMgCgF/jE7J1kbxEdLjQDIFshngFG1b3qFvWyVjq7CHUQ2JG30xI4DxwGvgY+DiEcbbThQvwGxFgGbAKeB46oX6k3NtJwsRhQzW3AQfXFervJxWoAwBLgZeDtWkGL2YBpnlafTFV2ggEAr6pXxCo6xYCVwP2xik4xAGBjrLCTDOiPFXaSAdHlsJMMiNI1oOwEyqZrQNkJlE3HG5DnutoYsAc4RnZZahfQ0E/PHAwDHwB/AGvJDmivbpPWBdTBxIHFiHpdVWwwfm2uVfaol1Rp9VVyyMNgqwZMqDck4pepQzkTizFk1eBnaG0035ll1IBm5oBDIYShWEUI4TzZV7Uo3g8hTCS0vid7/VUIzRjwd536M60k0mRfp4sSSk2C/0TK1qs9IYTUHcHY4zEGfFknh63AiqqyDalgtQdYH6k6DpyoofNdnTxmiexMPEePJuL71bFGn7uqtrH5ZszEKZA6kMituLfYaq/6b0RkXH248l+Yjl2vnkgkNdCA1mOJtsfNruRPx/Woj9TIq9jlUX0vkZjqqPqpelSdTMT8qV7agM5y9WSij0n1SEXr1xr57Ct08JXEVql/1RCtx64mtB5qQeeM2p7b5+o96rkcSe3NofVODp1z6t3tGPvMxLZVXG6EKfU1Z8wRTej0qK/b+K200xZ8UlwrudXqR9begR1Stxagdad6uIbOhPqhen0RY2vqEpLZ0nQX2TrcR7Y5GgUOVHZohWB2nLUBuIPsus5K4CTZj6MvQgi/F6XVpUuXLh3N/4LZQfQlM+IfAAAAAElFTkSuQmCC') RETURNING id;"); category_id = database->get_integer("INSERT INTO categories (name, description, thumbnail) VALUES ('Baby', '', 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAEAAAABACAYAAACqaXHeAAAABmJLR0QA/wD/AP+gvaeTAAAD9UlEQVR4nO2bS4gVRxSG//IxzvhYZaNGxpnJJghCBBdCdi4iIRsNqLjQvYgEAllkJbjwAQkJqItsgskQJIIJYjDim8EgQUEQjeAwhBkFRUVdRGKI5nNR3Vpz0923H9WPq/0t+9Y9db7TVX2rq/tKLS0tLW8wpu4EfAEYSR9J+kDSW5ImJB0yxtyoNbEqABYBv/F/ngG7gRl151gawBJgPELeZXfdeeYG2ARMAA+APcAc57PBFPLhSHi3To9cAJ8A/3XIXAc+B/YCj1LIh+yI6qPWiyD2wrVC0rCkxZKQdEfSJUnrJH0lfzmOGmO2dB6c5Sl4JoBhSZ9JWitpUUXdPoo6WGkBgH5JuyRtk9RXZd+STlTc33SAxcDvGeasT8aC6Vab/AgwWZP8OLCwTvmlwJ81yd8EliTlV+qwAEYknZM0WGY/MTyUtMIYM5XUqLQlYs3yknSgm7xUUgEaIC9Jd9M08l4A7G983fKStJkUN0FeCxDIn1f98pK0StLWbo28FaBh8iGruzXwUoAGDftOrnVrUPhn0JFfWjSWZ65Ket8Y81dSo0IjABiUdEbNk78haU03eanACACGZOd80+SfSBo2xtxP0zjXCAjO/Fk1T16S5knqT9s4cwGCMz8mu4nRVFambZipAIF8VRe8xwW+m3qTJXUBHPmhPBll5EdJI5Iu5Py+3xUudvd1oqJb2J+B2UG/c4FTOWJ87FN+iOru548BfR39DwC/Zozzni/5KjczbgEDMXnMAY6mjHMbH9tfQD9wpSTZOL4h5g4OmA0cThFjf2H5oMOdparG8x0wMyanmcBownefApl+oeKqvUDS9hx1y8pPkkY7jm2R9APBhdDFGPNc0r2EePuMMZOFswLWlX+iOYId1jOAgxGf/4J9juDmtSsh3kWc54ZFC7DHu+50juCcYezQ/j6i3XGCCyPwRUK8ScDfEybgW+/Kr5gmn6IIp4GvE+JNYfcg/QHs9+/9kjFgfky/cdMhjingHa/yQSKflmHucAaYG9N33EjoxP+Zd5JY5ln4n4hj3YrwRy3yThJ51uBR3ASGsWv8TiKnA8lX+3KGfUQSy4EnBeXHgbeDeH1EL2enjQTgy9rlnWQ2YN+vKSTvxEssQqPknaQ35yjCU+xucVS8uCLcapy8k/RG4N+MRThOzMoMuwKMuiY0T95Juo4iNEM+hHzT4SgdmxxOvG4rvObIh+BpJGBfYe0t+ZCiRehp+RDyT4feG/ZxkG8kvB7yIZ6K0JvyIQWL0NvyITmL8HrIhwDrSX8DdZmY5XJPg72LPJkgfh/7vn/VL0tHUtqboth/aHwo+xh9QPZ/ABclnTfG/F1Wvy0tLS0tGXgBtI9sAulPU94AAAAASUVORK5CYII=') RETURNING id;"); - category_id = database->get_integer("INSERT INTO categories (name, description, thumbnail) VALUES ('Arts, Crafts, Sewing & Party Supplies', 'DIY & Handmade', 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAAABmJLR0QA/wD/AP+gvaeTAAABaklEQVRIie2Vvy9DURzFz1f8WNRQNRBdWCwSE4vJRpgkBiwSi8TqD7FIRVgsNv4ANquFQYhJmwaJDg2DpvWx3Cqv13u3tYmTvNz37vfcc873m7z3pD8HYA7Iu2s2gL8IXACboQZ5GrhP4HYCz8AWUPJxOjx79lUjIc+4pLSkZUkPoQYbkh7d/U2CwaRbJyTlErgNAFmgCtSAbAzvwI2yDPT5OL4OZGZ5SWeuvhqTZcqt+2ZWDsz/mW7JpbsFzFNPuQ5rwGhL4k6gG3hyJtOe+oyrncTpeEckSWZWkXToHtc8lPp4tkNDNwEYA96BFyAVqR0DV77xtWpyDlSAhch+AVj/lbgTygBdnv0BoCfpfGx7wLCkFUnzkkYkDUl6lVSUdCfpVNKRmRVbTZ0CdoA3klEFckBvqHgauAwQjuIayIQY7LYhXsdeVM/3hhYlDQa124ySmfW3ebY9+DpI+gfEC5p90/zxU/GPOj4A/ViPv/A3dWcAAAAASUVORK5CYII=') RETURNING id;"); + category_id = database->get_integer("INSERT INTO categories (name, description, thumbnail) VALUES ('Arts, Crafts, Sewing & Party Supplies', 'DIY & Handmade', 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAEAAAABACAYAAACqaXHeAAAACXBIWXMAAAsTAAALEwEAmpwYAAADZ0lEQVR4nO2aSWgUQRSGyxh3AwETxA2CuIBIiIoyQXDBCCGgKCQXRTEXEdccFA/iIXqIS+LFgzuCKIpiwIsQEAUJKBrNQcQFBBUVRUUFFfdPKtOEztg10z1d1VNt+oM6TA31/qrX1bW810IkJCQkJAQDKAM2ASedshEYJSICGAZUOaU4Kt0egAbgE//yEagXhgFWA+9cuq+A+aZ1ewAWA79QI/9bJAwBLAf+eOi+Nz4DgQHAA3Jz35D+QOCpS+c58N31e4MJ3V6ASvwzXWgGqHXZb3Pqql11R3Vr9gFYGsABS4RmgL0u++udunJX3XHdmn0AFgRwwFyhGeC6y75cBA8At111W3Vr9gEYAXz2MfjXwGChEWAQ8DWL5k9ggk5NT4DdPhzQJDQDzMmheUG3ZrYncTFHZ5qFZoAtOTTn6dbMtR2uAe4o9mS5VRUJjQDnsgy+W6dWIIBTik7VaNaRe76KRp1agZBHUEWnzmjUGJtl8G/lvUCXVr6vwyOPjskVu1STRn0WB7To0AgFsF3RuXWa7O/Pcu+o0KERCmA08MOjg7c02b9W0K3PD0C7opOVIe0WKa7eRk6beQPUKTrZGtLuNIXdLmETpJ/UM4+OvpGHp5DBDy9WCtsAmhWdXRbC5kEPey913zW0IC8jimjRpRA2b3rY2yFsBehQ3NTGaLoByt9lwlZIB0y92JaHrRkedsxGfcIi301n4cvkYR621ureViMBaFXMglRAO8cy2neIOABMVVyTjwS0053Rvk7EBaDTwwHyRDfcZ/uhGcfrx7pjDEYBGhWvwSqf7d3h7t4ocGwgnbv74OGAqz7bb3a1kXZGirgBHHKm8V3ghBPXW+izbRPwxFlL9ok4ApSEPbLKJ+933UiwGaDCiR+ukBkc4DBw3imnnd8tMsHp5ACnyJ1AxBnSHy/IRMo98kO+/13ALmB2LLZB0uGxnT7T6EGRKbc2YKKwDWC8zM4C3zDPbycEN9mGgRc7T/wL0SM/jtgDDClkAKSTwnMDGBf14CcpYoCF4kVkOQLSn8jZNHj3hak8Cgdcxl7Omh58LfZTY9IBV7CfdlODL1XkAG1DnkVKTDigmvhQZcIBs1wXGNvLTO0OSOjvACkLprbfkooy7WUjDYkDdEMyA+j3r0CqXy+CCQkJCeI/5C+Xr1Nj9eUFLAAAAABJRU5ErkJggg==') RETURNING id;"); category_id = database->get_integer("INSERT INTO categories (name, description, thumbnail) VALUES ('Stationery & Office Supplies', '', 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAEAAAABACAYAAACqaXHeAAAABmJLR0QA/wD/AP+gvaeTAAACvklEQVR4nO2bPWtUQRiFn1dFiwiCKPiJX72g1a6ton/AGK0sBEvBRrEVLVXQSv+AmgSLWAqmTCqRpDCCQSRisAhiuoh6LHYDIe7d+biT3N2985R33znzzmHncHf2XshkMpkaY6mEJF0EXpvZSgKtvcAV4BQwFDD0O/AWmDCzP2X78EbSBUm/JT1LoDUiaVnleC/peIq1+TR8QtLSmsmvl9BaNTIF85J2pVxrp4aHJM2sm3hF0pkILZM0l2jxq9zdiHWvbfhlwcTfJB0I1DuZePGS9ME175Z4C7gFXCr4bD8wLml7gN6xEr0U4cyBKAMknQPuO8qaQEgohpiVTDPYAElHgefAVo/yqyoRipvBtpBiSUPABLAnYNgTSbNmNhXUWXceAtMJ9dy0Q+9FZBgtSjro0B8O0BtOta6QLXAbGImcZx+tUNwROX7D8DKgHXr3Ss7VAJ6W1EiO04DA0HPRc6HoE4JFofcRmOkybjdwtsP1x5KmzazbWBcNSV6FZjZWYh4oCKElSUcc40zSK98QCwxBb1zri70TvGNmX7oVmJmAa5H6m0asAT98iszMq65KyvwWGAiyAVU3kJAx64Br0CAZEEU2oOoGqqb2BgSdB/Q4TUmj6y+aWdGxHTBYBhwCgs8Jar8FsgFVN1A1/ZoBU8DXFEL9asCj0gcdbWq/BbIBVTdQNbU3oF9DcNTjvBMA15lA7b8B2YCqG6ia2hvQryHYiWlazw0EMUgGLMTcHtd+C2QDqm6gagYpAw53+tvdlQuDZEAD+O9UGMcT8XkLRI672enrVpJfifW8NGMNaEaO68bnDdCcdxX00haYBeYSazpvjHrGgPYzRTeAVK+6fAIeuIp6xgAAM3tD612hnyWl3gHnzWzZVeiTAUmOn9exUPSBmY1JmgQuA6eBnQG6i8AkrZem/pZrMZPJZGrAP+s7Gd91ZI+yAAAAAElFTkSuQmCC') RETURNING id;"); - category_id = database->get_integer("INSERT INTO categories (name, description, thumbnail) VALUES ('Tools & Home Improvement', '', 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAEAAAABACAYAAACqaXHeAAAABmJLR0QA/wD/AP+gvaeTAAAB+ElEQVR4nO2aP08UQRiHf3MhJEJBYUJoNCEWVGJiZ+E1fANjKGhICB0XvgBfwEhjcr1/So8PAC1+A6OWUgM9mJDAY6HFBWfIMns377r7PuXsTPY3z83u7ey7kuM4juM4zhhAD9gHzmk+58BboDdJAdvGk8phK2euKWsva/izop8zKCXgQY0gVszlDJrcdfOfMmMdYBoAA0lLkj6HEL7d1betK6AvaU/SV2AIJH/oVq6AMYKkgSQk7cY6tHUF3GYHeBo70BUBPUmvUwe6wpNYY5cEzMYauyQgSkrAr6IpJsNlzqCUgC81glhxnDMo9RzwQdKKpE1Ji7mJCnEm6aOkT9ZBGgMwimyXR7G+fhO0DmCNC7AOYI3JbhB4KGlD0nNJ81M4xYtI21WsY3EBwCtJ7yUtFD71z1hjKJkAWJN0pPLibyQ9CyF8v32g2D0ACJKGsrnshrHJFwVYLVQfGOcaeEdDXoktJ9oPpnCuK0kn+vNS9MddHUsKiO7HQwjrBTP8Q+efA1yAdQBrOi9g6gCPgSPgMvFXNfn6fpP4O/kqZNX361LCetVvDbLq+3UpIaBq3T6rvl+Xdl5398AFWAewxgVYB7DGBVgHsMYFWAewpoSAqnX7rPp+XUoIqPqtQVZ9v/EAj4BD4CKxCzwF3rR2O+w4juM4TmP5DXXEtAtM4reOAAAAAElFTkSuQmCC') RETURNING id;"); // Hardware - Heating, cooling, flooring, paint, etc. - category_id = database->get_integer("INSERT INTO categories (name, description, thumbnail) VALUES ('Motor Vehicles, Automotive Parts & Accessories', 'Auto, Tires & Industrial', 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAAABmJLR0QA/wD/AP+gvaeTAAAB+ElEQVRIid2VT0tVURTF121gBBEWSIOKwCihQTWxRArKgQPBSU0C7Ss40HmQfoOaBw3CpCAHQdC0aUWpCKEWCuVLRV+ivlLw1+Dug7vTeafX0BY8zrr77r3WO//2lf5bAG3AGPAGGADGgUlgEHgGrABLwCPg9L+KXwWqwCbwFPgOTAM9JhqjAlxsVLwFWEyIzAOfEvGAWeBIIwYPrOCdFQE8tFkErAHtwGXjASN/Ez8K1IAtoNeWaAs4EQn1uZp+F68Ch3IGIfmnK9oGRt3zh6imiJbupn9/IPLosrEm6YvxiqTbLmfMFxRFgaRRF+rOGYST0CtpwXifpCmX81p/4pXjl3IGp2xcknTG+GdJh13Ox4SBj9W/E8APW8c5t6Yz0Z40JeoOuve13AwCdiVtG2+yX0BR9x8mEBus23hD0jfj3ZJWXE5zQueY49WcQdjYVpUnSSr3YsbltCUMfGw+ZzBh42NJ54y/0O8n41rCwB/N94n3JdxF2wGeG1+k7EMBE1FN9qLFBs3WGjaAk5StYpO97hrQ72ruuHi+VVjBfUsecjzMKmANuAJ0RMbDWXEzaAG+AquUH5iA1HfAo7F2bSad7HXPYWAdeAvcApYT4hXgQkqr7qUBzkq6J+m8pHFJ7ZKOS3oiqVPSdUk7kl5KulsUxUIdqX2OXwDXyZR82SRGAAAAAElFTkSuQmCC') RETURNING id;");//category_id = database->get_integer("INSERT INTO categories (name, description, thumbnail) VALUES ('Motor Vehicles, Automotive Parts & Accessories', 'Auto, Tires & Industrial', 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAAABmJLR0QA/wD/AP+gvaeTAAAA1UlEQVRIie2UvQ4BURCF7yxRIqIiosJjewLvoFlBI5EoEI+gFrF8milurp87N0gknGoz+51zZopd5/76agENYAjsea4TkAO9lPAmsIwEhxqnbL5Q0wroRviqsgdLeB2Yq2ENtAyeTPkiBtaAmcIbS7i5QM+cKrgF2pZw9YqlYKTQDuhYwz0/wDmciwccnXOV1OBAFxEp+YPMe341/K6yOPKayimwiEg4A/CR8P13XRBsa9LHL/ALnn/mNt1k+AWzNxQ8zgAGwAQoEn/RqCcH+m9Y8td0BU99R2bmnxZMAAAAAElFTkSuQmCC') RETURNING id;"); - category_id = database->get_integer("INSERT INTO categories (name, description, thumbnail) VALUES ('Beauty & Personal Care', 'Cosmetics;Health, Beauty & Personal Care;Hygiene', 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAEAAAABACAYAAACqaXHeAAAABmJLR0QA/wD/AP+gvaeTAAADIUlEQVR4nO2bvY8VVRiHn1fQmPCxjTZqQmRjgYbKGAgojQUhgQTExZZ/YBsTC8TEjlgaNDYYxA5CBQUkfCTEhEDAimBloIFCAp2wiWzYx+KyZF3n7k7OzDkzYefp5pyc9/ee352Z8zHnwsDAwMAKJtoKpG4FvmwY5gFwGTgbEXPNsyqIOmV7/K5uKJH3KyVEEvgQuKiuzy3UVwMA3qP5I7UsfTYA4EBugdUtxroHnK4on2oQc7JB21q0NgqMQ7VJ+4jImmPfH4HsDAZ0nUDXDAZ0nUDXrHgD2pwHvEBdA2wE3mkh1i7gPnAnImaaxsuKukc9q860uDCaZ0Y9o+7uup//Q51Uf8vQ6XFcUd/tut8AqDvURwU7P89D9eOm+TeaZqqbgGvARNNEEvkb2BYRt1MDJBugvg7cpsCCZRn+BDZHxD9FVdWvxtyaz9QT6jZHo0FTnTXqdvVXdW6MZvZ9g8VJrVIfVCQyq+7PqPv5c43F/KWWm9Oon4z5Jb4poP3tGO3tubUXJnG4IoEnbdzyNbTXWj3POJQSL/W2eaui7GZEPEmMV5uIeAzcrKh6OyVeqgFvVJQ9SoyVwsOKsjdTAqUaUDV8lvyQUaWVNKQPq8EWY01psw3QLljxd8BgQNcJdM2KNyDLllgCAqeAi8+vPwW+AFblFu6DAbPA3og4t6DsuHocOA+8mlO8D4/AkUWdByAiLgPf5RbvgwE/L1F3LLd4HwyomtfXqWuFPhjw/hJ1H+QW74MBS63jv84t3gcDptRf1Bc7y+qEegL4LLd4qWHwJHCJ0eeyaWDdovqDwD71OqNl7VYg+wkxKGPAkYg4PH+hngGuVmhPADsL5PMfSjwCPy68iIgbwI0CurXo6h2Q/XBWXUoYML3wQt0CfFRAtxYl3gGH1EngAqPPaNOFdGtRKpEDFDj1mUIf5gGdkmrAbKtZtMPTlEapBtxNbJeTOymNUg04TdkPIcsxR/VB7WVJMiAibgE/pbTNxA9NTokkoa5Wjzo6ENEVz9Tv1eTRrPGMTN3M6D8BG4HXmsaryVNG76FTEfFHIc2BgYGBl49/AYo6o3PMW70cAAAAAElFTkSuQmCC') RETURNING id;"); - category_id = database->get_integer("INSERT INTO categories (name, description, thumbnail) VALUES ('Pharmacy, Health & Wellness', '', 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAEAAAABACAYAAACqaXHeAAAABmJLR0QA/wD/AP+gvaeTAAAE90lEQVR4nO2aXYhVVRTH/0snp4/xo6YpMqd0xmgsgqgUayJTqomwHKiHopeilyB6CXqOHgQpepWJCAmlUoicocgg6QNCsvwgkzQdxTRLS2tqnBmbkV8P+9rcOXPP2efuc+5N8/xhGM45a6//f61z9t17r72lAgUKFChw4cLqQQJcIWmBpHmSrpLUImmapKaSyaCkvyX9Kum4pIOSvjezk7XWVpMEADMlLZf0oKR7JF0X6OqQpC8kbZL0gZn9mY/CGgG4GVgDDJM/hoE3gZv+6zgnAZgO9ABjNQg8ijFgNTA9D+2ZuwDQJuljSfNTmA9L2i9pSNJfcn1fcr8FTZIuk9RW+u/DPkldZnawWs25AffJ/5TwtvYArwEPAdcDqRIOtAIPAKuAHQn+jwALah1nnMjpQH+MsF7grhy5bgXWAWcqcO0DmvxecgbwRgUxx4CHa8i5BDhUgbenVpxxQtqY/IN3FLixDtytwIEI9ygwt9bc5SJejQg4A9xbR/5FFV7AqnrxC9gdIX+vbuTjGt6JaNgV4idoGAQuj9waNrOREF+hABolXVp+z8x+r6eGAgX+B0g7M2uQ9KSkxZKaa6ooO05I2iLpbTMb8xl7EwC0SNos6Zbs2uqKXZKWmdlvSUZTUjhap/MveMlpXuszSvwCgHa5VVddKkc1AJJuMLP+OIMGj4PFqk3w/ZKOlP5M0pzSX1vOPCYXQ3ACOnIUs19Sj6SNcW8EmC+pW9Kzktpz4g1fnwAbKqy8qsUJ4Dngoip4pwHPAydz4F+fJQHfZiTfS4YVItAOfJdRw85Q8ilkK25+BczwcMwCZnlsZgBbM+gYAtKMdpOI52YgPQxc4/H/Im4ZPQa84LGdTXLpzYfqy/JAZwbCJR7fTbgixlmMAomFUGBpBj13xvlN+jSu9uQoDr1m9rnHpkUTR6AGuR2jWJjZp5I+DNQU6zspAS2BZC8HtkuDlwLbBSXgygCifjPbEdAuFcxsm6QDAU1jY0lKwMUBRL0BbapFX0CbS+IeJM0EQxKwO3oDeErSCo3vBMcJWgsMl10Pys0a3/JxpEBsLHkn4OfyC6Bb0pqUbTsr3OsGjpnZprJ7RwN0xcaS1AV864RK+CNyvTDARxS3ezjSIHYanpSAkCpvdOR4X25DNBSjkj7ycKRBrIaktxwifHb5hZl9A3TKrfAayx7NlFvxlaNH0kDZ9WlJfWa2PYkjJYISMBRAtFAukH9RGhYnDI3APE1OwCspt7oXBeiKjSWpCxwPIFoBTA1olwol38sDmv4S9yApAT8GEDVLuj+gXVp0KWyCdjjuQd4JkKSV+JefpLw3/tD5XBmoqfpYgEbgdODq62mP76lMrPYMAInzDuCZQC0jwLSqE1Ai3RZIegqIjt9R3/cBPwA7gcR+DdyBK2yEYGtQ8CXi1wNJwRVFMh+YADpwZ4FCsTrJv6+vbs6gfY6kr8lwZAboktvmujaDjk+CW+IqNyMZsg+u7LUBd5wuLW8r7uvLeu5whKznCYG+jCLOYhh4F3gcd3Y4ytMMPAGsJ7+Tpht98aXZHF2mbF0hDqfkdoYk113SHI6sFkvN7LMkg7Tb41vktpjOJ3xpZnf7jNImoEPSdiVUVs4xDEm6zcz2+gxTbRiY2R5Jj0k6t4+rOwxIejRN8FKVO79Aq6RHNLG8dS5hUK4sf8RrWaBAgQIFChS44PEPMB69xeBgPgAAAAAASUVORK5CYII=') RETURNING id;"); + category_id = database->get_integer("INSERT INTO categories (name, description, thumbnail) VALUES ('Tools & Home Improvement', '', 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAEAAAABACAYAAACqaXHeAAAACXBIWXMAAAsTAAALEwEAmpwYAAABXUlEQVR4nO3ZS0oDQRSF4WtwB451Az424QIEwQX4GOoiIg5EJ0EXYISMegO6EjfgRB05iRPRX4q0EIhCNanursf5oCcZ1T251VT1NRMRERGZAwyAa+CN+Lk1Xrk1WyjACek5ChnAPekZhwygIj2VAgiFxDsAOAUugO1SA6jq376BW2C11AB+3ZQewFej7UB+ATjD0gOYlB5ApQBKOQoToAOOSc9hyAAG9RXzlfi9AJfz1+GlA0idAkAdUGkLLNI7wGIBrAFnwF29X0M/z0sdhdsE7APvdG8YQ/G7wGcPxbvr8Fbfxa8AT/Rj1GvxDrDT0z8/avxJrA3A3j+LbOMlOAHOgU2LBXDwV/VWChQA6gC0BRZZ7oAN4BH4oKv5fkyYFd/tfD8mwLTz+X5M8BfPdTUkBeBPHWA5Qh3gTR1gOUId4E0dYDlCR2Eems73swKsMwth6jvfFxEREbEO/ADiFbekRtGUMAAAAABJRU5ErkJggg==') RETURNING id;"); // Hardware - Heating, cooling, flooring, paint, etc. + category_id = database->get_integer("INSERT INTO categories (name, description, thumbnail) VALUES ('Motor Vehicles, Automotive Parts & Accessories', 'Auto, Tires & Industrial', 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAEAAAABACAYAAACqaXHeAAAACXBIWXMAAAsTAAALEwEAmpwYAAAFjElEQVR4nO2ba4hVVRSAj1ia2mvGLMseakX0I7LQ+tGP/gVSSY9JA6UyzXLCAqnwkRmlZkUQURYJzWgjRlYaCSVB6WQhlNjLmXLKXhBlTS9LM535YjHr3k7Hvc/ed99z7r0D94P7a52z7tr77r32WmuvG0V16tSpUyc7gOHAZOBRYBXwItAGzALGAsOAa4AHgBaVrwGagTOBo4GrgOX6fivwEDBJZFGtQp/xzwN/8x8HgCeB04BTgKeBfTH5IeA54AyduMeBP7Dzuz4zPKoVgAHA3cD+hLGfARfoM7cAfyXk3wIXq/xqoBt/fgaaqj32CBgMvGQw8HVd6ufp8k7yDjBCdSwmjF7ZRtUc/CDgNYNhHwELgQ8thn8AHKs6llI+y6s1Ac8EGNsDnKvvi1PMitsqPfgbyjB2O3CpwWeUgzjbCZUa/MnAL2UaLPs3az4Vn1SJCVgdYNw/Ae98DszTzy7PdxbkPfhxuo9N/Am8a5FdCWwtYfDr4kGPBkem0ybJb8AJeU7AOsOXfgPcDBwDrDTIt+m7DcBuj0FslhPGcups8Xh/WV6DH62RW5LZKh9hcWxTVC5xwa8eAY71FwRO9AiYRD4sjwl40PBle+VM1zB3vUEuzvIofX8mGRxnmjO4mJlHuLvb8EWS5CzS/W+iLabD5QM6gIEetgzUZ9Noz3oCJgR694Pyq+oKsTnP/20lT3uaPY7ZU7OcgEWUxw4P7+2d6qrDlcwwjVuznIB28qUlwCapFaSxIavBH5nI4U1haLnR3uQAu6Y4dP4kviuLCRhv+YIejQqvsMibNO53IUdrY4BdDepj0jg7iwmYbvnVC1ndVMughupe3egw8v0ybJPUOtOVdRhaj0uyKyZfYpDvjMnnZL3/Y7qlXpjGfaG6i1hi8E0OI4oOSGt4adwVBQLc49C9JlR3EUuC82xM/oZB/kRMbqoaxZkYBQJc7tC9OVR3EaDLoPhhlQ2xyBfG3t/mMHJcFIgUXR26O0N1F7EkHwuASzRnN7Ffg6dBKc8UGBuVV4pPY0+o7iKGcnah+uIKbdGYXRKmNILzd81A09gbqruIJQXOksNy/xLL8mkcDNVdpD4BGLeAa4l/VcIKqPkt0O1wcl8Y5PP0MvN7jwmoeSfY5TjmttvqchLjA1/392NwqyPQedMgXxGTb6piIPR2XpXgDTG5XG8n2VhCvJ5nKNyWVzK0MyY33e5+4pBnlQytqkQyND0l3W2wVIukSeJ+YCRwXX9Phy+0KJ+qTRBpHNCLjjSkqNEQYFejR5B2Vp4lsV6yI6Qkdr1D555MSmIVKoq2Rtlf0q4vVacV4F7Hl31Z5gTkURaf5auvnMJor0aEp1u2hJwAr3pmjs0l2HO7Q5fYMspXn+/VmOlXXuuoHK2MhawfO4zuLOFqTJ7NtxKURBsbTZefQ1R+p0Euy3RoCW01szO6HJ0RZQ19zYymOvyNKh9puR7v0PtBudr+wWF4d6F9zmLDSR7tOflcjwva1prkvZh8RYph+/RocrHF0iAx2PM0WhLlBXC+xaFdFlslIf1ASV4WT5/w+q94nib5ttFiPn+lRnhExjGDpOHzgTssKbmJ+bkOPrbXTUWSOY4tkDcdhW6U3AGmBYbGO1Juk8tBkq/xFRl8AeApD6OSSL/fOVID8EikSiG7qK/EJEmiPBOd2lUmPURJ3lKPPsAjxPZhaVQt6CuKvmA5yho1ajMVLdoL3hqYG5hZyjuLqzb4AvpLzjWkzLLcL9JnZhiSl67YHyomAt+VMHjp/rg2qiXoW/ItiWjwkPqK0ZowtSR8Q4/WFEcBx8mlK/Cj45x/LKSjpGLQt/SbdDCtGj2u1UbJMcDx2t/ziMYUhT9N3SStbbqtJumfplarjmXab1y7f5qqU6dOnagf8i84W5AagP7l2QAAAABJRU5ErkJggg==') RETURNING id;");//category_id = database->get_integer("INSERT INTO categories (name, description, thumbnail) VALUES ('Motor Vehicles, Automotive Parts & Accessories', 'Auto, Tires & Industrial', 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAAABmJLR0QA/wD/AP+gvaeTAAAA1UlEQVRIie2UvQ4BURCF7yxRIqIiosJjewLvoFlBI5EoEI+gFrF8milurp87N0gknGoz+51zZopd5/76agENYAjsea4TkAO9lPAmsIwEhxqnbL5Q0wroRviqsgdLeB2Yq2ENtAyeTPkiBtaAmcIbS7i5QM+cKrgF2pZw9YqlYKTQDuhYwz0/wDmciwccnXOV1OBAFxEp+YPMe341/K6yOPKayimwiEg4A/CR8P13XRBsa9LHL/ALnn/mNt1k+AWzNxQ8zgAGwAQoEn/RqCcH+m9Y8td0BU99R2bmnxZMAAAAAElFTkSuQmCC') RETURNING id;"); + category_id = database->get_integer("INSERT INTO categories (name, description, thumbnail) VALUES ('Beauty & Personal Care', 'Cosmetics;Health, Beauty & Personal Care;Hygiene', 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAEAAAABACAYAAACqaXHeAAAACXBIWXMAAAsTAAALEwEAmpwYAAACPUlEQVR4nO2bP2sUQRiHXzQQ0ZDUgiKSKkQFEVSMprFKUBCiXyB+A8FCFIxKUgf/1CpcCDlioZV2iaCVtYX4AQxaREVBk/jIkCHIMXuJzO7M7M77wFbHzu+d52bn7t7bFVEURVH8AU4Dbc/jAXAJ2CV1A7hCebwDDknGAgwfgH7JWIBhSjIX8F4asAn68EvqDp5I3UEF+CF1BxXgh9QdVIAfUkeAfcBRYAx/xuxYeyV1gIvAC+An5WPGfA5ckNQABoHXhGMJOCwpAIwCXwjPZ+Bs7MkPAavE4xtwJNbk9wAfiY/pG/TGEHC9oKAN4AlwxnwalPSJMgI8Bf4UZF4rZ1Y7BNgNrDgKWQMmpCKAyzajk09B+4jAuYJ34laA7NsF2SNVZ28B3HQU8KOMJb8dQF/B94wbVWdvATxyFLAkgQCWHfkPJWABC44CFgPmLzryFyRgAW1HAe1c8iV2AbHzpYRubxWoANEVEAj0EkD3AMrF/MiZBybtMQes57IJ/gbGHRnn7WuNFzDVJeduDgIOdMk5mIOA3m26To0XcLxLzokcBLS75DzLQYDhMTDwz/gDtq9IUwTMA1eBadvKdmHa6y+BV8DX/xw/aQHTHeeeLGhs+pKsgP2O89/kLuBtTgJmOs49ldslgG2imk1wBvhONSQtIAQqQAKugDnSoxVSwD3S405IAcfs3+CpsBH8Rgk2H29JhdmgkzcAPcD9yCvBZM+aWiQWbN7DZ1pXrRIemtrp0bKZw9EmriiKIg3gL31Q7N9UOef0AAAAAElFTkSuQmCC') RETURNING id;"); + category_id = database->get_integer("INSERT INTO categories (name, description, thumbnail) VALUES ('Drugs & Medications', '', 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAEAAAABACAYAAACqaXHeAAAACXBIWXMAAAsTAAALEwEAmpwYAAAC6ElEQVR4nO3by4sUVxTH8Ts+QAUFYYIzEtC9joriRrJOVq4kW0MIGh0JulCI+FgmvhbR1WShZi36B6gkoKIEdOljkWXMBDOObrJQfHzkMtWh7ame7sxUVVd31Xdf99zfPefcx7m3QqipqampKR8YwS4cwmlcxHkcxVfYgKEwSGAF9uN33fEnzmB96GewGPvwj/nxBhMYDv0GPsUt2fAMn4d+wUwexzDOkrcYD2UHWzHdhaCnuINruIFHeNfFd+P9LP5fnMLmNt+vwV780SESvgh9KP5KFNhlW0txEK/nmBOG+0X8exyfz9qOzzDVpt2JUAawBc/nEP/dAtvfgVdtlsh12SnJx/MHMrIT0yGNH7Jov5Seb7G1BE9S7DzMykYpPd9ic08be+sHXnzTISptn7A7DLr4BnicYvf7MGg53w7cTLH9Uxh0zzdIts2tXAqD7vkGuJvShx9DD7e3R3IzPrsvQ5hM6cPBXp7q7mFVLh1Ij8Q0dvbySFvYICT1w1biFnllkTnfjgdYnWlHPu7TaHKUbuV6Lz1fSCQkuX9VOnvLIj63QcAJ6fyNZb0M+9zTIakOxaU2jW/K5PnMIyFuruYQ/ysWZSF+LAfxCx6EDuJjtXk0q7r9XzmJn/cgdBD/AhuzurG5nbP4/z0IXYjftmDxkVhbVywdJ8YOE95LbA8ZXlROKZ62kVCY55uM9YpZg1Co+Aju6y3/pUNhYd8Aa5WDGAlHCvV8BF8qP/mIj+CwcpN92DeDs6oqPoJflDfst4a8wQVV9HwDHFNV8RF8rTxMFxL2zWCTKnq+QSwiFHAELp/nm8G5yoqPxPv05HlJNcI+DfxcOc83g0+Sp2bVE98gPjbs8mXm4IlvgG8rkfNzkVRkso6EyXbPYUuJmXTIak74LRZeQr+B4WR1mO8S+SwpcS38xqYE+4RzyXP2TrxPfoOJv8MsD4OEmWvpscSrsZByOXnxHaPkZFJeG+l1P2tqampqwmw+AO2Rw/WiIGDFAAAAAElFTkSuQmCC') RETURNING id;"); category_id = database->get_integer("INSERT INTO categories (name, description, thumbnail) VALUES ('Sports & Outdoors', 'Sporting Equipment;Outdoors & Camping;Hiking;Hunting;Fishing;Biking', 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAEAAAABACAYAAACqaXHeAAAABmJLR0QA/wD/AP+gvaeTAAAEPElEQVR4nO2a34sVZRjHv0+aGbEJ/boo291ItwQTFOumGyP6edWvTQsCY8OLCrzrOnRRwrv+AV2NfghhGwTSTd0VsSW1IpFFF5ViJbWK7rrqfrqYM3Cc887sO3Pmnfdo53N1ZuZ9n+/zPPP+mHnmSH369OnzP8ZiO+ACuF3SS5LWt059J+kDM/s7nlcNAWwGztDJDDAa27+gAE8AlxzBp1wCHovtZxAAA34sCD7lGNCTU7crgHUewaesrUv3uroM1cBwibb31CXaSwn4t0Tbf4J5EQvgxpzVP8sMsLwu3Z4ZAWY2K2mPR9N3zGwutD9RAJYA7xfc/feAJbH9DArJdvgy8CVwtjUtvgC2XDPbH7AM2AH80bqzl1v7+5bYvgUHWAMcKRjmb8f2MRjAg8BfHiv99ti+1g7wCH7bXDolnovtc20AzwMXPINPOQc8FNv3rgFeAOZLBp9yErgrdgyVAUaBixWDT/kKuCF2LKWpKfiUidjxlAJ4scbgU96MHZcXwDMUV3WqMg9sqtvfWh8tgfslTUm6qaKJI5KWS1qTc31G0m5J+yTNF9g5Z2ZF18MAfNLFHf4cGABWAr92OVqaL54Ct1B93k8A17fZuhc40UQC6qwHrJK0tEK/dyVtNbOL6Qkz+0XS45JO1+RbLnUm4GzJ9pclvW5m282M7EUzOyrpaUln6nAuOCTFjDJzd8zT7kZgKtQUqHsXeFbSx552j0p61Mz+9LQ9JGlI0jKP5tNmdsrHbu0Ar5C8yPgwDdwRxdGQAIPARyWScGtsn4MAbAK+90jCZ7F9DQawFHgDOL1IEh6O4V/pRRBYIelJSWslrWyd/l3StKTDZubctoDbJI1Lek2Sq7S9x8zeCqXfNcAwcIDiys4FYD/Jip1nZz1w3NH3wyb0qwa/DZhbZAi3M0vBPg8cdPQ52JR+2eB3lRDOMt5tAkLolwl+W47hBeAbkpeYidbvhZy2HXfCNwGh9H2DH8Y97A4BqxztVwOTjvazwGDZBITU903AAYexcQq+z5F813MN2X0VEhBM3yf4FXSutoeKxDNOZO/EHDDgm4DQ+j4J2JwxsIBj2BX0H6FzTo62XV8sAUH128mrBzyQOZ4ys599HTCzn5T8ubGddb79m9TPS8CdmeNjvuIFfbI2i2hMPy8B2QpNlbpBtk9H1aeAxvTzEnAyc5xXpi4i2+dEib6N6ecVMX/IHG8EVpvZcR9l4D5JGwpsfu3o1n4utP6iBm52bEOTJbahT7vZhmLrp4YmHFvVriInWuK7Hf32lhLvAX0BQySPkVkmgRFH+xFH5gHOA3dfbfqp0TGHQUgeMqZI3r33A9+S/zLyaiXxHtBPnRjPMezDzq7Ee0A/dWIM93DM4zywtRbxHtBPnRgkWZiKKjNzwF6qzrkI+lWKogOSntKVRcnflHzpOWxmZb8RXlX6ffr06XNN8R8f+FLdSRxelgAAAABJRU5ErkJggg==') RETURNING id;"); - category_id = database->get_integer("INSERT INTO categories (name, description, thumbnail) VALUES ('Real Estate, Property & Housing', '', 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAAABmJLR0QA/wD/AP+gvaeTAAABNUlEQVRIie3TsUqcURAF4G+DgqUgKZSAEJ8gwr5GumghFqlCCGKVVxBC0gbSaroQMBb6AipbaJpI0CIWqSVVBAsJx8K77Lr8/+8uSCCYAxdmhplz7tw7w3/cglbXSJI7JW61WvDgLkmrUCWwh6d4ifMS28VqX84rnBb7NxbKWaxVSg9PkrxPspRko8SeJxlL8rP4s0kOin1W6g+SHHZJmjpo4yNeYBl/sIMlbDW8xmd8HwxWCXzACp5hG/sYxyQ2GwTmMTeMwAzGSsEPfMEjXKCDXyXvBF9xWfzHmBgkqxrTDt5gCu+w5voD23hdOlvHWcl/W+JdfKI3pv/+HoyEwREcBn9/k5M8TPKtb/Fqb5ybOE4y3ahWRd4vUOcPJVJHPkIHzSJJjmoKRhVIkqNuzj3bg/uJK/g6RdFvw8VbAAAAAElFTkSuQmCC') RETURNING id;"); + category_id = database->get_integer("INSERT INTO categories (name, description, thumbnail) VALUES ('Real Estate, Property & Housing', '', 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAEAAAABACAYAAACqaXHeAAAACXBIWXMAAAsTAAALEwEAmpwYAAAC4ElEQVR4nO2ZPWgUURDHN1FEQQ0RIxiRoE0g2IiNgfRaGBRjSLAINn4UVkGwUJtUIY2VgYhgJ3jgV6EgEQIWNqIighFUEFQQsdDgF8b4k4ezOLfs3u3d7t7t5uYHw3HzZt6+98+8j9t4nmEYhmEYhgIosfwoeXHBBMAqgOWHLQHP9oB0NsF5YBToktiNwBjwpkLOe2CX2JGImN8qZrBKOe9TsWE2l9USeAasj8jpAl5H5E2ruJXAp5CYRRWzrYoAPVXGfyMrAQ6omL3ARfepfCMReXsC/V9OSwDgKHAsYE+yEqBb2lcD38X3DVgr/nUhOZ+BVYH+96cogFs6cUhFgC3SvgL4qPxXgRmx4ICuqH4H5HMN8LWIAhxSMTuB+zEePCLxG9zaVPnXiyjAS7frB2J3A7cj4n/6myZwEPjiNkH5PpaGACFjP5elAMhxNxiS4yb0i3LuqPYL4utXFbFYRAF8HgNDgbwpyjmu2l6I76zy3UthCdwFZpW9avRNcEblbVX+JWCzatsutkn5ThZpD5hV7e6s1ehJ+cvgQYznOMH+FEWAd0C7GuCC+D+oja1DxZ9W/Q2578r6VNvDogjgGFUx3e5m6N8NxHeY//Qq/1vKmVJtZ0IEaAc6A9YTIUBniN0kIwEWgtdalbNDqsHxXPl7Q/p5pNr7ggJE9N9Rw2+BUtab4BxwSu7+J4Brcub7PAUmxW6F5C/JieHH/BDfZAU7r/Knq8TOZy1AETEBPKuAmGBLgJbfA8alCrKy4L0A8WX5zHEvL5D0nC46mABYBWBLoMB7ANCmXnFVJGkFxHkGcMn/qZ6ryTsaJEBjRKDGyTdYgGxFoI7JN0GAbESgzsk3SYB0ReDf5N2/vOoios9+YDhg/SkKgIy5rWl/eZ9EA0gmQLJKIIXJ50CA+kQgpcnnRIDaRQAmUnpwXgRwTDTrhchwQksLeyfoNakC8oIJ4FkFxIRWXwKGYRiGYXitwF+FAy8M28uvbgAAAABJRU5ErkJggg==') RETURNING id;"); category_id = database->get_integer("INSERT INTO categories (name, description, thumbnail) VALUES ('Luggage & Travel', '', 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAEAAAABACAYAAACqaXHeAAAABmJLR0QA/wD/AP+gvaeTAAAC9klEQVR4nO2bPWjVUBiG38+K2C7iLzhZVyc7O/iDojhY3HSxautYFQQXF0HB2cEiKCLqIEoFCwoiaKU4V0REKFpxrFY61EFrfRyuQr3kJLnJPTkxNw90yTnN95435+R+J/ki1dTU1HQw5uOkwFFJ+5sOz0gaNjN8xCwVwAGiORFaWyEA3cB8hAFzwMbQ+goBGHXMgjuhtRUCcMRhAEDz/aF6AGuABYcBU0B3aI3eAZ7FzIKLofV5BzgVY8B3YEtojV4BNgG/YkyYALzkIqUBmIwxAGAotEavAOcTDJgFNoTW6Q1ga4IBALdD6/QK8CGFCXtCaFtWUJyxFH1GqGpuAOxMMQMALoTW6gWgC/icwoDCc4PIJQAMAveW/A3mCWJmi5Iep+i6QtJVQucGwLGmK7MAbMt5zoMplwF5Dc8NsDlC1CdgXY5z9gDfUhoQPjcAPkYIe0SO6Qk8bGEW3GrneLKIvekQdibHOY+3YADA7naOKYrlMW3jkgYijl8C5iV9zRBvZYv9R4BzGeL8kDRtZq+TOjqnM9AraTpD8DLxTtJJM3vq6hC7noFpSb3tVlUwi5IOm9n9qMakVHi8/XoKp0vSNRy/YEkGvGi/niCsknQoqqETZsBf+qIOFrUbLAORr+SSDNjhQUgoJqMOJhmw3YOQEMxJuhvV0AkzYFHSkJnNRjU6DfiTCP3vOcBbSXvNbNTVIS4Vdl39BUnDypYKr5d0pYX+U5KypsLvzexNhv9t4GkzNFi2zVCcWB/b4bEWBh9uO0wHPRBx3QSb1/9PNTYUX3LE2iepJ2Xfs2Y2kyNWalw3wWWSlu6enpjZy5yx+lP2m5B0I2esckGJH4sXArAr5dqv7IuRyykGX92yGUr8ctQ7QF+KwVf39Th1gQSvEgyobokMjSKpOKpdJAWcjhl8NX/zlwI8jzGg2oWSwFo6uVQWGIi5+h1RLP3AMfjql8vT6R9MAP2Oq1+6T2biHormYbX+fZ4gNT6auu4pXk1NTU1NFn4DHMdp490i7dkAAAAASUVORK5CYII=') RETURNING id;"); category_id = database->get_integer("INSERT INTO categories (name, description, thumbnail) VALUES ('Business, Industrial & Scientific', '', 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAEAAAABACAYAAACqaXHeAAAABmJLR0QA/wD/AP+gvaeTAAADDUlEQVR4nO2ZvWsUQRyG30lSaCJiVLSKYAoxooJIIIWiWImFYJcEYmnhPyD4B6iNpcFCUAjEkAQsRC20ERTxA4JFijSCImgR/IhJUHPJY3GJKN7uzsztZO5knubgbnbe9/fu7MzsnJRIJBKJRCLRwABbgBd4ENt73dRTvE0AbY5mdknqltQpaVbSlDFm3q80K71OSQ8lHQ6lYWOiFTgLvK4R8BJwBzgUQLcTeOV7521HQJGJrcAjC50KcKGk2ksr3iaAzEcA2CDpnqQ+C8+tkq4AFWPMVftS/9JrkXRK0vHVz56MppOSxn00XA1d8gj8J7DfQ6sLeG7R/xjgNG95QXXoL3gEAHDbUWsTMN0wxa+aGvIsPhTjBCq+JeP7eMvOv0xIGjTGVEJ0nhXA9hBiHjxVwOKl7AAWQgk60iepP6RAVgAzIUUdaJV0CxgIJWBqfQn0SJrO+r2AKUmXHdp3SLomqT2nzbKkIWOM0wpTF8B9zxn7tIfWGYqX3QowGKLWLFPdwFfH4icBn1EjoAe4CbwFlhslhKPAF8viPwB5w9hF9xgw7xh+Tcowsxu4C6wUaK0A+0qof023lBDK8iNgDzBcoHe9NEGVE0KZfgS0A7M5eovAtpI1jwBzoQLI2gfUxBizKOlGTpONks659Gmh+UTV1+NvZfb7u3/XC4AuSW+UfZbwXlK3MWapHmM1dA9I2ut6nTFmokwfa2bGCkZesJ1bQwD0FgTwMrbH4ADPCkKwOUprXoD+ggDWb98eA6ANeJcTwBLVCbOhcVoG/2T1kGI4p0mbpPO+/TcFVM/v83Zqn4CO2D7z8B4BkmSM+SxpJKdJp6ShejQaHqrvCFmvrwAzVP/0+H8BHhSsCCdje6wb4CAwCnwsKHa9mQceAwN4HsbYFD8I/Ihaph0jlP24Ub3zzVD8Ghdd6iscMsCopGZ6uZmTtNMY892msc1wOVGfn3Vns6Re28Y2Aezw9xINa882AYSZWcNiPRH+3xsUC1IAsQ3EJgUQ20BsUgCxDcQmBRDbQCKRSCQSiUQsfgGI7rZpsWTP5QAAAABJRU5ErkJggg==') RETURNING id;"); category_id = database->get_integer("INSERT INTO categories (name, description, thumbnail) VALUES ('Illegal', 'Banned and/or prohibited items', 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAEAAAABACAYAAACqaXHeAAAABmJLR0QA/wD/AP+gvaeTAAAFDUlEQVR4nO2bzW9WRRTGn2ljEaVFSghl40fStC5cYcNOSXVjQmo0fqAsWGI1oWyQmFQihkDUFQQ2xqVGoqa6smGPfwENkmoMpUHBapR+CG2I/bmYt0rrmfs5975NeJ/lvTPnOee5c+ecOzNXaqGFFlq4h+HqIgK2S3pCUr+kRyQ9JGlT4/aCpD8lXZX0g6QJ59xMXb5VAqANeAY4C3xPflwCzgCDQFuz48kMYCvwHjBdIOgQrjZsdjc7viCAzcBHwHzEwNdiHvgQ6Gp2vKsA7AOuVxj4WvwCvB7D91KTYONJfCLp1RzdFiRNSbohaV5Sm6QHJfVI6pV0fw5b5yS94Zybz9EnDoDHgR8zPK1FYBw4DOwkYULDT5z9wAFgrNE3DZNAX52xC9gF/Jbi2AzwPj79FeXZAozgJ8E0roGYMSY5tYvkie4WcATIM5TTODuAg8DNBN65ykXAD/ukJ3+BCocj0IN/nZJGQjX8QBfJ7/wyMFQJ+Wo/HDDa4LMwCXRWQfxFQvArWAT2RCe3/dkP3An48Xlssn0Zgm+WCKGRsDcWyWbCRU6IvE4RRgM+/EyMVwFf3lq4AAwRztW1iICvHUIT48myxrdip7xbNGZbYM86EKEHO0XOUeYDCjgWCOztNe3WgwgjAf6jRQ22YX/SzgAbjfbPAbcDTiwBz5eOMtnf+4Apg3uKIusJwLOBYI4l9GnqSAAOBbh3FzF21jB0m5TavpkiAN0B7tNFjFnLWOMZ+zZThG8Mzom8RrZj5/jDOWw0RQRg2OBbBrblMRJ6/3fmdKZ2EfAfbBYG8xh5yzAwT4HZtG4RgHbsbDRstQ8F9Khxbco5t5zXIefct5JekrRk3N4gaSymCM65vyX9ZNyyYgoKYK26Xi/hVK0iSLI2VTZbDUMCbDKu/VXYHf0rwguSFo3bGyR9TbxiyVoktWIKCmCh9Daac+68pBdli9Ah6atIIpC1YUiABeNalFWWhggvy34dOiR9GeF1sJ62FVNQgDnjWvY8moIa5oQdxrVZq2FIgCnjWm+RNBhCVSIA7QpksTxGQoVQf16HMnBFrROAgYCtXIVQqBQ+kDvCbHzRRMDvSaxFvlK4YeiSYWgsd3TZ+aKIAJw3+l8s4tCZgCNbchvLzllKBPzSmNX/VBFnBgOOjBSOMBtvYRGA44F+TxdxxAFXDGPTQEepKNO5cy+vAQ9gb9tNAcWKOPyxFAsHS0eZzp1rJADvBNoWWxRtGO3GLy2vxU2gp3SU6fyZRMCfKbBGzCxl5yzgg4AD49RweiuDCEPAd4H7J2I40IU/k2NhNEKMWXxIEiG0PXeNWLvEwGsJ5PujkKT7kCSChVdiO3AuQHRnHYrwaRXknfjDB6GRMErRdJPPjyHCwx7gMmAufsQg78NvjYUwToXZAT/bhyY8gF+B3qr4V5wYwE6NK7iJ36iMViwBG/F5PlQcgU95T8biTHNoIGUkgK8YD1Fiexpf2x8n/UjejdqCv8u5PsJzwt1YxG9XDeM3LdoTbLY3xD2C/6rLMuFdpsSwL3tUtlPSx5LynNtdkl+3Xzkqi/x64w5Jj0n639Z7Aj6T9KZzzlzvqw3AXvyZnLpwjdh5vizwafIkyRNkWcwCJ6jiHGAs4D+gjmKf1iiKK8C7VLgYEx349YTdwGlgguTiZS2WgYvAKeApKiyw6vxpapv++2nqYUndWv3T1B+SpiVNyv809XtdvrXQQgst3LP4B1dCXq0t0BR7AAAAAElFTkSuQmCC') RETURNING id;"); - category_id = database->get_integer("INSERT INTO categories (name, description, thumbnail) VALUES ('Miscellaneous', 'Others;Non-classified', 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAAABmJLR0QA/wD/AP+gvaeTAAAAy0lEQVRIie2UQQ6CMBBF3xjPACs9gbdgw0m8pSvFC8gJYOch/C5sk6ZpQ4EYNrwV06Z//h+gsDMHSa2kUcsZJLWhpkUNBuC00udoZudcA5UomJml1v35cP+wzGQ5e4PtGxyXHCr92mCrEZkDeAKPoPbUQO+ee6DO/RtTCT5Aahw34OLEGzN7S6omtH6zdXSS7sF6J+ke3TsvLyqpcrXi95NLEDuP69i5T1SWIHaeSJhyPitBzjkACec90KQE/n5dxwmuwLhG3GnslPMFJKbl/7w5wk8AAAAASUVORK5CYII=') RETURNING id;"); + category_id = database->get_integer("INSERT INTO categories (name, description, thumbnail) VALUES ('Miscellaneous', 'Others;Non-classified', 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAEAAAABACAYAAACqaXHeAAAACXBIWXMAAAsTAAALEwEAmpwYAAACbUlEQVR4nO2aT4oTQRSHS5mlbkTRCyg4oxdQZ+sJTFzPKUTcCK7nAEJWji4mzBHcZDMqguNC8ABKZlZDsjIb88ljSgjdVd1J6k+69H3QkG5eNfX7db1X1Z0yRlEURVGUTQBcMqUA3APeAWd0l1Pbx53Y4p8CM8pB+tqLJf5+YeL/MosyErgYUqVyEMOAM8rlNIYB80yd/SB5G3DsO+45j2FALoaB/RQTaqgBoZAPHQEm7EFpCqA1oI7WgFDIh9YAE/agtAagNaCO1oBQyIfWABP2oLQGUHgN+CFpEHAcl25AEtSAUCgcNSAU3Bw3FKOvwAR43xAjBa+tCI5aHu5ok0Ww54l9tfAV+RzY9cRJh73rAOAW8L1B/Ovq/4q51wE9R9wdxyf0L6sasI74rhjw2BE3WcWAdcV3xYBrdtgvcriCAaN1xdt79jdqgCA5L8PeFsFD4LpZ3gACxHtHjkllAPACGNvjeUP7G8ARMAVOrEnDHOJTGnDkuNb3tK/GntspMrn4lAZMHdcGnvau2EkO8SkNOHFcc6aBI3buaR9dfEoD9oBPC+cfgSue9rsLs4OIf9lSA6KJT10Et4CHwAP5bWOv2uI4kH1FlSlS1gm37fkwh/ikBph6nBjyecm08K0DQsTnnQZNPe6RI268ggHeb4LATeBbg/jBxhdCXKRDlZ+hBiwp/nIXlsJblcIoPAsxYJV3g5QGzB333fdsVNqzU9zUivRtaJINUW2bpPpL5Hy/ZZPU7/99m9w4hgFvKZc3MQzYKXSr7C9gO9gAwebarDDxT0xMgG2bDvL621WkbwfA3ajiFUVRFEUx/yJ/AKs6hY9h/vEkAAAAAElFTkSuQmCC') RETURNING id;"); //category_id = database->get_integer("INSERT INTO categories (name, description) VALUES ('Collectables & Art', '') RETURNING id;"); //category_id = database->get_integer("INSERT INTO categories (name, description) VALUES ('', '') RETURNING id;"); // NOTE: Categories also act as tags to be used for filtering specific products @@ -247,7 +248,7 @@ std::string neroshop::Backend::getDatabaseHash() { //---------------------------------------------------------------- QVariantList neroshop::Backend::getCategoryList(bool sort_alphabetically) const { // Do some database reading to fetch each category row (database reads do not require consensus) - neroshop::db::Sqlite3 * database = neroshop::db::Sqlite3::get_database(); + neroshop::db::Sqlite3 * database = neroshop::get_database(); if(!database) throw std::runtime_error("database is NULL"); sqlite3_stmt * stmt = nullptr; // Prepare (compile) statement @@ -284,7 +285,7 @@ QVariantList neroshop::Backend::getCategoryList(bool sort_alphabetically) const //---------------------------------------------------------------- int neroshop::Backend::getCategoryIdByName(const QString& category_name) const { - neroshop::db::Sqlite3 * database = neroshop::db::Sqlite3::get_database(); + neroshop::db::Sqlite3 * database = neroshop::get_database(); if(!database) throw std::runtime_error("database is NULL"); // Execute sqlite3 statement int category_id = database->get_integer_params("SELECT id FROM categories WHERE name = $1;", { category_name.toStdString() }); @@ -292,7 +293,7 @@ int neroshop::Backend::getCategoryIdByName(const QString& category_name) const { } //---------------------------------------------------------------- int neroshop::Backend::getCategoryProductCount(int category_id) const { - neroshop::db::Sqlite3 * database = neroshop::db::Sqlite3::get_database(); + neroshop::db::Sqlite3 * database = neroshop::get_database(); if(!database) throw std::runtime_error("database is NULL"); // Execute sqlite3 statement int category_product_count = database->get_integer_params("SELECT COUNT(*) FROM products WHERE category_id = $1;", { std::to_string(category_id) }); @@ -305,7 +306,7 @@ QVariantList neroshop::Backend::registerProduct(const QString& name, const QStri const QString& product_code, int category_id) const { - neroshop::db::Sqlite3 * database = neroshop::db::Sqlite3::get_database(); + neroshop::db::Sqlite3 * database = neroshop::get_database(); if(!database) throw std::runtime_error("database is NULL"); QString product_uuid = QUuid::createUuid().toString(); @@ -322,7 +323,7 @@ QVariantList neroshop::Backend::registerProduct(const QString& name, const QStri } //---------------------------------------------------------------- void neroshop::Backend::uploadProductImage(const QString& product_id, const QString& filename) { - neroshop::db::Sqlite3 * database = neroshop::db::Sqlite3::get_database(); + neroshop::db::Sqlite3 * database = neroshop::get_database(); if(!database) throw std::runtime_error("database is NULL"); database->execute("BEGIN;"); @@ -390,7 +391,7 @@ void neroshop::Backend::uploadProductImage(const QString& product_id, const QStr //---------------------------------------------------------------- QVariantList neroshop::Backend::getProductImages(const QString& product_id) { // Do some database reading to fetch each category row (database reads do not require consensus) - neroshop::db::Sqlite3 * database = neroshop::db::Sqlite3::get_database(); + neroshop::db::Sqlite3 * database = neroshop::get_database(); if(!database) throw std::runtime_error("database is NULL"); std::string command = "SELECT id, name FROM images WHERE product_id = $1";////std::string command = "SELECT id, name, data FROM images WHERE product_id = $1"; sqlite3_stmt * stmt = nullptr; @@ -429,7 +430,7 @@ QVariantList neroshop::Backend::getProductImages(const QString& product_id) { //---------------------------------------------------------------- //---------------------------------------------------------------- int neroshop::Backend::getProductStarCount(const QString& product_id) { - neroshop::db::Sqlite3 * database = neroshop::db::Sqlite3::get_database(); + neroshop::db::Sqlite3 * database = neroshop::get_database(); if(!database) throw std::runtime_error("database is NULL"); // Get total number of star ratings for a specific product unsigned int ratings_count = database->get_integer_params("SELECT COUNT(*) FROM product_ratings WHERE product_id = $1", { product_id.toStdString() }); @@ -437,7 +438,7 @@ int neroshop::Backend::getProductStarCount(const QString& product_id) { } //---------------------------------------------------------------- int neroshop::Backend::getProductStarCount(const QString& product_id, int star_number) { - neroshop::db::Sqlite3 * database = neroshop::db::Sqlite3::get_database(); + neroshop::db::Sqlite3 * database = neroshop::get_database(); if(!database) throw std::runtime_error("database is NULL"); // Get total number of N star ratings for a specific product if(star_number > 5) star_number = 5; @@ -447,7 +448,7 @@ int neroshop::Backend::getProductStarCount(const QString& product_id, int star_n } //---------------------------------------------------------------- float neroshop::Backend::getProductAverageStars(const QString& product_id) { - neroshop::db::Sqlite3 * database = neroshop::db::Sqlite3::get_database(); + neroshop::db::Sqlite3 * database = neroshop::get_database(); if(!database) throw std::runtime_error("database is NULL"); // Get number of star ratings for a specific product unsigned int total_star_ratings = database->get_integer_params("SELECT COUNT(*) FROM product_ratings WHERE product_id = $1", { product_id.toStdString() }); @@ -471,7 +472,7 @@ float neroshop::Backend::getProductAverageStars(const QString& product_id) { //---------------------------------------------------------------- //---------------------------------------------------------------- QString neroshop::Backend::getDisplayNameById(const QString& user_id) { - neroshop::db::Sqlite3 * database = neroshop::db::Sqlite3::get_database(); + neroshop::db::Sqlite3 * database = neroshop::get_database(); if(!database) throw std::runtime_error("database is NULL"); std::string display_name = database->get_text_params("SELECT name FROM users WHERE monero_address = $1", { user_id.toStdString() }); return QString::fromStdString(display_name); @@ -487,7 +488,7 @@ int neroshop::Backend::getCartMaximumQuantity() { } //---------------------------------------------------------------- int neroshop::Backend::getStockAvailable(const QString& product_id) { - neroshop::db::Sqlite3 * database = neroshop::db::Sqlite3::get_database(); + neroshop::db::Sqlite3 * database = neroshop::get_database(); if(!database) throw std::runtime_error("database is NULL"); int quantity = database->get_integer_params("SELECT quantity FROM listings WHERE product_id = $1 AND quantity > 0", { product_id.toStdString() }); return quantity; @@ -495,7 +496,7 @@ int neroshop::Backend::getStockAvailable(const QString& product_id) { //---------------------------------------------------------------- //---------------------------------------------------------------- QVariantList neroshop::Backend::getListings() { - neroshop::db::Sqlite3 * database = neroshop::db::Sqlite3::get_database(); + neroshop::db::Sqlite3 * database = neroshop::get_database(); if(!database) throw std::runtime_error("database is NULL"); std::string command = "SELECT DISTINCT * FROM listings JOIN products ON products.uuid = listings.product_id JOIN images ON images.product_id = listings.product_id GROUP BY images.product_id;";//WHERE stock_qty > 0;"; // image.product_id must be unique in this field to prevent duplicate listings! @@ -551,7 +552,7 @@ QVariantList neroshop::Backend::getListings() { } //---------------------------------------------------------------- QVariantList neroshop::Backend::getListingsByCategory(int category_id) { - neroshop::db::Sqlite3 * database = neroshop::db::Sqlite3::get_database(); + neroshop::db::Sqlite3 * database = neroshop::get_database(); if(!database) throw std::runtime_error("database is NULL"); //std::string command = "SELECT DISTINCT * FROM listings ORDER BY date ASC;";// LIMIT $1;";//WHERE stock_qty > 0;"; @@ -614,7 +615,7 @@ QVariantList neroshop::Backend::getListingsByCategory(int category_id) { } //---------------------------------------------------------------- QVariantList neroshop::Backend::getListingsByMostRecent() { - neroshop::db::Sqlite3 * database = neroshop::db::Sqlite3::get_database(); + neroshop::db::Sqlite3 * database = neroshop::get_database(); if(!database) throw std::runtime_error("database is NULL"); //std::string command = "SELECT DISTINCT * FROM listings ORDER BY date ASC;";// LIMIT $1;";//WHERE stock_qty > 0;"; @@ -671,7 +672,7 @@ QVariantList neroshop::Backend::getListingsByMostRecent() { } //---------------------------------------------------------------- QVariantList neroshop::Backend::getListingsByMostRecentLimit(int limit) { - neroshop::db::Sqlite3 * database = neroshop::db::Sqlite3::get_database(); + neroshop::db::Sqlite3 * database = neroshop::get_database(); if(!database) throw std::runtime_error("database is NULL"); //std::string command = "SELECT DISTINCT * FROM listings ORDER BY date ASC;";// LIMIT $1;";//WHERE stock_qty > 0;"; @@ -734,7 +735,7 @@ QVariantList neroshop::Backend::getListingsByMostRecentLimit(int limit) { } //---------------------------------------------------------------- QVariantList neroshop::Backend::getListingsByOldest() { - neroshop::db::Sqlite3 * database = neroshop::db::Sqlite3::get_database(); + neroshop::db::Sqlite3 * database = neroshop::get_database(); if(!database) throw std::runtime_error("database is NULL"); //std::string command = "SELECT DISTINCT * FROM listings ORDER BY date ASC;";// LIMIT $1;";//WHERE stock_qty > 0;"; @@ -791,7 +792,7 @@ QVariantList neroshop::Backend::getListingsByOldest() { } //---------------------------------------------------------------- QVariantList neroshop::Backend::getListingsByAlphabeticalOrder() { - neroshop::db::Sqlite3 * database = neroshop::db::Sqlite3::get_database(); + neroshop::db::Sqlite3 * database = neroshop::get_database(); if(!database) throw std::runtime_error("database is NULL"); //std::string command = "SELECT DISTINCT * FROM listings ORDER BY date ASC;";// LIMIT $1;";//WHERE stock_qty > 0;"; @@ -848,7 +849,7 @@ QVariantList neroshop::Backend::getListingsByAlphabeticalOrder() { } //---------------------------------------------------------------- QVariantList neroshop::Backend::getListingsByPriceLowest() { - neroshop::db::Sqlite3 * database = neroshop::db::Sqlite3::get_database(); + neroshop::db::Sqlite3 * database = neroshop::get_database(); if(!database) throw std::runtime_error("database is NULL"); //std::string command = "SELECT DISTINCT * FROM listings ORDER BY date ASC;";// LIMIT $1;";//WHERE stock_qty > 0;"; @@ -905,7 +906,7 @@ QVariantList neroshop::Backend::getListingsByPriceLowest() { } //---------------------------------------------------------------- QVariantList neroshop::Backend::getListingsByPriceHighest() { - neroshop::db::Sqlite3 * database = neroshop::db::Sqlite3::get_database(); + neroshop::db::Sqlite3 * database = neroshop::get_database(); if(!database) throw std::runtime_error("database is NULL"); //std::string command = "SELECT DISTINCT * FROM listings ORDER BY date ASC;";// LIMIT $1;";//WHERE stock_qty > 0;"; @@ -961,10 +962,20 @@ QVariantList neroshop::Backend::getListingsByPriceHighest() { return catalog_array; } //---------------------------------------------------------------- +QVariantList neroshop::Backend::getNodeListDefault(const QString& coin) const { + QVariantList node_list; + std::string network_type = neroshop::Script::get_string(neroshop::lua_state, "neroshop.monero.daemon.network_type"); + std::vector node_table = neroshop::Script::get_table_string(neroshop::lua_state, "neroshop." + coin.toStdString() + ".nodes." + network_type); // Get monero nodes from settings.lua////std::cout << "lua_query: " << "neroshop." + coin.toStdString() + ".nodes." + network_type << std::endl; + for(auto strings : node_table) { + node_list << QString::fromStdString(strings); + } + return node_list; +} //---------------------------------------------------------------- -QVariantList neroshop::Backend::getMoneroNodeList() const { +QVariantList neroshop::Backend::getNodeList(const QString& coin) const { const QUrl url(QStringLiteral("https://monero.fail/health.json")); QVariantList node_list; + QString coin_lower = coin.toLower(); // make coin name lowercase QNetworkAccessManager manager; QEventLoop loop; @@ -974,18 +985,20 @@ QVariantList neroshop::Backend::getMoneroNodeList() const { loop.exec(); QJsonParseError error; const auto json_doc = QJsonDocument::fromJson(reply->readAll(), &error); + // Use fallback monero node list if we fail to get the nodes from the url if (error.error != QJsonParseError::NoError) { - neroshop::print("Error reading json", 1); - return {}; + neroshop::print("Error reading json from " + url.toString().toStdString() + "\nUsing default nodes as fallback", 2); + return getNodeListDefault(coin_lower); } // Get monero nodes from the JSON QJsonObject root_obj = json_doc.object(); // {} - QJsonObject monero_obj = root_obj.value("monero").toObject(); // "monero": {} // "wownero": {} - QJsonObject clearnet_obj = monero_obj.value("clear").toObject(); // "clear": {} // "onion": {}, "web_compatible": {} - // Loop through monero nodes (clear) + QJsonObject coin_obj = root_obj.value(coin_lower).toObject(); // "monero": {} // "wownero": {} + QJsonObject clearnet_obj = coin_obj.value("clear").toObject(); // "clear": {} // "onion": {}, "web_compatible": {} + // Loop through monero nodes (clearnet) foreach(const QString& key, clearnet_obj.keys()) {//for (const auto monero_nodes : clearnet_obj) { QJsonObject monero_node_obj = clearnet_obj.value(key).toObject();//QJsonObject monero_node_obj = monero_nodes.toObject(); QVariantMap node_object; // Create an object for each row + if(key.contains("38081") || key.contains("38089")) { // Temporarily fetch only stagenet nodes (TODO: change to mainnet port upon release) node_object.insert("address", key); node_object.insert("available", monero_node_obj.value("available").toBool());//std::cout << "available: " << monero_node_obj.value("available").toBool() << "\n"; ////node_object.insert("", );//////std::cout << ": " << monero_node_obj.value("checks").toArray() << "\n"; @@ -994,6 +1007,7 @@ QVariantList neroshop::Backend::getMoneroNodeList() const { node_object.insert("datetime_failed", monero_node_obj.value("datetime_failed").toString());//std::cout << "datetime_failed: " << monero_node_obj.value("datetime_failed").toString().toStdString() << "\n"; node_object.insert("last_height", monero_node_obj.value("last_height").toInt());//std::cout << "last_height: " << monero_node_obj.value("last_height").toInt() << "\n"; node_list.append(node_object); // Add node object to the node list + } } return node_list; } @@ -1073,7 +1087,7 @@ QVariantList neroshop::Backend::validateDisplayName(const QString& display_name) //---------------------------------------------------------------- // TODO: replace function return type with enum QVariantList neroshop::Backend::checkDisplayName(const QString& display_name) const { - neroshop::db::Sqlite3 * database = neroshop::db::Sqlite3::get_database(); + neroshop::db::Sqlite3 * database = neroshop::get_database(); if(!database->table_exists("users")) { return {true, ""}; } std::string name = database->get_text_params("SELECT name FROM users WHERE name = $1 COLLATE NOCASE;", { display_name.toStdString() }); if(name.empty()) return { true, "Display name is available for use" };// Name is not taken which means that the user is good to go! @@ -1091,7 +1105,7 @@ QVariantList neroshop::Backend::checkDisplayName(const QString& display_name) co //---------------------------------------------------------------- // TODO: replace function return type with enum QVariantList neroshop::Backend::registerUser(WalletController* wallet_controller, const QString& display_name, UserController * user_controller) { - neroshop::db::Sqlite3 * database = neroshop::db::Sqlite3::get_database(); + neroshop::db::Sqlite3 * database = neroshop::get_database(); if(!database) throw std::runtime_error("database is NULL"); // Validate display name auto name_validation_result = validateDisplayName(display_name); @@ -1129,13 +1143,27 @@ QVariantList neroshop::Backend::registerUser(WalletController* wallet_controller } //---------------------------------------------------------------- bool neroshop::Backend::loginWithWalletFile(WalletController* wallet_controller, const QString& path, const QString& password, UserController * user_controller) { - neroshop::db::Sqlite3 * database = neroshop::db::Sqlite3::get_database(); + neroshop::Client * client = neroshop::Client::get_main_client(); + client->write("Mr server, I am logging in with my wallet file\n"); + client->write("Mr server, I am logging in with my wallet file 2\n"); + client->write("Mr server, I am logging in with my wallet file 3\n"); + + neroshop::db::Sqlite3 * database = neroshop::get_database(); if(!database) throw std::runtime_error("database is NULL"); // Open wallet file - if(!wallet_controller->open(path, password)) { - throw std::runtime_error("Invalid password or wallet network type"); - return false; - } + std::packaged_task open_wallet_task([wallet_controller, path, password]() -> bool { + if(!wallet_controller->open(path, password)) { + ////throw std::runtime_error("Invalid password or wallet network type"); + return false; + } + return true; + }); + std::future future_result = open_wallet_task.get_future(); + // move the task (function) to a separate thread to prevent blocking of the main thread + std::thread worker(std::move(open_wallet_task)); + worker.detach(); // join may block but detach won't + bool wallet_opened = future_result.get(); + if(!wallet_opened) return false; // Get the primary address std::string primary_address = wallet_controller->getPrimaryAddress().toStdString(); // Check database to see if user key (hash of primary address) exists @@ -1162,7 +1190,7 @@ bool neroshop::Backend::loginWithWalletFile(WalletController* wallet_controller, } //---------------------------------------------------------------- bool neroshop::Backend::loginWithMnemonic(WalletController* wallet_controller, const QString& mnemonic, UserController * user_controller) { - neroshop::db::Sqlite3 * database = neroshop::db::Sqlite3::get_database(); + neroshop::db::Sqlite3 * database = neroshop::get_database(); if(!database) throw std::runtime_error("database is NULL"); // Initialize monero wallet with existing wallet mnemonic if(!wallet_controller->restoreFromMnemonic(mnemonic)) { @@ -1190,7 +1218,7 @@ bool neroshop::Backend::loginWithMnemonic(WalletController* wallet_controller, c //---------------------------------------------------------------- bool neroshop::Backend::loginWithKeys(WalletController* wallet_controller, UserController * user_controller) { /* - neroshop::db::Sqlite3 * database = neroshop::db::Sqlite3::get_database(); + neroshop::db::Sqlite3 * database = neroshop::get_database(); if(!database) throw std::runtime_error("database is NULL"); // Get the wallet from the wallet controller neroshop::Wallet * wallet = wallet_controller->getWallet(); @@ -1236,34 +1264,16 @@ bool neroshop::Backend::loginWithHW(WalletController* wallet_controller, UserCon //---------------------------------------------------------------- //---------------------------------------------------------------- void neroshop::Backend::startServerDaemon() { - // on launching neroshop, start the neromon process, if it has not yet been started - int neromon = Process::get_process_by_name("neromon"); - if(neromon != -1) { - neroshop::print("neromon is already running in the background", 4); - return; - } - /*server_process = new Process(); // don't forget to delete this! - server_process->create("./neromon", ""); - // show all processes - ////Process::show_processes();*/ #ifdef Q_OS_WIN QString program = "neromon.exe"; #else QString program = "./neromon"; #endif - ////QStringList arguments; - ////arguments << "--ip" << "12.0.0.1"; - /*QProcess * process = new QProcess(this); - QString folder = ""; - process->start(program, QStringList() << folder); */ - - /*int exit_code = QProcess::execute(program, {}); - if(exit_code < 0) { throw std::runtime_error("program either cannot be started or has crashed"); }*/ - + // Note: If the calling process exits, the detached process will continue to run unaffected. qint64 pid = -1; bool success = QProcess::startDetached(program, {}, QString(), &pid); - //if(!success) { throw std::runtime_error("neromon could not be started") } + if(!success) { throw std::runtime_error("neroshop daemon process could not be started"); } std::cout << "\033[35mneromon started (pid: " << pid << ")\033[0m\n"; } //---------------------------------------------------------------- @@ -1272,8 +1282,9 @@ void neroshop::Backend::waitForServerDaemon() { } //---------------------------------------------------------------- void neroshop::Backend::connectToServerDaemon() { + // We will use JSON to write the request data that will sent to the server neroshop::Client * client = neroshop::Client::get_main_client(); - int client_port = 1234; + int client_port = 40441; std::string client_ip = "0.0.0.0";//"localhost";//0.0.0.0 means anyone can connect to your server if(!client->connect(client_port, client_ip)) { // free process @@ -1285,8 +1296,11 @@ void neroshop::Backend::connectToServerDaemon() { } //---------------------------------------------------------------- //---------------------------------------------------------------- -void neroshop::Backend::testWriteJson() { - QJsonObject jsonObject; // base Object (required) +#include +void neroshop::Backend::testWriteJson(const std::vector& args) { + neroshop::db::Sqlite3 * database = neroshop::get_database(); + if(!database) throw std::runtime_error("database is NULL"); + /*QJsonObject jsonObject; // base Object (required) //QJsonObject json_attributes_object; // subObject (optional) @@ -1302,9 +1316,10 @@ void neroshop::Backend::testWriteJson() { json_size_array.insert(json_size_array.size(), QJsonValue("L")); json_size_array.insert(json_size_array.size(), QJsonValue("XL")); - /*jsonObject.insert*/jsonObject.insert(QString("weight"), QJsonValue(12.5)); - /*jsonObject.insert*/jsonObject.insert(QString("color"), json_color_array); - /*jsonObject.insert*/jsonObject.insert(QString("size"), json_size_array); + //jsonObject.insert + jsonObject.insert(QString("weight"), QJsonValue(12.5)); + jsonObject.insert(QString("color"), json_color_array); + jsonObject.insert(QString("size"), json_size_array); //jsonObject.insert("attributes", json_attributes_object); @@ -1312,11 +1327,167 @@ void neroshop::Backend::testWriteJson() { QJsonDocument doc(jsonObject); QString strJson = doc.toJson(QJsonDocument::Compact); // https://doc.qt.io/qt-6/qjsondocument.html#JsonFormat-enum // Display JSON as string - std::cout << strJson.toStdString() << std::endl; + std::cout << strJson.toStdString() << std::endl;*/ + // Okay, so we (the server) have received a request from the client + std::string request = "{\n \"id\": \"2127323435\",\n \"jsonrpc\": \"2.0\",\n \"method\": \"query\",\n \"params\": {\n \"sql\": \"SELECT * FROM products WHERE condition = 'New' AND weight <= 0.0;\"\n }\n}";//"{\"id\":\"2127323435\",\"jsonrpc\":\"2.0\",\"method\":\"query\",\"params\":{\"1\":\"New\",\"2\":\"0.0\",\"count\":2,\"sql\":\"SELECT * FROM products WHERE condition = $1 AND weight <= $2;\"}}"; + // First process the request --------------------------------------- + QJsonObject response_object; // JSON-RPC Response object + QJsonParseError parse_error; + const auto json_doc = QJsonDocument::fromJson(QString::fromStdString(request).toUtf8(), &parse_error); + if (parse_error.error != QJsonParseError::NoError) { + neroshop::print("Error parsing client request", 1); + response_object.insert(QString("jsonrpc"), QJsonValue("2.0")); + // "result" MUST NOT exist if there was an error invoking the method + QJsonObject error_object; // "error" MUST be an Object as defined in section 5.1 + error_object.insert(QString("code"), QJsonValue("-32700")); + error_object.insert(QString("message"), QJsonValue("Parse error")); + QString data; + switch(parse_error.error) { + case QJsonParseError::UnterminatedObject: + data = "Unterminated object"; + break; + case QJsonParseError::MissingNameSeparator: + data = "Missing name separator"; + break; + case QJsonParseError::UnterminatedArray: + data = "Unterminated array"; + break; + case QJsonParseError::MissingValueSeparator: + data = "Missing value separator"; + break; + case QJsonParseError::IllegalValue: + data = "Illegal value"; + break; + case QJsonParseError::TerminationByNumber: + data = "Termination by number"; + break; + case QJsonParseError::IllegalNumber: + data = "Illegal number"; + break; + case QJsonParseError::IllegalEscapeSequence: + data = "Illegal escape sequence"; + break; + case QJsonParseError::IllegalUTF8String: + data = "Illegal utf8 string"; + break; + case QJsonParseError::UnterminatedString: + data = "Unterminated string"; + break; + case QJsonParseError::MissingObject: + data = "Missing object"; + break; + case QJsonParseError::DeepNesting: + data = "Deep nesting"; + break; + case QJsonParseError::DocumentTooLarge: + data = "Document too large"; + break; + case QJsonParseError::GarbageAtEnd: + data = "Garbage at end"; + break; + } + if(!data.isEmpty()) error_object.insert(QString("data"), QJsonValue(data)); // additional information about the error which may be omitted + response_object.insert(QString("error"), QJsonValue(error_object)); + response_object.insert(QString("id"), QJsonValue(QJsonValue::Null)); // "id" MUST be Null if there was an error in detecting the id in the Request object (e.g. Parse error/Invalid Request) + // TODO: return a response with an error_object + std::cout << "\033[91m" << QJsonDocument(response_object).toJson().toStdString() << "\033[0m\n"; + return; + } + std::cout << "Request received:\n\033[33m" << json_doc.toJson().toStdString() << "\033[0m\n"; + QJsonObject request_object = json_doc.object(); + // Retrieve keys and values from request_object + QJsonValue jsonrpc_version = request_object.value("jsonrpc"); + assert(jsonrpc_version.isString()); + assert(jsonrpc_version.toString() == "2.0"); + QJsonValue method = request_object.value("method"); + assert(method.isString()); + QJsonValue params = request_object.value("params"); // "params" MAY be omitted + QJsonValue id = request_object.value("id"); + if(id.isUndefined()) { + // a request object without an "id" member is assumed to be NOTIFICATION ... + // a Notification signifies the Client's lack of interest in the corresponding Response object, and as such no Response object needs to be returned to the client. The Server MUST NOT reply to a Notification, including those that are within a batch request. + // exit function, with an empty string object perhaps + //return ""; + } + // Execute the request (run function and get return value) + if(params.isObject()) {//if(params.isArray()) { //if(params.isUndefined()) { + if(method.toString() == "query") { std::cout << "method= query\n"; + QJsonValue sql = params.toObject().value("sql"); + assert(sql.isString()); + std::vector args; + QJsonValue arg_count = params.toObject().value("count"); + if(!arg_count.isUndefined()) { + for(int index = 0; index < arg_count.toInt(); index++) { + QJsonValue argument = params.toObject().value(QString::fromStdString(std::to_string(index + 1))); + assert(argument.isString()); + std::cout << argument.toString().toStdString() << " (arg: " << (index + 1) << ")" << std::endl; + args.push_back(argument.toString().toStdString()); // Store arguments in std::vector + } + assert(args.size() == arg_count.toInt()); // Make sure the number of args is the same as the count + // TODO: check whether sqlite method whether its a select or non-select statement + // select statements should use database->get_*params functions + database->execute_params(sql.toString().toStdString(), args); // Execute query with n args + } + else if(arg_count.isUndefined()) { // No args + // TODO: check whether sqlite method whether its a select or non-select statement + // select statements should use database->get_* functions + database->execute(sql.toString().toStdString()); // Execute query with zero args + } + } + //------------------------------------------------------- + else { std::cout << "method= " << method.toString().toStdString() << "\n"; + //params.value(""); + } + } + // After execution, get the result then return it as a JSON-RPC response message + ////response_object.insert(QString("jsonrpc"), QJsonValue("2.0")); + /* + + response_object.insert(QString("jsonrpc"), QJsonValue("2.0")); + response_object.insert(QString("result"), QJsonValue()); // "result" MUST NOT exist if there was an error invoking the method. + QJsonObject error_object; // "error" MUST be an Object as defined in section 5.1 + //error_object.insert(QString("code"), QJsonValue()); + //error_object.insert(QString("message"), QJsonValue()); + //error_object.insert(QString("data"), QJsonValue()); + response_object.insert(QString("error"), QJsonValue(error_object)); // "error" MUST NOT exist if there was no error triggered during invocation. + response_object.insert(QString("id"), QJsonValue(QString::fromStdString(random_id))); // "id" MUST be the same as the request object's id + // Convert JSON to string then display it (for debugging purposes) + QJsonDocument json_doc(response_object); + QString json_str = json_doc.toJson();////(QJsonDocument::Compact); + std::cout << json_str.toStdString() << "\n";*/ + + + /*std::string command = "SELECT * FROM users;"; + + nlohmann::json json; + json["jsonrpc"] = "2.0"; + json["method"] = "select";////get_method_type(command); + json["params"]["sql"] = command; + json["params"]["count"] = args.size(); + for(int index = 0; index < args.size(); index++) { + std::string arg_key = std::to_string(index + 1); + std::string arg_value = args[index]; + json["params"][arg_key] = arg_value; + } + // Generate random number for id (id can be either a string or an integer or null which is not recommended) + std::random_device rd; + std::mt19937 gen(rd()); + std::uniform_int_distribution<> distr(1, 9); + std::string random_id; + for(int n = 0; n < 10; ++n) { + int random_integer = distr(gen); + random_id = random_id + std::to_string(random_integer); + } + json["id"] = random_id; + // Dump JSON to string + std::string request = json.dump(4); + #ifdef NEROSHOP_DEBUG + std::cout << request << std::endl; + #endif*/ } //---------------------------------------------------------------- void neroshop::Backend::testfts5() { - neroshop::db::Sqlite3 * database = neroshop::db::Sqlite3::get_database(); + neroshop::db::Sqlite3 * database = neroshop::get_database(); if(!database) throw std::runtime_error("database is NULL"); database->execute("CREATE VIRTUAL TABLE email USING fts5(sender, title, body);"); diff --git a/src/gui/backend.hpp b/src/gui/backend.hpp index 7586866..1cb1836 100644 --- a/src/gui/backend.hpp +++ b/src/gui/backend.hpp @@ -47,7 +47,8 @@ public: Q_INVOKABLE int getCategoryIdByName(const QString& category_name) const; Q_INVOKABLE int getCategoryProductCount(int category_id) const; // returns number of products that fall under a specific category - Q_INVOKABLE QVariantList getMoneroNodeList() const; + Q_INVOKABLE QVariantList getNodeList(const QString& coin) const; + Q_INVOKABLE QVariantList getNodeListDefault(const QString& coin) const; Q_INVOKABLE bool isWalletDaemonRunning() const; QVariantList validateDisplayName(const QString& display_name) const; // Validates display name based on regex requirements @@ -86,7 +87,7 @@ public: Q_INVOKABLE int getStockAvailable(const QString& product_id); //Q_INVOKABLE void (); - static void testWriteJson(); + static void testWriteJson(const std::vector& args); static void testfts5(); // Test function static void startServerDaemon(); diff --git a/src/gui/image_loader.cpp b/src/gui/image_loader.cpp index d868a44..41e3bf3 100644 --- a/src/gui/image_loader.cpp +++ b/src/gui/image_loader.cpp @@ -1621,7 +1621,7 @@ QImage ImageLoader::load(const QString &id) const std::cout << "failed to load image sorry\n"; }*/ // Load product image from file (TEXT) - crashes on certain images - neroshop::db::Sqlite3 * database = neroshop::db::Sqlite3::get_database(); + neroshop::db::Sqlite3 * database = neroshop::get_database(); if(!database) throw std::runtime_error("database is NULL"); std::string command = (!query.hasQueryItem(QString("image_id"))) ? "SELECT name FROM images WHERE product_id = $1" : // use default image if key 'image_id' not found in query @@ -1638,7 +1638,7 @@ QImage ImageLoader::load(const QString &id) const } QPair ImageLoader::getProductImage(const QString &product_id) const { - neroshop::db::Sqlite3 * database = neroshop::db::Sqlite3::get_database(); + neroshop::db::Sqlite3 * database = neroshop::get_database(); if(!database) throw std::runtime_error("database is NULL"); std::string command = "SELECT data FROM images WHERE product_id = $1"; sqlite3_stmt * statement = nullptr; diff --git a/src/gui/main.cpp b/src/gui/main.cpp index c955273..d0b6ee6 100644 --- a/src/gui/main.cpp +++ b/src/gui/main.cpp @@ -14,8 +14,8 @@ using namespace neroshop; static const QString WALLET_QR_PROVIDER {"wallet_qr"}; -static const QString AVATAR_IMAGE_PROVIER {"avatar"}; -static const QString CATALOG_IMAGE_PROVIER {"catalog"}; +static const QString AVATAR_IMAGE_PROVIDER {"avatar"}; +static const QString CATALOG_IMAGE_PROVIDER {"catalog"}; bool isIOS = false; bool isAndroid = false; @@ -68,11 +68,11 @@ int main(int argc, char *argv[]) QQmlApplicationEngine engine; //-------------------------- // start server daemon - //Backend::startServerDaemon(); + /*Backend::startServerDaemon(); // wait for daemon server to open - //Backend::waitForServerDaemon(); + Backend::waitForServerDaemon(); // connect to server daemon - //Backend::connectToServerDaemon(); + Backend::connectToServerDaemon();*/ // Configuration file must be loaded right after Qt Application object has been created so that we can get the correct config location // open configuration script neroshop::open_configuration_file(); @@ -85,7 +85,8 @@ int main(int argc, char *argv[]) // start database Backend::initializeDatabase(); // testing - //Backend::testfts5();//Backend::testWriteJson(); + //Backend::testfts5();// + Backend::testWriteJson({"New"}); // import paths engine.addImportPath(":/fonts"); // import FontAwesome 1.0 // platform macros @@ -123,8 +124,8 @@ int main(int argc, char *argv[]) qRegisterMetaType(); engine.addImageProvider(WALLET_QR_PROVIDER, new WalletQrProvider(WALLET_QR_PROVIDER)); - engine.addImageProvider(AVATAR_IMAGE_PROVIER, new ImageProvider(AVATAR_IMAGE_PROVIER)); - engine.addImageProvider(CATALOG_IMAGE_PROVIER, new ImageProvider(CATALOG_IMAGE_PROVIER)); + engine.addImageProvider(AVATAR_IMAGE_PROVIDER, new ImageProvider(AVATAR_IMAGE_PROVIDER)); + engine.addImageProvider(CATALOG_IMAGE_PROVIDER, new ImageProvider(CATALOG_IMAGE_PROVIDER)); //-------------------------- // Load main.qml from the "qml/" directory engine.load(QUrl(QStringLiteral("qrc:/qml/main.qml"))); diff --git a/src/gui/wallet_controller.cpp b/src/gui/wallet_controller.cpp index ae5be0a..b947ba0 100644 --- a/src/gui/wallet_controller.cpp +++ b/src/gui/wallet_controller.cpp @@ -235,32 +235,40 @@ double neroshop::WalletController::getBalanceUnlocked(unsigned int account_index QVariantList neroshop::WalletController::getTransfers() const { + if (!_wallet.get()) throw std::runtime_error("neroshop::Wallet is not initialized"); + if (!_wallet->get_monero_wallet()) throw std::runtime_error("monero_wallet_full is not opened"); // TODO: make this function async or put in a separate thread - if (!_wallet.get()) - throw std::runtime_error("neroshop::Wallet is not initialized"); - if (!_wallet->get_monero_wallet()) - throw std::runtime_error("monero_wallet_full is not opened"); - double piconero = 0.000000000001; - monero_transfer_query transfer_query; // optional - auto transfers = _wallet->get_monero_wallet()->get_transfers(transfer_query); - - QVariantList transfers_list; - - for (auto transfer : transfers) { /*for(int i = 0; i < transfers.size(); i++) { - monero_transfer * transfer = transfers[i].get();*/ - - QVariantMap transfer_object; - transfer_object.insert("amount", (transfer->m_amount.get() * piconero)); - transfer_object.insert("account_index", transfer->m_account_index.get()); // obviously account index 0 - transfer_object.insert("is_incoming", transfer->is_incoming().get()); - transfer_object.insert("is_outgoing", transfer->is_outgoing().get()); - monero_tx_wallet * tx_wallet = transfer->m_tx.get(); - ////transfer_object.insert("", tx_wallet->); - //std::cout << ": " << tx_wallet-> << "\n"; + std::packaged_task get_transfers_task([this]() -> QVariantList { + double piconero = 0.000000000001; + monero_transfer_query transfer_query; // optional + auto transfers = _wallet->get_monero_wallet()->get_transfers(transfer_query); + + QVariantList transfers_list; + + for (auto transfer : transfers) { /*for(int i = 0; i < transfers.size(); i++) { + monero_transfer * transfer = transfers[i].get();*/ + + QVariantMap transfer_object; + transfer_object.insert("amount", (transfer->m_amount.get() * piconero)); + transfer_object.insert("account_index", transfer->m_account_index.get()); // obviously account index 0 + transfer_object.insert("is_incoming", transfer->is_incoming().get()); + transfer_object.insert("is_outgoing", transfer->is_outgoing().get()); + monero_tx_wallet * tx_wallet = transfer->m_tx.get(); + ////transfer_object.insert("", tx_wallet->); + //std::cout << ": " << tx_wallet-> << "\n"; - transfers_list.append(transfer_object); - } - return transfers_list; + transfers_list.append(transfer_object); + } + return transfers_list; + }); + + std::future future_result = get_transfers_task.get_future(); + // move the task (function) to a separate thread to prevent blocking of the main thread + std::thread worker(std::move(get_transfers_task)); + worker.detach(); // join may block but detach won't + QVariantList transfers_result = future_result.get(); + + return transfers_result; }