diff --git a/backend/controllers/history.js b/backend/controllers/history.js new file mode 100644 index 0000000..f2af282 --- /dev/null +++ b/backend/controllers/history.js @@ -0,0 +1,53 @@ +const db = require("../utils/database"); + +// TODO: Get the recommondaed product given the userID +exports.HistoryByUserId = async (req, res) => { + const { id } = req.body; + try { + const [data] = await db.execute( + ` + WITH RankedImages AS ( + 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, + ROW_NUMBER() OVER (PARTITION BY P.ProductID ORDER BY I.URL) AS RowNum + 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 History H ON H.ProductID = P.ProductID + WHERE U.UserID = ? + ) + SELECT + ProductID, + ProductName, + Price, + DateUploaded, + SellerName, + ProductImage, + Category + FROM RankedImages + WHERE RowNum = 1; + `, + [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/controllers/product.js b/backend/controllers/product.js index 1fcf06b..c8f9885 100644 --- a/backend/controllers/product.js +++ b/backend/controllers/product.js @@ -24,18 +24,31 @@ exports.addToFavorite = async (req, res) => { exports.getAllProducts = async (req, res) => { try { const [data, fields] = await db.execute(` + WITH RankedImages AS ( + 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, + ROW_NUMBER() OVER (PARTITION BY P.ProductID ORDER BY I.URL) AS RowNum + 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 + ) 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; + ProductID, + ProductName, + Price, + DateUploaded, + SellerName, + ProductImage, + Category + FROM RankedImages + WHERE RowNum = 1; `); console.log(data); diff --git a/backend/controllers/recommendation.js b/backend/controllers/recommendation.js index 488b089..5518d46 100644 --- a/backend/controllers/recommendation.js +++ b/backend/controllers/recommendation.js @@ -6,20 +6,34 @@ exports.RecommondationByUserId = async (req, res) => { try { const [data, fields] = await db.execute( ` + WITH RankedImages AS ( + 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, + ROW_NUMBER() OVER (PARTITION BY P.ProductID ORDER BY I.URL) AS RowNum + 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 = ? + ) 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 = ?;`, + ProductID, + ProductName, + Price, + DateUploaded, + SellerName, + ProductImage, + Category + FROM RankedImages + WHERE RowNum = 1; + `, [id], ); @@ -37,3 +51,64 @@ exports.RecommondationByUserId = async (req, res) => { }); } }; + +// Add this to your existing controller file +exports.submitReview = async (req, res) => { + const { productId, reviewerName, rating, comment } = req.body; + + // Validate required fields + if (!productId || !reviewerName || !rating || !comment) { + return res.status(400).json({ + success: false, + message: "Missing required fields", + }); + } + + try { + // Insert the review into the database + const [result] = await db.execute( + ` + INSERT INTO Review ( + ProductID, + ReviewerName, + Rating, + Comment, + ReviewDate + ) VALUES (?, ?, ?, ?, NOW()) + `, + [productId, reviewerName, rating, comment], + ); + + // Get the inserted review id + const reviewId = result.insertId; + + // Fetch the newly created review to return to client + const [newReview] = await db.execute( + ` + SELECT + ReviewID as id, + ProductID, + ReviewerName, + Rating, + Comment, + ReviewDate + FROM Review + WHERE ReviewID = ? + `, + [reviewId], + ); + + res.status(201).json({ + success: true, + message: "Review submitted successfully", + data: newReview[0], + }); + } catch (error) { + console.error("Error submitting review:", error); + return res.status(500).json({ + success: false, + message: "Database error occurred", + error: error.message, + }); + } +}; diff --git a/backend/controllers/review.js b/backend/controllers/review.js new file mode 100644 index 0000000..5de7d1e --- /dev/null +++ b/backend/controllers/review.js @@ -0,0 +1,133 @@ +const db = require("../utils/database"); + +exports.getreview = async (req, res) => { + const { id } = req.params; + console.log("Received Product ID:", id); + + try { + const [data] = 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 + 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], + ); + + // Log raw data for debugging + console.log("Raw Database Result:", data); + + console.log(data); + res.json({ + success: true, + message: "Products fetched successfully", + data, + }); + } catch (error) { + console.error("Full Error Details:", error); + return res.status(500).json({ + success: false, + message: "Database error occurred", + error: error.message, + }); + } +}; + +// Add this to your existing controller file +exports.submitReview = async (req, res) => { + const { productId, userId, rating, comment } = req.body; + + // Validate required fields + if (!productId || !userId || !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 { + // Insert the review into the database + const [result] = await db.execute( + ` + INSERT INTO Review ( + ProductID, + UserID, + Rating, + Comment + ) VALUES (?, ?, ?, ?) + `, + [productId, userId, rating, comment], + ); + + // Get the inserted review id + const reviewId = result.insertId; + + // 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 = ? + `, + [reviewId], + ); + + res.status(201).json({ + success: false, + message: "Review submitted successfully", + data: newReview[0], + }); + } catch (error) { + console.error("Error submitting review:", error); + return res.status(500).json({ + success: false, + message: "Database error occurred", + error: error.message, + }); + } +}; diff --git a/backend/index.js b/backend/index.js index c3df135..c7b4328 100644 --- a/backend/index.js +++ b/backend/index.js @@ -7,6 +7,9 @@ const userRouter = require("./routes/user"); const productRouter = require("./routes/product"); const searchRouter = require("./routes/search"); const recommendedRouter = require("./routes/recommendation"); +const history = require("./routes/history"); +const review = require("./routes/review"); + const { generateEmailTransporter } = require("./utils/mail"); const { cleanupExpiredCodes, @@ -38,6 +41,8 @@ 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/review", review); //prefix with /api/ // Set up a scheduler to run cleanup every hour setInterval(cleanupExpiredCodes, 60 * 60 * 1000); diff --git a/backend/routes/history.js b/backend/routes/history.js new file mode 100644 index 0000000..8d78efa --- /dev/null +++ b/backend/routes/history.js @@ -0,0 +1,8 @@ +// routes/product.js +const express = require("express"); +const { HistoryByUserId } = require("../controllers/history"); +const router = express.Router(); + +router.post("/history", HistoryByUserId); + +module.exports = router; diff --git a/backend/routes/review.js b/backend/routes/review.js new file mode 100644 index 0000000..de3504f --- /dev/null +++ b/backend/routes/review.js @@ -0,0 +1,9 @@ +// routes/product.js +const express = require("express"); +const { getreview, submitReview } = require("../controllers/review"); +const router = express.Router(); + +router.get("/:id", getreview); +router.post("/add", submitReview); + +module.exports = router; diff --git a/frontend/public/image8.jpg b/frontend/public/image8.jpg new file mode 100644 index 0000000..eb1e1a1 Binary files /dev/null and b/frontend/public/image8.jpg differ diff --git a/frontend/src/pages/Home.jsx b/frontend/src/pages/Home.jsx index d55af11..92f3f3c 100644 --- a/frontend/src/pages/Home.jsx +++ b/frontend/src/pages/Home.jsx @@ -6,6 +6,7 @@ const Home = () => { const navigate = useNavigate(); const [listings, setListings] = useState([]); const [recommended, setRecommended] = useState([]); + const [history, sethistory] = useState([]); const [error, setError] = useState(null); useEffect(() => { @@ -38,7 +39,6 @@ const Home = () => { 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 @@ -73,7 +73,6 @@ const Home = () => { 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 @@ -90,6 +89,49 @@ const Home = () => { fetchProducts(); }, []); + 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/get/history", { + 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) { + sethistory( + 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 + 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(); + }, []); + // Toggle favorite status const toggleFavorite = (id, e) => { e.preventDefault(); // Prevent navigation when clicking the heart icon @@ -138,26 +180,6 @@ const Home = () => { - {/* Categories */} - {/*
-

