update project with new architecture

This commit is contained in:
2023-04-13 07:46:35 +02:00
parent d0a3b10cfe
commit 67a02c33a2
333 changed files with 3764 additions and 1254 deletions

89
app/middlewares/checkHeaders.js Executable file
View File

@@ -0,0 +1,89 @@
const path = require( 'path' );
const config = require( '../tribes/townconf.js' );
const checkHeaders = ( req, res, next ) => {
/**
* @apiDefine apxHeader
* @apiGroup Middleware
* @apiDescription Header is mandatory to access apxtrib see tribes/townconf.json.exposedHeaders
* A turn around can be done with a simple get params has to be sent in the get url. Usefull to send simple get without header like ?xworkon=tribeName&xlang=en... priority is given to headers
* For performance, tokens are store globaly in req.app.locals.tokens={xpaganid:xauth}
* if xlang is not in config.languagesAvailable
*
* @apiHeader {string} xauth Pagans unique jwt token store in local town Pagans data or "noauth"
* @apiHeader {string} xpaganid Pagans unique Pagan id in uuid format or "nouuid"
* @apiHeader {string} xlang the 2 letter langage it request the api (if not exist the 2 first letter of Accept-Language header ) if lang does not exist in the town then en is set (as it always exist in en).
* @apiHeader {string} xtribe Tribes id where Pagan belong to
* @apiHeader {string} xworkon Tribes on which pagansId want and have accessright to work on.
* @apiHeader {string} xapp Name of app that send the request (tribesId:websiteName) cpaganid have to have accessright on this app}
*
* @apiError missingexposedHeaders it miss an exposedHeaders
*
* @apiErrorExample {json} Error-Response:
* HTTP/1/1 404 Not Found
* {
* status:404,
* info:"|middleware|missingheaders",
* moreinfo: xpaganid xauth
* }
*
* @apiHeaderExample {json} Header-Exemple:
* {
* xtribe:"apache",
* xpaganid:"12123211222",
* xworkon:"sioux",
* xauth:"",
* xlang:"en",
* xapp:""
* }
*/
req.session = {};
const header = {};
if (!req.header('xlang') && req.header('Content-Language')) req.params.xlang=req.header('Content-Language');
let missingheader = [];
for( const h of config.exposedHeaders ) {
//console.log( h, req.header( h ) )
if( req.params[ h ] ) {
header[ h ] = req.params[ h ]
} else if( req.header( h ) ) {
header[ h ] = req.header( h )
} else {
missingheade.push(h);
}
};
//console.log( 'header', header )
// store in session the header information
req.session.header = header;
// Each header have to be declared
if( missingheader != "" ) {
// bad request
return res.status( 400 )
.json( {
ref:"headers"
info: "missingheader",
moreinfo: missingheader
} );
};
//console.log( req.app.locals.tribeids )
if( !req.app.locals.tribeids.includes( header.xtribe ) ) {
return res.status( 400 )
.json( {
ref:"headers"
info: 'tribeiddoesnotexist',
moreinfo: header.xtribe
} );
}
if( !req.app.locals.tribeids.includes( header.xworkon ) ) {
return res.status( 400 )
.send( {
info: [ 'workondoesnotexist' ],
ref: 'headers',
moreinfo:header.xworkon
} );
}
if( !config.languages.includes( header.xlang ) ) {
header.xlang="en";
}
next();
};
module.exports = checkHeaders;

View File

@@ -0,0 +1,42 @@
const fs = require( 'fs-extra' );
const glob = require( 'glob' );
const path = require( 'path' );
const config = require( '../tribes/townconf.js' );
const hasAccessrighton = ( object, action, ownby ) => {
/*
@action (mandatory) : CRUDO
@object (mandatory)= name of a folder object in /tribeid space can be a tree for example objects/items
@ownby (option) = list des uuid propriétaire
return next() if all action exist in req.app.local.tokens[UUID].ACCESSRIGHTS.data[object]
OR if last action ="O" and uuid exist in ownBy
Careffull if you have many action CRO let O at the end this will force req.right at true if the owner try an action on this object
*/
return ( req, res, next ) => {
//console.log( 'err.stack hasAccessrights', err.statck )
//console.log( `test accessright on object:${object} for ${req.session.header.xworkon}:`, req.app.locals.tokens[ req.session.header.xpaganid ].ACCESSRIGHTS.data[ req.session.header.xworkon ] )
req.right = false;
if( req.app.locals.tokens[ req.session.header.xpaganid ].ACCESSRIGHTS.data[ req.session.header.xworkon ] && req.app.locals.tokens[ req.session.header.xpaganid ].ACCESSRIGHTS.data[ req.session.header.xworkon ][ object ] ) {
req.right = true;
[ ...action ].forEach( a => {
if( a == "O" && ownby && ownby.includes( req.session.header.xpaganid ) ) {
req.right = true;
} else {
req.right = req.right && req.app.locals.tokens[ req.session.header.xpaganid ].ACCESSRIGHTS.data[ req.session.header.xworkon ][ object ].includes( a )
}
} )
}
//console.log( 'Access data autorise? ', req.right )
if( !req.right ) {
return res.status( 403 )
.json( {
info:'forbiddenAccessright',
ref: 'headers',
moreinfo: {xpaganid:req.session.header.xpaganid,object:object, xworkon:req.session.header.xworkon, action:action}
} )
}
next();
}
}
module.exports = hasAccessrighton;

View File

@@ -0,0 +1,115 @@
const jwt = require( 'jwt-simple' );
const jsonfile = require( 'jsonfile' );
const fs = require( 'fs-extra' );
const moment = require( 'moment' );
const glob = require( 'glob' );
//const path = require( 'path' );
// Check if package is installed or not to pickup the right config file
//const src = '..'; // ( __dirname.indexOf( '/node_modules/' ) > -1 ) ? '../../..' : '..';
//const config = require( path.normalize( `${__dirname}/${src}/config.js` ) );
const config = require( '../tribes/townconf.js' );
const isAuthenticated = ( req, res, next ) => {
/*
check if authenticated with valid token
if not => set req.session.header.xauth=1
if yes => set for xWorkon
req.session.header.accessrights={
app:{'tribeid:website':[liste of menu]},
data:{ "sitewebsrc": "RWCDO",
"contacts": "RWCDO"}}
Liste of menu is linked with the app tht h
ave to be consistent with accessrights.data
data, list of object accessright Read Write Create Delete Owner
a xuuid can read any objet if R
if O wner means that it can only read write its object create by himself
*/
console.log( 'isAuthenticated()?' );
//console.log( 'req.app.locals.tokens', req.app.locals.tokens )
//console.log( 'req.session.header', req.session.header );
// Check if token exist or not
req.session.header.accessrights = { app: "", data: {} }
if( req.session.header.xpaganid == config.devnoauthxuuid && req.session.header.xauth == config.devnoauthxauth ) {
console.log( 'isAuthenticated yes: carrefull using a bypass password give you accessrights={}' );
} else if( req.session.header.xpaganid == "1" || !req.app.locals.tokens[ req.session.header.xpaganid ] ) {
console.log( `isAuthenticated no : uuid=1 (value=${req.session.header.xpaganid}) or locals.tokens[uuid] empty ` );
console.log( 'req.app.locals.tokens de xpaganid', req.app.locals.tokens[ req.session.header.xpaganid ] );
console.log( 'list key uuid de req.app.locals.tokens', Object.keys( req.app.locals.tokens ) )
req.session.header.xauth = "1"
} else if( req.app.locals.tokens[ req.session.header.xpaganid ].TOKEN !== req.session.header.xauth ) {
// console.log(req.session.header.xuuid);
// console.log(req.session.header.xauth);
// update tokens from file in case recently logged
try {
console.log( 'token not in list of token (req.app.locals.tokens) try to refresh from file' );
req.app.locals.tokens = fs.readJsonSync( `${config.tmp}/tokens.json` );
} catch ( err ) {
console.log( `check isAuthenticated issue in reading ${config.tmp}/tokens.json` );
}
if( req.app.locals.tokens[ req.session.header.xpaganid ].TOKEN !== req.session.header.xauth ) {
// if still does not exist then out
console.log( 'isAuthenticated no, token outdated' );
req.session.header.xauth = "1"
req.session.header.xpaganid = "1"
}
}
if( req.session.header.xauth == "1" ) {
//return res.status( 403 )
return res.status( 403 )
.json( {
info: [ 'forbiddenAccess' ],
model: 'Pagans',
moreinfo: 'isAuthenticated faill'
} )
} else {
console.log( 'isAuthenticated yes' );
if( req.app.locals.tokens[ req.session.header.xpaganid ] ) {
//console.log( `accessright pour ${req.session.header.xpaganid}`, req.app.locals.tokens[ req.session.header.xpaganid ].ACCESSRIGHTS );
//set header.accessrights from tokens.json
req.session.header.accessrights = req.app.locals.tokens[ req.session.header.xpaganid ].ACCESSRIGHTS
} else {
// case of bypass no accessright available
req.session.header.accessrights = {}
}
// Once per day, clean old token
const currentday = moment()
.date();
console.log( 'test si menagedone' + currentday, !fs.existsSync( `${config.tmp}/menagedone${currentday}` ) )
if( !fs.existsSync( `${config.tmp}/menagedone${currentday}` ) ) {
glob.sync( `${config.tmp}/menagedone*` )
.forEach( f => {
fs.remove( f, ( err ) => {
if( err ) {
console.log( 'err remove menagedone', err )
}
} )
} );
glob.sync( `${config.tmp}/mdcreator*.log` )
.forEach( f => {
fs.remove( f, ( err ) => {
if( err ) {
console.log( 'err remove mdcreator log', err )
}
} )
} );
const newtokens = {};
for( const k of Object.keys( req.app.locals.tokens ) ) {
try {
const decodedToken = jwt.decode( req.app.locals.tokens[ k ].TOKEN, config.jwtSecret );
//console.log( moment( decodedToken.expiration ), moment() )
//console.log( moment( decodedToken.expiration ) >= moment() )
if( moment( decodedToken.expiration ) >= moment() ) {
newtokens[ k ] = req.app.locals.tokens[ k ];
}
} catch ( err ) {
console.log( "Check isAuthenticated cleaning token ", err );
}
};
req.app.locals.tokens = newtokens;
fs.outputJsonSync( `${config.tmp}/tokens.json`, newtokens );
fs.writeFileSync( `${config.tmp}/menagedone${currentday}`, 'fichier semaphore to clean data each day can be deleted with no consequence', 'utf-8' );
}
next();
}
};
module.exports = isAuthenticated;

View File

@@ -0,0 +1,6 @@
{
"missingheader":"This header miss to have a valid request: {{#moreinfo}} {{.}} {{/moreinfo}}",
"tribeiddoesnotexist":"Header xtribe: {{moreinfo}} does not exist",
"workondoesnotexist":"Header xworkon: {{moreinfo}} does not exist",
"forbiddenAccessright":"Pagan {{moreinfo.xpaganid}} has not access right to act {{moreinfo.action}} onto object {{moreinfo.object}} for tribe {{moreinfo.xworkon}}"
}

248
app/models/Checkjson.js Executable file
View File

@@ -0,0 +1,248 @@
/*
This module have to be use in back as well front
can be include in project with
- into a browser : <script src="https://townName.nationName.dns/nationchains/contracts/Checkjson.js"></script>
- into a node.js : const Checkjson = require( `../nationchains/socialworld/contracts/Checkjson.js`);
*/
// --##
const Checkjson = {};
Checkjson.schema = {};
Checkjson.schema.properties = {};
Checkjson.schema.properties.type = {};
Checkjson.schema.properties.type.string = (str) => typeof str === "string";
Checkjson.schema.properties.type.number = (n) => typeof n === "number";
Checkjson.schema.properties.type.boolean = (n) => typeof n === "boolean";
Checkjson.schema.properties.type.integer = (n) =>
n != "" && !isNaN(n) && Math.round(n) == n;
Checkjson.schema.properties.type.float = (n) =>
n != "" && !isNaN(n) && Math.round(n) != n; //not yet in json schema
Checkjson.schema.properties.minLength = (str, min) =>
typeof str === "string" && str.length > parseInt(min);
Checkjson.schema.properties.maxLength = (str, max) =>
typeof str === "string" && str.length < parseInt(max);
Checkjson.schema.properties.multipleOf = (n, val) =>
typeof n === "number" &&
typeof val === "number" &&
parseFloat(n) / parseFloat(val) -
Math.round(parseFloat(n) / parseFloat(val)) <
0.0000001;
Checkjson.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;
};
Checkjson.schema.properties.pattern = (str, pattern) => {
try {
new RegExp(pattern);
} catch (e) {
return false;
}
return pattern.test(str);
};
Checkjson.schema.properties.enum = (str, enumvalues) =>
typeof str === "string" && enumvalues.includes(str);
// see format https://json-schema.org/understanding-json-schema/reference/string.html#format
Checkjson.schema.properties.format = {
"date-time": / /,
stringalphaonly:/^[A-Za-z0-9]{3,}$/,
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}$)/,
};
Checkjson.schema.properties.default
Checkjson.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" &&
!Checkjson.schema.properties.type[properties[p].type]
) {
res.err.push({
info: "|Checkjson|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 (!Checkjson.schema.properties.type[tp])
res.err.push({
info: "|Checkjson|typedoesnotexistinschema",
moreinfo: `${tp} of ${properties[p].type}`,
});
});
}
if (
properties[p].format &&
!Checkjson.schema.properties.format[properties[p].format]
) {
res.err.push({
info: "|Checkjson|formatdoesnotexistinschema",
moreinfo: ` ${properties[p].format}`,
});
}
if (properties[p].enum && !Array.isArray(properties[p].enum)) {
res.err.push({
info: "|Checkjson|enumisnotarrayinschema",
moreinfo: ` ${properties[p].enum}`,
});
}
});
}
// 406 means not acceptable
if (res.err.length > 0) res.status = 406;
return res;
};
Checkjson.schema.data = (schema, data, withschemacheck) => {
/* validate a data set with a schema in a context ctx */
/*
console.log('#################')
console.log(schema);
console.log('---------')
console.log(data)
*/
if (withschemacheck) {
const validschema = Checkjson.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 (Checkjson.schema.properties.type[typ](data[p])) valid = true;
});
if (!valid)
res.err.push({
info: "|Checkjson|dataerrpropertie",
moreinfo: `${p} : ${data[p]}`,
});
if (
properties[p].minLength &&
!Checkjson.schema.properties.minLength(data[p], properties[p].minLength)
) {
res.err.push({
info: "|Checkjson|dataerrpropertie",
moreinfo: `${p} : ${data[p]} minLength:${properties[p].minLength}`,
});
}
if (
properties[p].maxLength &&
!Checkjson.schema.properties.maxLength(data[p], properties[p].maxLength)
) {
res.err.push({
info: "|Checkjson|dataerrpropertie",
moreinfo: `${p} : ${data[p]} maxLength:${properties[p].maxLength}`,
});
}
if (
properties[p].multipleOf &&
!Checkjson.schema.properties.multipleOf(data[p], properties[p].multipleOf)
) {
res.err.push({
info: "|Checkjson|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 (
!Checkjson.schema.properties.range(
data[p],
properties[p].minimum,
properties[p].exclusiveMinimum,
properties[p].maximum,
properties[p].exclusiveMaximum
)
) {
res.err.push({
info: "|Checkjson|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 &&
!Checkjson.schema.properties.enum(data[p], properties[p].enum)
) {
res.err.push({
info: "|Checkjson|dataerrpropertie",
moreinfo: `${p} : ${data[p]} not in enum list :${properties[p].enum}`,
});
}
if (properties[p].format) {
properties[p].pattern =
Checkjson.schema.properties.format[properties[p].format];
}
if (
properties[p].pattern &&
!Checkjson.schema.properties.pattern(data[p], properties[p].pattern)
) {
res.err.push({
info: "|Checkjson|dataerrpropertie",
moreinfo: `${p} : ${data[p]} problem pattern or format ${properties[p].pattern}`,
});
}
} else if (schema.required && schema.required.includes(p)) {
res.err.push({
info: "|Checkjson|dataerrpropertiesrequired",
moreinfo: `${p}`,
});
}
});
}
if (res.err.length > 0) res.status = 417;
return res;
};
if (typeof module !== "undefined") module.exports = Checkjson;

117
app/models/Contracts.js Executable file
View File

@@ -0,0 +1,117 @@
const fs = require( 'fs-extra' );
const jsonfile = require( 'jsonfile' );
const glob = require( 'glob' );
const moment = require( 'moment' );
const axios = require( 'axios' );
// Check if package is installed or not to pickup the right config file
const config = require( '../tribes/townconf.js' );
/*
Model that will process actions plan for each client like sending email campain, or anything that
are plan in /tribes/tribeid/actions/todo
*/
const Cards = {}; //require('../../models/Cards');
const Contracts = {};
/*
Send if envoicampain a liste of email in param.msg.destperso with param.headers
if not envoicampain, it just return a test about what to send
@param = {headers, msg:{destperso}}
*/
Contracts.sendcampain = async ( param, envoicampain ) => {
if( envoicampain ) {
// Carefull w the action post outputs/msg just wait the feedback of the 1st message
const retcampain = await axios.post( 'https://mail.maildigit.fr/outputs/msg', param.msg, {
headers: param.headers
} );
if( retcampain.status !== 200 ) {
console.log( "err", retcampain.payload.moreinfo );
fs.appendFileSync( `${config.tribes}/log_erreurglobal.txt`, moment( new Date() )
.format( 'YYYYMMDD HH:mm:ss' ) + ' - IMPOSSIBLE TO SEND CAMPAIN TODO for :' + param.tribeid + ' -- ' + retcampain.payload.moreinfo + '\n', 'utf-8' );
};
return retcampain;
} else {
// permet de tester ce qu'il y a à envoyer
let premieremail = "";
for( let i = 0; i < param.msg.destperso.length; i++ ) {
premieremail += param.msg.destperso[ 0 ].email + ",";
}
return {
status: 201,
payload: {
info: [ 'simplecomptage' ],
model: 'Contracts',
moreinfo: "#email: " + param.msg.destperso.length + " - 5 1st emails: " + premieremail
}
};
}
}
Contracts.initActiontodo = async ( envoie ) => {
const datedeb = moment( new Date() )
.format( 'YYYYMMDD HH:mm:ss' );
let todo, actiondone;
let log = {
nbaction: 0,
nbactionexec: 0,
nbactionerr: 0,
actionlist: ""
};
const listclient = fs.readJsonSync( `${config.tribes}/tribeids.json` );
for( let clid in listclient ) {
console.log( listclient[ clid ] );
let listaction = glob.sync( `${config.tribes}/${listclient[clid]}/actions/todo/*.json` );
for( let action in listaction ) {
console.log( listaction[ action ] )
log.nbaction++;
todo = fs.readJsonSync( listaction[ action ] );
let passdate = true;
// currentdate doit etre après la startDate si existe et avant valideuntilDate si existe
// console.log('test now est avant date start ', moment() < moment(todo.startDate, 'YYYYMMDD HH:mm:ss').toDate());
if( todo.startDate && ( moment() < moment( todo.startDate, 'YYYYMMDD HH:mm:ss' )
.toDate() ) ) {
passdate = false;
};
// currentdate ne doit pas depasser la date de validité de la tache
// console.log('test now est après la date de validite ', moment() > moment(todo.validuntilDate, 'YYYYMMDD HH:mm:ss').toDate());
if( todo.valideuntilDate && ( moment() > moment( todo.validuntilDate, 'YYYYMMDD HH:mm:ss' )
.toDate() ) ) {
passdate = false;
};
// currentdate
if( passdate && todo.action && todo.error == "" ) {
log.nbactionexec++;
const actiondone = await Contracts[ todo.action ]( todo, envoie );
todo.datesRun.push( moment( new Date() )
.format( 'YYYYMMDD HH:mm:ss' ) );
//console.log("actiondone"
log.actionlist += "STATUS:" + actiondone.status + " -- " + listaction[ action ] + "\n";
if( actiondone.status == 200 ) {
todo.error = "";
} else {
log.nbactionerr++;
todo.error += "status : " + actiondone.status + ' ' + actiondone.payload.moreinfo;
};
if( parseInt( todo.maxnumberoftime ) && todo.maxnumberoftime != "999" && ( todo.datesRun.length >= parseInt( todo.maxnumberoftime ) ) ) {
//archive en done this triggeraction
fs.outputJsonSync( listaction[ action ].replace( '/todo/', '/done/' ), todo, {
spaces: 2
} );
fs.unlinkSync( listaction[ action ] );
} else {
fs.outputJsonSync( listaction[ action ], todo, {
spaces: 2
} );
}
} else {
log.actionlist += "STATUS : not executed " + listaction[ action ] + "\n";
};
};
};
const trace = "###################### LOGS ####################\nSTART:" + datedeb + " END:" + moment( new Date() )
.format( 'YYYYMMDD HH:mm:ss' ) + "\n nombre d'actions analysées : " + log.nbaction + " dont executées : " + log.nbactionexec + " dont en erreur: " + log.nbactionerr + "\n" + log.actionlist;
fs.appendFileSync( `${config.tribes}/log.txt`, trace, 'utf-8' );
return "done";
}
module.exports = Contracts;

400
app/models/Messages.js Executable file
View File

@@ -0,0 +1,400 @@
const bcrypt = require( 'bcrypt' );
const fs = require( 'fs-extra' );
const path = require( 'path' );
const jsonfile = require( 'jsonfile' );
const glob = require( 'glob' );
const Mustache = require( 'mustache' );
const jwt = require( 'jwt-simple' );
const { DateTime } = require( 'luxon' );
const UUID = require( 'uuid' );
const Outputs = require( '../models/Outputs.js' );
const config = require( '../tribes/townconf.js' );
const Checkjson = require( `./Checkjson.js`);
/*
Message manager
* Manage apxtrib message at different level
* this means that an object (most often a user or a contact) want to send a question to an other user.
To know more http://gitlab.ndda.fr/philc/apxtrib/-/wikis/HOWTONotification
*/
const Messages = {};
Messages.init = () => {
console.group( 'init Message' );
Messages.aggregate();
}
Messages.byEmailwithmailjet = ( tribeid, msg ) => {
/* @tribeid requester
@msg =[{
To:[{Email,Name}],
Cc:[{Email,Name}],
Bcc:[{Email,Name}]}],
Subject:"Subject",
TextPart:"texte content",
HTMLPart:"html content"
}]
If tribeid has a mailjet account it use it if not then it use the apxtrib @mailjetconf = {apikeypub:, apikeypriv:, From:{Email:,Name:}}
This send a bunch of messages check the mailjet account used
FYI: Basic account is 200 email /days 6000 /month
Log is stored into
tribeidsender/messages/logs/sent/timestamp.json
@todo GUI to manage statistics and notification alert limit sender email
*/
console.log( 'Envoie mailjet' )
const confclient = fs.readJsonSync( `${config.tribes}/${tribeid}/clientconf.json` );
let tribeidsender = tribeid;
if( confclient.smtp && confclient.smtp.mailjet ) {
mailjetconf = confclient.smtp.mailjet;
} else {
const confapxtrib = fs.readJsonSync( `${config.tribes}/${config.mayorId}/clientconf.json` );
if( !( confapxtrib.smtp && confapxtrib.smtp.mailjet ) ) {
return { status: 403, data: { models: "Messages", info: [ "nosmtpmailjet" ], moreinfo: "missing smtpmailjet parameter in apxtrib contact your admin to activate an mailjet account on this server." } }
}
tribeidsender = "apxtrib";
mailjetconf = confapxtrib.smtp.mailjet;
}
//add from from setings account
const MSG = msg.map( m => { m.From = mailjetconf.From; return m; } );
const mailjet = require( 'node-mailjet' )
.connect( mailjetconf.apikeypub, mailjetconf.apikeypriv );
const request = mailjet.post( 'send', { version: 'v3.1' } )
.request( { "Messages": MSG } );
request
.then( result => {
//store into tribeidsender/messages/logs/sent/timestamp.json
const t = Date.now();
MSG.result = result.body;
fs.outputJson( `${config.tribes}/${tribeidsender}/messages/logs/sent/${t}.json`, MSG )
console.log( result.body )
} )
.catch( err => {
const t = Date.now();
MSG.result = err;
fs.outputJson( `${config.tribes}/${tribeidsender}/messages/logs/error/${t}.json`, MSG )
console.log( err.statusCode, err )
} )
};
Messages.buildemail = ( tribeid, tplmessage, data ) => {
/* @tribeid => client concerned by sending email get info from clientconf.json (customization, useradmin:{ EMAIL,LOGIN} , ...)
@tplmessage => folder where template is available (ex: apxtrib/referentials/dataManagement/template/order)
@data { destemail = email to
if destuuid => then it get user EMAIL if exiat
subject = mail subject
// any relevant information for template message
msgcontenthtml = html msg content
msgcontenttxt = text msg content
....}
@return msg
mail part ready to send by mailjet or other smtp
*/
if( !fs.existsSync( `${config.tribes}/${tribeid}/clientconf.json` ) ) {
return { status: 404, data: { model: "Messages", info: [ "tribeiddoesnotexist" ], moreinfo: `${tribeid} does not exist` } }
}
if( !fs.existsSync( `${config.tribes}/${tplmessage}` ) ) {
return { status: 404, data: { model: "Messages", info: [ "tplmessagedoesnotexist" ], moreinfo: `tpl does not exist ${tplmessage}` } }
}
const clientconf = fs.readJsonSync( `${config.tribes}/${tribeid}/clientconf.json` )
//add clientconf.customization into data for template
data.customization = clientconf.customization;
//manage receiver
const msg = { To: [ { Email: clientconf.useradmin.EMAIL, Name: clientconf.useradmin.LOGIN } ] };
if( data.destemail ) {
msg.To.push( { Email: data.destemail } )
}
if( data.destuuid && fs.exist( `${config.tribes}/${tribeid}/users/${data.destuuid}.json` ) ) {
const uuidconf = fs.readJsonSync( `${config.tribes}/${tribeid}/users/${data.destuuid}.json` );
if( uuidconf.EMAIL && uuidconf.EMAIL.length > 4 ) {
msg.To.push( { Email: uuidconf.EMAIL, Name: uuidconf.LOGIN } )
}
}
//console.log( data )
// email content
msg.Subject = `Message from ${tribeid}`;
if( data.subject ) msg.Subject = data.subject;
msg.TextPart = Mustache.render( fs.readFileSync( `${config.tribes}/${data.tplmessage}/contenttxt.mustache`, 'utf-8' ), data );
msg.HTMLPart = Mustache.render( fs.readFileSync( `${config.tribes}/${data.tplmessage}/contenthtml.mustache`, 'utf-8' ), data );
return {
status: 200,
data: { model: "Messages", info: [ "msgcustomsuccessfull" ], msg: msg }
}
};
Messages.postinfo = ( data ) => {
/*
Data must have:
at least one of desttribeid:,destuuid:,destemail:
if an email have to be sent tplmessage:"tribeid/referentials/dataManagement/template/tplname",
at least one of contactemail:,contactphone,contactlogin,contactuuid
any other usefull keys
Data is stored into contacts object .json
*/
let contact = "";
[ 'contactemail', 'contactphone', 'contactuuid', 'contactlogin' ].forEach( c => {
if( data[ c ] ) contact += c + "##" + data[ c ] + "###";
} )
console.log( contact )
if( contact == "" ) {
return { status: 404, data: { model: "Messages", info: [ "contactundefine" ], moreinfo: "no contact field found in this form" } }
}
if( !data.desttribeid ) {
return { status: 404, data: { model: "Messages", info: [ "tribeidundefine" ], moreinfo: "no desttribeid field found in this form" } }
}
// save new message in contacts
let messages = {};
if( fs.existsSync( `${config.tribes}/${data.desttribeid}/contacts/${contact}.json` ) ) {
messages = fs.readJsonSync( `${config.tribes}/${data.desttribeid}/contacts/${contact}.json`, 'utf-8' );
}
messages[ Date.now() ] = data;
fs.outputJsonSync( `${config.tribes}/${data.desttribeid}/contacts/${contact}.json`, messages );
// if templatemessage exist then we send alert email
// data content have to be consistent with tplmessage to generate a clean email
if( data.tplmessage && data.tplmessage != "" &&
fs.existsSync( `${config.tribes}/${data.tplmessage}` ) ) {
if( !( data.msgtplhtml && data.msgtpltxt ) ) {
data.msgcontenthtml = `<p>${data.contactname} - ${contact.replace(/###/g,' ').replace(/##/g,":")}</p><p>Message: ${data.contactmessage}</p>`
data.msgcontenttxt = `${data.contactname} - ${contact}/nMessage:${data.contactmessage}\n`;
} else {
data.msgcontenthtml = Mustache.render( data.msgtplhtml, data )
data.msgcontenttxt = Mustache.render( data.msgtpltxt, data )
}
const msg = Messages.buildemail( data.desttribeid, data.tplmessage, data )
if( msg.status == 200 ) {
Messages.byEmailwithmailjet( data.desttribeid, [ msg.data.msg ] );
}
// we get error message eventualy but email feedback sent is not in real time
return msg;
} else {
return { status: 404, data: { info: "missingtpl", model: "Messages", moreinfo: `missing template ${data.tplmessage}` } }
}
}
Messages.aggregate = () => {
// collect each json file and add them to a global.json notifat require level
const dest = {};
try {
glob.sync( `${ config.tribes }/**/notif_*.json` )
.forEach( f => {
//console.log( 'find ', f )
const repglob = `${path.dirname(f)}/global.json`;
if( !dest[ repglob ] ) { dest[ repglob ] = [] }
dest[ repglob ].push( fs.readJsonSync( f, 'utf-8' ) );
fs.removeSync( f );
} )
//console.log( dest )
Object.keys( dest )
.forEach( g => {
let notif = [];
if( fs.existsSync( g ) ) { notif = fs.readJsonSync( g ) };
fs.writeJsonSync( g, notif.concat( dest[ g ] ), 'utf-8' );
} )
} catch ( err ) {
Console.log( "ATTENTION, des Messages risquent de disparaitre et ne sont pas traitées." );
}
}
Messages.object = ( data, header ) => {
/*
Create or replace an object no check at all here this is only
a way to add / replace object
if deeper thing have to be checheck or done then a callback:{tribeidplugin,pluginname,functionname}
data.descttribeid tribeid to send at least to admin
data.tplmessage = folder of emailtemplate
*/
console.log( 'data', data )
console.log( `${config.tribes}/${header.xworkon}/${data.object}` )
if( !fs.existsSync( `${config.tribes}/${header.xworkon}/${data.object}` ) ) {
return {
status: 404,
data: {
model: "Messages",
info: [ 'UnknownObjectfortribeid' ],
moreinfo: `This object ${data.object} does not exist for this tribeid ${header.xworkon}`
}
};
}
if( data.uuid == 0 ) {
data.uuid = UUID.v4();
}
if( data.callback ) {
// check from plugin data and add relevant data
const Plug = require( `${config.tribes}/${data.callback.tribeid}/plugins/${data.callback.plugins}/Model.js` );
const Checkjson = Plug[ data.callback.function ]( header.xworkon, data );
if( Checkjson.status == 200 ) {
data = Checkjson.data.data;
} else {
return check;
}
};
fs.outputJsonSync( `${config.tribes}/${header.xworkon}/${data.object}/${data.uuid}.json`, data );
// if templatemessage exist then we try to send alert email
if( data.tplmessage && data.tplmessage != "" &&
fs.existsSync( `${config.tribes}/${data.tplmessage}` ) ) {
const msg = Messages.buildemail( data.desttribeid, data.tplmessage, data )
if( msg.status == 200 ) {
console.log( 'WARN EMAIL DESACTIVATED CHANGE TO ACTIVATE in Messages.js' )
//Messages.byEmailwithmailjet( data.desttribeid, [ msg.data.msg ] );
}
// we get error message eventualy but email feedback sent is not in real time see notification alert in case of email not sent.
};
//Sendback data with new information
return {
status: 200,
data: {
model: "Messages",
info: [ 'Successregisterobject' ],
msg: data
}
};
}
Messages.notification = ( data, header ) => {
//check if valid notification
if( !req.body.elapseddays ) { req.body.elapseddays = 3 }
let missingkey = "";
[ 'uuid', 'title', 'desc', 'icon', 'elapseddays', 'classicon' ].forEach( k => {
if( !data[ k ] ) missingkey += ` ${k},`
if( k == "classicon" && !( [ 'text-danger', 'text-primary', 'text-success', 'text-warning', 'text-info' ].includes( data[ k ] ) ) ) {
missingkey += ` classicon is not well set ${data[k]}`;
}
} )
if( missingkey != '' ) {
return {
status: 422,
data: {
model: "Messages",
info: [ 'InvalidNote' ],
moreinfo: `invalid notification sent cause missing key ${missingkey}`
}
};
}
if( !fs.existsSync( `${config.tribes}/${header.xworkon}/${data.object}` ) ) {
return {
status: 404,
data: {
model: "Messages",
info: [ 'UnknownObjectfortribeid' ],
moreinfo: `This object ${data.object} does not exist for this tribeid ${data.tribeid}`
}
};
}
//by default store in tmp/notification this is only for sysadmin
// user adminapxtrib
let notdest = `${config.tmp}`;
if( data.app ) {
const destapp = `${config.tribes}/${data.app.split(':')[0]}/spacedev/${data.app.split(':')[1]}`;
if( fs.existsSync( destapp ) ) {
notdest = destapp;
}
}
if( data.plugins ) {
const destplugin = `${config.tribes}/${data.plugins.split(':')[0]}/plugins/${data.plugins.split(':')[1]}`;
if( fs.existsSync( destplugin ) ) {
notdest = destplugin;
}
}
if( data.object ) {
const destobject = `${config.tribes}/${data.tribeid}/${data.object}/`;
if( fs.existsSync( destobject ) ) {
notdest = destobject;
}
}
if( !data.time ) {
data.time = Date.now();
}
fs.outputJsonSync( `${notdest}/Messages/notif_${data.time}.json`, data );
return {
status: 200,
data: {
model: "Messages",
info: [ 'Successregisternotification' ],
notif: data
}
};
}
Messages.request = ( tribeid, uuid, ACCESSRIGHTS, apprequest ) => {
// list notif for each app
// list notif for each tribeid / objects if Owned
// list notif for
// check uuid notification
// Collect all notification and agregate them at relevant level;
Messages.aggregate();
//for test purpose
//const notif = fs.readJsonSync( `${config.tribes}/ndda/spacedev/mesa/src/static/components/notification/data_notiflist_fr.json` );
let notif;
if( !fs.existsSync( `${config.tribes}/${apprequest.tribeid}/spacedev/${apprequest.website}/src/static/components/notification/data_notiflist_${apprequest.lang}.json` ) ) {
// by default we send back this but this highlght an issue
notif = {
"iconnotif": "bell",
"number": 1,
"notifheader": "Your last Messages",
"notiffooter": "Check all Messages",
"actionnotifmanager": "",
"href": "?action=notification.view",
"notifs": [ {
"urldetail": "#",
"classicon": "text-danger",
"icon": "alert",
"title": `File does not exist`,
"desc": ` ${config.tribes}/${apprequest.tribeid}/spacedev/${apprequest.website}/src/static/components/notification/data_notiflist_${apprequest.lang}.json`,
"elapse": "il y a 13mn"
} ]
};
} else {
notif = fs.readJsonSync( `${config.tribes}/${apprequest.tribeid}/spacedev/${apprequest.website}/src/static/components/notification/data_notiflist_${apprequest.lang}.json` );
//clean up example notif
notif.notifs = [];
}
//check notification for plugins of tribeid of the login
glob.sync( `${config.tribes}/${tribeid}/plugins/*/Messages/global.json` )
Object.keys( ACCESSRIGHTS.app )
.forEach( a => {
// check for each app if notification about app
const appnot = `${config.tribes}/${a.split(':')[0]}/spacedev/${a.split(':')[1]}/Messages/global.json`;
if( fs.existsSync( appnot ) ) {
notif.notifs = notif.notifs.concat( fs.readJsonSync( appnot ) );
}
} );
Object.keys( ACCESSRIGHTS.plugin )
.forEach( p => {
// each plugin
if( ACCESSRIGHTS.plugin[ p ].profil == "owner" ) {
const pluginnot = `${config.tribes}/${p.split(':')[0]}/plugins/${p.split(':')[1]}/Messages/global.json`;
if( fs.existsSync( pluginnot ) ) {
notif.notifs = notif.notifs.concat( fs.readJsonSync( pluginnot ) );
}
}
} );
Object.keys( ACCESSRIGHTS.data )
.forEach( c => {
// each tribeid
Object.keys( ACCESSRIGHTS.data[ c ] )
.forEach( o => {
const cliobjnot = `${config.tribes}/${c}/${o}/Messages/global.json`
//check for each tribeid / Object per accessright user
if( fs.existsSync( cliobjnot ) ) {
console.log( `droit sur client ${c} objet ${o} : ${ACCESSRIGHTS.data[ c ][ o ]}` );
//check if intersection between user accessrigth for this object and the notification accessright is not empty @Todo replace true by intersec
console.log( 'WARN no actif filter all notif are shared with any authenticated user' )
const newnotif = fs.readJsonSync( cliobjnot )
.filter( n => { return true } );
notif.notifs = notif.notifs.concat( newnotif );
}
} )
} )
return {
status: 200,
data: {
model: "Messages",
info: [ 'successgetnotification' ],
notif: notif
}
};
}
module.exports = Messages;

