expressCart/routes/product.js

481 lines
18 KiB
JavaScript
Raw Normal View History

2018-02-05 23:20:44 +10:00
const express = require('express');
const common = require('../lib/common');
2019-06-15 14:46:08 +10:00
const { restrict, checkAccess } = require('../lib/auth');
const { indexProducts } = require('../lib/indexing');
const { validateJson } = require('../lib/schema');
2018-02-05 23:20:44 +10:00
const colors = require('colors');
const rimraf = require('rimraf');
const fs = require('fs');
const path = require('path');
const router = express.Router();
2019-10-26 11:08:53 +10:00
router.get('/admin/products', restrict, async (req, res, next) => {
2018-02-05 23:20:44 +10:00
const db = req.app.db;
// get the top results
2019-10-26 11:08:53 +10:00
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
2018-02-05 23:20:44 +10:00
});
});
2019-10-26 11:08:53 +10:00
router.get('/admin/products/filter/:search', async (req, res, next) => {
2018-02-05 23:20:44 +10:00
const db = req.app.db;
2019-07-12 18:06:34 +10:00
const searchTerm = req.params.search;
const productsIndex = req.app.productsIndex;
2018-02-05 23:20:44 +10:00
2019-07-12 18:06:34 +10:00
const lunrIdArray = [];
2018-02-05 23:20:44 +10:00
productsIndex.search(searchTerm).forEach((id) => {
lunrIdArray.push(common.getId(id.ref));
});
// we search on the lunr indexes
2019-10-26 11:08:53 +10:00
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
2018-02-05 23:20:44 +10:00
});
});
// insert form
2019-06-15 14:46:08 +10:00
router.get('/admin/product/new', restrict, checkAccess, (req, res) => {
2018-02-05 23:20:44 +10:00
res.render('product_new', {
title: 'New product',
session: req.session,
productTitle: common.clearSessionValue(req.session, 'productTitle'),
productDescription: common.clearSessionValue(req.session, 'productDescription'),
productPrice: common.clearSessionValue(req.session, 'productPrice'),
productPermalink: common.clearSessionValue(req.session, 'productPermalink'),
message: common.clearSessionValue(req.session, 'message'),
messageType: common.clearSessionValue(req.session, 'messageType'),
editor: true,
admin: true,
helpers: req.handlebars.helpers,
2018-02-23 03:41:24 +10:00
config: req.app.config
2018-02-05 23:20:44 +10:00
});
});
// insert new product form action
2019-10-26 11:08:53 +10:00
router.post('/admin/product/insert', restrict, checkAccess, async (req, res) => {
2018-02-05 23:20:44 +10:00
const db = req.app.db;
// 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');
}
}
2019-07-12 18:06:34 +10:00
const doc = {
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),
2019-02-09 20:46:41 +10:00
productAddedDate: new Date(),
productStock: common.safeParseInt(req.body.productStock) || null
2018-02-05 23:20:44 +10:00
};
// Validate the body again schema
const schemaResult = validateJson('newProduct', doc);
if(!schemaResult.valid){
// If API request, return json
if(req.apiAuthenticated){
res.status(400).json(schemaResult.errors);
return;
}
console.log('schemaResult errors', schemaResult.errors);
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/new');
return;
}
2019-10-26 11:20:58 +10:00
// Check permalink doesn't already exist
2019-10-29 18:26:30 +10:00
const product = await db.products.countDocuments({ productPermalink: req.body.productPermalink });
2019-10-26 11:08:53 +10:00
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;
// If API request, return json
if(req.apiAuthenticated){
res.status(400).json({ error: 'Permalink already exists. Pick a new one.' });
return;
2018-02-05 23:20:44 +10:00
}
2019-10-26 11:08:53 +10:00
// redirect to insert
res.redirect('/admin/product/new');
return;
}
try{
2019-10-26 11:20:58 +10:00
const newDoc = await db.products.insertOne(doc);
2019-10-26 11:08:53 +10:00
// get the new ID
2019-10-26 11:20:58 +10:00
const newId = newDoc.insertedId;
2019-10-26 11:08:53 +10:00
// 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){
2019-10-26 11:08:53 +10:00
res.status(200).json({ message: 'New product successfully created' });
return;
}
2018-02-05 23:20:44 +10:00
2019-10-26 11:08:53 +10:00
// 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;
}
2019-10-26 11:08:53 +10:00
// redirect to insert
res.redirect('/admin/product/new');
}
2018-02-05 23:20:44 +10:00
});
// render the editor
2019-10-26 11:08:53 +10:00
router.get('/admin/product/edit/:id', restrict, checkAccess, async (req, res) => {
2018-02-05 23:20:44 +10:00
const db = req.app.db;
2019-10-26 11:08:53 +10:00
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;
}
2018-02-05 23:20:44 +10:00
2019-10-26 11:08:53 +10:00
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
2018-02-05 23:20:44 +10:00
});
});
2019-10-26 11:08:53 +10:00
// Remove option from product
router.post('/admin/product/removeoption', restrict, checkAccess, async (req, res) => {
2018-02-05 23:20:44 +10:00
const db = req.app.db;
2019-10-26 11:08:53 +10:00
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];
2018-02-05 23:20:44 +10:00
2019-10-26 11:08:53 +10:00
try{
2019-10-29 18:26:30 +10:00
const updateOption = await db.products.updateOne({ _id: common.getId(req.body.productId) }, { $set: { productOptions: opts } });
2019-10-26 11:08:53 +10:00
if(updateOption.result.nModified === 1){
res.status(200).json({ message: 'Option successfully removed' });
2019-06-20 13:14:04 +10:00
return;
}
2019-10-26 11:08:53 +10:00
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.' });
});
2019-06-20 13:14:04 +10:00
2019-10-26 11:08:53 +10:00
// Update an existing product form action
router.post('/admin/product/update', restrict, checkAccess, async (req, res) => {
const db = req.app.db;
const product = await db.products.findOne({ _id: common.getId(req.body.productId) });
if(!product){
req.session.message = 'Failed updating product.';
req.session.messageType = 'danger';
// 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;
}
2019-10-29 18:26:30 +10:00
const count = await db.products.countDocuments({ productPermalink: req.body.productPermalink, _id: { $ne: common.getId(product._id) } });
2019-10-26 11:08:53 +10:00
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' });
2018-02-05 23:20:44 +10:00
return;
}
2019-06-20 13:14:04 +10:00
2019-10-26 11:08:53 +10:00
// 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;
2019-06-20 13:14:04 +10:00
2019-10-26 11:08:53 +10:00
// 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){
2019-11-03 09:18:34 +10:00
productDoc.productImage = images[0].path;
2019-10-26 11:08:53 +10:00
}else{
2019-11-03 09:18:34 +10:00
productDoc.productImage = '/uploads/placeholder.png';
2019-10-26 11:08:53 +10:00
}
}else{
2019-11-03 09:18:34 +10:00
productDoc.productImage = product.productImage;
2019-10-26 11:08:53 +10:00
}
try{
2019-10-29 18:26:30 +10:00
await db.products.updateOne({ _id: common.getId(req.body.productId) }, { $set: productDoc }, {});
2019-10-26 11:08:53 +10:00
// Update the index
indexProducts(req.app)
.then(() => {
// If API request, return json
if(req.apiAuthenticated){
res.status(200).json({ message: 'Successfully saved', product: productDoc });
2018-02-05 23:20:44 +10:00
return;
}
2018-12-11 23:31:56 +10:00
2019-10-26 11:08:53 +10:00
req.session.message = 'Successfully saved';
req.session.messageType = 'success';
res.redirect('/admin/product/edit/' + req.body.productId);
2018-02-05 23:20:44 +10:00
});
2019-10-26 11:08:53 +10:00
}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);
}
2018-02-05 23:20:44 +10:00
});
// delete product
2019-10-26 11:08:53 +10:00
router.get('/admin/product/delete/:id', restrict, checkAccess, async (req, res) => {
2018-02-05 23:20:44 +10:00
const db = req.app.db;
2019-10-26 11:08:53 +10:00
// remove the product
2019-10-29 18:26:30 +10:00
await db.products.deleteOne({ _id: common.getId(req.params.id) }, {});
2019-10-26 11:08:53 +10:00
// delete any images and folder
rimraf('public/uploads/' + req.params.id, (err) => {
2018-02-05 23:20:44 +10:00
if(err){
console.info(err.stack);
}
2019-10-26 11:08:53 +10:00
// re-index products
indexProducts(req.app)
.then(() => {
// redirect home
req.session.message = 'Product successfully deleted';
req.session.messageType = 'success';
res.redirect('/admin/products');
2018-02-05 23:20:44 +10:00
});
});
});
// update the published state based on an ajax call from the frontend
2019-10-26 11:08:53 +10:00
router.post('/admin/product/published_state', restrict, checkAccess, async (req, res) => {
2018-02-05 23:20:44 +10:00
const db = req.app.db;
2019-10-26 11:08:53 +10:00
try{
2019-10-29 18:26:30 +10:00
await db.products.updateOne({ _id: common.getId(req.body.id) }, { $set: { productPublished: common.convertBool(req.body.state) } }, { multi: false });
2019-10-26 11:08:53 +10:00
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');
}
2018-02-05 23:20:44 +10:00
});
// set as main product image
2019-10-26 11:08:53 +10:00
router.post('/admin/product/setasmainimage', restrict, checkAccess, async (req, res) => {
2018-02-05 23:20:44 +10:00
const db = req.app.db;
2019-10-26 11:08:53 +10:00
try{
// update the productImage to the db
2019-10-29 18:26:30 +10:00
await db.products.updateOne({ _id: common.getId(req.body.product_id) }, { $set: { productImage: req.body.productImage } }, { multi: false });
2019-10-26 11:08:53 +10:00
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.' });
}
2018-02-05 23:20:44 +10:00
});
// deletes a product image
2019-10-26 11:08:53 +10:00
router.post('/admin/product/deleteimage', restrict, checkAccess, async (req, res) => {
2018-02-05 23:20:44 +10:00
const db = req.app.db;
// get the productImage from the db
2019-10-26 11:08:53 +10:00
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
2019-10-29 18:26:30 +10:00
await db.products.updateOne({ _id: common.getId(req.body.product_id) }, { $set: { productImage: null } }, { multi: false });
2019-10-26 11:08:53 +10:00
// 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' });
}
});
}
2018-02-05 23:20:44 +10:00
});
module.exports = router;