add review and read review now done

This commit is contained in:
Mann Patel
2025-04-12 13:10:17 -06:00
parent 0e32389482
commit 0f8bb622a4
16 changed files with 408 additions and 196 deletions

View File

@@ -37,7 +37,6 @@ exports.HistoryByUserId = async (req, res) => {
[id],
);
console.log(data);
res.json({
success: true,
message: "Products fetched successfully",

View File

@@ -1,12 +1,12 @@
const db = require("../utils/database");
exports.addToFavorite = async (req, res) => {
exports.addFavorite = async (req, res) => {
const { userID, productsID } = req.body;
try {
// Use parameterized query to prevent SQL injection
const [result] = await db.execute(
"INSERT INTO Favorites (UserID, ProductID) VALUES unique(?, ?)",
"INSERT INTO Favorites (UserID, ProductID) VALUES (?, ?)",
[userID, productsID],
);
@@ -14,13 +14,41 @@ exports.addToFavorite = async (req, res) => {
success: true,
message: "Product added to favorites successfully",
});
console.log(result);
} catch (error) {
console.error("Error adding favorite product:", error);
return res.json({ error: "Could not add favorite product" });
}
};
exports.getFavorites = async (req, res) => {
const { userID } = req.body;
try {
const [favorites] = await db.execute(
`
SELECT
p.*,
u.Name AS SellerName,
i.URL AS image_url
FROM Favorites f
JOIN Product p ON f.ProductID = p.ProductID
JOIN User u ON p.UserID = u.UserID
LEFT JOIN Image_URL i ON p.ProductID = i.ProductID
WHERE f.UserID = ?
`,
[userID],
);
res.json({
success: true,
favorites: favorites,
});
} catch (error) {
console.error("Error retrieving favorites:", error);
res.status(500).json({ error: "Could not retrieve favorite products" });
}
};
// Get all products along with their image URLs
exports.getAllProducts = async (req, res) => {
try {
@@ -52,7 +80,6 @@ exports.getAllProducts = async (req, res) => {
WHERE RowNum = 1;
`);
console.log(data);
res.json({
success: true,
message: "Products fetched successfully",
@@ -74,7 +101,7 @@ exports.getProductById = async (req, res) => {
try {
const [data] = await db.execute(
`
SELECT p.*,U.Name AS SellerName, i.URL AS image_url
SELECT p.*,U.Name AS SellerName,U.Email as SellerEmail,U.Phone as SellerPhone, 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

View File

@@ -1,13 +1,17 @@
const db = require("../utils/database");
exports.getreview = async (req, res) => {
/**
* Get reviews for a specific product
* Returns both reviews for the product and reviews by the product owner for other products
*/
exports.getReviews = async (req, res) => {
const { id } = req.params;
console.log("Received Product ID:", id);
try {
const [data] = await db.execute(
`
SELECT
// First query: Get reviews for this specific product
const [productReviews] = await db.execute(
`SELECT
R.ReviewID,
R.UserID,
R.ProductID,
@@ -15,44 +19,49 @@ exports.getreview = async (req, res) => {
R.Rating,
R.Date AS ReviewDate,
U.Name AS ReviewerName,
P.Name AS ProductName
P.Name AS ProductName,
'product' AS ReviewType
FROM Review R
JOIN User U ON R.UserID = U.UserID
JOIN Product P ON R.ProductID = P.ProductID
WHERE R.ProductID = ?
UNION
SELECT
R.ReviewID,
R.UserID,
R.ProductID,
R.Comment,
R.Rating,
R.Date AS ReviewDate,
U.Name AS ReviewerName,
P.Name AS ProductName
FROM Review R
JOIN User U ON R.UserID = U.UserID
JOIN Product P ON R.ProductID = P.ProductID
WHERE P.UserID = (
SELECT UserID
FROM Product
WHERE ProductID = ?
)
AND R.UserID != P.UserID;
`,
[id, id],
WHERE R.ProductID = ?`,
[id],
);
// Log raw data for debugging
console.log("Raw Database Result:", data);
// // Second query: Get reviews written by the product owner for other products
// const [sellerReviews] = await db.execute(
// `SELECT
// R.ReviewID,
// R.UserID,
// R.ProductID,
// R.Comment,
// R.Rating,
// R.Date AS ReviewDate,
// U.Name AS ReviewerName,
// P.Name AS ProductName,
// 'seller' AS ReviewType
// FROM Review R
// JOIN User U ON R.UserID = U.UserID
// JOIN Product P ON R.ProductID = P.ProductID
// WHERE R.UserID = (
// SELECT UserID
// FROM Product
// WHERE ProductID = ?
// )
// AND R.ProductID != ?`,
// [id, id],
// );
// Combine the results
const combinedReviews = [...productReviews];
// Log data for debugging
console.log("Combined Reviews:", combinedReviews);
console.log(data);
res.json({
success: true,
message: "Products fetched successfully",
data,
message: "Reviews fetched successfully",
data: combinedReviews,
});
} catch (error) {
console.error("Full Error Details:", error);
@@ -64,7 +73,9 @@ exports.getreview = async (req, res) => {
}
};
// Add this to your existing controller file
/**
* Submit a new review for a product
*/
exports.submitReview = async (req, res) => {
const { productId, userId, rating, comment } = req.body;
@@ -85,16 +96,41 @@ exports.submitReview = async (req, res) => {
}
try {
// Check if user has already reviewed this product
const [existingReview] = await db.execute(
`SELECT ReviewID FROM Review WHERE ProductID = ? AND UserID = ?`,
[productId, userId],
);
if (existingReview.length > 0) {
return res.status(400).json({
success: false,
message: "You have already reviewed this product",
});
}
// Check if user is trying to review their own product
const [productOwner] = await db.execute(
`SELECT UserID FROM Product WHERE ProductID = ?`,
[productId],
);
if (productOwner.length > 0 && productOwner[0].UserID === userId) {
return res.status(400).json({
success: false,
message: "You cannot review your own product",
});
}
// Insert the review into the database
const [result] = await db.execute(
`
INSERT INTO Review (
`INSERT INTO Review (
ProductID,
UserID,
Rating,
Comment
) VALUES (?, ?, ?, ?)
`,
Comment,
Date
) VALUES (?, ?, ?, ?, NOW())`,
[productId, userId, rating, comment],
);
@@ -103,22 +139,24 @@ exports.submitReview = async (req, res) => {
// Fetch the newly created review to return to client
const [newReview] = await db.execute(
`
SELECT
ReviewID as id,
ProductID,
UserID,
Rating,
Comment,
Date as ReviewDate
FROM Review
WHERE ReviewID = ?
`,
`SELECT
R.ReviewID,
R.ProductID,
R.UserID,
R.Rating,
R.Comment,
R.Date AS ReviewDate,
U.Name AS ReviewerName,
P.Name AS ProductName
FROM Review R
JOIN User U ON R.UserID = U.UserID
JOIN Product P ON R.ProductID = P.ProductID
WHERE R.ReviewID = ?`,
[reviewId],
);
res.status(201).json({
success: false,
success: true, // Fixed from false to true
message: "Review submitted successfully",
data: newReview[0],
});
@@ -131,3 +169,134 @@ exports.submitReview = async (req, res) => {
});
}
};
// /**
// * Update an existing review
// */
// exports.updateReview = async (req, res) => {
// const { reviewId } = req.params;
// const { rating, comment } = req.body;
// const userId = req.body.userId; // Assuming you have middleware that validates the user
// // Validate required fields
// if (!reviewId || !rating || !comment) {
// return res.status(400).json({
// success: false,
// message: "Missing required fields",
// });
// }
// // Validate rating is between 1 and 5
// if (rating < 1 || rating > 5) {
// return res.status(400).json({
// success: false,
// message: "Rating must be between 1 and 5",
// });
// }
// try {
// // Check if review exists and belongs to the user
// const [existingReview] = await db.execute(
// `SELECT ReviewID, UserID FROM Review WHERE ReviewID = ?`,
// [reviewId],
// );
// if (existingReview.length === 0) {
// return res.status(404).json({
// success: false,
// message: "Review not found",
// });
// }
// if (existingReview[0].UserID !== userId) {
// return res.status(403).json({
// success: false,
// message: "You can only update your own reviews",
// });
// }
// // Update the review
// await db.execute(
// `UPDATE Review
// SET Rating = ?, Comment = ?, Date = NOW()
// WHERE ReviewID = ?`,
// [rating, comment, reviewId],
// );
// // Fetch the updated review
// const [updatedReview] = await db.execute(
// `SELECT
// R.ReviewID,
// R.ProductID,
// R.UserID,
// R.Rating,
// R.Comment,
// R.Date AS ReviewDate,
// U.Name AS ReviewerName,
// P.Name AS ProductName
// FROM Review R
// JOIN User U ON R.UserID = U.UserID
// JOIN Product P ON R.ProductID = P.ProductID
// WHERE R.ReviewID = ?`,
// [reviewId],
// );
// res.json({
// success: true,
// message: "Review updated successfully",
// data: updatedReview[0],
// });
// } catch (error) {
// console.error("Error updating review:", error);
// return res.status(500).json({
// success: false,
// message: "Database error occurred",
// error: error.message,
// });
// }
// };
// /**
// * Delete a review
// */
// exports.deleteReview = async (req, res) => {
// const { reviewId } = req.params;
// const userId = req.body.userId; // Assuming you have middleware that validates the user
// try {
// // Check if review exists and belongs to the user
// const [existingReview] = await db.execute(
// `SELECT ReviewID, UserID FROM Review WHERE ReviewID = ?`,
// [reviewId],
// );
// if (existingReview.length === 0) {
// return res.status(404).json({
// success: false,
// message: "Review not found",
// });
// }
// if (existingReview[0].UserID !== userId) {
// return res.status(403).json({
// success: false,
// message: "You can only delete your own reviews",
// });
// }
// // Delete the review
// await db.execute(`DELETE FROM Review WHERE ReviewID = ?`, [reviewId]);
// res.json({
// success: true,
// message: "Review deleted successfully",
// });
// } catch (error) {
// console.error("Error deleting review:", error);
// return res.status(500).json({
// success: false,
// message: "Database error occurred",
// error: error.message,
// });
// }
// };

View File

@@ -39,9 +39,9 @@ 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
app.use("/api/Engine", recommendedRouter); //prefix with /api/
app.use("/api/get", history); //prefix with /api/
app.use("/api/search", searchRouter); //prefix with /api/product
app.use("/api/engine", recommendedRouter); //prefix with /api/
app.use("/api/history", history); //prefix with /api/
app.use("/api/review", review); //prefix with /api/
// Set up a scheduler to run cleanup every hour

View File

@@ -3,6 +3,6 @@ const express = require("express");
const { HistoryByUserId } = require("../controllers/history");
const router = express.Router();
router.post("/history", HistoryByUserId);
router.post("/getHistory", HistoryByUserId);
module.exports = router;

View File

@@ -1,7 +1,8 @@
// routes/product.js
const express = require("express");
const {
addToFavorite,
addFavorite,
getFavorites,
getAllProducts,
getProductById,
} = require("../controllers/product");
@@ -13,8 +14,10 @@ router.use((req, res, next) => {
next();
});
router.post("/add_fav_product", addToFavorite);
router.get("/get_product", getAllProducts);
router.post("/addFavorite", addFavorite);
router.post("/getFavorites", getFavorites);
router.get("/getProduct", getAllProducts);
router.get("/:id", getProductById); // Simplified route
module.exports = router;

View File

@@ -1,9 +1,9 @@
// routes/product.js
const express = require("express");
const { getreview, submitReview } = require("../controllers/review");
const { getReviews, submitReview } = require("../controllers/review");
const router = express.Router();
router.get("/:id", getreview);
router.get("/:id", getReviews);
router.post("/add", submitReview);
module.exports = router;

View File

@@ -9,6 +9,6 @@ router.use((req, res, next) => {
next();
});
router.get("/search", searchProductsByName);
router.get("/getProduct", searchProductsByName);
module.exports = router;

View File

@@ -11,9 +11,8 @@ const Home = () => {
const storedUser = JSON.parse(sessionStorage.getItem("user"));
const handleLinkClick = async (id) => {
// Example: append to localStorage or call analytics
const response = await fetch(
"http://localhost:3030/api/product/add_fav_product",
"http://localhost:3030/api/product/addFavorites",
{
method: "POST",
headers: {
@@ -32,6 +31,16 @@ const Home = () => {
console.log(`Add Product -> History: ${id}`);
};
function reloadPage() {
var doctTimestamp = new Date(performance.timing.domLoading).getTime();
var now = Date.now();
var tenSec = 10 * 1000;
if (now > doctTimestamp + tenSec) {
location.reload();
}
}
reloadPage();
useEffect(() => {
const fetchrecomProducts = async () => {
// Get the user's data from localStorage
@@ -76,13 +85,15 @@ const Home = () => {
}
};
fetchrecomProducts();
//reloadPage();
}, []);
reloadPage();
useEffect(() => {
const fetchProducts = async () => {
try {
const response = await fetch(
"http://localhost:3030/api/product/get_product",
"http://localhost:3030/api/product/getProduct",
);
if (!response.ok) throw new Error("Failed to fetch products");
@@ -118,7 +129,9 @@ const Home = () => {
const storedUser = JSON.parse(sessionStorage.getItem("user"));
console.log(storedUser);
try {
const response = await fetch("http://localhost:3030/api/get/history", {
const response = await fetch(
"http://localhost:3030/api/history/getHistory",
{
method: "POST",
headers: {
"Content-Type": "application/json",
@@ -126,7 +139,8 @@ const Home = () => {
body: JSON.stringify({
id: storedUser.ID,
}),
});
},
);
if (!response.ok) throw new Error("Failed to fetch products");
const data = await response.json();

View File

@@ -1,6 +1,15 @@
import { useState, useEffect, setErrors } from "react";
import { useParams, Link, isSession } from "react-router-dom";
import { Heart, ArrowLeft, Tag, User, Calendar, Star } from "lucide-react";
import { useState, useEffect } from "react";
import { useParams, Link } from "react-router-dom";
import {
Heart,
ArrowLeft,
Tag,
User,
Calendar,
Star,
Phone,
Mail,
} from "lucide-react";
const ProductDetail = () => {
const { id } = useParams();
@@ -8,16 +17,19 @@ const ProductDetail = () => {
const [loading, setLoading] = useState({
product: true,
reviews: true,
submitting: false,
});
const [error, setError] = useState({
product: null,
reviews: null,
submit: null,
});
const [isFavorite, setIsFavorite] = useState(false);
const [contactForm, showContactForm, setShowContactForm] = useState(false);
const [showContactOptions, setShowContactOptions] = useState(false);
const [currentImage, setCurrentImage] = useState(0);
const [reviews, setReviews] = useState([]);
const [showReviewForm, setShowReviewForm] = useState(false);
const storedUser = JSON.parse(sessionStorage.getItem("user"));
const [reviewForm, setReviewForm] = useState({
rating: 3,
@@ -42,39 +54,70 @@ const ProductDetail = () => {
}));
};
const handleSubmitReview = async () => {
try {
// Ensure userId is present
if (!userData.userId) {
throw new Error("User ID is missing. Unable to update profile.");
}
const handleSubmitReview = async (e) => {
e.preventDefault(); // Prevent form default behavior
setIsLoading(true);
setError(null);
try {
setLoading((prev) => ({ ...prev, submitting: true }));
setError((prev) => ({ ...prev, submit: null }));
const reviewData = {
productId: id,
rating: reviewForm.rating,
comment: reviewForm.comment,
userId: storedUser.ID,
};
const response = await fetch(`http://localhost:3030/api/review/add`, {
method: "POST", // or "PUT" if your backend supports it
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify(userData),
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify(reviewData),
});
const result = await response.json();
if (!response.ok) {
throw new Error(result.error || "Failed to update profile");
// Check if API returned an error message even with 200 status
if (!result.success) {
throw new Error(result.message || "Failed to submit review");
}
console.log("Profile updated successfully:", result);
alert("Profile updated successfully!");
} catch (error) {
console.error("Error updating profile:", error);
setError(
error.message || "An error occurred while updating your profile.",
alert("Review submitted successfully!");
setReviewForm({
rating: 3,
comment: "",
name: "",
});
setShowReviewForm(false);
try {
setLoading((prev) => ({ ...prev, reviews: true }));
const reviewsResponse = await fetch(
`http://localhost:3030/api/review/${id}`,
);
const reviewsResult = await reviewsResponse.json();
if (reviewsResult.success) {
setReviews(reviewsResult.data || []);
setError((prev) => ({ ...prev, reviews: null }));
} else {
throw new Error(reviewsResult.message || "Error fetching reviews");
}
} catch (reviewsError) {
console.error("Error fetching reviews:", reviewsError);
setError((prev) => ({ ...prev, reviews: reviewsError.message }));
} finally {
setIsLoading(false);
setLoading((prev) => ({ ...prev, reviews: false }));
}
} catch (error) {
console.error("Error submitting review:", error);
alert(`Error: ${error.message}`);
setError((prev) => ({
...prev,
submit: error.message,
}));
} finally {
setLoading((prev) => ({ ...prev, submitting: false }));
}
};
@@ -172,48 +215,6 @@ const ProductDetail = () => {
}
};
// Handle form input changes
const handleContactInputChange = (e) => {
const { id, value } = e.target;
setContactForm((prev) => ({
...prev,
[id]: value,
}));
};
// Handle message submission with improved validation
const handleSendMessage = (e) => {
e.preventDefault();
// Basic validation
if (!contactForm.email || !contactForm.phone) {
alert("Please fill in all required fields");
return;
}
// Email validation
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
if (!emailRegex.test(contactForm.email)) {
alert("Please enter a valid email address");
return;
}
// TODO: Implement actual message sending logic
try {
// Mock API call
console.log("Message sent:", contactForm);
setContactForm({
email: "",
phone: "",
message: "Hi, is this item still available?",
});
setShowContactForm(false);
alert("Message sent to seller!");
} catch (error) {
alert(`Failed to send message: ${error.message}`);
}
};
// Image navigation
const nextImage = () => {
if (product?.images?.length > 0) {
@@ -417,12 +418,38 @@ const ProductDetail = () => {
</p>
</div>
{/* <button
onClick={() => setShowContactForm(!showContactForm)}
<div className="relative">
<button
onClick={() => setShowContactOptions(!showContactOptions)}
className="w-full bg-green-500 hover:bg-green-600 text-white font-medium py-3 px-4 mb-3"
>
Contact Seller
</button> */}
</button>
{showContactOptions && (
<div className="absolute z-10 w-full bg-white border border-gray-200 shadow-md">
{product.SellerPhone && (
<a
href={`tel:${product.SellerPhone}`}
className="flex items-center gap-2 p-3 hover:bg-gray-50 border-b border-gray-100"
>
<Phone className="h-5 w-5 text-green-500" />
<span>Call Seller</span>
</a>
)}
{product.SellerEmail && (
<a
href={`mailto:${product.SellerEmail}`}
className="flex items-center gap-2 p-3 hover:bg-gray-50"
>
<Mail className="h-5 w-5 text-green-500" />
<span>Email Seller</span>
</a>
)}
</div>
)}
</div>
<div className="pt-4 border-t border-gray-200">
{/* Seller Info */}
@@ -441,29 +468,6 @@ const ProductDetail = () => {
</p>
</div>
</div>
{/* Contact Options */}
{showContactForm && (
<div className="mt-4 border border-gray-200 p-4">
<h3 className="font-medium text-gray-800 mb-2">
Contact Seller
</h3>
<div className="flex flex-col sm:flex-row gap-3">
<a
href={`tel:${contactForm.phone}`}
className="bg-green-500 hover:bg-green-600 text-white font-medium py-2 px-4 text-center "
>
Call Seller
</a>
<a
href={`mailto:${contactForm.email}`}
className="bg-green-500 hover:bg-green-600 text-white font-medium py-2 px-4 text-center "
>
Email Seller
</a>
</div>
</div>
)}
</div>
</div>
</div>
@@ -495,7 +499,7 @@ const ProductDetail = () => {
>
<div className="flex justify-between mb-2">
<div className="font-medium text-gray-800">
{review.ReviewerName || "Anonymous"}
{review.ReviewerName}
</div>
<div className="text-sm text-gray-500">
{review.ReviewDate
@@ -546,20 +550,6 @@ const ProductDetail = () => {
</div>
<form onSubmit={handleSubmitReview}>
<div className="mb-4">
<label htmlFor="name" className="block text-gray-700 mb-1">
Your Name <span className="text-red-500">*</span>
</label>
<input
type="text"
id="name"
value={reviewForm.name}
onChange={handleReviewInputChange}
className="w-full p-3 border border-gray-300 rounded focus:outline-none focus:border-green-500"
required
/>
</div>
<div className="mb-4">
<label className="block text-gray-700 mb-1">
Rating <span className="text-red-500">*</span>
@@ -612,8 +602,9 @@ const ProductDetail = () => {
<button
type="submit"
className="px-4 py-2 bg-green-500 text-white rounded hover:bg-green-600"
disabled={loading.submitting}
>
Submit Review
{loading.submitting ? "Submitting..." : "Submit Review"}
</button>
</div>
</form>

View File

@@ -27,7 +27,7 @@ const SearchPage = () => {
try {
const response = await axios.get(
`http://localhost:3030/api/search_products/search`,
`http://localhost:3030/api/search/getProduct`,
{
params: { name: query },
},

View File

@@ -101,7 +101,8 @@ CREATE TABLE Favorites (
UserID INT,
ProductID INT,
FOREIGN KEY (UserID) REFERENCES User (UserID),
FOREIGN KEY (ProductID) REFERENCES Product (ProductID)
FOREIGN KEY (ProductID) REFERENCES Product (ProductID),
UNIQUE (UserID, ProductID) -- Prevents duplicate favorites
);
-- Product-Category Junction Table (Many-to-Many)

6
package-lock.json generated Normal file
View File

@@ -0,0 +1,6 @@
{
"name": "Campus-Plug",
"lockfileVersion": 3,
"requires": true,
"packages": {}
}

View File

@@ -108,6 +108,10 @@ def get_user_history(user_id):
return final
def delete_user_recommendations(userId):
pass
def get_recommendations(user_id, top_n=10):
try:
# Get all products and user history with their category vectors

View File

@@ -6,8 +6,6 @@ from flask import Flask, request, jsonify
from flask_cors import CORS
from app import get_recommendations
#import time
app = Flask(__name__)
CORS(app) # Enable CORS for all routes