226
app/models/Nations.js Executable file
View File

@@ -0,0 +1,226 @@
const bcrypt = require("bcrypt");
const fs = require("fs-extra");
const glob = require("glob");
const jwt = require("jwt-simple");
const axios = require("axios");
const path=require('path');
//const config = require("../tribes/townconf.js");
const Odmdb = require('./Odmdb.js');
// lowercase 1st letter is normal
const towns = require('./Towns.js');
const pagans = require('./Pagans.js');
/*
Blockchain manager
* Manage network directory of nations and towns
* read Blockchain and search,
* submit a transaction (now) or contract (futur) to store from userA.pubkey to userB.pubkey a number of AXESS
* mine to be able to register a block and create AXESS
* manage APIXP rules 20 M APIXP 1AXESS = 1 block validation
* manage contract = action if something appened validate by a proof of work
*/
const Nations = {};
Nations.init = () => {
console.group("init Nations");
};
Nations.updateChains = async (newtown) =>{
/**
* @newtown {object} optional to request a registration in the nationchain network
* if newtown exist then it send a request to update itself else it just refresh from existing town.
* Check public nationchains are up to date from the existing list of towns
* Each object to sync have a /index/config.json with key lastupdate = timestamp last update
* tribes is not synchonized and contain private information
* A town is a network node of the nationchains and allow to synchronize new
*/
const res= {status:400};
const ref2update={}
glob.sync('nationchains/**/index/config.json').forEach(f=>{
const ref=fs.readJsonSync(f)
ref2update[path.basename(ref.schema,'.json')]=ref.lastupdate;
})
console.log(ref2update)
// Get list of town to check n of them have fresh update
const knowntowns =fs.readJsonSync('nationchains/towns/index/towns_townId_all.json');
let promiselistblock=[]
let towidlist=[]
Object.keys(knowntowns).forEach(townid=>{
// identify the town with the highest block to update town
promiselistblock.push(axios.get(`${knowntowns[townid].url}/blocks/index/config.json`));
townidlistblock.push(townid)
});
let selectedtown=""
let blocnum=0
await Promise.all(promiselistblock)
.then(rep=>{
for (let pos=0;pos<townidlist.length;pos++){
if (rep[pos].blocnum > blocnum) {
selectedtown=townidlist[pos]
blocnum=rep[pos].blocnum
}
}
})
.catch(err=>{
console.log(err)
})
let promiselistref=[]
Object.keys(ref2update).forEach(ob=>{
promiselistref.push(axios.get(`${knowntowns[selectedtown].url}/${obj}/index/config.json`));
})
await Promise.all(promiselistref)
.then(rep=>{
for (let pos=0;pos<townidlist.length;pos++){
//si axios lastupdate > local lastupate => recupe _all et regenere tous les objets par ecrasement
}
})
.catch(err=>{
console.log(err)
})
return res
}
Nation.update=(nationsource)=>{
/**
* Update object nation with last update
*/
}
Nations.synchronize = () => {
/*
Run process to communicate with a list of towns to update network and transaction
*/
//update himself then send to other information
if (process.env.NODE_ENV != "prod") {
// Not concerned
return {};
}
const initcurrentinstance = {
fixedIP: "",
lastblocknumber: 0,
firsttimeupdate: 0,
lastimeupdate: 0,
positifupdate: 0,
negatifupdate: 0,
pubkeyadmin: "",
tribeids: [],
logins: [],
knowninstance: [],
};
let currentinstance = initcurrentinstance;
try {
currentinstance = fs.readFileSync(
`${config.tribes}/${config.mayorId}/nationchains/nodes/${config.rootURL}`,
"utf-8"
);
} catch (err) {
console.log("first init");
}
const loginsglob = fs.readJsonSync(`${config.tmp}/loginsglob.json`, "utf-8");
currentinstance.logins = Object.keys(loginsglob);
currentinstance.tribeids = [...new Set(Object.values(loginsglob))];
currentinstance.instanceknown = glob.Sync(
`${config.tribes}/${config.mayorId}/nationchains/nodes/*`
);
//Save it
fs.outputJsonSync(
`${config.tribes}/${config.mayorId}/nationchains/nodes/${config.rootURL}`,
currentinstance
);
// proof of work
// try to find a key based on last block with difficulty
// if find then send to all for update and try to get token
// in any case rerun Nations.synchronize()
currentinstance.instanceknown.forEach((u) => {
if (u != config.rootURL) {
//send currentinstance info and get back state of
axios
.post(`https://${u}/nationchains/push`, currentinstance)
.then((rep) => {
newdata = rep.payload.moreinfo;
//Available update info
fs.readJson(
`${config.tribes}/${config.mayorId}/nationchains/nodes/${u}`,
(err, data) => {
if (err) {
data.negatifupdate += 1;
data.lasttimeupdate = Date.now();
} else {
data.positifupdate += 1;
data.lastimeupdate = Date.now();
data.tribeids = newdata.tribeids;
data.logins = newdata.logins;
data.lastblocknumber = newdata.lastblocknumber;
newdata.knowninstance.forEach((k) => {
if (!data.knowninstance.includes(k)) {
data.knowninstance.push(k);
//init the domain for next update
initcurrentinstance.firsttimeupdate = Date.now();
fs.outputJson(
`${config.tribes}/${config.mayorId}/nationchains/nodes/${k}`,
initcurrentinstance,
"utf-8"
);
}
});
}
//save with info
fs.outputJson(
`${config.tribes}/${config.mayorId}/nationchains/nodes/${u}`,
data
);
}
);
})
.catch((err) => {
//Not available
data.negatifupdate += 1;
data.lasttimeupdate = Date.now();
fs.outputJson(
`${config.tribes}/${config.mayorId}/nationchains/nodes/${u}`,
data
);
});
}
});
};
Nations.create = (conf) => {
/*
@conf from a nationchains/socialworld/setup/townSetup {object, nationName, townName, dns}
@return
*/
const res = {};
if (conf.object=="towns"){
Odmdb.create("nationchains/socialworld/objects","towns",conf);
}
const nation_town = fs.readJsonSync(
"./nationchains/socialworld/objects/towns/searchindex/towns_nationId_townId.json"
);
if (!ObjectKeys(nation_town).includes(conf.nationName)) {
res.status = 404;
res.info = `your nationName ${conf.nationName} does not exist you have to choose an existing one`;
return res;
}
if (nation_town[conf.nationName].includes(conf.townName)) {
res.status = 409;
res.info = `This conf.townName already exist you have to find a unique town name per nation`;
return res;
}
const towndata = {
uuid: conf.townName,
nationid: conf.nationName,
url: `${conf.townName}.${conf.nationName}.${conf.dns}`,
status: (conf.dns=="unchain")? "unchain" : "tochain",
};
const metatown=fs.readJsonSync('./nationchains/socialworld/metaobject/towns.json');
Odmdb.add(objectpath, towns, metatown,towndata)
fs.outputJsonSync(
`./nationchains/socialworld/objects/towns/${townName}.json`,
towndata
);
res.status=200
res.info=`${townName} create for ${nationName} nation`;
return res
};
module.exports = Nationchains;

262
app/models/Odmdb.js Normal file
View File

@@ -0,0 +1,262 @@
const glob = require("glob");
const path = require("path");
const fs = require("fs-extra");
const axios = require('axios');
//const config = require( '../tribes/townconf.js' );
const Checkjson = require(`./Checkjson.js`);
/* This manage Objects for indexing and check and act to CRUD
objectpath/objects/schema/objectName.json
/objectNames/searchindes/objectName_valueofkey_uuildlist.json
/objectNames/uuid.json
*/
const Odmdb = {};
/*
Input: metaobject => data mapper of Key: Value
objname + an object {} + action Checkjson => get a valid or not answer
objname + an object {} + action search => apply matching algo to find probalistic object id
objname + action index => update /searcindex of objects concern
*/
Odmdb.setObject=(schemaPath, objectPath, objectName, schema, lgjson, lg)=>{
/*
@schemapath {string} path to create or replace a schema ${schemaPath}/schema/
@objectPath {string} path where object are store
@objectName {string} name of the object
@schema {object} the json schema for this object
@lgjson {object} the json file for a specific language
@lg {string} the 2 letters language
a shema :
schemaPath/schema/objectName.json
/lg/objectName_{lg}.json
an object :
objectPath/objectName/index/config.json ={"schema":"relativpathfile or http"}
/uniqueid.json defining schema
*/
if (!fs.existsSync(schemaPath)){
return {status:404, ref:"Odmdb", info:"pathnamedoesnotexist", moreinfo:{fullpath:schemaPath}}
}
if (!fs.existsSync(objectPath)){
return {status:404, ref:"Odmdb", info:"pathnamedoesnotexist",moreinfo:{fullpath:objectPath}}
}
// store schema file if not empty undefined or {}
if (schema && !(Object.keys(schema).length === 0 && schema.constructor === Object)){
fs.outputJSONSync(`${schemaPath}/schema/${objectName}.json`,schema, {spaces:2})
}
if (lgjson && lg && !(Object.keys(lgjson).length === 0 && lgjson.constructor === Object)){
fs.outputJSONSync(`${schemaPath}/lg/${objectName}_${lg}.json`,lgjson, {spaces:2})
}
//create environnement object with the new schema config
if (!fs.existsSync(`${objectPath}/${objectName}`)){
fs.outputJsonSync(`${objectPath}/${objectName}/index/config.json`,{schema:`${schemaPath}/schema/${objectName}.json`},{spaces:2})
}
return {status:200}
}
Odmdb.schema = (schemaPath, objectName, withschemacheck) => {
// Return schema if exist and objectpath contain objectName { status:200;data:schema}
if (!fs.existsSync(`${schemaPath}/${objectName}`))
return {
status: 404,
info: "|odmdb|schemapathnamedoesnotexist",
moreinfo: `${schemaPath}/${objectName}`,
};
if (!fs.existsSync(`${objectPath}/schema/${objectName}.json`)) {
return {
status: 404,
info: `|odmdb|schemanotfound`,
moreinfo: `file not found ${schemaPath}/schema/${objectName}.json`,
};
}
const schema = fs.readJsonSync(`${schemaPath}/schema/${objectName}.json`);
// check schema apx validity specificities primary unique ans searchindex
if (withschemacheck) {
if (!schema.apxprimarykey) {
// code 422: unprocessable Content
return {
status: 422,
info: "|Odmdb|apxprimarykeynotfound",
moreinfo: `${schemaPath}/schema/${objectName}.json`,
};
} else {
if (
!(
schema.apxsearchindex[schema.apxprimarykey] &&
schema.apxsearchindex[schema.apxprimarykey].list
)
) {
return {
status: 422,
info: "|Odmdb|apxprimaryketnotinsearchindexlist",
moreinfo: `${schemaPath}/schema/${objectName}.json`,
};
}
if (schema.apxuniquekey) {
schema.apxuniquekey.forEach((k) => {
if (
!(
schema.apxsearchindex[k] &&
schema.apxsearchindex[k][schema.apxprimarykey]
)
) {
return {
status: 422,
info: "|Odmdb|apxuniquekeynotinsearchindex",
moreinfo: `${schemaPath}/schema/${objectName}.json`,
};
}
});
}
}
const validschema = Checkjson.schema.validation(schema);
if (validschema.status != 200) return validschema;
}
return {
status: 200,
data: schema,
};
};
Odmdb.Checkjson = (objectPath, objectName, data, withschemacheck) => {
/*
@objectPath path to the folder that contain /objects/objectName/ /lg/objectName_{lg}.json /schema/objectName.json
@objectName name of object
@data data to check based on schema objectName definition
@return status:200 Data is consistent with schema and primarykey does not exist
status:201 Data is consistent with schema and primarykey does already exist
status:other means unconsistent schema:
404: schema does not exist
or unconsitent data and schema from Checkjson.js Checkjson.schema.data
*/
const res = { status: 200 };
//get schema link of object
const schemaPath = fs.readJsonSync(`${objectPath}/${objectName}/index/config.json`)['schema']
if (schemaPath.substring(0,4)=="http"){
// lance requete http pour recuperer le schema
}else{
schema=="!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!"
}
// check schema validity
const schema = Odmdb.schema(objectPath, objectName, withschemacheck);
if (schema.status != 200) return schema;
console.log("SCHEMA for checking:");
console.log(schema.data);
console.log("DATA to check:");
console.log(data);
// withschemacheck at false, if check then it is done at Odmdb.schema
const validate = Checkjson.schema.data(schema.data, data, false);
if (validate.status != 200) {
return validate;
}
if (
schema.data.apxprimarykey &&
data[k] &&
fs.existsSync(`${objectPath}/${objectName}/${data[k]}.json}`)
) {
res.status = 201; // means created => exist an object with this primary key
}
if (schema.data.apxuniquekey) {
schema.data.apxuniquekey.forEach((k) => {
if (
data[k] &&
fs.existsSync(
`${objectPath}/${objectName}/searchindex/${objectName}_${k}_${schema.data.apxprimarykey}.json}`
) &&
fs.readJsonSync(
`${objectPath}/${objectName}/searchindex/${objectName}_${k}_${schema.data.apxprimarykey}.json}`
)[k]
) {
res.status = 201; // means created => exist as primary key
}
});
}
return res;
};
Odmdb.search = (objectPath, objectName, search) => {
/*
@search= {
txt: string,
algo: match | pattern | fuzzy
fieldstring:[list of field],
indexfilter:{index1:[val1,val2 | ] }
}
Return data:[uuids]
example: search exact match hill in townId
heavy search={txt:"hill",algo:"match",fieldstring:"toxnId"}
light search={txt:"hill", algo:"match", indexfilter:{"key":"townId","value":[]}}
light search={txt:"hill", algo:"match", indexfilter:{"key":"nationId","value":"ants"}}
*/
const schema = Odmdb.schema(objectPath, objectName);
if (schema.status != 200) return schema;
};
Odmdb.get = (objectPath, objectName, uuidprimarykeyList, fieldList) => {
/*
@uuidprimarykeyList list of uuid requested
@fieldList key to return for each object
Return objectName {status:200; data:{found:[{primarykey,field}],notfound:[uuid]}
if all primarykey exist then data.notfound does not exist
if all primarykey does not exist data.found does not exist
*/
const res = { status: 200, data: {} };
uuidprimarykeyList.forEach((id) => {
if (fs.existsSync(`${objectPath}/${objectName}/${id}.json`)) {
if (!res.data.found) res.data.found = [];
const objectdata = fs.readJsonSync(
`${objectPath}/${objectName}/${id}.json`
);
if (!fieldList) {
res.data.found.push(objectdata);
} else {
const objinfo = {};
fieldlList.forEach((k) => {
if (objectdata[k]) objinfo[k] = objectdata[k];
});
res.data.found.push(objinfo);
}
} else {
if (!res.data.notfound) res.data.notfound = [];
}
});
return res;
};
Odmdb.create = (objectPath, objectName, data) => {
/*
Create an objects data into objectName
@objectPath path to the folder that contain /objects/objectName/ /objectsInfo/objectName_lg.json /objectsMeta/objectName.json
@objectName name of object
@data data to check based on objectsMeta definition
*/
};
Odmdb.update = (objectPath, objectName, data) => {
/*
Create an objects data into objectName
@objectPath path to the folder that contain /objects/objectName/ /objectsInfo/objectName_lg.json /objectsMeta/objectName.json
@objectName name of object
@data data to check based on objectsMeta definition
*/
};
Odmdb.delete = (objectPath, objectName, data) => {
/*
Create an objects data into objectName
@objectPath path to the folder that contain /objects/objectName/ /objectsInfo/objectName_lg.json /objectsMeta/objectName.json
@objectName name of object
@data data to check based on objectsMeta definition
*/
};
/*console.log("test Odmdb");
console.log(
Odmdb.check(
"/media/phil/usbfarm/apxtrib/nationchains/socialworld/objects",
"nations",
{ nationId: "123", status: "unchain" }
)
);*/
module.exports = Odmdb;

557
app/models/Outputs.js Executable file
View File

@@ -0,0 +1,557 @@
const fs = require( 'fs-extra' );
const path = require( 'path' );
const formidable = require( 'formidable' );
const jsonfile = require( 'jsonfile' );
const mustache = require( 'mustache' );
const moment = require( 'moment' );
const UUID = require( 'uuid' );
const pdf = require( "pdf-creator-node" );
const nodemailer = require( 'nodemailer' );
const smtpTransport = require( 'nodemailer-smtp-transport' );
const axios = require( 'axios' );
const { GoogleSpreadsheet } = require( 'google-spreadsheet' );
const config = require( '../tribes/townconf.js' );
const Checkjson = require( `./Checkjson.js` );
const Outputs = {};
Outputs.ggsheet2json = async ( req, header ) => {
/*
req.ggsource = key name of the ggsheet into clientconf
req.sheets = [list of sheet names to import]
into /ggsheets/ ggsource.json = {resultfolder,productIdSpreadsheet,
googleDriveCredentials}
*/
if( !fs.existsSync( `${config.tribes}/${header.xworkon}/${req.ggsource}` ) ) {
// Misconfiguration
return {
status: 404,
payload: {
data: {},
info: [ 'Misconfiguration' ],
moreinfo: `${header.xworkon}/${req.ggsource} missing file check gitlabdocumentation`,
model: 'Outputs'
}
};
}
const confgg = fs.readJsonSync( `${config.tribes}/${header.xworkon}/${req.ggsource}`, 'utf-8' )
//const ggconnect = clientconf.ggsheet[ req.ggsource ]
//googleDriveCredentials;
//console.log( ggconnect )
doc = new GoogleSpreadsheet( confgg.productIdSpreadsheet );
await doc.useServiceAccountAuth( confgg.googleDriveCredentials );
await doc.loadInfo();
let result = [];
let invalidfor = "";
//console.log( "sheets", req.sheets );
for( const sheetName of req.sheets ) {
console.log( 'loading: ', sheetName );
if( !doc.sheetsByTitle[ sheetName ] ) {
invalidfor += " " + sheetName;
} else {
sheet = doc.sheetsByTitle[ sheetName ]
await sheet.loadHeaderRow();
const records = await sheet.getRows( { offset: 1 } )
records.forEach( record => {
let offer = {}
for( let index = 0; index < record._sheet.headerValues.length; index++ ) {
offer[ record._sheet.headerValues[ index ] ] = record[ record._sheet.headerValues[ index ] ];
}
result.push( offer );
} );
}
}
const idfile = UUID.v4();
fs.outputJsonSync( `${config.tribes}/${header.xworkon}/${confgg.resultfolder}/${idfile}.json`, result, 'utf8' );
if( invalidfor == "" ) {
return {
status: 200,
payload: {
data: `${confgg.resultfolder}/${idfile}.json`,
info: [ 'Successfull' ],
model: 'Outputs'
}
}
} else {
return {
status: 207,
payload: {
data: `${confgg.resultfolder}/${idfile}.json`,
info: [ 'PartialySuccessfull' ],
moreinfo: `Following sheetNames does not exist :${invalidfor}`,
model: 'Outputs'
}
}
};
};
// REVOIR TOUT CE QU'IL Y A EN DESSOUS et ré-écrire avec la logique de ggsheet2json phil 25/10/2021
///////////////////////////////////////////////////
const sleep = ( milliseconds = 500 ) => new Promise( resolve => setTimeout( resolve, milliseconds ) );
/*
Process any data to a specific output:
emailer => generate a finale text file to send
csv => export json file to csv data
pdf => generate a customized document
*/
Outputs.envoiemail = async ( msg, nowait, nbfois ) => {
// console.log('{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}');
// console.log('msg to send', msg);
console.log( 'nbfois', nbfois );
let transporter = nodemailer.createTransport( msg.smtp );
if( !nowait ) {
console.log( 'attente 1er msg avant d envoyer les autres' );
const transport = await transporter.verify();
console.log( 'transport', transport );
if( transport.error ) {
console.log( 'Probleme de smtp', error );
return {
status: 500,
payload: {
info: ''
}
};
} else {
let rep = await transporter.sendMail( msg );
console.log( 'rep sendMail', rep );
if( rep.accepted && rep.accepted.length > 0 && rep.rejected.length == 0 ) {
fs.appendFileSync( `${config.tribes}/${msg.headers['x-client-nd-id']}/logs/${msg.headers['x-campaign-id']}_success.txt`, moment( new Date() )
.format( 'YYYYMMDD HH:mm:ss' ) + ' - Success after waiting 1st email to send ' + msg.to + '\n', 'utf-8' );
return {
status: '200',
payload: {
info: [ 'send1stemailok' ],
model: 'Outputs'
}
};
}
// status à tester si necessaire de detecter une erreur
// if (rep.rejectedErrors) {
fs.appendFileSync( `${config.tribes}/${msg.headers['x-client-nd-id']}/logs/${msg.headers['x-campaign-id']}_error.txt`, moment( new Date() )
.format( 'YYYYMMDD HH:mm:ss' ) + ' - err after waiting result\n ' + msg.to, 'utf-8' );
return {
status: '500',
payload: {
info: [ 'rejectedError' ],
model: 'Outputs'
}
};
}
} else {
transporter.sendMail( msg, async ( err, info ) => {
if( err ) {
if( nbfois < 4 ) {
console.log( 'nouvelle tentative ', nbfois );
await sleep( 600000 ); // attends 60sec pour faire une niéme tentative
Outputs.envoiemail( msg, true, nbfois + 1 );
} else {
// logerror in file
console.log( 'err', err )
fs.appendFileSync( `${config.tribes}/${msg.headers['x-client-nd-id']}/logs/${msg.headers['x-campaign-id']}_error.txt`, moment( new Date() )
.format( 'YYYYMMDD HH:mm:ss' ) + ' - err after 4 tries to ' + info.rejected.join( ',' ) + '\n', 'utf-8' );
// console.log('msg.to not well sent', msg.to);
}
} else {
console.log( 'info', info )
// console.log('msg.to well sent', msg.to);
fs.appendFileSync( `${config.tribes}/${msg.headers['x-client-nd-id']}/logs/${msg.headers['x-campaign-id']}_success.txt`, moment( new Date() )
.format( 'YYYYMMDD HH:mm:ss' ) + ' - Success after ' + nbfois + ' tries to ' + info.accepted.join( ',' ) + '\n', 'utf-8' );
}
} );
}
// return something to not wait the rest of email
return {
status: '200',
payload: {
info: [ 'send1stemailok' ],
model: 'Outputs'
}
};
};
Outputs.generemsg = async ( msg, header ) => {
/*
wait msg sent and return result sent
*/
// Recupere les parametre smtp du domainName à utiliser
console.log( 'pass Outputs.generemsg' )
try {
const confclientexpediteur = fs.readJsonSync( `${config.tribes}/${msg.tribeidperso.tribeidexpediteur}/clientconf.json` );
//console.log('expediteur', confclientexpediteur);
msg.smtp = confclientexpediteur.smtp;
/* const confclient = fs.readJsonSync(
`${config.tribes}/${msg.tribeidperso.tribeid}/clientconf.json`
);*/
} catch ( err ) {
console.log( 'la conf smtp du client n\'est pas definie' );
return {
status: 404,
payload: {
info: [ 'smtpnotdefined' ],
model: 'Outputs'
}
};
}
console.log( msg );
if( !msg.template.sendascontent && msg.template.htmlfile ) {
try {
msg.template.html = fs.readFileSync( config.sharedData + '/' + msg.template.htmlfile + '/contentinline.mustache', 'utf-8' );
msg.template.text = fs.readFileSync( config.sharedData + '/' + msg.template.htmlfile + '/contenttxt.mustache', 'utf-8' );
} catch ( err ) {
console.log( 'WARNING, html file template missing ' + config.sharedData + '/' + msg.template.htmlfile );
console.log( err );
return {
status: 404,
payload: {
info: [ 'fileUnknown' ],
model: 'UploadFiles',
moreinfo: 'Template unavailable, check ' + config.sharedData + '/' + msg.template.htmlfile + '/contentinline.mustache and contenttxt.mustache'
}
};
}
}
if( msg.template.html.length == 0 ) {
console.log( 'template.html est vide' )
return {
status: 404,
payload: {
info: [ 'ERRnotemplate' ],
model: 'Outputs',
moreinfo: 'No template email check '
}
};
}
// mustache any data into
// console.log(msg);
const msg2send = {};
msg2send.smtp = msg.smtp;
msg2send.from = msg.tribeidperso.from;
if( msg.tribeidperso.cc ) msg2send.cc = msg.tribeidperso.cc;
if( msg.tribeidperso.bcc ) msg2send.bcc = msg.tribeidperso.bcc;
if( msg.tribeidperso.replyTo ) msg2send.replyTo = msg.tribeidperso.replyTo;
msg2send.headers = {
'x-campaign-id': msg.tribeidperso.messageId,
'x-client-nd-id': msg.tribeidperso.tribeid,
'x-template-nd-id': msg.tribeidperso.templateId
};
// we get in datacust= {tribeidperso: with clientconf,destperso: personnalise data to send for email}
// console.log(msg);
console.log( 'nb de message to send:', msg.destperso.length );
//console.log(msg.destperso);
//msg.destperso.forEach(async (perso, pos) => {
let pos;
let pass1ermsg = false;
for( pos = 0; pos < msg.destperso.length; pos++ ) {
const datacust = {
tribeidperso: msg.tribeidperso,
destperso: msg.destperso[ pos ]
};
// Evaluation of each field if mustache exist
const datacusteval = {};
Object.keys( datacust.tribeidperso )
.forEach( k => {
if( typeof datacust.tribeidperso[ k ] === 'string' || datacust.tribeidperso[ k ] instanceof String ) {
datacusteval[ k ] = mustache.render( datacust.tribeidperso[ k ], datacust )
} else {
datacusteval[ k ] = datacust.tribeidperso[ k ];
}
} )
Object.keys( datacust.destperso )
.forEach( k => {
if( typeof datacust.destperso[ k ] === 'string' || datacust.destperso[ k ] instanceof String ) {
datacusteval[ k ] = mustache.render( datacust.destperso[ k ], datacust )
} else {
datacusteval[ k ] = datacust.destperso[ k ];
}
} )
msg2send.to = msg.destperso[ pos ].email;
console.log( 'msg2send.to ' + msg2send.to + ' pos:' + pos );
// console.log('avec datacusteval ', datacusteval)
msg2send.subject = mustache.render( msg.template.subject, datacusteval );
msg2send.text = mustache.render( msg.template.text, datacusteval );
msg2send.html = mustache.render( msg.template.html, datacusteval );
let nowait = true;
if( config.emailerurl == 'http://devapia.maildigit.fr:3015' ) {
fs.writeFileSync( 'devdata/tmp/test.html', msg2send.html, 'utf-8' );
console.log( 'lancement email sur dev, pour controler le mail générer voir ds ./config.js il faut changer config.emailerurl avec https://mail.maildigit.fr pour envoyer le mail ' )
return {
status: 200,
payload: {
info: [ 'msgsentok' ],
model: 'Outputs',
moreinfo: "parametrer sur emailer de dev et pas de production"
}
}
}
if( pos == 0 ) {
nowait = false;
/* we are waiting the first email was sent ok then we send all other
check NEEDDATA/OVH/workspace/emailerovh to send emailer with nodemailer and nodemailer-smtp-transport
*/
// console.log('envoie msg', msg);
//console.log(msg2send);
const ret = await Outputs.envoiemail( msg2send, nowait, 0 );
console.log( 'ret 1er msg', ret );
if( ret.status == 200 ) {
pass1ermsg = true;
};
} else if( pass1ermsg ) {
console.log( '###############################################' )
console.log( "envoie msg numero: " + pos + " email: " + msg2send.to )
//console.log(msg2send)
Outputs.envoiemail( msg2send, nowait, 0 );
/*Outputs.envoiemail(msg2send, nowait, 0).then(rep => {
console.log("envoie email" + pos)
}).catch(err => {
console.log(err);
});*/
};
};
if( pass1ermsg ) {
return {
status: 200,
payload: {
info: [ 'msgsentok' ],
model: 'Outputs'
}
};
} else {
return {
status: 500,
payload: {
info: [ 'msgsentko' ],
model: 'Ouputs',
moreinfo: "1er msg non envoyé car erreur"
}
}
}
};
Outputs.sendMailcampain = async ( msg, headersmsg ) => {
/*
Permet de lancer une campagne de mail personnalisé en mode web service
avec axios config.emailerurl https://mail qui peut être sur un autre serveur que celui en cours
Attention headermsg doit être retraduit avec les champs envoyé par un navigateur
Si le serveur en cours appelle cette fonction les champs du header doivent changer
voir config.js node_env .exposedHeaders
Pour un exemple de msg voir u exemple type de message envoyé dans un tribeid/domain/clientconf.json
avec l'envoi d'email
*/
//console.log(msg)
// On ajoute le contenu du template directement dans la demande
if( msg.template.sendascontent && msg.template.htmlfile ) {
try {
console.log( 'test', msg.template.sendascontent )
msg.template.html = fs.readFileSync( config.sharedData + '/' + msg.template.htmlfile + '/contentinline.mustache', 'utf-8' );
msg.template.text = fs.readFileSync( config.sharedData + '/' + msg.template.htmlfile + '/contenttxt.mustache', 'utf-8' );
} catch ( err ) {
console.log( 'WARNING, html file template missing ' + config.sharedData + '/' + msg.template.htmlfile );
//console.log(err);
return {
status: 404,
payload: {
info: [ 'fileUnknown' ],
model: 'UploadFiles',
moreinfo: 'Template unavailable, check ' + config.sharedData + '/' + msg.template.htmlfile + '/contentinline.mustache and contenttxt.mustache'
}
};
}
delete msg.template.htmlfile;
if( msg.template.html.length == 0 ) {
return {
status: 404,
payload: {
info: [ 'ERRnotemplate' ],
model: 'Outputs',
moreinfo: 'No template email check '
}
};
}
}
console.log( 'envoie sur', `${config.emailerurl}/outputs/msg` )
//console.log(msg)
// on check si les key de headermsg sont des key traduite via exposedHeaders
// (cas ou c'est l'application qui envoie un email)
if( headersmsg.xtribeid ) {
Object.keys( config.exposedHeaders )
.forEach( h => {
headersmsg[ h ] = headersmsg[ config.exposedHeaders[ h ] ];
delete headersmsg[ config.exposedHeaders[ h ] ];
} );
}
// on ajoute le code pour la signature
headersmsg.hashbody = msg.code;
console.log( 'header after traduction: ', headersmsg )
try {
const resCamp = await axios.post( `${config.emailerurl}/outputs/msg`, msg, {
headers: headersmsg
} );
//console.log('Etat:', resCamp);
console.log( 'Tried to send 1st email of the campain ' + msg.destperso[ 0 ].email );
// it just check the 1st email in destperso to return an answer if 1st is ok then all other are send in queue
if( resCamp ) {
return resCamp;
} else {
return { status: 200, payload: { info: [ 'CampainSent' ], model: 'Outputs' } };
}
} catch ( err ) {
// aios error handler
return { status: 401, payload: { info: [ 'erreuraxios' ], model: 'Outputs', moreinfo: err.message } }
}
};
Outputs.get = function ( filename, header ) {
// check file exist
const file = `${config.tribes}/${header.xworkon}/${filename}`;
// console.log('fichier demande ', file);
if( !fs.existsSync( file ) ) {
// console.log('le fichier demande n existe pas ', file);
return {
status: 404,
payload: {
info: [ 'fileUnknown' ],
model: 'UploadFiles'
}
};
} else {
console.log( 'envoie le fichier ', file );
return {
status: 200,
payload: {
info: [ 'fileknown' ],
model: 'UploadFiles',
file: file
}
};
}
};
Outputs.addjson = function ( data, header ) {
/*
Le header = {X-WorkOn:"",destinationfile:"", filename:""}
Le body = {jsonp:{},callback:function to launch after download,'code':'mot cle pour verifier que le fichier est à garder'}
*/
// console.log(req.body.jsonp);
try {
fs.outputJsonSync( header.destinationfile + '/' + header.filename, data.jsonp );
if( data.callback ) {
const execCB = require( `${__base}/models/tribeid/${header.xworkon
}` );
execCB[ data.callback ]();
}
return {
status: 200,
payload: {
info: [ 'wellUpload' ],
model: 'UploadFiles',
render: {
destination: header.destinationfile,
filename: header.filename
}
}
};
} catch ( err ) {
console.log( 'Impossible de sauvegarder le fichier, A COMPRENDRE', err );
return {
status: 503,
payload: {
info: [ 'savingError' ],
model: 'UploadFiles'
}
};
}
};
Outputs.add = function ( req, header ) {
const form = new formidable.IncomingForm();
console.log( 'req.headers', req.headers );
console.log( 'req.params', req.params );
console.log( 'req.query', req.query );
console.log( 'req.body', req.body );
let destinationfile = `${config.tribes}/${header.xworkon}/${header.destinationfile
}`;
form.parse( req, function ( err, fields, files ) {
console.log( 'files', files.file.path );
console.log( 'fields', fields );
const oldpath = files.file.path;
destinationfile += '/' + files.file.name;
console.log( 'oldpath', oldpath );
console.log( 'destinationfile', destinationfile );
fs.copyFile( oldpath, destinationfile, function ( err ) {
if( err ) {
console.log( err );
return {
status: 500,
payload: {
info: [ 'savingError' ],
model: 'UploadFiles'
}
};
} else {
console.log( 'passe' );
fs.unlink( oldpath );
return {
status: 200,
payload: {
info: [ 'wellUpload' ],
model: 'UploadFiles',
render: {
destination: destinationfile
}
}
};
}
} );
} );
};
Outputs.generepdf = ( req, header ) => {
return new Promise( ( resolve, reject ) => {
let options = {
format: "A4",
orientation: "portrait",
border: "10mm",
footer: {
height: "20mm",
contents: {
default: '<span style="color: #444;">{{page}}</span>/<span>{{pages}}</span>', // html pagination if edit needed
}
}
};
let document = {
html: req.html,
data: {
data: req.data,
},
path: `${config.tribes}/${header.xtribe}/outputs/${UUID.v4()}.pdf`,
type: "",
};
pdf // generate pdf
.create( document, options )
.then( ( res ) => {
resolve( {
status: 200,
payload: {
info: [ 'wellPdfGenerated' ],
model: 'Outputs',
data: {
path: document.path,
filename: req.data.filename
},
render: {
filename: req.data.filename
}
}
} );
} )
.catch( ( err ) => {
reject( {
status: 500,
payload: {
info: [ 'pdfGenerationError' ],
model: 'Outputs',
error: err
}
} );
} );
} );
};
module.exports = Outputs;

