update checkdata.js

This commit is contained in:
2023-03-27 07:52:21 +02:00
parent a03e35c9e2
commit d0a3b10cfe
83 changed files with 1608 additions and 825 deletions

View File

@@ -3,183 +3,299 @@ This module have to be independant of any external package
it is shared between back and front and is usefull
to apply common check in front before sending it in back
can be include in project with
<script src="https://apiback.maildigit.fr/js/checkdata.js"></script>
or with const checkdata = require('../public/js/checkdata.js')
- into a browser : <script src="https://townName.nationName.dns/socialworld/contracts/check.js"></script>
- into a node.js : const check = require( `../nationchains/socialworld/contracts/check.js`);
*/
// --##
const checkdata = {};
// each checkdata.test. return true or false
checkdata.test = {};
checkdata.test.emailadress = ( ctx, email ) => {
const regExp = /^(([^<>()[\]\\.,;:\s@\"]+(\.[^<>()[\]\\.,;:\s@\"]+)*)|(\".+\"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/;
return regExp.test( email );
const check = {};
check.schema = {};
check.schema.properties = {};
check.schema.properties.type = {};
check.schema.properties.type.string = (str) => typeof str === "string";
check.schema.properties.type.number = (n) => typeof n === "number";
check.schema.properties.type.integer = (n) =>
n != "" && !isNaN(n) && Math.round(n) == n;
check.schema.properties.type.float = (n) =>
n != "" && !isNaN(n) && Math.round(n) != n; //not yet in json schema
check.schema.properties.minLength = (str, min) =>
typeof str === "string" && str.length > parseInt(min);
check.schema.properties.maxLength = (str, max) =>
typeof str === "string" && str.length < parseInt(max);
check.schema.properties.multipleOf = (n, val) =>
typeof n === "number" &&
typeof val === "number" &&
parseFloat(n) / parseFloat(val) -
Math.round(parseFloat(n) / parseFloat(val)) <
0.0000001;
check.schema.properties.range = (
n,
minimum,
exclusiveMinimum,
maximum,
exclusiveMaximum
) => {
//console.log(minimum,exclusiveMinimum,maximum, exclusiveMaximum,n)
if (typeof n !== "number") return false;
if (minimum && parseFloat(n) < parseFloat(minimum)) return false;
if (exclusiveMinimum && parseFloat(n) <= parseFloat(exclusiveMinimum))
return false;
if (maximum && parseFloat(n) > parseFloat(maximum)) return false;
if (exclusiveMaximum && parseFloat(n) >= parseFloat(exclusiveMaximum))
return false;
return true;
};
check.schema.properties.pattern = (str, pattern) => {
try {
new RegExp(pattern);
} catch (e) {
return false;
}
return pattern.test(str);
};
check.schema.properties.enum = (str, enumvalues) =>
typeof str === "string" && enumvalues.includes(str);
// see format https://json-schema.org/understanding-json-schema/reference/string.html#format
check.schema.properties.format = {
"date-time": / /,
time: / /,
date: / /,
duration: / /,
email:
/^(([^<>()[\]\\.,;:\s@\"]+(\.[^<>()[\]\\.,;:\s@\"]+)*)|(\".+\"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/,
"idn-email": / /,
uuid: /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/,
uri: / /,
"uri-reference": / /,
iri: / /,
hostname: / /,
"idn-hostname": / /,
ipv4: /^([09]{1,3}.){3}.([09]{1,3})$/,
ipv6: /^((([09A-Fa-f]{1,4}:){7}[09A-Fa-f]{1,4})|(([09A-Fa-f]{1,4}:){6}:[09A-Fa-f]{1,4})|(([09A-Fa-f]{1,4}:){5}:([09A-Fa-f]{1,4}:)?[09A-Fa-f]{1,4})|(([09A-Fa-f]{1,4}:){4}:([09A-Fa-f]{1,4}:){0,2}[09A-Fa-f]{1,4})|(([09A-Fa-f]{1,4}:){3}:([09A-Fa-f]{1,4}:){0,3}[09A-Fa-f]{1,4})|(([09A-Fa-f]{1,4}:){2}:([09A-Fa-f]{1,4}:){0,4}[09A-Fa-f]{1,4})|(([09A-Fa-f]{1,4}:){6}((b((25[05])|(1d{2})|(2[04]d)|(d{1,2}))b).){3}(b((25[05])|(1d{2})|(2[04]d)|(d{1,2}))b))|(([09A-Fa-f]{1,4}:){0,5}:((b((25[05])|(1d{2})|(2[04]d)|(d{1,2}))b).){3}(b((25[05])|(1d{2})|(2[04]d)|(d{1,2}))b))|(::([09A-Fa-f]{1,4}:){0,5}((b((25[05])|(1d{2})|(2[04]d)|(d{1,2}))b).){3}(b((25[05])|(1d{2})|(2[04]d)|(d{1,2}))b))|([09A-Fa-f]{1,4}::([09A-Fa-f]{1,4}:){0,5}[09A-Fa-f]{1,4})|(::([09A-Fa-f]{1,4}:){0,6}[09A-Fa-f]{1,4})|(([09A-Fa-f]{1,4}:){1,7}:))$/,
telephonefr: /^0[1-9][0-9]{9}$/,
telephoneinter: /^\+*(\d{3})*[0-9,\-]{8,}/,
password:
/^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)(?=.*[$@$!%*?&.])[A-Za-z\d$@$!%*?&.{}:|\s]{8,}/,
postalcodefr: /(^\d{5}$)|(^\d{5}-\d{4}$)/,
};
check.schema.validation = (schema) => {
/*validate a schema structure*/
const res = { status: 200, err: [] };
if (schema.properties) {
Object.keys(schema.properties).forEach((p) => {
const properties = schema.properties;
if (
properties[p].type &&
typeof properties[p].type === "string" &&
!check.schema.properties.type[properties[p].type]
) {
res.err.push({
info: "|checkdata|typedoesnotexistinschema",
moreinfo: ` ${properties[p].type}`,
});
}
if (
properties[p].type &&
typeof properties[p].type === "object" &&
Array.isArray(properties[p].type)
) {
properties[p].type.forEach((tp) => {
if (!check.schema.properties.type[tp])
res.err.push({
info: "|checkdata|typedoesnotexistinschema",
moreinfo: `${tp} of ${properties[p].type}`,
});
});
}
if (
properties[p].format &&
!check.schema.properties.format[properties[p].format]
) {
res.err.push({
info: "|checkdata|formatdoesnotexistinschema",
moreinfo: ` ${properties[p].format}`,
});
}
if (properties[p].enum && !Array.isArray(properties[p].enum)) {
res.err.push({
info: "|checkdata|enumisnotarrayinschema",
moreinfo: ` ${properties[p].enum}`,
});
}
});
}
// 406 means not acceptable
if (res.err.length > 0) res.status = 406;
return res;
};
check.schema.data = (schema, ctx, data) => {
/* validate a data set with a schema in a context ctx */
/*
console.log('#################')
console.log(schema);
console.log('---------')
console.log(data)
*/
const validschema = check.schema.validation(schema);
if (validschema.status != 200) return validschema;
const res = { status: 200, err: [] };
if (schema.properties) {
const properties = schema.properties;
Object.keys(properties).forEach((p) => {
//type is mandatory in a propertie
if (data[p]) {
const typlist =
properties[p].type && typeof properties[p].type === "string"
? [properties[p].type]
: properties[p].type;
let valid = false;
typlist.forEach((typ) => {
// at least one test have to be ok
if (check.schema.properties.type[typ](data[p])) valid = true;
});
if (!valid)
res.err.push({
info: "|checkdata|dataerrpropertie",
moreinfo: `${p} : ${data[p]}`,
});
if (
properties[p].minLength &&
!check.schema.properties.minLength(data[p], properties[p].minLength)
) {
res.err.push({
info: "|checkdata|dataerrpropertie",
moreinfo: `${p} : ${data[p]} minLength:${properties[p].minLength}`,
});
}
if (
properties[p].maxLength &&
!check.schema.properties.maxLength(data[p], properties[p].maxLength)
) {
res.err.push({
info: "|checkdata|dataerrpropertie",
moreinfo: `${p} : ${data[p]} maxLength:${properties[p].maxLength}`,
});
}
if (
properties[p].multipleOf &&
!check.schema.properties.multipleOf(data[p], properties[p].multipleOf)
) {
res.err.push({
info: "|checkdata|dataerrpropertie",
moreinfo: `${p} : ${data[p]} not a multipleOf:${properties[p].multipleOf}`,
});
}
if (
properties[p].minimum ||
properties[p].maximum ||
properties[p].exclusiveMinimum ||
properties[p].exclusiveMaximum
) {
// test range
if (
!check.schema.properties.range(
data[p],
properties[p].minimum,
properties[p].exclusiveMinimum,
properties[p].maximum,
properties[p].exclusiveMaximum
)
) {
res.err.push({
info: "|checkdata|dataerrpropertie",
moreinfo: `${p} : ${data[p]} not in range ${properties[p].minimum} exclu: ${properties[p].exclusiveMinimum} and ${properties[p].maximum} exclu: ${properties[p].exclusiveMaximum}`,
});
}
}
if (
properties[p].enum &&
!check.schema.properties.enum(data[p], properties[p].enum)
) {
res.err.push({
info: "|checkdata|dataerrpropertie",
moreinfo: `${p} : ${data[p]} not in enum list :${properties[p].enum}`,
});
}
if (properties[p].format) {
properties[p].pattern =
check.schema.properties.format[properties[p].format];
}
if (
properties[p].pattern &&
!check.schema.properties.pattern(data[p], properties[p].pattern)
) {
res.err.push({
info: "|checkdata|dataerrpropertie",
moreinfo: `${p} : ${data[p]} problem pattern or format ${properties[p].pattern}`,
});
}
} else if (schema.required.includes(p)) {
res.err.push({
info: "|checkdata|dataerrpropertiesrequired",
moreinfo: `${p}`,
});
}
});
}
if (res.err.length > 0) res.status = 417;
return res;
};
/*
* @emaillist = "email1,email2, email3"
* it check if each eamil separate by , are correct
*/
checkdata.test.emailadresslist = ( ctx, emaillist ) => {
//console.log(emaillist.split(','))
if( emaillist.length > 0 ) {
const emails = emaillist.split( ',' );
for( var i in emails ) {
//console.log(emails[i])
if( !checkdata.test.emailadress( "", emails[ i ].trim() ) ) {
return false
}
}
};
return true;
};
checkdata.test.password = ( ctx, pwd ) => {
const regExp = new RegExp(
/^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)(?=.*[$@$!%*?&.])[A-Za-z\d$@$!%*?&.{}:|\s]{8,}/
);
return regExp.test( pwd );
};
checkdata.test.required = ( ctx, val ) =>
( val != null && val != 'undefined' && val.length > 0 ) || ( !!val && val.constructor === Array && val.length > 0 ) || ( !!val && val.constructor === Object && Object.keys( val )
.length > 0 );
checkdata.test.isNumber = ( ctx, n ) => typeof n === 'number';
checkdata.test.isInt = ( ctx, n ) => n != '' && !isNaN( n ) && Math.round( n ) == n;
checkdata.test.isFloat = ( ctx, n ) => n != '' && !isNaN( n ) && Math.round( n ) != n;
checkdata.test.unique = ( ctx, val ) => {
if( ctx.list[ ctx.currentfield ] ) {
return !ctx.list[ ctx.currentfield ].includes( val );
} else {
console.log( 'ERR no list for field:' + ctx.currentfield );
return false;
}
};
checkdata.test.isDateDay = ( ctx, dateDay ) => true;
/* checkdata.test.filterInvalidInArray = (array, validate) =>
array ? array.filter(el => !validate(el)) : true;
// return true when every elements is valid
Normalize data link to check.schema.properties.format
or any normalization to get consistent data
*/
checkdata.test.postalCode = ( ctx, postalCode ) => {
if( postalCode.length == 0 ) return true;
const regExp = new RegExp( /(^\d{5}$)|(^\d{5}-\d{4}$)/ );
return regExp.test( postalCode );
const normalize={};
normalize.telephonefr =(phone)=>{
phone = phone.trim().replace(/[- .]/g, "");
if (
check.schema.properties.format.telephoenfr(phone) &&
phone.length == 10 &&
phone[0] == "0"
) {
phone = "+33 " + phone.substring(1);
}
return phone;
};
/**
* PHONE
*/
checkdata.test.phoneNumber = ( ctx, phoneNumber ) => {
if( phoneNumber.length == 0 ) return true;
phoneNumber = phoneNumber.trim()
.replace( /[- .]/g, '' )
//french number
const regExpfr = new RegExp( /^0[1-9][0-9]{9}$/ );
const regExpInternational = new RegExp( /^\+*(\d{3})*[0-9,\-]{8,}/ );
return regExpfr.test( phoneNumber ) || regExpInternational.test( phoneNumber );
};
/*
* @phonelist = "phone1,phone2,phone3"
* it check if each phone separate by , are correct
*/
checkdata.test.phoneNumberlist = ( ctx, phonelist ) => {
//console.log(emaillist.split(','))
if( phonelist.length > 0 ) {
const phones = phonelist.split( ',' );
for( var i in phones ) {
//console.log(emails[i])
if( !checkdata.test.phoneNumber( "", phones[ i ].trim() ) ) {
return false
}
}
};
return true;
normalize.zfill10 = (num) => {
let s = num + "";
while (s.length < 10) s = "0" + s;
return s;
};
// checkdata.normalize take a correct data then reformat it to harmonise it
checkdata.normalize = {};
checkdata.normalize.phoneNumber = ( ctx, phone ) => {
phone = phone.trim()
.replace( /[- .]/g, '' );
if( checkdata.test.phoneNumber( '', phone ) && phone.length == 10 && phone[ 0 ] == "0" ) {
phone = '+33 ' + phone.substring( 1 );
}
return phone;
}
checkdata.normalize.upperCase = ( ctx, txt ) => txt.toUpperCase();
checkdata.normalize.lowerCase = ( ctx, txt ) => txt.toLowerCase();
check.test.unique = (ctx, val) => {
if (ctx.list[ctx.currentfield]) {
return !ctx.list[ctx.currentfield].includes(val);
} else {
console.log("ERR no list for field:" + ctx.currentfield);
return false;
}
};
// check.normalize take a correct data then reformat it to harmonise it
check.normalize = {};
check.normalize.phoneNumber = (ctx, phone) => {
phone = phone.trim().replace(/[- .]/g, "");
if (
check.test.phoneNumber("", phone) &&
phone.length == 10 &&
phone[0] == "0"
) {
phone = "+33 " + phone.substring(1);
}
return phone;
};
check.normalize.upperCase = (ctx, txt) => txt.toUpperCase();
check.normalize.lowerCase = (ctx, txt) => txt.toLowerCase();
// fixe 10 position et complete par des 0 devant
checkdata.normalize.zfill10 = ( ctx, num ) => {
let s = num + '';
while( s.length < 10 ) s = '0' + s;
return s;
};
/*let tt = "+33 1 02.03 04 05";
console.log(checkdata.test.phoneNumber('', tt))
console.log(checkdata.normalize.phoneNumber('', tt))
*/
checkdata.evaluate = ( contexte, referential, data ) => {
/*
* contexte object {} with full info for evaluation
* file referential path to get object to apply
* data related to object
- return {validefor =[keyword of error] if empty no error,
clean data eventually reformated
updateDatabase}
*/
console.log( 'contexte', contexte );
console.log( 'referentiel', referential );
console.log( 'data', data );
const invalidefor = [];
const objectdef = {};
const listfield = referential.map( ch => {
objectdef[ ch.idfield ] = ch;
return ch.idfield;
} );
Object.keys( data )
.forEach( field => {
if( !listfield.includes( field ) ) {
// some data can be inside an object with no control at all
// they are used for process only
// i leave it in case it will become a non sens
// invalidefor.push('ERRFIELD unknown of referentials ' + field);
} else {
if( objectdef[ field ].check ) {
// check data with rule list in check
objectdef[ field ].check.forEach( ctrl => {
console.log( 'ctrl', ctrl );
contexte.currentfield = field;
if( !checkdata.test[ ctrl ] ) {
invalidefor.push( 'ERR check function does not exist :' + ctrl + '___' + field )
} else {
if( !checkdata.test[ ctrl ]( contexte, data[ field ] ) )
invalidefor.push( 'ERR' + ctrl + '___' + field );
}
} );
}
if( objectdef[ field ].nouserupdate ) {
// check if user can modify this information
console.log(
'evaluation :' + field + ' -- ' + objectdef[ field ].nouserupdate,
eval( objectdef[ field ].nouserupdate )
);
const evalright = eval( objectdef[ field ].nouserupdate );
objectdef[ field ].nouserupdate = evalright;
}
}
} );
console.log( {
invalidefor,
data
} );
return {
invalidefor,
data
};
check.normalize.zfill10 = (ctx, num) => {
let s = num + "";
while (s.length < 10) s = "0" + s;
return s;
};
if( typeof module !== 'undefined' ) module.exports = checkdata;
if (typeof module !== "undefined") module.exports = check;

View File

@@ -0,0 +1,5 @@
{
"typedoesnnotexistinschema":"This type in your propertie is not manage by checkdata.js",
"dataerrpropertie":"Check your data that not fit your schema rules propertie",
"dataerrpropertiesrequired":"This propertie is required and not present in your data"
}

View File

@@ -0,0 +1,36 @@
/*
Ants nation contract
*/
const Contract={
object:"nation",
id:"ants",
version:"0.0.0",
dt_create:"20230307",
dt_update:"",
info:"https://apxtrib.crabdance.com/nation_base.html",
};
Contract.trigger = ()=>{
// Identification of rule to trig
// Create token for a tribe =>
}
Contract.druidAllowedtoCreateToken = (tokenconf, hashtokenconf)=>{
/*
@tokenconf ={
tokenvalue: float value in apxtr,
quantity: int number of token,
druidId: uuid,
costrequest: float cost of this in apxtr to the mayorId
}
Check balance druid wallet
@return {status:200, data:{transacId:blocktimestamp,tokens:[token1, token2, ...]}
{status:<>200, info:'error message'}
Store transaction in open block with transacId
A token = druidUuid_hash(with blockchain elected privatekey)
Druid can sale his token with his rules at a tribe level but any user that request an exchange ffrom a token
*/
}
module.exports = Contract;

View File

@@ -6,7 +6,7 @@ const moment = require( 'moment' );
const config = require( '../config' );
const utils = {};
console.log( "Check in /utils/index.js to find usefull function for your dev.\n Feel free to send suggestion, code to maintainer of apixtribe project (see /package.json to get email).\n We'll add to the roadmap to add it." );
console.log( "Check in /utils/index.js to find usefull function for your dev.\n Feel free to send suggestion, code to maintainer of apxtrib project (see /package.json to get email).\n We'll add to the roadmap to add it." );
/**
* EMAIL

View File

@@ -0,0 +1,36 @@
/*
Town contract:town_base
*/
const Contract={
object:"town",
id:"town_base",
version:"0.0.0",
dt_create:"20230307",
dt_update:"",
info:"https://apxtrib.crabdance.com/town_base.html",
};
Contract.trigger = ()=>{
// Identification of rule to trig
// Create token for a tribe =>
}
Contract.druidAllowedtoCreateToken = (tokenconf, hashtokenconf)=>{
/*
@tokenconf ={
tokenvalue: float value in apxtr,
quantity: int number of token,
druidId: uuid,
costrequest: float cost of this in apxtr to the mayorId
}
Check balance druid wallet
@return {status:200, data:{transacId:blocktimestamp,tokens:[token1, token2, ...]}
{status:<>200, info:'error message'}
Store transaction in open block with transacId
A token = druidUuid_hash(with blockchain elected privatekey)
Druid can sale his token with his rules at a tribe level but any user that request an exchange ffrom a token
*/
}
module.exports = Contract;

View File

@@ -0,0 +1,146 @@
/*
Unit testing
*/
const assert = require("assert");
const checkdata = require("../checkdata.js");
const ut = { name: "checkdata" };
const schema = {
$schema: "http://json-schema.org/schema#",
title: "Dummy schema to test checkdata.js",
description: "Checkdata is use on server as well as into a browser",
$comment: "We change schema type on the fly to simplify the test",
type: "Object",
properties: {
totest: {},
},
};
const testproperties = [
{
name: "test1",
data: { totest: "blabla" },
properties: { totest: { type: "string" } },
status: 200
},
{
name: "test2",
data: { totest: 123 },
properties: { totest: { type: "string" } },
status: 417
},
{
name: "test3",
data: { totest: 123.13 },
properties: { totest: { type: "integer" } },
status: 417
},
{
name: "test4",
data: { totest: 123 },
properties: { totest: { type: "number" } },
status: 200
},
{
name: "test5",
data: { totest: 12312 },
properties: { totest: { type: "number" } },
status: 200
},
{
name: "test6",
data: { totest: 12.313 },
properties: { totest: { type: "float" } },
status: 200
},
{
name: "test7",
data: { totest: "blablab sfde" },
properties: { totest: { type: "string", minLength: 1111 } },
status: 417
},
{
name: "test8",
data: { totest: "blablab sfde" },
properties: { totest: { type: "string", minLength: 4, maxLength: 128} },
status: 200
},
{
name: "test9",
data: { totest: 12 },
properties: { totest: { type: "integer", multipleOf:3} },
status: 200
},
{
name: "test10",
data: { totest: 9 },
properties: { totest: { type: "number", minimum:-10, exclusiveMaximum:10} },
status: 200
},
{
name: "test11",
data: { totest: 10 },
properties: { totest: { type: "number", minimum:-10, exclusiveMaximum:10} },
status: 417
},
{
name: "test12",
data: { totest: "gfhrtabcdgfr" },
properties: { totest: { type: "string", pattern:/.*abc.*/} },
status: 200
},
{
name: "test13",
data: { totest: "toto@google.com" },
properties: { totest: { type: "string", format:"email"} },
status: 200
},
{
name: "test14",
data: { totest: "Aze123@0" },
properties: { totest: { type: "string", format:"password"} },
status: 200
},
{
name: "test15",
data: { totest: "value1" },
properties: { totest: { type: "string", enum:["value1","value2","value3"]} },
status: 200
},
{
name: "test16",
data: { totest: ["t1","t2"] },
properties: { totest: { type: ["string", "number"] }},
status: 417
}
,
{
name: "test17",
data: { totest: 12 },
properties: { totest: { type: ["string", "number"] }},
status: 200
}
];
ut.testproperties = (options) => {
let msg = "";
testproperties.forEach((t) => {
schema.properties = t.properties;
const res = checkdata.schema.data(schema, {}, t.data);
if (res.status != t.status) {
msg = (msg == "") ? "Unconsistent testproperties() name list: " : `${msg},`;
if (options.verbose) {
console.log(t)
console.log(res);
}
msg += res.err.map((e) => ` ${t.name} ${e.info}`);
}
});
return assert.deepEqual(msg, "", msg);
};
ut.run = (options) => {
console.log("Test checkdata properties");
ut.testproperties(options);
};
module.exports = ut;