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 glob = require('glob');
const Ajv = require('ajv');
const ajv = new Ajv();
const ajv = new Ajv({ allErrors: true, jsonPointers: true });
const addSchemas = () => {
const schemaFiles = glob.sync('./lib/**/*.json');
@ -28,12 +28,18 @@ const addSchemas = () => {
});
ajv.addKeyword('isNotEmpty', {
type: 'string',
validate: (schema, data) => {
return typeof data === 'string' && data.trim() !== '';
validate: function validate(schema, data){
const result = 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) => {

View File

@ -6,17 +6,23 @@
"type": "string"
},
"productPermalink": {
"type": "string"
"type": "string",
"isNotEmpty": true,
"minLength": 2
},
"productTitle": {
"type": "string"
"type": "string",
"isNotEmpty": true,
"minLength": 5
},
"productPrice": {
"type": "string",
"format": "amount"
},
"productDescription": {
"type": "string"
"type": "string",
"isNotEmpty": true,
"minLength": 25
},
"productPublished": {
"type": "boolean"
@ -34,6 +40,14 @@
"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": [
"productId"
]

View File

@ -3,17 +3,23 @@
"type": "object",
"properties": {
"productPermalink": {
"type": "string"
"type": "string",
"isNotEmpty": true,
"minLength": 2
},
"productTitle": {
"type": "string"
"type": "string",
"isNotEmpty": true,
"minLength": 5
},
"productPrice": {
"type": "string",
"format": "amount"
},
"productDescription": {
"type": "string"
"type": "string",
"isNotEmpty": true,
"minLength": 25
},
"productPublished": {
"type": "boolean"
@ -31,6 +37,13 @@
"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": [
"productPermalink",
"productTitle",

14
package-lock.json generated
View File

@ -610,6 +610,11 @@
"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": {
"version": "0.1.4",
"resolved": "https://registry.npmjs.org/align-text/-/align-text-0.1.4.tgz",
@ -1377,7 +1382,8 @@
},
"kind-of": {
"version": "6.0.2",
"resolved": "",
"resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.2.tgz",
"integrity": "sha512-s5kLOcnH0XqDO+FvuaLX8DDjZ18CGFk7VygH40QoKPUQhW4e2rvM0rwUq0t8IQDOwYSeLK01U90OjzBTme2QqA==",
"dev": true
}
}
@ -4046,7 +4052,8 @@
},
"kind-of": {
"version": "6.0.2",
"resolved": "",
"resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.2.tgz",
"integrity": "sha512-s5kLOcnH0XqDO+FvuaLX8DDjZ18CGFk7VygH40QoKPUQhW4e2rvM0rwUq0t8IQDOwYSeLK01U90OjzBTme2QqA==",
"dev": true
}
}
@ -10137,7 +10144,8 @@
},
"kind-of": {
"version": "6.0.2",
"resolved": "",
"resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.2.tgz",
"integrity": "sha512-s5kLOcnH0XqDO+FvuaLX8DDjZ18CGFk7VygH40QoKPUQhW4e2rvM0rwUq0t8IQDOwYSeLK01U90OjzBTme2QqA==",
"dev": true
}
}

View File

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

View File

@ -218,6 +218,12 @@ $(document).ready(function (){
showNotification(msg.message, 'success', false, '/admin/product/edit/' + msg.productId);
})
.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');
});
}
@ -250,6 +256,13 @@ $(document).ready(function (){
showNotification(msg.message, 'success', true);
})
.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');
});
}
@ -842,3 +855,11 @@ function globalSearch(){
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
const schemaValidate = validateJson('editProduct', productDoc);
if(!schemaValidate.result){
res.status(400).json({
message: 'Form invalid. Please check values and try again.',
error: schemaValidate.errors
});
res.status(400).json(schemaValidate.errors);
return;
}

View File

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

View File

@ -259,27 +259,8 @@
{{/if}}
<script src="/javascripts/pushy.min.js"></script>
{{#if admin}}
<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>
{{> partials/globalSearchModal}}
{{> partials/validationModal}}
{{/if}}
{{> partials/confirmModal}}
<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>