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) => (
-
- 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 */}
-
-
-
-
-
-
-
-
-
-
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'})