diff --git a/backend/controllers/product.js b/backend/controllers/product.js index 44185a5..31ca2ec 100644 --- a/backend/controllers/product.js +++ b/backend/controllers/product.js @@ -391,9 +391,9 @@ exports.getProductWithPagination = async (req, res) => { } }; -exports.removeProduct = async (req, res) => { +exports.removeAnyProduct = async (req, res) => { const { id } = req.params; - + console.log(id); try { const [result] = await db.execute( `DELETE FROM Product WHERE ProductID = ?`, diff --git a/backend/routes/product.js b/backend/routes/product.js index 09000cd..5862209 100644 --- a/backend/routes/product.js +++ b/backend/routes/product.js @@ -8,6 +8,7 @@ const { getProductById, addProduct, removeProduct, + removeAnyProduct, getProductWithPagination, myProduct, updateProduct, @@ -30,7 +31,7 @@ router.post("/addProduct", addProduct); router.get("/getProduct", getAllProducts); //Remove product -router.delete("/:id", removeProduct); +router.delete("/any/:id", removeAnyProduct); //Get products with pagination router.get("/getProductWithPagination", getProductWithPagination); diff --git a/frontend/src/App.jsx b/frontend/src/App.jsx index 38d0009..3cd6a94 100644 --- a/frontend/src/App.jsx +++ b/frontend/src/App.jsx @@ -4,6 +4,7 @@ import { Routes, Route, Navigate, + useLocation, } from "react-router-dom"; import Navbar from "./components/Navbar"; import Home from "./pages/Home"; @@ -12,14 +13,10 @@ import Selling from "./pages/Selling"; import Transactions from "./pages/Transactions"; import Favorites from "./pages/Favorites"; import ProductDetail from "./pages/ProductDetail"; -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 SearchPage from "./pages/SearchPage"; +import Dashboard from "./pages/Dashboard"; // The single consolidated dashboard component import DashboardNav from "./components/DashboardNav"; -import CategoryDashboard from "./pages/CategoryDashboard"; import { verifyIsAdmin } from "./api/admin"; -import TransactionDashboard from "./pages/TransactionDashboard"; function App() { // Authentication state - initialize from localStorage if available @@ -42,6 +39,18 @@ function App() { useState(false); const [recommendations, setRecommendations] = useState([]); + // Admin state + const [isAdmin, setIsAdmin] = useState(false); + const [showAdminDashboard, setShowAdminDashboard] = useState(false); + + // Check URL to determine if we're in admin mode + useEffect(() => { + // If URL contains /admin, set showAdminDashboard to true + if (window.location.pathname.includes("/admin")) { + setShowAdminDashboard(true); + } + }, []); + // New verification states const [verificationStep, setVerificationStep] = useState("initial"); // 'initial', 'code-sent', 'verifying' const [tempUserData, setTempUserData] = useState(null); @@ -127,9 +136,6 @@ function App() { } }; - const [isAdmin, setIsAdmin] = useState(false); - const [showAdminDashboard, setShowAdminDashboard] = useState(false); - useEffect(() => { const userInfo = sessionStorage.getItem("user") ? JSON.parse(sessionStorage.getItem("user")) @@ -142,9 +148,14 @@ function App() { const handleShowAdminDashboard = () => { setShowAdminDashboard(true); + // Update URL without reloading page + window.history.pushState({}, "", "/admin"); }; + const handleCloseAdminDashboard = () => { setShowAdminDashboard(false); + // Update URL without reloading page + window.history.pushState({}, "", "/"); }; // Send verification code @@ -436,6 +447,7 @@ function App() { setVerificationStep("initial"); setTempUserData(null); setRecommendations([]); + setShowAdminDashboard(false); // Clear localStorage sessionStorage.removeItem("user"); @@ -774,126 +786,119 @@ function App() { return children; }; - // If user is admin, show admin naviagtion - if (showAdminDashboard) { - return ( - + return ( + + {/* If admin dashboard should be shown */} + {showAdminDashboard ? (
- {/* Admin routes */} - } /> - } /> - } /> - } /> - } - /> - } /> + {/* Single admin route for consolidated dashboard */} + } /> + {/* Any other path in admin mode should go to dashboard */} + } />
-
- ); - } + ) : ( + /* Normal user interface */ +
+ {/* Show loading overlay when generating recommendations */} + {isGeneratingRecommendations && } - return ( - -
- {/* Show loading overlay when generating recommendations */} - {isGeneratingRecommendations && } - - {/* Only show navbar when authenticated */} - {isAuthenticated && ( - - )} - - {/* Public routes */} - : } - /> - {/* Protected routes */} - -
- -
- - } - /> - - - - } - /> - -
- -
- - } - /> - -
- -
- - } - /> - -
- -
- - } - /> - -
- -
- - } - /> - -
- -
- - } - /> - {/* Redirect to login for any unmatched routes */} - } - /> -
-
+ {/* Only show navbar when authenticated */} + {isAuthenticated && ( + + )} + + {/* Public routes */} + : + } + /> + {/* Protected routes */} + +
+ +
+ + } + /> + + + + } + /> + +
+ +
+ + } + /> + +
+ +
+ + } + /> + +
+ +
+ + } + /> + +
+ +
+ + } + /> + +
+ +
+ + } + /> + {/* Redirect to login for any unmatched routes */} + } + /> +
+
+ )}
); } diff --git a/frontend/src/api/admin.js b/frontend/src/api/admin.js index bea2a15..7aaa648 100644 --- a/frontend/src/api/admin.js +++ b/frontend/src/api/admin.js @@ -1,77 +1,20 @@ -import client from "./client"; +// api.js +import axios from "axios"; + +const client = axios.create({ + baseURL: "http://localhost:3030/api", +}); + +// Users export const getUsers = async (page, limit = 10) => { try { const { data } = await client.get( - `/user/getUserWithPagination?page=${page}&limit=${limit}` + `/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 }; + return handleError(error); } }; @@ -80,20 +23,7 @@ export const removeUser = async (id) => { 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 }; + return handleError(error); } }; @@ -102,9 +32,70 @@ export const verifyIsAdmin = async (id) => { 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 }; + return handleError(error); + } +}; + +// Products +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) { + return handleError(error); + } +}; + +export const removeProduct = async (id) => { + try { + const { data } = await client.delete(`/product/any/${id}`); + return { message: data.message }; + } catch (error) { + return handleError(error); + } +}; + +// Categories +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) { + return handleError(error); + } +}; + +export const addCategory = async (name) => { + try { + const { data } = await client.post(`/category/addCategory`, { name }); + return { message: data.message }; + } catch (error) { + return handleError(error); + } +}; + +export const removeCategory = async (id) => { + try { + const { data } = await client.delete(`/category/${id}`); + return { message: data.message }; + } catch (error) { + return handleError(error); + } +}; + +// Transactions +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) { + return handleError(error); } }; @@ -113,8 +104,16 @@ export const removeTransaction = async (id) => { 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 }; + return handleError(error); } }; + +// Shared Error Handler +const handleError = (error) => { + const { response } = error; + if (response?.data) return response.data; + return { error: error.message || error }; +}; + +// Optional: export client if you want to use it elsewhere +export default client; diff --git a/frontend/src/api/client.js b/frontend/src/api/client.js deleted file mode 100644 index bcb4831..0000000 --- a/frontend/src/api/client.js +++ /dev/null @@ -1,3 +0,0 @@ -import axios from "axios"; -const client = axios.create({ baseURL: "http://localhost:3030/api" }); -export default client; diff --git a/frontend/src/components/DashboardNav.jsx b/frontend/src/components/DashboardNav.jsx index 5f19de2..2013901 100644 --- a/frontend/src/components/DashboardNav.jsx +++ b/frontend/src/components/DashboardNav.jsx @@ -1,98 +1,15 @@ -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 ( -
-
- -
- - Go back to user page -
-
+
+
); } diff --git a/frontend/src/pages/CategoryDashboard.jsx b/frontend/src/pages/CategoryDashboard.jsx deleted file mode 100644 index e08c82f..0000000 --- a/frontend/src/pages/CategoryDashboard.jsx +++ /dev/null @@ -1,105 +0,0 @@ -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 ( -
-

- CATEGORIES -

- - - {categories.length > 0 ? ( - <> - - - - - - - - - - {categories.map((category) => ( - - - - - - ))} - -
CategoryIDNameAction
{category.CategoryID}{category.Name} - { - handleRemove(category.CategoryID); - }} - className="hover:text-red-600 cursor-pointer transition-all text-xl" - /> -
- - - ) : ( -

- No category exists! -

- )} -
- ); -} diff --git a/frontend/src/pages/Dashboard.jsx b/frontend/src/pages/Dashboard.jsx index dbcc762..6b991f6 100644 --- a/frontend/src/pages/Dashboard.jsx +++ b/frontend/src/pages/Dashboard.jsx @@ -1,7 +1,362 @@ -export default function Dashboard() { +import { useEffect, useState, useCallback } from "react"; +import { + getUsers, + removeUser, + getTransactions, + removeTransaction, + getProducts, + removeProduct, + getCategories, + removeCategory, +} from "../api/admin"; +import { MdDelete } from "react-icons/md"; +import { IoAddCircleSharp } from "react-icons/io5"; +import { FaHome } from "react-icons/fa"; +import Pagination from "../components/Pagination"; +import CategoryForm from "../components/CategoryForm"; +import DashboardNav from "../components/DashboardNav"; +import { useNavigate } from "react-router-dom"; + +// Spinner Component +const Spinner = () => ( +
+
+
+); + +// Empty State Component +const EmptyState = () => ( +
+ + + +

No data found

+

+ No records are currently available. +

+
+); + +// Generic Dashboard Component +const Dashboard = ({ + fetchDataFn, + deleteFn, + columns, + idKey, + refreshKey = 0, + headerAction = null, +}) => { + const navigate = useNavigate(); + const [items, setItems] = useState([]); + const [total, setTotal] = useState(0); + const [currentPage, setCurrentPage] = useState(1); + const [loading, setLoading] = useState(true); + const pageLimit = 10; + + const fetchItems = useCallback( + (page = 1, limit = 10) => { + setLoading(true); + fetchDataFn(page, limit) + .then((res) => { + const data = + res.users || res.products || res.transactions || res.data || []; + setItems(data); + setTotal(res.total); + }) + .catch((error) => { + console.error("Error fetching data:", error); + setItems([]); + setTotal(0); + }) + .finally(() => setLoading(false)); + }, + [fetchDataFn], + ); + + const handleRemove = (id) => { + if (window.confirm("Are you sure you want to delete this item?")) { + setLoading(true); + deleteFn(id) + .then(() => fetchItems(currentPage)) + .catch((error) => { + console.error("Error removing item:", error); + setLoading(false); + }); + } + }; + + useEffect(() => { + fetchItems(currentPage); + }, [fetchItems, currentPage, refreshKey]); + + const onChangePage = (page) => { + setCurrentPage(page); + }; + + if (loading) return ; + return ( -
- Welcome to admin dashboard +
+
+

+ Total: {total} +

+ {headerAction &&
{headerAction}
} +
+ + {items.length > 0 ? ( +
+ + + + {columns.map((col) => ( + + ))} + + + + + {items.map((item) => ( + + {columns.map((col) => ( + + ))} + + + ))} + +
+ {col.label} + + Action +
+ {item[col.key] || "—"} + + +
+
+ ) : ( + + )} + + {total > pageLimit && ( +
+ +
+ )} +
+ ); +}; + +// Main Admin Tabs +export default function AdminDashboardTabs() { + const [activeTab, setActiveTab] = useState(0); + const [tabData, setTabData] = useState({ + users: { loaded: false, data: null }, + products: { loaded: false, data: null }, + transactions: { loaded: false, data: null }, + categories: { loaded: false, data: null }, + }); + + const [visible, setVisible] = useState(false); + const [categoryRefreshKey, setCategoryRefreshKey] = useState(0); + const toggleForm = () => setVisible((v) => !v); + + // Preload all tab data + useEffect(() => { + const tabKeys = ["users", "products", "transactions", "categories"]; + const loadTabData = async (index) => { + if (index === tabKeys.length) return; + + setTabData((prev) => ({ + ...prev, + [tabKeys[index]]: { ...prev[tabKeys[index]], loaded: true }, + })); + + // Load next tab after a short delay + setTimeout(() => loadTabData(index + 1), 100); + }; + + loadTabData(0); + }, []); + + const tabs = [ + { + title: "Users", + icon: "👥", + key: "users", + component: () => ( + + ), + }, + { + title: "Products", + icon: "📦", + key: "products", + component: () => ( + + ), + }, + { + title: "Transactions", + icon: "💰", + key: "transactions", + component: () => ( + + ), + }, + { + title: "Categories", + icon: "🏷️", + key: "categories", + component: () => ( + <> + + + Add Category + + } + /> + { + setCategoryRefreshKey((prev) => prev + 1); + toggleForm(); + }} + /> + + ), + }, + ]; + + return ( +
+
+
+

