From 643b9e357cbfbdfb34dc18f2670fd7e452dedee5 Mon Sep 17 00:00:00 2001 From: Mann Patel <130435633+MannPatel0@users.noreply.github.com> Date: Thu, 3 Apr 2025 18:56:39 -0600 Subject: [PATCH] recommendation engine 75% polished --- backend/controllers/product.js | 5 +- backend/controllers/recommendation.js | 39 +++++++++ backend/index.js | 2 + backend/routes/recommendation.js | 8 ++ frontend/src/pages/Home.jsx | 76 ++++++++++++++---- frontend/src/pages/ProductDetail.jsx | 10 +-- frontend/src/pages/Settings.jsx | 56 +------------ mysql-code/Schema.sql | 7 +- .../__pycache__/app.cpython-313.pyc | Bin 0 -> 5045 bytes recommondation-engine/{example1.py => app.py} | 41 ++++++++-- recommondation-engine/server.py | 7 +- 11 files changed, 162 insertions(+), 89 deletions(-) create mode 100644 backend/controllers/recommendation.js create mode 100644 backend/routes/recommendation.js create mode 100644 recommondation-engine/__pycache__/app.cpython-313.pyc rename recommondation-engine/{example1.py => app.py} (73%) diff --git a/backend/controllers/product.js b/backend/controllers/product.js index 71396b8..1fcf06b 100644 --- a/backend/controllers/product.js +++ b/backend/controllers/product.js @@ -33,7 +33,7 @@ exports.getAllProducts = async (req, res) => { I.URL AS ProductImage, C.Name AS Category FROM Product P - JOIN Image_URL 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; `); @@ -60,9 +60,10 @@ exports.getProductById = async (req, res) => { try { const [data] = await db.execute( ` - SELECT p.*, i.URL AS image_url + SELECT p.*,U.Name AS SellerName, i.URL AS image_url FROM Product p LEFT JOIN Image_URL i ON p.ProductID = i.ProductID + JOIN User U ON p.UserID = U.UserID WHERE p.ProductID = ? `, [id], diff --git a/backend/controllers/recommendation.js b/backend/controllers/recommendation.js new file mode 100644 index 0000000..488b089 --- /dev/null +++ b/backend/controllers/recommendation.js @@ -0,0 +1,39 @@ +const db = require("../utils/database"); + +// TODO: Get the recommondaed product given the userID +exports.RecommondationByUserId = async (req, res) => { + const { id } = req.body; + try { + 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 + JOIN Recommendation R ON P.ProductID = R.RecommendedProductID + Where R.UserID = ?;`, + [id], + ); + + console.log(data); + res.json({ + success: true, + message: "Products fetched successfully", + data, + }); + } catch (error) { + console.error("Error finding products:", error); + return res.status(500).json({ + found: false, + error: "Database error occurred", + }); + } +}; diff --git a/backend/index.js b/backend/index.js index c78cda7..c3df135 100644 --- a/backend/index.js +++ b/backend/index.js @@ -6,6 +6,7 @@ const db = require("./utils/database"); const userRouter = require("./routes/user"); const productRouter = require("./routes/product"); const searchRouter = require("./routes/search"); +const recommendedRouter = require("./routes/recommendation"); const { generateEmailTransporter } = require("./utils/mail"); const { cleanupExpiredCodes, @@ -36,6 +37,7 @@ checkDatabaseConnection(db); 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 +app.use("/api/Engine", recommendedRouter); //prefix with /api/ // Set up a scheduler to run cleanup every hour setInterval(cleanupExpiredCodes, 60 * 60 * 1000); diff --git a/backend/routes/recommendation.js b/backend/routes/recommendation.js new file mode 100644 index 0000000..e252a34 --- /dev/null +++ b/backend/routes/recommendation.js @@ -0,0 +1,8 @@ +// routes/product.js +const express = require("express"); +const { RecommondationByUserId } = require("../controllers/recommendation"); +const router = express.Router(); + +router.post("/recommended", RecommondationByUserId); + +module.exports = router; diff --git a/frontend/src/pages/Home.jsx b/frontend/src/pages/Home.jsx index da47d13..d55af11 100644 --- a/frontend/src/pages/Home.jsx +++ b/frontend/src/pages/Home.jsx @@ -1,12 +1,60 @@ import { useState, useEffect } from "react"; import { Link, useNavigate } from "react-router-dom"; -import { Tag, Book, Laptop, Sofa, Utensils, Gift, Heart } from "lucide-react"; +import { Tag, Heart } from "lucide-react"; const Home = () => { const navigate = useNavigate(); const [listings, setListings] = useState([]); + const [recommended, setRecommended] = useState([]); const [error, setError] = useState(null); + useEffect(() => { + const fetchrecomProducts = async () => { + // Get the user's data from localStorage + const storedUser = JSON.parse(sessionStorage.getItem("user")); + console.log(storedUser); + try { + const response = await fetch( + "http://localhost:3030/api/engine/recommended", + { + method: "POST", + headers: { + "Content-Type": "application/json", + }, + body: JSON.stringify({ + id: storedUser.ID, + }), + }, + ); + if (!response.ok) throw new Error("Failed to fetch products"); + + const data = await response.json(); + console.log(data); + if (data.success) { + setRecommended( + data.data.map((product) => ({ + id: product.ProductID, + title: product.ProductName, // Use the alias from SQL + price: product.Price, + 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.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"); + } + } catch (error) { + console.error("Error fetching products:", error); + setError(error.message); + } + }; + fetchrecomProducts(); + }, []); + useEffect(() => { const fetchProducts = async () => { try { @@ -134,25 +182,25 @@ const Home = () => { id="RecomContainer" className="overflow-x-auto whitespace-nowrap flex space-x-6 scroll-smooth scrollbar-hide px-10 pl-0" > - {listings.map((listing) => ( + {recommended.map((recommended) => (
{listing.title}
-
- Condition: - {product.condition} -
+
Posted on {product.Date} @@ -287,11 +284,10 @@ const ProductDetail = () => {

- {product.UserID || "Unknown Seller"} + {product.SellerName || "Unknown Seller"}

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

diff --git a/frontend/src/pages/Settings.jsx b/frontend/src/pages/Settings.jsx index 150ff05..a7e3992 100644 --- a/frontend/src/pages/Settings.jsx +++ b/frontend/src/pages/Settings.jsx @@ -415,64 +415,10 @@ const Settings = () => { - {/* Privacy Section */} -
-
-
- -

Privacy

-
-
- -
-
-
-
- -
-

Search History

-

- Delete all your search history on StudentMarket -

-
-
- -
- -
-
- -
-

- Browsing History -

-

- Delete all your browsing history on StudentMarket -

-
-
- -
-
-
-
- {/* Delete Account (Danger Zone) */}
-

Danger Zone

+

Danger Zone !!!

diff --git a/mysql-code/Schema.sql b/mysql-code/Schema.sql index 3f36cf3..df51a58 100644 --- a/mysql-code/Schema.sql +++ b/mysql-code/Schema.sql @@ -61,7 +61,7 @@ CREATE TABLE Review ( ), Date DATETIME DEFAULT CURRENT_TIMESTAMP, FOREIGN KEY (UserID) REFERENCES User (UserID), - FOREIGN KEY (ProductID) REFERENCES Product (ProductID) + FOREIGN KEY (ProductID) REFERENCES Pprint(item[0])roduct (ProductID) ); -- Transaction Entity (Many-to-One with User, Many-to-One with Product) @@ -77,16 +77,17 @@ CREATE TABLE Transaction ( -- Recommendation Entity (Many-to-One with User, Many-to-One with Product) CREATE TABLE Recommendation ( - RecommendationID_PK INT PRIMARY KEY, + RecommendationID_PK INT AUTO_INCREMENT PRIMARY KEY, UserID INT, RecommendedProductID INT, + Date DATETIME DEFAULT CURRENT_TIMESTAMP, FOREIGN KEY (UserID) REFERENCES User (UserID), FOREIGN KEY (RecommendedProductID) REFERENCES Product (ProductID) ); -- History Entity (Many-to-One with User, Many-to-One with Product) CREATE TABLE History ( - HistoryID INT PRIMARY KEY, + HistoryID INT AUTO_INCREMENT PRIMARY KEY, UserID INT, ProductID INT, Date DATETIME DEFAULT CURRENT_TIMESTAMP, diff --git a/recommondation-engine/__pycache__/app.cpython-313.pyc b/recommondation-engine/__pycache__/app.cpython-313.pyc new file mode 100644 index 0000000000000000000000000000000000000000..7d88e62118d3a8eafce7c80036b6c7642567999e GIT binary patch literal 5045 zcmd5=OKcm*8J>MF-CYgGmYl*E_A@Lzfq9iqnFaw{_9sQi2QpO5dyh<#h*5S+ z+AuX;O_^Fw%PLs2=An?ss#(Zd>6#`^B!Fb#zcmcvLp0?S5;0L%*e!uFt|?PXCaENy zWRh%>JI(4yMu$<1PuQNUkyf%-4AZjNzP{e)Z5A7bW&6eyd{wpbStYH;Sevaj$Qu}% z6-*V|;-28 zPSN$AK_gwrsk&wMoY$-ih08tXlw7`Gp171PWO}fgHgY*bhuNU_302R)X7wof{K@=0 z4!~HB@Ib=s0kMcS+d4iP|6u%f+p)E_W93k1In+*mty0B<+K*Qd*Um4FRs6``QDP%o z&;{+h%fA6dVV8-zPB>MI$<9rXbJ!N#dP=5t-u+Xxu_Od7$Z2j1?g(^}))|>@MHku- z(pi~JGUPaQPKMKyxgbh%j^z8e6R-t3Gqw*QAA+kV$PCnqeT>fUtcA?~+gb=cNEc-R zwCII7JX2(kogjPaW9VMlJK?LBH{v8yZvkr+C&)2v3$nM-V(i(f_MmZSTQkVkcLhd5 zyVQ$tqdkd7(d1Bk=-i}qPO;RCf#*jCrQwP3F*pBcZ>>T8y>?+JA?4F2n?^Y+JvtywlJaMyp*XbZm4*_7F{wW(y#>uuHucsck5{UN zh2OhLXZqS)Qr<0&4-HRB7sp2uQmrmkpA06J#$oz~g3?){jH~YsW4L{*UAJl8oOL^& zpBR@fNds>*Pjtqu{Zw>p%jVMsY!aJRuc_&RrP|_*YNf9z*{scGHNXufZ{%$rY*N*y zZ7!WP02F*SU$uLD0Cv{)rmKgcsiqw;)hwVyDxFmTNo=uJHE;XssgyQt`(`v<$);ut zDxSB6Dju3P=akwEHs)-PrCzgAL<5uD!TP>KjQ5f`_heKn1zM)^)l)X{2`Fxbzqtuw z5q%wKfA0s2!{tEh^*5K^d@r>)e3$k9H1STN)cV{8d#cEjd-HT&) z+PaEjd0*F!*$=K3ePyw|wExWN=<3V-%CVYDqX z+}C3jN_WIHf?JQnHCm>3#Wnh|xW)jku`*M`HNdbMuJM3ytjs&OMr0k=*d|kvLTm(HJ#J{poZC(K6+! z_yh-a_I?-Yl>fV^)67N*XCRoe^>YVxx_07I^puM_GOF8+&P=h;&j}NJJ=KU0A>cy5 zXHNs7G}$iOL&@~1ng+m^xFkiG(@s`g=M^&bG|+jLPHO;*DcL=sndn*^ZCLH1oFSOEd5%cU@YbUzB2g=RtMuyG`qP zo6V_;j>&1-f`T)^87!O2V@(|w>itmr*zxj{EFNDW^1eEKV@X?#Xdbqdf zf9OS#&X4APF?ZufD^IM2pCfhMH;>=!T0O8H9xnPTJlcQsli81ZZg)Pj*7?kOXWxpv zGQ4uW)cMk9ldE`ja;7)1W|EkuEmn_}pC;N3te^8Cu&infaX z_~hrxAC7z;Deb#>TO3^zN58Ewq}?NvK;Mu14-fdz7e`tKc=n6$1tEQl=ZN%q&#}y{ z_NNA3VQ#&`K|0-R^CEP0Y<}gtmv%sD;cYrMMa;|z zGS|4_GPBb=pQIfNVH`h+?Q3d327?j%1RY$DrataJc^k|w@AWS4d4^avxl>L6%Z7JF z=NoGP8wRe^M>nm6-ib@pcco^_d_SK^fK5Z(u;nKJ4gWK3T~y!`_u$yj!oWx(IW#dT zjU*<=rEOTyEjy3!@kY5ieW~tA#iUpJRzCa15U)wEC@pcJOD7N4n_6peF0{{ z2SI>;0Uo|);TK6p#6~uISxH~ThaejhY@S&A$RL1}j&bAZwpq)z8V4}0j zpCZTisKTfCQV#sG6?*;-+It5Dze4*TumaWbpiQD$ Y9t=`Zs^#Gz)j`Fntrt3|uruaA0K7#&Hvj+t literal 0 HcmV?d00001 diff --git a/recommondation-engine/example1.py b/recommondation-engine/app.py similarity index 73% rename from recommondation-engine/example1.py rename to recommondation-engine/app.py index 621469d..95da178 100644 --- a/recommondation-engine/example1.py +++ b/recommondation-engine/app.py @@ -4,6 +4,7 @@ import mysql.connector from sklearn.metrics.pairwise import cosine_similarity import numpy as np import logging +from unittest import result def database(): db_connection = mysql.connector.connect( @@ -83,27 +84,57 @@ def get_user_history(user_id): return final -def get_recommendations(user_id, top_n=40): +def get_recommendations(user_id, top_n=10): try: # Get all products and user history with their category vectors all_products = get_all_products() user_history = get_user_history(user_id) - # if not user_history: # # Cold start: return popular products # return get_popular_products(top_n) - # Calculate similarity between all products and user history user_profile = np.mean(user_history, axis=0) # Average user preferences similarities = cosine_similarity([user_profile], all_products) - # finds the indices of the top N products that have the highest # cosine similarity with the user's profile and sorted from most similar to least similar. product_indices = similarities[0].argsort()[-top_n:][::-1] print("product", product_indices) + # Get the recommended product IDs + recommended_products = [all_products[i][0] for i in product_indices] # Product IDs + + # Upload the recommendations to the database + history_upload(user_id, product_indices) # Pass the indices directly to history_upload + # Return recommended product IDs - return [all_products[i][0] for i in product_indices] # Product IDs + return recommended_products except Exception as e: logging.error(f"Recommendation error for user {user_id}: {str(e)}") # return get_popular_products(top_n) # Fallback to popular products + +def history_upload(userID, anrr): + db_con = database() + cursor = db_con.cursor() + + try: + for item in anrr: + #Product ID starts form index 1 + item_value = item + 1 + print(item_value) + # Use parameterized queries to prevent SQL injection + cursor.execute(f"INSERT INTO Recommendation (UserID, RecommendedProductID) VALUES ({userID}, {item_value});") + + # Commit the changes + db_con.commit() + + # If you need results, you'd typically fetch them after a SELECT query + # results = cursor.fetchall() + # print(results) + + except Exception as e: + print(f"Error: {e}") + db_con.rollback() + finally: + # Close the cursor and connection + cursor.close() + db_con.close() diff --git a/recommondation-engine/server.py b/recommondation-engine/server.py index c371a3c..83dbe4d 100644 --- a/recommondation-engine/server.py +++ b/recommondation-engine/server.py @@ -1,6 +1,8 @@ from flask import Flask, request, jsonify from flask_cors import CORS -from example1 import get_recommendations +from app import get_recommendations +from app import history_upload + import time @@ -18,8 +20,7 @@ def handle_session_data(): if not user_id or not email or is_authenticated is None: return jsonify({'error': 'Invalid data'}), 400 - print(get_recommendations(user_id)) - time.sleep(2) + get_recommendations(user_id) print(f"Received session data: User ID: {user_id}, Email: {email}, Authenticated: {is_authenticated}") return jsonify({'message': 'Session data received successfully'})