Merge pull request #3 from MannPatel0/esther2

Finish admin
This commit is contained in:
Esther Tran
2025-04-20 20:59:36 -06:00
committed by GitHub
27 changed files with 1167 additions and 77 deletions

View File

@@ -0,0 +1,47 @@
const db = require("../utils/database");
exports.getAllCategoriesWithPagination = async (req, res) => {
const limit = +req.query?.limit;
const page = +req.query?.page;
const offset = (page - 1) * limit;
try {
const [data, _] = await db.execute(
"SELECT * FROM Category C ORDER BY C.CategoryID ASC LIMIT ? OFFSET ?",
[limit.toString(), offset.toString()]
);
const [result] = await db.execute("SELECT COUNT(*) AS count FROM Category");
const { count: total } = result[0];
return res.json({ data, total });
} catch (error) {
res.json({ error: "Cannot fetch categories from database!" });
}
};
exports.addCategory = async (req, res) => {
const { name } = req.body;
try {
const [result] = await db.execute(
"INSERT INTO Category (Name) VALUES (?)",
[name]
);
res.json({ message: "Adding new category successfully!" });
} catch (error) {
res.json({ error: "Cannot add new category!" });
}
};
exports.removeCategory = async (req, res) => {
const { id } = req.params;
try {
const [result] = await db.execute(
`DELETE FROM Category WHERE CategoryID = ?`,
[id]
);
res.json({ message: "Delete category successfully!" });
} catch (error) {
res.json({ error: "Cannot remove category from database!" });
}
};

View File

