diff --git a/individual/backend/csp2-reciproco/auth.js b/individual/backend/csp2-reciproco/auth.js index 63f8a6c..3a9bf34 100644 --- a/individual/backend/csp2-reciproco/auth.js +++ b/individual/backend/csp2-reciproco/auth.js @@ -51,3 +51,17 @@ exports.verifyAdmin = (req, res, next) => { } } +exports.verifyUser = (req, res, next) => { + const authenticatedUserId = req.user.userId; + const requestedUserId = req.body.userId; + + if (authenticatedUserId && authenticatedUserId === requestedUserId) { + // User is authenticated, and the requested userId matches the authenticated userId + next(); + } else { + return res.status(403).json({ + message: "Permission denied.", + }); + } + }; + \ No newline at end of file diff --git a/individual/backend/csp2-reciproco/controllers/cart.js b/individual/backend/csp2-reciproco/controllers/cart.js new file mode 100644 index 0000000..1256022 --- /dev/null +++ b/individual/backend/csp2-reciproco/controllers/cart.js @@ -0,0 +1,72 @@ +// userController.js +const User = require('../model/Cart'); + +exports.addToCart = async (req, res) => { + try { + const { userId, productId, quantity } = req.body; + + // Check if the user exists + const user = await User.findById(userId); + if (!user) { + return res.status(404).json({ message: 'User not found.' }); + } + + // Check if the product exists + const product = await Product.findById(productId); + if (!product) { + return res.status(404).json({ message: 'Product not found.' }); + } + + // Check if the product is active + if (!product.isActive) { + return res.status(400).json({ message: 'Product is not active.' }); + } + + // Check if the quantity is valid + if (quantity <= 0) { + return res.status(400).json({ message: 'Invalid quantity.' }); + } + + // Check if the user already has a cart + let cart = await Cart.findOne({ userId }); + + // If the user doesn't have a cart, create a new one + if (!cart) { + cart = new Cart({ userId, products: [], totalAmount: 0 }); + } + + // Check if the product is already in the cart + const existingProduct = cart.products.find( + (cartProduct) => cartProduct.productId.toString() === productId + ); + + // If the product is already in the cart, update the quantity and subtotal + if (existingProduct) { + existingProduct.quantity += quantity; + existingProduct.subtotal = existingProduct.quantity * existingProduct.price; + } else { + // If the product is not in the cart, add it + cart.products.push({ + productId, + productName: product.name, + quantity, + price: product.price, + subtotal: quantity * product.price, + }); + } + + // Update the total amount in the cart + cart.totalAmount = cart.products.reduce( + (total, product) => total + product.subtotal, + 0 + ); + + // Save the updated cart + await cart.save(); + + return res.status(200).json({ message: 'Product added to cart successfully.' }); + } catch (error) { + console.error(error); + return res.status(500).json({ message: 'Internal server error.' }); + } +} diff --git a/individual/backend/csp2-reciproco/controllers/order.js b/individual/backend/csp2-reciproco/controllers/order.js index 969414b..108a720 100644 --- a/individual/backend/csp2-reciproco/controllers/order.js +++ b/individual/backend/csp2-reciproco/controllers/order.js @@ -10,6 +10,13 @@ exports.createOrder = async (req, res) => { return res.status(404).json({ message: "User not found" }) } + // Check if the user is an admin + if (user.isAdmin) { + return res + .status(403) + .json({ message: "Admins cannot create orders" }) + } + const newOrder = { products: products, totalAmount: totalAmount, @@ -28,3 +35,31 @@ exports.createOrder = async (req, res) => { res.status(500).json({ message: "Internal Server Error" }) } } + +// Retrieve authenticated user's orders +exports.getOrders = async (req, res) => { + try { + const { userId } = req.body + + const user = await User.findById(userId) + + if (!user) { + return res.status(404).json({ message: "User not found" }) + } + + // Return the user details in the response + res.send({ orderedProducts: user.orderedProducts }) + } catch (error) { + console.error(error) + res.status(500).json({ message: "Internal server error" }) + } +} + +exports.getAllOrders = async (req, res) => { + try { + const orders = await User.find({ "orderedProducts.0": { $exists: true } }, "orderedProducts"); + res.status(200).json({ success: true, data: orders }); + } catch (error) { + res.status(500).json({ success: false, error: error.message }); + } + }; diff --git a/individual/backend/csp2-reciproco/controllers/user.js b/individual/backend/csp2-reciproco/controllers/user.js index 56d99e1..adcd048 100644 --- a/individual/backend/csp2-reciproco/controllers/user.js +++ b/individual/backend/csp2-reciproco/controllers/user.js @@ -1,9 +1,12 @@ -const jwt = require('jsonwebtoken'); +const jwt = require("jsonwebtoken") const bcrypt = require("bcrypt") const faker = require("faker") const User = require("../model/User") const auth = require("../auth") +// Secret Sauce +require("dotenv").config() + // Controller function for user registration exports.registerUser = async (req, res) => { try { @@ -46,49 +49,52 @@ exports.registerUser = async (req, res) => { // Controller function for user authentication exports.authenticateUser = async (req, res) => { - try { - const { email, password } = req.body; - - const user = await User.findOne({ email }); - - if (!user) { - return res.status(401).json({ message: 'Invalid credentials' }); - } - - const passwordMatch = await bcrypt.compare(password, user.password); - - if (!passwordMatch) { - return res.status(401).json({ message: 'Invalid credentials' }); - } - - // Generate JWT token using the function from auth.js - const token = auth.generateToken(user._id, user.email, user.isAdmin); - - // Decode JWT token to get expiration time - const decodedToken = jwt.decode(token); - - if (decodedToken) { - const expiration = new Date(decodedToken.exp * 1000); // Convert seconds to milliseconds - // Log token expiration - console.log(`Login success. Token will expire on: ${expiration}`); - } else { - console.error('Error decoding token'); - } - - // Return user details and token - res.status(200).json({ - userId: user._id, - email: user.email, - firstName: user.firstName, - lastName: user.lastName, - isAdmin: user.isAdmin, - token: token, - }); - } catch (error) { - console.error(error); - res.status(500).json({ message: 'Internal server error' }); - } -}; + try { + const { email, password } = req.body + + const user = await User.findOne({ email }) + + if (!user) { + return res.status(401).json({ message: "Invalid credentials" }) + } + + const passwordMatch = await bcrypt.compare(password, user.password) + + if (!passwordMatch) { + return res.status(401).json({ message: "Invalid credentials" }) + } + + // Generate JWT token using the function from auth.js + const token = auth.generateToken(user._id, user.email, user.isAdmin) + + // Decode JWT token to get expiration time + const decodedToken = jwt.decode(token) + + if (decodedToken) { + const expiration = new Date(decodedToken.exp * 1000) // Convert seconds to milliseconds + // Log token expiration + console.log( + `Authenticate success. Token will expire on: ${expiration}` + ) + } else { + console.error("Error decoding token") + } + + // Return user details and token + res.status(200).json({ + userId: user._id, + email: user.email, + firstName: user.firstName, + lastName: user.lastName, + isAdmin: user.isAdmin, + token: token, + }) + } catch (error) { + console.error(error) + res.status(500).json({ message: "Internal server error" }) + } +} + // Controller function for updating user data (including email, firstName, lastName, and password) exports.updateUserData = async (req, res) => { try { @@ -148,29 +154,51 @@ exports.updateUserData = async (req, res) => { } exports.getUserDetails = async (req, res) => { - try { - const { userId } = req.params; - - const userIdFromToken = req.user.userId; - - if (userIdFromToken !== userId) { - return res.status(403).json({ - message: "Permission denied. You can only retrieve your own data.", - }); - } - - const user = await User.findById(userId); - - if (!user) { - return res.status(404).json({ message: "User not found" }); - } - - // Return the user details in the response - res.status(200).json({ - user, - }); - } catch (error) { - console.error(error); - res.status(500).json({ message: "Internal server error" }); - } -}; \ No newline at end of file + try { + const { userId } = req.body; + + const user = await User.findById(userId); + + if (!user) { + return res.status(404).json({ message: "User not found" }); + } + + // Return the user details in the response + res.status(200).json({ + user, + }); + } catch (error) { + console.error(error); + res.status(500).json({ message: "Internal server error" }); + } + }; + + +exports.setAdmin = async (req, res) => { + try { + const { userId } = req.body // assuming userId is sent in the request body + + const user = await User.findById(userId) + + if (!user) { + return res.status(404).json({ error: "User not found." }) + } + + user.isAdmin = true + await user.save() + + res.json({ message: "User is now an admin." }) + } catch (error) { + console.error(error) + res.status(500).json({ error: "Internal server error" }) + } +} + +exports.getAllOrders = async (req, res) => { + try { + const orders = await User.find({}, "orderedProducts"); + res.status(200).json({ success: true, data: orders }); + } catch (error) { + res.status(500).json({ success: false, error: error.message }); + } + }; \ No newline at end of file diff --git a/individual/backend/csp2-reciproco/index.js b/individual/backend/csp2-reciproco/index.js index 41cbf10..d272228 100644 --- a/individual/backend/csp2-reciproco/index.js +++ b/individual/backend/csp2-reciproco/index.js @@ -5,6 +5,7 @@ const cors = require("cors") require("dotenv").config() const userRoute = require("./routes/user") const productRoute = require("./routes/product") +const cartRoute = require("./routes/cart") // Server start const app = express() @@ -31,6 +32,7 @@ mongoose // Routes app.use("/user", userRoute, productRoute) +app.use("/cart", cartRoute) // Server up app.listen(process.env.PORT || 3000, () => { diff --git a/individual/backend/csp2-reciproco/model/Cart.js b/individual/backend/csp2-reciproco/model/Cart.js new file mode 100644 index 0000000..d7bfebb --- /dev/null +++ b/individual/backend/csp2-reciproco/model/Cart.js @@ -0,0 +1,27 @@ +const mongoose = require('mongoose'); + +const cartProductSchema = new mongoose.Schema({ + productId: { + type: mongoose.Schema.Types.ObjectId, + ref: 'Product', + required: true, + }, + productName: { type: String, required: true }, + quantity: { type: Number, required: true }, + price: { type: Number, required: true }, + subtotal: { type: Number, required: true }, +}); + +const cartSchema = new mongoose.Schema({ + userId: { + type: mongoose.Schema.Types.ObjectId, + ref: 'User', + required: true, + }, + products: [cartProductSchema], + totalAmount: { type: Number, required: true }, +}); + +const Cart = mongoose.model('Cart', cartSchema); + +module.exports = Cart; diff --git a/individual/backend/csp2-reciproco/readme.md b/individual/backend/csp2-reciproco/readme.md index 9b15250..7b6ba8b 100644 --- a/individual/backend/csp2-reciproco/readme.md +++ b/individual/backend/csp2-reciproco/readme.md @@ -1,3 +1,16 @@ + + +**** Stretch Goals **** ++ Set user as Admin ( Admin Only ) ++ Retrieve Authenticated User's Orders ++ Retrieve all orders ( Admin Only ) ++ Add to Cart ( Added Products, Change Product Quantities, Remove Products From Cart, Subtotal for each item, Total price for all items) ++ Authentication Token with expiration (1hr) ++ dotenv ++ faker (Auto Generate Names) ++ getUserDetails function ( Detects if the user tries to get the details of the other useId's + Token auth) ++ Secure verification that match Token and UserId to next() + **** Accounts **** User: ( Password: wapatu ) @@ -86,4 +99,8 @@ http://localhost:3000/user/products/65545a1e6fa9d841e1518d1d RETRIEVE OWN USER DATA --> Use Get -http://localhost:3000/user/65535cb526b586a3e2fd56cc \ No newline at end of file +http://localhost:3000/user/retrieveUser +{ + + "userId": "6554ac8dd7fbf9ee90217e77" +} diff --git a/individual/backend/csp2-reciproco/routes/cart.js b/individual/backend/csp2-reciproco/routes/cart.js new file mode 100644 index 0000000..fe32e4a --- /dev/null +++ b/individual/backend/csp2-reciproco/routes/cart.js @@ -0,0 +1,10 @@ +const express = require('express'); +const router = express.Router(); +const cart = require('../controllers/cart'); +const auth = require("../auth") + +const { authenticateToken, } = auth +// Add route to handle adding a product to the cart +router.post('/add-to-cart', authenticateToken, cart.addToCart); + +module.exports = router; diff --git a/individual/backend/csp2-reciproco/routes/user.js b/individual/backend/csp2-reciproco/routes/user.js index d37c045..8a510a0 100644 --- a/individual/backend/csp2-reciproco/routes/user.js +++ b/individual/backend/csp2-reciproco/routes/user.js @@ -4,13 +4,13 @@ const userController = require("../controllers/user") const orderController = require("../controllers/order") const auth = require("../auth"); -const { authenticateToken, } = auth; +const { authenticateToken, verifyAdmin, verifyUser } = auth; // User registration route router.post("/register", userController.registerUser) // User authentication route -router.post("/login", userController.authenticateUser) +router.post("/authenticate", userController.authenticateUser) // Update user data route router.put("/update", authenticateToken, userController.updateUserData) @@ -19,6 +19,16 @@ router.put("/update", authenticateToken, userController.updateUserData) router.post("/order", orderController.createOrder); // Retrieve user details -router.get('/:userId', authenticateToken, userController.getUserDetails); +router.get('/retrieveUser', authenticateToken, verifyUser, userController.getUserDetails); + +// EXCLUSIVE ADMIN ACCOUNT +// Set user to Admin User +router.post('/set-admin', authenticateToken, verifyAdmin, userController.setAdmin); + +// Route to retrieve authenticated user's orders +router.post("/getOrders", authenticateToken, verifyUser, orderController.getOrders); + +// Route to retrieve all orders +router.get("/orders-all", authenticateToken, verifyAdmin, orderController.getAllOrders); module.exports = router