Moved menu to the DB and started modernizing old code

react_convert
Mark Moffat 2018-01-14 18:02:10 +01:00
parent ceb23e1ce1
commit ecef3b8224
7 changed files with 222 additions and 195 deletions

View File

@ -6,7 +6,7 @@
"promise" "promise"
], ],
"parserOptions": { "parserOptions": {
"ecmaVersion": 6 "ecmaVersion": 2017
}, },
"globals": { "globals": {
"$": true "$": true

20
app.js
View File

@ -260,16 +260,22 @@ app.use((err, req, res, next) => {
}); });
}); });
// Nodejs version check
if(parseInt(process.version.split('.')[0].replace('v', '')) <= 7){
console.log(colors.red('Please use Node.js version 7.x or above'));
process.exit(2);
}
app.on('uncaughtException', (err) => { app.on('uncaughtException', (err) => {
console.error(colors.red(err.stack)); console.error(colors.red(err.stack));
process.exit(); process.exit(2);
}); });
MongoClient.connect(config.databaseConnectionString, {}, (err, client) => { MongoClient.connect(config.databaseConnectionString, {}, (err, client) => {
// On connection error we display then exit // On connection error we display then exit
if(err){ if(err){
console.log(colors.red('Error connecting to MongoDB: ' + err)); console.log(colors.red('Error connecting to MongoDB: ' + err));
process.exit(); process.exit(2);
} }
// select DB // select DB
@ -281,22 +287,22 @@ MongoClient.connect(config.databaseConnectionString, {}, (err, client) => {
db.products = db.collection('products'); db.products = db.collection('products');
db.orders = db.collection('orders'); db.orders = db.collection('orders');
db.pages = db.collection('pages'); db.pages = db.collection('pages');
db.menu = db.collection('menu');
// add db to app for routes // add db to app for routes
app.db = db; app.db = db;
// add indexing // add indexing
common.runIndexing(app) common.runIndexing(app)
.then(common.testData(db)) .then(common.testData(db, app))
.then(app.listen(app.get('port')))
.then(() => { .then(() => {
// lift the app // lift the app
app.listen(app.get('port'), () => { console.log(colors.green('expressCart running on host: http://localhost:' + app.get('port')));
console.log(colors.green('expressCart running on host: http://localhost:' + app.get('port')));
});
}) })
.catch(() => { .catch(() => {
console.error(colors.red('Error setting up indexes:' + err)); console.error(colors.red('Error setting up indexes:' + err));
process.exit(); process.exit(2);
}); });
}); });

View File

@ -1,67 +1,83 @@
[ {
{ "products":[
"productPermalink": "duckworth-jacket", {
"productTitle": "Duckworth Woolfill Jacket", "productPermalink": "duckworth-jacket",
"productPrice": "188.00", "productTitle": "Duckworth Woolfill Jacket",
"productDescription": "<p style=\"margin-bottom: 25px; text-rendering: optimizeLegibility;\">Inspired by the timeless, functional style of your grandfather's work coat, the Foraker features brass buttons and 4 patch pockets. Crafted in Bristol, Tennessee, our 10oz organic duck canvas is light enough for an early summer morning, but rugged enough to handle your days work.<\/p><ul class=\"tabs-content\" style=\"margin-right: 0px; margin-bottom: 25px; margin-left: 20px; padding: 0px; text-rendering: optimizeLegibility;\"><li style=\"margin-bottom: 0px;\">100% Organic Duck Canvas.<\/li><\/ul>", "productPrice": "188.00",
"productPublished": "true", "productDescription": "<p style=\"margin-bottom: 25px; text-rendering: optimizeLegibility;\">Inspired by the timeless, functional style of your grandfather's work coat, the Foraker features brass buttons and 4 patch pockets. Crafted in Bristol, Tennessee, our 10oz organic duck canvas is light enough for an early summer morning, but rugged enough to handle your days work.<\/p><ul class=\"tabs-content\" style=\"margin-right: 0px; margin-bottom: 25px; margin-left: 20px; padding: 0px; text-rendering: optimizeLegibility;\"><li style=\"margin-bottom: 0px;\">100% Organic Duck Canvas.<\/li><\/ul>",
"productAddedDate": { "productPublished": "true",
"$date": "2016-05-01T11:22:49.442Z" "productAddedDate": {
"$date": "2016-05-01T11:22:49.442Z"
},
"productTags": "organic, jacket",
"productOptions": "{\"Size\":{\"optName\":\"Size\",\"optLabel\":\"Select size\",\"optType\":\"select\",\"optOptions\":[\"S\",\"M\",\"L\",\"XL\"]},\"Colour\":{\"optName\":\"Colour\",\"optLabel\":\"Select colour\",\"optType\":\"select\",\"optOptions\":[\"Harvest\",\"Navy\"]}}",
"productImage": "/uploads/duckworth-jacket/woolfill-jacket_6c39ae23-c0c8-4821-85f4-4b5d64333c62_grande.jpg"
}, },
"productTags": "organic, jacket", {
"productOptions": "{\"Size\":{\"optName\":\"Size\",\"optLabel\":\"Select size\",\"optType\":\"select\",\"optOptions\":[\"S\",\"M\",\"L\",\"XL\"]},\"Colour\":{\"optName\":\"Colour\",\"optLabel\":\"Select colour\",\"optType\":\"select\",\"optOptions\":[\"Harvest\",\"Navy\"]}}", "productPermalink": "5-panel-cap",
"productImage": "/uploads/duckworth-jacket/woolfill-jacket_6c39ae23-c0c8-4821-85f4-4b5d64333c62_grande.jpg" "productTitle": "5 Panel Camp Cap",
}, "productPrice": "48",
{ "productDescription": "<p style=\"margin-bottom: 25px; text-rendering: optimizeLegibility;\">A classic 5 panel hat with our United By Blue logo on the front and an adjustable strap to keep fit and secure. Made with recycled polyester and organic cotton mix.<\/p><ul style=\"margin-right: 0px; margin-bottom: 25px; margin-left: 20px; padding: 0px; text-rendering: optimizeLegibility;\"><li style=\"margin-bottom: 0px;\">Made in&nbsp;New Jersey<\/li><li style=\"margin-bottom: 0px;\">7oz Eco-Twill fabric:&nbsp;35% organic cotton, 65% recycled PET&nbsp;(plastic water and soda bottles)&nbsp;<\/li><li style=\"margin-bottom: 0px;\">Embossed leather patch<\/li><\/ul><ul class=\"tabs\" style=\"margin-right: 0px; margin-bottom: 25px; margin-left: 20px; padding: 0px; text-rendering: optimizeLegibility; color: rgb(28, 29, 29); font-family: Arapey, serif; line-height: 25.008px;\"><\/ul>",
"productPermalink": "5-panel-cap", "productPublished": "true",
"productTitle": "5 Panel Camp Cap", "productAddedDate": {
"productPrice": "48", "$date": "2016-07-10T05:01:15.744Z"
"productDescription": "<p style=\"margin-bottom: 25px; text-rendering: optimizeLegibility;\">A classic 5 panel hat with our United By Blue logo on the front and an adjustable strap to keep fit and secure. Made with recycled polyester and organic cotton mix.<\/p><ul style=\"margin-right: 0px; margin-bottom: 25px; margin-left: 20px; padding: 0px; text-rendering: optimizeLegibility;\"><li style=\"margin-bottom: 0px;\">Made in&nbsp;New Jersey<\/li><li style=\"margin-bottom: 0px;\">7oz Eco-Twill fabric:&nbsp;35% organic cotton, 65% recycled PET&nbsp;(plastic water and soda bottles)&nbsp;<\/li><li style=\"margin-bottom: 0px;\">Embossed leather patch<\/li><\/ul><ul class=\"tabs\" style=\"margin-right: 0px; margin-bottom: 25px; margin-left: 20px; padding: 0px; text-rendering: optimizeLegibility; color: rgb(28, 29, 29); font-family: Arapey, serif; line-height: 25.008px;\"><\/ul>", },
"productPublished": "true", "productTags": "panel, cap",
"productAddedDate": { "productOptions": "{\"colour\":{\"optName\":\"colour\",\"optLabel\":\"Select colour\",\"optType\":\"select\",\"optOptions\":[\"Heather green\",\"Burnt orange\",\"Slate grey\",\"Navy blue\"]}}",
"$date": "2016-07-10T05:01:15.744Z" "productImage": "/uploads/5-panel-cap/5-panel-hat_4ee20a27-8d5a-490e-a2fc-1f9c3beb7bf5_grande.jpg"
}, },
"productTags": "panel, cap", {
"productOptions": "{\"colour\":{\"optName\":\"colour\",\"optLabel\":\"Select colour\",\"optType\":\"select\",\"optOptions\":[\"Heather green\",\"Burnt orange\",\"Slate grey\",\"Navy blue\"]}}", "productPermalink": "ranger-boot",
"productImage": "/uploads/5-panel-cap/5-panel-hat_4ee20a27-8d5a-490e-a2fc-1f9c3beb7bf5_grande.jpg" "productTitle": "Red Wing Iron Ranger Boot",
}, "productPrice": "310",
{ "productDescription": "<p style=\"margin-bottom: 25px; text-rendering: optimizeLegibility;\">The Mesabi Iron Range lies in northern Minnesota, a rugged and remote area known for its iron mines. The local residents who work these mines are proudly known as Iron Rangers, individuals with a sense of adventure and a determined personality. Originally designed to be worn in the iron mines, Iron Ranger work boots had to be as tough as the people who wore them in demanding conditions. Iron Ranger boots are built with a double layer of leaver over the toe to provide an extra measure of safety.<\/p><ul class=\"tabs-content\" style=\"margin-right: 0px; margin-bottom: 25px; margin-left: 20px; padding: 0px; text-rendering: optimizeLegibility;\"><li style=\"margin-bottom: 0px;\">6 inch, Amber Harness leather with Nitrile Cork sole.<\/li><\/ul>",
"productPermalink": "ranger-boot", "productPublished": "true",
"productTitle": "Red Wing Iron Ranger Boot", "productAddedDate": {
"productPrice": "310", "$date": "2016-07-10T05:29:04.941Z"
"productDescription": "<p style=\"margin-bottom: 25px; text-rendering: optimizeLegibility;\">The Mesabi Iron Range lies in northern Minnesota, a rugged and remote area known for its iron mines. The local residents who work these mines are proudly known as Iron Rangers, individuals with a sense of adventure and a determined personality. Originally designed to be worn in the iron mines, Iron Ranger work boots had to be as tough as the people who wore them in demanding conditions. Iron Ranger boots are built with a double layer of leaver over the toe to provide an extra measure of safety.<\/p><ul class=\"tabs-content\" style=\"margin-right: 0px; margin-bottom: 25px; margin-left: 20px; padding: 0px; text-rendering: optimizeLegibility;\"><li style=\"margin-bottom: 0px;\">6 inch, Amber Harness leather with Nitrile Cork sole.<\/li><\/ul>", },
"productPublished": "true", "productTags": "ranger, boot, leather",
"productAddedDate": { "productImage": "/uploads/ranger-boot/boot_grande.jpg",
"$date": "2016-07-10T05:29:04.941Z" "productOptions": "{\"size\":{\"optName\":\"size\",\"optLabel\":\"Select size\",\"optType\":\"select\",\"optOptions\":[\"7.5\",\"8\",\"8.5\",\"9\",\"9.5\",\"10\",\"10.5\",\"11\"]}}"
}, },
"productTags": "ranger, boot, leather", {
"productImage": "/uploads/ranger-boot/boot_grande.jpg", "productPermalink": "whitney-pullover",
"productOptions": "{\"size\":{\"optName\":\"size\",\"optLabel\":\"Select size\",\"optType\":\"select\",\"optOptions\":[\"7.5\",\"8\",\"8.5\",\"9\",\"9.5\",\"10\",\"10.5\",\"11\"]}}" "productTitle": "Whitney pullover",
}, "productPrice": "138",
{ "productDescription": "<p style=\"margin-bottom: 25px; text-rendering: optimizeLegibility;\">Buttons are fussy. Sometimes you just want to roll out of bed, put on the pull over and get to the days work.&nbsp;Julie is 5'8\" and wearing a size small.<\/p><ul class=\"tabs-content\" style=\"margin-right: 0px; margin-bottom: 25px; margin-left: 20px; padding: 0px; text-rendering: optimizeLegibility;\"><li style=\"margin-bottom: 0px;\">100% Wool, Heavy 4 gauge thickness<\/li><li style=\"margin-bottom: 0px;\">Handmade in Nepal.<\/li><\/ul>",
"productPermalink": "whitney-pullover", "productPublished": "true",
"productTitle": "Whitney pullover", "productAddedDate": {
"productPrice": "138", "$date": "2016-05-01T11:23:08.374Z"
"productDescription": "<p style=\"margin-bottom: 25px; text-rendering: optimizeLegibility;\">Buttons are fussy. Sometimes you just want to roll out of bed, put on the pull over and get to the days work.&nbsp;Julie is 5'8\" and wearing a size small.<\/p><ul class=\"tabs-content\" style=\"margin-right: 0px; margin-bottom: 25px; margin-left: 20px; padding: 0px; text-rendering: optimizeLegibility;\"><li style=\"margin-bottom: 0px;\">100% Wool, Heavy 4 gauge thickness<\/li><li style=\"margin-bottom: 0px;\">Handmade in Nepal.<\/li><\/ul>", },
"productPublished": "true", "productImage": "/uploads/whitney-pullover/WhitneyPullover_Full_58e7b8d6-b939-4701-9e1d-9d853dff60ed_grande.jpeg",
"productAddedDate": { "productTags": "whitney, pullover",
"$date": "2016-05-01T11:23:08.374Z" "productOptions": "{\"size\":{\"optName\":\"size\",\"optLabel\":\"Select size\",\"optType\":\"select\",\"optOptions\":[\"S\",\"M\",\"L\",\"XL\"]}}"
}, },
"productImage": "/uploads/whitney-pullover/WhitneyPullover_Full_58e7b8d6-b939-4701-9e1d-9d853dff60ed_grande.jpeg", {
"productTags": "whitney, pullover", "productPermalink": "scout-backpack",
"productOptions": "{\"size\":{\"optName\":\"size\",\"optLabel\":\"Select size\",\"optType\":\"select\",\"optOptions\":[\"S\",\"M\",\"L\",\"XL\"]}}" "productTitle": "Scout Backpack",
}, "productPrice": "128.00",
{ "productDescription": "<p><span style=\"line-height: 1.42857;\">This durable backpack is ready for any adventure, large or small. Features adjustable and padded shoulder pads for comfort. Designed with a storm flap and a secured by two snap-button closure. Made with a waxed downpour proof exterior canvas and a soft cotton interior lining. Finished with brass hardware and genuine leather trimmings.<\/span><\/p><ul><li><span style=\"line-height: 1.42857;\">100% organic waxed 18 oz canvas<\/span><\/li><li><span style=\"line-height: 1.42857;\">Full grain genuine leather accents<\/span><\/li><li>Adjustable shoulder straps<\/li><li>Lifetime&amp;nbsp;Guarantee<\/li><\/ul>",
"productPermalink": "scout-backpack", "productPublished": "true",
"productTitle": "Scout Backpack", "productTags": "backpack, organic",
"productPrice": "128.00", "productAddedDate": {
"productDescription": "<p><span style=\"line-height: 1.42857;\">This durable backpack is ready for any adventure, large or small. Features adjustable and padded shoulder pads for comfort. Designed with a storm flap and a secured by two snap-button closure. Made with a waxed downpour proof exterior canvas and a soft cotton interior lining. Finished with brass hardware and genuine leather trimmings.<\/span><\/p><ul><li><span style=\"line-height: 1.42857;\">100% organic waxed 18 oz canvas<\/span><\/li><li><span style=\"line-height: 1.42857;\">Full grain genuine leather accents<\/span><\/li><li>Adjustable shoulder straps<\/li><li>Lifetime&amp;nbsp;Guarantee<\/li><\/ul>", "$date": "2016-07-10T05:46:26.974Z"
"productPublished": "true", },
"productTags": "backpack, organic", "productImage": "/uploads/scout-backpack/scout-backpack_a035275d-8975-4a05-8456-5e1ec35f020f_grande.jpg",
"productAddedDate": { "productOptions": "{\"colour\":{\"optName\":\"colour\",\"optLabel\":\"Select colour\",\"optType\":\"select\",\"optOptions\":[\"Navy\",\"Moss\",\"Nutmeg\",\"Khaki\"]}}"
"$date": "2016-07-10T05:46:26.974Z" }
}, ],
"productImage": "/uploads/scout-backpack/scout-backpack_a035275d-8975-4a05-8456-5e1ec35f020f_grande.jpg", "menu": {
"productOptions": "{\"colour\":{\"optName\":\"colour\",\"optLabel\":\"Select colour\",\"optType\":\"select\",\"optOptions\":[\"Navy\",\"Moss\",\"Nutmeg\",\"Khaki\"]}}" "items": [
{
"title" : "Backpacks",
"link" : "backpack",
"order" : 0
},
{
"title" : "Boots",
"link" : "boots",
"order" : 1
}
]
} }
] }

View File

@ -359,7 +359,6 @@ router.get('/product/new', common.restrict, (req, res) => {
// insert new product form action // insert new product form action
router.post('/product/insert', common.restrict, (req, res) => { router.post('/product/insert', common.restrict, (req, res) => {
let db = req.app.db; let db = req.app.db;
let config = common.getConfig();
let doc = { let doc = {
productPermalink: req.body.frmProductPermalink, productPermalink: req.body.frmProductPermalink,
@ -852,7 +851,8 @@ router.post('/settings/option/remove', common.restrict, (req, res) => {
}); });
// settings update // settings update
router.get('/settings/menu', common.restrict, (req, res) => { router.get('/settings/menu', common.restrict, async (req, res) => {
let db = req.app.db;
res.render('settings_menu', { res.render('settings_menu', {
title: 'Cart menu', title: 'Cart menu',
session: req.session, session: req.session,
@ -861,17 +861,18 @@ router.get('/settings/menu', common.restrict, (req, res) => {
messageType: common.clearSessionValue(req.session, 'messageType'), messageType: common.clearSessionValue(req.session, 'messageType'),
helpers: req.handlebars.helpers, helpers: req.handlebars.helpers,
config: common.getConfig(), config: common.getConfig(),
menu: common.getMenu().items menu: common.sortMenu(await common.getMenu(db))
}); });
}); });
// settings page list // settings page list
router.get('/settings/pages', common.restrict, (req, res) => { router.get('/settings/pages', common.restrict, (req, res) => {
let db = req.app.db; let db = req.app.db;
common.dbQuery(db.pages, {}, null, null, (err, pages) => { common.dbQuery(db.pages, {}, null, null, async (err, pages) => {
if(err){ if(err){
console.info(err.stack); console.info(err.stack);
} }
res.render('settings_pages', { res.render('settings_pages', {
title: 'Static pages', title: 'Static pages',
pages: pages, pages: pages,
@ -881,13 +882,15 @@ router.get('/settings/pages', common.restrict, (req, res) => {
messageType: common.clearSessionValue(req.session, 'messageType'), messageType: common.clearSessionValue(req.session, 'messageType'),
helpers: req.handlebars.helpers, helpers: req.handlebars.helpers,
config: common.getConfig(), config: common.getConfig(),
menu: common.getMenu().items menu: common.sortMenu(await common.getMenu(db))
}); });
}); });
}); });
// settings pages new // settings pages new
router.get('/settings/pages/new', common.restrict, (req, res) => { router.get('/settings/pages/new', common.restrict, async (req, res) => {
let db = req.app.db;
res.render('settings_page_edit', { res.render('settings_page_edit', {
title: 'Static pages', title: 'Static pages',
session: req.session, session: req.session,
@ -897,18 +900,19 @@ router.get('/settings/pages/new', common.restrict, (req, res) => {
messageType: common.clearSessionValue(req.session, 'messageType'), messageType: common.clearSessionValue(req.session, 'messageType'),
helpers: req.handlebars.helpers, helpers: req.handlebars.helpers,
config: common.getConfig(), config: common.getConfig(),
menu: common.getMenu().items menu: common.sortMenu(await common.getMenu(db))
}); });
}); });
// settings pages editor // settings pages editor
router.get('/settings/pages/edit/:page', common.restrict, (req, res) => { router.get('/settings/pages/edit/:page', common.restrict, (req, res) => {
let db = req.app.db; let db = req.app.db;
db.pages.findOne({_id: common.getId(req.params.page)}, (err, page) => { db.pages.findOne({_id: common.getId(req.params.page)}, async (err, page) => {
if(err){ if(err){
console.info(err.stack); console.info(err.stack);
} }
// page found // page found
const menu = common.sortMenu(await common.getMenu(db));
if(page){ if(page){
res.render('settings_page_edit', { res.render('settings_page_edit', {
title: 'Static pages', title: 'Static pages',
@ -920,7 +924,7 @@ router.get('/settings/pages/edit/:page', common.restrict, (req, res) => {
messageType: common.clearSessionValue(req.session, 'messageType'), messageType: common.clearSessionValue(req.session, 'messageType'),
helpers: req.handlebars.helpers, helpers: req.handlebars.helpers,
config: common.getConfig(), config: common.getConfig(),
menu: common.getMenu().items menu
}); });
}else{ }else{
// 404 it! // 404 it!
@ -930,9 +934,8 @@ router.get('/settings/pages/edit/:page', common.restrict, (req, res) => {
message: '404 Error - Page not found', message: '404 Error - Page not found',
helpers: req.handlebars.helpers, helpers: req.handlebars.helpers,
showFooter: 'showFooter', showFooter: 'showFooter',
menu: common.getMenu() menu
} });
);
} }
}); });
}); });

View File

@ -4,6 +4,7 @@ const colors = require('colors');
const lunr = require('lunr'); const lunr = require('lunr');
const fs = require('fs'); const fs = require('fs');
const escape = require('html-entities').AllHtmlEntities; const escape = require('html-entities').AllHtmlEntities;
let ObjectId = require('mongodb').ObjectID;
// common functions // common functions
exports.checkLogin = function(req, res, next){ exports.checkLogin = function(req, res, next){
@ -229,77 +230,99 @@ exports.updateConfig = function(fields){
} }
}; };
exports.getMenu = function(){ exports.getMenu = function(db){
let fs = require('fs'); return db.menu.findOne({});
let path = require('path');
let menuFile = JSON.parse(fs.readFileSync(path.join(__dirname, '../config/menu.json'), 'utf8'));
menuFile.items = _.sortBy(menuFile.items, 'order');
return menuFile;
}; };
// creates a new menu item // creates a new menu item
exports.newMenu = function(req, res){ exports.newMenu = function(req, res){
let fs = require('fs'); const db = req.app.db;
let path = require('path'); return exports.getMenu(db)
let menuJson = '../config/menu.json'; .then((menu) => {
let menuFile = require(menuJson); // if no menu present
if(!menu){
menu = {};
menu.items = [];
}
let newNav = {
title: req.body.navMenu,
link: req.body.navLink,
order: Object.keys(menu.items).length + 1
};
let newNav = { menu.items.push(newNav);
title: req.body.navMenu, return db.menu.updateOne({}, {$set: {items: menu.items}}, {upsert: true})
link: req.body.navLink, .then(() => {
order: Object.keys(menuFile.items).length + 1 return true;
}; });
})
// add new menu item .catch((err) => {
menuFile.items.push(newNav); console.log('Error creating new menu', err);
// write file
try{
fs.writeFileSync(path.join(__dirname, '../config/menu.json'), JSON.stringify(menuFile));
return true;
}catch(e){
return false; return false;
} });
}; };
// delete a menu item // delete a menu item
exports.deleteMenu = function(req, res, menuIndex){ exports.deleteMenu = function(req, res, menuIndex){
let fs = require('fs'); const db = req.app.db;
let path = require('path'); return exports.getMenu(db)
let menuJson = '../config/menu.json'; .then((menu) => {
let menuFile = require(menuJson); // Remove menu item
menu.items.splice(menuIndex, 1);
delete menuFile.items[menuIndex]; return db.menu.updateOne({}, {$set: {items: menu.items}}, {upsert: true})
.then(() => {
// write file return true;
try{ });
fs.writeFileSync(path.join(__dirname, '../config/menu.json'), JSON.stringify(menuFile)); })
return true; .catch(() => {
}catch(e){
return false; return false;
} });
}; };
// updates and existing menu item // updates and existing menu item
exports.updateMenu = function(req, res){ exports.updateMenu = function(req, res){
let fs = require('fs'); const db = req.app.db;
let path = require('path'); return exports.getMenu(db)
let menuJson = '../config/menu.json'; .then((menu) => {
let menuFile = require(menuJson); // find menu item and update it
let menuIndex = _.findIndex(menu.items, ['title', req.body.navId]);
// find menu item and update it menu.items[menuIndex].title = req.body.navMenu;
let menuIndex = _.findIndex(menuFile.items, ['title', req.body.navId]); menu.items[menuIndex].link = req.body.navLink;
menuFile.items[menuIndex].title = req.body.navMenu; return db.menu.updateOne({}, {$set: {items: menu.items}}, {upsert: true})
menuFile.items[menuIndex].link = req.body.navLink; .then(() => {
return true;
// write file });
try{ })
fs.writeFileSync(path.join(__dirname, '../config/menu.json'), JSON.stringify(menuFile)); .catch(() => {
return true;
}catch(e){
return false; return false;
});
};
exports.sortMenu = function(menu){
if(menu && menu.items){
menu.items = _.sortBy(menu.items, 'order');
return menu;
} }
return{};
};
// orders the menu
exports.orderMenu = function(req, res){
const db = req.app.db;
return exports.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;
}
return db.menu.updateOne({}, {$set: {items: menu.items}}, {upsert: true})
.then(() => {
return true;
});
})
.catch(() => {
return false;
});
}; };
exports.getEmailTemplate = function(result){ exports.getEmailTemplate = function(result){
@ -360,36 +383,14 @@ exports.sendEmail = function(to, subject, body){
}); });
}; };
// orders the menu
exports.orderMenu = function(req, res){
let fs = require('fs');
let path = require('path');
let menuJson = '../config/menu.json';
let menuFile = require(menuJson);
// update the order
for(let i = 0; i < req.body.navId.length; i++){
_.find(menuFile.items, ['title', req.body.navId[i]]).order = i;
}
// write file
try{
fs.writeFileSync(path.join(__dirname, '../config/menu.json'), JSON.stringify(menuFile));
return true;
}catch(e){
return false;
}
};
// gets the correct type of index ID // gets the correct type of index ID
exports.getId = function(id){ exports.getId = function(id){
let ObjectID = require('mongodb').ObjectID;
if(id){ if(id){
if(id.length !== 24){ if(id.length !== 24){
return id; return id;
} }
} }
return ObjectID(id); return ObjectId(id);
}; };
// run the DB query // run the DB query
@ -496,7 +497,7 @@ exports.runIndexing = (app) => {
}); });
}; };
exports.testData = (db) => { exports.testData = (db, app) => {
db.products.count({}) db.products.count({})
.then((products) => { .then((products) => {
if(products > 0){ if(products > 0){
@ -505,9 +506,12 @@ exports.testData = (db) => {
console.info(colors.cyan('No products, inserting test data')); console.info(colors.cyan('No products, inserting test data'));
const testdata = fs.readFileSync('./bin/testdata.json', 'utf-8'); const testData = fs.readFileSync('./bin/testdata.json', 'utf-8');
const jsonData = JSON.parse(testData);
return Promise.all([ return Promise.all([
db.products.insertMany(JSON.parse(testdata)) db.products.insertMany(jsonData.products),
db.menu.insertOne(jsonData.menu),
exports.runIndexing(app)
]) ])
.catch((err) => { .catch((err) => {
console.info(colors.red('Error inserting test data. Check `/bin/testdata.json` is correctly formatted.', err)); console.info(colors.red('Error inserting test data. Check `/bin/testdata.json` is correctly formatted.', err));

View File

@ -4,12 +4,12 @@ const colors = require('colors');
const _ = require('lodash'); const _ = require('lodash');
const common = require('./common'); const common = require('./common');
router.get('/payment/:orderId', (req, res, next) => { router.get('/payment/:orderId', async (req, res, next) => {
let db = req.app.db; let db = req.app.db;
let config = common.getConfig(); let config = common.getConfig();
// render the payment complete message // render the payment complete message
db.orders.findOne({_id: common.getId(req.params.orderId)}, (err, result) => { db.orders.findOne({_id: common.getId(req.params.orderId)}, async (err, result) => {
if(err){ if(err){
console.info(err.stack); console.info(err.stack);
} }
@ -23,12 +23,12 @@ router.get('/payment/:orderId', (req, res, next) => {
messageType: common.clearSessionValue(req.session, 'messageType'), messageType: common.clearSessionValue(req.session, 'messageType'),
helpers: req.handlebars.helpers, helpers: req.handlebars.helpers,
showFooter: 'showFooter', showFooter: 'showFooter',
menu: common.getMenu() menu: common.sortMenu(await common.getMenu(db))
}); });
}); });
}); });
router.get('/checkout', (req, res, next) => { router.get('/checkout', async (req, res, next) => {
let config = common.getConfig(); let config = common.getConfig();
// if there is no items in the cart then render a failure // if there is no items in the cart then render a failure
@ -50,12 +50,11 @@ router.get('/checkout', (req, res, next) => {
message: common.clearSessionValue(req.session, 'message'), message: common.clearSessionValue(req.session, 'message'),
messageType: common.clearSessionValue(req.session, 'messageType'), messageType: common.clearSessionValue(req.session, 'messageType'),
helpers: req.handlebars.helpers, helpers: req.handlebars.helpers,
showFooter: 'showFooter', showFooter: 'showFooter'
menu: common.getMenu()
}); });
}); });
router.get('/pay', (req, res, next) => { router.get('/pay', async (req, res, next) => {
let config = common.getConfig(); let config = common.getConfig();
// if there is no items in the cart then render a failure // if there is no items in the cart then render a failure
@ -78,8 +77,7 @@ router.get('/pay', (req, res, next) => {
message: common.clearSessionValue(req.session, 'message'), message: common.clearSessionValue(req.session, 'message'),
messageType: common.clearSessionValue(req.session, 'messageType'), messageType: common.clearSessionValue(req.session, 'messageType'),
helpers: req.handlebars.helpers, helpers: req.handlebars.helpers,
showFooter: 'showFooter', showFooter: 'showFooter'
menu: common.getMenu()
}); });
}); });
@ -113,7 +111,7 @@ router.get('/product/:id', (req, res) => {
} }
// show the view // show the view
common.getImages(result._id, req, res, (images) => { common.getImages(result._id, req, res, async (images) => {
res.render(config.themeViews + 'product', { res.render(config.themeViews + 'product', {
title: result.productTitle, title: result.productTitle,
result: result, result: result,
@ -129,7 +127,7 @@ router.get('/product/:id', (req, res) => {
messageType: common.clearSessionValue(req.session, 'messageType'), messageType: common.clearSessionValue(req.session, 'messageType'),
helpers: req.handlebars.helpers, helpers: req.handlebars.helpers,
showFooter: 'showFooter', showFooter: 'showFooter',
menu: common.getMenu() menu: common.sortMenu(await common.getMenu(db))
}); });
}); });
} }
@ -240,6 +238,7 @@ router.post('/login_action', (req, res) => {
// search products // search products
router.get('/search/:searchTerm/:pageNum?', (req, res) => { router.get('/search/:searchTerm/:pageNum?', (req, res) => {
let db = req.app.db;
let searchTerm = req.params.searchTerm; let searchTerm = req.params.searchTerm;
let productsIndex = req.app.productsIndex; let productsIndex = req.app.productsIndex;
let config = common.getConfig(); let config = common.getConfig();
@ -256,7 +255,7 @@ router.get('/search/:searchTerm/:pageNum?', (req, res) => {
} }
// we search on the lunr indexes // we search on the lunr indexes
getData(req, pageNum, {_id: {$in: lunrIdArray}}, (err, results) => { getData(req, pageNum, {_id: {$in: lunrIdArray}}, async (err, results) => {
if(err){ if(err){
console.error(colors.red('Error searching for products', err)); console.error(colors.red('Error searching for products', err));
} }
@ -276,7 +275,7 @@ router.get('/search/:searchTerm/:pageNum?', (req, res) => {
pageNum: pageNum, pageNum: pageNum,
paginateUrl: 'search', paginateUrl: 'search',
config: config, config: config,
menu: common.getMenu(), menu: common.sortMenu(await common.getMenu(db)),
helpers: req.handlebars.helpers, helpers: req.handlebars.helpers,
showFooter: 'showFooter' showFooter: 'showFooter'
}); });
@ -285,6 +284,7 @@ router.get('/search/:searchTerm/:pageNum?', (req, res) => {
// search products // search products
router.get('/category/:cat/:pageNum?', (req, res) => { router.get('/category/:cat/:pageNum?', (req, res) => {
let db = req.app.db;
let searchTerm = req.params.cat; let searchTerm = req.params.cat;
let productsIndex = req.app.productsIndex; let productsIndex = req.app.productsIndex;
let config = common.getConfig(); let config = common.getConfig();
@ -292,18 +292,16 @@ router.get('/category/:cat/:pageNum?', (req, res) => {
let lunrIdArray = []; let lunrIdArray = [];
productsIndex.search(searchTerm).forEach((id) => { productsIndex.search(searchTerm).forEach((id) => {
lunrIdArray.push(common.getId(id.ref)) lunrIdArray.push(common.getId(id.ref));
}); });
let menuLink = _.find(common.getMenu().items, (obj) => { return obj.link === searchTerm; });
let pageNum = 1; let pageNum = 1;
if(req.params.pageNum){ if(req.params.pageNum){
pageNum = req.params.pageNum; pageNum = req.params.pageNum;
} }
// we search on the lunr indexes // we search on the lunr indexes
getData(req, pageNum, {_id: {$in: lunrIdArray}}, (err, results) => { getData(req, pageNum, {_id: {$in: lunrIdArray}}, async (err, results) => {
if(err){ if(err){
console.error(colors.red('Error getting products for category', err)); console.error(colors.red('Error getting products for category', err));
} }
@ -321,10 +319,10 @@ router.get('/category/:cat/:pageNum?', (req, res) => {
productsPerPage: numberProducts, productsPerPage: numberProducts,
totalProductCount: results.totalProducts, totalProductCount: results.totalProducts,
pageNum: pageNum, pageNum: pageNum,
menuLink: menuLink, menuLink: _.find(common.sortMenu(await common.getMenu(db)).items, (obj) => { return obj.link === searchTerm; }),
paginateUrl: 'category', paginateUrl: 'category',
config: config, config: config,
menu: common.getMenu(), menu: common.sortMenu(await common.getMenu(db)),
helpers: req.handlebars.helpers, helpers: req.handlebars.helpers,
showFooter: 'showFooter' showFooter: 'showFooter'
}); });
@ -365,10 +363,11 @@ router.get('/sitemap.xml', (req, res, next) => {
}); });
router.get('/page/:pageNum', (req, res, next) => { router.get('/page/:pageNum', (req, res, next) => {
let db = req.app.db;
let config = common.getConfig(); let config = common.getConfig();
let numberProducts = config.productsPerPage ? config.productsPerPage : 6; let numberProducts = config.productsPerPage ? config.productsPerPage : 6;
getData(req, req.params.pageNum, {}, (err, results) => { getData(req, req.params.pageNum, {}, async (err, results) => {
if(err){ if(err){
console.error(colors.red('Error getting products for page', err)); console.error(colors.red('Error getting products for page', err));
} }
@ -388,7 +387,7 @@ router.get('/page/:pageNum', (req, res, next) => {
paginateUrl: 'page', paginateUrl: 'page',
helpers: req.handlebars.helpers, helpers: req.handlebars.helpers,
showFooter: 'showFooter', showFooter: 'showFooter',
menu: common.getMenu() menu: common.sortMenu(await common.getMenu(db))
}); });
}); });
}); });
@ -400,7 +399,7 @@ router.get('/:page?', (req, res, next) => {
// if no page is specified, just render page 1 of the cart // if no page is specified, just render page 1 of the cart
if(!req.params.page){ if(!req.params.page){
getData(req, 1, {}, (err, results) => { getData(req, 1, {}, async (err, results) => {
if(err){ if(err){
console.error(colors.red('Error getting products for page', err)); console.error(colors.red('Error getting products for page', err));
} }
@ -420,7 +419,7 @@ router.get('/:page?', (req, res, next) => {
paginateUrl: 'page', paginateUrl: 'page',
helpers: req.handlebars.helpers, helpers: req.handlebars.helpers,
showFooter: 'showFooter', showFooter: 'showFooter',
menu: common.getMenu() menu: common.sortMenu(await common.getMenu(db))
}); });
}); });
}else{ }else{
@ -429,7 +428,7 @@ router.get('/:page?', (req, res, next) => {
return; return;
} }
// lets look for a page // lets look for a page
db.pages.findOne({pageSlug: req.params.page, pageEnabled: 'true'}, (err, page) => { db.pages.findOne({pageSlug: req.params.page, pageEnabled: 'true'}, async (err, page) => {
if(err){ if(err){
console.error(colors.red('Error getting page', err)); console.error(colors.red('Error getting page', err));
} }
@ -446,7 +445,7 @@ router.get('/:page?', (req, res, next) => {
metaDescription: common.getConfig().cartTitle + ' - ' + page, metaDescription: common.getConfig().cartTitle + ' - ' + page,
helpers: req.handlebars.helpers, helpers: req.handlebars.helpers,
showFooter: 'showFooter', showFooter: 'showFooter',
menu: common.getMenu() menu: common.sortMenu(await common.getMenu(db))
}); });
}else{ }else{
res.status(404).render('error', { res.status(404).render('error', {
@ -455,9 +454,8 @@ router.get('/:page?', (req, res, next) => {
message: '404 Error - Page not found', message: '404 Error - Page not found',
helpers: req.handlebars.helpers, helpers: req.handlebars.helpers,
showFooter: 'showFooter', showFooter: 'showFooter',
menu: common.getMenu() menu: common.sortMenu(await common.getMenu(db))
} });
);
} }
}); });
} }

View File

@ -10,7 +10,7 @@
<th>Link</th> <th>Link</th>
<th></th> <th></th>
<tbody id="draggable_list"> <tbody id="draggable_list">
{{#each menu}} {{#each menu.items}}
<tr class="drag-row"> <tr class="drag-row">
<form method="post" action="/admin/settings/menu/update"> <form method="post" action="/admin/settings/menu/update">
<input type="hidden" class="navId" name="navId" value="{{title}}"> <input type="hidden" class="navId" name="navId" value="{{title}}">
@ -24,7 +24,7 @@
<td class="col-md-2"> <td class="col-md-2">
<a class="btn btn-danger btn-sm" onclick="return confirm('Are you sure?');" href="/admin/settings/menu/delete/{{@key}}"> <a class="btn btn-danger btn-sm" onclick="return confirm('Are you sure?');" href="/admin/settings/menu/delete/{{@key}}">
<i class="fa fa-trash-o"></i> <i class="fa fa-trash-o"></i>
</a> </a>
<button type="submit" class="btn btn-success btn-sm"> <button type="submit" class="btn btn-success btn-sm">
<i class="fa fa-floppy-o"></i> <i class="fa fa-floppy-o"></i>
</button> </button>
@ -47,14 +47,14 @@
</button> </button>
</td> </td>
</form> </form>
</tr> </tr>
</tbody> </tbody>
</table> </table>
<p class="text-muted"> <p class="text-muted">
Here you can setup a menu to displayed on your shopping cart. You can use this menu to filter your products by specifying a keyword in the Here you can setup a menu to displayed on your shopping cart. You can use this menu to filter your products by specifying a keyword in the
"link" field. Eg: To show products with a keyword (or tag) of boots you would set the menu field to "Backpacks" and a link value "backpack". "link" field. Eg: To show products with a keyword (or tag) of boots you would set the menu field to "Backpacks" and a link value "backpack".
You can also use this menu to link to static pages, Eg: shipping, returns, help, about, contact etc. You can also use this menu to link to static pages, Eg: shipping, returns, help, about, contact etc.
</p> </p>
</div> </div>
</div> </div>
</div> </div>