492
app/models/OutputsDev.js Executable file
View File

@@ -0,0 +1,492 @@
const fs = require( 'fs-extra' );
const path = require( 'path' );
const formidable = require( 'formidable' );
const jsonfile = require( 'jsonfile' );
const mustache = require( 'mustache' );
const moment = require( 'moment' );
const UUID = require( 'uuid' );
const pdf = require( "pdf-creator-node" );
const nodemailer = require( 'nodemailer' );
const smtpTransport = require( 'nodemailer-smtp-transport' );
const axios = require( 'axios' );
const { GoogleSpreadsheet } = require( 'google-spreadsheet' );
const async = require( 'async' );
const config = require( '../tribes/townconf.js' );
const Checkjson = require( `${config.tribes}/${config.mayorId}/www/cdn/public/js/Checkjson` );
const Outputs = {};
const sleep = ( milliseconds = 500 ) => new Promise( resolve => setTimeout( resolve, milliseconds ) );
/*
Process any data to a specific output:
emailer => generate a finale text file to send
csv => export json file to csv data
pdf => generate a customized document
*/
Outputs.envoiemail = ( msg, next ) => {
let transporter = nodemailer.createTransport( msg.smtp );
transporter.sendMail( msg, async ( err, info ) => {
if( err ) {
next( err );
} else {
console.log( 'info', info )
fs.appendFileSync( `${config.tribes}/${msg.headers['x-client-nd-id']}/logs/${msg.headers['x-campaign-id']}_success.txt`, moment( new Date() )
.format( 'YYYYMMDD HH:mm:ss' ) + ' - Success after ' + '0' + ' tries to ' + info.accepted.join( ',' ) + '\n', 'utf-8' );
next( null );
}
} );
};
Outputs.setupmail = ( msg, msg2send, index ) => {
const datacust = {
tribeidperso: msg.tribeidperso,
destperso: index
};
// Evaluation of each field if mustache exist
const datacusteval = {};
Object.keys( datacust.tribeidperso )
.forEach( k => {
if( typeof datacust.tribeidperso[ k ] === 'string' || datacust.tribeidperso[ k ] instanceof String ) {
datacusteval[ k ] = mustache.render( datacust.tribeidperso[ k ], datacust )
} else {
datacusteval[ k ] = datacust.tribeidperso[ k ];
}
} )
Object.keys( datacust.destperso )
.forEach( k => {
if( typeof datacust.destperso[ k ] === 'string' || datacust.destperso[ k ] instanceof String ) {
datacusteval[ k ] = mustache.render( datacust.destperso[ k ], datacust )
} else {
datacusteval[ k ] = datacust.destperso[ k ];
}
} )
msg2send.to = index.email;
console.log( 'msg2send.to ' + msg2send.to );
msg2send.subject = mustache.render( msg.template.subject, datacusteval );
msg2send.text = mustache.render( msg.template.text, datacusteval );
msg2send.html = mustache.render( msg.template.html, datacusteval );
// TODO need to move that in generemsg
// if (config.emailerurl == 'http://devapia.maildigit.fr:3015') {
// fs.writeFileSync('devdata/tmp/test.html', msg2send.html, 'utf-8');
// console.log('lancement email sur dev, pour controler le mail générer voir ds ./config.js il faut changer config.emailerurl avec https://mail.maildigit.fr pour envoyer le mail ')
// return {
// status: 200,
// payload: {
// info: ['msgsentok'],
// model: 'Outputs',
// moreinfo: "parametrer sur emailer de dev et pas de production"
// }
// }
// }
return msg2send;
}
Outputs.envoiefirstmail = async ( msg ) => {
console.log( '###############################################' )
console.log( "envoie first msg email: " + msg.to )
let transporter = nodemailer.createTransport( msg.smtp );
console.log( 'attente 1er msg avant d envoyer les autres' );
const transport = await transporter.verify();
console.log( 'transport', transport );
if( transport.error ) {
console.log( 'Probleme de smtp', error );
return {
status: 500,
payload: {
info: ''
}
};
} else {
let rep = await transporter.sendMail( msg );
console.log( 'rep sendMail', rep );
if( rep.accepted && rep.accepted.length > 0 && rep.rejected.length == 0 ) {
fs.appendFileSync( `${config.tribes}/${msg.headers['x-client-nd-id']}/logs/${msg.headers['x-campaign-id']}_success.txt`, moment( new Date() )
.format( 'YYYYMMDD HH:mm:ss' ) + ' - Success after waiting 1st email to send ' + msg.to + '\n', 'utf-8' );
return {
status: '200',
payload: {
info: [ 'send1stemailok' ],
model: 'Outputs'
}
};
}
// status à tester si necessaire de detecter une erreur
// if (rep.rejectedErrors) {
fs.appendFileSync( `${config.tribes}/${msg.headers['x-client-nd-id']}/logs/${msg.headers['x-campaign-id']}_error.txt`, moment( new Date() )
.format( 'YYYYMMDD HH:mm:ss' ) + ' - err after waiting result\n ' + msg.to, 'utf-8' );
return {
status: '500',
payload: {
info: [ 'rejectedError' ],
model: 'Outputs'
}
};
}
}
Outputs.envoiemails = ( msg, msg2send, targets, iteration, resolve, reject ) => {
let newtargets = [];
async.each( targets, ( index, callback ) => { // iterate asynchronously in msg.destperso (targets)
let finalmsg = Outputs.setupmail( msg, msg2send, index );
console.log( '###############################################' )
console.log( "envoie msg email: " + finalmsg.to )
Outputs.envoiemail( finalmsg, ( err ) => {
if( err ) { // intentionally don't pass this error in callback, we dont want to break loop
newtargets.push( index ); // stock all errored mails for next try
}
} );
callback();
}, function ( err ) { // part executed only once all iterations are done
if( newtargets.length > 0 && iteration < 4 ) {
setTimeout( () => {
Outputs.envoiemails( msg, msg2send, newtargets, iteration + 1, resolve, reject );
}, 600000 );
} else resolve( newtargets ); // return not resolved errors after 4 trys for log
} );
}
Outputs.generemsg = async ( msg, header ) => {
/*
wait msg sent and return result sent
*/
// Recupere les parametre smtp du domainName à utiliser
console.log( 'pass Outputs.generemsg' )
try {
const confclientexpediteur = fs.readJsonSync( `${config.tribes}/${msg.tribeidperso.tribeidexpediteur}/clientconf.json` );
msg.smtp = confclientexpediteur.smtp;
} catch ( err ) {
console.log( 'la conf smtp du client n\'est pas definie' );
return {
status: 404,
payload: {
info: [ 'smtpnotdefined' ],
model: 'Outputs'
}
};
}
console.log( msg );
if( !msg.template.sendascontent && msg.template.htmlfile ) {
try {
msg.template.html = fs.readFileSync( config.sharedData + '/' + msg.template.htmlfile + '/contentinline.mustache', 'utf-8' );
msg.template.text = fs.readFileSync( config.sharedData + '/' + msg.template.htmlfile + '/contenttxt.mustache', 'utf-8' );
} catch ( err ) {
console.log( 'WARNING, html file template missing ' + config.sharedData + '/' + msg.template.htmlfile );
console.log( err );
return {
status: 404,
payload: {
info: [ 'fileUnknown' ],
model: 'UploadFiles',
moreinfo: 'Template unavailable, check ' + config.sharedData + '/' + msg.template.htmlfile + '/contentinline.mustache and contenttxt.mustache'
}
};
}
}
if( msg.template.html.length == 0 ) {
console.log( 'template.html est vide' )
return {
status: 404,
payload: {
info: [ 'ERRnotemplate' ],
model: 'Outputs',
moreinfo: 'No template email check '
}
};
}
// mustache any data into
const msg2send = {};
msg2send.smtp = msg.smtp;
msg2send.from = msg.tribeidperso.from;
if( msg.tribeidperso.cc ) msg2send.cc = msg.tribeidperso.cc;
if( msg.tribeidperso.bcc ) msg2send.bcc = msg.tribeidperso.bcc;
if( msg.tribeidperso.replyTo ) msg2send.replyTo = msg.tribeidperso.replyTo;
msg2send.headers = {
'x-campaign-id': msg.tribeidperso.messageId,
'x-client-nd-id': msg.tribeidperso.tribeid,
'x-template-nd-id': msg.tribeidperso.templateId
};
console.log( 'nb de message to send:', msg.destperso.length );
// send first mail
const ret = await Outputs.envoiefirstmail( Outputs.setupmail( msg, msg2send, msg.destperso[ 0 ] ) );
console.log( 'ret 1er msg', ret );
if( ret.status == 200 ) {
pass1ermsg = true;
msg.destperso.shift();
};
console.log( 'attente 1er msg avant d envoyer les autres' );
// send other mails
new Promise( ( resolve, reject ) => { // useless promise used for recursive calls in Outputs.envoiemails
Outputs.envoiemails( msg, msg2send, msg.destperso, 0, resolve, reject );
} )
.then( ( result ) => {
async.eachSeries( result, ( index, callback ) => { // variante of each but runs only a single operation at a time because of fs.appendFile
fs.appendFile( `${config.tribes}/${msg.headers['x-client-nd-id']}/logs/${msg.headers['x-campaign-id']}_error.txt`, moment( new Date() )
.format( 'YYYYMMDD HH:mm:ss' ) + ' - err after 4 tries to ' + info.rejected.join( ',' ) + '\n', 'utf-8', ( err ) => {
callback( err );
}, ( err ) => {
if( err ) console.log( err );
} );
console.log( 'msg.to not well sent', msg.to );
} );
} )
if( pass1ermsg ) {
return {
status: 200,
payload: {
info: [ 'msgsentok' ],
model: 'Outputs'
}
};
} else {
return {
status: 500,
payload: {
info: [ 'msgsentko' ],
model: 'Ouputs',
moreinfo: "1er msg non envoyé car erreur"
}
}
}
};
Outputs.sendMailcampain = async ( msg, headersmsg ) => {
/*
Permet de lancer une campagne de mail personnalisé en mode web service
avec axios config.emailerurl https://mail qui peut être sur un autre serveur que celui en cours
Attention headermsg doit être retraduit avec les champs envoyé par un navigateur
Si le serveur en cours appelle cette fonction les champs du header doivent changer
voir config.js node_env .exposedHeaders
Pour un exemple de msg voir u exemple type de message envoyé dans un tribeid/domain/clientconf.json
avec l'envoi d'email
*/
//console.log(msg)
// On ajoute le contenu du template directement dans la demande
if( msg.template.sendascontent && msg.template.htmlfile ) {
try {
console.log( 'test', msg.template.sendascontent )
msg.template.html = fs.readFileSync( config.sharedData + '/' + msg.template.htmlfile + '/contentinline.mustache', 'utf-8' );
msg.template.text = fs.readFileSync( config.sharedData + '/' + msg.template.htmlfile + '/contenttxt.mustache', 'utf-8' );
} catch ( err ) {
console.log( 'WARNING, html file template missing ' + config.sharedData + '/' + msg.template.htmlfile );
//console.log(err);
return {
status: 404,
payload: {
info: [ 'fileUnknown' ],
model: 'UploadFiles',
moreinfo: 'Template unavailable, check ' + config.sharedData + '/' + msg.template.htmlfile + '/contentinline.mustache and contenttxt.mustache'
}
};
}
delete msg.template.htmlfile;
if( msg.template.html.length == 0 ) {
return {
status: 404,
payload: {
info: [ 'ERRnotemplate' ],
model: 'Outputs',
moreinfo: 'No template email check '
}
};
}
}
console.log( 'envoie sur', `${config.emailerurl}/outputs/msg` )
//console.log(msg)
// on check si les key de headermsg sont des key traduite via exposedHeaders
// (cas ou c'est l'application qui envoie un email)
if( headersmsg.xtribeid ) {
Object.keys( config.exposedHeaders )
.forEach( h => {
headersmsg[ h ] = headersmsg[ config.exposedHeaders[ h ] ];
delete headersmsg[ config.exposedHeaders[ h ] ];
} );
}
// on ajoute le code pour la signature
headersmsg.hashbody = msg.code;
console.log( 'header after traduction: ', headersmsg )
try {
const resCamp = await axios.post( `${config.emailerurl}/outputs/msg`, msg, {
headers: headersmsg
} );
//console.log('Etat:', resCamp);
console.log( 'Tried to send 1st email of the campain ' + msg.destperso[ 0 ].email );
// it just check the 1st email in destperso to return an answer if 1st is ok then all other are send in queue
if( resCamp ) {
return resCamp;
} else {
return { status: 200, payload: { info: [ 'CampainSent' ], model: 'Outputs' } };
}
} catch ( err ) {
// aios error handler
return { status: 401, payload: { info: [ 'erreuraxios' ], model: 'Outputs', moreinfo: err.message } }
}
};
Outputs.get = function ( filename, header ) {
// check file exist
const file = `${config.tribes}/${header.xworkon}/${filename}`;
// console.log('fichier demande ', file);
if( !fs.existsSync( file ) ) {
// console.log('le fichier demande n existe pas ', file);
return {
status: 404,
payload: {
info: [ 'fileUnknown' ],
model: 'UploadFiles'
}
};
} else {
console.log( 'envoie le fichier ', file );
return {
status: 200,
payload: {
info: [ 'fileknown' ],
model: 'UploadFiles',
file: file
}
};
}
};
Outputs.addjson = function ( data, header ) {
/*
Le header = {X-WorkOn:"",destinationfile:"", filename:""}
Le body = {jsonp:{},callback:function to launch after download,'code':'mot cle pour verifier que le fichier est à garder'}
*/
// console.log(req.body.jsonp);
try {
fs.outputJsonSync( header.destinationfile + '/' + header.filename, data.jsonp );
if( data.callback ) {
const execCB = require( `${__base}/models/tribeid/${header.xworkon
}` );
execCB[ data.callback ]();
}
return {
status: 200,
payload: {
info: [ 'wellUpload' ],
model: 'UploadFiles',
render: {
destination: header.destinationfile,
filename: header.filename
}
}
};
} catch ( err ) {
console.log( 'Impossible de sauvegarder le fichier, A COMPRENDRE', err );
return {
status: 503,
payload: {
info: [ 'savingError' ],
model: 'UploadFiles'
}
};
}
};
Outputs.add = function ( req, header ) {
const form = new formidable.IncomingForm();
console.log( 'req.headers', req.headers );
console.log( 'req.params', req.params );
console.log( 'req.query', req.query );
console.log( 'req.body', req.body );
let destinationfile = `${config.tribes}/${header.xworkon}/${header.destinationfile
}`;
form.parse( req, function ( err, fields, files ) {
console.log( 'files', files.file.path );
console.log( 'fields', fields );
const oldpath = files.file.path;
destinationfile += '/' + files.file.name;
console.log( 'oldpath', oldpath );
console.log( 'destinationfile', destinationfile );
fs.copyFile( oldpath, destinationfile, function ( err ) {
if( err ) {
console.log( err );
return {
status: 500,
payload: {
info: [ 'savingError' ],
model: 'UploadFiles'
}
};
} else {
console.log( 'passe' );
fs.unlink( oldpath );
return {
status: 200,
payload: {
info: [ 'wellUpload' ],
model: 'UploadFiles',
render: {
destination: destinationfile
}
}
};
}
} );
} );
};
Outputs.sheettojson = async ( req, header ) => {
doc = new GoogleSpreadsheet( req.productIdSpreadsheet );
await doc.useServiceAccountAuth( req.googleDriveCredentials );
await doc.loadInfo();
let result = [];
for( const sheetName of req.sheets ) {
console.log( 'loading: ', sheetName );
sheet = doc.sheetsByTitle[ sheetName ]
await sheet.loadHeaderRow();
const records = await sheet.getRows( { offset: 1 } )
records.forEach( record => {
let offer = {}
for( let index = 0; index < record._sheet.headerValues.length; index++ ) {
offer[ record._sheet.headerValues[ index ] ] = record[ record._sheet.headerValues[ index ] ];
}
result.push( offer );
} );
}
return result;
};
Outputs.generepdf = ( req, header ) => {
return new Promise( ( resolve, reject ) => {
let options = {
format: "A4",
orientation: "portrait",
border: "10mm",
footer: {
height: "20mm",
contents: {
default: '<span style="color: #444;">{{page}}</span>/<span>{{pages}}</span>', // html pagination if edit needed
}
}
};
let document = {
html: req.html,
data: {
data: req.data,
},
path: `${config.tribes}/${header.xtribeid}/outputs/${UUID.v4()}.pdf`,
type: "",
};
pdf // generate pdf
.create( document, options )
.then( ( res ) => {
resolve( {
status: 200,
payload: {
info: [ 'wellPdfGenerated' ],
model: 'Outputs',
data: {
path: document.path,
filename: req.data.filename
},
render: {
filename: req.data.filename
}
}
} );
} )
.catch( ( err ) => {
reject( {
status: 500,
payload: {
info: [ 'pdfGenerationError' ],
model: 'Outputs',
error: err
}
} );
} );
} );
};
module.exports = Outputs;

773
app/models/Pagans.js Executable file
View File

