From 4b003da8dae68abcfadd917fbff687a37c3160ab Mon Sep 17 00:00:00 2001 From: ronreciproco123 Date: Wed, 15 Nov 2023 17:44:32 +0800 Subject: [PATCH] S50 Capstone added --- individual/backend/csp2-reciproco/Procfile | 1 + individual/backend/csp2-reciproco/auth.js | 85 ++++---- .../csp2-reciproco/controllers/order.js | 30 +++ .../csp2-reciproco/controllers/product.js | 141 +++++++++--- .../csp2-reciproco/controllers/user.js | 202 +++++++++++------- individual/backend/csp2-reciproco/index.js | 3 +- .../backend/csp2-reciproco/model/Product.js | 2 +- individual/backend/csp2-reciproco/readme.md | 89 ++++++++ .../backend/csp2-reciproco/routes/product.js | 34 ++- .../backend/csp2-reciproco/routes/user.js | 9 +- 10 files changed, 446 insertions(+), 150 deletions(-) create mode 100644 individual/backend/csp2-reciproco/Procfile create mode 100644 individual/backend/csp2-reciproco/controllers/order.js diff --git a/individual/backend/csp2-reciproco/Procfile b/individual/backend/csp2-reciproco/Procfile new file mode 100644 index 0000000..5ec9cc2 --- /dev/null +++ b/individual/backend/csp2-reciproco/Procfile @@ -0,0 +1 @@ +web: node index.js \ No newline at end of file diff --git a/individual/backend/csp2-reciproco/auth.js b/individual/backend/csp2-reciproco/auth.js index 3ccedd8..63f8a6c 100644 --- a/individual/backend/csp2-reciproco/auth.js +++ b/individual/backend/csp2-reciproco/auth.js @@ -1,40 +1,53 @@ -const jwt = require('jsonwebtoken'); +// middlewares/auth.js + +const jwt = require("jsonwebtoken") require("dotenv").config() -// Function to verify JWT token -exports.verifyToken = (token) => { - try { - const decoded = jwt.verify(token, process.env.SECRET_SAUCE); - return decoded; - } catch (error) { - console.error(error); - return null; // Return null if verification fails - } - }; - - // Middleware for verifying JWT token - exports.authenticateToken = (req, res, next) => { - // Extract the token from the Authorization header - const token = req.header('Authorization')?.replace('Bearer ', ''); - - if (!token) { - return res.status(401).json({ message: 'Unauthorized. Token not provided.' }); - } - - const decoded = exports.verifyToken(token); - - if (!decoded) { - return res.status(401).json({ message: 'Unauthorized. Invalid token.' }); - } - - // Attach the decoded information to the request for future use - req.user = decoded; - - // Proceed to the next middleware or route handler - next(); - }; +// Middleware for verifying and authenticating JWT token +exports.authenticateToken = (req, res, next) => { + // Extract the token from the Authorization header + const token = req.header("Authorization")?.replace("Bearer ", "") + + if (!token) { + return res + .status(401) + .json({ message: "Unauthorized. Token not provided." }) + } + + console.log("Token:", token) // Log the token to the console + + try { + const decoded = jwt.verify(token, process.env.SECRET_SAUCE) + req.user = decoded // Attach the decoded information to the request for future use + + // dEBUGGING PURPOSES + console.log("userId:", decoded.userId) + console.log("isAdmin:", decoded.isAdmin) + + next() + } catch (error) { + console.error("Token Verification Error:", error) + return res + .status(401) + .json({ message: "Unauthorized. Invalid or expired token." }) + } +} // Function to generate a JWT token -exports.generateToken = (userId, email) => { - return jwt.sign({ userId, email }, process.env.SECRET_SAUCE, { expiresIn: '1h' }); -}; +exports.generateToken = (userId, email, isAdmin) => { + return jwt.sign({ userId, email, isAdmin }, process.env.SECRET_SAUCE, { + expiresIn: "1h", + }) +} + +// Middleware to verify admin status +exports.verifyAdmin = (req, res, next) => { + if (req.user && req.user.isAdmin) { + next() + } else { + return res + .status(403) + .json({ message: "Action Forbidden. User is not an admin." }) + } +} + diff --git a/individual/backend/csp2-reciproco/controllers/order.js b/individual/backend/csp2-reciproco/controllers/order.js new file mode 100644 index 0000000..969414b --- /dev/null +++ b/individual/backend/csp2-reciproco/controllers/order.js @@ -0,0 +1,30 @@ +const User = require("../model/User") + +exports.createOrder = async (req, res) => { + try { + const { userId, products, totalAmount } = req.body + + const user = await User.findById(userId) + + if (!user) { + return res.status(404).json({ message: "User not found" }) + } + + const newOrder = { + products: products, + totalAmount: totalAmount, + purchaseOn: Date.now(), + } + + user.orderedProducts.push(newOrder) + await user.save() + + res.status(201).json({ + message: "Order created successfully", + order: newOrder, + }) + } catch (error) { + console.error(error) + res.status(500).json({ message: "Internal Server Error" }) + } +} diff --git a/individual/backend/csp2-reciproco/controllers/product.js b/individual/backend/csp2-reciproco/controllers/product.js index c35c837..2f2ee40 100644 --- a/individual/backend/csp2-reciproco/controllers/product.js +++ b/individual/backend/csp2-reciproco/controllers/product.js @@ -1,52 +1,141 @@ -// productController.js -const Product = require('../model/Product'); +const Product = require("../model/Product") -// Controller function for creating a product (accessible only by isAdmin) exports.createProduct = async (req, res) => { + try { + const { name, description, price } = req.body + + const newProduct = new Product({ name, description, price }) + await newProduct.save() + res.status(201).json(newProduct) + } catch (error) { + res.status(500).json({ error: error.message }) + } +} + +// Controller function for retrieving all products (accessible to both admin and normal user) +exports.getAllProducts = async (req, res) => { + try { + const products = await Product.find() + + res.status(200).json(products) + } catch (error) { + console.error(error) + res.status(500).json({ message: "Internal server error" }) + } +} + +// Controller function for retrieving all active products (accessible to both admin and normal user) +exports.getActiveProducts = async (req, res) => { + try { + const activeProducts = await Product.find({ isActive: true }) + + res.status(200).json(activeProducts) + } catch (error) { + console.error(error) + res.status(500).json({ message: "Internal server error" }) + } +} + +// Controller to retrieve a single product by id +exports.getProductById = async (req, res) => { + const productId = req.params.id; + try { - // Check if the user is an admin - if (!req.user.isAdmin) { - return res.status(403).json({ message: 'Permission denied. Only admins can create products.' }); + const product = await Product.findById(productId); + + if (!product) { + return res.status(404).json({ message: 'Product not found' }); } - const { name, description, price, isActive } = req.body; + res.status(200).json(product); + } catch (error) { + res.status(500).json({ message: 'Internal server error' }); + } +}; + + +// Controller function to update product information +exports.updateProduct = async (req, res) => { + const productId = req.params.id; + const { name, description, price, isActive } = req.body; + + try { + // Check if the product exists + const existingProduct = await Product.findById(productId); + if (!existingProduct) { + return res.status(404).json({ message: 'Product not found' }); + } - const newProduct = new Product({ - name, - description, - price, - isActive, - }); + // Update product information + existingProduct.name = name; + existingProduct.description = description; + existingProduct.price = price; + existingProduct.isActive = isActive; - await newProduct.save(); + // Save the updated product + const updatedProduct = await existingProduct.save(); - res.status(201).json({ message: 'Product created successfully' }); + res.status(200).json(updatedProduct); } catch (error) { console.error(error); - res.status(500).json({ message: 'Internal server error' }); + res.status(500).json({ message: 'Internal Server Error' }); } }; -// Controller function for retrieving all products (accessible to both admin and normal user) -exports.getAllProducts = async (req, res) => { +exports.activateProduct = async (req, res) => { + const { productId } = req.params; + try { - const products = await Product.find(); + // Find the product by ID + const product = await Product.findById(productId); + + if (!product) { + return res.status(404).json({ message: 'Product not found' }); + } - res.status(200).json(products); + // Check if the product is already active + if (product.isActive) { + return res.status(400).json({ message: 'Product is already active' }); + } + + // Activate the product + product.isActive = true; + + // Save the updated product + await product.save(); + + res.json({ message: 'Product activated successfully', product }); } catch (error) { console.error(error); - res.status(500).json({ message: 'Internal server error' }); + res.status(500).json({ message: 'Internal Server Error' }); } }; -// Controller function for retrieving all active products (accessible to both admin and normal user) -exports.getActiveProducts = async (req, res) => { +exports.archiveProduct = async (req, res) => { + const { productId } = req.params; + try { - const activeProducts = await Product.find({ isActive: true }); + // Find the product by ID + const product = await Product.findById(productId); + + if (!product) { + return res.status(404).json({ message: 'Product not found' }); + } - res.status(200).json(activeProducts); + // Check if the product is already active + if (!product.isActive) { + return res.status(400).json({ message: 'Product is already in Archive' }); + } + + // Activate the product + product.isActive = false; + + // Save the updated product + await product.save(); + + res.json({ message: 'Product successfuly archived', product }); } catch (error) { console.error(error); - res.status(500).json({ message: 'Internal server error' }); + res.status(500).json({ message: 'Internal Server Error' }); } }; diff --git a/individual/backend/csp2-reciproco/controllers/user.js b/individual/backend/csp2-reciproco/controllers/user.js index a5a9f45..56d99e1 100644 --- a/individual/backend/csp2-reciproco/controllers/user.js +++ b/individual/backend/csp2-reciproco/controllers/user.js @@ -1,3 +1,4 @@ +const jwt = require('jsonwebtoken'); const bcrypt = require("bcrypt") const faker = require("faker") const User = require("../model/User") @@ -5,61 +6,140 @@ const auth = require("../auth") // Controller function for user registration exports.registerUser = async (req, res) => { + try { + const { email, password, firstName, lastName } = req.body + + // Check if the email already exists + const existingUser = await User.findOne({ email }) + + if (existingUser) { + return res + .status(400) + .json({ message: "This Email is already registered." }) + } + + // If firstName and lastName are not provided, generate default values using faker + const autoGeneratedFirstName = firstName || faker.name.firstName() + const autoGeneratedLastName = lastName || faker.name.lastName() + + // Hash the password before saving it + const hashedPassword = await bcrypt.hash(password, 10) + + const newUser = new User({ + email, + password: hashedPassword, + firstName: autoGeneratedFirstName, + lastName: autoGeneratedLastName, + }) + + await newUser.save() + + res.status(201).json({ + message: + "User registered successfully. To update account details, acess user/update", + }) + } catch (error) { + console.error(error) + res.status(500).json({ message: "Internal server error" }) + } +} + +// Controller function for user authentication +exports.authenticateUser = async (req, res) => { try { - const { email, password, firstName, lastName } = req.body; + const { email, password } = req.body; - // If firstName and lastName are not provided, generate default values using faker - const autoGeneratedFirstName = firstName || faker.name.firstName(); - const autoGeneratedLastName = lastName || faker.name.lastName(); + const user = await User.findOne({ email }); - // Hash the password before saving it - const hashedPassword = await bcrypt.hash(password, 10); + if (!user) { + return res.status(401).json({ message: 'Invalid credentials' }); + } - const newUser = new User({ - email, - password: hashedPassword, - firstName: autoGeneratedFirstName, - lastName: autoGeneratedLastName, - }); + const passwordMatch = await bcrypt.compare(password, user.password); - await newUser.save(); + if (!passwordMatch) { + return res.status(401).json({ message: 'Invalid credentials' }); + } - res.status(201).json({ message: 'User registered successfully. To update account details, acess user/update' }); + // 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' }); } }; - - -// Controller function for user authentication -exports.authenticateUser = async (req, res) => { +// Controller function for updating user data (including email, firstName, lastName, and password) +exports.updateUserData = async (req, res) => { try { - const { email, password } = req.body + const { userId, newEmail, newFirstName, newLastName, newPassword } = + req.body + + const userIdFromToken = req.user.userId + if (userIdFromToken !== userId) { + return res.status(403).json({ + message: + "Permission denied. You can only update your own data.", + }) + } - const user = await User.findOne({ email }) + const user = await User.findById(userId) if (!user) { - return res.status(401).json({ message: "Invalid credentials" }) + return res.status(404).json({ message: "User not found" }) } - const passwordMatch = await bcrypt.compare(password, user.password) + // Update email if provided + if (newEmail) { + user.email = newEmail + } - if (!passwordMatch) { - return res.status(401).json({ message: "Invalid credentials" }) + // Update firstName if provided + if (newFirstName) { + user.firstName = newFirstName } - // Generate JWT token using the function from auth.js - const token = auth.generateToken(user._id, user.email) + // Update lastName if provided + if (newLastName) { + user.lastName = newLastName + } + + // Update password if provided + if (newPassword) { + const hashedPassword = await bcrypt.hash(newPassword, 10) + user.password = hashedPassword + } + + // Save the updated user data + await user.save() - // Return user details and token + // Fetch the updated user details + const updatedUser = await User.findById(userId) + + // Return the updated user details in the response res.status(200).json({ - userId: user._id, - email: user.email, - firstName: user.firstName, - lastName: user.lastName, - isAdmin: user.isAdmin, - token: token, + message: "User data updated successfully", + user: updatedUser, }) } catch (error) { console.error(error) @@ -67,58 +147,30 @@ exports.authenticateUser = async (req, res) => { } } -// Controller function for updating user data (including email, firstName, lastName, and password) -exports.updateUserData = async (req, res) => { +exports.getUserDetails = async (req, res) => { try { - const { userId, newEmail, newFirstName, newLastName, newPassword } = req.body; + const { userId } = req.params; - // Add authentication logic here using the authorization token - // For example: - // const userIdFromToken = req.user.userId; - // if (userIdFromToken !== userId) { - // return res.status(403).json({ message: 'Permission denied. You can only update your own data.' }); - // } + const userIdFromToken = req.user.userId; - // Your logic to update user data based on userId - // For example: + 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' }); - } - - // Update email if provided - if (newEmail) { - user.email = newEmail; - } - - // Update firstName if provided - if (newFirstName) { - user.firstName = newFirstName; + return res.status(404).json({ message: "User not found" }); } - // Update lastName if provided - if (newLastName) { - user.lastName = newLastName; - } - - // Update password if provided - if (newPassword) { - const hashedPassword = await bcrypt.hash(newPassword, 10); - user.password = hashedPassword; - } - - // Save the updated user data - await user.save(); - - // Fetch the updated user details - const updatedUser = await User.findById(userId); - - // Return the updated user details in the response - res.status(200).json({ message: 'User data updated successfully', user: updatedUser }); + // 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' }); + res.status(500).json({ message: "Internal server error" }); } }; \ No newline at end of file diff --git a/individual/backend/csp2-reciproco/index.js b/individual/backend/csp2-reciproco/index.js index 876caa4..41cbf10 100644 --- a/individual/backend/csp2-reciproco/index.js +++ b/individual/backend/csp2-reciproco/index.js @@ -4,6 +4,7 @@ const mongoose = require("mongoose") const cors = require("cors") require("dotenv").config() const userRoute = require("./routes/user") +const productRoute = require("./routes/product") // Server start const app = express() @@ -29,7 +30,7 @@ mongoose }) // Routes -app.use("/user", userRoute) +app.use("/user", userRoute, productRoute) // Server up app.listen(process.env.PORT || 3000, () => { diff --git a/individual/backend/csp2-reciproco/model/Product.js b/individual/backend/csp2-reciproco/model/Product.js index 46d222d..fb29970 100644 --- a/individual/backend/csp2-reciproco/model/Product.js +++ b/individual/backend/csp2-reciproco/model/Product.js @@ -4,7 +4,7 @@ const productSchema = new mongoose.Schema({ name: { type: String, required: true }, description: { type: String, required: true }, price: { type: Number, required: true }, - isActive: { type: Boolean, default: true }, + isActive: { type: Boolean, default: false }, createdOn: { type: Date, default: Date.now }, }); diff --git a/individual/backend/csp2-reciproco/readme.md b/individual/backend/csp2-reciproco/readme.md index e69de29..9b15250 100644 --- a/individual/backend/csp2-reciproco/readme.md +++ b/individual/backend/csp2-reciproco/readme.md @@ -0,0 +1,89 @@ +**** Accounts **** + +User: ( Password: wapatu ) +{ + "userId": "65544d9be5c01f6c0ca79200", + "email": "wapatu@example.com", + "firstName": "Estevan", + "lastName": "Cummings", + "isAdmin": false, + "token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VySWQiOiI2NTU0NGQ5YmU1YzAxZjZjMGNhNzkyMDAiLCJlbWFpbCI6IndhcGF0dUBleGFtcGxlLmNvbSIsImlzQWRtaW4iOmZhbHNlLCJpYXQiOjE3MDAwMjM3MjQsImV4cCI6MTcwMDAyNzMyNH0.dpWV9Zx64TH4RLgmV_RlyrMBCa0HwDe9wJRAkwAyjys" +} + +Admin: (Password is: admin ) +{ + "userId": "65535cb526b586a3e2fd56cc", + "email": "admin@email.com", + "isAdmin": true, + "token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VySWQiOiI2NTUzNWNiNTI2YjU4NmEzZTJmZDU2Y2MiLCJlbWFpbCI6ImFkbWluQGVtYWlsLmNvbSIsImlzQWRtaW4iOnRydWUsImlhdCI6MTcwMDAyMzgxMSwiZXhwIjoxNzAwMDI3NDExfQ.DrhpNlBJKpfHod7MfTalQ5j2-s8tnR630yh2-_EIYWw" +} + +**** Links **** + +REGISTER +http://localhost:3000/user/register +--> Use post +{ + "email": "admin@email.com", + "password": "admin" +} + +LOGIN +http://localhost:3000/user/login +--> Use post +{ + "email": "admin@email.com", + "password": "admin" +} + +UPDATE PROFILE +http://localhost:3000/user/update +--> Must use Admin token // use put +{ + "userId": "6554473388d9942bbf4de065", + "newEmail": "mundo@email.com", + "newFirstname": "Ron", + "newLastName": "Pogi", + "newPassword": "mundo" +} + +CREATE PRODUCT [ADMIN] +http://localhost:3000/user/products +--> Must use Admin token // use post +{ + "name": "Poring Card", + "description": "Description unknown", + "price": 500 +} + +GET ALL PRODUCT +http://localhost:3000/user/all +--> Use get + +GET ALL ACTIVE PRODUCT +http://localhost:3000/user/active +--> Use get + +GET A SINGLE PROUCT +http://localhost:3000/user/products/65538bd4a601aa30730f6d4c +--> Use get + +UPDATE A PRODUCT [ADMIN] +http://localhost:3000/user/products/65545a1e6fa9d841e1518d1d +--> Must use Admin token // Use put +{ + "name": "Christmas Cookie Card", + "description": "Updated Product Description", + "price": 29.99, + "isActive": false +} + + ACTIVATE / ARCHIVE A PRODUCT [ADMIN] + --> Use Put + http://localhost:3000/user/products/6554634e5cac4bcd6f2394ed/activate + http://localhost:3000/user/products/6554634e5cac4bcd6f2394ed/archive + + +RETRIEVE OWN USER DATA +--> Use Get +http://localhost:3000/user/65535cb526b586a3e2fd56cc \ No newline at end of file diff --git a/individual/backend/csp2-reciproco/routes/product.js b/individual/backend/csp2-reciproco/routes/product.js index ccdc2f1..8db2985 100644 --- a/individual/backend/csp2-reciproco/routes/product.js +++ b/individual/backend/csp2-reciproco/routes/product.js @@ -1,18 +1,32 @@ -const express = require('express'); -const router = express.Router(); -const productController = require('../controllers/product'); -const auth = require('./auth'); // Assuming you have an auth.js file with authentication middleware +const express = require("express") +const router = express.Router() +const productController = require("../controllers/product") +const auth = require("../auth") -// Middleware for authorization token -const authenticateToken = auth.authenticateToken; +const { authenticateToken, verifyAdmin } = auth +// S50 // Create a product route (accessible only by isAdmin) -router.post('/create', authenticateToken, productController.createProduct); +router.post("/products", authenticateToken, verifyAdmin, productController.createProduct) // Retrieve all products route (accessible to both admin and normal user) -router.get('/all', productController.getAllProducts); +router.get("/all", productController.getAllProducts) // Retrieve all active products route (accessible to both admin and normal user) -router.get('/active', productController.getActiveProducts); +router.get("/active", productController.getActiveProducts) -module.exports = router; +// S51 +// Retrieve a single product by ID +router.get('/products/:id', productController.getProductById); + +// Update product route + admin verification +router.put('/products/:id', authenticateToken, verifyAdmin, productController.updateProduct); + +// Archive a product +router.put('/products/:productId/archive', authenticateToken, verifyAdmin, productController.archiveProduct); + +// Activate a product +router.put('/products/:productId/activate', authenticateToken, verifyAdmin, productController.activateProduct); + + +module.exports = router diff --git a/individual/backend/csp2-reciproco/routes/user.js b/individual/backend/csp2-reciproco/routes/user.js index 14203b7..d37c045 100644 --- a/individual/backend/csp2-reciproco/routes/user.js +++ b/individual/backend/csp2-reciproco/routes/user.js @@ -1,9 +1,10 @@ const express = require("express") const router = express.Router() const userController = require("../controllers/user") +const orderController = require("../controllers/order") const auth = require("../auth"); -const { authenticateToken } = auth; +const { authenticateToken, } = auth; // User registration route router.post("/register", userController.registerUser) @@ -14,4 +15,10 @@ router.post("/login", userController.authenticateUser) // Update user data route router.put("/update", authenticateToken, userController.updateUserData) +// POST /users/order +router.post("/order", orderController.createOrder); + +// Retrieve user details +router.get('/:userId', authenticateToken, userController.getUserDetails); + module.exports = router