libnatpmp added and other changes

pull/147/head
larteyoh 12 months ago
parent 0abd5bd09f
commit d6aeff4b56

3
.gitmodules vendored

@ -22,3 +22,6 @@
[submodule "external/miniupnp"]
path = external/miniupnp
url = https://github.com/miniupnp/miniupnp.git
[submodule "external/libnatpmp"]
path = external/libnatpmp
url = https://github.com/miniupnp/libnatpmp.git

@ -395,6 +395,15 @@ if(EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/external/miniupnp/build/libminiupnpc.a")
set(miniupnp_src ${CMAKE_CURRENT_SOURCE_DIR}/external/miniupnp/build/libminiupnpc.a)
endif()
######################################
# libnatpmp
include_directories(${CMAKE_CURRENT_SOURCE_DIR}/external/libnatpmp/)
if(EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/external/build/libnatpmp.a")
option(NEROSHOP_USE_LIBNATPMP "Build neroshop with libnatpmp" ON)
message(STATUS "Using libnatpmp")
set(natpmp_src ${CMAKE_CURRENT_SOURCE_DIR}/external/build/libnatpmp.a)
endif()
######################################
# neroshop link directories
link_directories(${CMAKE_CURRENT_SOURCE_DIR}/build) # target_link_directories only available in CMake version 3.13 :(
@ -489,9 +498,12 @@ add_executable(${daemon_executable} src/daemon/main.cpp ${daemon_src})#target_li
if(NEROSHOP_USE_MINIUPNP)
target_compile_definitions(${daemon_executable} PRIVATE NEROSHOP_USE_MINIUPNP)
endif()
if(NEROSHOP_USE_LIBNATPMP)
target_compile_definitions(${daemon_executable} PRIVATE NEROSHOP_USE_LIBNATPMP)
endif()
target_compile_definitions(${daemon_executable} PRIVATE NEROSHOP_DEBUG)
target_include_directories(${daemon_executable} PRIVATE #[[${CMAKE_CURRENT_SOURCE_DIR}/src/]])
target_link_libraries(${daemon_executable} ${monero_cpp_src} ${sqlite_src} ${raft_src} ${monero_src} ${i2pd_src} ${miniupnp_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} ${monero_cpp_src} ${sqlite_src} ${raft_src} ${monero_src} ${i2pd_src} ${miniupnp_src} ${natpmp_src})#set(CMAKE_CXX_LINK_EXECUTABLE "${CMAKE_CXX_LINK_EXECUTABLE} -ldl") # fixes undefined reference to symbol 'dlsym@@GLIBC_2.2.5' error
######################################
# neroshop-console

@ -96,6 +96,7 @@ The name _neroshop_ is a combination of the words _nero_, which is Italian for _
| [libzmq](https://github.com/zeromq/libzmq) | ? | networking | :grey_question: |
| [libi2pd](https://github.com/PurpleI2P/i2pd) | latest | network proxy | :grey_question: |
| [miniupnp](https://github.com/miniupnp/miniupnp) | ? | automatic port forwarding | :o: |
| [libnatpmp](https://github.com/miniupnp/libnatpmp) | ? | automatic port forwarding | :o: |
### Compiling neroshop from source
**0. Install prerequisites**

@ -6,11 +6,6 @@ set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY ../build)
set(CMAKE_LIBRARY_OUTPUT_DIRECTORY ../build)
set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ../build)
option(NEROSHOP_USE_OPENPGP "Build neroshop with PGP support" OFF)
if(NEROSHOP_USE_OPENPGP)
add_subdirectory(openpgp)
endif()
# compile the source code of external dependencies into static libraries so there won't be a need to recompile each time there's a change in the makefile
set(monero_include_dirs monero-cpp/external/monero-project/contrib/epee/include/
monero-cpp/external/monero-project/external/easylogging++/
@ -71,6 +66,8 @@ target_include_directories(${i2pd_client_target} PUBLIC i2pd/i18n/ i2pd/libi2pd/
add_subdirectory(miniupnp/miniupnpc)
add_subdirectory(libnatpmp)
#[[
set(_target "")
set(_srcs )

@ -0,0 +1 @@
Subproject commit 6a850fd2bd9b08e6edc886382a1dbae2a7df55ec

@ -689,7 +689,7 @@ Popup {
productCodeField.text,
Backend.getCategoryIdByName(productCategoryBox.currentText),
-1, // subcategoryId
productTagsField.tags(),//productTagsField.tagList,
productTagsField.tags(),
productQuantityField.text,
productPriceField.text,
@ -707,6 +707,7 @@ Popup {
productWeightField.text = ""
productLocationBox.currentIndex = productLocationBox.find("Unspecified")//find("Worldwide")
productDescriptionEdit.text = ""
productTagsField.clearTags()
// Clear upload images as well
for(let i = 0; i < productImageRepeater.count; i++) {
let productImage = productImageRepeater.itemAt(i).children[0].children[0]

@ -7,6 +7,7 @@ import "." as NeroshopComponents
Item {
id: searchBar
width: childrenRect.width; height: childrenRect.height
property var model: Backend.getSearchResults(searchField.text)
TextField {
id: searchField
color: (NeroshopComponents.Style.darkTheme) ? "#ffffff" : "#000000"// textColor
@ -18,9 +19,6 @@ Item {
color: (NeroshopComponents.Style.darkTheme) ? "#050506" : "#f9f9fa"
radius: 5
}
onTextChanged: {//https://stackoverflow.com/questions/70284407/detect-changes-on-every-character-typed-in-a-textfield-qml
//console.log("Show search suggestions popup list")
}
Keys.onEnterPressed: searchButton.activate()
Keys.onReturnPressed: searchButton.activate()
}
@ -49,7 +47,8 @@ Item {
////if(searchField.length < 1) return;
console.log("Searching for " + searchField.text)
navBar.uncheckAllButtons()
pageLoader.setSource("qrc:/qml/pages/CatalogPage.qml", {"model": (searchField.text.length < 1) ? Backend.getListings() : Backend.getSearchResults(searchField.text)})//, {"model": [""]})
suggestionsPopup.close()
pageLoader.setSource("qrc:/qml/pages/CatalogPage.qml", {"model": (searchField.text.length < 1) ? Backend.getListings() : searchBar.model })//, {"model": [""]})
//console.log("page Loader Item (CatalogPage):", pageLoader.item)
//console.log("page Loader Item (CatalogPage.catalog):", pageLoader.catalog)//.item)
@ -66,4 +65,59 @@ Item {
cursorShape: Qt.PointingHandCursor
}
}
Popup {
id: suggestionsPopup
width: searchField.width// + (searchButton.anchors.leftMargin + searchButton.width)
height: Math.min(suggestionsList.contentHeight, (suggestionsList.delegateHeight * suggestionsPopup.maxSuggestions))
y: searchField.height + 1
closePolicy: Popup.CloseOnEscape | Popup.CloseOnPressOutside
visible: (searchField.activeFocus && searchField.text.length > 0 && suggestionsList.count > 0) ? true : false
background: Rectangle {
color: suggestionsList.interactive ? "transparent" : "red"
}
property int maxSuggestions: 10 // This is the max suggestions shown on the scrollview at a time rather
ListView {
id: suggestionsList
anchors.centerIn: parent
width: suggestionsPopup.width
height: suggestionsPopup.height
clip: true
interactive: false // Set to true for scrollbar to work with mouse wheel (but then it flicks x.x)
ScrollBar.vertical: ScrollBar { }
property real delegateHeight: 32
model: searchBar.model//.slice(0, suggestionsPopup.maxSuggestions) // Limit to the first 10 items
delegate: Rectangle {
width: suggestionsList.width
height: suggestionsList.delegateHeight
color: hovered ? NeroshopComponents.Style.getColorsFromTheme()[1] : NeroshopComponents.Style.getColorsFromTheme()[0]
property bool hovered: false
Text {
text: modelData.product_name
anchors.fill: parent
verticalAlignment: Text.AlignVCenter
horizontalAlignment: Text.AlignHCenter
color: "#ffffff"
}
MouseArea {
anchors.fill: parent
hoverEnabled: true
cursorShape: Qt.PointingHandCursor
onEntered: {
hovered = true
}
onExited: {
hovered = false
}
onClicked: {
searchField.text = modelData.product_name
searchButton.activate() // This does not work either :( ////suggestionsPopup.close() // Does not work :( => "ReferenceError: suggestionsPopup is not defined"
}
}
}
}
} // Popup
}

@ -9,7 +9,6 @@ Item {
implicitWidth: 500
implicitHeight: childrenRect.height
property string tagText: ""
property var tagList: ListModel {}//[]
property int maxTagCount: 12 // Maximum number of tags allowed
@ -24,6 +23,10 @@ Item {
}
return stringList
}
function clearTags() {
tagList.clear()
}
ColumnLayout {
width: parent.width
@ -110,13 +113,22 @@ Item {
let tagsToAdd = Math.min(tags.length, maxTagCount - tagList.count)
for (let i = 0; i < tagsToAdd; i++) {
if (tags[i] !== "") { // Skip empty tags after comment
if (tags[i] !== "" && !isTagDuplicate(tags[i])) { // Skip duplicate tags and empty tags after comma
tagList.append({ text: tags[i] })
}
}
tagInput.text = ""
}
}
function isTagDuplicate(tag) {
for (let i = 0; i < tagList.count; i++) {
if (tagList.get(i).text === tag) {
return true; // Found a duplicate tag
}
}
return false; // No duplicate tag found
}
}
} // ColumnLayout
}

@ -14,6 +14,10 @@ Page {
color: "transparent"
}
property var model: null//property string productId: ""
function openSellerPage() {
pageLoader.setSource("qrc:/qml/pages/ProfilePage.qml");//, {"model": [""]})
}
Flickable {
anchors.fill: parent
anchors.margins: 20

@ -2,6 +2,7 @@
import QtQuick 2.12
import QtQuick.Controls 2.12
import QtQuick.Layouts 1.12
import QtGraphicalEffects 1.12 // ColorOverlay
import "../components" as NeroshopComponents
@ -10,4 +11,337 @@ Page {
background: Rectangle {
color: "transparent"
}
ColumnLayout {
width: parent.width
spacing: 0//10 - topMargin already set for profilePictureRect
RowLayout {
spacing: 24
Column {
Layout.alignment: Qt.AlignTop | Qt.AlignLeft
Layout.leftMargin: 24; Layout.topMargin: 20
Rectangle {
id: profileCard
width: 400//Layout.fillWidth: true//width: parent.width
height: 500//Layout.preferredHeight: 200
color: (bannerImage.status != Image.Ready) ? "royalblue" : "transparent"
radius: 7
Image {
id: bannerImage
source: "file:///" + "/home/sid/Downloads/monero-support-your-local-cypherpunk-1920x1080.png"//"path/to/cover_art.jpg"
width: parent.width; height: (parent.height - infoRect.height)//parent.height////anchors.fill: parent
fillMode: Image.PreserveAspectCrop
mipmap: true
// Apply rounded rectangle mask (radius)
layer.enabled: true
layer.effect: OpacityMask {
maskSource: Rectangle {
radius: profileCard.radius
width: profileCard.width
height: profileCard.height - infoRect.height // height is different from profileCard's height so we can't use profileCard directly or it'll cause issues
}
}
}
// Bottom rect
Rectangle {
id: infoRect
anchors.bottom: parent.bottom
anchors.left: parent.left // so that margins will also apply to left and right sides
anchors.right: parent.right
width: profileCard.width//400
height: profileCard.height - 150//profileCard.height / 2
color: profilePictureRect.border.color//"#0e0e11"//"transparent"
radius: profileCard.radius
}
// Hide radius in between profileCard and infoRect
Rectangle {
width: infoRect.width - (parent.border.width * 2); height: infoRect.height / 2
anchors.left: parent.left; anchors.leftMargin: 0
anchors.right: parent.right; anchors.rightMargin: 0
anchors.top: infoRect.top; anchors.topMargin: -10
color: infoRect.color
border.width: parent.border.width; border.color: infoRect.color
radius: 0
}
Rectangle {
id: profilePictureRect
anchors.left: parent.left; anchors.leftMargin: 10//30
anchors.top: parent.top; anchors.topMargin: 50
width: 128//Layout.preferredWidth: 128
height: width//Layout.preferredHeight: Layout.preferredWidth
radius: 5
border.width: 7
border.color: NeroshopComponents.Style.getColorsFromTheme()[1]//"#343434"//"#808080"//"#0e0e11"//"#000000"//"#ffffff"
Image {
id: profilePicture
source: "file:///" + "/home/sid/Downloads/monero-geometric-logo-800x800.png"//"path/to/profile_picture.jpg"
anchors.centerIn: parent
width: parent.width - profilePictureRect.border.width; height: width
fillMode: Image.PreserveAspectFit
mipmap: true
// Apply rounded rectangle mask (radius)
layer.enabled: true
layer.effect: OpacityMask {
maskSource: profilePictureRect
}
}
}
// user identity column
Column {
id: nameIdColumn
anchors.top: profilePictureRect.bottom
anchors.left: profilePictureRect.left
anchors.leftMargin: 3
//anchors.horizontalCenter: profilePictureRect.horizontalCenter
//anchors.top: parent.top; anchors.topMargin: 10//20//anchors.verticalCenter: parent.verticalCenter
// display name
Text {
text: "layter" // Replace with actual user name
font.pixelSize: 16//32
//font.bold: true
color: (NeroshopComponents.Style.darkTheme) ? "#ffffff" : "#000000"
}
// user id
TextArea {
text: "5AWjbNUBf2EbbCw2v6ChrJUCdeRjfpcH5Y63wpWz37X6ZEiU9gvGeFqQpZczeVtZnd479FE4SDvKy7yF8ozj99QTRzcTY3a" // Replace with actual user ID
font.pixelSize: 16
//font.bold: true
color: "dimgray"
readOnly: true
wrapMode: Text.Wrap //Text.Wrap moves text to the newline when it reaches the width
selectByMouse: true
//background: Rectangle { color: "transparent" }
padding: 0; leftPadding: 0
width: 200
}
}
// stats column
Column {
id: statsRow
anchors.top: nameIdColumn.bottom
anchors.topMargin: 10
anchors.left: profilePictureRect.left
anchors.leftMargin: 1
property int textIconSpacing: 5
property real iconSize: 24
// stats row
Row {
spacing: 100
// reputation
Column {
spacing: statsRow.textIconSpacing
Text {
text: "Reputation"
font.pixelSize: 16//32
//font.bold: true
color: (NeroshopComponents.Style.darkTheme) ? "#ffffff" : "#000000"
}
Rectangle {
//anchors.verticalCenter: parent.verticalCenter
//anchors.left: parent.left; anchors.leftMargin: width / 2
width: 100; height: 26
color: "transparent"
border.color: "#ffffff"
radius: 3
Row {
anchors.fill: parent
spacing: 5
Item {
Image {
id: ratingIcon
source: "qrc:/images/rating.png"
width: statsRow.iconSize; height: width
mipmap: true
}
ColorOverlay {
anchors.fill: ratingIcon
source: ratingIcon
color: "#ffd700"//"#e6c200"
visible: ratingIcon.visible
}
}
Text {
text: "97%"
font.pixelSize: 16
color: (NeroshopComponents.Style.darkTheme) ? "#ffffff" : "#000000"
}
}
}
}
// products
Column {
spacing: statsRow.textIconSpacing
Text {
text: "Products" // Replace with actual user name
font.pixelSize: 16//32
//font.bold: true
color: (NeroshopComponents.Style.darkTheme) ? "#ffffff" : "#000000"
}
Rectangle {
//anchors.verticalCenter: parent.verticalCenter
//anchors.left: parent.left; anchors.leftMargin: width / 2
width: 64; height: 26
color: "transparent"
border.color: "#ffffff"
radius: 3
Image {
id: productIcon
source: "qrc:/images/open_parcel.png"
width: statsRow.iconSize; height: width
mipmap: true
}
ColorOverlay {
anchors.fill: productIcon
source: productIcon
color: "#4169e1"
visible: productIcon.visible
}
}
}
}
}
/*Column {
Row {
////anchors.verticalCenter: parent.verticalCenter
anchors.left: parent.left; anchors.leftMargin: width / 2
//width: 32; height: 32
//color: "#fffbe5"
//radius: 3//50
Image {
id: ratingIcon
source: "qrc:/images/rating.png"
width: 32; height: 32
anchors.centerIn: parent
}
ColorOverlay {
anchors.fill: ratingIcon
source: ratingIcon
color: "#ffd700"//"#e6c200"
visible: ratingIcon.visible
}
}
}*/
////}
/*Row {
layoutDirection: Qt.RightToLeft
anchors.right: parent.right; anchors.rightMargin: 30
anchors.verticalCenter: parent.verticalCenter // TODO: make this bottom/bottomPadding
Button {
width: contentItem.contentWidth + 20; height: contentItem.contentHeight + 20
text: qsTr("Message")
background: Rectangle {
color: NeroshopComponents.Style.neroshopPurpleColor
radius: 3
}
contentItem: Text {
text: parent.text
color: "#ffffff"
horizontalAlignment: Text.AlignHCenter
verticalAlignment: Text.AlignVCenter
font.bold: true
}
onClicked: {}
}
}*/ // Row 2
// TODO: show stats and reputation like good ratings(thumbs up), bad ratings (thumbs down)
// Mail letter icon for email, location icon for location, Link icon for website
} // Rectangle
} // Column
// Tabs
Rectangle {
id: tabsRect
Layout.alignment: Qt.AlignTop | Qt.AlignRight
Layout.topMargin: 20; Layout.rightMargin: 24;
Layout.fillWidth: true
Layout.preferredHeight: 50
color: "#343434"
radius: profileCard.radius
Row {
anchors.verticalCenter: parent.verticalCenter
anchors.left: parent.left; anchors.leftMargin: 10
spacing: 15
Button {
width: contentItem.contentWidth + 20; height: contentItem.contentHeight + 20
text: qsTr("Listing")
background: Rectangle {
color: NeroshopComponents.Style.neroshopPurpleColor
radius: 3
}
contentItem: Text {
text: parent.text
color: "#ffffff"
horizontalAlignment: Text.AlignHCenter
verticalAlignment: Text.AlignVCenter
font.bold: true
}
onClicked: {}
}
Button {
width: contentItem.contentWidth + 20; height: contentItem.contentHeight + 20
text: qsTr("Ratings")
background: Rectangle {
color: NeroshopComponents.Style.neroshopPurpleColor
radius: 3
}
contentItem: Text {
text: parent.text
color: "#ffffff"
horizontalAlignment: Text.AlignHCenter
verticalAlignment: Text.AlignVCenter
font.bold: true
}
onClicked: {}
}
}
}
} // RowLayout
/*NeroshopComponents.TabBar {
id: tabBar
Layout.alignment: Qt.AlignLeft | Qt.AlignTop
model: ["Store", "Ratings", "Details"]
color0: NeroshopComponents.Style.neroshopPurpleColor
Component.onCompleted: {
buttonAt(0).checked = true
}
}*/
/*TabBar {
id: tabBar
width: parent.width
TabButton {
text: qsTr("Store") // Listings
width: text.length * 10
}
TabButton {
text: qsTr("Ratings")
}
TabButton {
text: qsTr("Details") // About
}
}*/
/*StackView {
}*/
}
}

@ -22,6 +22,8 @@ neroshop::Mapper::~Mapper() {
user_ids.clear();
display_names.clear();
order_ids.clear();
product_ratings.clear();
seller_ratings.clear();
}
//-----------------------------------------------------------------------------
@ -113,11 +115,11 @@ void neroshop::Mapper::add(const std::string& key, const std::string& value) {
}
}
//-----------------------------------------------
/*if(metadata == "product_rating") {
if(metadata == "product_rating") {
// Map a product_rating's key to product_id
if (json.contains("product_id") && json["product_id"].is_string()) {
std::string product_id = json["product_id"].get<std::string>();
product_ids[product_id].push_back(key);
product_ratings[product_id].push_back(key);
}
}
//-----------------------------------------------
@ -125,9 +127,9 @@ void neroshop::Mapper::add(const std::string& key, const std::string& value) {
// Map a seller_rating's key to user_id
if (json.contains("seller_id") && json["seller_id"].is_string()) {
std::string seller_id = json["seller_id"].get<std::string>();
user_ids[seller_id].push_back(key);
seller_ratings[seller_id].push_back(key);
}
}*/
}
//-----------------------------------------------
sync(); // Sync to database
}
@ -338,6 +340,44 @@ void neroshop::Mapper::sync() {
}
}
//-----------------------------------------------
// Insert data from 'product_ratings'
for (const auto& entry : product_ratings) {
const std::string& search_term = entry.first;
const std::vector<std::string>& keys = entry.second;
const std::string content = "product_rating";
for (const std::string& key : keys) {
// Check if the record already exists
std::string select_query = "SELECT COUNT(*) FROM mappings WHERE search_term = ? AND key = ?;";
bool exists = database->get_integer_params(select_query, { search_term, key });
// If no duplicate record found, perform insertion
if(!exists) {
std::string insert_query = "INSERT INTO mappings (search_term, key, content) VALUES (?, ?, ?);";
database->execute_params(insert_query, { search_term, key, content });
}
}
}
//-----------------------------------------------
// Insert data from 'seller_ratings'
for (const auto& entry : seller_ratings) {
const std::string& search_term = entry.first;
const std::vector<std::string>& keys = entry.second;
const std::string content = "seller_rating";
for (const std::string& key : keys) {
// Check if the record already exists
std::string select_query = "SELECT COUNT(*) FROM mappings WHERE search_term = ? AND key = ?;";
bool exists = database->get_integer_params(select_query, { search_term, key });
// If no duplicate record found, perform insertion
if(!exists) {
std::string insert_query = "INSERT INTO mappings (search_term, key, content) VALUES (?, ?, ?);";
database->execute_params(insert_query, { search_term, key, content });
}
}
}
//-----------------------------------------------
database->execute("COMMIT;");
}

@ -23,7 +23,9 @@ struct Mapper { // maps search terms to DHT keys
std::unordered_map<std::string, std::vector<std::string>> user_ids; // maps a monero address (ID) to the corresponding account key
std::unordered_map<std::string, std::vector<std::string>> display_names; // maps a display name to a list of corresponding account keys
std::unordered_map<std::string, std::vector<std::string>> order_ids; // maps a order uuid to the corresponding order key.
std::unordered_map<std::string, std::vector<std::string>> product_ratings;
std::unordered_map<std::string, std::vector<std::string>> seller_ratings;
void add(const std::string& key, const std::string& value); // must be JSON value
void sync(); // syncs mapping data to local database
std::pair<std::string, std::string> serialize(); // Converts mapping data to JSON format

@ -318,7 +318,6 @@ std::vector<neroshop::Node*> neroshop::Node::lookup(const std::string& key) {
// Define the list of bootstrap nodes
std::vector<neroshop::Peer> bootstrap_nodes = {
{"127.0.0.1", NEROSHOP_P2P_DEFAULT_PORT},
{"node.neroshop.org", NEROSHOP_P2P_DEFAULT_PORT}, // $ ping neroshop.org # or nslookup neroshop.org
};
@ -432,11 +431,17 @@ int neroshop::Node::put(const std::string& key, const std::string& value) {
return false;
}
// If data is a duplicate, skip it
// If data is a duplicate, skip it and return success (true)
if (has_key(key) && get(key) == value) {
std::cout << "Data already exists. Skipping ...\n";
return true;
}
// If node has the key but the value has been altered, verify data integrity or ownership then update the data
if (has_key(key) && get(key) != value) {
std::cout << "Updating value for key (" << key << ")\n";
return set(key, value);
}
data[key] = value;
return has_key(key); // boolean
@ -463,19 +468,6 @@ int neroshop::Node::remove(const std::string& key) {
return (data.count(key) == 0); // boolean
}
bool neroshop::Node::has_key(const std::string& key) const {
return (data.count(key) > 0);
}
bool neroshop::Node::has_value(const std::string& value) const {
for (const auto& pair : data) {
if (pair.second == value) {
return true;
}
}
return false;
}
void neroshop::Node::map(const std::string& key, const std::string& value) {
if(!validate(key, value)) {
return;
@ -484,6 +476,39 @@ void neroshop::Node::map(const std::string& key, const std::string& value) {
mapper->add(key, value); // Temporarily stores the mapping in C++ for serialization before permanently adding it to the database
}
int neroshop::Node::set(const std::string& key, const std::string& value) {
nlohmann::json json = nlohmann::json::parse(value); // Already validated so we just need to parse it without checking for errors
// Detect when key is the same but the value has changed
if(has_key(key)) {
std::string preexisting_value = find_value(key);//std::cout << "preexisting_value: " << preexisting_value << std::endl;
// Compare the preexisting value with the new value
if (preexisting_value != value) {
std::cout << "Value modification detected. Performing signature verification ...\n";//std::cout << "Value mismatch. Skipping ...\n";//return false;}
// Verify signature using user's public key (required for account data and listing data)
if (json.contains("signature")) {
assert(json["signature"].is_string());
std::string signature = json["signature"].get<std::string>();//neroshop::base64_decode(json["signature"].get<std::string>());
// Get the public key and other account data from the user
/////std::string public_key = json["public_key"].get<std::string>();//user.get_public_key(); // Get the public key from the user object
////std::string user_id = json["monero_address"].get<std::string>();//user.get_id(); // Get the user ID from the user object
////std::cout << "Verifying existing key's signature ...\n";
/*bool verified = neroshop_crypto::rsa_public_verify(public_key, user_id, signature);
if(!verified) {
std::cerr << "Verification failed." << std::endl;
return false; // Verification failed, return false
}*/
}
}
}
data[key] = value;
return has_key(key); // boolean
}
//-------------------------------------------------------------------------------------
std::vector<uint8_t> neroshop::Node::send_query(const std::string& address, uint16_t port, const std::vector<uint8_t>& message, int recv_timeout) {
@ -748,7 +773,7 @@ int neroshop::Node::send_put(const std::string& key, const std::string& value) {
nodes_sent_count++;
}
//-----------------------------------------------
// Handle the case when there are fewer closest nodes than NEROSHOP_DHT_REPLICATION_FACTOR - this most likely means that the size of the network is tiny
// Handle the case when there are fewer closest nodes than NEROSHOP_DHT_REPLICATION_FACTOR - this most likely means that there are not enough nodes in the network
if (closest_nodes.size() < NEROSHOP_DHT_REPLICATION_FACTOR) return nodes_sent_count;
//-----------------------------------------------
// If the desired number of nodes is not reached due to non-responses, replace failed nodes with new nodes and continue sending put messages
@ -895,7 +920,7 @@ void neroshop::Node::send_map(const std::string& address, int port) {
query_object["args"]["key"] = key;
query_object["args"]["value"] = value;
std::string transaction_id = msgpack::generate_transaction_id();
query_object["tid"] = transaction_id; // tid should be unique for each put message
query_object["tid"] = transaction_id; // tid should be unique for each map message
std::vector<uint8_t> map_message = nlohmann::json::to_msgpack(query_object);
auto receive_buffer = send_query(address, port, map_message);
@ -935,32 +960,10 @@ bool neroshop::Node::validate(const std::string& key, const std::string& value)
std::cerr << "JSON parsing error: " << e.what() << std::endl;
return false; // Invalid value, return false
}
// Detect when key is the same but the value has changed
if(has_key(key)) {
std::string preexisting_value = find_value(key);//std::cout << "preexisting_value: " << preexisting_value << std::endl;
// Compare the preexisting value with the new value
if (preexisting_value != value) {
std::cout << "Value modification detected. Performing signature verification ...\n";//std::cout << "Value mismatch. Skipping ...\n";//return false;}
// Verify signature using user's public key (required for account data and listing data)
if (json.contains("signature")) {
assert(json["signature"].is_string());
std::string signature = neroshop::base64_decode(json["signature"].get<std::string>());
// Get the public key and other account data from the user
/////std::string public_key = json["public_key"].get<std::string>();//user.get_public_key(); // Get the public key from the user object
////std::string user_id = json["monero_address"].get<std::string>();//user.get_id(); // Get the user ID from the user object
////std::cout << "Verifying existing key's signature ...\n";
/*bool verified = neroshop_crypto::rsa_public_verify(public_key, user_id, signature);
if(!verified) {
std::cerr << "Verification failed." << std::endl;
return false; // Verification failed, return false
}*/
}
}
// Make sure value contains a metadata field
if(!json.contains("metadata")) {
std::cerr << "No metadata found\n";
return false;
}
return true;
@ -1328,6 +1331,19 @@ std::vector<std::pair<std::string, std::string>> neroshop::Node::get_data() cons
//-----------------------------------------------------------------------------
bool neroshop::Node::has_key(const std::string& key) const {
return (data.count(key) > 0);
}
bool neroshop::Node::has_value(const std::string& value) const {
for (const auto& pair : data) {
if (pair.second == value) {
return true;
}
}
return false;
}
bool neroshop::Node::is_bootstrap_node(const std::string& address, uint16_t port) {
for (const auto& bootstrap : bootstrap_nodes) {
if (neroshop::ip::resolve(bootstrap.address) == address && bootstrap.port == port) {

@ -50,6 +50,7 @@ private:
// Determines if node1 is closer to the target_id than node2
bool is_closer(const std::string& target_id, const std::string& node1_id, const std::string& node2_id);
//---------------------------------------------------
int set(const std::string& key, const std::string& value); // Updates the value without changing the key. set cannot be accessed directly but only through put
public:
Node(const std::string& address, int port, bool local); // Binds a socket to a port and initializes the DHT
//Node(const Node& other); // Copy constructor
@ -109,7 +110,6 @@ public:
//---------------------------------------------------
// DHT-based indexing (Inverted indexing)
void map(const std::string& key, const std::string& value); // Maps search terms to keys
//void persist(); // Backs up hash_table data to disk
//---------------------------------------------------
std::string get_id() const; // get ID of this node
std::string get_ip_address() const;

@ -40,6 +40,13 @@
neroshop::Backend::Backend(QObject *parent) : QObject(parent) {}
neroshop::Backend::~Backend() {
#ifdef NEROSHOP_DEBUG
std::cout << "backend deleted\n";
#endif
}
//----------------------------------------------------------------
//----------------------------------------------------------------
QString neroshop::Backend::urlToLocalFile(const QUrl &url) const
{
return url.toLocalFile();
@ -562,13 +569,13 @@ QVariantList neroshop::Backend::getInventory(const QString& user_id) {
}
//----------------------------------------------------------------
//----------------------------------------------------------------
QVariantList neroshop::Backend::getSearchResults(const QString& searchTerm) {
QVariantList neroshop::Backend::getSearchResults(const QString& searchTerm, int count) {
// Transition from Sqlite to DHT:
Client * client = Client::get_main_client();
neroshop::db::Sqlite3 * database = neroshop::get_database();
if(!database) throw std::runtime_error("database is NULL");
std::string command = "SELECT DISTINCT key FROM mappings WHERE (search_term MATCH ? AND content = 'listing');";//"SELECT DISTINCT key FROM mappings WHERE (search_term MATCH ? OR search_term LIKE '%' || ? || '%' COLLATE NOCASE) AND (content MATCH 'listing');";//"SELECT DISTINCT key FROM mappings WHERE search_term MATCH ? AND content MATCH 'listing';";
std::string command = "SELECT DISTINCT key FROM mappings WHERE (search_term MATCH ?1 OR search_term MATCH ?1 || '*') AND (content = 'listing') LIMIT ?2;";//"SELECT DISTINCT key FROM mappings WHERE (search_term MATCH ? OR search_term LIKE '%' || ? || '%' COLLATE NOCASE) AND (content MATCH 'listing');";//"SELECT DISTINCT key FROM mappings WHERE search_term MATCH ? AND content MATCH 'listing';";
sqlite3_stmt * stmt = nullptr;
// Prepare (compile) statement
if(sqlite3_prepare_v2(database->get_handle(), command.c_str(), -1, &stmt, nullptr) != SQLITE_OK) {
@ -584,14 +591,11 @@ QVariantList neroshop::Backend::getSearchResults(const QString& searchTerm) {
return {};//database->execute("ROLLBACK;"); return {};
}
/*// Bind value to parameter arguments
QByteArray searchTermByteArray = searchTerm.toUtf8();
if (sqlite3_bind_text(stmt, 1, searchTermByteArray.constData(), searchTermByteArray.length(), SQLITE_STATIC) != SQLITE_OK ||
sqlite3_bind_text(stmt, 2, searchTermByteArray.constData(), searchTermByteArray.length(), SQLITE_STATIC) != SQLITE_OK) {
neroshop::print("sqlite3_bind_text: " + std::string(sqlite3_errmsg(database->get_handle())), 1);
if(sqlite3_bind_int(stmt, 2, count) != SQLITE_OK) {
neroshop::print("sqlite3_bind_int: " + std::string(sqlite3_errmsg(database->get_handle())), 1);
sqlite3_finalize(stmt);
return {};
}*/
return {};//database->execute("ROLLBACK;"); return {};
}
//-------------------------------------------------------
// Check whether the prepared statement returns no data (for example an UPDATE)
if(sqlite3_column_count(stmt) == 0) {
@ -614,6 +618,10 @@ QVariantList neroshop::Backend::getSearchResults(const QString& searchTerm) {
// Parse the response
nlohmann::json json = nlohmann::json::parse(response);
if(json.contains("error")) {
int rescode = database->execute_params("DELETE FROM mappings WHERE key = ?1", { key.toStdString() });
if(rescode != SQLITE_OK) neroshop::print("sqlite error: DELETE failed", 1);
//emit categoryProductCountChanged();//(category_id);
//emit searchResultsChanged();
continue; // Key is lost or missing from DHT, skip to next iteration
}
@ -701,6 +709,10 @@ QVariantList neroshop::Backend::getListings() {
// Parse the response
nlohmann::json json = nlohmann::json::parse(response);
if(json.contains("error")) {
int rescode = database->execute_params("DELETE FROM mappings WHERE key = ?1", { key.toStdString() });
if(rescode != SQLITE_OK) neroshop::print("sqlite error: DELETE failed", 1);
//emit categoryProductCountChanged();//(category_id);
//emit searchResultsChanged();
continue; // Key is lost or missing from DHT, skip to next iteration
}
@ -801,6 +813,10 @@ QVariantList neroshop::Backend::getListingsByCategory(int category_id) {
// Parse the response
nlohmann::json json = nlohmann::json::parse(response);
if(json.contains("error")) {
int rescode = database->execute_params("DELETE FROM mappings WHERE key = ?1", { key.toStdString() });
if(rescode != SQLITE_OK) neroshop::print("sqlite error: DELETE failed", 1);
//emit categoryProductCountChanged();//(category_id);
//emit searchResultsChanged();
continue; // Key is lost or missing from DHT, skip to next iteration
}

@ -18,8 +18,10 @@ class Backend : public QObject { // This class was created for storing utility f
Q_OBJECT
public:
Backend(QObject *parent = nullptr);
~Backend();
//Q_PROPERTY(int categoryProductCount READ getCategoryProductCount NOTIFY categoryProductCountChanged)
//Q_PROPERTY(QVariantList searchResults READ getSearchResults NOTIFY searchResultsChanged)
Q_INVOKABLE QString urlToLocalFile(const QUrl& url) const;
Q_INVOKABLE void copyTextToClipboard(const QString& text);
@ -62,7 +64,7 @@ public:
//Q_INVOKABLE QVariantList getProducts(); // Registered products
Q_INVOKABLE QVariantList getListingsByMostRecentLimit(int limit);
Q_INVOKABLE QVariantList getSearchResults(const QString& search_term);
Q_INVOKABLE QVariantList getSearchResults(const QString& search_term, int count = 1000); // count is the maximum number of search results (total). The search results (per page) can be between 10-100 or 50-100
// Products should be registered so that sellers can list pre-existing products without the need to duplicate a product which is unnecessary and can make the database bloated
Q_INVOKABLE void uploadProductImage(const QString& product_id, const QString& filename);
@ -85,8 +87,9 @@ public:
static int deleteExpiredOrders();
/*signals:
void categoryProductCountChanged();//(int category_id);*/
signals:
//void categoryProductCountChanged();//(int category_id);
//void searchResultsChanged();
private:
};
}

Loading…
Cancel
Save