From fc1580ddd62531b596294947787471f44c3c964e Mon Sep 17 00:00:00 2001 From: Mark Moffat Date: Mon, 17 Jun 2019 19:51:45 +0930 Subject: [PATCH] Starting to add schema validation to API endpoints --- app.js | 42 +- lib/common.js | 21 + lib/schema.js | 23 + lib/schemas/editProduct.json | 40 ++ lib/schemas/newProduct.json | 40 ++ package-lock.json | 775 ++++++++++++++++++++++++-- package.json | 2 + public/javascripts/expressCart.js | 19 +- public/javascripts/expressCart.min.js | 2 +- routes/index.js | 5 +- routes/product.js | 252 ++++++--- views/product_edit.hbs | 32 +- views/product_new.hbs | 32 +- views/themes/Cloth/product.hbs | 8 +- views/themes/Material/product.hbs | 8 +- views/themes/Mono/product.hbs | 8 +- 16 files changed, 1122 insertions(+), 187 deletions(-) create mode 100644 lib/schema.js create mode 100644 lib/schemas/editProduct.json create mode 100644 lib/schemas/newProduct.json diff --git a/app.js b/app.js index eacc7a3..87a85e6 100644 --- a/app.js +++ b/app.js @@ -13,6 +13,7 @@ const colors = require('colors'); const cron = require('node-cron'); const common = require('./lib/common'); const { runIndexing } = require('./lib/indexing'); +const { addSchemas } = require('./lib/schema'); const { initDb } = require('./lib/db'); let handlebars = require('express-handlebars'); @@ -78,7 +79,7 @@ app.set('view engine', 'hbs'); // helpers for the handlebar templating platform handlebars = handlebars.create({ helpers: { - perRowClass: function(numProducts){ + perRowClass: (numProducts) => { if(parseInt(numProducts) === 1){ return'col-md-12 col-xl-12 col m12 xl12 product-item'; } @@ -94,7 +95,7 @@ handlebars = handlebars.create({ return'col-md-6 col-xl-6 col m6 xl6 product-item'; }, - menuMatch: function(title, search){ + menuMatch: (title, search) => { if(!title || !search){ return''; } @@ -103,22 +104,22 @@ handlebars = handlebars.create({ } return''; }, - getTheme: function(view){ + getTheme: (view) => { return`themes/${config.theme}/${view}`; }, - formatAmount: function(amt){ + formatAmount: (amt) => { if(amt){ return numeral(amt).format('0.00'); } return'0.00'; }, - amountNoDecimal: function(amt){ + amountNoDecimal: (amt) => { if(amt){ return handlebars.helpers.formatAmount(amt).replace('.', ''); } return handlebars.helpers.formatAmount(amt); }, - getStatusColor: function (status){ + getStatusColor: (status) => { switch(status){ case'Paid': return'success'; @@ -138,52 +139,58 @@ handlebars = handlebars.create({ return'danger'; } }, - checkProductOptions: function (opts){ + checkProductOptions: (opts) => { if(opts){ return'true'; } return'false'; }, - currencySymbol: function(value){ + currencySymbol: (value) => { if(typeof value === 'undefined' || value === ''){ return'$'; } return value; }, - objectLength: function(obj){ + objectLength: (obj) => { if(obj){ return Object.keys(obj).length; } return 0; }, - checkedState: function (state){ + stringify: (obj) => { + if(obj){ + return JSON.stringify(obj); + } + return''; + }, + checkedState: (state) => { if(state === 'true' || state === true){ return'checked'; } return''; }, - selectState: function (state, value){ + selectState: (state, value) => { if(state === value){ return'selected'; } return''; }, - isNull: function (value, options){ + isNull: (value, options) => { if(typeof value === 'undefined' || value === ''){ return options.fn(this); } return options.inverse(this); }, - toLower: function (value){ + toLower: (value) => { if(value){ return value.toLowerCase(); } return null; }, - formatDate: function (date, format){ + formatDate: (date, format) => { return moment(date).format(format); }, - ifCond: function (v1, operator, v2, options){ + ifCond: (v1, operator, v2, options) => { switch(operator){ case'==': return(v1 === v2) ? options.fn(this) : options.inverse(this); @@ -207,7 +214,7 @@ handlebars = handlebars.create({ return options.inverse(this); } }, - isAnAdmin: function (value, options){ + isAnAdmin: (value, options) => { if(value === 'true' || value === true){ return options.fn(this); } @@ -355,6 +362,9 @@ initDb(config.databaseConnectionString, async (err, db) => { config.trackStock = true; } + // Process schemas + await addSchemas(); + // We index when not in test env if(process.env.NODE_ENV !== 'test'){ try{ diff --git a/lib/common.js b/lib/common.js index 3a95733..f3a31c7 100755 --- a/lib/common.js +++ b/lib/common.js @@ -40,6 +40,18 @@ const mongoSanitize = (param) => { return param; }; +const safeParseInt = (param) => { + if(param){ + try{ + return parseInt(param); + }catch(ex){ + return param; + } + }else{ + return param; + } +}; + const checkboxBool = (param) => { if(param && param === 'on'){ return true; @@ -47,6 +59,13 @@ const checkboxBool = (param) => { return false; }; +const convertBool = (value) => { + if(value === 'true' || value === true){ + return true; + } + return false; +}; + const showCartCloseBtn = (page) => { let showCartCloseButton = true; if(page === 'checkout' || page === 'pay'){ @@ -512,7 +531,9 @@ module.exports = { fileSizeLimit, cleanHtml, mongoSanitize, + safeParseInt, checkboxBool, + convertBool, showCartCloseBtn, addSitemapProducts, clearSessionValue, diff --git a/lib/schema.js b/lib/schema.js new file mode 100644 index 0000000..995316f --- /dev/null +++ b/lib/schema.js @@ -0,0 +1,23 @@ +const path = require('path'); +const fs = require('fs'); +const _ = require('lodash'); +const Validator = require('jsonschema').Validator; +const v = new Validator(); +const glob = require('glob-fs')(); + +const addSchemas = () => { + const schemaFiles = glob.readdirSync('./lib/**/*.json'); + _.forEach(schemaFiles, (file) => { + const fileData = JSON.parse(fs.readFileSync(file, 'utf-8')); + v.addSchema(fileData, path.basename(schemaFiles[0], '.json')); + }); +}; + +const validateJson = (schema, json) => { + return v.validate(json, schema); +}; + +module.exports = { + addSchemas, + validateJson +}; diff --git a/lib/schemas/editProduct.json b/lib/schemas/editProduct.json new file mode 100644 index 0000000..d027dbd --- /dev/null +++ b/lib/schemas/editProduct.json @@ -0,0 +1,40 @@ +{ + "id": "/editProduct", + "type": "object", + "properties": { + "productPermalink": { + "type": "string" + }, + "productTitle": { + "type": "string" + }, + "productPrice": { + "type": "number" + }, + "productDescription": { + "type": "string" + }, + "productPublished": { + "type": "boolean" + }, + "productTags": { + "type": "string" + }, + "productOptions": { + "type": ["object", "string", "null"] + }, + "productComment": { + "type": "boolean" + }, + "productStock": { + "type": ["number", "null"] + } + }, + "required": [ + "productPermalink", + "productTitle", + "productPrice", + "productDescription", + "productPublished" + ] +} \ No newline at end of file diff --git a/lib/schemas/newProduct.json b/lib/schemas/newProduct.json new file mode 100644 index 0000000..32677a8 --- /dev/null +++ b/lib/schemas/newProduct.json @@ -0,0 +1,40 @@ +{ + "id": "/newProduct", + "type": "object", + "properties": { + "productPermalink": { + "type": "string" + }, + "productTitle": { + "type": "string" + }, + "productPrice": { + "type": "number" + }, + "productDescription": { + "type": "string" + }, + "productPublished": { + "type": "boolean" + }, + "productTags": { + "type": "string" + }, + "productOptions": { + "type": ["object", "string", "null"] + }, + "productComment": { + "type": "boolean" + }, + "productStock": { + "type": ["number", "null"] + } + }, + "required": [ + "productPermalink", + "productTitle", + "productPrice", + "productDescription", + "productPublished" + ] +} \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index a70010f..aab6441 100644 --- a/package-lock.json +++ b/package-lock.json @@ -643,8 +643,15 @@ "ansi-wrap": { "version": "0.1.0", "resolved": "https://registry.npmjs.org/ansi-wrap/-/ansi-wrap-0.1.0.tgz", - "integrity": "sha1-qCJQ3bABXponyoLoLqYDu/pF768=", - "dev": true + "integrity": "sha1-qCJQ3bABXponyoLoLqYDu/pF768=" + }, + "ansi-yellow": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/ansi-yellow/-/ansi-yellow-0.1.1.tgz", + "integrity": "sha1-y5NW8vRscy8OMZnmEClVp32oPB0=", + "requires": { + "ansi-wrap": "0.1.0" + } }, "anymatch": { "version": "3.0.2", @@ -711,8 +718,7 @@ "arr-flatten": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/arr-flatten/-/arr-flatten-1.1.0.tgz", - "integrity": "sha512-L3hKV5R/p5o81R7O02IGnwpDmkp6E982XhtbuwSe3O4qOtMMMtodicASA1Cny2U+aCXcNpml+m4dPsvsJ3jatg==", - "dev": true + "integrity": "sha512-L3hKV5R/p5o81R7O02IGnwpDmkp6E982XhtbuwSe3O4qOtMMMtodicASA1Cny2U+aCXcNpml+m4dPsvsJ3jatg==" }, "arr-map": { "version": "2.0.2", @@ -2374,8 +2380,7 @@ "component-emitter": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/component-emitter/-/component-emitter-1.2.1.tgz", - "integrity": "sha1-E3kY1teCg/ffemt8WmPhQOaUJeY=", - "dev": true + "integrity": "sha1-E3kY1teCg/ffemt8WmPhQOaUJeY=" }, "concat-map": { "version": "0.0.1", @@ -3014,6 +3019,11 @@ "is-obj": "^1.0.0" } }, + "dotdir-regex": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/dotdir-regex/-/dotdir-regex-0.1.0.tgz", + "integrity": "sha1-1F30yIY75vVZPXFpFDgXZ+k4wLY=" + }, "duplexer3": { "version": "0.1.4", "resolved": "https://registry.npmjs.org/duplexer3/-/duplexer3-0.1.4.tgz", @@ -3083,6 +3093,11 @@ "once": "^1.4.0" } }, + "ends-with": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/ends-with/-/ends-with-0.2.0.tgz", + "integrity": "sha1-L52pjVelDP2kVxzkM5AAUA9Oa4o=" + }, "entities": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/entities/-/entities-1.1.1.tgz", @@ -3554,6 +3569,44 @@ } } }, + "expand-range": { + "version": "1.8.2", + "resolved": "https://registry.npmjs.org/expand-range/-/expand-range-1.8.2.tgz", + "integrity": "sha1-opnv/TNf4nIeuujiV+x5ZE/IUzc=", + "requires": { + "fill-range": "^2.1.0" + }, + "dependencies": { + "fill-range": { + "version": "2.2.4", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-2.2.4.tgz", + "integrity": "sha512-cnrcCbj01+j2gTG921VZPnHbjmdAf8oQV/iGeV2kZxGSyfYjjTyY79ErsK1WJWMpw6DaApEX72binqJE+/d+5Q==", + "requires": { + "is-number": "^2.1.0", + "isobject": "^2.0.0", + "randomatic": "^3.0.0", + "repeat-element": "^1.1.2", + "repeat-string": "^1.5.2" + } + }, + "is-number": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-2.1.0.tgz", + "integrity": "sha1-Afy7s5NGOlSPL0ZszhbezknbkI8=", + "requires": { + "kind-of": "^3.0.2" + } + }, + "isobject": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/isobject/-/isobject-2.1.0.tgz", + "integrity": "sha1-8GVWEJaj8dou9GJy+BXIQNh+DIk=", + "requires": { + "isarray": "1.0.0" + } + } + } + }, "expand-tilde": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/expand-tilde/-/expand-tilde-2.0.2.tgz", @@ -3568,6 +3621,21 @@ "resolved": "https://registry.npmjs.org/expect-ct/-/expect-ct-0.2.0.tgz", "integrity": "sha512-6SK3MG/Bbhm8MsgyJAylg+ucIOU71/FzyFalcfu5nY19dH8y/z0tBJU0wrNBXD4B27EoQtqPF/9wqH0iYAd04g==" }, + "export-files": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/export-files/-/export-files-2.1.1.tgz", + "integrity": "sha1-u/ZFdAU6CeTrmOX0NQHVcrLDzn8=", + "requires": { + "lazy-cache": "^1.0.3" + }, + "dependencies": { + "lazy-cache": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/lazy-cache/-/lazy-cache-1.0.4.tgz", + "integrity": "sha1-odePw6UEdMuAhF07O24dpJpEbo4=" + } + } + }, "express": { "version": "4.17.1", "resolved": "https://registry.npmjs.org/express/-/express-4.17.1.tgz", @@ -3899,6 +3967,11 @@ } } }, + "filename-regex": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/filename-regex/-/filename-regex-2.0.1.tgz", + "integrity": "sha1-wcS5vuPglyXdsQa3XB4wH+LxiyY=" + }, "fill-range": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-4.0.0.tgz", @@ -4052,8 +4125,7 @@ "for-in": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/for-in/-/for-in-1.0.2.tgz", - "integrity": "sha1-gQaNKVqBQuwKxybG4iAMMPttXoA=", - "dev": true + "integrity": "sha1-gQaNKVqBQuwKxybG4iAMMPttXoA=" }, "for-own": { "version": "1.0.0", @@ -4105,6 +4177,11 @@ "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", "integrity": "sha1-PYyt2Q2XZWn6g1qx+OSyOhBWBac=" }, + "fs-exists-sync": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/fs-exists-sync/-/fs-exists-sync-0.1.0.tgz", + "integrity": "sha1-mC1ok6+RjnLQjeyehnP/K1qNat0=" + }, "fs-mkdirp-stream": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/fs-mkdirp-stream/-/fs-mkdirp-stream-1.0.0.tgz", @@ -4187,6 +4264,447 @@ "path-is-absolute": "^1.0.0" } }, + "glob-base": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/glob-base/-/glob-base-0.3.0.tgz", + "integrity": "sha1-27Fk9iIbHAscz4Kuoyi0l98Oo8Q=", + "requires": { + "glob-parent": "^2.0.0", + "is-glob": "^2.0.0" + }, + "dependencies": { + "glob-parent": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-2.0.0.tgz", + "integrity": "sha1-gTg9ctsFT8zPUzbaqQLxgvbtuyg=", + "requires": { + "is-glob": "^2.0.0" + } + }, + "is-extglob": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-1.0.0.tgz", + "integrity": "sha1-rEaBd8SUNAWgkvyPKXYMb/xiBsA=" + }, + "is-glob": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-2.0.1.tgz", + "integrity": "sha1-0Jb5JqPe1WAPP9/ZEZjLCIjC2GM=", + "requires": { + "is-extglob": "^1.0.0" + } + } + } + }, + "glob-fs": { + "version": "0.1.7", + "resolved": "https://registry.npmjs.org/glob-fs/-/glob-fs-0.1.7.tgz", + "integrity": "sha512-f0U3u9xK8MEYtKDCnZXvZrZAy4uNp+KSA4xfaKI/NxbE6HXhqUBQ485Uwd6jQa/Q6z1yKi804WT9y53RrwuMxQ==", + "requires": { + "async": "^1.3.0", + "bluebird": "^2.9.33", + "component-emitter": "^1.2.0", + "ends-with": "^0.2.0", + "export-files": "^2.0.1", + "extend-shallow": "^2.0.0", + "get-value": "^1.1.5", + "glob-fs-dotfiles": "^0.1.6", + "glob-fs-gitignore": "^0.1.5", + "glob-parent": "^1.2.0", + "graceful-fs": "^4.1.2", + "is-dotdir": "^0.1.0", + "is-dotfile": "^1.0.1", + "is-glob": "^2.0.0", + "is-windows": "^0.1.0", + "kind-of": "^2.0.0", + "lazy-cache": "^0.1.0", + "micromatch": "github:jonschlinkert/micromatch#2.2.0", + "mixin-object": "^2.0.0", + "object-visit": "^0.1.0", + "object.omit": "^1.1.0", + "parse-filepath": "^0.6.1", + "relative": "^3.0.1", + "set-value": "^0.2.0", + "starts-with": "^1.0.2", + "through2": "^2.0.0" + }, + "dependencies": { + "arr-diff": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/arr-diff/-/arr-diff-1.1.0.tgz", + "integrity": "sha1-aHwydYFjWI/vfeezb6vklesaOZo=", + "requires": { + "arr-flatten": "^1.0.1", + "array-slice": "^0.2.3" + } + }, + "array-slice": { + "version": "0.2.3", + "resolved": "https://registry.npmjs.org/array-slice/-/array-slice-0.2.3.tgz", + "integrity": "sha1-3Tz7gO15c6dRF82sabC5nshhhvU=" + }, + "array-unique": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/array-unique/-/array-unique-0.2.1.tgz", + "integrity": "sha1-odl8yvy8JiXMcPrc6zalDFiwGlM=" + }, + "async": { + "version": "1.5.2", + "resolved": "https://registry.npmjs.org/async/-/async-1.5.2.tgz", + "integrity": "sha1-7GphrlZIDAw8skHJVhjiCJL5Zyo=" + }, + "bluebird": { + "version": "2.11.0", + "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-2.11.0.tgz", + "integrity": "sha1-U0uQM8AiyVecVro7Plpcqvu2UOE=" + }, + "braces": { + "version": "1.8.5", + "resolved": "https://registry.npmjs.org/braces/-/braces-1.8.5.tgz", + "integrity": "sha1-uneWLhLf+WnWt2cR6RS3N4V79qc=", + "requires": { + "expand-range": "^1.8.1", + "preserve": "^0.2.0", + "repeat-element": "^1.1.2" + } + }, + "expand-brackets": { + "version": "0.1.5", + "resolved": "https://registry.npmjs.org/expand-brackets/-/expand-brackets-0.1.5.tgz", + "integrity": "sha1-3wcoTjQqgHzXM6xa9yQR5YHRF3s=", + "requires": { + "is-posix-bracket": "^0.1.0" + } + }, + "extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "requires": { + "is-extendable": "^0.1.0" + } + }, + "extglob": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/extglob/-/extglob-0.3.2.tgz", + "integrity": "sha1-Lhj/PS9JqydlzskCPwEdqo2DSaE=", + "requires": { + "is-extglob": "^1.0.0" + } + }, + "get-value": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/get-value/-/get-value-1.3.1.tgz", + "integrity": "sha1-isfvTyA4I5KyZGVI+bmtLcbIlkI=", + "requires": { + "arr-flatten": "^1.0.1", + "is-extendable": "^0.1.1", + "lazy-cache": "^0.2.4", + "noncharacters": "^1.1.0" + }, + "dependencies": { + "lazy-cache": { + "version": "0.2.7", + "resolved": "https://registry.npmjs.org/lazy-cache/-/lazy-cache-0.2.7.tgz", + "integrity": "sha1-f+3fLctu23fRHvHRF6tf/fCrG2U=" + } + } + }, + "glob-parent": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-1.3.0.tgz", + "integrity": "sha1-lx7dgW7V21hwW1gHlkemTQrveWg=", + "requires": { + "is-glob": "^2.0.0" + } + }, + "is-absolute": { + "version": "0.2.6", + "resolved": "https://registry.npmjs.org/is-absolute/-/is-absolute-0.2.6.tgz", + "integrity": "sha1-IN5p89uULvLYe5wto28XIjWxtes=", + "requires": { + "is-relative": "^0.2.1", + "is-windows": "^0.2.0" + }, + "dependencies": { + "is-windows": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/is-windows/-/is-windows-0.2.0.tgz", + "integrity": "sha1-3hqm1j6indJIc3tp8f+LgALSEIw=" + } + } + }, + "is-extglob": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-1.0.0.tgz", + "integrity": "sha1-rEaBd8SUNAWgkvyPKXYMb/xiBsA=" + }, + "is-glob": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-2.0.1.tgz", + "integrity": "sha1-0Jb5JqPe1WAPP9/ZEZjLCIjC2GM=", + "requires": { + "is-extglob": "^1.0.0" + } + }, + "is-relative": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/is-relative/-/is-relative-0.2.1.tgz", + "integrity": "sha1-0n9MfVFtF1+2ENuEu+7yPDvJeqU=", + "requires": { + "is-unc-path": "^0.1.1" + } + }, + "is-unc-path": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/is-unc-path/-/is-unc-path-0.1.2.tgz", + "integrity": "sha1-arBTpyVzwQJQ/0FqOBTDUXivObk=", + "requires": { + "unc-path-regex": "^0.1.0" + } + }, + "is-windows": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/is-windows/-/is-windows-0.1.1.tgz", + "integrity": "sha1-vjEHFUMc+rzMVKs5USEPoLbQGr4=" + }, + "isobject": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/isobject/-/isobject-1.0.2.tgz", + "integrity": "sha1-8Pm4zpLdVA+gdAiC44NaLgIux4o=" + }, + "kind-of": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-2.0.1.tgz", + "integrity": "sha1-AY7HpM5+OobLkUG+UZ0kyPqpgbU=", + "requires": { + "is-buffer": "^1.0.2" + } + }, + "micromatch": { + "version": "github:jonschlinkert/micromatch#5017fd78202e04c684cc31d3c2fb1f469ea222ff", + "from": "github:jonschlinkert/micromatch#2.2.0", + "requires": { + "arr-diff": "^1.0.1", + "array-unique": "^0.2.1", + "braces": "^1.8.0", + "expand-brackets": "^0.1.1", + "extglob": "^0.3.0", + "filename-regex": "^2.0.0", + "is-glob": "^1.1.3", + "kind-of": "^1.1.0", + "object.omit": "^1.1.0", + "parse-glob": "^3.0.1", + "regex-cache": "^0.4.2" + }, + "dependencies": { + "is-glob": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-1.1.3.tgz", + "integrity": "sha1-tMZLgwPTkRRJKkYNNkzPsNPAoEU=" + }, + "kind-of": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-1.1.0.tgz", + "integrity": "sha1-FAo9LUGjbS78+pN3tiwk+ElaXEQ=" + } + } + }, + "object-visit": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/object-visit/-/object-visit-0.1.0.tgz", + "integrity": "sha1-sbtnSfIo7nbgxC84UdKKFNIzziY=", + "requires": { + "isobject": "^1.0.0" + } + }, + "parse-filepath": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/parse-filepath/-/parse-filepath-0.6.3.tgz", + "integrity": "sha1-OOF6c+Xk5ndrrpUG/DzLFLw6K4A=", + "requires": { + "is-absolute": "^0.2.2", + "map-cache": "^0.2.0" + } + }, + "set-value": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/set-value/-/set-value-0.2.0.tgz", + "integrity": "sha1-c7CmglwVjGoWqCu9yVd1vyqCX6s=", + "requires": { + "isobject": "^1.0.0", + "noncharacters": "^1.1.0" + } + } + } + }, + "glob-fs-dotfiles": { + "version": "0.1.6", + "resolved": "https://registry.npmjs.org/glob-fs-dotfiles/-/glob-fs-dotfiles-0.1.6.tgz", + "integrity": "sha1-tPF7c8GIQYq6R80gbPWnImtKiUk=" + }, + "glob-fs-gitignore": { + "version": "0.1.6", + "resolved": "https://registry.npmjs.org/glob-fs-gitignore/-/glob-fs-gitignore-0.1.6.tgz", + "integrity": "sha1-iF5vQS+FnMWXVhVIKdvVVybN6ZI=", + "requires": { + "findup-sync": "^1.0.0", + "micromatch": "^2.3.11", + "parse-gitignore": "^0.2.0" + }, + "dependencies": { + "arr-diff": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/arr-diff/-/arr-diff-2.0.0.tgz", + "integrity": "sha1-jzuCf5Vai9ZpaX5KQlasPOrjVs8=", + "requires": { + "arr-flatten": "^1.0.1" + } + }, + "array-unique": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/array-unique/-/array-unique-0.2.1.tgz", + "integrity": "sha1-odl8yvy8JiXMcPrc6zalDFiwGlM=" + }, + "braces": { + "version": "1.8.5", + "resolved": "https://registry.npmjs.org/braces/-/braces-1.8.5.tgz", + "integrity": "sha1-uneWLhLf+WnWt2cR6RS3N4V79qc=", + "requires": { + "expand-range": "^1.8.1", + "preserve": "^0.2.0", + "repeat-element": "^1.1.2" + } + }, + "detect-file": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/detect-file/-/detect-file-0.1.0.tgz", + "integrity": "sha1-STXe39lIhkjgBrASlWbpOGcR6mM=", + "requires": { + "fs-exists-sync": "^0.1.0" + } + }, + "expand-brackets": { + "version": "0.1.5", + "resolved": "https://registry.npmjs.org/expand-brackets/-/expand-brackets-0.1.5.tgz", + "integrity": "sha1-3wcoTjQqgHzXM6xa9yQR5YHRF3s=", + "requires": { + "is-posix-bracket": "^0.1.0" + } + }, + "expand-tilde": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/expand-tilde/-/expand-tilde-1.2.2.tgz", + "integrity": "sha1-C4HrqJflo9MdHD0QL48BRB5VlEk=", + "requires": { + "os-homedir": "^1.0.1" + } + }, + "extglob": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/extglob/-/extglob-0.3.2.tgz", + "integrity": "sha1-Lhj/PS9JqydlzskCPwEdqo2DSaE=", + "requires": { + "is-extglob": "^1.0.0" + } + }, + "findup-sync": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/findup-sync/-/findup-sync-1.0.0.tgz", + "integrity": "sha1-b35LV7buOkA3tEFOrt6j9Y9x4Ow=", + "requires": { + "detect-file": "^0.1.0", + "is-glob": "^2.0.1", + "micromatch": "^2.3.7", + "resolve-dir": "^0.1.0" + } + }, + "for-own": { + "version": "0.1.5", + "resolved": "https://registry.npmjs.org/for-own/-/for-own-0.1.5.tgz", + "integrity": "sha1-UmXGgaTylNq78XyVCbZ2OqhFEM4=", + "requires": { + "for-in": "^1.0.1" + } + }, + "global-modules": { + "version": "0.2.3", + "resolved": "https://registry.npmjs.org/global-modules/-/global-modules-0.2.3.tgz", + "integrity": "sha1-6lo77ULG1s6ZWk+KEmm12uIjgo0=", + "requires": { + "global-prefix": "^0.1.4", + "is-windows": "^0.2.0" + } + }, + "global-prefix": { + "version": "0.1.5", + "resolved": "https://registry.npmjs.org/global-prefix/-/global-prefix-0.1.5.tgz", + "integrity": "sha1-jTvGuNo8qBEqFg2NSW/wRiv+948=", + "requires": { + "homedir-polyfill": "^1.0.0", + "ini": "^1.3.4", + "is-windows": "^0.2.0", + "which": "^1.2.12" + } + }, + "is-extglob": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-1.0.0.tgz", + "integrity": "sha1-rEaBd8SUNAWgkvyPKXYMb/xiBsA=" + }, + "is-glob": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-2.0.1.tgz", + "integrity": "sha1-0Jb5JqPe1WAPP9/ZEZjLCIjC2GM=", + "requires": { + "is-extglob": "^1.0.0" + } + }, + "is-windows": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/is-windows/-/is-windows-0.2.0.tgz", + "integrity": "sha1-3hqm1j6indJIc3tp8f+LgALSEIw=" + }, + "micromatch": { + "version": "2.3.11", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-2.3.11.tgz", + "integrity": "sha1-hmd8l9FyCzY0MdBNDRUpO9OMFWU=", + "requires": { + "arr-diff": "^2.0.0", + "array-unique": "^0.2.1", + "braces": "^1.8.2", + "expand-brackets": "^0.1.4", + "extglob": "^0.3.1", + "filename-regex": "^2.0.0", + "is-extglob": "^1.0.0", + "is-glob": "^2.0.1", + "kind-of": "^3.0.2", + "normalize-path": "^2.0.1", + "object.omit": "^2.0.0", + "parse-glob": "^3.0.4", + "regex-cache": "^0.4.2" + } + }, + "object.omit": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/object.omit/-/object.omit-2.0.1.tgz", + "integrity": "sha1-Gpx0SCnznbuFjHbKNXmuKlTr0fo=", + "requires": { + "for-own": "^0.1.4", + "is-extendable": "^0.1.1" + } + }, + "resolve-dir": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/resolve-dir/-/resolve-dir-0.1.1.tgz", + "integrity": "sha1-shklmlYC+sXFxJatiUpujMQwJh4=", + "requires": { + "expand-tilde": "^1.2.2", + "global-modules": "^0.2.3" + } + } + } + }, "glob-parent": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.0.0.tgz", @@ -5227,7 +5745,6 @@ "version": "1.0.3", "resolved": "https://registry.npmjs.org/homedir-polyfill/-/homedir-polyfill-1.0.3.tgz", "integrity": "sha512-eSmmWE5bZTK2Nou4g0AI3zZ9rswp7GRKoKXS1BLUkvPviOqs4YTN1djQIqrXy9k5gEtdLPy86JjRwsNM9tnDcA==", - "dev": true, "requires": { "parse-passwd": "^1.0.0" } @@ -5447,8 +5964,7 @@ "ini": { "version": "1.3.5", "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.5.tgz", - "integrity": "sha512-RZY5huIKCMRWDUqZlEi72f/lmXKMvuszcMBduliQ3nnWbx9X/ZBQO7DijMEYS9EhHBb2qacRUMtC7svLwe0lcw==", - "dev": true + "integrity": "sha512-RZY5huIKCMRWDUqZlEi72f/lmXKMvuszcMBduliQ3nnWbx9X/ZBQO7DijMEYS9EhHBb2qacRUMtC7svLwe0lcw==" }, "inquirer": { "version": "0.12.0", @@ -5531,8 +6047,7 @@ "is-buffer": { "version": "1.1.6", "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.6.tgz", - "integrity": "sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==", - "dev": true + "integrity": "sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==" }, "is-builtin-module": { "version": "1.0.0", @@ -5592,6 +6107,27 @@ } } }, + "is-dotdir": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/is-dotdir/-/is-dotdir-0.1.0.tgz", + "integrity": "sha1-2h5UZPWfw6g8HYIrWs4JG0X+azE=", + "requires": { + "dotdir-regex": "^0.1.0" + } + }, + "is-dotfile": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/is-dotfile/-/is-dotfile-1.0.3.tgz", + "integrity": "sha1-pqLzL/0t+wT1yiXs0Pa4PPeYoeE=" + }, + "is-equal-shallow": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/is-equal-shallow/-/is-equal-shallow-0.1.3.tgz", + "integrity": "sha1-IjgJj8Ih3gvPpdnqxMRdY4qhxTQ=", + "requires": { + "is-primitive": "^2.0.0" + } + }, "is-error": { "version": "2.2.2", "resolved": "https://registry.npmjs.org/is-error/-/is-error-2.2.2.tgz", @@ -5601,8 +6137,7 @@ "is-extendable": { "version": "0.1.1", "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-0.1.1.tgz", - "integrity": "sha1-YrEQ4omkcUGOPsNqYX1HLjAd/Ik=", - "dev": true + "integrity": "sha1-YrEQ4omkcUGOPsNqYX1HLjAd/Ik=" }, "is-extglob": { "version": "2.1.1", @@ -5729,6 +6264,16 @@ "isobject": "^3.0.1" } }, + "is-posix-bracket": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/is-posix-bracket/-/is-posix-bracket-0.1.1.tgz", + "integrity": "sha1-MzTceXdDaOkvAW5vvAqI9c1ua8Q=" + }, + "is-primitive": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-primitive/-/is-primitive-2.0.0.tgz", + "integrity": "sha1-IHurkWOEmcB7Kt8kCkGochADRXU=" + }, "is-promise": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/is-promise/-/is-promise-2.1.0.tgz", @@ -5833,8 +6378,7 @@ "isexe": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", - "integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=", - "dev": true + "integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=" }, "isobject": { "version": "3.0.1", @@ -5931,6 +6475,11 @@ "integrity": "sha1-T9kss04OnbPInIYi7PUfm5eMbLk=", "dev": true }, + "jsonschema": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/jsonschema/-/jsonschema-1.2.4.tgz", + "integrity": "sha512-lz1nOH69GbsVHeVgEdvyavc/33oymY1AZwtePMiMj4HZPMbP5OIKK3zT9INMWjwua/V4Z4yq7wSlBbSG+g4AEw==" + }, "just-debounce": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/just-debounce/-/just-debounce-1.0.0.tgz", @@ -5950,7 +6499,6 @@ "version": "3.2.2", "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", - "dev": true, "requires": { "is-buffer": "^1.1.5" } @@ -5974,6 +6522,14 @@ "package-json": "^6.3.0" } }, + "lazy-cache": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/lazy-cache/-/lazy-cache-0.1.0.tgz", + "integrity": "sha1-1s1FAlHUFbcBA3ZfYxMKAEmgN5U=", + "requires": { + "ansi-yellow": "^0.1.1" + } + }, "lazystream": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/lazystream/-/lazystream-1.0.0.tgz", @@ -6267,8 +6823,7 @@ "map-cache": { "version": "0.2.2", "resolved": "https://registry.npmjs.org/map-cache/-/map-cache-0.2.2.tgz", - "integrity": "sha1-wyq9C9ZSXZsFFkW7TyasXcmKDb8=", - "dev": true + "integrity": "sha1-wyq9C9ZSXZsFFkW7TyasXcmKDb8=" }, "map-obj": { "version": "2.0.0", @@ -6337,6 +6892,11 @@ } } }, + "math-random": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/math-random/-/math-random-1.0.4.tgz", + "integrity": "sha512-rUxjysqif/BZQH2yhd5Aaq7vXMSx9NdEsQcyA07uEzIvxgI7zIr33gGsh+RU0/XjmQpCW7RsVof1vlkvQVCK5A==" + }, "md5-hex": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/md5-hex/-/md5-hex-3.0.0.tgz", @@ -6606,6 +7166,22 @@ } } }, + "mixin-object": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/mixin-object/-/mixin-object-2.0.1.tgz", + "integrity": "sha1-T7lJRB2rGCVA8f4DW6YOGUel5X4=", + "requires": { + "for-in": "^0.1.3", + "is-extendable": "^0.1.1" + }, + "dependencies": { + "for-in": { + "version": "0.1.8", + "resolved": "https://registry.npmjs.org/for-in/-/for-in-0.1.8.tgz", + "integrity": "sha1-2Hc5COMSVhCZUrH9ubP6hn0ndeE=" + } + } + }, "mkdirp": { "version": "0.5.1", "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz", @@ -6813,6 +7389,11 @@ "resolved": "https://registry.npmjs.org/nodemailer/-/nodemailer-4.7.0.tgz", "integrity": "sha512-IludxDypFpYw4xpzKdMAozBSkzKHmNBvGanUREjJItgJ2NYcK/s8+PggVhj7c2yGFQykKsnnmv1+Aqo0ZfjHmw==" }, + "noncharacters": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/noncharacters/-/noncharacters-1.1.0.tgz", + "integrity": "sha1-rzPfMP1Q7TxTzSAiWPJa2pC1QNI=" + }, "normalize-package-data": { "version": "2.4.0", "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-2.4.0.tgz", @@ -6829,7 +7410,6 @@ "version": "2.1.1", "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-2.1.1.tgz", "integrity": "sha1-GrKLVW4Zg2Oowab35vogE3/mrtk=", - "dev": true, "requires": { "remove-trailing-separator": "^1.0.1" } @@ -6950,6 +7530,30 @@ "make-iterator": "^1.0.0" } }, + "object.omit": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/object.omit/-/object.omit-1.1.0.tgz", + "integrity": "sha1-nRfqFneOUFfeundSxvVfFJaCnpQ=", + "requires": { + "for-own": "^0.1.3", + "isobject": "^1.0.0" + }, + "dependencies": { + "for-own": { + "version": "0.1.5", + "resolved": "https://registry.npmjs.org/for-own/-/for-own-0.1.5.tgz", + "integrity": "sha1-UmXGgaTylNq78XyVCbZ2OqhFEM4=", + "requires": { + "for-in": "^1.0.1" + } + }, + "isobject": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/isobject/-/isobject-1.0.2.tgz", + "integrity": "sha1-8Pm4zpLdVA+gdAiC44NaLgIux4o=" + } + } + }, "object.pick": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/object.pick/-/object.pick-1.3.0.tgz", @@ -7142,8 +7746,7 @@ "os-homedir": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/os-homedir/-/os-homedir-1.0.2.tgz", - "integrity": "sha1-/7xJiDNuDoM94MFox+8VISGqf7M=", - "dev": true + "integrity": "sha1-/7xJiDNuDoM94MFox+8VISGqf7M=" }, "os-locale": { "version": "1.4.0", @@ -7253,6 +7856,57 @@ "path-root": "^0.1.1" } }, + "parse-gitignore": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/parse-gitignore/-/parse-gitignore-0.2.0.tgz", + "integrity": "sha1-mHBtCfD5PuhjSLch/+4GBrwJPXQ=", + "requires": { + "ends-with": "^0.2.0", + "is-glob": "^2.0.0", + "starts-with": "^1.0.0" + }, + "dependencies": { + "is-extglob": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-1.0.0.tgz", + "integrity": "sha1-rEaBd8SUNAWgkvyPKXYMb/xiBsA=" + }, + "is-glob": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-2.0.1.tgz", + "integrity": "sha1-0Jb5JqPe1WAPP9/ZEZjLCIjC2GM=", + "requires": { + "is-extglob": "^1.0.0" + } + } + } + }, + "parse-glob": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/parse-glob/-/parse-glob-3.0.4.tgz", + "integrity": "sha1-ssN2z7EfNVE7rdFz7wu246OIORw=", + "requires": { + "glob-base": "^0.3.0", + "is-dotfile": "^1.0.0", + "is-extglob": "^1.0.0", + "is-glob": "^2.0.0" + }, + "dependencies": { + "is-extglob": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-1.0.0.tgz", + "integrity": "sha1-rEaBd8SUNAWgkvyPKXYMb/xiBsA=" + }, + "is-glob": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-2.0.1.tgz", + "integrity": "sha1-0Jb5JqPe1WAPP9/ZEZjLCIjC2GM=", + "requires": { + "is-extglob": "^1.0.0" + } + } + } + }, "parse-json": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-2.2.0.tgz", @@ -7277,8 +7931,7 @@ "parse-passwd": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/parse-passwd/-/parse-passwd-1.0.0.tgz", - "integrity": "sha1-bVuTSkVpk7I9N/QKOC1vFmao5cY=", - "dev": true + "integrity": "sha1-bVuTSkVpk7I9N/QKOC1vFmao5cY=" }, "parseurl": { "version": "1.3.3", @@ -7663,6 +8316,11 @@ "integrity": "sha1-6SQ0v6XqjBn0HN/UAddBo8gZ2Jc=", "dev": true }, + "preserve": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/preserve/-/preserve-0.2.0.tgz", + "integrity": "sha1-gV7R9uvGWSb4ZbMQwHE7yzMVzks=" + }, "pretty-hrtime": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/pretty-hrtime/-/pretty-hrtime-1.0.3.tgz", @@ -7759,6 +8417,28 @@ "resolved": "https://registry.npmjs.org/random-bytes/-/random-bytes-1.0.0.tgz", "integrity": "sha1-T2ih3Arli9P7lYSMMDJNt11kNgs=" }, + "randomatic": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/randomatic/-/randomatic-3.1.1.tgz", + "integrity": "sha512-TuDE5KxZ0J461RVjrJZCJc+J+zCkTb1MbH9AQUq68sMhOMcy9jLcb3BrZKgp9q9Ncltdg4QVqWrH02W2EFFVYw==", + "requires": { + "is-number": "^4.0.0", + "kind-of": "^6.0.0", + "math-random": "^1.0.1" + }, + "dependencies": { + "is-number": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-4.0.0.tgz", + "integrity": "sha512-rSklcAIlf1OmFdyAqbnWTLVelsQ58uvZ66S/ZyawjWqIviTWCjg2PzVGw8WUA+nNuPTqb4wgA+NszrJ+08LlgQ==" + }, + "kind-of": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.2.tgz", + "integrity": "sha512-s5kLOcnH0XqDO+FvuaLX8DDjZ18CGFk7VygH40QoKPUQhW4e2rvM0rwUq0t8IQDOwYSeLK01U90OjzBTme2QqA==" + } + } + }, "range-parser": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", @@ -7908,6 +8588,14 @@ "regenerate": "^1.4.0" } }, + "regex-cache": { + "version": "0.4.4", + "resolved": "https://registry.npmjs.org/regex-cache/-/regex-cache-0.4.4.tgz", + "integrity": "sha512-nVIZwtCjkC9YgvWkpM55B5rBhBYRZhAaJbgcFYXXsHnbZ9UZI9nnVWYZpBlCqv9ho2eZryPnWrZGsOdPwVWXWQ==", + "requires": { + "is-equal-shallow": "^0.1.3" + } + }, "regex-not": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/regex-not/-/regex-not-1.0.2.tgz", @@ -7974,6 +8662,24 @@ } } }, + "relative": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/relative/-/relative-3.0.2.tgz", + "integrity": "sha1-Dc2OxUpdNaPBXhBFA9ZTdbWlNn8=", + "requires": { + "isobject": "^2.0.0" + }, + "dependencies": { + "isobject": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/isobject/-/isobject-2.1.0.tgz", + "integrity": "sha1-8GVWEJaj8dou9GJy+BXIQNh+DIk=", + "requires": { + "isarray": "1.0.0" + } + } + } + }, "release-zalgo": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/release-zalgo/-/release-zalgo-1.0.0.tgz", @@ -8007,20 +8713,17 @@ "remove-trailing-separator": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/remove-trailing-separator/-/remove-trailing-separator-1.1.0.tgz", - "integrity": "sha1-wkvOKig62tW8P1jg1IJJuSN52O8=", - "dev": true + "integrity": "sha1-wkvOKig62tW8P1jg1IJJuSN52O8=" }, "repeat-element": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/repeat-element/-/repeat-element-1.1.2.tgz", - "integrity": "sha1-7wiaF40Ug7quTZPrmLT55OEdmQo=", - "dev": true + "integrity": "sha1-7wiaF40Ug7quTZPrmLT55OEdmQo=" }, "repeat-string": { "version": "1.6.1", "resolved": "https://registry.npmjs.org/repeat-string/-/repeat-string-1.6.1.tgz", - "integrity": "sha1-jcrkcOHIirwtYA//Sndihtp15jc=", - "dev": true + "integrity": "sha1-jcrkcOHIirwtYA//Sndihtp15jc=" }, "replace-ext": { "version": "1.0.0", @@ -8657,6 +9360,11 @@ "integrity": "sha512-MTX+MeG5U994cazkjd/9KNAapsHnibjMLnfXodlkXw76JEea0UiNzrqidzo1emMwk7w5Qhc9jd4Bn9TBb1MFwA==", "dev": true }, + "starts-with": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/starts-with/-/starts-with-1.0.2.tgz", + "integrity": "sha1-Fnk6cp2J1M89T7LtovkIrjV/GW8=" + }, "static-extend": { "version": "0.1.2", "resolved": "https://registry.npmjs.org/static-extend/-/static-extend-0.1.2.tgz", @@ -9023,7 +9731,6 @@ "version": "2.0.3", "resolved": "https://registry.npmjs.org/through2/-/through2-2.0.3.tgz", "integrity": "sha1-AARWmzfHx0ujnEPzzteNGtlBQL4=", - "dev": true, "requires": { "readable-stream": "^2.1.5", "xtend": "~4.0.1" @@ -9219,8 +9926,7 @@ "unc-path-regex": { "version": "0.1.2", "resolved": "https://registry.npmjs.org/unc-path-regex/-/unc-path-regex-0.1.2.tgz", - "integrity": "sha1-5z3T17DXxe2G+6xrCufYxqadUPo=", - "dev": true + "integrity": "sha1-5z3T17DXxe2G+6xrCufYxqadUPo=" }, "underscore": { "version": "1.8.3", @@ -9644,7 +10350,6 @@ "version": "1.3.0", "resolved": "https://registry.npmjs.org/which/-/which-1.3.0.tgz", "integrity": "sha512-xcJpopdamTuY5duC/KnTTNBraPK54YwpenP4lzxU8H91GudWpFv38u0CKjclE1Wi2EH2EDz5LRcHcKbCIzqGyg==", - "dev": true, "requires": { "isexe": "^2.0.0" } diff --git a/package.json b/package.json index 83dfe98..2220852 100644 --- a/package.json +++ b/package.json @@ -24,8 +24,10 @@ "express-handlebars": "^3.1.0", "express-session": "^1.16.1", "glob": "^7.1.4", + "glob-fs": "^0.1.7", "helmet": "^3.18.0", "html-entities": "^1.2.0", + "jsonschema": "^1.2.4", "lodash": "^4.17.11", "lunr": "^2.3.6", "mime-db": "^1.40.0", diff --git a/public/javascripts/expressCart.js b/public/javascripts/expressCart.js index 1aa3105..c6a6078 100644 --- a/public/javascripts/expressCart.js +++ b/public/javascripts/expressCart.js @@ -103,7 +103,7 @@ $(document).ready(function (){ $(this).addClass('table table-hover'); }); - $('#frmProductTags').tokenfield(); + $('#productTags').tokenfield(); $(document).on('click', '.dashboard_list', function(e){ window.document.location = $(this).attr('href'); @@ -245,8 +245,8 @@ $(document).ready(function (){ var optOptions = $('#product_optOptions').val(); var optJson = {}; - if($('#productOptJson').val() !== ''){ - optJson = JSON.parse($('#productOptJson').val()); + if($('#productOptions').val() !== ''){ + optJson = JSON.parse($('#productOptions').val()); } var html = '
  • '; @@ -271,7 +271,7 @@ $(document).ready(function (){ }; // write new json back to field - $('#productOptJson').val(JSON.stringify(optJson)); + $('#productOptions').val(JSON.stringify(optJson)); // clear inputs $('#product_optName').val(''); @@ -593,14 +593,15 @@ $(document).ready(function (){ // Call to API to check if a permalink is available $(document).on('click', '#validate_permalink', function(e){ - if($('#frmProductPermalink').val() !== ''){ + if($('#productPermalink').val() !== ''){ $.ajax({ method: 'POST', url: '/admin/api/validate_permalink', - data: { 'permalink': $('#frmProductPermalink').val(), 'docId': $('#frmProductId').val() } + data: { 'permalink': $('#productPermalink').val(), 'docId': $('#frmProductId').val() } }) .done(function(msg){ - showNotification(msg, 'success'); + console.log('msg', msg); + showNotification(msg.message, 'success'); }) .fail(function(msg){ showNotification(msg.responseJSON.message, 'danger'); @@ -654,8 +655,8 @@ $(document).ready(function (){ // create a permalink from the product title if no permalink has already been set $(document).on('click', '#frm_edit_product_save', function(e){ - if($('#frmProductPermalink').val() === '' && $('#frmProductTitle').val() !== ''){ - $('#frmProductPermalink').val(slugify($('#frmProductTitle').val())); + if($('#productPermalink').val() === '' && $('#productTitle').val() !== ''){ + $('#productPermalink').val(slugify($('#productTitle').val())); } }); diff --git a/public/javascripts/expressCart.min.js b/public/javascripts/expressCart.min.js index 94dc94a..076416a 100644 --- a/public/javascripts/expressCart.min.js +++ b/public/javascripts/expressCart.min.js @@ -1 +1 @@ -function deleteFromCart(t){$.ajax({method:"POST",url:"/product/removefromcart",data:{cartId:t.attr("data-id")}}).done(function(e){$("#cart-count").text(e.totalCartItems),0===e.totalCartItems?($(t).closest(".cart-row").hide("slow",function(){$(t).closest(".cart-row").remove()}),$(".cart-contents-shipping").hide("slow",function(){$(".cart-contents-shipping").remove()}),showNotification(e.message,"success"),setTimeout(function(){window.location="/"},3700)):($(t).closest(".cart-row").hide("slow",function(){$(t).closest(".cart-row").remove()}),showNotification(e.message,"success"))}).fail(function(t){showNotification(t.responseJSON.message,"danger")})}function slugify(t){return $.trim(t).replace(/[^a-z0-9-æøå]/gi,"-").replace(/-+/g,"-").replace(/^-|-$/g,"").replace(/æ/gi,"ae").replace(/ø/gi,"oe").replace(/å/gi,"a").toLowerCase()}function cartUpdate(t){$(t).val()>0?""!==$(t).val()&&updateCart():$(t).val(1)}function updateCart(){var t=[];$(".cart-product-quantity").each(function(){var e={cartIndex:$(this).attr("id"),itemQuantity:$(this).val(),productId:$(this).attr("data-id")};t.push(e)}),$.ajax({method:"POST",url:"/product/updatecart",data:{items:JSON.stringify(t)}}).done(function(t){updateCartDiv(),$("#cart-count").text(t.totalCartItems)}).fail(function(t){showNotification(t.responseJSON.message,"danger",!0)})}function updateCartDiv(){var t=window.location.pathname.split("/").length>0?window.location.pathname.split("/")[1]:"";$.ajax({method:"GET",url:"/cartPartial",data:{path:t}}).done(function(t){$("#cart").html(t)}).fail(function(t){showNotification(t.responseJSON.message,"danger")})}function getSelectedOptions(){var t={};return $(".product-opt").each(function(){if("opt-"!==$(this).attr("name")){var e=$(this).val().trim();"radio"===$(this).attr("type")&&(e=$('input[name="'+$(this).attr("name")+'"]:checked').val()),t[$(this).attr("name").substring(4,$(this).attr("name").length)]=e}else t[$(this).val().trim()]=$(this).prop("checked")}),t}function showNotification(t,e,a){a=a||!1,$("#notify_message").removeClass(),$("#notify_message").addClass("alert-"+e),$("#notify_message").html(t),$("#notify_message").slideDown(600).delay(2500).slideUp(600,function(){!0===a&&location.reload()})}$(document).ready(function(){if("Material"===$("#cartTheme").val()&&$(".materialboxed").materialbox(),$(window).width()<768&&($(".menu-side").on("click",function(t){t.preventDefault(),$('.menu-side li:not(".active")').slideToggle()}),$('.menu-side li:not(".active")').hide(),$(".menu-side>.active").html(''),$(".menu-side>.active").addClass("menu-side-mobile"),0===$("#navbar ul li").length&&$("#navbar").hide(),$("#offcanvasClose").hide()),$(".shipping-form input").each(function(t){$(this).wrap("
    ");var e=$(this).attr("placeholder");$(this).after('")}),$(".shipping-form input").on("focus",function(){$(this).next().addClass("floatLabel"),$(this).next().removeClass("hidden")}),$(".shipping-form input").on("blur",function(){""===$(this).val()&&($(this).next().addClass("hidden"),$(this).next().removeClass("floatLabel"))}),$(".menu-btn").on("click",function(t){t.preventDefault()}),$("#sendTestEmail").on("click",function(t){t.preventDefault(),$.ajax({method:"POST",url:"/admin/testEmail"}).done(function(t){showNotification(t,"success")}).fail(function(t){showNotification(t.responseJSON.message,"danger")})}),$("#footerHtml").length){var t=window.CodeMirror.fromTextArea(document.getElementById("footerHtml"),{mode:"xml",tabMode:"indent",theme:"flatly",lineNumbers:!0,htmlMode:!0,fixedGutter:!1});t.setValue(t.getValue())}if($("#googleAnalytics").length&&window.CodeMirror.fromTextArea(document.getElementById("googleAnalytics"),{mode:"xml",tabMode:"indent",theme:"flatly",lineNumbers:!0,htmlMode:!0,fixedGutter:!1}),$("#customCss").length){var e=window.CodeMirror.fromTextArea(document.getElementById("customCss"),{mode:"text/css",tabMode:"indent",theme:"flatly",lineNumbers:!0}),a=window.cssbeautify(e.getValue(),{indent:" ",autosemicolon:!0});e.setValue(a)}if($("table").each(function(){$(this).addClass("table table-hover")}),$("#frmProductTags").tokenfield(),$(document).on("click",".dashboard_list",function(t){window.document.location=$(this).attr("href")}).hover(function(){$(this).toggleClass("hover")}),$(".product-title").dotdotdot({ellipsis:"..."}),$('input[class="published_state"]').change(function(){$.ajax({method:"POST",url:"/admin/product/published_state",data:{id:this.id,state:this.checked}}).done(function(t){showNotification(t.message,"success")}).fail(function(t){showNotification(t.responseJSON.message,"danger")})}),$(document).on("click",".btn-qty-minus",function(t){var e=$(t.target).parent().parent().find(".cart-product-quantity");$(e).val(parseInt(e.val())-1),cartUpdate(e)}),$(document).on("click",".btn-qty-add",function(t){var e=$(t.target).parent().parent().find(".cart-product-quantity");$(e).val(parseInt(e.val())+1),cartUpdate(e)}),$(document).on("change",".cart-product-quantity",function(t){cartUpdate(t.target)}),$(document).on("click",".btn-delete-from-cart",function(t){deleteFromCart($(t.target))}),$(document).on("click",".orderFilterByStatus",function(t){t.preventDefault(),window.location="/admin/orders/bystatus/"+$("#orderStatusFilter").val()}),$("#pager").length){var o=$("#pageNum").val(),i=$("#productsPerPage").val(),n=$("#totalProductCount").val(),r=$("#paginateUrl").val(),s=$("#searchTerm").val();""!==s&&(s+="/");var c="/"+r+"/"+s+"{{number}}",d=Math.ceil(n/i);parseInt(n)>parseInt(i)&&$("#pager").bootpag({total:d,page:o,maxVisible:5,href:c,wrapClass:"pagination",prevClass:"waves-effect",nextClass:"waves-effect",activeClass:"pag-active waves-effect"})}if($(document).on("click","#btnPageUpdate",function(t){t.preventDefault(),$.ajax({method:"POST",url:"/admin/settings/pages/update",data:{page_id:$("#page_id").val(),pageName:$("#pageName").val(),pageSlug:$("#pageSlug").val(),pageEnabled:$("#pageEnabled").is(":checked"),pageContent:$("#pageContent").val()}}).done(function(t){showNotification(t.message,"success",!0)}).fail(function(t){showNotification(t.responseJSON.message,"danger")})}),$(document).on("click","#btnGenerateAPIkey",function(t){t.preventDefault(),$.ajax({method:"POST",url:"/admin/createApiKey"}).done(function(t){$("#apiKey").val(t.apiKey),showNotification(t.message,"success",!0)}).fail(function(t){showNotification(t.responseJSON.message,"danger")})}),$(document).on("click",".product_opt_remove",function(t){t.preventDefault();var e=$(this).closest("li").find(".opt-name").html();$.ajax({method:"POST",url:"/admin/settings/option/remove/",data:{productId:$("#frmProductId").val(),optName:e}}).done(function(t){showNotification(t.message,"success",!0)}).fail(function(t){showNotification(t.responseJSON.message,"danger")})}),$(document).on("click","#product_opt_add",function(t){t.preventDefault();var e=$("#product_optName").val(),a=$("#product_optLabel").val(),o=$("#product_optType").val(),i=$("#product_optOptions").val(),n={};""!==$("#productOptJson").val()&&(n=JSON.parse($("#productOptJson").val()));var r='
  • ';r+='
    ',r+='
    '+e+"
    ",r+='
    '+a+"
    ",r+='
    '+o+"
    ",r+='
    '+i+"
    ",r+='
    ',r+='',r+="
  • ",$("#product_opt_wrapper").append(r),n[e]={optName:e,optLabel:a,optType:o,optOptions:$.grep(i.split(","),function(t){return 0===t||t})},$("#productOptJson").val(JSON.stringify(n)),$("#product_optName").val(""),$("#product_optLabel").val(""),$("#product_optOptions").val("")}),$("#stripeButton").validator().on("click",function(t){(t.preventDefault(),0===$("#shipping-form").validator("validate").has(".has-error").length)&&window.StripeCheckout.configure({key:$("#stripeButton").data("key"),image:$("#stripeButton").data("image"),locale:"auto",token:function(t){$("#shipping-form").append(''),$("#shipping-form").submit()}}).open({name:$("#stripeButton").data("name"),description:$("#stripeButton").data("description"),zipCode:$("#stripeButton").data("zipCode"),amount:$("#stripeButton").data("amount"),currency:$("#stripeButton").data("currency")})}),$("#settingsForm").validator().on("submit",function(t){t.isDefaultPrevented()||(t.preventDefault(),$("#footerHtml_input").val($(".CodeMirror")[0].CodeMirror.getValue()),$("#googleAnalytics_input").val($(".CodeMirror")[1].CodeMirror.getValue()),$("#customCss_input").val($(".CodeMirror")[2].CodeMirror.getValue()),$.ajax({method:"POST",url:"/admin/settings/update",data:$("#settingsForm").serialize()}).done(function(t){showNotification(t.message,"success")}).fail(function(t){showNotification(t.responseJSON.message,"danger")}))}),$("#customerLogout").on("click",function(t){$.ajax({method:"POST",url:"/customer/logout",data:{}}).done(function(t){location.reload()})}),$("#createCustomerAccount").validator().on("click",function(t){t.preventDefault(),0===$("#shipping-form").validator("validate").has(".has-error").length&&$.ajax({method:"POST",url:"/customer/create",data:{email:$("#shipEmail").val(),firstName:$("#shipFirstname").val(),lastName:$("#shipLastname").val(),address1:$("#shipAddr1").val(),address2:$("#shipAddr2").val(),country:$("#shipCountry").val(),state:$("#shipState").val(),postcode:$("#shipPostcode").val(),phone:$("#shipPhoneNumber").val(),password:$("#newCustomerPassword").val()}}).done(function(t){location.reload()}).fail(function(t){showNotification(t.responseJSON.err,"danger")})}),$("#loginForm").on("click",function(t){t.isDefaultPrevented()||(t.preventDefault(),$.ajax({method:"POST",url:"/admin/login_action",data:{email:$("#email").val(),password:$("#password").val()}}).done(function(t){window.location="/admin"}).fail(function(t){showNotification(t.responseJSON.message,"danger")})),t.preventDefault()}),$("#customerLogin").on("click",function(t){t.isDefaultPrevented()||(t.preventDefault(),$.ajax({method:"POST",url:"/customer/login_action",data:{loginEmail:$("#customerLoginEmail").val(),loginPassword:$("#customerLoginPassword").val()}}).done(function(t){var e=t.customer;$("#shipEmail").val(e.email),$("#shipFirstname").val(e.firstName),$("#shipLastname").val(e.lastName),$("#shipAddr1").val(e.address1),$("#shipAddr2").val(e.address2),$("#shipCountry").val(e.country),$("#shipState").val(e.state),$("#shipPostcode").val(e.postcode),$("#shipPhoneNumber").val(e.phone),location.reload()}).fail(function(t){showNotification(t.responseJSON.message,"danger")})),t.preventDefault()}),$(document).on("click",".image-next",function(t){var e=$(".thumbnail-image"),a=0,o=0;$(".thumbnail-image").each(function(){$("#product-title-image").attr("src")===$(this).attr("src")&&(o=a+1===e.length||a+1<0?0:a+1),a++}),$("#product-title-image").attr("src",$(e).eq(o).attr("src"))}),$(document).on("click",".image-prev",function(t){var e=$(".thumbnail-image"),a=0,o=0;$(".thumbnail-image").each(function(){$("#product-title-image").attr("src")===$(this).attr("src")&&(o=a-1===e.length||a-1<0?e.length-1:a-1),a++}),$("#product-title-image").attr("src",$(e).eq(o).attr("src"))}),$(document).on("click","#orderStatusUpdate",function(t){$.ajax({method:"POST",url:"/admin/order/statusupdate",data:{order_id:$("#order_id").val(),status:$("#orderStatus").val()}}).done(function(t){showNotification(t.message,"success",!0)}).fail(function(t){showNotification(t.responseJSON.message,"danger")})}),$(document).on("click",".product-add-to-cart",function(t){var e=getSelectedOptions();parseInt($("#product_quantity").val())<0&&$("#product_quantity").val(0),$.ajax({method:"POST",url:"/product/addtocart",data:{productId:$("#productId").val(),productQuantity:$("#product_quantity").val(),productOptions:JSON.stringify(e),productComment:$("#product_comment").val()}}).done(function(t){$("#cart-count").text(t.totalCartItems),updateCartDiv(),showNotification(t.message,"success")}).fail(function(t){showNotification(t.responseJSON.message,"danger")})}),$(".cart-product-quantity").on("input",function(){cartUpdate()}),$(document).on("click",".pushy-link",function(t){$("body").removeClass("pushy-open-right")}),$(document).on("click",".add-to-cart",function(t){var e="/product/"+$(this).attr("data-id");$(this).attr("data-link")&&(e="/product/"+$(this).attr("data-link")),"true"===$(this).attr("data-has-options")?window.location=e:$.ajax({method:"POST",url:"/product/addtocart",data:{productId:$(this).attr("data-id")}}).done(function(t){$("#cart-count").text(t.totalCartItems),updateCartDiv(),showNotification(t.message,"success")}).fail(function(t){showNotification(t.responseJSON.message,"danger")})}),$(document).on("click","#empty-cart",function(t){$.ajax({method:"POST",url:"/product/emptycart"}).done(function(t){$("#cart-count").text(t.totalCartItems),updateCartDiv(),showNotification(t.message,"success",!0)})}),$(".qty-btn-minus").on("click",function(){var t=parseInt($("#product_quantity").val())-1;$("#product_quantity").val(t>0?t:1)}),$(".qty-btn-plus").on("click",function(){$("#product_quantity").val(parseInt($("#product_quantity").val())+1)}),$(".thumbnail-image").on("click",function(){$("#product-title-image").attr("src",$(this).attr("src"))}),$(".set-as-main-image").on("click",function(){$.ajax({method:"POST",url:"/admin/product/setasmainimage",data:{product_id:$("#frmProductId").val(),productImage:$(this).attr("data-id")}}).done(function(t){showNotification(t.message,"success",!0)}).fail(function(t){showNotification(t.responseJSON.message,"danger")})}),$(".btn-delete-image").on("click",function(){$.ajax({method:"POST",url:"/admin/product/deleteimage",data:{product_id:$("#frmProductId").val(),productImage:$(this).attr("data-id")}}).done(function(t){showNotification(t.message,"success",!0)}).fail(function(t){showNotification(t.responseJSON.message,"danger")})}),$(document).on("click","#validate_permalink",function(t){""!==$("#frmProductPermalink").val()?$.ajax({method:"POST",url:"/admin/api/validate_permalink",data:{permalink:$("#frmProductPermalink").val(),docId:$("#frmProductId").val()}}).done(function(t){showNotification(t,"success")}).fail(function(t){showNotification(t.responseJSON.message,"danger")}):showNotification("Please enter a permalink to validate","danger")}),$(document).on("click","#btn_product_filter",function(t){""!==$("#product_filter").val()?window.location.href="/admin/products/filter/"+$("#product_filter").val():showNotification("Please enter a keyword to filter","danger")}),$(document).on("click","#btn_order_filter",function(t){""!==$("#order_filter").val()?window.location.href="/admin/orders/filter/"+$("#order_filter").val():showNotification("Please enter a keyword to filter","danger")}),$(document).on("click","#btn_customer_filter",function(t){""!==$("#customer_filter").val()?window.location.href="/admin/customers/filter/"+$("#customer_filter").val():showNotification("Please enter a keyword to filter","danger")}),$(document).on("click","#btn_search_reset",function(t){window.location.replace("/")}),$(document).on("click","#btn_search",function(t){t.preventDefault(),""===$("#frm_search").val().trim()?showNotification("Please enter a search value","danger"):window.location.href="/search/"+$("#frm_search").val()}),$(document).on("click","#frm_edit_product_save",function(t){""===$("#frmProductPermalink").val()&&""!==$("#frmProductTitle").val()&&$("#frmProductPermalink").val(slugify($("#frmProductTitle").val()))}),""!==$("#input_notify_message").val()){var l=$("#input_notify_message").val(),u=$("#input_notify_messageType").val();$("#input_notify_message").val(""),$("#input_notify_messageType").val(""),showNotification(l,u,!1)}}); \ No newline at end of file +function deleteFromCart(t){$.ajax({method:"POST",url:"/product/removefromcart",data:{cartId:t.attr("data-id")}}).done(function(e){$("#cart-count").text(e.totalCartItems),0===e.totalCartItems?($(t).closest(".cart-row").hide("slow",function(){$(t).closest(".cart-row").remove()}),$(".cart-contents-shipping").hide("slow",function(){$(".cart-contents-shipping").remove()}),showNotification(e.message,"success"),setTimeout(function(){window.location="/"},3700)):($(t).closest(".cart-row").hide("slow",function(){$(t).closest(".cart-row").remove()}),showNotification(e.message,"success"))}).fail(function(t){showNotification(t.responseJSON.message,"danger")})}function slugify(t){return $.trim(t).replace(/[^a-z0-9-æøå]/gi,"-").replace(/-+/g,"-").replace(/^-|-$/g,"").replace(/æ/gi,"ae").replace(/ø/gi,"oe").replace(/å/gi,"a").toLowerCase()}function cartUpdate(t){$(t).val()>0?""!==$(t).val()&&updateCart():$(t).val(1)}function updateCart(){var t=[];$(".cart-product-quantity").each(function(){var e={cartIndex:$(this).attr("id"),itemQuantity:$(this).val(),productId:$(this).attr("data-id")};t.push(e)}),$.ajax({method:"POST",url:"/product/updatecart",data:{items:JSON.stringify(t)}}).done(function(t){updateCartDiv(),$("#cart-count").text(t.totalCartItems)}).fail(function(t){showNotification(t.responseJSON.message,"danger",!0)})}function updateCartDiv(){var t=window.location.pathname.split("/").length>0?window.location.pathname.split("/")[1]:"";$.ajax({method:"GET",url:"/cartPartial",data:{path:t}}).done(function(t){$("#cart").html(t)}).fail(function(t){showNotification(t.responseJSON.message,"danger")})}function getSelectedOptions(){var t={};return $(".product-opt").each(function(){if("opt-"!==$(this).attr("name")){var e=$(this).val().trim();"radio"===$(this).attr("type")&&(e=$('input[name="'+$(this).attr("name")+'"]:checked').val()),t[$(this).attr("name").substring(4,$(this).attr("name").length)]=e}else t[$(this).val().trim()]=$(this).prop("checked")}),t}function showNotification(t,e,a){a=a||!1,$("#notify_message").removeClass(),$("#notify_message").addClass("alert-"+e),$("#notify_message").html(t),$("#notify_message").slideDown(600).delay(2500).slideUp(600,function(){!0===a&&location.reload()})}$(document).ready(function(){if("Material"===$("#cartTheme").val()&&$(".materialboxed").materialbox(),$(window).width()<768&&($(".menu-side").on("click",function(t){t.preventDefault(),$('.menu-side li:not(".active")').slideToggle()}),$('.menu-side li:not(".active")').hide(),$(".menu-side>.active").html(''),$(".menu-side>.active").addClass("menu-side-mobile"),0===$("#navbar ul li").length&&$("#navbar").hide(),$("#offcanvasClose").hide()),$(".shipping-form input").each(function(t){$(this).wrap("
    ");var e=$(this).attr("placeholder");$(this).after('")}),$(".shipping-form input").on("focus",function(){$(this).next().addClass("floatLabel"),$(this).next().removeClass("hidden")}),$(".shipping-form input").on("blur",function(){""===$(this).val()&&($(this).next().addClass("hidden"),$(this).next().removeClass("floatLabel"))}),$(".menu-btn").on("click",function(t){t.preventDefault()}),$("#sendTestEmail").on("click",function(t){t.preventDefault(),$.ajax({method:"POST",url:"/admin/testEmail"}).done(function(t){showNotification(t,"success")}).fail(function(t){showNotification(t.responseJSON.message,"danger")})}),$("#footerHtml").length){var t=window.CodeMirror.fromTextArea(document.getElementById("footerHtml"),{mode:"xml",tabMode:"indent",theme:"flatly",lineNumbers:!0,htmlMode:!0,fixedGutter:!1});t.setValue(t.getValue())}if($("#googleAnalytics").length&&window.CodeMirror.fromTextArea(document.getElementById("googleAnalytics"),{mode:"xml",tabMode:"indent",theme:"flatly",lineNumbers:!0,htmlMode:!0,fixedGutter:!1}),$("#customCss").length){var e=window.CodeMirror.fromTextArea(document.getElementById("customCss"),{mode:"text/css",tabMode:"indent",theme:"flatly",lineNumbers:!0}),a=window.cssbeautify(e.getValue(),{indent:" ",autosemicolon:!0});e.setValue(a)}if($("table").each(function(){$(this).addClass("table table-hover")}),$("#productTags").tokenfield(),$(document).on("click",".dashboard_list",function(t){window.document.location=$(this).attr("href")}).hover(function(){$(this).toggleClass("hover")}),$(".product-title").dotdotdot({ellipsis:"..."}),$('input[class="published_state"]').change(function(){$.ajax({method:"POST",url:"/admin/product/published_state",data:{id:this.id,state:this.checked}}).done(function(t){showNotification(t.message,"success")}).fail(function(t){showNotification(t.responseJSON.message,"danger")})}),$(document).on("click",".btn-qty-minus",function(t){var e=$(t.target).parent().parent().find(".cart-product-quantity");$(e).val(parseInt(e.val())-1),cartUpdate(e)}),$(document).on("click",".btn-qty-add",function(t){var e=$(t.target).parent().parent().find(".cart-product-quantity");$(e).val(parseInt(e.val())+1),cartUpdate(e)}),$(document).on("change",".cart-product-quantity",function(t){cartUpdate(t.target)}),$(document).on("click",".btn-delete-from-cart",function(t){deleteFromCart($(t.target))}),$(document).on("click",".orderFilterByStatus",function(t){t.preventDefault(),window.location="/admin/orders/bystatus/"+$("#orderStatusFilter").val()}),$("#pager").length){var o=$("#pageNum").val(),i=$("#productsPerPage").val(),n=$("#totalProductCount").val(),r=$("#paginateUrl").val(),s=$("#searchTerm").val();""!==s&&(s+="/");var c="/"+r+"/"+s+"{{number}}",d=Math.ceil(n/i);parseInt(n)>parseInt(i)&&$("#pager").bootpag({total:d,page:o,maxVisible:5,href:c,wrapClass:"pagination",prevClass:"waves-effect",nextClass:"waves-effect",activeClass:"pag-active waves-effect"})}if($(document).on("click","#btnPageUpdate",function(t){t.preventDefault(),$.ajax({method:"POST",url:"/admin/settings/pages/update",data:{page_id:$("#page_id").val(),pageName:$("#pageName").val(),pageSlug:$("#pageSlug").val(),pageEnabled:$("#pageEnabled").is(":checked"),pageContent:$("#pageContent").val()}}).done(function(t){showNotification(t.message,"success",!0)}).fail(function(t){showNotification(t.responseJSON.message,"danger")})}),$(document).on("click","#btnGenerateAPIkey",function(t){t.preventDefault(),$.ajax({method:"POST",url:"/admin/createApiKey"}).done(function(t){$("#apiKey").val(t.apiKey),showNotification(t.message,"success",!0)}).fail(function(t){showNotification(t.responseJSON.message,"danger")})}),$(document).on("click",".product_opt_remove",function(t){t.preventDefault();var e=$(this).closest("li").find(".opt-name").html();$.ajax({method:"POST",url:"/admin/settings/option/remove/",data:{productId:$("#frmProductId").val(),optName:e}}).done(function(t){showNotification(t.message,"success",!0)}).fail(function(t){showNotification(t.responseJSON.message,"danger")})}),$(document).on("click","#product_opt_add",function(t){t.preventDefault();var e=$("#product_optName").val(),a=$("#product_optLabel").val(),o=$("#product_optType").val(),i=$("#product_optOptions").val(),n={};""!==$("#productOptions").val()&&(n=JSON.parse($("#productOptions").val()));var r='
  • ';r+='
    ',r+='
    '+e+"
    ",r+='
    '+a+"
    ",r+='
    '+o+"
    ",r+='
    '+i+"
    ",r+='
    ',r+='',r+="
  • ",$("#product_opt_wrapper").append(r),n[e]={optName:e,optLabel:a,optType:o,optOptions:$.grep(i.split(","),function(t){return 0===t||t})},$("#productOptions").val(JSON.stringify(n)),$("#product_optName").val(""),$("#product_optLabel").val(""),$("#product_optOptions").val("")}),$("#stripeButton").validator().on("click",function(t){(t.preventDefault(),0===$("#shipping-form").validator("validate").has(".has-error").length)&&window.StripeCheckout.configure({key:$("#stripeButton").data("key"),image:$("#stripeButton").data("image"),locale:"auto",token:function(t){$("#shipping-form").append(''),$("#shipping-form").submit()}}).open({name:$("#stripeButton").data("name"),description:$("#stripeButton").data("description"),zipCode:$("#stripeButton").data("zipCode"),amount:$("#stripeButton").data("amount"),currency:$("#stripeButton").data("currency")})}),$("#settingsForm").validator().on("submit",function(t){t.isDefaultPrevented()||(t.preventDefault(),$("#footerHtml_input").val($(".CodeMirror")[0].CodeMirror.getValue()),$("#googleAnalytics_input").val($(".CodeMirror")[1].CodeMirror.getValue()),$("#customCss_input").val($(".CodeMirror")[2].CodeMirror.getValue()),$.ajax({method:"POST",url:"/admin/settings/update",data:$("#settingsForm").serialize()}).done(function(t){showNotification(t.message,"success")}).fail(function(t){showNotification(t.responseJSON.message,"danger")}))}),$("#customerLogout").on("click",function(t){$.ajax({method:"POST",url:"/customer/logout",data:{}}).done(function(t){location.reload()})}),$("#createCustomerAccount").validator().on("click",function(t){t.preventDefault(),0===$("#shipping-form").validator("validate").has(".has-error").length&&$.ajax({method:"POST",url:"/customer/create",data:{email:$("#shipEmail").val(),firstName:$("#shipFirstname").val(),lastName:$("#shipLastname").val(),address1:$("#shipAddr1").val(),address2:$("#shipAddr2").val(),country:$("#shipCountry").val(),state:$("#shipState").val(),postcode:$("#shipPostcode").val(),phone:$("#shipPhoneNumber").val(),password:$("#newCustomerPassword").val()}}).done(function(t){location.reload()}).fail(function(t){showNotification(t.responseJSON.err,"danger")})}),$("#loginForm").on("click",function(t){t.isDefaultPrevented()||(t.preventDefault(),$.ajax({method:"POST",url:"/admin/login_action",data:{email:$("#email").val(),password:$("#password").val()}}).done(function(t){window.location="/admin"}).fail(function(t){showNotification(t.responseJSON.message,"danger")})),t.preventDefault()}),$("#customerLogin").on("click",function(t){t.isDefaultPrevented()||(t.preventDefault(),$.ajax({method:"POST",url:"/customer/login_action",data:{loginEmail:$("#customerLoginEmail").val(),loginPassword:$("#customerLoginPassword").val()}}).done(function(t){var e=t.customer;$("#shipEmail").val(e.email),$("#shipFirstname").val(e.firstName),$("#shipLastname").val(e.lastName),$("#shipAddr1").val(e.address1),$("#shipAddr2").val(e.address2),$("#shipCountry").val(e.country),$("#shipState").val(e.state),$("#shipPostcode").val(e.postcode),$("#shipPhoneNumber").val(e.phone),location.reload()}).fail(function(t){showNotification(t.responseJSON.message,"danger")})),t.preventDefault()}),$(document).on("click",".image-next",function(t){var e=$(".thumbnail-image"),a=0,o=0;$(".thumbnail-image").each(function(){$("#product-title-image").attr("src")===$(this).attr("src")&&(o=a+1===e.length||a+1<0?0:a+1),a++}),$("#product-title-image").attr("src",$(e).eq(o).attr("src"))}),$(document).on("click",".image-prev",function(t){var e=$(".thumbnail-image"),a=0,o=0;$(".thumbnail-image").each(function(){$("#product-title-image").attr("src")===$(this).attr("src")&&(o=a-1===e.length||a-1<0?e.length-1:a-1),a++}),$("#product-title-image").attr("src",$(e).eq(o).attr("src"))}),$(document).on("click","#orderStatusUpdate",function(t){$.ajax({method:"POST",url:"/admin/order/statusupdate",data:{order_id:$("#order_id").val(),status:$("#orderStatus").val()}}).done(function(t){showNotification(t.message,"success",!0)}).fail(function(t){showNotification(t.responseJSON.message,"danger")})}),$(document).on("click",".product-add-to-cart",function(t){var e=getSelectedOptions();parseInt($("#product_quantity").val())<0&&$("#product_quantity").val(0),$.ajax({method:"POST",url:"/product/addtocart",data:{productId:$("#productId").val(),productQuantity:$("#product_quantity").val(),productOptions:JSON.stringify(e),productComment:$("#product_comment").val()}}).done(function(t){$("#cart-count").text(t.totalCartItems),updateCartDiv(),showNotification(t.message,"success")}).fail(function(t){showNotification(t.responseJSON.message,"danger")})}),$(".cart-product-quantity").on("input",function(){cartUpdate()}),$(document).on("click",".pushy-link",function(t){$("body").removeClass("pushy-open-right")}),$(document).on("click",".add-to-cart",function(t){var e="/product/"+$(this).attr("data-id");$(this).attr("data-link")&&(e="/product/"+$(this).attr("data-link")),"true"===$(this).attr("data-has-options")?window.location=e:$.ajax({method:"POST",url:"/product/addtocart",data:{productId:$(this).attr("data-id")}}).done(function(t){$("#cart-count").text(t.totalCartItems),updateCartDiv(),showNotification(t.message,"success")}).fail(function(t){showNotification(t.responseJSON.message,"danger")})}),$(document).on("click","#empty-cart",function(t){$.ajax({method:"POST",url:"/product/emptycart"}).done(function(t){$("#cart-count").text(t.totalCartItems),updateCartDiv(),showNotification(t.message,"success",!0)})}),$(".qty-btn-minus").on("click",function(){var t=parseInt($("#product_quantity").val())-1;$("#product_quantity").val(t>0?t:1)}),$(".qty-btn-plus").on("click",function(){$("#product_quantity").val(parseInt($("#product_quantity").val())+1)}),$(".thumbnail-image").on("click",function(){$("#product-title-image").attr("src",$(this).attr("src"))}),$(".set-as-main-image").on("click",function(){$.ajax({method:"POST",url:"/admin/product/setasmainimage",data:{product_id:$("#frmProductId").val(),productImage:$(this).attr("data-id")}}).done(function(t){showNotification(t.message,"success",!0)}).fail(function(t){showNotification(t.responseJSON.message,"danger")})}),$(".btn-delete-image").on("click",function(){$.ajax({method:"POST",url:"/admin/product/deleteimage",data:{product_id:$("#frmProductId").val(),productImage:$(this).attr("data-id")}}).done(function(t){showNotification(t.message,"success",!0)}).fail(function(t){showNotification(t.responseJSON.message,"danger")})}),$(document).on("click","#validate_permalink",function(t){""!==$("#productPermalink").val()?$.ajax({method:"POST",url:"/admin/api/validate_permalink",data:{permalink:$("#productPermalink").val(),docId:$("#frmProductId").val()}}).done(function(t){console.log("msg",t),showNotification(t.message,"success")}).fail(function(t){showNotification(t.responseJSON.message,"danger")}):showNotification("Please enter a permalink to validate","danger")}),$(document).on("click","#btn_product_filter",function(t){""!==$("#product_filter").val()?window.location.href="/admin/products/filter/"+$("#product_filter").val():showNotification("Please enter a keyword to filter","danger")}),$(document).on("click","#btn_order_filter",function(t){""!==$("#order_filter").val()?window.location.href="/admin/orders/filter/"+$("#order_filter").val():showNotification("Please enter a keyword to filter","danger")}),$(document).on("click","#btn_customer_filter",function(t){""!==$("#customer_filter").val()?window.location.href="/admin/customers/filter/"+$("#customer_filter").val():showNotification("Please enter a keyword to filter","danger")}),$(document).on("click","#btn_search_reset",function(t){window.location.replace("/")}),$(document).on("click","#btn_search",function(t){t.preventDefault(),""===$("#frm_search").val().trim()?showNotification("Please enter a search value","danger"):window.location.href="/search/"+$("#frm_search").val()}),$(document).on("click","#frm_edit_product_save",function(t){""===$("#productPermalink").val()&&""!==$("#productTitle").val()&&$("#productPermalink").val(slugify($("#productTitle").val()))}),""!==$("#input_notify_message").val()){var l=$("#input_notify_message").val(),u=$("#input_notify_messageType").val();$("#input_notify_message").val(""),$("#input_notify_messageType").val(""),showNotification(l,u,!1)}}); \ No newline at end of file diff --git a/routes/index.js b/routes/index.js index a5942bb..24a00f0 100644 --- a/routes/index.js +++ b/routes/index.js @@ -147,10 +147,7 @@ router.get('/product/:id', (req, res) => { if(err || result == null || result.productPublished === 'false'){ res.render('error', { title: 'Not found', message: 'Product not found', helpers: req.handlebars.helpers, config }); }else{ - let productOptions = {}; - if(result.productOptions){ - productOptions = JSON.parse(result.productOptions); - } + let productOptions = result.productOptions; // If JSON query param return json instead if(req.query.json === 'true'){ diff --git a/routes/product.js b/routes/product.js index ffeafd9..1c0f446 100644 --- a/routes/product.js +++ b/routes/product.js @@ -1,6 +1,8 @@ const express = require('express'); const common = require('../lib/common'); const { restrict, checkAccess } = require('../lib/auth'); +const { indexProducts } = require('../lib/indexing'); +const { validateJson } = require('../lib/schema'); const colors = require('colors'); const rimraf = require('rimraf'); const fs = require('fs'); @@ -78,76 +80,132 @@ router.get('/admin/product/new', restrict, checkAccess, (req, res) => { router.post('/admin/product/insert', restrict, checkAccess, (req, res) => { 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'); + } + } + let doc = { - productPermalink: req.body.frmProductPermalink, - productTitle: common.cleanHtml(req.body.frmProductTitle), - productPrice: req.body.frmProductPrice, - productDescription: common.cleanHtml(req.body.frmProductDescription), - productPublished: req.body.frmProductPublished, - productTags: req.body.frmProductTags, - productOptions: common.cleanHtml(req.body.productOptJson), - productComment: common.checkboxBool(req.body.frmProductComment), + 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), productAddedDate: new Date(), - productStock: req.body.frmProductStock ? parseInt(req.body.frmProductStock) : null + productStock: common.safeParseInt(req.body.productStock) || null }; - db.products.count({ 'productPermalink': req.body.frmProductPermalink }, (err, product) => { + // 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; + } + + db.products.count({ 'productPermalink': req.body.productPermalink }, (err, product) => { if(err){ console.info(err.stack); } - if(product > 0 && req.body.frmProductPermalink !== ''){ + 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.frmProductTitle; - req.session.productDescription = req.body.frmProductDescription; - req.session.productPrice = req.body.frmProductPrice; - req.session.productPermalink = req.body.frmProductPermalink; - req.session.productPermalink = req.body.productOptJson; - req.session.productComment = common.checkboxBool(req.body.frmProductComment); - req.session.productTags = req.body.frmProductTags; - req.session.productStock = req.body.frmProductStock ? parseInt(req.body.frmProductStock) : null; + 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/insert'); - }else{ - 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.frmProductTitle; - req.session.productDescription = req.body.frmProductDescription; - req.session.productPrice = req.body.frmProductPrice; - req.session.productPermalink = req.body.frmProductPermalink; - req.session.productPermalink = req.body.productOptJson; - req.session.productComment = common.checkboxBool(req.body.frmProductComment); - req.session.productTags = req.body.frmProductTags; - req.session.productStock = req.body.frmProductStock ? parseInt(req.body.frmProductStock) : null; - - req.session.message = 'Error: Inserting product'; - req.session.messageType = 'danger'; - - // redirect to insert - res.redirect('/admin/product/new'); - }else{ - // get the new ID - let newId = newDoc.insertedIds[0]; - - // add to lunr index - common.indexProducts(req.app) - .then(() => { - req.session.message = 'New product successfully created'; - req.session.messageType = 'success'; - - // redirect to new doc - res.redirect('/admin/product/edit/' + newId); - }); - } - }); + res.redirect('/admin/product/new'); + 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 + let 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({ error: 'New product successfully created' }); + return; + } + + // redirect to new doc + res.redirect('/admin/product/edit/' + newId); + }); + }); }); }); @@ -162,7 +220,7 @@ router.get('/admin/product/edit/:id', restrict, checkAccess, (req, res) => { } let options = {}; if(result.productOptions){ - options = JSON.parse(result.productOptions); + options = result.productOptions; } res.render('product_edit', { @@ -194,7 +252,7 @@ router.post('/admin/product/update', restrict, checkAccess, (req, res) => { res.redirect('/admin/product/edit/' + req.body.frmProductId); return; } - db.products.count({ 'productPermalink': req.body.frmProductPermalink, _id: { $ne: common.getId(product._id) } }, (err, count) => { + db.products.count({ 'productPermalink': req.body.productPermalink, _id: { $ne: common.getId(product._id) } }, (err, count) => { if(err){ console.info(err.stack); req.session.message = 'Failed updating product.'; @@ -203,37 +261,75 @@ router.post('/admin/product/update', restrict, checkAccess, (req, res) => { return; } - if(count > 0 && req.body.frmProductPermalink !== ''){ + if(count > 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.frmProductTitle; - req.session.productDescription = req.body.frmProductDescription; - req.session.productPrice = req.body.frmProductPrice; - req.session.productPermalink = req.body.frmProductPermalink; - req.session.productTags = req.body.frmProductTags; - req.session.productOptions = req.body.productOptJson; - req.session.productComment = common.checkboxBool(req.body.frmProductComment); - req.session.productStock = req.body.frmProductStock ? req.body.frmProductStock : null; + 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.frmProductId); - }else{ + }else{ common.getImages(req.body.frmProductId, 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'); + } + } + let productDoc = { - productTitle: common.cleanHtml(req.body.frmProductTitle), - productDescription: common.cleanHtml(req.body.frmProductDescription), - productPublished: req.body.frmProductPublished, - productPrice: req.body.frmProductPrice, - productPermalink: req.body.frmProductPermalink, - productTags: common.cleanHtml(req.body.frmProductTags), - productOptions: common.cleanHtml(req.body.productOptJson), - productComment: common.checkboxBool(req.body.frmProductComment), - productStock: req.body.frmProductStock ? parseInt(req.body.frmProductStock) : null + 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; + } + + 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/edit/' + req.body.frmProductId); + return; + } + // if no featured image if(!product.productImage){ if(images.length > 0){ @@ -253,7 +349,7 @@ router.post('/admin/product/update', restrict, checkAccess, (req, res) => { res.redirect('/admin/product/edit/' + req.body.frmProductId); }else{ // Update the index - common.indexProducts(req.app) + indexProducts(req.app) .then(() => { req.session.message = 'Successfully saved'; req.session.messageType = 'success'; @@ -283,7 +379,7 @@ router.get('/admin/product/delete/:id', restrict, checkAccess, (req, res) => { } // remove the index - common.indexProducts(req.app) + indexProducts(req.app) .then(() => { // redirect home req.session.message = 'Product successfully deleted'; diff --git a/views/product_edit.hbs b/views/product_edit.hbs index 2e76b48..a7327ea 100644 --- a/views/product_edit.hbs +++ b/views/product_edit.hbs @@ -10,24 +10,24 @@

    Edit product

    - +
    - +
    - +
    {{currencySymbol config.currencySymbol}} - +
    - +
    - @@ -35,23 +35,23 @@
    {{#if config.trackStock}}
    - +
    - +
    {{/if}}
    - +
    - + @@ -60,7 +60,7 @@
    - +
      @@ -108,21 +108,21 @@
    - +

    Allow free form comments when adding products to cart

    - +
    - +

    Tag words used to indexed products, making them easier to find and filter.

    diff --git a/views/product_new.hbs b/views/product_new.hbs index 1bcd842..7d338bc 100644 --- a/views/product_new.hbs +++ b/views/product_new.hbs @@ -9,24 +9,24 @@

    New product

    - +
    - +
    - +
    $ - +
    - +
    - @@ -34,23 +34,23 @@
    {{#if config.trackStock}}
    - +
    - +
    {{/if}}
    - +
    - + @@ -59,7 +59,7 @@
    - +
      @@ -107,21 +107,21 @@
    - +

    Allow free form comments when adding products to cart

    - +
    - +

    Tag words used to indexed products, making them easier to find and filter.

    diff --git a/views/themes/Cloth/product.hbs b/views/themes/Cloth/product.hbs index fc7cbcd..ed51e0d 100644 --- a/views/themes/Cloth/product.hbs +++ b/views/themes/Cloth/product.hbs @@ -12,17 +12,17 @@ {{#ifCond this.optType '==' "select"}} {{this.optName}} {{/ifCond}} {{#ifCond this.optType '==' "radio"}} - {{#each this.optOptions}} + {{#each ../this.optOptions}} {{this.optName}}
    @@ -31,7 +31,7 @@ {{#ifCond this.optType '==' "checkbox"}}
    {{/ifCond}} diff --git a/views/themes/Material/product.hbs b/views/themes/Material/product.hbs index ef9005a..20572e2 100644 --- a/views/themes/Material/product.hbs +++ b/views/themes/Material/product.hbs @@ -12,17 +12,17 @@ {{#ifCond this.optType '==' "select"}} {{this.optName}} {{/ifCond}} {{#ifCond this.optType '==' "radio"}} - {{#each this.optOptions}} + {{#each ../this.optOptions}} {{this.optName}}
    @@ -31,7 +31,7 @@ {{#ifCond this.optType '==' "checkbox"}}
    {{/ifCond}} diff --git a/views/themes/Mono/product.hbs b/views/themes/Mono/product.hbs index 746e4bc..a15b531 100644 --- a/views/themes/Mono/product.hbs +++ b/views/themes/Mono/product.hbs @@ -12,17 +12,17 @@ {{#ifCond this.optType '==' "select"}} {{this.optName}} {{/ifCond}} {{#ifCond this.optType '==' "radio"}} - {{#each this.optOptions}} + {{#each ../this.optOptions}} {{this.optName}}
    @@ -31,7 +31,7 @@ {{#ifCond this.optType '==' "checkbox"}}
    {{/ifCond}}