================================================
FILE: readme.md
================================================
# welcome to pro backend developer course code files
## If you like this course, please drop a thanks node at my social profiles.
[CoderCommunity](https://web.codercommunity.io)
[Website](https://hiteshchoudahry.com)
[Instagram](https://instagram.com/hiteshchoudharyofficial)
of course there are bugs in these code files. As you teach you focus more on concept and less on production level execution. If you find any bug, consider that as your challenge to fix them.
================================================
FILE: socialapp/.gitignore
================================================
node_modules/
================================================
FILE: socialapp/index.js
================================================
const express = require("express");
const format = require("date-format");
const app = express();
// swagger docs related
const swaggerUi = require("swagger-ui-express");
const YAML = require("yamljs");
const swaggerDocument = YAML.load("./swagger.yaml");
app.use("/api-docs", swaggerUi.serve, swaggerUi.setup(swaggerDocument));
const PORT = process.env.PORT || 4000;
app.get("/", (req, res) => {
res.status(200).send("
Hello from LCO
");
});
app.get("/api/v1/instagram", (req, res) => {
const instaSocial = {
username: "hiteshchoudharyOfficial",
folowers: 66,
follows: 70,
date: format.asString("dd[MM] - hh:mm:ss", new Date()),
};
res.status(200).json(instaSocial);
});
app.get("/api/v1/facebook", (req, res) => {
const instaSocial = {
username: "hiteshchoudharyPage",
folowers: 88,
follows: 10,
date: format.asString("dd[MM] - hh:mm:ss", new Date()),
};
res.status(200).json(instaSocial);
});
app.get("/api/v1/linkedin", (req, res) => {
const instaSocial = {
username: "hiteshchoudhary",
folowers: 800,
follows: 80,
date: new Date(),
};
res.status(200).json(instaSocial);
});
app.get("/api/v1/:token", (req, res) => {
console.log(req.params.token);
res.status(200).json({ param: req.params.token });
});
app.listen(PORT, () => {
console.log(`Server is running at ${PORT}`);
});
================================================
FILE: socialapp/nodemon.json
================================================
{
"ext": ".js, .json, .yaml, .jsx"
}
================================================
FILE: socialapp/package.json
================================================
{
"name": "socialapp",
"version": "1.0.0",
"description": "a basic social media app",
"main": "index.js",
"scripts": {
"start": "nodemon index.js"
},
"keywords": [
"express",
"api"
],
"author": "Hitesh Choudhary",
"license": "ISC",
"dependencies": {
"date-format": "^3.0.0",
"express": "^4.17.1",
"swagger-ui-express": "^4.1.6",
"yamljs": "^0.3.0"
},
"devDependencies": {
"nodemon": "^2.0.13"
}
}
================================================
FILE: socialapp/swagger.yaml
================================================
openapi: 3.0.0
info:
title: Social App
description: Our fist Social app at LCO - Hitesh
version: 1.0.1
servers:
- url: http://localhost:4000/api/v1
description: localhost version of our app
- url: https://localhost:4000/api/v1
description: this is just a dummy api url, it doesn't work
components:
securitySchemes:
BasicAuth:
type: http
scheme: basic
BearerAuth:
type: http
scheme: bearer
paths:
/instagram:
get:
summary: returns username, followers and follows
responses:
'200': # status code
description: returns an object
content:
application/json:
schema:
type: object
properties:
username:
type: string
followers:
type: string
follows:
type: string
post:
summary: returns username, followers and follows
responses:
'200': # status code
description: returns an object
content:
application/json:
schema:
type: object
properties:
username:
type: string
followers:
type: string
follows:
type: string
/{token}:
get:
summary: returns whatever is there in parameters
parameters:
- name: token
default: 5
in: path
schema:
type: string
responses:
'200': # status code
description: returns an object
content:
application/json:
schema:
type: object
properties:
params:
type: string
================================================
FILE: tshirtstore/.gitignore
================================================
.env
node_modules/
================================================
FILE: tshirtstore/README.md
================================================
# Tshirt store for pro backend developer course
Please add env variables for the project
---
### PORT=4000
### DB_URL=
### JWT_SECRET=
### JWT_EXPIRY=
### COOKIE_TIME=
### CLOUDINARY_NAME=
### CLOUDINARY_API_KEY=
### CLOUDINARY_API_SECRET=
### SMTP_HOST=
### SMTP_PORT=
### SMTP_USER=
### SMTP_PASS=
### STRIPE_API_KEY=
### STRIPE_SECRET=
### RAZORPAY_API_KEY=
### RAZORPAY_SECRET=
---
[LearnCodeOnline](https://courses.learncodeonline.in/learn)
updat the variables before deploying
================================================
FILE: tshirtstore/app.js
================================================
const express = require("express");
require("dotenv").config();
const app = express();
const morgan = require("morgan");
const cookieParser = require("cookie-parser");
const fileUpload = require("express-fileupload");
//for swagger documentation
const swaggerUi = require("swagger-ui-express");
const YAML = require("yamljs");
const swaggerDocument = YAML.load("./swagger.yaml");
app.use("/api-docs", swaggerUi.serve, swaggerUi.setup(swaggerDocument));
//custom error middleware for easy front end
const productionError = require("./middlewares/productionError");
//regular middleware
app.use(express.json());
app.use(express.urlencoded({ extended: true }));
//cookies and file middleware
app.use(cookieParser());
app.use(
fileUpload({
useTempFiles: true,
tempFileDir: "/tmp/",
})
);
//temp check
app.set("view engine", "ejs");
//morgan middleware
app.use(morgan("tiny"));
//import all routes here
const home = require("./routes/home");
const user = require("./routes/user");
const product = require("./routes/product");
const payment = require("./routes/payment");
const order = require("./routes/order");
//router middleware
app.use("/api/v1", home);
app.use("/api/v1", user);
app.use("/api/v1", product);
app.use("/api/v1", payment);
app.use("/api/v1", order);
app.get("/signuptest", (req, res) => {
res.render("signuptest");
});
app.get("/", (req, res) => {
res.render("home");
});
//to handle production error
app.use(productionError);
// export app js
module.exports = app;
================================================
FILE: tshirtstore/config/db.js
================================================
const mongoose = require("mongoose");
const connectWithDb = () => {
mongoose
.connect(process.env.DB_URL, {
useNewUrlParser: true,
useUnifiedTopology: true,
})
.then(console.log(`DB GOT CONNECTED`))
.catch((error) => {
console.log(`DB CONNECTION ISSUES`);
console.log(error);
process.exit(1);
});
};
module.exports = connectWithDb;
================================================
FILE: tshirtstore/controllers/homeController.js
================================================
const BigPromise = require("../middlewares/bigPromise");
exports.home = BigPromise(async (req, res) => {
// const db = await something()
res.status(200).json({
success: true,
greeting: "Hello from API",
});
});
exports.homeDummy = async (req, res) => {
try {
// const db = await something()
res.status(200).json({
success: true,
greeting: "this is another dummy route",
});
} catch (error) {
console.log(error);
}
};
================================================
FILE: tshirtstore/controllers/orderController.js
================================================
const Order = require("../models/order");
const Product = require("../models/product");
const BigPromise = require("../middlewares/bigPromise");
const CustomError = require("../utils/customError");
exports.createOrder = BigPromise(async (req, res, next) => {
const {
shippingInfo,
orderItems,
paymentInfo,
taxAmount,
shippingAmount,
totalAmount,
} = req.body;
const order = await Order.create({
shippingInfo,
orderItems,
paymentInfo,
taxAmount,
shippingAmount,
totalAmount,
user: req.user._id,
});
res.status(200).json({
success: true,
order,
});
});
exports.getOneOrder = BigPromise(async (req, res, next) => {
const order = await Order.findById(req.params.id).populate(
"user",
"name email"
);
if (!order) {
return next(new CustomError("please check order id", 401));
}
res.status(200).json({
success: true,
order,
});
});
exports.getLoggedInOrders = BigPromise(async (req, res, next) => {
const order = await Order.find({ user: req.user._id });
if (!order) {
return next(new CustomError("please check order id", 401));
}
res.status(200).json({
success: true,
order,
});
});
exports.admingetAllOrders = BigPromise(async (req, res, next) => {
const orders = await Order.find();
res.status(200).json({
success: true,
orders,
});
});
exports.adminUpdateOrder = BigPromise(async (req, res, next) => {
const order = await Order.findById(req.params.id);
if (order.orderStatus === "Delivered") {
return next(new CustomError("Order is already marked for delivered", 401));
}
order.orderStatus = req.body.orderStatus;
order.orderItems.forEach(async (prod) => {
await updateProductStock(prod.product, prod.quantity);
});
await order.save();
res.status(200).json({
success: true,
order,
});
});
exports.adminDeleteOrder = BigPromise(async (req, res, next) => {
const order = await Order.findById(req.params.id);
await order.remove();
res.status(200).json({
success: true,
});
});
async function updateProductStock(productId, quantity) {
const product = await Product.findById(productId);
product.stock = product.stock - quantity;
await product.save({ validateBeforeSave: false });
}
================================================
FILE: tshirtstore/controllers/paymentController.js
================================================
const BigPromise = require("../middlewares/bigPromise");
const stripe = require("stripe")(process.env.STRIPE_SECRET);
const Razorpay = require("razorpay");
exports.sendStripeKey = BigPromise(async (req, res, next) => {
res.status(200).json({
stripekey: process.env.STRIPE_API_KEY,
});
});
exports.captureStripePayment = BigPromise(async (req, res, next) => {
const paymentIntent = await stripe.paymentIntents.create({
amount: req.body.amount,
currency: "inr",
//optional
metadata: { integration_check: "accept_a_payment" },
});
res.status(200).json({
success: true,
amount: req.body.amount,
client_secret: paymentIntent.client_secret,
//you can optionally send id as well
});
});
exports.sendRazorpayKey = BigPromise(async (req, res, next) => {
res.status(200).json({
razorpaykey: process.env.RAZORPAY_API_KEY,
});
});
exports.captureRazorpayPayment = BigPromise(async (req, res, next) => {
var instance = new Razorpay({
key_id: process.env.RAZORPAY_API_KEY,
key_secret: process.env.RAZORPAY_SECRET,
});
var options = {
amount: req.body.amount, // amount in the smallest currency unit
currency: "INR",
};
console.log(req.body.amount);
const myOrder = await instance.orders.create(options);
res.status(200).json({
success: true,
amount: req.body.amount,
order: myOrder,
});
});
================================================
FILE: tshirtstore/controllers/productController.js
================================================
const Product = require("../models/product");
const BigPromise = require("../middlewares/bigPromise");
const CustomError = require("../utils/customError");
const cloudinary = require("cloudinary");
const WhereClause = require("../utils/whereClause");
exports.addProduct = BigPromise(async (req, res, next) => {
// images
let imageArray = [];
if (!req.files) {
return next(new CustomError("images are required", 401));
}
console.log("RESULT", req.files.photos[0]);
if (req.files) {
for (let index = 0; index < req.files.photos.length; index++) {
console.log("UPLOAD START...");
let result = await cloudinary.v2.uploader.upload(
req.files.photos[index].tempFilePath,
{
folder: "products",
}
);
// another way to upload and check error
// let result
// cloudinary.v2.uploader.upload(req.files.photos[index].tempFilePath, {
// folder: "products"
// }, function(error, result){console.log(result, error)})
console.log("RESULT", result);
imageArray.push({
id: result.public_id,
secure_url: result.secure_url,
});
}
}
req.body.photos = imageArray;
req.body.user = req.user.id;
const product = await Product.create(req.body);
res.status(200).json({
success: true,
product,
});
});
exports.getAllProduct = BigPromise(async (req, res, next) => {
const resultPerPage = 3;
const totalcountProduct = await Product.countDocuments();
const productsObj = new WhereClause(Product.find(), req.query)
.search()
.filter();
let products = await productsObj.base;
const filteredProductNumber = products.length;
//products.limit().skip()
productsObj.pager(resultPerPage);
products = await productsObj.base.clone();
res.status(200).json({
success: true,
products,
filteredProductNumber,
totalcountProduct,
resultPerPage,
});
});
exports.getOneProduct = BigPromise(async (req, res, next) => {
const product = await Product.findById(req.params.id);
if (!product) {
return next(new CustomError("No product found with this id", 401));
}
res.status(200).json({
success: true,
product,
});
});
exports.addReview = BigPromise(async (req, res, next) => {
const { rating, comment, productId } = req.body;
const review = {
user: req.user._id,
name: req.user.name,
rating: Number(rating),
comment,
};
const product = await Product.findById(productId);
const AlreadyReview = product.reviews.find(
(rev) => rev.user.toString() === req.user._id.toString()
);
if (AlreadyReview) {
product.reviews.forEach((review) => {
if (review.user.toString() === req.user._id.toString()) {
review.comment = comment;
review.rating = rating;
}
});
} else {
product.reviews.push(review);
product.numberOfReviews = product.reviews.length;
}
// adjust ratings
product.ratings =
product.reviews.reduce((acc, item) => item.rating + acc, 0) /
product.reviews.length;
//save
await product.save({ validateBeforeSave: false });
res.status(200).json({
success: true,
});
});
exports.deleteReview = BigPromise(async (req, res, next) => {
const { productId } = req.query;
const product = await Product.findById(productId);
const reviews = product.reviews.filter(
(rev) => rev.user.toString() === req.user._id.toString()
);
const numberOfReviews = reviews.length;
// adjust ratings
product.ratings =
product.reviews.reduce((acc, item) => item.rating + acc, 0) /
product.reviews.length;
//update the product
await Product.findByIdAndUpdate(
productId,
{
reviews,
ratings,
numberOfReviews,
},
{
new: true,
runValidators: true,
useFindAndModify: false,
}
);
res.status(200).json({
success: true,
});
});
exports.getOnlyReviewsForOneProduct = BigPromise(async (req, res, next) => {
const product = await Product.findById(req.query.id);
res.status(200).json({
success: true,
reviews: product.reviews,
});
});
// admin only controllers
exports.adminGetAllProduct = BigPromise(async (req, res, next) => {
const products = await Product.find();
res.status(200).json({
success: true,
products,
});
});
exports.adminUpdateOneProduct = BigPromise(async (req, res, next) => {
let product = await Product.findById(req.params.id);
if (!product) {
return next(new CustomError("No product found with this id", 401));
}
let imagesArray = [];
if (req.files) {
//destroy the existing image
for (let index = 0; index < product.photos.length; index++) {
const res = await cloudinary.v2.uploader.destroy(
product.photos[index].id
);
}
for (let index = 0; index < req.files.photos.length; index++) {
let result = await cloudinary.v2.uploader.upload(
req.files.photos[index].tempFilePath,
{
folder: "products", //folder name -> .env
}
);
imagesArray.push({
id: result.public_id,
secure_url: result.secure_url,
});
}
}
req.body.photos = imagesArray;
product = await Product.findByIdAndUpdate(req.params.id, req.body, {
new: true,
runValidators: true,
useFindAndModify: false,
});
res.status(200).json({
success: true,
product,
});
});
exports.adminDeleteOneProduct = BigPromise(async (req, res, next) => {
const product = await Product.findById(req.params.id);
if (!product) {
return next(new CustomError("No product found with this id", 401));
}
//destroy the existing image
for (let index = 0; index < product.photos.length; index++) {
const res = await cloudinary.v2.uploader.destroy(product.photos[index].id);
}
await product.remove();
res.status(200).json({
success: true,
message: "Product was deleted !",
});
});
================================================
FILE: tshirtstore/controllers/userController.js
================================================
const User = require("../models/user");
const BigPromise = require("../middlewares/bigPromise");
const CustomError = require("../utils/customError");
const cookieToken = require("../utils/cookieToken");
const cloudinary = require("cloudinary");
const mailHelper = require("../utils/emailHelper");
const crypto = require("crypto");
exports.signup = BigPromise(async (req, res, next) => {
//let result;
console.log(req.body);
if (!req.files) {
return next(new CustomError("photo is required for signup", 400));
}
const { name, email, password } = req.body;
if (!email || !name || !password) {
return next(new CustomError("Name, email and password are required", 400));
}
let file = req.files.photo;
const result = await cloudinary.v2.uploader.upload(file.tempFilePath, {
folder: "users",
width: 150,
crop: "scale",
});
const user = await User.create({
name,
email,
password,
photo: {
id: result.public_id,
secure_url: result.secure_url,
},
});
cookieToken(user, res);
});
exports.login = BigPromise(async (req, res, next) => {
const { email, password } = req.body;
// check for presence of email and password
if (!email || !password) {
return next(new CustomError("please provide email and password", 400));
}
// get user from DB
const user = await User.findOne({ email }).select("+password");
// if user not found in DB
if (!user) {
return next(
new CustomError("Email or password does not match or exist", 400)
);
}
// match the password
const isPasswordCorrect = await user.isValidatedPassword(password);
//if password do not match
if (!isPasswordCorrect) {
return next(
new CustomError("Email or password does not match or exist", 400)
);
}
// if all goes good and we send the token
cookieToken(user, res);
});
exports.logout = BigPromise(async (req, res, next) => {
//clear the cookie
res.cookie("token", null, {
expires: new Date(Date.now()),
httpOnly: true,
});
//send JSON response for success
res.status(200).json({
succes: true,
message: "Logout success",
});
});
exports.forgotPassword = BigPromise(async (req, res, next) => {
// collect email
const { email } = req.body;
console.log(email);
// find user in database
const user = await User.findOne({ email });
// if user not found in database
if (!user) {
return next(new CustomError("Email not found as registered", 400));
}
//get token from user model methods
const forgotToken = user.getForgotPasswordToken();
// save user fields in DB
await user.save({ validateBeforeSave: false });
// create a URL
// const myUrl = `${req.protocol}://${req.get(
// "host"
// )}/api/v1/password/reset/${forgotToken}`;
//URL for deployment as front end might be running at different URL
const myUrl = `${process.env.FRONT_END}/password/reset/${forgotToken}`;
// craft a message
const message = `Copy paste this link in your URL and hit enter \n\n ${myUrl}`;
// attempt to send email
try {
await mailHelper({
email: user.email,
subject: "LCO TStore - Password reset email",
message,
});
// json reponse if email is success
res.status(200).json({
succes: true,
message: "Email sent successfully",
});
} catch (error) {
// reset user fields if things goes wrong
user.forgotPasswordToken = undefined;
user.forgotPasswordExpiry = undefined;
await user.save({ validateBeforeSave: false });
// send error response
return next(new CustomError(error.message, 500));
}
});
exports.passwordReset = BigPromise(async (req, res, next) => {
//get token from params
const token = req.params.token;
// hash the token as db also stores the hashed version
const encryToken = crypto.createHash("sha256").update(token).digest("hex");
// find user based on hased on token and time in future
const user = await User.findOne({
encryToken,
forgotPasswordExpiry: { $gt: Date.now() },
});
if (!user) {
return next(new CustomError("Token is invalid or expired", 400));
}
// check if password and conf password matched
if (req.body.password !== req.body.confirmPassword) {
return next(
new CustomError("password and confirm password do not match", 400)
);
}
// update password field in DB
user.password = req.body.password;
// reset token fields
user.forgotPasswordToken = undefined;
user.forgotPasswordExpiry = undefined;
// save the user
await user.save();
// send a JSON response OR send token
cookieToken(user, res);
});
exports.getLoggedInUserDetails = BigPromise(async (req, res, next) => {
//req.user will be added by middleware
// find user by id
const user = await User.findById(req.user.id);
//send response and user data
res.status(200).json({
success: true,
user,
});
});
exports.changePassword = BigPromise(async (req, res, next) => {
// get user from middleware
const userId = req.user.id;
// get user from database
const user = await User.findById(userId).select("+password");
//check if old password is correct
const isCorrectOldPassword = await user.isValidatedPassword(
req.body.oldPassword
);
if (!isCorrectOldPassword) {
return next(new CustomError("old password is incorrect", 400));
}
// allow to set new password
user.password = req.body.password;
// save user and send fresh token
await user.save();
cookieToken(user, res);
});
exports.updateUserDetails = BigPromise(async (req, res, next) => {
// add a check for email and name in body
// collect data from body
const newData = {
name: req.body.name,
email: req.body.email,
};
// if photo comes to us
if (req.files) {
const user = await User.findById(req.user.id);
const imageId = user.photo.id;
// delete photo on cloudinary
const resp = await cloudinary.v2.uploader.destroy(imageId);
// upload the new photo
const result = await cloudinary.v2.uploader.upload(
req.files.photo.tempFilePath,
{
folder: "users",
width: 150,
crop: "scale",
}
);
// add photo data in newData object
newData.photo = {
id: result.public_id,
secure_url: result.secure_url,
};
}
// update the data in user
const user = await User.findByIdAndUpdate(req.user.id, newData, {
new: true,
runValidators: true,
useFindAndModify: false,
});
res.status(200).json({
success: true,
});
});
exports.adminAllUser = BigPromise(async (req, res, next) => {
// select all users
const users = await User.find();
// send all users
res.status(200).json({
success: true,
users,
});
});
exports.admingetOneUser = BigPromise(async (req, res, next) => {
// get id from url and get user from database
const user = await User.findById(req.params.id);
if (!user) {
next(new CustomError("No user found", 400));
}
// send user
res.status(200).json({
success: true,
user,
});
});
exports.adminUpdateOneUserDetails = BigPromise(async (req, res, next) => {
// add a check for email and name in body
// get data from request body
const newData = {
name: req.body.name,
email: req.body.email,
role: req.body.role,
};
// update the user in database
const user = await User.findByIdAndUpdate(req.params.id, newData, {
new: true,
runValidators: true,
useFindAndModify: false,
});
res.status(200).json({
success: true,
});
});
exports.adminDeleteOneUser = BigPromise(async (req, res, next) => {
// get user from url
const user = await User.findById(req.params.id);
if (!user) {
return next(new CustomError("No Such user found", 401));
}
// get image id from user in database
const imageId = user.photo.id;
// delete image from cloudinary
await cloudinary.v2.uploader.destroy(imageId);
// remove user from databse
await user.remove();
res.status(200).json({
success: true,
});
});
exports.managerAllUser = BigPromise(async (req, res, next) => {
// select the user with role of user
const users = await User.find({ role: "user" });
res.status(200).json({
success: true,
users,
});
});
================================================
FILE: tshirtstore/index.js
================================================
const app = require("./app");
const connectWithDb = require("./config/db");
require("dotenv").config();
const cloudinary = require("cloudinary");
// connect with databases
connectWithDb();
//cloudinary config goes here
cloudinary.config({
cloud_name: process.env.CLOUDINARY_NAME,
api_key: process.env.CLOUDINARY_API_KEY,
api_secret: process.env.CLOUDINARY_API_SECRET,
});
app.listen(process.env.PORT, () => {
console.log(`Server is running at port: ${process.env.PORT}`);
});
================================================
FILE: tshirtstore/lcotshirtstore/.gitignore
================================================
.env
node_modules/
================================================
FILE: tshirtstore/lcotshirtstore/app.js
================================================
const express = require("express");
require("dotenv").config();
const app = express();
const morgan = require("morgan");
const cookieParser = require("cookie-parser");
const fileUpload = require("express-fileupload");
//for swagger documentation
const swaggerUi = require("swagger-ui-express");
const YAML = require("yamljs");
const swaggerDocument = YAML.load("./swagger.yaml");
app.use("/api-docs", swaggerUi.serve, swaggerUi.setup(swaggerDocument));
//regular middleware
app.use(express.json());
app.use(express.urlencoded({ extended: true }));
//cookies and file middleware
app.use(cookieParser());
app.use(
fileUpload({
useTempFiles: true,
tempFileDir: "/tmp/",
})
);
//temp check
app.set("view engine", "ejs");
//morgan middleware
app.use(morgan("tiny"));
//import all routes here
const home = require("./routes/home");
const user = require("./routes/user");
const product = require("./routes/product");
const payment = require("./routes/payment");
const order = require("./routes/order");
//router middleware
app.use("/api/v1", home);
app.use("/api/v1", user);
app.use("/api/v1", product);
app.use("/api/v1", payment);
app.use("/api/v1", order);
app.get("/signuptest", (req, res) => {
res.render("signuptest");
});
// export app js
module.exports = app;
================================================
FILE: tshirtstore/lcotshirtstore/config/db.js
================================================
const mongoose = require("mongoose");
const connectWithDb = () => {
mongoose
.connect(process.env.DB_URL, {
useNewUrlParser: true,
useUnifiedTopology: true,
})
.then(console.log(`DB GOT CONNECTED`))
.catch((error) => {
console.log(`DB CONNECTION ISSUES`);
console.log(error);
process.exit(1);
});
};
module.exports = connectWithDb;
================================================
FILE: tshirtstore/lcotshirtstore/controllers/homeController.js
================================================
const BigPromise = require("../middlewares/bigPromise");
exports.home = BigPromise(async (req, res) => {
// const db = await something()
res.status(200).json({
success: true,
greeting: "Hello from API",
});
});
exports.homeDummy = async (req, res) => {
try {
// const db = await something()
res.status(200).json({
success: true,
greeting: "this is another dummy route",
});
} catch (error) {
console.log(error);
}
};
================================================
FILE: tshirtstore/lcotshirtstore/controllers/orderController.js
================================================
const Order = require("../models/order");
const Product = require("../models/product");
const BigPromise = require("../middlewares/bigPromise");
const CustomError = require("../utils/customError");
exports.createOrder = BigPromise(async (req, res, next) => {
const {
shippingInfo,
orderItems,
paymentInfo,
taxAmount,
shippingAmount,
totalAmount,
} = req.body;
const order = await Order.create({
shippingInfo,
orderItems,
paymentInfo,
taxAmount,
shippingAmount,
totalAmount,
user: req.user._id,
});
res.status(200).json({
success: true,
order,
});
});
exports.getOneOrder = BigPromise(async (req, res, next) => {
const order = await Order.findById(req.params.id).populate(
"user",
"name email"
);
if (!order) {
return next(new CustomError("please check order id", 401));
}
res.status(200).json({
success: true,
order,
});
});
exports.getLoggedInOrders = BigPromise(async (req, res, next) => {
const order = await Order.find({ user: req.user._id });
if (!order) {
return next(new CustomError("please check order id", 401));
}
res.status(200).json({
success: true,
order,
});
});
exports.admingetAllOrders = BigPromise(async (req, res, next) => {
const orders = await Order.find();
res.status(200).json({
success: true,
orders,
});
});
exports.adminUpdateOrder = BigPromise(async (req, res, next) => {
const order = await Order.findById(req.params.id);
if (order.orderStatus === "Delivered") {
return next(new CustomError("Order is already marked for delivered", 401));
}
order.orderStatus = req.body.orderStatus;
order.orderItems.forEach(async (prod) => {
await updateProductStock(prod.product, prod.quantity);
});
await order.save();
res.status(200).json({
success: true,
orders,
});
});
exports.adminDeleteOrder = BigPromise(async (req, res, next) => {
const order = await Order.findById(req.params.id);
await order.remove();
res.status(200).json({
success: true,
});
});
async function updateProductStock(productId, quantity) {
const product = await Product.findById(productId);
product.stock = product.stock - quantity;
await product.save({ validateBeforeSave: false });
}
================================================
FILE: tshirtstore/lcotshirtstore/controllers/paymentController.js
================================================
const BigPromise = require("../middlewares/bigPromise");
const stripe = require("stripe")(process.env.STRIPE_SECRET);
exports.sendStripeKey = BigPromise(async (req, res, next) => {
res.status(200).json({
stripekey: process.env.STRIPE_API_KEY,
});
});
exports.captureStripePayment = BigPromise(async (req, res, next) => {
const paymentIntent = await stripe.paymentIntents.create({
amount: req.body.amount,
currency: "inr",
//optional
metadata: { integration_check: "accept_a_payment" },
});
res.status(200).json({
success: true,
amount: req.body.amount,
client_secret: paymentIntent.client_secret,
//you can optionally send id as well
});
});
exports.sendRazorpayKey = BigPromise(async (req, res, next) => {
res.status(200).json({
stripekey: process.env.RAZORPAY_API_KEY,
});
});
exports.captureRazorpayPayment = BigPromise(async (req, res, next) => {
var instance = new Razorpay({
key_id: process.env.RAZORPAY_API_KEY,
key_secret: process.env.RAZORPAY_SECRET,
});
var options = {
amount: req.body.amount, // amount in the smallest currency unit
currency: "INR",
};
const myOrder = await instance.orders.create(options);
res.staus(200).json({
success: true,
amount: req.body.amount,
order: myOrder,
});
});
================================================
FILE: tshirtstore/lcotshirtstore/controllers/productController.js
================================================
const Product = require("../models/product");
const BigPromise = require("../middlewares/bigPromise");
const CustomError = require("../utils/customError");
const cloudinary = require("cloudinary");
const WhereClause = require("../utils/whereClause");
exports.addProduct = BigPromise(async (req, res, next) => {
// images
let imageArray = [];
if (!req.files) {
return next(new CustomError("images are required", 401));
}
if (req.files) {
for (let index = 0; index < req.files.photos.length; index++) {
let result = await cloudinary.v2.uploader.upload(
req.files.photos[index].tempFilePath,
{
folder: "products",
}
);
imageArray.push({
id: result.public_id,
secure_url: result.secure_url,
});
}
}
req.body.photos = imageArray;
req.body.user = req.user.id;
const product = await Product.create(req.body);
res.status(200).json({
success: true,
product,
});
});
exports.getAllProduct = BigPromise(async (req, res, next) => {
const resultPerPage = 6;
const totalcountProduct = await Product.countDocuments();
const productsObj = new WhereClause(Product.find(), req.query)
.search()
.filter();
let products = await productsObj.base;
const filteredProductNumber = products.length;
//products.limit().skip()
productsObj.pager(resultPerPage);
products = await productsObj.base.clone();
res.status(200).json({
success: true,
products,
filteredProductNumber,
totalcountProduct,
});
});
exports.getOneProduct = BigPromise(async (req, res, next) => {
const product = await Product.findById(req.params.id);
if (!product) {
return next(new CustomError("No product found with this id", 401));
}
res.status(200).json({
success: true,
product,
});
});
exports.addReview = BigPromise(async (req, res, next) => {
const { rating, comment, productId } = req.body;
const review = {
user: req.user._id,
name: req.user.name,
rating: Number(rating),
comment,
};
const product = await Product.findById(productId);
const AlreadyReview = product.reviews.find(
(rev) => rev.user.toString() === req.user._id.toString()
);
if (AlreadyReview) {
product.reviews.forEach((review) => {
if (review.user.toString() === req.user._id.toString()) {
review.comment = comment;
review.rating = rating;
}
});
} else {
product.reviews.push(review);
product.numberOfReviews = product.reviews.length;
}
// adjust ratings
product.ratings =
product.reviews.reduce((acc, item) => item.rating + acc, 0) /
product.reviews.length;
//save
await product.save({ validateBeforeSave: false });
res.status(200).json({
success: true,
});
});
exports.deleteReview = BigPromise(async (req, res, next) => {
const { productId } = req.query;
const product = await Product.findById(productId);
const reviews = product.reviews.filter(
(rev) => rev.user.toString() === req.user._id.toString()
);
const numberOfReviews = reviews.length;
// adjust ratings
product.ratings =
product.reviews.reduce((acc, item) => item.rating + acc, 0) /
product.reviews.length;
//update the product
await Product.findByIdAndUpdate(
productId,
{
reviews,
ratings,
numberOfReviews,
},
{
new: true,
runValidators: true,
useFindAndModify: false,
}
);
res.status(200).json({
success: true,
});
});
exports.getOnlyReviewsForOneProduct = BigPromise(async (req, res, next) => {
const product = await Product.findById(req.query.id);
res.status(200).json({
success: true,
reviews: product.reviews,
});
});
// admin only controllers
exports.adminGetAllProduct = BigPromise(async (req, res, next) => {
const products = await Product.find();
res.status(200).json({
success: true,
products,
});
});
exports.adminUpdateOneProduct = BigPromise(async (req, res, next) => {
let product = await Product.findById(req.params.id);
if (!product) {
return next(new CustomError("No product found with this id", 401));
}
let imagesArray = [];
if (req.files) {
//destroy the existing image
for (let index = 0; index < product.photos.length; index++) {
const res = await cloudinary.v2.uploader.destroy(
product.photos[index].id
);
}
for (let index = 0; index < req.files.photos.length; index++) {
let result = await cloudinary.v2.uploader.upload(
req.files.photos[index].tempFilePath,
{
folder: "products", //folder name -> .env
}
);
imagesArray.push({
id: result.public_id,
secure_url: result.secure_url,
});
}
}
req.body.photos = imagesArray;
product = await Product.findByIdAndUpdate(req.params.id, req.body, {
new: true,
runValidators: true,
useFindAndModify: false,
});
res.status(200).json({
success: true,
product,
});
});
exports.adminDeleteOneProduct = BigPromise(async (req, res, next) => {
const product = await Product.findById(req.params.id);
if (!product) {
return next(new CustomError("No product found with this id", 401));
}
//destroy the existing image
for (let index = 0; index < product.photos.length; index++) {
const res = await cloudinary.v2.uploader.destroy(product.photos[index].id);
}
await product.remove();
res.status(200).json({
success: true,
message: "Product was deleted !",
});
});
================================================
FILE: tshirtstore/lcotshirtstore/controllers/userController.js
================================================
const User = require("../models/user");
const BigPromise = require("../middlewares/bigPromise");
const CustomError = require("../utils/customError");
const cookieToken = require("../utils/cookieToken");
const cloudinary = require("cloudinary");
const mailHelper = require("../utils/emailHelper");
const crypto = require("crypto");
exports.signup = BigPromise(async (req, res, next) => {
//let result;
if (!req.files) {
return next(new CustomError("photo is required for signup", 400));
}
const { name, email, password } = req.body;
if (!email || !name || !password) {
return next(new CustomError("Name, email and password are required", 400));
}
let file = req.files.photo;
const result = await cloudinary.v2.uploader.upload(file.tempFilePath, {
folder: "users",
width: 150,
crop: "scale",
});
const user = await User.create({
name,
email,
password,
photo: {
id: result.public_id,
secure_url: result.secure_url,
},
});
cookieToken(user, res);
});
exports.login = BigPromise(async (req, res, next) => {
const { email, password } = req.body;
// check for presence of email and password
if (!email || !password) {
return next(new CustomError("please provide email and password", 400));
}
// get user from DB
const user = await User.findOne({ email }).select("+password");
// if user not found in DB
if (!user) {
return next(
new CustomError("Email or password does not match or exist", 400)
);
}
// match the password
const isPasswordCorrect = await user.isValidatedPassword(password);
//if password do not match
if (!isPasswordCorrect) {
return next(
new CustomError("Email or password does not match or exist", 400)
);
}
// if all goes good and we send the token
cookieToken(user, res);
});
exports.logout = BigPromise(async (req, res, next) => {
//clear the cookie
res.cookie("token", null, {
expires: new Date(Date.now()),
httpOnly: true,
});
//send JSON response for success
res.status(200).json({
succes: true,
message: "Logout success",
});
});
exports.forgotPassword = BigPromise(async (req, res, next) => {
// collect email
const { email } = req.body;
// find user in database
const user = await User.findOne({ email });
// if user not found in database
if (!user) {
return next(new CustomError("Email not found as registered", 400));
}
//get token from user model methods
const forgotToken = user.getForgotPasswordToken();
// save user fields in DB
await user.save({ validateBeforeSave: false });
// create a URL
const myUrl = `${req.protocol}://${req.get(
"host"
)}/api/v1/password/reset/${forgotToken}`;
// craft a message
const message = `Copy paste this link in your URL and hit enter \n\n ${myUrl}`;
// attempt to send email
try {
await mailHelper({
email: user.email,
subject: "LCO TStore - Password reset email",
message,
});
// json reponse if email is success
res.status(200).json({
succes: true,
message: "Email sent successfully",
});
} catch (error) {
// reset user fields if things goes wrong
user.forgotPasswordToken = undefined;
user.forgotPasswordExpiry = undefined;
await user.save({ validateBeforeSave: false });
// send error response
return next(new CustomError(error.message, 500));
}
});
exports.passwordReset = BigPromise(async (req, res, next) => {
//get token from params
const token = req.params.token;
// hash the token as db also stores the hashed version
const encryToken = crypto.createHash("sha256").update(token).digest("hex");
// find user based on hased on token and time in future
const user = await User.findOne({
encryToken,
forgotPasswordExpiry: { $gt: Date.now() },
});
if (!user) {
return next(new CustomError("Token is invalid or expired", 400));
}
// check if password and conf password matched
if (req.body.password !== req.body.confirmPassword) {
return next(
new CustomError("password and confirm password do not match", 400)
);
}
// update password field in DB
user.password = req.body.password;
// reset token fields
user.forgotPasswordToken = undefined;
user.forgotPasswordExpiry = undefined;
// save the user
await user.save();
// send a JSON response OR send token
cookieToken(user, res);
});
exports.getLoggedInUserDetails = BigPromise(async (req, res, next) => {
//req.user will be added by middleware
// find user by id
const user = await User.findById(req.user.id);
//send response and user data
res.status(200).json({
success: true,
user,
});
});
exports.changePassword = BigPromise(async (req, res, next) => {
// get user from middleware
const userId = req.user.id;
// get user from database
const user = await User.findById(userId).select("+password");
//check if old password is correct
const isCorrectOldPassword = await user.isValidatedPassword(
req.body.oldPassword
);
if (!isCorrectOldPassword) {
return next(new CustomError("old password is incorrect", 400));
}
// allow to set new password
user.password = req.body.password;
// save user and send fresh token
await user.save();
cookieToken(user, res);
});
exports.updateUserDetails = BigPromise(async (req, res, next) => {
// add a check for email and name in body
// collect data from body
const newData = {
name: req.body.name,
email: req.body.email,
};
// if photo comes to us
if (req.files) {
const user = await User.findById(req.user.id);
const imageId = user.photo.id;
// delete photo on cloudinary
const resp = await cloudinary.v2.uploader.destroy(imageId);
// upload the new photo
const result = await cloudinary.v2.uploader.upload(
req.files.photo.tempFilePath,
{
folder: "users",
width: 150,
crop: "scale",
}
);
// add photo data in newData object
newData.photo = {
id: result.public_id,
secure_url: result.secure_url,
};
}
// update the data in user
const user = await User.findByIdAndUpdate(req.user.id, newData, {
new: true,
runValidators: true,
useFindAndModify: false,
});
res.status(200).json({
success: true,
});
});
exports.adminAllUser = BigPromise(async (req, res, next) => {
// select all users
const users = await User.find();
// send all users
res.status(200).json({
success: true,
users,
});
});
exports.admingetOneUser = BigPromise(async (req, res, next) => {
// get id from url and get user from database
const user = await User.findById(req.params.id);
if (!user) {
next(new CustomError("No user found", 400));
}
// send user
res.status(200).json({
success: true,
user,
});
});
exports.adminUpdateOneUserDetails = BigPromise(async (req, res, next) => {
// add a check for email and name in body
// get data from request body
const newData = {
name: req.body.name,
email: req.body.email,
role: req.body.role,
};
// update the user in database
const user = await User.findByIdAndUpdate(req.params.id, newData, {
new: true,
runValidators: true,
useFindAndModify: false,
});
res.status(200).json({
success: true,
});
});
exports.adminDeleteOneUser = BigPromise(async (req, res, next) => {
// get user from url
const user = await User.findById(req.params.id);
if (!user) {
return next(new CustomError("No Such user found", 401));
}
// get image id from user in database
const imageId = user.photo.id;
// delete image from cloudinary
await cloudinary.v2.uploader.destroy(imageId);
// remove user from databse
await user.remove();
res.status(200).json({
success: true,
});
});
exports.managerAllUser = BigPromise(async (req, res, next) => {
// select the user with role of user
const users = await User.find({ role: "user" });
res.status(200).json({
success: true,
users,
});
});
================================================
FILE: tshirtstore/lcotshirtstore/index.js
================================================
const app = require("./app");
const connectWithDb = require("./config/db");
require("dotenv").config();
const cloudinary = require("cloudinary");
// connect with databases
connectWithDb();
//cloudinary config goes here
cloudinary.config({
cloud_name: process.env.CLOUDINARY_NAME,
api_key: process.env.CLOUDINARY_API_KEY,
api_secret: process.env.CLOUDINARY_API_SECRET,
});
app.listen(process.env.PORT, () => {
console.log(`Server is running at port: ${process.env.PORT}`);
});
================================================
FILE: tshirtstore/lcotshirtstore/middlewares/bigPromise.js
================================================
// try catch and async - await || use promise
module.exports = (func) => (req, res, next) =>
Promise.resolve(func(req, res, next)).catch(next);
================================================
FILE: tshirtstore/lcotshirtstore/middlewares/user.js
================================================
const User = require("../models/user");
const BigPromise = require("../middlewares/bigPromise");
const CustomError = require("../utils/customError");
const jwt = require("jsonwebtoken");
exports.isLoggedIn = BigPromise(async (req, res, next) => {
const token =
req.cookies.token || req.header("Authorization").replace("Bearer ", "");
if (!token) {
return next(new CustomError("Login first to access this page", 401));
}
const decoded = jwt.verify(token, process.env.JWT_SECRET);
req.user = await User.findById(decoded.id);
next();
});
exports.customRole = (...roles) => {
return (req, res, next) => {
if (!roles.includes(req.user.role)) {
return next(new CustomError("You are not allowed for this resouce", 403));
}
next();
};
};
================================================
FILE: tshirtstore/lcotshirtstore/models/order.js
================================================
const mongoose = require("mongoose");
// shippingInfo{}
// user,
// paymentInfo{}
// taxAmount
// ShippingAmount
// totalAmount
// orderStatus
// deliveredAt
// createdAt
// -------------
// orderItems: [ {} ]
// - name
// - quantity
// - image[0]
// - price
// - product
const orderSchema = new mongoose.Schema({
shippingInfo: {
address: {
type: String,
required: true,
},
city: {
type: String,
required: true,
},
phoneNo: {
type: String,
required: true,
},
postalCode: {
type: String,
required: true,
},
state: {
type: String,
required: true,
},
country: {
type: String,
required: true,
},
},
user: {
type: mongoose.Schema.ObjectId, //mongoose.Schema.Types.ObjectId
ref: "User",
required: true,
},
orderItems: [
{
name: {
type: String,
required: true,
},
quantity: {
type: Number,
required: true,
},
image: {
type: String,
required: true,
},
price: {
type: Number,
required: true,
},
product: {
type: mongoose.Schema.ObjectId, //mongoose.Schema.Types.ObjectId
ref: "Product",
required: true,
},
},
],
paymentInfo: {
id: {
type: String,
},
},
taxAmount: {
type: Number,
required: true,
},
shippingAmount: {
type: Number,
required: true,
},
totalAmount: {
type: Number,
required: true,
},
orderStatus: {
type: String,
required: true,
default: "processing",
},
deliveredAt: {
type: Date,
},
createdAt: {
type: Date,
default: Date.now,
},
});
module.exports = mongoose.model("Order", orderSchema);
// ref link
// https://stackoverflow.com/questions/28997636/should-i-use-schema-types-objectid-or-schema-objectid-when-defining-a-mongoose-s
================================================
FILE: tshirtstore/lcotshirtstore/models/product.js
================================================
const mongoose = require("mongoose");
const productSchema = new mongoose.Schema({
name: {
type: String,
required: [true, "please provide product name"],
trim: true,
maxlength: [120, "Product name should not be more than 120 characters"],
},
price: {
type: Number,
required: [true, "please provide product price"],
maxlength: [6, "Product price should not be more than 6 digits"],
},
description: {
type: String,
required: [true, "please provide product description"],
},
photos: [
{
id: {
type: String,
required: true,
},
secure_url: {
type: String,
required: true,
},
},
],
category: {
type: String,
required: [
true,
"please select category from- short-sleeves, long-sleeves, sweat-shirts, hoodies",
],
enum: {
values: ["shortsleeves", "longsleeves", "sweatshirt", "hoodies"],
message:
"please select category ONLY from - short-sleeves, long-sleeves, sweat-shirts and hoodies ",
},
},
//this field was updated in order videos later
stock: {
type: Number,
required: [true, "please add a number in stock"],
},
brand: {
type: String,
required: [true, "please add a brand for clothing"],
},
ratings: {
type: Number,
default: 0,
},
numberOfReviews: {
type: Number,
default: 0,
},
reviews: [
{
user: {
type: mongoose.Schema.ObjectId,
ref: "User",
required: true,
},
name: {
type: String,
required: true,
},
rating: {
type: Number,
required: true,
},
comment: {
type: String,
required: true,
},
},
],
user: {
type: mongoose.Schema.ObjectId,
ref: "User",
required: true,
},
createdAt: {
type: Date,
default: Date.now,
},
});
module.exports = mongoose.model("Product", productSchema);
================================================
FILE: tshirtstore/lcotshirtstore/models/user.js
================================================
const mongoose = require("mongoose");
const validator = require("validator");
const bcrypt = require("bcryptjs");
const jwt = require("jsonwebtoken");
const crypto = require("crypto");
const userSchema = new mongoose.Schema({
name: {
type: String,
required: [true, "Please provide a name"],
maxlength: [40, "Name should be under 40 characters"],
},
email: {
type: String,
required: [true, "Please provide an email"],
validate: [validator.isEmail, "Please enter email in correct format"],
unique: true,
},
password: {
type: String,
required: [true, "Please provide a password"],
minlength: [6, "password should be atleast 6 char"],
select: false,
},
role: {
type: String,
default: "user",
},
photo: {
id: {
type: String,
required: true,
},
secure_url: {
type: String,
required: true,
},
},
forgotPasswordToken: String,
forgotPasswordExpiry: Date,
createdAt: {
type: Date,
default: Date.now,
},
});
//encrypt password before save - HOOKS
userSchema.pre("save", async function (next) {
if (!this.isModified("password")) {
return next();
}
this.password = await bcrypt.hash(this.password, 10);
});
// validate the password with passed on user password
userSchema.methods.isValidatedPassword = async function (usersendPassword) {
return await bcrypt.compare(usersendPassword, this.password);
};
//create and return jwt token
userSchema.methods.getJwtToken = function () {
return jwt.sign({ id: this._id }, process.env.JWT_SECRET, {
expiresIn: process.env.JWT_EXPIRY,
});
};
//generate forgot password token (string)
userSchema.methods.getForgotPasswordToken = function () {
// generate a long and randomg string
const forgotToken = crypto.randomBytes(20).toString("hex");
// getting a hash - make sure to get a hash on backend
this.forgotPasswordToken = crypto
.createHash("sha256")
.update(forgotToken)
.digest("hex");
//time of token
this.forgotPasswordExpiry = Date.now() + 20 * 60 * 1000;
return forgotToken;
};
module.exports = mongoose.model("User", userSchema);
================================================
FILE: tshirtstore/lcotshirtstore/nodemon.json
================================================
{
"ext": ".js, .jsx, .yaml"
}
================================================
FILE: tshirtstore/lcotshirtstore/package.json
================================================
{
"name": "tshirtstore",
"version": "1.0.0",
"description": "a backend api for tshirt store",
"main": "index.js",
"scripts": {
"start": "node index.js",
"dev": "nodemon index.js"
},
"keywords": [
"api",
"backend"
],
"author": "hitesh choudhary",
"license": "ISC",
"dependencies": {
"bcryptjs": "^2.4.3",
"cloudinary": "^1.27.1",
"cookie-parser": "^1.4.5",
"dotenv": "^10.0.0",
"ejs": "^3.1.6",
"express": "^4.17.1",
"express-fileupload": "^1.2.1",
"jsonwebtoken": "^8.5.1",
"mongoose": "^6.0.12",
"morgan": "^1.10.0",
"nodemailer": "^6.7.0",
"razorpay": "^2.0.7",
"stripe": "^8.184.0",
"swagger-ui-express": "^4.1.6",
"validator": "^13.6.0",
"yamljs": "^0.3.0"
},
"devDependencies": {
"nodemon": "^2.0.14"
}
}
================================================
FILE: tshirtstore/lcotshirtstore/routes/home.js
================================================
const express = require("express");
const router = express.Router();
const { home, homeDummy } = require("../controllers/homeController");
router.route("/").get(home);
router.route("/dummy").get(homeDummy);
module.exports = router;
================================================
FILE: tshirtstore/lcotshirtstore/routes/order.js
================================================
const express = require("express");
const {
createOrder,
getOneOrder,
getLoggedInOrders,
admingetAllOrders,
adminUpdateOrder,
adminDeleteOrder,
} = require("../controllers/orderController");
const router = express.Router();
const { isLoggedIn, customRole } = require("../middlewares/user");
router.route("/order/create").post(isLoggedIn, createOrder);
router.route("/order/:id").get(isLoggedIn, getOneOrder);
router.route("/myorder").get(isLoggedIn, getLoggedInOrders);
//admin routes
router
.route("/admin/orders")
.get(isLoggedIn, customRole("admin"), admingetAllOrders);
router
.route("/admin/order/:id")
.put(isLoggedIn, customRole("admin"), adminUpdateOrder)
.delete(isLoggedIn, customRole("admin"), adminDeleteOrder);
module.exports = router;
================================================
FILE: tshirtstore/lcotshirtstore/routes/payment.js
================================================
const express = require("express");
const router = express.Router();
const {
sendRazorpayKey,
sendStripeKey,
captureStripePayment,
captureRazorpayPayment,
} = require("../controllers/paymentController");
const { isLoggedIn } = require("../middlewares/user");
router.route("/stripekey").get(isLoggedIn, sendStripeKey);
router.route("/razorpaykey").get(isLoggedIn, sendRazorpayKey);
router.route("/capturestripe").post(isLoggedIn, captureStripePayment);
router.route("/capturerazorpay").post(isLoggedIn, captureRazorpayPayment);
module.exports = router;
================================================
FILE: tshirtstore/lcotshirtstore/routes/product.js
================================================
const express = require("express");
const {
addProduct,
getAllProduct,
adminGetAllProduct,
getOneProduct,
adminUpdateOneProduct,
adminDeleteOneProduct,
addReview,
deleteReview,
getOnlyReviewsForOneProduct,
} = require("../controllers/productController");
const router = express.Router();
const { isLoggedIn, customRole } = require("../middlewares/user");
//user routes
router.route("/products").get(getAllProduct);
router.route("/product/:id").get(getOneProduct);
router.route("/review").put(isLoggedIn, addReview);
router.route("/review").delete(isLoggedIn, deleteReview);
router.route("/reviews").get(isLoggedIn, getOnlyReviewsForOneProduct);
//admin routes
router
.route("/admin/product/add")
.post(isLoggedIn, customRole("admin"), addProduct);
router
.route("/admin/products")
.get(isLoggedIn, customRole("admin"), adminGetAllProduct);
router
.route("/admin/product/:id")
.put(isLoggedIn, customRole("admin"), adminUpdateOneProduct)
.delete(isLoggedIn, customRole("admin"), adminDeleteOneProduct);
module.exports = router;
================================================
FILE: tshirtstore/lcotshirtstore/routes/user.js
================================================
const express = require("express");
const router = express.Router();
const {
signup,
login,
logout,
forgotPassword,
passwordReset,
getLoggedInUserDetails,
changePassword,
updateUserDetails,
adminAllUser,
managerAllUser,
admingetOneUser,
adminUpdateOneUserDetails,
adminDeleteOneUser,
} = require("../controllers/userController");
const { isLoggedIn, customRole } = require("../middlewares/user");
router.route("/signup").post(signup);
router.route("/login").post(login);
router.route("/logout").get(logout);
router.route("/forgotPassword").post(forgotPassword);
router.route("/password/reset/:token").post(passwordReset);
router.route("/userdashboard").get(isLoggedIn, getLoggedInUserDetails);
router.route("/password/update").post(isLoggedIn, changePassword);
router.route("/userdashboard/update").post(isLoggedIn, updateUserDetails);
//admin only routes
router.route("/admin/users").get(isLoggedIn, customRole("admin"), adminAllUser);
router
.route("/admin/user/:id")
.get(isLoggedIn, customRole("admin"), admingetOneUser)
.put(isLoggedIn, customRole("admin"), adminUpdateOneUserDetails)
.delete(isLoggedIn, customRole("admin"), adminDeleteOneUser);
// manager only route
router
.route("/manager/users")
.get(isLoggedIn, customRole("manager"), managerAllUser);
module.exports = router;
================================================
FILE: tshirtstore/lcotshirtstore/swagger.yaml
================================================
openapi: "3.0.0"
info:
title: Tshirt store API
description: LCO - a course to create API for ecomm store
version: 1.1.0
contact:
email: hitesh@lco.dev
url: "https://lco.dev"
# servers:
# - url: "https://localhost:4000/api/v1"
# description: for local host - secure
# - url: "http://localhost:4000/api/v1"
# description: for local host - regular
servers:
- url: "{protocol}://localhost:4000/api/{version}"
description: for local host
variables:
version:
enum:
- v1
- v2
default: v1
protocol:
enum:
- http
- https
default: http
components:
securitySchemes:
cookieAuth:
type: apiKey
in: cookie
name: token
BearerAuth:
type: http
scheme: bearer
paths:
/dummy:
get:
tags:
- Home
summary: returns a greet message from LCO
responses:
200:
description: All good success
content:
application/json:
schema:
type: string
example: "mystring"
400:
description: Bad request
500:
description: internal server error
/signup:
post:
tags:
- User
summary: signup a new user. required files are - name, email, password and photo
requestBody:
content:
multipart/form-data:
schema:
type: object
required:
- name
- email
- password
- photo
properties:
name:
type: string
required: true
email:
type: string
password:
type: string
photo:
in: formData
description: The uploaded file data
type: file
format: binary
responses:
200:
description: All good success
content:
application/json:
schema:
type: object
properties:
success:
type: boolean
token:
type: string
user:
type: object
properties:
name:
type: string
email:
type: string
role:
type: string
photo:
type: object
properties:
id:
type: string
secure_url:
type: string
_id:
type: string
createdAt:
type: string
400:
description: Bad request
500:
description: internal server error
/login:
post:
tags:
- User
summary: login a new user. required files are - email and password. Also add httpOnly cookie
requestBody:
content:
application/json:
schema:
type: object
required:
- email
- password
properties:
email:
type: string
password:
type: string
responses:
200:
description: All good success
content:
application/json:
schema:
type: object
properties:
success:
type: boolean
token:
type: string
user:
type: object
properties:
name:
type: string
email:
type: string
role:
type: string
photo:
type: object
properties:
id:
type: string
secure_url:
type: string
_id:
type: string
createdAt:
type: string
400:
description: Bad request
500:
description: internal server error
/logout:
get:
tags:
- User
summary: get request to logout the user. Also removes httpOnly cookies
responses:
200:
description: All good success
content:
application/json:
schema:
type: object
properties:
success:
type: boolean
message:
type: string
400:
description: Bad request
500:
description: internal server error
/forgotPassword:
post:
tags:
- User
summary: sends an email with link to forgot password. Contains the token for user validation
requestBody:
content:
application/json:
schema:
type: object
required:
- email
properties:
email:
type: string
responses:
200:
description: All good success
content:
application/json:
schema:
type: object
properties:
success:
type: boolean
message:
type: string
400:
description: Bad request
500:
description: internal server error
/password/reset/{token}:
post:
tags:
- User
summary: Allows user to reset password, validated based on token. send password and confirm password fields
parameters:
- name: token
in: path
required: true
schema:
type: string
requestBody:
content:
application/json:
schema:
type: object
required:
- password
- confirmPassword
properties:
password:
type: string
confirmPassword:
type: string
responses:
200:
description: All good success
content:
application/json:
schema:
type: object
properties:
success:
type: boolean
token:
type: string
user:
type: object
properties:
name:
type: string
email:
type: string
role:
type: string
photo:
type: object
properties:
id:
type: string
secure_url:
type: string
_id:
type: string
createdAt:
type: string
400:
description: Bad request
500:
description: internal server error
/userdashboard:
get:
tags:
- User
summary: Gets all details about logged in user. Send token in cookies as named token or send Bearer Auth
requestBody:
responses:
200:
description: All good success
content:
application/json:
schema:
type: object
properties:
success:
type: boolean
user:
type: object
properties:
name:
type: string
email:
type: string
role:
type: string
photo:
type: object
properties:
id:
type: string
secure_url:
type: string
_id:
type: string
createdAt:
type: string
400:
description: Bad request
500:
description: internal server error
/password/update:
post:
tags:
- User
summary: User can update the password if he is logged in. Send oldPassword and password
requestBody:
content:
application/json:
schema:
type: object
required:
- oldPassword
- password
properties:
oldPassword:
type: string
password:
type: string
responses:
200:
description: All good success
content:
application/json:
schema:
type: object
properties:
token:
type: string
user:
type: object
properties:
name:
type: string
email:
type: string
role:
type: string
photo:
type: object
properties:
id:
type: string
secure_url:
type: string
_id:
type: string
createdAt:
type: string
400:
description: Bad request
500:
description: internal server error
/userdashboard/update:
post:
tags:
- User
summary: User can update the name, emails and photo. Photo is optional
requestBody:
content:
multipart/form-data:
schema:
type: object
required:
- name
- email
properties:
name:
type: string
email:
type: string
photo:
in: formData
description: The uploaded file data
type: file
format: binary
responses:
200:
description: All good success
content:
application/json:
schema:
type: object
properties:
success:
type: boolean
400:
description: Bad request
500:
description: internal server error
/admin/users:
get:
tags:
- Admin
summary: If user is admin, response will get array of all users
requestBody:
responses:
200:
description: All good success
content:
application/json:
schema:
type: array
items:
type: object
properties:
user:
type: object
properties:
name:
type: string
email:
type: string
role:
type: string
photo:
type: object
properties:
id:
type: string
secure_url:
type: string
_id:
type: string
createdAt:
type: string
400:
description: Bad request
500:
description: internal server error
/admin/user/{id}:
get:
tags:
- Admin
summary: If user is admin, response will get details of 1 user
parameters:
- name: id
in: path
required: true
schema:
type: string
requestBody:
responses:
200:
description: All good success
content:
application/json:
schema:
type: object
properties:
user:
type: object
properties:
name:
type: string
email:
type: string
role:
type: string
photo:
type: object
properties:
id:
type: string
secure_url:
type: string
_id:
type: string
createdAt:
type: string
400:
description: Bad request
500:
description: internal server error
put:
tags:
- Admin
summary: If user is admin, response will get details of 1 user
parameters:
- name: id
in: path
required: true
schema:
type: string
- name: name
in: formData
required: true
schema:
type: string
- name: email
in: formData
required: true
schema:
type: string
- name: role
in: formData
required: true
schema:
type: string
enum: [user, admin, manager]
requestBody:
responses:
200:
description: All good success
content:
application/json:
schema:
type: object
properties:
success:
type: boolean
400:
description: Bad request
500:
description: internal server error
delete:
tags:
- Admin
summary: If user is admin, delete the user with given id
parameters:
- name: id
in: path
required: true
schema:
type: string
requestBody:
responses:
200:
description: All good success
content:
application/json:
schema:
type: object
properties:
success:
type: boolean
400:
description: Bad request
500:
description: internal server error
/manager/users:
get:
tags:
- Manager
summary: If manager, response will get array of all users whose role is user
requestBody:
responses:
200:
description: All good success
content:
application/json:
schema:
type: array
items:
type: object
properties:
user:
type: object
properties:
name:
type: string
email:
type: string
role:
type: string
photo:
type: object
properties:
id:
type: string
secure_url:
type: string
_id:
type: string
createdAt:
type: string
400:
description: Bad request
500:
description: internal server error
================================================
FILE: tshirtstore/lcotshirtstore/utils/cookieToken.js
================================================
const cookieToken = (user, res) => {
const token = user.getJwtToken();
const options = {
expires: new Date(
Date.now() + process.env.COOKIE_TIME * 24 * 60 * 60 * 1000
),
httpOnly: true,
};
user.password = undefined;
res.status(200).cookie("token", token, options).json({
success: true,
token,
user,
});
};
module.exports = cookieToken;
================================================
FILE: tshirtstore/lcotshirtstore/utils/customError.js
================================================
class CustomError extends Error {
constructor(message, code) {
super(message);
this.code = code;
}
}
module.exports = CustomError;
================================================
FILE: tshirtstore/lcotshirtstore/utils/demotest.js
================================================
// User.find({ qty: { $lte: 20 } })
// /api/v1/product?search=coder&page=2&category=shortsleeves&rating[gte]=4
// &price[lte]=999&price[gte]=199
// rating: { $gte: '4' }
// URL - https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/replace
// const p = 'gte gte lte mygte';
// const regex = /\b(gte|lte)\b/g;
// console.log(p.replace(regex, m => `$${m}`));
================================================
FILE: tshirtstore/lcotshirtstore/utils/emailHelper.js
================================================
const nodemailer = require("nodemailer");
const mailHelper = async (option) => {
const transporter = nodemailer.createTransport({
host: process.env.SMTP_HOST,
port: process.env.SMTP_PORT,
auth: {
user: process.env.SMTP_USER, // generated ethereal user
pass: process.env.SMTP_PASS, // generated ethereal password
},
});
const message = {
from: "hitesh@lco.dev", // sender address
to: option.email, // list of receivers
subject: option.subject, // Subject line
text: option.message, // plain text body
};
// send mail with defined transport object
await transporter.sendMail(message);
};
module.exports = mailHelper;
================================================
FILE: tshirtstore/lcotshirtstore/utils/testOrder.json
================================================
{
"shippingInfo": {
"address": "1 Jaipur",
"city": "Jaipur",
"phoneNo": "9898989898",
"postalCode": "302020",
"state": "Rajasthan",
"country": "India"
},
"orderItems": [
{
"name": "Pro Coder tshirts",
"quantity": 1,
"image": "https://res.cloudinary.com/dk92l1yoc/image/upload/v1635757229/products/b4laryk4dbp6vdrvq3wv.png",
"price": 999,
"product": "617facb5333fd4c0fdfdee65"
}
],
"paymentInfo": {
"id": "testString"
},
"taxAmount": 40,
"shippingAmount": 10,
"totalAmount": 100
}
================================================
FILE: tshirtstore/lcotshirtstore/utils/whereClause.js
================================================
// base - Product.find()
// base - Product.find(email: {"hitesh@lco.dev"})
//bigQ - //search=coder&page=2&category=shortsleeves&rating[gte]=4
// &price[lte]=999&price[gte]=199&limit=5
class WhereClause {
constructor(base, bigQ) {
this.base = base;
this.bigQ = bigQ;
}
search() {
const searchword = this.bigQ.search
? {
name: {
$regex: this.bigQ.search,
$options: "i",
},
}
: {};
this.base = this.base.find({ ...searchword });
return this;
}
filter() {
const copyQ = { ...this.bigQ };
delete copyQ["search"];
delete copyQ["limit"];
delete copyQ["page"];
//convert bigQ into a string => copyQ
let stringOfCopyQ = JSON.stringify(copyQ);
stringOfCopyQ = stringOfCopyQ.replace(
/\b(gte|lte|gt|lt)\b/g,
(m) => `$${m}`
);
const jsonOfCopyQ = JSON.parse(stringOfCopyQ);
this.base = this.base.find(jsonOfCopyQ);
return this;
}
pager(resultperPage) {
let currentPage = 1;
if (this.bigQ.page) {
currentPage = this.bigQ.page;
}
const skipVal = resultperPage * (currentPage - 1);
this.base = this.base.limit(resultperPage).skip(skipVal);
return this;
}
}
module.exports = WhereClause;
================================================
FILE: tshirtstore/lcotshirtstore/views/signuptest.ejs
================================================
GET form
POST form
================================================
FILE: tshirtstore/middlewares/bigPromise.js
================================================
// try catch and async - await || use promise
module.exports = (func) => (req, res, next) =>
Promise.resolve(func(req, res, next)).catch(next);
================================================
FILE: tshirtstore/middlewares/productionError.js
================================================
const CustomError = require("../utils/customError");
module.exports = (err, req, res, next) => {
let error = { ...err };
error.message = err.message;
res.status(error.statusCode || 500).json({
success: false,
message: error.message || "Internal Server Error",
});
};
================================================
FILE: tshirtstore/middlewares/user.js
================================================
const User = require("../models/user");
const BigPromise = require("../middlewares/bigPromise");
const CustomError = require("../utils/customError");
const jwt = require("jsonwebtoken");
exports.isLoggedIn = BigPromise(async (req, res, next) => {
// const token = req.cookies.token || req.header("Authorization").replace("Bearer ", "");
// check token first in cookies
let token = req.cookies.token;
// if token not found in cookies, check if header contains Auth field
if (!token && req.header("Authorization")) {
token = req.header("Authorization").replace("Bearer ", "");
}
if (!token) {
return next(new CustomError("Login first to access this page", 401));
}
const decoded = jwt.verify(token, process.env.JWT_SECRET);
req.user = await User.findById(decoded.id);
next();
});
exports.customRole = (...roles) => {
return (req, res, next) => {
if (!roles.includes(req.user.role)) {
return next(new CustomError("You are not allowed for this resouce", 403));
}
console.log(req.user.role);
next();
};
};
================================================
FILE: tshirtstore/models/order.js
================================================
const mongoose = require("mongoose");
// shippingInfo{}
// user,
// paymentInfo{}
// taxAmount
// ShippingAmount
// totalAmount
// orderStatus
// deliveredAt
// createdAt
// -------------
// orderItems: [ {} ]
// - name
// - quantity
// - image[0]
// - price
// - product
const orderSchema = new mongoose.Schema({
shippingInfo: {
address: {
type: String,
required: true,
},
city: {
type: String,
required: true,
},
phoneNo: {
type: String,
required: true,
},
postalCode: {
type: String,
required: true,
},
state: {
type: String,
// required: true,
},
country: {
type: String,
required: true,
},
},
user: {
type: mongoose.Schema.ObjectId, //mongoose.Schema.Types.ObjectId
ref: "User",
required: true,
},
orderItems: [
{
name: {
type: String,
required: true,
},
quantity: {
type: Number,
required: true,
},
image: {
type: String,
required: true,
},
price: {
type: Number,
required: true,
},
product: {
type: mongoose.Schema.ObjectId, //mongoose.Schema.Types.ObjectId
ref: "Product",
required: true,
},
},
],
paymentInfo: {
id: {
type: String,
},
},
taxAmount: {
type: Number,
required: true,
},
shippingAmount: {
type: Number,
required: true,
},
totalAmount: {
type: Number,
required: true,
},
orderStatus: {
type: String,
required: true,
default: "processing",
},
deliveredAt: {
type: Date,
},
createdAt: {
type: Date,
default: Date.now,
},
});
module.exports = mongoose.model("Order", orderSchema);
// ref link
// https://stackoverflow.com/questions/28997636/should-i-use-schema-types-objectid-or-schema-objectid-when-defining-a-mongoose-s
================================================
FILE: tshirtstore/models/product.js
================================================
const mongoose = require("mongoose");
const productSchema = new mongoose.Schema({
name: {
type: String,
required: [true, "please provide product name"],
trim: true,
maxlength: [120, "Product name should not be more than 120 characters"],
},
price: {
type: Number,
required: [true, "please provide product price"],
maxlength: [6, "Product price should not be more than 6 digits"],
},
description: {
type: String,
required: [true, "please provide product description"],
},
photos: [
{
id: {
type: String,
required: true,
},
secure_url: {
type: String,
required: true,
},
},
],
category: {
type: String,
required: [
true,
"please select category from- short-sleeves, long-sleeves, sweat-shirts, hoodies",
],
enum: {
values: ["shortsleeves", "longsleeves", "sweatshirt", "hoodies"],
message:
"please select category ONLY from - short-sleeves, long-sleeves, sweat-shirts and hoodies ",
},
},
//this field was updated in order videos later
stock: {
type: Number,
required: [true, "please add a number in stock"],
},
brand: {
type: String,
required: [true, "please add a brand for clothing"],
},
ratings: {
type: Number,
default: 0,
},
numberOfReviews: {
type: Number,
default: 0,
},
reviews: [
{
user: {
type: mongoose.Schema.ObjectId,
ref: "User",
required: true,
},
name: {
type: String,
required: true,
},
rating: {
type: Number,
required: true,
},
comment: {
type: String,
required: true,
},
},
],
user: {
type: mongoose.Schema.ObjectId,
ref: "User",
required: true,
},
createdAt: {
type: Date,
default: Date.now,
},
});
module.exports = mongoose.model("Product", productSchema);
================================================
FILE: tshirtstore/models/user.js
================================================
const mongoose = require("mongoose");
const validator = require("validator");
const bcrypt = require("bcryptjs");
const jwt = require("jsonwebtoken");
const crypto = require("crypto");
const userSchema = new mongoose.Schema({
name: {
type: String,
required: [true, "Please provide a name"],
maxlength: [40, "Name should be under 40 characters"],
},
email: {
type: String,
required: [true, "Please provide an email"],
validate: [validator.isEmail, "Please enter email in correct format"],
unique: true,
},
password: {
type: String,
required: [true, "Please provide a password"],
minlength: [6, "password should be atleast 6 char"],
select: false,
},
role: {
type: String,
default: "user",
},
photo: {
id: {
type: String,
required: true,
},
secure_url: {
type: String,
required: true,
},
},
forgotPasswordToken: String,
forgotPasswordExpiry: Date,
createdAt: {
type: Date,
default: Date.now,
},
});
//encrypt password before save - HOOKS
userSchema.pre("save", async function (next) {
if (!this.isModified("password")) {
return next();
}
this.password = await bcrypt.hash(this.password, 10);
});
// validate the password with passed on user password
userSchema.methods.isValidatedPassword = async function (usersendPassword) {
return await bcrypt.compare(usersendPassword, this.password);
};
//create and return jwt token
userSchema.methods.getJwtToken = function () {
return jwt.sign({ id: this._id }, process.env.JWT_SECRET, {
expiresIn: process.env.JWT_EXPIRY,
});
};
//generate forgot password token (string)
userSchema.methods.getForgotPasswordToken = function () {
// generate a long and randomg string
const forgotToken = crypto.randomBytes(20).toString("hex");
// getting a hash - make sure to get a hash on backend
this.forgotPasswordToken = crypto
.createHash("sha256")
.update(forgotToken)
.digest("hex");
//time of token
this.forgotPasswordExpiry = Date.now() + 20 * 60 * 1000;
return forgotToken;
};
module.exports = mongoose.model("User", userSchema);
================================================
FILE: tshirtstore/nodemon.json
================================================
{
"ext": ".js, .jsx, .yaml"
}
================================================
FILE: tshirtstore/package.json
================================================
{
"name": "tshirtstore",
"version": "1.0.0",
"description": "a backend api for tshirt store",
"main": "index.js",
"scripts": {
"start": "node index.js",
"dev": "nodemon index.js"
},
"keywords": [
"api",
"backend"
],
"author": "hitesh choudhary",
"license": "ISC",
"dependencies": {
"bcryptjs": "^2.4.3",
"cloudinary": "^1.27.1",
"cookie-parser": "^1.4.5",
"dotenv": "^10.0.0",
"ejs": "^3.1.6",
"express": "^4.17.1",
"express-fileupload": "^1.2.1",
"jsonwebtoken": "^8.5.1",
"mongoose": "^6.0.12",
"morgan": "^1.10.0",
"nodemailer": "^6.7.0",
"razorpay": "^2.0.7",
"stripe": "^8.184.0",
"swagger-ui-express": "^4.1.6",
"validator": "^13.6.0",
"yamljs": "^0.3.0"
},
"devDependencies": {
"nodemon": "^2.0.14"
}
}
================================================
FILE: tshirtstore/routes/home.js
================================================
const express = require("express");
const router = express.Router();
const { home, homeDummy } = require("../controllers/homeController");
router.route("/").get(home);
router.route("/dummy").get(homeDummy);
module.exports = router;
================================================
FILE: tshirtstore/routes/order.js
================================================
const express = require("express");
const {
createOrder,
getOneOrder,
getLoggedInOrders,
admingetAllOrders,
adminUpdateOrder,
adminDeleteOrder,
} = require("../controllers/orderController");
const router = express.Router();
const { isLoggedIn, customRole } = require("../middlewares/user");
router.route("/order/create").post(isLoggedIn, createOrder);
router.route("/order/:id").get(isLoggedIn, getOneOrder);
router.route("/myorder").get(isLoggedIn, getLoggedInOrders);
//admin routes
router
.route("/admin/orders")
.get(isLoggedIn, customRole("admin"), admingetAllOrders);
router
.route("/admin/order/:id")
.put(isLoggedIn, customRole("admin"), adminUpdateOrder)
.delete(isLoggedIn, customRole("admin"), adminDeleteOrder);
module.exports = router;
================================================
FILE: tshirtstore/routes/payment.js
================================================
const express = require("express");
const router = express.Router();
const {
sendRazorpayKey,
sendStripeKey,
captureStripePayment,
captureRazorpayPayment,
} = require("../controllers/paymentController");
const { isLoggedIn } = require("../middlewares/user");
router.route("/stripekey").get(isLoggedIn, sendStripeKey);
router.route("/razorpaykey").get(isLoggedIn, sendRazorpayKey);
router.route("/capturestripe").post(isLoggedIn, captureStripePayment);
router.route("/capturerazorpay").post(isLoggedIn, captureRazorpayPayment);
module.exports = router;
================================================
FILE: tshirtstore/routes/product.js
================================================
const express = require("express");
const {
addProduct,
getAllProduct,
adminGetAllProduct,
getOneProduct,
adminUpdateOneProduct,
adminDeleteOneProduct,
addReview,
deleteReview,
getOnlyReviewsForOneProduct,
} = require("../controllers/productController");
const router = express.Router();
const { isLoggedIn, customRole } = require("../middlewares/user");
//user routes
router.route("/products").get(getAllProduct);
router.route("/product/:id").get(getOneProduct);
router.route("/review").put(isLoggedIn, addReview);
router.route("/review").delete(isLoggedIn, deleteReview);
router.route("/reviews").get(isLoggedIn, getOnlyReviewsForOneProduct);
//admin routes
router
.route("/admin/product/add")
.post(isLoggedIn, customRole("admin"), addProduct);
router
.route("/admin/products")
.get(isLoggedIn, customRole("admin"), adminGetAllProduct);
router
.route("/admin/product/:id")
.put(isLoggedIn, customRole("admin"), adminUpdateOneProduct)
.delete(isLoggedIn, customRole("admin"), adminDeleteOneProduct);
module.exports = router;
================================================
FILE: tshirtstore/routes/user.js
================================================
const express = require("express");
const router = express.Router();
const {
signup,
login,
logout,
forgotPassword,
passwordReset,
getLoggedInUserDetails,
changePassword,
updateUserDetails,
adminAllUser,
managerAllUser,
admingetOneUser,
adminUpdateOneUserDetails,
adminDeleteOneUser,
} = require("../controllers/userController");
const { isLoggedIn, customRole } = require("../middlewares/user");
router.route("/signup").post(signup);
router.route("/login").post(login);
router.route("/logout").get(logout);
router.route("/forgotPassword").post(forgotPassword);
router.route("/password/reset/:token").put(passwordReset);
router.route("/userdashboard").get(isLoggedIn, getLoggedInUserDetails);
router.route("/password/update").put(isLoggedIn, changePassword);
router.route("/userdashboard/update").put(isLoggedIn, updateUserDetails);
//admin only routes
router.route("/admin/users").get(isLoggedIn, customRole("admin"), adminAllUser);
router
.route("/admin/user/:id")
.get(isLoggedIn, customRole("admin"), admingetOneUser)
.put(isLoggedIn, customRole("admin"), adminUpdateOneUserDetails)
.delete(isLoggedIn, customRole("admin"), adminDeleteOneUser);
// manager only route
router
.route("/manager/users")
.get(isLoggedIn, customRole("manager"), managerAllUser);
module.exports = router;
================================================
FILE: tshirtstore/swagger.yaml
================================================
openapi: "3.0.0"
info:
title: Tshirt store API
description: LCO - a course to create API for ecomm store
version: 1.1.0
contact:
email: hitesh@lco.dev
url: "https://lco.dev"
# servers:
# - url: "https://localhost:4000/api/v1"
# description: for local host - secure
# - url: "http://localhost:4000/api/v1"
# description: for local host - regular
servers:
- url: "{protocol}://{url}/api/{version}"
description: for local host
variables:
version:
enum:
- v1
- v2
default: v1
url:
enum:
- localhost:4000
- lcotshirtstore.herokuapp.com
default: lcotshirtstore.herokuapp.com
protocol:
enum:
- http
- https
default: https
components:
securitySchemes:
cookieAuth:
type: apiKey
in: cookie
name: token
BearerAuth:
type: http
scheme: bearer
paths:
/dummy:
get:
tags:
- Home
summary: returns a greet message from LCO
responses:
200:
description: All good success
content:
application/json:
schema:
type: string
example: "mystring"
400:
description: Bad request
500:
description: internal server error
/signup:
post:
tags:
- User
summary: signup a new user. required files are - name, email, password and photo
requestBody:
content:
multipart/form-data:
schema:
type: object
required:
- name
- email
- password
- photo
properties:
name:
type: string
required: true
email:
type: string
password:
type: string
photo:
in: formData
description: The uploaded file data
type: file
format: binary
responses:
200:
description: All good success
content:
application/json:
schema:
type: object
properties:
success:
type: boolean
token:
type: string
user:
type: object
properties:
name:
type: string
email:
type: string
role:
type: string
photo:
type: object
properties:
id:
type: string
secure_url:
type: string
_id:
type: string
createdAt:
type: string
400:
description: Bad request
500:
description: internal server error
/login:
post:
tags:
- User
summary: login a new user. required files are - email and password. Also adds httpOnly cookie in browser. It sends token in response too.
requestBody:
content:
application/json:
schema:
type: object
required:
- email
- password
properties:
email:
type: string
password:
type: string
responses:
200:
description: All good success
content:
application/json:
schema:
type: object
properties:
success:
type: boolean
token:
type: string
user:
type: object
properties:
name:
type: string
email:
type: string
role:
type: string
photo:
type: object
properties:
id:
type: string
secure_url:
type: string
_id:
type: string
createdAt:
type: string
400:
description: Bad request
500:
description: internal server error
/logout:
get:
tags:
- User
summary: get request to logout the user. Also removes httpOnly cookies
responses:
200:
description: All good success
content:
application/json:
schema:
type: object
properties:
success:
type: boolean
message:
type: string
400:
description: Bad request
500:
description: internal server error
/forgotPassword:
post:
tags:
- User
summary: sends an email with link to forgot password. Contains the token for user validation. Public hosted version will not send email to your account. Use your own SMTP details to access this feature on local project
requestBody:
content:
application/json:
schema:
type: object
required:
- email
properties:
email:
type: string
responses:
200:
description: All good success
content:
application/json:
schema:
type: object
properties:
success:
type: boolean
message:
type: string
400:
description: Bad request
500:
description: internal server error
/password/reset/{token}:
post:
tags:
- User
summary: Allows user to reset password, validated based on token. send password and confirm password fields
parameters:
- name: token
in: path
required: true
schema:
type: string
requestBody:
content:
application/json:
schema:
type: object
required:
- password
- confirmPassword
properties:
password:
type: string
confirmPassword:
type: string
responses:
200:
description: All good success
content:
application/json:
schema:
type: object
properties:
success:
type: boolean
token:
type: string
user:
type: object
properties:
name:
type: string
email:
type: string
role:
type: string
photo:
type: object
properties:
id:
type: string
secure_url:
type: string
_id:
type: string
createdAt:
type: string
400:
description: Bad request
500:
description: internal server error
/userdashboard:
get:
tags:
- User
summary: Gets all details about logged in user. Send token in cookies as named token or send Bearer Auth
requestBody:
responses:
200:
description: All good success
content:
application/json:
schema:
type: object
properties:
success:
type: boolean
user:
type: object
properties:
name:
type: string
email:
type: string
role:
type: string
photo:
type: object
properties:
id:
type: string
secure_url:
type: string
_id:
type: string
createdAt:
type: string
400:
description: Bad request
500:
description: internal server error
/password/update:
post:
tags:
- User
summary: User can update the password if he is logged in. Send oldPassword and password
requestBody:
content:
application/json:
schema:
type: object
required:
- oldPassword
- password
properties:
oldPassword:
type: string
password:
type: string
responses:
200:
description: All good success
content:
application/json:
schema:
type: object
properties:
token:
type: string
user:
type: object
properties:
name:
type: string
email:
type: string
role:
type: string
photo:
type: object
properties:
id:
type: string
secure_url:
type: string
_id:
type: string
createdAt:
type: string
400:
description: Bad request
500:
description: internal server error
/userdashboard/update:
post:
tags:
- User
summary: User can update the name, emails and photo. Photo is optional
requestBody:
content:
multipart/form-data:
schema:
type: object
required:
- name
- email
properties:
name:
type: string
email:
type: string
photo:
in: formData
description: The uploaded file data
type: file
format: binary
responses:
200:
description: All good success
content:
application/json:
schema:
type: object
properties:
success:
type: boolean
400:
description: Bad request
500:
description: internal server error
/admin/users:
get:
tags:
- Admin
summary: If user is admin, response will get array of all users
requestBody:
responses:
200:
description: All good success
content:
application/json:
schema:
type: array
items:
type: object
properties:
user:
type: object
properties:
name:
type: string
email:
type: string
role:
type: string
photo:
type: object
properties:
id:
type: string
secure_url:
type: string
_id:
type: string
createdAt:
type: string
400:
description: Bad request
500:
description: internal server error
/admin/user/{id}:
get:
tags:
- Admin
summary: If user is admin, response will get details of 1 user
parameters:
- name: id
in: path
required: true
schema:
type: string
requestBody:
responses:
200:
description: All good success
content:
application/json:
schema:
type: object
properties:
user:
type: object
properties:
name:
type: string
email:
type: string
role:
type: string
photo:
type: object
properties:
id:
type: string
secure_url:
type: string
_id:
type: string
createdAt:
type: string
400:
description: Bad request
500:
description: internal server error
put:
tags:
- Admin
summary: If user is admin, he can update mentioned fields in user
parameters:
- name: id
in: path
required: true
schema:
type: string
- name: name
in: formData
required: true
schema:
type: string
- name: email
in: formData
required: true
schema:
type: string
- name: role
in: formData
required: true
schema:
type: string
enum: [user, admin, manager]
requestBody:
responses:
200:
description: All good success
content:
application/json:
schema:
type: object
properties:
success:
type: boolean
400:
description: Bad request
500:
description: internal server error
delete:
tags:
- Admin
summary: If user is admin, delete the user with given id
parameters:
- name: id
in: path
required: true
schema:
type: string
requestBody:
responses:
200:
description: All good success
content:
application/json:
schema:
type: object
properties:
success:
type: boolean
400:
description: Bad request
500:
description: internal server error
/manager/users:
get:
tags:
- Manager
summary: If manager, response will get array of all users whose role is user
requestBody:
responses:
200:
description: All good success
content:
application/json:
schema:
type: array
items:
type: object
properties:
user:
type: object
properties:
name:
type: string
email:
type: string
role:
type: string
photo:
type: object
properties:
id:
type: string
secure_url:
type: string
_id:
type: string
createdAt:
type: string
400:
description: Bad request
500:
description: internal server error
/products:
get:
tags:
- Product
summary: A simple get request will give you all available products
requestBody:
responses:
200:
description: All good success
content:
application/json:
schema:
type: object
properties:
success:
type: boolean
filteredProductNumber:
type: number
totalcountProduct:
type: number
products:
type: array
items:
type: object
properties:
_id:
type: string
name:
type: string
price:
type: number
description:
type: string
category:
type: string
brand:
type: string
ratings:
type: number
numberOfReviews:
type: number
user:
type: string
photos:
type: array
items:
type: object
properties:
id:
type: string
secure_url:
type: string
reviews:
type: array
items:
type: object
properties:
id:
type: string
comment:
type: string
user:
type: string
name:
type: string
rating:
type: number
400:
description: Bad request
500:
description: internal server error
/product/{id}:
get:
tags:
- Product
summary: Get a single product
parameters:
- name: id
in: path
required: true
schema:
type: string
requestBody:
responses:
200:
description: All good success
content:
application/json:
schema:
type: object
properties:
success:
type: boolean
product:
type: object
properties:
_id:
type: string
name:
type: string
price:
type: number
description:
type: string
category:
type: string
brand:
type: string
ratings:
type: number
numberOfReviews:
type: number
user:
type: string
photos:
type: array
items:
type: object
properties:
id:
type: string
secure_url:
type: string
reviews:
type: array
items:
type: object
properties:
id:
type: string
comment:
type: string
user:
type: string
name:
type: string
rating:
type: number
400:
description: Bad request
500:
description: internal server error
/admin/products:
get:
tags:
- Admin
summary: If user is admin, he get all products
requestBody:
responses:
200:
description: All good success
content:
application/json:
schema:
type: object
properties:
success:
type: boolean
products:
type: array
items:
type: object
properties:
_id:
type: string
name:
type: string
price:
type: number
description:
type: string
category:
type: string
brand:
type: string
ratings:
type: number
numberOfReviews:
type: number
user:
type: string
photos:
type: array
items:
type: object
properties:
id:
type: string
secure_url:
type: string
reviews:
type: array
items:
type: object
properties:
id:
type: string
comment:
type: string
user:
type: string
name:
type: string
rating:
type: number
400:
description: Bad request
500:
description: internal server error
/admin/product/add:
post:
tags:
- Admin
summary: If admin, Add a product
requestBody:
content:
multipart/form-data:
schema:
type: object
required:
- name
- price
- description
- category
- stock
- brand
- photos
properties:
name:
type: string
required: true
price:
type: number
required: true
description:
type: string
required: true
category:
type: string
required: true
enum: [shortsleeves, longsleeves, sweatshirt, hoodies]
stock:
type: number
required: true
brand:
type: string
required: true
photos:
in: formData
description: The uploaded photos
type: array
items:
type: file
format: binary
responses:
200:
description: All good success
content:
application/json:
schema:
type: object
properties:
success:
type: boolean
product:
type: object
properties:
_id:
type: string
name:
type: string
price:
type: number
description:
type: string
category:
type: string
brand:
type: string
ratings:
type: number
numberOfReviews:
type: number
user:
type: string
photos:
type: array
items:
type: object
properties:
id:
type: string
secure_url:
type: string
reviews:
type: array
items:
type: object
properties:
id:
type: string
comment:
type: string
user:
type: string
name:
type: string
rating:
type: number
400:
description: Bad request
500:
description: internal server error
/admin/product/{id}:
put:
tags:
- Admin
summary: If admin, update the product. photos are optional to pass. Rest all body is set to be updated to what you pass here.
parameters:
- name: id
in: path
required: true
schema:
type: string
requestBody:
content:
multipart/form-data:
schema:
type: object
required:
- name
- price
- description
- category
- stock
- brand
properties:
name:
type: string
required: true
price:
type: number
required: true
description:
type: number
required: true
category:
type: string
required: true
enum: [shortsleeves, longsleeves, sweatshirt, hoodies]
stock:
type: number
required: true
brand:
type: string
required: true
photos:
in: formData
description: The uploaded photos
type: file
format: binary
responses:
200:
description: All good success
content:
application/json:
schema:
type: object
properties:
success:
type: boolean
product:
type: object
properties:
_id:
type: string
name:
type: string
price:
type: number
description:
type: string
category:
type: string
brand:
type: string
ratings:
type: number
numberOfReviews:
type: number
user:
type: string
photos:
type: array
items:
type: object
properties:
id:
type: string
secure_url:
type: string
reviews:
type: array
items:
type: object
properties:
id:
type: string
comment:
type: string
user:
type: string
name:
type: string
rating:
type: number
400:
description: Bad request
500:
description: internal server error
delete:
tags:
- Admin
summary: If admin, pass the id of product and product will be removed
parameters:
- name: id
in: path
required: true
schema:
type: string
requestBody:
responses:
200:
description: All good success
content:
application/json:
schema:
type: object
properties:
success:
type: boolean
message:
type: string
400:
description: Bad request
500:
description: internal server error
/reviews:
get:
tags:
- Product
summary: A simple get request will give you all reviews for given products
parameters:
- name: id
in: query
description: id of the product
required: true
schema:
type: string
requestBody:
responses:
200:
description: All good success
content:
application/json:
schema:
type: object
properties:
success:
type: boolean
reviews:
type: array
items:
type: object
properties:
user:
type: string
name:
type: string
rating:
type: number
comment:
type: string
_id:
type: string
400:
description: Bad request
500:
description: internal server error
/review:
put:
tags:
- Product
summary: A logged in user can post a review on any product. If review is already posted, it will just update the existing review
parameters:
requestBody:
content:
application/json:
schema:
type: object
required:
- rating
- comment
- productId
properties:
rating:
type: number
required: true
comment:
type: string
required: true
productId:
type: string
required: true
responses:
200:
description: All good success
content:
application/json:
schema:
type: object
properties:
success:
type: boolean
reviews:
type: array
items:
type: object
properties:
user:
type: string
name:
type: string
rating:
type: number
comment:
type: string
_id:
type: string
400:
description: Bad request
500:
description: internal server error
delete:
tags:
- Product
summary: Delete the review of logged in user on given product id.
parameters:
- name: productId
in: query
required: true
schema:
type: string
requestBody:
content:
application/json:
schema:
type: object
required:
- rating
- comment
- productId
properties:
rating:
type: number
required: true
comment:
type: string
required: true
productId:
type: string
required: true
responses:
200:
description: All good success
content:
application/json:
schema:
type: object
properties:
success:
type: boolean
reviews:
type: array
items:
type: object
properties:
user:
type: string
name:
type: string
rating:
type: number
comment:
type: string
_id:
type: string
400:
description: Bad request
500:
description: internal server error
/order/create:
post:
tags:
- Order
summary: create a new order with given details
parameters:
requestBody:
content:
application/json:
schema:
type: object
required:
- shippingInfo
- paymentInfo
- taxAmount
- shippingAmount
- totalAmount
- orderItems
properties:
shippingInfo:
type: object
properties:
address:
type: string
city:
type: string
phoneNo:
type: string
postalCode:
type: string
state:
type: string
country:
type: string
paymentInfo:
type: object
properties:
id:
type: string
taxAmount:
type: number
shippingAmount:
type: number
totalAmount:
type: number
orderItems:
type: array
items:
type: object
properties:
name:
type: string
quantity:
type: string
image:
type: string
price:
type: number
product:
type: string
description: id of product
responses:
200:
description: All good success
content:
application/json:
schema:
type: object
properties:
success:
type: boolean
order:
type: object
properties:
_id:
type: string
user:
type: string
orderStatus:
type: string
createdAt:
type: string
shippingInfo:
type: object
properties:
address:
type: string
city:
type: string
phoneNo:
type: string
postalCode:
type: string
state:
type: string
country:
type: string
paymentInfo:
type: object
properties:
id:
type: string
taxAmount:
type: number
shippingAmount:
type: number
totalAmount:
type: number
orderItems:
type: array
items:
type: object
properties:
name:
type: string
quantity:
type: string
image:
type: string
price:
type: number
product:
type: string
description: id of product
400:
description: Bad request
500:
description: internal server error
/order/{id}:
get:
tags:
- Order
summary: get details about a order with order id
parameters:
- name: id
in: path
required: true
description: id of the order
schema:
type: string
requestBody:
responses:
200:
description: All good success
content:
application/json:
schema:
type: object
properties:
success:
type: boolean
order:
type: object
properties:
_id:
type: string
user:
type: object
properties:
name:
type: string
email:
type: string
id:
type: string
orderStatus:
type: string
createdAt:
type: string
shippingInfo:
type: object
properties:
address:
type: string
city:
type: string
phoneNo:
type: string
postalCode:
type: string
state:
type: string
country:
type: string
paymentInfo:
type: object
properties:
id:
type: string
taxAmount:
type: number
shippingAmount:
type: number
totalAmount:
type: number
orderItems:
type: array
items:
type: object
properties:
name:
type: string
quantity:
type: string
image:
type: string
price:
type: number
product:
type: string
description: id of product
400:
description: Bad request
500:
description: internal server error
/myorder:
get:
tags:
- Order
summary: get order of logged in user
requestBody:
responses:
200:
description: All good success
content:
application/json:
schema:
type: object
properties:
success:
type: boolean
order:
type: object
properties:
_id:
type: string
user:
type: object
properties:
name:
type: string
email:
type: string
id:
type: string
orderStatus:
type: string
createdAt:
type: string
shippingInfo:
type: object
properties:
address:
type: string
city:
type: string
phoneNo:
type: string
postalCode:
type: string
state:
type: string
country:
type: string
paymentInfo:
type: object
properties:
id:
type: string
taxAmount:
type: number
shippingAmount:
type: number
totalAmount:
type: number
orderItems:
type: array
items:
type: object
properties:
name:
type: string
quantity:
type: string
image:
type: string
price:
type: number
product:
type: string
description: id of product
400:
description: Bad request
500:
description: internal server error
/admin/orders:
get:
tags:
- Admin
summary: if admin, get list of all orders
requestBody:
responses:
200:
description: All good success
content:
application/json:
schema:
type: object
properties:
success:
type: boolean
orders:
type: array
items:
properties:
_id:
type: string
user:
type: string
orderStatus:
type: string
createdAt:
type: string
shippingInfo:
type: object
properties:
address:
type: string
city:
type: string
phoneNo:
type: string
postalCode:
type: string
state:
type: string
country:
type: string
paymentInfo:
type: object
properties:
id:
type: string
taxAmount:
type: number
shippingAmount:
type: number
totalAmount:
type: number
orderItems:
type: array
items:
type: object
properties:
name:
type: string
quantity:
type: string
image:
type: string
price:
type: number
product:
type: string
description: id of product
400:
description: Bad request
500:
description: internal server error
/admin/order/{id}:
put:
tags:
- Admin
summary: if admin, update the status of the order
parameters:
- name: id
in: path
required: true
description: id of the order
schema:
type: string
requestBody:
content:
multipart/form-data:
schema:
type: object
required:
- orderStatus
properties:
orderStatus:
type: string
enum: [Processing, Delivered]
required: true
responses:
200:
description: All good success
content:
application/json:
schema:
type: object
properties:
success:
type: boolean
order:
type: object
properties:
_id:
type: string
user:
type: string
orderStatus:
type: string
createdAt:
type: string
shippingInfo:
type: object
properties:
address:
type: string
city:
type: string
phoneNo:
type: string
postalCode:
type: string
state:
type: string
country:
type: string
paymentInfo:
type: object
properties:
id:
type: string
taxAmount:
type: number
shippingAmount:
type: number
totalAmount:
type: number
orderItems:
type: array
items:
type: object
properties:
name:
type: string
quantity:
type: string
image:
type: string
price:
type: number
product:
type: string
description: id of product
400:
description: Bad request
500:
description: internal server error
delete:
tags:
- Admin
summary: if admin, delete an order with given id
parameters:
- name: id
in: path
required: true
description: id of the order
schema:
type: string
requestBody:
responses:
200:
description: All good success
content:
application/json:
schema:
type: object
properties:
success:
type: boolean
400:
description: Bad request
500:
description: internal server error
/stripekey:
get:
tags:
- Payment
summary: gets you the public stripe key
requestBody:
responses:
200:
description: All good success
content:
application/json:
schema:
type: object
properties:
stripekey:
type: string
400:
description: Bad request
500:
description: internal server error
/razorpaykey:
get:
tags:
- Payment
summary: gets you the public razorpay key
requestBody:
responses:
200:
description: All good success
content:
application/json:
schema:
type: object
properties:
razorpaykey:
type: string
400:
description: Bad request
500:
description: internal server error
/capturestripe:
post:
tags:
- Payment
summary: send amount in number. Send amount after multiplying by 100. Also request from swagger UI are not proper as to capture payment we need to send dummy card details
requestBody:
content:
multipart/form-data:
schema:
type: object
required:
- amount
properties:
amount:
type: number
responses:
200:
description: All good success
content:
application/json:
schema:
type: object
properties:
success:
type: boolean
amount:
type: number
client_secret:
type: string
400:
description: Bad request
500:
description: internal server error
/capturerazorpay:
post:
tags:
- Payment
summary: send amount in number. Send amount after multiplying by 100. Check orders tab, not payments. Also request from swagger UI are not proper as to capture payment we need to send dummy card details.
requestBody:
content:
multipart/form-data:
schema:
type: object
required:
- amount
properties:
amount:
type: number
responses:
200:
description: All good success
content:
application/json:
schema:
type: object
properties:
success:
type: boolean
amount:
type: number
order:
type: object
properties:
id:
type: string
amount:
type: number
currency:
type: string
400:
description: Bad request
500:
description: internal server error
================================================
FILE: tshirtstore/utils/cookieToken.js
================================================
const cookieToken = (user, res) => {
const token = user.getJwtToken();
const options = {
expires: new Date(
Date.now() + process.env.COOKIE_TIME * 24 * 60 * 60 * 1000
),
httpOnly: true,
};
user.password = undefined;
res.status(200).cookie("token", token, options).json({
success: true,
token,
user,
});
};
module.exports = cookieToken;
================================================
FILE: tshirtstore/utils/customError.js
================================================
class CustomError extends Error {
constructor(message, statusCode) {
super(message);
this.statusCode = statusCode;
Error.captureStackTrace(this, this.constructor)
}
}
module.exports = CustomError;
================================================
FILE: tshirtstore/utils/demotest.js
================================================
// User.find({ qty: { $lte: 20 } })
// /api/v1/product?search=coder&page=2&category=shortsleeves&rating[gte]=4
// &price[lte]=999&price[gte]=199
// rating: { $gte: '4' }
// URL - https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/replace
// const p = 'gte gte lte mygte';
// const regex = /\b(gte|lte)\b/g;
// console.log(p.replace(regex, m => `$${m}`));
================================================
FILE: tshirtstore/utils/emailHelper.js
================================================
const nodemailer = require("nodemailer");
const mailHelper = async (option) => {
const transporter = nodemailer.createTransport({
host: process.env.SMTP_HOST,
port: process.env.SMTP_PORT,
auth: {
user: process.env.SMTP_USER, // generated ethereal user
pass: process.env.SMTP_PASS, // generated ethereal password
},
});
const message = {
from: "hitesh@lco.dev", // sender address
to: option.email, // list of receivers
subject: option.subject, // Subject line
text: option.message, // plain text body
};
// send mail with defined transport object
await transporter.sendMail(message);
};
module.exports = mailHelper;
================================================
FILE: tshirtstore/utils/testOrder.json
================================================
{
"shippingInfo": {
"address": "1 Jaipur",
"city": "Jaipur",
"phoneNo": "9898989898",
"postalCode": "302020",
"state": "Rajasthan",
"country": "India"
},
"orderItems": [
{
"name": "Pro Coder tshirts",
"quantity": 1,
"image": "https://res.cloudinary.com/dk92l1yoc/image/upload/v1635757229/products/b4laryk4dbp6vdrvq3wv.png",
"price": 999,
"product": "617facb5333fd4c0fdfdee65"
}
],
"paymentInfo": {
"id": "testString"
},
"taxAmount": 40,
"shippingAmount": 10,
"totalAmount": 100
}
================================================
FILE: tshirtstore/utils/whereClause.js
================================================
// base - Product.find()
// base - Product.find(email: {"hitesh@lco.dev"})
//bigQ - //search=coder&page=2&category=shortsleeves&rating[gte]=4
// &price[lte]=999&price[gte]=199&limit=5
class WhereClause {
constructor(base, bigQ) {
this.base = base;
this.bigQ = bigQ;
}
search() {
const searchword = this.bigQ.search
? {
name: {
$regex: this.bigQ.search,
$options: "i",
},
}
: {};
this.base = this.base.find({ ...searchword });
return this;
}
filter() {
const copyQ = { ...this.bigQ };
delete copyQ["search"];
delete copyQ["limit"];
delete copyQ["page"];
//convert bigQ into a string => copyQ
let stringOfCopyQ = JSON.stringify(copyQ);
stringOfCopyQ = stringOfCopyQ.replace(
/\b(gte|lte|gt|lt)\b/g,
(m) => `$${m}`
);
const jsonOfCopyQ = JSON.parse(stringOfCopyQ);
this.base = this.base.find(jsonOfCopyQ);
return this;
}
pager(resultperPage) {
let currentPage = 1;
if (this.bigQ.page) {
currentPage = this.bigQ.page;
}
const skipVal = resultperPage * (currentPage - 1);
this.base = this.base.limit(resultperPage).skip(skipVal);
return this;
}
}
module.exports = WhereClause;
================================================
FILE: tshirtstore/views/home.ejs
================================================
LCO Tshirt store
Read API docs here
All API docs are available here. You have access to everything except admin credentials. If you are a team member of LCO, get it from me (Hitesh)