Categories

-
- {categories.map((category) => ( - - ))} -
-
*/} - {/* Recent Listings */}

@@ -219,8 +241,6 @@ const Home = () => {
{recommended.category} - - {recommended.condition}
@@ -311,8 +331,6 @@ const Home = () => {
{listing.category} - - {listing.condition}
@@ -341,6 +359,94 @@ const Home = () => {
+ + {/* Recent Listings */} +
+

History

+ +
+ {/* Left Button - Overlaid on products */} + + + {/* Scrollable Listings Container */} +
+ {history.map((history) => ( + +
+ {history.title} + +
+ +
+

+ {history.title} +

+ + ${history.price} + + +
+ + {history.category} +
+ +
+ + {history.datePosted} + + + {history.seller} + +
+
+ + ))} +
+ + {/* Right Button - Overlaid on products */} + +
+

); }; diff --git a/frontend/src/pages/ProductDetail.jsx b/frontend/src/pages/ProductDetail.jsx index aca2887..b64a998 100644 --- a/frontend/src/pages/ProductDetail.jsx +++ b/frontend/src/pages/ProductDetail.jsx @@ -1,50 +1,145 @@ -import { useState, useEffect } from "react"; -import { useParams, Link } from "react-router-dom"; -import { Heart, ArrowLeft, Tag, User, Calendar } from "lucide-react"; +import { useState, useEffect, setErrors } from "react"; +import { useParams, Link, isSession } from "react-router-dom"; +import { Heart, ArrowLeft, Tag, User, Calendar, Star } from "lucide-react"; const ProductDetail = () => { const { id } = useParams(); const [product, setProduct] = useState(null); - const [loading, setLoading] = useState(true); - const [error, setError] = useState(null); + const [loading, setLoading] = useState({ + product: true, + reviews: true, + }); + const [error, setError] = useState({ + product: null, + reviews: null, + }); const [isFavorite, setIsFavorite] = useState(false); const [showContactForm, setShowContactForm] = useState(false); - const [message, setMessage] = useState(""); const [currentImage, setCurrentImage] = useState(0); + const [reviews, setReviews] = useState([]); + const [showReviewForm, setShowReviewForm] = useState(false); - // Fetch product details + const [reviewForm, setReviewForm] = useState({ + rating: 3, + comment: "", + name: "", + }); + + // Add this function to handle review input changes + const handleReviewInputChange = (e) => { + const { id, value } = e.target; + setReviewForm((prev) => ({ + ...prev, + [id]: value, + })); + }; + + // Add this function to handle star rating selection + const handleRatingChange = (rating) => { + setReviewForm((prev) => ({ + ...prev, + rating, + })); + }; + + const handleSubmitReview = async () => { + try { + // Ensure userId is present + if (!userData.userId) { + throw new Error("User ID is missing. Unable to update profile."); + } + + setIsLoading(true); + setError(null); + + 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), + }); + + const result = await response.json(); + + if (!response.ok) { + throw new Error(result.error || "Failed to update profile"); + } + + 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.", + ); + } finally { + setIsLoading(false); + } + }; + + // Fetch product data useEffect(() => { const fetchProduct = async () => { try { - setLoading(true); + setLoading((prev) => ({ ...prev, product: true })); const response = await fetch(`http://localhost:3030/api/product/${id}`); if (!response.ok) { - throw new Error("Failed to fetch product"); + throw new Error(`HTTP error! Status: ${response.status}`); } const result = await response.json(); - console.log(result); if (result.success) { setProduct(result.data); - setError(null); + setError((prev) => ({ ...prev, product: null })); } else { throw new Error(result.message || "Error fetching product"); } } catch (error) { console.error("Error fetching product:", error); - setError(error.message); - setProduct(null); + setError((prev) => ({ ...prev, product: error.message })); } finally { - setLoading(false); + setLoading((prev) => ({ ...prev, product: false })); } }; fetchProduct(); }, [id]); - // Handle favorite toggle + // Fetch reviews data + useEffect(() => { + const fetchReviews = async () => { + try { + setLoading((prev) => ({ ...prev, reviews: true })); + const response = await fetch(`http://localhost:3030/api/review/${id}`); + + if (!response.ok) { + throw new Error(`HTTP error! Status: ${response.status}`); + } + + const result = await response.json(); + + if (result.success) { + setReviews(result.data || []); + setError((prev) => ({ ...prev, reviews: null })); + } else { + throw new Error(result.message || "Error fetching reviews"); + } + } catch (error) { + console.error("Error fetching reviews:", error); + setError((prev) => ({ ...prev, reviews: error.message })); + setReviews([]); + } finally { + setLoading((prev) => ({ ...prev, reviews: false })); + } + }; + + fetchReviews(); + }, [id]); + + // Handle favorite toggle with error handling const toggleFavorite = async () => { try { const response = await fetch( @@ -61,28 +156,67 @@ const ProductDetail = () => { }, ); + if (!response.ok) { + throw new Error(`HTTP error! Status: ${response.status}`); + } + const result = await response.json(); if (result.success) { setIsFavorite(!isFavorite); + } else { + throw new Error(result.message || "Failed to toggle favorite"); } } catch (error) { console.error("Error toggling favorite:", error); + alert(`Failed to add to favorites: ${error.message}`); } }; - // Handle message submission + // 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 - console.log("Message sent:", message); - setMessage(""); - setShowContactForm(false); - alert("Message sent to seller!"); + 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 && product.images) { + if (product?.images?.length > 0) { setCurrentImage((prev) => prev === product.images.length - 1 ? 0 : prev + 1, ); @@ -90,7 +224,7 @@ const ProductDetail = () => { }; const prevImage = () => { - if (product && product.images) { + if (product?.images?.length > 0) { setCurrentImage((prev) => prev === 0 ? product.images.length - 1 : prev - 1, ); @@ -101,8 +235,22 @@ const ProductDetail = () => { setCurrentImage(index); }; - // Render loading state - if (loading) { + // Function to render stars based on rating + const renderStars = (rating) => { + const stars = []; + for (let i = 1; i <= 5; i++) { + stars.push( + , + ); + } + return stars; + }; + + // Render loading state for the entire page + if (loading.product) { return (
@@ -110,13 +258,30 @@ const ProductDetail = () => { ); } - // Render error state - if (error) { + // Render error state for product + if (error.product) { return (

Error Loading Product

-

{error}

+

{error.product}

+ + Back to Listings + +
+
+ ); + } + + // Safety check for product + if (!product) { + return ( +
+
+

