From 10215af9ff0880133bc161647d81c569d2dfa68b Mon Sep 17 00:00:00 2001 From: Mark Moffat Date: Sat, 15 Jun 2019 15:35:31 +0930 Subject: [PATCH] Fixed settings saving + add sending of order hooks --- README.md | 6 ++++ app.js | 8 +++--- config/baseSchema.json | 3 ++ lib/common.js | 63 +++++++++++++++++++++++++++++++++--------- routes/admin.js | 3 +- 5 files changed, 65 insertions(+), 18 deletions(-) diff --git a/README.md b/README.md index a7c78fa..89f044f 100644 --- a/README.md +++ b/README.md @@ -51,6 +51,12 @@ Sometimes you might want some default sample/test data. To create this, run `npm There is currently a limited API for certain functions of the app. Using the API can be done by firstly generating an API key via the Admin login. `Admin > My Account > API Key (Generate) button`. Once an API Key is generated it will need to be supplied in a header called `apiKey` to authenticate requests. +## Hooks / Integrations + +On the completion of a order if a `orderHook` URL is configured, expressCart will POST the data to the configured URL. This is handy or IFTTT or Zapier Webhooks where you may want to use the integration methods to retrieve the order details in other systems. + +Example use might be to send all orders to a Google Docs spreadsheet or an accounting package or a packing slip software etc. + ## Admin Visit: [http://127.0.0.1:1111/admin](http://127.0.0.1:1111/admin) diff --git a/app.js b/app.js index 463a482..eacc7a3 100644 --- a/app.js +++ b/app.js @@ -20,15 +20,15 @@ let handlebars = require('express-handlebars'); const Ajv = require('ajv'); const ajv = new Ajv({ useDefaults: true }); -const baseConfig = ajv.validate(require('./config/baseSchema'), require('./config/settings.json')); +// get config +let config = common.getConfig(); + +const baseConfig = ajv.validate(require('./config/baseSchema'), config); if(baseConfig === false){ console.log(colors.red(`settings.json incorrect: ${ajv.errorsText()}`)); process.exit(2); } -// get config -let config = common.getConfig(); - // Validate the payment gateway config if(config.paymentGateway === 'paypal'){ const paypalConfig = ajv.validate(require('./config/paypalSchema'), require('./config/paypal.json')); diff --git a/config/baseSchema.json b/config/baseSchema.json index 7d9ccee..e712345 100644 --- a/config/baseSchema.json +++ b/config/baseSchema.json @@ -101,6 +101,9 @@ "trackStock": { "type": "boolean", "default": false + }, + "orderHook": { + "format": "uri-template" } }, "required": [ diff --git a/lib/common.js b/lib/common.js index f6c55ef..3a95733 100755 --- a/lib/common.js +++ b/lib/common.js @@ -2,6 +2,7 @@ const _ = require('lodash'); const uglifycss = require('uglifycss'); const colors = require('colors'); const cheerio = require('cheerio'); +const axios = require('axios'); const fs = require('fs'); const path = require('path'); const glob = require('glob'); @@ -164,16 +165,16 @@ const getImages = (dir, req, res, callback) => { }); }; -const getConfigFilename = () => { - let filename = path.join(__dirname, '../config', 'settings-local.json'); - if(fs.existsSync(filename)){ - return filename; - } - return path.join(__dirname, '../config', 'settings.json'); -}; - const getConfig = () => { - let config = JSON.parse(fs.readFileSync(getConfigFilename(), 'utf8')); + let config = JSON.parse(fs.readFileSync(path.join(__dirname, '../config', 'settings.json'), 'utf8')); + const localConfigFilePath = path.join(__dirname, '../config', 'settings-local.json'); + + // Check for local config file and merge with base settings + if(fs.existsSync(localConfigFilePath)){ + const localConfigFile = JSON.parse(fs.readFileSync(localConfigFilePath, 'utf8')); + config = Object.assign(config, localConfigFile); + } + config.customCss = typeof config.customCss !== 'undefined' ? escape.decode(config.customCss) : null; config.footerHtml = typeof config.footerHtml !== 'undefined' ? escape.decode(config.footerHtml) : null; config.googleAnalytics = typeof config.googleAnalytics !== 'undefined' ? escape.decode(config.googleAnalytics) : null; @@ -272,9 +273,31 @@ const updateConfig = (fields) => { settingsFile['productsPerPage'] = parseInt(fields['productsPerPage']); } - // write file + // If we have a local settings file (not git tracked) we loop its settings and save + // and changes made to them. All other settings get updated to the base settings file. + const localSettingsFile = path.join(__dirname, '../config', 'settings-local.json'); + if(fs.existsSync(localSettingsFile)){ + const localSettings = JSON.parse(fs.readFileSync(localSettingsFile)); + _.forEach(localSettings, (value, key) => { + if(fields[key]){ + localSettings[key] = fields[key]; + + // Exists in local so remove from main settings file + delete settingsFile[key]; + } + }); + // Save our local settings + try{ + fs.writeFileSync(localSettingsFile, JSON.stringify(localSettings, null, 4)); + }catch(exception){ + console.log('Failed to save local settings file', exception); + } + } + + // write base settings file + const baseSettingsFile = path.join(__dirname, '../config', 'settings.json'); try{ - fs.writeFileSync(getConfigFilename(), JSON.stringify(settingsFile, null, 4)); + fs.writeFileSync(baseSettingsFile, JSON.stringify(settingsFile, null, 4)); return true; }catch(exception){ return false; @@ -470,6 +493,20 @@ const getData = (req, page, query) => { }); }; +const hooker = (order) => { + let config = getConfig(); + + return axios.post(config.orderHook, order, { responseType: 'application/json' }) + .then((response) => { + if(response.status === 200){ + console.info('Successfully called order hook'); + } + }) + .catch((err) => { + console.log('Error calling hook:', err); + }); +}; + module.exports = { allowedMimeType, fileSizeLimit, @@ -483,7 +520,6 @@ module.exports = { checkDirectorySync, getThemes, getImages, - getConfigFilename, getConfig, getPaymentConfig, updateConfig, @@ -496,5 +532,6 @@ module.exports = { getEmailTemplate, sendEmail, getId, - getData + getData, + hooker }; diff --git a/routes/admin.js b/routes/admin.js index 66f099c..92cd021 100644 --- a/routes/admin.js +++ b/routes/admin.js @@ -1,4 +1,5 @@ const express = require('express'); +const _ = require('lodash'); const common = require('../lib/common'); const { restrict, checkAccess } = require('../lib/auth'); const escape = require('html-entities').AllHtmlEntities; @@ -191,7 +192,7 @@ router.post('/admin/createApiKey', restrict, checkAccess, async (req, res) => { // settings update router.post('/admin/settings/update', restrict, checkAccess, (req, res) => { - let result = common.updateConfig(req.body); + const result = common.updateConfig(req.body); if(result === true){ res.status(200).json({ message: 'Settings successfully updated' }); res.configDirty = true;