Fixed validation, added modal errors + partial

master
Mark Moffat 2020-03-17 19:08:46 +10:30
parent 6806806fa2
commit 9911fe04a9
12 changed files with 122 additions and 43 deletions

View File

@ -3,7 +3,7 @@ const fs = require('fs');
const moment = require('moment'); const moment = require('moment');
const glob = require('glob'); const glob = require('glob');
const Ajv = require('ajv'); const Ajv = require('ajv');
const ajv = new Ajv(); const ajv = new Ajv({ allErrors: true, jsonPointers: true });
const addSchemas = () => { const addSchemas = () => {
const schemaFiles = glob.sync('./lib/**/*.json'); const schemaFiles = glob.sync('./lib/**/*.json');
@ -28,12 +28,18 @@ const addSchemas = () => {
}); });
ajv.addKeyword('isNotEmpty', { ajv.addKeyword('isNotEmpty', {
type: 'string', validate: function validate(schema, data){
validate: (schema, data) => { const result = typeof data === 'string' && data.trim() !== '';
return typeof data === 'string' && data.trim() !== ''; if(!result){
console.log('result', result);
validate.errors = [{ keyword: 'isNotEmpty', message: 'Cannot be an empty string', params: { keyword: 'isNotEmpty' } }];
}
return result;
}, },
errors: false errors: true
}); });
require('ajv-errors')(ajv);
}; };
const validateJson = (schema, json) => { const validateJson = (schema, json) => {

View File

@ -6,17 +6,23 @@
"type": "string" "type": "string"
}, },
"productPermalink": { "productPermalink": {
"type": "string" "type": "string",
"isNotEmpty": true,
"minLength": 2
}, },
"productTitle": { "productTitle": {
"type": "string" "type": "string",
"isNotEmpty": true,
"minLength": 5
}, },
"productPrice": { "productPrice": {
"type": "string", "type": "string",
"format": "amount" "format": "amount"
}, },
"productDescription": { "productDescription": {
"type": "string" "type": "string",
"isNotEmpty": true,
"minLength": 25
}, },
"productPublished": { "productPublished": {
"type": "boolean" "type": "boolean"
@ -34,6 +40,14 @@
"type": ["number", "null"] "type": ["number", "null"]
} }
}, },
"errorMessage": {
"isNotEmpty": "This is my custom error message",
"properties": {
"productPrice": "Should be a full 2 decimal value. Eg: 10.99",
"productPublished": "Should be either true or false",
"productComment": "Should be either true or false"
}
},
"required": [ "required": [
"productId" "productId"
] ]

View File

