expressCart/lib/common.js

573 lines
16 KiB
JavaScript
Raw Normal View History

2018-01-07 06:07:10 +10:00
const _ = require('lodash');
const uglifycss = require('uglifycss');
const colors = require('colors');
2018-01-15 07:11:22 +10:00
const cheerio = require('cheerio');
const axios = require('axios');
2018-01-07 23:10:16 +10:00
const fs = require('fs');
2018-01-15 07:11:22 +10:00
const path = require('path');
const glob = require('glob');
const async = require('async');
const nodemailer = require('nodemailer');
2018-12-11 23:31:56 +10:00
const sanitizeHtml = require('sanitize-html');
2018-01-07 06:07:10 +10:00
const escape = require('html-entities').AllHtmlEntities;
const mkdirp = require('mkdirp');
2019-06-15 14:46:08 +10:00
const ObjectId = require('mongodb').ObjectID;
2018-06-01 21:31:23 +10:00
// Allowed mime types for product images
2019-06-15 14:46:08 +10:00
const allowedMimeType = [
2018-06-01 21:31:23 +10:00
'image/jpeg',
'image/png',
'image/gif',
'image/bmp',
'image/webp'
];
2019-06-15 14:46:08 +10:00
const fileSizeLimit = 10485760;
2018-06-01 21:31:23 +10:00
2018-01-07 04:55:48 +10:00
// common functions
2019-06-15 14:46:08 +10:00
const cleanHtml = (html) => {
2018-12-11 23:31:56 +10:00
return sanitizeHtml(html);
};
2019-06-15 14:46:08 +10:00
const mongoSanitize = (param) => {
if(param instanceof Object){
for(const key in param){
if(/^\$/.test(key)){
2018-08-31 13:30:27 +10:00
delete param[key];
}
}
}
return param;
};
const safeParseInt = (param) => {
if(param){
try{
return parseInt(param);
}catch(ex){
return param;
}
}else{
return param;
}
};
2019-06-15 14:46:08 +10:00
const checkboxBool = (param) => {
if(param && param === 'on'){
return true;
}
return false;
};
const convertBool = (value) => {
if(value === 'true' || value === true){
return true;
}
return false;
};
2019-06-15 14:46:08 +10:00
const showCartCloseBtn = (page) => {
2018-01-07 04:55:48 +10:00
let showCartCloseButton = true;
if(page === 'checkout' || page === 'pay'){
showCartCloseButton = false;
}
return showCartCloseButton;
};
// adds products to sitemap.xml
2019-06-15 14:46:08 +10:00
const addSitemapProducts = (req, res, cb) => {
2019-07-12 18:06:34 +10:00
const db = req.app.db;
2018-01-15 07:11:22 +10:00
2019-07-12 18:06:34 +10:00
const config = getConfig();
const hostname = config.baseUrl;
2018-01-07 04:55:48 +10:00
db.products.find({ productPublished: true }).toArray((err, products) => {
2019-07-12 18:06:34 +10:00
const posts = [];
2018-01-07 04:55:48 +10:00
if(err){
cb(null, posts);
}
async.eachSeries(products, (item, callback) => {
2019-07-12 18:06:34 +10:00
const post = {};
2018-01-07 04:55:48 +10:00
let url = item._id;
if(item.productPermalink){
url = item.productPermalink;
}
post.url = hostname + '/' + url;
post.changefreq = 'weekly';
post.priority = 0.7;
posts.push(post);
callback(null, posts);
}, () => {
cb(null, posts);
});
});
};
2019-06-15 14:46:08 +10:00
const clearSessionValue = (session, sessionVar) => {
2018-01-07 04:55:48 +10:00
let temp;
if(session){
temp = session[sessionVar];
session[sessionVar] = null;
}
return temp;
};
2019-06-15 14:46:08 +10:00
const updateTotalCartAmount = (req, res) => {
2019-07-12 18:06:34 +10:00
const config = getConfig();
2018-01-07 04:55:48 +10:00
req.session.totalCartAmount = 0;
_(req.session.cart).forEach((item) => {
req.session.totalCartAmount = req.session.totalCartAmount + item.totalItemPrice;
});
// under the free shipping threshold
if(req.session.totalCartAmount < config.freeShippingAmount){
req.session.totalCartAmount = req.session.totalCartAmount + parseInt(config.flatShipping);
req.session.shippingCostApplied = true;
}else{
req.session.shippingCostApplied = false;
}
};
2019-06-15 14:46:08 +10:00
const checkDirectorySync = (directory) => {
2018-01-07 04:55:48 +10:00
try{
fs.statSync(directory);
}catch(e){
try{
2019-02-09 13:13:44 +10:00
fs.mkdirSync(directory);
}catch(err){
mkdirp.sync(directory);// error : directory & sub directories to be newly created
}
2018-01-07 04:55:48 +10:00
}
};
2019-06-15 14:46:08 +10:00
const getThemes = () => {
2018-02-12 05:47:26 +10:00
return fs.readdirSync(path.join(__dirname, '../', 'views', 'themes')).filter(file => fs.statSync(path.join(path.join(__dirname, '../', 'views', 'themes'), file)).isDirectory());
2018-01-07 04:55:48 +10:00
};
const getImages = async (id, req, res, callback) => {
2019-07-12 18:06:34 +10:00
const db = req.app.db;
2018-01-07 04:55:48 +10:00
const product = await db.products.findOne({ _id: getId(id) });
2019-10-26 11:08:53 +10:00
if(!product){
return[];
}
2018-01-07 23:10:16 +10:00
2019-10-26 11:08:53 +10:00
// loop files in /public/uploads/
const files = await glob.sync(`public/uploads/${product._id.toString()}/**`, { nosort: true });
2019-10-26 11:08:53 +10:00
// sort array
files.sort();
// declare the array of objects
const fileList = [];
// loop these files
for(let i = 0; i < files.length; i++){
// only want files
if(fs.lstatSync(files[i]).isDirectory() === false){
// declare the file object and set its values
const file = {
id: i,
path: files[i].substring(6)
};
if(product.productImage === files[i].substring(6)){
file.productImage = true;
2018-01-07 04:55:48 +10:00
}
2019-10-26 11:08:53 +10:00
// push the file object into the array
fileList.push(file);
}
}
return fileList;
2018-01-07 04:55:48 +10:00
};
const getConfig = () => {
let config = JSON.parse(fs.readFileSync(path.join(__dirname, '../config', 'settings.json'), 'utf8'));
const localConfigFilePath = path.join(__dirname, '../config', 'settings-local.json');
// Check for local config file and merge with base settings
if(fs.existsSync(localConfigFilePath)){
const localConfigFile = JSON.parse(fs.readFileSync(localConfigFilePath, 'utf8'));
config = Object.assign(config, localConfigFile);
}
2018-01-07 04:55:48 +10:00
config.customCss = typeof config.customCss !== 'undefined' ? escape.decode(config.customCss) : null;
config.footerHtml = typeof config.footerHtml !== 'undefined' ? escape.decode(config.footerHtml) : null;
config.googleAnalytics = typeof config.googleAnalytics !== 'undefined' ? escape.decode(config.googleAnalytics) : null;
// set the environment for files
config.env = '.min';
if(process.env.NODE_ENV === 'development' || process.env.NODE_ENV === undefined){
config.env = '';
}
// setup theme
config.themeViews = '';
if(typeof config.theme === 'undefined' || config.theme === ''){
config.theme = 'Cloth'; // Default to Cloth theme
2018-01-07 04:55:48 +10:00
}
2018-02-12 05:47:26 +10:00
config.themeViews = '../views/themes/' + config.theme + '/';
2018-01-07 04:55:48 +10:00
// if db set to mongodb override connection with MONGODB_CONNECTION_STRING env var
2018-01-07 05:35:49 +10:00
config.databaseConnectionString = process.env.MONGODB_CONNECTION_STRING || config.databaseConnectionString;
2018-01-07 04:55:48 +10:00
return config;
};
2019-06-15 14:46:08 +10:00
const getPaymentConfig = () => {
2019-07-12 18:06:34 +10:00
const siteConfig = getConfig();
2019-06-15 10:55:23 +10:00
const gateConfigFile = path.join(__dirname, '../config', `${siteConfig.paymentGateway}.json`);
2018-01-07 04:55:48 +10:00
let config = [];
2019-06-15 10:55:23 +10:00
if(fs.existsSync(gateConfigFile)){
config = JSON.parse(fs.readFileSync(gateConfigFile, 'utf8'));
}
// If a local config we combine the objects. Local configs are .gitignored
2019-07-12 18:06:34 +10:00
const localConfig = path.join(__dirname, '../config', `${siteConfig.paymentGateway}-local.json`);
2019-06-15 10:55:23 +10:00
if(fs.existsSync(localConfig)){
const localConfigObj = JSON.parse(fs.readFileSync(localConfig, 'utf8'));
config = Object.assign(config, localConfigObj);
2018-01-07 04:55:48 +10:00
}
return config;
};
2019-06-15 14:46:08 +10:00
const updateConfig = (fields) => {
2019-07-12 18:06:34 +10:00
const settingsFile = getConfig();
2018-01-07 04:55:48 +10:00
_.forEach(fields, (value, key) => {
settingsFile[key] = value;
if(key === 'customCss_input'){
2019-11-03 09:18:34 +10:00
settingsFile.customCss = escape.encode(uglifycss.processString(value));
2018-01-07 04:55:48 +10:00
}
if(key === 'footerHtml_input'){
2019-07-12 18:06:34 +10:00
const footerHtml = typeof value !== 'undefined' || value === '' ? escape.encode(value) : '';
2019-11-03 09:18:34 +10:00
settingsFile.footerHtml = footerHtml;
2018-01-07 04:55:48 +10:00
}
if(key === 'googleAnalytics_input'){
2019-07-12 18:06:34 +10:00
const googleAnalytics = typeof value !== 'undefined' ? escape.encode(value) : '';
2019-11-03 09:18:34 +10:00
settingsFile.googleAnalytics = googleAnalytics;
2018-01-07 04:55:48 +10:00
}
});
// delete settings
2019-11-03 09:18:34 +10:00
delete settingsFile.customCss_input;
delete settingsFile.footerHtml_input;
delete settingsFile.googleAnalytics_input;
2018-01-07 04:55:48 +10:00
2019-11-03 09:18:34 +10:00
if(fields.emailSecure === 'on'){
settingsFile.emailSecure = true;
2018-01-07 04:55:48 +10:00
}else{
2019-11-03 09:18:34 +10:00
settingsFile.emailSecure = false;
2018-01-07 04:55:48 +10:00
}
2019-11-03 09:18:34 +10:00
if(!fields.menuEnabled){
settingsFile.menuEnabled = false;
2018-01-07 04:55:48 +10:00
}else{
2019-11-03 09:18:34 +10:00
settingsFile.menuEnabled = true;
}
2019-11-03 09:18:34 +10:00
if(fields.emailPort){
settingsFile.emailPort = parseInt(fields.emailPort);
}
2019-11-03 09:18:34 +10:00
if(fields.flatShipping){
settingsFile.flatShipping = parseInt(fields.flatShipping);
}
2019-11-03 09:18:34 +10:00
if(fields.freeShippingAmount){
settingsFile.freeShippingAmount = parseInt(fields.freeShippingAmount);
}
2019-11-03 09:18:34 +10:00
if(fields.productsPerRow){
settingsFile.productsPerRow = parseInt(fields.productsPerRow);
}
2019-11-03 09:18:34 +10:00
if(fields.productsPerPage){
settingsFile.productsPerPage = parseInt(fields.productsPerPage);
2018-01-07 04:55:48 +10:00
}
// If we have a local settings file (not git tracked) we loop its settings and save
// and changes made to them. All other settings get updated to the base settings file.
const localSettingsFile = path.join(__dirname, '../config', 'settings-local.json');
if(fs.existsSync(localSettingsFile)){
const localSettings = JSON.parse(fs.readFileSync(localSettingsFile));
_.forEach(localSettings, (value, key) => {
if(fields[key]){
localSettings[key] = fields[key];
// Exists in local so remove from main settings file
delete settingsFile[key];
}
});
// Save our local settings
try{
fs.writeFileSync(localSettingsFile, JSON.stringify(localSettings, null, 4));
}catch(exception){
console.log('Failed to save local settings file', exception);
}
}
// write base settings file
const baseSettingsFile = path.join(__dirname, '../config', 'settings.json');
2018-01-07 04:55:48 +10:00
try{
fs.writeFileSync(baseSettingsFile, JSON.stringify(settingsFile, null, 4));
2018-01-07 04:55:48 +10:00
return true;
}catch(exception){
return false;
}
};
const updateConfigLocal = (field) => {
const localSettingsFile = path.join(__dirname, '../config', 'settings-local.json');
try{
let localSettings = {};
if(fs.existsSync(localSettingsFile)){
localSettings = JSON.parse(fs.readFileSync(localSettingsFile));
}
Object.assign(localSettings, field);
fs.writeFileSync(localSettingsFile, JSON.stringify(localSettings, null, 4));
}catch(exception){
console.log('Failed to save local settings file', exception);
}
};
2019-06-15 14:46:08 +10:00
const getMenu = (db) => {
return db.menu.findOne({});
2018-01-07 04:55:48 +10:00
};
// creates a new menu item
2019-06-15 14:46:08 +10:00
const newMenu = (req, res) => {
const db = req.app.db;
2019-06-15 14:46:08 +10:00
return getMenu(db)
.then((menu) => {
// if no menu present
if(!menu){
menu = {};
menu.items = [];
}
2019-07-12 18:06:34 +10:00
const newNav = {
title: req.body.navMenu,
link: req.body.navLink,
order: Object.keys(menu.items).length + 1
};
menu.items.push(newNav);
2019-06-15 14:46:08 +10:00
return db.menu.updateOne({}, { $set: { items: menu.items } }, { upsert: true })
.then(() => {
return true;
});
})
.catch((err) => {
console.log('Error creating new menu', err);
2018-01-07 04:55:48 +10:00
return false;
});
2018-01-07 04:55:48 +10:00
};
// delete a menu item
2019-06-15 14:46:08 +10:00
const deleteMenu = (req, res, menuIndex) => {
const db = req.app.db;
2019-06-15 14:46:08 +10:00
return getMenu(db)
.then((menu) => {
// Remove menu item
menu.items.splice(menuIndex, 1);
2019-06-15 14:46:08 +10:00
return db.menu.updateOne({}, { $set: { items: menu.items } }, { upsert: true })
.then(() => {
return true;
});
})
.catch(() => {
2018-01-07 04:55:48 +10:00
return false;
});
2018-01-07 04:55:48 +10:00
};
// updates and existing menu item
2019-06-15 14:46:08 +10:00
const updateMenu = (req, res) => {
const db = req.app.db;
2019-06-15 14:46:08 +10:00
return getMenu(db)
.then((menu) => {
// find menu item and update it
2019-07-12 18:06:34 +10:00
const menuIndex = _.findIndex(menu.items, ['title', req.body.navId]);
menu.items[menuIndex].title = req.body.navMenu;
menu.items[menuIndex].link = req.body.navLink;
2019-06-15 14:46:08 +10:00
return db.menu.updateOne({}, { $set: { items: menu.items } }, { upsert: true })
.then(() => {
return true;
});
})
.catch(() => {
return false;
});
};
2018-01-07 04:55:48 +10:00
2019-06-15 14:46:08 +10:00
const sortMenu = (menu) => {
if(menu && menu.items){
menu.items = _.sortBy(menu.items, 'order');
return menu;
}
return{};
};
2018-01-07 04:55:48 +10:00
// orders the menu
2019-06-15 14:46:08 +10:00
const orderMenu = (req, res) => {
const db = req.app.db;
2019-06-15 14:46:08 +10:00
return getMenu(db)
.then((menu) => {
// update the order
for(let i = 0; i < req.body.navId.length; i++){
_.find(menu.items, ['title', req.body.navId[i]]).order = i;
}
2019-06-15 14:46:08 +10:00
return db.menu.updateOne({}, { $set: { items: menu.items } }, { upsert: true })
.then(() => {
return true;
});
})
.catch(() => {
2018-01-07 04:55:48 +10:00
return false;
});
2018-01-07 04:55:48 +10:00
};
2019-06-15 14:46:08 +10:00
const getEmailTemplate = (result) => {
2019-07-12 18:06:34 +10:00
const config = getConfig();
2018-01-07 04:55:48 +10:00
2019-07-12 18:06:34 +10:00
const template = fs.readFileSync(path.join(__dirname, '../public/email_template.html'), 'utf8');
2018-01-07 04:55:48 +10:00
$ = cheerio.load(template);
$('#brand').text(config.cartTitle);
$('#paymentResult').text(result.message);
if(result.paymentApproved === true){
$('#paymentResult').addClass('text-success');
}else{
$('#paymentResult').addClass('text-danger');
}
$('#paymentMessage').text('Thanks for shopping with us. We hope you will shop with us again soon.');
$('#paymentDetails').html(result.paymentDetails);
return $.html();
};
2019-06-15 14:46:08 +10:00
const sendEmail = (to, subject, body) => {
2019-07-12 18:06:34 +10:00
const config = getConfig();
2018-01-07 04:55:48 +10:00
2019-07-12 18:06:34 +10:00
const emailSettings = {
2018-01-07 04:55:48 +10:00
host: config.emailHost,
port: config.emailPort,
secure: config.emailSecure,
auth: {
user: config.emailUser,
pass: config.emailPassword
}
};
// outlook needs this setting
if(config.emailHost === 'smtp-mail.outlook.com'){
2019-06-15 14:46:08 +10:00
emailSettings.tls = { ciphers: 'SSLv3' };
2018-01-07 04:55:48 +10:00
}
2019-07-12 18:06:34 +10:00
const transporter = nodemailer.createTransport(emailSettings);
2018-01-07 04:55:48 +10:00
2019-07-12 18:06:34 +10:00
const mailOptions = {
2018-01-07 04:55:48 +10:00
from: config.emailAddress, // sender address
to: to, // list of receivers
subject: subject, // Subject line
html: body// html body
};
transporter.sendMail(mailOptions, (error, info) => {
if(error){
return console.error(colors.red(error));
}
return true;
});
};
// gets the correct type of index ID
2019-06-15 14:46:08 +10:00
const getId = (id) => {
2018-01-07 04:55:48 +10:00
if(id){
if(id.length !== 24){
return id;
}
}
return ObjectId(id);
2018-01-07 04:55:48 +10:00
};
2019-06-15 14:46:08 +10:00
const getData = (req, page, query) => {
2019-07-12 18:06:34 +10:00
const db = req.app.db;
const config = getConfig();
const numberProducts = config.productsPerPage ? config.productsPerPage : 6;
2018-01-15 07:11:22 +10:00
let skip = 0;
if(page > 1){
skip = (page - 1) * numberProducts;
}
2018-02-12 05:47:26 +10:00
if(!query){
query = {};
}
2018-01-15 07:11:22 +10:00
2019-11-03 09:18:34 +10:00
query.productPublished = { $ne: false };
2018-01-15 07:11:22 +10:00
// Run our queries
return Promise.all([
db.products.find(query).skip(skip).limit(parseInt(numberProducts)).toArray(),
2019-10-29 18:26:30 +10:00
db.products.countDocuments(query)
2018-01-15 07:11:22 +10:00
])
.then((result) => {
2019-06-15 14:46:08 +10:00
const returnData = { data: result[0], totalProducts: result[1] };
2018-01-15 07:11:22 +10:00
return returnData;
})
.catch((err) => {
throw new Error('Error retrieving products');
});
};
const hooker = (order) => {
2019-07-12 18:06:34 +10:00
const config = getConfig();
return axios.post(config.orderHook, order, { responseType: 'application/json' })
.then((response) => {
if(response.status === 200){
console.info('Successfully called order hook');
}
})
.catch((err) => {
console.log('Error calling hook:', err);
});
};
2019-06-15 14:46:08 +10:00
module.exports = {
allowedMimeType,
fileSizeLimit,
cleanHtml,
mongoSanitize,
safeParseInt,
2019-06-15 14:46:08 +10:00
checkboxBool,
convertBool,
2019-06-15 14:46:08 +10:00
showCartCloseBtn,
addSitemapProducts,
clearSessionValue,
updateTotalCartAmount,
checkDirectorySync,
getThemes,
getImages,
getConfig,
getPaymentConfig,
updateConfig,
updateConfigLocal,
2019-06-15 14:46:08 +10:00
getMenu,
newMenu,
deleteMenu,
updateMenu,
sortMenu,
orderMenu,
getEmailTemplate,
sendEmail,
getId,
getData,
hooker
2018-01-07 06:07:10 +10:00
};