564 lines
16 KiB
JavaScript
564 lines
16 KiB
JavaScript
const glob = require("glob");
|
|
const path = require("path");
|
|
const fs = require("fs-extra");
|
|
const axios = require("axios");
|
|
const dayjs = require("dayjs");
|
|
const Mustache = require("mustache");
|
|
const Checkjson = require(`./Checkjson.js`);
|
|
//const smtp = require("smtp-client");
|
|
const nodemailer = require("nodemailer");
|
|
const conf = require(`../../../adminapi/objects/tribes/itm/adminapi.json`);
|
|
const currentmod = "Notifications";
|
|
const log = process.env.ACTIVELOG
|
|
? process.env.ACTIVELOG.split(",").includes(currentmod)
|
|
: false;
|
|
/**
|
|
* To manage any communication between Pagan
|
|
* mayor druid emailing/sms/paper from tribe register smtp, simcard, mail api to Person(s) / Pagan(s)
|
|
* volatile notification message from tribe activities to Pagans / person ()
|
|
*
|
|
*/
|
|
const Notifications = {};
|
|
|
|
Notifications.get = (alias, tribeId) => {
|
|
const notiffile = `../${req.params.tribe}/notifications/${req.params.alias}.json`;
|
|
const msg = fs.existsSync(notiffile) ? fs.readJSONSync(notiffile) : {};
|
|
return {
|
|
status: 200,
|
|
ref: "Notification",
|
|
msg: "Messagelist",
|
|
data: { notif: [{ tribeId, msg }] },
|
|
};
|
|
};
|
|
/**
|
|
* Get statistic of registering email phone
|
|
*/
|
|
Notifications.statmaillist = (tribe) => {
|
|
const statinfo = {};
|
|
let csv = "email/phone;name;srckey\n";
|
|
const src = `../${tribe}/objects/maillinglists/*.json`;
|
|
console.log(path.resolve(src));
|
|
glob.sync(src).forEach((f) => {
|
|
const name = path.basename(f, ".json");
|
|
const mlst = fs.readJSONSync(f);
|
|
Object.keys(mlst).forEach((c) => {
|
|
csv += `"${c}";"${name}";"${mlst[c].srckeys.join("-")}"\n`;
|
|
mlst[c].srckeys.forEach((s) => {
|
|
if (!statinfo[s]) statinfo[s] = {};
|
|
if (!statinfo[s][name]) statinfo[s][name] = 0;
|
|
statinfo[s][name]++;
|
|
});
|
|
});
|
|
});
|
|
// fichier csv stocker en local en attendant d'avoir un back pour stocker la reponse dans data.csv
|
|
fs.outputFileSync(`../${tribe}/mailinglst.csv`, csv, "utf-8");
|
|
return {
|
|
status: 200,
|
|
ref: "Notifications",
|
|
msg: "statistics",
|
|
data: statinfo,
|
|
};
|
|
};
|
|
|
|
/**
|
|
* Register an typekey(email) or (phone) key into mailinglist for a tribe
|
|
*
|
|
*/
|
|
Notifications.registertolist = (key, typekey, tribe, mlist, srckey, uuid) => {
|
|
key = key.toLowerCase();
|
|
typekey = typekey == "telephone" ? "telephonefr" : typekey;
|
|
if (!Checkjson.testformat(key, typekey))
|
|
return {
|
|
status: 400,
|
|
ref: "Notifications",
|
|
msg: "formaterror",
|
|
data: { fielderr: typekey, format: typekey },
|
|
};
|
|
|
|
const destin = `../${tribe}/objects/maillinglists/${typekey}_${mlist}.json`;
|
|
if (!fs.existsSync(destin)) {
|
|
console.log(
|
|
`######## Attention tentative d'ecriture non autorisé,le fichier n'existe pas ${destin} créer le à la main vide {}`,
|
|
);
|
|
return {
|
|
status: 406,
|
|
ref: "Notifications",
|
|
msg: "destinnotallow",
|
|
data: { destin },
|
|
};
|
|
}
|
|
|
|
const filestorage = fs.existsSync(destin) ? fs.readJsonSync(destin) : {};
|
|
//if (log) console.log(currentmod,`filestorage`,filestorage, key, (filestorage[key]));
|
|
if (filestorage[key]) {
|
|
filestorage[key].dt_update = dayjs().toISOString();
|
|
if (!filestorage[key].srckeys.includes(srckey))
|
|
filestorage[key].srckeys.push(srckey);
|
|
if (!filestorage[key].uuids.includes(uuid))
|
|
filestorage[key].uuids.push(uuid);
|
|
} else {
|
|
filestorage[key] = {};
|
|
filestorage[key].dt_create = dayjs().toISOString();
|
|
filestorage[key].srckeys = [srckey];
|
|
filestorage[key].uuids = [uuid];
|
|
}
|
|
fs.outputJSONSync(destin, filestorage);
|
|
return {
|
|
status: 200,
|
|
ref: "Notifications",
|
|
msg: "registersuccess",
|
|
data: { key, typekey, tribe, mlist, srckey, uuid },
|
|
};
|
|
};
|
|
/**
|
|
* Unsubscribe an eamil or phone from a mailinglist for a tribe
|
|
*/
|
|
Notifications.unregisterfromlist = (key, typekey, tribe, mlist) => {
|
|
key = key.toLowerCase();
|
|
};
|
|
/**
|
|
* Message to send to an alias from an anonymous or not
|
|
*/
|
|
Notifications.sendcontact = (body, header) => {};
|
|
Notifications.sendsms = async (data, tribeId) => {
|
|
/**
|
|
* Never use need wallet in mailjet to test
|
|
* To set up with mailjet see https://dev.mailjet.com/sms/guides/send-sms-api/#authentication
|
|
*
|
|
* @param {string} data.To a phone number with international +3360101010101
|
|
* @param {string} data.Text text to send
|
|
*
|
|
* a conf.sms with {url:"smsurl", Token:"", From:""}
|
|
*
|
|
*
|
|
*/
|
|
|
|
if (!conf.sms) {
|
|
return {
|
|
status: 412,
|
|
ref: "Notifications",
|
|
msg: "missingconf",
|
|
data: { tribe: tribeId },
|
|
};
|
|
}
|
|
let missingk = [];
|
|
["To", "Text"].forEach((k) => {
|
|
if (!data[k]) {
|
|
missingk.push(k);
|
|
}
|
|
});
|
|
if (missingk.length > 0) {
|
|
return {
|
|
status: 428,
|
|
ref: "Notifications",
|
|
msg: "missingdata",
|
|
data: { missingk: missingk },
|
|
};
|
|
}
|
|
let confsms = conf.sms;
|
|
if (
|
|
fs.existsSync(
|
|
`../adminapi/objects/tribes/itm/${req.session.header.xtribe}.json`,
|
|
)
|
|
) {
|
|
const conftrib = fs.readJSONSync(
|
|
`../adminapi/objects/tribes/itm/${req.session.header.xtribe}.json`,
|
|
);
|
|
if (conftrib.sms) confsms = conftrib.sms;
|
|
}
|
|
data.From = confsms.From;
|
|
const sendsms = await axios.post(confsms.url, {
|
|
headers: {
|
|
Authorization: `Bearer ${confsms.MJ_TOKEN}`,
|
|
"Content-Type": "application/json",
|
|
},
|
|
body: JSON.stringify(data),
|
|
});
|
|
if (sendsms.status == 200) {
|
|
return {
|
|
status: 200,
|
|
ref: "Notifications",
|
|
msg: "successfullsentsms",
|
|
data: {},
|
|
};
|
|
} else {
|
|
return {
|
|
status: sendsms.status,
|
|
ref: "Notifications",
|
|
msg: "errsendsms",
|
|
data: { err: sendsms.data },
|
|
};
|
|
}
|
|
|
|
/* si tout se passe bien:
|
|
{
|
|
"From": "MJPilot",
|
|
"To": "+33600000000",
|
|
"Text": "Have a nice SMS flight with Mailjet !",
|
|
"MessageId": "2034075536371630429",
|
|
"SmsCount": 1,
|
|
"CreationTS": 1521626400,
|
|
"SentTS": 1521626402,
|
|
"Cost": {
|
|
"Value": 0.0012,
|
|
"Currency": "EUR"
|
|
},
|
|
"Status": {
|
|
"Code": 2,
|
|
"Name": "sent",
|
|
"Description": "Message sent"
|
|
}
|
|
}
|
|
}
|
|
*/
|
|
};
|
|
Notifications.manageemail = async (
|
|
data,
|
|
templatename,
|
|
tribe,
|
|
lg,
|
|
numberpersecond = 10,
|
|
profils = ["anonymous"],
|
|
) => {
|
|
const sleep = (ms) => {
|
|
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
};
|
|
/**
|
|
* Manage email publipostage
|
|
* data must contain
|
|
* {
|
|
* from:"", //mandatory
|
|
* cc:[],bcc:[], present in all email sent
|
|
* emailsto:[ //mandatory
|
|
* {
|
|
* to, //mandatory
|
|
* cc,bcc,
|
|
* attachements:[{filename:"text.txt",pathfile:"/media/phil/textA.txt","contenttype":"text/plain"}],
|
|
* any properties to template
|
|
* }
|
|
* ]
|
|
* }
|
|
* emailsto contain the personnalisation data in data we can have directly cc bcc, that is added to the main
|
|
*
|
|
* template a name that exist in /tribe/{{templatename}}_{{lg}}.js
|
|
*
|
|
*/
|
|
//console.log(data)
|
|
const start = Date.now();
|
|
if (!data.emailsto || data.emailsto.length == 0) {
|
|
return {
|
|
status: 406,
|
|
ref: "Notifications",
|
|
msg: "emailstomissing",
|
|
data: data,
|
|
};
|
|
}
|
|
//if (typeof data.emailsto === "string") data.emailsto = [data.emailsto];
|
|
const tplpath = `../${tribe}/template/${templatename}_${lg}.js`;
|
|
if (!fs.existsSync(tplpath)) {
|
|
return {
|
|
status: 404,
|
|
ref: "Notification",
|
|
msg: "templatenotfound",
|
|
data: { template: tplpath },
|
|
};
|
|
}
|
|
const results = { fail: [], success: [] };
|
|
const emailtosend = {};
|
|
// init with common propertie
|
|
Object.keys(data).forEach((k) => {
|
|
if (!["emailsto"].includes(k)) emailtosend[k] = data[k];
|
|
});
|
|
const tplemail = require(path.resolve(tplpath));
|
|
// check allowed profil
|
|
if (!tplemail.allowedprofils) {
|
|
return {
|
|
status: 406,
|
|
ref: "Notification",
|
|
msg: "missingallowedprofils",
|
|
data: { template: tplpath },
|
|
};
|
|
}
|
|
if (!profils.includes("druid")) {
|
|
const setallow = new Set(tplemail.allowedprofils);
|
|
if (profils.filter((v) => setallow.has(v)).length == 0) {
|
|
return {
|
|
status: 403,
|
|
ref: "Notification",
|
|
msg: "profilsnotallowed",
|
|
data: { template: tplpath, profils },
|
|
};
|
|
}
|
|
}
|
|
|
|
let sendit = { status: 200, ref: "Notifications", msg: "successfullsend" };
|
|
|
|
for (let i = 0; i < data.emailsto.length; i++) {
|
|
const email = data.emailsto[i];
|
|
if (typeof email.to === "string") {
|
|
email.to = [email.to];
|
|
}
|
|
emailtosend.to = "";
|
|
email.to.forEach((em) => {
|
|
if (typeof em === "string" && Checkjson.testformat(em, "email")) {
|
|
emailtosend.to += em.toLowerCase().trim() + ",";
|
|
} else {
|
|
results.fail.push({ to: em, err: ["errorformat"] });
|
|
}
|
|
});
|
|
if (emailtosend.to.length > 2) {
|
|
emailtosend.to = emailtosend.to.slice(0, -1);
|
|
emailtosend.templatename = templatename;
|
|
emailtosend.lg = lg;
|
|
emailtosend.subject = Mustache.render(tplemail.subject, email);
|
|
emailtosend.html = Mustache.render(tplemail.html, email);
|
|
emailtosend.text = Mustache.render(tplemail.text, email);
|
|
if (email.cc) {
|
|
emailtosend.cc += emailtosend.cc && emailtosend.cc != "" ? "," : "";
|
|
emailtosend.cc += email.cc.join(",");
|
|
}
|
|
if (email.bcc) {
|
|
emailtosend.bcc += emailtosend.bcc && emailtosend.bcc != "" ? "," : "";
|
|
emailtosend.bcc += email.bcc.join(",");
|
|
}
|
|
if (email.attachments) {
|
|
email.attachments.forEach((a) => tplemail.attachments.push(a));
|
|
}
|
|
//console.log(emailtosend)
|
|
sendit = await Notifications.sendmail(emailtosend, tribe);
|
|
//const sendit={status:200}
|
|
//console.log(sendit)
|
|
if (sendit.status == 200) {
|
|
results.success.push(emailtosend.to);
|
|
} else {
|
|
results.fail.push({ to: emailtosend.to, status: sendit.status });
|
|
console.log(sendit);
|
|
}
|
|
if ((i + 1) % numberpersecond === 0) {
|
|
console.log("Prochain lot attend 1 seconde");
|
|
await sleep(1000);
|
|
}
|
|
}
|
|
}
|
|
fs.outputJSONSync(
|
|
`../${tribe}/logs/campains/${templatename}_${lg}_${Date.now()}`,
|
|
results,
|
|
);
|
|
return {
|
|
status: 200,
|
|
ref: "Notifications",
|
|
msg: "emailmanage",
|
|
data: {
|
|
emailsucces: results.success.length,
|
|
emailfail: results.fail.length,
|
|
failsdetail: results.fail,
|
|
timems: Date.now() - start,
|
|
},
|
|
};
|
|
};
|
|
|
|
Notifications.sendmail = async (data, tribe) => {
|
|
/**
|
|
* * in conf global or in /itm/{tribe}.json must have valid parameter emailcontact must be authorized by the smtp
|
|
* "emailcontact": "noreply@smatchit.io",
|
|
* "smtp": {
|
|
* "host": "smtp-relay.brevo.com",
|
|
* "port": 587,
|
|
* "secure": false,
|
|
* "auth": {
|
|
* "user": "xx",
|
|
* "pass": "yy"
|
|
* }
|
|
* }
|
|
* See https://nodemailer.com/message/ for available fields to add
|
|
* @param {string} [data.from] an email authorized by smtp used priority from header xtribe
|
|
* @param {string} data.to list of email separate by ,
|
|
* @param {string} data.subject
|
|
* @param {string} data.html
|
|
* @param {string} data.text
|
|
* @param {string} [data.Cc] list of email in copy
|
|
* @param {string} [data.Bcc] list of email in hidden copy
|
|
* @param {string} [data.attachments] array of
|
|
* {filename:'filename.txt',content:'txt'},
|
|
* {filename:'img.svg',path:"https://....svg", contentType:'image/svg'}
|
|
* {filename:'img.svg',path:"https://....svg", contentType :'text/plain'}
|
|
* {filename:'img.png',path:"data:text/svg;base64.aGVsbG8gd29ybGQ="}
|
|
*
|
|
* @example data
|
|
* {"to":"wall-ants@ndda.fr",
|
|
* "subject":"Test",
|
|
* "html":"<h1>test welcome</h1>",
|
|
* "text":"test welcome",
|
|
* "attachments":[{filename:"text.txt",pathfile:"/media/phil/textA.txt","contenttype":"text/plain"}]
|
|
* }
|
|
* @return {object}
|
|
* { status: 200, ref:"pagans",msg:"",data: { } }
|
|
*
|
|
*
|
|
*/
|
|
async function ensureContact(email,BREVO_APIKEY) {
|
|
try {
|
|
await axios.post(
|
|
"https://api.brevo.com/v3/contacts",
|
|
{
|
|
email: email,
|
|
emailBlacklisted: false,
|
|
updateEnabled: true,
|
|
},
|
|
{
|
|
headers: {
|
|
"api-key": BREVO_APIKEY,
|
|
"Content-Type": "application/json",
|
|
},
|
|
},
|
|
);
|
|
} catch (err) {
|
|
console.log("Brevo contact error:", err.response?.data || err.message);
|
|
}
|
|
}
|
|
if (!conf.smtp || !conf.emailcontact) {
|
|
return {
|
|
status: 412,
|
|
ref: "Notifications",
|
|
msg: "missingconf",
|
|
data: { tribe: tribe },
|
|
};
|
|
}
|
|
|
|
if (!data.from) {
|
|
data.from = conf.emailcontact;
|
|
}
|
|
|
|
let missingk = [];
|
|
["from", "to", "subject", "html", "text"].forEach((k) => {
|
|
if (!data[k]) {
|
|
missingk.push(k);
|
|
}
|
|
});
|
|
if (missingk.length > 0) {
|
|
return {
|
|
status: 428,
|
|
ref: "Notifications",
|
|
msg: "missingdata",
|
|
data: { missingk: missingk },
|
|
};
|
|
}
|
|
let confsmtp = conf.smtp;
|
|
const conftribfile = `../adminapi/objects/tribes/itm/${tribe}.json`;
|
|
if (fs.existsSync(conftribfile)) {
|
|
const conftrib = fs.readJSONSync(conftribfile);
|
|
if (!conftrib.emailcontact) {
|
|
return {
|
|
status: 428,
|
|
ref: "Notifications",
|
|
msg: "missingemailcontactinconf",
|
|
data: { tribe },
|
|
};
|
|
}
|
|
confsmtp = conftrib.smtp;
|
|
if (!data.from || data.from == conf.emailcontact)
|
|
data.from = conftrib.emailcontact;
|
|
}
|
|
const toEmails = data.to.split(",");
|
|
for (const email of toEmails) {
|
|
await ensureContact(email,conftrib.BREVO.APIKEY);
|
|
}
|
|
//console.log(confsmtp);
|
|
const transporter = await nodemailer.createTransport(confsmtp);
|
|
if (data.filelist) {
|
|
data.attachments = data.attachments || [];
|
|
let missingfile = [];
|
|
data.filelist.forEach((fo) => {
|
|
if (fs.existsSync(fo.pathfile)) {
|
|
data.attachments.push({
|
|
filename: fo.filename || path.basename(fo.pathfile),
|
|
path: fo.pathfile,
|
|
contentType: fo.contenttype || "application/octet-stream",
|
|
});
|
|
} else {
|
|
missingfile.push(fo.pathfile);
|
|
}
|
|
});
|
|
if (missingfile.length > 0) {
|
|
return {
|
|
status: 428,
|
|
ref: "Notifications",
|
|
msg: "missingfiles",
|
|
data: { missingfiles: missingfile },
|
|
};
|
|
}
|
|
}
|
|
//console.log("data:", data);
|
|
let res;
|
|
let error;
|
|
/*console.log(
|
|
"from:",
|
|
data.from,
|
|
" to:",
|
|
data.to,
|
|
" replyto:",
|
|
data.replyto,
|
|
" cc:",
|
|
data.cc,
|
|
" bcc:",
|
|
data.bcc,
|
|
" subject:",
|
|
data.subject
|
|
);
|
|
*/
|
|
fs.outputFile(
|
|
`../${tribe}/logs/template/${data.templatename}_${data.lg}.html`,
|
|
data.html,
|
|
);
|
|
|
|
try {
|
|
res = await transporter.sendMail(data);
|
|
} catch (err) {
|
|
console.log("Erreur :", err);
|
|
if (err.code) {
|
|
error = err.code;
|
|
} else {
|
|
error = err;
|
|
}
|
|
}
|
|
console.log("sendmail result:", res);
|
|
if (res && res.accepted) {
|
|
// Normalize emails for comparison
|
|
const normalizedAccepted = res.accepted.map((email) =>
|
|
email.trim().toLowerCase(),
|
|
);
|
|
const toEmails = data.to
|
|
.split(",")
|
|
.map((email) => email.trim().toLowerCase());
|
|
console.log(
|
|
"normalizedAccepted:",
|
|
normalizedAccepted,
|
|
"toEmails:",
|
|
toEmails,
|
|
);
|
|
const allAccepted = toEmails.every((email) =>
|
|
normalizedAccepted.includes(email),
|
|
);
|
|
|
|
if (allAccepted && (!res.rejected || res.rejected.length === 0)) {
|
|
data.accepted = res.accepted;
|
|
data.rejected = res.rejected;
|
|
return {
|
|
status: 200,
|
|
ref: "Notifications",
|
|
msg: "successfullsentemail",
|
|
data,
|
|
};
|
|
} else {
|
|
data.accepted = res.accepted;
|
|
data.rejected = res.rejected;
|
|
return { status: 410, ref: "Notifications", msg: "errsendmail", data };
|
|
}
|
|
} else {
|
|
data.errmailer = error;
|
|
return { status: 417, ref: "Notifications", msg: "errsendmail", data };
|
|
}
|
|
};
|
|
|
|
module.exports = Notifications;
|