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 checkdata = require( `${config.tribes}/${config.mayorId}/www/cdn/public/js/checkdata` ); 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 = jsonfile.readFileSync( `${config.tribes}/${msg.tribeidperso.tribeidexpediteur}/clientconf.json` ); //console.log('expediteur', confclientexpediteur); msg.smtp = confclientexpediteur.smtp; /* const confclient = jsonfile.readFileSync( `${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 { 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.generepdf = ( req, header ) => { return new Promise( ( resolve, reject ) => { let options = { format: "A4", orientation: "portrait", border: "10mm", footer: { height: "20mm", contents: { default: '{{page}}/{{pages}}', // 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;