diff --git a/README.md b/README.md index 47f82f1..8659323 100644 --- a/README.md +++ b/README.md @@ -24,6 +24,6 @@ ### Database 1. To Create the DB use the command bellow ```Bash - python3 ./SQL_code/init-db.py + python3 ./mysql-code/init-db.py ``` - MySql Version 9.2.0 diff --git a/SQL_code/init-db.py b/SQL_code/init-db.py deleted file mode 100644 index 910368a..0000000 --- a/SQL_code/init-db.py +++ /dev/null @@ -1,3 +0,0 @@ -import subprocess -if (subprocess.run("mysql -u root mysql < SQL_code/Schema.sql", shell=True, check=True)): - print("successfully created the Marketplace databse") diff --git a/backend/controllers/product.js b/backend/controllers/product.js index 54f640e..71396b8 100644 --- a/backend/controllers/product.js +++ b/backend/controllers/product.js @@ -7,7 +7,7 @@ exports.addToFavorite = async (req, res) => { // Use parameterized query to prevent SQL injection const [result] = await db.execute( "INSERT INTO Favorites (UserID, ProductID) VALUES (?, ?)", - [userID, productsID] + [userID, productsID], ); res.json({ @@ -20,18 +20,32 @@ exports.addToFavorite = async (req, res) => { } }; -//Get all products +// Get all products along with their image URLs exports.getAllProducts = async (req, res) => { try { - const [data, fields] = await db.execute("SELECT * FROM Product"); + const [data, fields] = await db.execute(` + SELECT + P.ProductID, + P.Name AS ProductName, + P.Price, + P.Date AS DateUploaded, + U.Name AS SellerName, + I.URL AS ProductImage, + C.Name AS Category + FROM Product P + JOIN Image_URL I ON p.ProductID = i.ProductID + JOIN User U ON P.UserID = U.UserID + JOIN Category C ON P.CategoryID = C.CategoryID; + `); + console.log(data); res.json({ success: true, - message: "Product added to favorites successfully", + message: "Products fetched successfully", data, }); } catch (error) { - console.error("Error finding user:", error); + console.error("Error finding products:", error); return res.status(500).json({ found: false, error: "Database error occurred", @@ -39,6 +53,61 @@ exports.getAllProducts = async (req, res) => { } }; +exports.getProductById = async (req, res) => { + const { id } = req.params; + console.log("Received Product ID:", id); + + try { + const [data] = await db.execute( + ` + SELECT p.*, i.URL AS image_url + FROM Product p + LEFT JOIN Image_URL i ON p.ProductID = i.ProductID + WHERE p.ProductID = ? + `, + [id], + ); + + // Log raw data for debugging + console.log("Raw Database Result:", data); + + if (data.length === 0) { + console.log("No product found with ID:", id); + return res.status(404).json({ + success: false, + message: "Product not found", + }); + } + + // Collect all image URLs + const images = data + .map((row) => row.image_url) + .filter((url) => url !== null); + + // Create product object with all details from first row and collected images + const product = { + ...data[0], // Base product details + images: images, // Collected image URLs + }; + + // Log processed product for debugging + console.log("Processed Product:", product); + + res.json({ + success: true, + message: "Product fetched successfully", + data: product, + }); + } catch (error) { + console.error("Full Error Details:", error); + return res.status(500).json({ + success: false, + message: "Database error occurred", + error: error.message, + }); + } +}; + // db_con.query( // "SELECT ProductID FROM product WHERE ProductID = ?", // [productID], diff --git a/backend/controllers/search.js b/backend/controllers/search.js new file mode 100644 index 0000000..ba6700c --- /dev/null +++ b/backend/controllers/search.js @@ -0,0 +1,164 @@ +const db = require("../utils/database"); + +exports.searchProductsByName = async (req, res) => { + const { name } = req.query; + + if (name.length === 0) { + console.log("Searching for products with no name", name); + } + + console.log("Searching for products with name:", name); + + try { + // Modify SQL to return all products when no search term is provided + const sql = ` + SELECT p.*, i.URL as image + FROM Product p + LEFT JOIN Image_URL i ON p.ProductID = i.ProductID + ${name ? "WHERE p.Name LIKE ?" : ""} + ORDER BY p.ProductID + `; + + const params = name ? [`%${name}%`] : []; + console.log("Executing SQL:", sql); + console.log("With parameters:", params); + + const [data] = await db.execute(sql, params); + + console.log("Raw Database Result:", data); + + if (data.length === 0) { + console.log("No products found matching:", name); + return res.status(404).json({ + success: false, + message: "No products found matching your search", + }); + } + + // Group products by ProductID to handle multiple images per product + const productsMap = new Map(); + + data.forEach((row) => { + if (!productsMap.has(row.ProductID)) { + const product = { + ProductID: row.ProductID, + Name: row.Name, + Description: row.Description, + Price: row.Price, + images: row.image, + }; + productsMap.set(row.ProductID, product); + } else if (row.image_url) { + productsMap.get(row.ProductID).images.push(row.image_url); + } + }); + + const products = Array.from(productsMap.values()); + + console.log("Processed Products:", products); + + res.json({ + success: true, + message: "Products fetched successfully", + data: products, + count: products.length, + }); + } catch (error) { + console.error("Database Error:", error); + return res.status(500).json({ + success: false, + message: "Database error occurred", + error: error.message || "Unknown database error", + }); + } +}; + +// exports.searchProductsByName = async (req, res) => { +// const { name } = req.query; + +// // Add better validation and error handling +// if (!name || typeof name !== "string") { +// return res.status(400).json({ +// success: false, +// message: "Valid search term is required", +// }); +// } + +// console.log("Searching for products with name:", name); + +// try { +// // Log the SQL query and parameters for debugging +// const sql = ` +// SELECT p.*, i.URL AS image_url +// FROM Product p +// LEFT JOIN Image_URL i ON p.ProductID = i.ProductID +// WHERE p.Name LIKE ? +// `; +// const params = [`%${name}%`]; +// console.log("Executing SQL:", sql); +// console.log("With parameters:", params); + +// const [data] = await db.execute(sql, params); + +// // Log raw data for debugging +// console.log("Raw Database Result:", data); + +// if (data.length === 0) { +// console.log("No products found matching:", name); +// return res.status(404).json({ +// success: false, +// message: "No products found matching your search", +// }); +// } + +// // Group products by ProductID to handle multiple images per product +// const productsMap = new Map(); + +// data.forEach((row) => { +// if (!productsMap.has(row.ProductID)) { +// // Create a clean object without circular references +// const product = { +// ProductID: row.ProductID, +// Name: row.Name, +// Description: row.Description, +// Price: row.Price, +// // Add any other product fields you need +// images: row.image_url ? [row.image_url] : [], +// }; +// productsMap.set(row.ProductID, product); +// } else if (row.image_url) { +// // Add additional image to existing product +// productsMap.get(row.ProductID).images.push(row.image_url); +// } +// }); + +// // Convert map to array of products +// const products = Array.from(productsMap.values()); + +// // Log processed products for debugging +// console.log("Processed Products:", products); + +// res.json({ +// success: true, +// message: "Products fetched successfully", +// data: products, +// count: products.length, +// }); +// } catch (error) { +// // Enhanced error logging +// console.error("Database Error Details:", { +// message: error.message, +// code: error.code, +// errno: error.errno, +// sqlState: error.sqlState, +// sqlMessage: error.sqlMessage, +// sql: error.sql, +// }); + +// return res.status(500).json({ +// success: false, +// message: "Database error occurred", +// error: error.message || "Unknown database error", +// }); +// } +// }; diff --git a/backend/index.js b/backend/index.js index 01fc880..c78cda7 100644 --- a/backend/index.js +++ b/backend/index.js @@ -5,6 +5,7 @@ const db = require("./utils/database"); const userRouter = require("./routes/user"); const productRouter = require("./routes/product"); +const searchRouter = require("./routes/search"); const { generateEmailTransporter } = require("./utils/mail"); const { cleanupExpiredCodes, @@ -34,6 +35,7 @@ checkDatabaseConnection(db); //Routes app.use("/api/user", userRouter); //prefix with /api/user app.use("/api/product", productRouter); //prefix with /api/product +app.use("/api/search_products", searchRouter); //prefix with /api/product // Set up a scheduler to run cleanup every hour setInterval(cleanupExpiredCodes, 60 * 60 * 1000); diff --git a/backend/recommondation.go b/backend/recommondation.go deleted file mode 100644 index 3a82edb..0000000 --- a/backend/recommondation.go +++ /dev/null @@ -1,43 +0,0 @@ -package main - -import ( - "fmt" - "math" -) - -func cosine(x, y []int) float64 { - var dotProduct, normX, normY float64 - for i := 1; i < len(x); i++ { - dotProduct += float64(x[i] * y[i]) - normX += float64(x[i] * x[i]) - normY += float64(y[i] * y[i]) - } - return dotProduct / (math.Sqrt(normX) * math.Sqrt(normY)) -} - -func main() { - history := [][]int{ - {1, 0, 1, 0}, - {0, 1, 0, 1}, - {1, 0, 1, 1}, - } - - productCategory := [][]int{ - {1, 1, 0, 0}, - {0, 0, 0, 1}, - {0, 0, 1, 0}, - {0, 0, 1, 1}, - {0, 1, 0, 0}, - - {0, 1, 1, 1}, - {1, 1, 1, 0}, - {0, 0, 0, 1}, - {1, 1, 1, 1}, - } - - // Calculate similarity between first search and each product - for i, product := range productCategory { - sim := cosine(history[0], product) - fmt.Printf("Similarity with product %d: %f\n", i, sim) - } -} diff --git a/backend/routes/product.js b/backend/routes/product.js index 55d3761..90c7914 100644 --- a/backend/routes/product.js +++ b/backend/routes/product.js @@ -1,10 +1,20 @@ +// routes/product.js const express = require("express"); -const { addToFavorite, getAllProducts } = require("../controllers/product"); - +const { + addToFavorite, + getAllProducts, + getProductById, +} = require("../controllers/product"); const router = express.Router(); -router.post("/add_fav_product", addToFavorite); +// Add detailed logging middleware +router.use((req, res, next) => { + console.log(`Incoming ${req.method} request to ${req.path}`); + next(); +}); +router.post("/add_fav_product", addToFavorite); router.get("/get_product", getAllProducts); +router.get("/:id", getProductById); // Simplified route module.exports = router; diff --git a/backend/routes/search.js b/backend/routes/search.js new file mode 100644 index 0000000..2871eba --- /dev/null +++ b/backend/routes/search.js @@ -0,0 +1,14 @@ +// routes/product.js +const express = require("express"); +const { searchProductsByName } = require("../controllers/search"); +const router = express.Router(); + +// Add detailed logging middleware +router.use((req, res, next) => { + console.log(`Incoming ${req.method} request to ${req.path}`); + next(); +}); + +router.get("/search", searchProductsByName); + +module.exports = router; diff --git a/backend/utils/database.js b/backend/utils/database.js index 020e67c..689785e 100644 --- a/backend/utils/database.js +++ b/backend/utils/database.js @@ -4,8 +4,7 @@ const mysql = require("mysql2"); const pool = mysql.createPool({ host: "localhost", user: "root", - database: "marketplace", - password: "12345678", + database: "Marketplace", }); //Export a promise for promise-based query diff --git a/frontend/package-lock.json b/frontend/package-lock.json index 2d81364..7b44684 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -9,6 +9,7 @@ "version": "0.0.0", "dependencies": { "@tailwindcss/vite": "^4.0.9", + "axios": "^1.8.4", "lucide-react": "^0.477.0", "react": "^19.0.0", "react-dom": "^19.0.0", @@ -1770,6 +1771,12 @@ "node": ">= 0.4" } }, + "node_modules/asynckit": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==", + "license": "MIT" + }, "node_modules/autoprefixer": { "version": "10.4.20", "resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.4.20.tgz", @@ -1824,6 +1831,17 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/axios": { + "version": "1.8.4", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.8.4.tgz", + "integrity": "sha512-eBSYY4Y68NNlHbHBMdeDmKNtDgXWhQsJcGqzO3iLUM0GraQFSS9cVgPX5I9b3lbdFKyYoAEGAZF1DwhTaljNAw==", + "license": "MIT", + "dependencies": { + "follow-redirects": "^1.15.6", + "form-data": "^4.0.0", + "proxy-from-env": "^1.1.0" + } + }, "node_modules/balanced-match": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", @@ -1898,7 +1916,6 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==", - "dev": true, "license": "MIT", "dependencies": { "es-errors": "^1.3.0", @@ -1993,6 +2010,18 @@ "dev": true, "license": "MIT" }, + "node_modules/combined-stream": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", + "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", + "license": "MIT", + "dependencies": { + "delayed-stream": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, "node_modules/concat-map": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", @@ -2153,6 +2182,15 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/delayed-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", + "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", + "license": "MIT", + "engines": { + "node": ">=0.4.0" + } + }, "node_modules/detect-libc": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-1.0.3.tgz", @@ -2182,7 +2220,6 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", - "dev": true, "license": "MIT", "dependencies": { "call-bind-apply-helpers": "^1.0.1", @@ -2283,7 +2320,6 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", - "dev": true, "license": "MIT", "engines": { "node": ">= 0.4" @@ -2293,7 +2329,6 @@ "version": "1.3.0", "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", - "dev": true, "license": "MIT", "engines": { "node": ">= 0.4" @@ -2331,7 +2366,6 @@ "version": "1.1.1", "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz", "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==", - "dev": true, "license": "MIT", "dependencies": { "es-errors": "^1.3.0" @@ -2344,7 +2378,6 @@ "version": "2.1.0", "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz", "integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==", - "dev": true, "license": "MIT", "dependencies": { "es-errors": "^1.3.0", @@ -2732,6 +2765,26 @@ "dev": true, "license": "ISC" }, + "node_modules/follow-redirects": { + "version": "1.15.9", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.9.tgz", + "integrity": "sha512-gew4GsXizNgdoRyqmyfMHyAmXsZDk6mHkSxZFCzW9gwlbtOW44CDtYavM+y+72qD/Vq2l550kMF52DT8fOLJqQ==", + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/RubenVerborgh" + } + ], + "license": "MIT", + "engines": { + "node": ">=4.0" + }, + "peerDependenciesMeta": { + "debug": { + "optional": true + } + } + }, "node_modules/for-each": { "version": "0.3.5", "resolved": "https://registry.npmjs.org/for-each/-/for-each-0.3.5.tgz", @@ -2748,6 +2801,21 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/form-data": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.2.tgz", + "integrity": "sha512-hGfm/slu0ZabnNt4oaRZ6uREyfCj6P4fT/n6A1rGV+Z0VdGXjfOhVUpkn6qVQONHGIFwmveGXyDs75+nr6FM8w==", + "license": "MIT", + "dependencies": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "es-set-tostringtag": "^2.1.0", + "mime-types": "^2.1.12" + }, + "engines": { + "node": ">= 6" + } + }, "node_modules/fraction.js": { "version": "4.3.7", "resolved": "https://registry.npmjs.org/fraction.js/-/fraction.js-4.3.7.tgz", @@ -2780,7 +2848,6 @@ "version": "1.1.2", "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", - "dev": true, "license": "MIT", "funding": { "url": "https://github.com/sponsors/ljharb" @@ -2831,7 +2898,6 @@ "version": "1.2.7", "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.7.tgz", "integrity": "sha512-VW6Pxhsrk0KAOqs3WEd0klDiF/+V7gQOpAvY1jVU/LHmaD/kQO4523aiJuikX/QAKYiW6x8Jh+RJej1almdtCA==", - "dev": true, "license": "MIT", "dependencies": { "call-bind-apply-helpers": "^1.0.1", @@ -2856,7 +2922,6 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", - "dev": true, "license": "MIT", "dependencies": { "dunder-proto": "^1.0.1", @@ -2931,7 +2996,6 @@ "version": "1.2.0", "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", - "dev": true, "license": "MIT", "engines": { "node": ">= 0.4" @@ -3002,7 +3066,6 @@ "version": "1.1.0", "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", - "dev": true, "license": "MIT", "engines": { "node": ">= 0.4" @@ -3015,7 +3078,6 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz", "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==", - "dev": true, "license": "MIT", "dependencies": { "has-symbols": "^1.0.3" @@ -3031,7 +3093,6 @@ "version": "2.0.2", "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", - "dev": true, "license": "MIT", "dependencies": { "function-bind": "^1.1.2" @@ -3895,12 +3956,32 @@ "version": "1.1.0", "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==", - "dev": true, "license": "MIT", "engines": { "node": ">= 0.4" } }, + "node_modules/mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "license": "MIT", + "dependencies": { + "mime-db": "1.52.0" + }, + "engines": { + "node": ">= 0.6" + } + }, "node_modules/minimatch": { "version": "3.1.2", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", @@ -4251,6 +4332,12 @@ "react-is": "^16.13.1" } }, + "node_modules/proxy-from-env": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", + "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==", + "license": "MIT" + }, "node_modules/punycode": { "version": "2.3.1", "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", diff --git a/frontend/package.json b/frontend/package.json index aa89b14..8a323b6 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -11,6 +11,7 @@ }, "dependencies": { "@tailwindcss/vite": "^4.0.9", + "axios": "^1.8.4", "lucide-react": "^0.477.0", "react": "^19.0.0", "react-dom": "^19.0.0", diff --git a/frontend/src/App.jsx b/frontend/src/App.jsx index 5a65c60..12b3231 100644 --- a/frontend/src/App.jsx +++ b/frontend/src/App.jsx @@ -13,6 +13,8 @@ import Transactions from "./pages/Transactions"; import Favorites from "./pages/Favorites"; import ProductDetail from "./pages/ProductDetail"; import ItemForm from "./pages/MyListings"; +import SearchPage from "./pages/SearchPage"; // Make sure to import the SearchPage +import axios from "axios"; function App() { // Authentication state - initialize from localStorage if available @@ -30,6 +32,8 @@ function App() { const [error, setError] = useState(""); const [isLoading, setIsLoading] = useState(false); + const [userId, setUserId] = useState(null); + // New verification states const [verificationStep, setVerificationStep] = useState("initial"); // 'initial', 'code-sent', 'verifying' const [tempUserData, setTempUserData] = useState(null); @@ -70,7 +74,7 @@ function App() { email: userData.email, // Add any other required fields }), - } + }, ); if (!response.ok) { @@ -119,7 +123,7 @@ function App() { email: tempUserData.email, code: code, }), - } + }, ); if (!response.ok) { @@ -163,7 +167,7 @@ function App() { "Content-Type": "application/json", }, body: JSON.stringify(userData), - } + }, ); if (!response.ok) { @@ -189,6 +193,9 @@ function App() { sessionStorage.setItem("isAuthenticated", "true"); sessionStorage.setItem("user", JSON.stringify(newUser)); + // After successful signup, send session data to server + sendSessionDataToServer(); // Call it after signup + // Reset verification steps setVerificationStep("initial"); setTempUserData(null); @@ -222,11 +229,12 @@ function App() { setError("Email and password are required"); setIsLoading(false); return; - } else if (!formValues.email.endsWith("@ucalgary.ca")) { - setError("Please use your UCalgary email address (@ucalgary.ca)"); - setIsLoading(false); - return; } + // else if (!formValues.email.endsWith("@ucalgary.ca")) { + // setError("Please use your UCalgary email address (@ucalgary.ca)"); + // setIsLoading(false); + // return; + // } try { if (isSignUp) { // Handle Sign Up with verification @@ -265,7 +273,7 @@ function App() { email: formValues.email, password: formValues.password, }), - } + }, ); if (!response.ok) { @@ -291,6 +299,9 @@ function App() { sessionStorage.setItem("isAuthenticated", "true"); sessionStorage.setItem("user", JSON.stringify(userObj)); + // After successful signup, send session data to server + sendSessionDataToServer(); // Call it after signup + sessionStorage.getItem("user"); console.log("Login successful for:", userData.email); @@ -355,6 +366,48 @@ function App() { setError(""); }; + const sendSessionDataToServer = async () => { + try { + // Retrieve data from sessionStorage + const user = JSON.parse(sessionStorage.getItem("user")); + const isAuthenticated = + sessionStorage.getItem("isAuthenticated") === "true"; + + if (!user || !isAuthenticated) { + console.log("User is not authenticated"); + return; + } + + // Prepare the data to send + const requestData = { + userId: user.ID, // or user.ID depending on your user structure + email: user.email, + isAuthenticated, + }; + + console.log("Sending user data to the server:", requestData); + + // Send data to Python server (replace with your actual server URL) + const response = await fetch("http://localhost:5000/api/user/session", { + method: "POST", + headers: { + "Content-Type": "application/json", + }, + body: JSON.stringify(requestData), + }); + + // Check the response + if (response.ok) { + const result = await response.json(); + console.log("Server response:", result); + } else { + console.error("Failed to send session data to the server"); + } + } catch (error) { + console.error("Error sending session data:", error); + } + }; + // Login component const LoginComponent = () => (
@@ -509,8 +562,8 @@ function App() { {isLoading ? "Please wait..." : isSignUp - ? "Create Account" - : "Sign In"} + ? "Create Account" + : "Sign In"}
@@ -633,6 +686,16 @@ function App() { } /> + +
+ +
+ + } + /> { const [searchQuery, setSearchQuery] = useState(""); + const navigate = useNavigate(); const handleSearchChange = (e) => { setSearchQuery(e.target.value); @@ -12,8 +13,14 @@ const Navbar = ({ onLogout, userName }) => { const handleSearchSubmit = (e) => { e.preventDefault(); - console.log("Searching for:", searchQuery); - // TODO: Implement search functionality + + // if (!searchQuery.trim()) return; + + // Navigate to search page with query + navigate({ + pathname: "/search", + search: `?name=${encodeURIComponent(searchQuery)}`, + }); }; return ( @@ -41,13 +48,19 @@ const Navbar = ({ onLogout, userName }) => {
+ @@ -61,6 +74,7 @@ const Navbar = ({ onLogout, userName }) => { > + {/* User Profile */} diff --git a/frontend/src/pages/Home.jsx b/frontend/src/pages/Home.jsx index 300fd9d..da47d13 100644 --- a/frontend/src/pages/Home.jsx +++ b/frontend/src/pages/Home.jsx @@ -11,7 +11,7 @@ const Home = () => { const fetchProducts = async () => { try { const response = await fetch( - "http://localhost:3030/api/product/get_product" + "http://localhost:3030/api/product/get_product", ); if (!response.ok) throw new Error("Failed to fetch products"); @@ -21,15 +21,15 @@ const Home = () => { setListings( data.data.map((product) => ({ id: product.ProductID, - title: product.Name, + title: product.ProductName, // Use the alias from SQL price: product.Price, - category: product.CategoryID, - image: product.ImageURL, + category: product.Category, // Ensure this gets the category name + image: product.ProductImage, // Use the alias for image URL condition: "New", // Modify based on actual data - seller: "Unknown", // Modify if seller info is available - datePosted: "Just now", - isFavorite: false, - })) + seller: product.SellerName, // Fetch seller name properly + datePosted: product.DateUploaded, // Use the actual date + isFavorite: false, // Default state + })), ); } else { throw new Error(data.message || "Error fetching products"); @@ -39,7 +39,6 @@ const Home = () => { setError(error.message); } }; - fetchProducts(); }, []); @@ -50,8 +49,8 @@ const Home = () => { prevListings.map((listing) => listing.id === id ? { ...listing, isFavorite: !listing.isFavorite } - : listing - ) + : listing, + ), ); }; @@ -112,65 +111,186 @@ const Home = () => { */} {/* Recent Listings */} -
+

- Recent Listings + Recommendation

-
- {listings.map((listing) => ( - -
- {listing.title} - -
-
-
+
+ {/* Left Button - Overlaid on products */} + + + {/* Scrollable Listings Container */} +
+ {listings.map((listing) => ( + +
+ {listing.title} + +
+ +

{listing.title}

- + ${listing.price} + +
+ + {listing.category} + + {listing.condition} +
+ +
+ + {listing.datePosted} + + + {listing.seller} + +
+
+ + ))} +
+ + {/* Right Button - Overlaid on products */} + +
+
+ + {/* Recent Listings */} +
+

+ Recent Listings +

+ +
+ {/* Left Button - Overlaid on products */} + + + {/* Scrollable Listings Container */} +
+ {listings.map((listing) => ( + +
+ {listing.title} +
-
- - {listing.category} - - {listing.condition} -
+
+

+ {listing.title} +

+ + ${listing.price} + -
- - {listing.datePosted} - - - {listing.seller} - +
+ + {listing.category} + + {listing.condition} +
+ +
+ + {listing.datePosted} + + + {listing.seller} + +
-
- - ))} + + ))} +
+ + {/* Right Button - Overlaid on products */} +
diff --git a/frontend/src/pages/ProductDetail.jsx b/frontend/src/pages/ProductDetail.jsx index 6b32e8e..15c8d86 100644 --- a/frontend/src/pages/ProductDetail.jsx +++ b/frontend/src/pages/ProductDetail.jsx @@ -1,129 +1,166 @@ -import { useState } from "react"; +import { useState, useEffect } from "react"; import { useParams, Link } from "react-router-dom"; -import { - Heart, - ArrowLeft, - Tag, - User, - Calendar, - Share, - Flag, -} from "lucide-react"; +import { Heart, ArrowLeft, Tag, User, Calendar } from "lucide-react"; const ProductDetail = () => { const { id } = useParams(); + const [product, setProduct] = useState(null); + const [loading, setLoading] = useState(true); + const [error, setError] = useState(null); const [isFavorite, setIsFavorite] = useState(false); const [showContactForm, setShowContactForm] = useState(false); const [message, setMessage] = useState(""); const [currentImage, setCurrentImage] = useState(0); - // Sample data for demonstration - const product = [ - { - id: 0, - title: "Dell XPS 13 Laptop - 2023 Model", - price: 850, - shortDescription: - "Dell XPS 13 laptop in excellent condition. Intel Core i7, 16GB RAM, 512GB SSD. Includes charger and original box.", - description: - "Selling my Dell XPS 13 laptop. Only 6 months old and in excellent condition. Intel Core i7 processor, 16GB RAM, 512GB SSD. Battery life is still excellent (around 10 hours of regular use). Comes with original charger and box. Selling because I'm upgrading to a MacBook for design work.\n\nSpecs:\n- Intel Core i7 11th Gen\n- 16GB RAM\n- 512GB NVMe SSD\n- 13.4\" FHD+ Display (1920x1200)\n- Windows 11 Pro\n- Backlit Keyboard\n- Thunderbolt 4 ports", - condition: "Like New", - category: - "Electronics, Electronics, Electronics, Electronics , Electronics , Electronics, Electronicss", - datePosted: "2023-03-02", - images: [ - "/image1.avif", - "/image2.avif", - "/image3.avif", - "/image3.avif", - "/image3.avif", - ], - seller: { - name: "Michael T.", - rating: 4.8, - memberSince: "January 2022", - avatar: "/Profile.jpg", - }, - }, - ]; + // Fetch product details + useEffect(() => { + const fetchProduct = async () => { + try { + setLoading(true); + const response = await fetch(`http://localhost:3030/api/product/${id}`); - console.log(product[id]); + if (!response.ok) { + throw new Error("Failed to fetch product"); + } - const toggleFavorite = () => { - setIsFavorite(!isFavorite); + const result = await response.json(); + console.log(result); + + if (result.success) { + setProduct(result.data); + setError(null); + } else { + throw new Error(result.message || "Error fetching product"); + } + } catch (error) { + console.error("Error fetching product:", error); + setError(error.message); + setProduct(null); + } finally { + setLoading(false); + } + }; + + fetchProduct(); + }, [id]); + + // Handle favorite toggle + const toggleFavorite = async () => { + try { + const response = await fetch( + "http://localhost:3030/api/product/add_to_favorite", + { + method: "POST", + headers: { + "Content-Type": "application/json", + }, + body: JSON.stringify({ + userID: 1, // Replace with actual user ID + productsID: id, + }), + }, + ); + + const result = await response.json(); + if (result.success) { + setIsFavorite(!isFavorite); + } + } catch (error) { + console.error("Error toggling favorite:", error); + } }; + // Handle message submission const handleSendMessage = (e) => { e.preventDefault(); - // TODO: this would send the message to the seller + // TODO: Implement actual message sending logic console.log("Message sent:", message); setMessage(""); setShowContactForm(false); - // Show confirmation or success message alert("Message sent to seller!"); }; - // Function to split description into paragraphs - const formatDescription = (text) => { - return text.split("\n\n").map((paragraph, index) => ( -

- {paragraph.split("\n").map((line, i) => ( - - {line} - {i < paragraph.split("\n").length - 1 &&
} -
- ))} -

- )); - }; - - // image navigation + // Image navigation const nextImage = () => { - setCurrentImage((prev) => - prev === product.images.length - 1 ? 0 : prev + 1, - ); + if (product && product.images) { + setCurrentImage((prev) => + prev === product.images.length - 1 ? 0 : prev + 1, + ); + } }; const prevImage = () => { - setCurrentImage((prev) => - prev === 0 ? product.images.length - 1 : prev - 1, - ); + if (product && product.images) { + setCurrentImage((prev) => + prev === 0 ? product.images.length - 1 : prev - 1, + ); + } }; const selectImage = (index) => { setCurrentImage(index); }; + // Render loading state + if (loading) { + return ( +
+
+
+ ); + } + + // Render error state + if (error) { + return ( +
+
+

Error Loading Product

+

{error}

+ + Back to Listings + +
+
+ ); + } + + // Render product details return (
- {/* Breadcrumb & Back Link */}
- Back to listings + Back
- {/* Left Column - Images */}
- {/* Main Image */}
- {product[id].title} + {product.images && product.images.length > 0 ? ( + {product.Name} + ) : ( +
+ No Image Available +
+ )}
- {/* Thumbnail Images */} - {product[id].images.length > 1 && ( + {product.images && product.images.length > 1 && (
- {product[id].images.map((image, index) => ( + {product.images.map((image, index) => (
{ > {`${product[id].title}
@@ -140,13 +177,11 @@ const ProductDetail = () => { )}
- {/* Right Column - Details */}
- {/* Product Info Card */}

- {product[id].title} + {product.Name}

- ${product[id].price} + ${product.Price}
-
- {product[id].category} + {product.Category}
Condition: - {product[id].condition} + {product.condition}
- Posted on {product[id].datePosted} + Posted on {product.Date}
- {/* Short Description */}
-

{product[id].shortDescription}

+

{product.Description}

- {/* Contact Button */} - {/* TODO:Contact Form */} {showContactForm && (

@@ -204,8 +235,8 @@ const ProductDetail = () => { setEmail(e.target.value)} + value={message} + onChange={(e) => setMessage(e.target.value)} className="w-full p-3 border border-gray-300 focus:outline-none focus:border-green-500" required /> @@ -217,8 +248,6 @@ const ProductDetail = () => { setPhone(e.target.value)} className="w-full p-3 border border-gray-300 focus:outline-none focus:border-green-500" required /> @@ -233,8 +262,8 @@ const ProductDetail = () => { setContactMessage(e.target.value)} + value={message} + onChange={(e) => setMessage(e.target.value)} placeholder="Hi, is this item still available?" className="w-full p-3 border border-gray-300 focus:outline-none focus:border-green-500" /> @@ -249,35 +278,27 @@ const ProductDetail = () => {

)} - {/* Seller Info */}
- {product[id].seller.avatar ? ( - Seller - ) : ( -
- -
- )} +
+ +

- {product[id].seller.name} + {product.UserID || "Unknown Seller"}

- Member since {product[id].seller.memberSince} + Member since{" "} + {product.seller ? product.seller.memberSince : "N/A"}

Rating:{" "} - {product[id].seller.rating}/5 + {product.seller ? `${product.seller.rating}/5` : "N/A"}
@@ -285,15 +306,12 @@ const ProductDetail = () => {
- {/* Description Section */} -
+ {/*

Description

-
- {formatDescription(product[id].description)} -
+
{product.Description}
-
+
*/}
); }; diff --git a/frontend/src/pages/SearchPage.jsx b/frontend/src/pages/SearchPage.jsx new file mode 100644 index 0000000..905eb70 --- /dev/null +++ b/frontend/src/pages/SearchPage.jsx @@ -0,0 +1,203 @@ +import React, { useState, useEffect } from "react"; +import { Filter, Grid, Heart, Tag, X } from "lucide-react"; +import { useLocation, Link } from "react-router-dom"; +import axios from "axios"; + +const SearchPage = () => { + const location = useLocation(); + const queryParams = new URLSearchParams(location.search); + const nameParam = queryParams.get("name") || ""; + const initialSearchQuery = location.state?.query || nameParam || ""; + + const [products, setProducts] = useState([]); + const [filteredProducts, setFilteredProducts] = useState([]); + const [searchQuery, setSearchQuery] = useState(initialSearchQuery); + const [loading, setLoading] = useState(false); + const [error, setError] = useState(null); + const [priceRange, setPriceRange] = useState({ min: 0, max: 1000 }); + const [isFilterOpen, setIsFilterOpen] = useState(false); + + useEffect(() => { + fetchProducts(initialSearchQuery); + }, [initialSearchQuery]); + + const fetchProducts = async (query) => { + setLoading(true); + setError(null); + + try { + const response = await axios.get( + `http://localhost:3030/api/search_products/search`, + { + params: { name: query }, + }, + ); + + if (response.data.success) { + const transformedProducts = response.data.data.map((product) => ({ + id: product.ProductID, + title: product.Name, + description: product.Description || "", + price: product.Price || 0, + category: product.Category || "Uncategorized", + condition: product.Condition || "Used", + image: product.images, + seller: product.SellerName || "Unknown Seller", + isFavorite: false, + })); + + setProducts(transformedProducts); + setFilteredProducts(transformedProducts); + } else { + setError(response.data.message || "Failed to fetch products"); + setProducts([]); + setFilteredProducts([]); + } + } catch (err) { + console.error("Error fetching products:", err); + setError(err.response?.data?.message || "Error connecting to the server"); + setProducts([]); + setFilteredProducts([]); + } finally { + setLoading(false); + } + }; + + const toggleFavorite = (id, e) => { + e.preventDefault(); + setProducts((prev) => + prev.map((product) => + product.id === id + ? { ...product, isFavorite: !product.isFavorite } + : product, + ), + ); + }; + + const filterProducts = () => { + let result = products; + result = result.filter( + (product) => + product.price >= priceRange.min && product.price <= priceRange.max, + ); + setFilteredProducts(result); + }; + + const applyFilters = () => { + filterProducts(); + setIsFilterOpen(false); + }; + + const resetFilters = () => { + setPriceRange({ min: 0, max: 1000 }); + setFilteredProducts(products); + }; + + return ( +
+
+
+
+

Filters

+ +
+ +
+
+

Price Range

+
+
+ + setPriceRange((prev) => ({ + ...prev, + min: Number(e.target.value), + })) + } + className="w-full p-2 border rounded text-gray-700" + /> + + setPriceRange((prev) => ({ + ...prev, + max: Number(e.target.value), + })) + } + className="w-full p-2 border rounded text-gray-700" + /> +
+
+
+
+ + +
+
+
+ +
+

+ {filteredProducts.length} Results + {searchQuery && ( + + {" "} + for "{searchQuery}" + + )} +

+ +
+ {filteredProducts.map((listing) => ( + + {listing.title} +
+

+ {listing.title} +

+

+ ${Number(listing.price).toFixed(2)} +

+
+ + ))} +
+
+
+
+ ); +}; + +export default SearchPage; diff --git a/SQL_code/Schema.sql b/mysql-code/Schema.sql similarity index 100% rename from SQL_code/Schema.sql rename to mysql-code/Schema.sql diff --git a/mysql-code/example-data.sql b/mysql-code/example-data.sql new file mode 100644 index 0000000..a2b7242 --- /dev/null +++ b/mysql-code/example-data.sql @@ -0,0 +1,761 @@ +-- Inserting sample data into the Marketplace database +-- Clear existing data (if needed) +SET + FOREIGN_KEY_CHECKS = 0; + +TRUNCATE TABLE Product_Category; + +TRUNCATE TABLE Favorites; + +TRUNCATE TABLE History; + +TRUNCATE TABLE Recommendation; + +TRUNCATE TABLE Transaction; + +TRUNCATE TABLE Review; + +TRUNCATE TABLE Image_URL; + +TRUNCATE TABLE Product; + +TRUNCATE TABLE Category; + +TRUNCATE TABLE UserRole; + +TRUNCATE TABLE User; + +TRUNCATE TABLE AuthVerification; + +SET + FOREIGN_KEY_CHECKS = 1; + +-- Insert Users +INSERT INTO + User ( + UserID, + Name, + Email, + UCID, + Password, + Phone, + Address + ) +VALUES + ( + 1, + 'John Doe', + 'john.doe@example.com', + 'U123456', + 'hashedpassword1', + '555-123-4567', + '123 Main St, Calgary, AB' + ), + ( + 2, + 'Jane Smith', + 'jane.smith@example.com', + 'U234567', + 'hashedpassword2', + '555-234-5678', + '456 Oak Ave, Calgary, AB' + ), + ( + 3, + 'Michael Brown', + 'michael.b@example.com', + 'U345678', + 'hashedpassword3', + '555-345-6789', + '789 Pine Rd, Calgary, AB' + ), + ( + 4, + 'Sarah Wilson', + 'sarah.w@example.com', + 'U456789', + 'hashedpassword4', + '555-456-7890', + '101 Elm Blvd, Calgary, AB' + ), + ( + 5, + 'David Taylor', + 'david.t@example.com', + 'U567890', + 'hashedpassword5', + '555-567-8901', + '202 Maple Dr, Calgary, AB' + ); + +-- Insert User Roles +INSERT INTO + UserRole (UserID, Client, Admin) +VALUES + (1, TRUE, TRUE), + (2, TRUE, FALSE), + (3, TRUE, FALSE), + (4, TRUE, FALSE), + (5, TRUE, FALSE); + +-- Insert Categories +INSERT INTO + Category (CategoryID, Name) +VALUES + (1, 'Textbooks'), + (2, 'Electronics'), + (3, 'Furniture'), + (4, 'Clothing'), + (5, 'Sports Equipment'), + (6, 'Musical Instruments'), + (7, 'Art Supplies'), + (8, 'Kitchen Appliances'), + (9, 'Gaming'), + (10, 'Bicycles'), + (11, 'Computer Accessories'), + (12, 'Stationery'), + (13, 'Fitness Equipment'), + (14, 'Winter Sports'), + (15, 'Lab Equipment'), + (16, 'Camping Gear'), + (17, 'School Supplies'), + (18, 'Office Furniture'), + (19, 'Books (Non-textbook)'), + (20, 'Math & Science Resources'), + (21, 'Engineering Tools'), + (22, 'Backpacks & Bags'), + (23, 'Audio Equipment'), + (24, 'Dorm Essentials'), + (25, 'Smartphones & Tablets'), + (26, 'Winter Clothing'), + (27, 'Photography Equipment'), + (28, 'Event Tickets'), + (29, 'Software Licenses'), + (30, 'Transportation (Car Pool)'); + +-- Insert Products +INSERT INTO + Product ( + ProductID, + Name, + Price, + StockQuantity, + UserID, + Description, + CategoryID, + Date + ) +VALUES + ( + 1, + 'Calculus Textbook 8th Edition', + 79.99, + 5, + 1, + 'Like new calculus textbook, minor highlighting', + 1, + '2024-10-15 10:00:00' + ), + ( + 2, + 'HP Laptop', + 699.99, + 1, + 2, + '2023 HP Pavilion, 16GB RAM, 512GB SSD', + 2, + '2024-10-10 14:30:00' + ), + ( + 3, + 'Dorm Desk', + 120.00, + 1, + 3, + 'Sturdy desk perfect for studying, minor scratches', + 3, + '2024-10-12 09:15:00' + ), + ( + 4, + 'University Hoodie', + 35.00, + 3, + 1, + 'Size L, university logo, worn twice', + 4, + '2024-10-14 16:45:00' + ), + ( + 5, + 'Basketball', + 25.50, + 1, + 4, + 'Slightly used indoor basketball', + 5, + '2024-10-11 11:20:00' + ), + ( + 6, + 'Acoustic Guitar', + 175.00, + 1, + 2, + 'Beginner acoustic guitar with case', + 6, + '2024-10-09 13:10:00' + ), + ( + 7, + 'Physics Textbook', + 65.00, + 2, + 5, + 'University Physics 14th Edition, good condition', + 1, + '2024-10-08 10:30:00' + ), + ( + 8, + 'Mini Fridge', + 85.00, + 1, + 3, + 'Small dorm fridge, works perfectly', + 8, + '2024-10-13 15:00:00' + ), + ( + 9, + 'PlayStation 5 Controller', + 55.00, + 1, + 4, + 'Extra controller, barely used', + 9, + '2024-10-07 17:20:00' + ), + ( + 10, + 'Mountain Bike', + 350.00, + 1, + 5, + 'Trek mountain bike, great condition, new tires', + 10, + '2024-10-06 14:00:00' + ), + ( + 11, + 'Wireless Mouse', + 22.99, + 3, + 1, + 'Logitech wireless mouse with battery', + 11, + '2024-10-05 09:30:00' + ), + ( + 12, + 'Chemistry Lab Coat', + 30.00, + 2, + 2, + 'Size M, required for chem labs', + 15, + '2024-10-04 13:45:00' + ), + ( + 13, + 'Graphing Calculator', + 75.00, + 1, + 3, + 'TI-84 Plus, perfect working condition', + 12, + '2024-10-03 11:15:00' + ), + ( + 14, + 'Yoga Mat', + 20.00, + 1, + 4, + 'Thick yoga mat, barely used', + 13, + '2024-10-02 16:00:00' + ), + ( + 15, + 'Winter Jacket', + 120.00, + 1, + 5, + 'Columbia winter jacket, size XL, very warm', + 26, + '2024-10-01 10:20:00' + ), + ( + 16, + 'Computer Science Textbook', + 70.00, + 1, + 1, + 'Introduction to Algorithms, like new', + 1, + '2024-09-30 14:30:00' + ), + ( + 17, + 'Desk Lamp', + 15.00, + 2, + 2, + 'LED desk lamp with adjustable brightness', + 24, + '2024-09-29 12:00:00' + ), + ( + 18, + 'Scientific Calculator', + 25.00, + 1, + 3, + 'Casio scientific calculator', + 12, + '2024-09-28 11:30:00' + ), + ( + 19, + 'Bluetooth Speaker', + 45.00, + 1, + 4, + 'JBL Bluetooth speaker, great sound', + 23, + '2024-09-27 15:45:00' + ), + ( + 20, + 'Backpack', + 40.00, + 1, + 5, + 'North Face backpack, lots of pockets', + 22, + '2024-09-26 09:15:00' + ); + +-- Insert Image URLs +INSERT INTO + Image_URL (URL, ProductID) +VALUES + ('/image1.avif', 1), + ('/image1.avif', 2), + ('/image1.avif', 3), + ('/image1.avif', 4), + ('/image1.avif', 5), + ('/image1.avif', 6), + ('/image1.avif', 7), + ('/image1.avif', 8), + ('/image1.avif', 9), + ('/image1.avif', 10); + +-- Insert Product-Category relationships (products with multiple categories) +INSERT INTO + Product_Category (ProductID, CategoryID) +VALUES + (1, 1), + (1, 17), + (1, 20), -- Calculus book: Textbooks, School Supplies, Math Resources + (2, 2), + (2, 11), + (2, 25), -- Laptop: Electronics, Computer Accessories, Smartphones & Tablets + (3, 3), + (3, 18), + (3, 24), -- Desk: Furniture, Office Furniture, Dorm Essentials + (4, 4), + (4, 26), -- Hoodie: Clothing, Winter Clothing + (5, 5), + (5, 13), -- Basketball: Sports Equipment, Fitness Equipment + (6, 6), + (6, 23), -- Guitar: Musical Instruments, Audio Equipment + (7, 1), + (7, 15), + (7, 20), -- Physics book: Textbooks, Lab Equipment, Math & Science Resources + (8, 8), + (8, 24), -- Mini Fridge: Kitchen Appliances, Dorm Essentials + (9, 9), + (9, 2), -- PS5 Controller: Gaming, Electronics + (10, 10), + (10, 5), + (10, 13), -- Mountain Bike: Bicycles, Sports Equipment, Fitness Equipment + (11, 11), + (11, 2), -- Mouse: Computer Accessories, Electronics + (12, 15), + (12, 17), -- Lab Coat: Lab Equipment, School Supplies + (13, 12), + (13, 17), + (13, 20), -- Calculator: Stationery, School Supplies, Math & Science Resources + (14, 13), + (14, 5), -- Yoga Mat: Fitness Equipment, Sports Equipment + (15, 26), + (15, 4), + (15, 14), -- Winter Jacket: Winter Clothing, Clothing, Winter Sports + (16, 1), + (16, 17), + (16, 19), -- CS Book: Textbooks, School Supplies, Books (Non-textbook) + (17, 24), + (17, 2), -- Desk Lamp: Dorm Essentials, Electronics + (18, 12), + (18, 17), + (18, 20), -- Scientific Calculator: Stationery, School Supplies, Math & Science + (19, 23), + (19, 2), + (19, 24), -- Bluetooth Speaker: Audio Equipment, Electronics, Dorm Essentials + (20, 22), + (20, 17), + (20, 24); + +-- Backpack: Backpacks & Bags, School Supplies, Dorm Essentials +-- Insert History records +-- +INSERT INTO + History (HistoryID, UserID, ProductID, Date) +VALUES + (1, 1, 1, '2024-10-15 11:30:00'), + (2, 1, 2, '2024-10-14 13:45:00'), + (3, 1, 5, '2024-10-13 09:20:00'), + (4, 1, 4, '2024-10-12 16:10:00'); + +-- +INSERT INTO + History (HistoryID, UserID, ProductID, Date) +VALUES + (1, 2, 1, '2024-10-15 11:30:00'), -- User 2 viewed Calculus Textbook + (2, 3, 2, '2024-10-14 13:45:00'), -- User 3 viewed HP Laptop + (3, 4, 3, '2024-10-13 09:20:00'), -- User 4 viewed Dorm Desk + (4, 5, 4, '2024-10-12 16:10:00'), -- User 5 viewed University Hoodie + (5, 1, 5, '2024-10-11 14:30:00'), -- User 1 viewed Basketball + (6, 2, 6, '2024-10-10 10:15:00'), -- User 2 viewed Acoustic Guitar + (7, 3, 7, '2024-10-09 15:40:00'), -- User 3 viewed Physics Textbook + (8, 4, 8, '2024-10-08 11:25:00'), -- User 4 viewed Mini Fridge + (9, 5, 9, '2024-10-07 17:50:00'), -- User 5 viewed PS5 Controller + (10, 1, 10, '2024-10-06 14:15:00'); + +-- User 1 viewed Mountain Bike +-- Insert Reviews +INSERT INTO + Review ( + ReviewID, + UserID, + ProductID, + Comment, + Rating, + Date + ) +VALUES + ( + 1, + 2, + 1, + 'Great condition, exactly as described!', + 5, + '2024-10-16 09:30:00' + ), + ( + 2, + 3, + 2, + 'Works well, but had a small scratch not mentioned in the listing.', + 4, + '2024-10-15 14:20:00' + ), + ( + 3, + 4, + 6, + 'Perfect for beginners, sounds great!', + 5, + '2024-10-14 11:10:00' + ), + ( + 4, + 5, + 8, + 'Keeps my drinks cold, but a bit noisy at night.', + 3, + '2024-10-13 16:45:00' + ), + ( + 5, + 1, + 10, + 'Excellent bike, well maintained!', + 5, + '2024-10-12 13:25:00' + ); + +-- Insert Favorites +INSERT INTO + Favorites (UserID, ProductID) +VALUES + (1, 2), -- User 1 likes HP Laptop + (1, 7), -- User 1 likes Physics Textbook + (2, 3), -- User 2 likes Dorm Desk + (2, 10), -- User 2 likes Mountain Bike + (3, 6), -- User 3 likes Acoustic Guitar + (4, 5), -- User 4 likes Basketball + (5, 8); + +-- User 5 likes Mini Fridge +-- Insert Transactions +INSERT INTO + Transaction ( + TransactionID, + UserID, + ProductID, + Date, + PaymentStatus + ) +VALUES + (1, 2, 1, '2024-10-16 10:30:00', 'Completed'), + (2, 3, 6, '2024-10-15 15:45:00', 'Completed'), + (3, 4, 8, '2024-10-14 12:20:00', 'Pending'), + (4, 5, 10, '2024-10-13 17:10:00', 'Completed'), + (5, 1, 4, '2024-10-12 14:30:00', 'Completed'); + +-- Insert Recommendations +INSERT INTO + Recommendation (RecommendationID_PK, UserID, RecommendedProductID) +VALUES + (1, 1, 7), -- Recommend Physics Textbook to User 1 + (2, 1, 13), -- Recommend Graphing Calculator to User 1 + (3, 2, 3), -- Recommend Dorm Desk to User 2 + (4, 2, 17), -- Recommend Desk Lamp to User 2 + (5, 3, 16), -- Recommend CS Textbook to User 3 + (6, 4, 14), -- Recommend Yoga Mat to User 4 + (7, 5, 15); + +INSERT INTO + Recommendation (RecommendationID_PK, UserID, RecommendedProductID) +VALUES + (12, 1, 19), + (13, 1, 9), + (14, 1, 11), + (15, 1, 16), + -- Insert Authentication records +INSERT INTO + AuthVerification (Email, VerificationCode, Authenticated, Date) +VALUES + ( + 'john.doe@example.com', + '123456', + TRUE, + '2024-10-01 09:00:00' + ), + ( + 'jane.smith@example.com', + '234567', + TRUE, + '2024-10-02 10:15:00' + ), + ( + 'michael.b@example.com', + '345678', + TRUE, + '2024-10-03 11:30:00' + ), + ( + 'sarah.w@example.com', + '456789', + TRUE, + '2024-10-04 12:45:00' + ), + ( + 'david.t@example.com', + '567890', + TRUE, + '2024-10-05 14:00:00' + ); + +INSERT INTO + Product ( + ProductID, + Name, + Description, + Price, + StockQuantity, + CategoryID + ) +VALUES + ( + 101, + 'Smart Coffee Maker', + 'Wi-Fi enabled coffee machine with scheduling feature', + 129.99, + 50, + 11 + ), + ( + 102, + 'Ergonomic Office Chair', + 'Adjustable mesh chair with lumbar support', + 199.99, + 35, + 12 + ), + ( + 103, + 'Wireless Mechanical Keyboard', + 'RGB-backlit wireless keyboard with mechanical switches', + 89.99, + 60, + 13 + ), + ( + 104, + 'Portable Solar Charger', + 'Foldable solar power bank with USB-C support', + 59.99, + 40, + 14 + ), + ( + 105, + 'Noise-Canceling Headphones', + 'Over-ear Bluetooth headphones with ANC', + 179.99, + 25, + 15 + ), + ( + 106, + 'Smart Water Bottle', + 'Tracks water intake and glows as a hydration reminder', + 39.99, + 75, + 11 + ), + ( + 107, + 'Compact Air Purifier', + 'HEPA filter air purifier for small rooms', + 149.99, + 30, + 16 + ), + ( + 108, + 'Smart LED Desk Lamp', + 'Adjustable LED lamp with voice control', + 69.99, + 45, + 12 + ), + ( + 109, + '4K Streaming Device', + 'HDMI streaming stick with voice remote', + 49.99, + 80, + 17 + ), + ( + 110, + 'Smart Plant Monitor', + 'Bluetooth-enabled sensor for plant health tracking', + 34.99, + 55, + 18 + ), + ( + 111, + 'Wireless Charging Pad', + 'Fast-charging pad for Qi-compatible devices', + 29.99, + 90, + 13 + ), + ( + 112, + 'Mini Projector', + 'Portable projector with built-in speakers', + 129.99, + 20, + 14 + ), + ( + 113, + 'Foldable Bluetooth Keyboard', + 'Ultra-thin keyboard for travel use', + 39.99, + 70, + 19 + ), + ( + 114, + 'Smart Alarm Clock', + 'AI-powered alarm clock with sunrise simulation', + 79.99, + 40, + 15 + ), + ( + 115, + 'Touchscreen Toaster', + 'Customizable toaster with a digital display', + 99.99, + 30, + 11 + ), + ( + 116, + 'Cordless Vacuum Cleaner', + 'Lightweight handheld vacuum with strong suction', + 159.99, + 25, + 16 + ), + ( + 117, + 'Smart Bike Lock', + 'Fingerprint and app-controlled bike security lock', + 89.99, + 35, + 20 + ), + ( + 118, + 'Bluetooth Sleep Headband', + 'Comfortable sleep headband with built-in speakers', + 49.99, + 60, + 18 + ), + ( + 119, + 'Retro Game Console', + 'Plug-and-play console with 500+ classic games', + 79.99, + 50, + 17 + ), + ( + 120, + 'Automatic Pet Feeder', + 'App-controlled food dispenser for pets', + 99.99, + 40, + 20 + ); + +SELECT + p.*, + i.URL AS image_url +FROM + Product p + LEFT JOIN Image_URL i ON p.ProductID = i.ProductID +WHERE + p.ProductID = 1 diff --git a/mysql-code/init-db.py b/mysql-code/init-db.py new file mode 100644 index 0000000..a222193 --- /dev/null +++ b/mysql-code/init-db.py @@ -0,0 +1,4 @@ +import subprocess + +if (subprocess.run("mysql -u root mysql < mysql-code/Schema.sql", shell=True, check=True)): + print("successfully created the Marketplace databse") diff --git a/recommondation-engine/client.js b/recommondation-engine/client.js new file mode 100644 index 0000000..67258ae --- /dev/null +++ b/recommondation-engine/client.js @@ -0,0 +1,48 @@ +const net = require("net"); + +// Function to get recommendations from the Python server +function getRecommendations(userId) { + const client = new net.Socket(); + + // Connect to the server on localhost at port 9999 + client.connect(9999, "localhost", function () { + console.log(`Connected to server, sending user_id: ${userId}`); + + // Send the user_id in JSON format + const message = JSON.stringify({ user_id: userId }); + client.write(message); + }); + + // Listen for data from the server + client.on("data", function (data) { + const recommendations = JSON.parse(data.toString()); + console.log( + `Recommendations for User ${userId}:`, + recommendations.recommendations, + ); + + // Close the connection after receiving the response + client.destroy(); + }); + + // Handle connection errors + client.on("error", function (error) { + console.error("Connection error:", error.message); + }); + + // Handle connection close + client.on("close", function () { + console.log(`Connection to server closed for User ${userId}`); + }); +} + +// Function to simulate multiple users requesting recommendations +function simulateClients() { + for (let i = 1; i <= 5; i++) { + setTimeout(() => { + getRecommendations(i); // Simulate clients with IDs 1 to 5 + }, i * 1000); // Stagger requests every second + } +} + +simulateClients(); diff --git a/recommondation-engine/server.py b/recommondation-engine/server.py new file mode 100644 index 0000000..89850b1 --- /dev/null +++ b/recommondation-engine/server.py @@ -0,0 +1,26 @@ +from flask import Flask, request, jsonify +from flask_cors import CORS + +app = Flask(__name__) +CORS(app) # Enable CORS for all routes + +@app.route('/api/user/session', methods=['POST']) +def handle_session_data(): + try: + data = request.get_json() + user_id = data.get('userId') + email = data.get('email') + is_authenticated = data.get('isAuthenticated') + + if not user_id or not email or is_authenticated is None: + return jsonify({'error': 'Invalid data'}), 400 + + print(f"Received session data: User ID: {user_id}, Email: {email}, Authenticated: {is_authenticated}") + return jsonify({'message': 'Session data received successfully'}) + + except Exception as e: + print(f"Error: {e}") + return jsonify({'error': 'Server error'}), 500 + +if __name__ == '__main__': + app.run(debug=True, host='0.0.0.0', port=5000)