├── 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 |
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 |
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 |
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 |
--------------------------------------------------------------------------------