557 lines
18 KiB
JavaScript
Executable File
557 lines
18 KiB
JavaScript
Executable File
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('../nationchains/socialworld/contracts/checkdata.js')
|
|
|
|
const logger = require('../core/logger')
|
|
|
|
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;
|
|
// logger.info( ggconnect )
|
|
doc = new GoogleSpreadsheet(confgg.productIdSpreadsheet)
|
|
await doc.useServiceAccountAuth(confgg.googleDriveCredentials)
|
|
await doc.loadInfo()
|
|
const result = []
|
|
let invalidfor = ''
|
|
// logger.info( "sheets", req.sheets );
|
|
for (const sheetName of req.sheets) {
|
|
logger.info('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 => {
|
|
const 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) => {
|
|
// logger.info('{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}');
|
|
// logger.info('msg to send', msg);
|
|
logger.info('nbfois', nbfois)
|
|
const transporter = nodemailer.createTransport(msg.smtp)
|
|
if (!nowait) {
|
|
logger.info('attente 1er msg avant d envoyer les autres')
|
|
const transport = await transporter.verify()
|
|
logger.info('transport', transport)
|
|
if (transport.error) {
|
|
logger.info('Probleme de smtp', error)
|
|
return {
|
|
status: 500,
|
|
payload: {
|
|
info: ''
|
|
}
|
|
}
|
|
} else {
|
|
const rep = await transporter.sendMail(msg)
|
|
logger.info('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) {
|
|
logger.info('nouvelle tentative ', nbfois)
|
|
await sleep(600000) // attends 60sec pour faire une niéme tentative
|
|
Outputs.envoiemail(msg, true, nbfois + 1)
|
|
} else {
|
|
// logerror in file
|
|
logger.info('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')
|
|
// logger.info('msg.to not well sent', msg.to);
|
|
}
|
|
} else {
|
|
logger.info('info', info)
|
|
// logger.info('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
|
|
logger.info('pass Outputs.generemsg')
|
|
try {
|
|
const confclientexpediteur = jsonfile.readFileSync(`${config.tribes}/${msg.tribeidperso.tribeidexpediteur}/clientconf.json`)
|
|
// logger.info('expediteur', confclientexpediteur);
|
|
msg.smtp = confclientexpediteur.smtp
|
|
/* const confclient = jsonfile.readFileSync(
|
|
`${config.tribes}/${msg.tribeidperso.tribeid}/clientconf.json`
|
|
); */
|
|
} catch (err) {
|
|
logger.info('la conf smtp du client n\'est pas definie')
|
|
return {
|
|
status: 404,
|
|
payload: {
|
|
info: ['smtpnotdefined'],
|
|
model: 'Outputs'
|
|
}
|
|
}
|
|
}
|
|
logger.info(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) {
|
|
logger.info('WARNING, html file template missing ' + config.sharedData + '/' + msg.template.htmlfile)
|
|
logger.info(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) {
|
|
logger.info('template.html est vide')
|
|
return {
|
|
status: 404,
|
|
payload: {
|
|
info: ['ERRnotemplate'],
|
|
model: 'Outputs',
|
|
moreinfo: 'No template email check '
|
|
}
|
|
}
|
|
}
|
|
// mustache any data into
|
|
// logger.info(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}
|
|
// logger.info(msg);
|
|
logger.info('nb de message to send:', msg.destperso.length)
|
|
// logger.info(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
|
|
logger.info('msg2send.to ' + msg2send.to + ' pos:' + pos)
|
|
// logger.info('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')
|
|
logger.info('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
|
|
*/
|
|
// logger.info('envoie msg', msg);
|
|
// logger.info(msg2send);
|
|
const ret = await Outputs.envoiemail(msg2send, nowait, 0)
|
|
logger.info('ret 1er msg', ret)
|
|
if (ret.status === 200) {
|
|
pass1ermsg = true
|
|
};
|
|
} else if (pass1ermsg) {
|
|
logger.info('###############################################')
|
|
logger.info('envoie msg numero: ' + pos + ' email: ' + msg2send.to)
|
|
// logger.info(msg2send)
|
|
Outputs.envoiemail(msg2send, nowait, 0)
|
|
/* Outputs.envoiemail(msg2send, nowait, 0).then(rep => {
|
|
logger.info("envoie email" + pos)
|
|
}).catch(err => {
|
|
logger.info(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
|
|
*/
|
|
// logger.info(msg)
|
|
// On ajoute le contenu du template directement dans la demande
|
|
if (msg.template.sendascontent && msg.template.htmlfile) {
|
|
try {
|
|
logger.info('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) {
|
|
logger.info('WARNING, html file template missing ' + config.sharedData + '/' + msg.template.htmlfile)
|
|
// logger.info(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 '
|
|
}
|
|
}
|
|
}
|
|
}
|
|
logger.info('envoie sur', `${config.emailerurl}/outputs/msg`)
|
|
// logger.info(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
|
|
logger.info('header after traduction: ', headersmsg)
|
|
try {
|
|
const resCamp = await axios.post(`${config.emailerurl}/outputs/msg`, msg, {
|
|
headers: headersmsg
|
|
})
|
|
// logger.info('Etat:', resCamp);
|
|
logger.info('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}`
|
|
// logger.info('fichier demande ', file);
|
|
if (!fs.existsSync(file)) {
|
|
// logger.info('le fichier demande n existe pas ', file);
|
|
return {
|
|
status: 404,
|
|
payload: {
|
|
info: ['fileUnknown'],
|
|
model: 'UploadFiles'
|
|
}
|
|
}
|
|
} else {
|
|
logger.info('envoie le fichier ', file)
|
|
return {
|
|
status: 200,
|
|
payload: {
|
|
info: ['fileknown'],
|
|
model: 'UploadFiles',
|
|
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'}
|
|
*/
|
|
// logger.info(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) {
|
|
logger.info('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()
|
|
logger.info('req.headers', req.headers)
|
|
logger.info('req.params', req.params)
|
|
logger.info('req.query', req.query)
|
|
logger.info('req.body', req.body)
|
|
let destinationfile = `${config.tribes}/${header.xworkon}/${header.destinationfile
|
|
}`
|
|
form.parse(req, function (err, fields, files) {
|
|
logger.info('files', files.file.path)
|
|
logger.info('fields', fields)
|
|
const oldpath = files.file.path
|
|
destinationfile += '/' + files.file.name
|
|
logger.info('oldpath', oldpath)
|
|
logger.info('destinationfile', destinationfile)
|
|
fs.copyFile(oldpath, destinationfile, function (err) {
|
|
if (err) {
|
|
logger.info(err)
|
|
return {
|
|
status: 500,
|
|
payload: {
|
|
info: ['savingError'],
|
|
model: 'UploadFiles'
|
|
}
|
|
}
|
|
} else {
|
|
logger.info('passe')
|
|
fs.unlink(oldpath)
|
|
return {
|
|
status: 200,
|
|
payload: {
|
|
info: ['wellUpload'],
|
|
model: 'UploadFiles',
|
|
render: {
|
|
destination: destinationfile
|
|
}
|
|
}
|
|
}
|
|
}
|
|
})
|
|
})
|
|
}
|
|
Outputs.generepdf = (req, header) => {
|
|
return new Promise((resolve, reject) => {
|
|
const 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
|
|
}
|
|
}
|
|
}
|
|
const 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
|