Fixed validation, added modal errors + partial
parent
6806806fa2
commit
9911fe04a9
|
@ -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) => {
|
||||||
|
|
|
@ -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"
|
||||||
]
|
]
|
||||||
|
|
|
@ -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",
|
||||||
|
|
|
@ -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
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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",
|
||||||
|
|
|
@ -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
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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: {
|
||||||
|
|
|
@ -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>
|
||||||
|
|
|
@ -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>
|
|
@ -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">×</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>
|
Loading…
Reference in New Issue