@ -3,17 +3,23 @@
"type": "object", "type": "object",
"properties": { "properties": {
"productPermalink": { "productPermalink": {
"type": "string" "type": "string",
"isNotEmpty": true,
"minLength": 2
}, },
"productTitle": { "productTitle": {
"type": "string" "type": "string",
"isNotEmpty": true,
"minLength": 5
}, },
"productPrice": { "productPrice": {
"type": "string", "type": "string",
"format": "amount" "format": "amount"
}, },
"productDescription": { "productDescription": {
"type": "string" "type": "string",
"isNotEmpty": true,
"minLength": 25
}, },
"productPublished": { "productPublished": {
"type": "boolean" "type": "boolean"
@ -31,6 +37,13 @@
"type": ["number", "null"] "type": ["number", "null"]
} }
}, },
"errorMessage": {
"properties": {
"productPrice": "Should be a full 2 decimal value. Eg: 10.99",
"productPublished": "Should be either true or false",
"productComment": "Should be either true or false"
}
},
"required": [ "required": [
"productPermalink", "productPermalink",
"productTitle", "productTitle",

14
package-lock.json generated
View File

@ -610,6 +610,11 @@
"uri-js": "^4.2.2" "uri-js": "^4.2.2"
} }
}, },
"ajv-errors": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/ajv-errors/-/ajv-errors-1.0.1.tgz",
"integrity": "sha512-DCRfO/4nQ+89p/RK43i8Ezd41EqdGIU4ld7nGF8OQ14oc/we5rEntLCUa7+jrn3nn83BosfwZA0wb4pon2o8iQ=="
},
"align-text": { "align-text": {
"version": "0.1.4", "version": "0.1.4",
"resolved": "https://registry.npmjs.org/align-text/-/align-text-0.1.4.tgz", "resolved": "https://registry.npmjs.org/align-text/-/align-text-0.1.4.tgz",
@ -1377,7 +1382,8 @@
}, },
"kind-of": { "kind-of": {
"version": "6.0.2", "version": "6.0.2",
"resolved": "", "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.2.tgz",
"integrity": "sha512-s5kLOcnH0XqDO+FvuaLX8DDjZ18CGFk7VygH40QoKPUQhW4e2rvM0rwUq0t8IQDOwYSeLK01U90OjzBTme2QqA==",
"dev": true "dev": true
} }
} }
@ -4046,7 +4052,8 @@
}, },
"kind-of": { "kind-of": {
"version": "6.0.2", "version": "6.0.2",
"resolved": "", "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.2.tgz",
"integrity": "sha512-s5kLOcnH0XqDO+FvuaLX8DDjZ18CGFk7VygH40QoKPUQhW4e2rvM0rwUq0t8IQDOwYSeLK01U90OjzBTme2QqA==",
"dev": true "dev": true
} }
} }
@ -10137,7 +10144,8 @@
}, },
"kind-of": { "kind-of": {
"version": "6.0.2", "version": "6.0.2",
"resolved": "", "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.2.tgz",
"integrity": "sha512-s5kLOcnH0XqDO+FvuaLX8DDjZ18CGFk7VygH40QoKPUQhW4e2rvM0rwUq0t8IQDOwYSeLK01U90OjzBTme2QqA==",
"dev": true "dev": true
} }
} }

View File

@ -28,6 +28,7 @@
"dependencies": { "dependencies": {
"@adyen/api-library": "^2.1.7", "@adyen/api-library": "^2.1.7",
"ajv": "^6.10.2", "ajv": "^6.10.2",
"ajv-errors": "^1.0.1",
"async": "^2.6.3", "async": "^2.6.3",
"axios": "^0.19.0", "axios": "^0.19.0",
"bcryptjs": "^2.4.3", "bcryptjs": "^2.4.3",

View File

@ -218,6 +218,12 @@ $(document).ready(function (){
showNotification(msg.message, 'success', false, '/admin/product/edit/' + msg.productId); showNotification(msg.message, 'success', false, '/admin/product/edit/' + msg.productId);
}) })
.fail(function(msg){ .fail(function(msg){
if(msg.responseJSON.length > 0){
var errorMessages = validationErrors(msg.responseJSON);
$('#validationModalBody').html(errorMessages);
$('#validationModal').modal('show');
return;
}
showNotification(msg.responseJSON.message, 'danger'); showNotification(msg.responseJSON.message, 'danger');
}); });
} }
@ -250,6 +256,13 @@ $(document).ready(function (){
showNotification(msg.message, 'success', true); showNotification(msg.message, 'success', true);
}) })
.fail(function(msg){ .fail(function(msg){
if(msg.responseJSON.length > 0){
var errorMessages = validationErrors(msg.responseJSON);
console.log('errorMessages', errorMessages);
$('#validationModalBody').html(errorMessages);
$('#validationModal').modal('show');
return;
}
showNotification(msg.responseJSON.message, 'danger'); showNotification(msg.responseJSON.message, 'danger');
}); });
} }
@ -842,3 +855,11 @@ function globalSearch(){
feather.replace(); feather.replace();
}); });
} }
function validationErrors(errors){
var errorMessage = '';
errors.forEach((value) => {
errorMessage += `<p>${value.dataPath.replace('/', '')} - <span class="text-danger">${value.message}<span></p>`;
});
return errorMessage;
}

File diff suppressed because one or more lines are too long

View File

