const express = require('express'); const path = require('path'); const logger = require('morgan'); const cookieParser = require('cookie-parser'); const bodyParser = require('body-parser'); const session = require('express-session'); const bcrypt = require('bcrypt-nodejs'); const lunr = require('lunr'); const moment = require('moment'); const MongoStore = require('connect-mongodb-session')(session); const MongoClient = require('mongodb').MongoClient; const numeral = require('numeral'); const helmet = require('helmet'); const colors = require('colors'); const common = require('./routes/common'); const frameguard = require('frameguard'); let handlebars = require('express-handlebars'); // require the routes const index = require('./routes/index'); const admin = require('./routes/admin'); const paypal = require('./routes/paypal'); const stripe = require('./routes/stripe'); const app = express(); // view engine setup app.set('views', path.join(__dirname, '/views')); app.engine('hbs', handlebars({extname: 'hbs', layoutsDir: path.join(__dirname, 'views', 'layouts'), defaultLayout: 'layout.hbs'})); app.set('view engine', 'hbs'); // helpers for the handlebar templating platform handlebars = handlebars.create({ partialsDir: [ 'views/partials/' ], helpers: { perRowClass: function(numProducts){ if(parseInt(numProducts) === 1){ return'col-md-12 col-xl-12 product-item'; } if(parseInt(numProducts) === 2){ return'col-md-6 col-xl-6 product-item'; } if(parseInt(numProducts) === 3){ return'col-md-4 col-xl-4 product-item'; } if(parseInt(numProducts) === 4){ return'col-md-3 col-xl-3 product-item'; } return'col-md-6 col-xl-6 product-item'; }, formatAmount: function(amt){ if(amt){ return numeral(amt).format('0.00'); } return'0.00'; }, amountNoDecimal: function(amt){ if(amt){ return handlebars.helpers.formatAmount(amt).replace('.', ''); } return handlebars.helpers.formatAmount(amt); }, getStatusColor: function (status){ switch(status){ case'Paid': return'success'; case'Approved': return'success'; case'Approved - Processing': return'success'; case'Failed': return'danger'; case'Completed': return'success'; case'Shipped': return'success'; case'Pending': return'warning'; default: return'danger'; } }, checkProductOptions: function (opts){ if(opts){ return'true'; } return'false'; }, currencySymbol: function(value){ if(typeof value === 'undefined' || value === ''){ return'$'; } return value; }, objectLength: function(obj){ if(obj){ return Object.keys(obj).length; } return 0; }, checkedState: function (state){ if(state === 'true' || state === true){ return'checked'; } return''; }, selectState: function (state, value){ if(state === value){ return'selected'; } return''; }, isNull: function (value, options){ if(typeof value === 'undefined' || value === ''){ return options.fn(this); } return options.inverse(this); }, formatDate: function (date, format){ return moment(date).format(format); }, ifCond: function (v1, operator, v2, options){ switch(operator){ case'==': return(v1 === v2) ? options.fn(this) : options.inverse(this); case'!=': return(v1 !== v2) ? options.fn(this) : options.inverse(this); case'===': return(v1 === v2) ? options.fn(this) : options.inverse(this); case'<': return(v1 < v2) ? options.fn(this) : options.inverse(this); case'<=': return(v1 <= v2) ? options.fn(this) : options.inverse(this); case'>': return(v1 > v2) ? options.fn(this) : options.inverse(this); case'>=': return(v1 >= v2) ? options.fn(this) : options.inverse(this); case'&&': return(v1 && v2) ? options.fn(this) : options.inverse(this); case'||': return(v1 || v2) ? options.fn(this) : options.inverse(this); default: return options.inverse(this); } }, isAnAdmin: function (value, options){ if(value === 'true'){ return options.fn(this); } return options.inverse(this); } } }); // get config let config = common.getConfig(); // var session store let store = new MongoStore({ uri: config.databaseConnectionString, collection: 'sessions' }); app.enable('trust proxy'); app.use(helmet()); app.set('port', process.env.PORT || 1111); app.use(logger('dev')); app.use(bodyParser.json()); app.use(bodyParser.urlencoded({extended: false})); app.use(cookieParser('5TOCyfH3HuszKGzFZntk')); app.use(session({ resave: true, saveUninitialized: true, secret: 'pAgGxo8Hzg7PFlv1HpO8Eg0Y6xtP7zYx', cookie: { path: '/', httpOnly: true, maxAge: 3600000 * 24 }, store: store })); // allows for codecanyon app.use(frameguard({ action: 'allow-from', domain: 'http://preview.codecanyon.net' })); // serving static content app.use(express.static(path.join(__dirname, 'public'))); // Make stuff accessible to our router app.use((req, res, next) => { req.handlebars = handlebars; req.bcrypt = bcrypt; next(); }); // setup the routes app.use('/', index); app.use('/admin', admin); app.use('/paypal', paypal); app.use('/stripe', stripe); // catch 404 and forward to error handler app.use((req, res, next) => { let err = new Error('Not Found'); err.status = 404; next(err); }); // error handlers // development error handler // will print stacktrace if(app.get('env') === 'development'){ app.use((err, req, res, next) => { console.error(colors.red(err.stack)); res.status(err.status || 500); res.render('error', { message: err.message, error: err, helpers: handlebars.helpers }); }); } // production error handler // no stacktraces leaked to user app.use((err, req, res, next) => { console.error(colors.red(err.stack)); res.status(err.status || 500); res.render('error', { message: err.message, error: {}, helpers: handlebars.helpers }); }); app.on('uncaughtException', (err) => { console.error(colors.red(err.stack)); process.exit(); }); MongoClient.connect(config.databaseConnectionString, {}, (err, client) => { // On connection error we display then exit if(err){ console.log(colors.red('Error connecting to MongoDB: ' + err)); process.exit(); } // select DB const db = client.db('expresscart'); // setup the collections db.users = db.collection('users'); db.products = db.collection('products'); db.orders = db.collection('orders'); db.pages = db.collection('pages'); // add db to app for routes app.db = db; // add indexing runIndexing(app, (err) => { if(err){ console.error(colors.red('Error setting up indexes:' + err)); process.exit(); } // lift the app app.listen(app.get('port'), () => { console.log(colors.green('expressCart running on host: http://localhost:' + app.get('port'))); }); }); }); function indexProducts(app, cb){ // index all products in lunr on startup common.dbQuery(app.db.products, {}, null, null, (err, productsList) => { if(err){ console.error(colors.red(err.stack)); } // setup lunr indexing const productsIndex = lunr(function (){ this.field('productTitle', {boost: 10}); this.field('productTags', {boost: 5}); this.field('productDescription'); const lunrIndex = this; // add to lunr index productsList.forEach((product) => { let doc = { 'productTitle': product.productTitle, 'productTags': product.productTags, 'productDescription': product.productDescription, 'id': product._id }; lunrIndex.add(doc); }); }); app.productsIndex = productsIndex; cb(null); }); } function indexOrders(app, cb){ // index all orders in lunr on startup common.dbQuery(app.db.orders, {}, null, null, (err, ordersList) => { if(err){ console.error(colors.red(err.stack)); } // setup lunr indexing const ordersIndex = lunr(function (){ this.field('orderEmail', {boost: 10}); this.field('orderLastname', {boost: 5}); this.field('orderPostcode'); const lunrIndex = this; // add to lunr index ordersList.forEach((order) => { let doc = { 'orderLastname': order.orderLastname, 'orderEmail': order.orderEmail, 'orderPostcode': order.orderPostcode, 'id': order._id }; lunrIndex.add(doc); }); }); app.ordersIndex = ordersIndex; cb(null); }); } // start indexing products and orders function runIndexing(app, cb){ console.info(colors.yellow('Setting up indexes..')); indexProducts(app, (err) => { if(err){ console.error(colors.red('Error setting up products index: ' + err)); cb(err); } console.log(colors.cyan('- Product indexing complete')); indexOrders(app, (err) => { if(err){ console.error(colors.red('Error setting up products index: ' + err)); cb(err); } console.log(colors.cyan('- Order indexing complete')); cb(null); }); }); } module.exports = app;