@@ -0,0 +1,773 @@
const bcrypt = require( 'bcrypt' );
const fs = require( 'fs-extra' );
const glob = require( 'glob' );
const moment = require( 'moment' );
const jwt = require( 'jwt-simple' );
const UUID = require( 'uuid' );
const Outputs = require( './Outputs.js' );
const config = require( '../tribes/townconf.js' );
const Checkjson = require( `./Checkjson.js`);
/*
Gestion des utilisateurs connecte
/tribes/tribeid/users/
UUID.json
/searchindex/indexTOKEN = {TOKEN:UUID}
to check authentification
indexLOGIN = {LOGIN:UUID}
to check if LOGIN exist
Used for referntial:
To update
Global: datashared/referentials/dataManagement/object/users.json
Local: data/tribe/*${header.workOn}/referentials/dataManagement/object/users.json
To use after a server rerun
data/tribe/*${header.workOn}/referentials/${header.langue}/object/users.json
Faire comme contact charger les index au lancement
et changer la logique de user/id/profil o
On initialise les objets au lancement du serveur
this.uids[u.UUID] = [u.LOGIN, u.EMAIL, u.password, u.profil, u.TOKEN];
this.logins[u.LOGIN] = u.UUID;
this.tokens[u.UUID] = u.TOKEN;
on stocke /domain/tribeid/users/logins.json et /uids.json
on stocke /domain/tribeids.json et tokkens.json
On retourne l'objet {tribeids:[list tribeid], tokens:{UUID:TOKEN}}
*/
const Pagans = {};
Pagans.init = tribeids => {
console.group( 'init Pagans logins, tokens ...' );
// store globaly tokens
const tokens = {};
const emailsglob = {};
const loginsglob = {};
// For each tribeid create series of indexes
//console.log(tribeids);
tribeids.forEach( tribeid => {
// Reset for each domain
const uids = {};
const logins = {};
const emails = {};
//check folder exist
/*if( !fs.existsSync( `${config.tribes}/${tribeid}/users` ) ) {
fs.mkdirSync( `${config.tribes}/${tribeid}/users` )
}
if( !fs.existsSync( `${config.tribes}/${tribeid}/users/searchindex` ) ) {
fs.mkdirSync( `${config.tribes}/${tribeid}/users/searchindex` )
}*/
glob.sync( `${config.tribes}/${tribeid}/users/*.json` )
.forEach( file => {
//console.log( file );
const u = fs.readJsonSync( file, 'utf-8' );
if( !u.TOKEN ) {
u.TOKEN = '';
}
//console.log( u )
uids[ u.UUID ] = [ u.LOGIN, u.EMAIL, u.PASSWORD, u.ACCESSRIGHTS, u.TOKEN ];
logins[ u.LOGIN ] = u.UUID;
loginsglob[ u.LOGIN ] = tribeid;
// On ne charge que les TOKEN valide
let decodedTOKEN = {};
if( u.TOKEN != '' ) {
try {
decodedTOKEN = jwt.decode( u.TOKEN, config.jwtSecret );
//console.log( 'decodeTOKEN', decodedTOKEN )
if( moment( decodedTOKEN.expiration ) > moment() ) {
tokens[ u.UUID ] = { TOKEN: u.TOKEN, ACCESSRIGHTS: u.ACCESSRIGHTS };
//console.log( `add token valid for ${u.UUID}:`, tokens[ u.UUID ] )
}
} catch ( err ) {
console.log( 'pb de TOKEN impossible a decoder' + u.TOKEN, err );
}
}
if( u.EMAIL ) {
emails[ u.EMAIL ] = u.UUID;
emailsglob[ u.EMAIL ] = tribeid;
}
} );
// UIDS INDEX BY DOMAIN ={UUID:[LOGIN,EMAIL,psw,accessRight,TOKEN]}
fs.outputJson( `${config.tribes}/${tribeid}/users/searchindex/uids.json`, uids, {
spaces: 2
}, err => {
if( err ) throw err;
} );
// LOGINS INDEX BY DOMAIN ={LOGIN:UUID}
fs.outputJson( `${config.tribes}/${tribeid}/users/searchindex/logins.json`, logins, {
spaces: 2
}, err => {
if( err ) throw err;
} );
// EMAILS INDEX BY DOMAIN ={EMAIL:UUID}
fs.outputJson( `${config.tribes}/${tribeid}/users/searchindex/emails.json`, emails, {
spaces: 2
}, err => {
if( err ) throw err;
} );
} );
// EMAILS et appartenance domain
fs.outputJson( `${config.tmp}/emailsglob.json`, emailsglob, {
spaces: 2
}, err => {
if( err ) throw err;
} );
// logins et appartenance domain (permet de retrouver tribeid pour un LOGIN)
fs.outputJson( `${config.tmp}/loginsglob.json`, loginsglob, {
spaces: 2
}, err => {
if( err ) throw err;
} );
// TOKENS GLOBAL INDEX Centralise tous les TOKEN pour plus de rapidité
// {UUID:TOKEN}
fs.outputJson( `${config.tmp}/tokens.json`, tokens, {
spaces: 2
}, err => {
if( err ) throw err;
} );
console.groupEnd();
return { tokens };
};
/**
* Update indexes: logins, uids and tokens
* @param {object} user User object
* @param {string} tribeid - The client identifier
* @rm {boolean} false => update, true => remove
*/
Pagans.updateDatabase = ( user, tribeid, rm = false ) => {
console.group( `Pagans.updateDatabase for ${tribeid} with user ${user.LOGIN}` )
console.assert( config.loglevel == "quiet", 'user', user );
const loginsIndex = `${config.tribes}/${tribeid}/users/searchindex/logins.json`;
fs.readJson( loginsIndex, function ( err, logins ) {
console.assert( config.loglevel == "quiet", 'logins', logins );
try {
if( rm ) {
delete logins[ user.LOGIN ];
} else {
logins[ user.LOGIN ] = user.UUID;
}
fs.outputJson( loginsIndex, logins, {
spaces: 2
}, err => {
if( err ) console.log( err );
} );
} catch ( err ) {
console.log( 'Gros pb de mise à jour Pagans.updateDatabase conflit des logins' );
}
} );
const uidsIndex = `${config.tribes}/${tribeid}/users/searchindex/uids.json`;
fs.readJson( uidsIndex, function ( err, uids ) {
try {
if( rm ) {
delete uids[ user.UUID ];
} else {
uids[ user.UUID ] = [
user.LOGIN,
user.EMAIL,
user.PASSWORD,
user.ACCESSRIGHTS,
user.TOKEN
];
}
fs.outputJson( uidsIndex, uids, {
spaces: 2
}, err => {
if( err ) console.log( err );
} );
} catch ( err ) {
console.log( 'Gros pb de mise à jour Pagans.updateDatabase conflit des uids si ce reproduit passer en mode sync bloquant' );
}
} );
const emailsIndex = `${config.tribes}/${tribeid}/users/searchindex/emails.json`;
fs.readJson( emailsIndex, function ( err, emails ) {
console.assert( config.loglevel == "quiet", 'emailss', emails );
try {
if( rm ) {
delete emails[ user.EMAIL ];
} else {
emails[ user.EMAIL ] = user.UUID;
}
fs.outputJson( emailsIndex, emails, {
spaces: 2
}, err => {
if( err ) console.log( err );
} );
} catch ( err ) {
console.log( 'Gros pb de mise à jour Pagans.updateDatabase conflit des emails' );
}
} );
const tokensIndex = `${config.tmp}/tokens.json`;
let tokens = {};
try {
tokens = fs.readJsonSync( tokensIndex );
} catch ( err ) {
console.log( 'tokens.json not available' )
}
// tokens[user.UUID] = user.TOKEN;
tokens[ user.UUID ] = { TOKEN: user.TOKEN, ACCESSRIGHTS: user.ACCESSRIGHTS };
fs.outputJsonSync( tokensIndex, tokens, {
spaces: 2
} );
/*
fs.readJson(tokensIndex, function(err, tokens) {
tokens[user.UUID] = user.TOKEN;
fs.outputJson(tokensIndex, tokens, { spaces: 2 }, err => {
if (err) console.log(err);
});
});*/
console.groupEnd();
};
/**
* Read the profil.json of an user
* @param {[type]} UUID ID of the user
* @param {[type]} tribeid tribeid
* @return {Promise} - Return a promise resolved with user data or rejected with an error
*/
// A S U P P R I M E R utiliser getinfousers pour recuperer des indexs de users
// en créer d'autres si necessaire
Pagans.getUserlist = ( header, filter, field ) => {
console.group( `getUserlist filter with filter ${filter} to get ${field}` );
/* const getuser = Pagans.getUser(header.xuuid, header.xtribeid);
if (getuser.status != 200)
return { status: getuser.status, data: getuser.payload };
const user = getuser.payload.data;
// console.log(user);
// check if update accessright allowed
// choose the level depending of ownby xuuid
let accessright = user.objectRights[header.xtribeid].users[0];
if (user.ownby.includes(header.xtribeid)) {
accessright = user.objectRights[header.xtribeid].users[1];
}
// Check update is possible at least user itself ownby itself
console.log(accessright);
console.log(accessright & 4);
if ((accessright & 4) != 4) {
return {
status: 403,
data: { info: ['forbiddenAccess'], model: 'Pagans' }
};
}
*/
const Userlist = [];
glob.sync( `${config.tribes}/${header.xworkon}/users/*/profil.json` )
.forEach( f => {
const infouser = fs.readJsonSync( f );
// Ajouter filter et liste de field
if( filter != 'all' ) {
// decode filter et test
}
const info = {};
field.split( '______' )
.forEach( k => {
if( ![ 'password' ].includes( k ) ) info[ k ] = infouser[ k ];
} );
Userlist.push( info );
} );
// console.log('userlist', Userlist);
console.groupEnd();
return {
status: 200,
data: {
data: Userlist
}
};
};
Pagans.getinfoPagans = ( tribeid, accessrights, listindex ) => {
const info = {}
const object = 'users';
const indexs = listindex.split( '_' )
let access = true;
indexs.forEach( index => {
access = !( [ 'emails', 'logins', 'uids' ].includes( index ) && !( accessrights.data[ object ] && accessrights.data[ object ].includes( 'R' ) ) );
if( access && fs.existsSync( `${config.tribes}/${tribeid}/${object}/searchindex/${index}.json` ) ) {
info[ index ] = fs.readJsonSync( `${config.tribes}/${tribeid}/${object}/searchindex/${index}.json` )
}
} )
console.log( info )
return { status: 200, data: { info: info } }
}
Pagans.getUser = ( UUID, tribeid, accessrights ) => {
console.assert( config.loglevel == "quiet", `getUser on ${UUID} for ${tribeid} with ${JSON.stringify(accessrights)}` );
if( !fs.existsSync( `${config.tribes}/${tribeid}/users/${UUID}.json` ) ) {
return {
status: 404,
data: {
info: [ 'useridNotfound' ],
model: 'Pagans',
render: {
UUID,
tribeid
}
}
}
}
const user = fs.readJsonSync( `${config.tribes}/${tribeid}/users/${UUID}.json` );
let access = true;
//console.log("test accessrights.data['users'].includes('R')", accessrights.data['users'].includes('R'))
console.assert( config.loglevel == "quiet", 'accessrights', accessrights )
access = accessrights.users && ( accessrights.users.includes( 'R' ) || ( accessrights.users.includes( 'O' ) && user.OWNEDBY.includes( UUID ) ) );
if( access ) {
return {
status: 200,
data: {
user: user,
model: "User",
info: [ "Authorizedtogetuserinfo" ]
}
};
}
return {
status: 403,
data: {
info: [ 'Forbidden' ],
model: 'Pagans',
render: {
UUID,
tribeid
}
}
};
};
Pagans.getUserIdFromEMAIL = ( tribeid, EMAIL ) => {
if( !Checkjson.test.EMAIL( EMAIL ) ) {
return {
status: 400,
data: {
info: [ 'ERREMAIL' ],
model: 'Pagans'
}
};
}
const emailsIndex = fs.readJsonSync( `${config.tribes}/${tribeid}/users/searchindex/emails.json` );
if( !emailsIndex.hasOwnProperty( EMAIL ) ) {
return {
status: 404,
data: {
info: [ 'userEMAILNotfound' ],
model: 'Pagans'
}
};
}
return emailsIndex[ EMAIL ];
};
Pagans.updateUserpassword = ( UUID, header, data ) => {
const getUser = Pagans.getUser( UUID, header.xtribeid, { users: 'W' } );
if( getUser.status == 200 ) {
const user = getUser.data.user;
// console.log('user exist', user);
const match = bcrypt.compareSync( data.password, user.PASSWORD );
if( !match ) {
return {
status: 401,
data: {
info: [ 'checkCredentials' ],
model: 'Pagans'
}
};
}
// console.log('Credentials are matching!');
if( Checkjson.test.password( {}, data.pswnew ) ) {
user.PASSWORD = bcrypt.hashSync( data.pswnew, config.saltRounds );
fs.outputJsonSync( `${config.tribes}/${header.xworkon}/users/${UUID}.json`, user, {
spaces: 2
} );
Pagans.updateDatabase( user, header.xworkon, false );
return {
status: 200,
data: {
info: [ 'successfulUpdate' ],
model: 'Pagans'
}
};
} else {
return {
status: 401,
data: {
info: [ 'pswTooSimple' ],
model: 'Pagans'
}
};
}
}
};
Pagans.createUser = ( header, data ) => {
/*
@input data={PUBKEY,EMAIL,LOGIN,UUID} check and create for header xworkon a user with generic password
*/
console.log( 'createUser on header.xworkon:' + header.xworkon + ' by user:' + header.xpaganid );
console.assert( config.loglevel == "quiet", 'with data:', data );
const ref = fs.readJsonSync( `${config.tribes}/${header.xworkon}/referentials/${header.xlang}/object/users.json` );
const logins = fs.readJsonSync( `${config.tribes}/${header.xworkon}/users/searchindex/logins.json` );
const LOGIN = Object.keys( logins );
console.assert( config.loglevel == "quiet", 'LOGIN list', LOGIN );
const emails = fs.readJsonSync( `${config.tribes}/${header.xworkon}/users/searchindex/emails.json` );
console.assert( config.loglevel == "quiet", 'emails', emails );
const EMAIL = Object.keys( emails );
console.assert( config.loglevel == "quiet", 'EMAIL list', EMAIL );
// list.UUID est forcement unique car on est en update et pas en create
if( !data.UUID ) data.UUID = UUID.v4();
// pour la logique de Checkjson il faut passer le parametre
const Checkjson = Checkjson.evaluate( {
list: {
LOGIN,
EMAIL,
UUID: []
}
}, ref, data );
console.assert( config.loglevel == "quiet", 'check & clean data before update ', check );
if( Checkjson.invalidefor.length > 0 ) {
return {
status: 403,
data: {
model: 'Pagans',
info: Checkjson.invalidefor
}
};
}
const clientConfig = fs.readJsonSync( `${config.tribes}/${header.xworkon}/clientconf.json` );
const user = Checkjson.data;
user.DATE_CREATE = new Date()
.toISOString();
user.PASSWORD = bcrypt.hashSync( clientConfig.genericpsw, config.saltRounds );
user.OWNBY = [ data.UUID ];
user.OBJECT = 'users';
// set le minimum de droit sur l'objet users dont il est le Owner
if( data.ACCESSRIGHTS ) {
user.ACCESSRIGHTS = data.ACCESSRIGHTS
} else {
user.ACCESSRIGHTS = { app: {}, data: {} };
}
user.ACCESSRIGHTS.data[ header.xworkon ] = { users: "O" };
fs.outputJsonSync( `${config.tribes}/${header.xworkon}/users/${user.UUID}.json`, user, {
spaces: 2
} );
Pagans.updateDatabase( user, header.xworkon, false );
return {
status: 200,
data: {
uuid: user.UUID,
info: [ 'successfulCreate' ],
model: 'Pagans'
}
};
};
Pagans.updateUser = ( UUID, header, data ) => {
console.log( 'updateUser UUID:' + UUID + ' on header.xworkon:' + header.xworkon + ' by user' + header.xpaganid );
// console.log('header', header);
console.assert( config.loglevel == "quiet", 'with data', data );
const getuser = Pagans.getUser( UUID, header.xworkon, { users: 'R' } );
if( getuser.status != 200 ) return {
status: getuser.status,
data: getuser.data.user
};
const user = getuser.data.user;
/*
let userconnected = user;
if (UUID != header.xuuid) {
// mean connected user want to change other user
const getuserconnected = Pagans.getUser(header.xuuid, header.xtribeid);
userconnected = getuserconnected.payload.data;
}
console.log('user to update', user);
console.log('user connected that request update', userconnected);
// check if update accessright allowed
// choose the level depending of ownby xuuid
let accessright = userconnected.objectRights[header.xworkon].users[0];
if (user.ownby.includes(header.xuuid)) {
accessright = userconnected.objectRights[header.xworkon].users[1];
}
// Check update is possible at least user itself ownby itself
console.log(accessright);
console.log(accessright & 2);
if ((accessright & 2) != 2) {
return {
status: 403,
data: { info: ['forbiddenAccess'], model: 'Pagans' }
};
}
*/
const ref = fs.readJsonSync( `${config.tribes}/${header.xworkon}/referentials/object/users_${
header.xlang
}.json` );
const logins = fs.readJsonSync( `${config.tribes}/${header.xworkon}/users/searchindex/logins.json` );
const LOGIN = Object.keys( logins )
.filter( l => logins[ l ] != user.UUID );
// console.log( 'LOGIN list', LOGIN );
const emails = fs.readJsonSync( `${config.tribes}/${header.xworkon}/users/searchindex/emails.json` );
// console.log( 'emails', emails );
const EMAIL = Object.keys( emails )
.filter( e => emails[ e ] != user.UUID );
// console.log( 'EMAIL list', EMAIL );
// list.UUID est forcement unique car on est en update et pas en create
// pour la logique de Checkjson il faut passer le parametre
const Checkjson = Checkjson.evaluate( {
profil: user[ 'apps' + header.xworkon + 'profil' ],
list: {
LOGIN,
EMAIL,
UUID: []
}
}, ref, data );
if( Checkjson.invalidefor.length > 0 ) {
return {
status: 403,
data: {
model: 'Pagans',
info: Checkjson.invalidefor,
}
};
}
data = Checkjson.data;
let saveuser = false;
let updateDatabase = false;
Object.keys( data )
.forEach( k => {
//console.log( user[ k ] )
//console.log( data[ k ] )
//console.log( '---' )
if( user[ k ] != data[ k ] ) {
user[ k ] = data[ k ];
saveuser = true;
if( [ 'TOKEN', 'LOGIN', 'EMAIL' ].includes( k ) ) updateDatabase = true;
// attention si LOGIN ou EMAIL change il faut checker qu il n existe pas dejà risque de conflit
}
} );
if( saveuser ) {
//console.log( 'mise à jour user profile.json' );
if( data.TOKEN ) {
user.date_lastLOGIN = new Date()
.toISOString();
} else {
user.date_update = new Date()
.toISOString();
}
try {
fs.outputJsonSync( `${config.tribes}/${header.xworkon}/users/${UUID}.json`, user, {
spaces: 2
} );
//console.log( 'declenche updatabase', updateDatabase )
if( updateDatabase ) {
// mean index have to be updated
Pagans.updateDatabase( user, header.xworkon, false );
console.assert( config.loglevel == "quiet", 'MISE A JOUR DU TOKEN ou de l\'EMAIL ou du LOGIN' );
}
} catch ( err ) {
console.log( 'ERRRRR need to understand update impossible of user: ' + UUID + ' in domain:' + header.xworkon + ' from user ' + header.xpaganid + ' of domain:' + header.xtribe );
console.log( 'with data :', data );
return {
status: 400,
data: {
info: [ 'failtoWritefs' ],
model: 'Pagans'
}
};
}
}
return {
status: 200,
data: {
info: [ 'successfulUpdate' ],
model: 'Pagans'
}
};
};
Pagans.deleteUser = ( UUID, header ) => {
// Delete remove from users object UUID and update index
// Activity is not deleted => means some activity can concern an UUID that does not exist anymore.
// update index
const infouser = fs.readJsonSync( `${config.tribes}/${header.xworkon}/users/${UUID}.json` );
Pagans.updateDatabase( infouser, header.xworkon, true );
fs.removeSync( `${config.tribes}/${header.xworkon}/users/${UUID}.json` );
return {
status: 200,
data: {
info: [ 'successfulDelete' ],
modedl: 'Pagans'
}
};
};
/*
@header { xtribeid: client domain name data
A VERIFIER inutile ,xworkon: client domain name where user is store and where data are stored
,xuuid: 1 if unknown else if user id auhtneticated
,xlanguage: langue used fr en return referential in this lang
,xauth: TOKEN valid for 24h'}
workon=xtribeid in an app client usage
in case of xtribeid=mymaidigit,
if workon==mymaildigit => admin role on any xworkon
else adlin role only in xworkon specified
@body {LOGIN: password:}
return {status:200, data:{data:{TOKEN,UUID}}}
return {status:401,data:{info:[code list], model: referential}}
*/
Pagans.loginUser = ( header, body, checkpsw ) => {
// On recupere tribeid du LOGIN
// ATTENTION xworkon peut être different du user xtribeid
//(cas d'un user qui a des droits sur un autre domain et qui travail sur cet autre domain)
// les function Pagans utilise le domain de travail xWorkon
// il faut donc modifier le header au moment du LOGIN
// pour que l'update du user au moment du LOGIN concerne bien le bon domain
header.xworkon = header.xtribe
const LOGINdom = fs.readJsonSync( `${config.tmp}/loginsglob.json` );
console.assert( config.loglevel == "quiet", LOGINdom )
console.assert( config.loglevel == "quiet", body )
if( !LOGINdom[ body.LOGIN ] ) {
return {
status: 401,
data: { info: [ 'LoginDoesNotExist' ], model: 'Pagans' }
};
}
const logins = fs.readJsonSync( `${config.tribes}/${LOGINdom[body.LOGIN]}/users/searchindex/logins.json` );
if( !Object.keys( logins )
.includes( body.LOGIN ) ) {
return {
status: 401,
data: {
info: [ 'LOGINDoesNotExist' ],
model: 'Pagans',
moreinfo: `Le login ${body.LOGIN} does not exist for tribeid ${LOGINdom[body.LOGIN]}`
}
};
}
// Load user
const uid = logins[ body.LOGIN ];
const getUser = Pagans.getUser( uid, LOGINdom[ body.LOGIN ], { users: 'R' } );
console.log( 'getPagans', getUser )
if( getUser.status != 200 ) {
return { status: 200, data: { model: 'Pagans', user: getUser.data.user } };
}
const user = getUser.data.user;
console.log( 'user', user )
if( checkpsw ) {
const match = bcrypt.compareSync( body.PASSWORD, user.PASSWORD );
if( !match ) {
return {
status: 401,
data: {
info: [ 'checkCredentials' ],
model: 'Pagans'
}
};
}
}
// Mise à jour user pour app LOGIN
user.tribeid = LOGINdom[ body.LOGIN ]
user.TOKEN = jwt.encode( {
expiration: moment()
.add( 1, 'day' ),
UUID: user.UUID
}, config.jwtSecret );
// on met à jour le header qui authentifie le user connecte
header.xtribe = LOGINdom[ body.LOGIN ]
header.xpaganid = user.UUID;
header.xauth = user.TOKEN;
// On modifie xworkon car au LOGIN on est forcemenet identifiable sur tribeid
// xworkon est utiliser pour travailler sur un environnement client different de celui
// de l'appartenace du user
header.xworkon = LOGINdom[ body.LOGIN ];
const majuser = Pagans.updateUser( user.UUID, header, {
UUID: user.UUID,
TOKEN: user.TOKEN
} );
// Enleve info confidentiel
delete user.PASSWORD;
if( user.ACCESSRIGHTS.data[ "Alltribeid" ] ) {
//cas admin on transforme les droits sur tous les tribeid existant
const newaccessrightsdata = {}
fs.readJsonSync( `${config.tribes}/tribeids.json` )
.forEach( cid => {
newaccessrightsdata[ cid ] = user.ACCESSRIGHTS.data[ "Alltribeid" ]
} )
user.ACCESSRIGHTS.data = newaccessrightsdata;
}
// on recupere le menu de l app qui demande le LOGIN si existe dans user.ACCESSRIGHTS.app[]
// header['X-app'] = tribeid:Projet pour récuperer le menu correspondant
console.assert( config.loglevel == "quiet", "header.xapp", header.xapp )
console.assert( config.loglevel == "quiet", "user.ACCESSRIGHTS.app[header.xapp]", user.ACCESSRIGHTS.app[ header.xapp ] );
return {
status: 200,
data: {
model: "Pagans",
info: [ 'loginSuccess' ],
user: user
}
};
};
Pagans.getlinkwithoutpsw = async ( EMAIL, header ) => {
// check le domain d'appartenance de l'eamail dans /tribes/emailsglob.json={email:cleintId}
// on remplace le header.xtribeid
const domforemail = fs.readJsonSync( `${config.tribes}/emailsglob.json`, 'utf-8' );
if( domforemail[ EMAIL ] ) {
header.xtribe = domforemail[ EMAIL ]
} else {
return { status: 404, info: { model: 'Pagans', info: [ 'emailnotfound' ], moreinfo: "email does not exist in /emailsglob.json" } };
}
// recupere le uuid du user dans /tribes/tribeid/users/searchindex/emails.json
// puis l'ensemble des info des user du domain /uuids.json
// infoforuuid[uuidforemail[EMAIL]] permet de récupérer toutes info du user, droit, etc...
const uuidforemail = fs.readJsonSync( `${config.tribes}/${header.xtribe}/users/searchindex/emails.json`, 'utf8' );
const infoforuuid = fs.readJsonSync( `${config.tribes}/${header.xtribe}/users/searchindex/uids.json`, 'utf8' );
// On recupere le modele d'email appemailinfo qui doit être présent dans clientconf.json
let confdom = fs.readJsonSync( `${config.tribes}/${header.xtribe}/clientconf.json`, 'utf8' );
let checkinfomail = "";
if( !confdom.appemailinfo ) {
checkinfomail += ' Erreur de clientconfig il manque un objet appemailinfo pour poursuivre';
}
if( checkinfomail != "" ) {
console.log( `Pb de config pour ${header.xtribe} ${checkinfomail} ` )
return {
status: 500,
info: {
model: 'Pagans',
info: [ 'objectmissingclientconf' ],
moreinfo: checkinfomail
}
};
}
let simulelogin;
try {
simulelogin = await Pagans.loginUser( header, {
LOGIN: infoforuuid[ uuidforemail[ EMAIL ] ][ 0 ],
PASSWORD: ""
}, false );
console.log( 'info simulelogin', simulelogin )
} catch ( err ) {
return {
status: 501,
info: {
model: 'Pagans',
info: [ 'loginImpossible' ],
moreinfo: "Impossible de se loger avec " + infoforuuid[ uuidforemail[ EMAIL ] ][ 0 ]
}
};
}
const url = `${config.rootURL}?xauth=${simulelogin.data.TOKEN}&xuuid=${simulelogin.data.UUID}&xtribeid=${simulelogin.data.tribeid}&xworkOn=${header.xworkon}&xlang=${header.xlang}`
//console.log('envoi email avec' + url)
confdom.appemailinfo.msg.destperso = [ {} ];
confdom.appemailinfo.msg.destperso[ 0 ].email = EMAIL;
confdom.appemailinfo.msg.destperso[ 0 ].subject = "Lien de réinitialisation valable 1h"
confdom.appemailinfo.msg.destperso[ 0 ].titre = "Vous avez oublier votre mot de passe"
confdom.appemailinfo.msg.destperso[ 0 ].texte = `
<p style='color: #999999; font-size: 16px; line-height: 24px; margin: 0;text-align:justify;'>
Bonjour,<br> Vous nous avez signalé que vous avez oublié votre mot de passe pour cette email.
Avec le lien suivant vous allez pouvoir utiliser votre interface 1h maximum.
<a href="${url}">Clicker ICI</a>.<br>
Nous vous conseillons de changer votre mot de passe.</p>
`
//console.log('envoi header :', header);
Outputs.sendMailcampain( confdom.appemailinfo.msg, header );
console.log( confdom.appemailinfo );
return {
status: 200,
info: {
model: 'Pagans',
info: [ 'emailsentforReinit' ],
moreinfo: 'EMAIL sent with unique link ' + url
}
};
}
module.exports = Pagans;

416
app/models/Referentials.js Executable file
View File

@@ -0,0 +1,416 @@
const glob = require( 'glob' );
const path = require( 'path' );
const fs = require( 'fs-extra' );
const config = require( '../tribes/townconf.js' );
const Referentials = {};
/*
Manage Referential object data
each object is compose of a list of fields
each fields have it owns structur and check
those common rules allow to be used to manage:
- forms (creation to collect data)
- data check
- data access rights
common referential data stored in /data/shared/referential/
*/
Referentials.clientconf = ( xworkOn, listkey ) => {
/*
Retourne les info d'un clientconf.json sur une liste de [cle]
*/
let conf = {};
let dataconf = {};
//console.log( `${config.tribes}/${xworkOn}/clientconf.json` )
try {
conf = fs.readJsonSync( `${config.tribes}/${xworkOn}/clientconf.json` );
// remove information notrelevant for
[ 'emailFrom', 'emailClient', 'emailCc', 'commentkey', 'clezoomprivate', 'stripekeyprivate', 'genericpsw' ].forEach( c => {
delete conf[ c ];
} );
listkey.forEach( k => dataconf[ k ] = conf[ k ] )
//console.log( 'dataconf', dataconf )
} catch ( err ) {
console.log( 'Attention demande sur clienId inconnu ' + xworkOn );
}
return {
status: 200,
payload: {
data: dataconf
}
};
};
Referentials.clientconfglob = () => ( {
status: 200,
payload: {
data: fs.readJsonSync( `${config.tmp}/clientconfglob.json` )
}
} );
Referentials.getref = ( origin, source, ref, xworkOn, xlang ) => {
// If request origin then send back referential with all language in it
// if not origin or json source return by language
let referent = {};
let src;
if( origin && [ 'object', 'data' ].includes( source ) ) {
src = `${config.tribes}/${xworkOn}/referentials/${source}/${ref}.json`
} else {
src = `${config.tribes}/${xworkOn}/referentials/${source}/${ref}_${xlang}.json`;
}
//console.log( src )
try {
referent = fs.readJsonSync( src );
} catch ( err ) {
console.log( `Request ${src} does not exist ` );
}
return {
status: 200,
payload: {
data: referent
}
};
};
Referentials.putref = ( source, name, xworkOn, data ) => {
/*
We get a referential, we have 3 kinds of sources:
* data = [{uuid,DESC:{fr:,en,},DESCLONG:{fr:,en:}, other field}]
only DESC and DESCLONG have a translation
* json = are full complex object in language name_lg.json
* object = [{uuid,DESC:{fr,en},DESCLONG:{fr,en}},tpl,}]
Difference between data and object is that object defines rule to manage an object, and how to create a forms to get data each data is saved in one folder object/uuid.json and have to respect the corresponding object referentials definition.
For data and object it is possible only to put a file with all language available.
When store this script erase with the new file per _lg
name for source=json must end by _lg
*/
//console.log( data )
const pat = /.*_..\.json$/;
const file = `${config.tribes}/${xworkOn}/referentials/${source}/${name}.json`
if( [ 'object', 'data' ].includes( source ) ) {
if( pat.test( name ) ) {
return { status: 404, payload: { model: 'Referentials', info: [ 'nameunconsistent' ], moreinfo: "can not be update with one lang need a full file with language for object or data" } }
} else {
fs.outputJsonSync( file, data );
return Refernetials.update( xworkOn, source, name )
}
} else {
if( !pat.test( name ) ) {
return { status: 404, payload: { model: 'Referentials', info: [ 'nameunconsistent' ], moreinfo: "can not be update without a lang _lg.json a referential json" } }
} else {
fs.outputJsonSync( file, data );
return { status: 200, payload: { model: 'Referentials', info: [ 'successfull' ], moreinfo: "ref json updated " } }
}
};
};
Referentials.updatefull = ( tribeid ) => {
let err = "";
let nbrefupdate = 0;
const pat = /.*_..\.json$/;
[ 'object', 'data' ].forEach( o => {
glob.sync( `${config.tribes}/${tribeid}/referentials/${o}/*.json` )
.forEach( f => {
if( !pat.test( f ) ) {
const res = Referentials.update( tribeid, o, path.basename( f, '.json' ) );
if( res.status != 200 ) {
err += `Error on ${o}/${path.basename(f)}`
} else {
nbrefupdate += 1;
}
}
} )
} );
if( err != "" ) {
return { status: 500, payload: { info: [ 'Errupdateref' ], model: "Referentials", moreinfo: err } }
}
return { status: 200, payload: { info: [ 'Success' ], model: "Referentials", moreinfo: `Number of object and data ref updated: ${nbrefupdate}` } }
};
Referentials.inittribeid = () => {
console.log( "Clientconf list for this server", `${config.tribes}/**/clientconf.json` );
const TribesGlobalConfig = glob.sync( `${config.tribes}/**/clientconf.json` )
.map( f => fs.readJsonSync( f ) );
// store global conf for sharing to other api
fs.outputJsonSync( `${config.tmp}/clientconfglob.json`, TribesGlobalConfig, {
spaces: 2
} );
return { status: 200, payload: { moreinfo: TribesGlobalConfig } }
}
Referentials.generetribeids = () => {
const tribeids = [];
fs.readJsonSync( `${config.tmp}/clientconfglob.json` )
.forEach( c => {
if( !tribeids.includes( c.tribeid ) ) tribeids.push( c.tribeid );
} );
fs.outputJsonSync( `${config.tmp}/tribeids.json`, tribeids );
console.log( `update ${config.tribes}/tribeids` );
return tribeids;
}
Referentials.genereallowedDOM = () => {
const confglob = fs.readJsonSync( `${config.tmp}/clientconfglob.json` );
let allDom = [];
confglob.forEach( c => {
c.allowedDOMs.forEach( d => {
if( !allDom.includes( d ) ) allDom = allDom.concat( d );
} )
} );
return allDom;
};
/* A voir si encore necessaire pour générer un environnement identique
sur un autre server
// Génére les domaines s'ils n'existe pas dans le repertoire
function genereEnvClient() {
const confglob = fs.readJsonSync(
`${config.sharedData}/clientconfglob.json`
);
confglob.forEach(function(c) {
config.tribeidsConf[c.tribeid] = c;
if (c.allowedURLs) {
c.allowedURLs.forEach(u => {
if (!config.allowedURLs.includes(u)) {
config.allowedURLs.push(u);
}
});
} else {
console.log('erreur de fichier config d\'un site pour ', c);
}
// GLOBAL Tribes IDS INDEX
maketribeidsIndex();
if (!fs.existsSync(`${config.tribes}/${c.tribeid}`)) {
const execSync = require('child_process').execSync;
execSync(`cp -r ${config.tribes}/modele ${config.tribes}/${c.tribeid}`);
}
});
}
*/
Referentials.update = ( tribeid, source, name ) => {
/*
Replace for each language the referential name for a tribeid
After each update the version number is incremented by 1 in clientconf.json
*/
if( !fs.existsSync( `${config.tribes}/${tribeid}/referentials/${source}/${name}.json` ) ) {
return { status: 500, payload: { info: [ "unknownRef" ], model: "Referentials", moreinfo: `file does not exist ${config.tribes}/${tribeid}/referentials/${source}/${name}.json` } }
};
const clientconf = fs.readJsonSync( `${config.tribes}/${tribeid}/clientconf.json` );
if( !clientconf.langueReferential ) {
return { status: 500, payload: { info: [ "missingConf" ], model: "Referentials", moreinfo: ` ${config.tribes}/${tribeid}/clientconf.json does not contain langueReferential array` } }
}
const ref = fs.readJsonSync( `${config.tribes}/${tribeid}/referentials/${source}/${name}.json` );
clientconf.langueReferential.forEach( lg => {
//manage translation
let refnew = [];
refnew = [];
ref.forEach( d => {
if( d.DESC ) d.DESC = d.DESC[ lg ];
if( d.DESCLONG ) d.DESCLONG = d.DESCLONG[ lg ];
if( d.INFO ) d.INFO = d.INFO[ lg ];
if( d.desc ) d.desc = d.desc[ lg ];
if( d.desclong ) d.desclong = d.desclong[ lg ];
if( d.info ) d.info = d.info[ lg ];
if( d.placeholder ) d.placeholder = d.placeholder[ lg ];
refnew.push( d )
} )
//save new ref in language
//console.log( "New ref", refnew )
console.log( `Update referentials per lg ${config.tribes}/${tribeid}/referentials/${source}/${name}_${lg}.json` )
fs.outputJsonSync( `${config.tribes}/${tribeid}/referentials/${source}/${name}_${lg}.json`, refnew, {
spaces: 2
} );
} );
//upgrade version number
if( !clientconf.referentials ) clientconf.referentials = {};
if( !clientconf.referentials[ source ] ) clientconf.referentials[ source ] = {};
if( !clientconf.referentials[ source ][ name ] ) clientconf.referentials[ source ][ name ] = { version: 0 };
clientconf.referentials[ source ][ name ].version += 1;
fs.outputJsonSync( `${config.tribes}/${tribeid}/clientconf.json`, clientconf, 'utf8' );
return {
status: 200,
payload: {
info: [ 'successUpdate' ],
model: "Referentials",
moreinfo: `${name} updated`
}
}
};
//console.log( Referentials.update( 'apxtrib', "object", "user" ) )
Referentials.genereobjet = ( tribeid, destination, tplmustache, objet, filtre ) => {
/* @TODO
Genere des objets d'agregat
@tribeid = data/tribee/ identifiant client
@destinations = [] of destination
@tplmustache = fichier mustache de mise en page
@objet = nom d'objet contact, companies, items, users
@filtre = fonction a executer
*/
}
//////// EN DESSOUS DE CETTE LIGNE A SUPPRIMER
/*
Le principe consistait à partager des referentiels dans shareddataLa gestion est trop compliqué => le principe chaque client duplique un referentiel pour que les app qui s'appuie sur des composants communs puissent fonctionner
*/
/*Referentials.genereClientObjectASUPP = () => {
const confglob = fs.readJsonSync( `${config.tmp}/clientconfglob.json` );
// Check and update folder and data per lang
// c as tribeid
confglob.forEach( ( c, postribeid ) => {
//check folder are well create
const lstfolder = [ 'actions', 'actions/done', 'actions/todo', 'cards', 'logs', 'orders', 'orders/reservation', 'orders/purchase', 'orders/contact', 'public', 'public/reservation', 'tags', 'tags/stats', 'tags/archives', 'tags/hits', 'tags/imgtg', 'users' ];
lstfolder.forEach( fol => {
if( !fs.existsSync( `${config.tribes}/${c.tribeid}/${fol}` ) ) {
fs.mkdirSync( `${config.tribes}/${c.tribeid}/${fol}` );
}
} )
if( c.referentials && !c.langue ) { console.log( `ERREUR referentials mais pas de langue:[] pour ${c.tribeid}/clientconf.json` ) }
if( c.referentials && c.langue ) {
let majclientconf = false;
// Create and check Object structure
Object.keys( c.referentials.object )
.forEach( o => {
// if object exist in shared then it merge sharedObject and domain referential object
let objfull = [];
const objshared = `${config.sharedData}/referentials/dataManagement/object/${o}.json`;
if( fs.existsSync( objshared ) ) {
objfull = objfull.concat( fs.readJsonSync( objshared ) );
}
const objdomain = `${config.tribes}/${c.tribeid}/referentials/dataManagement/object/${o}.json`;
if( fs.existsSync( objdomain ) ) {
objfull = objfull.concat( fs.readJsonSync( objdomain ) );
}
c.langue.forEach( lg => {
const objfulllg = objfull.map( field => {
if( field.DESC ) field.DESC = field.DESC[ lg ];
if( field.DESCLONG ) field.DESCLONG = field.DESCLONG[ lg ];
return field;
} );
const objectdomlg = `${config.tribes}/${c.tribeid}/referentials/${lg}/object/${o}.json`;
let savedObject = {};
if( fs.existsSync( objectdomlg ) ) {
savedObject = fs.readJsonSync( objectdomlg );
}
// TODO Always true change later to update only if needded
if( !fs.existsSync( objectdomlg ) || objfulllg.length !== savedObject.length || 1 == 1 ) {
fs.outputJsonSync( objectdomlg, objfulllg, {
spaces: 2
} );
confglob[ postribeid ].referentials.object[ o ].version += 1;
majclientconf = true;
}
} );
} );
// datafile
Object.keys( c.referentials.data )
.forEach( d => {
// if object exist in shared then it merge sharedObject and domain referential object
// console.log(c.tribeid + '--' + d);
let datafull = [];
const datashared = `${
config.sharedData
}/referentials/dataManagement/data/${d}.json`;
if( fs.existsSync( datashared ) ) {
datafull = datafull.concat( fs.readJsonSync( datashared ) );
}
const datadomain = `${config.tribes}/${
c.tribeid
}/referentials/dataManagement/data/${d}.json`;
if( fs.existsSync( datadomain ) ) {
datafull = datafull.concat( fs.readJsonSync( datadomain ) );
}
/* const defdata = `${config.tribes}/${
c.tribeid
}/referentials/dataManagement/data/${d}.json`;
*/
// for each Langues => generate fr.obj and compare it with existing file
// if diff then => upgrade version number in clientconf
// console.log(datafull);
// this could be improved by usind d.meta wich is the object that DESCribe this data
/* c.langue.forEach( lg => {
let meta;
if( c.referentials.data[ d ].meta ) {
meta = fs.readJsonSync( `${config.tribes}/${c.tribeid}/referentials/${lg}/object/${
c.referentials.data[d].meta
}.json` );
}
let datalg;
const datafulllg = datafull.map( tup => {
datalg = {};
meta.forEach( ch => {
if( tup[ ch.idfield ] ) {
if( ch.multilangue ) {
datalg[ ch.idfield ] = tup[ ch.idfield ][ lg ];
} else {
datalg[ ch.idfield ] = tup[ ch.idfield ];
}
}
} );
return datalg;
} );
// lit le fichier correspondant et le compare si différent le sauvegarde
// stocke l'information d'upgrade d ela version
const datadomlg = `${config.tribes}/${
c.tribeid
}/referentials/${lg}/data/${d}.json`;
let saveddata = {};
if( fs.existsSync( datadomlg ) ) {
saveddata = fs.readJsonSync( datadomlg );
}
// Condition to improve
// TODO always change file to improvelater by detecting real change.
if( !fs.existsSync( datadomlg ) || datafulllg.length != saveddata.length || 1 == 1 ) {
fs.outputJsonSync( datadomlg, datafulllg, {
spaces: 2
} );
confglob[ postribeid ].referentials.data[ d ].version += 1;
majclientconf = true;
}
} );
} );
// json file that have to start with lg {lg:'':{json }}
Object.keys( c.referentials.json )
.forEach( j => {
// if object exist in shared then it merge sharedObject and domain referential object
// console.log(c.tribeid + '--' + d);
let jsonfull = [];
const jsondomain = `${config.tribes}/${c.tribeid}/referentials/dataManagement/json/${j}.json`;
if( fs.existsSync( jsondomain ) ) {
jsonfull = fs.readJsonSync( jsondomain );
}
c.langue.forEach( lg => {
const jsondomlg = `${config.tribes}/${
c.tribeid
}/referentials/${lg}/json/${j}.json`;
// console.log('jsondomlg', jsondomlg);
let datalg = jsonfull;
if( jsonfull[ lg ] ) {
datalg = jsonfull[ lg ];
}
fs.outputJsonSync( jsondomlg, datalg, {
spaces: 2
} );
} );
} );
// update clientconf domain with updated version
if( majclientconf ) {
fs.outputJsonSync( `${config.tribes}/${c.tribeid}/clientconf.json`, c, {
spaces: 2
} );
}
}
} );
// update global conf
fs.outputJsonSync( `${config.tmp}/clientconfglob.json`, confglob, {
spaces: 2
} );
};*/
module.exports = Referentials;

