mirror of https://github.com/layters/testshop
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
732 lines
39 KiB
732 lines
39 KiB
#include "seller.hpp"
|
|
|
|
#include "cart.hpp"
|
|
#include "tools/logger.hpp"
|
|
#include "tools/uuid.hpp"
|
|
#include "database/database.hpp"
|
|
#include "product.hpp"
|
|
#include "wallet/wallet.hpp"
|
|
#include "listing.hpp"
|
|
#include "product.hpp"
|
|
#include "protocol/transport/client.hpp"
|
|
#include "protocol/p2p/serializer.hpp"
|
|
#include "category.hpp"
|
|
#include "tools/base64.hpp"
|
|
#include "crypto/rsa.hpp"
|
|
#include "tools/timestamp.hpp"
|
|
|
|
#include <cmath> // floor
|
|
#include <random>
|
|
|
|
neroshop::Seller::Seller()
|
|
{}
|
|
////////////////////
|
|
////////////////////
|
|
neroshop::Seller::Seller(const std::string& name) : Seller() {
|
|
set_name(name);
|
|
}
|
|
////////////////////
|
|
////////////////////
|
|
neroshop::Seller::~Seller() {
|
|
// clear customer orders
|
|
customer_order_list.clear(); // will reset (delete) all customer orders
|
|
#ifdef NEROSHOP_DEBUG
|
|
std::cout << "seller deleted\n";
|
|
#endif
|
|
}
|
|
////////////////////
|
|
////////////////////
|
|
////////////////////
|
|
////////////////////
|
|
////////////////////
|
|
////////////////////
|
|
std::string neroshop::Seller::list_item(
|
|
const std::string& name,
|
|
const std::string& description,
|
|
const std::vector<Attribute>& attributes,
|
|
const std::string& product_code,
|
|
int category_id,
|
|
const std::vector<int>& subcategory_ids,
|
|
const std::vector<std::string>& tags,
|
|
const std::vector<Image>& images,
|
|
|
|
unsigned int quantity,
|
|
double price,
|
|
const std::string& currency,
|
|
const std::string& condition,
|
|
const std::string& location,
|
|
unsigned int quantity_per_order
|
|
) const
|
|
{
|
|
// Transition from Sqlite to DHT:
|
|
Client * client = Client::get_main_client();
|
|
// Create product object
|
|
const std::string listing_id = neroshop::uuid::generate();//std::cout << "listing id: " << listing_id << "\n";
|
|
const std::string product_id = listing_id;
|
|
Product product {
|
|
product_id, name, description, attributes,
|
|
product_code, static_cast<unsigned int>(category_id), subcategory_ids, tags,
|
|
images
|
|
};
|
|
// Create listing object
|
|
const std::string seller_id = get_id();//std::cout << "seller_id: " << seller_id << "\n";
|
|
|
|
std::string created_at = neroshop::timestamp::get_current_utc_timestamp();//std::cout << "created_at: " << created_at << "\n";
|
|
|
|
std::string signature = wallet->sign_message(listing_id, monero_message_signature_type::SIGN_WITH_SPEND_KEY);//std::cout << "signature: " << signature << "\n\n";
|
|
|
|
#ifdef NEROSHOP_DEBUG0
|
|
auto result = wallet->verify_message(listing_id, signature);
|
|
std::cout << "\033[1mverified: " << (result == 1 ? "\033[32mpass" : "\033[91mfail") << "\033[0m\n";
|
|
assert(result == true);
|
|
#endif
|
|
|
|
Listing listing { listing_id, product, seller_id,
|
|
quantity, price, currency,
|
|
condition, location, created_at, signature, quantity_per_order
|
|
};//listing.print_listing();
|
|
|
|
auto data = Serializer::serialize(listing);
|
|
std::string key = data.first;
|
|
std::string value = data.second;//std::cout << "key: " << data.first << "\nvalue: " << data.second << "\n";
|
|
|
|
// Send put request to neighboring nodes (and your node too JIC)
|
|
std::string response;
|
|
client->put(key, value, response);
|
|
std::cout << "Received response (put): " << response << "\n";
|
|
|
|
// Return listing key
|
|
return key;
|
|
}
|
|
////////////////////
|
|
void neroshop::Seller::delist_item(const std::string& listing_key) {
|
|
// Transition from Sqlite to DHT:
|
|
Client * client = Client::get_main_client();
|
|
// TODO: remove product from table cart_item, table images, table products, and table product_ratings as well
|
|
// Get the value of the corresponding key from the DHT
|
|
std::string response;
|
|
client->get(listing_key, response); // TODO: error handling
|
|
std::cout << "Received response (get): " << response << "\n";
|
|
// Parse the response
|
|
nlohmann::json json = nlohmann::json::parse(response);
|
|
if(json.contains("error")) {
|
|
neroshop::print("set_stock_quantity: key is lost or missing from DHT", 1);
|
|
return; // Key is lost or missing from DHT, return
|
|
}
|
|
|
|
const auto& response_obj = json["response"];
|
|
assert(response_obj.is_object());
|
|
if (response_obj.contains("value") && response_obj["value"].is_string()) {
|
|
const auto& value = response_obj["value"].get<std::string>();
|
|
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 != "listing") { std::cerr << "Invalid metadata. \"listing\" expected, got \"" << metadata << "\" instead\n"; return; }
|
|
// Verify ownership
|
|
std::string seller_id = value_obj["seller_id"].get<std::string>();
|
|
if(seller_id != wallet->get_primary_address()) {
|
|
neroshop::print("delist_item: you cannot delist this since you are not the listing's creator", 1);
|
|
return;
|
|
}
|
|
// Verify the signature
|
|
std::string listing_id = value_obj["id"].get<std::string>();
|
|
std::string old_signature = value_obj["signature"].get<std::string>();
|
|
bool self_verified = wallet->verify_message(listing_id, old_signature);
|
|
if(!self_verified) { neroshop::print("Data verification failed.", 1); return; }
|
|
// Might be a good idea to set the stock quantity to zero beforehand
|
|
value_obj["quantity"] = 0;
|
|
// Finally, set the expiration date
|
|
// But extend the expiration date to give enough time for all nodes in the network to update the listing in their hash tables
|
|
value_obj["expiration_date"] = neroshop::timestamp::get_utc_timestamp_after_duration(24, "hour");
|
|
// Re-sign to reflect the modification
|
|
std::string signature = wallet->sign_message(listing_id, monero_message_signature_type::SIGN_WITH_SPEND_KEY);
|
|
value_obj["signature"] = signature;
|
|
value_obj["last_updated"] = neroshop::timestamp::get_current_utc_timestamp();
|
|
// Send set request containing the updated value with the same key as before
|
|
std::string modified_value = value_obj.dump();
|
|
std::string response;
|
|
client->set(listing_key, modified_value, response);
|
|
std::cout << "Received response (set): " << response << "\n";
|
|
}
|
|
}
|
|
////////////////////
|
|
////////////////////
|
|
////////////////////
|
|
// the moment the seller logs in, they should be notified that they have a pending transaction from a customer
|
|
// and should respond swiftly
|
|
// if seller accepts the order, then an address will be generated from seller's wallet and sent to the customer
|
|
// if seller rejects the order, their stock_qty is increased by the failed order's qty
|
|
void neroshop::Seller::load_customer_orders() {
|
|
/*db::Sqlite3 db("neroshop.db");
|
|
///////////
|
|
if(!db.table_exists("order_item")) return; // seller has probably never received an order from a customer before
|
|
// check for orders made by customers
|
|
// get last inserted order item
|
|
int last_order_item = db.get_column_integer("order_item ORDER BY id DESC LIMIT 1", "*");
|
|
// get all order_items
|
|
int customer_order_item_count = db.get_column_integer("order_item", "COUNT(*)", "seller_id = " + get_id());
|
|
//std::cout << "number of items that customers have ordered from you: " << customer_order_item_count << std::endl;
|
|
if(customer_order_item_count < 1) neroshop::print("No buyer has ordered an item from you yet");
|
|
if(customer_order_item_count > 0) {
|
|
for(unsigned int i = 1; i <= last_order_item; i++) {
|
|
//if order_item's order_id is duplicated, then it means there are multiple unique items in the order
|
|
unsigned int order_product_id = db.get_column_integer("order_item", "id", "id = " + std::to_string(i) + " AND seller_id = " + get_id());
|
|
if(order_product_id == 0) continue; // skip 0's
|
|
// get order_id of the order_item
|
|
unsigned int order_id = db.get_column_integer("order_item", "order_id", "id = " + std::to_string(i) + " AND seller_id = " + get_id());//if(order_id == 0) continue; // skip 0's
|
|
// store order_ids if not already stored
|
|
if(std::find(customer_order_list.begin(), customer_order_list.end(), order_id) == customer_order_list.end()) {
|
|
customer_order_list.push_back(order_id); //Order * order = new Order(order_id);//customer_order_list.push_back(order);
|
|
neroshop::print("Customer order (id: " + std::to_string(order_id) + ") has been loaded");
|
|
}
|
|
// get items in the order_item table
|
|
const std::string& product_id = db.get_column_integer("order_item", "product_id", "id = " + std::to_string(i) + " AND seller_id = " + get_id());
|
|
unsigned int item_qty = db.get_column_integer("order_item", "item_qty", "id = " + std::to_string(i) + " AND seller_id = " + get_id());
|
|
Product item(product_id); // item obj will die at the end of this scope
|
|
std::cout << "You've received an order (id: " << order_id << ") from a customer "
|
|
<< "containing items: " << item.get_name() << " (id: " << product_id << ", qty: " << item_qty << ")" << std::endl;
|
|
}
|
|
}
|
|
///////////
|
|
db.close();*/
|
|
////////////////////////////////
|
|
// postgresql
|
|
////////////////////////////////
|
|
#if defined(NEROSHOP_USE_POSTGRESQL)
|
|
//database->connect("host=127.0.0.1 port=5432 user=postgres password=postgres dbname=neroshoptest");
|
|
////////////////////////////////
|
|
// get number of order_items to be purchased by customers from this particular seller
|
|
int seller_customer_order_item_count = database->get_integer_params("SELECT COUNT(*) FROM order_item WHERE seller_id = $1", { get_id() });
|
|
if(seller_customer_order_item_count < 1) {neroshop::print("No buyer has ordered an item from you yet"); return;}
|
|
// load customer orders
|
|
std::string command = "SELECT order_id FROM order_item WHERE seller_id = $1 ORDER BY order_id";
|
|
std::vector<const char *> param_values = { get_id().c_str() };
|
|
PGresult * result = PQexecParams(database->get_handle(), command.c_str(), 1, nullptr, param_values.data(), nullptr, nullptr, 0);
|
|
if (PQresultStatus(result) != PGRES_TUPLES_OK) {
|
|
neroshop::print("Seller::load_customer_orders(): No customer orders found", 2);
|
|
PQclear(result);
|
|
//exit(1);
|
|
return; // exit so we don't double free "result"
|
|
}
|
|
int rows = PQntuples(result);
|
|
for(int i = 0; i < rows; i++) {
|
|
int order_id = std::stoi(PQgetvalue(result, i, 0));
|
|
// store order_ids if not already stored
|
|
if(std::find(customer_order_list.begin(), customer_order_list.end(), order_id) == customer_order_list.end()) {
|
|
customer_order_list.push_back(order_id); //Order * order = new Order(order_id);//customer_order_list.push_back(order);
|
|
neroshop::print("Customer order (id: " + std::to_string(order_id) + ") has been loaded");
|
|
}
|
|
/*#ifdef NEROSHOP_DEBUG0
|
|
// get items in the order_item table
|
|
const std::string& product_id = database->get_integer_params("SELECT product_id FROM order_item WHERE id = $1 AND seller_id = $2", { std::to_string(i), get_id() });
|
|
unsigned int item_qty = database->get_integer_params("SELECT item_qty FROM order_item WHERE id = $1 AND seller_id = $2", { std::to_string(i), get_id() });
|
|
Product item(product_id); // item obj will die at the end of this scope
|
|
std::cout << "You've received an order (id: " << order_id << ") from a customer "
|
|
<< "containing items: " << item.get_name() << " (id: " << product_id << ", qty: " << item_qty << ")" << std::endl;
|
|
#endif */
|
|
} //database->get_integer_params("SELECT product_id FROM order_item WHERE id = $1 AND seller_id = $2", { std::to_string(i), get_id() });
|
|
////////////////////////////////
|
|
|
|
////////////////////////////////
|
|
#endif
|
|
}
|
|
////////////////////
|
|
// THIS FUNCTION WILL BE LISTENING FOR ANY NEW (PENDING) ORDERS AT ALL TIMES
|
|
void neroshop::Seller::update_customer_orders() { // this function is faster (I think) than load_customer_orders()
|
|
#if defined(NEROSHOP_USE_POSTGRESQL)
|
|
//database->connect("host=127.0.0.1 port=5432 user=postgres password=postgres dbname=neroshoptest");
|
|
////////////////////////////////
|
|
//customer_order_list.clear(); // No need to clear customer_orders since it only inserts unique order_ids, so it will not take any duplicates
|
|
std::string command = "SELECT order_id FROM order_item WHERE seller_id = $1";
|
|
std::vector<const char *> param_values = { get_id().c_str() };
|
|
PGresult * result = PQexecParams(database->get_handle(), command.c_str(), 1, nullptr, param_values.data(), nullptr, nullptr, 0);
|
|
if (PQresultStatus(result) != PGRES_TUPLES_OK) {
|
|
neroshop::print("Seller::update_customer_orders(): No customer orders found", 2);
|
|
PQclear(result);
|
|
//exit(1);
|
|
return; // exit so we don't double free "result" or double close the database
|
|
}
|
|
int rows = PQntuples(result);
|
|
for(int i = 0; i < rows; i++) {
|
|
int customer_order_id = std::stoi(PQgetvalue(result, i, 0));
|
|
// store order_ids if not already stored (does NOT store duplicates)
|
|
if(std::find(customer_order_list.begin(), customer_order_list.end(), customer_order_id) == customer_order_list.end()) {
|
|
// check if order is a pending order
|
|
bool is_pending = database->get_integer_params("SELECT id FROM orders WHERE id = $1 AND status = $2", { std::to_string(customer_order_id), "Pending" });
|
|
if(is_pending) {
|
|
std::cout << "You have received a new customer order (status: PENDING)" << std::endl; // for terminal
|
|
neroshop::Message::get_first()->set_text("You have received a new customer order (status: PENDING)");//\n Do you wish to proceed with this order?");
|
|
// maybe send the seller an email as well? :O
|
|
// Do you wish to process this order?
|
|
// [accept] [decline]
|
|
// box text
|
|
//int vertical_padding = 20; // top and bottom padding
|
|
neroshop::Message::get_first()->get_label(0)->set_alignment("none");
|
|
neroshop::Message::get_first()->get_label(0)->set_relative_position((neroshop::Message::get_first()->get_width() / 2) - (neroshop::Message::get_first()->get_label(0)->get_string().length() * 10/*neroshop::Message::get_first()->get_label(0)->get_width()*/ / 2), ((neroshop::Message::get_first()->get_height() - 10/*neroshop::Message::get_first()->get_label(0)->get_height()*/) / 2) - 20); // 50=label_rel_y_pos
|
|
// box buttons - CRASH SITE
|
|
Button * button0 = neroshop::Message::get_first()->get_button(0);
|
|
Button * button1 = neroshop::Message::get_first()->get_button(1);
|
|
button0->set_text("Accept");//Accept//Respond//View//Reply//Answer//Return
|
|
button1->set_text("Decline");//Decline//Refuse//Ignore//Reject//Forget//Mark as read
|
|
button0->set_width(100);
|
|
button1->set_width(100);
|
|
// the height of the msgbox almost NEVER changes, only its width
|
|
int button_gap = 10; // the space between button0 and button1
|
|
button0->set_relative_position((neroshop::Message::get_first()->get_width() / 2) - (button0->get_width() / 2) - ((button1->get_width() + button_gap) / 2), neroshop::Message::get_first()->get_height() - button0->get_height() - 20);//20 = bottom_padding
|
|
button1->set_relative_position(button0->get_relative_x() + button0->get_width() + button_gap, button0->get_relative_y());
|
|
button0->set_color(0, 107, 61, 1.0);//(99, 151, 84, 1.0);//
|
|
button1->set_color(214, 31, 31, 1.0);//(224, 60, 50, 1.0);//
|
|
button0->show();
|
|
button1->show();
|
|
// Supply a subaddress or generate a unique subaddress from your wallet (for receiving funds from the customer)
|
|
// BUT if a seller supplies an address, then he/she will not receive the notification that they've received a deposit of x amount of xmr into their wallet
|
|
//[supply address] [generate]
|
|
// if no, then seller must supply a unique subaddress
|
|
////if(!monero_utils::is_valid_address(random_subaddress, monero_network_type::STAGENET)) {
|
|
// neroshop::print(random_subaddress + " is not a valid address", 1);
|
|
//}
|
|
// if yes then a unique subaddress will be generated from your wallet for receiving funds from the customer
|
|
// if user chooses to generate a unique subaddress:
|
|
if(has_wallet_synced()) {
|
|
std::string subaddress;
|
|
on_order_received(subaddress);
|
|
neroshop::print("generated unique subaddress: " + subaddress);
|
|
// add the address to the seller's address book so they know which order the address belongs
|
|
////wallet->address_book_add(subaddress, "For customer order with id: " + std::to_string(customer_order_id));
|
|
}
|
|
// if not connected to daemon or remote node, print "You cannot generate unless you connect to a node"
|
|
// if no then you can retrieve your stock back
|
|
neroshop::Message::get_first()->show();
|
|
}
|
|
// store order
|
|
customer_order_list.push_back(customer_order_id);
|
|
}
|
|
}
|
|
PQclear(result); // free result
|
|
////////////////////////////////
|
|
#endif
|
|
}
|
|
////////////////////
|
|
////////////////////
|
|
////////////////////
|
|
////////////////////
|
|
////////////////////
|
|
////////////////////
|
|
// setters - item and inventory-related stuff
|
|
////////////////////
|
|
void neroshop::Seller::set_stock_quantity(const std::string& listing_key, int quantity) {
|
|
// Transition from Sqlite to DHT:
|
|
Client * client = Client::get_main_client();
|
|
|
|
// Get the value of the corresponding key from the DHT
|
|
std::string response;
|
|
client->get(listing_key, response); // TODO: error handling
|
|
std::cout << "Received response (get): " << response << "\n";
|
|
// Parse the response
|
|
nlohmann::json json = nlohmann::json::parse(response);
|
|
if(json.contains("error")) {
|
|
neroshop::print("set_stock_quantity: key is lost or missing from DHT", 1);
|
|
return; // Key is lost or missing from DHT, return
|
|
}
|
|
|
|
const auto& response_obj = json["response"];
|
|
assert(response_obj.is_object());
|
|
if (response_obj.contains("value") && response_obj["value"].is_string()) {
|
|
const auto& value = response_obj["value"].get<std::string>();
|
|
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 != "listing") { std::cerr << "Invalid metadata. \"listing\" expected, got \"" << metadata << "\" instead\n"; return; }
|
|
// Verify ownership of the data (listing)
|
|
std::string seller_id = value_obj["seller_id"].get<std::string>();
|
|
if(seller_id != wallet->get_primary_address()) {
|
|
neroshop::print("set_stock_quantity: you cannot modify this listing since you are not the listing's creator", 1);
|
|
return;
|
|
}
|
|
// Verify the signature
|
|
std::string listing_id = value_obj["id"].get<std::string>();
|
|
std::string old_signature = value_obj["signature"].get<std::string>();
|
|
bool self_verified = wallet->verify_message(listing_id, old_signature);
|
|
if(!self_verified) { neroshop::print("Data verification failed.", 1); return; }
|
|
// Finally, modify the quantity
|
|
value_obj["quantity"] = quantity;
|
|
// Re-sign to reflect the modification
|
|
std::string signature = wallet->sign_message(listing_id, monero_message_signature_type::SIGN_WITH_SPEND_KEY);
|
|
value_obj["signature"] = signature;
|
|
// Add a last_modified or last_updated field so nodes can compare dates and choose the most recent listing
|
|
value_obj["last_updated"] = neroshop::timestamp::get_current_utc_timestamp();
|
|
// Send set request containing the updated value with the same key as before
|
|
std::string modified_value = value_obj.dump();
|
|
std::string response;
|
|
client->set(listing_key, modified_value, response);
|
|
std::cout << "Received response (set): " << response << "\n";
|
|
}
|
|
}
|
|
////////////////////
|
|
////////////////////
|
|
////////////////////
|
|
////////////////////
|
|
////////////////////
|
|
////////////////////
|
|
////////////////////
|
|
////////////////////
|
|
////////////////////
|
|
////////////////////
|
|
////////////////////
|
|
////////////////////
|
|
////////////////////
|
|
////////////////////
|
|
// getters - seller rating system
|
|
////////////////////
|
|
unsigned int neroshop::Seller::get_good_ratings() const {
|
|
/*db::Sqlite3 db("neroshop.db");
|
|
if(db.table_exists("seller_ratings")) {
|
|
unsigned int good_ratings_count = db.get_column_integer("seller_ratings", "COUNT(score)", "seller_id = " + get_id() + " AND score = " + std::to_string(1));
|
|
return good_ratings_count;
|
|
}
|
|
db.close();*/
|
|
////////////////////////////////
|
|
// postgresql
|
|
////////////////////////////////
|
|
#if defined(NEROSHOP_USE_POSTGRESQL)
|
|
//database->connect("host=127.0.0.1 port=5432 user=postgres password=postgres dbname=neroshoptest");
|
|
if(!database->table_exists("seller_ratings")) { return 0;}
|
|
unsigned int good_ratings_count = database->get_integer_params("SELECT COUNT(score) FROM seller_ratings WHERE seller_id = $1 AND score = $2", { get_id(), std::to_string(1) });
|
|
|
|
return good_ratings_count;
|
|
////////////////////////////////
|
|
#endif
|
|
return 0;
|
|
}
|
|
unsigned int neroshop::Seller::get_bad_ratings() const {
|
|
/*db::Sqlite3 db("neroshop.db");
|
|
if(db.table_exists("seller_ratings")) {
|
|
unsigned int bad_ratings_count = db.get_column_integer("seller_ratings", "COUNT(score)", "seller_id = " + get_id() + " AND score = " + std::to_string(0));
|
|
return bad_ratings_count;
|
|
}
|
|
db.close();*/
|
|
////////////////////////////////
|
|
// postgresql
|
|
////////////////////////////////
|
|
#if defined(NEROSHOP_USE_POSTGRESQL)
|
|
//database->connect("host=127.0.0.1 port=5432 user=postgres password=postgres dbname=neroshoptest");
|
|
if(!database->table_exists("seller_ratings")) { return 0;}
|
|
unsigned int bad_ratings_count = database->get_integer_params("SELECT COUNT(score) FROM seller_ratings WHERE seller_id = $1 AND score = $2", { get_id(), std::to_string(0) });
|
|
|
|
return bad_ratings_count;
|
|
////////////////////////////////
|
|
#endif
|
|
return 0;
|
|
}
|
|
////////////////////
|
|
unsigned int neroshop::Seller::get_ratings_count() const {
|
|
/*db::Sqlite3 db("neroshop.db");
|
|
if(db.table_exists("seller_ratings")) {
|
|
unsigned int ratings_count = db.get_column_integer("seller_ratings", "COUNT(*)", "seller_id = " + get_id());
|
|
return ratings_count;
|
|
}
|
|
db.close();*/
|
|
////////////////////////////////
|
|
// postgresql
|
|
////////////////////////////////
|
|
#if defined(NEROSHOP_USE_POSTGRESQL)
|
|
//database->connect("host=127.0.0.1 port=5432 user=postgres password=postgres dbname=neroshoptest");
|
|
if(!database->table_exists("seller_ratings")) { return 0;}
|
|
unsigned int ratings_count = database->get_integer_params("SELECT COUNT(*) FROM seller_ratings WHERE seller_id = $1", { get_id() });
|
|
|
|
return ratings_count;
|
|
////////////////////////////////
|
|
#endif
|
|
return 0;
|
|
}
|
|
////////////////////
|
|
unsigned int neroshop::Seller::get_total_ratings() const {
|
|
return get_ratings_count();
|
|
}
|
|
////////////////////
|
|
unsigned int neroshop::Seller::get_reputation() const {
|
|
/*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() });
|
|
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
|
|
unsigned int good_ratings = database->get_integer_params("SELECT COUNT(score) FROM seller_ratings WHERE seller_id = $1 AND score = $2", { get_id(), std::to_string(1) });
|
|
// Calculate seller reputation
|
|
double reputation = (good_ratings / static_cast<double>(ratings_count)) * 100;
|
|
return static_cast<int>(reputation); // convert reputation to an integer (for easier readability)*/
|
|
return 0;
|
|
}
|
|
////////////////////
|
|
std::vector<unsigned int> neroshop::Seller::get_top_rated_sellers(unsigned int limit) {
|
|
#if defined(NEROSHOP_USE_POSTGRESQL)
|
|
// get n seller_ids with the most positive (good) ratings
|
|
// ISSUE: both seller_4 and seller_1 have the same number of 1_score_values but seller_1 has the highest reputation and it places seller_4 first [solved - by using reputation in addition]
|
|
std::string command = "SELECT users.id FROM users JOIN seller_ratings ON users.id = seller_ratings.seller_id WHERE score = 1 GROUP BY users.id ORDER BY COUNT(score) DESC LIMIT $1;";
|
|
std::vector<const char *> param_values = { std::to_string(limit).c_str() };
|
|
PGresult * result = PQexecParams(database->get_handle(), command.c_str(), 1, nullptr, param_values.data(), nullptr, nullptr, 0);
|
|
if (PQresultStatus(result) != PGRES_TUPLES_OK) {
|
|
neroshop::print("Seller::get_top_rated_sellers(): No sellers found", 2);
|
|
PQclear(result);
|
|
return {}; // exit so that we don't double free "result"
|
|
}
|
|
int rows = PQntuples(result);
|
|
std::vector<unsigned int> top_rated_seller_ids = {};
|
|
for(int i = 0; i < rows; i++) {
|
|
int seller_id = std::stoi(PQgetvalue(result, i, 0));
|
|
// calculate the reputation of each seller_id
|
|
unsigned int ratings_count = database->get_integer_params("SELECT COUNT(*) FROM seller_ratings WHERE seller_id = $1", { std::to_string(seller_id) });
|
|
if(ratings_count == 0) continue; // seller has not yet been rated so his or her reputation will be 0%. Skip this seller
|
|
int good_ratings = database->get_integer_params("SELECT COUNT(score) FROM seller_ratings WHERE seller_id = $1 AND score = $2", { std::to_string(seller_id), std::to_string(1) });
|
|
double reputation = (good_ratings / static_cast<double>(ratings_count)) * 100;
|
|
// store the top rated seller_ids (only if they have a certain high reputation)
|
|
if(reputation >= 90) { // a reputation of 90 and above makes you a top rated seller (maybe I will reduce it to 85 just to be more fair or nah)
|
|
top_rated_seller_ids.push_back(seller_id);
|
|
if(std::find(top_rated_seller_ids.begin(), top_rated_seller_ids.end(), seller_id) != top_rated_seller_ids.end()) std::cout << "top rated sellers: " << seller_id << " (reputation: " << static_cast<int>(reputation) << ")" << std::endl;
|
|
}
|
|
}
|
|
PQclear(result); // free result
|
|
//--------------------------------------------------------------
|
|
// get n seller_ids with the least negative (bad) reviews
|
|
// amongst the sellers with the most positive (good) reviews
|
|
// "SELECT users.id, COUNT(score) AS bad_ratings_count FROM users JOIN seller_ratings ON users.id = seller_ratings.seller_id WHERE score = 0 GROUP BY users.id ORDER BY COUNT(score) ASC;"
|
|
// ...
|
|
//--------------------------------------------------------------
|
|
return top_rated_seller_ids;
|
|
#endif
|
|
return {};
|
|
}
|
|
////////////////////
|
|
////////////////////
|
|
////////////////////
|
|
////////////////////
|
|
////////////////////
|
|
////////////////////
|
|
// getters - order-related stuff
|
|
////////////////////
|
|
unsigned int neroshop::Seller::get_customer_order(unsigned int index) const {
|
|
if(customer_order_list.empty()) return 0;//return nullptr;
|
|
if(index > (customer_order_list.size() - 1)) throw std::out_of_range("neroshop::Seller::get_customer_order(): attempt to access invalid index");
|
|
return customer_order_list[index];
|
|
}
|
|
////////////////////
|
|
unsigned int neroshop::Seller::get_customer_order_count() const {
|
|
return customer_order_list.size();
|
|
}
|
|
////////////////////
|
|
std::vector<int> neroshop::Seller::get_pending_customer_orders() {
|
|
#if defined(NEROSHOP_USE_POSTGRESQL)
|
|
std::vector<int> pending_order_list;
|
|
////////////////////////////////
|
|
//database->connect("host=127.0.0.1 port=5432 user=postgres password=postgres dbname=neroshoptest");
|
|
////////////////////////////////
|
|
// update customer_order_list (by adding any new orders or orders that have not yet been added)
|
|
update_customer_orders();
|
|
////////////////////////////////
|
|
// now lets get all the pending orders from the UPDATED customer_order_list
|
|
std::string pending_order_msg = "Pending orders awaiting seller approval: (ids: ";
|
|
for(int i = 0; i < customer_order_list.size(); i++) {
|
|
int pending_orders = database->get_integer_params("SELECT id FROM orders WHERE id = $1 AND status = $2", { std::to_string(customer_order_list[i]), "Pending" });
|
|
if(pending_orders != 0) {
|
|
pending_order_list.push_back(customer_order_list[i]);
|
|
// gather ids of pending_orders
|
|
pending_order_msg += std::to_string(pending_orders) + "; ";
|
|
}
|
|
}
|
|
pending_order_msg.append(")");
|
|
pending_order_msg = neroshop::string::remove_last_of(pending_order_msg, "; ");
|
|
#ifdef NEROSHOP_DEBUG
|
|
std::cout << pending_order_msg << std::endl;
|
|
#endif
|
|
////////////////////////////////
|
|
// notify seller of a pending customer order
|
|
/*if(pending_order_list.size() > 0) {
|
|
Message::get_first()->set_text("You have " + std::to_string(pending_order_list.size()) + " pending customer orders");//\n Do you wish to proceed with this order?");
|
|
//neroshop::Message message("You have " + std::to_string(pending_order_list.size()) + " pending customer orders");
|
|
// Do you wish to process this order?
|
|
// [accept] [decline]
|
|
// Supply a subaddress or generate a unique subaddress from your wallet (for receiving funds from the customer)
|
|
// if yes then a unique subaddress will be generated from your wallet for receiving funds from the customer
|
|
//[supply address] [generate]
|
|
// if not connected to daemon or remote node, print "You cannot generate unless you connect to a node"
|
|
// if no then you can retrieve your stock back
|
|
Message::get_first()->show();//message.show();//Message::get_first()->show();
|
|
//message.draw();
|
|
}*/
|
|
////////////////////////////////
|
|
|
|
////////////////////////////////
|
|
return pending_order_list;
|
|
#endif
|
|
return {};
|
|
}
|
|
////////////////////
|
|
////////////////////
|
|
////////////////////
|
|
// getters - sales and statistics-related stuff
|
|
////////////////////
|
|
unsigned int neroshop::Seller::get_products_count() const {
|
|
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(key) FROM mappings WHERE search_term = $1 AND content = 'listing';", { get_id() });
|
|
return products_listed;
|
|
}
|
|
////////////////////
|
|
unsigned int neroshop::Seller::get_sales_count() const {
|
|
#if defined(NEROSHOP_USE_POSTGRESQL)
|
|
// should item not be considered sold until the order is done processing or nah ?
|
|
int items_sold = database->get_integer_params("SELECT SUM(item_qty) FROM order_item WHERE seller_id = $1;", { get_id() });
|
|
return items_sold;
|
|
#endif
|
|
return 0;
|
|
}
|
|
////////////////////
|
|
unsigned int neroshop::Seller::get_units_sold(const std::string& product_id) const {
|
|
#if defined(NEROSHOP_USE_POSTGRESQL)
|
|
int units_sold = database->get_integer_params("SELECT SUM(item_qty) FROM order_item WHERE product_id = $1 AND seller_id = $2", { product_id, get_id() });
|
|
return units_sold;
|
|
#endif
|
|
return 0;
|
|
}
|
|
////////////////////
|
|
unsigned int neroshop::Seller::get_units_sold(const neroshop::Product& item) const {
|
|
return get_units_sold(item.get_id());
|
|
}
|
|
////////////////////
|
|
double neroshop::Seller::get_sales_profit() const {
|
|
#if defined(NEROSHOP_USE_POSTGRESQL)
|
|
double profit_from_sales = database->get_real_params("SELECT SUM(item_price * item_qty) FROM order_item WHERE seller_id = $1;", { get_id() });//neroshop::print("The overall profit made from all sales combined is: $" + std::to_string(profit_from_sales), 3);
|
|
return profit_from_sales;
|
|
#endif
|
|
return 0.0;
|
|
}
|
|
////////////////////
|
|
double neroshop::Seller::get_profits_made(const std::string& product_id) const {
|
|
#if defined(NEROSHOP_USE_POSTGRESQL)
|
|
double item_profits = database->get_real_params("SELECT SUM(item_price * item_qty) FROM order_item WHERE product_id = $1 AND seller_id = $2;", { product_id, get_id() });//std::string item_name = database->get_text_params("SELECT name FROM item WHERE id = $1", { product_id });neroshop::print("The overall profit made from \"" + item_name + "\" is: $" + std::to_string(item_profits), 3);
|
|
return item_profits;
|
|
#endif
|
|
return 0.0;
|
|
}
|
|
////////////////////
|
|
double neroshop::Seller::get_profits_made(const neroshop::Product& item) const {
|
|
return get_profits_made(item.get_id());
|
|
}
|
|
////////////////////
|
|
unsigned int neroshop::Seller::get_product_id_with_most_sales() const { // this function is preferred over the "_by_mode" version as it provides the most accurate best-selling product_id result
|
|
#if defined(NEROSHOP_USE_POSTGRESQL)
|
|
// get the item with the biggest quantity sold (returns multiple results but I've limited it to 1)
|
|
int item_with_biggest_qty = database->get_integer_params("SELECT product_id FROM order_item WHERE seller_id = $1 GROUP BY product_id ORDER BY SUM(item_qty) DESC LIMIT 1;", { get_id() }); // from the biggest to smallest sum of item_qty
|
|
#ifdef NEROSHOP_DEBUG
|
|
std::string item_name = database->get_text_params("SELECT name FROM item WHERE id = $1", { std::to_string(item_with_biggest_qty) });
|
|
neroshop::print("\"" + item_name + "\" is your best-selling item with a sale of " + std::to_string(get_units_sold(item_with_biggest_qty)) + " units", 3);
|
|
#endif
|
|
return item_with_biggest_qty;
|
|
#endif
|
|
return 0;
|
|
}
|
|
////////////////////
|
|
unsigned int neroshop::Seller::get_product_id_with_most_orders() const {
|
|
#if defined(NEROSHOP_USE_POSTGRESQL)
|
|
// get the item with the most occurences in all orders - if two items are the most occuring then it will select the lowest product_id of the two (unless I add DESC)
|
|
int item_with_most_occurrences = database->get_integer_params("SELECT MODE() WITHIN GROUP (ORDER BY product_id) FROM order_item WHERE seller_id = $1;", { get_id() });
|
|
#ifdef NEROSHOP_DEBUG
|
|
std::string item_name = database->get_text_params("SELECT name FROM item WHERE id = $1", { std::to_string(item_with_most_occurrences) });
|
|
int times_occured = database->get_integer_params("SELECT COUNT(*) FROM order_item WHERE product_id = $1 AND seller_id = $2;", { std::to_string(item_with_most_occurrences), get_id() });
|
|
neroshop::print("\"" + item_name + "\" is your most ordered item occuring a total of " + std::to_string(times_occured) + " times in all orders", 2);
|
|
#endif
|
|
return item_with_most_occurrences;
|
|
#endif
|
|
return 0;
|
|
}
|
|
////////////////////
|
|
////////////////////
|
|
////////////////////
|
|
////////////////////
|
|
////////////////////
|
|
////////////////////
|
|
// boolean
|
|
////////////////////
|
|
bool neroshop::Seller::has_listed(const std::string& product_id) const {
|
|
#if defined(NEROSHOP_USE_POSTGRESQL)
|
|
bool listed = (database->get_text_params("SELECT EXISTS(SELECT product_id FROM inventory WHERE product_id = $1 AND seller_id = $2);", { product_id, get_id() }) == "t") ? true : false;
|
|
return listed;
|
|
#endif
|
|
return false;
|
|
}
|
|
////////////////////
|
|
bool neroshop::Seller::has_listed(const neroshop::Product& item) const {
|
|
return has_listed(item.get_id());
|
|
}
|
|
////////////////////
|
|
bool neroshop::Seller::has_stock(const std::string& product_id) const {
|
|
#if defined(NEROSHOP_USE_POSTGRESQL)
|
|
bool in_stock = (database->get_text_params("SELECT EXISTS(SELECT product_id FROM inventory WHERE product_id = $1 AND seller_id = $2 AND stock_qty > 0);", { product_id, get_id() }) == "t") ? true : false;
|
|
return in_stock;
|
|
#endif
|
|
return false;
|
|
}
|
|
////////////////////
|
|
bool neroshop::Seller::has_stock(const neroshop::Product& item) const {
|
|
return has_stock(item.get_id());
|
|
}
|
|
////////////////////
|
|
////////////////////
|
|
////////////////////
|
|
// callbacks
|
|
////////////////////
|
|
neroshop::User * neroshop::Seller::on_login(const neroshop::Wallet& wallet) { // assumes user data already exists in database
|
|
std::string monero_primary_address = wallet.get_primary_address();
|
|
if(!wallet.is_valid_address(monero_primary_address)) {
|
|
neroshop::print("Invalid monero address");
|
|
return nullptr;
|
|
}
|
|
// create a new user (seller)
|
|
neroshop::User * user = new Seller();
|
|
// set user properties retrieved from database
|
|
dynamic_cast<Seller *>(user)->set_logged(true); // protected, so can only be accessed by child class obj
|
|
dynamic_cast<Seller *>(user)->set_id(monero_primary_address);
|
|
dynamic_cast<Seller *>(user)->set_wallet(wallet);
|
|
dynamic_cast<Seller *>(user)->set_account_type(UserAccountType::Seller);
|
|
//-------------------------------
|
|
/*// load orders
|
|
dynamic_cast<Seller *>(user)->load_orders();*/
|
|
// load wishlists
|
|
dynamic_cast<Seller *>(user)->load_favorites();
|
|
/*// load customer_orders
|
|
static_cast<Seller *>(user)->load_customer_orders();*/
|
|
// Load cart (into memory)
|
|
user->get_cart()->load(user->get_id());
|
|
return user;
|
|
}
|
|
////////////////////
|
|
void neroshop::Seller::on_order_received(std::string& subaddress) {
|
|
if(!wallet.get()) throw std::runtime_error("wallet has not been initialized");
|
|
if(!wallet->get_monero_wallet()) throw std::runtime_error("monero_wallet_full is not opened");
|
|
// TODO: check if order type is a direct pay/no escrow before generating a new subaddress
|
|
// if wallet is not properly synced with the daemon, you can only generate used addresses
|
|
// unless wallet is synced to a daemon, you will not be able to generate any unique addresses
|
|
if(!wallet->get_monero_wallet()->is_synced()) throw std::runtime_error("wallet is not synced with a daemon"); // Indicates if the wallet is synced with the daemon.
|
|
// generate 10 new subaddress after each order (just to be sure there are enough unused subaddresses to choose from)
|
|
for(int i = 0; i < 10; i++) wallet->address_new();
|
|
// get a list of all unused subaddresses
|
|
std::vector<monero::monero_subaddress> unused_subaddress_list = wallet->get_addresses_unused(0);
|
|
// now pick from the list of unused subaddresses (random)
|
|
std::random_device rd; // Generating random numbers with C++11's random requires an engine and a distribution.
|
|
std::mt19937 mt(rd()); // This is an engine based on the Mersenne Twister 19937 (64 bits):
|
|
std::uniform_real_distribution<double> dist(0, unused_subaddress_list.size() - 1);
|
|
subaddress = unused_subaddress_list[static_cast<int>(dist(mt))].m_address.get();
|
|
// copy random subaddress
|
|
// USED SUBADDRESS IS NOT REMOVED FROM Wallet::address_unused() UNTIL THE SECOND CONFIRMATION (OUTPUT RECEIVED ...)
|
|
#ifdef NEROSHOP_DEBUG0
|
|
std::cout << std::endl << "subaddress (random): " << subaddress << "\n";
|
|
#endif
|
|
// also, generate a qrcode too
|
|
}
|
|
////////////////////
|
|
////////////////////
|
|
////////////////////
|
|
////////////////////
|
|
////////////////////
|