diff --git a/lib/auth.js b/lib/auth.js index 95f49fd..ff0c9be 100644 --- a/lib/auth.js +++ b/lib/auth.js @@ -10,9 +10,9 @@ const restrictedRoutes = [ { route: '/admin/product/published_state', response: 'json' }, { route: '/admin/product/setasmainimage', response: 'json' }, { route: '/admin/product/deleteimage', response: 'json' }, + { route: '/admin/product/removeoption', response: 'json' }, { route: '/admin/order/statusupdate', response: 'json' }, { route: '/admin/settings/update', response: 'json' }, - { route: '/admin/settings/option/remove', response: 'json' }, { route: '/admin/settings/pages/new', response: 'redirect' }, { route: '/admin/settings/pages/edit/:page', response: 'redirect' }, { route: '/admin/settings/pages/update', response: 'json' }, diff --git a/lib/common.js b/lib/common.js index 649f5bb..ccd614e 100755 --- a/lib/common.js +++ b/lib/common.js @@ -147,41 +147,40 @@ const getThemes = () => { return fs.readdirSync(path.join(__dirname, '../', 'views', 'themes')).filter(file => fs.statSync(path.join(path.join(__dirname, '../', 'views', 'themes'), file)).isDirectory()); }; -const getImages = (dir, req, res, callback) => { +const getImages = async (dir, req, res, callback) => { const db = req.app.db; - db.products.findOne({ _id: getId(dir) }, (err, product) => { - if(err){ - console.error(colors.red('Error getting images', err)); - } + const product = await db.products.findOne({ _id: getId(dir) }); + if(!product){ + return[]; + } - // loop files in /public/uploads/ - glob('public/uploads/' + product.productPermalink + '/**', { nosort: true }, (er, files) => { - // sort array - files.sort(); + // loop files in /public/uploads/ + const files = await glob.sync(`public/uploads/${product.productPermalink}/**`, { nosort: true }); - // declare the array of objects - const fileList = []; + // sort array + files.sort(); - // 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; - } - // push the file object into the array - fileList.push(file); - } + // 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; } - callback(fileList); - }); - }); + // push the file object into the array + fileList.push(file); + } + } + return fileList; }; const getConfig = () => { diff --git a/routes/admin.js b/routes/admin.js index 0cf07d6..36fd266 100644 --- a/routes/admin.js +++ b/routes/admin.js @@ -26,97 +26,81 @@ router.get('/admin/logout', (req, res) => { }); // login form -router.get('/admin/login', (req, res) => { +router.get('/admin/login', async (req, res) => { const db = req.app.db; - db.users.count({}, (err, userCount) => { - if(err){ - // if there are no users set the "needsSetup" session - req.session.needsSetup = true; - res.redirect('/admin/setup'); - } - // we check for a user. If one exists, redirect to login form otherwise setup - if(userCount > 0){ - // set needsSetup to false as a user exists - req.session.needsSetup = false; - res.render('login', { - title: 'Login', - referringUrl: req.header('Referer'), - config: req.app.config, - message: common.clearSessionValue(req.session, 'message'), - messageType: common.clearSessionValue(req.session, 'messageType'), - helpers: req.handlebars.helpers, - showFooter: 'showFooter' - }); - }else{ - // if there are no users set the "needsSetup" session - req.session.needsSetup = true; - res.redirect('/admin/setup'); - } - }); + const userCount = await db.users.count({}); + // we check for a user. If one exists, redirect to login form otherwise setup + if(userCount && userCount > 0){ + // set needsSetup to false as a user exists + req.session.needsSetup = false; + res.render('login', { + title: 'Login', + referringUrl: req.header('Referer'), + config: req.app.config, + message: common.clearSessionValue(req.session, 'message'), + messageType: common.clearSessionValue(req.session, 'messageType'), + helpers: req.handlebars.helpers, + showFooter: 'showFooter' + }); + }else{ + // if there are no users set the "needsSetup" session + req.session.needsSetup = true; + res.redirect('/admin/setup'); + } }); // login the user and check the password -router.post('/admin/login_action', (req, res) => { +router.post('/admin/login_action', async (req, res) => { const db = req.app.db; - db.users.findOne({ userEmail: common.mongoSanitize(req.body.email) }, (err, user) => { - if(err){ - res.status(400).json({ message: 'A user with that email does not exist.' }); + const user = await db.users.findOne({ userEmail: common.mongoSanitize(req.body.email) }); + if(!user || user === null){ + res.status(400).json({ message: 'A user with that email does not exist.' }); + return; + } + + // we have a user under that email so we compare the password + bcrypt.compare(req.body.password, user.userPassword) + .then((result) => { + if(result){ + req.session.user = req.body.email; + req.session.usersName = user.usersName; + req.session.userId = user._id.toString(); + req.session.isAdmin = user.isAdmin; + res.status(200).json({ message: 'Login successful' }); return; } - - // check if user exists with that email - if(user === undefined || user === null){ - res.status(400).json({ message: 'A user with that email does not exist.' }); - }else{ - // we have a user under that email so we compare the password - bcrypt.compare(req.body.password, user.userPassword) - .then((result) => { - if(result){ - req.session.user = req.body.email; - req.session.usersName = user.usersName; - req.session.userId = user._id.toString(); - req.session.isAdmin = user.isAdmin; - res.status(200).json({ message: 'Login successful' }); - }else{ - // password is not correct - res.status(400).json({ message: 'Access denied. Check password and try again.' }); - } - }); - } + // password is not correct + res.status(400).json({ message: 'Access denied. Check password and try again.' }); }); }); // setup form is shown when there are no users setup in the DB -router.get('/admin/setup', (req, res) => { +router.get('/admin/setup', async (req, res) => { const db = req.app.db; - db.users.count({}, (err, userCount) => { - if(err){ - console.error(colors.red('Error getting users for setup', err)); - } - // dont allow the user to "re-setup" if a user exists. - // set needsSetup to false as a user exists - req.session.needsSetup = false; - if(userCount === 0){ - req.session.needsSetup = true; - res.render('setup', { - title: 'Setup', - config: req.app.config, - helpers: req.handlebars.helpers, - message: common.clearSessionValue(req.session, 'message'), - messageType: common.clearSessionValue(req.session, 'messageType'), - showFooter: 'showFooter' - }); - }else{ - res.redirect('/admin/login'); - } - }); + const userCount = await db.users.count({}); + // dont allow the user to "re-setup" if a user exists. + // set needsSetup to false as a user exists + req.session.needsSetup = false; + if(userCount && userCount === 0){ + req.session.needsSetup = true; + res.render('setup', { + title: 'Setup', + config: req.app.config, + helpers: req.handlebars.helpers, + message: common.clearSessionValue(req.session, 'message'), + messageType: common.clearSessionValue(req.session, 'messageType'), + showFooter: 'showFooter' + }); + return; + } + res.redirect('/admin/login'); }); // insert a user -router.post('/admin/setup_action', (req, res) => { +router.post('/admin/setup_action', async (req, res) => { const db = req.app.db; const doc = { @@ -127,29 +111,24 @@ router.post('/admin/setup_action', (req, res) => { }; // check for users - db.users.count({}, (err, userCount) => { - if(err){ - console.info(err.stack); - } - if(userCount === 0){ - // email is ok to be used. - db.users.insert(doc, (err, doc) => { - // show the view - if(err){ - console.error(colors.red('Failed to insert user: ' + err)); - req.session.message = 'Setup failed'; - req.session.messageType = 'danger'; - res.redirect('/admin/setup'); - }else{ - req.session.message = 'User account inserted'; - req.session.messageType = 'success'; - res.redirect('/admin/login'); - } - }); - }else{ + const userCount = await db.users.count({}); + if(userCount && userCount === 0){ + // email is ok to be used. + try{ + await db.users.insert(doc); + req.session.message = 'User account inserted'; + req.session.messageType = 'success'; res.redirect('/admin/login'); + return; + }catch(ex){ + console.error(colors.red('Failed to insert user: ' + ex)); + req.session.message = 'Setup failed'; + req.session.messageType = 'danger'; + res.redirect('/admin/setup'); + return; } - }); + } + res.redirect('/admin/login'); }); // settings update @@ -200,33 +179,6 @@ router.post('/admin/settings/update', restrict, checkAccess, (req, res) => { res.status(400).json({ message: 'Permission denied' }); }); -// settings update -router.post('/admin/settings/option/remove', restrict, checkAccess, (req, res) => { - const db = req.app.db; - db.products.findOne({ _id: common.getId(req.body.productId) }, (err, product) => { - if(err){ - console.info(err.stack); - } - if(product && product.productOptions){ - const optJson = JSON.parse(product.productOptions); - delete optJson[req.body.optName]; - - db.products.update({ _id: common.getId(req.body.productId) }, { $set: { productOptions: JSON.stringify(optJson) } }, (err, numReplaced) => { - if(err){ - console.info(err.stack); - } - if(numReplaced.result.nModified === 1){ - res.status(200).json({ message: 'Option successfully removed' }); - }else{ - res.status(400).json({ message: 'Failed to remove option. Please try again.' }); - } - }); - }else{ - res.status(400).json({ message: 'Product not found. Try saving before removing.' }); - } - }); -}); - // settings update router.get('/admin/settings/menu', restrict, async (req, res) => { const db = req.app.db; @@ -243,24 +195,20 @@ router.get('/admin/settings/menu', restrict, async (req, res) => { }); // settings page list -router.get('/admin/settings/pages', restrict, (req, res) => { +router.get('/admin/settings/pages', restrict, async (req, res) => { const db = req.app.db; - db.pages.find({}).toArray(async (err, pages) => { - if(err){ - console.info(err.stack); - } + const pages = await db.pages.find({}).toArray(); - res.render('settings_pages', { - title: 'Static pages', - pages: pages, - session: req.session, - admin: true, - message: common.clearSessionValue(req.session, 'message'), - messageType: common.clearSessionValue(req.session, 'messageType'), - helpers: req.handlebars.helpers, - config: req.app.config, - menu: common.sortMenu(await common.getMenu(db)) - }); + res.render('settings_pages', { + title: 'Static pages', + pages: pages, + session: req.session, + admin: true, + message: common.clearSessionValue(req.session, 'message'), + messageType: common.clearSessionValue(req.session, 'messageType'), + helpers: req.handlebars.helpers, + config: req.app.config, + menu: common.sortMenu(await common.getMenu(db)) }); }); @@ -282,43 +230,38 @@ router.get('/admin/settings/pages/new', restrict, checkAccess, async (req, res) }); // settings pages editor -router.get('/admin/settings/pages/edit/:page', restrict, checkAccess, (req, res) => { +router.get('/admin/settings/pages/edit/:page', restrict, checkAccess, async (req, res) => { const db = req.app.db; - db.pages.findOne({ _id: common.getId(req.params.page) }, async (err, page) => { - if(err){ - console.info(err.stack); - } - // page found - const menu = common.sortMenu(await common.getMenu(db)); - if(page){ - res.render('settings_page_edit', { - title: 'Static pages', - page: page, - button_text: 'Update', - session: req.session, - admin: true, - message: common.clearSessionValue(req.session, 'message'), - messageType: common.clearSessionValue(req.session, 'messageType'), - helpers: req.handlebars.helpers, - config: req.app.config, - menu - }); - }else{ - // 404 it! - res.status(404).render('error', { - title: '404 Error - Page not found', - config: req.app.config, - message: '404 Error - Page not found', - helpers: req.handlebars.helpers, - showFooter: 'showFooter', - menu - }); - } + const page = await db.pages.findOne({ _id: common.getId(req.params.page) }); + const menu = common.sortMenu(await common.getMenu(db)); + if(!page){ + res.status(404).render('error', { + title: '404 Error - Page not found', + config: req.app.config, + message: '404 Error - Page not found', + helpers: req.handlebars.helpers, + showFooter: 'showFooter', + menu + }); + return; + } + + res.render('settings_page_edit', { + title: 'Static pages', + page: page, + button_text: 'Update', + session: req.session, + admin: true, + message: common.clearSessionValue(req.session, 'message'), + messageType: common.clearSessionValue(req.session, 'messageType'), + helpers: req.handlebars.helpers, + config: req.app.config, + menu }); }); // settings update page -router.post('/admin/settings/pages/update', restrict, checkAccess, (req, res) => { +router.post('/admin/settings/pages/update', restrict, checkAccess, async (req, res) => { const db = req.app.db; const doc = { @@ -330,47 +273,43 @@ router.post('/admin/settings/pages/update', restrict, checkAccess, (req, res) => if(req.body.page_id){ // existing page - db.pages.findOne({ _id: common.getId(req.body.page_id) }, (err, page) => { - if(err){ - console.info(err.stack); - } - if(page){ - db.pages.update({ _id: common.getId(req.body.page_id) }, { $set: doc }, {}, (err, numReplaced) => { - if(err){ - console.info(err.stack); - } - res.status(200).json({ message: 'Page updated successfully', page_id: req.body.page_id }); - }); - }else{ - res.status(400).json({ message: 'Page not found' }); - } - }); + const page = await db.pages.findOne({ _id: common.getId(req.body.page_id) }); + if(!page){ + res.status(400).json({ message: 'Page not found' }); + } + + try{ + await db.pages.update({ _id: common.getId(req.body.page_id) }, { $set: doc }, {}); + res.status(200).json({ message: 'Page updated successfully', page_id: req.body.page_id }); + }catch(ex){ + res.status(400).json({ message: 'Error updating page. Please try again.' }); + } }else{ // insert page - db.pages.insert(doc, (err, newDoc) => { - if(err){ - res.status(400).json({ message: 'Error creating page. Please try again.' }); - }else{ - res.status(200).json({ message: 'New page successfully created', page_id: newDoc._id }); - } - }); + try{ + const newDoc = await db.pages.insert(doc); + res.status(200).json({ message: 'New page successfully created', page_id: newDoc._id }); + return; + }catch(ex){ + res.status(400).json({ message: 'Error creating page. Please try again.' }); + } } }); // settings delete page -router.get('/admin/settings/pages/delete/:page', restrict, checkAccess, (req, res) => { +router.get('/admin/settings/pages/delete/:page', restrict, checkAccess, async (req, res) => { const db = req.app.db; - db.pages.remove({ _id: common.getId(req.params.page) }, {}, (err, numRemoved) => { - if(err){ - req.session.message = 'Error deleting page. Please try again.'; - req.session.messageType = 'danger'; - res.redirect('/admin/settings/pages'); - return; - } + try{ + await db.pages.remove({ _id: common.getId(req.params.page) }, {}); req.session.message = 'Page successfully deleted'; req.session.messageType = 'success'; res.redirect('/admin/settings/pages'); - }); + return; + }catch(ex){ + req.session.message = 'Error deleting page. Please try again.'; + req.session.messageType = 'danger'; + res.redirect('/admin/settings/pages'); + } }); // new menu item @@ -414,7 +353,7 @@ router.post('/admin/settings/menu/save_order', restrict, checkAccess, (req, res) }); // validate the permalink -router.post('/admin/api/validate_permalink', (req, res) => { +router.post('/admin/api/validate_permalink', async (req, res) => { // if doc id is provided it checks for permalink in any products other that one provided, // else it just checks for any products with that permalink const db = req.app.db; @@ -426,21 +365,17 @@ router.post('/admin/api/validate_permalink', (req, res) => { query = { productPermalink: req.body.permalink, _id: { $ne: common.getId(req.body.docId) } }; } - db.products.count(query, (err, products) => { - if(err){ - console.info(err.stack); - } - if(products > 0){ - res.status(400).json({ message: 'Permalink already exists' }); - }else{ - res.status(200).json({ message: 'Permalink validated successfully' }); - } - }); + const products = await db.products.count(query); + if(products && products > 0){ + res.status(400).json({ message: 'Permalink already exists' }); + return; + } + res.status(200).json({ message: 'Permalink validated successfully' }); }); // upload the file const upload = multer({ dest: 'public/uploads/' }); -router.post('/admin/file/upload', restrict, checkAccess, upload.single('upload_file'), (req, res, next) => { +router.post('/admin/file/upload', restrict, checkAccess, upload.single('upload_file'), async (req, res, next) => { const db = req.app.db; if(req.file){ @@ -462,59 +397,53 @@ router.post('/admin/file/upload', restrict, checkAccess, upload.single('upload_f } // get the product form the DB - db.products.findOne({ _id: common.getId(req.body.productId) }, (err, product) => { - if(err){ - console.info(err.stack); - // delete the temp file. - fs.unlinkSync(file.path); - - // Redirect to error - req.session.message = 'File upload error. Please try again.'; - req.session.messageType = 'danger'; - res.redirect('/admin/product/edit/' + req.body.productId); - return; - } - - const productPath = product.productPermalink; - const uploadDir = path.join('public/uploads', productPath); - - // Check directory and create (if needed) - common.checkDirectorySync(uploadDir); - - const source = fs.createReadStream(file.path); - const dest = fs.createWriteStream(path.join(uploadDir, file.originalname.replace(/ /g, '_'))); - - // save the new file - source.pipe(dest); - source.on('end', () => { }); - + const product = await db.products.findOne({ _id: common.getId(req.body.productId) }); + if(!product){ // delete the temp file. fs.unlinkSync(file.path); - const imagePath = path.join('/uploads', productPath, file.originalname.replace(/ /g, '_')); + // Redirect to error + req.session.message = 'File upload error. Please try again.'; + req.session.messageType = 'danger'; + res.redirect('/admin/product/edit/' + req.body.productId); + return; + } - // if there isn't a product featured image, set this one - if(!product.productImage){ - db.products.update({ _id: common.getId(req.body.productId) }, { $set: { productImage: imagePath } }, { multi: false }, (err, numReplaced) => { - if(err){ - console.info(err.stack); - } - req.session.message = 'File uploaded successfully'; - req.session.messageType = 'success'; - res.redirect('/admin/product/edit/' + req.body.productId); - }); - }else{ - req.session.message = 'File uploaded successfully'; - req.session.messageType = 'success'; - res.redirect('/admin/product/edit/' + req.body.productId); - } - }); - }else{ - // Redirect to error - req.session.message = 'File upload error. Please select a file.'; - req.session.messageType = 'danger'; + const productPath = product.productPermalink; + const uploadDir = path.join('public/uploads', productPath); + + // Check directory and create (if needed) + common.checkDirectorySync(uploadDir); + + const source = fs.createReadStream(file.path); + const dest = fs.createWriteStream(path.join(uploadDir, file.originalname.replace(/ /g, '_'))); + + // save the new file + source.pipe(dest); + source.on('end', () => { }); + + // delete the temp file. + fs.unlinkSync(file.path); + + const imagePath = path.join('/uploads', productPath, file.originalname.replace(/ /g, '_')); + + // if there isn't a product featured image, set this one + if(!product.productImage){ + await db.products.update({ _id: common.getId(req.body.productId) }, { $set: { productImage: imagePath } }, { multi: false }); + req.session.message = 'File uploaded successfully'; + req.session.messageType = 'success'; + res.redirect('/admin/product/edit/' + req.body.productId); + return; + } + req.session.message = 'File uploaded successfully'; + req.session.messageType = 'success'; res.redirect('/admin/product/edit/' + req.body.productId); + return; } + // Redirect to error + req.session.message = 'File upload error. Please select a file.'; + req.session.messageType = 'danger'; + res.redirect('/admin/product/edit/' + req.body.productId); }); // delete a file via ajax request @@ -526,66 +455,65 @@ router.post('/admin/testEmail', restrict, (req, res) => { }); // delete a file via ajax request -router.post('/admin/file/delete', restrict, checkAccess, (req, res) => { +router.post('/admin/file/delete', restrict, checkAccess, async (req, res) => { req.session.message = null; req.session.messageType = null; - fs.unlink('public/' + req.body.img, (err) => { - if(err){ - console.error(colors.red('File delete error: ' + err)); - res.writeHead(400, { 'Content-Type': 'application/text' }); - res.end('Failed to delete file: ' + err); - }else{ - res.writeHead(200, { 'Content-Type': 'application/text' }); - res.end('File deleted successfully'); - } - }); + try{ + await fs.unlinkSync('public/' + req.body.img); + res.writeHead(200, { 'Content-Type': 'application/text' }); + res.end('File deleted successfully'); + }catch(ex){ + console.error(colors.red('File delete error: ' + ex)); + res.writeHead(400, { 'Content-Type': 'application/text' }); + res.end('Failed to delete file: ' + ex); + } }); -router.get('/admin/files', restrict, (req, res) => { +router.get('/admin/files', restrict, async (req, res) => { // loop files in /public/uploads/ - glob('public/uploads/**', { nosort: true }, (er, files) => { - // sort array - files.sort(); + const files = await glob.sync('public/uploads/**', { nosort: true }); - // declare the array of objects - const fileList = []; - const dirList = []; + // sort array + files.sort(); - // 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) - }; + // declare the array of objects + const fileList = []; + const dirList = []; - // push the file object into the array - fileList.push(file); - }else{ - const dir = { - id: i, - path: files[i].substring(6) - }; + // 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) + }; - // push the dir object into the array - dirList.push(dir); - } + // push the file object into the array + fileList.push(file); + }else{ + const dir = { + id: i, + path: files[i].substring(6) + }; + + // push the dir object into the array + dirList.push(dir); } + } - // render the files route - res.render('files', { - title: 'Files', - files: fileList, - admin: true, - dirs: dirList, - session: req.session, - config: common.get(), - message: common.clearSessionValue(req.session, 'message'), - messageType: common.clearSessionValue(req.session, 'messageType') - }); + // render the files route + res.render('files', { + title: 'Files', + files: fileList, + admin: true, + dirs: dirList, + session: req.session, + config: common.get(), + message: common.clearSessionValue(req.session, 'message'), + messageType: common.clearSessionValue(req.session, 'messageType') }); }); diff --git a/routes/index.js b/routes/index.js index f4ece32..f3e762b 100644 --- a/routes/index.js +++ b/routes/index.js @@ -22,49 +22,49 @@ router.get('/payment/:orderId', async (req, res, next) => { const db = req.app.db; const config = req.app.config; - // render the payment complete message - db.orders.findOne({ _id: getId(req.params.orderId) }, async (err, order) => { - if(err){ - console.info(err.stack); - } + // Get the order + const order = await db.orders.findOne({ _id: getId(req.params.orderId) }); + if(!order){ + res.render('error', { title: 'Not found', message: 'Order not found', helpers: req.handlebars.helpers, config }); + return; + } - // If stock management is turned on payment approved update stock level - if(config.trackStock && req.session.paymentApproved){ - order.orderProducts.forEach(async (product) => { - const dbProduct = await db.products.findOne({ _id: getId(product.productId) }); - let newStockLevel = dbProduct.productStock - product.quantity; - if(newStockLevel < 1){ - newStockLevel = 0; + // If stock management is turned on payment approved update stock level + if(config.trackStock && req.session.paymentApproved){ + order.orderProducts.forEach(async (product) => { + const dbProduct = await db.products.findOne({ _id: getId(product.productId) }); + let newStockLevel = dbProduct.productStock - product.quantity; + if(newStockLevel < 1){ + newStockLevel = 0; + } + + // Update product stock + await db.products.update({ + _id: getId(product.productId) + }, { + $set: { + productStock: newStockLevel } - - // Update product stock - await db.products.update({ - _id: getId(product.productId) - }, { - $set: { - productStock: newStockLevel - } - }, { multi: false }); - }); - } - - // If hooks are configured, send hook - if(config.orderHook){ - await hooker(order); - }; - - res.render(`${config.themeViews}payment_complete`, { - title: 'Payment complete', - config: req.app.config, - session: req.session, - pageCloseBtn: showCartCloseBtn('payment'), - result: order, - message: clearSessionValue(req.session, 'message'), - messageType: clearSessionValue(req.session, 'messageType'), - helpers: req.handlebars.helpers, - showFooter: 'showFooter', - menu: sortMenu(await getMenu(db)) + }, { multi: false }); }); + } + + // If hooks are configured, send hook + if(config.orderHook){ + await hooker(order); + }; + + res.render(`${config.themeViews}payment_complete`, { + title: 'Payment complete', + config: req.app.config, + session: req.session, + pageCloseBtn: showCartCloseBtn('payment'), + result: order, + message: clearSessionValue(req.session, 'message'), + messageType: clearSessionValue(req.session, 'messageType'), + helpers: req.handlebars.helpers, + showFooter: 'showFooter', + menu: sortMenu(await getMenu(db)) }); }); @@ -135,47 +135,46 @@ router.get('/cartPartial', (req, res) => { }); // show an individual product -router.get('/product/:id', (req, res) => { +router.get('/product/:id', async (req, res) => { const db = req.app.db; const config = req.app.config; - db.products.findOne({ $or: [{ _id: getId(req.params.id) }, { productPermalink: req.params.id }] }, (err, result) => { - // render 404 if page is not published - if(err){ - res.render('error', { title: 'Not found', message: 'Product not found', helpers: req.handlebars.helpers, config }); - } - if(err || result == null || result.productPublished === false){ - res.render('error', { title: 'Not found', message: 'Product not found', helpers: req.handlebars.helpers, config }); - }else{ - const productOptions = result.productOptions; + const product = await db.products.findOne({ $or: [{ _id: getId(req.params.id) }, { productPermalink: req.params.id }] }); + if(!product){ + res.render('error', { title: 'Not found', message: 'Order not found', helpers: req.handlebars.helpers, config }); + return; + } + if(product.productPublished === false){ + res.render('error', { title: 'Not found', message: 'Product not found', helpers: req.handlebars.helpers, config }); + return; + } + const productOptions = product.productOptions; - // If JSON query param return json instead - if(req.query.json === 'true'){ - res.status(200).json(result); - return; - } + // If JSON query param return json instead + if(req.query.json === 'true'){ + res.status(200).json(product); + return; + } - // show the view - getImages(result._id, req, res, async (images) => { - res.render(`${config.themeViews}product`, { - title: result.productTitle, - result: result, - productOptions: productOptions, - images: images, - productDescription: result.productDescription, - metaDescription: config.cartTitle + ' - ' + result.productTitle, - pageCloseBtn: showCartCloseBtn('product'), - config: config, - session: req.session, - pageUrl: config.baseUrl + req.originalUrl, - message: clearSessionValue(req.session, 'message'), - messageType: clearSessionValue(req.session, 'messageType'), - helpers: req.handlebars.helpers, - showFooter: 'showFooter', - menu: sortMenu(await getMenu(db)) - }); - }); - } + // show the view + const images = await getImages(product._id, req, res); + + res.render(`${config.themeViews}product`, { + title: product.productTitle, + result: product, + productOptions: productOptions, + images: images, + productDescription: product.productDescription, + metaDescription: config.cartTitle + ' - ' + product.productTitle, + pageCloseBtn: showCartCloseBtn('product'), + config: config, + session: req.session, + pageUrl: config.baseUrl + req.originalUrl, + message: clearSessionValue(req.session, 'message'), + messageType: clearSessionValue(req.session, 'messageType'), + helpers: req.handlebars.helpers, + showFooter: 'showFooter', + menu: sortMenu(await getMenu(db)) }); }); @@ -187,39 +186,35 @@ router.post('/product/updatecart', (req, res, next) => { let hasError = false; let stockError = false; - async.eachSeries(cartItems, (cartItem, callback) => { + async.eachSeries(cartItems, async (cartItem, callback) => { const productQuantity = cartItem.itemQuantity ? cartItem.itemQuantity : 1; if(cartItem.itemQuantity === 0){ // quantity equals zero so we remove the item req.session.cart.splice(cartItem.cartIndex, 1); callback(null); }else{ - db.products.findOne({ _id: getId(cartItem.productId) }, (err, product) => { - if(err){ - console.error(colors.red('Error updating cart', err)); - } - if(product){ - // If stock management on check there is sufficient stock for this product - if(config.trackStock){ - if(productQuantity > product.productStock){ - hasError = true; - stockError = true; - callback(null); - return; - } - } - - const productPrice = parseFloat(product.productPrice).toFixed(2); - if(req.session.cart[cartItem.cartIndex]){ - req.session.cart[cartItem.cartIndex].quantity = productQuantity; - req.session.cart[cartItem.cartIndex].totalItemPrice = productPrice * productQuantity; + const product = await db.products.findOne({ _id: getId(cartItem.productId) }); + if(product){ + // If stock management on check there is sufficient stock for this product + if(config.trackStock){ + if(productQuantity > product.productStock){ + hasError = true; + stockError = true; callback(null); + return; } - }else{ - hasError = true; + } + + const productPrice = parseFloat(product.productPrice).toFixed(2); + if(req.session.cart[cartItem.cartIndex]){ + req.session.cart[cartItem.cartIndex].quantity = productQuantity; + req.session.cart[cartItem.cartIndex].totalItemPrice = productPrice * productQuantity; callback(null); } - }); + }else{ + hasError = true; + callback(null); + } } }, async () => { // update total cart amount @@ -289,7 +284,7 @@ router.post('/product/emptycart', async (req, res, next) => { }); // Add item to cart -router.post('/product/addtocart', (req, res, next) => { +router.post('/product/addtocart', async (req, res, next) => { const db = req.app.db; const config = req.app.config; let productQuantity = req.body.productQuantity ? parseInt(req.body.productQuantity) : 1; @@ -306,108 +301,102 @@ router.post('/product/addtocart', (req, res, next) => { } // Get the item from the DB - db.products.findOne({ _id: getId(req.body.productId) }, async (err, product) => { - if(err){ - console.error(colors.red('Error adding to cart', err)); - return res.status(400).json({ message: 'Error updating cart. Please try again.' }); - } + const product = await db.products.findOne({ _id: getId(req.body.productId) }); + // No product found + if(!product){ + return res.status(400).json({ message: 'Error updating cart. Please try again.' }); + } - // No product found - if(!product){ - return res.status(400).json({ message: 'Error updating cart. Please try again.' }); - } - - // If stock management on check there is sufficient stock for this product - if(config.trackStock && product.productStock){ - const stockHeld = await db.cart.aggregate( - { - $match: { - cart: { $elemMatch: { productId: product._id.toString() } } - } - }, - { $unwind: '$cart' }, - { - $group: { - _id: '$cart.productId', - sumHeld: { $sum: '$cart.quantity' } - } - }, - { - $project: { - sumHeld: 1 - } + // If stock management on check there is sufficient stock for this product + if(config.trackStock && product.productStock){ + const stockHeld = await db.cart.aggregate( + { + $match: { + cart: { $elemMatch: { productId: product._id.toString() } } } - ).toArray(); - - // If there is stock - if(stockHeld.length > 0){ - const totalHeld = _.find(stockHeld, { _id: product._id.toString() }).sumHeld; - const netStock = product.productStock - totalHeld; - - // Check there is sufficient stock - if(productQuantity > netStock){ - return res.status(400).json({ message: 'There is insufficient stock of this product.' }); + }, + { $unwind: '$cart' }, + { + $group: { + _id: '$cart.productId', + sumHeld: { $sum: '$cart.quantity' } + } + }, + { + $project: { + sumHeld: 1 } } + ).toArray(); + + // If there is stock + if(stockHeld.length > 0){ + const totalHeld = _.find(stockHeld, { _id: product._id.toString() }).sumHeld; + const netStock = product.productStock - totalHeld; + + // Check there is sufficient stock + if(productQuantity > netStock){ + return res.status(400).json({ message: 'There is insufficient stock of this product.' }); + } } + } - const productPrice = parseFloat(product.productPrice).toFixed(2); + const productPrice = parseFloat(product.productPrice).toFixed(2); - // Doc used to test if existing in the cart with the options. If not found, we add new. - let options = {}; - if(req.body.productOptions){ - options = JSON.parse(req.body.productOptions); - } - const findDoc = { - productId: req.body.productId, - options: options - }; + // Doc used to test if existing in the cart with the options. If not found, we add new. + let options = {}; + if(req.body.productOptions){ + options = JSON.parse(req.body.productOptions); + } + const findDoc = { + productId: req.body.productId, + options: options + }; - // if exists we add to the existing value - const cartIndex = _.findIndex(req.session.cart, findDoc); - let cartQuantity = 0; - if(cartIndex > -1){ - cartQuantity = parseInt(req.session.cart[cartIndex].quantity) + productQuantity; - req.session.cart[cartIndex].quantity = cartQuantity; - req.session.cart[cartIndex].totalItemPrice = productPrice * parseInt(req.session.cart[cartIndex].quantity); + // if exists we add to the existing value + const cartIndex = _.findIndex(req.session.cart, findDoc); + let cartQuantity = 0; + if(cartIndex > -1){ + cartQuantity = parseInt(req.session.cart[cartIndex].quantity) + productQuantity; + req.session.cart[cartIndex].quantity = cartQuantity; + req.session.cart[cartIndex].totalItemPrice = productPrice * parseInt(req.session.cart[cartIndex].quantity); + }else{ + // Doesnt exist so we add to the cart session + req.session.cartTotalItems = req.session.cartTotalItems + productQuantity; + + // Set the card quantity + cartQuantity = productQuantity; + + // new product deets + const productObj = {}; + productObj.productId = req.body.productId; + productObj.title = product.productTitle; + productObj.quantity = productQuantity; + productObj.totalItemPrice = productPrice * productQuantity; + productObj.options = options; + productObj.productImage = product.productImage; + productObj.productComment = productComment; + if(product.productPermalink){ + productObj.link = product.productPermalink; }else{ - // Doesnt exist so we add to the cart session - req.session.cartTotalItems = req.session.cartTotalItems + productQuantity; - - // Set the card quantity - cartQuantity = productQuantity; - - // new product deets - const productObj = {}; - productObj.productId = req.body.productId; - productObj.title = product.productTitle; - productObj.quantity = productQuantity; - productObj.totalItemPrice = productPrice * productQuantity; - productObj.options = options; - productObj.productImage = product.productImage; - productObj.productComment = productComment; - if(product.productPermalink){ - productObj.link = product.productPermalink; - }else{ - productObj.link = product._id; - } - - // merge into the current cart - req.session.cart.push(productObj); + productObj.link = product._id; } - // Update cart to the DB - await db.cart.update({ sessionId: req.session.id }, { - $set: { cart: req.session.cart } - }, { upsert: true }); + // merge into the current cart + req.session.cart.push(productObj); + } - // update total cart amount - updateTotalCartAmount(req, res); + // Update cart to the DB + await db.cart.update({ sessionId: req.session.id }, { + $set: { cart: req.session.cart } + }, { upsert: true }); - // update how many products in the shopping cart - req.session.cartTotalItems = req.session.cart.reduce((a, b) => +a + +b.quantity, 0); - return res.status(200).json({ message: 'Cart successfully updated', totalCartItems: req.session.cartTotalItems }); - }); + // update total cart amount + updateTotalCartAmount(req, res); + + // update how many products in the shopping cart + req.session.cartTotalItems = req.session.cart.reduce((a, b) => +a + +b.quantity, 0); + return res.status(200).json({ message: 'Cart successfully updated', totalCartItems: req.session.cartTotalItems }); }); // search products @@ -594,7 +583,7 @@ router.get('/page/:pageNum', (req, res, next) => { }); // The main entry point of the shop -router.get('/:page?', (req, res, next) => { +router.get('/:page?', async (req, res, next) => { const db = req.app.db; const config = req.app.config; const numberProducts = config.productsPerPage ? config.productsPerPage : 6; @@ -639,37 +628,33 @@ router.get('/:page?', (req, res, next) => { return; } // lets look for a page - db.pages.findOne({ pageSlug: req.params.page, pageEnabled: 'true' }, async (err, page) => { - if(err){ - console.error(colors.red('Error getting page', err)); - } - // if we have a page lets render it, else throw 404 - if(page){ - res.render(`${config.themeViews}page`, { - title: page.pageName, - page: page, - searchTerm: req.params.page, - session: req.session, - message: clearSessionValue(req.session, 'message'), - messageType: clearSessionValue(req.session, 'messageType'), - pageCloseBtn: showCartCloseBtn('page'), - config: req.app.config, - metaDescription: req.app.config.cartTitle + ' - ' + page, - helpers: req.handlebars.helpers, - showFooter: 'showFooter', - menu: sortMenu(await getMenu(db)) - }); - }else{ - res.status(404).render('error', { - title: '404 Error - Page not found', - config: req.app.config, - message: '404 Error - Page not found', - helpers: req.handlebars.helpers, - showFooter: 'showFooter', - menu: sortMenu(await getMenu(db)) - }); - } - }); + const page = db.pages.findOne({ pageSlug: req.params.page, pageEnabled: 'true' }); + // if we have a page lets render it, else throw 404 + if(page){ + res.render(`${config.themeViews}page`, { + title: page.pageName, + page: page, + searchTerm: req.params.page, + session: req.session, + message: clearSessionValue(req.session, 'message'), + messageType: clearSessionValue(req.session, 'messageType'), + pageCloseBtn: showCartCloseBtn('page'), + config: req.app.config, + metaDescription: req.app.config.cartTitle + ' - ' + page, + helpers: req.handlebars.helpers, + showFooter: 'showFooter', + menu: sortMenu(await getMenu(db)) + }); + }else{ + res.status(404).render('error', { + title: '404 Error - Page not found', + config: req.app.config, + message: '404 Error - Page not found', + helpers: req.handlebars.helpers, + showFooter: 'showFooter', + menu: sortMenu(await getMenu(db)) + }); + } } }); diff --git a/routes/product.js b/routes/product.js index 7b89ecd..2f35d87 100644 --- a/routes/product.js +++ b/routes/product.js @@ -9,27 +9,23 @@ const fs = require('fs'); const path = require('path'); const router = express.Router(); -router.get('/admin/products', restrict, (req, res, next) => { +router.get('/admin/products', restrict, async (req, res, next) => { const db = req.app.db; // get the top results - db.products.find({}).sort({ productAddedDate: -1 }).limit(10).toArray((err, topResults) => { - if(err){ - console.info(err.stack); - } - res.render('products', { - title: 'Cart', - top_results: topResults, - session: req.session, - admin: true, - config: req.app.config, - message: common.clearSessionValue(req.session, 'message'), - messageType: common.clearSessionValue(req.session, 'messageType'), - helpers: req.handlebars.helpers - }); + const topResults = await db.products.find({}).sort({ productAddedDate: -1 }).limit(10).toArray(); + res.render('products', { + title: 'Cart', + top_results: topResults, + session: req.session, + admin: true, + config: req.app.config, + message: common.clearSessionValue(req.session, 'message'), + messageType: common.clearSessionValue(req.session, 'messageType'), + helpers: req.handlebars.helpers }); }); -router.get('/admin/products/filter/:search', (req, res, next) => { +router.get('/admin/products/filter/:search', async (req, res, next) => { const db = req.app.db; const searchTerm = req.params.search; const productsIndex = req.app.productsIndex; @@ -40,21 +36,17 @@ router.get('/admin/products/filter/:search', (req, res, next) => { }); // we search on the lunr indexes - db.products.find({ _id: { $in: lunrIdArray } }).toArray((err, results) => { - if(err){ - console.error(colors.red('Error searching', err)); - } - res.render('products', { - title: 'Results', - results: results, - admin: true, - config: req.app.config, - session: req.session, - searchTerm: searchTerm, - message: common.clearSessionValue(req.session, 'message'), - messageType: common.clearSessionValue(req.session, 'messageType'), - helpers: req.handlebars.helpers - }); + const results = await db.products.find({ _id: { $in: lunrIdArray } }).toArray(); + res.render('products', { + title: 'Results', + results: results, + admin: true, + config: req.app.config, + session: req.session, + searchTerm: searchTerm, + message: common.clearSessionValue(req.session, 'message'), + messageType: common.clearSessionValue(req.session, 'messageType'), + helpers: req.handlebars.helpers }); }); @@ -77,7 +69,7 @@ router.get('/admin/product/new', restrict, checkAccess, (req, res) => { }); // insert new product form action -router.post('/admin/product/insert', restrict, checkAccess, (req, res) => { +router.post('/admin/product/insert', restrict, checkAccess, async (req, res) => { const db = req.app.db; // Process supplied options @@ -131,363 +123,357 @@ router.post('/admin/product/insert', restrict, checkAccess, (req, res) => { return; } - db.products.count({ productPermalink: req.body.productPermalink }, (err, product) => { - if(err){ - console.info(err.stack); - } - if(product > 0 && req.body.productPermalink !== ''){ - // permalink exits - req.session.message = 'Permalink already exists. Pick a new one.'; - req.session.messageType = 'danger'; + const product = await db.products.count({ productPermalink: req.body.productPermalink }); + if(product > 0 && req.body.productPermalink !== ''){ + // permalink exits + req.session.message = 'Permalink already exists. Pick a new one.'; + req.session.messageType = 'danger'; - // keep the current stuff - req.session.productTitle = req.body.productTitle; - req.session.productDescription = req.body.productDescription; - req.session.productPrice = req.body.productPrice; - req.session.productPermalink = req.body.productPermalink; - req.session.productOptions = productOptions; - req.session.productComment = common.checkboxBool(req.body.productComment); - req.session.productTags = req.body.productTags; - req.session.productStock = req.body.productStock ? parseInt(req.body.productStock) : null; + // keep the current stuff + req.session.productTitle = req.body.productTitle; + req.session.productDescription = req.body.productDescription; + req.session.productPrice = req.body.productPrice; + req.session.productPermalink = req.body.productPermalink; + req.session.productOptions = productOptions; + req.session.productComment = common.checkboxBool(req.body.productComment); + req.session.productTags = req.body.productTags; + req.session.productStock = req.body.productStock ? parseInt(req.body.productStock) : null; + + // If API request, return json + if(req.apiAuthenticated){ + res.status(400).json({ error: 'Permalink already exists. Pick a new one.' }); + return; + } + + // redirect to insert + res.redirect('/admin/product/new'); + return; + } + + try{ + const newDoc = await db.products.insert(doc); + // get the new ID + const newId = newDoc.insertedIds[0]; + + // add to lunr index + indexProducts(req.app) + .then(() => { + req.session.message = 'New product successfully created'; + req.session.messageType = 'success'; // If API request, return json if(req.apiAuthenticated){ - res.status(400).json({ error: 'Permalink already exists. Pick a new one.' }); + res.status(200).json({ message: 'New product successfully created' }); return; } - // redirect to insert - res.redirect('/admin/product/new'); + // redirect to new doc + res.redirect('/admin/product/edit/' + newId); + }); + }catch(ex){ + console.log(colors.red('Error inserting document: ' + ex)); + + // keep the current stuff + req.session.productTitle = req.body.productTitle; + req.session.productDescription = req.body.productDescription; + req.session.productPrice = req.body.productPrice; + req.session.productPermalink = req.body.productPermalink; + req.session.productOptions = productOptions; + req.session.productComment = common.checkboxBool(req.body.productComment); + req.session.productTags = req.body.productTags; + req.session.productStock = req.body.productStock ? parseInt(req.body.productStock) : null; + + req.session.message = 'Error: Inserting product'; + req.session.messageType = 'danger'; + + // If API request, return json + if(req.apiAuthenticated){ + res.status(400).json({ error: 'Error inserting document' }); return; } - db.products.insert(doc, (err, newDoc) => { - if(err){ - console.log(colors.red('Error inserting document: ' + err)); - // keep the current stuff - req.session.productTitle = req.body.productTitle; - req.session.productDescription = req.body.productDescription; - req.session.productPrice = req.body.productPrice; - req.session.productPermalink = req.body.productPermalink; - req.session.productOptions = productOptions; - req.session.productComment = common.checkboxBool(req.body.productComment); - req.session.productTags = req.body.productTags; - req.session.productStock = req.body.productStock ? parseInt(req.body.productStock) : null; - - req.session.message = 'Error: Inserting product'; - req.session.messageType = 'danger'; - - // If API request, return json - if(req.apiAuthenticated){ - res.status(400).json({ error: `Error inserting document: ${err}` }); - return; - } - - // redirect to insert - res.redirect('/admin/product/new'); - return; - } - // get the new ID - const newId = newDoc.insertedIds[0]; - - // add to lunr index - indexProducts(req.app) - .then(() => { - req.session.message = 'New product successfully created'; - req.session.messageType = 'success'; - - // If API request, return json - if(req.apiAuthenticated){ - res.status(200).json({ message: 'New product successfully created' }); - return; - } - - // redirect to new doc - res.redirect('/admin/product/edit/' + newId); - }); - }); - }); + // redirect to insert + res.redirect('/admin/product/new'); + } }); // render the editor -router.get('/admin/product/edit/:id', restrict, checkAccess, (req, res) => { +router.get('/admin/product/edit/:id', restrict, checkAccess, async (req, res) => { const db = req.app.db; - common.getImages(req.params.id, req, res, (images) => { - db.products.findOne({ _id: common.getId(req.params.id) }, (err, result) => { - if(err){ - console.info(err.stack); - } - let options = {}; - if(result.productOptions){ - options = result.productOptions; - } + const images = await common.getImages(req.params.id, req, res); + const product = await db.products.findOne({ _id: common.getId(req.params.id) }); + if(!product){ + req.session.message = 'Product not found'; + req.session.messageType = 'danger'; + res.redirect('/admin/products'); + return; + } + let options = {}; + if(product.productOptions){ + options = product.productOptions; + } - res.render('product_edit', { - title: 'Edit product', - result: result, - images: images, - options: options, - admin: true, - session: req.session, - message: common.clearSessionValue(req.session, 'message'), - messageType: common.clearSessionValue(req.session, 'messageType'), - config: req.app.config, - editor: true, - helpers: req.handlebars.helpers - }); - }); + res.render('product_edit', { + title: 'Edit product', + result: product, + images: images, + options: options, + admin: true, + session: req.session, + message: common.clearSessionValue(req.session, 'message'), + messageType: common.clearSessionValue(req.session, 'messageType'), + config: req.app.config, + editor: true, + helpers: req.handlebars.helpers }); }); +// Remove option from product +router.post('/admin/product/removeoption', restrict, checkAccess, async (req, res) => { + const db = req.app.db; + const product = await db.products.findOne({ _id: common.getId(req.body.productId) }); + if(product && product.productOptions){ + const opts = product.productOptions; + delete opts[req.body.optName]; + + try{ + const updateOption = await db.products.update({ _id: common.getId(req.body.productId) }, { $set: { productOptions: opts } }); + if(updateOption.result.nModified === 1){ + res.status(200).json({ message: 'Option successfully removed' }); + return; + } + res.status(400).json({ message: 'Failed to remove option. Please try again.' }); + return; + }catch(ex){ + res.status(400).json({ message: 'Failed to remove option. Please try again.' }); + return; + } + } + res.status(400).json({ message: 'Product not found. Try saving before removing.' }); +}); + // Update an existing product form action -router.post('/admin/product/update', restrict, checkAccess, (req, res) => { +router.post('/admin/product/update', restrict, checkAccess, async (req, res) => { const db = req.app.db; - db.products.findOne({ _id: common.getId(req.body.productId) }, (err, product) => { - if(err){ - console.info(err.stack); - req.session.message = 'Failed updating product.'; - req.session.messageType = 'danger'; + const product = await db.products.findOne({ _id: common.getId(req.body.productId) }); - // If API request, return json - if(req.apiAuthenticated){ - res.status(400).json({ messge: 'Failed to update product' }); - return; - } + if(!product){ + req.session.message = 'Failed updating product.'; + req.session.messageType = 'danger'; - res.redirect('/admin/product/edit/' + req.body.productId); + // If API request, return json + if(req.apiAuthenticated){ + res.status(400).json({ messge: 'Failed to update product' }); return; } - db.products.count({ productPermalink: req.body.productPermalink, _id: { $ne: common.getId(product._id) } }, (err, count) => { - if(err){ - console.info(err.stack); - // If API request, return json - if(req.apiAuthenticated){ - res.status(400).json({ messge: 'Failed to update product' }); - return; - } + res.redirect('/admin/product/edit/' + req.body.productId); + return; + } + const count = await db.products.count({ productPermalink: req.body.productPermalink, _id: { $ne: common.getId(product._id) } }); + if(count > 0 && req.body.productPermalink !== ''){ + // If API request, return json + if(req.apiAuthenticated){ + res.status(400).json({ messge: 'Permalink already exists. Pick a new one' }); + return; + } - req.session.message = 'Failed updating product.'; - req.session.messageType = 'danger'; - res.redirect('/admin/product/edit/' + req.body.productId); + // permalink exits + req.session.message = 'Permalink already exists. Pick a new one.'; + req.session.messageType = 'danger'; + + // keep the current stuff + req.session.productTitle = req.body.productTitle; + req.session.productDescription = req.body.productDescription; + req.session.productPrice = req.body.productPrice; + req.session.productPermalink = req.body.productPermalink; + req.session.productTags = req.body.productTags; + req.session.productOptions = req.body.productOptions; + req.session.productComment = common.checkboxBool(req.body.productComment); + req.session.productStock = req.body.productStock ? req.body.productStock : null; + + // redirect to insert + res.redirect('/admin/product/edit/' + req.body.productId); + return; + } + const images = await common.getImages(req.body.productId, req, res); + // Process supplied options + let productOptions = req.body.productOptions; + if(productOptions && typeof productOptions !== 'object'){ + try{ + productOptions = JSON.parse(req.body.productOptions); + }catch(ex){ + console.log('Failure to parse options'); + } + } + + const productDoc = { + productId: req.body.productId, + productPermalink: req.body.productPermalink, + productTitle: common.cleanHtml(req.body.productTitle), + productPrice: common.safeParseInt(req.body.productPrice), + productDescription: common.cleanHtml(req.body.productDescription), + productPublished: common.convertBool(req.body.productPublished), + productTags: req.body.productTags, + productOptions: productOptions || null, + productComment: common.checkboxBool(req.body.productComment), + productStock: common.safeParseInt(req.body.productStock) || null + }; + + // Validate the body again schema + const schemaResult = validateJson('editProduct', productDoc); + if(!schemaResult.valid){ + // If API request, return json + if(req.apiAuthenticated){ + res.status(400).json(schemaResult.errors); + return; + } + + req.session.message = 'Form invalid. Please check values and try again.'; + req.session.messageType = 'danger'; + + // keep the current stuff + req.session.productTitle = req.body.productTitle; + req.session.productDescription = req.body.productDescription; + req.session.productPrice = req.body.productPrice; + req.session.productPermalink = req.body.productPermalink; + req.session.productOptions = productOptions; + req.session.productComment = common.checkboxBool(req.body.productComment); + req.session.productTags = req.body.productTags; + req.session.productStock = req.body.productStock ? parseInt(req.body.productStock) : null; + + // redirect to insert + res.redirect('/admin/product/edit/' + req.body.productId); + return; + } + + // Remove productId from doc + delete productDoc.productId; + + // if no featured image + if(!product.productImage){ + if(images.length > 0){ + productDoc['productImage'] = images[0].path; + }else{ + productDoc['productImage'] = '/uploads/placeholder.png'; + } + }else{ + productDoc['productImage'] = product.productImage; + } + + try{ + await db.products.update({ _id: common.getId(req.body.productId) }, { $set: productDoc }, {}); + // Update the index + indexProducts(req.app) + .then(() => { + // If API request, return json + if(req.apiAuthenticated){ + res.status(200).json({ message: 'Successfully saved', product: productDoc }); return; } - if(count > 0 && req.body.productPermalink !== ''){ - // If API request, return json - if(req.apiAuthenticated){ - res.status(400).json({ messge: 'Permalink already exists. Pick a new one' }); - return; - } - - // permalink exits - req.session.message = 'Permalink already exists. Pick a new one.'; - req.session.messageType = 'danger'; - - // keep the current stuff - req.session.productTitle = req.body.productTitle; - req.session.productDescription = req.body.productDescription; - req.session.productPrice = req.body.productPrice; - req.session.productPermalink = req.body.productPermalink; - req.session.productTags = req.body.productTags; - req.session.productOptions = req.body.productOptions; - req.session.productComment = common.checkboxBool(req.body.productComment); - req.session.productStock = req.body.productStock ? req.body.productStock : null; - - // redirect to insert - res.redirect('/admin/product/edit/' + req.body.productId); - }else{ - common.getImages(req.body.productId, req, res, (images) => { - // Process supplied options - let productOptions = req.body.productOptions; - if(productOptions && typeof productOptions !== 'object'){ - try{ - productOptions = JSON.parse(req.body.productOptions); - }catch(ex){ - console.log('Failure to parse options'); - } - } - - const productDoc = { - productId: req.body.productId, - productPermalink: req.body.productPermalink, - productTitle: common.cleanHtml(req.body.productTitle), - productPrice: common.safeParseInt(req.body.productPrice), - productDescription: common.cleanHtml(req.body.productDescription), - productPublished: common.convertBool(req.body.productPublished), - productTags: req.body.productTags, - productOptions: productOptions || null, - productComment: common.checkboxBool(req.body.productComment), - productStock: common.safeParseInt(req.body.productStock) || null - }; - - // Validate the body again schema - const schemaResult = validateJson('editProduct', productDoc); - if(!schemaResult.valid){ - // If API request, return json - if(req.apiAuthenticated){ - res.status(400).json(schemaResult.errors); - return; - } - - req.session.message = 'Form invalid. Please check values and try again.'; - req.session.messageType = 'danger'; - - // keep the current stuff - req.session.productTitle = req.body.productTitle; - req.session.productDescription = req.body.productDescription; - req.session.productPrice = req.body.productPrice; - req.session.productPermalink = req.body.productPermalink; - req.session.productOptions = productOptions; - req.session.productComment = common.checkboxBool(req.body.productComment); - req.session.productTags = req.body.productTags; - req.session.productStock = req.body.productStock ? parseInt(req.body.productStock) : null; - - // redirect to insert - res.redirect('/admin/product/edit/' + req.body.productId); - return; - } - - // Remove productId from doc - delete productDoc.productId; - - // if no featured image - if(!product.productImage){ - if(images.length > 0){ - productDoc['productImage'] = images[0].path; - }else{ - productDoc['productImage'] = '/uploads/placeholder.png'; - } - }else{ - productDoc['productImage'] = product.productImage; - } - - db.products.update({ _id: common.getId(req.body.productId) }, { $set: productDoc }, {}, (err, numReplaced) => { - if(err){ - // If API request, return json - if(req.apiAuthenticated){ - res.status(400).json({ messge: 'Failed to save. Please try again' }); - return; - } - - console.error(colors.red('Failed to save product: ' + err)); - req.session.message = 'Failed to save. Please try again'; - req.session.messageType = 'danger'; - res.redirect('/admin/product/edit/' + req.body.productId); - }else{ - // Update the index - indexProducts(req.app) - .then(() => { - // If API request, return json - if(req.apiAuthenticated){ - res.status(200).json({ message: 'Successfully saved', product: productDoc }); - return; - } - - req.session.message = 'Successfully saved'; - req.session.messageType = 'success'; - res.redirect('/admin/product/edit/' + req.body.productId); - }); - } - }); - }); - } + req.session.message = 'Successfully saved'; + req.session.messageType = 'success'; + res.redirect('/admin/product/edit/' + req.body.productId); }); - }); + }catch(ex){ + // If API request, return json + if(req.apiAuthenticated){ + res.status(400).json({ messge: 'Failed to save. Please try again' }); + return; + } + + console.error(colors.red('Failed to save product: ' + ex)); + req.session.message = 'Failed to save. Please try again'; + req.session.messageType = 'danger'; + res.redirect('/admin/product/edit/' + req.body.productId); + } }); // delete product -router.get('/admin/product/delete/:id', restrict, checkAccess, (req, res) => { +router.get('/admin/product/delete/:id', restrict, checkAccess, async (req, res) => { const db = req.app.db; - // remove the article - db.products.remove({ _id: common.getId(req.params.id) }, {}, (err, numRemoved) => { + // remove the product + await db.products.remove({ _id: common.getId(req.params.id) }, {}); + + // delete any images and folder + rimraf('public/uploads/' + req.params.id, (err) => { if(err){ console.info(err.stack); } - // delete any images and folder - rimraf('public/uploads/' + req.params.id, (err) => { - if(err){ - console.info(err.stack); - } - // remove the index - indexProducts(req.app) - .then(() => { - // redirect home - req.session.message = 'Product successfully deleted'; - req.session.messageType = 'success'; - res.redirect('/admin/products'); - }); + // re-index products + indexProducts(req.app) + .then(() => { + // redirect home + req.session.message = 'Product successfully deleted'; + req.session.messageType = 'success'; + res.redirect('/admin/products'); }); }); }); // update the published state based on an ajax call from the frontend -router.post('/admin/product/published_state', restrict, checkAccess, (req, res) => { +router.post('/admin/product/published_state', restrict, checkAccess, async (req, res) => { const db = req.app.db; - db.products.update({ _id: common.getId(req.body.id) }, { $set: { productPublished: common.convertBool(req.body.state) } }, { multi: false }, (err, numReplaced) => { - if(err){ - console.error(colors.red('Failed to update the published state: ' + err)); - res.status(400).json('Published state not updated'); - }else{ - res.status(200).json('Published state updated'); - } - }); + try{ + await db.products.update({ _id: common.getId(req.body.id) }, { $set: { productPublished: common.convertBool(req.body.state) } }, { multi: false }); + res.status(200).json('Published state updated'); + }catch(ex){ + console.error(colors.red('Failed to update the published state: ' + ex)); + res.status(400).json('Published state not updated'); + } }); // set as main product image -router.post('/admin/product/setasmainimage', restrict, checkAccess, (req, res) => { +router.post('/admin/product/setasmainimage', restrict, checkAccess, async (req, res) => { const db = req.app.db; - // update the productImage to the db - db.products.update({ _id: common.getId(req.body.product_id) }, { $set: { productImage: req.body.productImage } }, { multi: false }, (err, numReplaced) => { - if(err){ - res.status(400).json({ message: 'Unable to set as main image. Please try again.' }); - }else{ - res.status(200).json({ message: 'Main image successfully set' }); - } - }); + try{ + // update the productImage to the db + await db.products.update({ _id: common.getId(req.body.product_id) }, { $set: { productImage: req.body.productImage } }, { multi: false }); + res.status(200).json({ message: 'Main image successfully set' }); + }catch(ex){ + res.status(400).json({ message: 'Unable to set as main image. Please try again.' }); + } }); // deletes a product image -router.post('/admin/product/deleteimage', restrict, checkAccess, (req, res) => { +router.post('/admin/product/deleteimage', restrict, checkAccess, async (req, res) => { const db = req.app.db; // get the productImage from the db - db.products.findOne({ _id: common.getId(req.body.product_id) }, (err, product) => { - if(err){ - console.info(err.stack); - } - if(req.body.productImage === product.productImage){ - // set the produt_image to null - db.products.update({ _id: common.getId(req.body.product_id) }, { $set: { productImage: null } }, { multi: false }, (err, numReplaced) => { - if(err){ - console.info(err.stack); - } - // remove the image from disk - fs.unlink(path.join('public', req.body.productImage), (err) => { - if(err){ - res.status(400).json({ message: 'Image not removed, please try again.' }); - }else{ - res.status(200).json({ message: 'Image successfully deleted' }); - } - }); - }); - }else{ - // remove the image from disk - fs.unlink(path.join('public', req.body.productImage), (err) => { - if(err){ - res.status(400).json({ message: 'Image not removed, please try again.' }); - }else{ - res.status(200).json({ message: 'Image successfully deleted' }); - } - }); - } - }); + const product = await db.products.findOne({ _id: common.getId(req.body.product_id) }); + if(!product){ + res.status(400).json({ message: 'Product not found' }); + return; + } + if(req.body.productImage === product.productImage){ + // set the productImage to null + await db.products.update({ _id: common.getId(req.body.product_id) }, { $set: { productImage: null } }, { multi: false }); + + // remove the image from disk + fs.unlink(path.join('public', req.body.productImage), (err) => { + if(err){ + res.status(400).json({ message: 'Image not removed, please try again.' }); + }else{ + res.status(200).json({ message: 'Image successfully deleted' }); + } + }); + }else{ + // remove the image from disk + fs.unlink(path.join('public', req.body.productImage), (err) => { + if(err){ + res.status(400).json({ message: 'Image not removed, please try again.' }); + }else{ + res.status(200).json({ message: 'Image successfully deleted' }); + } + }); + } }); module.exports = router;