Advertisement
Guest User

Untitled

a guest
Aug 14th, 2018
453
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
  1. 'use strict';
  2.  
  3. var app = require('../../server/server');
  4. var constants = require('../../server/constants');
  5. var Enumerable = require('linq');
  6. var emailer = require('emailer');
  7. var templateManager = require('../../server/email-templates/template-manager');
  8. var watt = require('watt');
  9. var encryptor = require('encryptor');
  10. var key = require('../../server/constants').ENCRYPTION_KEY;
  11. var async = require('async');
  12.  
  13. emailer.init(constants.DEFAULT_EMAIL_SENDER, constants.DEFAULT_EMAIL_SENDER_PASSWORD);
  14.  
  15. module.exports = function (Meeting) {
  16.   Meeting.on('editNextMeetings', function (data) {
  17.     editNextMeetings(data.editThisAndFollowing, data.diffMeetings, data.currentMeeting, function (err) {
  18.       if (err) console.log(err);
  19.       console.log('editNextMeetings ' + (err ? 'failed' : 'succeeded') + '!!');
  20.     });
  21.   });
  22.  
  23.   Meeting.on('createNextMeetings', function (data) {
  24.     createNextMeetings(data.currentMeeting, data.datesOfNextMeetings, data.invitees, data.agendaItems, function (err) {
  25.       if (err) console.log(err);
  26.       console.log('createNextMeetings ' + (err ? 'failed' : 'succeeded') + '!!');
  27.     });
  28.   });
  29.  
  30.   Meeting.on('notifyUser', function (data) {
  31.     app.models.EnvironmentUser.findById(data.envUserId, {
  32.       include: ['user', { environment: 'config' }]
  33.     }, function (err, environmentUser) {
  34.       if (err) {
  35.         console.log('err:', err);
  36.         return;
  37.       }
  38.       if (!environmentUser) {
  39.         console.log('Cannot found EnvironmentUser with ID %d', data.envUserId);
  40.         return;
  41.       }
  42.       const icalToolkit = require('ical-toolkit');
  43.       //Create a builder
  44.       const builder = icalToolkit.createIcsFileBuilder();
  45.       builder.spacers = true; //Add space in ICS file, better human reading. Default: true
  46.       builder.NEWLINE_CHAR = '\r\n'; //Newline char to use.
  47.       builder.throwError = false; //If true throws errors, else returns error when you do .toString() to generate the file contents.
  48.       builder.ignoreTZIDMismatch = true; //If TZID is invalid, ignore or not to ignore!
  49.       builder.calname = 'Yo Cal';
  50.       builder.method = 'CANCEL';
  51.       //Add events
  52.       builder.events.push({
  53.         start: data.meeting.startDate,
  54.         end: data.meeting.endDate,
  55.         transp: 'OPAQUE',
  56.         summary: data.meeting.name,
  57.         uid: data.meeting.idEnvironment + '' + data.meeting.id + '@eboard.com',
  58.         location: data.meeting.location,
  59.         description: 'Meeting details/Détails de la réunion',
  60.         organizer: {
  61.           name: environmentUser.environment().config().allowEditOrganiserNameInCalender ? environmentUser.environment().config().edittedOrganiserName : data.meeting.organiser().user().firstName + ' ' + data.meeting.organiser().user().lastName,
  62.           email: environmentUser.environment().config().mailFrom
  63.         },
  64.         attendees: [{
  65.           name: environmentUser.user().firstName + ' ' + environmentUser.user().lastName,
  66.           email: environmentUser.user().email,
  67.           rsvp: true
  68.         }],
  69.         method: 'CANCEL',
  70.         status: 'CANCELLED'
  71.       });
  72.       //Try to build
  73.       let icsContent = builder.toString();
  74.       if (icsContent instanceof Error)
  75.         icsContent = '';
  76.       const moment = require('moment');
  77.       emailer.init(environmentUser.environment().config().smtpUsername, environmentUser.environment().config().smtpPassword, environmentUser.environment().config().smtpServerAddress);
  78.       const emailContent = `<td bgcolor="#ffffff" align="left" height="100%" valign="top" width="100%" style="padding-bottom: 40px;padding:30px;">
  79.           <p>Dear Sir/Madam,</p>
  80.           <p><br></p>
  81.           <p>Kindly note that the following meeting has been cancelled: <strong> `+ data.meeting.name + `</strong></p>
  82.           <p><strong>Company: </strong>`+ data.meeting.company().name + `</p>
  83.           <p><strong>Date: </strong>`+ moment(data.meeting.startDate).format('DD MMM YYYY') + `</p>
  84.           <p><strong>Location: </strong>`+ data.meeting.location + `</p>
  85.           <p><br></p>
  86.           <p>We sincerely apologise for any inconvenience caused.</p>
  87.           <p><br></p>
  88.           <p><strong>Best Regards,</strong></p>
  89.           <p>The Support Team</p>
  90.           <p><br></p>
  91.       </td>`;
  92.       templateManager.injectContent(emailContent, function (injectError, response) {
  93.         if (!injectError) {
  94.           const subject = 'Cancel_' + data.meeting.company().name + '_' + data.meeting.name + '_' + moment(data.meeting.startDate).format('DD MMM YYYY');
  95.           emailer.sendEmail({
  96.             from: environmentUser.environment().config().mailFrom,
  97.             to: environmentUser.user().email,
  98.             cc: data.organiser.email,
  99.             subject: subject,
  100.             isHtml: true,
  101.             body: response,
  102.             alternatives: [{
  103.               headers: { 'Content-Transfer-Encoding': '7bit' },
  104.               contentType: 'text/calendar; charset="utf-8"; method=REPLY',
  105.               content: new Buffer(icsContent ? icsContent : '')
  106.             }]
  107.           }, function (errr, mailInfo) {
  108.             app.models.Email.create({
  109.               from: environmentUser.environment().config().mailFrom,
  110.               to: environmentUser.user().email,
  111.               content: response,
  112.               subject: subject,
  113.               idEnvironment: data.meeting.idEnvironment,
  114.               idMeeting: data.meeting.id,
  115.               date: new Date(),
  116.               type: 'MEETING_DELETE'
  117.             }, function (ex, res) { });
  118.           });
  119.         }
  120.       });
  121.     });
  122.   });
  123.  
  124.   /**
  125.    *
  126.    * @param {Meeting} meeting
  127.    * @param {String} companyName
  128.    * @param {EboardUser} organiser
  129.    * @param {Boolean} showAttendance
  130.    * @param {Number} destId  environmentUser ID of the recipient
  131.    * @returns {String}
  132.    */
  133.   function generateMeetingRequestHtml(meeting, companyName, organiser, showAttendance = false, destId = null) {
  134.     const moment = require('moment');
  135.     const content = `<p>Dear Sir/Madam,</p>
  136.       <p><br></p>
  137.       <p>You have been convened to the following meeting:</p>
  138.       <p><strong>Name: </strong>` + meeting.name + `</p>
  139.       <p><strong>Company: </strong>` + companyName + `</p>
  140.       <p><strong>Date: </strong>` + moment(meeting.startDate).format('DD MMM YYYY') + `</p>
  141.       <p><strong>Time: </strong>` + moment(meeting.startDate).format('HH:mm') + `</p>
  142.       <p><strong>Location: </strong>` + meeting.location + `</p>
  143.       <p><br></p>
  144.       <p><strong>Best Regards,</strong></p>
  145.       <p>` + (organiser.lastName + ' ' + organiser.firstName) + `</p>
  146.       <p><br></p>`;
  147.     const attendance = `<p>To book your calendar please accept/decline the invitation above.</p>
  148.       <p><br></p>
  149.       <p>For your attendance to be visible in PRO meeting, please select one of the following:</p>
  150.       <a href="` + constants.HOSTNAME + `add_attendance.html?idMeeting=` + meeting.id + `&idUser=` + destId + `&attending=1" style="background-color:#EB7035;border:1px solid #EB7035;border-radius:3px;color:#ffffff;display:inline-block;font-family:sans-serif;font-size:16px;line-height:44px;text-align:center;text-decoration:none;width:150px;-webkit-text-size-adjust:none;mso-hide:all;">Yes</a>&nbsp;&nbsp;&nbsp;
  151.       <a href="` + constants.HOSTNAME + `add_attendance.html?idMeeting=` + meeting.id + `&idUser=` + destId + `&attending=0" style="margin-left:30px;background-color:#EB7035;border:1px solid #EB7035;border-radius:3px;color:#ffffff;display:inline-block;font-family:sans-serif;font-size:16px;line-height:44px;text-align:center;text-decoration:none;width:150px;-webkit-text-size-adjust:none;mso-hide:all;">No</a>`;
  152.     return showAttendance ? content + attendance : content;
  153.   }
  154.  
  155.   /**
  156.    *
  157.    * @param {Meeting} meeting
  158.    * @param {String} companyName
  159.    * @param {EboardUser} organiser
  160.    */
  161.   function generateMeetingUpdateHtml(meeting, companyName, organiser) {
  162.     const moment = require('moment');
  163.     const content = `<p>Dear Sir/Madam,</p>
  164.       <p><br></p>
  165.       <p>Kindly note that the details of the meeting <strong>` + meeting.name + `</strong> scheduled for company <strong>` + companyName + `</strong> have been modified to the following:</p>
  166.       <p><strong>Date: </strong>` + moment(meeting.startDate).format('DD MMM YYYY') + `</p>
  167.       <p><strong>Time: </strong>` + moment(meeting.startDate).format('HH:mm') + `</p>
  168.       <p><strong>Location: </strong>` + meeting.location + `</p>
  169.       <p><br></p>
  170.       <p>We sincerely apologise for any inconvenience caused.</p>
  171.       <p><br></p>
  172.       <p><strong>Best Regards,</strong></p>
  173.       <p>` + (organiser.lastName + ' ' + organiser.firstName) + `</p>
  174.       <p><br></p>`;
  175.     return content;
  176.   }
  177.  
  178.   /**
  179.    *
  180.    * @param {Meeting} meeting
  181.    * @param {String} companyName
  182.    */
  183.   function generateMeetingCancellationHtml(meeting, companyName) {
  184.     const moment = require('moment');
  185.     const content = `<p>Dear Sir/Madam,</p>
  186.       <p><br></p>
  187.       <p>Kindly note that the following meeting has been cancelled: <strong> `+ meeting.name + `</strong></p>
  188.       <p><strong>Company: </strong>` + companyName + `</p>
  189.       <p><strong>Date: </strong>` + moment(meeting.startDate).format('DD MMM YYYY') + `</p>
  190.       <p><strong>Location: </strong>` + meeting.location + `</p>
  191.       <p><br></p>
  192.       <p>We sincerely apologise for any inconvenience caused.</p>
  193.       <p><br></p>
  194.       <p><strong>Best Regards,</strong></p>
  195.       <p>The Support Team</p>
  196.       <p><br></p>`;
  197.     return content;
  198.   }
  199.  
  200.   /**
  201.    *
  202.    * @param {EnvironmentConfig} config
  203.    * @param {Meeting} meeting
  204.    * @param {EboardUser} organiser
  205.    * @param {Array<EboardUser>} invitees
  206.    * @param {Function(Error, String)} callback
  207.    */
  208.   function generateICSFileV2(config, meeting, organiser, invitees, callback) {
  209.     const icalToolkit = require('ical-toolkit');
  210.  
  211.     //Create a builder
  212.     const builder = icalToolkit.createIcsFileBuilder();
  213.  
  214.     /*
  215.      * Settings (All Default values shown below. It is optional to specify)
  216.      * */
  217.     builder.spacers = true; //Add space in ICS file, better human reading. Default: true
  218.     builder.NEWLINE_CHAR = '\r\n'; //Newline char to use.
  219.     builder.throwError = false; //If true throws errors, else returns error when you do .toString() to generate the file contents.
  220.     builder.ignoreTZIDMismatch = true; //If TZID is invalid, ignore or not to ignore!
  221.  
  222.     /**
  223.      * Build ICS
  224.      * */
  225.     //Name of calander 'X-WR-CALNAME' tag.
  226.     builder.calname = 'Yo Cal';
  227.  
  228.     //Method
  229.     builder.method = 'REQUEST';
  230.  
  231.     //Add events
  232.     builder.events.push({
  233.       //Event start time, Required: type Date()
  234.       start: meeting.startDate,
  235.  
  236.       //Event end time, Required: type Date()
  237.       end: meeting.endDate,
  238.  
  239.       //transp. Will add TRANSP:OPAQUE to block calendar.
  240.       transp: 'OPAQUE',
  241.  
  242.       //Event summary, Required: type String
  243.       summary: meeting.name,
  244.  
  245.       //Event identifier, Optional, default auto generated
  246.       uid: meeting.idEnvironment + '' + meeting.id + '@eboard.com',
  247.  
  248.       //Location of event, optional.
  249.       location: meeting.location,
  250.  
  251.       //Optional description of event.
  252.       description: 'Meeting details/Détails de la réunion',
  253.  
  254.       //Optional Organizer info
  255.       organizer: {
  256.         name: config.allowEditOrganiserNameInCalender ? config.edittedOrganiserName : organiser.firstName + ' ' + organiser.lastName,
  257.         email: config.mailFrom
  258.       },
  259.       attendees: (invitees || []).map(invitee => { return { name: invitee.firstName + ' ' + invitee.lastName, email: invitee.email, rsvp: true }; }),
  260.  
  261.       //What to do on addition
  262.       method: 'REQUEST'
  263.     });
  264.  
  265.     //Try to build
  266.     const icsFileContent = builder.toString();
  267.  
  268.     //Check if there was an error (Only required if yu configured to return error, else error will be thrown.)
  269.     if (icsFileContent instanceof Error) {
  270.       if (callback) callback(icsFileContent);
  271.       return;
  272.     }
  273.  
  274.     if (callback) callback(null, icsFileContent);
  275.   }
  276.  
  277.   /**
  278.    *
  279.    * @param {EnvironmentConfig} config
  280.    * @param {Meeting} meeting
  281.    * @param {EboardUser} organiser
  282.    * @param {Array<EboardUser>} invitees
  283.    * @param {Function(Error, String)} callback
  284.    */
  285.   function deleteICSFileV2(config, meeting, organiser, invitees, callback) {
  286.     const icalToolkit = require('ical-toolkit');
  287.  
  288.     //Create a builder
  289.     const builder = icalToolkit.createIcsFileBuilder();
  290.  
  291.     /*
  292.      * Settings (All Default values shown below. It is optional to specify)
  293.      * */
  294.     builder.spacers = true; //Add space in ICS file, better human reading. Default: true
  295.     builder.NEWLINE_CHAR = '\r\n'; //Newline char to use.
  296.     builder.throwError = false; //If true throws errors, else returns error when you do .toString() to generate the file contents.
  297.     builder.ignoreTZIDMismatch = true; //If TZID is invalid, ignore or not to ignore!
  298.  
  299.     /**
  300.      * Build ICS
  301.      * */
  302.     //Name of calander 'X-WR-CALNAME' tag.
  303.     builder.calname = 'Yo Cal';
  304.  
  305.     //Method
  306.     builder.method = 'CANCEL';
  307.  
  308.     //Add events
  309.     builder.events.push({
  310.       //Event start time, Required: type Date()
  311.       start: meeting.startDate,
  312.  
  313.       //Event end time, Required: type Date()
  314.       end: meeting.endDate,
  315.  
  316.       //transp. Will add TRANSP:OPAQUE to block calendar.
  317.       transp: 'OPAQUE',
  318.  
  319.       //Event summary, Required: type String
  320.       summary: meeting.name,
  321.  
  322.       //Event identifier, Optional, default auto generated
  323.       uid: meeting.idEnvironment + '' + meeting.id + '@eboard.com',
  324.  
  325.       //Location of event, optional.
  326.       location: meeting.location,
  327.  
  328.       //Optional description of event.
  329.       description: 'Meeting details/Détails de la réunion',
  330.  
  331.       //Optional Organizer info
  332.       organizer: {
  333.         name: config.allowEditOrganiserNameInCalender ? config.edittedOrganiserName : organiser.firstName + ' ' + organiser.lastName,
  334.         email: config.mailFrom
  335.       },
  336.       attendees: (invitees || []).map(invitee => { return { name: invitee.firstName + ' ' + invitee.lastName, email: invitee.email, rsvp: true }; }),
  337.  
  338.       //What to do on addition
  339.       method: 'CANCEL',
  340.  
  341.       //Status of event
  342.       status: 'CANCELLED'
  343.     });
  344.  
  345.  
  346.     //Try to build
  347.     const icsFileContent = builder.toString();
  348.  
  349.     //Check if there was an error (Only required if yu configured to return error, else error will be thrown.)
  350.     if (icsFileContent instanceof Error) {
  351.       if (callback) callback(icsFileContent);
  352.       return;
  353.     }
  354.  
  355.     if (callback) callback(null, icsFileContent);
  356.   }
  357.  
  358.   /**
  359.    *
  360.    * @param {EnvironmentConfig} config
  361.    * @param {Meeting} meeting
  362.    * @param {EboardUser} organiser
  363.    * @param {Array<EboardUser>} invitees
  364.    * @param {EboardUser} to
  365.    * @param {String} subject
  366.    * @param {String} emailMessage
  367.    * @param {Function(Error)} callback
  368.    */
  369.   function sendEmailWithIcsFileV2(config, meeting, organiser, invitees, to, subject, emailMessage, callback) {
  370.     const messageHtml = '<td bgcolor="#ffffff" align="left" height="100%" valign="top" width="100%" style="padding-bottom: 40px;padding:30px;">' + emailMessage + '</td>';
  371.     templateManager.injectContent(messageHtml, function (injectErr, response) {
  372.       if (injectErr) {
  373.         if (callback) callback(injectErr);
  374.         return;
  375.       }
  376.  
  377.       generateICSFileV2(config, meeting, organiser, invitees, function (err, icsContent) {
  378.         if (err) {
  379.           if (callback) callback(err);
  380.           return;
  381.         }
  382.         const headers = {};
  383.         headers['Content-Transfer-Encoding'] = '7bit';
  384.         let ccEmail = to.ccEmail;
  385.         if (!ccEmail) {
  386.           ccEmail = '';
  387.         } else {
  388.           ccEmail = '; ' + ccEmail;
  389.         }
  390.         // TODO: emailer.init()
  391.         emailer.sendEmail({
  392.           from: config.mailFrom,
  393.           to: to.email + ccEmail,
  394.           subject: subject,
  395.           isHtml: true,
  396.           body: response,
  397.           alternatives: [{
  398.             headers: headers,
  399.             contentType: 'text/calendar; charset="utf-8"; method=REPLY',
  400.             content: new Buffer(icsContent ? icsContent : '')
  401.           }]
  402.         }, function (err) {
  403.           if (err) {
  404.             if (callback) callback(err);
  405.             return;
  406.           }
  407.           if (callback) callback();
  408.         });
  409.       });
  410.     });
  411.   }
  412.  
  413.   /**
  414.    *
  415.    * @param {EnvironmentConfig} config
  416.    * @param {Meeting} meeting
  417.    * @param {EboardUser} organiser
  418.    * @param {Array<EboardUser>} invitees
  419.    * @param {Array<EboardUser>} to default empty
  420.    * @param {String} subject
  421.    * @param {String} emailMessage
  422.    * @param {Function(Error)} callback
  423.    */
  424.   function sendEmailInBatchWithIcsFileV2(config, meeting, organiser, invitees, to = null, subject, emailMessage, callback) {
  425.     const messageHtml = '<td bgcolor="#ffffff" align="left" height="100%" valign="top" width="100%" style="padding-bottom: 40px;padding:30px;">' + emailMessage + '</td>';
  426.     templateManager.injectContent(messageHtml, function (injectErr, response) {
  427.       if (injectErr) {
  428.         if (callback) callback(injectErr);
  429.         return;
  430.       }
  431.       generateICSFileV2(config, meeting, organiser, invitees, function (err, icsContent) {
  432.         if (err) {
  433.           if (callback) callback(err);
  434.           return;
  435.         }
  436.         const headers = {};
  437.         headers['Content-Transfer-Encoding'] = '7bit';
  438.         const invList = [];
  439.         const orgList = [];
  440.         orgList.push(organiser.email);
  441.         if (organiser.ccEmail) {
  442.           orgList.push(organiser.ccEmail);
  443.         }
  444.         const recipients = Array.isArray(to) && to.length ? to : invitees;
  445.         for (var invitee of recipients) {
  446.           invList.push(invitee.email);
  447.           if (invitee.ccEmail)
  448.             invList.push(invitee.ccEmail);
  449.         }
  450.         // if (config.allowSendMailToAllOrganiser) {
  451.         //   const meetingTypeOrgs = meeting.meetingType().organisers();
  452.         //   meetingTypeOrgs = Enumerable.from(meetingTypeOrgs).where(function (mo) {
  453.         //     return mo.isActive && mo.eboardUserId != meeting.organiser().eboardUserId;
  454.         //   }).toArray();
  455.         //   for (var org of meetingTypeOrgs) {
  456.         //     orgList.push(org.eboardUser().email);
  457.         //     if (org.eboardUser().ccEmail) {
  458.         //       orgList.push(org.eboardUser().ccEmail);
  459.         //     }
  460.         //   }
  461.         // }
  462.         // TODO: emailer.init()
  463.         emailer.sendEmail({
  464.           from: config.mailFrom,
  465.           to: invList,
  466.           subject: subject,
  467.           isHtml: true,
  468.           body: response,
  469.           cc: orgList,
  470.           alternatives: [{
  471.             headers: headers,
  472.             contentType: 'text/calendar; charset="utf-8"; method=REPLY',
  473.             content: new Buffer(icsContent ? icsContent : '')
  474.           }],
  475.         }, function (err) {
  476.           if (err) {
  477.             if (callback) callback(err);
  478.             return;
  479.           }
  480.           if (callback) callback();
  481.         });
  482.       });
  483.     });
  484.   }
  485.  
  486.   /**
  487.    *
  488.    * @param {EnvironmentConfig} config
  489.    * @param {Meeting} meeting
  490.    * @param {EboardUser} organiser
  491.    * @param {Array<EboardUser>} invitees
  492.    * @param {Array<EboardUser>} meetingTypeOrganisers
  493.    * @param {Array<EboardUser>} to
  494.    * @param {String} subject
  495.    * @param {String} emailMessage
  496.    * @param {Function(Error)} callback
  497.    */
  498.   function sendCancellationEmailWithIcs(config, meeting, organiser, invitees, meetingTypeOrganisers, to, subject, emailMessage, callback) {
  499.     const messageHtml = '<td bgcolor="#ffffff" align="left" height="100%" valign="top" width="100%" style="padding-bottom: 40px;padding:30px;">' + emailMessage + '</td>';
  500.     const recipients = Array.isArray(to) && to.length ? to : invitees;
  501.     const cc = recipients.map(i => i.email);
  502.     cc = cc.concat(recipients.map(i => i.ccEmail).filter(m => typeof m === 'string' && m.length));
  503.     templateManager.injectContent(messageHtml, function (err, response) {
  504.       if (err/* || !config.deleteMeetingWithNotif*/) {
  505.         if (callback) callback(err);
  506.         return;
  507.       }
  508.       deleteICSFileV2(config, meeting, organiser, invitees, function (err, icsContent) {
  509.         if (err) {
  510.           if (callback) callback(err);
  511.           return;
  512.         }
  513.         if (config.allowSendMailToAllOrganiser)
  514.           cc = cc.concat(meetingTypeOrganisers.filter(o => cc.indexOf(o.email) === -1).map(o => o.email));
  515.         emailer.init(config.smtpUsername, config.smtpPassword, config.smtpServerAddress);
  516.         const headers = {};
  517.         headers['Content-Transfer-Encoding'] = '7bit';
  518.         emailer.sendEmail({
  519.           from: config.mailFrom,
  520.           to: organiser.email,
  521.           subject: subject,
  522.           isHtml: true,
  523.           body: response,
  524.           cc: cc.filter((v, i, s) => s.indexOf(v) === i),
  525.           alternatives: [{
  526.             headers: headers,
  527.             contentType: 'text/calendar; charset="utf-8"; method=REPLY',
  528.             content: new Buffer(icsContent ? icsContent : '')
  529.           }]
  530.         }, function (err) {
  531.           if (callback) callback(err);
  532.         });
  533.       });
  534.     });
  535.   }
  536.  
  537.   Meeting.on('notifyNewUserV2', function (data) {
  538.     Meeting.findById(data.meeting.id, {
  539.       include: [
  540.         { environment: 'config' },
  541.         { organiser: 'user' },
  542.         { invitees: { environmentUser: 'user' } },
  543.         { meetingType: { organisers: 'user' } },
  544.         'company'
  545.       ]
  546.     }, function (err, meeting) {
  547.       if (err || !meeting) return;
  548.       app.models.EnvironmentUser.find({
  549.         where: {
  550.           and: [
  551.             { id: { inq: data.ids } },
  552.             { isActive: true },
  553.             { isDeleted: false }
  554.           ]
  555.         },
  556.         include: 'user'
  557.       }, function (err, environmentUsers) {
  558.         if (err || !environmentUsers) return;
  559.         const moment = require('moment');
  560.         const subject = 'New_' + meeting.company().name + '_' + meeting.name + '_' + moment(meeting.startDate).format('DD MMM YYYY');
  561.         if (meeting.environment().config().showAttendance) {
  562.           async.each(environmentUsers, function (environmentUser, callback) {
  563.             app.models.MeetingAttendance.findOrCreate({
  564.               idMeeting: meeting.id,
  565.               idEnvironmentUser: environmentUser.id
  566.             }, function (err) {
  567.               if (err) return callback(err);
  568.               const emailMessage = generateMeetingRequestHtml(meeting, meeting.company().name, meeting.organiser().user(), true, environmentUser.id);
  569.               sendEmailWithIcsFileV2(
  570.                 meeting.environment().config(), meeting, meeting.organiser().user(),
  571.                 meeting.invitees().map(invitee => invitee.environmentUser().user()),
  572.                 environmentUser.user(), subject, emailMessage, callback
  573.               );
  574.             });
  575.           }, function (err) {
  576.             if (err) console.log('sendEmailWithIcsFileV2:', err);
  577.           });
  578.         } else { // Batch
  579.           const emailMessage = generateMeetingRequestHtml(meeting, meeting.company().name, meeting.organiser().user());
  580.           sendEmailInBatchWithIcsFileV2(
  581.             meeting.environment().config(), meeting, meeting.organiser().user(),
  582.             meeting.invitees().map(invitee => invitee.environmentUser().user()),
  583.             environmentUsers.map(environmentUser => environmentUser.user()), subject,
  584.             emailMessage, function (err) { if (err) console.log('sendEmailInBatchWithIcsFileV2:', err); }
  585.           );
  586.         }
  587.         // if (updateState) {
  588.         // TODO: Update state if new meeting (MEETING_CREATED)
  589.         // }
  590.       });
  591.     });
  592.   });
  593.  
  594.   function generateICSFile(aMeeting, cb) {
  595.  
  596.     var icalToolkit = require('ical-toolkit');
  597.     app.models.Environment.findById(aMeeting.idEnvironment, {
  598.       include: ['config']
  599.     }, function (errorEnv, currentEnv) {
  600.  
  601.       if (errorEnv) {
  602.         if (cb) {
  603.           cb(errorEnv);
  604.         }
  605.       } else {
  606.  
  607.         //Create a builder
  608.         var builder = icalToolkit.createIcsFileBuilder();
  609.  
  610.         /*
  611.          * Settings (All Default values shown below. It is optional to specify)
  612.          * */
  613.         builder.spacers = true; //Add space in ICS file, better human reading. Default: true
  614.         builder.NEWLINE_CHAR = '\r\n'; //Newline char to use.
  615.         builder.throwError = false; //If true throws errors, else returns error when you do .toString() to generate the file contents.
  616.         builder.ignoreTZIDMismatch = true; //If TZID is invalid, ignore or not to ignore!
  617.  
  618.  
  619.         /**
  620.          * Build ICS
  621.          * */
  622.  
  623.         //Name of calander 'X-WR-CALNAME' tag.
  624.         builder.calname = 'Yo Cal';
  625.  
  626.         //Cal timezone 'X-WR-TIMEZONE' tag. Optional. We recommend it to be same as tzid.
  627.         //builder.timezone = 'america/new_york';
  628.  
  629.         //Time Zone ID. This will automatically add VTIMEZONE info.
  630.         //builder.tzid = 'america/new_york';
  631.  
  632.         //Method
  633.         builder.method = 'REQUEST';
  634.  
  635.  
  636.         //  var sequenceNumber = aMeeting.startDate.getDate() + '' + aMeeting.startDate.getHours() + '' + aMeeting.startDate.getSeconds();
  637.         //var sequenceNumber = 150;
  638.         var allInvitees = aMeeting.invitees() || [];
  639.  
  640.         var attendees = [];
  641.         for (var invitee of allInvitees) {
  642.           attendees.push({
  643.             name: invitee.environmentUser().eboardUser().firstName + " " + invitee.environmentUser().eboardUser().lastName,
  644.             email: invitee.environmentUser().eboardUser().email,
  645.             rsvp: true
  646.           });
  647.         }
  648.         //Add events
  649.         builder.events.push({
  650.  
  651.           //Event start time, Required: type Date()
  652.           start: aMeeting.startDate,
  653.  
  654.           //Event end time, Required: type Date()
  655.           end: aMeeting.endDate,
  656.  
  657.           //transp. Will add TRANSP:OPAQUE to block calendar.
  658.           transp: 'OPAQUE',
  659.  
  660.           //Event summary, Required: type String
  661.           summary: aMeeting.name,
  662.  
  663.           //All Optionals Below
  664.           //sequence: sequenceNumber,
  665.           //Alarms, array in minutes
  666.           //alarms: [15, 10, 5],
  667.  
  668.           //Event identifier, Optional, default auto generated
  669.           uid: aMeeting.idEnvironment + "" + aMeeting.id + "@eboard.com",
  670.  
  671.           //Location of event, optional.
  672.           location: aMeeting.location,
  673.  
  674.           //Optional description of event.
  675.           description: 'Meeting details/Détails de la réunion',
  676.  
  677.           //Optional Organizer info
  678.           organizer: {
  679.             name: currentEnv.config().allowEditOrganiserNameInCalender ? currentEnv.config().edittedOrganiserName : aMeeting.organiser().eboardUser().firstName + " " + aMeeting.organiser().eboardUser().lastName,
  680.             //if the sender and receiver have same email like oragniser, add to calendar will not be displayed
  681.             //use a hardcoded unique email as a work around
  682.             email: currentEnv.config().mailFrom // 'promeeting@agileum.com' //'someone@agileum.com' //aMeeting.organiser().eboardUser().email
  683.             // sentBy: 'person_acting_on_behalf_of_organizer@email.com' //OPTIONAL email address of the person who is acting on behalf of organizer.
  684.           },
  685.           attendees: attendees,
  686.  
  687.  
  688.           //What to do on addition
  689.           method: 'REQUEST',
  690.  
  691.           //Status of event
  692.           // status: 'CONFIRMED',
  693.  
  694.           //Url for event on core application, Optional.
  695.           // url: 'http://yahoo.com'
  696.         });
  697.  
  698.  
  699.         //Try to build
  700.         var icsFileContent = builder.toString();
  701.  
  702.         //Check if there was an error (Only required if yu configured to return error, else error will be thrown.)
  703.         if (icsFileContent instanceof Error) {
  704.           console.log('Returned Error, you can also configure to throw errors!');
  705.           //handle error
  706.           cb(null);
  707.         }
  708.         /*
  709.             //Here is the ics file content.
  710.             // console.log(icsFileContent);
  711.             icsFileContent = `BEGIN:VCALENDAR
  712.             VERSION:2.0
  713.             PRODID:-//Schedule a meeting
  714.             METHOD:REQUEST
  715.             BEGIN:VEVENT
  716.             DTSTART:20160429T060000Z
  717.             DTSTAMP:20160427T125420Z
  718.             DTEND:20160429T073000Z
  719.             LOCATION:1st floor, The factory building, Vivea Business Park, Moka, St Pierre
  720.             DESCRIPTION:Meeting details/Détails de la réunion
  721.             SUMMARY:Ford Committee meeting
  722.             ORGANIZER;CN=Kavida Pillai:mailto:kavida.pillai@agileum.com
  723.             ATTENDEE;CN=Kav Collins;RSVP=TRUE:mailto:kavida15@hotmail.com
  724.             CLASS:PUBLIC
  725.             UID:12
  726.             SEQUENCE:1
  727.             PRIORITY:5
  728.             END:VEVENT
  729.             END:VCALENDAR`;
  730.  
  731.  
  732.            icsFileContent = `BEGIN:VCALENDAR
  733.             VERSION:2.0
  734.             PRODID:-//Schedule a meeting
  735.             METHOD:REQUEST
  736.  
  737.             BEGIN:VEVENT
  738.             DTSTART:20160429T060000Z
  739.             DTSTAMP:20160427T125420Z
  740.             DTEND:20160429T073000Z
  741.             LOCATION:1st floor, The factory building, Vivea Business Park, Moka, St Pierre
  742.             DESCRIPTION:Meeting details/Détails de la réunion
  743.             SUMMARY:Ford Committee (without chairman) 1
  744.             ORGANIZER;CN=Kavida Pillai:mailto:kavida.pillai@agileum.com
  745.             ATTENDEE;CN=Kav Collins;RSVP=TRUE:mailto:kavida15@hotmail.com
  746.             CLASS:PUBLIC
  747.             UID:29
  748.             SEQUENCE:229
  749.             PRIORITY:5
  750.             END:VEVENT
  751.             END:VCALENDAR
  752.             `;
  753.             */
  754.  
  755.  
  756.         return cb(icsFileContent);
  757.         /*
  758.                                 var fs = require('fs');
  759.                                 var path = require('path');
  760.                                 var dest = path.resolve(__dirname, '../../server/storage/tmp/');
  761.  
  762.                                 //remove special characters
  763.                                 var removeDiacritics = require('diacritics').remove;
  764.                                 var meetingName = removeDiacritics(aMeeting.name);
  765.                                 meetingName = meetingName.replace(/\s+/g, '_').toLowerCase();
  766.                                 meetingName = meetingName.replace('%20', '_');
  767.                                 var filePath = path.join(dest, meetingName.trim() + ".ics");
  768.  
  769.                                 fs.writeFileSync(filePath, icsFileContent);
  770.                                 cb(filePath);
  771.                                 */
  772.       }
  773.     });
  774.  
  775.   }
  776.  
  777.  
  778.   function sendEmailWithIcsFile(envUser, subject, emailMessage, meeting, shouldInjectAttendance, cb) {
  779.  
  780.     var receiverUserId = envUser.id;
  781.     var os = require("os");
  782.     var portNumber = app.get('port');
  783.  
  784.     /*---------*/
  785.     var baseUrl = app.get('url').replace(/\/$/, '');
  786.     // console.log(baseUrl + ':' + portNumber + "/");
  787.     var networkInterfaces = os.networkInterfaces();
  788.     // console.log(networkInterfaces);
  789.  
  790.     /*---------*/
  791.  
  792.     //change ip when on test
  793.     //"http://" + os.hostname() + ":" + portNumber + "/";
  794.  
  795.     var hostName = "https://www.promeeting-agileum.com:8443/";
  796.     // To book your calendar please accept/decline the invitation above.
  797.  
  798.     // For your attendance to be visible in PRO meeting, please select one of the following:
  799.     var attendanceDiv = '<p>To book your calendar please accept/decline the invitation above.</p>' +
  800.       '<p><br></p>' +
  801.       '<p>For your attendance to be visible in PRO meeting, please select one of the following:</p>' +
  802.       '<a href="' + hostName + 'add_attendance.html?idMeeting=' + meeting.id + '&idUser=' + receiverUserId + '&attending=1" style="background-color:#EB7035;border:1px solid #EB7035;border-radius:3px;color:#ffffff;display:inline-block;font-family:sans-serif;font-size:16px;line-height:44px;text-align:center;text-decoration:none;width:150px;-webkit-text-size-adjust:none;mso-hide:all;">Yes</a>' + '&nbsp;&nbsp;&nbsp;' +
  803.       '<a href="' + hostName + 'add_attendance.html?idMeeting=' + meeting.id + '&idUser=' + receiverUserId + '&attending=0" style="margin-left:30px;background-color:#EB7035;border:1px solid #EB7035;border-radius:3px;color:#ffffff;display:inline-block;font-family:sans-serif;font-size:16px;line-height:44px;text-align:center;text-decoration:none;width:150px;-webkit-text-size-adjust:none;mso-hide:all;">No</a>';
  804.  
  805.     var emailMessageHtml = '<td bgcolor="#ffffff" align="left" height="100%" valign="top" width="100%" style="padding-bottom: 40px;padding:30px;">' + emailMessage + (shouldInjectAttendance ? attendanceDiv : '') + '</td>';
  806.  
  807.  
  808.     templateManager.injectContent(emailMessageHtml, function (injectErr, response) {
  809.       if (injectErr) {
  810.         console.log(injectErr);
  811.  
  812.         if (cb) {
  813.           cb(injectErr);
  814.         }
  815.  
  816.         return;
  817.       }
  818.  
  819.  
  820.       generateICSFile(meeting, function (icsContent) {
  821.  
  822.         // console.log(icsContent);
  823.         app.models.Environment.findById(meeting.idEnvironment, {
  824.           include: ['config']
  825.         }, function (errorEnv, currentEnv) {
  826.  
  827.           if (errorEnv) {
  828.  
  829.             if (cb) {
  830.               cb(errorEnv);
  831.             }
  832.           } else {
  833.             //emailer.init(currentEnv.config().smtpUsername, currentEnv.config().smtpPassword, currentEnv.config().smtpServerAddress);
  834.             var headers = {};
  835.             headers['Content-Transfer-Encoding'] = '7bit';
  836.             var ccEmail = envUser.eboardUser().ccEmail;
  837.             if (ccEmail === null) {
  838.               ccEmail = '';
  839.             } else {
  840.               ccEmail = '; ' + ccEmail;
  841.             }
  842.  
  843.             console.log('From: ' + currentEnv.config().mailFrom);
  844.  
  845.             emailer.sendEmail({
  846.               from: currentEnv.config().mailFrom,
  847.               to: envUser.eboardUser().email + ccEmail,
  848.               subject: subject,
  849.               isHtml: true,
  850.               body: response,
  851.               //cc: ccEmail,
  852.               alternatives: [{
  853.                 headers: headers,
  854.                 contentType: 'text/calendar; charset="utf-8"; method=REPLY',
  855.                 content: new Buffer(icsContent ? icsContent : '')
  856.               }],
  857.               //attachments: [icsPath]
  858.             }, function (err, mailInfo) {
  859.  
  860.               /*app.models.Email.create({
  861.                 from: currentEnv.config().mailFrom,
  862.                 to: envUser.eboardUser().email,
  863.                 content: response,
  864.                 idEnvironment: currentEnv.id,
  865.                 idMeeting: meeting.id,
  866.                 subject: subject,
  867.                 date: new Date(),
  868.                 type: 'MEETING_PUBLISH'
  869.               }, function (ex, res) {});*/
  870.  
  871.               if (err) {
  872.                 console.log(err);
  873.                 if (cb)
  874.                   return cb(err);
  875.               }
  876.               // console.log(mailInfo);
  877.               if (cb)
  878.                 cb();
  879.             });
  880.           }
  881.         });
  882.       });
  883.  
  884.     });
  885.  
  886.   }
  887.  
  888.   Meeting.getAllMeetings = function (data, cb) {
  889.  
  890.     Meeting.find({
  891.       order: 'name ASC',
  892.       include: ['meetingType', {
  893.         attendance: 'environmentUser'
  894.       }, {
  895.           organiser: ['eboardUser']
  896.         }, {
  897.           agendaItems: [{
  898.             documents: ['file', {
  899.               votes: [{
  900.                 environmentUser: ['eboardUser']
  901.               }]
  902.             }]
  903.           }]
  904.         }, {
  905.           company: ['users', 'companyLogo', {
  906.             meetingTypes: ['company', {
  907.               users: ['permission', {
  908.                 role: 'permission'
  909.               }, {
  910.                   environmentUser: 'eboardUser'
  911.                 }]
  912.             }, {
  913.                 organisers: ['eboardUser']
  914.               }]
  915.           }]
  916.         }, {
  917.           invitees: ['permission', {
  918.             role: 'permission'
  919.           }, {
  920.               environmentUser: ['eboardUser', 'profilePicture', 'role']
  921.             }]
  922.         }],
  923.       where: {
  924.         and: [{
  925.           isDeleted: false
  926.         }, {
  927.           idEnvironment: data.idEnvironment
  928.         }]
  929.       }
  930.     }, function (error, meetings) {
  931.       if (error) {
  932.         return cb(error);
  933.       } else {
  934.         app.models.EnvironmentUser.findOne({
  935.           include: 'role',
  936.           where: {
  937.             and: [{
  938.               eboardUserId: data.idUser
  939.             }, {
  940.               environmentId: data.idEnvironment
  941.             }, {
  942.               isDeleted: false
  943.             }]
  944.           }
  945.         }, function (err, myself) {
  946.           if (err) {
  947.             return cb(err, null);
  948.           }
  949.           var currentUserId = myself.id;
  950.  
  951.           var amIMeetingOrganiser = myself.role().name.toLowerCase() == constants.MEETING_ORGANISER;
  952.  
  953.           if (amIMeetingOrganiser) {
  954.             //if MO manages any of the meeting company
  955.             var meetingsInCompaniesIManage = Enumerable.from(meetings)
  956.               .where(function (m) {
  957.                 return Enumerable.from(m.company().users())
  958.                   .where(function (u) {
  959.                     return u.id == currentUserId;
  960.                   })
  961.                   .firstOrDefault(null) !== null;
  962.               })
  963.               .toArray();
  964.  
  965.             //find all the meeting types MO manages
  966.             var mTypeIds = Enumerable.from(meetingsInCompaniesIManage)
  967.               .select(function (m) {
  968.                 return m.idMeetingType;
  969.               })
  970.               .toArray();
  971.  
  972.             var whereFilter = {
  973.               include: [
  974.                 'organisers'
  975.               ],
  976.               where: {
  977.                 or: []
  978.               }
  979.             };
  980.             for (var i = 0; i < mTypeIds.length; i++) {
  981.               whereFilter.where.or.push({
  982.                 id: mTypeIds[i]
  983.               });
  984.             }
  985.             app.models.MeetingType.find(whereFilter, function (anError, meetingTypes) {
  986.  
  987.               var meetingTypesIdsIManage = Enumerable.from(meetingTypes)
  988.                 .where(function (mt) {
  989.                   return Enumerable.from(mt.organisers())
  990.                     .where(function (o) {
  991.                       return o.id == currentUserId;
  992.                     })
  993.                     .firstOrDefault(null) !== null;
  994.                 }).select(function (m) {
  995.                   return m.id;
  996.                 })
  997.                 .toArray();
  998.  
  999.               var myMeetings = [];
  1000.               for (var aMeet of meetingsInCompaniesIManage) {
  1001.  
  1002.                 var isMyMeeting = Enumerable.from(meetingTypesIdsIManage)
  1003.                   .where(function (mTId) {
  1004.                     return aMeet.idMeetingType == mTId;
  1005.                   })
  1006.                   .firstOrDefault(null) !== null;
  1007.  
  1008.                 if (isMyMeeting) {
  1009.                   myMeetings.push(aMeet);
  1010.                 }
  1011.               }
  1012.  
  1013.               var resultsForOrganiser = data.showMeetingOver ? (Enumerable.from(myMeetings).where(function (m) {
  1014.                 return m.state == "MEETING_OVER";
  1015.               }).toArray()) : (Enumerable.from(myMeetings).where(function (m) {
  1016.                 return m.state != "MEETING_OVER";
  1017.               }).toArray());
  1018.  
  1019.  
  1020.               for (var meet of resultsForOrganiser) {
  1021.                 meet.information = encryptor.decryptText(meet.information, key);
  1022.                 meet.decisionTaken = encryptor.decryptText(meet.decisionTaken, key);
  1023.               }
  1024.  
  1025.               return cb(null, resultsForOrganiser);
  1026.             });
  1027.           } else {
  1028.             //if user is invited in meeting
  1029.             var amIDirector = myself.role().name.toLowerCase() == constants.USER;
  1030.  
  1031.             app.models.Environment.findById(data.idEnvironment, {
  1032.               include: ['config']
  1033.             }, function (errorEnv, currentEnv) {
  1034.  
  1035.               if (errorEnv) {
  1036.                 if (cb) {
  1037.                   cb(errorEnv);
  1038.                 }
  1039.               } else {
  1040.  
  1041.                 if (amIDirector) {
  1042.                   var meetingsImInvitedTo = Enumerable.from(meetings)
  1043.                     .where(function (m) {
  1044.                       return Enumerable.from(m.invitees())
  1045.                         .where(function (i) {
  1046.                           return i.idEnvironmentUser == currentUserId;
  1047.                         })
  1048.                         .firstOrDefault(null) !== null;
  1049.                     })
  1050.                     .toArray();
  1051.  
  1052.                   var showUnpublishedMeetings = !currentEnv.config().viewPublishedMeeting || false;
  1053.                   var meetingState = (showUnpublishedMeetings ? ("MEETING_CREATED" || "MEETING_PUBLISHED") : ("MEETING_PUBLISHED"));
  1054.                   var results = data.showMeetingOver ? (Enumerable.from(meetingsImInvitedTo).where(function (m) {
  1055.                     return m.state == "MEETING_OVER";
  1056.                   }).toArray()) : (Enumerable.from(meetingsImInvitedTo).where(function (m) {
  1057.                     return (m.state == meetingState || m.state == "MEETING_UPDATED" || m.state == "MEETING_PUBLISHED");
  1058.                   }).toArray());
  1059.  
  1060.                   /*var showUnpublishedMeetings = !currentEnv.config().viewPublishedMeeting || false;
  1061.  
  1062.                   var filteredResults = Enumerable.from(results).where(function(m) {
  1063.                     return m.state == (showUnpublishedMeetings ? ("MEETING_CREATED" || "MEETING_PUBLISHED") : "MEETING_PUBLISHED");
  1064.                   }).toArray();*/
  1065.  
  1066.                   for (var meet of results) {
  1067.                     meet.information = encryptor.decryptText(meet.information, key);
  1068.                     meet.decisionTaken = encryptor.decryptText(meet.decisionTaken, key);
  1069.                   }
  1070.                   return cb(null, results);
  1071.  
  1072.                 } else {
  1073.                   //SU OR Admin
  1074.                   var resultsForAdmin = data.showMeetingOver ? (Enumerable.from(meetings).where(function (m) {
  1075.                     return m.state == "MEETING_OVER";
  1076.                   }).toArray()) : (Enumerable.from(meetings).where(function (m) {
  1077.                     return m.state != "MEETING_OVER";
  1078.                   }).toArray());
  1079.                   for (var meetig of resultsForAdmin) {
  1080.                     meetig.information = encryptor.decryptText(meetig.information, key);
  1081.                     meetig.decisionTaken = encryptor.decryptText(meetig.decisionTaken, key);
  1082.                   }
  1083.                   return cb(null, resultsForAdmin);
  1084.                 }
  1085.               }
  1086.             });
  1087.           }
  1088.         });
  1089.       }
  1090.     });
  1091.   };
  1092.  
  1093.   Meeting.remoteMethod('getAllMeetings', {
  1094.     accepts: {
  1095.       arg: 'data',
  1096.       type: 'object',
  1097.       http: {
  1098.         source: 'body'
  1099.       }
  1100.     },
  1101.     returns: {
  1102.       root: true,
  1103.       arg: '',
  1104.       type: 'Meeting'
  1105.     },
  1106.     http: {
  1107.       path: '/getAllMeetings',
  1108.       verb: 'post'
  1109.     }
  1110.   });
  1111.  
  1112.   Meeting.beforeRemote('getAllMeetingsV2', function (ctx, unused, cb) {
  1113.     if (!ctx.args.data['idEnvironment'])
  1114.       return cb(new Error('idEnvironment is missing'));
  1115.     if (!ctx.args.data['idUser'])
  1116.       return cb(new Error('idUser is missing'));
  1117.     app.models.EnvironmentUser.findOne({
  1118.       include: 'role',
  1119.       where: {
  1120.         and: [{
  1121.           eboardUserId: ctx.args.data['idUser']
  1122.         },
  1123.         {
  1124.           environmentId: ctx.args.data['idEnvironment']
  1125.         },
  1126.         {
  1127.           isDeleted: false
  1128.         },
  1129.         {
  1130.           isActive: true
  1131.         }
  1132.         ]
  1133.       }
  1134.     }, function (err, user) {
  1135.       if (err)
  1136.         return cb(err);
  1137.       if (!user)
  1138.         return cb('user not found');
  1139.       ctx.args.data['userId'] = user.id;
  1140.       ctx.args.data['userRole'] = user.role().name.toLowerCase();
  1141.       cb();
  1142.     });
  1143.   });
  1144.  
  1145.   Meeting.getAllMeetingsV2 = function (data, cb) {
  1146.     const filter = {
  1147.       skip: 0,
  1148.       limit: 50,
  1149.       order: 'startDate ASC',
  1150.       include: [
  1151.         'meetingType',
  1152.         {
  1153.           attendance: 'environmentUser'
  1154.         },
  1155.         {
  1156.           organiser: ['eboardUser']
  1157.         },
  1158.         {
  1159.           agendaItems: [{
  1160.             documents: [
  1161.               'file',
  1162.               {
  1163.                 votes: [{
  1164.                   environmentUser: ['eboardUser']
  1165.                 }]
  1166.               }
  1167.             ]
  1168.           }]
  1169.         },
  1170.         {
  1171.           company: [
  1172.             'users',
  1173.             'companyLogo',
  1174.             {
  1175.               meetingTypes: [
  1176.                 'company',
  1177.                 {
  1178.                   users: [
  1179.                     'permission',
  1180.                     {
  1181.                       role: 'permission'
  1182.                     },
  1183.                     {
  1184.                       environmentUser: 'eboardUser'
  1185.                     }
  1186.                   ]
  1187.                 },
  1188.                 {
  1189.                   organisers: ['eboardUser']
  1190.                 }
  1191.               ]
  1192.             }
  1193.           ]
  1194.         },
  1195.         {
  1196.           invitees: [
  1197.             'permission',
  1198.             {
  1199.               role: 'permission'
  1200.             },
  1201.             {
  1202.               environmentUser: [
  1203.                 'eboardUser',
  1204.                 'profilePicture',
  1205.                 'role'
  1206.               ]
  1207.             }
  1208.           ]
  1209.         }
  1210.       ]
  1211.     };
  1212.     if (data.perPage > 0)
  1213.       filter.limit = data.perPage;
  1214.     if (data.page > 0)
  1215.       filter.skip = (data.page - 1) * filter.limit;
  1216.     if (data.orderBy)
  1217.       filter.order = data.orderBy;
  1218.     switch (data.userRole) {
  1219.       case constants.MEETING_ORGANISER: // Meeting Organizer
  1220.         let query = `SELECT DISTINCT meeting.id FROM meeting
  1221.           INNER JOIN companyuser ON meeting.idcompany = companyuser.companyid
  1222.           INNER JOIN meetingtype ON meeting.idmeetingtype = meetingtype.id
  1223.           INNER JOIN meetingtypeorganiser ON meetingtype.id = meetingtypeorganiser.meetingtypeid
  1224.           WHERE meeting.idenvironment = ` + data.idEnvironment + `
  1225.           AND companyuser.environmentuserid = ` + data.userId + `
  1226.           AND meetingtypeorganiser.environmentuserid = ` + data.userId + `
  1227.           AND meeting.isdeleted = FALSE
  1228.           AND companyuser.isdeleted = FALSE
  1229.           AND meetingtype.isdeleted = FALSE
  1230.           AND meetingtypeorganiser.isdeleted = FALSE`;
  1231.         if (data.showMeetingOver)
  1232.           query += " AND meeting.state = 'MEETING_OVER'";
  1233.         else
  1234.           query += " AND meeting.state != 'MEETING_OVER'";
  1235.         if (data.startDate) {
  1236.           if (isValidDate(new Date(data.startDate)))
  1237.             query += " AND meeting.startdate >= timestamp '" + data.startDate.substring(0, 10) + " " + data.startDate.substring(11) + "'";
  1238.         }
  1239.         if (data.endDate) {
  1240.           if (isValidDate(new Date(data.endDate)))
  1241.             query += " AND meeting.startdate <= timestamp '" + data.endDate.substring(0, 10) + " " + data.endDate.substring(11) + "'";
  1242.         }
  1243.         app.datasources.eboardDatasource.connector.execute(query, function (err, arr) {
  1244.           if (err)
  1245.             return cb(err);
  1246.           const meetingIds = arr.map(function (v) {
  1247.             return v.id;
  1248.           });
  1249.           filter.where = {
  1250.             id: {
  1251.               inq: meetingIds
  1252.             }
  1253.           };
  1254.           const count = meetingIds.length;
  1255.           Meeting.find(filter, function (error, meetings) {
  1256.             if (error)
  1257.               return cb(error);
  1258.             const result = {
  1259.               pagination: {
  1260.                 page: (filter.skip / filter.limit) + 1,
  1261.                 pageSize: filter.limit,
  1262.                 totalPages: Math.ceil(count / filter.limit),
  1263.                 totalItems: count
  1264.               },
  1265.               items: meetings
  1266.             };
  1267.             for (var meeting of result.items) {
  1268.               meeting.information = encryptor.decryptText(meeting.information, key)
  1269.               meeting.decisionTaken = encryptor.decryptText(meeting.decisionTaken, key)
  1270.             }
  1271.             cb(null, result);
  1272.           });
  1273.         });
  1274.         break;
  1275.       case constants.USER: // Director
  1276.         app.models.Environment.findById(data.idEnvironment, {
  1277.           // fields
  1278.           include: ['config']
  1279.         }, function (errorEnv, currentEnv) {
  1280.           if (errorEnv)
  1281.             return cb(errorEnv);
  1282.           let query = `SELECT DISTINCT meeting.id FROM meeting
  1283.             INNER JOIN meetinginvitee ON meetinginvitee.idmeeting = meeting.id
  1284.             WHERE meeting.idenvironment = ` + data.idEnvironment + `
  1285.             AND meetinginvitee.idenvironmentuser = ` + data.userId + `
  1286.             AND meeting.isdeleted = FALSE
  1287.             AND meetinginvitee.isdeleted = FALSE`;
  1288.           if (data.showMeetingOver)
  1289.             query += " AND meeting.state = 'MEETING_OVER'";
  1290.           else {
  1291.             if (currentEnv.config().viewPublishedMeeting)
  1292.               query += " AND meeting.state IN ('MEETING_PUBLISHED', 'MEETING_UPDATED')";
  1293.             else
  1294.               query += " AND meeting.state IN ('MEETING_CREATED', 'MEETING_PUBLISHED', 'MEETING_UPDATED')";
  1295.           }
  1296.           if (data.startDate) {
  1297.             if (isValidDate(new Date(data.startDate)))
  1298.               query += " AND meeting.startdate >= timestamp '" + data.startDate.substring(0, 10) + " " + data.startDate.substring(11) + "'";
  1299.           }
  1300.           if (data.endDate) {
  1301.             if (isValidDate(new Date(data.endDate)))
  1302.               query += " AND meeting.startdate <= timestamp '" + data.endDate.substring(0, 10) + " " + data.endDate.substring(11) + "'";
  1303.           }
  1304.           app.datasources.eboardDatasource.connector.execute(query, function (err, arr) {
  1305.             if (err)
  1306.               return cb(err);
  1307.             const meetingIds = arr.map(function (v) {
  1308.               return v.id;
  1309.             });
  1310.             filter.where = {
  1311.               id: {
  1312.                 inq: meetingIds
  1313.               }
  1314.             };
  1315.             const count = meetingIds.length;
  1316.             Meeting.find(filter, function (error, meetings) {
  1317.               if (error)
  1318.                 return cb(error);
  1319.               const result = {
  1320.                 pagination: {
  1321.                   page: (filter.skip / filter.limit) + 1,
  1322.                   pageSize: filter.limit,
  1323.                   totalPages: Math.ceil(count / filter.limit),
  1324.                   totalItems: count
  1325.                 },
  1326.                 items: meetings
  1327.               };
  1328.               for (var meeting of result.items) {
  1329.                 meeting.information = encryptor.decryptText(meeting.information, key)
  1330.                 meeting.decisionTaken = encryptor.decryptText(meeting.decisionTaken, key)
  1331.               }
  1332.               cb(null, result);
  1333.             });
  1334.           });
  1335.         });
  1336.         break;
  1337.       default: // SU OR Admin
  1338.         filter.where = {
  1339.           and: [{
  1340.             idEnvironment: data.idEnvironment
  1341.           },
  1342.           {
  1343.             isDeleted: false
  1344.           }
  1345.           ]
  1346.         };
  1347.         filter.where.and.push(data.showMeetingOver ? {
  1348.           state: 'MEETING_OVER'
  1349.         } : {
  1350.             state: {
  1351.               neq: 'MEETING_OVER'
  1352.             }
  1353.           });
  1354.         if (data.startDate) {
  1355.           const startDate = new Date(data.startDate);
  1356.           if (isValidDate(startDate))
  1357.             filter.where.and.push({
  1358.               startDate: {
  1359.                 gte: startDate
  1360.               }
  1361.             });
  1362.         }
  1363.         if (data.endDate) {
  1364.           const endDate = new Date(data.endDate);
  1365.           if (isValidDate(endDate))
  1366.             filter.where.and.push({
  1367.               startDate: {
  1368.                 lte: endDate
  1369.               }
  1370.             });
  1371.         }
  1372.         Meeting.count(filter.where, function (err, count) {
  1373.           if (err)
  1374.             return cb(err);
  1375.           Meeting.find(filter, function (error, meetings) {
  1376.             if (error)
  1377.               return cb(error);
  1378.             const result = {
  1379.               pagination: {
  1380.                 page: (filter.skip / filter.limit) + 1,
  1381.                 pageSize: filter.limit,
  1382.                 totalPages: Math.ceil(count / filter.limit),
  1383.                 totalItems: count
  1384.               },
  1385.               items: meetings
  1386.             };
  1387.             for (var meeting of result.items) {
  1388.               meeting.information = encryptor.decryptText(meeting.information, key)
  1389.               meeting.decisionTaken = encryptor.decryptText(meeting.decisionTaken, key)
  1390.             }
  1391.             cb(null, result);
  1392.           });
  1393.         });
  1394.     }
  1395.   }
  1396.  
  1397.   Meeting.remoteMethod('getAllMeetingsV2', {
  1398.     accepts: {
  1399.       arg: 'data',
  1400.       type: 'object',
  1401.       http: {
  1402.         source: 'body'
  1403.       }
  1404.     },
  1405.     returns: {
  1406.       root: true,
  1407.       arg: '',
  1408.       type: 'object'
  1409.     },
  1410.     http: {
  1411.       path: '/getAllMeetingsV2',
  1412.       verb: 'post'
  1413.     }
  1414.   });
  1415.  
  1416.   function isValidDate(d) {
  1417.     if (Object.prototype.toString.call(d) === "[object Date]") {
  1418.       if (isNaN(d.getTime())) // d.valueOf() could also work
  1419.         return false;
  1420.       return true;
  1421.     }
  1422.     return false;
  1423.   }
  1424.  
  1425.   /**
  1426.    *
  1427.    * @param {String} rule
  1428.    * @returns {Object}
  1429.    */
  1430.   function parseRecurringPattern(rule) {
  1431.     const pattern = {};
  1432.     rule.split(';').forEach(v => {
  1433.       const parts = v.split('=');
  1434.       pattern[parts[0]] = parts[1];
  1435.     });
  1436.     return pattern;
  1437.   }
  1438.  
  1439.   /**
  1440.    *
  1441.    * @param {Number} nth 1-4 for 1st, 2nd, 3rd or 4th weekday of the month
  1442.    * @param {Number} day 0-6 for Sunday to Saturday
  1443.    * @param {Date} date
  1444.    * @returns {Date|Boolean} false if there isn't an nth weekday
  1445.    */
  1446.   function getMonthlyWeekday(nth, day, date) {
  1447.     const moment = require('moment');
  1448.     let a = moment(date).date(1); // 1st day of month
  1449.     const b = moment(a).add(1, 'months').subtract(1, 'days'); // last day of month
  1450.     let count = 0;
  1451.     while (a.toDate().getTime() <= b.toDate().getTime()) {
  1452.       if (a.day() === day && ++count === nth)
  1453.         return a.toDate();
  1454.       a.add(1, 'days');
  1455.     }
  1456.     return false;
  1457.   }
  1458.  
  1459.   /**
  1460.    *
  1461.    * @param {Date} start
  1462.    * @param {Date} until
  1463.    * @returns {Array<Date>}
  1464.    */
  1465.   function daily(start, until) {
  1466.     const moment = require('moment');
  1467.     const next = [];
  1468.     let a = moment(start).add(1, 'days');
  1469.     while (a.toDate().getTime() <= until.getTime()) {
  1470.       next.push(a.toDate());
  1471.       a.add(1, 'days');
  1472.     }
  1473.     return next;
  1474.   }
  1475.  
  1476.   /**
  1477.    *
  1478.    * @param {Date} start
  1479.    * @param {Array<Number>} weekdays 0-6 for Sunday to Saturday
  1480.    * @param {Date} until
  1481.    * @param {Number} interval default 1
  1482.    * @returns {Array<Date>}
  1483.    */
  1484.   function weekly(start, weekdays = [], until, interval = 1) {
  1485.     const moment = require('moment');
  1486.     const next = [];
  1487.     let a = moment(start);
  1488.     for (var i = 0; i < weekdays.length; i++) {
  1489.       if (weekdays[i] > a.day() && a.day(weekdays[i]).toDate().getTime() <= until.getTime())
  1490.         next.push(a.toDate());
  1491.     }
  1492.     a.add(interval, 'weeks');
  1493.     while (a.toDate().getTime() <= until.getTime()) {
  1494.       for (var i = 0; i < weekdays.length; i++) {
  1495.         if (a.day(weekdays[i]).toDate().getTime() <= until.getTime())
  1496.           next.push(a.toDate());
  1497.       }
  1498.       a.add(interval, 'weeks');
  1499.     }
  1500.     return next;
  1501.   }
  1502.  
  1503.   /**
  1504.    *
  1505.    * @param {Date} start
  1506.    * @param {Number} nth 1-4 for 1st, 2nd, 3rd or 4th weekday of the month
  1507.    * @param {Array<Number>} weekdays 0-6 for Sunday to Saturday
  1508.    * @param {Number} monthday 1-31
  1509.    * @param {Date} until
  1510.    * @returns {Array<Date>}
  1511.    */
  1512.   function monthly(start, nth = null, weekdays = null, monthday = null, until) {
  1513.     const moment = require('moment');
  1514.     const next = [];
  1515.     let a = moment(start);
  1516.     if (nth && Array.isArray(weekdays)) {
  1517.       for (var i = 0; i < weekdays.length; i++) {
  1518.         const mwd = moment(getMonthlyWeekday(nth, weekdays[i], start));
  1519.         if (mwd.date() > a.date() && mwd.toDate().getTime() <= until.getTime())
  1520.           next.push(mwd.toDate());
  1521.       }
  1522.     }
  1523.     a.add(1, 'months');
  1524.     while (a.toDate().getTime() <= until.getTime()) {
  1525.       if (monthday) {
  1526.         if (a.daysInMonth() >= monthday && a.date(monthday).toDate().getTime() <= until.getTime())
  1527.           next.push(a.toDate());
  1528.       } else if (Array.isArray(weekdays)) {
  1529.         for (var i = 0; i < weekdays.length; i++) {
  1530.           const mwd = moment(getMonthlyWeekday(nth, weekdays[i], a.toDate()));
  1531.           if (mwd.toDate().getTime() <= until.getTime())
  1532.             next.push(mwd.toDate());
  1533.         }
  1534.       }
  1535.       a.add(1, 'months');
  1536.     }
  1537.     return next;
  1538.   }
  1539.  
  1540.   /**
  1541.    *
  1542.    * @param {Date} start
  1543.    * @param {Date} until
  1544.    * @returns {Array<Date>}
  1545.    */
  1546.   function yearly(start, until) {
  1547.     const moment = require('moment');
  1548.     const next = [];
  1549.     let a = moment(start).add(1, 'years');
  1550.     while (a.toDate().getTime() <= until.getTime()) {
  1551.       next.push(a.toDate());
  1552.       a.add(1, 'years');
  1553.     }
  1554.     return next;
  1555.   }
  1556.  
  1557.   /**
  1558.    *
  1559.    * @param {String} byday
  1560.    * @returns {Array<Number>}
  1561.    */
  1562.   function parseWeekDays(byday) {
  1563.     return byday.split(',').map(d => {
  1564.       switch (d) {
  1565.         case 'SUN': return 0;
  1566.         case 'MON': return 1;
  1567.         case 'TUE': return 2;
  1568.         case 'WED': return 3;
  1569.         case 'THU': return 4;
  1570.         case 'FRI': return 5;
  1571.         case 'SAT': return 6;
  1572.       }
  1573.     }).sort((a, b) => a - b);
  1574.   }
  1575.  
  1576.   /**
  1577.    *
  1578.    * @param {Meeting} meeting
  1579.    * @returns {Array<Date>}
  1580.    */
  1581.   function generateNextOccurrences(meeting) {
  1582.     const moment = require('moment');
  1583.     let occurrences = [];
  1584.     if (meeting.isRecurring) {
  1585.       const pattern = parseRecurringPattern(meeting.recurringPattern);
  1586.       switch (pattern['FREQ']) {
  1587.         case 'DAILY':
  1588.           occurrences = daily(moment(meeting.startDate).toDate(), new Date(pattern['UNTIL']));
  1589.           break;
  1590.         case 'WEEKLY':
  1591.           occurrences = weekly(moment(meeting.startDate).toDate(), parseWeekDays(pattern['BYDAY']), new Date(pattern['UNTIL']), Number(pattern['INTERVAL']));
  1592.           break;
  1593.         case 'MONTHLY':
  1594.           occurrences = monthly(
  1595.             moment(meeting.startDate).toDate(),
  1596.             pattern['BYSETPOS'] ? Number(pattern['BYSETPOS']) : null,
  1597.             pattern['BYDAY'] ? parseWeekDays(pattern['BYDAY']) : null,
  1598.             pattern['BYMONTHDAY'] ? Number(pattern['BYMONTHDAY']) : null,
  1599.             new Date(pattern['UNTIL'])
  1600.           );
  1601.           break;
  1602.         case 'YEARLY':
  1603.           occurrences = yearly(moment(meeting.startDate).toDate(), new Date(pattern['UNTIL']));
  1604.           break;
  1605.       }
  1606.       occurrences.sort((a, b) => a.getTime() - b.getTime());
  1607.     }
  1608.     return occurrences;
  1609.   }
  1610.  
  1611.   /**
  1612.    *
  1613.    * @param {Array<Object>} oldOccurrences
  1614.    * @param {Array<Date>} newOccurrences
  1615.    * @param {Boolean} dirty default false
  1616.    * @returns {Object|Boolean} false if there have been no changes
  1617.    */
  1618.   function diffOccurrences(oldOccurrences, newOccurrences, dirty = false) {
  1619.     const oldOccurrencesISOString = oldOccurrences.map(o => o.startdate.toISOString().substr(0, 10));
  1620.     const newOccurrencesISOString = newOccurrences.map(d => d.toISOString().substr(0, 10));
  1621.     const toRemove = oldOccurrences.filter(o => newOccurrencesISOString.indexOf(o.startdate.toISOString().substr(0, 10)) === -1); // Some occurrences have been removed
  1622.     const toAdd = newOccurrences.filter(d => oldOccurrencesISOString.indexOf(d.toISOString().substr(0, 10)) === -1); // Occurrences have been added
  1623.     const toUpdate = dirty ? oldOccurrences.filter(o => toRemove.map(r => r.id).indexOf(o.id) === -1) : []; // Occurrences have been modified
  1624.     let diff = false;
  1625.     if (toRemove.length || toAdd.length || toUpdate.length) {
  1626.       diff = {};
  1627.       if (toRemove.length) diff['remove'] = toRemove; // Array<OldOccurrence>
  1628.       if (toAdd.length) diff['add'] = toAdd; // Array<Date>
  1629.       if (toUpdate.length) diff['update'] = toUpdate; // Array<OldOccurrence>
  1630.     }
  1631.     return diff;
  1632.   }
  1633.  
  1634.   /**
  1635.    *
  1636.    * @param {Meeting} oldMeeting
  1637.    * @param {Meeting} newMeeting
  1638.    * @returns {Object|Boolean} false if there have been no changes
  1639.    */
  1640.   function diffMeetings(oldMeeting, newMeeting) {
  1641.     const moment = require('moment');
  1642.     let diff = false;
  1643.     if (!oldMeeting || !newMeeting)
  1644.       return diff;
  1645.     if (oldMeeting.name !== newMeeting.name)
  1646.       diff = { name: newMeeting.name };
  1647.     const oldStartDate = moment(oldMeeting.startDate).toDate();
  1648.     const newStartDate = moment(newMeeting.startDate).toDate();
  1649.     const oldEndDate = moment(oldMeeting.endDate).toDate();
  1650.     const newEndDate = moment(newMeeting.endDate).toDate();
  1651.     if (oldStartDate.getTime() !== newStartDate.getTime() || oldEndDate.getTime() !== newEndDate.getTime()) {
  1652.       if (!diff) diff = {};
  1653.       diff['startDate'] = newStartDate;
  1654.       diff['endDate'] = newEndDate;
  1655.     }
  1656.     if (oldMeeting.location !== newMeeting.location) {
  1657.       if (!diff) diff = {};
  1658.       diff['location'] = newMeeting.location;
  1659.     }
  1660.     const oldIdOrganiser = oldMeeting.organiser ? (typeof oldMeeting.organiser === 'function' && oldMeeting.organiser().id) || oldMeeting.organiser.id : oldMeeting.idOrganiser;
  1661.     const newIdOrganiser = newMeeting.organiser ? (typeof newMeeting.organiser === 'function' && newMeeting.organiser().id) || newMeeting.organiser.id : newMeeting.idOrganiser;
  1662.     if (oldIdOrganiser !== newIdOrganiser) {
  1663.       if (!diff) diff = {};
  1664.       diff['idOrganiser'] = newIdOrganiser;
  1665.     }
  1666.     const oldAgendaItems = (typeof oldMeeting.agendaItems === 'function' && oldMeeting.agendaItems()) || oldMeeting.agendaItems || [];
  1667.     const newAgendaItems = (typeof newMeeting.agendaItems === 'function' && newMeeting.agendaItems()) || newMeeting.agendaItems || [];
  1668.     const toRemoveAgendaItems = oldAgendaItems.filter(a => newAgendaItems.map(n => n.id).indexOf(a.id) === -1);
  1669.     const toAddAgendaItems = newAgendaItems.filter(a => oldAgendaItems.map(o => o.id).indexOf(a.id) === -1);
  1670.     const toUpdateAgendaItems = oldAgendaItems.filter(o => {
  1671.       if (toRemoveAgendaItems.map(r => r.id).indexOf(o.id) !== -1)
  1672.         return false;
  1673.       for (var i = 0; i < newAgendaItems.length; i++) {
  1674.         if (newAgendaItems[i].id === o.id && newAgendaItems[i].title !== o.title)
  1675.           return true;
  1676.       }
  1677.       return false;
  1678.     }).map(o => {
  1679.       const obj = { oldValue: o.title };
  1680.       for (var i = 0; i < newAgendaItems.length; i++) {
  1681.         if (newAgendaItems[i].id === o.id) {
  1682.           obj.newValue = newAgendaItems[i].title;
  1683.           break;
  1684.         }
  1685.       }
  1686.       return obj;
  1687.     });
  1688.     if (toRemoveAgendaItems.length || toAddAgendaItems.length || toUpdateAgendaItems.length) {
  1689.       if (!diff) diff = {};
  1690.       diff['agendaItems'] = {};
  1691.       if (toRemoveAgendaItems.length) diff['agendaItems']['remove'] = toRemoveAgendaItems; // Object | AgendaItem
  1692.       if (toAddAgendaItems.length) diff['agendaItems']['add'] = toAddAgendaItems; // Object | AgendaItem
  1693.       if (toUpdateAgendaItems.length) diff['agendaItems']['update'] = toUpdateAgendaItems; // Object | AgendaItem
  1694.     }
  1695.     const oldInvitees = ((typeof oldMeeting.invitees === 'function' && oldMeeting.invitees()) || oldMeeting.invitees || [])
  1696.       .concat(oldMeeting.otherInvitees || [])
  1697.       .map(o => o.environmentUser ? (typeof o.environmentUser === 'function' && o.environmentUser().id) || o.environmentUser.id : o.idEnvironmentUser)
  1698.       .filter(id => !isNaN(Number(id)));
  1699.     const newInvitees = ((typeof newMeeting.invitees === 'function' && newMeeting.invitees()) || newMeeting.invitees || [])
  1700.       .concat(newMeeting.otherInvitees || [])
  1701.       .map(n => n.environmentUser ? (typeof n.environmentUser === 'function' && n.environmentUser().id) || n.environmentUser.id : n.idEnvironmentUser)
  1702.       .filter(id => !isNaN(Number(id)));
  1703.     const toRemoveInvitees = oldInvitees.filter(o => newInvitees.indexOf(o) === -1);
  1704.     const toAddInvitees = newInvitees.filter(n => oldInvitees.indexOf(n) === -1);
  1705.     if (toRemoveInvitees.length || toAddInvitees.length) {
  1706.       if (!diff) diff = {};
  1707.       diff['invitees'] = {};
  1708.       if (toRemoveInvitees.length) diff['invitees']['remove'] = toRemoveInvitees; /* environnementUser IDs */
  1709.       if (toAddInvitees.length) diff['invitees']['add'] = toAddInvitees; /* environnementUser IDs */
  1710.     }
  1711.     if (newMeeting.isRecurring !== oldMeeting.isRecurring) {
  1712.       if (!diff) diff = {};
  1713.       diff['isRecurring'] = { oldValue: oldMeeting.isRecurring, newValue: newMeeting.isRecurring };
  1714.     }
  1715.     if (newMeeting.recurringPattern !== oldMeeting.recurringPattern) {
  1716.       if (!diff) diff = {};
  1717.       diff['recurringPattern'] = { oldValue: oldMeeting.recurringPattern, newValue: newMeeting.recurringPattern };
  1718.     }
  1719.     return diff;
  1720.   }
  1721.  
  1722.   /**
  1723.    *
  1724.    * @param {Number} parentId
  1725.    * @param {Boolean} inclusive
  1726.    * @param {Function} callback
  1727.    */
  1728.   function nextMeetings(parentId, inclusive = true, callback) {
  1729.     const query = `
  1730.       WITH RECURSIVE nextmeetings AS (
  1731.         SELECT id, parentId, startDate FROM meeting WHERE id = `+ parentId + `
  1732.         UNION
  1733.         SELECT m.id, m.parentId, m.startDate FROM meeting m INNER JOIN nextmeetings n ON n.id = m.parentId WHERE isDeleted = FALSE
  1734.       ) SELECT * FROM nextmeetings`;
  1735.     if (!inclusive)
  1736.       query += ' WHERE id != ' + parentId;
  1737.     app.datasources.eboardDatasource.connector.execute(query, function (err, next) {
  1738.       if (err || !next) {
  1739.         if (callback) callback(err);
  1740.         return;
  1741.       }
  1742.       next.sort((a, b) => a.startdate.getTime() - b.startdate.getTime());
  1743.       if (callback) callback(null, next);
  1744.     });
  1745.   }
  1746.  
  1747.   /**
  1748.    *
  1749.    * @param {Array<Number>} meetingIds
  1750.    * @param {Function} callback
  1751.    */
  1752.   function buildMeetingTree(meetingIds, callback) {
  1753.     if (!Array.isArray(meetingIds) || !meetingIds.length) {
  1754.       if (callback) callback(null, false);
  1755.       return;
  1756.     }
  1757.     const query = `
  1758.       WITH occurrences AS (
  1759.         SELECT id, ROW_NUMBER () OVER (ORDER BY startdate) AS rowno FROM meeting WHERE id IN (`+ meetingIds.filter(id => !!id).join() + `)
  1760.       ) UPDATE meeting SET parentid = CASE WHEN o.rowno = 1 THEN NULL ELSE (SELECT id FROM occurrences WHERE rowno = o.rowno - 1) END
  1761.       FROM occurrences o WHERE o.id = meeting.id`;
  1762.     app.datasources.eboardDatasource.connector.execute(query, function (err) {
  1763.       if (callback) callback(err, !!!err);
  1764.     });
  1765.   }
  1766.  
  1767.   /**
  1768.    *
  1769.    * @param {Meeting} meeting
  1770.    * @param {Function} callback
  1771.    */
  1772.   function cancelMeeting(meeting, callback) {
  1773.     const icalToolkit = require('ical-toolkit');
  1774.     const moment = require('moment');
  1775.     async.each(meeting.invitees(), function (invitee, cb) {
  1776.       const builder = icalToolkit.createIcsFileBuilder();
  1777.       builder.spacers = true; //Add space in ICS file, better human reading. Default: true
  1778.       builder.NEWLINE_CHAR = '\r\n'; //Newline char to use.
  1779.       builder.throwError = false; //If true throws errors, else returns error when you do .toString() to generate the file contents.
  1780.       builder.ignoreTZIDMismatch = true; //If TZID is invalid, ignore or not to ignore!
  1781.       builder.calname = 'Yo Cal';
  1782.       builder.method = 'CANCEL';
  1783.       builder.events.push({
  1784.         start: meeting.startDate,
  1785.         end: meeting.endDate,
  1786.         transp: 'OPAQUE',
  1787.         summary: meeting.name,
  1788.         uid: meeting.idEnvironment + '' + meeting.id + '@eboard.com',
  1789.         location: meeting.location,
  1790.         description: 'Meeting details/Détails de la réunion',
  1791.         organizer: {
  1792.           name: meeting.environment().config().allowEditOrganiserNameInCalender ? meeting.environment().config().edittedOrganiserName : meeting.organiser().user().firstName + ' ' + meeting.organiser().user().lastName,
  1793.           email: meeting.environment().config().mailFrom
  1794.         },
  1795.         attendees: [{
  1796.           name: invitee.environmentUser().user().firstName + ' ' + invitee.environmentUser().user().lastName,
  1797.           email: invitee.environmentUser().user().email,
  1798.           rsvp: true
  1799.         }],
  1800.         method: 'CANCEL',
  1801.         status: 'CANCELLED'
  1802.       });
  1803.       let icsContent = builder.toString();
  1804.       if (icsContent instanceof Error)
  1805.         return cb(icsContent);
  1806.       emailer.init(meeting.environment().config().smtpUsername, meeting.environment().config().smtpPassword, meeting.environment().config().smtpServerAddress);
  1807.       const emailContent = `<td bgcolor="#ffffff" align="left" height="100%" valign="top" width="100%" style="padding-bottom: 40px;padding:30px;">
  1808.           <p>Dear Sir/Madam,</p>
  1809.           <p><br></p>
  1810.           <p>Note that the following meeting has been cancelled:</p>
  1811.           <p><strong>Name: </strong>`+ meeting.name + `</p>
  1812.           <p><strong>Company: </strong>`+ meeting.company().name + `</p>
  1813.           <p><strong>Date: </strong>`+ moment(meeting.startDate).format('DD MMM YYYY') + `</p>
  1814.           <p><strong>Location: </strong>`+ meeting.location + `</p>
  1815.           <p><br></p>
  1816.           <p>We sincerely apologise for any inconvenience caused.</p>
  1817.           <p><br></p>
  1818.           <p><strong>Best Regards,</strong></p>
  1819.           <p>The Support Team</p>
  1820.           <p><br></p>
  1821.       </td>`;
  1822.       templateManager.injectContent(emailContent, function (injectError, response) {
  1823.         if (injectError) return cb(injectError);
  1824.         const subject = 'Cancel_' + meeting.company().name + '_' + meeting.name + '_' + moment(meeting.startDate).format('DD MMM YYYY');
  1825.         emailer.sendEmail({
  1826.           from: meeting.environment().config().mailFrom,
  1827.           to: invitee.environmentUser().user().email,
  1828.           subject: subject,
  1829.           isHtml: true,
  1830.           body: response,
  1831.           alternatives: [{
  1832.             headers: { 'Content-Transfer-Encoding': '7bit' },
  1833.             contentType: 'text/calendar; charset="utf-8"; method=REPLY',
  1834.             content: new Buffer(icsContent ? icsContent : '')
  1835.           }]
  1836.         }, function (errr/*, mailInfo*/) {
  1837.           if (errr) return cb(errr);
  1838.           app.models.Email.create({
  1839.             from: meeting.environment().config().mailFrom,
  1840.             to: invitee.environmentUser().user().email,
  1841.             content: response,
  1842.             subject: subject,
  1843.             idEnvironment: meeting.idEnvironment,
  1844.             idMeeting: meeting.id,
  1845.             date: new Date(),
  1846.             type: 'MEETING_DELETE'
  1847.           }, cb);
  1848.         });
  1849.       });
  1850.     }, callback);
  1851.   }
  1852.  
  1853.   /**
  1854.    *
  1855.    * @param {Array<Number>} ids IDs of meetings
  1856.    * @param {Function} callback
  1857.    */
  1858.   function cancelMeetings(ids, callback) {
  1859.     if (!Array.isArray(ids) || !ids.length) {
  1860.       if (callback) callback();
  1861.       return;
  1862.     }
  1863.     Meeting.find({
  1864.       where: {
  1865.         and: [
  1866.           { id: { inq: ids } },
  1867.           { isDeleted: false }
  1868.         ]
  1869.       },
  1870.       include: [
  1871.         { environment: 'config' },
  1872.         { organiser: 'user' },
  1873.         { invitees: { environmentUser: 'user' } },
  1874.         'company'
  1875.       ]
  1876.     }, function (err, meetings) {
  1877.       if (err || !meetings || !meetings.length) {
  1878.         if (callback) callback(err);
  1879.         return;
  1880.       }
  1881.       async.each(meetings, function (meeting, next) {
  1882.         meeting.updateAttributes({
  1883.           modified: new Date(),
  1884.           isDeleted: true
  1885.         }, function (err, updatedMeeting) {
  1886.           if (err || updatedMeeting.state === constants.MEETING_CREATED || updatedMeeting.state === constants.MEETING_OVER)
  1887.             return next(err);
  1888.           cancelMeeting(meeting, next);
  1889.         });
  1890.       }, function (err) {
  1891.         if (err) {
  1892.           if (callback) callback(err);
  1893.           return;
  1894.         }
  1895.         if (callback) callback(null, meetings.map(m => m.id));
  1896.       });
  1897.     });
  1898.   }
  1899.  
  1900.   /**
  1901.    *
  1902.    * @param {EnvironmentConfig} config
  1903.    * @param {EnvironmentUser} environmentUser
  1904.    * @param {String} subject
  1905.    * @param {String} message
  1906.    * @param {Meeting} meeting
  1907.    * @param {Boolean} showAttendance
  1908.    * @param {Function} callback
  1909.    */
  1910.   // function sendMeetingUpdateWithIcs(config, environmentUser, subject, message, meeting, showAttendance, callback) {
  1911.   //   sendEmailWithIcsFile(environmentUser, subject, message, meeting, showAttendance, function (sendErr) {
  1912.   //     app.models.Email.create({
  1913.   //       from: config.mailFrom,
  1914.   //       to: environmentUser.eboardUser().email,
  1915.   //       content: message,
  1916.   //       idEnvironment: meeting.idEnvironment,
  1917.   //       idMeeting: meeting.id,
  1918.   //       date: new Date(),
  1919.   //       subject: subject,
  1920.   //       type: 'MEETING_NOTIFY'
  1921.   //     }, function (ex) {
  1922.   //       if (sendErr) {
  1923.   //         if (callback) callback(sendErr);
  1924.   //         return;
  1925.   //       }
  1926.   //       if (ex) {
  1927.   //         if (callback) callback(ex);
  1928.   //         return;
  1929.   //       }
  1930.   //       if (callback) callback();
  1931.   //     });
  1932.   //   });
  1933.   // }
  1934.  
  1935.   /**
  1936.    *
  1937.    * @param {String} subject
  1938.    * @param {String} message
  1939.    * @param {Meeting} meeting
  1940.    * @param {Function} callback
  1941.    */
  1942.   // function sendBatchMeetingUpdateWithIcs(subject, message, meeting, callback) {
  1943.   //   sendEmailInBatchWithIcsFile(subject, message, meeting, function (sendErr) {
  1944.   //     app.models.Email.create({
  1945.   //       from: meeting.environment().config().mailFrom,
  1946.   //       to: meeting.organiser().user().email,
  1947.   //       content: message,
  1948.   //       idEnvironment: meeting.idEnvironment,
  1949.   //       idMeeting: meeting.id,
  1950.   //       subject: subject,
  1951.   //       date: new Date(),
  1952.   //       type: 'MEETING_NOTIFY'
  1953.   //     }, function (ex) {
  1954.   //       if (sendErr) {
  1955.   //         if (callback) callback(sendErr);
  1956.   //         return;
  1957.   //       }
  1958.   //       if (ex) {
  1959.   //         if (callback) callback(ex);
  1960.   //         return;
  1961.   //       }
  1962.   //       if (callback) callback();
  1963.   //     });
  1964.   //   });
  1965.   // }
  1966.  
  1967.   /**
  1968.    *
  1969.    * @param {Meeting} meeting
  1970.    * @param {Function} callback
  1971.    */
  1972.   // function meetingUpdate(meeting, callback) {
  1973.   //   const moment = require('moment');
  1974.   //   const subject = 'Update_' + meeting.company().name + '_' + meeting.name + '_' + moment(meeting.startDate).format('DD MMM YYYY');
  1975.   //   const sender = meeting.environment().config().allowSendMailToAllOrganiser ?
  1976.   //     meeting.organiser().user().lastName + ' ' + meeting.organiser().user().firstName :
  1977.   //     'The Support Team';
  1978.   //   const message = `<p>Dear Sir/Madam,</p>
  1979.   //     <p><br></p>
  1980.   //     <p>Note that the details of the meeting `+ meeting.name + ` scheduled for company ` + meeting.company().name + ` have been modified to the following:</p>
  1981.   //     <p><strong>Date: </strong>`+ moment(meeting.startDate).format('DD MMM YYYY') + `</p>
  1982.   //     <p><strong>Time: </strong>`+ moment(meeting.startDate).format('HH:mm') + `</p>
  1983.   //     <p><strong>Location: </strong>`+ meeting.location + `</p>
  1984.   //     <p><br></p>
  1985.   //     <p>We sincerely apologise for any inconvenience caused.</p>
  1986.   //     <p><br></p>
  1987.   //     <p><strong>Best Regards,</strong></p>
  1988.   //     <p>`+ sender + `</p>
  1989.   //     <p><br></p>`;
  1990.   //   emailer.init(meeting.environment().config().smtpUsername, meeting.environment().config().smtpPassword, meeting.environment().config().smtpServerAddress);
  1991.   //   if (meeting.environment().config().showAttendance) {
  1992.   //     async.parallel([
  1993.   //       function (next) { // Notify the organiser
  1994.   //         sendMeetingUpdateWithIcs(meeting.environment().config(), meeting.organiser(), subject, message, meeting, false, next);
  1995.   //       },
  1996.   //       function (next) { // Notify other organisers
  1997.   //         if (!meeting.environment().config().allowSendMailToAllOrganiser)
  1998.   //           return next();
  1999.   //         async.each(meeting.meetingType().organisers().filter(mo => mo.isActive && mo.eboardUserId != meeting.organiser().eboardUserId), function (org, nxt) {
  2000.   //           sendMeetingUpdateWithIcs(meeting.environment().config(), org, subject, message, meeting, false, nxt);
  2001.   //         }, next);
  2002.   //       },
  2003.   //       function (next) { // Notify each invitee
  2004.   //         async.each(meeting.invitees(), function (invitee, nxt) {
  2005.   //           sendMeetingUpdateWithIcs(meeting.environment().config(), invitee.environmentUser(), subject, message, meeting, meeting.environment().config().showAttendance, nxt);
  2006.   //         }, next);
  2007.   //       }
  2008.   //     ], callback);
  2009.   //   } else {
  2010.   //     sendBatchMeetingUpdateWithIcs(subject, message, meeting, callback);
  2011.   //   }
  2012.   // }
  2013.  
  2014.   /**
  2015.    *
  2016.    * @param {Meeting} cMeeting current meeting to update containing diff
  2017.    * @param {Object} diff
  2018.    * @param {Array<Number>} ids ID of meetings to update
  2019.    * @param {Function} callback
  2020.    */
  2021.   function updateMeetings(cMeeting, diff, ids, callback) {
  2022.     if (!diff) {
  2023.       if (callback) callback();
  2024.       return;
  2025.     }
  2026.     Meeting.find({
  2027.       where: {
  2028.         and: [
  2029.           { id: { inq: ids.filter(id => id !== cMeeting.id) } },
  2030.           { isDeleted: false }
  2031.         ]
  2032.       },
  2033.       include: [
  2034.         { invitees: 'environmentUser' },
  2035.         'agendaItems'
  2036.       ]
  2037.     }, function (err, meetings) {
  2038.       if (err || !meetings) {
  2039.         if (callback) callback(err);
  2040.         return;
  2041.       }
  2042.       async.each(meetings, function (meeting, next) {
  2043.         const diffObj = {};
  2044.         if (diff.isRecurring)
  2045.           diffObj.isRecurring = diff.isRecurring.newValue;
  2046.         if (diff.recurringPattern)
  2047.           diffObj.recurringPattern = diff.recurringPattern.newValue;
  2048.         if (diff.startDate || diff.endDate) {
  2049.           diffObj.startDate = new Date(meeting.startDate.toISOString().substr(0, 10) + cMeeting.startDate.toISOString().substr(10));
  2050.           diffObj.endDate = new Date(meeting.endDate.toISOString().substr(0, 10) + cMeeting.endDate.toISOString().substr(10));
  2051.         }
  2052.         if (diff.location)
  2053.           diffObj.location = diff.location;
  2054.         if (diff.idOrganiser)
  2055.           diffObj.idOrganiser = diff.idOrganiser;
  2056.         diffObj.state = meeting.state === constants.MEETING_PUBLISHED ? constants.MEETING_UPDATED : meeting.state;
  2057.  
  2058.         async.parallel([
  2059.           function (cbk) {
  2060.             meeting.updateAttributes(diffObj, cbk);
  2061.           },
  2062.           function (cbk) { // agendaItems
  2063.             if (!diff.agendaItems) return cbk();
  2064.             async.parallel([
  2065.               function (cb) { // remove
  2066.                 if (!diff.agendaItems.remove || !diff.agendaItems.remove.length)
  2067.                   return cb();
  2068.                 const removedAgendaItemIds = meeting.agendaItems().filter(a => diff.agendaItems.remove.map(r => r.title).indexOf(a.title) !== -1).map(a => a.id);
  2069.                 if (!removedAgendaItemIds.length)
  2070.                   return cb();
  2071.                 app.models.AgendaItem.updateAll({
  2072.                   and: [
  2073.                     { id: { inq: removedAgendaItemIds } },
  2074.                     { isDeleted: false }
  2075.                   ]
  2076.                 }, { isDeleted: true }, cb);
  2077.               },
  2078.               function (cb) { // update
  2079.                 if (!diff.agendaItems.update || !diff.agendaItems.update.length)
  2080.                   return cb();
  2081.                 const updatedAgendaItems = meeting.agendaItems().filter(
  2082.                   a => diff.agendaItems.update.map(u => u.oldValue).indexOf(a.title) !== -1
  2083.                 ).map(a => {
  2084.                   const idx = diff.agendaItems.update.map(u => u.oldValue).indexOf(a.title);
  2085.                   return { id: a.id, title: diff.agendaItems.update[idx].newValue };
  2086.                 });
  2087.                 if (!updatedAgendaItems.length)
  2088.                   return cb();
  2089.                 async.each(updatedAgendaItems, function (item, nxt) {
  2090.                   app.models.AgendaItem.replaceById(item.id, { /*idMeeting: meeting.id,*/ title: item.title }, nxt);
  2091.                 }, cb);
  2092.               },
  2093.               function (cb) { // add
  2094.                 if (!diff.agendaItems.add || !diff.agendaItems.add.length)
  2095.                   return cb();
  2096.                 const addedAgendaItems = diff.agendaItems.add.map(a => a.title).filter(
  2097.                   title => meeting.agendaItems().map(a => a.title).indexOf(title) === -1
  2098.                     && (diff.agendaItems.remove || []).map(r => r.title).indexOf(title) === -1
  2099.                     && (diff.agendaItems.update || []).map(u => u.newValue).indexOf(title) === -1
  2100.                 );
  2101.                 if (!addedAgendaItems.length)
  2102.                   return cb();
  2103.                 async.each(addedAgendaItems, function (title, nxt) {
  2104.                   app.models.AgendaItem.create({ idMeeting: meeting.id, title: title }, nxt);
  2105.                 }, cb);
  2106.               }
  2107.             ], cbk);
  2108.           },
  2109.           function (cbk) { // invitees
  2110.             if (!diff.invitees) return cbk();
  2111.             async.parallel([
  2112.               function (cb) { // remove
  2113.  
  2114.                 if (!diff.invitees.remove || !diff.invitees.remove.length)
  2115.                   return cb();
  2116.  
  2117.                 async.parallel([
  2118.                   function (nxt) {
  2119.                     if (meeting.state === constants.MEETING_CREATED || meeting.state === constants.MEETING_OVER)
  2120.                       return nxt();
  2121.                     // TODO: Send meeting cancellation
  2122.                     callback(null, 'one');
  2123.                   },
  2124.                   function (nxt) {
  2125.                     app.models.MeetingInvitee.destroyAll({
  2126.                       and: [
  2127.                         { idMeeting: meeting.id },
  2128.                         { idEnvironmentUser: { inq: diff.invitees.remove } }
  2129.                       ]
  2130.                     }, nxt);
  2131.                   }
  2132.                 ], cb);
  2133.  
  2134.               },
  2135.               function (cb) { // add
  2136.                 if (!diff.invitees.add || !diff.invitees.add.length)
  2137.                   return cb();
  2138.                 const existingInvitees = meeting.invitees().map(i => i.environmentUser().id);
  2139.                 const inviteesToAdd = diff.invitees.add.filter(id => existingInvitees.indexOf(id) === -1);
  2140.                 if (!inviteesToAdd || !inviteesToAdd.length)
  2141.                   return cb();
  2142.                 app.models.EnvironmentUser.find({ // Consider only active users
  2143.                   fields: { id: true },
  2144.                   where: {
  2145.                     and: [
  2146.                       { id: { inq: inviteesToAdd } },
  2147.                       { isActive: true },
  2148.                       { isDeleted: false }
  2149.                     ]
  2150.                   }
  2151.                 }, function (err, activeUsers) {
  2152.                   if (err || !activeUsers) return cb(err);
  2153.                   async.each(inviteesToAdd.filter(id => activeUsers.map(u => u.id).indexOf(id) !== -1), function (uid, nxt) {
  2154.                     const idx = cMeeting.invitees.map(i => i.idEnvironmentUser).indexOf(uid);
  2155.                     if (idx === -1) return nxt();
  2156.                     const invitee = cMeeting.invitees[idx];
  2157.                     if (!invitee.permission) return nxt(); // should be considered as an error
  2158.                     const permission = invitee.permission;
  2159.                     delete permission.id;
  2160.                     app.models.Permission.create(permission, function (err, newPermission) {
  2161.                       if (err || !newPermission) return nxt(err);
  2162.                       app.models.MeetingInvitee.create({
  2163.                         idMeeting: meeting.id,
  2164.                         idPermission: newPermission.id,
  2165.                         idMeetingRole: invitee.idMeetingRole,
  2166.                         idEnvironmentUser: uid
  2167.                       }, nxt);
  2168.                     });
  2169.                   }, cb);
  2170.                   // TODO: Send meeting request
  2171.                 });
  2172.               }
  2173.             ], cbk);
  2174.           }
  2175.         ], next);
  2176.  
  2177.       }, function (err) {
  2178.         if (callback)
  2179.           callback(err, meetings.map(m => m.id));
  2180.       });
  2181.     });
  2182.   }
  2183.  
  2184.   /**
  2185.    *
  2186.    * @param {Meeting} cMeeting
  2187.    * @param {Array<Date>} datesOfNextMeetings
  2188.    * @param {Array<MeetingInvitee>} invitees
  2189.    * @param {Array<AgendaItem>} agendaItems
  2190.    * @param {Function} callback
  2191.    */
  2192.   function createNextMeetings(cMeeting, datesOfNextMeetings, invitees, agendaItems, callback) {
  2193.     if (!Array.isArray(datesOfNextMeetings) || !datesOfNextMeetings.length) {
  2194.       if (callback) callback();
  2195.       return;
  2196.     }
  2197.     const moment = require('moment');
  2198.     const ids = [];
  2199.     const diff = moment(cMeeting.endDate).toDate().getTime() - moment(cMeeting.startDate).toDate().getTime();
  2200.     async.eachOfSeries(datesOfNextMeetings, function (startDate, i, next) {
  2201.       const meeting = JSON.parse(JSON.stringify(cMeeting));
  2202.       delete meeting.id;
  2203.       delete meeting.information
  2204.       delete meeting.decisionTaken
  2205.       meeting.startDate = startDate;
  2206.       meeting.endDate = new Date(startDate.getTime() + diff);
  2207.       meeting.parentId = i > 0 ? ids[i - 1] : cMeeting.id;
  2208.       Meeting.create(meeting, function (err, newMeeting) {
  2209.         if (err || !newMeeting) return next(err);
  2210.         ids.push(newMeeting.id);
  2211.         async.parallel([
  2212.           function (nxt) { // save the invitees
  2213.             async.each(invitees, function (aNewInvitee, cb) {
  2214.               if (!aNewInvitee.permission) return cb(); // should be considered as an error
  2215.               var inviteePermission = aNewInvitee.permission;
  2216.               delete inviteePermission.id;
  2217.               app.models.Permission.create(inviteePermission, function (err, newInviteePermission) {
  2218.                 if (err || !newInviteePermission) return cb(err);
  2219.                 app.models.MeetingInvitee.create({
  2220.                   idMeeting: newMeeting.id,
  2221.                   idPermission: newInviteePermission.id,
  2222.                   idMeetingRole: aNewInvitee.idMeetingRole,
  2223.                   idEnvironmentUser: aNewInvitee.idEnvironmentUser
  2224.                 }, cb);
  2225.               });
  2226.             }, nxt);
  2227.           },
  2228.           function (nxt) { // save the agenda items
  2229.             if (!Array.isArray(agendaItems) || !agendaItems.length)
  2230.               return nxt();
  2231.             async.each(agendaItems, function (item, cb) {
  2232.               delete item.id;
  2233.               item.idMeeting = newMeeting.id;
  2234.               app.models.AgendaItem.create(item, cb);
  2235.             }, nxt);
  2236.           }
  2237.         ], next);
  2238.       });
  2239.     }, function (err) {
  2240.       if (callback) callback(err, ids);
  2241.     });
  2242.   }
  2243.  
  2244.   /**
  2245.    *
  2246.    * @param {Boolean} editThisAndFollowing default false
  2247.    * @param {Object} diffMeetings
  2248.    * @param {Meeting} cMeeting current meeting
  2249.    * @param {Function} callback
  2250.    */
  2251.   function editNextMeetings(editThisAndFollowing = false, diffMeetings, cMeeting, callback) {
  2252.     const moment = require('moment');
  2253.     if (editThisAndFollowing) {
  2254.       nextMeetings(cMeeting.id, true, function (err, nextmeetings) {
  2255.         if (err) {
  2256.           if (callback) callback(err);
  2257.           return;
  2258.         }
  2259.         const newOccurrences = generateNextOccurrences(cMeeting);
  2260.         newOccurrences.unshift(moment(cMeeting.startDate).toDate());
  2261.         const diffoccurrences = diffOccurrences(nextmeetings, newOccurrences, !!diffMeetings);
  2262.         if (!diffoccurrences) {
  2263.           if (callback) callback();
  2264.           return;
  2265.         }
  2266.         async.parallel([
  2267.           function (next) { // Cancel meetings
  2268.             if (!diffoccurrences.remove || !diffoccurrences.remove.length)
  2269.               return next();
  2270.             cancelMeetings(diffoccurrences.remove.map(o => o.id).filter(id => id !== cMeeting.id), next);
  2271.           },
  2272.           function (next) { // Update meetings
  2273.             if (!diffoccurrences.update || !diffoccurrences.update.length)
  2274.               return next();
  2275.             updateMeetings(cMeeting, diffMeetings, diffoccurrences.update.map(o => o.id), next);
  2276.           },
  2277.           function (next) { // Create meetings
  2278.             if (!diffoccurrences.add || !diffoccurrences.add.length)
  2279.               return next();
  2280.             createNextMeetings(cMeeting, diffoccurrences.add, cMeeting.invitees, cMeeting.agendaItems, next);
  2281.           }
  2282.         ], function (err, results) {
  2283.           if (err) {
  2284.             if (callback) callback(err);
  2285.             return;
  2286.           }
  2287.           buildMeetingTree([cMeeting.id].concat(results[1]).concat(results[2]), callback);
  2288.         });
  2289.       });
  2290.     } else if (diffMeetings && diffMeetings.isRecurring) {
  2291.       if (!diffMeetings.isRecurring.oldValue && diffMeetings.isRecurring.newValue) { // Create next meetings
  2292.         createNextMeetings(cMeeting, generateNextOccurrences(cMeeting), cMeeting.invitees, cMeeting.agendaItems, callback);
  2293.       } else if (diffMeetings.isRecurring.oldValue && !diffMeetings.isRecurring.newValue) { // Cancel next meetings
  2294.         nextMeetings(cMeeting.id, false, function (err, nextmeetings) {
  2295.           if (err) {
  2296.             if (callback) callback(err);
  2297.             return;
  2298.           }
  2299.           cancelMeetings(nextmeetings.map(m => m.id), callback);
  2300.         });
  2301.       } else if (callback) {
  2302.         callback();
  2303.       }
  2304.     } else if (callback) {
  2305.       callback();
  2306.     }
  2307.   }
  2308.  
  2309.   function publishNextMeetings(cMeeting, callback) {
  2310.     nextMeetings(cMeeting.id, false, function (err, nextmeetings) {
  2311.       if (err || !nextmeetings || !nextmeetings.length) {
  2312.         if (callback) callback(err);
  2313.         return;
  2314.       }
  2315.       async.each(nextmeetings, function (m, next) {
  2316.         Meeting.findOne({
  2317.           // include
  2318.           where: {
  2319.             and: [
  2320.               { state: constants.MEETING_CREATED },
  2321.               { isDeleted: false }
  2322.             ]
  2323.           }
  2324.         }, function (err, meeting) {
  2325.           if (err || !meeting || meeting.state !== constants.MEETING_CREATED)
  2326.             return next(err);
  2327.           // TODO: Send meeting request
  2328.           meeting.updateAttributes({ state: constants.MEETING_PUBLISHED, modified: new Date() }, next);
  2329.         });
  2330.       }, function (err) {
  2331.         if (callback) callback(err);
  2332.       });
  2333.     });
  2334.   }
  2335.  
  2336.   function deleteNextMeetings(cMeeting, callback) {
  2337.     nextMeetings(cMeeting.id, false, function (err, nextmeetings) {
  2338.       if (err || !nextmeetings || !nextmeetings.length) {
  2339.         if (callback) callback(err);
  2340.         return;
  2341.       }
  2342.       async.each(nextmeetings, function (m, next) {
  2343.         Meeting.findById(m.id/*, { include: [] }*/, function (err, meeting) {
  2344.           if (err || !meeting)
  2345.             return next(err);
  2346.           meeting.updateAttributes({ isDeleted: true, modified: new Date() }, function (err) {
  2347.             if (err || meeting.state === constants.MEETING_CREATED || meeting.state === constants.MEETING_OVER)
  2348.               return next(err);
  2349.             // TODO: Send meeting cancellation
  2350.             next();
  2351.           });
  2352.         });
  2353.       }, function (err) {
  2354.         if (callback) callback(err);
  2355.       });
  2356.     });
  2357.   }
  2358.  
  2359.   Meeting.saveMeeting = function (meeting, cb) {
  2360.     if (meeting.recurrence && meeting.recurrence.dirty) {
  2361.       meeting.isRecurring = meeting.recurrence.isRecurring;
  2362.       meeting.recurringPattern = meeting.recurrence.pattern;
  2363.     }
  2364.     if (meeting.id) { // updating meeting
  2365.       Meeting.findById(meeting.id, {
  2366.         include: [
  2367.           { environment: 'config' },
  2368.           { organiser: 'user' },
  2369.           { invitees: 'environmentUser' },
  2370.           'company',
  2371.           'agendaItems'
  2372.         ]
  2373.       }, function (err, meetingToUpdate) {
  2374.         if (err) return cb(err);
  2375.         if (!meetingToUpdate) {
  2376.           var notFound = new Error();
  2377.           notFound.status = 404;
  2378.           notFound.message = 'Meeting not found!';
  2379.           notFound.name = 'MEETING_NOT_FOUND';
  2380.           return cb(notFound);
  2381.         }
  2382.         const diffmeetings = diffMeetings(meetingToUpdate, meeting);
  2383.         meeting.state = diffmeetings && meetingToUpdate.state === constants.MEETING_PUBLISHED ? constants.MEETING_UPDATED : meetingToUpdate.state;
  2384.         if ((diffmeetings && diffmeetings.isRecurring && diffmeetings.isRecurring.oldValue && !diffmeetings.isRecurring.newValue)
  2385.           || meeting.editThisAndFollowing)
  2386.           meeting.parentId = null;
  2387.         let removedEnvironmentUsersId = [];
  2388.         let addedEnvironmentUsersId = [];
  2389.         if (meetingToUpdate.state !== constants.MEETING_CREATED) {
  2390.           const oldEnvironmentUsersId = (meetingToUpdate.invitees() || []).map(mi => mi.idEnvironmentUser);
  2391.           const newEnvironmentUsersId = (meeting.invitees || []).map(mi => {
  2392.             if (mi.environmentUser)
  2393.               return mi.environmentUser.id;
  2394.             return mi.idEnvironmentUser;
  2395.           }).concat((meeting.otherInvitees || []).map(moi => {
  2396.             if (moi.environmentUser)
  2397.               return moi.environmentUser.id;
  2398.             return moi.idEnvironmentUser;
  2399.           }));
  2400.           if (newEnvironmentUsersId.length < oldEnvironmentUsersId.length && meetingToUpdate.environment().config().notifyUserIfRemoved) // some users were removed from the meeting
  2401.             removedEnvironmentUsersId = oldEnvironmentUsersId.filter(id => newEnvironmentUsersId.indexOf(id) === -1);
  2402.           addedEnvironmentUsersId = newEnvironmentUsersId.filter(i => oldEnvironmentUsersId.indexOf(i) === -1); // some users were added to the meeting
  2403.         }
  2404.         meeting.modified = new Date();
  2405.         meeting.information = encryptor.encryptText(meeting.information, key);
  2406.         meeting.decisionTaken = encryptor.encryptText(meeting.decisionTaken, key);
  2407.         meetingToUpdate.patchAttributes(meeting, function (err, updatedMeeting) {
  2408.           if (err || !updatedMeeting) return cb(err);
  2409.           async.parallel([
  2410.             function (callback) { // invitees
  2411.               if (!diffmeetings || !diffmeetings.invitees)
  2412.                 return callback();
  2413.               meetingToUpdate.invitees.destroyAll(function (err) {
  2414.                 if (err) return callback(err);
  2415.                 async.each(meeting.invitees, function (invitee, next) { // update the invitees
  2416.                   if (!invitee.permission) return next(); // should be considered as an error
  2417.                   var invPermission = invitee.permission;
  2418.                   delete invPermission.id;
  2419.                   app.models.Permission.create(invPermission, function (err, newPermission) {
  2420.                     if (err || !newPermission) return next(err);
  2421.                     app.models.MeetingInvitee.create({
  2422.                       idMeeting: meeting.id,
  2423.                       idPermission: newPermission.id,
  2424.                       idMeetingRole: invitee.idMeetingRole,
  2425.                       idEnvironmentUser: invitee.idEnvironmentUser
  2426.                     }, next);
  2427.                   });
  2428.                 }, callback);
  2429.               });
  2430.             },
  2431.             function (callback) { // agenda items
  2432.               if (!diffmeetings || !diffmeetings.agendaItems || !Array.isArray(meeting.agendaItems) || !meeting.agendaItems.length)
  2433.                 return callback();
  2434.               async.each(meeting.agendaItems, function (item, next) { // update the agenda items
  2435.                 item.idMeeting = meeting.id;
  2436.                 app.models.AgendaItem.findById(item.id, function (err, agendaItem) {
  2437.                   if (err || !agendaItem) return next(err);
  2438.                   agendaItem.patchAttributes(item, function (err) {
  2439.                     if (err) return next(err);
  2440.                     async.each(item.documents, function (doc, nxt) { // update the agenda documents
  2441.                       app.models.AgendaDocument.findById(doc.id, function (err, agendaDocument) {
  2442.                         if (err || !agendaDocument) return nxt(err);
  2443.                         agendaDocument.patchAttributes(doc, nxt);
  2444.                       });
  2445.                     }, next);
  2446.                   });
  2447.                 });
  2448.               }, callback);
  2449.             }
  2450.           ], function (err) {
  2451.             if (err) return cb(err);
  2452.             cb(null, updatedMeeting);
  2453.             Meeting.emit('editNextMeetings', {
  2454.               editThisAndFollowing: !!meeting.editThisAndFollowing,
  2455.               diffMeetings: diffmeetings,
  2456.               currentMeeting: meeting
  2457.             });
  2458.             (removedEnvironmentUsersId.filter((v, i, s) => s.indexOf(v) === i)).forEach(function (id) {
  2459.               Meeting.emit('notifyUser', {
  2460.                 meeting: meetingToUpdate,
  2461.                 envUserId: id,
  2462.                 organiser: meetingToUpdate.organiser().user()
  2463.               });
  2464.             });
  2465.             const batchNewIds = addedEnvironmentUsersId.filter((v, i, s) => s.indexOf(v) === i);
  2466.             if (batchNewIds.length) {
  2467.               Meeting.emit('notifyNewUserV2', {
  2468.                 meeting: meetingToUpdate,
  2469.                 ids: batchNewIds
  2470.               });
  2471.             }
  2472.           });
  2473.         });
  2474.       });
  2475.     } else { // create meeting
  2476.       delete meeting.id;
  2477.       meeting.information = encryptor.encryptText(meeting.information, key);
  2478.       meeting.decisionTaken = encryptor.encryptText(meeting.decisionTaken, key);
  2479.       Meeting.create(meeting, function (err, newMeeting) {
  2480.         if (err || !newMeeting) return cb(err);
  2481.         const datesOfNextMeetings = generateNextOccurrences(newMeeting);
  2482.         async.parallel([
  2483.           function (callback) { // invitees
  2484.             async.each(meeting.invitees, function (invitee, next) { // save the invitees
  2485.               if (!invitee.permission) return next(); // should be considered as an error
  2486.               var invPermission = invitee.permission;
  2487.               delete invPermission.id;
  2488.               app.models.Permission.create(invPermission, function (err, newPermission) {
  2489.                 if (err || !newPermission) return next(err);
  2490.                 app.models.MeetingInvitee.create({
  2491.                   idMeeting: newMeeting.id,
  2492.                   idPermission: newPermission.id,
  2493.                   idMeetingRole: invitee.idMeetingRole,
  2494.                   idEnvironmentUser: invitee.idEnvironmentUser
  2495.                 }, next);
  2496.               });
  2497.             }, callback);
  2498.           },
  2499.           function (callback) { // agenda items
  2500.             if (!Array.isArray(meeting.agendaItems) || !meeting.agendaItems.length)
  2501.               return callback();
  2502.             async.each(meeting.agendaItems, function (item, next) { // save the agenda items
  2503.               item.idMeeting = newMeeting.id;
  2504.               app.models.AgendaItem.findById(item.id, function (err, agendaItem) {
  2505.                 if (err || !agendaItem) return next(err);
  2506.                 agendaItem.patchAttributes(item, function (err) {
  2507.                   if (err) return next(err);
  2508.                   async.each(item.documents, function (doc, nxt) { // save the agenda documents
  2509.                     app.models.AgendaDocument.findById(doc.id, function (err, agendaDocument) {
  2510.                       if (err || !agendaDocument) return nxt(err);
  2511.                       agendaDocument.patchAttributes(doc, nxt);
  2512.                     });
  2513.                   }, next);
  2514.                 });
  2515.               });
  2516.             }, callback);
  2517.           }
  2518.         ], function (err) {
  2519.           if (err) return cb(err);
  2520.           cb(null, newMeeting);
  2521.           if (datesOfNextMeetings.length) {
  2522.             Meeting.emit('createNextMeetings', {
  2523.               datesOfNextMeetings: datesOfNextMeetings,
  2524.               currentMeeting: newMeeting,
  2525.               invitees: meeting.invitees,
  2526.               agendaItems: meeting.agendaItems
  2527.             });
  2528.           }
  2529.         });
  2530.       });
  2531.     }
  2532.   };
  2533.  
  2534.   Meeting.remoteMethod('saveMeeting', {
  2535.     accepts: {
  2536.       arg: 'data',
  2537.       type: 'object',
  2538.       http: {
  2539.         source: 'body'
  2540.       }
  2541.     },
  2542.     returns: {
  2543.       root: true,
  2544.       arg: '',
  2545.       type: 'Meeting'
  2546.     },
  2547.     http: {
  2548.       path: '/saveMeeting',
  2549.       verb: 'post'
  2550.     }
  2551.   });
  2552.  
  2553.   Meeting.setMeetingOver = function (data, cb) {
  2554.  
  2555.     app.models.Meeting.findById(data.idMeeting, {
  2556.       include: ['company', {
  2557.         organiser: ['eboardUser']
  2558.       }]
  2559.     }, function (error, currentMeeting) {
  2560.  
  2561.       currentMeeting.state = "MEETING_OVER";
  2562.       app.models.Meeting.upsert(currentMeeting, function (err, meetingUpdated) {
  2563.         cb(err, meetingUpdated);
  2564.       });
  2565.  
  2566.       app.models.Environment.findById(data.idEnvironment, {
  2567.         include: ['config']
  2568.       }, function (error, currentEnv) {
  2569.  
  2570.         emailer.init(currentEnv.config().smtpUsername, currentEnv.config().smtpPassword, currentEnv.config().smtpServerAddress);
  2571.  
  2572.         var templateContent = currentEnv.config().meetingOverTemplate; //'<p>' + currentMeeting.name + '</p>' + '<p>Information</p>' + encryptor.decryptText(currentMeeting.information) + '<p>Decision Taken</p>' + encryptor.decryptText(currentMeeting.decisionTaken);
  2573.         templateContent = templateContent.replace('[MEETING_NAME]', currentMeeting.name);
  2574.         templateContent = templateContent.replace('[INFORMATION]', encryptor.decryptText(currentMeeting.information, key));
  2575.         templateContent = templateContent.replace('[DECISION]', encryptor.decryptText(currentMeeting.decisionTaken, key));
  2576.         var emailContent = '<td bgcolor="#ffffff" align="left" height="100%" valign="top" width="100%" style="padding-bottom: 40px;padding:30px;">' + templateContent + '</td>';
  2577.         templateManager.injectContent(emailContent, function (injectError, response) {
  2578.           if (!injectError) {
  2579.             var subject = 'Archived Meeting';
  2580.             var mailInfo = emailer.sendEmail({
  2581.               from: currentEnv.config().mailFrom,
  2582.               to: currentMeeting.organiser().eboardUser().email,
  2583.               subject: subject,
  2584.               isHtml: true,
  2585.               body: response
  2586.             }, function (err) {
  2587.               app.models.Email.create({
  2588.                 from: currentEnv.config().mailFrom,
  2589.                 to: currentMeeting.organiser().eboardUser().email,
  2590.                 content: response,
  2591.                 idEnvironment: currentEnv.id,
  2592.                 idMeeting: currentMeeting.id,
  2593.                 date: new Date(),
  2594.                 subject: subject,
  2595.                 type: 'Meeting Over',
  2596.               }, function (ex, res) { });
  2597.             });
  2598.             //return cb(null,currentMeeting);
  2599.           }
  2600.         });
  2601.       });
  2602.     });
  2603.   };
  2604.  
  2605.   Meeting.remoteMethod(
  2606.     'setMeetingOver', {
  2607.       accepts: {
  2608.         arg: 'data',
  2609.         type: 'object',
  2610.         http: {
  2611.           source: 'body'
  2612.         }
  2613.       },
  2614.       returns: {
  2615.         root: true,
  2616.         arg: '',
  2617.         type: 'Meeting'
  2618.       },
  2619.       http: {
  2620.         path: '/setMeetingOver',
  2621.         verb: 'post'
  2622.       }
  2623.     });
  2624.  
  2625.  
  2626.  
  2627.   Meeting.sendAnnotatedDocument = function (data, cb) {
  2628.  
  2629.     var runAsync = watt(function* (next) {
  2630.  
  2631.  
  2632.       var annotatedDoc = yield app.models.RemoteFile.findById(data.documentId, next);
  2633.  
  2634.       var currentMeeting = yield app.models.Meeting.findById(data.idMeeting, {
  2635.         include: ['company']
  2636.       }, next);
  2637.  
  2638.       var currentEnv = yield app.models.Environment.findById(currentMeeting.idEnvironment, {
  2639.         include: ['config']
  2640.       }, next);
  2641.  
  2642.       var currentEnvUser = yield app.models.EnvironmentUser.findById(data.idUser, {
  2643.         include: ['eboardUser']
  2644.       }, next);
  2645.  
  2646.       for (var recipientId of data.recipients) {
  2647.         var currentRecipient = yield app.models.EnvironmentUser.findById(recipientId, {
  2648.           include: ['eboardUser']
  2649.         }, next);
  2650.         if (currentRecipient) {
  2651.           var moment = require('moment');
  2652.  
  2653.           var templateContent = currentEnv.config().annotatedDocumentTemplate;
  2654.           templateContent = templateContent.replace('[DOC_NAME]', annotatedDoc.name);
  2655.           templateContent = templateContent.replace('[SENDER_NAME]', currentEnvUser.eboardUser().firstName + " " + currentEnvUser.eboardUser().lastName);
  2656.           templateContent = templateContent.replace('[MEETING_NAME]', currentMeeting.name);
  2657.           templateContent = templateContent.replace('[COMPANY_NAME]', currentMeeting.company().name);
  2658.           templateContent = templateContent.replace('[DATE]', moment(currentMeeting.startDate).format('DD MMM YYYY'));
  2659.           // templateContent = templateContent.replace('[TIME]', moment(currentMeeting.startDate).zone("+04:00").format('HH:mm'));
  2660.           templateContent = templateContent.replace('[LOCATION_NAME]', currentMeeting.location);
  2661.  
  2662.           var emailContent = '<td bgcolor="#ffffff" align="left" height="100%" valign="top" width="100%" style="padding-bottom: 40px;padding:30px;">' + templateContent + '</td>';
  2663.  
  2664.           var response = yield templateManager.injectContent(emailContent, next);
  2665.           var appRoot = require('app-root-path');
  2666.           var fileNameOnDisk = Enumerable.from(annotatedDoc.url.split("/")).last();
  2667.           var filePath = require('path').resolve(appRoot.path + '/server/storage/' + currentEnv.id + '/' + fileNameOnDisk);
  2668.           var tmpFolder = require('path').resolve(appRoot.path + '/server/storage/tmp/');
  2669.           var decryptedPath = require('path').join(tmpFolder, annotatedDoc.name);
  2670.           yield encryptor.decryptFile(filePath, decryptedPath, key, next);
  2671.  
  2672.           emailer.init(currentEnv.config().smtpUsername, currentEnv.config().smtpPassword, currentEnv.config().smtpServerAddress);
  2673.  
  2674.           var subject = 'Meeting Annotation Document';
  2675.  
  2676.           var mailInfo = yield emailer.sendEmail({
  2677.             from: currentEnv.config().smtpUsername,
  2678.             to: currentRecipient.eboardUser().email,
  2679.             subject: subject,
  2680.             isHtml: true,
  2681.             body: response,
  2682.             attachments: [{ // file on disk as an attachment
  2683.               filename: annotatedDoc.name,
  2684.               path: decryptedPath // stream this file
  2685.             }]
  2686.           }, next);
  2687.  
  2688.           app.models.Email.create({
  2689.             from: currentEnv.config().smtpUsername,
  2690.             to: currentRecipient.eboardUser().email,
  2691.             content: response,
  2692.             idEnvironment: currentMeeting.idEnvironment,
  2693.             idMeeting: data.idMeeting,
  2694.             subject: subject,
  2695.             date: new Date(),
  2696.             hasDocument: true,
  2697.             type: 'SEND_ANNOTATED_DOC'
  2698.           }, function (ex, res) { });
  2699.         }
  2700.       }
  2701.       return currentMeeting;
  2702.     });
  2703.  
  2704.     runAsync(function (err, response) {
  2705.       if (err) {
  2706.         console.error(err);
  2707.       }
  2708.       cb(err, response);
  2709.     });
  2710.   };
  2711.  
  2712.   Meeting.remoteMethod(
  2713.     'sendAnnotatedDocument', {
  2714.       accepts: {
  2715.         arg: 'data',
  2716.         type: 'object',
  2717.         http: {
  2718.           source: 'body'
  2719.         }
  2720.       },
  2721.       returns: {
  2722.         root: true,
  2723.         arg: '',
  2724.         type: 'Meeting'
  2725.       },
  2726.       http: {
  2727.         path: '/sendAnnotatedDocument',
  2728.         verb: 'post'
  2729.       }
  2730.     });
  2731.  
  2732.  
  2733.   Meeting.addAttendance = function (idMeeting, idUser, attending, cb) {
  2734.     idMeeting = parseInt(idMeeting, 10);
  2735.     idUser = parseInt(idUser, 10);
  2736.     attending = parseInt(attending, 10);
  2737.  
  2738.     var isAttending = attending == 1;
  2739.     app.models.MeetingAttendance.findOne({
  2740.       include: 'environmentUser',
  2741.       where: {
  2742.         and: [
  2743.           { idMeeting: idMeeting },
  2744.           { idEnvironmentUser: idUser },
  2745.           { isDeleted: false }
  2746.         ]
  2747.       }
  2748.     }, function (error, theUser) {
  2749.       if (error) {
  2750.         return cb(error, {
  2751.           success: false
  2752.         });
  2753.       }
  2754.  
  2755.       if (theUser) {
  2756.         theUser.patchAttributes({
  2757.           isAttending: isAttending
  2758.         }, function (err, theUser) {
  2759.           if (err) {
  2760.             return cb(error, {
  2761.               success: false
  2762.             });
  2763.           } else {
  2764.             cb(null, {
  2765.               success: true
  2766.             });
  2767.           }
  2768.  
  2769.         });
  2770.       }
  2771.     });
  2772.   };
  2773.  
  2774.   Meeting.remoteMethod(
  2775.     'addAttendance', {
  2776.       accepts: [{
  2777.         arg: 'idMeeting',
  2778.         type: 'string'
  2779.  
  2780.       }, {
  2781.         arg: 'idUser',
  2782.         type: 'string'
  2783.  
  2784.       }, {
  2785.         arg: 'attending',
  2786.         type: 'string'
  2787.  
  2788.       }],
  2789.  
  2790.       http: {
  2791.         path: '/addAttendance',
  2792.         verb: 'get'
  2793.       }
  2794.     });
  2795.  
  2796.   Meeting.publishMeeting = function (data, cb) {
  2797.     Meeting.findOne({
  2798.       order: 'name ASC',
  2799.       include: ['attendance', {
  2800.         meetingType: {
  2801.           organisers: ['eboardUser']
  2802.         }
  2803.       }, {
  2804.           organiser: ['eboardUser']
  2805.         }, {
  2806.           agendaItems: [{
  2807.             documents: ["file", {
  2808.               votes: [{
  2809.                 environmentUser: ['eboardUser']
  2810.               }]
  2811.             }]
  2812.           }]
  2813.         }, {
  2814.           company: ['users', 'companyLogo']
  2815.         }, {
  2816.           invitees: ['permission', {
  2817.             role: 'permission'
  2818.           }, {
  2819.               environmentUser: 'eboardUser'
  2820.             }]
  2821.         }],
  2822.       where: {
  2823.         and: [{
  2824.           isDeleted: false
  2825.         }, {
  2826.           id: data.idMeeting
  2827.         }]
  2828.       }
  2829.     }, function (error, meeting) {
  2830.       if (error)
  2831.         return cb(error);
  2832.       if (!meeting) {
  2833.         const notFound = new Error();
  2834.         notFound.status = 404;
  2835.         notFound.message = 'Meeting not found';
  2836.         notFound.name = 'NO_MEETING';
  2837.         return cb(notFound);
  2838.       }
  2839.       if (meeting.isRecurring) {
  2840.         // TODO: publish next meetings if not published
  2841.         // publishNextMeetings(meeting, callback);
  2842.       }
  2843.       app.models.Environment.findById(meeting.idEnvironment, {
  2844.         include: ['config']
  2845.       }, function (errorEnv, currentEnv) {
  2846.         if (errorEnv)
  2847.           return cb(errorEnv);
  2848.         emailer.init(currentEnv.config().smtpUsername, currentEnv.config().smtpPassword, currentEnv.config().smtpServerAddress);
  2849.         if (currentEnv.config().publishMeetingWithNotif) {
  2850.           var moment = require('moment');
  2851.           var subject = 'New_' + meeting.company().name + '_' + meeting.name + '_' + moment(meeting.startDate).format('DD MMM YYYY');
  2852.           if (currentEnv.config().showAttendance) {
  2853.             //invite the organiser
  2854.             sendEmailWithIcsFile(meeting.organiser(), subject, data.message, meeting, false, function (sendErr) {
  2855.               app.models.Email.create({
  2856.                 from: currentEnv.config().mailFrom,
  2857.                 to: meeting.organiser().eboardUser().email,
  2858.                 content: data.message,
  2859.                 idEnvironment: currentEnv.id,
  2860.                 idMeeting: meeting.id,
  2861.                 subject: subject,
  2862.                 date: new Date(),
  2863.                 type: 'MEETING_PUBLISH'
  2864.               }, function (ex, res) { });
  2865.             });
  2866.             var allInvitees = meeting.invitees();
  2867.             if (currentEnv.config().allowSendMailToAllOrganiser) {
  2868.               var meetingTypeOrg = meeting.meetingType().organisers();
  2869.               meetingTypeOrg = Enumerable.from(meetingTypeOrg).where(function (mo) {
  2870.                 return mo.isActive && mo.eboardUserId != meeting.organiser().eboardUserId;
  2871.               }).toArray();
  2872.               for (var org of meetingTypeOrg) {
  2873.                 sendEmailWithIcsFile(org, subject, data.message, meeting, false, function (sendErr) {
  2874.                   app.models.Email.create({
  2875.                     from: currentEnv.config().mailFrom,
  2876.                     to: org.eboardUser().email,
  2877.                     content: data.message,
  2878.                     idEnvironment: currentEnv.id,
  2879.                     idMeeting: meeting.id,
  2880.                     subject: subject,
  2881.                     date: new Date(),
  2882.                     type: 'MEETING_PUBLISH'
  2883.                   }, function (ex, res) { });
  2884.                 });
  2885.               }
  2886.             }
  2887.             for (var invitee of allInvitees) {
  2888.               //add attendance
  2889.               app.models.MeetingAttendance.findOrCreate({
  2890.                 idMeeting: meeting.id,
  2891.                 idEnvironmentUser: invitee.environmentUser().id
  2892.               }, function (attendError, theUser) {
  2893.                 if (attendError) {
  2894.                   console.log(attendError);
  2895.                 }
  2896.               });
  2897.               sendEmailWithIcsFile(invitee.environmentUser(), subject, data.message, meeting, currentEnv.config().showAttendance, function (sendErr) {
  2898.                 app.models.Email.create({
  2899.                   from: currentEnv.config().mailFrom,
  2900.                   to: invitee.environmentUser().eboardUser().email,
  2901.                   content: data.message,
  2902.                   idEnvironment: currentEnv.id,
  2903.                   idMeeting: meeting.id,
  2904.                   subject: subject,
  2905.                   date: new Date(),
  2906.                   type: 'MEETING_PUBLISH'
  2907.                 }, function (ex, res) { });
  2908.               });
  2909.             }
  2910.           } else {
  2911.             sendEmailInBatchWithIcsFile(subject, data.message, meeting, function (sendErr) {
  2912.               app.models.Email.create({
  2913.                 from: currentEnv.config().mailFrom,
  2914.                 to: meeting.organiser().eboardUser().email,
  2915.                 content: data.message,
  2916.                 idEnvironment: currentEnv.id,
  2917.                 idMeeting: meeting.id,
  2918.                 subject: subject,
  2919.                 date: new Date(),
  2920.                 type: 'MEETING_PUBLISH'
  2921.               }, function (ex, res) { });
  2922.             });
  2923.           }
  2924.         }
  2925.       });
  2926.       meeting.state = constants.MEETING_PUBLISHED;
  2927.       Meeting.upsert(meeting, function (err, meetingUpdated) {
  2928.         cb(err, meetingUpdated);
  2929.       });
  2930.     });
  2931.   };
  2932.  
  2933.  
  2934.   Meeting.remoteMethod(
  2935.     'publishMeeting', {
  2936.       accepts: {
  2937.         arg: 'data',
  2938.         type: 'object',
  2939.         http: {
  2940.           source: 'body'
  2941.         }
  2942.       },
  2943.       returns: {
  2944.         root: true,
  2945.         arg: '',
  2946.         type: 'Meeting'
  2947.       },
  2948.       http: {
  2949.         path: '/publishMeeting',
  2950.         verb: 'post'
  2951.       }
  2952.     });
  2953.  
  2954.   function deleteICSFile(aMeeting, cb) {
  2955.  
  2956.     var icalToolkit = require('ical-toolkit');
  2957.  
  2958.     app.models.Environment.findById(aMeeting.idEnvironment, {
  2959.       include: ['config']
  2960.     }, function (errorEnv, currentEnv) {
  2961.  
  2962.       if (errorEnv) {
  2963.         if (cb) {
  2964.           cb(errorEnv);
  2965.         }
  2966.       } else {
  2967.  
  2968.         //Create a builder
  2969.         var builder = icalToolkit.createIcsFileBuilder();
  2970.  
  2971.         /*
  2972.          * Settings (All Default values shown below. It is optional to specify)
  2973.          * */
  2974.         builder.spacers = true; //Add space in ICS file, better human reading. Default: true
  2975.         builder.NEWLINE_CHAR = '\r\n'; //Newline char to use.
  2976.         builder.throwError = false; //If true throws errors, else returns error when you do .toString() to generate the file contents.
  2977.         builder.ignoreTZIDMismatch = true; //If TZID is invalid, ignore or not to ignore!
  2978.  
  2979.  
  2980.         /**
  2981.          * Build ICS
  2982.          * */
  2983.  
  2984.         //Name of calander 'X-WR-CALNAME' tag.
  2985.         builder.calname = 'Yo Cal';
  2986.  
  2987.         //Cal timezone 'X-WR-TIMEZONE' tag. Optional. We recommend it to be same as tzid.
  2988.         //builder.timezone = 'america/new_york';
  2989.  
  2990.         //Time Zone ID. This will automatically add VTIMEZONE info.
  2991.         //builder.tzid = 'america/new_york';
  2992.  
  2993.         //Method
  2994.         builder.method = 'CANCEL';
  2995.  
  2996.         //var sequenceNumber = aMeeting.startDate.getDate() + '' + aMeeting.startDate.getHours() + '' + aMeeting.startDate.getSeconds();
  2997.         //var sequenceNumber = 150;
  2998.         var allInvitees = aMeeting.invitees() || [];
  2999.  
  3000.         var attendees = [];
  3001.         for (var invitee of allInvitees) {
  3002.           attendees.push({
  3003.             name: invitee.environmentUser().eboardUser().firstName + " " + invitee.environmentUser().eboardUser().lastName,
  3004.             email: invitee.environmentUser().eboardUser().email,
  3005.             rsvp: true
  3006.           });
  3007.         }
  3008.         //Add events
  3009.         builder.events.push({
  3010.  
  3011.           //Event start time, Required: type Date()
  3012.           start: aMeeting.startDate,
  3013.  
  3014.           //Event end time, Required: type Date()
  3015.           end: aMeeting.endDate,
  3016.  
  3017.           //transp. Will add TRANSP:OPAQUE to block calendar.
  3018.           transp: 'OPAQUE',
  3019.  
  3020.           //Event summary, Required: type String
  3021.           summary: aMeeting.name,
  3022.  
  3023.           //All Optionals Below
  3024.           //sequence: sequenceNumber,
  3025.           //Alarms, array in minutes
  3026.           //alarms: [15, 10, 5],
  3027.  
  3028.           //Event identifier, Optional, default auto generated
  3029.           uid: aMeeting.idEnvironment + "" + aMeeting.id + "@eboard.com",
  3030.  
  3031.           //Location of event, optional.
  3032.           location: aMeeting.location,
  3033.  
  3034.           //Optional description of event.
  3035.           description: 'Meeting details/Détails de la réunion',
  3036.  
  3037.           //Optional Organizer info
  3038.           organizer: {
  3039.             name: currentEnv.config().allowEditOrganiserNameInCalender ? currentEnv.config().edittedOrganiserName : aMeeting.organiser().eboardUser().firstName + " " + aMeeting.organiser().eboardUser().lastName,
  3040.             //if the sender and receiver have same email like oragniser, add to calendar will not be displayed
  3041.             //use a hardcoded unique email as a work around
  3042.             email: currentEnv.config().mailFrom //'promeeting@agileum.com' //'someone@agileum.com' //aMeeting.organiser().eboardUser().email
  3043.             // sentBy: 'person_acting_on_behalf_of_organizer@email.com' //OPTIONAL email address of the person who is acting on behalf of organizer.
  3044.           },
  3045.           attendees: attendees,
  3046.  
  3047.  
  3048.           //What to do on addition
  3049.           method: 'CANCEL',
  3050.  
  3051.           //Status of event
  3052.           status: 'CANCELLED',
  3053.  
  3054.           //Url for event on core application, Optional.
  3055.           // url: 'http://yahoo.com'
  3056.         });
  3057.  
  3058.  
  3059.         //Try to build
  3060.         var icsFileContent = builder.toString();
  3061.  
  3062.         //Check if there was an error (Only required if yu configured to return error, else error will be thrown.)
  3063.         if (icsFileContent instanceof Error) {
  3064.           console.log('Returned Error, you can also configure to throw errors!');
  3065.           //handle error
  3066.           cb(null);
  3067.         }
  3068.         console.log(icsFileContent);
  3069.         return cb(icsFileContent);
  3070.       }
  3071.     });
  3072.   }
  3073.  
  3074.   Meeting.deletePublishedMeeting = function (data, cb) {
  3075.     Meeting.findOne({
  3076.       order: 'name ASC',
  3077.       include: [
  3078.         { meetingType: { organisers: ['eboardUser'] } },
  3079.         { organiser: ['eboardUser'] },
  3080.         {
  3081.           agendaItems: [
  3082.             {
  3083.               documents: [
  3084.                 'file',
  3085.                 {
  3086.                   votes: [
  3087.                     { environmentUser: ['eboardUser'] }
  3088.                   ]
  3089.                 }
  3090.               ]
  3091.             }
  3092.           ]
  3093.         },
  3094.         { company: ['users', 'companyLogo'] },
  3095.         {
  3096.           invitees: [
  3097.             'permission',
  3098.             { role: 'permission' },
  3099.             { environmentUser: 'eboardUser' }
  3100.           ]
  3101.         }
  3102.       ],
  3103.       where: {
  3104.         and: [
  3105.           { isDeleted: false },
  3106.           { id: data.idMeeting }
  3107.         ]
  3108.       }
  3109.     }, function (error, meeting) {
  3110.       if (error)
  3111.         return cb(error);
  3112.       if (!meeting) {
  3113.         var notFound = new Error();
  3114.         notFound.status = 404;
  3115.         notFound.message = 'Meeting not found';
  3116.         notFound.name = 'NO_MEETING';
  3117.         return cb(notFound);
  3118.       }
  3119.       if (data.deleteThisAndFollowing && meeting.isRecurring) {
  3120.         // TODO: Delete next meetings
  3121.         // deleteNextMeetings(meeting, callback);
  3122.       }
  3123.       var moment = require('moment');
  3124.       var emailMessage = '<td bgcolor="#ffffff" align="left" height="100%" valign="top" width="100%" style="padding-bottom: 40px;padding:30px;">' + data.template + '</td>';
  3125.       var meetingInvitees = Enumerable.from(meeting.invitees()).select(function (m) { return m.environmentUser().eboardUser().email; }).toArray();
  3126.       meetingInvitees.push(Enumerable.from(meeting.invitees()).select(function (m) { return m.environmentUser().eboardUser().ccEmail; }).firstOrDefault(null));
  3127.       templateManager.injectContent(emailMessage, function (err, response) {
  3128.         if (err)
  3129.           console.log(err);
  3130.         app.models.Environment.findById(meeting.idEnvironment, {
  3131.           include: ['config']
  3132.         }, function (errorEnv, currentEnv) {
  3133.           if (errorEnv)
  3134.             return cb(errorEnv);
  3135.           if (currentEnv.config().deleteMeetingWithNotif) {
  3136.             deleteICSFile(meeting, function (icsContent) {
  3137.               if (currentEnv.config().allowSendMailToAllOrganiser) {
  3138.                 var meetingTypeOrg = meeting.meetingType().organisers();
  3139.                 meetingTypeOrg = Enumerable.from(meetingTypeOrg).where(function (mo) { return mo.isActive && mo.eboardUserId != meeting.organiser().eboardUserId; }).toArray();
  3140.                 for (var org of meetingTypeOrg)
  3141.                   meetingInvitees.push(org.eboardUser().email);
  3142.               }
  3143.               emailer.init(currentEnv.config().smtpUsername, currentEnv.config().smtpPassword, currentEnv.config().smtpServerAddress);
  3144.               var headers = {};
  3145.               headers['Content-Transfer-Encoding'] = '7bit';
  3146.               var subject = 'Cancel_' + meeting.company().name + '_' + meeting.name + '_' + moment(meeting.startDate).format('DD MMM YYYY');
  3147.               emailer.sendEmail({
  3148.                 from: currentEnv.config().mailFrom,
  3149.                 to: meeting.organiser().eboardUser().email,
  3150.                 subject: subject,
  3151.                 isHtml: true,
  3152.                 body: response,
  3153.                 cc: meetingInvitees,
  3154.                 alternatives: [{
  3155.                   headers: headers,
  3156.                   contentType: 'text/calendar; charset="utf-8"; method=REPLY',
  3157.                   content: new Buffer(icsContent ? icsContent : '')
  3158.                 }]
  3159.               }, function (errr, mailInfo) {
  3160.                 app.models.Email.create({
  3161.                   from: currentEnv.config().mailFrom,
  3162.                   to: meeting.organiser().eboardUser().email,
  3163.                   content: response,
  3164.                   subject: subject,
  3165.                   idEnvironment: meeting.idEnvironment,
  3166.                   idMeeting: meeting.id,
  3167.                   date: new Date(),
  3168.                   type: 'MEETING_DELETE'
  3169.                 }, function (ex, res) { });
  3170.                 for (var anEmail of meetingInvitees) {
  3171.                   app.models.Email.create({
  3172.                     from: currentEnv.config().mailFrom,
  3173.                     to: anEmail,
  3174.                     content: response,
  3175.                     subject: subject,
  3176.                     idEnvironment: meeting.idEnvironment,
  3177.                     idMeeting: meeting.id,
  3178.                     date: new Date(),
  3179.                     type: 'MEETING_DELETE'
  3180.                   }, function (ex, res) { });
  3181.                 }
  3182.                 if (errr)
  3183.                   console.log(err);
  3184.                 console.log(mailInfo);
  3185.               });
  3186.             });
  3187.           }
  3188.         });
  3189.         meeting.isDeleted = true;
  3190.         Meeting.upsert(meeting, function (err, meetingUpdated) {
  3191.           cb(err, meetingUpdated);
  3192.         });
  3193.       });
  3194.     });
  3195.   };
  3196.  
  3197.   Meeting.remoteMethod('deletePublishedMeeting', {
  3198.     accepts: {
  3199.       arg: 'data',
  3200.       type: 'object',
  3201.       http: {
  3202.         source: 'body'
  3203.       }
  3204.     },
  3205.     returns: {
  3206.       root: true,
  3207.       arg: '',
  3208.       type: 'Meeting'
  3209.     },
  3210.     http: {
  3211.       path: '/deletePublishedMeeting',
  3212.       verb: 'post'
  3213.     }
  3214.   });
  3215.  
  3216.   Meeting.notifyMeeting = function (data, cb) {
  3217.     var notifyUpdate = data.notifyUpdate;
  3218.     Meeting.findOne({
  3219.       order: 'name ASC',
  3220.       include: [
  3221.         { meetingType: { organisers: ['eboardUser'] } },
  3222.         { organiser: ['eboardUser'] },
  3223.         {
  3224.           agendaItems: [
  3225.             {
  3226.               documents: [
  3227.                 'file',
  3228.                 { votes: [{ environmentUser: ['eboardUser'] }] }
  3229.               ]
  3230.             }
  3231.           ]
  3232.         },
  3233.         { company: ['users', 'companyLogo'] },
  3234.         {
  3235.           invitees: [
  3236.             'permission',
  3237.             { role: 'permission' },
  3238.             { environmentUser: 'eboardUser' }
  3239.           ]
  3240.         }
  3241.       ],
  3242.       where: {
  3243.         and: [
  3244.           { isDeleted: false },
  3245.           { id: data.idMeeting }
  3246.         ]
  3247.       }
  3248.     }, function (error, meeting) {
  3249.       if (error)
  3250.         return cb(error);
  3251.       var moment = require('moment');
  3252.       var subject = 'Update_' + meeting.company().name + '_' + meeting.name + '_' + moment(meeting.startDate).format('DD MMM YYYY');
  3253.       var allInvitees = meeting.invitees();
  3254.       app.models.Environment.findById(meeting.idEnvironment, {
  3255.         include: ['config']
  3256.       }, function (errorEnv, currentEnv) {
  3257.         if (errorEnv)
  3258.           return cb(errorEnv);
  3259.         emailer.init(currentEnv.config().smtpUsername, currentEnv.config().smtpPassword, currentEnv.config().smtpServerAddress);
  3260.         if (notifyUpdate) {
  3261.           var emailHtml = '<td bgcolor="#ffffff" align="left" height="100%" valign="top" width="100%" style="padding-bottom: 40px;padding:30px;">' + data.message + '</td>';
  3262.           templateManager.injectContent(emailHtml, function (injectErr, response) {
  3263.             if (injectErr)
  3264.               return cb(injectErr);
  3265.             var invEmailList = [];
  3266.             var orgEmailList = [];
  3267.             orgEmailList.push(meeting.organiser().eboardUser().email);
  3268.             for (var invitee of allInvitees) {
  3269.               invEmailList.push(invitee.environmentUser().eboardUser().email);
  3270.               if (invitee.environmentUser().eboardUser().ccEmail !== null)
  3271.                 invEmailList.push(invitee.environmentUser().eboardUser().ccEmail);
  3272.             }
  3273.             if (currentEnv.config().allowSendMailToAllOrganiser) {
  3274.               var meetingTypeOrg = meeting.meetingType().organisers();
  3275.               meetingTypeOrg = Enumerable.from(meetingTypeOrg).where(function (mo) {
  3276.                 return mo.isActive && mo.eboardUserId != meeting.organiser().eboardUserId;
  3277.               }).toArray();
  3278.               for (var org of meetingTypeOrg) {
  3279.                 orgEmailList.push(org.eboardUser().email);
  3280.                 if (org.eboardUser().ccEmail !== null)
  3281.                   orgEmailList.push(org.eboardUser().ccEmail);
  3282.               }
  3283.             }
  3284.             emailer.sendEmail({
  3285.               from: currentEnv.config().mailFrom,
  3286.               to: invEmailList,
  3287.               subject: subject,
  3288.               isHtml: true,
  3289.               body: response,
  3290.               cc: orgEmailList,
  3291.             }, function (err, mailInfo) {
  3292.               app.models.Email.create({
  3293.                 from: currentEnv.config().mailFrom,
  3294.                 to: orgEmailList + invEmailList,
  3295.                 content: response,
  3296.                 idEnvironment: currentEnv.id,
  3297.                 idMeeting: meeting.id,
  3298.                 subject: subject,
  3299.                 date: new Date(),
  3300.                 type: 'MEETING_NOTIFY'
  3301.               }, function (ex, res) { });
  3302.               if (err) {
  3303.                 console.log(err);
  3304.                 // if (cb)
  3305.                 //   return cb(err);
  3306.               }
  3307.               console.log(mailInfo);
  3308.               // if (cb)
  3309.               //   cb();
  3310.             });
  3311.           });
  3312.         } else {
  3313.           if (currentEnv.config().showAttendance) {
  3314.             //update the organiser
  3315.             sendEmailWithIcsFile(meeting.organiser(), subject, data.message, meeting, false, function (sendErr) {
  3316.               app.models.Email.create({
  3317.                 from: currentEnv.config().mailFrom,
  3318.                 to: meeting.organiser().eboardUser().email,
  3319.                 content: data.message,
  3320.                 idEnvironment: currentEnv.id,
  3321.                 idMeeting: meeting.id,
  3322.                 subject: subject,
  3323.                 date: new Date(),
  3324.                 type: 'MEETING_NOTIFY'
  3325.               }, function (ex, res) { });
  3326.             });
  3327.             if (currentEnv.config().allowSendMailToAllOrganiser) {
  3328.               var meetingTypeOrg = meeting.meetingType().organisers();
  3329.               meetingTypeOrg = Enumerable.from(meetingTypeOrg).where(function (mo) {
  3330.                 return mo.isActive && mo.eboardUserId != meeting.organiser().eboardUserId;
  3331.               }).toArray();
  3332.               for (var org of meetingTypeOrg) {
  3333.                 sendEmailWithIcsFile(org, subject, data.message, meeting, false, function (sendErr) {
  3334.                   app.models.Email.create({
  3335.                     from: currentEnv.config().mailFrom,
  3336.                     to: org.eboardUser().email,
  3337.                     content: data.message,
  3338.                     idEnvironment: currentEnv.id,
  3339.                     idMeeting: meeting.id,
  3340.                     subject: subject,
  3341.                     date: new Date(),
  3342.                     type: 'MEETING_NOTIFY'
  3343.                   }, function (ex, res) { });
  3344.                 });
  3345.               }
  3346.             }
  3347.             for (var invitee of allInvitees) {
  3348.               app.models.MeetingAttendance.findOrCreate({
  3349.                 idMeeting: meeting.id,
  3350.                 idEnvironmentUser: invitee.environmentUser().id
  3351.               }, function (attendErrorr, theInv) {
  3352.                 if (attendErrorr)
  3353.                   console.log(attendErrorr);
  3354.               });
  3355.               sendEmailWithIcsFile(invitee.environmentUser(), subject, data.message, meeting, currentEnv.config().showAttendance, function (sendErr) {
  3356.                 app.models.Email.create({
  3357.                   from: currentEnv.config().mailFrom,
  3358.                   to: invitee.environmentUser().eboardUser().email,
  3359.                   content: data.message,
  3360.                   idEnvironment: meeting.idEnvironment,
  3361.                   idMeeting: data.idMeeting,
  3362.                   date: new Date(),
  3363.                   subject: subject,
  3364.                   type: 'MEETING_NOTIFY'
  3365.                 }, function (ex, res) { });
  3366.               });
  3367.             }
  3368.           } else {
  3369.             sendEmailInBatchWithIcsFile(subject, data.message, meeting, function (sendErr) {
  3370.               app.models.Email.create({
  3371.                 from: currentEnv.config().mailFrom,
  3372.                 to: meeting.organiser().eboardUser().email,
  3373.                 content: data.message,
  3374.                 idEnvironment: currentEnv.id,
  3375.                 idMeeting: meeting.id,
  3376.                 subject: subject,
  3377.                 date: new Date(),
  3378.                 type: 'MEETING_NOTIFY'
  3379.               }, function (ex, res) { });
  3380.             });
  3381.           }
  3382.         }
  3383.       });
  3384.       meeting.state = constants.MEETING_PUBLISHED;
  3385.       Meeting.upsert(meeting, function (err, meetingUpdated) {
  3386.         cb(err, meetingUpdated);
  3387.       });
  3388.     });
  3389.   };
  3390.  
  3391.   Meeting.remoteMethod('notifyMeeting', {
  3392.     accepts: {
  3393.       arg: 'data',
  3394.       type: 'object',
  3395.       http: {
  3396.         source: 'body'
  3397.       }
  3398.     },
  3399.     returns: {
  3400.       root: true,
  3401.       arg: '',
  3402.       type: 'Meeting'
  3403.     },
  3404.     http: {
  3405.       path: '/notifyMeeting',
  3406.       verb: 'post'
  3407.     }
  3408.   });
  3409.  
  3410.  
  3411.   Meeting.searchMeetings = function (data, cb) {
  3412.  
  3413.     this.getAllMeetings({
  3414.       idUser: data.idUser,
  3415.       idEnvironment: data.idEnvironment
  3416.     }, function (err, meetings) {
  3417.       if (err) {
  3418.         cb(err, null);
  3419.       } else {
  3420.         //filter now
  3421.         var filteredMeetings = Enumerable.from(meetings)
  3422.           .where(function (m) {
  3423.             var shouldSearchByDocument = data.documentName || data.documentName !== "";
  3424.             var hasDocument;
  3425.  
  3426.             if (shouldSearchByDocument) {
  3427.               var allAgendas = Enumerable.from(m.agendaItems()).where(function (a) {
  3428.                 return a.isDeleted === false;
  3429.               }).toArray() || [];
  3430.  
  3431.               var allDocuments = [];
  3432.  
  3433.               for (var ag of allAgendas) {
  3434.                 for (var doc of ag.documents()) {
  3435.                   if (doc.isDeleted === false) {
  3436.                     allDocuments.push(doc);
  3437.                   }
  3438.                 }
  3439.               }
  3440.  
  3441.               hasDocument = Enumerable.from(allDocuments).where(function (agendaDoc) {
  3442.                 return agendaDoc.file().name.toLowerCase().indexOf(data.documentName.toLowerCase()) !== -1;
  3443.               }).firstOrDefault(null) !== null;
  3444.             }
  3445.             var hasMeetingName = data.name || data.name !== "";
  3446.  
  3447.             return (data.idCompany ? m.idCompany == data.idCompany : true) &&
  3448.               (hasMeetingName ? (m.name.toLowerCase().indexOf(data.name.toLowerCase()) !== -1) : true) &&
  3449.               (data.idMeetingType ? m.idMeetingType == data.idMeetingType : true) &&
  3450.               (shouldSearchByDocument ? hasDocument : true);
  3451.           }).toArray();
  3452.  
  3453.         cb(null, filteredMeetings);
  3454.  
  3455.         //end
  3456.       }
  3457.     });
  3458.   };
  3459.  
  3460.   Meeting.remoteMethod(
  3461.     'searchMeetings', {
  3462.       accepts: {
  3463.         arg: 'data',
  3464.         type: 'object',
  3465.         http: {
  3466.           source: 'body'
  3467.         }
  3468.       },
  3469.       returns: {
  3470.         root: true,
  3471.         arg: '',
  3472.         type: 'Meeting'
  3473.       },
  3474.       http: {
  3475.         path: '/searchMeetings',
  3476.         verb: 'post'
  3477.       }
  3478.     });
  3479.  
  3480.   function sendEmailInBatchWithIcsFile(subject, emailMessage, meeting, cb) {
  3481.  
  3482.     var emailTempHtml = '<td bgcolor="#ffffff" align="left" height="100%" valign="top" width="100%" style="padding-bottom: 40px;padding:30px;">' + emailMessage + '</td>';
  3483.  
  3484.  
  3485.     templateManager.injectContent(emailTempHtml, function (injectErr, response) {
  3486.       if (injectErr) {
  3487.         console.log(injectErr);
  3488.  
  3489.         if (cb) {
  3490.           cb(injectErr);
  3491.         }
  3492.         return;
  3493.       }
  3494.  
  3495.       generateICSFile(meeting, function (icsContent) {
  3496.  
  3497.         console.log(icsContent);
  3498.         app.models.Environment.findById(meeting.idEnvironment, {
  3499.           include: ['config']
  3500.         }, function (errorEnv, currentEnv) {
  3501.  
  3502.           if (errorEnv) {
  3503.  
  3504.             if (cb) {
  3505.               cb(errorEnv);
  3506.             }
  3507.           } else {
  3508.  
  3509.             var headers = {};
  3510.             headers['Content-Transfer-Encoding'] = '7bit';
  3511.  
  3512.             console.log('From: ' + currentEnv.config().mailFrom);
  3513.  
  3514.             var invList = [];
  3515.             var orgList = [];
  3516.  
  3517.             orgList.push(meeting.organiser().eboardUser().email);
  3518.  
  3519.             if (meeting.organiser().eboardUser().ccEmail !== null) {
  3520.               orgList.push(meeting.organiser().eboardUser().ccEmail);
  3521.             }
  3522.  
  3523.             for (var invitee of meeting.invitees()) {
  3524.               invList.push(invitee.environmentUser().eboardUser().email);
  3525.               if (invitee.environmentUser().eboardUser().ccEmail !== null) {
  3526.                 invList.push(invitee.environmentUser().eboardUser().ccEmail);
  3527.               }
  3528.             }
  3529.  
  3530.             if (currentEnv.config().allowSendMailToAllOrganiser) {
  3531.  
  3532.               var meetingTypeOrgs = meeting.meetingType().organisers();
  3533.  
  3534.               meetingTypeOrgs = Enumerable.from(meetingTypeOrgs).where(function (mo) {
  3535.                 return mo.isActive && mo.eboardUserId != meeting.organiser().eboardUserId;
  3536.               }).toArray();
  3537.  
  3538.               for (var org of meetingTypeOrgs) {
  3539.                 orgList.push(org.eboardUser().email);
  3540.                 if (org.eboardUser().ccEmail !== null) {
  3541.                   orgList.push(org.eboardUser().ccEmail);
  3542.                 }
  3543.               }
  3544.             }
  3545.             emailer.sendEmail({
  3546.               from: currentEnv.config().mailFrom,
  3547.               to: invList,
  3548.               subject: subject,
  3549.               isHtml: true,
  3550.               body: response,
  3551.               cc: orgList,
  3552.               alternatives: [{
  3553.                 headers: headers,
  3554.                 contentType: 'text/calendar; charset="utf-8"; method=REPLY',
  3555.                 content: new Buffer(icsContent ? icsContent : '')
  3556.               }],
  3557.             }, function (err, mailInfo) {
  3558.  
  3559.               if (err) {
  3560.                 console.log(err);
  3561.                 if (cb)
  3562.                   return cb(err);
  3563.               }
  3564.               console.log(mailInfo);
  3565.               if (cb)
  3566.                 cb();
  3567.             });
  3568.           }
  3569.         });
  3570.       });
  3571.     });
  3572.   }
  3573.  
  3574.  
  3575. };
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement