Finish admin dashboard and update sql code
This commit is contained in:
13
.gitignore
vendored
Normal file
13
.gitignore
vendored
Normal file
@@ -0,0 +1,13 @@
|
||||
# Node_modules
|
||||
*/node_modules
|
||||
.DS_Store
|
||||
|
||||
.vscode/*
|
||||
!.vscode/extensions.json
|
||||
.idea
|
||||
.DS_Store
|
||||
*.suo
|
||||
*.ntvs*
|
||||
*.njsproj
|
||||
*.sln
|
||||
*.sw?
|
||||
201
LICENSE
Normal file
201
LICENSE
Normal file
@@ -0,0 +1,201 @@
|
||||
Apache License
|
||||
Version 2.0, January 2004
|
||||
http://www.apache.org/licenses/
|
||||
|
||||
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||
|
||||
1. Definitions.
|
||||
|
||||
"License" shall mean the terms and conditions for use, reproduction,
|
||||
and distribution as defined by Sections 1 through 9 of this document.
|
||||
|
||||
"Licensor" shall mean the copyright owner or entity authorized by
|
||||
the copyright owner that is granting the License.
|
||||
|
||||
"Legal Entity" shall mean the union of the acting entity and all
|
||||
other entities that control, are controlled by, or are under common
|
||||
control with that entity. For the purposes of this definition,
|
||||
"control" means (i) the power, direct or indirect, to cause the
|
||||
direction or management of such entity, whether by contract or
|
||||
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||
|
||||
"You" (or "Your") shall mean an individual or Legal Entity
|
||||
exercising permissions granted by this License.
|
||||
|
||||
"Source" form shall mean the preferred form for making modifications,
|
||||
including but not limited to software source code, documentation
|
||||
source, and configuration files.
|
||||
|
||||
"Object" form shall mean any form resulting from mechanical
|
||||
transformation or translation of a Source form, including but
|
||||
not limited to compiled object code, generated documentation,
|
||||
and conversions to other media types.
|
||||
|
||||
"Work" shall mean the work of authorship, whether in Source or
|
||||
Object form, made available under the License, as indicated by a
|
||||
copyright notice that is included in or attached to the work
|
||||
(an example is provided in the Appendix below).
|
||||
|
||||
"Derivative Works" shall mean any work, whether in Source or Object
|
||||
form, that is based on (or derived from) the Work and for which the
|
||||
editorial revisions, annotations, elaborations, or other modifications
|
||||
represent, as a whole, an original work of authorship. For the purposes
|
||||
of this License, Derivative Works shall not include works that remain
|
||||
separable from, or merely link (or bind by name) to the interfaces of,
|
||||
the Work and Derivative Works thereof.
|
||||
|
||||
"Contribution" shall mean any work of authorship, including
|
||||
the original version of the Work and any modifications or additions
|
||||
to that Work or Derivative Works thereof, that is intentionally
|
||||
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||
or by an individual or Legal Entity authorized to submit on behalf of
|
||||
the copyright owner. For the purposes of this definition, "submitted"
|
||||
means any form of electronic, verbal, or written communication sent
|
||||
to the Licensor or its representatives, including but not limited to
|
||||
communication on electronic mailing lists, source code control systems,
|
||||
and issue tracking systems that are managed by, or on behalf of, the
|
||||
Licensor for the purpose of discussing and improving the Work, but
|
||||
excluding communication that is conspicuously marked or otherwise
|
||||
designated in writing by the copyright owner as "Not a Contribution."
|
||||
|
||||
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||
on behalf of whom a Contribution has been received by Licensor and
|
||||
subsequently incorporated within the Work.
|
||||
|
||||
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
copyright license to reproduce, prepare Derivative Works of,
|
||||
publicly display, publicly perform, sublicense, and distribute the
|
||||
Work and such Derivative Works in Source or Object form.
|
||||
|
||||
3. Grant of Patent License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
(except as stated in this section) patent license to make, have made,
|
||||
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||
where such license applies only to those patent claims licensable
|
||||
by such Contributor that are necessarily infringed by their
|
||||
Contribution(s) alone or by combination of their Contribution(s)
|
||||
with the Work to which such Contribution(s) was submitted. If You
|
||||
institute patent litigation against any entity (including a
|
||||
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||
or a Contribution incorporated within the Work constitutes direct
|
||||
or contributory patent infringement, then any patent licenses
|
||||
granted to You under this License for that Work shall terminate
|
||||
as of the date such litigation is filed.
|
||||
|
||||
4. Redistribution. You may reproduce and distribute copies of the
|
||||
Work or Derivative Works thereof in any medium, with or without
|
||||
modifications, and in Source or Object form, provided that You
|
||||
meet the following conditions:
|
||||
|
||||
(a) You must give any other recipients of the Work or
|
||||
Derivative Works a copy of this License; and
|
||||
|
||||
(b) You must cause any modified files to carry prominent notices
|
||||
stating that You changed the files; and
|
||||
|
||||
(c) You must retain, in the Source form of any Derivative Works
|
||||
that You distribute, all copyright, patent, trademark, and
|
||||
attribution notices from the Source form of the Work,
|
||||
excluding those notices that do not pertain to any part of
|
||||
the Derivative Works; and
|
||||
|
||||
(d) If the Work includes a "NOTICE" text file as part of its
|
||||
distribution, then any Derivative Works that You distribute must
|
||||
include a readable copy of the attribution notices contained
|
||||
within such NOTICE file, excluding those notices that do not
|
||||
pertain to any part of the Derivative Works, in at least one
|
||||
of the following places: within a NOTICE text file distributed
|
||||
as part of the Derivative Works; within the Source form or
|
||||
documentation, if provided along with the Derivative Works; or,
|
||||
within a display generated by the Derivative Works, if and
|
||||
wherever such third-party notices normally appear. The contents
|
||||
of the NOTICE file are for informational purposes only and
|
||||
do not modify the License. You may add Your own attribution
|
||||
notices within Derivative Works that You distribute, alongside
|
||||
or as an addendum to the NOTICE text from the Work, provided
|
||||
that such additional attribution notices cannot be construed
|
||||
as modifying the License.
|
||||
|
||||
You may add Your own copyright statement to Your modifications and
|
||||
may provide additional or different license terms and conditions
|
||||
for use, reproduction, or distribution of Your modifications, or
|
||||
for any such Derivative Works as a whole, provided Your use,
|
||||
reproduction, and distribution of the Work otherwise complies with
|
||||
the conditions stated in this License.
|
||||
|
||||
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||
any Contribution intentionally submitted for inclusion in the Work
|
||||
by You to the Licensor shall be under the terms and conditions of
|
||||
this License, without any additional terms or conditions.
|
||||
Notwithstanding the above, nothing herein shall supersede or modify
|
||||
the terms of any separate license agreement you may have executed
|
||||
with Licensor regarding such Contributions.
|
||||
|
||||
6. Trademarks. This License does not grant permission to use the trade
|
||||
names, trademarks, service marks, or product names of the Licensor,
|
||||
except as required for reasonable and customary use in describing the
|
||||
origin of the Work and reproducing the content of the NOTICE file.
|
||||
|
||||
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||
agreed to in writing, Licensor provides the Work (and each
|
||||
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||
implied, including, without limitation, any warranties or conditions
|
||||
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||
appropriateness of using or redistributing the Work and assume any
|
||||
risks associated with Your exercise of permissions under this License.
|
||||
|
||||
8. Limitation of Liability. In no event and under no legal theory,
|
||||
whether in tort (including negligence), contract, or otherwise,
|
||||
unless required by applicable law (such as deliberate and grossly
|
||||
negligent acts) or agreed to in writing, shall any Contributor be
|
||||
liable to You for damages, including any direct, indirect, special,
|
||||
incidental, or consequential damages of any character arising as a
|
||||
result of this License or out of the use or inability to use the
|
||||
Work (including but not limited to damages for loss of goodwill,
|
||||
work stoppage, computer failure or malfunction, or any and all
|
||||
other commercial damages or losses), even if such Contributor
|
||||
has been advised of the possibility of such damages.
|
||||
|
||||
9. Accepting Warranty or Additional Liability. While redistributing
|
||||
the Work or Derivative Works thereof, You may choose to offer,
|
||||
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||
or other liability obligations and/or rights consistent with this
|
||||
License. However, in accepting such obligations, You may act only
|
||||
on Your own behalf and on Your sole responsibility, not on behalf
|
||||
of any other Contributor, and only if You agree to indemnify,
|
||||
defend, and hold each Contributor harmless for any liability
|
||||
incurred by, or claims asserted against, such Contributor by reason
|
||||
of your accepting any such warranty or additional liability.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
APPENDIX: How to apply the Apache License to your work.
|
||||
|
||||
To apply the Apache License to your work, attach the following
|
||||
boilerplate notice, with the fields enclosed by brackets "[]"
|
||||
replaced with your own identifying information. (Don't include
|
||||
the brackets!) The text should be enclosed in the appropriate
|
||||
comment syntax for the file format. We also recommend that a
|
||||
file or class name and description of purpose be included on the
|
||||
same "printed page" as the copyright notice for easier
|
||||
identification within third-party archives.
|
||||
|
||||
Copyright [yyyy] [name of copyright owner]
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
34
README.md
Normal file
34
README.md
Normal file
@@ -0,0 +1,34 @@
|
||||
### Some ground rules
|
||||
1. Add both node_modules from Slient and Server to your ```gitignore``` file
|
||||
2. Make a brach with the following naming conventionp, refix it with your name ```Your-Name Branch-Name```.
|
||||
---
|
||||
|
||||
### Frontend
|
||||
1. `cd frontend` into the dir and then type command
|
||||
```Bash
|
||||
#Install the needed lib with the command bellow
|
||||
npm install
|
||||
#Start The Server
|
||||
npm run dev
|
||||
```
|
||||
---
|
||||
|
||||
### Backend
|
||||
1. `cd backend` into the dir and then type command
|
||||
```Bash
|
||||
#Install the needed lib with the command bellow
|
||||
npm install
|
||||
#Start The Server
|
||||
npm run dev
|
||||
```
|
||||
---
|
||||
|
||||
### Database
|
||||
1. MySql Version 9.2.0
|
||||
2. To Create the DataBase use the command bellow:
|
||||
```Bash
|
||||
1. mysql -u root
|
||||
2. use Marketplace;
|
||||
3. \. PathToYour/Schema.sql
|
||||
3. \. PathToYour/Init-Data.sql
|
||||
```
|
||||
47
backend/controllers/category.js
Normal file
47
backend/controllers/category.js
Normal 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!" });
|
||||
}
|
||||
};
|
||||
90
backend/controllers/history.js
Normal file
90
backend/controllers/history.js
Normal file
@@ -0,0 +1,90 @@
|
||||
const db = require("../utils/database");
|
||||
|
||||
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 H.UserID = ?
|
||||
)
|
||||
SELECT
|
||||
ProductID,
|
||||
ProductName,
|
||||
Price,
|
||||
DateUploaded,
|
||||
SellerName,
|
||||
ProductImage,
|
||||
Category
|
||||
FROM RankedImages
|
||||
WHERE RowNum = 1;
|
||||
`,
|
||||
[id],
|
||||
);
|
||||
|
||||
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",
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
exports.AddHistory = async (req, res) => {
|
||||
const { userID, productID } = req.body;
|
||||
console.log(userID);
|
||||
try {
|
||||
// Use parameterized query to prevent SQL injection
|
||||
const [result] = await db.execute(
|
||||
`INSERT INTO History (UserID, ProductID) VALUES (?, ?)`,
|
||||
[userID, productID],
|
||||
);
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
message: "Product added to history successfully",
|
||||
});
|
||||
} catch (error) {
|
||||
console.error("Error adding favorite product:", error);
|
||||
return res.json({ error: "Could not add favorite product" });
|
||||
}
|
||||
};
|
||||
|
||||
exports.DelHistory = async (req, res) => {
|
||||
const { userID, productID } = req.body;
|
||||
console.log(userID);
|
||||
try {
|
||||
// Use parameterized query to prevent SQL injection
|
||||
const [result] = await db.execute(`DELETE FROM History WHERE UserID=?`, [
|
||||
userID,
|
||||
]);
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
message: "Product deleted from History successfully",
|
||||
});
|
||||
} catch (error) {
|
||||
console.error("Error adding favorite product:", error);
|
||||
return res.json({ error: "Could not add favorite product" });
|
||||
}
|
||||
};
|
||||
269
backend/controllers/product.js
Normal file
269
backend/controllers/product.js
Normal file
@@ -0,0 +1,269 @@
|
||||
const db = require("../utils/database");
|
||||
|
||||
exports.addFavorite = async (req, res) => {
|
||||
const { userID, productID } = req.body;
|
||||
console.log(userID);
|
||||
try {
|
||||
// Use parameterized query to prevent SQL injection
|
||||
const [result] = await db.execute(
|
||||
`INSERT INTO Favorites (UserID, ProductID) VALUES (?, ?)`,
|
||||
[userID, productID]
|
||||
);
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
message: "Product added to favorites successfully",
|
||||
});
|
||||
} catch (error) {
|
||||
console.error("Error adding favorite product:", error);
|
||||
return res.json({ error: "Could not add favorite product" });
|
||||
}
|
||||
};
|
||||
|
||||
exports.removeFavorite = async (req, res) => {
|
||||
const { userID, productID } = req.body;
|
||||
console.log(userID);
|
||||
try {
|
||||
// Use parameterized query to prevent SQL injection
|
||||
const [result] = await db.execute(
|
||||
`DELETE FROM Favorites WHERE UserID = ? AND ProductID = ?`,
|
||||
[userID, productID]
|
||||
);
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
message: "Product removed from favorites successfully",
|
||||
});
|
||||
} catch (error) {
|
||||
console.error("Error removing favorite product:", error);
|
||||
return res.json({ error: "Could not remove favorite product" });
|
||||
}
|
||||
};
|
||||
|
||||
exports.getFavorites = async (req, res) => {
|
||||
const { userID } = req.body;
|
||||
|
||||
try {
|
||||
const [favorites] = await db.execute(
|
||||
`
|
||||
SELECT
|
||||
p.ProductID,
|
||||
p.Name,
|
||||
p.Description,
|
||||
p.Price,
|
||||
p.CategoryID,
|
||||
p.UserID,
|
||||
p.Date,
|
||||
u.Name AS SellerName,
|
||||
MIN(i.URL) AS image_url
|
||||
FROM Favorites f
|
||||
JOIN Product p ON f.ProductID = p.ProductID
|
||||
JOIN User u ON p.UserID = u.UserID
|
||||
LEFT JOIN Image_URL i ON p.ProductID = i.ProductID
|
||||
WHERE f.UserID = ?
|
||||
GROUP BY
|
||||
p.ProductID,
|
||||
p.Name,
|
||||
p.Description,
|
||||
p.Price,
|
||||
p.CategoryID,
|
||||
p.UserID,
|
||||
p.Date,
|
||||
u.Name;
|
||||
`,
|
||||
[userID]
|
||||
);
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
favorites: favorites,
|
||||
});
|
||||
} catch (error) {
|
||||
console.error("Error retrieving favorites:", error);
|
||||
res.status(500).json({ error: "Could not retrieve favorite products" });
|
||||
}
|
||||
};
|
||||
|
||||
// Get all products along with their image URLs
|
||||
exports.getAllProducts = async (req, res) => {
|
||||
try {
|
||||
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
|
||||
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
|
||||
GROUP BY
|
||||
P.ProductID,
|
||||
P.Name,
|
||||
P.Price,
|
||||
P.Date,
|
||||
U.Name,
|
||||
C.Name;
|
||||
`);
|
||||
|
||||
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",
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
exports.getProductById = async (req, res) => {
|
||||
const { id } = req.params;
|
||||
console.log("Received Product ID:", id);
|
||||
|
||||
try {
|
||||
const [data] = await db.execute(
|
||||
`
|
||||
SELECT p.*,U.Name AS SellerName,U.Email as SellerEmail,U.Phone as SellerPhone, i.URL AS image_url
|
||||
FROM Product p
|
||||
LEFT JOIN Image_URL i ON p.ProductID = i.ProductID
|
||||
JOIN User U ON p.UserID = U.UserID
|
||||
WHERE p.ProductID = ?
|
||||
`,
|
||||
[id]
|
||||
);
|
||||
|
||||
// Log raw data for debugging
|
||||
console.log("Raw Database Result:", data);
|
||||
|
||||
if (data.length === 0) {
|
||||
console.log("No product found with ID:", id);
|
||||
return res.status(404).json({
|
||||
success: false,
|
||||
message: "Product not found",
|
||||
});
|
||||
}
|
||||
|
||||
// Collect all image URLs
|
||||
const images = data
|
||||
.map((row) => row.image_url)
|
||||
.filter((url) => url !== null);
|
||||
|
||||
// Create product object with all details from first row and collected images
|
||||
const product = {
|
||||
...data[0], // Base product details
|
||||
images: images, // Collected image URLs
|
||||
};
|
||||
|
||||
// Log processed product for debugging
|
||||
console.log("Processed Product:", product);
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
message: "Product fetched successfully",
|
||||
data: product,
|
||||
});
|
||||
} catch (error) {
|
||||
console.error("Full Error Details:", error);
|
||||
return res.status(500).json({
|
||||
success: false,
|
||||
message: "Database error occurred",
|
||||
error: error.message,
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
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(
|
||||
// "SELECT ProductID FROM product WHERE ProductID = ?",
|
||||
// [productID],
|
||||
// (err, results) => {
|
||||
// if (err) {
|
||||
// console.error("Error checking product:", err);
|
||||
// return res.json({ error: "Database error" });
|
||||
// }
|
||||
|
||||
// if (results.length === 0) {
|
||||
// return res.json({ error: "Product does not exist" });
|
||||
// }
|
||||
// },
|
||||
// );
|
||||
|
||||
// db_con.query(
|
||||
// "INSERT INTO Favorites (UserID, ProductID) VALUES (?, ?)",
|
||||
// [userID, productID],
|
||||
// (err, result) => {
|
||||
// if (err) {
|
||||
// console.error("Error adding favorite product:", err);
|
||||
// return res.json({ error: "Could not add favorite product" });
|
||||
// }
|
||||
// res.json({
|
||||
// success: true,
|
||||
// message: "Product added to favorites successfully",
|
||||
// });
|
||||
// },
|
||||
// );
|
||||
53
backend/controllers/recommendation.js
Normal file
53
backend/controllers/recommendation.js
Normal file
@@ -0,0 +1,53 @@
|
||||
const db = require("../utils/database");
|
||||
|
||||
// TODO: Get the recommondaed product given the userID
|
||||
exports.RecommondationByUserId = async (req, res) => {
|
||||
const { id } = req.body;
|
||||
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
|
||||
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",
|
||||
});
|
||||
}
|
||||
};
|
||||
302
backend/controllers/review.js
Normal file
302
backend/controllers/review.js
Normal file
@@ -0,0 +1,302 @@
|
||||
const db = require("../utils/database");
|
||||
|
||||
/**
|
||||
* Get reviews for a specific product
|
||||
* Returns both reviews for the product and reviews by the product owner for other products
|
||||
*/
|
||||
exports.getReviews = async (req, res) => {
|
||||
const { id } = req.params;
|
||||
console.log("Received Product ID:", id);
|
||||
|
||||
try {
|
||||
// First query: Get reviews for this specific product
|
||||
const [productReviews] = 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,
|
||||
'product' AS ReviewType
|
||||
FROM Review R
|
||||
JOIN User U ON R.UserID = U.UserID
|
||||
JOIN Product P ON R.ProductID = P.ProductID
|
||||
WHERE R.ProductID = ?`,
|
||||
[id],
|
||||
);
|
||||
|
||||
// // Second query: Get reviews written by the product owner for other products
|
||||
// const [sellerReviews] = await db.execute(
|
||||
// `SELECT
|
||||
// R.ReviewID,
|
||||
// R.UserID,
|
||||
// R.ProductID,
|
||||
// R.Comment,
|
||||
// R.Rating,
|
||||
// R.Date AS ReviewDate,
|
||||
// U.Name AS ReviewerName,
|
||||
// P.Name AS ProductName,
|
||||
// 'seller' AS ReviewType
|
||||
// FROM Review R
|
||||
// JOIN User U ON R.UserID = U.UserID
|
||||
// JOIN Product P ON R.ProductID = P.ProductID
|
||||
// WHERE R.UserID = (
|
||||
// SELECT UserID
|
||||
// FROM Product
|
||||
// WHERE ProductID = ?
|
||||
// )
|
||||
// AND R.ProductID != ?`,
|
||||
// [id, id],
|
||||
// );
|
||||
|
||||
// Combine the results
|
||||
const combinedReviews = [...productReviews];
|
||||
|
||||
// Log data for debugging
|
||||
console.log("Combined Reviews:", combinedReviews);
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
message: "Reviews fetched successfully",
|
||||
data: combinedReviews,
|
||||
});
|
||||
} catch (error) {
|
||||
console.error("Full Error Details:", error);
|
||||
return res.status(500).json({
|
||||
success: false,
|
||||
message: "Database error occurred",
|
||||
error: error.message,
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Submit a new review for a product
|
||||
*/
|
||||
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 {
|
||||
// Check if user has already reviewed this product
|
||||
const [existingReview] = await db.execute(
|
||||
`SELECT ReviewID FROM Review WHERE ProductID = ? AND UserID = ?`,
|
||||
[productId, userId],
|
||||
);
|
||||
|
||||
if (existingReview.length > 0) {
|
||||
return res.status(400).json({
|
||||
success: false,
|
||||
message: "You have already reviewed this product",
|
||||
});
|
||||
}
|
||||
|
||||
// Check if user is trying to review their own product
|
||||
const [productOwner] = await db.execute(
|
||||
`SELECT UserID FROM Product WHERE ProductID = ?`,
|
||||
[productId],
|
||||
);
|
||||
|
||||
if (productOwner.length > 0 && productOwner[0].UserID === userId) {
|
||||
return res.status(400).json({
|
||||
success: false,
|
||||
message: "You cannot review your own product",
|
||||
});
|
||||
}
|
||||
|
||||
// Insert the review into the database
|
||||
const [result] = await db.execute(
|
||||
`INSERT INTO Review (
|
||||
ProductID,
|
||||
UserID,
|
||||
Rating,
|
||||
Comment,
|
||||
Date
|
||||
) VALUES (?, ?, ?, ?, NOW())`,
|
||||
[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
|
||||
R.ReviewID,
|
||||
R.ProductID,
|
||||
R.UserID,
|
||||
R.Rating,
|
||||
R.Comment,
|
||||
R.Date AS ReviewDate,
|
||||
U.Name AS ReviewerName,
|
||||
P.Name AS ProductName
|
||||
FROM Review R
|
||||
JOIN User U ON R.UserID = U.UserID
|
||||
JOIN Product P ON R.ProductID = P.ProductID
|
||||
WHERE R.ReviewID = ?`,
|
||||
[reviewId],
|
||||
);
|
||||
|
||||
res.status(201).json({
|
||||
success: true, // Fixed from false to 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,
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
// /**
|
||||
// * Update an existing review
|
||||
// */
|
||||
// exports.updateReview = async (req, res) => {
|
||||
// const { reviewId } = req.params;
|
||||
// const { rating, comment } = req.body;
|
||||
// const userId = req.body.userId; // Assuming you have middleware that validates the user
|
||||
|
||||
// // Validate required fields
|
||||
// if (!reviewId || !rating || !comment) {
|
||||
// return res.status(400).json({
|
||||
// success: false,
|
||||
// message: "Missing required fields",
|
||||
// });
|
||||
// }
|
||||
|
||||
// // Validate rating is between 1 and 5
|
||||
// if (rating < 1 || rating > 5) {
|
||||
// return res.status(400).json({
|
||||
// success: false,
|
||||
// message: "Rating must be between 1 and 5",
|
||||
// });
|
||||
// }
|
||||
|
||||
// try {
|
||||
// // Check if review exists and belongs to the user
|
||||
// const [existingReview] = await db.execute(
|
||||
// `SELECT ReviewID, UserID FROM Review WHERE ReviewID = ?`,
|
||||
// [reviewId],
|
||||
// );
|
||||
|
||||
// if (existingReview.length === 0) {
|
||||
// return res.status(404).json({
|
||||
// success: false,
|
||||
// message: "Review not found",
|
||||
// });
|
||||
// }
|
||||
|
||||
// if (existingReview[0].UserID !== userId) {
|
||||
// return res.status(403).json({
|
||||
// success: false,
|
||||
// message: "You can only update your own reviews",
|
||||
// });
|
||||
// }
|
||||
|
||||
// // Update the review
|
||||
// await db.execute(
|
||||
// `UPDATE Review
|
||||
// SET Rating = ?, Comment = ?, Date = NOW()
|
||||
// WHERE ReviewID = ?`,
|
||||
// [rating, comment, reviewId],
|
||||
// );
|
||||
|
||||
// // Fetch the updated review
|
||||
// const [updatedReview] = await db.execute(
|
||||
// `SELECT
|
||||
// R.ReviewID,
|
||||
// R.ProductID,
|
||||
// R.UserID,
|
||||
// R.Rating,
|
||||
// R.Comment,
|
||||
// R.Date AS ReviewDate,
|
||||
// U.Name AS ReviewerName,
|
||||
// P.Name AS ProductName
|
||||
// FROM Review R
|
||||
// JOIN User U ON R.UserID = U.UserID
|
||||
// JOIN Product P ON R.ProductID = P.ProductID
|
||||
// WHERE R.ReviewID = ?`,
|
||||
// [reviewId],
|
||||
// );
|
||||
|
||||
// res.json({
|
||||
// success: true,
|
||||
// message: "Review updated successfully",
|
||||
// data: updatedReview[0],
|
||||
// });
|
||||
// } catch (error) {
|
||||
// console.error("Error updating review:", error);
|
||||
// return res.status(500).json({
|
||||
// success: false,
|
||||
// message: "Database error occurred",
|
||||
// error: error.message,
|
||||
// });
|
||||
// }
|
||||
// };
|
||||
|
||||
// /**
|
||||
// * Delete a review
|
||||
// */
|
||||
// exports.deleteReview = async (req, res) => {
|
||||
// const { reviewId } = req.params;
|
||||
// const userId = req.body.userId; // Assuming you have middleware that validates the user
|
||||
|
||||
// try {
|
||||
// // Check if review exists and belongs to the user
|
||||
// const [existingReview] = await db.execute(
|
||||
// `SELECT ReviewID, UserID FROM Review WHERE ReviewID = ?`,
|
||||
// [reviewId],
|
||||
// );
|
||||
|
||||
// if (existingReview.length === 0) {
|
||||
// return res.status(404).json({
|
||||
// success: false,
|
||||
// message: "Review not found",
|
||||
// });
|
||||
// }
|
||||
|
||||
// if (existingReview[0].UserID !== userId) {
|
||||
// return res.status(403).json({
|
||||
// success: false,
|
||||
// message: "You can only delete your own reviews",
|
||||
// });
|
||||
// }
|
||||
|
||||
// // Delete the review
|
||||
// await db.execute(`DELETE FROM Review WHERE ReviewID = ?`, [reviewId]);
|
||||
|
||||
// res.json({
|
||||
// success: true,
|
||||
// message: "Review deleted successfully",
|
||||
// });
|
||||
// } catch (error) {
|
||||
// console.error("Error deleting review:", error);
|
||||
// return res.status(500).json({
|
||||
// success: false,
|
||||
// message: "Database error occurred",
|
||||
// error: error.message,
|
||||
// });
|
||||
// }
|
||||
// };
|
||||
164
backend/controllers/search.js
Normal file
164
backend/controllers/search.js
Normal file
@@ -0,0 +1,164 @@
|
||||
const db = require("../utils/database");
|
||||
|
||||
exports.searchProductsByName = async (req, res) => {
|
||||
const { name } = req.query;
|
||||
|
||||
if (name.length === 0) {
|
||||
console.log("Searching for products with no name", name);
|
||||
}
|
||||
|
||||
console.log("Searching for products with name:", name);
|
||||
|
||||
try {
|
||||
// Modify SQL to return all products when no search term is provided
|
||||
const sql = `
|
||||
SELECT p.*, i.URL as image
|
||||
FROM Product p
|
||||
LEFT JOIN Image_URL i ON p.ProductID = i.ProductID
|
||||
${name ? "WHERE p.Name LIKE ?" : ""}
|
||||
ORDER BY p.ProductID
|
||||
`;
|
||||
|
||||
const params = name ? [`%${name}%`] : [];
|
||||
console.log("Executing SQL:", sql);
|
||||
console.log("With parameters:", params);
|
||||
|
||||
const [data] = await db.execute(sql, params);
|
||||
|
||||
console.log("Raw Database Result:", data);
|
||||
|
||||
if (data.length === 0) {
|
||||
console.log("No products found matching:", name);
|
||||
return res.status(404).json({
|
||||
success: false,
|
||||
message: "No products found matching your search",
|
||||
});
|
||||
}
|
||||
|
||||
// Group products by ProductID to handle multiple images per product
|
||||
const productsMap = new Map();
|
||||
|
||||
data.forEach((row) => {
|
||||
if (!productsMap.has(row.ProductID)) {
|
||||
const product = {
|
||||
ProductID: row.ProductID,
|
||||
Name: row.Name,
|
||||
Description: row.Description,
|
||||
Price: row.Price,
|
||||
images: row.image,
|
||||
};
|
||||
productsMap.set(row.ProductID, product);
|
||||
} else if (row.image_url) {
|
||||
productsMap.get(row.ProductID).images.push(row.image_url);
|
||||
}
|
||||
});
|
||||
|
||||
const products = Array.from(productsMap.values());
|
||||
|
||||
console.log("Processed Products:", products);
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
message: "Products fetched successfully",
|
||||
data: products,
|
||||
count: products.length,
|
||||
});
|
||||
} catch (error) {
|
||||
console.error("Database Error:", error);
|
||||
return res.status(500).json({
|
||||
success: false,
|
||||
message: "Database error occurred",
|
||||
error: error.message || "Unknown database error",
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
// exports.searchProductsByName = async (req, res) => {
|
||||
// const { name } = req.query;
|
||||
|
||||
// // Add better validation and error handling
|
||||
// if (!name || typeof name !== "string") {
|
||||
// return res.status(400).json({
|
||||
// success: false,
|
||||
// message: "Valid search term is required",
|
||||
// });
|
||||
// }
|
||||
|
||||
// console.log("Searching for products with name:", name);
|
||||
|
||||
// try {
|
||||
// // Log the SQL query and parameters for debugging
|
||||
// const sql = `
|
||||
// SELECT p.*, i.URL AS image_url
|
||||
// FROM Product p
|
||||
// LEFT JOIN Image_URL i ON p.ProductID = i.ProductID
|
||||
// WHERE p.Name LIKE ?
|
||||
// `;
|
||||
// const params = [`%${name}%`];
|
||||
// console.log("Executing SQL:", sql);
|
||||
// console.log("With parameters:", params);
|
||||
|
||||
// const [data] = await db.execute(sql, params);
|
||||
|
||||
// // Log raw data for debugging
|
||||
// console.log("Raw Database Result:", data);
|
||||
|
||||
// if (data.length === 0) {
|
||||
// console.log("No products found matching:", name);
|
||||
// return res.status(404).json({
|
||||
// success: false,
|
||||
// message: "No products found matching your search",
|
||||
// });
|
||||
// }
|
||||
|
||||
// // Group products by ProductID to handle multiple images per product
|
||||
// const productsMap = new Map();
|
||||
|
||||
// data.forEach((row) => {
|
||||
// if (!productsMap.has(row.ProductID)) {
|
||||
// // Create a clean object without circular references
|
||||
// const product = {
|
||||
// ProductID: row.ProductID,
|
||||
// Name: row.Name,
|
||||
// Description: row.Description,
|
||||
// Price: row.Price,
|
||||
// // Add any other product fields you need
|
||||
// images: row.image_url ? [row.image_url] : [],
|
||||
// };
|
||||
// productsMap.set(row.ProductID, product);
|
||||
// } else if (row.image_url) {
|
||||
// // Add additional image to existing product
|
||||
// productsMap.get(row.ProductID).images.push(row.image_url);
|
||||
// }
|
||||
// });
|
||||
|
||||
// // Convert map to array of products
|
||||
// const products = Array.from(productsMap.values());
|
||||
|
||||
// // Log processed products for debugging
|
||||
// console.log("Processed Products:", products);
|
||||
|
||||
// res.json({
|
||||
// success: true,
|
||||
// message: "Products fetched successfully",
|
||||
// data: products,
|
||||
// count: products.length,
|
||||
// });
|
||||
// } catch (error) {
|
||||
// // Enhanced error logging
|
||||
// console.error("Database Error Details:", {
|
||||
// message: error.message,
|
||||
// code: error.code,
|
||||
// errno: error.errno,
|
||||
// sqlState: error.sqlState,
|
||||
// sqlMessage: error.sqlMessage,
|
||||
// sql: error.sql,
|
||||
// });
|
||||
|
||||
// return res.status(500).json({
|
||||
// success: false,
|
||||
// message: "Database error occurred",
|
||||
// error: error.message || "Unknown database error",
|
||||
// });
|
||||
// }
|
||||
// };
|
||||
308
backend/controllers/user.js
Normal file
308
backend/controllers/user.js
Normal file
@@ -0,0 +1,308 @@
|
||||
const crypto = require("crypto");
|
||||
const db = require("../utils/database");
|
||||
const { sendVerificationEmail } = require("../utils/helper");
|
||||
|
||||
exports.sendVerificationCode = async (req, res) => {
|
||||
const { email } = req.body;
|
||||
|
||||
if (!email) {
|
||||
return res.status(400).json({ error: "Email is required" });
|
||||
}
|
||||
|
||||
try {
|
||||
// Generate a random 6-digit code
|
||||
const verificationCode = crypto.randomInt(100000, 999999).toString();
|
||||
console.log(
|
||||
`Generated verification code for ${email}: ${verificationCode}`
|
||||
);
|
||||
|
||||
// Check if email already exists in verification table
|
||||
const [results, fields] = await db.execute(
|
||||
"SELECT * FROM AuthVerification WHERE Email = ?",
|
||||
[email]
|
||||
);
|
||||
|
||||
if (results.length > 0) {
|
||||
// Update existing record
|
||||
const [result] = await db.execute(
|
||||
`UPDATE AuthVerification SET VerificationCode = ?, Authenticated = FALSE, Date = CURRENT_TIMESTAMP
|
||||
WHERE Email = ?`,
|
||||
[verificationCode, email]
|
||||
);
|
||||
|
||||
// Send email and respond
|
||||
await sendVerificationEmail(email, verificationCode);
|
||||
res.json({ success: true, message: "Verification code sent" });
|
||||
} else {
|
||||
// Insert new record
|
||||
const [result] = await db.execute(
|
||||
"INSERT INTO AuthVerification (Email, VerificationCode, Authenticated) VALUES (?, ?, FALSE)",
|
||||
[email, verificationCode]
|
||||
);
|
||||
// Send email and respond
|
||||
await sendVerificationEmail(email, verificationCode);
|
||||
res.json({ success: true, message: "Verification code sent" });
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Error:", error);
|
||||
res.status(500).json({ error: "Server error" });
|
||||
}
|
||||
};
|
||||
|
||||
exports.verifyCode = async (req, res) => {
|
||||
const { email, code } = req.body;
|
||||
|
||||
if (!email || !code) {
|
||||
return res.status(400).json({ error: "Email and code are required" });
|
||||
}
|
||||
|
||||
console.log(`Attempting to verify code for ${email}: ${code}`);
|
||||
|
||||
try {
|
||||
// Check verification code
|
||||
const [results, fields] = await db.execute(
|
||||
"SELECT * FROM AuthVerification WHERE Email = ? AND VerificationCode = ? AND Authenticated = 0 AND Date > DATE_SUB(NOW(), INTERVAL 15 MINUTE)",
|
||||
[email, code]
|
||||
);
|
||||
if (results.length === 0) {
|
||||
console.log(`Invalid or expired verification code for email ${email}`);
|
||||
return res
|
||||
.status(400)
|
||||
.json({ error: "Invalid or expired verification code" });
|
||||
}
|
||||
|
||||
const userId = results[0].UserID;
|
||||
|
||||
// Mark as authenticated
|
||||
const [result] = await db.execute(
|
||||
"UPDATE AuthVerification SET Authenticated = TRUE WHERE Email = ?",
|
||||
[email]
|
||||
);
|
||||
res.json({
|
||||
success: true,
|
||||
message: "Verification successful",
|
||||
userId,
|
||||
});
|
||||
} catch (error) {
|
||||
console.log("Error: ", error);
|
||||
res.status(500).json({ error: "Database error!" });
|
||||
}
|
||||
};
|
||||
|
||||
exports.completeSignUp = async (req, res) => {
|
||||
const data = req.body;
|
||||
|
||||
try {
|
||||
const [results, fields] = await db.execute(
|
||||
`SELECT * FROM AuthVerification WHERE Email = ? AND Authenticated = 1;`,
|
||||
[data.email]
|
||||
);
|
||||
|
||||
if (results.length === 0) {
|
||||
return res.status(400).json({ error: "Email not verified" });
|
||||
}
|
||||
|
||||
// Create the user
|
||||
const [createResult] = await db.execute(
|
||||
`INSERT INTO User (Name, Email, UCID, Password, Phone, Address)
|
||||
VALUES ('${data.name}', '${data.email}', '${data.UCID}', '${data.password}', '${data.phone}', '${data.address}')`
|
||||
);
|
||||
|
||||
// Insert role using the user's ID
|
||||
const [insertResult] = await db.execute(
|
||||
`INSERT INTO UserRole (UserID, Client, Admin)
|
||||
VALUES (LAST_INSERT_ID(), ${data.client || true}, ${
|
||||
data.admin || false
|
||||
})`
|
||||
);
|
||||
|
||||
// Delete verification record
|
||||
const [deleteResult] = await db.execute(
|
||||
`DELETE FROM AuthVerification WHERE Email = '${data.email}'`
|
||||
);
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
message: "User registration completed successfully",
|
||||
name: data.name,
|
||||
email: data.email,
|
||||
UCID: data.UCID,
|
||||
});
|
||||
} catch (error) {
|
||||
console.log("Error: ", error);
|
||||
res.status(500).json({ error: "Database error!" });
|
||||
}
|
||||
};
|
||||
|
||||
exports.getAllUser = async (req, res) => {
|
||||
try {
|
||||
const [users, fields] = await db.execute("SELECT * FROM User;");
|
||||
res.json({ users });
|
||||
} catch (error) {
|
||||
console.error("Errors: ", error);
|
||||
return res.status(500).json({ error: "\nCould not fetch users!" });
|
||||
}
|
||||
};
|
||||
|
||||
exports.findUserByEmail = async (req, res) => {
|
||||
const { email } = req.body;
|
||||
|
||||
// Input validation
|
||||
if (!email) {
|
||||
return res.status(400).json({
|
||||
found: false,
|
||||
error: "Email is required",
|
||||
});
|
||||
}
|
||||
|
||||
try {
|
||||
// Query to find user with matching email and password
|
||||
const query = "SELECT * FROM User WHERE email = ?";
|
||||
const [data, fields] = await db.execute(query, [email]);
|
||||
|
||||
// Check if user was found
|
||||
if (data && data.length > 0) {
|
||||
console.log(data);
|
||||
const user = data[0];
|
||||
|
||||
// Return all user data except password
|
||||
return res.json({
|
||||
found: true,
|
||||
userID: user.UserID,
|
||||
name: user.Name,
|
||||
email: user.Email,
|
||||
UCID: user.UCID,
|
||||
phone: user.Phone,
|
||||
address: user.Address,
|
||||
// Include any other fields your user might have
|
||||
// Make sure the field names match exactly with your database column names
|
||||
});
|
||||
} else {
|
||||
// User not found or invalid credentials
|
||||
return res.json({
|
||||
found: false,
|
||||
error: "Invalid email or password",
|
||||
});
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Error finding user:", error);
|
||||
return res.status(500).json({
|
||||
found: false,
|
||||
error: "Database error occurred",
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
exports.updateUser = async (req, res) => {
|
||||
try {
|
||||
const userId = req.body?.userId;
|
||||
const name = req.body?.name;
|
||||
const email = req.body?.email;
|
||||
const phone = req.body?.phone;
|
||||
const UCID = req.body?.UCID;
|
||||
const address = req.body?.address;
|
||||
|
||||
if (!userId) {
|
||||
return res.status(400).json({ error: "User ID is required" });
|
||||
}
|
||||
|
||||
// Build updateData manually
|
||||
const updateData = {};
|
||||
if (name) updateData.name = name;
|
||||
if (email) updateData.email = email;
|
||||
if (phone) updateData.phone = phone;
|
||||
if (UCID) updateData.UCID = UCID;
|
||||
if (address) updateData.address = address;
|
||||
|
||||
if (Object.keys(updateData).length === 0) {
|
||||
return res.status(400).json({ error: "No valid fields to update" });
|
||||
}
|
||||
|
||||
const updateFields = [];
|
||||
const values = [];
|
||||
|
||||
Object.entries(updateData).forEach(([key, value]) => {
|
||||
updateFields.push(`${key} = ?`);
|
||||
values.push(value);
|
||||
});
|
||||
|
||||
values.push(userId);
|
||||
|
||||
const query = `UPDATE User SET ${updateFields.join(", ")} WHERE userId = ?`;
|
||||
const [updateResult] = await db.execute(query, values);
|
||||
|
||||
if (updateResult.affectedRows === 0) {
|
||||
return res.status(404).json({ error: "User not found" });
|
||||
}
|
||||
|
||||
res.json({ success: true, message: "User updated successfully" });
|
||||
} catch (error) {
|
||||
console.error("Error updating user:", error);
|
||||
return res.status(500).json({ error: "Could not update user" });
|
||||
}
|
||||
};
|
||||
|
||||
exports.deleteUser = async (req, res) => {
|
||||
const { userId } = req.body;
|
||||
|
||||
if (!userId) {
|
||||
return res.status(400).json({ error: "User ID is required" });
|
||||
}
|
||||
|
||||
try {
|
||||
// Delete from UserRole first (assuming foreign key constraint)
|
||||
const [result1] = await db.execute(
|
||||
"DELETE FROM UserRole WHERE UserID = ?",
|
||||
[userId]
|
||||
);
|
||||
|
||||
// Then delete from User table
|
||||
const [result2] = await db.execute("DELETE FROM User WHERE UserID = ?", [
|
||||
userId,
|
||||
]);
|
||||
|
||||
if (result2.affectedRows === 0) {
|
||||
return res.status(404).json({ error: "User not found" });
|
||||
}
|
||||
|
||||
res.json({ success: true, message: "User deleted successfully" });
|
||||
} catch (error) {
|
||||
console.error("Error: ", error);
|
||||
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!" });
|
||||
}
|
||||
};
|
||||
56
backend/index.js
Normal file
56
backend/index.js
Normal file
@@ -0,0 +1,56 @@
|
||||
const express = require("express");
|
||||
const cors = require("cors");
|
||||
|
||||
const db = require("./utils/database");
|
||||
|
||||
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 categoryRouter = require("./routes/category");
|
||||
|
||||
const { generateEmailTransporter } = require("./utils/mail");
|
||||
const {
|
||||
// cleanupExpiredCodes,
|
||||
checkDatabaseConnection,
|
||||
} = require("./utils/helper");
|
||||
|
||||
const app = express();
|
||||
|
||||
app.use(cors());
|
||||
app.use(express.json());
|
||||
|
||||
// Configure email transporter for Zoho
|
||||
const transporter = generateEmailTransporter();
|
||||
// Test the email connection
|
||||
transporter
|
||||
.verify()
|
||||
.then(() => {
|
||||
console.log("Email connection successful!");
|
||||
})
|
||||
.catch((error) => {
|
||||
console.error("Email connection failed:", error);
|
||||
});
|
||||
|
||||
checkDatabaseConnection(db);
|
||||
|
||||
//Routes
|
||||
app.use("/api/user", userRouter);
|
||||
app.use("/api/product", productRouter);
|
||||
app.use("/api/search", searchRouter);
|
||||
app.use("/api/engine", recommendedRouter);
|
||||
app.use("/api/history", history);
|
||||
app.use("/api/review", review);
|
||||
app.use("/api/category", categoryRouter);
|
||||
|
||||
// Set up a scheduler to run cleanup every hour
|
||||
// clean_up_time = 30*60*1000;
|
||||
// setInterval(cleanupExpiredCodes, clean_up_time);
|
||||
|
||||
app.listen(3030, () => {
|
||||
console.log(`Running Backend on http://localhost:3030/`);
|
||||
console.log(`Send verification code: POST /send-verification`);
|
||||
console.log(`Verify code: POST /verify-code`);
|
||||
});
|
||||
1592
backend/package-lock.json
generated
Normal file
1592
backend/package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
28
backend/package.json
Normal file
28
backend/package.json
Normal file
@@ -0,0 +1,28 @@
|
||||
{
|
||||
"name": "server",
|
||||
"version": "1.0.0",
|
||||
"main": "index.js",
|
||||
"type": "commonjs",
|
||||
"scripts": {
|
||||
"test": "echo \"Error: no test specified\" && exit 1",
|
||||
"dev": "nodemon index.js"
|
||||
},
|
||||
"keywords": [],
|
||||
"author": "",
|
||||
"license": "ISC",
|
||||
"description": "",
|
||||
"dependencies": {
|
||||
"body-parser": "^1.20.3",
|
||||
"cors": "^2.8.5",
|
||||
"crypto": "^1.0.1",
|
||||
"dotenv": "^16.4.7",
|
||||
"express": "^4.21.2",
|
||||
"jsonwebtoken": "^9.0.2",
|
||||
"mysql": "^2.18.1",
|
||||
"mysql2": "^3.12.0",
|
||||
"nodemailer": "^6.10.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"nodemon": "^3.1.9"
|
||||
}
|
||||
}
|
||||
14
backend/routes/category.js
Normal file
14
backend/routes/category.js
Normal 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;
|
||||
14
backend/routes/history.js
Normal file
14
backend/routes/history.js
Normal file
@@ -0,0 +1,14 @@
|
||||
// routes/product.js
|
||||
const express = require("express");
|
||||
const {
|
||||
HistoryByUserId,
|
||||
DelHistory,
|
||||
AddHistory,
|
||||
} = require("../controllers/history");
|
||||
const router = express.Router();
|
||||
|
||||
router.post("/getHistory", HistoryByUserId);
|
||||
router.post("/delHistory", DelHistory);
|
||||
router.post("/addHistory", AddHistory);
|
||||
|
||||
module.exports = router;
|
||||
34
backend/routes/product.js
Normal file
34
backend/routes/product.js
Normal file
@@ -0,0 +1,34 @@
|
||||
// routes/product.js
|
||||
const express = require("express");
|
||||
const {
|
||||
addFavorite,
|
||||
getFavorites,
|
||||
removeFavorite,
|
||||
getAllProducts,
|
||||
getProductById,
|
||||
getProductWithPagination,
|
||||
removeProduct,
|
||||
} = require("../controllers/product");
|
||||
const router = express.Router();
|
||||
|
||||
// Add detailed logging middleware
|
||||
router.use((req, res, next) => {
|
||||
console.log(`Incoming ${req.method} request to ${req.path}`);
|
||||
next();
|
||||
});
|
||||
|
||||
router.post("/addFavorite", addFavorite);
|
||||
router.post("/getFavorites", getFavorites);
|
||||
router.post("/delFavorite", removeFavorite);
|
||||
|
||||
router.get("/getProduct", getAllProducts);
|
||||
|
||||
//Get products with pagination
|
||||
router.get("/getProductWithPagination", getProductWithPagination);
|
||||
|
||||
router.get("/:id", getProductById); // Simplified route
|
||||
|
||||
//Remove product
|
||||
router.delete("/:id", removeProduct);
|
||||
|
||||
module.exports = router;
|
||||
8
backend/routes/recommendation.js
Normal file
8
backend/routes/recommendation.js
Normal file
@@ -0,0 +1,8 @@
|
||||
// routes/product.js
|
||||
const express = require("express");
|
||||
const { RecommondationByUserId } = require("../controllers/recommendation");
|
||||
const router = express.Router();
|
||||
|
||||
router.post("/recommended", RecommondationByUserId);
|
||||
|
||||
module.exports = router;
|
||||
9
backend/routes/review.js
Normal file
9
backend/routes/review.js
Normal file
@@ -0,0 +1,9 @@
|
||||
// routes/product.js
|
||||
const express = require("express");
|
||||
const { getReviews, submitReview } = require("../controllers/review");
|
||||
const router = express.Router();
|
||||
|
||||
router.get("/:id", getReviews);
|
||||
router.post("/add", submitReview);
|
||||
|
||||
module.exports = router;
|
||||
14
backend/routes/search.js
Normal file
14
backend/routes/search.js
Normal file
@@ -0,0 +1,14 @@
|
||||
// routes/product.js
|
||||
const express = require("express");
|
||||
const { searchProductsByName } = require("../controllers/search");
|
||||
const router = express.Router();
|
||||
|
||||
// Add detailed logging middleware
|
||||
router.use((req, res, next) => {
|
||||
console.log(`Incoming ${req.method} request to ${req.path}`);
|
||||
next();
|
||||
});
|
||||
|
||||
router.get("/getProduct", searchProductsByName);
|
||||
|
||||
module.exports = router;
|
||||
42
backend/routes/user.js
Normal file
42
backend/routes/user.js
Normal file
@@ -0,0 +1,42 @@
|
||||
const express = require("express");
|
||||
const {
|
||||
sendVerificationCode,
|
||||
verifyCode,
|
||||
completeSignUp,
|
||||
getAllUser,
|
||||
findUserByEmail,
|
||||
updateUser,
|
||||
deleteUser,
|
||||
getUsersWithPagination,
|
||||
isAdmin,
|
||||
} = require("../controllers/user");
|
||||
|
||||
const router = express.Router();
|
||||
|
||||
// Generate and send verification code for signup
|
||||
router.post("/send-verification", sendVerificationCode);
|
||||
|
||||
// Verify the code
|
||||
router.post("/verify-code", verifyCode);
|
||||
|
||||
// Create a users and Complete user registration after verification
|
||||
router.post("/complete-signup", completeSignUp);
|
||||
|
||||
//Fetch all users data:
|
||||
router.get("/fetch_all_users", getAllUser);
|
||||
|
||||
//Fetch user with pagination
|
||||
router.get("/getUserWithPagination", getUsersWithPagination);
|
||||
|
||||
//Fetch One user Data with all fields:
|
||||
router.post("/find_user", findUserByEmail);
|
||||
|
||||
//Update A uses Data:
|
||||
router.post("/update", updateUser);
|
||||
|
||||
//Delete A uses Data:
|
||||
router.post("/delete", deleteUser);
|
||||
|
||||
router.get("/isAdmin/:id", isAdmin);
|
||||
|
||||
module.exports = router;
|
||||
12
backend/utils/database.js
Normal file
12
backend/utils/database.js
Normal file
@@ -0,0 +1,12 @@
|
||||
const mysql = require("mysql2");
|
||||
|
||||
//Create a pool of connections to allow multiple query happen at the same time
|
||||
const pool = mysql.createPool({
|
||||
host: "localhost",
|
||||
user: "root",
|
||||
database: "Marketplace",
|
||||
password: "12345678",
|
||||
});
|
||||
|
||||
//Export a promise for promise-based query
|
||||
module.exports = pool.promise();
|
||||
49
backend/utils/helper.js
Normal file
49
backend/utils/helper.js
Normal file
@@ -0,0 +1,49 @@
|
||||
const { generateEmailTransporter } = require("./mail");
|
||||
const db = require("./database");
|
||||
|
||||
// Helper function to send email
|
||||
async function sendVerificationEmail(email, verificationCode) {
|
||||
const transporter = generateEmailTransporter();
|
||||
|
||||
// Send the email with Zoho
|
||||
await transporter.sendMail({
|
||||
from: "campusplug@zohomailcloud.ca",
|
||||
to: email,
|
||||
subject: "Campus Plug: Signup Verification Code",
|
||||
text: `Your verification code is: ${verificationCode}. This code will expire in 15 minutes.`,
|
||||
html: `<p>Your verification code is: <strong>${verificationCode}</strong></p><p>This code will expire in 15 minutes.</p>`,
|
||||
});
|
||||
|
||||
console.log(`Verification code sent to ${email}`);
|
||||
}
|
||||
|
||||
// Clean up expired verification codes (run this periodically)
|
||||
// function cleanupExpiredCodes() {
|
||||
// db.query(
|
||||
// "DELETE FROM AuthVerification WHERE Date < DATE_SUB(NOW(), INTERVAL 15 MINUTE) AND Authenticated = 0",
|
||||
// (err, result) => {
|
||||
// if (err) {
|
||||
// console.error("Error cleaning up expired codes:", err);
|
||||
// } else {
|
||||
// console.log(`Cleaned up ${result} expired verification codes`);
|
||||
// }
|
||||
// }
|
||||
// );
|
||||
// }
|
||||
|
||||
const checkDatabaseConnection = async (db) => {
|
||||
try {
|
||||
const connection = await db.getConnection();
|
||||
//If no error, release the connection
|
||||
console.log("Connect to database successfully!");
|
||||
connection.release();
|
||||
} catch (error) {
|
||||
console.log("Cannot connect to database: ", error.sqlMessage);
|
||||
}
|
||||
};
|
||||
|
||||
module.exports = {
|
||||
sendVerificationEmail,
|
||||
// cleanupExpiredCodes,
|
||||
checkDatabaseConnection,
|
||||
};
|
||||
12
backend/utils/mail.js
Normal file
12
backend/utils/mail.js
Normal file
@@ -0,0 +1,12 @@
|
||||
const nodemailer = require("nodemailer");
|
||||
|
||||
exports.generateEmailTransporter = () =>
|
||||
nodemailer.createTransport({
|
||||
host: "smtp.zohocloud.ca",
|
||||
secure: true,
|
||||
port: 465,
|
||||
auth: {
|
||||
user: "campusplug@zohomailcloud.ca", //Zoho email
|
||||
pass: "e0YRrNSeJZQd", //Zoho password
|
||||
},
|
||||
});
|
||||
1
frontend
Submodule
1
frontend
Submodule
Submodule frontend added at 99cc24f229
456
mysql-code/Init-Data.sql
Normal file
456
mysql-code/Init-Data.sql
Normal file
@@ -0,0 +1,456 @@
|
||||
-- Inserting sample data into the Marketplace database
|
||||
SET
|
||||
FOREIGN_KEY_CHECKS = 0;
|
||||
|
||||
TRUNCATE TABLE Product_Category;
|
||||
|
||||
TRUNCATE TABLE Favorites;
|
||||
|
||||
TRUNCATE TABLE History;
|
||||
|
||||
TRUNCATE TABLE Recommendation;
|
||||
|
||||
TRUNCATE TABLE Transaction;
|
||||
|
||||
TRUNCATE TABLE Review;
|
||||
|
||||
TRUNCATE TABLE Image_URL;
|
||||
|
||||
TRUNCATE TABLE Product;
|
||||
|
||||
TRUNCATE TABLE Category;
|
||||
|
||||
TRUNCATE TABLE UserRole;
|
||||
|
||||
TRUNCATE TABLE User;
|
||||
|
||||
TRUNCATE TABLE AuthVerification;
|
||||
|
||||
SET
|
||||
FOREIGN_KEY_CHECKS = 1;
|
||||
|
||||
-- Insert Users
|
||||
INSERT INTO
|
||||
User (
|
||||
UserID,
|
||||
Name,
|
||||
Email,
|
||||
UCID,
|
||||
Password,
|
||||
Phone,
|
||||
Address
|
||||
)
|
||||
VALUES
|
||||
(
|
||||
1,
|
||||
'John Doe',
|
||||
'john.doe@example.com',
|
||||
'U123456',
|
||||
'hashedpassword1',
|
||||
'555-123-4567',
|
||||
'123 Main St, Calgary, AB'
|
||||
),
|
||||
(
|
||||
2,
|
||||
'Jane Smith',
|
||||
'jane.smith@example.com',
|
||||
'U234567',
|
||||
'hashedpassword2',
|
||||
'555-234-5678',
|
||||
'456 Oak Ave, Calgary, AB'
|
||||
);
|
||||
|
||||
-- Insert User Roles
|
||||
INSERT INTO
|
||||
UserRole (UserID, Client, Admin)
|
||||
VALUES
|
||||
(1, TRUE, TRUE),
|
||||
(2, TRUE, FALSE);
|
||||
|
||||
-- Insert Categories
|
||||
INSERT INTO
|
||||
Category (Name)
|
||||
VALUES
|
||||
('Textbooks'),
|
||||
('Electronics'),
|
||||
('Furniture'),
|
||||
('Clothing'),
|
||||
('Sports Equipment'),
|
||||
('Musical Instruments'),
|
||||
('Art Supplies'),
|
||||
('Kitchen Appliances'),
|
||||
('Gaming'),
|
||||
( 'Bicycles'),
|
||||
( 'Computer Accessories'),
|
||||
( 'Stationery'),
|
||||
( 'Fitness Equipment'),
|
||||
( 'Winter Sports'),
|
||||
( 'Lab Equipment'),
|
||||
( 'Camping Gear'),
|
||||
( 'School Supplies'),
|
||||
( 'Office Furniture'),
|
||||
( 'Books (-textbook)'),
|
||||
( 'Math & Science Resources'),
|
||||
( 'Engineering Tools'),
|
||||
( 'Backpacks & Bags'),
|
||||
( 'Audio Equipment'),
|
||||
( 'Dorm Essentials'),
|
||||
( 'Smartphones & Tablets'),
|
||||
( 'Winter Clothing'),
|
||||
( 'Photography Equipment'),
|
||||
( 'Event Tickets'),
|
||||
( 'Software Licenses'),
|
||||
( 'Transportation (Car Pool)');
|
||||
|
||||
-- Insert Products
|
||||
INSERT INTO
|
||||
Product (
|
||||
ProductID,
|
||||
Name,
|
||||
Price,
|
||||
StockQuantity,
|
||||
UserID,
|
||||
Description,
|
||||
CategoryID,
|
||||
Date
|
||||
)
|
||||
VALUES
|
||||
(
|
||||
1,
|
||||
'Calculus Textbook 8th Edition',
|
||||
79.99,
|
||||
5,
|
||||
1,
|
||||
'Like new calculus textbook, minor highlighting',
|
||||
1,
|
||||
'2024-10-15 10:00:00'
|
||||
),
|
||||
(
|
||||
2,
|
||||
'HP Laptop',
|
||||
699.99,
|
||||
1,
|
||||
1,
|
||||
'2023 HP Pavilion, 16GB RAM, 512GB SSD',
|
||||
2,
|
||||
'2024-10-10 14:30:00'
|
||||
),
|
||||
(
|
||||
3,
|
||||
'Dorm Desk',
|
||||
120.00,
|
||||
1,
|
||||
2,
|
||||
'Sturdy desk perfect for studying, minor scratches',
|
||||
3,
|
||||
'2024-10-12 09:15:00'
|
||||
),
|
||||
(
|
||||
4,
|
||||
'University Hoodie',
|
||||
35.00,
|
||||
3,
|
||||
2,
|
||||
'Size L, university logo, worn twice',
|
||||
4,
|
||||
'2024-10-14 16:45:00'
|
||||
),
|
||||
(
|
||||
5,
|
||||
'Basketball',
|
||||
25.50,
|
||||
1,
|
||||
2,
|
||||
'Slightly used indoor basketball',
|
||||
5,
|
||||
'2024-10-11 11:20:00'
|
||||
),
|
||||
(
|
||||
6,
|
||||
'Acoustic Guitar',
|
||||
175.00,
|
||||
1,
|
||||
1,
|
||||
'Beginner acoustic guitar with case',
|
||||
6,
|
||||
'2024-10-09 13:10:00'
|
||||
),
|
||||
(
|
||||
7,
|
||||
'Physics Textbook',
|
||||
65.00,
|
||||
2,
|
||||
2,
|
||||
'University Physics 14th Edition, good condition',
|
||||
1,
|
||||
'2024-10-08 10:30:00'
|
||||
),
|
||||
(
|
||||
8,
|
||||
'Mini Fridge',
|
||||
85.00,
|
||||
1,
|
||||
1,
|
||||
'Small dorm fridge, works perfectly',
|
||||
8,
|
||||
'2024-10-13 15:00:00'
|
||||
),
|
||||
(
|
||||
9,
|
||||
'PlayStation 5 Controller',
|
||||
55.00,
|
||||
1,
|
||||
2,
|
||||
'Extra controller, barely used',
|
||||
9,
|
||||
'2024-10-07 17:20:00'
|
||||
),
|
||||
(
|
||||
10,
|
||||
'Mountain Bike',
|
||||
350.00,
|
||||
1,
|
||||
1,
|
||||
'Trek mountain bike, great condition, new tires',
|
||||
10,
|
||||
'2024-10-06 14:00:00'
|
||||
),
|
||||
(
|
||||
11,
|
||||
'Wireless Mouse',
|
||||
22.99,
|
||||
3,
|
||||
1,
|
||||
'Logitech wireless mouse with battery',
|
||||
11,
|
||||
'2024-10-05 09:30:00'
|
||||
),
|
||||
(
|
||||
12,
|
||||
'Chemistry Lab Coat',
|
||||
30.00,
|
||||
2,
|
||||
2,
|
||||
'Size M, required for chem labs',
|
||||
15,
|
||||
'2024-10-04 13:45:00'
|
||||
),
|
||||
(
|
||||
13,
|
||||
'Graphing Calculator',
|
||||
75.00,
|
||||
1,
|
||||
1,
|
||||
'TI-84 Plus, perfect working condition',
|
||||
12,
|
||||
'2024-10-03 11:15:00'
|
||||
),
|
||||
(
|
||||
14,
|
||||
'Yoga Mat',
|
||||
20.00,
|
||||
1,
|
||||
2,
|
||||
'Thick yoga mat, barely used',
|
||||
13,
|
||||
'2024-10-02 16:00:00'
|
||||
),
|
||||
(
|
||||
15,
|
||||
'Winter Jacket',
|
||||
120.00,
|
||||
1,
|
||||
1,
|
||||
'Columbia winter jacket, size XL, very warm',
|
||||
26,
|
||||
'2024-10-01 10:20:00'
|
||||
),
|
||||
(
|
||||
16,
|
||||
'Computer Science Textbook',
|
||||
70.00,
|
||||
1,
|
||||
2,
|
||||
'Introduction to Algorithms, like new',
|
||||
1,
|
||||
'2024-09-30 14:30:00'
|
||||
),
|
||||
(
|
||||
17,
|
||||
'Desk Lamp',
|
||||
15.00,
|
||||
2,
|
||||
2,
|
||||
'LED desk lamp with adjustable brightness',
|
||||
24,
|
||||
'2024-09-29 12:00:00'
|
||||
),
|
||||
(
|
||||
18,
|
||||
'Scientific Calculator',
|
||||
25.00,
|
||||
1,
|
||||
1,
|
||||
'Casio scientific calculator',
|
||||
12,
|
||||
'2024-09-28 11:30:00'
|
||||
),
|
||||
(
|
||||
19,
|
||||
'Bluetooth Speaker',
|
||||
45.00,
|
||||
1,
|
||||
1,
|
||||
'JBL Bluetooth speaker, great sound',
|
||||
23,
|
||||
'2024-09-27 15:45:00'
|
||||
),
|
||||
(
|
||||
20,
|
||||
'Backpack',
|
||||
40.00,
|
||||
1,
|
||||
2,
|
||||
'North Face backpack, lots of pockets',
|
||||
22,
|
||||
'2024-09-26 09:15:00'
|
||||
);
|
||||
|
||||
INSERT INTO
|
||||
Image_URL (URL, ProductID)
|
||||
VALUES
|
||||
('/Pictures/Dell1.jpg', 1),
|
||||
('/Pictures/Dell2.jpg', 1),
|
||||
('/Pictures/Dell3.jpg', 1),
|
||||
('/Pictures/HP-Laptop1.jpg', 2),
|
||||
('/Pictures/HP-Laptop1.jpg', 2),
|
||||
('/Pictures/Dorm-Desk.jpg', 3),
|
||||
('/Pictures/University-Hoodie.jpg', 4),
|
||||
('/Pictures/Basketball.jpg', 5),
|
||||
('/Pictures/Acoustic-Guitar.jpg', 6),
|
||||
('/Pictures/Physics-Textbook.jpg', 7),
|
||||
('/Pictures/Mini-Fridge.jpg', 8),
|
||||
('/Pictures/Controller.jpg', 9),
|
||||
('/Pictures/Mountain-Bike.jpg', 10),
|
||||
('/Pictures/Wireless-Mouse.jpg', 11),
|
||||
('/Pictures/Lab-Coat.jpg', 12),
|
||||
('/Pictures/Calculator.jpg', 13),
|
||||
('/Pictures/Yoga-Mat.jpg', 14),
|
||||
('/Pictures/Winter-Jacket.jpg', 15),
|
||||
('/Pictures/CS-Textbook.jpg', 16),
|
||||
('/Pictures/Desk-Lamp.jpg', 17),
|
||||
('/Pictures/HP-Calculator.jpg', 18),
|
||||
('/Pictures/Bluetooth-Speaker.jpg', 19),
|
||||
('/Pictures/Backpack.jpg', 20);
|
||||
|
||||
-- Insert Product-Category relationships (products with multiple categories)
|
||||
INSERT INTO
|
||||
Product_Category (ProductID, CategoryID)
|
||||
VALUES
|
||||
(1, 1),
|
||||
(1, 17),
|
||||
(1, 20), -- Calculus book: Textbooks, School Supplies, Math Resources
|
||||
(2, 2),
|
||||
(2, 11),
|
||||
(2, 25), -- Laptop: Electronics, Computer Accessories, Smartphones & Tablets
|
||||
(3, 3),
|
||||
(3, 18),
|
||||
(3, 24), -- Desk: Furniture, Office Furniture, Dorm Essentials
|
||||
(4, 4),
|
||||
(4, 26), -- Hoodie: Clothing, Winter Clothing
|
||||
(5, 5),
|
||||
(5, 13), -- Basketball: Sports Equipment, Fitness Equipment
|
||||
(6, 6),
|
||||
(6, 23), -- Guitar: Musical Instruments, Audio Equipment
|
||||
(7, 1),
|
||||
(7, 15),
|
||||
(7, 20), -- Physics book: Textbooks, Lab Equipment, Math & Science Resources
|
||||
(8, 8),
|
||||
(8, 24), -- Mini Fridge: Kitchen Appliances, Dorm Essentials
|
||||
(9, 9),
|
||||
(9, 2), -- PS5 Controller: Gaming, Electronics
|
||||
(10, 10),
|
||||
(10, 5),
|
||||
(10, 13), -- Mountain Bike: Bicycles, Sports Equipment, Fitness Equipment
|
||||
(11, 11),
|
||||
(11, 2), -- Mouse: Computer Accessories, Electronics
|
||||
(12, 15),
|
||||
(12, 17), -- Lab Coat: Lab Equipment, School Supplies
|
||||
(13, 12),
|
||||
(13, 17),
|
||||
(13, 20), -- Calculator: Stationery, School Supplies, Math & Science Resources
|
||||
(14, 13),
|
||||
(14, 5), -- Yoga Mat: Fitness Equipment, Sports Equipment
|
||||
(15, 26),
|
||||
(15, 4),
|
||||
(15, 14), -- Winter Jacket: Winter Clothing, Clothing, Winter Sports
|
||||
(16, 1),
|
||||
(16, 17),
|
||||
(16, 19), -- CS Book: Textbooks, School Supplies, Books (Non-textbook)
|
||||
(17, 24),
|
||||
(17, 2), -- Desk Lamp: Dorm Essentials, Electronics
|
||||
(18, 12),
|
||||
(18, 17),
|
||||
(18, 20), -- Scientific Calculator: Stationery, School Supplies, Math & Science
|
||||
(19, 23),
|
||||
(19, 2),
|
||||
(19, 24), -- Bluetooth Speaker: Audio Equipment, Electronics, Dorm Essentials
|
||||
(20, 22),
|
||||
(20, 17),
|
||||
(20, 24);
|
||||
|
||||
-- Insert History records
|
||||
INSERT INTO
|
||||
History (HistoryID, UserID, ProductID)
|
||||
VALUES
|
||||
(1, 1, 1),
|
||||
(2, 1, 3),
|
||||
(3, 1, 5),
|
||||
(4, 1, 7),
|
||||
(5, 1, 9),
|
||||
(6, 1, 11),
|
||||
(7, 2, 2),
|
||||
(8, 2, 4),
|
||||
(9, 2, 5),
|
||||
(10, 1, 15),
|
||||
(11, 1, 18);
|
||||
|
||||
-- Insert Favorites
|
||||
INSERT INTO
|
||||
Favorites (UserID, ProductID)
|
||||
VALUES
|
||||
(1, 2), -- User 1 likes HP Laptop
|
||||
(1, 7), -- User 1 likes Physics Textbook
|
||||
(2, 3), -- User 2 likes Dorm Desk
|
||||
(2, 10), -- User 2 likes Mountain Bike
|
||||
(1, 6), -- User 3 likes Acoustic Guitar
|
||||
(1, 5), -- User 4 likes Basketball
|
||||
(2, 8);
|
||||
|
||||
-- User 5 likes Mini Fridge
|
||||
-- Insert Transactions
|
||||
INSERT INTO
|
||||
Transaction (
|
||||
TransactionID,
|
||||
UserID,
|
||||
ProductID,
|
||||
Date,
|
||||
PaymentStatus
|
||||
)
|
||||
VALUES
|
||||
(1, 1, 1, '2024-10-16 10:30:00', 'Completed'),
|
||||
(2, 1, 6, '2024-10-15 15:45:00', 'Completed'),
|
||||
(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 ()
|
||||
);
|
||||
125
mysql-code/Schema.sql
Normal file
125
mysql-code/Schema.sql
Normal file
@@ -0,0 +1,125 @@
|
||||
-- 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 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
|
||||
);
|
||||
|
||||
-- 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 SET NULL,
|
||||
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 SET NULL
|
||||
);
|
||||
|
||||
-- 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
|
||||
);
|
||||
163
recommondation-engine/app.py
Normal file
163
recommondation-engine/app.py
Normal file
@@ -0,0 +1,163 @@
|
||||
# pip install mysql.connector
|
||||
#
|
||||
|
||||
|
||||
import mysql.connector
|
||||
from sklearn.metrics.pairwise import cosine_similarity
|
||||
import numpy as np
|
||||
import logging
|
||||
from unittest import result
|
||||
|
||||
def database():
|
||||
db_connection = mysql.connector.connect(
|
||||
host = "localhost",
|
||||
port = "3306",
|
||||
user = "root",
|
||||
database = "Marketplace"
|
||||
)
|
||||
return db_connection
|
||||
|
||||
def get_popular_products():
|
||||
pass
|
||||
|
||||
|
||||
def delete_user_recommendation(userID, Array):
|
||||
db_con = database()
|
||||
cursor = db_con.cursor()
|
||||
|
||||
try:
|
||||
for item in Array:
|
||||
#Product ID starts form index 1
|
||||
item_value = item + 1
|
||||
print(item_value)
|
||||
# Use parameterized queries to prevent SQL injection
|
||||
cursor.execute(f"INTO Recommendation (UserID, RecommendedProductID) VALUES ({userID}, {item_value});")
|
||||
|
||||
db_con.commit()
|
||||
|
||||
#results = cursor.fetchall()
|
||||
#print(results)
|
||||
except:
|
||||
pass
|
||||
|
||||
def get_all_products():
|
||||
|
||||
db_con = database()
|
||||
cursor = db_con.cursor()
|
||||
|
||||
cursor.execute("SELECT CategoryID FROM Category")
|
||||
categories = cursor.fetchall()
|
||||
|
||||
select_clause = "SELECT p.ProductID"
|
||||
for category in categories:
|
||||
category_id = category[0]
|
||||
select_clause += f", MAX(CASE WHEN pc.CategoryID = {category_id} THEN 1 ELSE 0 END) AS `Cat_{category_id}`"
|
||||
|
||||
final_query = f"""
|
||||
{select_clause}
|
||||
FROM Product p
|
||||
LEFT JOIN Product_Category pc ON p.ProductID = pc.ProductID
|
||||
LEFT JOIN Category c ON pc.CategoryID = c.CategoryID
|
||||
GROUP BY p.ProductID;
|
||||
"""
|
||||
|
||||
cursor.execute(final_query)
|
||||
results = cursor.fetchall()
|
||||
|
||||
final = []
|
||||
for row in results:
|
||||
text_list = list(row)
|
||||
text_list.pop(0)
|
||||
final.append(text_list)
|
||||
|
||||
cursor.close()
|
||||
db_con.close()
|
||||
return final
|
||||
|
||||
def get_user_history(user_id):
|
||||
db_con = database()
|
||||
cursor = db_con.cursor()
|
||||
|
||||
cursor.execute("SELECT CategoryID FROM Category")
|
||||
categories = cursor.fetchall()
|
||||
|
||||
select_clause = "SELECT p.ProductID"
|
||||
for category in categories:
|
||||
category_id = category[0] # get the uid of the catefory and then append that to the new column
|
||||
select_clause += f", MAX(CASE WHEN pc.CategoryID = {category_id} THEN 1 ELSE 0 END) AS `Cat_{category_id}`"
|
||||
|
||||
final_query = f"""
|
||||
{select_clause}
|
||||
FROM Product p
|
||||
LEFT JOIN Product_Category pc ON p.ProductID = pc.ProductID
|
||||
LEFT JOIN Category c ON pc.CategoryID = c.CategoryID
|
||||
where p.ProductID in (select ProductID from History where UserID = {user_id})
|
||||
GROUP BY p.ProductID;
|
||||
"""
|
||||
|
||||
cursor.execute(final_query)
|
||||
results = cursor.fetchall()
|
||||
final = []
|
||||
for row in results:
|
||||
text_list = list(row)
|
||||
text_list.pop(0)
|
||||
final.append(text_list)
|
||||
|
||||
cursor.close()
|
||||
db_con.close()
|
||||
return final
|
||||
|
||||
def get_recommendations(user_id, top_n=10):
|
||||
try:
|
||||
# Get all products and user history with their category vectors
|
||||
all_products = get_all_products()
|
||||
user_history = get_user_history(user_id)
|
||||
# if not user_history:
|
||||
# #Cold start: return popular products
|
||||
# return get_popular_products(top_n)
|
||||
# Calculate similarity between all products and user history
|
||||
user_profile = np.mean(user_history, axis=0) # Average user preferences
|
||||
similarities = cosine_similarity([user_profile], all_products)
|
||||
# finds the indices of the top N products that have the highest
|
||||
# cosine similarity with the user's profile and sorted from most similar to least similar.
|
||||
product_indices = similarities[0].argsort()[-top_n:][::-1]
|
||||
print("product", product_indices)
|
||||
|
||||
# Get the recommended product IDs
|
||||
recommended_products = [all_products[i][0] for i in product_indices] # Product IDs
|
||||
|
||||
# Upload the recommendations to the database
|
||||
history_upload(user_id, product_indices) # Pass the indices directly to history_upload
|
||||
|
||||
# Return recommended product IDs
|
||||
return recommended_products
|
||||
except Exception as e:
|
||||
logging.error(f"Recommendation error for user {user_id}: {str(e)}")
|
||||
# return get_popular_products(top_n) # Fallback to popular products
|
||||
|
||||
def history_upload(userID, anrr):
|
||||
db_con = database()
|
||||
cursor = db_con.cursor()
|
||||
|
||||
try:
|
||||
for item in anrr:
|
||||
#Product ID starts form index 1
|
||||
item_value = item + 1
|
||||
print(item_value)
|
||||
# Use parameterized queries to prevent SQL injection
|
||||
cursor.execute(f"INSERT INTO Recommendation (UserID, RecommendedProductID) VALUES ({userID}, {item_value});")
|
||||
|
||||
# Commit the changes
|
||||
db_con.commit()
|
||||
|
||||
# If you need results, you'd typically fetch them after a SELECT query
|
||||
#results = cursor.fetchall()
|
||||
#print(results)
|
||||
|
||||
except Exception as e:
|
||||
print(f"Error: {e}")
|
||||
db_con.rollback()
|
||||
finally:
|
||||
# Close the cursor and connection
|
||||
cursor.close()
|
||||
db_con.close()
|
||||
34
recommondation-engine/server.py
Normal file
34
recommondation-engine/server.py
Normal file
@@ -0,0 +1,34 @@
|
||||
|
||||
|
||||
|
||||
|
||||
from flask import Flask, request, jsonify
|
||||
from flask_cors import CORS
|
||||
from app import get_recommendations
|
||||
|
||||
|
||||
app = Flask(__name__)
|
||||
CORS(app) # Enable CORS for all routes
|
||||
|
||||
@app.route('/api/user/session', methods=['POST'])
|
||||
def handle_session_data():
|
||||
try:
|
||||
data = request.get_json()
|
||||
user_id = data.get('userId')
|
||||
email = data.get('email')
|
||||
is_authenticated = data.get('isAuthenticated')
|
||||
|
||||
if not user_id or not email or is_authenticated is None:
|
||||
return jsonify({'error': 'Invalid data'}), 400
|
||||
|
||||
get_recommendations(user_id)
|
||||
|
||||
print(f"Received session data: User ID: {user_id}, Email: {email}, Authenticated: {is_authenticated}")
|
||||
return jsonify({'message': 'Session data received successfully'})
|
||||
|
||||
except Exception as e:
|
||||
print(f"Error: {e}")
|
||||
return jsonify({'error': 'Server error'}), 500
|
||||
|
||||
if __name__ == '__main__':
|
||||
app.run(debug=True, host='0.0.0.0', port=5000)
|
||||
Reference in New Issue
Block a user