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 checkdata = require( `${config.tribes}/${config.mayorId}/www/cdn/public/js/checkdata` ); /* Message manager * Manage apixtribe 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/apixtribe/-/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 apixtribe @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 confapixtribe = fs.readJsonSync( `${config.tribes}/${config.mayorId}/clientconf.json` ); if( !( confapixtribe.smtp && confapixtribe.smtp.mailjet ) ) { return { status: 403, data: { models: "Messages", info: [ "nosmtpmailjet" ], moreinfo: "missing smtpmailjet parameter in apixtribe contact your admin to activate an mailjet account on this server." } } } tribeidsender = "apixtribe"; mailjetconf = confapixtribe.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: apixtribe/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 = `

${data.contactname} - ${contact.replace(/###/g,' ').replace(/##/g,":")}

Message: ${data.contactmessage}

` 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 check = Plug[ data.callback.function ]( header.xworkon, data ); if( check.status == 200 ) { data = check.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 adminapixtribe 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 = jsonfile.readFileSync( `${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 = jsonfile.readFileSync( `${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;