Compare commits
6 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d6b5e8ff1b | ||
|
|
7ffba4c14c | ||
|
|
c6d98b6d77 | ||
|
|
b4ac53a8d0 | ||
|
|
b7937018e5 | ||
|
|
9d05adacfb |
25
README.md
25
README.md
@@ -1,6 +1,9 @@
|
||||
### Some ground rules
|
||||
1. Add both node_modules from Slient and Server to your ```gitignore``` file
|
||||
2. Make a brach with the following naming conventionp, prefix it with your name ```Your-Name Branch-Name```.
|
||||
### Ground rules
|
||||
|
||||
<img class='fullimage' alt='Fin-Track Application Screenshot' src='./assets/CampusPlug.png'/>
|
||||
|
||||
1. Add both node_modules from Client and Server to your ```gitignore``` file
|
||||
2. Make a brach with the following naming convention, prefix it with your name ```Your-Name Branch-Name```.
|
||||
---
|
||||
|
||||
### Frontend
|
||||
@@ -19,25 +22,19 @@
|
||||
```
|
||||
---
|
||||
|
||||
### Recommendation
|
||||
1. `cd recommendation-engine` into the dir and then type command
|
||||
### Recommendation system
|
||||
1. Install the dependencies `pip install scikit-learn numpy mysql.connector flask flask-cors`
|
||||
2. `cd recommendation-engine` into the dir and then type command
|
||||
```Bash
|
||||
1. python3 server.py #Start The Server
|
||||
```
|
||||
---
|
||||
|
||||
### Recommendation system
|
||||
1. Install the dependencies
|
||||
```Bash
|
||||
pip install mysql.connector
|
||||
```
|
||||
---
|
||||
|
||||
### Database
|
||||
1. MySql Version 9.2.0
|
||||
2. To Create the DataBase use the command bellow:
|
||||
```Bash
|
||||
1. mysql -u root
|
||||
2. \. PathToYour/Schema.sql
|
||||
3. \. PathToYour/Init-Data.sql
|
||||
2. \. PathTo/Schema.sql
|
||||
3. \. PathTo/Init-Data.sql
|
||||
```
|
||||
|
||||
BIN
assets/CampusPlug.png
Executable file
BIN
assets/CampusPlug.png
Executable file
Binary file not shown.
|
After Width: | Height: | Size: 3.7 MiB |
@@ -41,12 +41,7 @@ exports.removeProduct = async (req, res) => {
|
||||
await db.execute(`DELETE FROM Image_URL WHERE ProductID = ?`, [productID]);
|
||||
await db.execute(`DELETE FROM History WHERE ProductID = ?`, [productID]);
|
||||
await db.execute(`DELETE FROM Favorites WHERE ProductID = ?`, [productID]);
|
||||
await db.execute(`DELETE FROM Product_Category WHERE ProductID = ?`, [
|
||||
productID,
|
||||
]);
|
||||
await db.execute(`DELETE FROM Product_Category WHERE ProductID = ?`, [
|
||||
productID,
|
||||
]);
|
||||
|
||||
await db.execute(`DELETE FROM Transaction WHERE ProductID = ?`, [
|
||||
productID,
|
||||
]);
|
||||
|
||||
@@ -96,7 +96,7 @@ exports.getTransactionsByProduct = async (req, res) => {
|
||||
MIN(I.URL) AS Image_URL
|
||||
FROM Transaction T
|
||||
JOIN Product P ON T.ProductID = P.ProductID
|
||||
LEFT JOIN Image_URL I ON P.ProductID = I.ProductID
|
||||
JOIN Image_URL I ON I.ProductID = T.ProductID
|
||||
GROUP BY T.TransactionID, T.UserID, T.ProductID, T.Date, T.PaymentStatus, P.Name`,
|
||||
);
|
||||
|
||||
@@ -123,11 +123,19 @@ exports.getTransactionsByUser = async (req, res) => {
|
||||
T.Date,
|
||||
T.PaymentStatus,
|
||||
P.Name AS ProductName,
|
||||
I.URL AS Image_URL
|
||||
MIN(I.URL) AS Image_URL
|
||||
FROM Transaction T
|
||||
JOIN Product P ON T.ProductID = P.ProductID
|
||||
LEFT JOIN Image_URL I ON P.ProductID = I.ProductID
|
||||
WHERE T.UserID = ?`,
|
||||
JOIN Image_URL I ON I.ProductID = T.ProductID
|
||||
WHERE T.UserID = ?
|
||||
GROUP BY
|
||||
T.TransactionID,
|
||||
T.UserID,
|
||||
T.ProductID,
|
||||
T.Date,
|
||||
T.PaymentStatus,
|
||||
P.Name;
|
||||
`,
|
||||
[userID],
|
||||
);
|
||||
|
||||
@@ -155,7 +163,7 @@ exports.getAllTransactions = async (req, res) => {
|
||||
MIN(I.URL) AS Image_URL
|
||||
FROM Transaction T
|
||||
JOIN Product P ON T.ProductID = P.ProductID
|
||||
LEFT JOIN Image_URL I ON P.ProductID = I.ProductID
|
||||
JOIN Image_URL I ON P.ProductID = I.ProductID
|
||||
GROUP BY T.TransactionID, T.UserID, T.ProductID, T.Date, T.PaymentStatus, P.Name`,
|
||||
);
|
||||
|
||||
@@ -169,34 +177,6 @@ exports.getAllTransactions = async (req, res) => {
|
||||
}
|
||||
};
|
||||
|
||||
// Update the payment status of a transaction
|
||||
exports.updatePaymentStatus = async (req, res) => {
|
||||
const { transactionID, paymentStatus } = req.body;
|
||||
|
||||
try {
|
||||
const [result] = await db.execute(
|
||||
`UPDATE Transaction
|
||||
SET PaymentStatus = ?
|
||||
WHERE TransactionID = ?`,
|
||||
[paymentStatus, transactionID],
|
||||
);
|
||||
|
||||
if (result.affectedRows === 0) {
|
||||
return res
|
||||
.status(404)
|
||||
.json({ success: false, message: "Transaction not found" });
|
||||
}
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
message: "Payment status updated successfully",
|
||||
});
|
||||
} catch (error) {
|
||||
console.error("Error updating payment status:", error);
|
||||
res.status(500).json({ error: "Could not update payment status" });
|
||||
}
|
||||
};
|
||||
|
||||
// Delete a transaction
|
||||
exports.deleteTransaction = async (req, res) => {
|
||||
const { transactionID } = req.body;
|
||||
@@ -223,3 +203,24 @@ exports.deleteTransaction = async (req, res) => {
|
||||
res.status(500).json({ error: "Could not delete transaction" });
|
||||
}
|
||||
};
|
||||
|
||||
exports.updateTransactionStatus = async (req, res) => {
|
||||
const { transactionID } = req.body;
|
||||
|
||||
try {
|
||||
const [result] = await db.execute(
|
||||
`UPDATE Transaction
|
||||
SET PaymentStatus = 'completed'
|
||||
WHERE TransactionID = ?;`,
|
||||
[transactionID],
|
||||
);
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
message: "Transaction updated successfully",
|
||||
});
|
||||
} catch (error) {
|
||||
console.error("Error deleting transaction:", error);
|
||||
res.status(500).json({ error: "Could not delete transaction" });
|
||||
}
|
||||
};
|
||||
|
||||
@@ -5,10 +5,10 @@ const {
|
||||
getTransactionsByProduct,
|
||||
getTransactionsByUser,
|
||||
getAllTransactions,
|
||||
updatePaymentStatus,
|
||||
deleteTransaction,
|
||||
getTransactionWithPagination,
|
||||
removeTransation,
|
||||
updateTransactionStatus,
|
||||
} = require("../controllers/transaction");
|
||||
const router = express.Router();
|
||||
|
||||
@@ -18,24 +18,12 @@ router.use((req, res, next) => {
|
||||
next();
|
||||
});
|
||||
|
||||
// Create a new transaction
|
||||
router.post("/createTransaction", createTransaction);
|
||||
|
||||
// Get all transactions for a specific product
|
||||
router.get("/getTransactionsByProduct/:productID", getTransactionsByProduct);
|
||||
|
||||
// Get all transactions for a specific user
|
||||
router.post("/getTransactionsByUser", getTransactionsByUser);
|
||||
|
||||
// Get all transactions in the system
|
||||
router.post("/updateStatus", updateTransactionStatus);
|
||||
router.post("/getAllTransactions", getAllTransactions);
|
||||
|
||||
// Update payment status on a transaction
|
||||
router.patch("/updatePaymentStatus", updatePaymentStatus);
|
||||
|
||||
// Delete a transaction
|
||||
router.delete("/deleteTransaction", deleteTransaction);
|
||||
|
||||
router.get("/getTransactions", getTransactionWithPagination);
|
||||
router.delete("/:id", removeTransation);
|
||||
|
||||
|
||||
@@ -86,54 +86,9 @@ function App() {
|
||||
|
||||
// Generate product recommendations
|
||||
const generateProductRecommendations = async () => {
|
||||
try {
|
||||
setIsGeneratingRecommendations(true);
|
||||
|
||||
// Add a short delay to simulate calculation time
|
||||
await new Promise((resolve) => setTimeout(resolve, 500));
|
||||
|
||||
console.log("Generating product recommendations for user:", user.ID);
|
||||
|
||||
// Make API call to get recommendations
|
||||
const response = await fetch(
|
||||
"http://localhost:3030/api/recommendations/generate",
|
||||
{
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
body: JSON.stringify({
|
||||
userId: user.ID,
|
||||
}),
|
||||
},
|
||||
);
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error("Failed to generate recommendations");
|
||||
}
|
||||
|
||||
const result = await response.json();
|
||||
|
||||
if (result.success) {
|
||||
console.log(
|
||||
"Recommendations generated successfully:",
|
||||
result.recommendations,
|
||||
);
|
||||
setRecommendations(result.recommendations);
|
||||
|
||||
// Store recommendations in session storage for access across the app
|
||||
sessionStorage.setItem(
|
||||
"userRecommendations",
|
||||
JSON.stringify(result.recommendations),
|
||||
);
|
||||
} else {
|
||||
console.error("Error generating recommendations:", result.message);
|
||||
}
|
||||
} catch (err) {
|
||||
console.error("Error generating product recommendations:", err);
|
||||
} finally {
|
||||
setIsGeneratingRecommendations(false);
|
||||
}
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
@@ -256,7 +211,6 @@ function App() {
|
||||
// Complete signup
|
||||
const completeSignUp = async (userData) => {
|
||||
try {
|
||||
setIsLoading(true);
|
||||
setError("");
|
||||
|
||||
console.log("Completing signup for:", userData.email);
|
||||
|
||||
@@ -1,32 +1,45 @@
|
||||
import { useState, useEffect } from "react";
|
||||
import { Link } from "react-router-dom";
|
||||
import { Calendar, CreditCard, Trash2 } from "lucide-react";
|
||||
import FloatingAlert from "../components/FloatingAlert"; // adjust path if needed
|
||||
|
||||
const Transactions = () => {
|
||||
const [transactions, setTransactions] = useState([]);
|
||||
const [showAlert, setShowAlert] = useState(false);
|
||||
const storedUser = JSON.parse(sessionStorage.getItem("user"));
|
||||
|
||||
function reloadPage() {
|
||||
const docTimestamp = new Date(performance.timing.domLoading).getTime();
|
||||
const now = Date.now();
|
||||
if (now > docTimestamp) {
|
||||
location.reload();
|
||||
}
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
const fetchTransactions = async () => {
|
||||
try {
|
||||
const response = await fetch(
|
||||
"http://localhost:3030/api/transaction/getAllTransactions",
|
||||
"http://localhost:3030/api/transaction/getTransactionsByUser",
|
||||
{
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify({ userID: 1 }), // replace with actual userID
|
||||
body: JSON.stringify({ userID: storedUser.ID }),
|
||||
},
|
||||
);
|
||||
if (!response.ok) throw new Error(`HTTP ${response.status}`);
|
||||
const { transactions: txData } = await response.json();
|
||||
if (!Array.isArray(txData)) return;
|
||||
|
||||
console.log(txData);
|
||||
|
||||
setTransactions(
|
||||
txData.map((tx) => ({
|
||||
id: tx.TransactionID,
|
||||
productId: tx.ProductID,
|
||||
name: tx.ProductName || "Unnamed Product",
|
||||
name: tx.ProductName,
|
||||
price: tx.Price != null ? parseFloat(tx.Price) : null,
|
||||
image: tx.Image_URL || "/default-image.jpg",
|
||||
image: tx.Image_URL,
|
||||
date: tx.Date,
|
||||
status: tx.PaymentStatus,
|
||||
})),
|
||||
@@ -52,6 +65,7 @@ const Transactions = () => {
|
||||
const data = await res.json();
|
||||
if (data.success) {
|
||||
setTransactions((prev) => prev.filter((tx) => tx.id !== id));
|
||||
reloadPage();
|
||||
} else {
|
||||
console.error("Delete failed:", data.message);
|
||||
}
|
||||
@@ -60,6 +74,26 @@ const Transactions = () => {
|
||||
}
|
||||
};
|
||||
|
||||
const updateTransaction = async (id) => {
|
||||
try {
|
||||
const res = await fetch(
|
||||
"http://localhost:3030/api/transaction/updateStatus",
|
||||
{
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify({ transactionID: id }),
|
||||
},
|
||||
);
|
||||
const data = await res.json();
|
||||
if (data) {
|
||||
setShowAlert(true);
|
||||
reloadPage();
|
||||
}
|
||||
} catch (err) {
|
||||
console.error("Error deleting transaction:", err);
|
||||
}
|
||||
};
|
||||
|
||||
const formatDate = (dateString) => {
|
||||
const d = new Date(dateString);
|
||||
return d.toLocaleDateString("en-US", {
|
||||
@@ -71,6 +105,12 @@ const Transactions = () => {
|
||||
|
||||
return (
|
||||
<div className="max-w-6xl mx-auto">
|
||||
{showAlert && (
|
||||
<FloatingAlert
|
||||
message="Status Updated"
|
||||
onClose={() => setShowAlert(false)}
|
||||
/>
|
||||
)}
|
||||
<div className="flex justify-between items-center mb-6">
|
||||
<h1 className="text-2xl font-bold text-gray-800">My Transactions</h1>
|
||||
</div>
|
||||
@@ -99,28 +139,34 @@ const Transactions = () => {
|
||||
key={tx.id}
|
||||
className="relative border-2 border-gray-200 overflow-hidden hover:shadow-md transition-shadow"
|
||||
>
|
||||
{/* Delete Button */}
|
||||
<div className="absolute bottom-2 right-2 flex gap-2 z-10">
|
||||
<button
|
||||
onClick={(e) => {
|
||||
e.preventDefault();
|
||||
updateTransaction(tx.id);
|
||||
}}
|
||||
className="text-emerald-600 hover:text-emerald-700 text-sm font-medium"
|
||||
>
|
||||
Complete
|
||||
</button>
|
||||
<button
|
||||
onClick={(e) => {
|
||||
e.preventDefault();
|
||||
deleteTransaction(tx.id);
|
||||
}}
|
||||
className="absolute bottom-2 right-2 text-red-500 hover:text-red-600 z-10"
|
||||
className="text-red-500 hover:text-red-600"
|
||||
>
|
||||
<Trash2 size={20} />
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<Link to={`/product/${tx.productId}`}>
|
||||
<div className="h-48 bg-gray-200 flex items-center justify-center">
|
||||
{tx.image ? (
|
||||
<img
|
||||
src={tx.image}
|
||||
alt={tx.name}
|
||||
className="w-full h-full object-cover"
|
||||
/>
|
||||
) : (
|
||||
<div className="text-gray-400">No image</div>
|
||||
)}
|
||||
</div>
|
||||
<div className="p-4">
|
||||
<h3 className="text-lg font-semibold text-gray-800">
|
||||
|
||||
@@ -2,8 +2,6 @@
|
||||
SET
|
||||
FOREIGN_KEY_CHECKS = 0;
|
||||
|
||||
TRUNCATE TABLE Product_Category;
|
||||
|
||||
TRUNCATE TABLE Favorites;
|
||||
|
||||
TRUNCATE TABLE History;
|
||||
@@ -344,62 +342,6 @@ VALUES
|
||||
('/Uploads/Bluetooth-Speaker.jpg', 19),
|
||||
('/Uploads/Backpack.jpg', 20);
|
||||
|
||||
-- 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);
|
||||
|
||||
-- Insert History records
|
||||
INSERT INTO
|
||||
History (HistoryID, UserID, ProductID)
|
||||
@@ -428,7 +370,6 @@ VALUES
|
||||
(1, 5), -- User 4 likes Basketball
|
||||
(2, 8);
|
||||
|
||||
-- User 5 likes Mini Fridge
|
||||
-- Insert Transactions
|
||||
INSERT INTO
|
||||
Transaction (
|
||||
|
||||
@@ -105,15 +105,6 @@ CREATE TABLE Favorites (
|
||||
UNIQUE (UserID, ProductID)
|
||||
);
|
||||
|
||||
-- Product-Category Junction Table (Many-to-Many)
|
||||
CREATE TABLE Product_Category (
|
||||
ProductID INT,
|
||||
CategoryID INT,
|
||||
PRIMARY KEY (ProductID, CategoryID),
|
||||
FOREIGN KEY (ProductID) REFERENCES Product (ProductID) ON DELETE CASCADE,
|
||||
FOREIGN KEY (CategoryID) REFERENCES Category (CategoryID) ON DELETE CASCADE
|
||||
);
|
||||
|
||||
-- Login Authentication table
|
||||
CREATE TABLE AuthVerification (
|
||||
UserID INT AUTO_INCREMENT PRIMARY KEY,
|
||||
|
||||
Binary file not shown.
@@ -19,9 +19,9 @@ def delete_user_recommendations(user_id):
|
||||
cursor = db_con.cursor()
|
||||
|
||||
try:
|
||||
cursor.execute("DELETE FROM Recommendation WHERE UserID = %s", (user_id,))
|
||||
db_con.commit()
|
||||
print(f"Deleted existing recommendations for user {user_id}")
|
||||
cursor.execute(f"DELETE FROM Recommendation WHERE UserID = {user_id}")
|
||||
db_con.commit()
|
||||
logging.info(f"Deleted existing recommendations for user {user_id}")
|
||||
return True
|
||||
except Exception as e:
|
||||
@@ -32,14 +32,12 @@ def delete_user_recommendations(user_id):
|
||||
cursor.close()
|
||||
db_con.close()
|
||||
|
||||
def get_random_products(count=10, exclude_list=None):
|
||||
"""Get random products from the database, excluding any in the exclude_list"""
|
||||
def get_random_products(count=0, exclude_list=None):
|
||||
db_con = database()
|
||||
cursor = db_con.cursor()
|
||||
|
||||
try:
|
||||
if exclude_list and len(exclude_list) > 0:
|
||||
# Convert exclude_list to string for SQL IN clause
|
||||
exclude_str = ', '.join(map(str, exclude_list))
|
||||
cursor.execute(f"SELECT ProductID FROM Product WHERE ProductID NOT IN ({exclude_str}) ORDER BY RAND() LIMIT {count}")
|
||||
else:
|
||||
@@ -55,13 +53,11 @@ def get_random_products(count=10, exclude_list=None):
|
||||
cursor.close()
|
||||
db_con.close()
|
||||
|
||||
def get_popular_products(count=10):
|
||||
"""Get popular products based on history table frequency"""
|
||||
def get_popular_products(count=5):
|
||||
db_con = database()
|
||||
cursor = db_con.cursor()
|
||||
|
||||
try:
|
||||
# Get products that appear most frequently in history
|
||||
cursor.execute("""
|
||||
SELECT ProductID, COUNT(*) as count
|
||||
FROM History
|
||||
@@ -72,7 +68,6 @@ def get_popular_products(count=10):
|
||||
|
||||
popular_products = [row[0] for row in cursor.fetchall()]
|
||||
|
||||
# If not enough popular products, supplement with random ones
|
||||
if len(popular_products) < count:
|
||||
random_products = get_random_products(count - len(popular_products), popular_products)
|
||||
popular_products.extend(random_products)
|
||||
@@ -81,23 +76,20 @@ def get_popular_products(count=10):
|
||||
|
||||
except Exception as e:
|
||||
logging.error(f"Error getting popular products: {str(e)}")
|
||||
return get_random_products(count) # Fallback to random products
|
||||
return get_random_products(count)
|
||||
finally:
|
||||
cursor.close()
|
||||
db_con.close()
|
||||
|
||||
def has_user_history_or_recommendations(user_id):
|
||||
"""Check if user exists in History or Recommendation table"""
|
||||
db_con = database()
|
||||
cursor = db_con.cursor()
|
||||
|
||||
try:
|
||||
# Check if user has history
|
||||
cursor.execute("SELECT COUNT(*) FROM History WHERE UserID = %s", (user_id,))
|
||||
cursor.execute(f"SELECT COUNT(*) FROM History WHERE UserID = {user_id}" )
|
||||
history_count = cursor.fetchone()[0]
|
||||
|
||||
# Check if user has recommendations
|
||||
cursor.execute("SELECT COUNT(*) FROM Recommendation WHERE UserID = %s", (user_id,))
|
||||
cursor.execute(f"SELECT COUNT(*) FROM Recommendation WHERE UserID = {user_id}")
|
||||
recommendation_count = cursor.fetchone()[0]
|
||||
|
||||
return history_count > 0 or recommendation_count > 0
|
||||
@@ -120,13 +112,11 @@ def get_all_products():
|
||||
select_clause = "SELECT p.ProductID"
|
||||
for category in categories:
|
||||
category_id = category[0]
|
||||
select_clause += f", MAX(CASE WHEN pc.CategoryID = {category_id} THEN 1 ELSE 0 END) AS `Cat_{category_id}`"
|
||||
select_clause += f", MAX(CASE WHEN p.CategoryID = {category_id} THEN 1 ELSE 0 END) AS `Cat_{category_id}`"
|
||||
|
||||
final_query = f"""
|
||||
{select_clause}
|
||||
FROM Product p
|
||||
LEFT JOIN Product_Category pc ON p.ProductID = pc.ProductID
|
||||
LEFT JOIN Category c ON pc.CategoryID = c.CategoryID
|
||||
GROUP BY p.ProductID;
|
||||
"""
|
||||
|
||||
@@ -137,13 +127,13 @@ def get_all_products():
|
||||
product_ids = []
|
||||
for row in results:
|
||||
text_list = list(row)
|
||||
product_id = text_list.pop(0) # Save the product ID before removing it
|
||||
product_id = text_list.pop(0)
|
||||
final.append(text_list)
|
||||
product_ids.append(product_id)
|
||||
|
||||
cursor.close()
|
||||
db_con.close()
|
||||
return final, product_ids # Return both feature vectors and product IDs
|
||||
return final, product_ids
|
||||
except Exception as e:
|
||||
logging.error(f"Error getting all products: {str(e)}")
|
||||
cursor.close()
|
||||
@@ -160,15 +150,13 @@ def get_user_history(user_id):
|
||||
|
||||
select_clause = "SELECT p.ProductID"
|
||||
for category in categories:
|
||||
category_id = category[0] # get the uid of the category and then append that to the new column
|
||||
select_clause += f", MAX(CASE WHEN pc.CategoryID = {category_id} THEN 1 ELSE 0 END) AS `Cat_{category_id}`"
|
||||
category_id = category[0]
|
||||
select_clause += f", MAX(CASE WHEN p.CategoryID = {category_id} THEN 1 ELSE 0 END) AS `Cat_{category_id}`"
|
||||
|
||||
final_query = f"""
|
||||
{select_clause}
|
||||
FROM Product p
|
||||
LEFT JOIN Product_Category pc ON p.ProductID = pc.ProductID
|
||||
LEFT JOIN Category c ON pc.CategoryID = c.CategoryID
|
||||
where p.ProductID in (select ProductID from History where UserID = {user_id})
|
||||
WHERE p.ProductID IN (SELECT ProductID FROM History WHERE UserID = {user_id})
|
||||
GROUP BY p.ProductID;
|
||||
"""
|
||||
|
||||
@@ -191,82 +179,63 @@ def get_user_history(user_id):
|
||||
|
||||
def get_recommendations(user_id, top_n=5):
|
||||
try:
|
||||
# Always delete existing recommendations first
|
||||
delete_user_recommendations(user_id)
|
||||
|
||||
# Check if user has history or recommendations
|
||||
if not has_user_history_or_recommendations(user_id):
|
||||
# Cold start: return random products
|
||||
random_recs = get_random_products(top_n)
|
||||
# Store these random recommendations
|
||||
history_upload(user_id, random_recs)
|
||||
recommendation_upload(user_id, random_recs)
|
||||
|
||||
# Add 5 more unique random products
|
||||
additional_random = get_random_products(5, random_recs)
|
||||
history_upload(user_id, additional_random)
|
||||
recommendation_upload(user_id, additional_random)
|
||||
|
||||
return random_recs + additional_random
|
||||
|
||||
# Get all products and user history with their category vectors
|
||||
all_product_features, all_product_ids = get_all_products()
|
||||
user_history = get_user_history(user_id)
|
||||
|
||||
if not user_history:
|
||||
# User exists but has no history yet
|
||||
popular_recs = get_popular_products(top_n)
|
||||
history_upload(user_id, popular_recs)
|
||||
recommendation_upload(user_id, popular_recs)
|
||||
|
||||
# Add 5 more unique random products
|
||||
additional_random = get_random_products(5, popular_recs)
|
||||
history_upload(user_id, additional_random)
|
||||
recommendation_upload(user_id, additional_random)
|
||||
|
||||
return popular_recs + additional_random
|
||||
|
||||
# Calculate similarity between all products and user history
|
||||
user_profile = np.mean(user_history, axis=0) # Average user preferences
|
||||
user_profile = np.mean(user_history, axis=0)
|
||||
similarities = cosine_similarity([user_profile], all_product_features)
|
||||
print(similarities)
|
||||
|
||||
# Get indices of the top N products sorted by similarity
|
||||
product_indices = similarities[0].argsort()[-top_n:][::-1]
|
||||
|
||||
# Get the actual product IDs using the indices
|
||||
recommended_product_ids = [all_product_ids[i] for i in product_indices]
|
||||
print(recommended_product_ids)
|
||||
|
||||
# Upload the core recommendations to the database
|
||||
history_upload(user_id, recommended_product_ids)
|
||||
recommendation_upload(user_id, recommended_product_ids)
|
||||
|
||||
# Add 5 more unique random products that aren't in the recommendations
|
||||
additional_random = get_random_products(5, recommended_product_ids)
|
||||
history_upload(user_id, additional_random)
|
||||
recommendation_upload(user_id, additional_random)
|
||||
|
||||
# Return both the similarity-based recommendations and the random ones
|
||||
return recommended_product_ids + additional_random
|
||||
|
||||
except Exception as e:
|
||||
logging.error(f"Recommendation error for user {user_id}: {str(e)}")
|
||||
# Fallback to random products
|
||||
random_products = get_random_products(top_n + 5)
|
||||
return random_products
|
||||
|
||||
def history_upload(userID, products):
|
||||
"""Upload product recommendations to the database"""
|
||||
def recommendation_upload(userID, products):
|
||||
db_con = database()
|
||||
cursor = db_con.cursor()
|
||||
|
||||
try:
|
||||
for product_id in products:
|
||||
# Use parameterized queries to prevent SQL injection
|
||||
cursor.execute("INSERT INTO Recommendation (UserID, RecommendedProductID) VALUES (%s, %s)",
|
||||
(userID, product_id))
|
||||
|
||||
# Commit the changes
|
||||
db_con.commit()
|
||||
|
||||
except Exception as e:
|
||||
logging.error(f"Error uploading recommendations: {str(e)}")
|
||||
db_con.rollback()
|
||||
finally:
|
||||
# Close the cursor and connection
|
||||
cursor.close()
|
||||
db_con.close()
|
||||
|
||||
Reference in New Issue
Block a user