@ -249,10 +249,7 @@ router.post('/admin/product/update', restrict, checkAccess, async (req, res) =>
// Validate the body again schema // Validate the body again schema
const schemaValidate = validateJson('editProduct', productDoc); const schemaValidate = validateJson('editProduct', productDoc);
if(!schemaValidate.result){ if(!schemaValidate.result){
res.status(400).json({ res.status(400).json(schemaValidate.errors);
message: 'Form invalid. Please check values and try again.',
error: schemaValidate.errors
});
return; return;
} }

View File

@ -64,7 +64,7 @@ test('[Success] Add a product', async t => {
productPermalink: 'test-jacket', productPermalink: 'test-jacket',
productTitle: 'Test Jacket', productTitle: 'Test Jacket',
productPrice: '100.00', productPrice: '100.00',
productDescription: 'Test desc', productDescription: 'Test product description used to describe the product',
productPublished: true, productPublished: true,
productTags: 'organic, jacket', productTags: 'organic, jacket',
productOptions: { productOptions: {
@ -94,7 +94,7 @@ test('[Fail] Add a product - Duplicate permalink', async t => {
productPermalink: 'test-jacket', productPermalink: 'test-jacket',
productTitle: 'Test Jacket - blue', productTitle: 'Test Jacket - blue',
productPrice: '100.00', productPrice: '100.00',
productDescription: 'Test desc blue', productDescription: 'Test product description used to describe the product',
productPublished: true, productPublished: true,
productTags: 'organic, jacket, blue', productTags: 'organic, jacket, blue',
productOptions: { productOptions: {
@ -124,7 +124,7 @@ test('[Success] Update a product', async t => {
productId: g.products[0]._id, productId: g.products[0]._id,
productTitle: 'Test Jacket', productTitle: 'Test Jacket',
productPrice: '200.00', productPrice: '200.00',
productDescription: 'Test desc', productDescription: 'Test product description used to describe the product',
productPublished: true, productPublished: true,
productTags: 'organic, jacket', productTags: 'organic, jacket',
productOptions: { productOptions: {

View File

@ -259,27 +259,8 @@
{{/if}} {{/if}}
<script src="/javascripts/pushy.min.js"></script> <script src="/javascripts/pushy.min.js"></script>
{{#if admin}} {{#if admin}}
<div class="modal fade" id="globalSearchModal" tabindex="-1" role="dialog" aria-labelledby="globalSearchModal" {{> partials/globalSearchModal}}
aria-hidden="true"> {{> partials/validationModal}}
<div class="modal-dialog modal-lg" role="document">
<div class="modal-content global-search-modal-content">
<div class="modal-header global-search-modal-header"></div>
<div class="modal-body">
<form>
<div class="form-group global-search-form">
<div class="input-group">
<div class="input-group-prepend">
<label class="input-group-text search-input-addon" for="global-search-value">{{{feather 'search'}}}</label>
</div>
<input type="text" class="form-control form-control-lg" id="global-search-value">
</div>
</div>
</form>
<ul class="list-group col-12 invisible" id="global-search-results"></ul>
</div>
</div>
</div>
</div>
{{/if}} {{/if}}
{{> partials/confirmModal}} {{> partials/confirmModal}}
<script> <script>

View File

@ -0,0 +1,20 @@
<div class="modal fade" id="globalSearchModal" tabindex="-1" role="dialog" aria-labelledby="globalSearchModal" aria-hidden="true">
<div class="modal-dialog modal-lg" role="document">
<div class="modal-content global-search-modal-content">
<div class="modal-header global-search-modal-header"></div>
<div class="modal-body">
<form>
<div class="form-group global-search-form">
<div class="input-group">
<div class="input-group-prepend">
<label class="input-group-text search-input-addon" for="global-search-value">{{{feather 'search'}}}</label>
</div>
<input type="text" class="form-control form-control-lg" id="global-search-value">
</div>
</div>
</form>
<ul class="list-group col-12 invisible" id="global-search-results"></ul>
</div>
</div>
</div>
</div>

View File

@ -0,0 +1,18 @@
<div class="modal fade" id="validationModal" tabindex="-1" role="dialog" aria-labelledby="validationModalLabel" aria-hidden="true">
<div class="modal-dialog modal-lg" role="document">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title text-danger" id="validationModalLabel">Error</h5>
<button type="button" class="close" data-dismiss="modal" aria-label="Close">
<span aria-hidden="true">&times;</span>
</button>
</div>
<div class="modal-body">
<div id="validationModalBody"></div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-primary mr-auto" data-dismiss="modal">Close</button>
</div>
</div>
</div>
</div>