do some work on profile page, edit readme, etc.

pull/168/head
larteyoh 11 months ago
parent a80c3babe4
commit bd1847eecc

@ -1,12 +1,12 @@
# neroshop - WORK IN PROGRESS / ON HOLD
# neroshop - WORK IN PROGRESS
[![banner](assets/images/appicons/LogoLight250x250.png)](https://github.com/larteyoh/testshop "neroshop logo")
A decentralized P2P (peer-to-peer) marketplace for [**Monero**](https://getmonero.org/) users (PoC)
__Disclaimer: This is an experimental/hobbyist project that is not ready for production use. Use at your own risk.__
__The neroshop team is comprised of a single developer that operates independently and is not affiliated, associated, authorized, endorsed by, or in any way officially connected with the Monero project, Monero team or any other organization.__
__Disclaimer: The neroshop team is comprised of a single developer that operates independently and is not affiliated, associated, authorized, endorsed by, or in any way officially connected with the Monero project, Monero team or any other organization.__
__Also, this is an experimental/hobbyist project that is not ready for production use. Use at your own risk.__
## Table of contents
<!-- - [The history behind neroshop](#about)-->
@ -55,7 +55,7 @@ The name _neroshop_ is a combination of the words _nero_, which is Italian for _
-->
## Feature Status
- [x] Distributed P2P network (need help with I2P integration!!!)
- [x] Distributed P2P network (need help with I2P integration or NAT traversal!!!)
- [ ] Buy and sell products and services with Monero
- [x] No KYC
- [x] No censorship (censorship-resistant)
@ -265,6 +265,8 @@ amount sender timestamp
## Resources
* Website: [neroshop.org](https://neroshop.org/)
* Neroshop DHT Specification: [specs](https://github.com/larteyoh/specs)
* Wiki: [Wikipage](https://github.com/larteyoh/testshop/wiki)
* Git (Unofficial): [github.com/larteyoh/testshop](https://github.com/larteyoh/testshop)

@ -62,6 +62,8 @@ Object {
property string tag: "\uf02b"
property string tags: "\uf02c"
property string truck: "\uf0d1"
property string thumbsUp: "\uf164"
property string thumbsDown: "\uf165"
property string dolly: "\uf472"
property string personDolly: "\uf4d0" // use this icon for item pickup option
property string paperPlane: "\uf1d8"

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 58 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 87 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 62 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 91 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 28 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 483 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 532 B

@ -101,6 +101,8 @@
<file>assets/images/star.png</file>
<file>assets/images/star_half.png</file>
<file>assets/images/trash.png</file>
<file>assets/images/thumbs_down.png</file>
<file>assets/images/thumbs_up.png</file>
<file>assets/images/usb.png</file>
<file>assets/images/user.png</file>
<file>assets/images/wallet.png</file>

@ -21,8 +21,8 @@ ApplicationWindow {
title: qsTr("neroshop" + " v" + neroshopVersion)
width: 1280//Script.getJsonRootObject()["window_width"]
height: 900//Script.getJsonRootObject()["window_height"]
minimumWidth: 850
minimumHeight: 500
minimumWidth: 1024
minimumHeight: 768
color: NeroshopComponents.Style.getColorsFromTheme()[0]
header: Rectangle {

@ -132,6 +132,7 @@ Page {
messageBox.open()
return; // exit function and do not proceed any further
}
// Save the avatar image to datastore folder for later use
let account_key = register_result[1];
if(avatarImage.status === Image.Ready) {
Backend.saveAvatarImage(Backend.urlToLocalFile(avatarImage.source), account_key)

@ -15,7 +15,7 @@ Page {
}
property var model: null
function openSellerPage() {
pageLoader.setSource("qrc:/qml/pages/ProfilePage.qml");//, {"model": [""]})
pageLoader.setSource("qrc:/qml/pages/ProfilePage.qml", {"productModel": productPage.model})
}
Flickable {

@ -4,6 +4,8 @@ import QtQuick.Controls 2.12
import QtQuick.Layouts 1.12
import QtGraphicalEffects 1.12 // ColorOverlay
import FontAwesome 1.0
import "../components" as NeroshopComponents
Page {
@ -11,25 +13,53 @@ Page {
background: Rectangle {
color: "transparent"
}
property var sellerModel: null // accountModel
property var listingModel: null
property var ratingsModel: null
property var userModel: Backend.getUser(productModel.seller_id) // accountModel
property var productModel: null // <- the product listing that redirected user to this profile page
property var ratingsModel: Backend.getSellerRatings(productModel.seller_id)
property var listingsModel: null//Backend.getListingsBySearchTerm(productModel.seller_id)// or Backend.getInventory(productModel.seller_id)
ColumnLayout {
width: parent.width
spacing: 0//10 - topMargin already set for profilePictureRect
RowLayout {
spacing: 24
Column {
// Back button
Button {
id: backButton
Layout.alignment: Qt.AlignTop | Qt.AlignLeft
Layout.leftMargin: 24; Layout.topMargin: 20
implicitWidth: contentItem.contentWidth + 40; implicitHeight: contentItem.contentHeight + 20
text: qsTr("← Back")//" Back")
hoverEnabled: true
contentItem: Text {
horizontalAlignment: Text.AlignHCenter
verticalAlignment: Text.AlignVCenter
text: backButton.text
color: "#ffffff"
}
background: Rectangle {
radius: 5
color: backButton.hovered ? NeroshopComponents.Style.neroshopPurpleColor : "#50446f"
}
onClicked: {
pageLoader.setSource("qrc:/qml/pages/ProductPage.qml", {"model": productModel})
}
MouseArea {
anchors.fill: parent
onPressed: mouse.accepted = false
cursorShape: Qt.PointingHandCursor
}
}
ColumnLayout {
Layout.alignment: Qt.AlignTop | Qt.AlignLeft
Layout.leftMargin: 24; Layout.topMargin: 20
Layout.leftMargin: 24; Layout.rightMargin: Layout.leftMargin
Layout.topMargin: 20
Layout.fillWidth: true
Rectangle {
id: profileCard
width: 400//Layout.fillWidth: true//width: parent.width
height: 500//Layout.preferredHeight: 200
Layout.fillWidth: true//width: parent.width//width: 400//
Layout.preferredHeight: 300//height: 500//
color: (bannerImage.status != Image.Ready) ? "royalblue" : "transparent"
radius: 7
@ -80,17 +110,19 @@ Page {
width: 128//Layout.preferredWidth: 128
height: width//Layout.preferredHeight: Layout.preferredWidth
color: infoRect.color// originally no color was set for this so the default was white
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"
source: !userModel.hasOwnProperty("avatar") ? "qrc:/assets/images/appicons/LogoLight250x250.png" : "image://avatar?id=%1&image_id=%2".arg(userModel.key).arg(userModel.avatar.name)
anchors.centerIn: parent
width: parent.width - profilePictureRect.border.width; height: width
width: parent.width - (profilePictureRect.border.width * 2); height: width
fillMode: Image.PreserveAspectFit
mipmap: true
asynchronous: true
// Apply rounded rectangle mask (radius)
layer.enabled: true
layer.effect: OpacityMask {
@ -103,28 +135,79 @@ Page {
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
anchors.leftMargin: profilePictureRect.border.width
// display name
Text {
text: "layter" // Replace with actual user name
text: userModel.hasOwnProperty("display_name") ? userModel.display_name : ""
font.pixelSize: 16//32
//font.bold: true
font.bold: true
color: (NeroshopComponents.Style.darkTheme) ? "#ffffff" : "#000000"
}
// user id
TextArea {
text: "5AWjbNUBf2EbbCw2v6ChrJUCdeRjfpcH5Y63wpWz37X6ZEiU9gvGeFqQpZczeVtZnd479FE4SDvKy7yF8ozj99QTRzcTY3a" // Replace with actual user ID
text: userModel.monero_address
font.pixelSize: 16
//font.bold: true
color: "dimgray"
color: (NeroshopComponents.Style.darkTheme) ? "#d0d0d0" : "#464646"
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
width: Math.min(infoRect.width, mainWindow.minimumWidth)//200
}
}
// buttonsRow
Row {
id: buttonsRow
layoutDirection: Qt.RightToLeft
anchors.right: parent.right; anchors.rightMargin: 24
anchors.top: profilePictureRect.top
anchors.topMargin: profilePicture.height / 3
spacing: 10
property real buttonRadius: 6
property string buttonColor: infoRect.color//NeroshopComponents.Style.neroshopPurpleColor
Button {
width: contentItem.contentWidth + 30; height: contentItem.contentHeight + 20
text: qsTr("Message")
background: Rectangle {
color: buttonsRow.buttonColor
radius: buttonsRow.buttonRadius
}
contentItem: Text {
text: parent.text
color: "#ffffff"
horizontalAlignment: Text.AlignHCenter
verticalAlignment: Text.AlignVCenter
}
onClicked: {}
MouseArea {
anchors.fill: parent
onPressed: mouse.accepted = false
cursorShape: Qt.PointingHandCursor
}
}
Button {
width: contentItem.contentWidth + 30; height: contentItem.contentHeight + 20
text: qsTr("Rate")
background: Rectangle {
color: buttonsRow.buttonColor
radius: buttonsRow.buttonRadius
}
contentItem: Text {
text: parent.text
color: "#ffffff"
horizontalAlignment: Text.AlignHCenter
verticalAlignment: Text.AlignVCenter
}
onClicked: {}
MouseArea {
anchors.fill: parent
onPressed: mouse.accepted = false
cursorShape: Qt.PointingHandCursor
}
}
}
// stats column
@ -133,34 +216,45 @@ Page {
anchors.top: nameIdColumn.bottom
anchors.topMargin: 10
anchors.left: profilePictureRect.left
anchors.leftMargin: 1
anchors.leftMargin: profilePictureRect.border.width
property int textIconSpacing: 5
property real iconSize: 24
width: profileCard.width
// stats row
Row {
id: statsRowActual
spacing: 100
// reputation
Column {
spacing: statsRow.textIconSpacing
Text {
// Deprecated/Replaced with hint (tooltip). Remove this soon!
/*Text {
text: "Reputation"
font.pixelSize: 16//32
//font.bold: true
color: (NeroshopComponents.Style.darkTheme) ? "#ffffff" : "#000000"
}
}*/
Row { // place thumbs in this row
spacing: 5
Rectangle {
//anchors.verticalCenter: parent.verticalCenter
//anchors.left: parent.left; anchors.leftMargin: width / 2
width: 100; height: 26
id: reputationRect
width: 100; height: 32
color: "transparent"
border.color: "#ffffff"
radius: 3
property bool hovered: false
Row {
anchors.fill: parent
anchors.centerIn: parent
spacing: 5
Item {
id: ratingIconRect
anchors.verticalCenter: parent.verticalCenter
width: childrenRect.width
height: childrenRect.height
Image {
id: ratingIcon
source: "qrc:/assets/images/rating.png"
@ -177,147 +271,258 @@ Page {
}
Text {
text: "97%"
anchors.verticalCenter: parent.verticalCenter
text: Backend.getSellerReputation(ratingsModel) + "%"
font.pixelSize: 16
color: (NeroshopComponents.Style.darkTheme) ? "#ffffff" : "#000000"
}
}
NeroshopComponents.Hint {
visible: parent.hovered
height: contentHeight + 20; width: contentWidth + 20
text: qsTr("Reputation")
pointer.visible: false;// delay: 0
}
MouseArea {
anchors.fill: parent
hoverEnabled: true
onEntered: parent.hovered = true
onExited: parent.hovered = false
}
}
}
// 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
// thumbs up/thumbs down
Rectangle {
anchors.top: parent.children[0].top
width: 50 + thumbsUpCountText.contentWidth; height: reputationRect.height
color: "transparent"
border.color: "#ffffff"
radius: 3
Row {
anchors.centerIn: parent
spacing: 5
Item {
id: thumbsUpImageItem
anchors.verticalCenter: parent.verticalCenter
width: childrenRect.width//statsRow.iconSize
height: childrenRect.height
visible: (thumbsUpImage.status === Image.Ready)
Image {
id: productIcon
source: "qrc:/assets/images/open_parcel.png"
id: thumbsUpImage
source: "qrc:/assets/images/thumbs_up.png"
width: statsRow.iconSize; height: width
mipmap: true
//mipmap: true
}
ColorOverlay {
anchors.fill: productIcon
source: productIcon
color: "#4169e1"
visible: productIcon.visible
id: thumbsUpImageOverlayer
anchors.fill: thumbsUpImage
source: thumbsUpImage
color: "green"//"#506a1a"
visible: thumbsUpImage.visible
}
}
Text {
id: fallbackThumbsUpIcon
anchors.verticalCenter: parent.verticalCenter
text:qsTr(FontAwesome.thumbsUp)
visible: !thumbsUpImageItem.visible
font.bold: true
color: thumbsUpImageOverlayer.color
}
Text {
id: thumbsUpCountText
anchors.verticalCenter: parent.verticalCenter
text: Backend.getSellerGoodRatings(ratingsModel)
font.pixelSize: 16
color: (NeroshopComponents.Style.darkTheme) ? "#ffffff" : "#000000"
}
}
}
}
}
/*Column {
Row {
////anchors.verticalCenter: parent.verticalCenter
anchors.left: parent.left; anchors.leftMargin: width / 2
//width: 32; height: 32
//color: "#fffbe5"
//radius: 3//50
} // thumbsUp Rect
// thumbs down rect
Rectangle {
anchors.top: parent.children[0].top
width: 50 + thumbsDownCountText.contentWidth; height: reputationRect.height
color: "transparent"
border.color: "#ffffff"
radius: 3
Row {
anchors.centerIn: parent
spacing: 5
Item {
id: thumbsDownImageItem
anchors.verticalCenter: parent.verticalCenter
width: childrenRect.width//statsRow.iconSize
height: childrenRect.height
visible: (thumbsDownImage.status === Image.Ready)
Image {
id: ratingIcon
source: "qrc:/assets/images/rating.png"
width: 32; height: 32
anchors.centerIn: parent
id: thumbsDownImage
source: "qrc:/assets/images/thumbs_down.png"
width: statsRow.iconSize + 2; height: width
//mipmap: true
}
ColorOverlay {
anchors.fill: ratingIcon
source: ratingIcon
color: "#ffd700"//"#e6c200"
visible: ratingIcon.visible
id: thumbsDownImageOverlayer
anchors.fill: thumbsDownImage
source: thumbsDownImage
color: "red"//"firebrick"
visible: thumbsDownImage.visible
}
}
}*/
////}
}
Text {
id: fallbackThumbsDownIcon
anchors.verticalCenter: parent.verticalCenter
text:qsTr(FontAwesome.thumbsDown)
visible: !thumbsDownImageItem.visible
font.bold: true
color: thumbsDownImageOverlayer.color
}
Text {
id: thumbsDownCountText
anchors.verticalCenter: parent.verticalCenter
text: Backend.getSellerBadRatings(ratingsModel)
font.pixelSize: 16
color: (NeroshopComponents.Style.darkTheme) ? "#ffffff" : "#000000"
}
}
} // thumbsDown Rect
} // row containing both thumbs and reputation
} // column for reputation text/reputation stats (can be safely removed)
} // end of statsRow
} // end of statsCol
/*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
// TODO: show mail letter icon for email, location icon for location, Link icon for website
} // profileCard (Rectangle)
} // ColumnLayout
// Tabs
Rectangle {
id: tabsRect
Layout.alignment: Qt.AlignTop | Qt.AlignRight
Layout.topMargin: 20; Layout.rightMargin: 24;
Layout.topMargin: 20;
Layout.leftMargin: 24; Layout.rightMargin: Layout.leftMargin
Layout.fillWidth: true
Layout.preferredHeight: 50
color: "#343434"
radius: profileCard.radius
color: "royalblue"//NeroshopComponents.Style.neroshopPurpleColor//infoRect.color
radius: 0//profileCard.radius
Row {
anchors.verticalCenter: parent.verticalCenter
id: tabButtonRow
anchors.bottom: parent.bottom//anchors.verticalCenter: parent.verticalCenter
anchors.left: parent.left; anchors.leftMargin: 10
spacing: 15
spacing: 0////15
property real buttonRadius: 5
property string buttonCheckedColor: NeroshopComponents.Style.getColorsFromTheme()[0]//"transparent"//TODO: this should be the same color as the stacklayout/tabpage
property string buttonUncheckedColor: tabsRect.color
Button {
width: contentItem.contentWidth + 20; height: contentItem.contentHeight + 20
text: qsTr("Listing")
id: listingsTabButton
width: (tabsRect.width / tabButtonRow.children.length) - tabButtonRow.anchors.leftMargin/*!listingsCountRect.visible ? 100 : 100 + listingsCountText.contentWidth*/; height: 40
text: qsTr("Listings")
autoExclusive: true
checkable: true
checked: true // default
background: Rectangle {
color: NeroshopComponents.Style.neroshopPurpleColor
radius: 3
color: parent.checked ? tabButtonRow.buttonCheckedColor : tabButtonRow.buttonUncheckedColor
radius: tabButtonRow.buttonRadius
// To hide bottom radius
Rectangle {
anchors.left: parent.left
anchors.bottom: parent.bottom
width: parent.width
height: 5
color: parent.color//"pink"// <- for testing
}
}
contentItem: Text {
text: parent.text
color: "#ffffff"
horizontalAlignment: Text.AlignHCenter
verticalAlignment: Text.AlignVCenter
font.bold: true
contentItem: Item {
anchors.fill: parent
Row {
anchors.centerIn: parent
spacing: 10
Text {
text: listingsTabButton.text
color: "#ffffff"
anchors.verticalCenter: parent.verticalCenter
font.bold: true
}
Rectangle {
id: listingsCountRect
anchors.verticalCenter: parent.verticalCenter
width: listingsCountText.contentWidth + 15; height: 20
color: "#101010"
radius: 3
visible: Number(listingsCountText.text) > 0 && !listingsTabButton.checked
Text {
id: listingsCountText
anchors.horizontalCenter: parent.horizontalCenter
anchors.verticalCenter: parent.verticalCenter
text: "0"
color: "#ffffff"
font.pixelSize: 12
}
}
}
}
onClicked: {}
}
Button {
width: contentItem.contentWidth + 20; height: contentItem.contentHeight + 20
id: ratingsTabButton
width: (tabsRect.width / tabButtonRow.children.length) - tabButtonRow.anchors.leftMargin/*!ratingsCountRect.visible ? 100 : 100 + ratingsCountText.contentWidth*/; height: 40
text: qsTr("Ratings")
autoExclusive: true
checkable: true
background: Rectangle {
color: NeroshopComponents.Style.neroshopPurpleColor
radius: 3
color: parent.checked ? tabButtonRow.buttonCheckedColor : tabButtonRow.buttonUncheckedColor
radius: tabButtonRow.buttonRadius
// To hide bottom radius
Rectangle {
anchors.left: parent.left
anchors.bottom: parent.bottom
width: parent.width
height: 5
color: parent.color
}
}
contentItem: Text {
text: parent.text
color: "#ffffff"
horizontalAlignment: Text.AlignHCenter
verticalAlignment: Text.AlignVCenter
font.bold: true
contentItem: Item {
anchors.fill: parent
Row {
anchors.centerIn: parent
spacing: 10
Text {
text: ratingsTabButton.text
color: "#ffffff"
anchors.verticalCenter: parent.verticalCenter
font.bold: true
}
Rectangle {
id: ratingsCountRect
anchors.verticalCenter: parent.verticalCenter
width: ratingsCountText.contentWidth + 15; height: 20
color: "#101010"
radius: 3
visible: Number(ratingsCountText.text) > 0 && !ratingsTabButton.checked
Text {
id: ratingsCountText
anchors.horizontalCenter: parent.horizontalCenter
anchors.verticalCenter: parent.verticalCenter
text: Backend.getSellerRatingsCount(ratingsModel)
color: "#ffffff"
font.pixelSize: 12
}
}
}
}
onClicked: {}
}
}
}
} // RowLayout
/*NeroshopComponents.TabBar {
id: tabBar

@ -358,6 +358,13 @@ void neroshop::Seller::set_stock_quantity(const std::string& listing_key, int qu
bool verified = wallet->verify_message(listing_id, signature);
// Finally, modify the quantity
value_obj["quantity"] = quantity;
// Add a last_modified or last_updated field so nodes can compare dates and choose the most recent listing
auto now = std::chrono::system_clock::now();
auto in_time_t = std::chrono::system_clock::to_time_t(now); // current time
std::stringstream datetime;
datetime << std::put_time(std::gmtime(&in_time_t), "%Y-%m-%d %H:%M:%S");
std::string utc_time = datetime.str();
value_obj["last_modified"] = utc_time;
// Send set request containing the updated value with the same key as before
std::string modified_value = value_obj.dump();
std::string response;

@ -602,9 +602,8 @@ QVariantList neroshop::Backend::getProductRatings(const QString& product_id) {
}
//----------------------------------------------------------------
//----------------------------------------------------------------
int neroshop::Backend::getSellerGoodRatings(const QString& user_id) {
int neroshop::Backend::getSellerGoodRatings(const QVariantList& seller_ratings) {
int good_ratings_count = 0;
QVariantList seller_ratings = getSellerRatings(user_id);
// Get seller's good (positive) ratings
for (const QVariant& variant : seller_ratings) {
QVariantMap rating_obj = variant.toMap();
@ -616,9 +615,13 @@ int neroshop::Backend::getSellerGoodRatings(const QString& user_id) {
return good_ratings_count;
}
//----------------------------------------------------------------
int neroshop::Backend::getSellerBadRatings(const QString& user_id) {
int bad_ratings_count = 0;
int neroshop::Backend::getSellerGoodRatings(const QString& user_id) {
QVariantList seller_ratings = getSellerRatings(user_id);
return getSellerGoodRatings(seller_ratings);
}
//----------------------------------------------------------------
int neroshop::Backend::getSellerBadRatings(const QVariantList& seller_ratings) {
int bad_ratings_count = 0;
// Get seller's bad (negative) ratings
for (const QVariant& variant : seller_ratings) {
QVariantMap rating_obj = variant.toMap();
@ -630,15 +633,22 @@ int neroshop::Backend::getSellerBadRatings(const QString& user_id) {
return bad_ratings_count;
}
//----------------------------------------------------------------
int neroshop::Backend::getSellerBadRatings(const QString& user_id) {
QVariantList seller_ratings = getSellerRatings(user_id);
return getSellerBadRatings(seller_ratings);
}
//----------------------------------------------------------------
int neroshop::Backend::getSellerRatingsCount(const QVariantList& seller_ratings) {
return seller_ratings.size();
}
//----------------------------------------------------------------
int neroshop::Backend::getSellerRatingsCount(const QString& user_id) {
QVariantList seller_ratings = getSellerRatings(user_id);
int ratings_count = seller_ratings.size();
return ratings_count;
return getSellerRatingsCount(seller_ratings);
}
//----------------------------------------------------------------
int neroshop::Backend::getSellerReputation(const QString& user_id) {
int neroshop::Backend::getSellerReputation(const QVariantList& seller_ratings) {
int good_ratings_count = 0, bad_ratings_count = 0;
QVariantList seller_ratings = getSellerRatings(user_id);
int ratings_count = seller_ratings.size();
if(ratings_count <= 0) return 0; // seller has not yet been rated so his or her reputation will be 0%
// Get seller's good (positive) ratings
@ -654,6 +664,11 @@ int neroshop::Backend::getSellerReputation(const QString& user_id) {
return static_cast<int>(reputation); // convert reputation to an integer (for easier readability)
}
//----------------------------------------------------------------
int neroshop::Backend::getSellerReputation(const QString& user_id) {
QVariantList seller_ratings = getSellerRatings(user_id);
return getSellerReputation(seller_ratings);
}
//----------------------------------------------------------------
// returns an array of ratings objects
QVariantList neroshop::Backend::getSellerRatings(const QString& user_id) {
Client * client = Client::get_main_client();
@ -721,13 +736,39 @@ QVariantList neroshop::Backend::getSellerRatings(const QString& user_id) {
//----------------------------------------------------------------
//----------------------------------------------------------------
QString neroshop::Backend::getDisplayNameByUserId(const QString& user_id) {
db::Sqlite3 * database = neroshop::get_database();
if(!database) throw std::runtime_error("database is NULL");
std::string key = database->get_text_params("SELECT key FROM mappings WHERE search_term = ?1 AND content = 'account' LIMIT 1;", { user_id.toStdString() });
if(key.empty()) return user_id; // Key will never be empty as long as it exists in DHT
std::string display_name = database->get_text_params("SELECT search_term FROM mappings WHERE key = ?1 AND LENGTH(search_term) <= 30 AND content = 'account'", { key });
if(!display_name.empty()) {
return QString::fromStdString(display_name);
}
// If the display name happens to be empty then it means the user's account (DHT) key is lost or missing
if(display_name.empty()) {
std::cout << "Account key is lost or missing\n";
// Remove account key from database
int rescode = database->execute_params("DELETE FROM mappings WHERE key = ?1", { key });
if(rescode != SQLITE_OK) neroshop::print("sqlite error: DELETE failed", 1);
}
return user_id;
}
QString neroshop::Backend::getKeyByUserId(const QString& user_id) { // not currently in use
db::Sqlite3 * database = neroshop::get_database();
if(!database) throw std::runtime_error("database is NULL");
std::string key = database->get_text_params("SELECT key FROM mappings WHERE search_term = $1 AND content = 'account' LIMIT 1;", { user_id.toStdString() });
return QString::fromStdString(key);
}
//----------------------------------------------------------------
QVariantMap neroshop::Backend::getUser(const QString& user_id) {
Client * client = Client::get_main_client();
db::Sqlite3 * database = neroshop::get_database();
if(!database) throw std::runtime_error("database is NULL");
std::string display_name = "";
std::string key = database->get_text_params("SELECT key FROM mappings WHERE search_term = $1 AND content = 'account' LIMIT 1;", { user_id.toStdString() });
if(key.empty()) return user_id;
if(key.empty()) return {};
// Get the value of the corresponding key from the DHT
std::string response;
client->get(key, response); // TODO: error handling
@ -737,8 +778,10 @@ QString neroshop::Backend::getDisplayNameByUserId(const QString& user_id) {
if(json.contains("error")) {
int rescode = database->execute_params("DELETE FROM mappings WHERE key = ?1", { key });
if(rescode != SQLITE_OK) neroshop::print("sqlite error: DELETE failed", 1);
return user_id; // Key is lost or missing from DHT, skip to next iteration
return {}; // Key is lost or missing from DHT, skip to next iteration
}
QVariantMap user_object;
const auto& response_obj = json["response"];
assert(response_obj.is_object());
@ -747,22 +790,25 @@ QString neroshop::Backend::getDisplayNameByUserId(const QString& user_id) {
nlohmann::json value_obj = nlohmann::json::parse(value);
assert(value_obj.is_object());//std::cout << value_obj.dump(4) << "\n";
std::string metadata = value_obj["metadata"].get<std::string>();
if (metadata != "user") { std::cerr << "Invalid metadata. \"user\" expected, got \"" << metadata << "\" instead\n"; return user_id; }
if (metadata != "user") { std::cerr << "Invalid metadata. \"user\" expected, got \"" << metadata << "\" instead\n"; return {}; }
user_object.insert("key", QString::fromStdString(key));
if(value_obj.contains("display_name") && value_obj["display_name"].is_string()) {
display_name = value_obj["display_name"].get<std::string>();
std::string display_name = value_obj["display_name"].get<std::string>();
user_object.insert("display_name", QString::fromStdString(display_name));
}
return (display_name.empty()) ? user_id : QString::fromStdString(display_name);
user_object.insert("monero_address", QString::fromStdString(value_obj["monero_address"].get<std::string>()));
user_object.insert("user_id", QString::fromStdString(value_obj["monero_address"].get<std::string>())); // alias
user_object.insert("public_key", QString::fromStdString(value_obj["public_key"].get<std::string>()));
if(value_obj.contains("avatar") && value_obj["avatar"].is_object()) {
const auto& avatar_obj = value_obj["avatar"];
QVariantMap avatar;
avatar.insert("name", QString::fromStdString(avatar_obj["name"].get<std::string>()));
user_object.insert("avatar", avatar);
}
user_object.insert("signature", QString::fromStdString(value_obj["signature"].get<std::string>()));
}
return user_id;
}
// un-tested
QString neroshop::Backend::getKeyByUserId(const QString& user_id) {
db::Sqlite3 * database = neroshop::get_database();
if(!database) throw std::runtime_error("database is NULL");
std::string key = database->get_text_params("SELECT key FROM mappings WHERE search_term = $1 AND content = 'account' LIMIT 1;", { user_id.toStdString() });
return QString::fromStdString(key);
return user_object;
}
//----------------------------------------------------------------
//----------------------------------------------------------------

@ -85,9 +85,13 @@ public:
Q_INVOKABLE int getProductStarCount(const QString& product_id, int star_number);
Q_INVOKABLE float getProductAverageStars(const QString& product_id);
Q_INVOKABLE int getSellerGoodRatings(const QVariantList& seller_ratings);
Q_INVOKABLE int getSellerGoodRatings(const QString& user_id);
Q_INVOKABLE int getSellerBadRatings(const QVariantList& seller_ratings);
Q_INVOKABLE int getSellerBadRatings(const QString& user_id);
Q_INVOKABLE int getSellerRatingsCount(const QVariantList& seller_ratings);
Q_INVOKABLE int getSellerRatingsCount(const QString& user_id);
Q_INVOKABLE int getSellerReputation(const QVariantList& seller_ratings);
Q_INVOKABLE int getSellerReputation(const QString& user_id);
// Rating models
Q_INVOKABLE QVariantList getProductRatings(const QString& product_id/*listing_id*/); // or do I use user account key?
@ -95,12 +99,14 @@ public:
Q_INVOKABLE QString getDisplayNameByUserId(const QString& user_id);
Q_INVOKABLE QString getKeyByUserId(const QString& user_id);
// User model
Q_INVOKABLE QVariantMap getUser(const QString& user_id);
Q_INVOKABLE int getCartMaximumItems();
Q_INVOKABLE int getCartMaximumQuantity();
Q_INVOKABLE int getStockAvailable(const QString& product_id);
// Inventory model
Q_INVOKABLE QVariantList getInventory(const QString& user_id);
Q_INVOKABLE void createOrder(UserController * user_controller, const QString& shipping_address);

@ -34,7 +34,7 @@ QImage ImageLoader::load(const QString &id) const
// You can use https://doc.qt.io/qt-5/qurlquery.html for parsing id
QImage result;
// listings
if(id.contains(QStringLiteral("listing"))) {
// Copy id text to a url (image://catalog?id=%1&index=%2)
QUrl url(QString(id).replace('/', '?'));
@ -44,25 +44,62 @@ QImage ImageLoader::load(const QString &id) const
if(!query.hasQueryItem(QString("id"))) {
std::cout << "key 'id' not found in query\n";
}
if(!query.hasQueryItem(QString("image_id"))) {
std::cout << "key 'image_id' not found in query. Using default image\n";
}
// Get key-value pair
QString listing_key = query.queryItemValue("id");//std::cout << "id: " << listing_key.toStdString() << std::endl;
QString image_name = query.queryItemValue(QString("image_id"));//std::cout << "image_id: " << image_id.toStdString() << std::endl;
if(query.hasQueryItem(QString("image_id"))) {
// Get key-value pair
QString listing_key = query.queryItemValue("id");//std::cout << "id: " << listing_key.toStdString() << std::endl;
QString image_name = query.queryItemValue(QString("image_id"));//std::cout << "image_id: " << image_id.toStdString() << std::endl;
// Note: Images with large resolutions (ex. 4000 pixels) will crash this code for some reason
// Note: Images with large resolutions (ex. 4000 pixels) will crash this code for some reason
QString filename = getProductImagePath(listing_key, image_name);//std::cout << "Loading image from: " << filename.toStdString() << "\n";
QString filename = getProductImagePath(listing_key, image_name);//std::cout << "Loading image from: " << filename.toStdString() << "\n";
if (!filename.isEmpty()) {
QFile file(filename);
if (file.open(QFile::ReadOnly)) {
result.loadFromData(file.readAll());
if (!filename.isEmpty()) {
QFile file(filename);
if (file.open(QFile::ReadOnly)) {
result.loadFromData(file.readAll());
}
}
}
}
}
// avatars
if(id.contains(QStringLiteral("avatar"))) {
// Copy id text to a url
QUrl url(QString(id).replace('/', '?'));
// Construct query object and parse the query string found in the url, using the default query delimiters (=, &)
QUrlQuery query(url);
if(!query.isEmpty()) {
if(!query.hasQueryItem(QString("id"))) {
std::cout << "key 'id' not found in query\n";
}
if(!query.hasQueryItem(QString("image_id"))) {
std::cout << "key 'image_id' not found in query. Using default image\n";
}
if(query.hasQueryItem(QString("image_id"))) {
// Get key-value pair
QString user_key = query.queryItemValue("id");//std::cout << "id: " << user_key.toStdString() << std::endl;
QString image_name = query.queryItemValue(QString("image_id"));//std::cout << "image_id: " << image_id.toStdString() << std::endl;
// Note: Images with large resolutions (ex. 4000 pixels) will crash this code for some reason
QString filename = getAvatarImagePath(user_key, image_name);//std::cout << "Loading image from: " << filename.toStdString() << "\n";
if (!filename.isEmpty()) {
QFile file(filename);
if (file.open(QFile::ReadOnly)) {
result.loadFromData(file.readAll());
}
}
}
}
}
return result;
}
@ -78,15 +115,15 @@ QString ImageLoader::getProductImagePath(const QString& listing_key, const QStri
return QString::fromStdString(product_image);
}
QString ImageLoader::getAvatarImagePath(const QString& user_key) const {
QString ImageLoader::getAvatarImagePath(const QString& user_key, const QString& image_name) const {
std::string config_path = NEROSHOP_DEFAULT_CONFIGURATION_PATH;
std::string cache_folder = config_path + "/" + NEROSHOP_CACHE_FOLDER_NAME;
std::string avatars_folder = cache_folder + "/" + NEROSHOP_AVATAR_FOLDER_NAME;
std::string user_key_folder = avatars_folder + "/" + user_key.toStdString();
//std::string avatar_image =
std::string avatar_image = user_key_folder + "/" + image_name.toStdString();
return "";//QString::fromStdString(avatar_image);
return QString::fromStdString(avatar_image);
}
static void preload()

@ -14,7 +14,7 @@ public:
QImage load(const QString &id) const;
QString getProductImagePath(const QString& listing_key, const QString& image_name/*or image id/index? index wont need a parameter*/) const;
QString getAvatarImagePath(const QString& user_key) const;
QString getAvatarImagePath(const QString& user_key, const QString& image_name) const;
};
#endif // IMAGELOADER_H

Loading…
Cancel
Save