Product Not Found

{
{product.images && product.images.length > 0 ? ( - {product.Name} + <> + {product.Name} { + e.target.onerror = null; + e.target.src = + "https://via.placeholder.com/400x300?text=Image+Not+Available"; + }} + /> + {product.images.length > 1 && ( +
+ +
+ {currentImage + 1}/{product.images.length} +
+
+ )} + ) : (
No Image Available @@ -170,6 +358,11 @@ const ProductDetail = () => { src={image} alt={`${product.Name} - view ${index + 1}`} className="w-full h-auto object-cover" + onError={(e) => { + e.target.onerror = null; + e.target.src = + "https://via.placeholder.com/100x100?text=Error"; + }} />
))} @@ -181,11 +374,14 @@ const ProductDetail = () => {

- {product.Name} + {product.Name || "Unnamed Product"}

- ${product.Price} + $ + {typeof product.Price === "number" + ? product.Price.toFixed(2) + : product.Price}
-
- - {product.Category} -
+ {product.Category && ( +
+ + {product.Category} +
+ )} -
- - Posted on {product.Date} -
+ {product.Date && ( +
+ + Posted on {product.Date} +
+ )}
-

{product.Description}

+

+ {product.Description || "No description available"} +

+ +
+ +
+
+ )} +
); }; diff --git a/frontend/src/pages/SearchPage.jsx b/frontend/src/pages/SearchPage.jsx index 905eb70..2fbcd48 100644 --- a/frontend/src/pages/SearchPage.jsx +++ b/frontend/src/pages/SearchPage.jsx @@ -96,12 +96,13 @@ const SearchPage = () => { return (
+ {/* Filter sidebar */}
@@ -110,9 +111,8 @@ const SearchPage = () => {
-
-
+

Price Range

@@ -126,7 +126,7 @@ const SearchPage = () => { min: Number(e.target.value), })) } - className="w-full p-2 border rounded text-gray-700" + className="w-full p-2 border text-gray-700" /> { max: Number(e.target.value), })) } - className="w-full p-2 border rounded text-gray-700" + className="w-full p-2 border text-gray-700" />
@@ -146,13 +146,13 @@ const SearchPage = () => {
@@ -160,6 +160,7 @@ const SearchPage = () => {
+ {/* Main content */}

{filteredProducts.length} Results @@ -170,18 +171,17 @@ const SearchPage = () => { )}

-
{filteredProducts.map((listing) => ( {listing.title}

diff --git a/mysql-code/Init-Data.sql b/mysql-code/Init-Data.sql index b75516e..a61e04c 100644 --- a/mysql-code/Init-Data.sql +++ b/mysql-code/Init-Data.sql @@ -321,6 +321,9 @@ INSERT INTO Image_URL (URL, ProductID) VALUES ('/image1.avif', 1), + ('/image2.avif', 1), + ('/image3.avif', 1), + ('/image8.jpg', 1), ('/image1.avif', 2), ('/image1.avif', 3), ('/image1.avif', 4), @@ -442,3 +445,14 @@ VALUES (3, 1, 8, '2024-10-14 12:20:00', 'Pending'), (4, 2, 10, '2024-10-13 17:10:00', 'Completed'), (5, 2, 4, '2024-10-12 14:30:00', 'Completed'); + +INSERT INTO + Review (UserID, ProductID, Comment, Rating, Date) +VALUES + ( + 1, + 1, + 'This is a great fake product! Totally recommend it.', + 5, + NOW () + ); diff --git a/mysql-code/Schema.sql b/mysql-code/Schema.sql index df51a58..9764c48 100644 --- a/mysql-code/Schema.sql +++ b/mysql-code/Schema.sql @@ -51,7 +51,7 @@ CREATE TABLE Image_URL ( -- Fixed Review Entity (Many-to-One with User, Many-to-One with Product) CREATE TABLE Review ( - ReviewID INT PRIMARY KEY, + ReviewID INT AUTO_INCREMENT PRIMARY KEY, UserID INT, ProductID INT, Comment TEXT, @@ -61,7 +61,7 @@ CREATE TABLE Review ( ), Date DATETIME DEFAULT CURRENT_TIMESTAMP, FOREIGN KEY (UserID) REFERENCES User (UserID), - FOREIGN KEY (ProductID) REFERENCES Pprint(item[0])roduct (ProductID) + FOREIGN KEY (ProductID) REFERENCES Product (ProductID) ); -- Transaction Entity (Many-to-One with User, Many-to-One with Product) @@ -270,16 +270,16 @@ CREATE TABLE AuthVerification ( -- -- WHERE -- -- CategoryID = 6; -- -- -- REVIEW OPERATIONS --- -- INSERT INTO --- -- Review (ReviewID, UserID, ProductID, Comment, Rating) --- -- VALUES --- -- ( --- -- 1, --- -- 1, --- -- 1, --- -- 'Great product, very satisfied with the purchase!', --- -- 5 --- -- ); +-- INSERT INTO +-- Review (ReviewID, UserID, ProductID, Comment, Rating) +-- VALUES +-- ( +-- 1, +-- 1, +-- 1, +-- 'Great product, very satisfied with the purchase!', +-- 5 +-- ); -- -- -- TRANSACTION OPERATIONS -- -- INSERT INTO -- -- Transaction (TransactionID, UserID, ProductID, PaymentStatus)