├── Procfile ├── .gitignore ├── .dockerignore ├── .env.example ├── .vscode └── settings.json ├── Dockerfile ├── README.md ├── tsconfig.server.json ├── next-env.d.ts ├── src ├── interfaces │ ├── productInterface.ts │ └── userDoc.ts ├── pages │ ├── global.css │ ├── index.module.css │ ├── _document.tsx │ ├── _app.tsx │ └── index.tsx ├── middlewares │ ├── logger.ts │ └── authMiddleware.ts ├── routes │ ├── index.ts │ ├── accountRoutes.ts │ ├── orderRoutes.ts │ └── productRoutes.ts ├── components │ ├── Ribbon.tsx │ └── ribbon.module.css ├── server.ts ├── app.ts ├── models │ ├── productModel.ts │ ├── orderModel.ts │ └── userModel.ts └── controllers │ ├── accountController.ts │ ├── orderController.ts │ └── productController.ts ├── tsconfig.json └── package.json /Procfile: -------------------------------------------------------------------------------- 1 | web: npm run start 2 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .env 2 | node_modules 3 | dist 4 | .next -------------------------------------------------------------------------------- /.dockerignore: -------------------------------------------------------------------------------- 1 | .git 2 | .gitignore 3 | node_modules/ 4 | .next -------------------------------------------------------------------------------- /.env.example: -------------------------------------------------------------------------------- 1 | DB_STRING=DBSTRING; 2 | SECRET_KEY=supersecretkey -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "typescript.tsdk": "node_modules/typescript/lib" 3 | } -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM node:14.16.0-alpine3.12 2 | 3 | WORKDIR /app 4 | COPY . /app 5 | RUN npm install 6 | EXPOSE 5000 7 | RUN npm run build 8 | CMD ["npm", "run", "start"] -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # MERN Commerce 2 | 3 | ## Technology Stack: 4 | 5 | - Node.js 6 | - Express.js 7 | - MongoDB 8 | - Mongoose 9 | - React.js 10 | - Next.js 11 | - Docker 12 | -------------------------------------------------------------------------------- /tsconfig.server.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "compilerOptions": { 4 | "module": "commonjs", 5 | "outDir": "dist", 6 | "noEmit": false 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /next-env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | /// 3 | 4 | // NOTE: This file should not be edited 5 | // see https://nextjs.org/docs/basic-features/typescript for more information. 6 | -------------------------------------------------------------------------------- /src/interfaces/productInterface.ts: -------------------------------------------------------------------------------- 1 | import mongoose from "mongoose"; 2 | 3 | export default interface ProductDoc extends mongoose.Document { 4 | title: string; 5 | description: string; 6 | price: Number; 7 | createdAt: Date; 8 | } 9 | -------------------------------------------------------------------------------- /src/pages/global.css: -------------------------------------------------------------------------------- 1 | main { 2 | width: 50%; 3 | margin: 0px auto; 4 | padding: 40px 0px; 5 | } 6 | 7 | body { 8 | background: #f1f1f1; 9 | font-family: "Rubik", sans-serif; 10 | overflow-x: hidden; 11 | } -------------------------------------------------------------------------------- /src/middlewares/logger.ts: -------------------------------------------------------------------------------- 1 | import { Request, Response } from "express"; 2 | 3 | export default function loggerMiddleware( 4 | req: Request, 5 | res: Response, 6 | next: any 7 | ) { 8 | console.log("Request logged:", req.method, req.path); 9 | next(); 10 | } 11 | -------------------------------------------------------------------------------- /src/routes/index.ts: -------------------------------------------------------------------------------- 1 | import AccountRoutes from "./accountRoutes"; 2 | import OrderRoutes from "./orderRoutes"; 3 | import ProductRoutes from "./productRoutes"; 4 | 5 | const appRoutes = [new AccountRoutes(), new ProductRoutes(), new OrderRoutes()]; 6 | 7 | export default appRoutes; 8 | -------------------------------------------------------------------------------- /src/interfaces/userDoc.ts: -------------------------------------------------------------------------------- 1 | import mongoose from "mongoose"; 2 | 3 | export default interface UserDoc extends mongoose.Document { 4 | firstName: string; 5 | lastName: string; 6 | username: string; 7 | email: string; 8 | password: string; 9 | role: string; 10 | timestamp: Date; 11 | } 12 | -------------------------------------------------------------------------------- /src/components/Ribbon.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import ribbonStyle from "./ribbon.module.css" 3 | 4 | export default function Ribbon() { 5 | return ( 6 | <> 7 |
8 | 12 | View on GitHub 13 | 14 |
15 | 16 | ); 17 | } 18 | -------------------------------------------------------------------------------- /src/pages/index.module.css: -------------------------------------------------------------------------------- 1 | .header { 2 | text-align: center; 3 | } 4 | 5 | .title { 6 | margin-bottom: 10px; 7 | } 8 | 9 | .endpoints li { 10 | margin: 5px 0px; 11 | } 12 | 13 | .endpoints code { 14 | background: #cd3771; 15 | color: white; 16 | padding: 2px 5px; 17 | border-radius: 5px; 18 | } 19 | 20 | .separator { 21 | height: 10px; 22 | width: 5px; 23 | } 24 | 25 | .fa_heart { 26 | color: red; 27 | } -------------------------------------------------------------------------------- /src/pages/_document.tsx: -------------------------------------------------------------------------------- 1 | import { GetStaticProps } from "next"; 2 | import { Html, Head, Main, NextScript } from 'next/document' 3 | 4 | 5 | export default function Document() { 6 | return ( 7 | 8 | 9 | 10 |
11 | 12 | 13 | 14 | ) 15 | } 16 | 17 | export async function getStaticProps(context: GetStaticProps) { 18 | return { 19 | props: { 20 | }, 21 | } 22 | } -------------------------------------------------------------------------------- /src/components/ribbon.module.css: -------------------------------------------------------------------------------- 1 | .ribbon { 2 | background-color: #a00; 3 | overflow: hidden; 4 | white-space: nowrap; 5 | position: absolute; 6 | right: -50px; 7 | top: 40px; 8 | transform: rotate(45deg); 9 | box-shadow: 0 0 10px #888; 10 | } 11 | 12 | .ribbon a { 13 | border: 1px solid #faa; 14 | color: #fff; 15 | display: block; 16 | margin: 1px 0; 17 | padding: 10px 50px; 18 | text-align: center; 19 | text-decoration: none; 20 | text-shadow: 0 0 5px #444; 21 | } -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "sourceMap": true, 4 | "target": "es6", 5 | "module": "commonjs", 6 | "outDir": "./dist", 7 | "baseUrl": "./src", 8 | "strict": true, 9 | "moduleResolution": "node", 10 | "lib": [ 11 | "dom", 12 | "dom.iterable", 13 | "esnext" 14 | ], 15 | "allowJs": true, 16 | "skipLibCheck": true, 17 | "forceConsistentCasingInFileNames": true, 18 | "esModuleInterop": true, 19 | "resolveJsonModule": true, 20 | "isolatedModules": true, 21 | "jsx": "preserve", 22 | "noEmit": true, 23 | "incremental": true 24 | }, 25 | "include": [ 26 | "src/**/*.ts", 27 | "src/**/*.tsx",], 28 | "exclude": [ 29 | "node_modules" 30 | ] 31 | } 32 | -------------------------------------------------------------------------------- /src/pages/_app.tsx: -------------------------------------------------------------------------------- 1 | import type { AppProps } from 'next/app'; 2 | import Head from "next/head"; 3 | import "./global.css" 4 | 5 | export default function MyApp({ Component, pageProps }: AppProps) { 6 | return ( 7 | <> 8 | 9 | 13 | 19 | MERN Commerce | Shakil Ahmed 20 | 21 | 22 | 23 | ) 24 | } 25 | -------------------------------------------------------------------------------- /src/middlewares/authMiddleware.ts: -------------------------------------------------------------------------------- 1 | import { Request, Response } from "express"; 2 | 3 | const jwt = require("jsonwebtoken"); 4 | 5 | export async function authenticate(req: Request, res: Response, next: any) { 6 | const tokenHeader = req.headers["authorization"]; 7 | 8 | if (typeof tokenHeader !== "undefined") { 9 | const token = (tokenHeader as string).split(" ")[1]; 10 | 11 | jwt.verify(token, process.env.SECRET_KEY, (err: any, authData: any) => { 12 | if (err) { 13 | req.body.user = null; 14 | } else { 15 | req.body.user = authData.user; 16 | } 17 | }); 18 | } 19 | next(); 20 | } 21 | 22 | export function authorize(...permittedRoles: any) { 23 | return (req: Request, res: Response, next: any) => { 24 | const { user } = req.body; 25 | 26 | if (user && permittedRoles.includes(user.role)) { 27 | next(); 28 | } else { 29 | res.status(400).send({ msg: "Method Not Allowed" }); 30 | } 31 | }; 32 | } 33 | -------------------------------------------------------------------------------- /src/server.ts: -------------------------------------------------------------------------------- 1 | import bodyParser from "body-parser"; 2 | import cors from "cors"; 3 | import App from "./app"; 4 | import appRoutes from "./routes"; 5 | import { authenticate } from "./middlewares/authMiddleware"; 6 | import loggerMiddleware from "./middlewares/logger"; 7 | 8 | const dotenv = require("dotenv"); 9 | const next = require("next"); 10 | 11 | dotenv.config(); 12 | const dev = process.env.NODE_ENV !== "production"; 13 | const nxt = next({ dev }); 14 | const handle = nxt.getRequestHandler(); 15 | 16 | nxt.prepare().then(() => { 17 | const app = new App({ 18 | port: process.env.PORT || 5000, 19 | middlewares: [ 20 | bodyParser.json(), 21 | bodyParser.urlencoded({ extended: false }), 22 | cors(), 23 | loggerMiddleware, 24 | authenticate, 25 | ], 26 | mongoURL: 27 | process.env.DB_STRING || "mongodb://127.0.0.1:27017/mern-commerce", 28 | routes: appRoutes, 29 | }); 30 | 31 | app.app.all("*", (req, res) => { 32 | return handle(req, res); 33 | }); 34 | 35 | app.listen(); 36 | }); 37 | -------------------------------------------------------------------------------- /src/routes/accountRoutes.ts: -------------------------------------------------------------------------------- 1 | import { Router } from "express"; 2 | import { authorize } from "../middlewares/authMiddleware"; 3 | import AccountController from "../controllers/accountController"; 4 | 5 | export default class UserRoutes { 6 | private router = Router(); 7 | private accountController: AccountController = new AccountController(); 8 | 9 | constructor() { 10 | this.initRoutes(); 11 | } 12 | 13 | private initRoutes(): void { 14 | this.router.post("/register", this.accountController.register); 15 | this.router.post("/login", this.accountController.login); 16 | this.router.get("/user", this.accountController.getUser); 17 | this.router.get( 18 | "/users", 19 | authorize("superadmin", "admin"), 20 | this.accountController.getUsers 21 | ); 22 | this.router.put( 23 | "/users", 24 | authorize("superadmin", "admin"), 25 | this.accountController.updateUser 26 | ); 27 | this.router.delete( 28 | "/users", 29 | authorize("superadmin", "admin"), 30 | this.accountController.deleteUser 31 | ); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /src/app.ts: -------------------------------------------------------------------------------- 1 | import express, { Application, Request, Response } from "express"; 2 | import mongoose from "mongoose"; 3 | 4 | class App { 5 | public app: Application; 6 | public port: number | string; 7 | 8 | constructor(appInit: { 9 | port: number | string; 10 | middlewares: any; 11 | mongoURL: string; 12 | routes: any; 13 | }) { 14 | this.app = express(); 15 | this.port = appInit.port; 16 | 17 | this.initMiddlewares(appInit.middlewares); 18 | this.connectDB(appInit.mongoURL); 19 | this.initRoutes(appInit.routes); 20 | } 21 | 22 | private initMiddlewares(middlewares: any) { 23 | middlewares.forEach((middleware: any) => { 24 | this.app.use(middleware); 25 | }); 26 | } 27 | 28 | private connectDB(mongoURL: string): void { 29 | mongoose.connect(mongoURL); 30 | console.log("Database connected successfully"); 31 | } 32 | 33 | private initRoutes(appRoutes: any): void { 34 | appRoutes.forEach((appRoutes: any) => { 35 | this.app.use("/api", appRoutes.router); 36 | }); 37 | } 38 | 39 | public listen() { 40 | this.app.listen(this.port, () => { 41 | console.log(`App is listening on port no ${this.port}`); 42 | }); 43 | } 44 | } 45 | 46 | export default App; 47 | -------------------------------------------------------------------------------- /src/routes/orderRoutes.ts: -------------------------------------------------------------------------------- 1 | import { Router } from "express"; 2 | import { authorize } from "../middlewares/authMiddleware"; 3 | import OrderController from "../controllers/orderController"; 4 | 5 | export default class OrderRoutes { 6 | private router = Router(); 7 | private orderController: OrderController = new OrderController(); 8 | 9 | constructor() { 10 | this.initRoutes(); 11 | } 12 | 13 | private initRoutes(): void { 14 | this.router.get( 15 | "/orders", 16 | authorize("superadmin", "admin"), 17 | this.orderController.getOrders 18 | ); 19 | this.router.get( 20 | "/order", 21 | authorize("superadmin", "admin", "user"), 22 | this.orderController.getOrderById 23 | ); 24 | this.router.post( 25 | "/orders", 26 | authorize("superadmin", "admin", "user"), 27 | this.orderController.createOrder 28 | ); 29 | this.router.put( 30 | "/orders", 31 | authorize("superadmin", "admin"), 32 | this.orderController.updateOrder 33 | ); 34 | this.router.delete( 35 | "/orders", 36 | authorize("superadmin", "admin"), 37 | this.orderController.deleteOrder 38 | ); 39 | 40 | this.router.get( 41 | "/orders/report", 42 | authorize("superadmin"), 43 | this.orderController.generateReport 44 | ); 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /src/models/productModel.ts: -------------------------------------------------------------------------------- 1 | import mongoose, { Schema } from "mongoose"; 2 | import ProductDoc from "../interfaces/productInterface"; 3 | 4 | const slugify = require("slugify"); 5 | 6 | const productSchema = new Schema({ 7 | title: { type: String, required: true, index: true }, 8 | description: { type: String, required: true }, 9 | price: { type: Number, required: true }, 10 | slug: { type: String, required: true }, 11 | category: { type: String, required: true }, 12 | image: { type: String, required: true }, 13 | createdAt: { type: Date, default: Date.now }, 14 | }); 15 | 16 | productSchema.pre("validate", async function (this: any, next: any) { 17 | if (this.title) { 18 | this.slug = slugify(this.title, { lower: true, strict: true }); 19 | } 20 | next(); 21 | }); 22 | 23 | export default mongoose.model("Product", productSchema); 24 | 25 | const categorySchema = new Schema({ 26 | title: { type: String, required: true, index: true }, 27 | slug: { type: String, required: true }, 28 | }); 29 | 30 | categorySchema.pre("validate", async function (this: any, next: any) { 31 | if (this.title) { 32 | this.slug = slugify(this.title, { lower: true, strict: true }); 33 | } 34 | next(); 35 | }); 36 | 37 | export const Category = mongoose.model("Category", categorySchema); 38 | -------------------------------------------------------------------------------- /src/models/orderModel.ts: -------------------------------------------------------------------------------- 1 | import mongoose, { Schema } from "mongoose"; 2 | 3 | const AutoIncrement = require("mongoose-sequence")(mongoose); 4 | 5 | const orderItemSchema = new Schema({ 6 | product: { type: Schema.Types.ObjectId, ref: "Product", required: true }, 7 | order: { 8 | type: Schema.Types.ObjectId, 9 | ref: "Order", 10 | required: true, 11 | }, 12 | price: { type: Number, required: true }, 13 | quantity: { type: Number, required: true }, 14 | timestamp: { type: Date, default: Date.now }, 15 | }); 16 | 17 | export const OrderItem = mongoose.model("OrderItem", orderItemSchema); 18 | 19 | const orderSchema = new Schema({ 20 | customer: { type: Schema.Types.ObjectId, ref: "User", required: true }, 21 | shippingAddress: { type: String, required: true }, 22 | orderEmail: { type: String, required: true }, 23 | status: { type: String, default: "pending", index: true }, 24 | timestamp: { type: Date, default: Date.now }, 25 | }); 26 | 27 | orderSchema.plugin(AutoIncrement, { inc_field: "orderNo" }); 28 | orderSchema.virtual("orderItems", { 29 | ref: "OrderItem", 30 | localField: "_id", 31 | foreignField: "order", 32 | }); 33 | orderSchema.set("toObject", { virtuals: true }); 34 | orderSchema.set("toJSON", { virtuals: true }); 35 | 36 | export default mongoose.model("Order", orderSchema); 37 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "ecom", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1", 8 | "dev": "nodemon src/server.ts", 9 | "start": "node dist/server.js", 10 | "build:server": "tsc --project tsconfig.server.json", 11 | "build:client": "next build", 12 | "build": "npm run build:client && npm run build:server", 13 | "export": "npm run build:next && next export" 14 | }, 15 | "author": "", 16 | "license": "ISC", 17 | "dependencies": { 18 | "@admin-bro/express": "^3.1.0", 19 | "@admin-bro/mongoose": "^1.1.0", 20 | "admin-bro": "^3.4.0", 21 | "axios": "^0.21.1", 22 | "bcrypt": "^5.0.0", 23 | "body-parser": "^1.19.0", 24 | "cors": "^2.8.5", 25 | "dotenv": "^8.2.0", 26 | "express": "^4.17.1", 27 | "express-formidable": "^1.2.0", 28 | "express-session": "^1.17.1", 29 | "jsonwebtoken": "^9.0.2", 30 | "mongoose": "^5.11.17", 31 | "mongoose-sequence": "^5.3.1", 32 | "next": "^13.0.3", 33 | "react": "^18.2.0", 34 | "react-dom": "^18.2.0", 35 | "slugify": "^1.5.0" 36 | }, 37 | "devDependencies": { 38 | "@types/cors": "^2.8.10", 39 | "@types/express": "^4.17.11", 40 | "@types/mongoose": "^5.10.3", 41 | "@types/react": "^17.0.52", 42 | "nodemon": "^3.0.1", 43 | "ts-node": "^10.9.1", 44 | "tslib": "^2.1.0", 45 | "typescript": "^4.1.5" 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /src/models/userModel.ts: -------------------------------------------------------------------------------- 1 | import mongoose, { Schema } from "mongoose"; 2 | import UserDoc from "../interfaces/userDoc"; 3 | 4 | const bcrypt = require("bcrypt"); 5 | 6 | const userSchema = new Schema({ 7 | firstName: { type: String, required: true }, 8 | lastName: { type: String, required: true }, 9 | username: { type: String, required: true, index: true }, 10 | email: { type: String, required: true, index: true }, 11 | password: { type: String, required: true, index: true }, 12 | role: { type: String, default: "user" }, 13 | timestamp: { type: Date, default: Date.now }, 14 | }); 15 | 16 | userSchema.path("username").validate(async (username: String) => { 17 | let usernameCount = await mongoose.models.User.countDocuments({ username }); 18 | return !usernameCount; 19 | }, "Username already exist"); 20 | 21 | userSchema.path("email").validate(async (email: String) => { 22 | let emailCount = await mongoose.models.User.countDocuments({ email }); 23 | return !emailCount; 24 | }, "Email already exist"); 25 | 26 | userSchema.path("role").validate(async (role: String) => { 27 | if (role === "superadmin") return false; 28 | }, "Can't be registered as superadmin. Please ask another superadmin to create account for you"); 29 | 30 | userSchema.pre("save", async function (this: any, next: any) { 31 | try { 32 | let salt = await bcrypt.genSalt(10); 33 | this.password = await bcrypt.hash(this.password, salt); 34 | next(); 35 | } catch (err) { 36 | console.log(err); 37 | } 38 | }); 39 | 40 | export default mongoose.model("User", userSchema); 41 | -------------------------------------------------------------------------------- /src/routes/productRoutes.ts: -------------------------------------------------------------------------------- 1 | import { Router } from "express"; 2 | import { authorize } from "../middlewares/authMiddleware"; 3 | import ProductController from "../controllers/productController"; 4 | 5 | export default class ProductRoutes { 6 | private router = Router(); 7 | private productController: ProductController = new ProductController(); 8 | 9 | constructor() { 10 | this.initRoutes(); 11 | } 12 | 13 | private initRoutes() { 14 | this.router.get("/products", this.productController.getProducts); 15 | this.router.get("/product", this.productController.getSingleProduct); 16 | this.router.get("/product/search", this.productController.searchProducts); 17 | this.router.get( 18 | "/products/categories", 19 | this.productController.getCategories 20 | ); 21 | this.router.post( 22 | "/products/categories", 23 | this.productController.addCategory 24 | ); 25 | this.router.get( 26 | "/category/:categorySlug", 27 | this.productController.getProductByCategory 28 | ); 29 | this.router.post( 30 | "/products", 31 | authorize("superadmin", "admin"), 32 | this.productController.createProduct 33 | ); 34 | this.router.put( 35 | "/products", 36 | authorize("superadmin", "admin"), 37 | this.productController.updateProduct 38 | ); 39 | this.router.delete( 40 | "/products", 41 | authorize("superadmin", "admin"), 42 | this.productController.deleteProduct 43 | ); 44 | this.router.get( 45 | "/products/generate", 46 | authorize("superadmin"), 47 | this.productController.generateProducts 48 | ); 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /src/controllers/accountController.ts: -------------------------------------------------------------------------------- 1 | import { Request, Response } from "express"; 2 | import User from "../models/userModel"; 3 | 4 | const jwt = require("jsonwebtoken"); 5 | const bcrypt = require("bcrypt"); 6 | 7 | export default class AccountController { 8 | public async register(req: Request, res: Response) { 9 | const newUser = new User(req.body); 10 | 11 | if (!req.body.username || !req.body.password) { 12 | res.status(400).send({ msg: "Please provide username and password" }); 13 | } 14 | 15 | try { 16 | let user = await newUser.save(); 17 | res.send(user); 18 | } catch (err) { 19 | res.status(400).send(err); 20 | } 21 | } 22 | 23 | public async login(req: Request, res: Response) { 24 | const { username } = req.body; 25 | 26 | try { 27 | let user = await User.findOne({ username }); 28 | 29 | if (user === null) { 30 | res.status(400).send({ msg: "User not found" }); 31 | } else { 32 | bcrypt.compare( 33 | req.body.password, 34 | user.password, 35 | (err: any, result: any) => { 36 | if (err) { 37 | res.send(err); 38 | } else if (result) { 39 | jwt.sign( 40 | { user }, 41 | process.env.SECRET_KEY, 42 | (err: any, token: any) => { 43 | if (err) res.send(err); 44 | res.send({ token }); 45 | } 46 | ); 47 | } else { 48 | res.status(400).send({ msg: "Invalid Password" }); 49 | } 50 | } 51 | ); 52 | } 53 | } catch (err) { 54 | res.send(err); 55 | } 56 | } 57 | 58 | public async getUsers(req: Request, res: Response) { 59 | try { 60 | let users = await User.find().exec(); 61 | res.send(users); 62 | } catch (err) { 63 | res.send(err); 64 | } 65 | } 66 | 67 | public async getUser(req: Request, res: Response) { 68 | let { user } = req.body; 69 | 70 | if (user) { 71 | res.send(user); 72 | } else { 73 | res.status(400).send({ msg: "User not found" }); 74 | } 75 | } 76 | 77 | public async updateUser(req: Request, res: Response) { 78 | let userId = req.body._id; 79 | let updatedUser = req.body; 80 | 81 | try { 82 | let user = await User.findOneAndUpdate({ _id: userId }, updatedUser, { 83 | upsert: true, 84 | new: true, 85 | }).exec(); 86 | res.send(user); 87 | } catch (err) { 88 | res.send(err); 89 | } 90 | } 91 | 92 | public async deleteUser(req: Request, res: Response) { 93 | let userId = req.body._id; 94 | 95 | try { 96 | let del = await User.deleteOne({ _id: userId }).exec(); 97 | 98 | // @ts-ignore 99 | if (del.deletedCount > 0) { 100 | res.send({ msg: "User deleted successfully" }); 101 | } else { 102 | res.status(500).send({ msg: "something error" }); 103 | } 104 | } catch (err) { 105 | res.send(err); 106 | } 107 | } 108 | } 109 | -------------------------------------------------------------------------------- /src/controllers/orderController.ts: -------------------------------------------------------------------------------- 1 | import { Request, Response } from "express"; 2 | import Product from "../models/productModel"; 3 | import Order, { OrderItem } from "../models/orderModel"; 4 | 5 | export default class OrderController { 6 | public async getOrders(req: Request, res: Response) { 7 | try { 8 | const data = await Order.find() 9 | .populate("customer") 10 | .populate({ 11 | path: "orderItems", 12 | select: "product price quantity", 13 | populate: { 14 | path: "product", 15 | model: "Product", 16 | }, 17 | }); 18 | res.send(data); 19 | } catch (err) { 20 | res.send(err); 21 | } 22 | } 23 | 24 | public async getOrderById(req: Request, res: Response) { 25 | try { 26 | let order = await Order.findOne({ _id: req.query.id }) 27 | .populate("customer", "firstName lastName username email role") 28 | .populate({ 29 | path: "orderItems", 30 | select: "product price quantity", 31 | populate: { 32 | path: "product", 33 | model: "Product", 34 | select: "title price description", 35 | }, 36 | }); 37 | res.send(order); 38 | } catch (err) { 39 | res.send(err); 40 | } 41 | } 42 | 43 | public async createOrder(req: Request, res: Response) { 44 | let newOrder = new Order({ 45 | customer: req.body.user._id, 46 | shippingAddress: req.body.shippingAddress, 47 | orderEmail: req.body.orderEmail, 48 | }); 49 | 50 | try { 51 | let orderInfo = await newOrder.save(); 52 | 53 | let orderItems = await Product.find({ 54 | _id: { $in: req.body.orderItems.map((item: any) => item.productId) }, 55 | }).exec(); 56 | 57 | req.body.orderItems.forEach((item: any, idx: any) => { 58 | let orderItem = new OrderItem({ 59 | product: item.productId, 60 | order: orderInfo._id, 61 | price: (orderItems[idx].price as number) * item.quantity, 62 | quantity: item.quantity, 63 | }); 64 | orderItem.save(); 65 | }); 66 | 67 | res.send(orderInfo); 68 | } catch (err) { 69 | res.send(err); 70 | } 71 | } 72 | 73 | public async updateOrder(req: Request, res: Response) { 74 | let statusArr = ["accepted", "delivered", "rejected"]; 75 | 76 | if (!statusArr.includes(req.body.status)) { 77 | req.body.status = "pending"; 78 | } 79 | 80 | try { 81 | let order = Order.findByIdAndUpdate(req.body._id, req.body, { 82 | new: true, 83 | }); 84 | res.send(order); 85 | } catch (err) { 86 | res.send(err); 87 | } 88 | } 89 | 90 | public async deleteOrder(req: Request, res: Response) { 91 | let orderId = req.body._id; 92 | 93 | try { 94 | let del = await Order.deleteOne({ _id: orderId }).exec(); 95 | if (del.deletedCount > 0) { 96 | res.send({ msg: "Order deleted successfully" }); 97 | } else { 98 | res.status(500).send({ msg: "something error" }); 99 | } 100 | } catch (err) { 101 | res.send(err); 102 | } 103 | } 104 | 105 | public async generateReport(req: Request, res: Response) { 106 | try { 107 | let totalOrders = await Order.aggregate([ 108 | { $match: {} }, 109 | { $group: { _id: "$status", count: { $sum: 1 } } }, 110 | { $project: { status: "$_id", count: 1, _id: 0 } }, 111 | ]); 112 | 113 | res.send(totalOrders); 114 | } catch (err) { 115 | res.send(err); 116 | } 117 | } 118 | } 119 | -------------------------------------------------------------------------------- /src/controllers/productController.ts: -------------------------------------------------------------------------------- 1 | import { Request, Response } from "express"; 2 | import axios from "axios"; 3 | import Product, { Category } from "../models/productModel"; 4 | 5 | export default class ProductController { 6 | public async createProduct(req: Request, res: Response) { 7 | const newProduct = new Product(req.body); 8 | 9 | try { 10 | let product = await newProduct.save(); 11 | res.send(product); 12 | } catch (err) { 13 | res.send(err); 14 | } 15 | } 16 | 17 | public async getProducts(req: Request, res: Response) { 18 | try { 19 | let products = await Product.find(); 20 | res.send(products); 21 | } catch (err) { 22 | res.send(err); 23 | } 24 | } 25 | 26 | public async getSingleProduct(req: Request, res: Response) { 27 | try { 28 | let product = await Product.findOne({ slug: req.query.slug }); 29 | res.send(product); 30 | } catch (err) { 31 | res.send(err); 32 | } 33 | } 34 | 35 | public async updateProduct(req: Request, res: Response) { 36 | let productId = req.body._id; 37 | let updatedProduct = req.body; 38 | 39 | try { 40 | let product = await Product.findOneAndUpdate( 41 | { _id: productId }, 42 | updatedProduct, 43 | { upsert: true, new: true } 44 | ); 45 | res.send(product); 46 | } catch (err) { 47 | res.send(err); 48 | } 49 | } 50 | 51 | public async deleteProduct(req: Request, res: Response) { 52 | let productId = req.body._id; 53 | 54 | try { 55 | let del = await Product.deleteOne({ _id: productId }).exec(); 56 | // @ts-ignore 57 | if (del.deletedCount > 0) { 58 | res.send({ msg: "Product deleted successfully" }); 59 | } else { 60 | res.status(500).send({ msg: "something error" }); 61 | } 62 | } catch (err) { 63 | res.send(err); 64 | } 65 | } 66 | 67 | public async generateProducts(req: Request, res: Response) { 68 | try { 69 | let productResponse = await axios.get( 70 | "https://fakestoreapi.com/products" 71 | ); 72 | let products = await Product.insertMany(productResponse.data); 73 | res.send(products); 74 | } catch (err) { 75 | res.send(err); 76 | } 77 | } 78 | 79 | public async searchProducts(req: Request, res: Response) { 80 | let query = req.query.q; 81 | var regex = new RegExp(query as string, "i"); 82 | 83 | try { 84 | let results = await Product.find({ title: regex }); 85 | res.send(results); 86 | } catch (err) { 87 | res.send(err); 88 | } 89 | } 90 | 91 | public async getProductByCategory(req: Request, res: Response) { 92 | try { 93 | if (req.params.categorySlug === "all") { 94 | let products = await Product.find(); 95 | res.send(products); 96 | } else { 97 | let category = await Category.findOne({ 98 | slug: req.params.categorySlug, 99 | }); 100 | let products = await Product.find({ category: category?._id }); 101 | res.send(products); 102 | } 103 | } catch (err) { 104 | res.send(err); 105 | } 106 | } 107 | 108 | public async getCategories(req: Request, res: Response) { 109 | try { 110 | let categories = await Category.find(); 111 | res.send(categories); 112 | } catch (err) { 113 | res.send(err); 114 | } 115 | } 116 | 117 | public async addCategory(req: Request, res: Response) { 118 | const newCategory = new Category(req.body); 119 | 120 | try { 121 | let category = await newCategory.save(); 122 | res.send(category); 123 | } catch (err) { 124 | res.send(err); 125 | } 126 | } 127 | } 128 | -------------------------------------------------------------------------------- /src/pages/index.tsx: -------------------------------------------------------------------------------- 1 | import React, { Component, Fragment, ReactElement } from "react"; 2 | import indexStyles from "./index.module.css"; 3 | import Ribbon from "components/Ribbon"; 4 | import { ApiClient } from "admin-bro"; 5 | 6 | const apiEndpoints: Record = { 7 | products: [ 8 | { 9 | title: ( 10 | <> 11 | GET /api/products get all products. 12 | 13 | ), 14 | url: "/api/products", 15 | }, 16 | { 17 | title: ( 18 | <> 19 | POST /api/products create a new product 20 | 21 | ), 22 | url: "/api/products", 23 | }, 24 | { 25 | title: ( 26 | <> 27 | PUT /api/products update a product 28 | 29 | ), 30 | url: "/api/products", 31 | }, 32 | { 33 | title: ( 34 | <> 35 | DELETE /api/products Delete a product 36 | 37 | ), 38 | url: "/api/products", 39 | }, 40 | { 41 | title: ( 42 | <> 43 | GET /api/products/generate Generate product from third party API 44 | 45 | ), 46 | url: "/api/products/generate", 47 | }, 48 | ], 49 | orders: [ 50 | { 51 | title: ( 52 | <> 53 | GET /api/orders get all orders 54 | 55 | ), 56 | url: "/api/orders", 57 | }, 58 | { 59 | title: ( 60 | <> 61 | GET /api/order/?id=ORDER_ID get an order by ID 62 | 63 | ), 64 | url: "/api/order/?id=ORDER_ID", 65 | }, 66 | { 67 | title: ( 68 | <> 69 | POST /api/orders create a new order 70 | 71 | ), 72 | url: "/api/orders", 73 | }, 74 | { 75 | title: ( 76 | <> 77 | PUT /api/orders update an order 78 | 79 | ), 80 | url: "/api/orders", 81 | }, 82 | { 83 | title: ( 84 | <> 85 | DELETE /api/orders delete an order 86 | 87 | ), 88 | url: "/api/orders", 89 | }, 90 | { 91 | title: ( 92 | <> 93 | GET /api/orders/report generate summary report 94 | 95 | ), 96 | url: "/api/orders/report", 97 | }, 98 | ], 99 | accounts: [ 100 | { 101 | title: ( 102 | <> 103 | POST /api/register create a new account 104 | 105 | ), 106 | url: "/api/register", 107 | }, 108 | { 109 | title: ( 110 | <> 111 | POST /api/login login as user 112 | 113 | ), 114 | url: "/api/login", 115 | }, 116 | { 117 | title: ( 118 | <> 119 | GET /api/users get all users 120 | 121 | ), 122 | url: "/api/users", 123 | }, 124 | { 125 | title: ( 126 | <> 127 | PUT /api/users update an user 128 | 129 | ), 130 | url: "/api/users", 131 | }, 132 | { 133 | title: ( 134 | <> 135 | DELETE /api/users delete an user 136 | 137 | ), 138 | url: "/api/users", 139 | }, 140 | ], 141 | }; 142 | 143 | export default function index() { 144 | 145 | return ( 146 | <> 147 | 148 |
149 |
150 |

MERN Commerce

151 |

152 | Made with by  153 | 154 | Shakil Ahmed 155 | 156 |

157 |
158 |
159 | Endpoints: 160 |
    161 | { 162 | Object.keys(apiEndpoints).map((endpointKey, index) => ( 163 | 164 | {apiEndpoints[endpointKey].map((productAPI, index) => ( 165 |
  • 166 | {productAPI.title} - 167 | 168 | 169 |
  • 170 | ))} 171 |
    172 |
    173 | ) 174 | ) 175 | } 176 | 177 |
178 |
179 |
180 | 181 | ); 182 | } 183 | --------------------------------------------------------------------------------