+ Admin Dashboard +

+
+ + {/* Mobile Tabs */} +
+ +
+ + {/* Desktop Tabs */} +
+ {tabs.map((tab, index) => ( + + ))} +
+ +
+
+ {tabData[tabs[activeTab].key].loaded && tabs[activeTab].component()} +
+
+
); } diff --git a/frontend/src/pages/ProductDashboard.jsx b/frontend/src/pages/ProductDashboard.jsx deleted file mode 100644 index 35ce13e..0000000 --- a/frontend/src/pages/ProductDashboard.jsx +++ /dev/null @@ -1,91 +0,0 @@ -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 ( -
-

- PRODUCTS -

- {products.length > 0 ? ( - <> - - - - - - - - - - - - - {products.map((product) => ( - - - - - - - - - ))} - -
ProductIDNamePriceCategorySellerAction
{product.ProductID}{product.ProductName}{product.Price}{product.Category ? product.Category : "N/A"}{product.SellerName ? product.SellerName : "N/A"} - { - handleRemoveProduct(product.ProductID); - }} - className="hover:text-red-600 cursor-pointer transition-all text-xl" - /> -
- - - ) : ( -

- No product exists! -

- )} -
- ); -} diff --git a/frontend/src/pages/TransactionDashboard.jsx b/frontend/src/pages/TransactionDashboard.jsx deleted file mode 100644 index 167d5ee..0000000 --- a/frontend/src/pages/TransactionDashboard.jsx +++ /dev/null @@ -1,91 +0,0 @@ -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 ( -
-

- TRANSACTIONS -

- {transactions.length > 0 ? ( - <> - - - - - - - - - - - - - {transactions.map((t) => ( - - - - - - - - - ))} - -
TransactionIDUserProductDateStatusAction
{t.TransactionID}{t.UserName ? t.UserName : "N/A"}{t.ProductName ? t.ProductName : "N/A"}{t.Date}{t.PaymentStatus} - { - handleRemoveTransaction(t.TransactionID); - }} - className="hover:text-red-600 cursor-pointer transition-all text-xl" - /> -
- - - ) : ( -

- No transaction exists! -

- )} -
- ); -} diff --git a/frontend/src/pages/UserDashboard.jsx b/frontend/src/pages/UserDashboard.jsx deleted file mode 100644 index 9cab96d..0000000 --- a/frontend/src/pages/UserDashboard.jsx +++ /dev/null @@ -1,91 +0,0 @@ -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 ( -
-

- USERS -

- {users.length > 0 ? ( - <> - {" "} - - - - - - - - - - - - - - {users.map((user) => ( - - - - - - - - - - ))} - -
UserIDUCIDNameEmailPhoneAddressAction
{user.UserID}{user.UCID}{user.Name}{user.Email}{user.Phone}{user.Address} - { - handleRemoveUser(user.UserID); - }} - className="hover:text-red-600 cursor-pointer transition-all text-xl" - /> -
- - - ) : ( -

- No user exists! -

- )} -
- ); -} diff --git a/frontend/src/schema.sql b/frontend/src/schema.sql new file mode 100644 index 0000000..8dafcf0 --- /dev/null +++ b/frontend/src/schema.sql @@ -0,0 +1,124 @@ +-- MySql Version 9.2.0 +CREATE DATABASE Marketplace; + +USE Marketplace; + +-- User Entity +CREATE TABLE User ( + UserID INT AUTO_INCREMENT PRIMARY KEY, + Name VARCHAR(100) NOT NULL, + Email VARCHAR(100) UNIQUE NOT NULL, + UCID VARCHAR(20) UNIQUE NOT NULL, + Password VARCHAR(255) NOT NULL, + Phone VARCHAR(20), + Address VARCHAR(255) +); + +CREATE TABLE UserRole ( + UserID INT, + Client BOOLEAN DEFAULT True, + Admin BOOLEAN DEFAULT FALSE, + PRIMARY KEY (UserID), + FOREIGN KEY (UserID) REFERENCES User (UserID) ON DELETE CASCADE +); + +-- Category Entity (must be created before Product or else error) +CREATE TABLE Category ( + CategoryID INT AUTO_INCREMENT PRIMARY KEY, + Name VARCHAR(255) NOT NULL +); + +-- Product Entity +CREATE TABLE Product ( + ProductID INT AUTO_INCREMENT PRIMARY KEY, + Name VARCHAR(255) NOT NULL, + Price DECIMAL(10, 2) NOT NULL, + StockQuantity INT, + UserID INT, + Description TEXT, + CategoryID INT NOT NULL, + Date DATETIME DEFAULT CURRENT_TIMESTAMP, + FOREIGN KEY (UserID) REFERENCES User (UserID) ON DELETE CASCADE, + FOREIGN KEY (CategoryID) REFERENCES Category (CategoryID) ON DELETE SET NULL +); + +-- Fixed Image_URL table +CREATE TABLE Image_URL ( + URL VARCHAR(255), + ProductID INT, + FOREIGN KEY (ProductID) REFERENCES Product (ProductID) ON DELETE CASCADE +); + +-- Fixed Review Entity (Many-to-One with User, Many-to-One with Product) +CREATE TABLE Review ( + ReviewID INT AUTO_INCREMENT PRIMARY KEY, + UserID INT, + ProductID INT, + Comment TEXT, + Rating INT CHECK ( + Rating >= 1 + AND Rating <= 5 + ), + Date DATETIME DEFAULT CURRENT_TIMESTAMP, + FOREIGN KEY (UserID) REFERENCES User (UserID) ON DELETE CASCADE, + FOREIGN KEY (ProductID) REFERENCES Product (ProductID) ON DELETE CASCADE +); + +-- Transaction Entity (Many-to-One with User, Many-to-One with Product) +CREATE TABLE Transaction ( + TransactionID INT PRIMARY KEY, + UserID INT, + ProductID INT, + Date DATETIME DEFAULT CURRENT_TIMESTAMP, + PaymentStatus VARCHAR(50), + FOREIGN KEY (UserID) REFERENCES User (UserID) ON DELETE CASCADE, + FOREIGN KEY (ProductID) REFERENCES Product (ProductID) ON DELETE CASCADE +); + +-- Recommendation Entity (Many-to-One with User, Many-to-One with Product) +CREATE TABLE Recommendation ( + RecommendationID_PK INT AUTO_INCREMENT PRIMARY KEY, + UserID INT, + RecommendedProductID INT, + Date DATETIME DEFAULT CURRENT_TIMESTAMP, + FOREIGN KEY (UserID) REFERENCES User (UserID) ON DELETE CASCADE, + FOREIGN KEY (RecommendedProductID) REFERENCES Product (ProductID) ON DELETE CASCADE +); + +-- History Entity (Many-to-One with User, Many-to-One with Product) +CREATE TABLE History ( + HistoryID INT AUTO_INCREMENT PRIMARY KEY, + UserID INT, + ProductID INT, + Date DATETIME DEFAULT CURRENT_TIMESTAMP, + FOREIGN KEY (UserID) REFERENCES User (UserID) ON DELETE CASCADE, + FOREIGN KEY (ProductID) REFERENCES Product (ProductID) ON DELETE CASCADE +); + +-- Favorites Entity (Many-to-One with User, Many-to-One with Product) +CREATE TABLE Favorites ( + FavoriteID INT AUTO_INCREMENT PRIMARY KEY, + UserID INT, + ProductID INT, + FOREIGN KEY (UserID) REFERENCES User (UserID) ON DELETE CASCADE, + FOREIGN KEY (ProductID) REFERENCES Product (ProductID) ON DELETE CASCADE, + UNIQUE (UserID, ProductID) +); + +-- Product-Category Junction Table (Many-to-Many) +CREATE TABLE Product_Category ( + ProductID INT, + CategoryID INT, + PRIMARY KEY (ProductID, CategoryID), + FOREIGN KEY (ProductID) REFERENCES Product (ProductID) ON DELETE CASCADE, + FOREIGN KEY (CategoryID) REFERENCES Category (CategoryID) ON DELETE CASCADE +); + +-- Login Authentication table +CREATE TABLE AuthVerification ( + UserID INT AUTO_INCREMENT PRIMARY KEY, + Email VARCHAR(100) UNIQUE NOT NULL, + VerificationCode VARCHAR(6) NOT NULL, + Authenticated BOOLEAN DEFAULT FALSE, + Date DATETIME DEFAULT CURRENT_TIMESTAMP +); diff --git a/mysql-code/Init-Data.sql b/mysql-code/Init-Data.sql index b636c7a..3c7dae5 100644 --- a/mysql-code/Init-Data.sql +++ b/mysql-code/Init-Data.sql @@ -101,7 +101,8 @@ VALUES ('Photography Equipment'), ('Event Tickets'), ('Software Licenses'), - ('Transportation (Car Pool)'); + ('Transportation (Car Pool)'), + ('Other'); -- Insert Products INSERT INTO diff --git a/mysql-code/Schema.sql b/mysql-code/Schema.sql index 508dc7d..e65a325 100644 --- a/mysql-code/Schema.sql +++ b/mysql-code/Schema.sql @@ -28,7 +28,6 @@ CREATE TABLE Category ( Name VARCHAR(255) NOT NULL ); --- Product Entity CREATE TABLE Product ( ProductID INT AUTO_INCREMENT PRIMARY KEY, Name VARCHAR(255) NOT NULL, @@ -38,8 +37,22 @@ CREATE TABLE Product ( Description TEXT, CategoryID INT NOT NULL, Date DATETIME DEFAULT CURRENT_TIMESTAMP, + FOREIGN KEY (UserID) REFERENCES User (UserID), + FOREIGN KEY (CategoryID) REFERENCES Category (CategoryID) +); + +-- Product Entity +CREATE TABLE Product ( + ProductID INT AUTO_INCREMENT PRIMARY KEY, + Name VARCHAR(255) NOT NULL, + Price DECIMAL(10, 2) NOT NULL, + StockQuantity INT, + UserID INT, + Description TEXT, + CategoryID INT, + Date DATETIME DEFAULT CURRENT_TIMESTAMP, FOREIGN KEY (UserID) REFERENCES User (UserID) ON DELETE SET NULL, - FOREIGN KEY (CategoryID) REFERENCES Category (CategoryID) ON DELETE SET NULL + FOREIGN KEY (CategoryID) REFERENCES Category (CategoryID) ); -- Fixed Image_URL table @@ -72,7 +85,7 @@ CREATE TABLE Transaction ( Date DATETIME DEFAULT CURRENT_TIMESTAMP, PaymentStatus VARCHAR(50), FOREIGN KEY (UserID) REFERENCES User (UserID) ON DELETE CASCADE, - FOREIGN KEY (ProductID) REFERENCES Product (ProductID) ON DELETE SET NULL + FOREIGN KEY (ProductID) REFERENCES Product (ProductID) ON DELETE SET NULL ); -- Recommendation Entity (Many-to-One with User, Many-to-One with Product)