2018-01-07 04:55:48 +10:00
|
|
|
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 moment = require('moment');
|
2019-06-01 22:22:14 +10:00
|
|
|
const _ = require('lodash');
|
2018-01-07 04:55:48 +10:00
|
|
|
const MongoStore = require('connect-mongodb-session')(session);
|
|
|
|
const numeral = require('numeral');
|
|
|
|
const helmet = require('helmet');
|
|
|
|
const colors = require('colors');
|
2019-06-01 22:22:14 +10:00
|
|
|
const cron = require('node-cron');
|
2018-02-06 04:20:30 +10:00
|
|
|
const common = require('./lib/common');
|
2019-06-15 14:46:08 +10:00
|
|
|
const { runIndexing } = require('./lib/indexing');
|
2019-06-17 20:21:45 +10:00
|
|
|
const { addSchemas } = require('./lib/schema');
|
2019-06-15 14:46:08 +10:00
|
|
|
const { initDb } = require('./lib/db');
|
2018-01-07 04:55:48 +10:00
|
|
|
let handlebars = require('express-handlebars');
|
|
|
|
|
2018-01-11 06:20:36 +10:00
|
|
|
// Validate our settings schema
|
|
|
|
const Ajv = require('ajv');
|
2019-06-15 14:46:08 +10:00
|
|
|
const ajv = new Ajv({ useDefaults: true });
|
2018-01-11 06:20:36 +10:00
|
|
|
|
2019-06-15 16:05:31 +10:00
|
|
|
// get config
|
|
|
|
let config = common.getConfig();
|
|
|
|
|
|
|
|
const baseConfig = ajv.validate(require('./config/baseSchema'), config);
|
2018-01-11 06:20:36 +10:00
|
|
|
if(baseConfig === false){
|
|
|
|
console.log(colors.red(`settings.json incorrect: ${ajv.errorsText()}`));
|
|
|
|
process.exit(2);
|
|
|
|
}
|
|
|
|
|
|
|
|
// Validate the payment gateway config
|
|
|
|
if(config.paymentGateway === 'paypal'){
|
|
|
|
const paypalConfig = ajv.validate(require('./config/paypalSchema'), require('./config/paypal.json'));
|
|
|
|
if(paypalConfig === false){
|
|
|
|
console.log(colors.red(`PayPal config is incorrect: ${ajv.errorsText()}`));
|
|
|
|
process.exit(2);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if(config.paymentGateway === 'stripe'){
|
|
|
|
const stripeConfig = ajv.validate(require('./config/stripeSchema'), require('./config/stripe.json'));
|
|
|
|
if(stripeConfig === false){
|
2018-02-06 08:04:42 +10:00
|
|
|
console.log(colors.red(`Stripe config is incorrect: ${ajv.errorsText()}`));
|
2018-01-11 06:20:36 +10:00
|
|
|
process.exit(2);
|
|
|
|
}
|
|
|
|
}
|
2018-02-05 07:39:42 +10:00
|
|
|
if(config.paymentGateway === 'authorizenet'){
|
|
|
|
const authorizenetConfig = ajv.validate(require('./config/authorizenetSchema'), require('./config/authorizenet.json'));
|
|
|
|
if(authorizenetConfig === false){
|
|
|
|
console.log(colors.red(`Authorizenet config is incorrect: ${ajv.errorsText()}`));
|
|
|
|
process.exit(2);
|
|
|
|
}
|
|
|
|
}
|
2018-01-11 06:20:36 +10:00
|
|
|
|
2018-01-07 04:55:48 +10:00
|
|
|
// require the routes
|
|
|
|
const index = require('./routes/index');
|
|
|
|
const admin = require('./routes/admin');
|
2018-02-05 23:20:53 +10:00
|
|
|
const product = require('./routes/product');
|
2018-02-03 23:26:09 +10:00
|
|
|
const customer = require('./routes/customer');
|
2018-02-05 23:20:53 +10:00
|
|
|
const order = require('./routes/order');
|
|
|
|
const user = require('./routes/user');
|
2018-01-08 04:34:20 +10:00
|
|
|
const paypal = require('./routes/payments/paypal');
|
|
|
|
const stripe = require('./routes/payments/stripe');
|
2018-02-05 07:39:42 +10:00
|
|
|
const authorizenet = require('./routes/payments/authorizenet');
|
2018-01-07 04:55:48 +10:00
|
|
|
|
|
|
|
const app = express();
|
|
|
|
|
|
|
|
// view engine setup
|
|
|
|
app.set('views', path.join(__dirname, '/views'));
|
2018-02-12 05:47:26 +10:00
|
|
|
app.engine('hbs', handlebars({
|
|
|
|
extname: 'hbs',
|
|
|
|
layoutsDir: path.join(__dirname, 'views', 'layouts'),
|
|
|
|
defaultLayout: 'layout.hbs',
|
|
|
|
partialsDir: [ path.join(__dirname, 'views') ]
|
|
|
|
}));
|
2018-01-07 04:55:48 +10:00
|
|
|
app.set('view engine', 'hbs');
|
|
|
|
|
|
|
|
// helpers for the handlebar templating platform
|
|
|
|
handlebars = handlebars.create({
|
|
|
|
helpers: {
|
2019-06-17 20:21:45 +10:00
|
|
|
perRowClass: (numProducts) => {
|
2018-01-07 04:55:48 +10:00
|
|
|
if(parseInt(numProducts) === 1){
|
2018-02-14 06:21:45 +10:00
|
|
|
return'col-md-12 col-xl-12 col m12 xl12 product-item';
|
2018-01-07 04:55:48 +10:00
|
|
|
}
|
|
|
|
if(parseInt(numProducts) === 2){
|
2018-02-14 06:21:45 +10:00
|
|
|
return'col-md-6 col-xl-6 col m6 xl6 product-item';
|
2018-01-07 04:55:48 +10:00
|
|
|
}
|
|
|
|
if(parseInt(numProducts) === 3){
|
2018-02-14 06:21:45 +10:00
|
|
|
return'col-md-4 col-xl-4 col m4 xl4 product-item';
|
2018-01-07 04:55:48 +10:00
|
|
|
}
|
|
|
|
if(parseInt(numProducts) === 4){
|
2018-02-14 06:21:45 +10:00
|
|
|
return'col-md-3 col-xl-3 col m3 xl3 product-item';
|
2018-01-07 04:55:48 +10:00
|
|
|
}
|
|
|
|
|
2018-02-14 06:21:45 +10:00
|
|
|
return'col-md-6 col-xl-6 col m6 xl6 product-item';
|
|
|
|
},
|
2019-06-17 20:21:45 +10:00
|
|
|
menuMatch: (title, search) => {
|
2018-02-17 23:30:28 +10:00
|
|
|
if(!title || !search){
|
|
|
|
return'';
|
|
|
|
}
|
|
|
|
if(title.toLowerCase().startsWith(search.toLowerCase())){
|
|
|
|
return'class="navActive"';
|
|
|
|
}
|
|
|
|
return'';
|
|
|
|
},
|
2019-06-17 20:21:45 +10:00
|
|
|
getTheme: (view) => {
|
2018-02-14 06:21:45 +10:00
|
|
|
return`themes/${config.theme}/${view}`;
|
2018-01-07 04:55:48 +10:00
|
|
|
},
|
2019-06-17 20:21:45 +10:00
|
|
|
formatAmount: (amt) => {
|
2018-01-07 04:55:48 +10:00
|
|
|
if(amt){
|
|
|
|
return numeral(amt).format('0.00');
|
|
|
|
}
|
|
|
|
return'0.00';
|
|
|
|
},
|
2019-06-17 20:21:45 +10:00
|
|
|
amountNoDecimal: (amt) => {
|
2018-01-07 04:55:48 +10:00
|
|
|
if(amt){
|
|
|
|
return handlebars.helpers.formatAmount(amt).replace('.', '');
|
|
|
|
}
|
|
|
|
return handlebars.helpers.formatAmount(amt);
|
|
|
|
},
|
2019-06-17 20:21:45 +10:00
|
|
|
getStatusColor: (status) => {
|
2018-01-07 04:55:48 +10:00
|
|
|
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';
|
|
|
|
}
|
|
|
|
},
|
2019-06-17 20:21:45 +10:00
|
|
|
checkProductOptions: (opts) => {
|
2018-01-07 04:55:48 +10:00
|
|
|
if(opts){
|
|
|
|
return'true';
|
|
|
|
}
|
|
|
|
return'false';
|
|
|
|
},
|
2019-06-17 20:21:45 +10:00
|
|
|
currencySymbol: (value) => {
|
2018-01-07 04:55:48 +10:00
|
|
|
if(typeof value === 'undefined' || value === ''){
|
|
|
|
return'$';
|
|
|
|
}
|
|
|
|
return value;
|
|
|
|
},
|
2019-06-17 20:21:45 +10:00
|
|
|
objectLength: (obj) => {
|
2018-01-07 04:55:48 +10:00
|
|
|
if(obj){
|
|
|
|
return Object.keys(obj).length;
|
|
|
|
}
|
|
|
|
return 0;
|
|
|
|
},
|
2019-06-17 20:21:45 +10:00
|
|
|
stringify: (obj) => {
|
|
|
|
if(obj){
|
|
|
|
return JSON.stringify(obj);
|
|
|
|
}
|
|
|
|
return'';
|
|
|
|
},
|
|
|
|
checkedState: (state) => {
|
2018-01-07 04:55:48 +10:00
|
|
|
if(state === 'true' || state === true){
|
|
|
|
return'checked';
|
|
|
|
}
|
|
|
|
return'';
|
|
|
|
},
|
2019-06-17 20:21:45 +10:00
|
|
|
selectState: (state, value) => {
|
2018-01-07 04:55:48 +10:00
|
|
|
if(state === value){
|
|
|
|
return'selected';
|
|
|
|
}
|
|
|
|
return'';
|
|
|
|
},
|
2019-06-17 20:21:45 +10:00
|
|
|
isNull: (value, options) => {
|
2018-01-07 04:55:48 +10:00
|
|
|
if(typeof value === 'undefined' || value === ''){
|
|
|
|
return options.fn(this);
|
|
|
|
}
|
|
|
|
return options.inverse(this);
|
|
|
|
},
|
2019-06-17 20:21:45 +10:00
|
|
|
toLower: (value) => {
|
2018-02-12 05:47:26 +10:00
|
|
|
if(value){
|
|
|
|
return value.toLowerCase();
|
|
|
|
}
|
|
|
|
return null;
|
|
|
|
},
|
2019-06-17 20:21:45 +10:00
|
|
|
formatDate: (date, format) => {
|
2018-01-07 04:55:48 +10:00
|
|
|
return moment(date).format(format);
|
|
|
|
},
|
2019-06-17 20:21:45 +10:00
|
|
|
ifCond: (v1, operator, v2, options) => {
|
2018-01-07 04:55:48 +10:00
|
|
|
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);
|
|
|
|
}
|
|
|
|
},
|
2019-06-17 20:21:45 +10:00
|
|
|
isAnAdmin: (value, options) => {
|
2018-02-04 00:08:51 +10:00
|
|
|
if(value === 'true' || value === true){
|
2018-01-07 04:55:48 +10:00
|
|
|
return options.fn(this);
|
|
|
|
}
|
|
|
|
return options.inverse(this);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
});
|
|
|
|
|
2018-01-15 07:11:22 +10:00
|
|
|
// session store
|
2018-01-07 04:55:48 +10:00
|
|
|
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());
|
2019-06-15 14:46:08 +10:00
|
|
|
app.use(bodyParser.urlencoded({ extended: false }));
|
2018-01-07 04:55:48 +10:00
|
|
|
app.use(cookieParser('5TOCyfH3HuszKGzFZntk'));
|
|
|
|
app.use(session({
|
|
|
|
resave: true,
|
|
|
|
saveUninitialized: true,
|
|
|
|
secret: 'pAgGxo8Hzg7PFlv1HpO8Eg0Y6xtP7zYx',
|
|
|
|
cookie: {
|
|
|
|
path: '/',
|
|
|
|
httpOnly: true,
|
2019-06-01 22:22:14 +10:00
|
|
|
maxAge: 900000
|
2018-01-07 04:55:48 +10:00
|
|
|
},
|
|
|
|
store: store
|
|
|
|
}));
|
|
|
|
|
|
|
|
// serving static content
|
|
|
|
app.use(express.static(path.join(__dirname, 'public')));
|
2018-02-12 05:47:26 +10:00
|
|
|
app.use(express.static(path.join(__dirname, 'views', 'themes')));
|
2018-01-07 04:55:48 +10:00
|
|
|
|
|
|
|
// Make stuff accessible to our router
|
|
|
|
app.use((req, res, next) => {
|
|
|
|
req.handlebars = handlebars;
|
|
|
|
next();
|
|
|
|
});
|
|
|
|
|
2018-09-02 08:48:29 +10:00
|
|
|
// update config when modified
|
|
|
|
app.use((req, res, next) => {
|
2018-10-05 23:15:39 +10:00
|
|
|
if(res.configDirty){
|
2018-09-02 08:48:29 +10:00
|
|
|
config = common.getConfig();
|
|
|
|
app.config = config;
|
|
|
|
}
|
2019-06-15 10:56:03 +10:00
|
|
|
next();
|
2018-09-02 08:48:29 +10:00
|
|
|
});
|
|
|
|
|
2018-05-21 23:36:12 +10:00
|
|
|
// Ran on all routes
|
|
|
|
app.use((req, res, next) => {
|
|
|
|
res.setHeader('Cache-Control', 'no-cache, no-store');
|
|
|
|
next();
|
|
|
|
});
|
|
|
|
|
2018-01-07 04:55:48 +10:00
|
|
|
// setup the routes
|
|
|
|
app.use('/', index);
|
2018-02-03 23:26:09 +10:00
|
|
|
app.use('/', customer);
|
2018-02-05 23:20:53 +10:00
|
|
|
app.use('/', product);
|
|
|
|
app.use('/', order);
|
|
|
|
app.use('/', user);
|
2018-02-06 05:51:04 +10:00
|
|
|
app.use('/', admin);
|
2018-01-07 04:55:48 +10:00
|
|
|
app.use('/paypal', paypal);
|
|
|
|
app.use('/stripe', stripe);
|
2018-02-05 07:39:42 +10:00
|
|
|
app.use('/authorizenet', authorizenet);
|
2018-01-07 04:55:48 +10:00
|
|
|
|
|
|
|
// 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
|
|
|
|
});
|
|
|
|
});
|
|
|
|
|
2018-01-15 03:02:10 +10:00
|
|
|
// Nodejs version check
|
2018-02-23 05:16:55 +10:00
|
|
|
const nodeVersionMajor = parseInt(process.version.split('.')[0].replace('v', ''));
|
|
|
|
if(nodeVersionMajor < 7){
|
|
|
|
console.log(colors.red(`Please use Node.js version 7.x or above. Current version: ${nodeVersionMajor}`));
|
2018-01-15 03:02:10 +10:00
|
|
|
process.exit(2);
|
|
|
|
}
|
|
|
|
|
2018-01-07 04:55:48 +10:00
|
|
|
app.on('uncaughtException', (err) => {
|
|
|
|
console.error(colors.red(err.stack));
|
2018-01-15 03:02:10 +10:00
|
|
|
process.exit(2);
|
2018-01-07 04:55:48 +10:00
|
|
|
});
|
|
|
|
|
2019-06-11 20:03:42 +10:00
|
|
|
initDb(config.databaseConnectionString, async (err, db) => {
|
2018-01-07 04:55:48 +10:00
|
|
|
// On connection error we display then exit
|
|
|
|
if(err){
|
|
|
|
console.log(colors.red('Error connecting to MongoDB: ' + err));
|
2018-01-15 03:02:10 +10:00
|
|
|
process.exit(2);
|
2018-01-07 04:55:48 +10:00
|
|
|
}
|
|
|
|
|
|
|
|
// add db to app for routes
|
|
|
|
app.db = db;
|
2018-02-23 03:41:24 +10:00
|
|
|
app.config = config;
|
|
|
|
app.port = app.get('port');
|
2018-01-07 04:55:48 +10:00
|
|
|
|
2019-06-01 22:22:14 +10:00
|
|
|
// Fire up the cron job to clear temp held stock
|
|
|
|
cron.schedule('*/1 * * * *', async () => {
|
|
|
|
const validSessions = await db.sessions.find({}).toArray();
|
|
|
|
const validSessionIds = [];
|
|
|
|
_.forEach(validSessions, (value) => {
|
|
|
|
validSessionIds.push(value._id);
|
|
|
|
});
|
|
|
|
|
|
|
|
// Remove any invalid cart holds
|
|
|
|
await db.cart.remove({
|
2019-06-15 14:46:08 +10:00
|
|
|
sessionId: { $nin: validSessionIds }
|
2019-06-01 22:22:14 +10:00
|
|
|
});
|
|
|
|
});
|
|
|
|
|
2019-06-09 22:49:35 +10:00
|
|
|
// Set trackStock for testing
|
|
|
|
if(process.env.NODE_ENV === 'test'){
|
|
|
|
config.trackStock = true;
|
|
|
|
}
|
|
|
|
|
2019-06-17 20:21:45 +10:00
|
|
|
// Process schemas
|
|
|
|
await addSchemas();
|
|
|
|
|
2019-06-11 20:03:42 +10:00
|
|
|
// We index when not in test env
|
|
|
|
if(process.env.NODE_ENV !== 'test'){
|
|
|
|
try{
|
2019-06-15 14:46:08 +10:00
|
|
|
await runIndexing(app);
|
2019-06-11 20:03:42 +10:00
|
|
|
}catch(ex){
|
|
|
|
console.error(colors.red('Error setting up indexes:' + err));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Start the app
|
|
|
|
try{
|
|
|
|
await app.listen(app.get('port'));
|
2018-02-23 03:41:24 +10:00
|
|
|
app.emit('appStarted');
|
2018-01-15 03:02:10 +10:00
|
|
|
console.log(colors.green('expressCart running on host: http://localhost:' + app.get('port')));
|
2019-06-11 20:03:42 +10:00
|
|
|
}catch(ex){
|
|
|
|
console.error(colors.red('Error starting expressCart app:' + err));
|
2018-01-15 03:02:10 +10:00
|
|
|
process.exit(2);
|
2019-06-11 20:03:42 +10:00
|
|
|
}
|
2018-01-07 04:55:48 +10:00
|
|
|
});
|
|
|
|
|
|
|
|
module.exports = app;
|