@@ -6,7 +6,7 @@ exports.addProduct = async (req, res) => {
try { try {
const [result] = await db.execute( const [result] = await db.execute(
`INSERT INTO Product (Name, Price, StockQuantity, UserID, Description, CategoryID) VALUES (?, ?, ?, ?, ?, ?)`, `INSERT INTO Product (Name, Price, StockQuantity, UserID, Description, CategoryID) VALUES (?, ?, ?, ?, ?, ?)`,
[name, price, qty, userID, description, category], [name, price, qty, userID, description, category]
); );
const productID = result.insertId; const productID = result.insertId;
@@ -15,7 +15,7 @@ exports.addProduct = async (req, res) => {
db.execute(`INSERT INTO Image_URL (URL, ProductID) VALUES (?, ?)`, [ db.execute(`INSERT INTO Image_URL (URL, ProductID) VALUES (?, ?)`, [
imagePath, imagePath,
productID, productID,
]), ])
); );
await Promise.all(imageInsertPromises); //perallel await Promise.all(imageInsertPromises); //perallel
@@ -39,7 +39,7 @@ exports.addFavorite = async (req, res) => {
// Use parameterized query to prevent SQL injection // Use parameterized query to prevent SQL injection
const [result] = await db.execute( const [result] = await db.execute(
`INSERT INTO Favorites (UserID, ProductID) VALUES (?, ?)`, `INSERT INTO Favorites (UserID, ProductID) VALUES (?, ?)`,
[userID, productID], [userID, productID]
); );
res.json({ res.json({
@@ -59,7 +59,7 @@ exports.removeFavorite = async (req, res) => {
// Use parameterized query to prevent SQL injection // Use parameterized query to prevent SQL injection
const [result] = await db.execute( const [result] = await db.execute(
`DELETE FROM Favorites WHERE UserID = ? AND ProductID = ?`, `DELETE FROM Favorites WHERE UserID = ? AND ProductID = ?`,
[userID, productID], [userID, productID]
); );
res.json({ res.json({
@@ -103,7 +103,7 @@ exports.getFavorites = async (req, res) => {
p.Date, p.Date,
u.Name; u.Name;
`, `,
[userID], [userID]
); );
res.json({ res.json({
@@ -168,7 +168,7 @@ exports.getProductById = async (req, res) => {
JOIN User U ON p.UserID = U.UserID JOIN User U ON p.UserID = U.UserID
WHERE p.ProductID = ? WHERE p.ProductID = ?
`, `,
[id], [id]
); );
// Log raw data for debugging // Log raw data for debugging
@@ -211,6 +211,65 @@ exports.getProductById = async (req, res) => {
} }
}; };
exports.getProductWithPagination = async (req, res) => {
const limit = +req.query.limit;
const page = +req.query.page;
const offset = (page - 1) * limit;
try {
const [data, fields] = await db.execute(
`
SELECT
P.ProductID,
P.Name AS ProductName,
P.Price,
P.Date AS DateUploaded,
U.Name AS SellerName,
MIN(I.URL) AS ProductImage,
C.Name AS Category
FROM Product P
LEFT JOIN Image_URL I ON P.ProductID = I.ProductID
LEFT JOIN User U ON P.UserID = U.UserID
LEFT JOIN Category C ON P.CategoryID = C.CategoryID
GROUP BY
P.ProductID,
P.Name,
P.Price,
P.Date,
U.Name,
C.Name
ORDER BY P.ProductID ASC
LIMIT ? OFFSET ?
`,
[limit.toString(), offset.toString()]
);
const [result] = await db.execute(
`SELECT COUNT(*) AS totalProd FROM Product`
);
const { totalProd } = result[0];
return res.json({ totalProd, products: data });
} catch (error) {
res.json({ error: "Error fetching products!" });
}
};
exports.removeProduct = async (req, res) => {
const { id } = req.params;
try {
const [result] = await db.execute(
`DELETE FROM Product WHERE ProductID = ?`,
[id]
);
res.json({ message: "Delete product successfully!" });
} catch (error) {
res.json({ error: "Cannot remove product from database!" });
}
};
// db_con.query( // db_con.query(
// "SELECT ProductID FROM product WHERE ProductID = ?", // "SELECT ProductID FROM product WHERE ProductID = ?",
// [productID], // [productID],

View File

@@ -0,0 +1,40 @@
const db = require("../utils/database");
exports.getTransactionWithPagination = async (req, res) => {
const limit = +req.query?.limit;
const page = +req.query?.page;
const offset = (page - 1) * limit;
try {
const [data, _] = await db.execute(
`SELECT T.TransactionID, DATE_FORMAT(T.Date, '%b-%d-%Y %h:%i %p') as Date, T.PaymentStatus, U.Name as UserName, P.Name as ProductName
FROM Transaction T
LEFT JOIN User U ON T.UserID = U.UserID
LEFT JOIN Product P ON T.ProductID = P.ProductID
ORDER BY T.TransactionID ASC LIMIT ? OFFSET ?`,
[limit.toString(), offset.toString()]
);
const [result] = await db.execute(
"SELECT COUNT(*) AS count FROM Transaction"
);
const { count: total } = result[0];
return res.json({ data, total });
} catch (error) {
res.json({ error: "Cannot fetch transactions from database!" });
}
};
exports.removeTransation = async (req, res) => {
const { id } = req.params;
try {
const [result] = await db.execute(
"DELETE FROM Transaction WHERE TransactionID = ?;",
[id.toString()]
);
return res.json({ message: "Remove transaction successfully!" });
} catch (error) {
return res
.status(500)
.json({ error: "Cannot remove transactions from database!" });
}
};

View File

@@ -13,13 +13,13 @@ exports.sendVerificationCode = async (req, res) => {
// Generate a random 6-digit code // Generate a random 6-digit code
const verificationCode = crypto.randomInt(100000, 999999).toString(); const verificationCode = crypto.randomInt(100000, 999999).toString();
console.log( console.log(
`Generated verification code for ${email}: ${verificationCode}`, `Generated verification code for ${email}: ${verificationCode}`
); );
// Check if email already exists in verification table // Check if email already exists in verification table
const [results, fields] = await db.execute( const [results, fields] = await db.execute(
"SELECT * FROM AuthVerification WHERE Email = ?", "SELECT * FROM AuthVerification WHERE Email = ?",
[email], [email]
); );
if (results.length > 0) { if (results.length > 0) {
@@ -27,7 +27,7 @@ exports.sendVerificationCode = async (req, res) => {
const [result] = await db.execute( const [result] = await db.execute(
`UPDATE AuthVerification SET VerificationCode = ?, Authenticated = FALSE, Date = CURRENT_TIMESTAMP `UPDATE AuthVerification SET VerificationCode = ?, Authenticated = FALSE, Date = CURRENT_TIMESTAMP
WHERE Email = ?`, WHERE Email = ?`,
[verificationCode, email], [verificationCode, email]
); );
// Send email and respond // Send email and respond
@@ -37,7 +37,7 @@ exports.sendVerificationCode = async (req, res) => {
// Insert new record // Insert new record
const [result] = await db.execute( const [result] = await db.execute(
"INSERT INTO AuthVerification (Email, VerificationCode, Authenticated) VALUES (?, ?, FALSE)", "INSERT INTO AuthVerification (Email, VerificationCode, Authenticated) VALUES (?, ?, FALSE)",
[email, verificationCode], [email, verificationCode]
); );
// Send email and respond // Send email and respond
await sendVerificationEmail(email, verificationCode); await sendVerificationEmail(email, verificationCode);
@@ -62,7 +62,7 @@ exports.verifyCode = async (req, res) => {
// Check verification code // Check verification code
const [results, fields] = await db.execute( const [results, fields] = await db.execute(
"SELECT * FROM AuthVerification WHERE Email = ? AND VerificationCode = ? AND Authenticated = 0 AND Date > DATE_SUB(NOW(), INTERVAL 15 MINUTE)", "SELECT * FROM AuthVerification WHERE Email = ? AND VerificationCode = ? AND Authenticated = 0 AND Date > DATE_SUB(NOW(), INTERVAL 15 MINUTE)",
[email, code], [email, code]
); );
if (results.length === 0) { if (results.length === 0) {
console.log(`Invalid or expired verification code for email ${email}`); console.log(`Invalid or expired verification code for email ${email}`);
@@ -76,7 +76,7 @@ exports.verifyCode = async (req, res) => {
// Mark as authenticated // Mark as authenticated
const [result] = await db.execute( const [result] = await db.execute(
"UPDATE AuthVerification SET Authenticated = TRUE WHERE Email = ?", "UPDATE AuthVerification SET Authenticated = TRUE WHERE Email = ?",
[email], [email]
); );
res.json({ res.json({
success: true, success: true,
@@ -95,7 +95,7 @@ exports.completeSignUp = async (req, res) => {
try { try {
const [results, fields] = await db.execute( const [results, fields] = await db.execute(
`SELECT * FROM AuthVerification WHERE Email = ? AND Authenticated = 1;`, `SELECT * FROM AuthVerification WHERE Email = ? AND Authenticated = 1;`,
[data.email], [data.email]
); );
if (results.length === 0) { if (results.length === 0) {
@@ -105,7 +105,7 @@ exports.completeSignUp = async (req, res) => {
// Create the user // Create the user
const [createResult] = await db.execute( const [createResult] = await db.execute(
`INSERT INTO User (Name, Email, UCID, Password, Phone, Address) `INSERT INTO User (Name, Email, UCID, Password, Phone, Address)
VALUES ('${data.name}', '${data.email}', '${data.UCID}', '${data.password}', '${data.phone}', '${data.address}')`, VALUES ('${data.name}', '${data.email}', '${data.UCID}', '${data.password}', '${data.phone}', '${data.address}')`
); );
// Insert role using the user's ID // Insert role using the user's ID
@@ -113,12 +113,12 @@ exports.completeSignUp = async (req, res) => {
`INSERT INTO UserRole (UserID, Client, Admin) `INSERT INTO UserRole (UserID, Client, Admin)
VALUES (LAST_INSERT_ID(), ${data.client || true}, ${ VALUES (LAST_INSERT_ID(), ${data.client || true}, ${
data.admin || false data.admin || false
})`, })`
); );
// Delete verification record // Delete verification record
const [deleteResult] = await db.execute( const [deleteResult] = await db.execute(
`DELETE FROM AuthVerification WHERE Email = '${data.email}'`, `DELETE FROM AuthVerification WHERE Email = '${data.email}'`
); );
res.json({ res.json({
@@ -310,7 +310,7 @@ exports.deleteUser = async (req, res) => {
// Delete from UserRole first (assuming foreign key constraint) // Delete from UserRole first (assuming foreign key constraint)
const [result1] = await db.execute( const [result1] = await db.execute(
"DELETE FROM UserRole WHERE UserID = ?", "DELETE FROM UserRole WHERE UserID = ?",
[userId], [userId]
); );
// Then delete from User table // Then delete from User table
@@ -328,3 +328,38 @@ exports.deleteUser = async (req, res) => {
return res.status(500).json({ error: "Could not delete user!" }); return res.status(500).json({ error: "Could not delete user!" });
} }
}; };
exports.getUsersWithPagination = async (req, res) => {
const limit = +req.query.limit;
const page = +req.query.page;
const offset = (page - 1) * limit;
try {
const [users, fields] = await db.execute(
"SELECT * FROM User LIMIT ? OFFSET ?",
[limit.toString(), offset.toString()]
);
const [result] = await db.execute("SELECT COUNT(*) AS count FROM User");
const { count: total } = result[0];
res.json({ users, total });
} catch (error) {
console.error("Errors: ", error);
return res.status(500).json({ error: "\nCould not fetch users!" });
}
};
exports.isAdmin = async (req, res) => {
const { id } = req.params;
try {
const [result] = await db.execute(
"SELECT R.Admin FROM marketplace.userrole R WHERE R.UserID = ?",
[id]
);
const { Admin } = result[0];
res.json({ isAdmin: Admin });
} catch (error) {
res.json({ error: "Cannot verify admin status!" });
}
};

View File

@@ -9,6 +9,8 @@ const searchRouter = require("./routes/search");
const recommendedRouter = require("./routes/recommendation"); const recommendedRouter = require("./routes/recommendation");
const history = require("./routes/history"); const history = require("./routes/history");
const review = require("./routes/review"); const review = require("./routes/review");
const categoryRouter = require("./routes/category");
const transactionRouter = require("./routes/transaction");
const { generateEmailTransporter } = require("./utils/mail"); const { generateEmailTransporter } = require("./utils/mail");
const { const {
@@ -42,10 +44,11 @@ app.use("/api/search", searchRouter);
app.use("/api/engine", recommendedRouter); app.use("/api/engine", recommendedRouter);
app.use("/api/history", history); app.use("/api/history", history);
app.use("/api/review", review); app.use("/api/review", review);
app.use("/api/category", categoryRouter);
app.use("/api/transaction", transactionRouter);
// Set up a scheduler to run cleanup every hour // Set up a scheduler to run cleanup every hour
clean_up_time = 30*60*1000; clean_up_time = 30 * 60 * 1000;
setInterval(cleanupExpiredCodes, clean_up_time); setInterval(cleanupExpiredCodes, clean_up_time);
app.listen(3030, () => { app.listen(3030, () => {

View File

@@ -0,0 +1,14 @@
const express = require("express");
const {
getAllCategoriesWithPagination,
addCategory,
removeCategory,
} = require("../controllers/category");
const router = express.Router();
router.get("/getCategories", getAllCategoriesWithPagination);
router.post("/addCategory", addCategory);
router.delete("/:id", removeCategory);
module.exports = router;

View File

@@ -7,6 +7,8 @@ const {
getAllProducts, getAllProducts,
getProductById, getProductById,
addProduct, addProduct,
removeProduct,
getProductWithPagination,
} = require("../controllers/product"); } = require("../controllers/product");
const router = express.Router(); const router = express.Router();
@@ -22,6 +24,12 @@ router.post("/delFavorite", removeFavorite);
router.post("/addProduct", addProduct); router.post("/addProduct", addProduct);
router.get("/getProduct", getAllProducts); router.get("/getProduct", getAllProducts);
//Remove product
router.delete("/:id", removeProduct);
//Get products with pagination
router.get("/getProductWithPagination", getProductWithPagination);
router.get("/:id", getProductById); // Simplified route router.get("/:id", getProductById); // Simplified route
module.exports = router; module.exports = router;

View File

@@ -0,0 +1,12 @@
const express = require("express");
const {
getTransactionWithPagination,
removeTransation,
} = require("../controllers/transaction");
const router = express.Router();
router.get("/getTransactions", getTransactionWithPagination);
router.delete("/:id", removeTransation);
module.exports = router;

View File

@@ -8,6 +8,8 @@ const {
updateUser, updateUser,
deleteUser, deleteUser,
doLogin, doLogin,
isAdmin,
getUsersWithPagination,
} = require("../controllers/user"); } = require("../controllers/user");
const router = express.Router(); const router = express.Router();
@@ -36,4 +38,10 @@ router.post("/update", updateUser);
//Delete A uses Data: //Delete A uses Data:
router.post("/delete", deleteUser); router.post("/delete", deleteUser);
//Check admin status
router.get("/isAdmin/:id", isAdmin);
//Fetch user with pagination
router.get("/getUserWithPagination", getUsersWithPagination);
module.exports = router; module.exports = router;

View File

@@ -4,6 +4,7 @@ const pool = mysql.createPool({
host: "localhost", host: "localhost",
user: "root", user: "root",
database: "Marketplace", database: "Marketplace",
password: "12345678",
}); });
module.exports = pool.promise(); module.exports = pool.promise();

View File

@@ -13,6 +13,7 @@
"lucide-react": "^0.477.0", "lucide-react": "^0.477.0",
"react": "^19.0.0", "react": "^19.0.0",
"react-dom": "^19.0.0", "react-dom": "^19.0.0",
"react-icons": "^5.5.0",
"react-router-dom": "^7.2.0" "react-router-dom": "^7.2.0"
}, },
"devDependencies": { "devDependencies": {
@@ -4369,6 +4370,15 @@
"react": "^19.0.0" "react": "^19.0.0"
} }
}, },
"node_modules/react-icons": {
"version": "5.5.0",
"resolved": "https://registry.npmjs.org/react-icons/-/react-icons-5.5.0.tgz",
"integrity": "sha512-MEFcXdkP3dLo8uumGI5xN3lDFNsRtrjbOEKDLD7yv76v4wpnEq2Lt2qeHaQOr34I/wPN3s3+N08WkQ+CW37Xiw==",
"license": "MIT",
"peerDependencies": {
"react": "*"
}
},
"node_modules/react-is": { "node_modules/react-is": {
"version": "16.13.1", "version": "16.13.1",
"resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz",

View File

@@ -15,6 +15,7 @@
"lucide-react": "^0.477.0", "lucide-react": "^0.477.0",
"react": "^19.0.0", "react": "^19.0.0",
"react-dom": "^19.0.0", "react-dom": "^19.0.0",
"react-icons": "^5.5.0",
"react-router-dom": "^7.2.0" "react-router-dom": "^7.2.0"
}, },
"devDependencies": { "devDependencies": {

View File

@@ -13,6 +13,13 @@ import Transactions from "./pages/Transactions";
import Favorites from "./pages/Favorites"; import Favorites from "./pages/Favorites";
import ProductDetail from "./pages/ProductDetail"; import ProductDetail from "./pages/ProductDetail";
import SearchPage from "./pages/SearchPage"; // Make sure to import the SearchPage import SearchPage from "./pages/SearchPage"; // Make sure to import the SearchPage
import Dashboard from "./pages/Dashboard";
import UserDashboard from "./pages/UserDashboard";
import ProductDashboard from "./pages/ProductDashboard";
import DashboardNav from "./components/DashboardNav";
import CategoryDashboard from "./pages/CategoryDashboard";
import { verifyIsAdmin } from "./api/admin";
import TransactionDashboard from "./pages/TransactionDashboard";
function App() { function App() {
// Authentication state - initialize from localStorage if available // Authentication state - initialize from localStorage if available
@@ -56,6 +63,26 @@ function App() {
sendSessionDataToServer(); sendSessionDataToServer();
}, []); }, []);
const [isAdmin, setIsAdmin] = useState(false);
const [showAdminDashboard, setShowAdminDashboard] = useState(false);
useEffect(() => {
const userInfo = sessionStorage.getItem("user")
? JSON.parse(sessionStorage.getItem("user"))
: "";
const id = userInfo?.ID;
verifyIsAdmin(id).then((data) => {
setIsAdmin(data.isAdmin);
});
}, [user]);
const handleShowAdminDashboard = () => {
setShowAdminDashboard(true);
};
const handleCloseAdminDashboard = () => {
setShowAdminDashboard(false);
};
// Send verification code // Send verification code
const sendVerificationCode = async (userData) => { const sendVerificationCode = async (userData) => {
try { try {
@@ -76,7 +103,7 @@ function App() {
email: userData.email, email: userData.email,
// Add any other required fields // Add any other required fields
}), }),
}, }
); );
if (!response.ok) { if (!response.ok) {
@@ -125,7 +152,7 @@ function App() {
email: tempUserData.email, email: tempUserData.email,
code: code, code: code,
}), }),
}, }
); );
if (!response.ok) { if (!response.ok) {
@@ -169,7 +196,7 @@ function App() {
"Content-Type": "application/json", "Content-Type": "application/json",
}, },
body: JSON.stringify(userData), body: JSON.stringify(userData),
}, }
); );
if (!response.ok) { if (!response.ok) {
@@ -275,7 +302,7 @@ function App() {
email: formValues.email, email: formValues.email,
password: formValues.password, password: formValues.password,
}), }),
}, }
); );
if (!response.ok) { if (!response.ok) {
@@ -672,12 +699,40 @@ function App() {
return children; return children;
}; };
// If user is admin, show admin naviagtion
if (showAdminDashboard) {
return (
<Router>
<div className="flex">
<DashboardNav handleCloseAdminDashboard={handleCloseAdminDashboard} />
<Routes>
{/* Admin routes */}
<Route path="/admin" element={<Dashboard />} />
<Route path="/admin/user" element={<UserDashboard />} />
<Route path="/admin/product" element={<ProductDashboard />} />
<Route path="/admin/category" element={<CategoryDashboard />} />
<Route
path="/admin/transaction"
element={<TransactionDashboard />}
/>
<Route path="*" element={<Dashboard />} />
</Routes>
</div>
</Router>
);
}
return ( return (
<Router> <Router>
<div className="min-h-screen bg-gray-50"> <div className="min-h-screen bg-gray-50">
{/* Only show navbar when authenticated */} {/* Only show navbar when authenticated */}
{isAuthenticated && ( {isAuthenticated && (
<Navbar onLogout={handleLogout} userName={user?.name} /> <Navbar
isAdmin={isAdmin}
onLogout={handleLogout}
userName={user?.name}
handleShowAdminDashboard={handleShowAdminDashboard}
/>
)} )}
<Routes> <Routes>
{/* Public routes */} {/* Public routes */}

120
frontend/src/api/admin.js Normal file
View File

@@ -0,0 +1,120 @@
import client from "./client";
export const getUsers = async (page, limit = 10) => {
try {
const { data } = await client.get(
`/user/getUserWithPagination?page=${page}&limit=${limit}`
);
return { users: data.users, total: data.total };
} catch (error) {
const { response } = error;
if (response?.data) return response.data;
return { error: error.message || error };
}
};
export const getProducts = async (page, limit = 10) => {
try {
const { data } = await client.get(
`/product/getProductWithPagination?limit=${limit}&page=${page}`
);
return { products: data.products, total: data.totalProd };
} catch (error) {
const { response } = error;
if (response?.data) return response.data;
return { error: error.message || error };
}
};
export const getCategories = async (page, limit = 10) => {
try {
const { data } = await client.get(
`/category/getCategories?page=${page}&limit=${limit}`
);
return { data: data.data, total: data.total };
} catch (error) {
const { response } = error;
if (response?.data) return response.data;
return { error: error.message || error };
}
};
export const getTransactions = async (page, limit = 10) => {
try {
const { data } = await client.get(
`/transaction/getTransactions?limit=${limit}&page=${page}`
);
return { transactions: data.data, total: data.total };
} catch (error) {
const { response } = error;
if (response?.data) return response.data;
return { error: error.message || error };
}
};
export const addCategory = async (name) => {
try {
const { data } = await client.post(`/category/addCategory`, { name: name });
return { message: data.message };
} catch (error) {
const { response } = error;
if (response?.data) return response.data;
return { error: error.message || error };
}
};
export const removeCategory = async (id) => {
try {
const { data } = await client.delete(`/category/${id}`);
return { message: data.message };
} catch (error) {
const { response } = error;
if (response?.data) return response.data;
return { error: error.message || error };
}
};
export const removeUser = async (id) => {
try {
const { data } = await client.post(`/user/delete`, { userId: id });
return { message: data.message };
} catch (error) {
const { response } = error;
if (response?.data) return response.data;
return { error: error.message || error };
}
};
export const removeProduct = async (id) => {
try {
const { data } = await client.delete(`/product/${id}`);
return { message: data.message };
} catch (error) {
const { response } = error;
if (response?.data) return response.data;
return { error: error.message || error };
}
};
export const verifyIsAdmin = async (id) => {
try {
const { data } = await client.get(`/user/isAdmin/${id}`);
return { isAdmin: data.isAdmin };
} catch (error) {
const { response } = error;
if (response?.data) return response.data;
return { error: error.message || error };
}
};
export const removeTransaction = async (id) => {
try {
const { data } = await client.delete(`/transaction/${id}`);
return { message: data.message };
} catch (error) {
const { response } = error;
if (response?.data) return response.data;
return { error: error.message || error };
}
};

View File

@@ -0,0 +1,3 @@
import axios from "axios";
const client = axios.create({ baseURL: "http://localhost:3030/api" });
export default client;

View File

@@ -0,0 +1,65 @@
import { useState } from "react";
import { MdAddBox } from "react-icons/md";
import { addCategory } from "../api/admin";
export default function CategoryForm({ visible, onAddCategory }) {
const [category, setCategory] = useState("");
const handleSubmit = (e) => {
e.preventDefault();
if (!category.trim()) {
document.getElementById("noti").innerHTML = "Category name is missing!";
document
.getElementById("noti")
.classList.add("bg-red-200", "text-red-500");
document.getElementById("noti").classList.remove("opacity-0");
return;
}
addCategory(category)
.then((message) => {
document
.getElementById("noti")
.classList.remove("opacity-0", "bg-red-200", "text-red-500");
document
.getElementById("noti")
.classList.add("bg-green-200", "text-green-800");
document.getElementById("noti").innerHTML = `${message.message}`;
setCategory("");
onAddCategory();
})
.catch((err) => {
console.log(err);
});
};
const handleChange = ({ target }) => {
setCategory(target.value);
if (target.value.trim())
document.getElementById("noti").classList.add("opacity-0");
};
if (!visible) return;
return (
<form onSubmit={handleSubmit} action="" className="flex p-2 items-center">
<label htmlFor="category" className="text-green-700">
Category:
</label>
<input
type="text"
className="border border-green-700 ml-2 rounded-sm focus:bg-green-100 text-green-900"
name="category"
id="category"
onChange={handleChange}
value={category}
/>
<button type="submit" className="text-2xl pl-1 text-green-700">
<MdAddBox className="text-3xl" />
</button>
<p
id="noti"
className="text-red-500 bg-red-200 px-2 rounded-sm opacity-0 mx-2"
></p>
</form>
);
}

View File

@@ -0,0 +1,98 @@
import { Link, NavLink } from "react-router-dom";
import { FaUserTag } from "react-icons/fa";
import { FaBoxArchive } from "react-icons/fa6";
import { MdOutlineCategory } from "react-icons/md";
import { FaArrowLeft } from "react-icons/fa";
import { FaMoneyBillTransfer } from "react-icons/fa6";
export default function DashboardNav({ handleCloseAdminDashboard }) {
const handleClick = () => {
handleCloseAdminDashboard();
};
return (
<div>
<div className="w-3xs h-screen bg-green-700 border border-green-700">
<ul>
<li>
<div className="flex-shrink-0 p-6 bg-green-200">
<Link to="/admin" className="flex items-center">
<img
src="/icon/icon-512.png"
alt="Campus Plug"
className="h-8 px-2 "
/>
<span className="hidden md:block text-green-700 font-bold text-2xl">
Campus Plug
</span>
</Link>
</div>
</li>
<li className="w-fit pl-10">
<NavLink
to="/admin/user"
className={({ isActive }) =>
(isActive
? " text-green-400"
: "text-white transition-all hover:text-green-200") +
" flex items-center px-5 text-lg pt-5 "
}
>
<FaUserTag />
<span className="pl-3">Users</span>
</NavLink>
</li>
<li className="w-fit pl-10">
<NavLink
to="/admin/product"
className={({ isActive }) =>
(isActive
? "text-green-400"
: "text-white transition-all hover:text-green-200") +
" flex items-center px-5 text-lg pt-5"
}
>
<FaBoxArchive />
<span className="pl-3">Products</span>
</NavLink>
</li>
<li className="w-fit pl-10">
<NavLink
to="/admin/category"
className={({ isActive }) =>
(isActive
? "text-green-400"
: "text-white transition-all hover:text-green-200") +
" flex items-center px-5 text-lg pt-5"
}
>
<MdOutlineCategory />
<span className="pl-3">Categories</span>
</NavLink>
</li>
<li className="w-fit pl-10">
<NavLink
to="/admin/transaction"
className={({ isActive }) =>
(isActive
? "text-green-400"
: "text-white transition-all hover:text-green-200") +
" flex items-center px-5 text-lg pt-5"
}
>
<FaMoneyBillTransfer />
<span className="pl-3">Transaction</span>
</NavLink>
</li>
</ul>
<div
onClick={handleClick}
className=" text-center my-8 underline text-white underline-offset-4 flex justify-center items-center hover:cursor-pointer h-fit w-fit mx-auto hover:text-green-200 transition"
>
<FaArrowLeft className="text-sm mx-2 mt-1" />
<span>Go back to user page</span>
</div>
</div>
</div>
);
}

View File

@@ -3,7 +3,7 @@ import { Link, useNavigate } from "react-router-dom";
import UserDropdown from "./UserDropdown"; import UserDropdown from "./UserDropdown";
import { Search, Heart } from "lucide-react"; import { Search, Heart } from "lucide-react";
const Navbar = ({ onLogout, userName }) => { const Navbar = ({ onLogout, userName, isAdmin, handleShowAdminDashboard }) => {
const [searchQuery, setSearchQuery] = useState(""); const [searchQuery, setSearchQuery] = useState("");
const navigate = useNavigate(); const navigate = useNavigate();
@@ -76,7 +76,12 @@ const Navbar = ({ onLogout, userName }) => {
</Link> </Link>
{/* User Profile */} {/* User Profile */}
<UserDropdown onLogout={onLogout} userName={userName} /> <UserDropdown
isAdmin={isAdmin}
onLogout={onLogout}
userName={userName}
handleShowAdminDashboard={handleShowAdminDashboard}
/>
</div> </div>
</div> </div>
</div> </div>

View File

@@ -0,0 +1,99 @@
import { useState } from "react";
import { NavLink } from "react-router-dom";
export default function Pagination({ pageNum, onChange }) {
const [currentPage, setCurrentPage] = useState(1);
const pages = [];
for (let i = 1; i <= pageNum; i++) {
pages.push(i);
}
const handleClick = (page) => {
setCurrentPage(page);
onChange(page);
};
const handleTogglePage = (type) => {
let current = currentPage;
if (type == "next")
current = current + 1 <= pageNum ? current + 1 : current;
else current = current - 1 >= 1 ? current - 1 : current;
setCurrentPage(current);
onChange(current);
};
return (
<>
<nav aria-label="Page navigation" className="flex justify-end">
<ul className="flex items-center -space-x-px h-8 text-sm mt-4 pr-0 font-bold">
<li>
<NavLink
onClick={() => {
handleTogglePage("previous");
}}
className=" flex items-center justify-center px-3 h-8 ms-0 leading-tight border-e-0 border-gray-300 rounded-s-lg hover:bg-gray-100 text-white bg-green-700 border border-gray-300 hover:bg-green-600 hover:text-white"
>
<span className="sr-only">Previous</span>
<svg
className="w-2.5 h-2.5 rtl:rotate-180"
aria-hidden="true"
xmlns="http://www.w3.org/2000/svg"
fill="none"
viewBox="0 0 6 10"
>
<path
stroke="currentColor"
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth="2"
d="M5 1 1 5l4 4"
/>
</svg>
</NavLink>
</li>
{pages.map((page) => (
<li key={page}>
<NavLink
className={`${
currentPage == page ? "bg-green-600" : "bg-green-700"
} +
" flex items-center justify-center px-3 h-8 leading-tight text-white border border-gray-300 hover:bg-green-600 hover:text-white"`}
onClick={() => {
handleClick(page);
}}
>
{page}
</NavLink>
</li>
))}
<li>
<NavLink
onClick={() => {
handleTogglePage("next");
}}
className="flex items-center justify-center px-3 h-8 leading-tight border border-gray-300 rounded-e-lg text-white bg-green-700 border border-gray-300 hover:bg-green-600 hover:text-white"
>
<span className="sr-only">Next</span>
<svg
className="w-2.5 h-2.5 rtl:rotate-180"
aria-hidden="true"
xmlns="http://www.w3.org/2000/svg"
fill="none"
viewBox="0 0 6 10"
>
<path
stroke="currentColor"
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth="2"
d="m1 9 4-4-4-4"
/>
</svg>
</NavLink>
</li>
</ul>
</nav>
</>
);
}

View File

@@ -1,8 +1,14 @@
import { useState, useRef, useEffect } from "react"; import { useState, useRef, useEffect } from "react";
import { Link, useNavigate } from "react-router-dom"; import { Link, useNavigate } from "react-router-dom";
import { User, Settings, ShoppingBag, DollarSign, LogOut } from "lucide-react"; import { User, Settings, ShoppingBag, DollarSign, LogOut } from "lucide-react";
import { RiAdminLine } from "react-icons/ri";
const UserDropdown = ({ onLogout, userName }) => { const UserDropdown = ({
onLogout,
userName,
isAdmin,
handleShowAdminDashboard,
}) => {
const [isOpen, setIsOpen] = useState(false); const [isOpen, setIsOpen] = useState(false);
const dropdownRef = useRef(null); const dropdownRef = useRef(null);
const navigate = useNavigate(); const navigate = useNavigate();
@@ -89,6 +95,20 @@ const UserDropdown = ({ onLogout, userName }) => {
Settings Settings
</Link> </Link>
{isAdmin ? (
<Link
className="flex items-center px-4 py-2 text-sm text-gray-700 hover:bg-gray-100"
onClick={() => {
handleShowAdminDashboard();
}}
>
<RiAdminLine className="h-4 w-4 mr-2 text-gray-500" />
Admin
</Link>
) : (
<></>
)}
<button <button
className="flex w-full items-center px-4 py-2 text-sm text-gray-700 hover:bg-gray-100" className="flex w-full items-center px-4 py-2 text-sm text-gray-700 hover:bg-gray-100"
onClick={handleLogout} onClick={handleLogout}

View File

@@ -0,0 +1,105 @@
import { useEffect, useState } from "react";
import { getCategories, removeCategory } from "../api/admin";
import { MdDelete } from "react-icons/md";
import Pagination from "../components/Pagination";
import { IoAddCircleSharp } from "react-icons/io5";
import CategoryForm from "../components/CategoryForm";
export default function CategoryDashboard() {
const [categories, setCategories] = useState([]);
const [total, setTotal] = useState(0);
const [currentPage, setCurrentPage] = useState(1);
let pageLimit = 10;
const [visible, setVisible] = useState(false);
const onChangePage = (page, limit = 10) => {
setCurrentPage(page);
fetchCategory(page, limit);
};
const fetchCategory = (page = 1, limit = 10) => {
getCategories(page, limit).then(({ data, total }) => {
setCategories(data);
setTotal(total);
});
};
const notiChange = () => {
fetchCategory(currentPage);
};
const handleToggleForm = () => {
setVisible((curr) => !curr);
};
const handleRemove = (id) => {
removeCategory(id)
.then(() => {
fetchCategory(currentPage);
})
.catch((err) => {
console.log(err);
});
};
//Get user when initialize the component
useEffect(fetchCategory, []);
return (
<div className="pt-10 p-20 w-full">
<h1 className="text-4xl pb-3 font-bold text-green-800 underline">
CATEGORIES
</h1>
<button
onClick={handleToggleForm}
className="flex justify-end items-center bg-blue-500 rounded-md px-2 text-white text-sm p-1 mb-1 ml-auto hover:cursor-pointer hover:bg-blue-600 transtion"
>
<span className="pr-1 text-xs">Add</span>
<IoAddCircleSharp />
</button>
<CategoryForm onAddCategory={notiChange} visible={visible} />
{categories.length > 0 ? (
<>
<table className="table-fixed w-full text-center border border-green-600">
<thead className="bg-green-600 h-10">
<tr>
<th>CategoryID</th>
<th>Name</th>
<th>Action</th>
</tr>
</thead>
<tbody>
{categories.map((category) => (
<tr
key={category.UserID}
className="border border-green-600 h-10"
>
<td>{category.CategoryID}</td>
<td>{category.Name}</td>
<td className="flex justify-center pt-2">
<MdDelete
onClick={() => {
handleRemove(category.CategoryID);
}}
className="hover:text-red-600 cursor-pointer transition-all text-xl"
/>
</td>
</tr>
))}
</tbody>
</table>
<Pagination
pageNum={Math.ceil(total / pageLimit)}
onChange={onChangePage}
/>
</>
) : (
<p className="text-red-700 text-xl bg-red-200 px-3 rounded-md py-1 w-fit">
No category exists!
</p>
)}
</div>
);
}

View File

@@ -0,0 +1,7 @@
export default function Dashboard() {
return (
<div className="text-3xl font-bold p-3 text-green-800">
Welcome to admin dashboard
</div>
);
}

View File

@@ -0,0 +1,91 @@
import { useEffect, useState } from "react";
import { getProducts, removeProduct } from "../api/admin";
import { MdDelete } from "react-icons/md";
import Pagination from "../components/Pagination";
export default function ProductDashboard() {
const [products, setProducts] = useState([]);
const [total, setTotal] = useState(0);
const [currentPage, setCurrentPage] = useState(1);
let pageLimit = 10;
const onChangePage = (page, limit = 10) => {
setCurrentPage(page);
fetchProducts(page, limit);
};
const fetchProducts = (page = 1, limit = 10) => {
getProducts(page, limit).then(({ products, total }) => {
setTotal(total);
setProducts(products);
});
};
const handleRemoveProduct = (id) => {
removeProduct(id)
.then((res) => {
fetchProducts(currentPage);
})
.catch((err) => {
console.log(err);
});
};
//Get user when initialize the component
useEffect(fetchProducts, []);
return (
<div className="pt-10 p-20">
<h1 className="text-4xl pb-3 font-bold text-green-800 underline">
PRODUCTS
</h1>
{products.length > 0 ? (
<>
<table className="table-fixed w-full text-center border border-green-600">
<thead className="bg-green-600 h-10">
<tr>
<th>ProductID</th>
<th>Name</th>
<th>Price</th>
<th>Category</th>
<th>Seller</th>
<th>Action</th>
</tr>
</thead>
<tbody>
{products.map((product) => (
<tr
key={product.ProductID}
className="border border-green-600 h-10"
>
<td>{product.ProductID}</td>
<td>{product.ProductName}</td>
<td>{product.Price}</td>
<td>{product.Category ? product.Category : "N/A"}</td>
<td>{product.SellerName ? product.SellerName : "N/A"}</td>
<td className="flex justify-center pt-2">
<MdDelete
onClick={() => {
handleRemoveProduct(product.ProductID);
}}
className="hover:text-red-600 cursor-pointer transition-all text-xl"
/>
</td>
</tr>
))}
</tbody>
</table>
<Pagination
onChange={onChangePage}
pageNum={Math.ceil(total / pageLimit)}
/>
</>
) : (
<p className="text-red-700 text-xl bg-red-200 px-3 rounded-md py-1 w-fit">
No product exists!
</p>
)}
</div>
);
}

View File

@@ -0,0 +1,91 @@
import { useEffect, useState } from "react";
import { getTransactions, removeTransaction } from "../api/admin";
import { MdDelete } from "react-icons/md";
import Pagination from "../components/Pagination";
export default function TransactionDashboard() {
const [transactions, setTransactions] = useState([]);
const [total, setTotal] = useState(0);
const [currentPage, setCurrentPage] = useState(1);
let pageLimit = 10;
const onChangePage = (page, limit = 10) => {
setCurrentPage(page);
fetchTransactions(page, limit);
};
const fetchTransactions = (page = 1, limit = 10) => {
getTransactions(page, limit).then(({ transactions, total }) => {
setTotal(total);
setTransactions(transactions);
});
};
const handleRemoveTransaction = (id) => {
removeTransaction(id)
.then(() => {
fetchTransactions(currentPage);
})
.catch((err) => {
console.log(err);
});
};
//Get user when initialize the component
useEffect(fetchTransactions, []);
return (
<div className="pt-10 p-20">
<h1 className="text-4xl pb-3 font-bold text-green-800 underline">
TRANSACTIONS
</h1>
{transactions.length > 0 ? (
<>
<table className="table-fixed w-full text-center border border-green-600">
<thead className="bg-green-600 h-10">
<tr>
<th>TransactionID</th>
<th>User</th>
<th>Product</th>
<th>Date</th>
<th>Status</th>
<th>Action</th>
</tr>
</thead>
<tbody>
{transactions.map((t) => (
<tr
key={t.TransactionID}
className="border border-green-600 h-10"
>
<td>{t.TransactionID}</td>
<td>{t.UserName ? t.UserName : "N/A"}</td>
<td>{t.ProductName ? t.ProductName : "N/A"}</td>
<td>{t.Date}</td>
<td>{t.PaymentStatus}</td>
<td className="flex justify-center pt-2">
<MdDelete
onClick={() => {
handleRemoveTransaction(t.TransactionID);
}}
className="hover:text-red-600 cursor-pointer transition-all text-xl"
/>
</td>
</tr>
))}
</tbody>
</table>
<Pagination
onChange={onChangePage}
pageNum={Math.ceil(total / pageLimit)}
/>
</>
) : (
<p className="text-red-700 text-xl bg-red-200 px-3 rounded-md py-1 w-fit">
No transaction exists!
</p>
)}
</div>
);
}

View File

@@ -0,0 +1,91 @@
import { useEffect, useState } from "react";
import { getUsers, removeUser } from "../api/admin";
import { MdDelete } from "react-icons/md";
import Pagination from "../components/Pagination";
export default function UserDashboard() {
const [users, setUsers] = useState([]);
const [total, setTotal] = useState(0);
const [currentPage, setCurrentPage] = useState(1);
let pageLimit = 10;
const onChangePage = (page, limit = 10) => {
setCurrentPage(page);
fetchUsers(page, limit);
};
const fetchUsers = (page = 1, limit = 10) => {
getUsers(page, limit).then(({ users, total }) => {
setUsers(users);
setTotal(total);
});
};
const handleRemoveUser = (id) => {
removeUser(id)
.then((res) => {
fetchUsers(currentPage);
})
.catch((err) => {
console.log(err);
});
};
//Get user when initialize the component
useEffect(fetchUsers, []);
return (
<div className="pt-10 p-20">
<h1 className="text-4xl pb-3 font-bold text-green-800 underline">
USERS
</h1>
{users.length > 0 ? (
<>
{" "}
<table className="table-fixed w-full text-center border border-green-600">
<thead className="bg-green-600 h-10">
<tr>
<th>UserID</th>
<th>UCID</th>
<th>Name</th>
<th>Email</th>
<th>Phone</th>
<th>Address</th>
<th>Action</th>
</tr>
</thead>
<tbody>
{users.map((user) => (
<tr key={user.UserID} className="border border-green-600 h-10">
<td>{user.UserID}</td>
<td>{user.UCID}</td>
<td>{user.Name}</td>
<td>{user.Email}</td>
<td>{user.Phone}</td>
<td>{user.Address}</td>
<td className="flex justify-center pt-2">
<MdDelete
onClick={() => {
handleRemoveUser(user.UserID);
}}
className="hover:text-red-600 cursor-pointer transition-all text-xl"
/>
</td>
</tr>
))}
</tbody>
</table>
<Pagination
pageNum={Math.ceil(total / pageLimit)}
onChange={onChangePage}
/>
</>
) : (
<p className="text-red-700 text-xl bg-red-200 px-3 rounded-md py-1">
No user exists!
</p>
)}
</div>
);
}

View File

@@ -67,40 +67,42 @@ VALUES
(1, TRUE, TRUE), (1, TRUE, TRUE),
(2, TRUE, FALSE); (2, TRUE, FALSE);
-- Insert Categories
-- Insert Categories -- Insert Categories
INSERT INTO INSERT INTO
Category (CategoryID, Name) Category (Name)
VALUES VALUES
(1, 'Textbooks'), ('Textbooks'),
(2, 'Electronics'), ('Electronics'),
(3, 'Furniture'), ('Furniture'),
(4, 'Clothing'), ('Clothing'),
(5, 'Sports Equipment'), ('Sports Equipment'),
(6, 'Musical Instruments'), ('Musical Instruments'),
(7, 'Art Supplies'), ('Art Supplies'),
(8, 'Kitchen Appliances'), ('Kitchen Appliances'),
(9, 'Gaming'), ('Gaming'),
(10, 'Bicycles'), ( 'Bicycles'),
(11, 'Computer Accessories'), ( 'Computer Accessories'),
(12, 'Stationery'), ( 'Stationery'),
(13, 'Fitness Equipment'), ( 'Fitness Equipment'),
(14, 'Winter Sports'), ( 'Winter Sports'),
(15, 'Lab Equipment'), ( 'Lab Equipment'),
(16, 'Camping Gear'), ( 'Camping Gear'),
(17, 'School Supplies'), ( 'School Supplies'),
(18, 'Office Furniture'), ( 'Office Furniture'),
(19, 'Books (Non-textbook)'), ( 'Books (-textbook)'),
(20, 'Math & Science Resources'), ( 'Math & Science Resources'),
(21, 'Engineering Tools'), ( 'Engineering Tools'),
(22, 'Backpacks & Bags'), ( 'Backpacks & Bags'),
(23, 'Audio Equipment'), ( 'Audio Equipment'),
(24, 'Dorm Essentials'), ( 'Dorm Essentials'),
(25, 'Smartphones & Tablets'), ( 'Smartphones & Tablets'),
(26, 'Winter Clothing'), ( 'Winter Clothing'),
(27, 'Photography Equipment'), ( 'Photography Equipment'),
(28, 'Event Tickets'), ( 'Event Tickets'),
(29, 'Software Licenses'), ( 'Software Licenses'),
(30, 'Transportation (Car Pool)'); ( 'Transportation (Car Pool)');
-- Insert Products -- Insert Products
INSERT INTO INSERT INTO

View File

@@ -24,7 +24,7 @@ CREATE TABLE UserRole (
-- Category Entity (must be created before Product or else error) -- Category Entity (must be created before Product or else error)
CREATE TABLE Category ( CREATE TABLE Category (
CategoryID INT PRIMARY KEY, CategoryID INT AUTO_INCREMENT PRIMARY KEY,
Name VARCHAR(255) NOT NULL Name VARCHAR(255) NOT NULL
); );
@@ -38,15 +38,15 @@ CREATE TABLE Product (
Description TEXT, Description TEXT,
CategoryID INT NOT NULL, CategoryID INT NOT NULL,
Date DATETIME DEFAULT CURRENT_TIMESTAMP, Date DATETIME DEFAULT CURRENT_TIMESTAMP,
FOREIGN KEY (UserID) REFERENCES User (UserID), FOREIGN KEY (UserID) REFERENCES User (UserID) ON DELETE SET NULL,
FOREIGN KEY (CategoryID) REFERENCES Category (CategoryID) FOREIGN KEY (CategoryID) REFERENCES Category (CategoryID) ON DELETE SET NULL
); );
-- Fixed Image_URL table -- Fixed Image_URL table
CREATE TABLE Image_URL ( CREATE TABLE Image_URL (
URL VARCHAR(255), URL VARCHAR(255),
ProductID INT, ProductID INT,
FOREIGN KEY (ProductID) REFERENCES Product (ProductID) FOREIGN KEY (ProductID) REFERENCES Product (ProductID) ON DELETE CASCADE
); );
-- Fixed Review Entity (Many-to-One with User, Many-to-One with Product) -- Fixed Review Entity (Many-to-One with User, Many-to-One with Product)
@@ -60,8 +60,8 @@ CREATE TABLE Review (
AND Rating <= 5 AND Rating <= 5
), ),
Date DATETIME DEFAULT CURRENT_TIMESTAMP, Date DATETIME DEFAULT CURRENT_TIMESTAMP,
FOREIGN KEY (UserID) REFERENCES User (UserID), FOREIGN KEY (UserID) REFERENCES User (UserID) ON DELETE SET NULL,
FOREIGN KEY (ProductID) REFERENCES Product (ProductID) FOREIGN KEY (ProductID) REFERENCES Product (ProductID) ON DELETE CASCADE
); );
-- Transaction Entity (Many-to-One with User, Many-to-One with Product) -- Transaction Entity (Many-to-One with User, Many-to-One with Product)
@@ -71,8 +71,8 @@ CREATE TABLE Transaction (
ProductID INT, ProductID INT,
Date DATETIME DEFAULT CURRENT_TIMESTAMP, Date DATETIME DEFAULT CURRENT_TIMESTAMP,
PaymentStatus VARCHAR(50), PaymentStatus VARCHAR(50),
FOREIGN KEY (UserID) REFERENCES User (UserID), FOREIGN KEY (UserID) REFERENCES User (UserID) ON DELETE CASCADE,
FOREIGN KEY (ProductID) REFERENCES Product (ProductID) FOREIGN KEY (ProductID) REFERENCES Product (ProductID) ON DELETE SET NULL
); );
-- Recommendation Entity (Many-to-One with User, Many-to-One with Product) -- Recommendation Entity (Many-to-One with User, Many-to-One with Product)
@@ -81,8 +81,8 @@ CREATE TABLE Recommendation (
UserID INT, UserID INT,
RecommendedProductID INT, RecommendedProductID INT,
Date DATETIME DEFAULT CURRENT_TIMESTAMP, Date DATETIME DEFAULT CURRENT_TIMESTAMP,
FOREIGN KEY (UserID) REFERENCES User (UserID), FOREIGN KEY (UserID) REFERENCES User (UserID) ON DELETE CASCADE,
FOREIGN KEY (RecommendedProductID) REFERENCES Product (ProductID) FOREIGN KEY (RecommendedProductID) REFERENCES Product (ProductID) ON DELETE CASCADE
); );
-- History Entity (Many-to-One with User, Many-to-One with Product) -- History Entity (Many-to-One with User, Many-to-One with Product)
@@ -91,8 +91,8 @@ CREATE TABLE History (
UserID INT, UserID INT,
ProductID INT, ProductID INT,
Date DATETIME DEFAULT CURRENT_TIMESTAMP, Date DATETIME DEFAULT CURRENT_TIMESTAMP,
FOREIGN KEY (UserID) REFERENCES User (UserID), FOREIGN KEY (UserID) REFERENCES User (UserID) ON DELETE CASCADE,
FOREIGN KEY (ProductID) REFERENCES Product (ProductID) FOREIGN KEY (ProductID) REFERENCES Product (ProductID) ON DELETE CASCADE
); );
-- Favorites Entity (Many-to-One with User, Many-to-One with Product) -- Favorites Entity (Many-to-One with User, Many-to-One with Product)
@@ -100,8 +100,8 @@ CREATE TABLE Favorites (
FavoriteID INT AUTO_INCREMENT PRIMARY KEY, FavoriteID INT AUTO_INCREMENT PRIMARY KEY,
UserID INT, UserID INT,
ProductID INT, ProductID INT,
FOREIGN KEY (UserID) REFERENCES User (UserID), FOREIGN KEY (UserID) REFERENCES User (UserID) ON DELETE CASCADE,
FOREIGN KEY (ProductID) REFERENCES Product (ProductID), FOREIGN KEY (ProductID) REFERENCES Product (ProductID) ON DELETE CASCADE,
UNIQUE (UserID, ProductID) UNIQUE (UserID, ProductID)
); );
@@ -110,8 +110,8 @@ CREATE TABLE Product_Category (
ProductID INT, ProductID INT,
CategoryID INT, CategoryID INT,
PRIMARY KEY (ProductID, CategoryID), PRIMARY KEY (ProductID, CategoryID),
FOREIGN KEY (ProductID) REFERENCES Product (ProductID), FOREIGN KEY (ProductID) REFERENCES Product (ProductID) ON DELETE CASCADE,
FOREIGN KEY (CategoryID) REFERENCES Category (CategoryID) FOREIGN KEY (CategoryID) REFERENCES Category (CategoryID) ON DELETE CASCADE
); );
-- Login Authentication table -- Login Authentication table