From 22a14cb6a4f7757f223e44dbe2800c99d4738757 Mon Sep 17 00:00:00 2001 From: Mann Patel <130435633+MannPatel0@users.noreply.github.com> Date: Fri, 21 Mar 2025 22:17:57 -0600 Subject: [PATCH 1/9] Updated SQL for Progress Report-3 --- SQL_code/Schema.sql | 323 +++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 321 insertions(+), 2 deletions(-) diff --git a/SQL_code/Schema.sql b/SQL_code/Schema.sql index 97b93c4..b63a22f 100644 --- a/SQL_code/Schema.sql +++ b/SQL_code/Schema.sql @@ -1,3 +1,7 @@ +-- MySql Version 9.2.0 +CREATE DATABASE Marketplace; + +Use Marketplace -- User Entity CREATE TABLE User ( UserID INT AUTO_INCREMENT PRIMARY KEY, @@ -32,6 +36,7 @@ CREATE TABLE Product ( UserID INT, Description TEXT, CategoryID INT NOT NULL, + Date DATETIME DEFAULT CURRENT_TIMESTAMP, FOREIGN KEY (UserID) REFERENCES User (UserID), FOREIGN KEY (CategoryID) REFERENCES Category (CategoryID) ); @@ -40,8 +45,6 @@ CREATE TABLE Image_URL ( URL VARCHAR(255), FOREIGN KEY (ProductID) REFERENCES Product (ProductID) ) - - -- Review Entity (Many-to-One with User, Many-to-One with Product) CREATE TABLE Review ( ReviewID INT PRIMARY KEY, @@ -113,3 +116,319 @@ CREATE TABLE AuthVerification ( Authenticated BOOLEAN DEFAULT FALSE, Date DATETIME DEFAULT CURRENT_TIMESTAMP ); + +-- Insert sample categories +INSERT INTO + Category (CategoryID, Name) +VALUES + (1, 'Electronics'), + (2, 'Clothing'), + (3, 'Books'), + (4, 'Home & Garden'), + (5, 'Sports & Outdoors'); + +-- USER CRUD OPERATIONS +-- Create User (INSERT) +INSERT INTO + User (Name, Email, UCID, Password, Phone, Address) +VALUES + ( + 'John Doe', + 'john@example.com', + 'UC123456', + 'hashed_password_here', + '555-123-4567', + '123 Main St' + ); + +-- Set user role +INSERT INTO + UserRole (UserID, Client, Admin) +VALUES + (LAST_INSERT_ID (), TRUE, FALSE); + +-- Read User (SELECT) +SELECT + u.*, + ur.Client, + ur.Admin +FROM + User u + JOIN UserRole ur ON u.UserID = ur.UserID +WHERE + u.UserID = 1; + +-- Update User (UPDATE) +UPDATE User +SET + Name = 'John Smith', + Phone = '555-987-6543', + Address = '456 Elm St' +WHERE + UserID = 1; + +-- Update User Role +UPDATE UserRole +SET + Admin = TRUE +WHERE + UserID = 1; + +-- Delete User (DELETE) +DELETE FROM User +WHERE + UserID = 1; + +-- Note: Cascade will delete related UserRole record +-- PRODUCT CRUD OPERATIONS +-- Create Product (INSERT) +INSERT INTO + Product ( + ProductID, + Name, + Price, + StockQuantity, + UserID, + Description, + CategoryID + ) +VALUES + ( + 1, + 'Smartphone', + 599.99, + 50, + 1, + 'Latest model smartphone with amazing features', + 1 + ); + +-- Add product images +INSERT INTO + Image_URL (URL, ProductID) +VALUES + ('https://example.com/images/smartphone1.jpg', 1), + ('https://example.com/images/smartphone2.jpg', 1); + +-- Read Product (SELECT) +SELECT + p.*, + c.Name as CategoryName, + u.Name as SellerName, + i.URL as ImageURL +FROM + Product p + JOIN Category c ON p.CategoryID = c.CategoryID + JOIN User u ON p.UserID = u.UserID + LEFT JOIN Image_URL i ON p.ProductID = i.ProductID +WHERE + p.ProductID = 1; + +-- Update Product (UPDATE) +UPDATE Product +SET + Name = 'Premium Smartphone', + Price = 649.99, + StockQuantity = 45, + Description = 'Updated description with new features' +WHERE + ProductID = 1; + +-- Delete Product (DELETE) +DELETE FROM Product +WHERE + ProductID = 1; + +-- CATEGORY CRUD OPERATIONS (Admin only for delete) +-- Create Category (INSERT) +INSERT INTO + Category (CategoryID, Name) +VALUES + (6, 'Toys & Games'); + +-- Read Category (SELECT) +SELECT + * +FROM + Category +WHERE + CategoryID = 6; + +-- Update Category (UPDATE) +UPDATE Category +SET + Name = 'Toys & Children''s Games' +WHERE + CategoryID = 6; + +-- Delete Category (DELETE) - Admin only +DELETE FROM Category +WHERE + CategoryID = 6; + +-- REVIEW OPERATIONS (Create only as specified) +-- Create Review (INSERT) +INSERT INTO + Review (ReviewID, UserID, ProductID, Comment, Rating) +VALUES + ( + 1, + 1, + 1, + 'Great product, very satisfied with the purchase!', + 5 + ); + +-- TRANSACTION OPERATIONS (Create only as specified) +-- Create Transaction (INSERT) +INSERT INTO + Transaction (TransactionID, UserID, ProductID, PaymentStatus) +VALUES + (1, 1, 1, 'Completed'); + +-- HISTORY CRUD OPERATIONS +-- Create History (INSERT) +INSERT INTO + History (HistoryID, UserID, ProductID) +VALUES + (1, 1, 1); + +-- Read History (SELECT) +SELECT + h.*, + p.Name as ProductName +FROM + History h + JOIN Product p ON h.ProductID = p.ProductID +WHERE + h.UserID = 1 +ORDER BY + h.Date DESC; + +-- Delete History (DELETE) +DELETE FROM History +WHERE + HistoryID = 1; + +-- FAVORITES CRUD OPERATIONS +-- Create Favorite (INSERT) +INSERT INTO + Favorites (UserID, ProductID) +VALUES + (1, 1); + +-- Read Favorites (SELECT) +SELECT + f.*, + p.Name as ProductName, + p.Price +FROM + Favorites f + JOIN Product p ON f.ProductID = p.ProductID +WHERE + f.UserID = 1; + +-- Delete Favorite (DELETE) +DELETE FROM Favorites +WHERE + FavoriteID = 1; + +-- RECOMMENDATION SYSTEM OPERATIONS +-- Create Recommendation (INSERT) +INSERT INTO + Recommendation (RecommendationID_PK, UserID, RecommendedProductID) +VALUES + (1, 1, 2); + +-- Read Recommendations (SELECT) +SELECT + r.*, + p.Name as RecommendedProductName, + p.Price, + p.Description +FROM + Recommendation r + JOIN Product p ON r.RecommendedProductID = p.ProductID +WHERE + r.UserID = 1; + +-- Update Recommendation (UPDATE) +UPDATE Recommendation +SET + RecommendedProductID = 3 +WHERE + RecommendationID_PK = 1; + +-- Delete Recommendation (DELETE) +DELETE FROM Recommendation +WHERE + RecommendationID_PK = 1; + +-- Authentication Operations +-- Create verification code +INSERT INTO + AuthVerification (Email, VerificationCode) +VALUES + ('new_user@example.com', '123456'); + +-- Update authentication status +UPDATE AuthVerification +SET + Authenticated = TRUE +WHERE + Email = 'new_user@example.com' + AND VerificationCode = '123456'; + +-- Complex Queries for Reports and Analysis +-- Get top-selling products +SELECT + p.ProductID, + p.Name, + COUNT(t.TransactionID) as SalesCount, + SUM(p.Price) as TotalRevenue +FROM + Product p + JOIN Transaction t ON p.ProductID = t.ProductID +WHERE + t.PaymentStatus = 'Completed' +GROUP BY + p.ProductID, + p.Name +ORDER BY + SalesCount DESC +LIMIT + 10; + +-- Get highest-rated products +SELECT + p.ProductID, + p.Name, + AVG(r.Rating) as AverageRating, + COUNT(r.ReviewID) as ReviewCount +FROM + Product p + JOIN Review r ON p.ProductID = r.ProductID +GROUP BY + p.ProductID, + p.Name +HAVING + ReviewCount >= 5 +ORDER BY + AverageRating DESC +LIMIT + 10; + +-- Get user purchase history with product details +SELECT + t.TransactionID, + t.Date, + p.Name, + p.Price, + t.PaymentStatus +FROM + Transaction t + JOIN Product p ON t.ProductID = p.ProductID +WHERE + t.UserID = 1 +ORDER BY + t.Date DESC; From 224df3c642dafcaf137833c680d46715429a0346 Mon Sep 17 00:00:00 2001 From: Mann Patel <130435633+MannPatel0@users.noreply.github.com> Date: Fri, 21 Mar 2025 22:59:27 -0600 Subject: [PATCH 2/9] Update Schema.sql --- SQL_code/Schema.sql | 105 ++++++++++++++++++++------------------------ 1 file changed, 47 insertions(+), 58 deletions(-) diff --git a/SQL_code/Schema.sql b/SQL_code/Schema.sql index b63a22f..220288c 100644 --- a/SQL_code/Schema.sql +++ b/SQL_code/Schema.sql @@ -1,7 +1,8 @@ -- MySql Version 9.2.0 CREATE DATABASE Marketplace; -Use Marketplace +USE Marketplace; + -- User Entity CREATE TABLE User ( UserID INT AUTO_INCREMENT PRIMARY KEY, @@ -21,7 +22,7 @@ CREATE TABLE UserRole ( FOREIGN KEY (UserID) REFERENCES User (UserID) ON DELETE CASCADE ); --- Category Entity (must be created before Product or else error) +-- Category Entity (must be created before Product or else error) CREATE TABLE Category ( CategoryID INT PRIMARY KEY, Name VARCHAR(255) NOT NULL @@ -41,11 +42,14 @@ CREATE TABLE Product ( FOREIGN KEY (CategoryID) REFERENCES Category (CategoryID) ); +-- Fixed Image_URL table CREATE TABLE Image_URL ( URL VARCHAR(255), + ProductID INT, FOREIGN KEY (ProductID) REFERENCES Product (ProductID) -) --- Review Entity (Many-to-One with User, Many-to-One with Product) +); + +-- Fixed Review Entity (Many-to-One with User, Many-to-One with Product) CREATE TABLE Review ( ReviewID INT PRIMARY KEY, UserID INT, @@ -108,7 +112,7 @@ CREATE TABLE Product_Category ( FOREIGN KEY (CategoryID) REFERENCES Category (CategoryID) ); --- Login Authentication table, Store the userID,and a emailed code of user who have not authenticated, +-- Login Authentication table CREATE TABLE AuthVerification ( UserID INT AUTO_INCREMENT PRIMARY KEY, Email VARCHAR(100) UNIQUE NOT NULL, @@ -174,12 +178,6 @@ SET WHERE UserID = 1; --- Delete User (DELETE) -DELETE FROM User -WHERE - UserID = 1; - --- Note: Cascade will delete related UserRole record -- PRODUCT CRUD OPERATIONS -- Create Product (INSERT) INSERT INTO @@ -203,12 +201,41 @@ VALUES 1 ); --- Add product images +-- Add product images with the placeholder URL INSERT INTO Image_URL (URL, ProductID) VALUES - ('https://example.com/images/smartphone1.jpg', 1), - ('https://example.com/images/smartphone2.jpg', 1); + ('https://picsum.photos/id/237/200/300', 1), + ('https://picsum.photos/id/237/200/300', 1); + +-- Create another product for recommendations +INSERT INTO + Product ( + ProductID, + Name, + Price, + StockQuantity, + UserID, + Description, + CategoryID + ) +VALUES + ( + 2, + 'Tablet', + 799.99, + 30, + 1, + 'High-performance tablet', + 1 + ); + +-- Add placeholder images for the second product +INSERT INTO + Image_URL (URL, ProductID) +VALUES + ('https://picsum.photos/id/237/200/300', 2), + ('https://picsum.photos/id/237/200/300', 2); -- Read Product (SELECT) SELECT @@ -234,12 +261,7 @@ SET WHERE ProductID = 1; --- Delete Product (DELETE) -DELETE FROM Product -WHERE - ProductID = 1; - --- CATEGORY CRUD OPERATIONS (Admin only for delete) +-- CATEGORY CRUD OPERATIONS -- Create Category (INSERT) INSERT INTO Category (CategoryID, Name) @@ -261,13 +283,7 @@ SET WHERE CategoryID = 6; --- Delete Category (DELETE) - Admin only -DELETE FROM Category -WHERE - CategoryID = 6; - --- REVIEW OPERATIONS (Create only as specified) --- Create Review (INSERT) +-- REVIEW OPERATIONS INSERT INTO Review (ReviewID, UserID, ProductID, Comment, Rating) VALUES @@ -279,15 +295,13 @@ VALUES 5 ); --- TRANSACTION OPERATIONS (Create only as specified) --- Create Transaction (INSERT) +-- TRANSACTION OPERATIONS INSERT INTO Transaction (TransactionID, UserID, ProductID, PaymentStatus) VALUES (1, 1, 1, 'Completed'); --- HISTORY CRUD OPERATIONS --- Create History (INSERT) +-- HISTORY OPERATIONS INSERT INTO History (HistoryID, UserID, ProductID) VALUES @@ -305,13 +319,7 @@ WHERE ORDER BY h.Date DESC; --- Delete History (DELETE) -DELETE FROM History -WHERE - HistoryID = 1; - --- FAVORITES CRUD OPERATIONS --- Create Favorite (INSERT) +-- FAVORITES OPERATIONS INSERT INTO Favorites (UserID, ProductID) VALUES @@ -328,13 +336,7 @@ FROM WHERE f.UserID = 1; --- Delete Favorite (DELETE) -DELETE FROM Favorites -WHERE - FavoriteID = 1; - --- RECOMMENDATION SYSTEM OPERATIONS --- Create Recommendation (INSERT) +-- RECOMMENDATION OPERATIONS INSERT INTO Recommendation (RecommendationID_PK, UserID, RecommendedProductID) VALUES @@ -352,18 +354,6 @@ FROM WHERE r.UserID = 1; --- Update Recommendation (UPDATE) -UPDATE Recommendation -SET - RecommendedProductID = 3 -WHERE - RecommendationID_PK = 1; - --- Delete Recommendation (DELETE) -DELETE FROM Recommendation -WHERE - RecommendationID_PK = 1; - -- Authentication Operations -- Create verification code INSERT INTO @@ -379,7 +369,6 @@ WHERE Email = 'new_user@example.com' AND VerificationCode = '123456'; --- Complex Queries for Reports and Analysis -- Get top-selling products SELECT p.ProductID, From 148fe95a1182945b6c5a7ffe77960d789d1ca66c Mon Sep 17 00:00:00 2001 From: Mann Patel <130435633+MannPatel0@users.noreply.github.com> Date: Sun, 23 Mar 2025 20:16:19 -0600 Subject: [PATCH 3/9] update --- .gitignore | 2 -- backend/recommondation.go | 43 ----------------------------- backend/utils/database.js | 1 - frontend/src/App.jsx | 21 +++++++------- {SQL_code => mysql-code}/Schema.sql | 0 {SQL_code => mysql-code}/init-db.py | 0 6 files changed, 11 insertions(+), 56 deletions(-) delete mode 100644 backend/recommondation.go rename {SQL_code => mysql-code}/Schema.sql (100%) rename {SQL_code => mysql-code}/init-db.py (100%) diff --git a/.gitignore b/.gitignore index c75f740..c9d2afa 100644 --- a/.gitignore +++ b/.gitignore @@ -2,8 +2,6 @@ */node_modules .DS_Store -*~/backend/index.js - .vscode/* !.vscode/extensions.json .idea 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/utils/database.js b/backend/utils/database.js index 020e67c..0bd82e5 100644 --- a/backend/utils/database.js +++ b/backend/utils/database.js @@ -5,7 +5,6 @@ const pool = mysql.createPool({ host: "localhost", user: "root", database: "marketplace", - password: "12345678", }); //Export a promise for promise-based query diff --git a/frontend/src/App.jsx b/frontend/src/App.jsx index 5a65c60..f6f42cd 100644 --- a/frontend/src/App.jsx +++ b/frontend/src/App.jsx @@ -70,7 +70,7 @@ function App() { email: userData.email, // Add any other required fields }), - } + }, ); if (!response.ok) { @@ -119,7 +119,7 @@ function App() { email: tempUserData.email, code: code, }), - } + }, ); if (!response.ok) { @@ -163,7 +163,7 @@ function App() { "Content-Type": "application/json", }, body: JSON.stringify(userData), - } + }, ); if (!response.ok) { @@ -222,11 +222,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 +266,7 @@ function App() { email: formValues.email, password: formValues.password, }), - } + }, ); if (!response.ok) { @@ -509,8 +510,8 @@ function App() { {isLoading ? "Please wait..." : isSignUp - ? "Create Account" - : "Sign In"} + ? "Create Account" + : "Sign In"} 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/SQL_code/init-db.py b/mysql-code/init-db.py similarity index 100% rename from SQL_code/init-db.py rename to mysql-code/init-db.py From e7a6e1dd8b11010a021973bdbe0e6e82abdc2bbe Mon Sep 17 00:00:00 2001 From: Mann Patel <130435633+MannPatel0@users.noreply.github.com> Date: Mon, 24 Mar 2025 23:04:12 -0600 Subject: [PATCH 4/9] Bug fix: Products No image --- backend/controllers/product.js | 56 ++++++++++- backend/routes/product.js | 8 +- frontend/src/pages/Home.jsx | 12 +-- frontend/src/pages/ProductDetail.jsx | 145 ++++++++++----------------- recommondation-engine/server1.py | 24 +++++ 5 files changed, 139 insertions(+), 106 deletions(-) create mode 100644 recommondation-engine/server1.py diff --git a/backend/controllers/product.js b/backend/controllers/product.js index 54f640e..bd40907 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,22 @@ 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.*, i.URL + FROM Product p + LEFT JOIN Image_URL i ON p.ProductID = i.ProductID + `); 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 +43,48 @@ exports.getAllProducts = async (req, res) => { } }; +// Get a single product by ID along with image URLs +exports.getProductById = async (req, res) => { + const { id } = req.params; + console.log(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], + ); + + if (data.length === 0) { + return res.status(404).json({ + success: false, + message: "Product not found", + }); + } + + // Assuming that `data` contains product information and the image URLs + const product = { + ...data[0], // First product found in the query + images: data.map((image) => image.image_url), // Collect all image URLs into an array + }; + + res.json({ + success: true, + message: "Product fetched successfully", + data: product, + }); + } catch (error) { + console.error("Error fetching product:", error); + return res.status(500).json({ + success: false, + error: "Database error occurred", + }); + } +}; + // db_con.query( // "SELECT ProductID FROM product WHERE ProductID = ?", // [productID], diff --git a/backend/routes/product.js b/backend/routes/product.js index 55d3761..aeea0d9 100644 --- a/backend/routes/product.js +++ b/backend/routes/product.js @@ -1,5 +1,9 @@ const express = require("express"); -const { addToFavorite, getAllProducts } = require("../controllers/product"); +const { + addToFavorite, + getAllProducts, + getProductById, +} = require("../controllers/product"); const router = express.Router(); @@ -7,4 +11,6 @@ router.post("/add_fav_product", addToFavorite); router.get("/get_product", getAllProducts); +router.post("/get_productID", getProductById); + module.exports = router; diff --git a/frontend/src/pages/Home.jsx b/frontend/src/pages/Home.jsx index 300fd9d..4ae5a6e 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"); @@ -24,12 +24,12 @@ const Home = () => { title: product.Name, price: product.Price, category: product.CategoryID, - image: product.ImageURL, + image: product.URL, condition: "New", // Modify based on actual data - seller: "Unknown", // Modify if seller info is available + seller: product.UserID, // Modify if seller info is available datePosted: "Just now", isFavorite: false, - })) + })), ); } else { throw new Error(data.message || "Error fetching products"); @@ -50,8 +50,8 @@ const Home = () => { prevListings.map((listing) => listing.id === id ? { ...listing, isFavorite: !listing.isFavorite } - : listing - ) + : listing, + ), ); }; diff --git a/frontend/src/pages/ProductDetail.jsx b/frontend/src/pages/ProductDetail.jsx index 6b32e8e..95a5286 100644 --- a/frontend/src/pages/ProductDetail.jsx +++ b/frontend/src/pages/ProductDetail.jsx @@ -1,83 +1,54 @@ -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 [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 { + const response = await fetch( + `http://localhost:3030/api/product/get_productID/${id}`, + ); + if (!response.ok) throw new Error("Failed to fetch product"); - console.log(product[id]); + const data = await response.json(); + if (data.success) { + setProduct(data.data); // Update the state with product details + } else { + throw new Error(data.message || "Error fetching product"); + } + } catch (error) { + console.error("Error fetching product:", error); + } + }; + fetchProduct(); + }, [id]); + + // Handle favorite toggle const toggleFavorite = () => { setIsFavorite(!isFavorite); }; + // Handle message submission const handleSendMessage = (e) => { e.preventDefault(); - // TODO: this would send the message to the seller + // Handle message logic here (send to seller) 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, @@ -94,9 +65,10 @@ const ProductDetail = () => { setCurrentImage(index); }; + if (!product) return
Loading...
; // Handle loading state + return (
- {/* Breadcrumb & Back Link */}
{
- {/* Left Column - Images */}
- {/* Main Image */}
{product[id].title}
- {/* Thumbnail Images */} - {product[id].images.length > 1 && ( + {product.images.length > 1 && (
- {product[id].images.map((image, index) => ( + {product.images.map((image, index) => (
{ > {`${product[id].title}
@@ -140,13 +109,11 @@ const ProductDetail = () => { )}
- {/* Right Column - Details */}
- {/* Product Info Card */}

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

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

{product[id].shortDescription}

+

{product.shortDescription}

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

@@ -204,8 +167,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 +180,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 +194,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,13 +210,12 @@ const ProductDetail = () => {

)} - {/* Seller Info */}
- {product[id].seller.avatar ? ( + {product.seller.avatar ? ( Seller @@ -267,17 +227,17 @@ const ProductDetail = () => {

- {product[id].seller.name} + {product.seller.name}

- Member since {product[id].seller.memberSince} + Member since {product.seller.memberSince}

Rating:{" "} - {product[id].seller.rating}/5 + {product.seller.rating}/5
@@ -285,13 +245,10 @@ const ProductDetail = () => {
- {/* Description Section */}

Description

-
- {formatDescription(product[id].description)} -
+
{product.description}
diff --git a/recommondation-engine/server1.py b/recommondation-engine/server1.py new file mode 100644 index 0000000..94f1fd4 --- /dev/null +++ b/recommondation-engine/server1.py @@ -0,0 +1,24 @@ +import asyncio +import websockets + +async def handle_client(websocket, path): + try: + # Receive user ID + user_id = await websocket.recv() + print(f"Received user ID: {user_id}") + + # Optional: You can add more logic here if needed + + except websockets.exceptions.ConnectionClosed: + print("Client disconnected") + except Exception as e: + print(f"Error processing request: {str(e)}") + +async def start_server(): + server = await websockets.serve(handle_client, "localhost", 5555) + print("WebSocket server started") + await server.wait_closed() + +# Run the server +if __name__ == "__main__": + asyncio.run(start_server()) From f52693dfc2e7c13017066fa5187b8aa88cd6ff61 Mon Sep 17 00:00:00 2001 From: Mann Patel <130435633+MannPatel0@users.noreply.github.com> Date: Tue, 25 Mar 2025 14:47:54 -0600 Subject: [PATCH 5/9] Homepg & ProductDet.. now updated --- backend/controllers/product.js | 43 ++++++-- backend/routes/product.js | 12 +- frontend/src/pages/Home.jsx | 12 +- frontend/src/pages/ProductDetail.jsx | 157 +++++++++++++++++++-------- 4 files changed, 156 insertions(+), 68 deletions(-) diff --git a/backend/controllers/product.js b/backend/controllers/product.js index bd40907..0a85686 100644 --- a/backend/controllers/product.js +++ b/backend/controllers/product.js @@ -24,9 +24,19 @@ exports.addToFavorite = async (req, res) => { exports.getAllProducts = async (req, res) => { try { const [data, fields] = await db.execute(` - SELECT p.*, i.URL - FROM Product p - LEFT JOIN Image_URL i ON p.ProductID = i.ProductID + 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 + LEFT JOIN + (SELECT ProductID, URL FROM Image_URL LIMIT 1) I ON P.ProductID = I.ProductID + JOIN User U ON P.UserID = U.UserID + JOIN Category C ON P.CategoryID = C.CategoryID; `); res.json({ @@ -43,10 +53,10 @@ exports.getAllProducts = async (req, res) => { } }; -// Get a single product by ID along with image URLs exports.getProductById = async (req, res) => { const { id } = req.params; - console.log(id); + console.log("Received Product ID:", id); + try { const [data] = await db.execute( ` @@ -58,29 +68,42 @@ exports.getProductById = async (req, res) => { [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", }); } - // Assuming that `data` contains product information and the image URLs + // 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], // First product found in the query - images: data.map((image) => image.image_url), // Collect all image URLs into an array + ...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("Error fetching product:", error); + console.error("Full Error Details:", error); return res.status(500).json({ success: false, - error: "Database error occurred", + message: "Database error occurred", + error: error.message, }); } }; diff --git a/backend/routes/product.js b/backend/routes/product.js index aeea0d9..90c7914 100644 --- a/backend/routes/product.js +++ b/backend/routes/product.js @@ -1,16 +1,20 @@ +// routes/product.js const express = require("express"); const { addToFavorite, getAllProducts, getProductById, } = require("../controllers/product"); - const router = express.Router(); +// 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.post("/get_productID", getProductById); +router.get("/:id", getProductById); // Simplified route module.exports = router; diff --git a/frontend/src/pages/Home.jsx b/frontend/src/pages/Home.jsx index 4ae5a6e..5b3fb4b 100644 --- a/frontend/src/pages/Home.jsx +++ b/frontend/src/pages/Home.jsx @@ -21,14 +21,14 @@ 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.URL, + 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: product.UserID, // 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 { diff --git a/frontend/src/pages/ProductDetail.jsx b/frontend/src/pages/ProductDetail.jsx index 95a5286..6ad1c10 100644 --- a/frontend/src/pages/ProductDetail.jsx +++ b/frontend/src/pages/ProductDetail.jsx @@ -5,6 +5,8 @@ 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(""); @@ -14,19 +16,28 @@ const ProductDetail = () => { useEffect(() => { const fetchProduct = async () => { try { - const response = await fetch( - `http://localhost:3030/api/product/get_productID/${id}`, - ); - if (!response.ok) throw new Error("Failed to fetch product"); + setLoading(true); + const response = await fetch(`http://localhost:3030/api/product/${id}`); - const data = await response.json(); - if (data.success) { - setProduct(data.data); // Update the state with product details + if (!response.ok) { + throw new Error("Failed to fetch product"); + } + + const result = await response.json(); + console.log(result); + + if (result.success) { + setProduct(result.data); + setError(null); } else { - throw new Error(data.message || "Error fetching product"); + throw new Error(result.message || "Error fetching product"); } } catch (error) { console.error("Error fetching product:", error); + setError(error.message); + setProduct(null); + } finally { + setLoading(false); } }; @@ -34,14 +45,35 @@ const ProductDetail = () => { }, [id]); // Handle favorite toggle - const toggleFavorite = () => { - setIsFavorite(!isFavorite); + 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(); - // Handle message logic here (send to seller) + // TODO: Implement actual message sending logic console.log("Message sent:", message); setMessage(""); setShowContactForm(false); @@ -50,23 +82,53 @@ const ProductDetail = () => { // 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); }; - if (!product) return
Loading...
; // Handle loading state + // Render loading state + if (loading) { + return ( +
+
+
+ ); + } + // Render error state + if (error) { + return ( +
+
+

Error Loading Product

+

{error}

+ + Back to Listings + +
+
+ ); + } + + // Render product details return (
@@ -82,15 +144,21 @@ const ProductDetail = () => {
- {product.title} + {product.images && product.images.length > 0 ? ( + {product.Name} + ) : ( +
+ No Image Available +
+ )}
- {product.images.length > 1 && ( + {product.images && product.images.length > 1 && (
{product.images.map((image, index) => (
{ > {`${product.title}
@@ -113,7 +181,7 @@ const ProductDetail = () => {

- {product.title} + {product.Name}

- ${product.price} + ${product.Price}
- {product.category} + {product.Category}
Condition: @@ -139,12 +207,12 @@ const ProductDetail = () => {
- Posted on {product.datePosted} + Posted on {product.Date}
-

{product.shortDescription}

+

{product.Description}

-
+ {/*

Description

-
{product.description}
+
{product.Description}
-
+
*/}
); }; From 7a87fc1e49ddd6381e843b1852fbf545ae1e1a49 Mon Sep 17 00:00:00 2001 From: Mann Patel <130435633+MannPatel0@users.noreply.github.com> Date: Tue, 25 Mar 2025 14:48:28 -0600 Subject: [PATCH 6/9] Initial Server and Client for recommond engine --- recommondation-engine/client.js | 48 ++++++++++++++++++++++++++++ recommondation-engine/server1.py | 54 +++++++++++++++++++++----------- 2 files changed, 84 insertions(+), 18 deletions(-) create mode 100644 recommondation-engine/client.js 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/server1.py b/recommondation-engine/server1.py index 94f1fd4..bd7cdad 100644 --- a/recommondation-engine/server1.py +++ b/recommondation-engine/server1.py @@ -1,24 +1,42 @@ -import asyncio -import websockets +import socket +import threading +import json -async def handle_client(websocket, path): - try: - # Receive user ID - user_id = await websocket.recv() - print(f"Received user ID: {user_id}") +# Sample recommendations function +def get_recommendations(user_id): + # This is a mock function. Replace it with your actual recommendation logic. + return {"recommendations": [f"Product {user_id} - Item 1", f"Product {user_id} - Item 2", f"Product {user_id} - Item 3"]} - # Optional: You can add more logic here if needed +# Handle client connection +def handle_client(client_socket, client_address): + print(f"New connection from {client_address}") - except websockets.exceptions.ConnectionClosed: - print("Client disconnected") - except Exception as e: - print(f"Error processing request: {str(e)}") + # Receive the client request (user_id) + request = client_socket.recv(1024).decode("utf-8") + if request: + data = json.loads(request) + user_id = data.get("user_id") -async def start_server(): - server = await websockets.serve(handle_client, "localhost", 5555) - print("WebSocket server started") - await server.wait_closed() + # Get recommendations for the user + recommendations = get_recommendations(user_id) + + # Send the response back to the client + client_socket.send(json.dumps(recommendations).encode("utf-8")) + + # Close the connection after sending the response + client_socket.close() + +# Start the server to handle multiple clients +def start_server(): + server = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + server.bind(("0.0.0.0", 9999)) # Bind to all interfaces on port 9999 + server.listen(5) + print("Server listening on port 9999...") + + while True: + client_socket, client_address = server.accept() + client_thread = threading.Thread(target=handle_client, args=(client_socket, client_address)) + client_thread.start() # Run the server -if __name__ == "__main__": - asyncio.run(start_server()) +start_server() From 91ec43627a1f4846ff451ef5f91752ea9d7cd732 Mon Sep 17 00:00:00 2001 From: Mann Patel <130435633+MannPatel0@users.noreply.github.com> Date: Sat, 29 Mar 2025 16:13:22 -0600 Subject: [PATCH 7/9] updated sql wit example dataset now --- README.md | 2 +- mysql-code/example-data.sql | 761 ++++++++++++++++++++++++++++++++++++ mysql-code/init-db.py | 3 +- 3 files changed, 764 insertions(+), 2 deletions(-) create mode 100644 mysql-code/example-data.sql 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/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 index 910368a..a222193 100644 --- a/mysql-code/init-db.py +++ b/mysql-code/init-db.py @@ -1,3 +1,4 @@ import subprocess -if (subprocess.run("mysql -u root mysql < SQL_code/Schema.sql", shell=True, check=True)): + +if (subprocess.run("mysql -u root mysql < mysql-code/Schema.sql", shell=True, check=True)): print("successfully created the Marketplace databse") From 2e77ef49f49017aa026967fabea276a0c9ce3efc Mon Sep 17 00:00:00 2001 From: Mann Patel <130435633+MannPatel0@users.noreply.github.com> Date: Sat, 29 Mar 2025 17:28:09 -0600 Subject: [PATCH 8/9] search bar now working --- backend/controllers/product.js | 4 +- backend/controllers/search.js | 164 ++++++++++++++++++++ backend/index.js | 2 + backend/routes/search.js | 14 ++ backend/utils/database.js | 2 +- frontend/package-lock.json | 115 ++++++++++++-- frontend/package.json | 1 + frontend/src/App.jsx | 11 ++ frontend/src/components/Navbar.jsx | 22 ++- frontend/src/pages/Home.jsx | 216 +++++++++++++++++++++------ frontend/src/pages/ProductDetail.jsx | 4 +- frontend/src/pages/SearchPage.jsx | 203 +++++++++++++++++++++++++ 12 files changed, 687 insertions(+), 71 deletions(-) create mode 100644 backend/controllers/search.js create mode 100644 backend/routes/search.js create mode 100644 frontend/src/pages/SearchPage.jsx diff --git a/backend/controllers/product.js b/backend/controllers/product.js index 0a85686..71396b8 100644 --- a/backend/controllers/product.js +++ b/backend/controllers/product.js @@ -33,12 +33,12 @@ exports.getAllProducts = async (req, res) => { I.URL AS ProductImage, C.Name AS Category FROM Product P - LEFT JOIN - (SELECT ProductID, URL FROM Image_URL LIMIT 1) I ON P.ProductID = I.ProductID + 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: "Products fetched successfully", 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/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 0bd82e5..689785e 100644 --- a/backend/utils/database.js +++ b/backend/utils/database.js @@ -4,7 +4,7 @@ const mysql = require("mysql2"); const pool = mysql.createPool({ host: "localhost", user: "root", - database: "marketplace", + 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 f6f42cd..bc7f83b 100644 --- a/frontend/src/App.jsx +++ b/frontend/src/App.jsx @@ -13,6 +13,7 @@ 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 function App() { // Authentication state - initialize from localStorage if available @@ -634,6 +635,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 5b3fb4b..da47d13 100644 --- a/frontend/src/pages/Home.jsx +++ b/frontend/src/pages/Home.jsx @@ -39,7 +39,6 @@ const Home = () => { setError(error.message); } }; - fetchProducts(); }, []); @@ -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 6ad1c10..15c8d86 100644 --- a/frontend/src/pages/ProductDetail.jsx +++ b/frontend/src/pages/ProductDetail.jsx @@ -133,11 +133,11 @@ const ProductDetail = () => {
- Back to listings + Back
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; From 71a90265d99dab823523a472dc7f041df02175e2 Mon Sep 17 00:00:00 2001 From: Mann Patel <130435633+MannPatel0@users.noreply.github.com> Date: Sun, 30 Mar 2025 00:20:42 -0600 Subject: [PATCH 9/9] update to engin --- frontend/src/App.jsx | 51 ++++++++++++++++++++++++++++++++ recommondation-engine/server.py | 26 ++++++++++++++++ recommondation-engine/server1.py | 42 -------------------------- 3 files changed, 77 insertions(+), 42 deletions(-) create mode 100644 recommondation-engine/server.py delete mode 100644 recommondation-engine/server1.py diff --git a/frontend/src/App.jsx b/frontend/src/App.jsx index bc7f83b..12b3231 100644 --- a/frontend/src/App.jsx +++ b/frontend/src/App.jsx @@ -14,6 +14,7 @@ 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 @@ -31,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); @@ -190,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); @@ -293,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); @@ -357,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 = () => (
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) diff --git a/recommondation-engine/server1.py b/recommondation-engine/server1.py deleted file mode 100644 index bd7cdad..0000000 --- a/recommondation-engine/server1.py +++ /dev/null @@ -1,42 +0,0 @@ -import socket -import threading -import json - -# Sample recommendations function -def get_recommendations(user_id): - # This is a mock function. Replace it with your actual recommendation logic. - return {"recommendations": [f"Product {user_id} - Item 1", f"Product {user_id} - Item 2", f"Product {user_id} - Item 3"]} - -# Handle client connection -def handle_client(client_socket, client_address): - print(f"New connection from {client_address}") - - # Receive the client request (user_id) - request = client_socket.recv(1024).decode("utf-8") - if request: - data = json.loads(request) - user_id = data.get("user_id") - - # Get recommendations for the user - recommendations = get_recommendations(user_id) - - # Send the response back to the client - client_socket.send(json.dumps(recommendations).encode("utf-8")) - - # Close the connection after sending the response - client_socket.close() - -# Start the server to handle multiple clients -def start_server(): - server = socket.socket(socket.AF_INET, socket.SOCK_STREAM) - server.bind(("0.0.0.0", 9999)) # Bind to all interfaces on port 9999 - server.listen(5) - print("Server listening on port 9999...") - - while True: - client_socket, client_address = server.accept() - client_thread = threading.Thread(target=handle_client, args=(client_socket, client_address)) - client_thread.start() - -# Run the server -start_server()