493 lines
16 KiB
JavaScript
493 lines
16 KiB
JavaScript
|
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 checkdata = require( `${config.tribes}/${config.mayorId}/www/cdn/public/js/checkdata` );
|
||
|
|
||
|
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 = jsonfile.readFileSync( `${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 {
|
||
|
jsonfile.writeFileSync( header.destinationfile + '/' + header.filename, data.jsonp );
|
||
|
if( data.callback ) {
|
||
|
const execCB = require( `${config.mainDir}/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;
|