220
app/models/Referentialssave.js Executable file
View File

@@ -0,0 +1,220 @@
const glob = require( 'glob' );
const path = require( 'path' );
const fs = require( 'fs-extra' );
// Check if package is installed or not to pickup the right config file
const config = require( '../tribes/townconf.js' );
const Referentials = {};
/*
Manage Referential object data
each object is compose of a list of fields
each fields have it owns structur and check
those common rules allow to be used to manage:
- forms (creation to collect data)
- data check
- data access rights
common referential data stored in /data/shared/referential/
*/
Referentials.clientconf = ( xworkOn, listkey ) => {
/*
Retourne les info d'un clientconf.json sur une liste de [cle]
*/
let conf = {};
let dataconf = {};
console.log( `${config.tribes}/${xworkOn}/clientconf.json` )
try {
conf = fs.readJsonSync( `${config.tribes}/${xworkOn}/clientconf.json` );
// remove information notrelevant for
[ 'emailFrom', 'emailClient', 'emailCc', 'commentkey', 'clezoomprivate', 'stripekeyprivate', 'genericpsw' ].forEach( c => {
delete conf[ c ];
} );
listkey.forEach( k => dataconf[ k ] = conf[ k ] )
console.log( 'dataconf', dataconf )
} catch ( err ) {
console.log( 'Attention demande sur clienId inconnu ' + xworkOn );
}
return {
status: 200,
payload: {
data: dataconf
}
};
};
Referentials.clientconfglob = () => ( {
status: 200,
payload: {
data: fs.readJsonSync( `${config.tmp}/clientconfglob.json` )
}
} );
Referentials.inittribeid = () => {
console.log( "Clientconf list for this server", `${config.tribes}/**/clientconf.json` );
const TribesGlobalConfig = glob.sync( `${config.tribes}/**/clientconf.json` )
.map( f => fs.readJsonSync( f ) );
// store global conf for sharing to other api
fs.outputJsonSync( `${config.tmp}/clientconfglob.json`, TribesGlobalConfig, {
spaces: 2
} );
return { status: 200, payload: { moreinfo: TribesGlobalConfig } }
}
Referentials.generetribeids = () => {
const tribeids = [];
fs.readJsonSync( `${config.tmp}/clientconfglob.json` )
.forEach( c => {
if( !tribeids.includes( c.tribeid ) ) tribeids.push( c.tribeid );
} );
fs.outputJsonSync( `${config.tmp}/tribeids.json`, tribeids );
console.log( `update ${config.tribes}/tribeids` );
return tribeids;
}
Referentials.genereallowedDOM = () => {
const confglob = fs.readJsonSync( `${config.tmp}/clientconfglob.json` );
let allDom = [];
confglob.forEach( c => {
c.allowedDOMs.forEach( d => {
if( !allDom.includes( d ) ) allDom = allDom.concat( d );
} )
} );
return allDom;
};
Referentials.getref = ( source, ref, xworkOn, xlang, singlelang = true ) => {
let referent = {};
let src = `${config.tribes}/${xworkOn}/referentials/${xlang}/${source}/${ref}.json`;
if( !singlelang ) {
//request full referential to manage
src = `${config.tribes}/${xworkOn}/referentials/dataManagement/${source}/${ref}.json`
}
console.log( src )
try {
referent = fs.readJsonSync( src );
} catch ( err ) {
console.log( `Attention demande de referentiel inexistant pour ${src} ` );
}
return {
status: 200,
payload: {
data: referent
}
};
};
Referentials.putref = ( source, name, xworkOn, data ) => {
/*
We get a referential, we have 3 kinds of sources:
* data = [{uuid,desc:{fr:,en,},desclong:{fr:,en:}, other field}]
only desc and desclong have a translation
* json = {"fr":{},"en":{}, ...} are full complex object in language
* object = [{uuid,desc:{fr,en},desclong:{fr,en}},tpl,}]
Difference between data and object is that object defines rule to manage an object, and how to create a forms to get data each data is saved in one folder object/uuid.json and have to respect the corresponding object referentials definition.
*/
console.log( data )
// Create a backup of the day hour if exist
const file = `${config.tribes}/${xworkOn}/referentials/dataManagement/${source}/${name}.json`
if( fs.existsSync( file ) ) {
//backup change done per hour
const origin = fs.readJsonSync( file, 'utf-8' )
fs.outputJsonSync( `${config.tribes}/${xworkOn}/referentials/dataManagementBackup/${source}/${name}${moment().format('YYYYMMDDHHmm')}.json`, origin, { spaces: 2 } )
} else {
console.log( `Referential ${name}.json does not exist this created it` )
}
console.log( 'ref backup before update', name );
fs.outputJsonSync( file, data, { spaces: 2 } );
// update/create new referential and new version
return Referentials.update( xworkOn, source, name );
};
Referentials.updatefull = ( tribeid ) => {
// source json are only per lg so no full update for json
let err = "";
[ 'object', 'data' ].forEach( o => {
glob.sync( `${config.tribes}/${tribeid}/referentials/dataManagement/${o}/*.json` )
.forEach( f => {
if( finipaspar_lg ) {
const res = Referentials.update( tribeid, o, path.basename( f, '.json' ) );
if( res.status != 200 ) {
err += `Error on ${o}/${path.basename(f)}`
}
}
} )
} );
if( err != "" ) {
return { status: 500, payload: { info: [ 'Errupdateref' ], model: "Referentials", moreinfo: err } }
}
return { status: 200, payload: { info: [ 'Success' ], model: "Referentials" } }
};
Referentials.update = ( tribeid, source, name ) => {
/*
Replace for each language the referential name for a tribeid
After each update the version number is incremented by 1 in clientconf.json
*/
if( !fs.existsSync( `${config.tribes}/${tribeid}/referentials/${source}/${name}.json` ) ) {
return { status: 500, payload: { info: [ "unknownRef" ], model: "Referentials", moreinfo: `file does not exist ${config.tribes}/${tribeid}/referentials/${source}/${name}.json` } }
};
const clientconf = fs.readJsonSync( `${config.tribes}/${tribeid}/clientconf.json` );
if( !clientconf.langueReferential ) {
return { status: 500, payload: { info: [ "missingConf" ], model: "Referentials", moreinfo: ` ${config.tribes}/${tribeid}/clientconf.json does not contain langueReferential array` } }
}
const ref = fs.readJsonSync( `${config.tribes}/${tribeid}/referentials/${source}/${name}.json` );
clientconf.langueReferential.forEach( lg => {
/*if( !fs.existsSync( `${config.tribes}/${tribeid}/referentials/${lg}` ) ) {
[ '', '/data', '/json', '/object' ].forEach( p => {
fs.mkdirSync( `${config.tribes}/${tribeid}/referentials/${lg}${p}` );
} )
}
*/
//manage translation
let refnew = [];
if( source == 'json' ) {
refnew = ref[ lg ]
} else {
refnew = [];
ref.forEach( d => {
if( d.desc ) d.desc = d.desc[ lg ];
if( d.desclong ) d.desclong = d.desclong[ lg ];
if( d.info ) d.info = d.info[ lg ];
if( d.placeholder ) d.placeholder = d.placeholder[ lg ];
refnew.push( d )
} )
}
//save new ref in language
console.log( "testtttt", refnew )
console.log( `${config.tribes}/${tribeid}/referentials/${source}/${name}_${lg}.json` )
fs.outputJsonSync( `${config.tribes}/${tribeid}/referentials/${source}/${name}_${lg}.json`, refnew, {
spaces: 2
} );
} );
//upgrade version number
if( !clientconf.referentials[ source ][ name ] ) clientconf.referentials[ source ][ name ] = { version: 0 };
clientconf.referentials[ source ][ name ].version += 1;
return {
status: 200,
payload: {
info: [ 'successUpdate' ],
model: "Referentials",
moreinfo: `${name} updated`
}
}
};
//console.log( Referentials.update( 'apxtrib', "object", "user" ) )
Referentials.genereobjet = ( tribeid, destination, tplmustache, objet, filtre ) => {
/* @TODO
Genere des objets d'agregat
@tribeid = data/tribee/ identifiant client
@destinations = [] of destination
@tplmustache = fichier mustache de mise en page
@objet = nom d'objet contact, companies, items, users
@filtre = fonction a executer
*/
}
module.exports = Referentials;

174
app/models/Setup.js Executable file
View File

@@ -0,0 +1,174 @@
const fs = require( 'fs-extra' );
const path = require( 'path' );
const dnsSync = require( 'dns-sync' );
const Mustache = require( 'mustache' );
const Nations = require('./Nations.js')
const Setup = {};
const nationsync = Nations.updateChains()
if (nationsync.status!=200){
console.log( '\x1b[31m Check your internet access, to setup this town we need to update the Nations. It seems we cannot do it' );
process.exit();
}
if( !fs.existsSync( '/etc/nginx/nginx.conf' ) ) {
console.log( '\x1b[31m Check documentation, nginx have to be installed on this server first, no /etc/nginx/nginx.conf available' );
process.exit();
}
if( !fs.existsSync( './nationchains/tribes/index/conf.json' ) ){
console.log( `\x1b[42m####################################\nWellcome into apxtrib, this is a first install.\nWe need to make this server accessible from internet subdomain.domain to current IP. This setup will create your unique tribeid, with an admin login user to let you connect to the parameter interface.\nCheck README's project to learn more. more.\n#####################################\x1b[0m` );
const townSetup = fs.readJsonSync( './app/setup/townSetup.json') ;
console.log( `Current setup conf from :./app/setup/townSetup.json\nChange with relevant setup data and rerun yarn setup` ) ;
console.log( townSetup )
const readline = require( 'readline' );
const rl = readline.createInterface( {
input: process.stdin,
output: process.stdout
} );
rl.question( 'This is the data from ./app/setup/townSetup.json used, is it correct to use as first install (Yes/no)?', function ( rep1 ) {
let quest = `This is a production install, please check that ${townSetup.townName}.${townSetup.nationName}.${townSetup.dns} IP is well redirect to tour server`;
if( rep1 !== "Yes" ) process.exit( 0 );
if( townSetup.dns == 'unchain' ) {
quest = `This is a development installation, please add in your /etc/hosts 127.0.0.1 ${townSetup.townName}.${townSetup.nationName}.${townSetup.dns} `;
}
rl.question( quest + '\nAre you sure to set this? (Yes/no)', function ( rep2 ) {
if( rep2 == "Yes" ) {
const Checkjson = Setup.check( townSetup );
if( Checkjson == "" ) {
const townconf=fs.readJsonSync('./app/setup/townconf.json')
// create tribes folder with townconf.json
const towndata={...townSetup,...townconf};
const Towns = require('./Towns');
const Tribes = require('./Tribes');
const Pagans = require('./Pagans');
if (!towndata.mayorid ) Pagans.create
Towns.create('./nationchains','./nationchains','towns',{...townSetup,...townconf});
//Nationschains.create(townSetup);
Tribes.create(townSetup);
// creer un lien symbolique vers /nationchains/ pour traiter les demandes via xworkon comme une tribe
//Setup.config( townSetup );
} else {
console.log( check );
}
} else {
console.log( 'Nothing done please, check setup/configsetup.json and answer twice Yes' )
}
rl.close();
} );
} );
rl.on( 'close', function () {
console.log( '\n Setup process ended' );
process.exit( 0 );
} );
} else {
console.log( 'Carefull you have already a config.js that is running. If you want to change remove config.js file and run again yarn setup' );
}
Setup.Checkjson = conf => {
var rep = "";
const nation_town=fs.readJsonSync('./nationchains/socialworld/objects/towns/searchindex/towns_nation_uuid.json');
if (!ObjectKeys(nation_town).includes(conf.nationName)){
rep+=`your nationName ${conf.nationName} does not exist you have to choose an existing one`;
}
if (nation_town[conf.nationName].includes(conf.townName)){
rep+=`This conf.townName already exist you have to find a unique town name per nation`;
}
const getnation = Odmdb.get('./nationchains/socialworld/objects','towns',[conf.NationName],[nationId])
//if getnation.data.notfound
conf.language.forEach( l => {
if( ![ "fr", "en", "it", "de", "sp" ].includes( l ) ) {
rep += l + " Only fr,en,it,de,sp are available \n";
}
} );
if( !fs.existsSync( `/home/${conf.sudoerUser}` ) ) {
rep += `/home/${conf.sudoerUser} does not exist, user has to be create with a /home on this server\n`;
}
try {
if ("true"== execSync("timeout 2 sudo id && sudo=\"true\" || sudo=\"false\";echo \"$sudo\"").toString().trim().split(/\r?\n/).slice(-1)) {
rep+=`${sudoerUser} is not sudoer please change this `;
} ;
}catch(err){
console.log(err);
rep+=" Check your user it seems to not be a sudoer"
}
if( conf.jwtsecret.length < 32 ) {
rep += "Your jwtsecretkey must have at least 32 characters"
}
if( conf.dns != 'unchain' && !dnsSync.resolve( `${conf.townName}.${conf.nationName}.${conf.dns}` ) ) {
rep += `\nresolving $${conf.townName}.${conf.nationName}.${conf.dns} will not responding valid IP, please setup domain redirection IP before runing this script`
}
return rep
};
Setup.config = ( townSetup ) => {
// Init this instance with a .config.js
Setup.configjs( townSetup );
// Create tribeid space + a user admin + webspace withe apxtrib webapp install
Setup.druidid( townSetup );
};
Setup.configjs = ( townSetup ) => {
// Set /config.js
let confapxtrib = fs.readFileSync( './setup/config.mustache', 'utf-8' );
fs.writeFileSync( './config.js', Mustache.render( confapxtrib, townSetup ), 'utf-8' );
if( fs.existsSync( './config.js' ) ) {
console.log( 'config.js successfully created.' );
} else {
console.log( "config.js not created, check what's wrong in tpl:", confapxtrib );
console.log( "for data :", townSetup );
process.exit();
}
};
Setup.druidid = ( townSetup ) => {
// create a tribeid with a user that will admin this instance into /tribes/tribeid /users
const config = require( '../config.js' );
// Need to do it on setup this is also done again in models/Tribes.js
console.log( `${config.tribes}/${townSetup.druidid}` )
fs.ensureDirSync( `${config.tribes}/${townSetup.druidid}` );
[ 'users', 'www', 'referentials', 'nationchains' ].forEach( r => {
fs.copySync( `${__base}/setup/tribes/apxtrib/${r}`, `${config.tribes}/${townSetup.druidid}/${r}` );
} )
/* const confcli = JSON.parse( Mustache.render( fs.readFileSync( `${__base}/setup/tribes/apxtrib/clientconf.mustache`, 'utf8' ), townSetup ) );
fs.outputJsonSync( `${config.tribes}/${townSetup.druidid}/clientconf.json`, confcli );
// Create a new tribeid + admin user for this tribeid
// with access to {druidid}:webapp as admin
*/
const Tribes = require( '../models/Tribes.js' );
const access = { app: {}, data: {} }
access.app[ `${townSetup.druidid}:webapp` ] = "admin";
access.data[ townSetup.druidid ] = { "users": "CRUDO", "referentials": "CRUDO", "www": "CRUDO" };
const createclient = Tribes.create( {
tribeid: townSetup.druidid,
genericpsw: townSetup.genericpsw,
lanquageReferential: townSetup.language,
useradmin: {
LOGIN: townSetup.login,
xlang: townSetup.language[ 0 ],
ACCESSRIGHTS: access
}
} );
if( createclient.status == 200 ) {
console.log( `Your tribeid domain was created with login : ${townSetup.login} and password: ${townSetup.genericpsw}, change it after the 1st login on https://${townSetup.subdomain}.${townSetup.domain}` );
// Create nginx conf for a first install
const confnginx = fs.readFileSync( './setup/nginx/nginx.conf.mustache', 'utf8' );
fs.outputFileSync( '/etc/nginx/nginx.conf', Mustache.render( confnginx, townSetup ), 'utf-8' );
// Create a spacedev for webapp of apxtrib
// that will be accesible in prod from https://subdomain.domain/ and in dev http://webapp.local.fr
const addspaceweb = Tribes.addspaceweb( {
setup: true,
dnsname: [ `${townSetup.subdomain}.${townSetup.domain}` ],
mode: townSetup.mode,
tribeid: townSetup.druidid,
website: 'webapp',
pageindex: "app_index_fr.html"
} );
if( addspaceweb.status == 200 ) {
console.log( `WELL DONE run yarn dev to test then yarn startpm2 ` )
}
} else {
console.log( 'Issue ', createclient )
}
}
module.exports = Setup;

311
app/models/Tags.js Executable file
View File

@@ -0,0 +1,311 @@
const fs = require( 'fs-extra' );
const path = require( 'path' );
const glob = require( 'glob' );
const moment = require( 'moment' );
// Check if package is installed or not to pickup the right config file
//const config = require( '../tribes/townconf.js' );
const config={}
const Tags = {};
/*
Keyword definition:
id: b64 encoded field that can be : email , uuid
tribeid: apiamaildigit client Id, (a folder /tribes/tribeid have to exist)
email: identifiant of a person
uuid: identifiant o
contact database
operationId: code name of an operation
messageId: code name of an templatesource have to exist in /datashared/templatesource/generic/messageId
*/
/*
Manage tag data collection
Popup survey manager
*/
Tags.info = ( data, req ) => {
//console.log('headers:', req.headers)
/*console.log('hostname', req.hostname)
console.log('req.ip', req.ip)
console.log('req.ips', req.ips)
console.log('req key', Object.keys(req))
*/
//console.log('req.rawHeaders', req.body)
data.useragent = `${req.headers['user-agent']}__${req.headers['accept-language']}__${req.headers['accept-encoding']}__${req.headers['connection']}`;
data.ips = req.ips;
data.ip = req.ip;
data.proxyip = req.connection.remoteAddress;
data.cookie = ""
Object.keys( req.headers )
.forEach( k => {
if( ![ 'user-agent', 'accept-language', 'accept-encoding', 'connection' ].includes( k ) ) {
data.cookie += `${k}__${req.headers['cookie']}|`
}
} )
//data.cookie = `${req.headers['cookie']}__${req.headers['upgrade-insecure-requests']}__${req.headers['if-modified-since']}__${req.headers['if-no-match']}__${req.headers['cache-control']}`;
return data
}
Tags.getfile = ( filename, req ) => {
const infotg = filename.split( '__' );
if( infotg.length < 3 && !fs.existsSync( `${config.tribes}/${infotg[1]}` ) ) {
return {
status: 400,
payload: { info: [ 'fileUnknown' ], model: 'UploadFiles' }
}
}
if( infotg[ 0 ] == "imgtg" ) {
fs.outputJson( `${config.tribes}/${infotg[1]}/tags/imgtg/${Date.now()}.json`, Tags.info( { filename: filename, messageId: infotg[ 2 ], operationId: infotg[ 3 ], identifiant: infotg[ 4 ] }, req ), function ( err ) {
if( err ) {
console.log( `Erreur de sauvegarde de tag:${filename}` )
}
} );
return {
status: 200,
payload: { moreinfo: "Declenche tag", filename: `${__base}/public/imgtg.png` }
}
}
return { status: 404, payload: {} }
}
Tags.savehits = ( req ) => {
if( !fs.existsSync( `${config.tribes}/${req.params.tribeid}` ) ) {
console.log( `Erreur d'envoi de tag sur ${req.params.tribeid} pour ${req.params.r}` );
return false;
} else {
const info = JSON.parse( JSON.stringify( req.body ) );
fs.outputJson( `${config.tribes}/${req.params.tribeid}/tags/hits/${Date.now()}.json`, Tags.info( info, req ), function ( err ) {
if( err ) {
console.log( `Erreur de sauvegarde de tag pour ${req.params.tribeid} check si /tags/hits et /tags/imgtg exist bien ` )
}
} );
}
return true;
}
Tags.dataloadstat = ( tribeid ) => {
/*
@TODO à appeler via une route pour agregation de tag
@TODO ajouter la suppression des fichiers hits traités qd le prog aura fait ses preuves en prod
@TODO corriger la prod pour que l'ip passe
Si on recharge plusieurs fois un hits (on ne le comptabilise pas si même r et même timestamps)
Manage tag info to agregate by user and by statistique
stats/data.json = {r:{
info:{useragent:"",ip:[]},
data:[ [timestamps, tit, cookie] ]
}
}
stats/graph.json = {"graphYears": {AAAA:#visites,"years":[AAAA,AAAA]},
"graphMonths": {AAAA:{"Jan":#visites,..},"monthsLabels":["Jan",..],"years":[AAAA,]},
"graphDaysOfWeek":{"labels":["Sun","Mon",...],"Sun":#visites},
"graphHoursOfDay":{"labels":["OOh","01h",...],"00h":#visites}
}
Pour tester des evolutions ou un client en dev (recuperer son repertoire de prod /tags )
ajouter Tags.dataloadstat('yes');
NODE_ENV=dev node ./models/Tags.js
*/
const agrege = {
data: {},
graph: {
visites: {
graphYears: { years: [] },
graphMonths: {
years: [],
monthsLabels: []
},
graphMonths: {
years: [],
monthsLabels: []
},
graphDayOfWeek: { labels: [] },
graphHourOfDay: { labels: [] }
},
visitors: {
graphMonths: {
years: [],
monthsLabels: []
}
}
}
};
try {
agrege.data = fs.readJsonSync( `${config.tribes}/${tribeid}/tags/stats/data.json`, "utf-8" );
agrege.graph = fs.readJsonSync( `${config.tribes}/${tribeid}/tags/stats/graph.json`, "utf-8" );
} catch ( err ) {
console.log( "ATTENTION tag reinitialisé en data.json et graph.json, s'il s'agit de 1ere connexion pas de pb. Le risque est de perdre les tag historiques" )
//return { status: 503, payload: { info: ['Errconfig'], model: 'Tags', moreinfo: `Il manque un ${config.tribes}/${tribeid}/tags/stats/data.json ou stats/graph.json` } }
}
glob.sync( `${config.tribes}/${tribeid}/tags/hits/*` )
.forEach( f => {
const hit = fs.readJsonSync( f );
const ts = parseInt( path.basename( f )
.split( ".json" )[ 0 ] );
//console.log(moment(ts).format('DD-MM-YYYY h:mm:ss'));
const tsm = moment( ts )
const year = tsm.format( 'YYYY' );
const month = tsm.format( 'MMM' );
const dayow = tsm.format( 'ddd' );
const hourod = tsm.format( 'HH' ) + "h";
let newvisitor = false;
let alreadydone = false;
//console.log(hit.r, ts)
// Agrege data pour # visiteur vs # de visiteur
if( agrege.data[ hit.r ] ) {
if( !agrege.data[ hit.r ].data.some( el => el[ 0 ] == ts ) ) {
//evite de charger plusieurs fois le même
agrege.data[ hit.r ].data.push( [ ts, hit.tit, hit.cookie ] )
} else {
alreadydone = true;
}
} else {
newvisitor = true;
agrege.data[ hit.r ] = {
info: { useragent: hit.useragent, ip: [ hit.ip ] },
data: [
[ ts, hit.tit, hit.cookie ]
]
}
}
if( !alreadydone ) {
if( newvisitor ) {
//traite Month
if( !agrege.graph.visitors.graphMonths[ year ] ) {
agrege.graph.visitors.graphMonths[ year ] = {}
agrege.graph.visitors.graphMonths.years.push( year )
}
if( agrege.graph.visitors.graphMonths[ year ][ month ] ) {
agrege.graph.visitors.graphMonths[ year ][ month ] += 1
} else {
agrege.graph.visitors.graphMonths[ year ][ month ] = 1
agrege.graph.visitors.graphMonths.monthsLabels.push( month )
}
}
//traite graphe Year #visite
if( agrege.graph.visites.graphYears[ year ] ) {
agrege.graph.visites.graphYears[ year ] += 1
} else {
agrege.graph.visites.graphYears[ year ] = 1
agrege.graph.visites.graphYears.years.push( year )
agrege.graph.visites.graphMonths[ year ] = {}
agrege.graph.visites.graphMonths.years.push( year )
}
//traite graphe Month
if( agrege.graph.visites.graphMonths[ year ][ month ] ) {
agrege.graph.visites.graphMonths[ year ][ month ] += 1
} else {
agrege.graph.visites.graphMonths[ year ][ month ] = 1
agrege.graph.visites.graphMonths.monthsLabels.push( month )
}
//traite graphe Days of week
if( agrege.graph.visites.graphDayOfWeek[ dayow ] ) {
agrege.graph.visites.graphDayOfWeek[ dayow ] += 1
} else {
agrege.graph.visites.graphDayOfWeek[ dayow ] = 1
agrege.graph.visites.graphDayOfWeek.labels.push( dayow )
}
//traite graphe Hour of day
if( agrege.graph.visites.graphHourOfDay[ hourod ] ) {
agrege.graph.visites.graphHourOfDay[ hourod ] += 1
} else {
agrege.graph.visites.graphHourOfDay[ hourod ] = 1
agrege.graph.visites.graphHourOfDay.labels.push( hourod )
}
}
} )
fs.outputJsonSync( `${config.tribes}/${tribeid}/tags/stats/data.json`, agrege.data, 'utf-8' );
fs.outputJsonSync( `${config.tribes}/${tribeid}/tags/stats/graph.json`, agrege.graph, 'utf-8' );
return { status: 200, payload: { info: [ 'Statsupdated' ], model: 'Tags' } }
}
//console.log(Tags.dataloadstat('yes'));
/*const ar = [
[1, 1],
[1, 2]
]
console.log(ar.some(el => el[0] == 1 && el[1] == 1))
console.log(ar.some(el => el == [1, 3]))
*/
Tags.nginxlog=(pathFile)=>{
/*
Read an nginx log file and return
@TODO standardiser le log nginx pour recuperer des données (IP,...)
@return {visites:{year:month:{day:number of visites}},
visitors:{year:month:{day: number of unique visitors for the day}}
year: number of unique visitors for the year}
month:month : number of unique visitors for the month]
}
*/
const log= fs.readFileSync(pathFile,'utf-8');
const logs=log.split('\n');
console.log(`nombre ligne ${logs.length}`)
const stat={visits:{},visitors:{}};
const visitor={}
var previousdt=moment();
var previoususeragent="";
console.log(moment(previousdt).format('DD/MMM/YYYY:hh:mm:ss'))
logs.forEach(l=>{
const elt= l.split('##');
const useragent = (elt[2] && elt[2].split("\" \"")[1]) ? elt[2].split("\" \"")[1]:"unknown";
//elt[0] = [30/Jun/2022:15:15:53 +0200]
const dttime = moment(elt[0].split(' ')[0].substring(1),'DD/MMM/YYYY:hh:mm:ss');
['visits','visitors'].forEach(ch=>{
if (!stat[ch][dttime.format('YYYY')]) stat[ch][dttime.format('YYYY')]={};
if (!stat[ch][dttime.format('YYYY')][dttime.format('MMM')]) stat[ch][dttime.format('YYYY')][dttime.format('MMM')]={}
if (!stat[ch][dttime.format('YYYY')][dttime.format('MMM')][dttime.format('DD')]) stat[ch][dttime.format('YYYY')][dttime.format('MMM')][[dttime.format('DD')]]=0
if (!visitor[dttime.format('YYYY')]) visitor[dttime.format('YYYY')]={};
if (!visitor[dttime.format('YYYY')][dttime.format('MMM')]) visitor[dttime.format('YYYY')][dttime.format('MMM')]={}
if (!visitor[dttime.format('YYYY')][dttime.format('MMM')][dttime.format('DD')]) visitor[dttime.format('YYYY')][dttime.format('MMM')][[dttime.format('DD')]]=[]
})
//console.log(elt[0].split(' ')[0]+ "####" + moment(elt[0],'DD/MMM/YYYY:hh:mm:ss').format('DD-MM-YYYY h:mm:ss'));
//console.log("EEEE"+moment(previousdt).format('DD/MMM/YYYY:hh:mm:ss') + "hhhhhhh" +moment(dttime).format('DD/MMM/YYYY:hh:mm:ss'))
//console.log(previousdt!=dttime)
if (!visitor[dttime.format('YYYY')][dttime.format('MMM')][dttime.format('DD')].includes(useragent)){
visitor[dttime.format('YYYY')][dttime.format('MMM')][dttime.format('DD')].push(useragent)
}
if (previoususeragent != useragent || parseInt(Math.round(previousdt.toDate().getTime()/100000)) != parseInt(Math.round(dttime.toDate().getTime()/100000))) {
/*console.log("###########################################################");
console.log(`${previousdt.toDate().getTime()} != ${dttime.toDate().getTime()}`);
console.log(moment(previousdt).format('DD/MMM/YYYY:hh:mm:ss') + "hhhhhhh" + moment(dttime).format('DD/MMM/YYYY:hh:mm:ss'))
console.log(`useragent: ${useragent} previoususeragent: ${previoususeragent}`);
*/
stat['visits'][dttime.format('YYYY')][dttime.format('MMM')][[dttime.format('DD')]]+=1
previousdt=dttime;
previoususeragent=useragent;
}else{
//console.log("identique compte pas" )
}
})
Object.keys(visitor).forEach(y=>{
var uniqvisitoryear=[];
Object.keys(visitor[y]).forEach(m=>{
var uniqvisitormonth=[];
Object.keys(visitor[y][m]).forEach(d=>{
uniqvisitormonth = [...uniqvisitormonth, ...visitor[y][m][d]]
stat.visitors[y][m][d] = visitor[y][m][d].length
})
uniqvisitoryear=[...uniqvisitoryear, ...uniqvisitormonth]
stat.visitors[y][m][m]=uniqvisitormonth.length;
})
stat.visitors[y][y]=uniqvisitoryear.length
})
return {status:200, data:stat}
}
Tags.statin2D=(stat,pathfile)=>{
if (stat.status==200){
stat=stat.data
}else {
return stat
}
let csv="type;year;month;day;Number\n\r";
["visits", "visitors"].forEach(ch=>{
Object.keys(stat[ch]).forEach(y=>{
Object.keys(stat[ch][y]).forEach(m=>{
Object.keys(stat[ch][y][m]).forEach(d=>{
csv+=`${ch};${y};${m};${d};${stat[ch][y][m][d]}\n\r`
})
})
})
})
fs.outputFileSync(pathfile,csv);
return {status:200, info:"fileready", moreinfo:pathfile}
}
console.log(Tags.statin2D(Tags.nginxlog('/home/phil/Documents/nginx/presentation.capsthd.access.log'),'/home/phil/Documents/nginx/stat.csv'));
module.exports = Tags;

