├── .gitignore ├── .prettierrc ├── utils ├── errorText.js ├── error.js ├── validateMongoDBId.js └── cloudinary.js ├── public └── images │ ├── blogs │ ├── images-1698161164993-887388735.jpeg │ └── images-1698161212914-299009208.jpeg │ └── products │ └── images-1698161143911-849877721.jpeg ├── config ├── db.js ├── jwtToken.js └── refreshToken.js ├── models ├── colorModel.js ├── blogCategoryModel.js ├── brandModel.js ├── prodCategoryModel.js ├── couponModel.js ├── cartModel.js ├── orderModel.js ├── blogModel.js ├── productModel.js └── userModel.js ├── middlewares ├── errorHandler.js ├── authMiddleware.js └── uploadImaged.js ├── routes ├── brandRoute.js ├── colorRoute.js ├── couponRoute.js ├── blogCategoryRoute.js ├── prodCategoryRoute.js ├── blogRoute.js ├── productRoute.js └── authRoutes.js ├── package.json ├── controller ├── emailController.js ├── colorController.js ├── brandController.js ├── couponController.js ├── prodCategoryController.js ├── blogCategoryController.js ├── blogController.js ├── productController.js └── userController.js ├── app.js ├── README.md └── E-Commerce.postman_collection.json /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | .env 3 | -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "singleQuote": true, 3 | "arrowParens": "avoid" 4 | } 5 | -------------------------------------------------------------------------------- /utils/errorText.js: -------------------------------------------------------------------------------- 1 | const SUCCESS = "success"; 2 | const FAIL = "fail"; 3 | const ERROR = "error"; 4 | 5 | export {SUCCESS,ERROR,FAIL} -------------------------------------------------------------------------------- /public/images/blogs/images-1698161164993-887388735.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/0xEbrahim/E-Commerce-API/HEAD/public/images/blogs/images-1698161164993-887388735.jpeg -------------------------------------------------------------------------------- /public/images/blogs/images-1698161212914-299009208.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/0xEbrahim/E-Commerce-API/HEAD/public/images/blogs/images-1698161212914-299009208.jpeg -------------------------------------------------------------------------------- /public/images/products/images-1698161143911-849877721.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/0xEbrahim/E-Commerce-API/HEAD/public/images/products/images-1698161143911-849877721.jpeg -------------------------------------------------------------------------------- /config/db.js: -------------------------------------------------------------------------------- 1 | import mongoose from "mongoose" 2 | mongoose.connect(process.env.MONGODB_URL) 3 | .then(()=>{ 4 | console.log("Connected to database") 5 | }).catch((err)=>{ 6 | console.log(err); 7 | }) 8 | -------------------------------------------------------------------------------- /config/jwtToken.js: -------------------------------------------------------------------------------- 1 | import jwt from 'jsonwebtoken'; 2 | 3 | const generateToken = async (id) => { 4 | return await jwt.sign({id: id},process.env.JWT_SECRET_KEY,{expiresIn:'1d'}); 5 | } 6 | 7 | export {generateToken}; 8 | -------------------------------------------------------------------------------- /config/refreshToken.js: -------------------------------------------------------------------------------- 1 | import jwt from 'jsonwebtoken'; 2 | 3 | const generateRefreshToken = async (id) => { 4 | return await jwt.sign({id: id},process.env.JWT_SECRET_KEY,{expiresIn:'3d'}); 5 | } 6 | 7 | export {generateRefreshToken}; 8 | -------------------------------------------------------------------------------- /utils/error.js: -------------------------------------------------------------------------------- 1 | class appError extends Error { 2 | constructor(){ 3 | super(); 4 | } 5 | create(message, statusCode , statusText){ 6 | this.message = message; 7 | this.statusCode = statusCode; 8 | this.statusText = statusText; 9 | return this; 10 | } 11 | } 12 | 13 | export default new appError -------------------------------------------------------------------------------- /models/colorModel.js: -------------------------------------------------------------------------------- 1 | import mongoose from "mongoose"; 2 | 3 | var colorSchema = new mongoose.Schema({ 4 | title: { 5 | type: String, 6 | required: true, 7 | unique: true, 8 | index: true, 9 | }, 10 | }, 11 | { 12 | timestamps: true, 13 | }); 14 | 15 | //Export the model 16 | export default mongoose.model('Color', colorSchema); -------------------------------------------------------------------------------- /utils/validateMongoDBId.js: -------------------------------------------------------------------------------- 1 | import mongoose from 'mongoose'; 2 | 3 | import appError from './error.js' 4 | import { ERROR } from './errorText.js'; 5 | 6 | 7 | const validateMongoId = (id)=>{ 8 | const isValid = mongoose.Types.ObjectId.isValid(id); 9 | if(!isValid){ 10 | throw appError.create("Invalid id.",400,ERROR); 11 | } 12 | } 13 | 14 | export {validateMongoId}; -------------------------------------------------------------------------------- /models/blogCategoryModel.js: -------------------------------------------------------------------------------- 1 | import mongoose from "mongoose"; 2 | 3 | var blogCategorySchema = new mongoose.Schema({ 4 | title:{ 5 | type:String, 6 | required:true, 7 | unique:true, 8 | index: true 9 | }, 10 | },{ 11 | timestamps: true 12 | }); 13 | 14 | //Export the model 15 | export default mongoose.model("blogCategory", blogCategorySchema) -------------------------------------------------------------------------------- /models/brandModel.js: -------------------------------------------------------------------------------- 1 | import mongoose from "mongoose"; 2 | 3 | var brandSchema = new mongoose.Schema( 4 | { 5 | title: { 6 | type: String, 7 | required: true, 8 | unique: true, 9 | index: true, 10 | }, 11 | }, 12 | { 13 | timestamps: true, 14 | } 15 | ); 16 | 17 | //Export the model 18 | export default mongoose.model("Brand", brandSchema); -------------------------------------------------------------------------------- /models/prodCategoryModel.js: -------------------------------------------------------------------------------- 1 | import mongoose from "mongoose"; 2 | 3 | var prodCategorySchema = new mongoose.Schema({ 4 | title:{ 5 | type:String, 6 | required:true, 7 | unique:true, 8 | index: true 9 | }, 10 | },{ 11 | timestamps: true 12 | }); 13 | 14 | //Export the model 15 | export default mongoose.model("prodCategory", prodCategorySchema) -------------------------------------------------------------------------------- /models/couponModel.js: -------------------------------------------------------------------------------- 1 | import mongoose from "mongoose"; 2 | 3 | var couponSchema = new mongoose.Schema({ 4 | name:{ 5 | type:String, 6 | required:true, 7 | unique:true, 8 | uppercase: true, 9 | }, 10 | expiry: { 11 | type: Date, 12 | required: true 13 | }, 14 | discount:{ 15 | type: Number, 16 | required: true 17 | } 18 | }); 19 | 20 | //Export the model 21 | export default mongoose.model("Coupon", couponSchema) -------------------------------------------------------------------------------- /middlewares/errorHandler.js: -------------------------------------------------------------------------------- 1 | import appError from '../utils/error.js' 2 | import { ERROR } from '../utils/errorText.js'; 3 | // not found 4 | const notFound = (req, res, next) => { 5 | const error = appError.create("Resource not found.",404,ERROR); 6 | next(error) 7 | } 8 | 9 | // error handler 10 | const errorHandler = (err, req, res, next) => { 11 | res.status(err.statusCode || 500).json({status : err.statusText || ERROR , message : err.message, code :err.statusCode || 500 , data : null}); 12 | } 13 | 14 | export {notFound, errorHandler} -------------------------------------------------------------------------------- /routes/brandRoute.js: -------------------------------------------------------------------------------- 1 | import express from "express" 2 | import { 3 | createBrand, 4 | updateBrand, 5 | deleteBrand, 6 | getBrand, 7 | getallBrand, 8 | } from "../controller/brandController.js" 9 | import { authMiddleware, isAdmin} from "../middlewares/authMiddleware.js" 10 | const router = express.Router(); 11 | 12 | router.post("/", authMiddleware, isAdmin, createBrand); 13 | router.put("/:id", authMiddleware, isAdmin, updateBrand); 14 | router.delete("/:id", authMiddleware, isAdmin, deleteBrand); 15 | router.get("/:id", getBrand); 16 | router.get("/", getallBrand); 17 | 18 | export default router -------------------------------------------------------------------------------- /routes/colorRoute.js: -------------------------------------------------------------------------------- 1 | import express from "express" 2 | import { 3 | createColor, 4 | updateColor, 5 | deleteColor, 6 | getColor, 7 | getallColor, 8 | } from "../controller/colorController.js" 9 | import { authMiddleware, isAdmin } from "../middlewares/authMiddleware.js"; 10 | const router = express.Router(); 11 | 12 | router.post("/", authMiddleware, isAdmin, createColor); 13 | router.put("/:id", authMiddleware, isAdmin, updateColor); 14 | router.delete("/:id", authMiddleware, isAdmin, deleteColor); 15 | router.get("/:id", getColor); 16 | router.get("/", getallColor); 17 | 18 | export default router -------------------------------------------------------------------------------- /models/cartModel.js: -------------------------------------------------------------------------------- 1 | import mongoose from "mongoose"; 2 | 3 | var cartSchema = new mongoose.Schema({ 4 | products: [{ 5 | product: { 6 | type: mongoose.Types.ObjectId, 7 | ref: 'Product' 8 | }, 9 | count: { 10 | type: Number, 11 | default: 0 12 | }, 13 | color: String, 14 | price: Number, 15 | },], 16 | cartTotal: Number, 17 | totalAfterDiscount:Number, 18 | orderedBy:{ 19 | type: mongoose.Types.ObjectId, 20 | ref: "User" 21 | } 22 | }, { 23 | timestamps: true 24 | }); 25 | 26 | //Export the model 27 | export default mongoose.model('Cart', cartSchema); -------------------------------------------------------------------------------- /utils/cloudinary.js: -------------------------------------------------------------------------------- 1 | import cloudinary from 'cloudinary'; 2 | 3 | cloudinary.config({ 4 | cloud_name: process.env.CLOUD_NAME, 5 | api_key: process.env.API_KEY, 6 | api_secret: process.env.API_SECRET 7 | }); 8 | 9 | const cloudinaryUpload = async(fileToUpload) => { 10 | return new Promise((resolve) => { 11 | cloudinary.uploader.upload(fileToUpload, (result) => { 12 | resolve( 13 | { 14 | url: result.secure_url, 15 | }, { 16 | resource_type: "auto" 17 | }) 18 | }) 19 | }) 20 | } 21 | 22 | export default cloudinaryUpload -------------------------------------------------------------------------------- /routes/couponRoute.js: -------------------------------------------------------------------------------- 1 | import express from 'express'; 2 | const router = express.Router(); 3 | import {createCoupon, deleteCoupon, getAllCoupons, getSingleCoupon, updateCoupon} from "../controller/couponController.js" 4 | import {isAdmin, authMiddleware} from "../middlewares/authMiddleware.js" 5 | 6 | router.post('/create', authMiddleware, isAdmin, createCoupon); 7 | router.get('/', authMiddleware, isAdmin, getAllCoupons) 8 | router.get('/:id', authMiddleware, isAdmin, getSingleCoupon) 9 | router.put('/:id', authMiddleware, isAdmin, updateCoupon) 10 | router.delete('/:id' ,authMiddleware, isAdmin, deleteCoupon) 11 | 12 | export default router -------------------------------------------------------------------------------- /routes/blogCategoryRoute.js: -------------------------------------------------------------------------------- 1 | import express from "express" 2 | import {createCategory, deleteCategory, getAllCategories, getSingleCategory, updateCategory} from "../controller/blogCategoryController.js" 3 | import { authMiddleware, isAdmin } from "../middlewares/authMiddleware.js"; 4 | const router = express.Router(); 5 | 6 | router.post('/create',authMiddleware, isAdmin, createCategory) 7 | router.get('/', authMiddleware, getAllCategories); 8 | router.get('/:id',authMiddleware, getSingleCategory); 9 | router.put('/:id', authMiddleware, isAdmin, updateCategory); 10 | router.delete('/:id', authMiddleware, isAdmin, deleteCategory); 11 | 12 | export default router -------------------------------------------------------------------------------- /routes/prodCategoryRoute.js: -------------------------------------------------------------------------------- 1 | import express from "express" 2 | import {createCategory, deleteCategory, getAllCategories, getSingleCategory, updateCategory} from "../controller/prodCategoryController.js" 3 | import { authMiddleware, isAdmin } from "../middlewares/authMiddleware.js"; 4 | const router = express.Router(); 5 | 6 | router.post('/create',authMiddleware, isAdmin, createCategory) 7 | router.get('/', authMiddleware, getAllCategories); 8 | router.get('/:id',authMiddleware, getSingleCategory); 9 | router.put('/:id', authMiddleware, isAdmin, updateCategory); 10 | router.delete('/:id', authMiddleware, isAdmin, deleteCategory); 11 | 12 | export default router -------------------------------------------------------------------------------- /models/orderModel.js: -------------------------------------------------------------------------------- 1 | import mongoose from "mongoose"; 2 | 3 | var orderSchema = new mongoose.Schema({ 4 | products: [{ 5 | product: { 6 | type: mongoose.Types.ObjectId, 7 | ref: 'Product' 8 | }, 9 | count: { 10 | type: Number, 11 | default: 0 12 | }, 13 | color: String, 14 | }], 15 | paymentIntent: { 16 | 17 | }, 18 | orderStatus: { 19 | type: String, 20 | default:"Not processed yet.", 21 | enum: ["Not processed yet.", "Cash on delivery", "Processing", "Cancelled", "Delivered"] 22 | }, 23 | orderedBy:{ 24 | type: mongoose.Types.ObjectId, 25 | ref: "User" 26 | } 27 | }, { 28 | timestamps: true 29 | }); 30 | 31 | //Export the model 32 | export default mongoose.model('Order', orderSchema); -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "module", 3 | "name": "e-commerce", 4 | "version": "1.0.0", 5 | "description": "", 6 | "main": "index.js", 7 | "scripts": { 8 | "test": "echo \"Error: no test specified\" && exit 1", 9 | "start": "nodemon app.js" 10 | }, 11 | "keywords": [], 12 | "author": "", 13 | "license": "ISC", 14 | "dependencies": { 15 | "bcrypt": "^5.1.1", 16 | "body-parser": "^1.20.2", 17 | "cloudinary": "^1.41.0", 18 | "cookie-parser": "^1.4.6", 19 | "dotenv": "^16.3.1", 20 | "express": "^4.18.2", 21 | "express-async-handler": "^1.2.0", 22 | "jsonwebtoken": "^9.0.2", 23 | "mongoose": "^7.6.3", 24 | "morgan": "^1.10.0", 25 | "multer": "^1.4.5-lts.1", 26 | "nodemailer": "^6.9.7", 27 | "sharp": "^0.32.6", 28 | "slugify": "^1.6.6", 29 | "uniqid": "^5.4.0" 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /routes/blogRoute.js: -------------------------------------------------------------------------------- 1 | import express from 'express'; 2 | const router = express.Router(); 3 | import {createBlog, deleteBlog, getAllBlogs, getSingleBlog, likeBlog, unlikeBlog, updateBlog, uploadImages} from "../controller/blogController.js" 4 | import { authMiddleware, isAdmin } from '../middlewares/authMiddleware.js'; 5 | import { blogImgResize, uploadPhoto } from '../middlewares/uploadImaged.js'; 6 | 7 | router.post('/create-blog',authMiddleware, isAdmin,createBlog); 8 | router.get('/',authMiddleware, getAllBlogs); 9 | router.get('/:id',authMiddleware, getSingleBlog); 10 | router.delete('/:id',authMiddleware, isAdmin, deleteBlog); 11 | router.put('/:id',authMiddleware, isAdmin, updateBlog); 12 | router.put('/upload/:id', authMiddleware, isAdmin, uploadPhoto.array('images', 10), blogImgResize, uploadImages) 13 | router.put('/like/:id', authMiddleware, likeBlog); 14 | router.put('/dislike/:id',authMiddleware, unlikeBlog) 15 | export default router -------------------------------------------------------------------------------- /routes/productRoute.js: -------------------------------------------------------------------------------- 1 | import express from 'express'; 2 | import { addToWishlist, createProduct, deleteProduct, getAllProducts, getSingleProduct, rating, updateProduct, uploadImages } from '../controller/productController.js'; 3 | import {authMiddleware, isAdmin} from '../middlewares/authMiddleware.js' 4 | import { productImgResize, uploadPhoto } from '../middlewares/uploadImaged.js'; 5 | const router = express.Router(); 6 | 7 | router.post('/add-product',authMiddleware, isAdmin, createProduct); 8 | router.put('/upload/:id', authMiddleware, isAdmin, uploadPhoto.array('images', 10), productImgResize, uploadImages) 9 | router.post('/add-to-wishlist/:id', authMiddleware, addToWishlist) 10 | router.get('/', getAllProducts) 11 | router.get('/:id', getSingleProduct) 12 | router.put('/rate/:id', authMiddleware, rating) 13 | router.put('/:id',authMiddleware, isAdmin, updateProduct) 14 | router.delete('/:id',authMiddleware, isAdmin, deleteProduct) 15 | export default router; -------------------------------------------------------------------------------- /controller/emailController.js: -------------------------------------------------------------------------------- 1 | import nodemailer from 'nodemailer'; 2 | import asyncHandler from 'express-async-handler'; 3 | import { ERROR, FAIL, SUCCESS } from '../utils/errorText.js'; 4 | import appError from '../utils/error.js'; 5 | import { validateMongoId } from "../utils/validateMongoDBId.js"; 6 | 7 | 8 | const sendEmail = asyncHandler(async(data, req, res) => { 9 | const transporter = nodemailer.createTransport({ 10 | host: "smtp-relay.brevo.com", 11 | port: 465, 12 | secure: true, 13 | auth: { 14 | user: process.env.MAIL, 15 | pass: process.env.PASS 16 | } 17 | }); 18 | 19 | const info = await transporter.sendMail({ 20 | from: '"Fred Foo 👻" ', // sender address 21 | to: data.to, // list of receivers 22 | subject: data.subject, // Subject line 23 | text: data.text, // plain text body 24 | html: data.htm, // html body 25 | }); 26 | 27 | console.log("Message sent: %s", info.messageId); 28 | }) 29 | 30 | export {sendEmail} -------------------------------------------------------------------------------- /middlewares/authMiddleware.js: -------------------------------------------------------------------------------- 1 | import User from '../models/userModel.js'; 2 | import jwt from 'jsonwebtoken'; 3 | import asyncHandler from 'express-async-handler'; 4 | import appError from '../utils/error.js' 5 | import { ERROR, FAIL } from '../utils/errorText.js'; 6 | const authMiddleware = asyncHandler(async(req, res, next) => { 7 | let token; 8 | if(req?.headers?.authorization?.startsWith('Bearer')){ 9 | token = req.headers.authorization.split(' ')[1]; 10 | if(token){ 11 | const decodedToken = jwt.verify(token, process.env.JWT_SECRET_KEY); 12 | const { id } = decodedToken; 13 | const user = await User.findById(id); 14 | req.user = user; 15 | next(); 16 | }else{ 17 | throw appError.create("Expired or Invalid token", 401 , FAIL) 18 | } 19 | }else{ 20 | throw appError.create("There is no token",401,FAIL); 21 | } 22 | }) 23 | 24 | const isAdmin = asyncHandler(async(req, res, next)=>{ 25 | const { email } = req.user; 26 | const adminUser = await User.findOne({email:email}); 27 | if(adminUser.role !== "admin"){ 28 | throw appError.create("Unauthorized, Only admin is allowed.",401,ERROR); 29 | }else{ 30 | next(); 31 | } 32 | }) 33 | 34 | 35 | export {authMiddleware, isAdmin} -------------------------------------------------------------------------------- /models/blogModel.js: -------------------------------------------------------------------------------- 1 | import mongoose from "mongoose"; 2 | 3 | var blogSchema = new mongoose.Schema({ 4 | title:{ 5 | type:String, 6 | required:true, 7 | }, 8 | description:{ 9 | type:String, 10 | required:true, 11 | }, 12 | category:{ 13 | type:String, 14 | required:true, 15 | }, 16 | numViews:{ 17 | type:Number, 18 | default : 0 19 | }, 20 | images:[], 21 | isLiked: { 22 | type: Boolean, 23 | default: false 24 | }, 25 | isDisliked : { 26 | type: Boolean, 27 | default: false, 28 | }, 29 | likes: [{ 30 | type: mongoose.Types.ObjectId, 31 | ref: "User" 32 | }], 33 | dislikes: [{ 34 | type: mongoose.Types.ObjectId, 35 | ref: "User" 36 | }], 37 | image: { 38 | type: String, 39 | default: "https://thumbs.dreamstime.com/z/blog-information-website-concept-workplace-background-text-view-above-127465079.jpg?w=992" 40 | }, 41 | author: { 42 | type: String, 43 | default: "Admin" 44 | } 45 | },{ 46 | toJSON: { 47 | virtuals: true 48 | }, 49 | toObject: { 50 | virtuals: true 51 | }, 52 | timestamps: true 53 | }); 54 | 55 | //Export the model 56 | export default mongoose.model("blog",blogSchema); -------------------------------------------------------------------------------- /models/productModel.js: -------------------------------------------------------------------------------- 1 | import mongoose from "mongoose"; 2 | 3 | var productSchema = new mongoose.Schema({ 4 | title:{ 5 | type:String, 6 | required:true, 7 | trim: true, 8 | }, 9 | slug:{ 10 | type:String, 11 | required:true, 12 | unique:true, 13 | lowercase:true 14 | }, 15 | description:{ 16 | type:String, 17 | required:true, 18 | }, 19 | price:{ 20 | type:Number, 21 | required:true, 22 | }, 23 | category: { 24 | type: String, 25 | required: true 26 | }, 27 | brand:{ 28 | type: String, 29 | required: true 30 | }, 31 | quantity:{ 32 | type:Number, 33 | required: true, 34 | //select : false 35 | }, 36 | sold:{ 37 | type: Number, 38 | default: 0, 39 | //select : false 40 | }, 41 | images:[], 42 | color : { 43 | type: String, 44 | required: true 45 | }, 46 | ratings: [{ 47 | star: Number, 48 | comment:String, 49 | postedby: { 50 | type: mongoose.Types.ObjectId, 51 | ref: 'User' 52 | }, 53 | }], 54 | totalRating : { 55 | type: String, 56 | default: 0, 57 | 58 | } 59 | }, 60 | { 61 | timestamps: true 62 | }); 63 | 64 | //Export the model 65 | export default mongoose.model("Product", productSchema); -------------------------------------------------------------------------------- /app.js: -------------------------------------------------------------------------------- 1 | import 'dotenv/config' 2 | import express from "express"; 3 | import morgan from 'morgan'; 4 | import bodyParser from "body-parser"; 5 | import './config/db.js' 6 | import authRoutes from './routes/authRoutes.js' 7 | import { errorHandler, notFound } from './middlewares/errorHandler.js'; 8 | import cookieParser from 'cookie-parser'; 9 | import productRouter from './routes/productRoute.js'; 10 | import blogRouter from "./routes/blogRoute.js" 11 | import prodCategoryRouter from "./routes/prodCategoryRoute.js" 12 | import blogCategoryRouter from "./routes/blogCategoryRoute.js" 13 | import couponRouter from "./routes/couponRoute.js" 14 | import colorRouter from "./routes/colorRoute.js" 15 | import brandRouter from "./routes/brandRoute.js" 16 | const PORT = process.env.PORT || 3000 ; 17 | const app = express(); 18 | 19 | app.use(bodyParser.json()) 20 | app.use(bodyParser.urlencoded({extended : false})); 21 | app.use(cookieParser()) 22 | app.use(morgan("dev")) 23 | 24 | app.use('/api/user', authRoutes); 25 | app.use('/api/product', productRouter); 26 | app.use('/api/blog', blogRouter); 27 | app.use('/api/prod-category', prodCategoryRouter) 28 | app.use('/api/blog-category', blogCategoryRouter); 29 | app.use('/api/coupon', couponRouter); 30 | app.use('/api/color', colorRouter) 31 | app.use('/api/brand', brandRouter) 32 | app.use(notFound) 33 | app.use(errorHandler) 34 | app.listen(PORT, () => { 35 | console.log("listening on port " + PORT); 36 | }) 37 | 38 | -------------------------------------------------------------------------------- /controller/colorController.js: -------------------------------------------------------------------------------- 1 | import Color from "../models/colorModel.js" 2 | import asyncHandler from "express-async-handler" 3 | import { validateMongoId as validateMongoDbId } from "../utils/validateMongoDBId.js"; 4 | 5 | const createColor = asyncHandler(async (req, res) => { 6 | try { 7 | const newColor = await Color.create(req.body); 8 | res.json(newColor); 9 | } catch (error) { 10 | throw new Error(error); 11 | } 12 | }); 13 | const updateColor = asyncHandler(async (req, res) => { 14 | const { id } = req.params; 15 | validateMongoDbId(id); 16 | try { 17 | const updatedColor = await Color.findByIdAndUpdate(id, req.body, { 18 | new: true, 19 | }); 20 | res.json(updatedColor); 21 | } catch (error) { 22 | throw new Error(error); 23 | } 24 | }); 25 | const deleteColor = asyncHandler(async (req, res) => { 26 | const { id } = req.params; 27 | validateMongoDbId(id); 28 | try { 29 | const deletedColor = await Color.findByIdAndDelete(id); 30 | res.json(deletedColor); 31 | } catch (error) { 32 | throw new Error(error); 33 | } 34 | }); 35 | const getColor = asyncHandler(async (req, res) => { 36 | const { id } = req.params; 37 | validateMongoDbId(id); 38 | try { 39 | const getaColor = await Color.findById(id); 40 | res.json(getaColor); 41 | } catch (error) { 42 | throw new Error(error); 43 | } 44 | }); 45 | const getallColor = asyncHandler(async (req, res) => { 46 | try { 47 | const getallColor = await Color.find(); 48 | res.json(getallColor); 49 | } catch (error) { 50 | throw new Error(error); 51 | } 52 | }); 53 | export { 54 | createColor, 55 | updateColor, 56 | deleteColor, 57 | getColor, 58 | getallColor, 59 | }; -------------------------------------------------------------------------------- /controller/brandController.js: -------------------------------------------------------------------------------- 1 | import Brand from "../models/brandModel.js" 2 | import asyncHandler from "express-async-handler" 3 | import { validateMongoId as validateMongoDbId } from "../utils/validateMongoDBId.js"; 4 | 5 | const createBrand = asyncHandler(async (req, res) => { 6 | try { 7 | const newBrand = await Brand.create(req.body); 8 | res.json(newBrand); 9 | } catch (error) { 10 | throw new Error(error); 11 | } 12 | }); 13 | const updateBrand = asyncHandler(async (req, res) => { 14 | const { id } = req.params; 15 | validateMongoDbId(id); 16 | try { 17 | const updatedBrand = await Brand.findByIdAndUpdate(id, req.body, { 18 | new: true, 19 | }); 20 | res.json(updatedBrand); 21 | } catch (error) { 22 | throw new Error(error); 23 | } 24 | }); 25 | const deleteBrand = asyncHandler(async (req, res) => { 26 | const { id } = req.params; 27 | validateMongoDbId(id); 28 | try { 29 | const deletedBrand = await Brand.findByIdAndDelete(id); 30 | res.json(deletedBrand); 31 | } catch (error) { 32 | throw new Error(error); 33 | } 34 | }); 35 | const getBrand = asyncHandler(async (req, res) => { 36 | const { id } = req.params; 37 | validateMongoDbId(id); 38 | try { 39 | const getaBrand = await Brand.findById(id); 40 | res.json(getaBrand); 41 | } catch (error) { 42 | throw new Error(error); 43 | } 44 | }); 45 | const getallBrand = asyncHandler(async (req, res) => { 46 | try { 47 | const getallBrand = await Brand.find(); 48 | res.json(getallBrand); 49 | } catch (error) { 50 | throw new Error(error); 51 | } 52 | }); 53 | 54 | export{ 55 | createBrand, 56 | updateBrand, 57 | deleteBrand, 58 | getBrand, 59 | getallBrand, 60 | }; -------------------------------------------------------------------------------- /controller/couponController.js: -------------------------------------------------------------------------------- 1 | import Coupon from "../models/couponModel.js" 2 | import AsyncHandler from "express-async-handler"; 3 | import { ERROR, FAIL, SUCCESS } from '../utils/errorText.js'; 4 | import appError from '../utils/error.js'; 5 | import { validateMongoId } from "../utils/validateMongoDBId.js"; 6 | 7 | const createCoupon = AsyncHandler(async(req, res, next) => { 8 | const newCoupon = await Coupon.create({...req.body}); 9 | res.status(201).json({status: SUCCESS, data: newCoupon}); 10 | }) 11 | 12 | const getAllCoupons = AsyncHandler(async(req, res, next) => { 13 | const coupons = await Coupon.find({}); 14 | res.status(200).json({status:SUCCESS, data: coupons}) 15 | }) 16 | 17 | const getSingleCoupon = AsyncHandler(async(req, res, next) => { 18 | const {id} = req.params; 19 | validateMongoId(id); 20 | const coupon = await Coupon.findById(id); 21 | if(!coupon){ 22 | throw appError.create("Resource not found", 404, ERROR) 23 | }else{ 24 | res.status(200).json({status: SUCCESS, data: coupon}) 25 | } 26 | }) 27 | 28 | const updateCoupon = AsyncHandler(async(req, res, next) => { 29 | const {id} = req.params; 30 | validateMongoId(id); 31 | const updatedCoupon = await Coupon.findByIdAndUpdate(id, {...req.body}, {new : true}); 32 | if(!updatedCoupon){ 33 | throw appError.create("Resource not found.", 404, ERROR) 34 | }else{ 35 | res.status(200).json({status: SUCCESS, data: updatedCoupon}) 36 | } 37 | }) 38 | 39 | const deleteCoupon = AsyncHandler(async(req, res, next) => { 40 | const {id} = req.params; 41 | validateMongoId(id); 42 | const deletedCoupon = await Coupon.findByIdAndDelete(id); 43 | if(!deletedCoupon){ 44 | throw appError.create("Resource not found.", 404, ERROR) 45 | }else{ 46 | res.status(200).json({status: SUCCESS, data : null}) 47 | } 48 | }) 49 | export {createCoupon, getAllCoupons, updateCoupon, getSingleCoupon, deleteCoupon} -------------------------------------------------------------------------------- /routes/authRoutes.js: -------------------------------------------------------------------------------- 1 | import express from 'express'; 2 | const router = express.Router(); 3 | import {registerUser , login, getAllUsers, getSingleUser, deleteSingleUser, updateSingleUser, blockUser, unBlockUser, deleteMyAccount, handleRefreshToken, logOut, forgotPasswordToken, changePassword, resetPassword, adminLogin, getWishlist, saveAddress, userCart, getUserCart, emptyCart, applyCoupon, createOrder, getOrders, updateOrderStatus} from '../controller/userController.js' 4 | import {authMiddleware, isAdmin} from '../middlewares/authMiddleware.js' 5 | 6 | 7 | router.post('/register', registerUser) 8 | router.post('/login',login) 9 | router.post('/admin-login', adminLogin) 10 | router.post('/cart', authMiddleware, userCart) 11 | router.post('/cart/create-order',authMiddleware, createOrder) 12 | router.post('/cart/apply-coupon',authMiddleware, applyCoupon) 13 | router.post('/forgot-password', forgotPasswordToken) 14 | router.get('/refresh', handleRefreshToken) 15 | router.get('/get-cart',authMiddleware, getUserCart) 16 | router.get('/wishlist', authMiddleware, getWishlist) 17 | router.get('/logout',logOut) 18 | router.get('/all-users',authMiddleware, isAdmin, getAllUsers) 19 | router.get('/cart/orders',authMiddleware, getOrders) 20 | router.get('/:id',authMiddleware,isAdmin,getSingleUser); 21 | router.delete('/empty-cart', authMiddleware, emptyCart) 22 | router.delete('/delete-user/:id',authMiddleware,isAdmin, deleteSingleUser); 23 | router.delete('/deleteMyAcount',authMiddleware,deleteMyAccount); 24 | router.put('/address', authMiddleware, saveAddress) 25 | router.put('/updateProfile',authMiddleware, updateSingleUser) 26 | router.put('/change-password',authMiddleware,changePassword); 27 | router.put('/update-order/:id', authMiddleware, isAdmin, updateOrderStatus) 28 | router.put('/block-user/:id',authMiddleware, isAdmin, blockUser); 29 | router.put('/unblock-user/:id',authMiddleware, isAdmin, unBlockUser) 30 | router.put('/reset-password/:token', resetPassword); 31 | 32 | export default router; -------------------------------------------------------------------------------- /controller/prodCategoryController.js: -------------------------------------------------------------------------------- 1 | import prodCategory from "../models/prodCategoryModel.js" 2 | import asyncHandler from 'express-async-handler'; 3 | import { ERROR, FAIL, SUCCESS } from '../utils/errorText.js'; 4 | import appError from '../utils/error.js'; 5 | import { validateMongoId } from "../utils/validateMongoDBId.js"; 6 | 7 | const createCategory = asyncHandler(async (req, res, next) => { 8 | const newCategory = await prodCategory.create({...req.body}); 9 | res.status(201).json({status: SUCCESS, data: newCategory}) 10 | }) 11 | 12 | const updateCategory = asyncHandler(async(req, res, next) => { 13 | const {id} = req.params; 14 | validateMongoId(id); 15 | const updatedCat = await prodCategory.findByIdAndUpdate(id,{...req.body}, {new: true}); 16 | if(!updateCategory){ 17 | throw appError.create("resource not found.", 404, FAIL); 18 | }else{ 19 | res.status(200).json({status:SUCCESS, data:updatedCat}); 20 | } 21 | }) 22 | 23 | const deleteCategory = asyncHandler(async(req, res, next) => { 24 | const {id} = req.params; 25 | validateMongoId(id); 26 | const deletedCat = await prodCategory.findByIdAndDelete(id); 27 | if(!deletedCat){ 28 | throw appError.create("Resource not found.", 404, FAIL) 29 | }else{ 30 | res.status(200).json({status: SUCCESS, data: null}) 31 | }}) 32 | 33 | const getAllCategories = asyncHandler(async (req, res, next) => { 34 | const limit = req.query.limit || 10; 35 | const page = req.query.page || 1; 36 | const skip = (page - 1) * limit; 37 | const categories = await prodCategory.find({}).skip(skip).limit(limit); 38 | res.status(200).json({status: SUCCESS, data: categories}) 39 | }) 40 | 41 | const getSingleCategory = asyncHandler(async(req, res, next) => { 42 | const {id} = req.params; 43 | validateMongoId(id); 44 | const category = await prodCategory.findById(id); 45 | if(!category){ 46 | throw appError.create("Resource not found.", 404, FAIL); 47 | }else{ 48 | res.status(200).json({status: SUCCESS, data: category}); 49 | } 50 | }) 51 | export {createCategory,updateCategory, deleteCategory, getAllCategories, getSingleCategory} -------------------------------------------------------------------------------- /controller/blogCategoryController.js: -------------------------------------------------------------------------------- 1 | import blogCategory from "../models/blogCategoryModel.js" 2 | import asyncHandler from 'express-async-handler'; 3 | import { ERROR, FAIL, SUCCESS } from '../utils/errorText.js'; 4 | import appError from '../utils/error.js'; 5 | import { validateMongoId } from "../utils/validateMongoDBId.js"; 6 | 7 | const createCategory = asyncHandler(async (req, res, next) => { 8 | const newCategory = await blogCategory.create({...req.body}); 9 | res.status(201).json({status: SUCCESS, data: newCategory}) 10 | }) 11 | 12 | const updateCategory = asyncHandler(async(req, res, next) => { 13 | const {id} = req.params; 14 | validateMongoId(id); 15 | const updatedCat = await blogCategory.findByIdAndUpdate(id,{...req.body}, {new: true}); 16 | if(!updateCategory){ 17 | throw appError.create("resource not found.", 404, FAIL); 18 | }else{ 19 | res.status(200).json({status:SUCCESS, data:updatedCat}); 20 | } 21 | }) 22 | 23 | const deleteCategory = asyncHandler(async(req, res, next) => { 24 | const {id} = req.params; 25 | validateMongoId(id); 26 | const deletedCat = await blogCategory.findByIdAndDelete(id); 27 | if(!deletedCat){ 28 | throw appError.create("Resource not found.", 404, FAIL) 29 | }else{ 30 | res.status(200).json({status: SUCCESS, data: null}) 31 | }}) 32 | 33 | const getAllCategories = asyncHandler(async (req, res, next) => { 34 | const limit = req.query.limit || 10; 35 | const page = req.query.page || 1; 36 | const skip = (page - 1) * limit; 37 | const categories = await blogCategory.find({}).skip(skip).limit(limit); 38 | res.status(200).json({status: SUCCESS, data: categories}) 39 | }) 40 | 41 | const getSingleCategory = asyncHandler(async(req, res, next) => { 42 | const {id} = req.params; 43 | validateMongoId(id); 44 | const category = await blogCategory.findById(id); 45 | if(!category){ 46 | throw appError.create("Resource not found.", 404, FAIL); 47 | }else{ 48 | res.status(200).json({status: SUCCESS, data: category}); 49 | } 50 | }) 51 | 52 | export {createCategory,updateCategory, deleteCategory, getAllCategories, getSingleCategory} -------------------------------------------------------------------------------- /middlewares/uploadImaged.js: -------------------------------------------------------------------------------- 1 | import multer from "multer"; 2 | import sharp from "sharp"; 3 | import path from "path" 4 | import { URL } from 'url'; 5 | import { fileURLToPath } from 'url'; 6 | 7 | const __filename = fileURLToPath(import.meta.url); 8 | const __dirname = path.dirname(__filename); 9 | import fs from "fs" 10 | 11 | 12 | const multerStorage = multer.diskStorage({ 13 | destination: function (req, file, cb) { 14 | cb(null, path.join(__dirname, "../public/images/")); 15 | }, 16 | filename: function (req, file, cb) { 17 | const uniquesuffix = Date.now() + "-" + Math.round(Math.random() * 1e9); 18 | cb(null, file.fieldname + "-" + uniquesuffix + ".jpeg"); 19 | }, 20 | }) 21 | 22 | const multerFilter = (req, file, cb) => { 23 | if(file.mimetype.startsWith('image')){ 24 | cb(null, true) 25 | }else{ 26 | cb({ 27 | message:"unsupported file format" 28 | }, false) 29 | } 30 | } 31 | 32 | const uploadPhoto = multer({ 33 | storage: multerStorage, 34 | fileFilter: multerFilter, 35 | limits: {fieldSize: 2000000}, 36 | // x : console.log(__dirname) 37 | }) 38 | 39 | 40 | const productImgResize = async(req, res, next) => { 41 | if(!req.files){ 42 | return next(); 43 | } 44 | await Promise.all( 45 | req.files.map(async file => { 46 | await sharp(file.path) 47 | .resize(300,300) 48 | .toFormat('jpeg') 49 | .jpeg({quality:90}) 50 | .toFile(`public/images/products/${file.filename}`) 51 | fs.unlinkSync(`public/images/products/${file.filename}`); 52 | }) 53 | ) 54 | next() 55 | } 56 | 57 | const blogImgResize = async(req, res, next) => { 58 | if(!req.files){ 59 | return next(); 60 | } 61 | await Promise.all( 62 | req.files.map(async file => { 63 | await sharp(file.path) 64 | .resize(300,300) 65 | .toFormat('jpeg') 66 | .jpeg({quality:90}) 67 | .toFile(`public/images/blogs/${file.filename}`) 68 | fs.unlinkSync(`public/images/blogs/${file.filename}`); 69 | 70 | }) 71 | ) 72 | next() 73 | } 74 | 75 | export {uploadPhoto, productImgResize, blogImgResize} -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ![](http://imgur.com/t3teAxi.png) 2 | ### :handbag: A simple RESTful API for Purchases and Products 3 | 4 | ## Features 5 | 6 | Products Features 7 | 8 | | Feature | Coded? | Description | 9 | |----------|:-------------:|:-------------| 10 | | Add a Product | ✔ | Ability of Add a Product on the System | 11 | | List Products | ✔ | Ability of List Products | 12 | | Edit a Product | ✔ | Ability of Edit a Product | 13 | | Delete a Product | ✔ | Ability of Delete a Product | 14 | | Stock | ✔ | Ability of Update the Stock | 15 | | Stock History | ✔ | Ability to see the Stock History | 16 | 17 | Purchase Features 18 | 19 | | Feature | Coded? | Description | 20 | |----------|:-------------:|:-------------| 21 | | Create a Cart | ✔ | Ability of Create a new Cart | 22 | | See Cart | ✔ | Ability to see the Cart and it items | 23 | | Remove a Cart | ✔ | Ability of Remove a Cart | 24 | | Add Item | ✔ | Ability of add a new Item on the Cart | 25 | | Remove a Item | ✔ | Ability of Remove a Item from the Cart | 26 | | Checkout | ✔ | Ability to Checkout | 27 | 28 | # eCommerce 29 | 30 | **eCommerce** it's an open source (test scenario) software made to create a easy and simple "Shop" API, where you have two micro services, one the **Products API** that stores and handles everything Related to Stock and Products. And the **Purchase API** where you can create orders (cart's) and checkout items. 31 | 32 | # E-Commerce-API 33 | 34 | E-Commerce-API is a simple RESTful API for purchases and products, built with Node.js, Express, and MongoDB. 35 | 36 | ## Features 37 | 38 | - Products API: handles everything related to stock and products, such as adding, listing, editing, deleting, and updating products. 39 | - Purchase API: handles everything related to orders and carts, such as creating, viewing, removing, adding items, removing items, and checking out. 40 | 41 | ## Installation 42 | 43 | To run this project, you need to have Node.js and MongoDB installed on your system. 44 | 45 | 1. Clone this repository: `git clone https://github.com/0xEbrahim/E-Commerce-API.git` 46 | 2. Install the dependencies: `npm install` 47 | 3. Start the server: `npm start` 48 | 49 | ## Usage 50 | 51 | The API endpoints are documented in the [Postman collection](https://github.com/0xEbrahim/E-Commerce-API/blob/main/E-Commerce.postman_collection.json). You can import it to Postman and test the API. 52 | 53 | 54 | -------------------------------------------------------------------------------- /models/userModel.js: -------------------------------------------------------------------------------- 1 | import mongoose from "mongoose"; 2 | import bcrypt, { hash } from 'bcrypt' 3 | import crypto from 'crypto' 4 | 5 | // Declare the Schema of the Mongo model 6 | var userSchema = new mongoose.Schema({ 7 | firstName:{ 8 | type:String, 9 | required:true, 10 | 11 | }, 12 | lastName:{ 13 | type:String, 14 | required:true, 15 | 16 | }, 17 | email:{ 18 | type:String, 19 | required:true, 20 | unique:true, 21 | }, 22 | mobile:{ 23 | type:String, 24 | required:true, 25 | unique:true, 26 | }, 27 | password:{ 28 | type:String, 29 | required:true, 30 | }, 31 | role:{ 32 | type:String, 33 | default:`user` 34 | }, 35 | isBlocked:{ 36 | type: Boolean, 37 | default: false 38 | }, 39 | cart:{ 40 | type:Array, 41 | default:[] 42 | }, 43 | address:{ 44 | type: String 45 | }, 46 | wishlist:[{ 47 | type: mongoose.Types.ObjectId, 48 | ref: "Product" 49 | }], 50 | refreshToken : { 51 | type:String, 52 | }, 53 | passwordChangedAt : { 54 | type : Date 55 | }, 56 | passwordResetToken:{ 57 | type:String 58 | }, 59 | passwordResetExpires:{ 60 | type:Date 61 | } 62 | }, 63 | { 64 | timestamps: true, 65 | } 66 | ); 67 | 68 | userSchema.pre("save", async function (next){ 69 | if(!this.isModified('password')){ 70 | next(); 71 | } 72 | const salt = await bcrypt.genSalt(10) 73 | this.password = await bcrypt.hash(this.password, salt); 74 | }) 75 | 76 | userSchema.methods.isMatchPassword = async function (enteredPassword){ 77 | return await bcrypt.compare(enteredPassword, this.password); 78 | } 79 | 80 | userSchema.methods.createPasswordResetToken = async function(){ 81 | const resetToken = crypto 82 | .randomBytes(32) 83 | .toString("hex") 84 | 85 | this.passwordResetToken = crypto 86 | .createHash('sha256') 87 | .update(resetToken) 88 | .digest("hex") 89 | 90 | //console.log(resetToken, this.passwordResetToken, hashed); 91 | this.passwordResetExpires = Date.now() + 30 * 60 * 1000; // 30 minutes 92 | return resetToken; 93 | } 94 | 95 | 96 | //Export the model 97 | export default mongoose.model('User', userSchema) -------------------------------------------------------------------------------- /controller/blogController.js: -------------------------------------------------------------------------------- 1 | import Blog from '../models/blogModel.js'; 2 | import User from '../models/userModel.js'; 3 | import asyncHandler from 'express-async-handler'; 4 | import appError from '../utils/error.js' 5 | import { ERROR, FAIL, SUCCESS } from '../utils/errorText.js'; 6 | import { validateMongoId } from '../utils/validateMongoDBId.js'; 7 | import cloudinaryUpload from '../utils/cloudinary.js'; 8 | import fs from 'fs' 9 | 10 | const createBlog = asyncHandler(async (req, res, next) => { 11 | const newBlog = await Blog.create(req.body); 12 | res.status(201).json({status: SUCCESS, data: newBlog}); 13 | }) 14 | 15 | const getAllBlogs = asyncHandler(async (req, res, next) => { 16 | const query = req.query; 17 | const limit = query.limit || 10; 18 | const page = query.page || 1; 19 | const blogs = await Blog 20 | .find({}, {__v : false}) 21 | .limit(limit) 22 | .skip(((page - 1) * limit)); 23 | 24 | res.json({status:SUCCESS, data : {blogs}}); 25 | }) 26 | 27 | const getSingleBlog = asyncHandler(async (req, res, next) => { 28 | const {id} = req.params; 29 | validateMongoId(id); 30 | const blog = await Blog.findById(id) 31 | .populate('likes') 32 | .populate('dislikes'); 33 | if(!blog){ 34 | throw appError.create("Resource not found.", 404 , ERROR); 35 | }else{ 36 | await Blog.findByIdAndUpdate(id,{$inc: {numViews: 1}}, {new: true}) 37 | res.status(200).json({status : SUCCESS, data: blog}) 38 | } 39 | }) 40 | 41 | const deleteBlog = asyncHandler(async(req, res, next) => { 42 | const {id} = req.params; 43 | validateMongoId(id); 44 | const deletedBlog = await Blog.findByIdAndDelete(id); 45 | if(!deletedBlog){ 46 | throw appError.create("Resource not found", 404, FAIL); 47 | }else{ 48 | res.status(200).json({status: SUCCESS, data : deletedBlog}); 49 | } 50 | }) 51 | 52 | const updateBlog = asyncHandler(async (req, res, next) => { 53 | const {id} = req.params; 54 | validateMongoId(id); 55 | const updatedBlog = await Blog.findByIdAndUpdate(id,{...req.body},{new : true}); 56 | if(!updatedBlog){ 57 | throw appError.create("Resource not found", 404, FAIL); 58 | }else{ 59 | res.status(200).json({status: SUCCESS, data: updatedBlog}); 60 | } 61 | }) 62 | 63 | 64 | const likeBlog = asyncHandler(async (req, res, next) => { 65 | let LikeTheBlog; 66 | const {id} = req.params; 67 | validateMongoId(id) 68 | const blog = await Blog.findById(id); 69 | const loginUserId = req?.user?._id; 70 | if(!blog){ 71 | throw appError.create("Resource not found", 404, ERROR); 72 | }else{ 73 | const isLiked = blog?.isLiked; 74 | const isDisLiked = blog?.dislikes?.find( 75 | userId => userId?.toString() === loginUserId?.toString() 76 | ); 77 | 78 | if(isDisLiked){ 79 | LikeTheBlog = await Blog.findByIdAndUpdate(id, 80 | {$pull:{ 81 | dislikes:loginUserId 82 | }, 83 | isDisliked:false 84 | }, 85 | { 86 | new: true 87 | }) 88 | } 89 | if(isLiked){ 90 | LikeTheBlog = await Blog.findByIdAndUpdate(id, 91 | {$pull:{ 92 | likes:loginUserId 93 | }, 94 | isLiked:false 95 | }, 96 | { 97 | new: true 98 | }) 99 | }else{ 100 | LikeTheBlog = await Blog.findByIdAndUpdate(id, 101 | {$push:{ 102 | likes:loginUserId 103 | }, 104 | isLiked:true 105 | }, 106 | { 107 | new: true 108 | }) 109 | } 110 | res.status(200).json({status: SUCCESS , data: LikeTheBlog}) 111 | }}) 112 | 113 | const unlikeBlog = asyncHandler(async (req, res, next) => { 114 | 115 | let disLikeTheBlog; 116 | const {id} = req.params; 117 | validateMongoId(id) 118 | const blog = await Blog.findById(id); 119 | const loginUserId = req?.user?._id; 120 | if(!blog){ 121 | throw appError.create("Resource not found", 404, ERROR); 122 | }else{ 123 | const isDisLiked = blog?.isDisliked; 124 | const isLiked = blog?.likes?.find( 125 | userId => userId?.toString() === loginUserId?.toString() 126 | ); 127 | if(isLiked){ 128 | await Blog.findByIdAndUpdate(id, 129 | {$pull:{ 130 | likes:loginUserId 131 | }, 132 | isLiked:false 133 | }, 134 | { 135 | new: true 136 | }) 137 | } 138 | if(isDisLiked){ 139 | disLikeTheBlog = await Blog.findByIdAndUpdate(id, 140 | {$pull: 141 | { 142 | dislikes:loginUserId 143 | }, 144 | isDisliked:false 145 | }, 146 | { 147 | new: true 148 | }) 149 | }else{ 150 | disLikeTheBlog = await Blog.findByIdAndUpdate(id, 151 | { 152 | $push:{ 153 | dislikes:loginUserId 154 | }, 155 | isDisliked:true 156 | }, 157 | { 158 | new: true 159 | }) 160 | } 161 | res.status(200).json({status: SUCCESS , data: disLikeTheBlog}) 162 | } 163 | }) 164 | 165 | const uploadImages = asyncHandler(async(req, res ,next) => { 166 | const {id} = req.params; 167 | validateMongoId(id); 168 | const uploader = (pth) => cloudinaryUpload(pth, "images"); 169 | const url = []; 170 | const files = req.files; 171 | //console.log("files " , files) 172 | for(const file of files){ 173 | const {path: pth} = file; 174 | //console.log("LST => path" , pth); 175 | const newPath = await uploader(pth); 176 | // console.log("NEW => ",newPath) 177 | url.push(newPath) 178 | fs.unlinkSync(pth); 179 | } 180 | const findBlog = await Blog.findByIdAndUpdate(id, {images: url.map(file=>{return file}), },{new : true}) 181 | res.status(200).json({status: SUCCESS, data : findBlog}) 182 | }) 183 | 184 | export {createBlog, uploadImages, getAllBlogs, getSingleBlog, deleteBlog, updateBlog, likeBlog, unlikeBlog} -------------------------------------------------------------------------------- /controller/productController.js: -------------------------------------------------------------------------------- 1 | import Product from "../models/productModel.js"; 2 | import User from '../models/userModel.js' 3 | import AsyncHandler from "express-async-handler"; 4 | import { ERROR, FAIL, SUCCESS } from '../utils/errorText.js'; 5 | import appError from '../utils/error.js'; 6 | import { validateMongoId } from "../utils/validateMongoDBId.js"; 7 | import slugify from "slugify"; 8 | import cloudinaryUpload from "../utils/cloudinary.js"; 9 | import fs from "fs" 10 | 11 | const createProduct = AsyncHandler(async(req, res, next) => { 12 | if(req.body.title){ 13 | req.body.slug = slugify(req.body.title); 14 | } 15 | 16 | const newProduct = await Product.create(req.body) 17 | res.status(201).json({status: SUCCESS, data : newProduct}) 18 | }) 19 | 20 | const updateProduct = AsyncHandler(async(req, res, next) => { 21 | const {id} = req.params; 22 | validateMongoId(id); 23 | const product = await Product.findById(id); 24 | if(!product){ 25 | throw appError.create("Resource not found", 404 , ERROR); 26 | }else{ 27 | if(req.body.title){ 28 | req.body.slug = slugify(req.body.title); 29 | } 30 | const updatedProduct = await Product.findByIdAndUpdate(id, {...req.body},{new:true}) 31 | res.status(200).json({status:SUCCESS,data:updatedProduct}); 32 | } 33 | }) 34 | 35 | const deleteProduct = AsyncHandler(async(req, res, next) => { 36 | const {id} = req.params; 37 | validateMongoId(id); 38 | const product = await Product.findById(id); 39 | if(!product){ 40 | throw appError.create("Resource not found", 404 , ERROR); 41 | }else{ 42 | const deletedProduct = await Product.findByIdAndDelete(id) 43 | res.status(200).json({status:SUCCESS,data:deletedProduct}); 44 | } 45 | }) 46 | 47 | const getAllProducts = AsyncHandler(async(req, res , next) => { 48 | 49 | // filtering 50 | const queryObj = { ...req.query }; 51 | const excludeFields = ["page", "sort", "limit", "fields"]; 52 | excludeFields.forEach((el) => delete queryObj[el]); 53 | let queryStr = JSON.stringify(queryObj); 54 | queryStr = queryStr.replace(/\b(gte|gt|lte|lt)\b/g, (match) => `$${match}`); 55 | 56 | let query = Product.find(JSON.parse(queryStr)); 57 | 58 | // sorting 59 | if(req.query.sort){ 60 | const sortBy = req.query.sort.split(",").join(" "); 61 | query = query.sort(sortBy) 62 | }else{ 63 | query = query.sort('-createdAt') 64 | } 65 | 66 | // limiting fields 67 | if(req.query.fields){ 68 | const fields = req.query.fields.split(",").join(" "); 69 | query = query.select(fields) 70 | }else{ 71 | query = query.select('-__v'); 72 | } 73 | 74 | // pagination 75 | const limit = req.query.limit ; 76 | const page = req.query.page ; 77 | const skip = (page - 1) * limit; 78 | query = query.skip(skip).limit(limit); 79 | if(req.query.page){ 80 | const productCount = await Product.countDocuments(); 81 | if(skip >= productCount){ 82 | throw appError.create("This page does not exist",404,ERROR); 83 | } 84 | } 85 | 86 | const products = await query.populate("ratings.postedby"); 87 | res.status(200).json({status:SUCCESS , data : products}) 88 | }) 89 | 90 | const getSingleProduct = AsyncHandler(async (req, res, next) => { 91 | const {id} = req.params; 92 | validateMongoId(id); 93 | const product = await Product.findById(id).populate("ratings.postedby"); 94 | if(!product){ 95 | throw appError.create("Resource not found", 404 , ERROR); 96 | }else{ 97 | res.status(200).json({status:SUCCESS , data:product}) 98 | } 99 | }) 100 | 101 | const addToWishlist = AsyncHandler(async (req, res, next) => { 102 | const {_id : id} = req.user; 103 | validateMongoId(id); 104 | const {id : productId} = req.params; 105 | validateMongoId(productId); 106 | let user = await User.findById(id); 107 | const alreadyAdded = user?.wishlist?.find((prodId)=>prodId?.toString()===productId.toString()); 108 | console.log(alreadyAdded) 109 | if(alreadyAdded){ 110 | user = await User.findByIdAndUpdate(id,{$pull: {wishlist : productId}}, {new:true}); 111 | }else{ 112 | user = await User.findByIdAndUpdate(id,{$push: {wishlist : productId}}, {new:true}); 113 | } 114 | const product = await Product.findById(productId); 115 | if(!user || !product){ 116 | throw appError.create("Resource not found.", 404 , ERROR); 117 | }else{ 118 | res.status(200).json({status: SUCCESS, data: user}) 119 | } 120 | }) 121 | 122 | const rating = AsyncHandler(async(req, res, next) => { 123 | const {_id : userId} = req.user; 124 | validateMongoId(userId); 125 | //console.log("Khaled Id : ", userId); 126 | const {id} = req.params; 127 | const {star, comment} = req.body; 128 | validateMongoId(id); 129 | const user = await User.findById(userId); 130 | // console.log("Khaled: ", user); 131 | let product = await Product.findById(id); 132 | 133 | if(!user || !product){ 134 | throw appError.create("Resource not found.", 404, ERROR); 135 | }else{ 136 | // console.log("prp ",product.ratings) 137 | let alreadyRated = product?.ratings?.find((usrId) => usrId?.postedby?.toString() === userId?.toString()); 138 | // return console.log("__++++___+++", alreadyRated) 139 | if(alreadyRated){ 140 | // console.log("+++++____+++++"); 141 | product = await Product.updateOne( 142 | { 143 | ratings: { $elemMatch: alreadyRated }, 144 | }, 145 | { 146 | $set: { "ratings.$.star": star, "ratings.$.comment": comment }, 147 | }, 148 | { 149 | new: true, 150 | } 151 | ); 152 | }else{ 153 | product = await Product.findByIdAndUpdate(id, { 154 | $push:{ 155 | ratings:{ 156 | star: star, 157 | comment: comment, 158 | postedby: userId, 159 | } 160 | } 161 | }, { 162 | new : true 163 | })} 164 | } 165 | 166 | const getAllRatings = await Product.findById(id) 167 | 168 | // calculating average rating 169 | let totRatings = getAllRatings.ratings.length; 170 | let ratingSum = getAllRatings.ratings 171 | .map((el)=>el.star) 172 | .reduce((prev, cur)=>prev+cur, 0); 173 | 174 | let actualRating = Math.round(ratingSum / totRatings); 175 | let finalProd = await Product.findByIdAndUpdate(id, { 176 | totalRating : actualRating 177 | }, 178 | { 179 | new : true 180 | } 181 | ) 182 | res.status(200).json({status: SUCCESS, data: finalProd}) 183 | }) 184 | 185 | 186 | const uploadImages = AsyncHandler(async(req, res ,next) => { 187 | const {id} = req.params; 188 | validateMongoId(id); 189 | const uploader = (pth) => cloudinaryUpload(pth, "images"); 190 | const url = []; 191 | const files = req.files; 192 | //console.log("files " , files) 193 | for(const file of files){ 194 | const {path: pth} = file; 195 | //console.log("LST => path" , pth); 196 | const newPath = await uploader(pth); 197 | // console.log("NEW => ",newPath) 198 | url.push(newPath) 199 | fs.unlinkSync(pth); 200 | } 201 | const findProduct = await Product.findByIdAndUpdate(id, {images: url.map(file=>{return file}), },{new : true}) 202 | res.status(200).json({status: SUCCESS, data : findProduct}) 203 | }) 204 | 205 | export {createProduct, getSingleProduct, getAllProducts, updateProduct, deleteProduct, addToWishlist, rating, uploadImages} -------------------------------------------------------------------------------- /controller/userController.js: -------------------------------------------------------------------------------- 1 | import User from '../models/userModel.js'; 2 | import Product from "../models/productModel.js" 3 | import Cart from "../models/cartModel.js" 4 | import Order from '../models/orderModel.js'; 5 | import Coupon from '../models/couponModel.js'; 6 | import asyncHandler from 'express-async-handler'; 7 | import appError from '../utils/error.js' 8 | import { ERROR, FAIL, SUCCESS } from '../utils/errorText.js'; 9 | import {generateToken} from '../config/jwtToken.js' 10 | import { validateMongoId } from '../utils/validateMongoDBId.js'; 11 | import { generateRefreshToken } from '../config/refreshToken.js'; 12 | import { sendEmail } from './emailController.js'; 13 | import jwt from 'jsonwebtoken'; 14 | import crypto from 'crypto' 15 | import uniqid from 'uniqid' 16 | import { stat } from 'fs'; 17 | 18 | const registerUser = asyncHandler( async (req, res, next) => { 19 | const email = req.body.email; 20 | const user = await User.findOne({email:email}); 21 | if(!user){ 22 | // create a new user 23 | const newUser = await User.create(req.body); 24 | return res.json( {message:SUCCESS, data: newUser}) 25 | }else{ 26 | throw appError.create("user already exists.",400, FAIL) 27 | } 28 | } 29 | ); 30 | const login = asyncHandler(async(req,res,next) => { 31 | const {email, password} = req.body; 32 | const user = await User.findOne({email:email}); 33 | if(!user){ 34 | //not Exist 35 | throw appError.create("Email or password is wrong.", 400 , FAIL); 36 | 37 | }else{ 38 | // user Exists 39 | const matchingPassword = await user.isMatchPassword(password); 40 | if(matchingPassword){ 41 | const refreshToken = await generateRefreshToken(user?._id); 42 | await User.findByIdAndUpdate(user?._id, { 43 | refreshToken:refreshToken 44 | },{ 45 | new:true 46 | }) 47 | 48 | res.cookie('refreshToken', refreshToken, { 49 | httpOnly: true, 50 | maxAge: 72 * 60 * 60 * 1000 51 | }); 52 | 53 | return res.status(200).json({status:SUCCESS, message:"Loggin success.", user:{ 54 | _id: user?._id, 55 | firstName:user?.firstName, 56 | lastName:user?.lastName, 57 | email:user?.email, 58 | mobile:user?.mobile, 59 | token: await generateToken(user?._id) 60 | }}) 61 | }else{ 62 | throw appError.create("Email or password is wrong.", 400 , FAIL); 63 | } 64 | } 65 | }) 66 | 67 | // admin login 68 | const adminLogin = asyncHandler(async(req,res,next) => { 69 | const {email, password} = req.body; 70 | const admin = await User.findOne({email:email}); 71 | if(admin.role !== "admin"){ 72 | throw appError.create("You are not an admin", 401 , ERROR) 73 | } 74 | if(!admin){ 75 | //not Exist 76 | throw appError.create("Email or password is wrong.", 400 , FAIL); 77 | }else{ 78 | // user Exists 79 | const matchingPassword = await admin.isMatchPassword(password); 80 | if(matchingPassword){ 81 | const refreshToken = await generateRefreshToken(admin?._id); 82 | await User.findByIdAndUpdate(admin?._id, { 83 | refreshToken:refreshToken 84 | },{ 85 | new:true 86 | }) 87 | 88 | res.cookie('refreshToken', refreshToken, { 89 | httpOnly: true, 90 | maxAge: 72 * 60 * 60 * 1000 91 | }); 92 | 93 | return res.status(200).json({status:SUCCESS, message:"Loggin success.", user:{ 94 | _id: admin?._id, 95 | firstName:admin?.firstName, 96 | lastName:admin?.lastName, 97 | email:admin?.email, 98 | mobile:admin?.mobile, 99 | token: await generateToken(admin?._id) 100 | }}) 101 | }else{ 102 | throw appError.create("Email or password is wrong.", 400 , FAIL); 103 | } 104 | } 105 | }) 106 | 107 | 108 | 109 | // handle refresh token 110 | 111 | const handleRefreshToken = asyncHandler(async (req, res, next)=>{ 112 | const cookie = req.cookies; 113 | if(!cookie?.refreshToken){ 114 | throw appError.create("No refresh token in cookies", 400, ERROR); 115 | } 116 | const refreshToken = cookie?.refreshToken; 117 | const user = await User.findOne({refreshToken: refreshToken}); 118 | if(!user) throw appError.create("Invalid refresh token.",401, FAIL); 119 | jwt.verify(user?.refreshToken, process.env.JWT_SECRET_KEY, asyncHandler(async (err, decoded)=> { 120 | if(err || user.id != decoded?.id){ 121 | throw appError.create("There is something wrong with refresh token", 401, ERROR); 122 | } 123 | const accessToken = await generateToken(user?._id); 124 | res.status(200).json({status: SUCCESS, data:{accessToken}}) 125 | })); 126 | }) 127 | 128 | // log-out 129 | 130 | const logOut = asyncHandler(async (req, res, next) => { 131 | const cookie = req.cookies; 132 | if(!cookie?.refreshToken){ 133 | throw appError.create("No refresh token in cookies", 400, ERROR); 134 | } 135 | const refreshToken = cookie?.refreshToken; 136 | const user = await User.findOne({refreshToken: refreshToken}); 137 | if(!user) { 138 | res.clearCookie('refreshToken', { 139 | httpOnly: true, 140 | secure:true, 141 | }) 142 | return res.sendStatus(204); 143 | } 144 | await User.findOneAndUpdate({refreshToken}, { 145 | refreshToken: "" 146 | }) 147 | res.clearCookie('refreshToken', { 148 | httpOnly: true, 149 | secure:true, 150 | }) 151 | return res.sendStatus(204); 152 | }) 153 | 154 | // user address 155 | 156 | const saveAddress = asyncHandler(async(req, res, next) => { 157 | const {_id} = req.user; 158 | validateMongoId(_id); 159 | const updatedUser = await User.findByIdAndUpdate(_id,{ 160 | address: req?.body?.address 161 | },{ 162 | new : true 163 | }) 164 | res.status(200).json({status: SUCCESS, data: updatedUser}) 165 | }) 166 | 167 | const getAllUsers = asyncHandler( async (req, res, next) => { 168 | const query = req.query; 169 | const limit = query.limit || 10; 170 | const page = query.page || 1; 171 | const users = await User 172 | .find({}, {__v : false}) 173 | .limit(limit) 174 | .skip(((page - 1) * limit)).populate('wishlist'); 175 | 176 | res.json({status:SUCCESS, data : {users}}); 177 | } 178 | ) 179 | 180 | const getSingleUser = asyncHandler(async(req, res, next)=>{ 181 | const {id} = req.params; 182 | validateMongoId(id) 183 | const user = await User.findById(id).populate('wishlist'); 184 | if(!user){ 185 | throw appError.create("Invalid user id.",404,ERROR); 186 | }else{ 187 | return res.status(200).json({status:SUCCESS,data:user}) 188 | } 189 | }) 190 | 191 | const deleteSingleUser = asyncHandler(async (req, res, next) => { 192 | const {id} = req.params; 193 | validateMongoId(id); 194 | const user = await User.findById(id); 195 | if(!user){ 196 | throw appError.create("Invalid user id.",404,ERROR); 197 | }else{ 198 | await User.findByIdAndDelete(id); 199 | res.status(200).json({status:SUCCESS,data:null}); 200 | } 201 | }) 202 | 203 | const deleteMyAccount = asyncHandler(async (req, res, next) => { 204 | const {_id} = req.user; 205 | validateMongoId(_id); 206 | const user = await User.findById(_id); 207 | if(!user){ 208 | throw appError.create("Invalid user id.",404,ERROR); 209 | }else{ 210 | await User.findByIdAndDelete(_id); 211 | res.status(200).json({status:SUCCESS,data:null}); 212 | } 213 | }) 214 | 215 | const updateSingleUser = asyncHandler(async (req, res, next) => { 216 | const { _id } = req.user; 217 | validateMongoId(_id) 218 | const user = await User.findById(_id); 219 | if(!user){ 220 | throw appError.create("Invalid user id.",404,ERROR); 221 | }else{ 222 | const updatedUser = await User.findByIdAndUpdate(_id,{...req.body},{new:true}); 223 | res.status(200).json({status:SUCCESS,data:updatedUser}); 224 | } 225 | }) 226 | 227 | const blockUser = asyncHandler(async(req, res, next)=>{ 228 | const {id} = req.params; 229 | validateMongoId(id); 230 | const userToBlock = await User.findByIdAndUpdate(id, {isBlocked:true},{new:true}); 231 | if(!userToBlock){ 232 | throw appError.create("Invalid user id.",404,FAIL) 233 | }else{ 234 | res.status(200).json({status:SUCCESS,message:"User blocked"}) 235 | } 236 | }) 237 | const unBlockUser = asyncHandler(async(req, res, next)=>{ 238 | const {id} = req.params; 239 | validateMongoId(id); 240 | const userToUnBlock = await User.findByIdAndUpdate(id, {isBlocked:false},{new:true}); 241 | if(!userToUnBlock){ 242 | throw appError.create("Invalid user id.",404,FAIL) 243 | }else{ 244 | res.status(200).json({status:SUCCESS,message:"User unblocked"}) 245 | } 246 | }) 247 | 248 | const changePassword = asyncHandler(async(req, res, next) => { 249 | const {_id} = req.user; 250 | validateMongoId(_id); 251 | const {password} = req.body; 252 | const user = await User.findById(_id); 253 | if(password){ 254 | const matchingPassword = await user.isMatchPassword(password); 255 | if(matchingPassword){ 256 | throw appError.create("New password can't be the same as old one.",400,FAIL); 257 | } 258 | user.password = password; 259 | const updatedUserWithPassword = await user.save(); 260 | res.status(201).json({status: SUCCESS, data: updatedUserWithPassword}); 261 | }else{ 262 | throw appError.create("Password can't be empty", 400, ERROR); 263 | } 264 | } 265 | ) 266 | 267 | const forgotPasswordToken = asyncHandler(async (req, res, next) => { 268 | const {email} = req.body; 269 | const user = await User.findOne({email: email}); 270 | if(!user){ 271 | throw appError.create("User not found", 404, ERROR); 272 | }else{ 273 | const token = await user.createPasswordResetToken(); 274 | //console.log(token) 275 | await user.save(); 276 | const resetURL = `Hi, please follow this link to reset your password. this link is valid till 30 minutes from now. Click Here` 277 | const data = { 278 | to: email, 279 | text:"Hey User", 280 | subject: "Forgot Password link", 281 | htm: resetURL, 282 | } 283 | sendEmail(data); 284 | res.status(200).json({status: SUCCESS, token: token}) 285 | } 286 | 287 | }) 288 | 289 | const resetPassword = asyncHandler(async(req, res, next) => { 290 | const {password} = req.body; 291 | const { token } = req.params; 292 | const hashedToken = crypto 293 | .createHash("sha256") 294 | .update(token) 295 | .digest("hex"); 296 | 297 | const user = await User.findOne({ 298 | passwordResetToken: hashedToken, 299 | passwordResetExpires: { $gt: Date.now() }, 300 | }); 301 | if(!user){ 302 | throw appError.create("Invalid token", 401, ERROR); 303 | }else{ 304 | user.password = password; 305 | user.passwordResetToken = undefined; 306 | user.passwordResetExpires = undefined; 307 | await user.save(); 308 | res.status(200).json({status: SUCCESS , data: user}) 309 | } 310 | }) 311 | 312 | const getWishlist = asyncHandler(async (req, res, next) => { 313 | const {_id} = req.user; 314 | validateMongoId(_id) 315 | const wishlist = await User.findById(_id,{wishlist:true}).populate("wishlist"); 316 | if(!wishlist){ 317 | throw appError.create("Resource not found.", 404 , ERROR) 318 | }else{ 319 | res.status(200).json({status: SUCCESS, data : wishlist}) 320 | } 321 | }) 322 | 323 | const userCart = asyncHandler(async(req, res, next) => { 324 | let products = []; 325 | const {cart} = req.body; 326 | const {_id} = req.user; 327 | //console.log("id -> ", _id); 328 | validateMongoId(_id); 329 | const user = await User.findById(_id); 330 | //console.log("User => " , user?._id); 331 | const alreadyHave = await Cart.findOne({orderedBy:user._id}); 332 | //console.log("have -> ", alreadyHave) 333 | if(alreadyHave){ 334 | await Cart.deleteOne(alreadyHave); 335 | } 336 | for(let i = 0 ; i < cart.length ; i++){ 337 | let obj = {}; 338 | obj.product = cart[i]._id; 339 | obj.count = cart[i].count; 340 | obj.color = cart[i].color; 341 | let getPrice = await Product.findById(cart[i]._id).select("price").exec() 342 | obj.price = getPrice.price; 343 | products.push(obj); 344 | } 345 | // console.log(products); 346 | let cartTot = 0; 347 | for(let i = 0 ; i < products.length ; i++){ 348 | cartTot += products[i].price * products[i].count; 349 | } 350 | // console.log(products, cartTot) 351 | let newCart = await new Cart({ 352 | products, 353 | cartTotal:cartTot, 354 | orderedBy: user?._id 355 | }).save() 356 | await (await newCart.populate("products.product")).populate("orderedBy") 357 | res.status(200).json({status: SUCCESS , data : newCart}) 358 | }) 359 | 360 | const getUserCart = asyncHandler(async (req, res, next) => { 361 | const {_id} = req.user; 362 | const userCart = await Cart.findOne({orderedBy:_id}).populate("products.product"); 363 | res.status(200).json({status: SUCCESS, data : userCart}) 364 | }) 365 | 366 | const emptyCart = asyncHandler(async(req, res, next) => { 367 | const {_id} = req.user; 368 | validateMongoId(_id) 369 | const deleteCart = await Cart.deleteOne({orderedBy:_id}); 370 | res.status(200).json({status: SUCCESS, data :null}) 371 | }) 372 | 373 | const applyCoupon = asyncHandler(async(req, res, next) => { 374 | const {coupon} = req.body; 375 | const {_id} = req.user; 376 | validateMongoId(_id); 377 | const validCoupon = await Coupon.findOne({name : coupon}) 378 | if(!validCoupon){ 379 | throw appError.create("Invalid or expired coupon.", 400, FAIL); 380 | }else{ 381 | const user = await User.findById(_id); 382 | let {products, cartTotal} = await Cart.findOne({orderedBy: user?._id}).populate("products.product"); 383 | let totalAfterDiscount = (cartTotal - (cartTotal * validCoupon.discount)/100).toFixed(2); 384 | await Cart.findOneAndUpdate({orderedBy:user?._id}, {totalAfterDiscount}, {new : true}) 385 | res.json(totAfterDiscount) 386 | } 387 | }) 388 | 389 | const createOrder = asyncHandler(async (req, res, next) => { 390 | const {COD, couponApplied} = req.body; 391 | const {_id} = req.user; 392 | validateMongoId(_id); 393 | if(!COD){ 394 | throw appError.create("Create cash order failed"); 395 | } 396 | const user = await User.findById(_id); 397 | let userCart = await Cart.findOne({orderedBy: user?._id}); 398 | let finalAmount = 0; 399 | if(couponApplied && userCart.totalAfterDiscount){ 400 | finalAmount = userCart.totalAfterDiscount; 401 | }else{ 402 | finalAmount = userCart.cartTotal; 403 | } 404 | let newOrder = await new Order({ 405 | products: userCart.products, 406 | paymentIntent: { 407 | id: uniqid(), 408 | method:"COD", 409 | amount: finalAmount, 410 | status: "Cash on delivery", 411 | created: Date.now(), 412 | currency: "usd" 413 | }, 414 | orderedBy: user?._id, 415 | orderStatus:"Cash on delivery", 416 | }).save() 417 | let update = userCart.products.map(item => { 418 | return { 419 | updateOne: { 420 | filter: {_id:item.product._id}, 421 | update:{$inc: {quantity: -item.count, sold: +item.count}} 422 | } 423 | } 424 | }) 425 | const updated = await Product.bulkWrite(update, {}); 426 | res.json({status: SUCCESS,}) 427 | }) 428 | 429 | const getOrders = asyncHandler(async(req, res, next) => { 430 | const {_id} = req.user; 431 | validateMongoId(_id); 432 | const user = await User.findById(_id); 433 | const orders = await Order.findOne({orderedBy : user?._id}).populate("products.product orderedBy") 434 | if(!user) 435 | { 436 | throw appError.create("Resource not found.", 404, ERROR); 437 | }else{ 438 | res.status(200).json({status : SUCCESS, data : orders}) 439 | } 440 | }) 441 | 442 | const updateOrderStatus = asyncHandler(async(req, res, next) => { 443 | const {status} = req.body; 444 | const {id} = req.params; 445 | validateMongoId(id); 446 | const order = await Order.findByIdAndUpdate(id, { 447 | orderStatus: status, 448 | paymentIntent: { 449 | status: status 450 | } 451 | }, {new : true}); 452 | res.json({status : SUCCESS, data : order}) 453 | }) 454 | 455 | export { 456 | registerUser, 457 | login, 458 | getAllUsers, 459 | getSingleUser, 460 | deleteSingleUser, 461 | updateSingleUser, 462 | blockUser, 463 | unBlockUser, 464 | deleteMyAccount, 465 | handleRefreshToken, 466 | logOut, 467 | changePassword, 468 | forgotPasswordToken, 469 | resetPassword, 470 | adminLogin, 471 | getWishlist, 472 | saveAddress, 473 | userCart, 474 | emptyCart, 475 | getUserCart, 476 | applyCoupon, 477 | createOrder, 478 | getOrders, 479 | updateOrderStatus 480 | 481 | } -------------------------------------------------------------------------------- /E-Commerce.postman_collection.json: -------------------------------------------------------------------------------- 1 | { 2 | "info": { 3 | "_postman_id": "42f04b8b-3827-4c4c-b814-8f1633929767", 4 | "name": "E-Commerce", 5 | "schema": "https://schema.getpostman.com/json/collection/v2.1.0/collection.json", 6 | "_exporter_id": "24393606" 7 | }, 8 | "item": [ 9 | { 10 | "name": "Auth", 11 | "item": [ 12 | { 13 | "name": "register", 14 | "request": { 15 | "method": "POST", 16 | "header": [] 17 | }, 18 | "response": [] 19 | }, 20 | { 21 | "name": "forgot password", 22 | "request": { 23 | "method": "POST", 24 | "header": [], 25 | "body": { 26 | "mode": "raw", 27 | "raw": "{\r\n \"email\":\"ebrahim.elsayed622003@gmail.com\"\r\n\r\n}", 28 | "options": { 29 | "raw": { 30 | "language": "json" 31 | } 32 | } 33 | }, 34 | "url": { 35 | "raw": "http://localhost:5000/api/user/reset-password-token", 36 | "protocol": "http", 37 | "host": [ 38 | "localhost" 39 | ], 40 | "port": "5000", 41 | "path": [ 42 | "api", 43 | "user", 44 | "reset-password-token" 45 | ] 46 | } 47 | }, 48 | "response": [] 49 | }, 50 | { 51 | "name": "login", 52 | "request": { 53 | "method": "POST", 54 | "header": [] 55 | }, 56 | "response": [] 57 | }, 58 | { 59 | "name": "unblock user", 60 | "request": { 61 | "method": "PUT", 62 | "header": [ 63 | { 64 | "key": "authorization", 65 | "value": "Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6IjY1MzNlZmJhNmRjNmRhYjVhMGExOTI5ZCIsImlhdCI6MTY5NzkwNTgwOCwiZXhwIjoxNjk4MTY1MDA4fQ.7yUzWXsA1dBT2F8gWK_Vm2y8xXHg2kt8IQ_KJkJJJmQ", 66 | "type": "text" 67 | } 68 | ], 69 | "url": { 70 | "raw": "http://localhost:5000/api/user/unblock-user/", 71 | "protocol": "http", 72 | "host": [ 73 | "localhost" 74 | ], 75 | "port": "5000", 76 | "path": [ 77 | "api", 78 | "user", 79 | "unblock-user", 80 | "" 81 | ] 82 | } 83 | }, 84 | "response": [] 85 | }, 86 | { 87 | "name": "logout", 88 | "request": { 89 | "method": "GET", 90 | "header": [] 91 | }, 92 | "response": [] 93 | }, 94 | { 95 | "name": "get all users", 96 | "request": { 97 | "method": "GET", 98 | "header": [] 99 | }, 100 | "response": [] 101 | }, 102 | { 103 | "name": "get Single User", 104 | "request": { 105 | "method": "GET", 106 | "header": [] 107 | }, 108 | "response": [] 109 | }, 110 | { 111 | "name": "delete single user", 112 | "request": { 113 | "method": "DELETE", 114 | "header": [], 115 | "url": { 116 | "raw": "http://localhost:5000/api/user/6532d7b24670788aa69a132b", 117 | "protocol": "http", 118 | "host": [ 119 | "localhost" 120 | ], 121 | "port": "5000", 122 | "path": [ 123 | "api", 124 | "user", 125 | "6532d7b24670788aa69a132b" 126 | ] 127 | } 128 | }, 129 | "response": [] 130 | }, 131 | { 132 | "name": "Update profile", 133 | "request": { 134 | "method": "PUT", 135 | "header": [ 136 | { 137 | "key": "authorization", 138 | "value": "Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6IjY1MzNlZmNkNmRjNmRhYjVhMGExOTJhMCIsImlhdCI6MTY5NzkwNDM0NCwiZXhwIjoxNjk4MTYzNTQ0fQ.58fWnXb4F2gyflg0MwmN1gpqqtFkiXAQjxY1IAeiLF8", 139 | "type": "text" 140 | } 141 | ], 142 | "body": { 143 | "mode": "raw", 144 | "raw": "{\r\n \"email\": \"ebrahim@email.com\",\r\n \"lastName\": \"Saeed\"\r\n }", 145 | "options": { 146 | "raw": { 147 | "language": "json" 148 | } 149 | } 150 | }, 151 | "url": { 152 | "raw": "http://localhost:5000/api/user/updateProfile", 153 | "protocol": "http", 154 | "host": [ 155 | "localhost" 156 | ], 157 | "port": "5000", 158 | "path": [ 159 | "api", 160 | "user", 161 | "updateProfile" 162 | ] 163 | } 164 | }, 165 | "response": [] 166 | }, 167 | { 168 | "name": "handle refresh token", 169 | "request": { 170 | "method": "GET", 171 | "header": [] 172 | }, 173 | "response": [] 174 | }, 175 | { 176 | "name": "block user", 177 | "request": { 178 | "method": "PUT", 179 | "header": [ 180 | { 181 | "key": "authorization", 182 | "value": "Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6IjY1MzNlZmJhNmRjNmRhYjVhMGExOTI5ZCIsImlhdCI6MTY5NzkwNTgwOCwiZXhwIjoxNjk4MTY1MDA4fQ.7yUzWXsA1dBT2F8gWK_Vm2y8xXHg2kt8IQ_KJkJJJmQ", 183 | "type": "text" 184 | } 185 | ], 186 | "url": { 187 | "raw": "http://localhost:5000/api/user/block-user/6533efcd6dc6dab5a0a192a0", 188 | "protocol": "http", 189 | "host": [ 190 | "localhost" 191 | ], 192 | "port": "5000", 193 | "path": [ 194 | "api", 195 | "user", 196 | "block-user", 197 | "6533efcd6dc6dab5a0a192a0" 198 | ] 199 | } 200 | }, 201 | "response": [] 202 | }, 203 | { 204 | "name": "change password", 205 | "request": { 206 | "method": "PUT", 207 | "header": [], 208 | "url": { 209 | "raw": "http://localhost:5000/api/user/reset-password", 210 | "protocol": "http", 211 | "host": [ 212 | "localhost" 213 | ], 214 | "port": "5000", 215 | "path": [ 216 | "api", 217 | "user", 218 | "reset-password" 219 | ] 220 | } 221 | }, 222 | "response": [] 223 | }, 224 | { 225 | "name": "reset password", 226 | "request": { 227 | "method": "GET", 228 | "header": [] 229 | }, 230 | "response": [] 231 | }, 232 | { 233 | "name": "admin login", 234 | "request": { 235 | "method": "POST", 236 | "header": [], 237 | "url": { 238 | "raw": "{{base_url}}/user/admin-login", 239 | "host": [ 240 | "{{base_url}}" 241 | ], 242 | "path": [ 243 | "user", 244 | "admin-login" 245 | ] 246 | } 247 | }, 248 | "response": [] 249 | }, 250 | { 251 | "name": "get wish list", 252 | "request": { 253 | "method": "GET", 254 | "header": [] 255 | }, 256 | "response": [] 257 | }, 258 | { 259 | "name": "save address", 260 | "request": { 261 | "auth": { 262 | "type": "bearer", 263 | "bearer": [ 264 | { 265 | "key": "token", 266 | "value": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6IjY1MzNlZmJhNmRjNmRhYjVhMGExOTI5ZCIsImlhdCI6MTY5ODE2MzIzNiwiZXhwIjoxNjk4MjQ5NjM2fQ.HcrshE2BWqe29YefLfXa8-zGY6AgrjnsoHwElc0hRz0", 267 | "type": "string" 268 | } 269 | ] 270 | }, 271 | "method": "PUT", 272 | "header": [], 273 | "body": { 274 | "mode": "raw", 275 | "raw": "{\r\n \"address\":\"Samara\"\r\n}", 276 | "options": { 277 | "raw": { 278 | "language": "json" 279 | } 280 | } 281 | }, 282 | "url": { 283 | "raw": "{{base_url}}/user/address", 284 | "host": [ 285 | "{{base_url}}" 286 | ], 287 | "path": [ 288 | "user", 289 | "address" 290 | ] 291 | } 292 | }, 293 | "response": [] 294 | }, 295 | { 296 | "name": "user cart", 297 | "request": { 298 | "auth": { 299 | "type": "bearer", 300 | "bearer": [ 301 | { 302 | "key": "token", 303 | "value": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6IjY1MzNlZmJhNmRjNmRhYjVhMGExOTI5ZCIsImlhdCI6MTY5ODE2MzIzNiwiZXhwIjoxNjk4MjQ5NjM2fQ.HcrshE2BWqe29YefLfXa8-zGY6AgrjnsoHwElc0hRz0", 304 | "type": "string" 305 | } 306 | ] 307 | }, 308 | "method": "POST", 309 | "header": [], 310 | "body": { 311 | "mode": "raw", 312 | "raw": "{\r\n \"cart\":[\r\n {\r\n \"_id\":\"6536462795fb2c8df571ab4d\",\r\n \"count\":3,\r\n \"color\":\"Red\"\r\n },{\r\n \"_id\":\"6536461f95fb2c8df571ab49\",\r\n \"count\":4,\r\n \"color\":\"Yellow\"\r\n }\r\n ]\r\n}", 313 | "options": { 314 | "raw": { 315 | "language": "json" 316 | } 317 | } 318 | }, 319 | "url": { 320 | "raw": "{{base_url}}/user/cart", 321 | "host": [ 322 | "{{base_url}}" 323 | ], 324 | "path": [ 325 | "user", 326 | "cart" 327 | ] 328 | } 329 | }, 330 | "response": [] 331 | }, 332 | { 333 | "name": "get user cart", 334 | "request": { 335 | "method": "GET", 336 | "header": [] 337 | }, 338 | "response": [] 339 | }, 340 | { 341 | "name": "empty cart", 342 | "request": { 343 | "method": "GET", 344 | "header": [] 345 | }, 346 | "response": [] 347 | }, 348 | { 349 | "name": "apply coupon", 350 | "request": { 351 | "method": "POST", 352 | "header": [], 353 | "url": { 354 | "raw": "{{base_url}}/user/cart/apply-coupon", 355 | "host": [ 356 | "{{base_url}}" 357 | ], 358 | "path": [ 359 | "user", 360 | "cart", 361 | "apply-coupon" 362 | ] 363 | } 364 | }, 365 | "response": [] 366 | }, 367 | { 368 | "name": "create order", 369 | "request": { 370 | "method": "GET", 371 | "header": [] 372 | }, 373 | "response": [] 374 | }, 375 | { 376 | "name": "get orders", 377 | "request": { 378 | "method": "GET", 379 | "header": [] 380 | }, 381 | "response": [] 382 | }, 383 | { 384 | "name": "update order status", 385 | "request": { 386 | "method": "GET", 387 | "header": [] 388 | }, 389 | "response": [] 390 | } 391 | ] 392 | }, 393 | { 394 | "name": "product", 395 | "item": [ 396 | { 397 | "name": "create product", 398 | "request": { 399 | "method": "POST", 400 | "header": [], 401 | "body": { 402 | "mode": "raw", 403 | "raw": "{\r\n \"title\":\"Hi\",\r\n \"slug\":\"hHfddHH\",\r\n \"description\":\"Hello\",\r\n \"price\":50,\r\n \"quantity\":50\r\n \r\n}", 404 | "options": { 405 | "raw": { 406 | "language": "json" 407 | } 408 | } 409 | }, 410 | "url": { 411 | "raw": "http://localhost:5000/api/product/add-product", 412 | "protocol": "http", 413 | "host": [ 414 | "localhost" 415 | ], 416 | "port": "5000", 417 | "path": [ 418 | "api", 419 | "product", 420 | "add-product" 421 | ] 422 | } 423 | }, 424 | "response": [] 425 | }, 426 | { 427 | "name": "get Product", 428 | "request": { 429 | "method": "GET", 430 | "header": [] 431 | }, 432 | "response": [] 433 | }, 434 | { 435 | "name": "get all products", 436 | "request": { 437 | "method": "GET", 438 | "header": [] 439 | }, 440 | "response": [] 441 | }, 442 | { 443 | "name": "update product", 444 | "request": { 445 | "method": "PUT", 446 | "header": [], 447 | "url": { 448 | "raw": "http://localhost:5000/api/product/65361cc2e0df8331ab346ac", 449 | "protocol": "http", 450 | "host": [ 451 | "localhost" 452 | ], 453 | "port": "5000", 454 | "path": [ 455 | "api", 456 | "product", 457 | "65361cc2e0df8331ab346ac" 458 | ] 459 | } 460 | }, 461 | "response": [] 462 | }, 463 | { 464 | "name": "delete product", 465 | "request": { 466 | "method": "DELETE", 467 | "header": [], 468 | "url": { 469 | "raw": "{{base_url}}/product/65361cc2e0df8331ab341ac6", 470 | "host": [ 471 | "{{base_url}}" 472 | ], 473 | "path": [ 474 | "product", 475 | "65361cc2e0df8331ab341ac6" 476 | ] 477 | } 478 | }, 479 | "response": [] 480 | }, 481 | { 482 | "name": "add to wishlist", 483 | "request": { 484 | "method": "GET", 485 | "header": [] 486 | }, 487 | "response": [] 488 | }, 489 | { 490 | "name": "rate product", 491 | "request": { 492 | "method": "PUT", 493 | "header": [] 494 | }, 495 | "response": [] 496 | }, 497 | { 498 | "name": "upload product image", 499 | "request": { 500 | "auth": { 501 | "type": "bearer", 502 | "bearer": [ 503 | { 504 | "key": "token", 505 | "value": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6IjY1MzNlZmJhNmRjNmRhYjVhMGExOTI5ZCIsImlhdCI6MTY5ODE1MjgwMiwiZXhwIjoxNjk4MjM5MjAyfQ.8VF2lsmZa0X1l8afPnzUi45hic4-99yddkxqXGVaYO8", 506 | "type": "string" 507 | } 508 | ] 509 | }, 510 | "method": "PUT", 511 | "header": [], 512 | "body": { 513 | "mode": "formdata", 514 | "formdata": [ 515 | { 516 | "key": "images", 517 | "type": "file", 518 | "src": "/C:/Users/ebrah/Downloads/images.jpeg" 519 | }, 520 | { 521 | "key": "images", 522 | "type": "file", 523 | "src": [], 524 | "disabled": true 525 | } 526 | ] 527 | }, 528 | "url": { 529 | "raw": "{{base_url}}/product/upload/6536462795fb2c8df571ab4d", 530 | "host": [ 531 | "{{base_url}}" 532 | ], 533 | "path": [ 534 | "product", 535 | "upload", 536 | "6536462795fb2c8df571ab4d" 537 | ] 538 | } 539 | }, 540 | "response": [] 541 | } 542 | ] 543 | }, 544 | { 545 | "name": "blog", 546 | "item": [ 547 | { 548 | "name": "create blog", 549 | "request": { 550 | "auth": { 551 | "type": "bearer", 552 | "bearer": [ 553 | { 554 | "key": "token", 555 | "value": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6IjY1MzNlZmJhNmRjNmRhYjVhMGExOTI5ZCIsImlhdCI6MTY5ODA3NjgxNSwiZXhwIjoxNjk4MTYzMjE1fQ.t9m4wxQmSiuXjt_TzLHc_A8uG-nSd9k8V7kigqRg27Y", 556 | "type": "string" 557 | } 558 | ] 559 | }, 560 | "method": "POST", 561 | "header": [], 562 | "body": { 563 | "mode": "raw", 564 | "raw": "{\r\n \"title\":\"not blog\",\r\n \"description\":\"Good Blog\",\r\n \"category\":\"Drama\"\r\n}", 565 | "options": { 566 | "raw": { 567 | "language": "json" 568 | } 569 | } 570 | }, 571 | "url": { 572 | "raw": "{{base_url}}/blog/create-blog", 573 | "host": [ 574 | "{{base_url}}" 575 | ], 576 | "path": [ 577 | "blog", 578 | "create-blog" 579 | ] 580 | } 581 | }, 582 | "response": [] 583 | }, 584 | { 585 | "name": "get all blogs", 586 | "request": { 587 | "method": "GET", 588 | "header": [] 589 | }, 590 | "response": [] 591 | }, 592 | { 593 | "name": "get single blog", 594 | "request": { 595 | "method": "GET", 596 | "header": [] 597 | }, 598 | "response": [] 599 | }, 600 | { 601 | "name": "delete blog", 602 | "request": { 603 | "method": "DELETE", 604 | "header": [], 605 | "url": { 606 | "raw": "{{base_url}}/blog/653698b711d52af3cdf131df", 607 | "host": [ 608 | "{{base_url}}" 609 | ], 610 | "path": [ 611 | "blog", 612 | "653698b711d52af3cdf131df" 613 | ] 614 | } 615 | }, 616 | "response": [] 617 | }, 618 | { 619 | "name": "update blog", 620 | "request": { 621 | "auth": { 622 | "type": "bearer", 623 | "bearer": [ 624 | { 625 | "key": "token", 626 | "value": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6IjY1MzNlZmJhNmRjNmRhYjVhMGExOTI5ZCIsImlhdCI6MTY5ODA3NjgxNSwiZXhwIjoxNjk4MTYzMjE1fQ.t9m4wxQmSiuXjt_TzLHc_A8uG-nSd9k8V7kigqRg27Y", 627 | "type": "string" 628 | } 629 | ] 630 | }, 631 | "method": "PUT", 632 | "header": [], 633 | "body": { 634 | "mode": "raw", 635 | "raw": "{\r\n \"title\":\"Title\"\r\n}", 636 | "options": { 637 | "raw": { 638 | "language": "json" 639 | } 640 | } 641 | }, 642 | "url": { 643 | "raw": "{{base_url}}/blog/653698be11d52af3cdf131e4", 644 | "host": [ 645 | "{{base_url}}" 646 | ], 647 | "path": [ 648 | "blog", 649 | "653698be11d52af3cdf131e4" 650 | ] 651 | } 652 | }, 653 | "response": [] 654 | }, 655 | { 656 | "name": "like blog", 657 | "request": { 658 | "auth": { 659 | "type": "bearer", 660 | "bearer": [ 661 | { 662 | "key": "token", 663 | "value": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6IjY1MzNlZmNkNmRjNmRhYjVhMGExOTJhMCIsImlhdCI6MTY5ODA4MDM1OSwiZXhwIjoxNjk4MTY2NzU5fQ.t6p72RBfLk0qq_pu8w8i3D24XdfUh06hQurly961iOQ", 664 | "type": "string" 665 | } 666 | ] 667 | }, 668 | "method": "PUT", 669 | "header": [], 670 | "url": { 671 | "raw": "{{base_url}}/blog/like/6536a71aeb68d7b705786226", 672 | "host": [ 673 | "{{base_url}}" 674 | ], 675 | "path": [ 676 | "blog", 677 | "like", 678 | "6536a71aeb68d7b705786226" 679 | ] 680 | } 681 | }, 682 | "response": [] 683 | }, 684 | { 685 | "name": "dislike", 686 | "request": { 687 | "method": "GET", 688 | "header": [] 689 | }, 690 | "response": [] 691 | }, 692 | { 693 | "name": "upload images", 694 | "request": { 695 | "method": "PUT", 696 | "header": [], 697 | "url": { 698 | "raw": "{{base_url}}/blog/upload/6536462795fb2c8df571ab4d", 699 | "host": [ 700 | "{{base_url}}" 701 | ], 702 | "path": [ 703 | "blog", 704 | "upload", 705 | "6536462795fb2c8df571ab4d" 706 | ] 707 | } 708 | }, 709 | "response": [] 710 | } 711 | ] 712 | }, 713 | { 714 | "name": "product Category", 715 | "item": [ 716 | { 717 | "name": "create category", 718 | "request": { 719 | "method": "POST", 720 | "header": [] 721 | }, 722 | "response": [] 723 | }, 724 | { 725 | "name": "update category", 726 | "request": { 727 | "auth": { 728 | "type": "bearer", 729 | "bearer": [ 730 | { 731 | "key": "token", 732 | "value": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6IjY1MzNlZmJhNmRjNmRhYjVhMGExOTI5ZCIsImlhdCI6MTY5ODE0MTcyMCwiZXhwIjoxNjk4MjI4MTIwfQ.EXQO3AGEgNvfg5WP-e5GqGH4euXDFgIT2BNjNwWgjGA", 733 | "type": "string" 734 | } 735 | ] 736 | }, 737 | "method": "PUT", 738 | "header": [], 739 | "body": { 740 | "mode": "raw", 741 | "raw": "{\r\n \"title\":\"Accessories\"\r\n}", 742 | "options": { 743 | "raw": { 744 | "language": "json" 745 | } 746 | } 747 | }, 748 | "url": { 749 | "raw": "{{base_url}}/prod-category/653797e1adb2316fc1257f49", 750 | "host": [ 751 | "{{base_url}}" 752 | ], 753 | "path": [ 754 | "prod-category", 755 | "653797e1adb2316fc1257f49" 756 | ] 757 | } 758 | }, 759 | "response": [] 760 | }, 761 | { 762 | "name": "delete category", 763 | "request": { 764 | "auth": { 765 | "type": "bearer", 766 | "bearer": [ 767 | { 768 | "key": "token", 769 | "value": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6IjY1MzNlZmJhNmRjNmRhYjVhMGExOTI5ZCIsImlhdCI6MTY5ODE0MTcyMCwiZXhwIjoxNjk4MjI4MTIwfQ.EXQO3AGEgNvfg5WP-e5GqGH4euXDFgIT2BNjNwWgjGA", 770 | "type": "string" 771 | } 772 | ] 773 | }, 774 | "method": "DELETE", 775 | "header": [], 776 | "url": { 777 | "raw": "{{base_url}}/prod-category/653797e1adb2316fc1257f47", 778 | "host": [ 779 | "{{base_url}}" 780 | ], 781 | "path": [ 782 | "prod-category", 783 | "653797e1adb2316fc1257f47" 784 | ] 785 | } 786 | }, 787 | "response": [] 788 | }, 789 | { 790 | "name": "get all cate", 791 | "request": { 792 | "auth": { 793 | "type": "bearer", 794 | "bearer": [ 795 | { 796 | "key": "token", 797 | "value": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6IjY1MzNlZmJhNmRjNmRhYjVhMGExOTI5ZCIsImlhdCI6MTY5ODE0MTcyMCwiZXhwIjoxNjk4MjI4MTIwfQ.EXQO3AGEgNvfg5WP-e5GqGH4euXDFgIT2BNjNwWgjGA", 798 | "type": "string" 799 | } 800 | ] 801 | }, 802 | "method": "GET", 803 | "header": [], 804 | "url": { 805 | "raw": "{{base_url}}/prod-category/", 806 | "host": [ 807 | "{{base_url}}" 808 | ], 809 | "path": [ 810 | "prod-category", 811 | "" 812 | ] 813 | } 814 | }, 815 | "response": [] 816 | }, 817 | { 818 | "name": "get single product", 819 | "request": { 820 | "auth": { 821 | "type": "bearer", 822 | "bearer": [ 823 | { 824 | "key": "token", 825 | "value": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6IjY1MzNlZmJhNmRjNmRhYjVhMGExOTI5ZCIsImlhdCI6MTY5ODE0MTcyMCwiZXhwIjoxNjk4MjI4MTIwfQ.EXQO3AGEgNvfg5WP-e5GqGH4euXDFgIT2BNjNwWgjGA", 826 | "type": "string" 827 | } 828 | ] 829 | }, 830 | "method": "GET", 831 | "header": [], 832 | "url": { 833 | "raw": "{{base_url}}/prod-category/65379b58a37724c66c882d07", 834 | "host": [ 835 | "{{base_url}}" 836 | ], 837 | "path": [ 838 | "prod-category", 839 | "65379b58a37724c66c882d07" 840 | ] 841 | } 842 | }, 843 | "response": [] 844 | } 845 | ] 846 | }, 847 | { 848 | "name": "blog Category", 849 | "item": [ 850 | { 851 | "name": "create category", 852 | "request": { 853 | "method": "POST", 854 | "header": [] 855 | }, 856 | "response": [] 857 | }, 858 | { 859 | "name": "update category", 860 | "request": { 861 | "method": "PUT", 862 | "header": [], 863 | "url": { 864 | "raw": "{{base_url}}/blog-category/", 865 | "host": [ 866 | "{{base_url}}" 867 | ], 868 | "path": [ 869 | "blog-category", 870 | "" 871 | ] 872 | } 873 | }, 874 | "response": [] 875 | }, 876 | { 877 | "name": "delete category", 878 | "request": { 879 | "method": "DELETE", 880 | "header": [], 881 | "url": { 882 | "raw": "{{base_url}}/blog-category/", 883 | "host": [ 884 | "{{base_url}}" 885 | ], 886 | "path": [ 887 | "blog-category", 888 | "" 889 | ] 890 | } 891 | }, 892 | "response": [] 893 | }, 894 | { 895 | "name": "get all cate", 896 | "request": { 897 | "auth": { 898 | "type": "bearer", 899 | "bearer": [ 900 | { 901 | "key": "token", 902 | "value": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6IjY1MzNlZmJhNmRjNmRhYjVhMGExOTI5ZCIsImlhdCI6MTY5ODE0MTcyMCwiZXhwIjoxNjk4MjI4MTIwfQ.EXQO3AGEgNvfg5WP-e5GqGH4euXDFgIT2BNjNwWgjGA", 903 | "type": "string" 904 | } 905 | ] 906 | }, 907 | "method": "GET", 908 | "header": [], 909 | "url": { 910 | "raw": "{{base_url}}/blog-category/", 911 | "host": [ 912 | "{{base_url}}" 913 | ], 914 | "path": [ 915 | "blog-category", 916 | "" 917 | ] 918 | } 919 | }, 920 | "response": [] 921 | }, 922 | { 923 | "name": "get single product", 924 | "request": { 925 | "method": "GET", 926 | "header": [], 927 | "url": { 928 | "raw": "{{base_url}}/blog-category/", 929 | "host": [ 930 | "{{base_url}}" 931 | ], 932 | "path": [ 933 | "blog-category", 934 | "" 935 | ] 936 | } 937 | }, 938 | "response": [] 939 | } 940 | ] 941 | }, 942 | { 943 | "name": "coupon", 944 | "item": [ 945 | { 946 | "name": "create coupon", 947 | "request": { 948 | "method": "POST", 949 | "header": [], 950 | "body": { 951 | "mode": "raw", 952 | "raw": "{\r\n \"name\":\"EUNE\",\r\n \r\n}", 953 | "options": { 954 | "raw": { 955 | "language": "json" 956 | } 957 | } 958 | }, 959 | "url": { 960 | "raw": "{{base_url}}/coupon/create", 961 | "host": [ 962 | "{{base_url}}" 963 | ], 964 | "path": [ 965 | "coupon", 966 | "create" 967 | ] 968 | } 969 | }, 970 | "response": [] 971 | }, 972 | { 973 | "name": "get all coupons", 974 | "request": { 975 | "method": "GET", 976 | "header": [] 977 | }, 978 | "response": [] 979 | }, 980 | { 981 | "name": "update coupon", 982 | "request": { 983 | "auth": { 984 | "type": "bearer", 985 | "bearer": [ 986 | { 987 | "key": "token", 988 | "value": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6IjY1MzNlZmJhNmRjNmRhYjVhMGExOTI5ZCIsImlhdCI6MTY5ODE1MjgwMiwiZXhwIjoxNjk4MjM5MjAyfQ.8VF2lsmZa0X1l8afPnzUi45hic4-99yddkxqXGVaYO8", 989 | "type": "string" 990 | } 991 | ] 992 | }, 993 | "method": "PUT", 994 | "header": [], 995 | "body": { 996 | "mode": "raw", 997 | "raw": "{\r\n \"name\" : \"EBRAHIM\"\r\n}", 998 | "options": { 999 | "raw": { 1000 | "language": "json" 1001 | } 1002 | } 1003 | }, 1004 | "url": { 1005 | "raw": "{{base_url}}/coupon/6537c1fe6bd949e9c204472a", 1006 | "host": [ 1007 | "{{base_url}}" 1008 | ], 1009 | "path": [ 1010 | "coupon", 1011 | "6537c1fe6bd949e9c204472a" 1012 | ] 1013 | } 1014 | }, 1015 | "response": [] 1016 | }, 1017 | { 1018 | "name": "get single coupon", 1019 | "request": { 1020 | "auth": { 1021 | "type": "bearer", 1022 | "bearer": [ 1023 | { 1024 | "key": "token", 1025 | "value": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6IjY1MzNlZmJhNmRjNmRhYjVhMGExOTI5ZCIsImlhdCI6MTY5ODE1MjgwMiwiZXhwIjoxNjk4MjM5MjAyfQ.8VF2lsmZa0X1l8afPnzUi45hic4-99yddkxqXGVaYO8", 1026 | "type": "string" 1027 | } 1028 | ] 1029 | }, 1030 | "method": "GET", 1031 | "header": [], 1032 | "url": { 1033 | "raw": "{{base_url}}/coupon/6537c1fe6bd949e9c204472a", 1034 | "host": [ 1035 | "{{base_url}}" 1036 | ], 1037 | "path": [ 1038 | "coupon", 1039 | "6537c1fe6bd949e9c204472a" 1040 | ] 1041 | } 1042 | }, 1043 | "response": [] 1044 | }, 1045 | { 1046 | "name": "delete coupon", 1047 | "request": { 1048 | "method": "DELETE", 1049 | "header": [] 1050 | }, 1051 | "response": [] 1052 | } 1053 | ] 1054 | } 1055 | ] 1056 | } --------------------------------------------------------------------------------