646
app/models/Toolsbox.js Executable file
View File

@@ -0,0 +1,646 @@
/* eslint-disable no-useless-escape */
const fs = require("fs");
const path = require("path");
const bcrypt = require("bcrypt");
const moment = require("moment");
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 apxtrib project (see /package.json to get email).\n We'll add to the roadmap to add it."
);
/**
* EMAIL
*/
/* const validateEmail = 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 validatePassword = pwd => {
const regExp = new RegExp(
/^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)(?=.*[$@$!%*?&.])[A-Za-z\d$@$!%*?&.{}:|\s]{8,}/
);
return regExp.test(pwd);
};
const filterInvalidInArray = (array, validate) =>
array ? array.filter(el => !validate(el)) : undefined; // return undefined when every elements is valid
/**
* POSTAL CODE
*/
/*
const validatePostalCode = postalCode =>
/(^\d{5}$)|(^\d{5}-\d{4}$)/.test(postalCode);
/**
* PHONE
*/
/* const validatePhoneNumber = phoneNumber =>
/((^0[1-9]|\+[0-9]{3})([-. ]?[0-9]{2}){4}$)/.test(phoneNumber);
const correctPhoneNumber = phone =>
phone[0] === '0' ? '+33' + phone.substr(1) : phone;
const Checkjson = (appProfil, referential, data) => {
// @TODO get a referentiel per object then check data validity and allowed access
// need to add referentiel manager
const invalidefor = [];
let updateDatabase = false;
Object.keys(data).forEach(field => {
switch (field) {
case 'token':
updateDatabase = true;
break;
case 'email':
if (!validateEmail(data.email)) {
invalidefor.push('ERREMAIL:' + field);
} else {
updateDatabase = true;
}
break;
case 'password':
if (!validatePassword(data.password)) {
invalidefor.push('ERRPWD:' + field);
} else {
data.password = bcrypt.hash(data.password, config.saltRounds);
updateDatabase = true;
}
break;
}
});
return { invalidefor, data, updateDatabase };
};
*/
//Permet d'attendre en milliseconde
// s'utilise avec async ()=>{
// await sleep(2000)
//}
utils.sleep = (ms) => {
return new Promise((resolve) => setTimeout(resolve, ms));
};
utils.normalize = {};
utils.normalize.telephonefr = (phone) => {
phone = phone.trim().replace(/[- .]/g, "");
if (
Checkjson.schema.properties.format.telephoenfr(phone) &&
phone.length == 10 &&
phone[0] == "0"
) {
phone = "+33 " + phone.substring(1);
}
return phone;
};
utils.normalize.zfill10 = (num) => {
let s = num + "";
while (s.length < 10) s = "0" + s;
return s;
};
utils.generemdp = (nbpos, fromchar) => {
if (!fromchar) {
const fromchar = "ABCDEFGHIJKLMNPQRSTUVWZY123456789";
}
//const chaine = "ABCDEFGHIJKLMNPQRSTUVWZY123456789";
let mdp = "";
for (var i = 0; i < nbpos; i++) {
var pos = Math.floor(Math.random() * fromchar.length);
mdp += fromchar.substring(pos, pos + 1);
}
return mdp;
};
utils.generecompteur = (filecpt, typeincrement) => {
let file = `${filecpt}/${typeincrement}.json`;
let prefix = "";
if ((typeincrement = "ANNEESEMAINE")) {
file = `${filecpt}/${typeincrement}${moment().format(
"YYYY"
)}${moment().format("WW")}.json`;
prefix = `${moment().format("YYYY")}${moment().format("WW")}`;
}
let num = 1;
try {
num = parseInt(fs.readFileSync(file, "utf8")) + 1;
} catch (err) {
console.log("Nouveau compteur incrementale ", file);
}
fs.writeFileSync(file, num, "utf8");
return prefix + num;
};
/**
* CSV
*/
utils.json2csv = (jsondata, options, callback) => {
// uniquement json = [{niv1:val,niv1:[liste of val]}]
// console.log('_________________________');
// console.log(jsondata)
// console.log('_________________________');
if (jsondata.length == 0) {
return callback("Empty json", null);
}
if (!options.retln) options.retln = "\n";
if (!options.sep) options.sep = ";";
if (!options.arraysplitsep) options.arraysplitsep = ",";
if (!options.replacespecialcarJson2Csv) {
options.replacespecialcarJson2Csv = [];
} else {
if (typeof options.replacespecialcarJson2Csv == "string") {
//permet de passer des regex en string
options.replacespecialcarJson2Csv = eval(
options.replacespecialcarJson2Csv
);
}
}
let etat = "";
let csv = "";
let entete = "";
let prem = true;
for (const j in jsondata) {
// console.log(jsondata[j])
for (const c in options.champs) {
if (prem) {
entete += options.champs[c] + options.sep;
}
if (jsondata[j][options.champs[c]]) {
if (options.array.indexOf(options.champs[c]) > -1) {
csv +=
jsondata[j][options.champs[c]].join(options.arraysplitsep) +
options.sep;
} else {
let currentValue = "";
if (jsondata[j][options.champs[c]])
currentValue += jsondata[j][options.champs[c]];
options.replacespecialcarJson2Csv.forEach((re) => {
//console.log(currentValue)
currentValue = currentValue.replace(re[1], re[0]);
});
csv += currentValue + options.sep;
}
} else {
csv += options.sep;
}
}
csv = csv.substring(0, csv.length - 1) + options.retln;
if (prem) {
prem = false;
entete = entete.substring(0, entete.length - 1) + options.retln;
// console.log(entete)
}
}
// return entete + csv;
if (etat == "") {
return callback(null, entete + csv);
} else {
return callback(etat, null);
}
};
/**
* Get headers from first line of CSV
* @param {array} lines array of string which contains each csv lines
* @return {array} string array of headers
*/
utils.getHeaders = (lines, sep) =>
lines[0].split(sep).map((i) => i.replace(/"/g, ""));
/**
* [csv2json description]
* @param {object} csv object of csv file that has been read
* @param {object} options object containing csv options, headers, ...
{retln:'code de retour de ligne \n ou \n\r',
sep:'code to split cells',
champs:[ch1,ch2,...] catch only those field,
array:[ch1, ] can have more than one field champs with same name then data are push into an array }
* @param {Function} callback callback function
* @return {callback} - return an error if error, else return json
it convert a csv file into a json = [{field:value}]
Usage example:
fiche.csv2article = (err, fiche) => {
if (!err) {
console.log(fiche)
}
}
utils.csv2json(fs.readFileSync('./devdata/tribee/aubergenville/infoexterne/localbusiness.csv', 'utf-8'), {
retln: "\n",
sep: ";",
champs: ["NOM", "OBJET", "ADRESSE_PRO", "CP_PRO", "VILLE_PRO", "ZONE", "PHONE_PRO", "HORAIRESDESC", "HORAIREDATA", "URL", "FACEBOOK", "INSTA", "EMAIL_PRO", "IMG", "TAG"],
array: ["TAG", "PHONE_PRO", "EMAIL_PRO"]
}, fiche.csv2article)
*/
utils.replacecarbtweendblquote = (csv, car, carremplacant) => {
/*
return csv text with any car betwenn 2 " by CARSEPARATOR
*/
let newcsv = "";
let txtencours = "";
let flagouvert = false;
const sepreg = new RegExp(`${car}`, "gmi");
for (let j = 0; j < csv.length; j++) {
//if((csv[j] == "\"" && csv[j + 1] && csv[j + 1] != "\"") || (csv[j] == "\"" && csv[j - 1] && csv[j - 1] != "\"") || (csv[j] == "\"" && csv[j - 1] && csv[j - 2] && csv[j - 1] != "\"" && csv[j - 2] != "\"")) {
if (csv[j] == '"') {
if (flagouvert) {
// on cherche à ferme une chaine de texte
if (csv[j + 1] == '"') {
//on a "" consecutif qu'on remplace par "" et on fait j+1
txtencours += '""';
j++;
} else {
// on a bien une fermeture
flagouvert = false;
newcsv += txtencours.replace(sepreg, carremplacant);
txtencours = '"';
}
} else {
// on ouvre une chaine
flagouvert = true;
//on met le contenu précédent ds newcsv
newcsv += txtencours;
txtencours = '"';
}
//} else if((csv[j] !== "\n") && (csv[j + 1] && csv[j] + csv[j + 1] !== "\n\r")) {
} else if (csv[j] !== "\n") {
txtencours += csv[j];
// } else if((csv[j] == "\n") || (csv[j + 1] && csv[j] + csv[j + 1] == "\n\r")) {
} else if (csv[j] == "\n") {
if (!flagouvert) txtencours += "\n";
}
}
return newcsv + txtencours;
};
utils.analysestring = (string) => {
let buftxt = "";
let bufcode = "";
let i = 0;
let avecRL = false;
for (let p = 0; p < string.length; p++) {
if (string[p].charCodeAt() == 10) {
buftxt += "[RL]";
avecRL = true;
} else {
buftxt += string[p];
}
bufcode += "-" + string[p].charCodeAt();
if (i == 20) {
if (avecRL) {
console.log(`${buftxt} - ${bufcode}`);
} else {
console.log(`${buftxt} ---- ${bufcode}`);
}
i = 0;
buftxt = "";
bufcode = "";
avecRL = false;
}
i++;
}
};
const txtstring = `32932,BK_F2F_B_COM_10x1H-09,"My Communication Workshop ""Session N°9 - 1H""","<p>&nbsp;</p>
<table>
<tbody>
<tr>
<td>
<p>Learner who needs to develop their ability to communicate effectively at work, both in writing and speaking</p>
</td>
</tr>
</tbody>
</table>",,english,2,0,,2,0,classroom,"0000-00-00 00:00:00","0000-00-00 00:00:00",0000-00-00,0000-00-00,https://www.yesnyoulearning.com/lms/index.php?r=player&course_id=32932,1101,,"BUSINESS KEYS",0,
32933,BK_F2F_B_COM_10x1H-10,"My Communication Workshop Session N°10 - 1H","<p>&nbsp;</p>
<table>
<tbody>
<tr>
<td>
<p>Learner who needs to develop their ability to communicate effectively at work, both in writing and speaking</p>
</td>
</tr>
</tbody>
</table>",,english,2,0,,2,0,classroom,"0000-00-00 00:00:00","0000-00-00 00:00:00",0000-00-00,0000-00-00,https://www.yesnyoulearning.com/lms/index.php?r=player&course_id=32933,1101,,"BUSINESS KEYS",0,
32934,BK_F2F_B_JOB_10x1H-01,"My Job Search Workshop Session N°1 - 1H","<p>PACK JOB SEARCH</p>",,english,2,0,,2,0,classroom,,,0000-00-00,0000-00-00,https://www.yesnyoulearning.com/lms/index.php?r=player&course_id=32934,1108,,,0,
32935,BK_F2F_B_JOB_10x1H-02,"My Job Search Workshop Session N°2 - 1H","<p>PACK JOB SEARCH</p>",,english,2,0,,2,0,classroom,,,0000-00-00,0000-00-00,https://www.yesnyoulearning.com/lms/index.php?r=player&course_id=32935,1108,,,0,`;
//utils.analysestring(txtstring)
//console.log(utils.replacecarbtweendblquote(txtstring, ",", 'CARSEPARATOR')
// .split("\n")[0].split(","))
utils.csv2json = (csv, options, callback) => {
// EN CAS DE PB AVEC UN FICHIER EXCEL RECALCITRANT
// l'ouvrir dans calc linux et sauvegarder csv utf8, ; , " enregistrer le contenu de la cellule comme affiché
console.log("\n--------------- CSV2JSON ---------------\n");
// Default CSV options
if (!options.retln) options.retln = "\n";
if (csv.indexOf("\n\r") > -1) options.retln = "\n\r";
if (!options.sep) options.sep = ";";
//gestion d un separateur dans une chaine de texte
//const regseptext = new RegExp(`${options.sep}(?!(?:[^"]*"[^"]*")*[^"]*$)`, 'gm');
//csv = csv.replace(regseptext, "CARACSEPAR");
// csv = utils.replacecarbtweendblquote(csv, options.retln, "RETLIGNE")
csv = utils.replacecarbtweendblquote(csv, options.sep, "CARSEPARATOR");
if (!options.replacespecialcarCsv2Json) {
options.replacespecialcarCsv2Json = [];
} else {
if (typeof options.replacespecialcarCsv2Json == "string") {
//permet de passer des regex en string
options.replacespecialcarCsv2Json = eval(
options.replacespecialcarCsv2Json
);
}
}
const result = [];
const lines = csv.split(options.retln);
const headers = utils.getHeaders(lines, options.sep);
let unknownHeaders = "";
//console.log('headers', headers)
//console.log('options.champs', options.champs)
headers.forEach((header) => {
// Si un header n'est pas présent dans la liste des champs prédéfinis
// on l'ajoute aux champs inconnus
if (options.champs.indexOf(header) === -1) {
unknownHeaders += `${header}, `;
}
});
if (unknownHeaders !== "") {
const errorMsg = `CSV2JSON() - Champs inconnus : ${unknownHeaders}`;
return callback(errorMsg, null);
}
lines.forEach((line, index) => {
// Skip headers line or empty lines
if (index === 0 || line.replace(/\s/g, "").length === 0) {
return;
}
// pour debuguer on met origincsv pour voir la ligne d'origine
const currentLineData = { origincsv: line, linenumber: index };
const currentLine = line.split(options.sep); // Current string in the line
for (let j = 0; j < headers.length; j++) {
// Si la ligne n'est pas vide
if (currentLine[j]) {
// On clean le champs
// ajout eventuel de modification de caracter reservé ; dans les libelléetc...
let currentValue = currentLine[j].trim();
//on transforme le caractere separateur modifié entre double quote
currentValue = currentValue.replace("CARSEPARATOR", options.sep);
options.replacespecialcarCsv2Json.forEach((re) => {
currentValue = currentValue.replace(re[0], re[1]);
});
// Si le header est un email
if (headers[j].includes("EMAIL")) {
// Supprimer tous les espaces
currentValue = currentLine[j].replace(/\s/g, "");
}
// on check si le chamos doit être numerique
if (options.numericfield.includes(headers[j])) {
currentValue = currentLine[j].replace(/\,/g, ".");
try {
const test = parseFloat(currentValue);
} catch (er) {
return callback(
`${headers[j]} contiens la valeur -${currentValue}- et devrait être numerique`,
null
);
}
}
if (currentValue) {
// Si le header actuel est de type array
// Cela signifie que le header apparaît plusieurs fois dans le CSV
// et que les valeurs correspondantes à ce header
// doivent être mis dans un array
if (options.array && options.array.indexOf(headers[j]) > -1) {
// Si le tableau pour ce header n'existe pas on le crée
if (!currentLineData[headers[j]]) {
currentLineData[headers[j]] = [];
}
if (options.arraysplitsep) {
currentValue.split(options.arraysplitsep).forEach((v) => {
currentLineData[headers[j]].push(v);
});
} else {
currentLineData[headers[j]].push(currentValue);
}
} else {
// Si un header est déjà présent pour la ligne
// alors que il n'est pas spécifié comme étant un array
// on retourne une erreur
if (currentLineData[headers[j]]) {
const errorMsg = `Le champ ${headers[j]} est présent plusieurs fois alors qu'il n'est pas spécifié comme étant un array !`;
return callback(errorMsg, null);
}
currentLineData[headers[j]] = currentValue;
}
}
}
}
result.push(currentLineData);
});
return callback(null, result);
};
/**
* [csvparam2json description]
* @param {object} csv object of csv file that has been read
* @param {object} options object containing csv options, headers, ...
{retln:'code de retour de ligne \n ou \n\r',
sep:'code to split cells',
champs:[ch1,ch2,...] catch only those field,
array:[ch1, ] can have more than one field champs with same name then data are push into an array }
* @param {Function} callback callback function
* @return {callback} - return an error if error, else return json
it converts a csv with 3 column col1;col2;col3 in a json in a tree
if in col1 we have __ => then it splits a leaf
col1 = xxxx__yyyy ; col2 = value ; col3 = comment that is ignored
return data = {xxxx:{yyyy:value}}
col1 = xxxx; col2 = value; col3 = comment ignored
return data = {xxxx:value}
Usage example:
fiche.csvparam2article = (err, fiche) => {
if (!err) {
console.log(fiche)
}
}
utils.csvparam2json(fs.readFileSync('./devdata/tribee/aubergenville/infoexterne/localbusiness.csv', 'utf-8'), {
retln: "\n",
sep: ";",
champs: ["NOM", "OBJET", "ADRESSE_PRO", "CP_PRO", "VILLE_PRO", "ZONE", "PHONE_PRO", "HORAIRESDESC", "HORAIREDATA", "URL", "FACEBOOK", "INSTA", "EMAIL_PRO", "IMG", "TAG"],
array: ["TAG", "PHONE_PRO", "EMAIL_PRO"]
}, fiche.csv2article)
*/
utils.csvparam2json = (csv, options, callback) => {
console.log("\n--------------- CSVPARAM2JSON ---------------\n");
let etat = "";
const param = {};
if (!options.retln) {
options.retln = "\n";
}
if (csv.indexOf("\n\r") > -1) {
options.retln = "\n\r";
}
if (!options.sep) {
options.sep = ";";
}
if (!options.seplevel) {
options.seplevel = "__";
}
if (!options.replacespecialcarCsv2Json) {
options.replacespecialcarCsv2Json = [];
} else {
if (typeof options.replacespecialcarCsv2Json == "string") {
//permet de passer des regex en string
options.replacespecialcarCsv2Json = eval(
options.replacespecialcarCsv2Json
);
}
}
const lines = csv.split(options.retln);
for (let i = 0; i < lines.length; i++) {
const infol = lines[i].split(options.sep);
//console.log(infol)
if (infol[0].length > 4 && infol.length < 2) {
// si le 1er element à plus de 4 caractere et s'il y a moins de 3 colonnes c'est qu'il y a un pb
etat += `Erreur sur ${lines[i]} moins de 3 column separé par ${options.sep}`;
continue;
}
// On ajoute ici la gestion de tous les caracteres spéciaux
// reservées pour le csv ; ' etc..'
if (infol[1] && infol[1] + "" == infol[1]) {
options.replacespecialcarCsv2Json.forEach((re) => {
//console.log("gggggggggggggggggggg", infol[1])
infol[1] = infol[1].replace(re[0], re[1]);
});
// console.log(infol[1])
infol[1] = infol[1].replace(/'|/g, '"');
//console.log(infol[1])
if (infol[1].toLowerCase() === "true") {
infol[1] = true;
} else if (infol[1].toLowerCase() === "false") {
infol[1] = false;
}
}
console.log(infol[1]);
//supprime des lignes vides
if (infol[0] == "") continue;
if (infol[0].indexOf(options.seplevel) == -1) {
param[infol[0]] = infol[1];
continue;
} else {
const arbre = infol[0].split(options.seplevel);
switch (arbre.length) {
case 1:
param[arbre[0]] = infol[1];
break;
case 2:
if (arbre[1] != "ARRAY") {
if (!param[arbre[0]]) param[arbre[0]] = {};
param[arbre[0]][arbre[1]] = infol[1];
} else {
if (!param[arbre[0]]) param[arbre[0]] = [];
//console.log('aff', infol[1].substring(1, infol[1].length - 1).replace(/""/g, '"'))
eval("result=" + infol[1]);
//.substring(1, infol[1].length - 1).replace(/""/g, '"'))
param[arbre[0]].push(result);
}
break;
case 3:
if (arbre[2] != "ARRAY") {
if (!param[arbre[0]]) param[arbre[0]] = {};
if (!param[arbre[0]][arbre[1]]) param[arbre[0]][arbre[1]] = {};
param[arbre[0]][arbre[1]][arbre[2]] = infol[1];
} else {
if (!param[arbre[0]]) param[arbre[0]] = {};
if (!param[arbre[0]][arbre[1]]) param[arbre[0]][arbre[1]] = [];
//eval("result = \"test\"");
//console.log(result);
eval("result=" + infol[1]);
//.substring(1, infol[1].length - 1).replace(/""/g, '"'))
param[arbre[0]][arbre[1]].push(result);
}
break;
case 4:
if (arbre[3] != "ARRAY") {
if (!param[arbre[0]]) param[arbre[0]] = {};
if (!param[arbre[0]][arbre[1]]) param[arbre[0]][arbre[1]] = {};
if (!param[arbre[0]][arbre[1]][arbre[2]])
param[arbre[0]][arbre[1]][arbre[2]] = {};
param[arbre[0]][arbre[1]][arbre[2]][arbre[3]] = infol[1];
} else {
if (!param[arbre[0]]) param[arbre[0]] = {};
if (!param[arbre[0]][arbre[1]]) param[arbre[0]][arbre[1]] = {};
if (!param[arbre[0]][arbre[1]][arbre[2]])
param[arbre[0]][arbre[1]][arbre[2]] = [];
eval("result=" + infol[1]);
//.substring(1, infol[1].length - 1).replace(/""/g, '"'))
param[arbre[0]][arbre[1]][arbre[2]].push(result);
break;
}
default:
break;
}
}
}
// JSON.parse(JSON.stringify(param))
console.log(
"kkkkkkkkkkkkkkkkkk",
param["catalogue"]["filtrecatalog"]["searchengine"]
);
if (etat == "") {
return callback(null, JSON.parse(JSON.stringify(param)));
} else {
return callback(etat, null);
}
};
utils.levenshtein = (a, b) => {
if (a.length === 0) return b.length;
if (b.length === 0) return a.length;
let tmp, i, j, prev, val, row;
// swap to save some memory O(min(a,b)) instead of O(a)
if (a.length > b.length) {
tmp = a;
a = b;
b = tmp;
}
row = Array(a.length + 1);
// init the row
for (i = 0; i <= a.length; i++) {
row[i] = i;
}
// fill in the rest
for (i = 1; i <= b.length; i++) {
prev = i;
for (j = 1; j <= a.length; j++) {
if (b[i - 1] === a[j - 1]) {
val = row[j - 1]; // match
} else {
val = Math.min(
row[j - 1] + 1, // substitution
Math.min(
prev + 1, // insertion
row[j] + 1
)
); // deletion
}
row[j - 1] = prev;
prev = val;
}
row[a.length] = prev;
}
return row[a.length];
};
utils.testinarray = (array, arrayreferent) => {
// au moins un element de array existe dans arryreferent
let exist = false;
if (arrayreferent) {
//console.log('arrrrrrrrrrrrrrr', arrayreferent)
array.forEach((e) => {
//console.log(e)
if (arrayreferent.includes(e)) exist = true;
});
}
return exist;
};
/*
DIRECTORY
*/
const isDirectory = (source) => fs.lstatSync(source).isDirectory();
const getDirectories = (source) =>
fs
.readdirSync(source)
.map((name) => path.join(source, name))
.filter(isDirectory);
module.exports = utils;

13
app/models/Towns.js Normal file
View File

@@ -0,0 +1,13 @@
const bcrypt = require( 'bcrypt' );
const fs = require( 'fs-extra' );
const glob = require( 'glob' );
const moment = require( 'moment' );
const jwt = require( 'jwt-simple' );
const UUID = require( 'uuid' );
const config = require( '../tribes/townconf.js' );
const Checkjson = require( `./Checkjson.js`);
const Towns = {};
module.exports= Towns;

351
app/models/Tribes.js Executable file
View File

@@ -0,0 +1,351 @@
const bcrypt = require( 'bcrypt' );
const fs = require( 'fs-extra' );
const path = require( 'path' );
const glob = require( 'glob' );
const Mustache = require( 'mustache' );
const execSync = require( 'child_process' )
.execSync;
const dnsSync = require( 'dns-sync' );
const jwt = require( 'jwt-simple' );
const moment = require( 'moment' );
const UUID = require( 'uuid' );
const Pagans = require( './Pagans.js' );
const config = require( '../tribes/townconf' );
const Checkjson = require( `./Checkjson.js`);
/*
tribeid manager
/tribes/tribeid
Manage a tribeid space
* create
* update by managing option and contract
* delete a tribeid
* check accountability and
*/
const Tribes = {};
Tribes.init = () => {
console.group( 'init Tribes' );
let tribeids = [];
let routes = glob.sync( './routes/*.js' )
.map( f => {
return { url: `/${path.basename(f,'.js')}`, route: f }
} );
let DOMs = [];
let appname = {};
TribesGlobalConfig = glob.sync( `${config.tribes}/**/clientconf.json` )
.map( f => {
const conf = fs.readJSONSync( f );
// check if plugins exist and add it in .plugins of each tribeid conf
conf.plugins = glob.sync( `${config.tribes}/${conf.tribeid}/plugins/**/package.json` )
.map( p => {
const pack = fs.readJsonSync( p, 'utf8' );
routes.push( { url: `/${pack.name}`, route: `${config.tribes}/${conf.tribeid}/plugins/${pack.name}/route.js` } );
return pack;
} );
//Add here any other info to get a global view and init
//...
tribeids.push( conf.tribeid );
DOMs = [ ...new Set( [ ...DOMs, ...conf.allowedDOMs ] ) ];
if( conf.website ) appname[ conf.tribeid ] = Object.keys( conf.website )
return conf;
} );
// store global conf fofs.existsSync( `${config.tmp}/clientconfglob.json` )r sharing to other api
fs.outputJsonSync( `${config.tmp}/clientconfglob.json`, TribesGlobalConfig, {
spaces: 2
} );
return { tribeids, routes, DOMs, appname }
}
Tribes.create = ( data ) => {
/* data = clientconf.json
{
"tribeid": "apxtrib",
"genericpsw": "Trze3aze!",
"website": {
"presentation":"https://www.apxtrib.org",
"webapp": "https://webapp.apxtrib.org"
},
"allowedDOMs": ["local.fr", "localhost:9002", "ndda.fr", "apxtrib.org"],
"clientname": "apxtrib",
"clientlogo": "",
"geoloc": [],
"useradmin": {PUBKEY:"",EMAIL:"",LOGIN:"adminapxtrib",UUID:"adminapxtrib"},
"smtp": {
"emailFrom": "support@apxtrib.org",
"emailcc": [],
"service": "gmail",
"auth": {
"user": "antonin.ha@gmail.com",
"pass": "Ha06110"
}
},
"accepted-language": "fr,en",
"langueReferential": ["fr"]
}
What about:
"tribeid": same than the folder where all the client's file are stored
"genericpsw": a generic password for new user need upper lowercase number ans special char
"dnsname": a domain name belonging to the client
"subdns": "www", a sub domain subdns.dnsname give a public web access to
"website": { keywebsite:url}, give access to config.tribes/tribeid/www/keywebsite/index.html,
"allowedDOMs": ["local.fr", "localhost:9002", "nnda.fr"], //for CORS, @TODO generate from prévious URL this allow this apxtrib instance to be accessible
"clientname": Name of the organisation if any,
"clientlogo": logo of the organisation if any,
"geoloc": [], if any
"useradmin": { this is the 1st user create automaticaly to make gui available for the 1st user
"PUBKEY":public key to be authentify without an email,
"EMAIL":user email, we need at least one of authentification set up after the user can use both or only one
"LOGIN": login to use for access admintribeid,
"UUID": unique id normaly UUID but a uuid admintribeid is the same person in any apxtrib instance so we use it by convention.
"xlang": lang used by this user
},
"smtp": { smtp used to send email by nodemailer lib basic example with a google account
"emailFrom": "support@xx.fr",
"emailcc": [],
"service": "gmail",
"auth": {
"user": "antonin.ha@gmail.com",
"pass": "Ha06110"
}
},
"accepted-language": "fr,en", list of accepted-language in terme of http request.
"langueReferential": ["fr"], list of the text that have to be translate in referentials
}
*/
//update tmp/confglog.json
const dataclient = Tribes.init();
//return in prod all instance apxinfo={tribeids:[],logins:[]}
// in dev return only local
//check tribeid name is unique
console.log( 'liste des tribeid', dataclient.tribeids )
if( dataclient.tribeids.includes( data.tribeid ) ) {
return { status: 403, payload: { model: "client", info: [ 'tribeidalreadyexist' ] } }
}
//loginsglob = {login:tribeid}
let loginsglob = {};
if( fs.existsSync( `${config.tmp}/loginsglob.json`, 'utf-8' ) ) {
loginsglob = fs.readJsonSync( `${config.tmp}/loginsglob.json`, 'utf-8' );
}
const logins = Object.keys( loginsglob );
if( logins.includes( data.useradmin.login ) ) {
return { status: 403, payload: { model: "client", info: [ 'loginalreadyexist' ] } }
}
fs.ensureDirSync( `${config.tribes}/${data.tribeid}` );
[ 'users', 'www', 'referentials', 'nationchains' ].forEach( r => {
fs.copySync( `${__base}/setup/tribes/apxtrib/${r}`, `${config.tribes}/${data.tribeid}/${r}` );
} )
fs.outputJsonSync( `${config.tribes}/${data.tribeid}/clientconf.json`, data );
const confcli = JSON.parse( Mustache.render( fs.readFileSync( `${__base}/setup/tribes/apxtrib/clientconf.mustache`, 'utf8' ), data ) );
fs.outputJsonSync( `${config.tribes}/${data.tribeid}/clientconf.json`, confcli );
return Pagans.createUser( {
xpaganid: "setup",
xworkon: data.tribeid,
xlang: data.useradmin.xlang
}, data.useradmin );
};
Tribes.archive = ( tribeid ) => {
//A faire zip un repertoire tribeid dans
// remove tribeid de data ou devdata
try {
fs.moveSync( `${config.tribes}/${tribeid}`, `${config.archivefolder}/${tribeid}` );
//update apxtribenv
Tribes.init();
return { status: 200, payload: { info: [ 'deletetribeidsuccessfull' ], models: 'Tribes', moreinfo: "TODO see in Tribes.archive" } }
} catch ( err ) {
console.log( "Erreur d'archivage", err )
return { status: 403, payload: { info: [ 'archiveerror' ], models: 'Tribes', moreinfo: err } }
}
}
////////////// Manage file for Tribes
Tribes.checkaccessfolder = ( folder, typeaccessrequested, useraccessrights, useruuid ) => {
// check folder right
}
Tribes.checkaccessfiles = ( listfile, typeaccessrequested, useraccessrights, useruuid ) => {
// @listfile to check accessright on file or folder
// @typeaccessrequested on files R read or download, U for pdate, D for delete , O for owned a Owner has all rights RUD on its files
// @useraccessright from its account /userd/uuid.json
// @useruuid public uuid user
// return {'ok':[file auhtorized],'ko':[files not authorized]}
const checkauthlistfile = { 'ok': [], 'ko': [] }
let structf = []
let inforep = { file: {}, dir: {} }
let done;
for( const f of listfile ) {
done = false;
if( !fs.existsSync( `${config.tribes}/${f}` ) ) {
done = true;
checkauthlistfile.ko.push( f )
console.log( `${f} file does not exist` )
} else {
structf = f.split( '/' );
}
//on ckeck tribeid existe / tribeid/object/
if( !done &&
useraccessrights.data[ structf[ 0 ] ] &&
useraccessrights.data[ structf[ 0 ] ][ structf[ 1 ] ] &&
useraccessrights.data[ structf[ 0 ] ][ structf[ 1 ] ].includes( typeaccessrequested ) ) {
done = true;
checkauthlistfile.ok.push( f );
} else {
// check if in folder we have a.info.json .file[f].shared{useruuid:'CRUDO'}
console.log( 'structf', structf )
if( fs.existsSync( `${config.tribes}/${structf.slice(0,-1).join('/')}/.info.json` ) ) {
inforep = fs.readJsonSync( `${config.tribes}/${structf.slice(0,-1).join('/')}/.info.json`, 'utf8' )
}
console.log( `no accessrights for ${f} for ${useruuid} ` )
}
if( !done && inforep.file[ f ] && inforep.file[ f ] && inforep.file[ f ].shared && inforep.file[ f ].shared[ useruuid ] && inforep.file[ f ].shared[ useruuid ].includes( typeaccessrequested ) ) {
done = true;
checkauthlistfile.ok.push( f )
}
// If no authorization then ko
if( !done ) {
checkauthlistfile.ko.push( f )
}
} // end loop for
//console.log( 'checkauthlistfile', checkauthlistfile )
return checkauthlistfile;
}
Tribes.dirls = ( tribeid, dir ) => {
/*
Return list of file into tribeid/dir
*/
let comment = { src: `${tribeid}/${dir}`, file: {}, dir: {} };
if( fs.existsSync( `${config.tribes}/${tribeid}/${dir}/.info.json` ) ) {
comment = fs.readJsonSync( `${config.tribes}/${tribeid}/${dir}/.info.json`, 'utf-8' );
}
const listfile = []
const listdir = []
glob.sync( `${config.tribes}/${tribeid}/${dir}/*` )
.forEach( f => {
//console.log( f )
const stats = fs.statSync( f );
// console.log( stats )
if( stats.isFile() ) {
listfile.push( path.basename( f ) )
if( !comment.file[ path.basename( f ) ] ) {
comment.file[ path.basename( f ) ] = { tags: [], info: "", thumbb64: "" };
}
comment.file[ path.basename( f ) ].mtime = stats.mtime;
comment.file[ path.basename( f ) ].ctime = stats.ctime;
comment.file[ path.basename( f ) ].size = stats.size;
}
if( stats.isDirectory() ) {
listdir.push( path.basename( f ) )
if( !comment.dir[ path.basename( f ) ] ) {
comment.dir[ path.basename( f ) ] = { tags: [], info: "", thumbb64: "" }
}
comment.dir[ path.basename( f ) ].nbfile = glob.sync( `${f}/*.*` )
.length;
comment.dir[ path.basename( f ) ].mtime = stats.mtime;
comment.dir[ path.basename( f ) ].ctime = stats.mtime;
console.log( 'comment.dir', comment.dir )
}
} );
// on remove les file or dir that was deleted
Object.keys( comment.file )
.forEach( f => {
if( !listfile.includes( f ) ) delete comment.file[ f ]
} )
Object.keys( comment.dir )
.forEach( d => {
if( !listdir.includes( d ) ) delete comment.dir[ d ]
} )
//console.log( comment )
fs.outputJson( `${config.tribes}/${tribeid}/${dir}/.info.json`, comment, 'utf-8' );
return { status: 200, payload: { info: [ 'succestogetls' ], models: 'Tribes', moreinfo: comment } }
};
Tribes.addspaceweb = ( data ) => {
/*
To create a public spaceweb accessible from https://dnsname/pageindex
input:
{dnsname:["archilinea.fr","www.archilinea.fr"], 1st is tha main dns other are just servername redirection
tribeid:"archilinea", from req.session.header.xworkon
website:"presentation",
pageindex:"app_index_fr.html"
mode:dev(local no ssl) | prod(IP + ssl)
}
output:
nginx conf and ssl to serve each https://dnsname to /{tribeid}/www/app/{website}
Carefull this action is executed with root and restart nginx + apxtrib to work
*/
data.configdomain = config.tribes;
data.porthttp = config.porthttp;
console.assert( config.loglevel == "quiet", 'data to create spaceweb:', data );
// create spaceweb app for tribeid/www/app/website/pageindexname.html
if( !fs.existsSync( `${config.tribes}/${data.tribeid}/www/app/${data.website}` ) ) {
fs.outputFileSync( `${config.tribes}/${data.tribeid}/www/app/${data.website}/${data.pageindex}`, `<h1>Hello ${data.tribeid} ${data.website} onto ${data.dnsname.join(',')}`, 'utf-8' )
}
//create logs folder
fs.ensureDirSync( `${config.tribes}/${data.tribeid}/logs/nginx` );
// add nginx http config
const confnginx = fs.readFileSync( 'setup/nginx/modelwebsite.conf.mustache', 'utf-8' );
fs.outputFileSync( `/etc/nginx/conf.d/${data.dnsname[0]}.conf`, Mustache.render( confnginx, data ), 'utf-8' );
if( data.dns == "unchain" ) {
//add in /etc/hosts
let hosts = fs.readFileSync( '/etc/hosts', 'utf8' );
let chg = false;
data.dnsname.forEach( d => {
if( !hosts.includes( `127.0.0.1 ${d}` ) ) {
hosts += `\n127.0.0.1 ${d}`;
chg = true;
}
if( chg ) {
fs.outputFileSync( '/etc/hosts', hosts, 'utf8' )
}
} );
};
//Ckeck dns respond
data.dnsname.forEach( d => {
if( !dnsSync.resolve( `${d}` ) ) {
rep += `\nresolving ${d} will not responding valid IP, please setup domain redirection IP before runing this script`
}
} )
//update clienconf.json
const clientconf = fs.readJsonSync( `${config.tribes}/${data.tribeid}/clientconf.json` );
clientconf.website[ data.website ] = data.dnsname[ 0 ];
//merge allowedDOMs in unique concat
clientconf.allowedDOMs = [ ...new Set( ...clientconf.allowedDOMs, ...data.dnsname ) ];
fs.outputJsonSync( `${config.tribes}/${data.tribeid}/clientconf.json`, clientconf, 'utf-8' );
if( !data.setup ) {
// in setup apxtrib is not running and it will be start manually at the 1st run
// in other case need to plan a restart for CORS
setTimeout( Tribes.restartapxtrib, 300000, data.clienId );
}
const nginxrestart = execSync( `sudo systemctl restart nginx` )
.toString();
console.log( 'Restart nginx', nginxrestart )
if( data.mode != "unchain" ) {
// get ssl certificate ATTENTION il faut ajouter -d devant chaque domain qui redirige vers l'espace web.
const certbot = execSync( `sudo certbot --nginx -d ${data.dnsname.join(' -d ')}` )
.toString();
console.log( 'certbot is running A CHECKER POUR UNE VRAIE PROD ????', certbot )
}
//sh execution to update change requested
return {
status: 200,
payload: {
model: "Tribes",
info: [ 'webspacecreated' ],
moreinfo: "Space web well created"
}
};
}
Tribes.restartapxtrib = ( tribeid ) => {
console.log( 'A restarting was requested 5mn ago from a new spacedev for ' + tribeid )
execSync( 'yarn restartpm2' );
}
module.exports = Tribes;

152
app/models/UploadFiles.js Executable file
View File

@@ -0,0 +1,152 @@
const fs = require( 'fs-extra' );
const path = require( 'path' );
const formidable = require( 'formidable' );
const jsonfile = require( 'jsonfile' );
const mustache = require( 'mustache' );
const config = require( '../tribes/townconf.js' );
/*
model A SUPPRIMER !!!!!!!!!!!!!!!!!!!!!!
les functions d'upload de file et de droits d'accès doivent être gérer dans Tribes
*/
const UploadFiles = {};
UploadFiles.get = function ( filename, header ) {
// check file exist
const file = `${config.tribes}/${header.xworkon}/${filename}`;
// console.log('fichier demande ', file);
if( !fs.existsSync( file ) ) {
// console.log('le fichier demande n existe pas ', file);
return {
status: 404,
payload: { info: [ 'fileUnknown' ], model: 'UploadFiles' }
};
} else {
console.log( 'envoie le fichier ', file );
return {
status: 200,
payload: { info: [ 'fileknown' ], model: 'UploadFiles', file: file }
};
}
};
UploadFiles.addjson = function ( data, header ) {
/*
Le header = {X-WorkOn:"",destinationfile:"", filename:""}
Le body = {jsonp:{},callback:function to launch after download,'code':'mot cle pour verifier que le fichier est à garder'}
*/
// console.log(req.body.jsonp);
try {
fs.outputJsonSync( header.destinationfile + '/' + header.filename, data.jsonp );
if( data.callback ) {
const execCB = require( `${__base}/models/tribeid/${
header.xworkon
}` );
execCB[ data.callback ]();
}
return {
status: 200,
payload: {
info: [ 'wellUpload' ],
model: 'UploadFiles',
render: {
destination: header.destinationfile,
filename: header.filename
}
}
};
} catch ( err ) {
console.log( 'Impossible de sauvegarder le fichier, A COMPRENDRE', err );
return {
status: 503,
payload: { info: [ 'savingError' ], model: 'UploadFiles' }
};
}
};
UploadFiles.add = function ( req, header ) {
const form = new formidable.IncomingForm();
console.log( 'req.headers', req.headers );
console.log( 'req.params', req.params );
console.log( 'req.query', req.query );
console.log( 'req.body', req.body );
let destinationfile = `${config.tribes}/${header.xworkon}/${
header.destinationfile
}`;
form.parse( req, function ( err, fields, files ) {
console.log( 'files', files.file.path );
console.log( 'fields', fields );
const oldpath = files.file.path;
destinationfile += '/' + files.file.name;
console.log( 'oldpath', oldpath );
console.log( 'destinationfile', destinationfile );
fs.copyFile( oldpath, destinationfile, function ( err ) {
if( err ) {
console.log( err );
return {
status: 500,
payload: { info: [ 'savingError' ], model: 'UploadFiles' }
};
} else {
console.log( 'passe' );
fs.unlink( oldpath );
return {
status: 200,
payload: {
info: [ 'wellUpload' ],
model: 'UploadFiles',
render: {
destination: destinationfile
}
}
};
}
} );
} );
};
UploadFiles.updateEvent = function ( domainId, eventId, event ) {
// checkAndCreateNeededDirectories(domainId);
const eventsFile = `${config.tribes}/${domainId}/actions/events/events.json`;
if( !fs.existsSync( eventsFile ) ) {
fs.outputJsonSync( eventsFile, {} );
return { status: 404, payload: 'You have not any events.' };
}
const events = fs.readJsonSync( eventsFile );
if( !events.hasOwnProperty( eventId ) ) {
return {
status: 404,
payload: 'The event you are trying to update does not exist.'
};
}
events[ eventId ] = {
eventName: event.eventName,
eventDate: event.eventDate,
eventDescription: event.eventDescription
};
fs.outputJsonSync( eventsFile, events, { spaces: 2 } );
return {
status: 200,
payload: events
};
};
UploadFiles.deleteEvent = function ( domainId, eventId ) {
// checkAndCreateNeededDirectories(domainId);
const eventsFile = `${config.tribes}/${domainId}/actions/events/events.json`;
if( !fs.existsSync( eventsFile ) ) {
fs.outputJsonSync( eventsFile, {} );
return { status: 404, payload: 'You have not any events.' };
}
const events = fs.readJsonSync( eventsFile );
if( events.hasOwnProperty( eventId ) ) {
delete events[ eventId ];
fs.outputJsonSync( eventsFile, events, { spaces: 2 } );
return {
status: 200,
payload: events
};
} else {
return {
status: 404,
payload: 'The event you are trying to delete does not exist.'
};
}
};
module.exports = UploadFiles;

View File

@@ -0,0 +1,5 @@
{
"typedoesnnotexistinschema":"This type in your propertie is not manage by Checkjson.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,4 @@
{
"schemanotfound":"Schema not found in {{fullpath}}",
"pathnamedoesnotexist":"ObjectPath or objectName does not exist {{fullpath}}"
}

View File

@@ -0,0 +1,152 @@
/*
Unit testing
*/
const assert = require("assert");
const Checkjson = require("../Checkjson.js");
const ut = { name: "Checkjson" };
const schema = {
$schema: "http://json-schema.org/schema#",
title: "Dummy schema to test Checkjson.js",
description: "Checkjson 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: "test0",
data: { totest: true },
properties: { totest: { type: "boolean" } },
status: 200
},
{
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 = Checkjson.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 Checkjson properties");
ut.testproperties(options);
};
module.exports = ut;

View File

@@ -0,0 +1,72 @@
/*
Unit testing
*/
const assert = require("assert");
const fs=require('fs-extra');
const path= require('path');
const Odmdb = require("../Odmdb.js");
const {generemdp} = require('../../nationchains/socialworld/contracts/toolsbox.js');
const ut = { name: "Odmdb" };
/*
We test only search and indexation here
Create Update Read and Delete are unit testing with specificities of each Object.
To do that we create in tmp a dummy data folder for a dummy schema object
*/
const schema = {
$schema: "http://json-schema.org/schema#",
title: "Dummy schema to test Checkjson.js",
description: "Checkjson 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: {
uuid: {
type:"string",
format:"uuid",
default:"=uuid.v4()"
},
dtcreate:{
type:"string",
format:"datetime",
default:"=date.now()"
},
tag:{
type:"string",
enum:["t1","t2","t3"],
default:"t1"
},
info:{
type:"string",
minLength: 10,
default:"=generemdp(255,'ABCDEFGHIJKLM 12340')"
}
},
required:["uuid"],
apxprimarykey:"uuid",
apxuniquekey:["info"],
apxsearchindex:{
"uuid":{"list":[],"taginfo":['tag','info'],"all":""},
"info":{"uuid":['uuid']}
}
};
const obj={tag:"t1",info:"Lorem ipsum A"}
ut.createanobject=(schema,obj)=>{
const res={status:200,err:[]}
return res
}
ut.run = (options) => {
const objectPath=path.resolve(__dirname,'../../tmp/testobjects');
const schemaPath=path.resolve(__dirname,'../../tmp/testschema');
if (!fs.existsSync(objectPath)) fs.ensureDirSync(objectPath);
if (!fs.existsSync(schemaPath)) fs.ensureDirSync(schemaPath);
const createenvobj=Odmdb.setObject(schemaPath,objectPath,"objtest",schema,{},"en");
assert.deepEqual(createenvobj,{status:200},JSON.stringify(createenvobj));
const checkschema= Odmdb.schema(schemaPath,"objtest",true)
assert.deepEqual(checkschema.status,200,JSON.stringify(checkschema))
};
module.exports = ut;

63
app/routes/messages.js Executable file
View File

@@ -0,0 +1,63 @@
const express = require( 'express' );
const path = require( 'path' );
// Classes
const Messages = require( '../models/Messages.js' );
// Middlewares ( prefix, object ) => {
const checkHeaders = require( '../middlewares/checkHeaders' );
const isAuthenticated = require( '../middlewares/isAuthenticated' );
const hasAccessrighton = require( '../middlewares/hasAccessrighton' );
const router = express.Router();
router.post( '/', checkHeaders, ( req, res ) => {
/*
add message to (no authentification and accessright needs) :
a tribeid or uuid => create a contact based on email.json or phone.json or email_phone.json
if req.body.orderuuid exist then it store the req.body in /orders/orderuuid.json an order with state = order
*/
// check if a receiver is well identify if not then it send message to all user tribeid to inform **
if( !req.body.desttribeid ) req.body.desttribeid = req.session.header.xworkon;
if( !req.body.lang ) req.body.lang = req.session.header.xlang;
console.log( '/messages t send for ', req.session.header.xworkon );
//console.log(' Content: ',req.body);
const result = Messages.postinfo( req.body );
res.status( result.status )
.send( result.data )
} );
router.put( '/:objectname/:uuid', checkHeaders, isAuthenticated, ( req, res ) => {
// message that will create an object and sendback an email.
// if objectnane/uuid_lg.json exist ans accessright is ste to U for the user then it replace object data with req.body.key value
// if does not exist and accessright C then it create it with uuid
// then if req.body.tplmessage => render email with data
// No data management are done here, if need you can add plugin to create a workflow based object
// if need specific data Checkjson => req.body.callback={tribeidpugin,pluginname,function} will run pluginname.function(data) add data run specific stuf before saved the message object in /objectname/data.uuid_lg/json
let result;
console.log( "object", req.params.objectname )
if( req.params.objectname == 'notifications' ) {
//uuid is a timestamp
req.body.time = req.params.uuid;
result = Messages.notification( req.body, req.session.header );
} else {
req.body.uuid = req.params.uuid;
req.body.object = req.params.objectname;
result = Messages.object( req.body, req.session.header );
}
//console.log( 'result', result );
res.status( result.status )
.json( result.data )
} );
router.get( '/user', checkHeaders, isAuthenticated, ( req, res ) => {
// run agregate for tribeid concerned
//
console.log( "request notifiation for user", req.session.header.xpaganid );
const app = {
tribeid: req.session.header.xapp.split( ':' )[ 0 ],
website: req.session.header.xapp.split( ':' )[ 1 ],
lang: req.session.header.xlang
};
res.send( Messages.request( req.session.header.xtribe, req.session.header.xpaganid,
req.app.locals.tokens[ req.session.header.xpaganid ].ACCESSRIGHTS, app ) );
} );
module.exports = router;

37
app/routes/nationchains.js Executable file
View File

@@ -0,0 +1,37 @@
const express = require( 'express' );
const config = require( '../tribes/townconf.js' );
// Classes
const Nationchains = require( '../models/Nationchains.js' );
// Middlewares
const checkHeaders = require( '../middlewares/checkHeaders' );
const isAuthenticated = require( '../middlewares/isAuthenticated' );
const hasAccessrighton = require( '../middlewares/hasAccessrighton' );
const router = express.Router();
/*
Manage the social world
@Todo
Manage a new nation
A major create a nation with at least a town => nation:{name, towns:[]} contracts/nationname.js + contracts/townsname.js
Manage a new towns in a nation => update nation:[nationname:towns:[]} contracts/townname.js
*/
router.post( '/push', checkHeaders, ( req, res ) => {
// Get information from other apxtrib instance in req.body
// check req.body.hashnext => means this is a candidate to validate next block
//
// return it's own information back with the last call to Nationchains.synchronize()
res.send( { status: 200, payload: { moreinfo: fs.readFileSync( `${config.tribes}/${config.mayorId}/nationchains/nodes/${config.rootURL}`, 'utf-8' ) } } )
} )
module.exports = router;

78
app/routes/odmdb.js Normal file
View File

@@ -0,0 +1,78 @@
const express = require( 'express' );
const glob = require( 'glob' );
const path = require( 'path' );
// Classes
const Odmdb = require( '../models/Odmdb.js' );
// Middlewares
const checkHeaders = require( '../middlewares/checkHeaders' );
const isAuthenticated = require( '../middlewares/isAuthenticated' );
const hasAccessrighton = require( '../middlewares/hasAccessrighton' );
const router = express.Router();
router.get('/searchauth/:objectname/:question',checkHeaders,isAuthenticated,( req, res ) => {
/**
*
*
*/
console.log( 'route referentials get all language' + req.params.objectname + '-' + req.params.question );
const getref = Referentials.getref( true, req.params.source, req.params.idref, req.session.header.xworkon, req.session.header.xlang );
// Return any status the data if any erreur return empty object
res.jsonp( getref.payload.data );
} );
router.get('schema/:objectname', checkHeaders, isAuthenticated,(req,res)=>{
/**
* @api {get} /odmdb/schema/:objectname
* @apiName GetSchema
* @apiGroup Odmdb
*
* @apiUse apxHeader
*
* @apiParam {String} objectname Mandatory if headers.xworkon == nationchains then into ./nationchains/ else into ./tribes/xworkon/
*
* @apiError (404) {string} info a key word to understand not found schema
* @apiError (404) {string} ref an string to find referential to get description of info in xlang request
* @apiError (404) {object} [moreinfo} an object with element to render ref_lg.json[info] to understand error
*
* @apiSuccess (200) {object} data contains schema requested
*
*/
const fullpath = path.resolve(`${__dirname}/tribes/${req.session.header.xworkon}/schema/${req.params.pathobjectname}.json`);
if (fs.existsSync(fullpath)){
res.status(200).json(data:fs.readJsonSync(fullpath))
}else{
res.status(404).json(info:"schemanotfound", ref:"odmdb", moreinfo:{fullpath})
}
})
router.put('schema/:objectname', checkHeaders, isAuthenticated,(req,res)=>{
/**
* @api {put} /odmdb/schema/:objectname
* @apiName putSchema
* @apiGroup Odmdb
*
* @apiUse apxHeader
*
* @apiParam {String} objectname Mandatory if headers.xworkon == nationchains then into ./nationchains/ else into ./tribes/xworkon/
* @apiBody {string} schemapath where to store schema .../schema
* @apiBody {string} objectpath where to store object ...objectname/index/config.json
* @apiBody {json} schema content
* @apiBody {json} schemalang content in lg
* @apiBody {string} lang define which schemalg is (2 letters)
*
* @apiError (404) {string} info a key word to understand not found schema
* @apiError (404) {string} ref an string to find referential to get description of info in xlang request
* @apiError (404) {object} [moreinfo} an object with element to render ref_lg.json[info] to understand error
*
* @apiSuccess (200) {object} data contains schema requested
*
*/
const fullpath = path.resolve(`${__dirname}/tribes/${req.session.header.xworkon}/schema/${req.params.pathobjectname}.json`);
const set=Odmdb.setObject(path.resolve(`${__dirname}/tribes/${req.session.header.xworkon}`),)
if (fs.existsSync(fullpath)){
res.status(200).json(data:fs.readJsonSync(fullpath))
}else{
res.status(404).json(info:"schemanotfound", ref:"odmdb", moreinfo:{fullpath})
}
})
module.exports = router;

65
app/routes/outputs.js Executable file
View File

@@ -0,0 +1,65 @@
// Upload de file
const express = require( 'express' );
const fs = require( 'fs-extra' );
// Classes
const UploadFile = require( '../models/UploadFiles' );
const Outputs = require( '../models/Outputs' );
//const Outputstest = require('../models/Outputstest');
// Middlewares
const checkHeaders = require( '../middlewares/checkHeaders' );
const isAuthenticated = require( '../middlewares/isAuthenticated' );
const router = express.Router();
router.post( '/ggsheet2json', checkHeaders, async ( req, res ) => {
console.log( 'route outputs sheet to json' );
let result = await Outputs.ggsheet2json( req.body, req.session.header );
res.send( result );
} );
// checkHeaders, isuploadFileValid
router.post( '/msg', checkHeaders, async ( req, res ) => {
console.log( 'route outputs msg post ' );
const envoi = await Outputs.generemsg( req.body, req.session.header );
res.status( envoi.status )
.send( {
payload: envoi.payload
} );
} );
/*test functionnalité
router.post('/msgtest', checkHeaders, isemailValid, async (req, res) => {
console.log('route outputs msg post en test');
const envoi = await Outputstest.generemsg(req.body, req.session.header);
res.status(envoi.status).send({
payload: envoi.payload
});
});
*/
router.post( '/template', checkHeaders, ( req, res ) => {
console.log( 'route outputs post de fichier template ' );
// a callback can be pass to req.body to run a specific process after upload
const saveFile = UploadFile.addjson( req.body, req.session.header );
console.log( saveFile );
res.send( saveFile );
// res.send({ status: 200, payload: { info: 'fine' } });
} );
router.post( '/pdf', checkHeaders, ( req, res ) => {
console.log( 'route outputs pdf post' );
Outputs.generepdf( req.body, req.session.header )
.then( ( doc ) => {
res.status( doc.status )
.download( doc.payload.data.path, doc.payload.data.name );
} )
.catch( ( err ) => {
console.log( err );
res.status( err.status )
.send( { payload: err.payload } );
} );
} );
module.exports = router;

211
app/routes/pagans.js Executable file
View File

@@ -0,0 +1,211 @@
const express = require( 'express' );
const path = require( 'path' );
// Classes
const Pagans = require( '../models/Pagans.js' );
// Middlewares
const checkHeaders = require( '../middlewares/checkHeaders' );
const isAuthenticated = require( '../middlewares/isAuthenticated' );
const hasAccessrighton = require( '../middlewares/hasAccessrighton' );
const router = express.Router();
/*
models/Pagans.js
Managed:
/data/tribee/client-Id/users/uuid.json
/searchindex/emails.json {email:uuid}
/login.json {login:uuid}
/uids.json {uuid;[[
login,
email,
encrypted psw,
accessrights]}
ACCESSRIGHTS = {
app:{"tribeid:appname":"profil"},
data:{"tribeid":{object:"CRUDO"}}
}
ACCESSRIGHTS is store into the token and is load into req.session.header.accessrights by hasAccessrighton() middleware
appname is a website space object /sitewebsrc/appname
website live is strored into /dist source in /src
This can be managed by maildigitcreator or not.
apxtrib/sitewebs/webapp is the webinterface of apxtrib
profil: admin / manager / user are key word to give specific access to data into model. Any kind of other profil can exist. It is usefull to manage specific menu in an app.
It is also possible to authorize update a field's object depending of rule into dataManagement/object/
{ field:X
nouserupdate: "!(['admin','manager'].includes(contexte.profil))",
}
data allow a user to access tribeid with Create Read Update Delete Own (CRUDO) on each object of a tribeid independantly of any app.
Create allow to create a new object respecting rules defined into /referentials/dataManagement/object/name.json
Update idem
Delete idem
Owner means it can be Write/Delete if field OWNER contain the UUID that try to act on this object. Usefull to allow someone to fully manage its objects.
*/
router.get( '/isauth', checkHeaders, isAuthenticated, ( req, res ) => {
if( req.session.header.xpaganid == "1" ) {
return res.status( 401 )
.send( { info: "not authenticate" } );
} else return res.status( 200 )
.send( { info: "well authenticated" } )
} )
router.post( '/login', checkHeaders, async ( req, res ) => {
// console.log('POST /users/login with: ', req.app.locals.header);
/*
Check un mot de passe pour un login pour obtenir un token d'authentification
valable 1 hour, 1 day
@header
@body.LOGIN
@body.PASSWORD
@checkpsw = true check si les 2 mot de passe cryptés correspondent
false bypass le contrôle et permet de générer un token
utile le temps de reinitialisé son mot de passe.
@return
*/
console.log( 'login for ', req.body, "in", req.session.header )
const log = await Pagans.loginUser( req.session.header, req.body, true );
console.log( "log user login", log );
if( log.status == 200 ) {
// update req.app.locals.tokens for this uuid just after login success then next isAuth will be valid
req.app.locals.tokens[ log.data.user.UUID ] = { TOKEN: log.data.user.TOKEN, ACCESSRIGHTS: log.data.user.ACCESSRIGHTS }
console.log( req.app.locals )
}
return res.status( log.status )
.send( log.data );
} );
router.get( '/getlinkwithoutpsw/:email', checkHeaders, async ( req, res ) => {
/*
Permet pour un email existant de renvoyer un email avec un lien valable 1h
@email est le compte pour lequel on demande un accès
Réponse:
Si email n'existe pas on n'envoie pas d'email
Si email existe on envoie un email avec un lien dont le token est valable 1h
@return
{status:200 ou erreur ,
payload:{
info:[list de key to appear in correct requester langue],
model:'Pagans',
moreinfo: 'texte pour log '
}
}
*/
console.log( `GET /users/getlinkwithoutpsw for email: ${req.params.email} tribeid :${req.header('X-Client-Id')}` );
if( !req.params.email ) {
return res.status( 404 )
.send( {
info: [ 'emailmissing' ],
model: 'Pagans'
} );
} else {
try {
const getlink = await Pagans.getlinkwithoutpsw( req.params.email, req.session.header );
console.log( 'getlink', getlink )
//met à jour le token créer pour le uuid
req.app.locals.tokens[ getlink.data.info.xuuid ] = getlink.data.info.token;
// attention si on relance le serveur le token temporaire est perdu
return res.status( getlink.status )
.send( getlink.data );
} catch ( err ) {
console.log( err )
return res.status( 500 )
.send( { info: [ 'errServer' ], model: 'Pagans' } );
}
}
} );
router.post( '/register', checkHeaders, async ( req, res ) => {
console.log( `POST /users for ${req.session.header.xtribe}` );
if( req.session.header.xauth == '123123' ) {
// Creation d'un utilisateur avec information de base aucun droit
// On modifie le contenu du form pour n egarder que login/email et psw
// pour le client_id permet de traiter un user en attente de validation
console.log( 'req du post', req );
}
} );
router.get( '/info/:listindex', checkHeaders, isAuthenticated, hasAccessrighton( 'users', 'R' ), async ( req, res ) => {
console.log( `get users info on tribeid ${req.session.header.xworkon} for ${req.params.listindex} with accessright`, req.session.header.accessrights.data );
const result = await Pagans.getinfoPagans( req.session.header.xpresworkon, req.session.header.accessrights, req.params.listindex );
res.status( result.status )
.send( result.data );
} );
router.get( '/list/:filter/:field', checkHeaders, isAuthenticated, hasAccessrighton( 'users', 'R' ), async ( req, res ) => {
console.log( 'GET /users/list/filtre/champs list for ' + req.session.header.xworkon );
if(
[ 'admin', 'manager' ].includes( req.session.header.decodetoken[ 'apps' + req.session.header.xworkon + 'profil' ] ) ) {
try {
const userslist = await Pagans.getUserlist( req.session.header, req.params.filter, req.params.field );
console.log( 'userslist', userslist );
if( userslist.status == 200 ) {
return res.status( userslist.status )
.send( userslist.data );
}
} catch ( err ) {
console.log( err );
return res.status( 400 )
.send( { info: 'erreur' } );
}
} else {
res.status( 403 )
.send( {
info: [ 'forbiddenAccess' ],
model: 'Pagans'
} );
}
} );
router.get( '/uuid/:id', checkHeaders, isAuthenticated, hasAccessrighton( 'users', 'R' ), async ( req, res ) => {
console.log( `GET /users/uuid/${req.params.id}` );
//console.log('req.app.locals: ', req.app.locals);
//console.log('req.session', req.session);
const result = await Pagans.getUser( req.params.id, req.session.header.xworkon, req.session.header.accessrights );
res.status( result.status )
.send( result.data );
} );
router.put( '/chgpsw/:id', checkHeaders, isAuthenticated, async ( req, res ) => {
console.log( `PUT update /users/chgpsw/${req.params.id}` );
try {
const majpsw = await Pagans.updateUserpassword( req.params.id, req.session.header, req.body );
res.status( majpsw.status )
.send( majpsw.data );
} catch ( {
status,
data
} ) {
res.status( status )
.send( data );
}
} );
router.post( '/uuid', checkHeaders, isAuthenticated, hasAccessrighton( 'users', 'C' ), async ( req, res ) => {
console.log( 'POST /users create for ' + req.session.header.xworkon, req.body );
const usercreate = await Pagans.createUser( req.session.header, req.body );
return res.status( usercreate.status )
.send( usercreate.data );
} );
router.put( '/uuid/:id', checkHeaders, isAuthenticated, hasAccessrighton( 'users', 'U' ), async ( req, res ) => {
console.log( `PUT update /users/${req.params.id}` );
// console.log('req.app.locals: ', req.app.locals);
// console.log('req.session', req.session);
try {
const majUser = await Pagans.updateUser( req.params.id, req.session.header, req.body );
res.status( majUser.status )
.send( majUser.data );
} catch ( {
status,
data
} ) {
res.status( status )
.send( data );
}
} );
router.delete( '/uuid/:id', checkHeaders, isAuthenticated, hasAccessrighton( 'users', 'D' ), ( req, res ) => {
console.log( `DELETE /users/uuid/${req.params.id}` );
const result = Pagans.deleteUser( req.params.id, req.session.header );
res.status( result.status )
.send( result.data );
} );
module.exports = router;

94
app/routes/referentials.js Executable file
View File

@@ -0,0 +1,94 @@
// Upload de file
const express = require( 'express' );
const glob = require( 'glob' );
const path = require( 'path' );
// Classes
const Referentials = require( '../models/Referentials' );
// Middlewares
const checkHeaders = require( '../middlewares/checkHeaders' );
const isAuthenticated = require( '../middlewares/isAuthenticated' );
const hasAccessrighton = require( '../middlewares/hasAccessrighton' );
const router = express.Router();
/*
* keylist = list of key at 1st level in clientconf.json separated by _
* we use header.xworkon
* To manage AccesRight obkect referentials does not follow the same logic than other object this is why
*/
router.get( '/clientconf/:keylist', checkHeaders, isAuthenticated, ( req, res ) => {
// retourne liste info (non sensible) du tribeid inside headers.xworkon sur keylist ="key1_key2"
/*
if (req.session.header.accessrights.data[ "Alltribeid" ] && req.session.header.accessrights.data[ "Alltribeid" ].referentials.includes('R') ;
*/
console.log( `get clientconf for ${req.session.header.xworkon} on ${req.params.keylist}` )
let dataref = {}
if( req.params.keylist.split( '_' )
.length > 0 ) {
const ref = Referentials.clientconf( req.session.header.xworkon, req.params.keylist.split( '_' ) )
if( ref.status == 200 ) {
dataref = ref.payload.data;
} else {
console.log( "erreur ", ref )
}
}
console.log( 'envoie en jsonp: dataref' )
res.jsonp( dataref )
} );
router.get( '/clientconfglob', checkHeaders, isAuthenticated, ( req, res ) => {
res.jsonp( Referentials.clientconfglob()
.payload.data );
} );
router.get( '/contentlist/:source', checkHeaders, isAuthenticated,
( req, res ) => {
const payload = [];
console.log( req.params.source, `${config.tribes}/${req.session.header.xworkon}/referentials/dataManagement/${req.params.source}/*.json` )
glob.sync( `${config.tribes}/${req.session.header.xworkon}/referentials/dataManagement/${req.params.source}/*.json` )
.forEach( f => {
payload.push( path.basename( f, '.json' ) );
} )
res.json( payload );
} );
router.get( '/contentfull/:source/:idref', checkHeaders, isAuthenticated,
( req, res ) => {
//only for data and object
console.log( 'route referentials get all language' + req.params.source + '-' + req.params.idref );
const getref = Referentials.getref( true, req.params.source, req.params.idref, req.session.header.xworkon, req.session.header.xlang );
// Return any status the data if any erreur return empty object
res.jsonp( getref.payload.data );
} );
router.get( '/content/:source/:idref', checkHeaders, isAuthenticated,
( req, res ) => {
console.log( 'route referentials get ' + req.params.source + '-' + req.params.idref );
const getref = Referentials.getref( false, req.params.source, req.params.idref, req.session.header.xworkon, req.session.header.xlang );
res.jsonp( getref.payload.data );
} );
// get with no authentification
router.get( '/contentnoauth/:source/:idref', checkHeaders,
( req, res ) => {
console.log( 'route referentials get ' + req.params.source + '-' + req.params.idref );
// @TODO check access right in clientconf before sending back json file
const getref = Referentials.getref( false, req.params.source, req.params.idref, req.session.header.xworkon, req.session.header.xlang );
res.jsonp( getref.payload.data );
} );
router.get( '/lg', ( req, res ) => {
console.log( req.headers[ "accept-language" ] )
let lg = '??';
if( req.headers[ "accept-language" ] && req.headers[ "accept-language" ].split( ',' )
.length > 0 ) {
lg = req.headers[ "accept-language" ].split( ',' )[ 0 ];
}
res.json( { lg } )
} );
router.put( '/content/:source/:idref', checkHeaders, isAuthenticated, ( req, res ) => {
console.log( `route put content for ${req.params.idref} that is a ${req.params.source}` );
const putref = Referentials.putref( req.params.source, req.params.idref, req.session.header.xworkon, req.body )
return res.status( putref.status )
.send( { payload: putref.payload } )
} );
//hasAccessrighton( 'referentials', 'U' )
router.get( '/updatefull', checkHeaders, isAuthenticated, hasAccessrighton( 'referentials', 'U' ), ( req, res ) => {
console.log( `route get to force update content updatefull is accessrighton` );
const updtref = Referentials.updatefull( req.session.header.xworkon )
return res.status( updtref.status )
.send( { payload: updtref.payload } )
} );
module.exports = router;

29
app/routes/tags.js Executable file
View File

@@ -0,0 +1,29 @@
//Installation d'un tag
/*
*/
// Upload de file
const express = require('express');
// Classes
const Tags = require('../models/Tags');
// Middlewares
const router = express.Router();
router.get('/:filename', (req, res) => {
//console.log('route tags get ', req.params.filename);
const savetag = Tags.getfile(req.params.filename, req);
if(savetag.status == 200) {
res.sendFile(savetag.payload.filename);
} else {
res.status(savetag.status)
.send({ payload: savetag.payload })
}
})
router.post('/:tribeid', (req, res) => {
//console.log('route tags post ', req.params.tribeid);
const savetag = Tags.savehits(req);
res.status(200)
.send('');
})
module.exports = router;

374
app/routes/tribes.js Executable file
View File

@@ -0,0 +1,374 @@
const express = require( 'express' );
const fs = require( 'fs-extra' );
const path = require( 'path' );
const config = require( '../tribes/townconf.js' );
// Classes
const Tribes = require( '../models/Tribes.js' );
// Middlewares
const checkHeaders = require( '../middlewares/checkHeaders' );
const isAuthenticated = require( '../middlewares/isAuthenticated' );
const hasAccessrighton = require( '../middlewares/hasAccessrighton' );
const router = express.Router();
router.get( '/clientconf/:tribeid', checkHeaders, isAuthenticated, ( req, res ) => {
/*
get a clientconf.json for a tribeid depending of user accessright
if tribeid == all and user is admin of apxtrib => get /tmp/clientconfglob.json
req.session.header.accessrights, req.session.header.apixpaganid
*/
console.log( `Tribes/clientconf for tribeid:${req.params.tribeid}` )
if( req.params.tribeid == "all" && req.session.header.accessrights.data.apxtrib && req.session.header.accessrights.data.apxtrib.tribeid && req.session.header.accessrights.data.apxtrib.tribeid.includes( 'R' ) ) {
res.status( 200 )
.send( { moreinfo: fs.readJsonSync( `${config.tmp}/clientconfglob.json`, 'utf-8' ) } );
return;
}
if( req.session.header.accessrights.data[ req.params.tribeid ] &&
req.session.header.accessrights.data[ req.params.tribeid ].tribeid &&
req.session.header.accessrights.data[ req.params.tribeid ].tribeid.includes( 'R' ) &&
fs.existsSync( `${config.tribes}/${req.params.tribeid}/clientconf.json` ) ) {
// const conftribeid = { moreinfo: {} }
// conftribeid.moreinfo[ req.params.tribeid ] = fs.readJsonSync( `${config.tribes}/${req.params.tribeid}/clientconf.json`, 'utf-8' );
res.status( 200 )
.send( { moreinfo: [ fs.readJsonSync( `${config.tribes}/${req.params.tribeid}/clientconf.json`, 'utf-8' ) ] } );
return;
}
// if not authorized or dos not exist return empty
// no specific message is send for security reason (check only log)
res.status( 403 )
.send( { info: [ 'forbidenAccess' ], models: 'Tribes' } )
.end();
} )
router.put( '/', checkHeaders, isAuthenticated, ( req, res ) => {
console.log( 'Create a new tribeid, with a useradmin' )
console.log( ' send data = clientconf.json with all parameter.' )
// !!!!! check for security any ; \n or so because data can be used into shell
const add = Tribes.create( req.body );
res.status( add.status )
.send( add.payload )
} )
router.delete( '/archivetribeid/:tribeid', checkHeaders, isAuthenticated, ( req, res ) => {
console.log( "request archive tribeid" )
const archive = Tribes.archive( req.params.tribeid );
res.status( archive.status )
.send( archive.payload )
} );
router.post( '/spaceweb', checkHeaders, isAuthenticated, ( req, res ) => {
// !!!!! check for security any ; \n or so because data can be used into shell
console.log( 'Create a new webapp for xworkon ' )
req.body.tribeid = req.session.header.xworkon;
const add = Tribes.addspaceweb( req.body )
res.status( add.status )
.send( add.payload )
} )
router.get( '/spaceweb/components/:tribeid/:website/:key', checkHeaders, ( req, res ) => {
// check if key is valid before continue
// exemple: get Tribes/spaceweb/components/ndda/mesa/123?rep=appmesatable/appsimpletable.mustache
const file = `${config.tribes}/${req.params.tribeid}/spacedev/${req.params.website}/src/ctatic/components/${req.query.path}`
console.log( `Request components file from ${file}` )
if( fs.existsSync( file ) ) {
res.sendFile( file );
} else {
res.send( `console.error("Missing components file in ${req.params.tribeid}/spacedev/${req.params.website}/src/ctatic/components/${req.query.path}");` );
}
} )
router.get( '/plugins/:tribeid/:pluginname/:key/:filename', ( req, res ) => {
// No accessright possible cause it is load on the fly
// @todo Check key to authorize access to the plugin (key comme from user ACCESSRIGHTS[tribeid plugin owner:pluginname]).key
// return a file into /:tribeid owner of plugin/plugins/:pluginname/components/:filename
// if not exist or invalid key then return console.error
const file = `${config.tribes}/${req.params.tribeid}/plugins/${req.params.pluginname}/components/${req.params.filename}`
console.log( 'Tribes/plugins/ ', file )
if( fs.existsSync( file ) ) {
res.sendFile( file );
} else {
res.send( `console.error("Missing plugin file in ${req.params.tribeid}/plugins/${req.params.pluginname}/components/${req.params.filename}");` );
}
} );
router.get( '/dirls', checkHeaders, isAuthenticated, ( req, res ) => {
// url /Tribes/dirls?rep=referentials/dataManagement
// request information about a req.query.rep from header xworkon/
// return
// {file:[{}],dir:[{}]}
// @todo check if isAuthorized and exist
console.log( 'request dirls', `${config.tribes}/${req.session.header.xworkon}/${req.query.rep}` );
if( !fs.existsSync( `${config.tribes}/${req.session.header.xworkon}/${req.query.rep}` ) ) {
res.status( 404 )
.send( { 'info': [ 'dirnotexist' ], model: 'Tribes' } );
}
const info = Tribes.dirls( req.session.header.xworkon, req.query.rep );
console.log( info )
res.status( info.status )
.send( info.payload );
} )
router.delete( '/ls', checkHeaders, isAuthenticated, ( req, res ) => {
// check Accessright with D or O on each
// url /Tribes/ls
// req.body.files=[listfiles file to delete ]
const authfiles = Tribes.checkaccessfiles( req.body, 'D', req.session.header.accessrights, req.session.header.apixpaganid );
authfiles.ok.forEach( f => { fs.remove( `${config.tribes}/${f}` ); } )
res.status( 200 )
.send( { 'info': [ 'fileauthdeleted' ], models: 'Tribes', moreinfo: authfiles } )
} );
router.put( '/sendjson', checkHeaders, isAuthenticated, ( req, res ) => {
//req.body = {object:spacedev, path:website/src/data/tpldataname_lg.json, data:{...}}
//console.log( req.body )
const dest = `${config.tribes}/${req.session.header.xworkon}/${req.body.object}/${req.body.path}`;
console.log( `Send json to saved to ${dest}` );
if( !( req.body.object && fs.existsSync( `${config.tribes}/${req.session.header.xworkon}/${req.body.object}` ) ) ) {
res.status( '404' )
.send( { info: [ 'objectmissiong' ], models: 'Tribes', moreinfo: `object: ${req.body.object} does not exist req.body must {object, data, path} into data ${req.session.header.xworkon}/${req.body.object}` } )
} else {
if( fs.existsSync( `${config.tribes}/${req.session.header.xworkon}/${req.body.object}/${req.body.path}` ) ) {
// exist so can be update check accessright update on this
hasAccessrighton( req.body.object, "U" );
} else {
hasAccessrighton( req.body.object, "C" );
}
fs.outputJsonSync( dest, req.body.data );
res.status( 200 )
.send( { info: [ 'filesaved' ], models: 'Tribes' } )
}
} );
router.post( '/downloadls', checkHeaders, isAuthenticated, ( req, res ) => {
// midlleware hasAccessrighton.js is not apply here only to access/update/create information inside an object
// to get file a user need accessrights to data: object: R or to Own it
// or if exist a .info.json into folder get shared as R in uuid
//req.body contain list of path file or folder if only 1 file then download it, otherwise zip list and send zip file
const authfiles = Tribes.checkaccessfiles( req.body.files, 'R', req.session.header.accessrights, req.session.header.xpaganid );
if( authfiles.ok.length == 1 ) {
// bidouille en attendnat de faire un .zip binaire propre
if( !authfiles.ok[ 0 ].includes( '.xml' ) ) {
res.status( 200 )
.download( `${config.tribes}/${authfiles.ok[0]}`, authfiles.ok[ 0 ] );
} else {
fs.copySync( `${config.tribes}/${authfiles.ok[0]}`, `${config.tribes}/${config.mayorId}/www/app/webapp/static/tmp/${authfiles.ok[ 0 ]}` )
}
} else if( authfiles.ok.length > 1 ) {
// on zip et on envoie
//res.status( 200 )
// .download( `${config.tribes}/${authfiles.ok[0]}`, authfiles.ok[ 0 ])
res.status( 200 )
.attachment( `${config.tribes}/${authfiles.ok[0]}` );
} else {
req.body.filepon
res.status( 403 )
.send( 'Forbidden access' )
}
} );
router.post( '/upfilepond', checkHeaders, isAuthenticated, ( req, res ) => {
console.log( 'post /Tribes/uploadfilepond' );
// Store file and return a unique id to save button
// that provide folder where to store it
const formidable = require( 'formidable' );
const form = formidable( { multiples: false } );
form.parse( req, ( err, fields, files ) => {
if( err ) { next( err ); return; }
//console.log( 'fields',fields);
// fileMetadaObject send
let context = JSON.parse( fields.filepond );
let idfile = files.filepond.path;
let name = files.filepond.name;
let subfolder = context.subfolder;
name = name.replace( /[ ,'"]/g, "_" );
//console.log( 'files.filepond:', files.filepond );
console.log( idfile, `${config.tribes}/${req.session.header.xworkon}/www/${subfolder}/${name}` )
// On le supprime s'il existe deja
fs.removeSync( `${config.tribes}/${req.session.header.xworkon}/www/${subfolder}/${name}` );
// mv tmp
fs.moveSync( idfile, `${config.tribes}/${req.session.header.xworkon}/www/${subfolder}/${name}` );
//res.status(200).send({models:"Tribes",info:["Savedsuccess"],moreinfo:{id:file.filepond.path}})
//return for filepond
res.writeHead( 200, { 'Content-Type': 'text/plain' } );
res.end( idfile );
} )
} );
router.delete( '/file', checkHeaders, isAuthenticated, ( req, res ) => {
//src = objectfolder with accessright/...
//hasAccessrighton( "www", "D" ),
if( !req.query.src ) {
res.status( 404 )
.send( { info: [ 'deleteerror' ], models: "Tribes", moreinfo: "your del req need a src" } )
return;
};
hasAccessrighton( req.query.src.split( '/' )[ 0 ], "D" );
console.log( 'Remove file', `${config.tribes}/${req.session.header.xworkon}/${req.query.src}` )
console.log( req.body )
fs.removeSync( `${config.tribes}/${req.session.header.xworkon}/${req.query.src}` );
res.status( 200 )
.send( { info: [ 'Successfullremove' ], models: "Tribes" } )
} );
router.post( '/uploadfile', checkHeaders, isAuthenticated, ( req, res ) => {
console.log( 'upload a file ' )
/* Authentification is needed to get a TOKEN
curl -X POST -H "xtribe: apxtrib" -H "xworkon: pvmsaveurs" -H "xlang: fr" -H "xpaganid: 1" -H "xauth: 1" -H "xapp: pvmsaveurs:pvmsaveurs" -H "Content-Type: application/json" -d '{"LOGIN":"adminapxtrib","PASSWORD":"Trze3aze!"}' http://pvmsaveurs.pvmsaveurs.fr/app/users/login
if exist replace xpaganidTOKEN with payload.TOKEN value
curl -H "xtribe: pvmsaveurs" -H "xworkon: pvmsaveurs" -H "xlang: fr" -H "xpaganid: adminapxtrib" -H "xauth: xpressuuisToken" -H "xapp: pvmsaveurs:pvmsaveurs" -F 'data=@filename.xx' http://pvmsaveurs.pvmsaveurs.fr/app/Tribes/uploadfile
*/
const formidable = require( 'formidable' );
const form = formidable( { multiples: false } );
form.parse( req, function ( err, fields, files ) {
//console.log( files.data )
var oldPath = files.data.path;
var newPath = `${config.tribes}/${req.session.header.xworkon}/${clientconf.uploadzip[files.data.name].dest}`;
console.log( 'oldPath', oldPath )
console.log( 'newPath', newPath )
var rawData = fs.readFileSync( oldPath )
fs.outputFile( newPath, rawData, function ( err ) {
if( err ) {
console.log( err );
return res.status( 405 )
.send( { info: [ 'savederror' ], models: "Tribes", moreinfo: "your file was not able to be saved into the server" } )
} else {
return res.status( 200 )
.send( {
info: [ "successfullsent" ],
models: "Tribes"
} );
}
} )
} );
} );
router.post( '/uploadzip', checkHeaders, ( req, res ) => {
console.log( 'uploadzip a file ' )
/* no authentification to upload a zip filename into /tribes/${xworkon}/${clientconf.uploadzip[filename].dest}
unzip it using the password ${clientconf.uploadzip[filename].psw
if no error then run the callback ${clientconf.uploadzip[filename].callback
but a password to unzip
in clientconf.json need to be set
"uploadzip": {
"articlesTribespvm.zip": {
"comment": "unzip with overwrite if same name",
"psw": "azPI1209qtrse",
"dest": "importexport/tmp",
"unzipoption": "-aoa",
"callback": "importexport/integrationitem.js"
}
},
Example:
cd where zip file is stored
curl -H "xtribe: pvmsaveurs" -H "xworkon: pvmsaveurs" -H "xlang: fr" -H "xpaganid: adminapxtrib" -H "xauth: 1" -H "xapp: pvmsaveurs:pvmsaveurs" -F 'data=@articlesTribespvm.zip' http://pvmsaveurs.pvmsaveurs.fr/app/Tribes/uploadzip
*/
const clientconf = fs.readJSONSync( `${config.tribes}/${req.session.header.xworkon}/clientconf.json` )
if( !clientconf.uploadzip ) {
return res.status( '404' )
.send( { info: [ "missconf" ], models: "Tribes", moreinfo: `no uploadzip in clientconf for ${req.session.header.xworkon} please contact apxtrib admin ` } );
};
const uploadzip = clientconf.uploadzip;
const formidable = require( 'formidable' );
const form = formidable( { multiples: false } );
form.parse( req, function ( err, fields, files ) {
//console.log( files.data )
var oldPath = files.data.path;
if( !Object.keys( clientconf.uploadzip )
.includes( files.data.name ) ) {
return res.status( 403 )
.send( { info: [ "notAllowed" ], models: "Tribes", moreinfo: `file ${files.data.name} not allowed to be upload` } )
} else {
console.log( "context:", clientconf.uploadzip[ files.data.name ] )
var newPath = `${config.tribes}/${req.session.header.xworkon}/${clientconf.uploadzip[files.data.name].dest}`;
//console.log( 'oldPath', oldPath )
//console.log( 'newPath', `${newPath}/${files.data.name}` )
fs.moveSync( oldPath, `${newPath}/${files.data.name}`, { overwrite: true } );
const cp = require( 'child_process' );
//console.log( `7z e -p${clientconf.uploadzip[ files.data.name ].psw} ${newPath}/${files.data.name}` );
console.log( '7z', [ 'e', `-p${clientconf.uploadzip[ files.data.name ].psw}`, `${newPath}/${files.data.name}`, `-o${config.tribes}/${req.session.header.xworkon}/${clientconf.uploadzip[ files.data.name ].dest}`, clientconf.uploadzip[ files.data.name ].unzipoption ] );
var newFiles = cp.spawnSync( '7z', [ 'e', `-p${clientconf.uploadzip[ files.data.name ].psw}`, `${newPath}/${files.data.name}`, `-o${config.tribes}/${req.session.header.xworkon}/${clientconf.uploadzip[ files.data.name ].dest}`, clientconf.uploadzip[ files.data.name ].unzipoption ] );
console.log( newFiles.output.toString() )
if( newFiles.output.toString()
.includes( 'Everything is Ok' ) ) {
if( clientconf.uploadzip[ files.data.name ].callback ) {
const integ = require( `${config.tribes}/${req.session.header.xworkon}/${clientconf.uploadzip[files.data.name].callback}` )
.run();
console.log( 'integration', integ )
return res.status( integ.status )
.send( integ.payload );
} else {
return res.status( 200 )
.send( {
info: [ "successfullsent" ],
models: "Tribes"
} );
}
} else {
return res.status( 400 )
.send( {
info: [ "zipfileerror" ],
models: "Tribes",
moreinfo: newFiles.output.toString()
} )
}
}
} )
} );
router.post( '/upload', checkHeaders, isAuthenticated, ( req, res ) => {
1 // ACHANGER VIA usage sendjson
// url /Tribes/upload?save=tmp&rep=referentials/dataManagement
// if save=tmp then store in a tmp file
// if save=ok then mv the tmp file to the folder
// midlleware hasAccessrighton.js is not apply here only to access/update/create information inside an object
// to upload a file a user need accessrights to data: object: C or to Own it
// or if dir.file exist a .info.json into folder get shared as C in uuid accessright
/*
to add in front
<form action="/upload" method="POST" enctype="multipart/form-data">
<input type="file" name="file"/>
<input type="submit" value="upload"/>
</form>
*/
console.log( 'Envoie image' )
console.log( 'body', req.body );
console.log( 'params', req.params );
//const authfolder = Tribes.checkaccessfiles( req.params.rep, 'C', req.session.header.accessrights, req.session.header.xpaganid );
// cheack autorisation to create or replace a file for this accessrights user
const authfolder = { ok: "tt" }
if( authfolder.ok ) {
if( req.params.save == 'file' ) {
if( fs.existsSync( req.body.filepond ) ) {
fs.mv( req.body.filepond, req.params.rep );
}
};
// voir si c'est toujours pertinent car upload est géré par filepond pour les image
if( req.params.save == 'upload' ) {
const form = formidable( { multiples: false } );
form.parse( req, ( err, fields, files ) => {
if( err ) { next( err ); return; }
let thefile = files.filebond.path;
fs.outputFileSync()
console.log( 'thefile:' + thefile );
res.writeHead( 200, { 'Content-Type': 'text/plain' } );
res.end( theFile );
} )
}
} else {
res.status( 403 )
.send( 'forbiden access' );
}
} );
/*
Manage tribeid into /data/tribee/tribeid
client space dedicated
@Todo
clientconfglob copy cut from Referentials.clientconfglob
clientconf.json copy cut from Referentials.clientconf
list of tribeid copy cut from Referentials.
Add a tribeid
update clientconf
*/
module.exports = router;

49
app/routes/uploadFiles.js Executable file
View File

@@ -0,0 +1,49 @@
// Upload de file
const express = require( 'express' );
const path = require( 'path' );
const jsonfile = require( 'jsonfile' );
const fs = require( 'fs' );
// Classes
const UploadFile = require( '../models/UploadFiles' );
// Middlewares
const checkHeaders = require( '../middlewares/checkHeaders' );
const isAuthenticated = require( '../middlewares/isAuthenticated' );
const router = express.Router();
router.post( '/', checkHeaders, ( req, res ) => {
console.log( 'route uploadFile post ' );
const saveFile = UploadFile.add( req, req.session.header );
res.send( saveFile );
// res.send({ status: 200, payload: { info: 'fine' } });
} );
router.post( '/json', checkHeaders, ( req, res ) => {
console.log( 'route uploadFile post de fichier json ' );
// a callback can be pass to req.body to run a specific process after upload
const saveFile = UploadFile.addjson( req.body, req.session.header );
console.log( saveFile );
res.send( saveFile );
// res.send({ status: 200, payload: { info: 'fine' } });
} );
router.get( '/:filename', checkHeaders, isAuthenticated, ( req, res ) => {
console.log( 'route uploadFile get ', req.params.filename );
const pushFile = UploadFile.get(
req.params.filename.replace( /______/g, '/' ),
req.session.header
);
if( pushFile.status == 200 ) {
if( path.extname( pushFile.payload.file ) === '.json' ) {
fs.readJson( pushFile.payload.file, ( err, p ) => {
if( err ) console.error( err );
res.jsonp( p );
} );
} else {
res.download( pushFile.payload.file, path.basename( pushFile.payload.file ) );
}
} else {
res.send( pushFile );
}
} );
module.exports = router;