<commit_before>import Promise from 'bluebird';
import Email from '../models/email.model';
import Box from '../models/box.model';
import config from '../../config/env';
import User from '../models/user.model';
import Analyzer from '../core/engine/analyzer';
import Socket from '../routes/socket';
import HtmlDisassembler from '../core/analysis/HtmlDisassembler'
import GmailConnector from '../core/mail/GmailConnector';
import EWSConnector from '../core/mail/EWSConnector';

exports.sendEmail = (req, res) => {

  if (req.user.isExchangeProvider()) {

    const emailConnector = req.user.createIMAPConnector();
    emailConnector.sendMail(req.body)
      .then(result => {
        return Box.findOne({ name: EWSConnector.staticBoxNames.send, user: req.user });
      })
      .then(box => {
        return req.user.createIMAPConnector().fetchBoxes(storeEmail, [box]);
      })
      .then(() => {
        res.status(200).send({ message: 'Finished fetching' });
      })
      .catch((err) => {
        console.log(err);
        res.status(400).send(err);
      });

  } else {

    req.user.createSMTPConnector().sendMail(req.body)
      .then(result => {
        return Box.findOne({ name: GmailConnector.staticBoxNames.send, user: req.user });
      })
      .then(box => {
        return req.user.createIMAPConnector().fetchBoxes(storeEmail, [box]);
      })
      .then(() => {
        res.status(200).send({ message: 'Finished fetching' });
      })
      .catch((err) => {
        console.log(err);
        res.status(400).send(err);
      });

  }
}

exports.append = (req, res) => {
  const user = req.user;
  const boxId = req.body.boxId;
  const emailConnector = user.createIMAPConnector();

  if (req.user.isExchangeProvider()) {

    // Box.findOne({name: EWSConnector.staticBoxNames.draft, user: user})
    Box.findOne({ _id: boxId, user: user })
      .then(box => {
        return [box, emailConnector.append(req.body, box.ewsId)]
      })
      .spread((box, msgData) => {
        return [msgData, emailConnector.fetchBoxes(storeEmail, [box])]
      })
      .spread((msgData, result) => {
        res.status(200).send({ msgData: 'ok' });
      })
      .catch((err) => {
        console.log(err);
        res.status(400).send(err);
      });

  } else {

    // Box.findOne({name: GmailConnector.staticBoxNames.draft, user: user})
    Box.findOne({ _id: boxId, user: user })
      .then(box => {
        return [box, emailConnector.append(box.name, user.email, req.body.to, req.body.subject, req.body.msgData)]
      })
      .spread((box, msgData) => {
        return [msgData, emailConnector.fetchBoxes(storeEmail, [box])]
      })
      .spread((msgData, result) => {
        res.status(200).send({ msgData: msgData });
      })
      .catch((err) => {
        console.log(err);
        res.status(400).send(err);
      });

  }
}

exports.move = (req, res) => {
  const emailId = req.params.id;
  const newBoxId = req.body.newBoxId;
  const user = req.user;

  const emailConnector = user.createIMAPConnector();

  if (req.user.isExchangeProvider()) {

    Email.findOne({ _id: emailId }).populate('boxes')
      .then(email => {
        return [email, Box.findOne({ _id: newBoxId, user: user })]
      })
      .spread((email, destBox) => {
        const srcBox = email.boxes[0];
        return [srcBox, destBox, emailConnector.move(email, destBox.ewsId)]
      })
      .spread((srcBox, destBox, msgId) => {
        return emailConnector.fetchBoxes(storeEmail, [srcBox, destBox])
      })
      .then((messages) => {
        res.status(200).send({ messages: messages });
      })
      .catch(err => {
        console.log(err);
        res.status(400).send(err);
      });

  } else {

    Email.findOne({ _id: emailId }).populate('boxes')
      .then(email => {
        return [email, Box.findOne({ _id: newBoxId, user: user })]
      })
      // TODO boxId from parameter
      .spread((email, destBox) => {
        const srcBox = email.boxes[0];
        return [srcBox, destBox, emailConnector.move(email.uid, srcBox.name, destBox.name)]
      })
      .spread((srcBox, destBox, msgId) => {
        return emailConnector.fetchBoxes(storeEmail, [srcBox, destBox])
      })
      .then((messages) => {
        res.status(200).send({ messages: messages });
      })
      .catch(err => {
        console.log(err);
        res.status(400).send(err);
      });
  }
}

exports.moveToTrash = (req, res) => {
  if (req.user.isExchangeProvider()) {
    Box.findOne({ name: EWSConnector.staticBoxNames.deleted, user: req.user })
      .then(box => {
        req.body.newBoxId = box._id;
        exports.move(req, res);
      });
  } else if (req.user.isGMailProvider()) {
    Box.findOne({ name: GmailConnector.staticBoxNames.deleted, user: req.user })
      .then(box => {
        req.body.newBoxId = box._id;
        exports.move(req, res);
      });
  }
}

/**
 * @api {post} /email/:id/flags Add Email Flags
 * @apiDescription Add the specified flags to this email.
 * @apiName AddEmailFlags
 * @apiGroup Email
 * @apiParam {String} id Email unique ID.
 * @apiParam {String[]} flags Email flags.
 * @apiSuccessExample Success-Response:
 * //TODO
 *     {
 *       "firstname": "John",
 *       "lastname": "Doe"
 *     }
 */
exports.addFlags = (req, res) => {
  const emailId = req.params.id;
  const flags = req.body.flags;
  const user = req.user;
  const emailConnector = req.user.createIMAPConnector();
  let email = null;

  if (req.user.isExchangeProvider()) {

    Email.findById(emailId)//.populate('box')
      .then(mail => {
        email = mail;
        return emailConnector.addFlags(mail, flags);
      })
      .then(() => {
        email.flags = email.flags.concat(flags);
        return email.save();
      })
      .then(() => {
        res.status(200).send({ message: 'Successfully added Flags' });
      })
      .catch(err => {
        res.status(400).send(err);
      });

  } else {

    Email.findById(emailId).populate('boxes')
      .then(mail => {
        email = mail;
        return emailConnector.addFlags(mail.uid, flags, email.boxes[0].name);
      })
      .then(() => {
        email.flags = email.flags.concat(flags);
        return email.save();
      })
      .then(() => {
        res.status(200).send({ message: 'Successfully added Flags' });
      })
      .catch(err => {
        res.status(400).send(err);
      });
  }
}

/**
 * @api {delete} /email/:id/flags Remove Email Flags
 * @apiDescription Removes the specified flags from this email.
 * @apiName RemoveEmailFlags
 * @apiGroup Email
 * @apiParam {String} id Email unique ID.
 * @apiParam {String[]} flags Email flags.
 * @apiSuccessExample Success-Response:
 * //TODO
 *     {
 *       "firstname": "John",
 *       "lastname": "Doe"
 *     }
 */
exports.delFlags = (req, res) => {
  const emailId = req.params.id;
  const flags = req.body.flags;
  const user = req.user;
  const emailConnector = req.user.createIMAPConnector();
  let email = null;

  if (req.user.isExchangeProvider()) {

    Email.findById(emailId)//.populate('box')
      .then(mail => {
        email = mail;
        return emailConnector.delFlags(mail, flags);
      })
      .then(() => {
        flags.forEach(f => {
          const index = email.flags.indexOf(f);
          if (index > -1)
            email.flags.splice(index, 1);
        });
        return email.save()
      })
      .then(() => {
        res.status(200).send({ message: 'Successfully deleted Flags' });
      })
      .catch((err) => {
        res.status(400).send(err);
      });

  } else {

    Email.findById(emailId).populate('boxes')
      .then(mail => {
        email = mail;
        return emailConnector.delFlags(mail.uid, flags, mail.boxes[0].name);
      })
      .then(() => {
        flags.forEach(f => {
          const index = email.flags.indexOf(f);
          if (index > -1)
            email.flags.splice(index, 1);
        });
        return email.save()
      })
      .then(() => {
        res.status(200).send({ message: 'Successfully deleted Flags' });
      })
      .catch((err) => {
        res.status(400).send(err);
      });

  }
}

/**
 * @api {get} /email/:id Get Email
 * @apiDescription Returns one single mail with all details
 * @apiName GetEmail
 * @apiGroup Email
 * @apiParam {String} id Email unique ID.
 * @apiSuccessExample Success-Response:
 * //TODO
 *     {
 *       "firstname": "John",
 *       "lastname": "Doe"
 *     }
 */
exports.getSingleMail = (req, res) => {
  const emailId = req.params.id;
  Email.findOne({ _id: emailId }).populate('attachments')
    .lean()
    .then((mail) => {
      console.log('retrieving email id...');
      console.log(mail);
      mail = replaceInlineAttachmentsSrc(mail, req.user);
      return (mail && (req.user.trello || req.user.sociocortex)) ? new Analyzer(mail, req.user).getEmailTasks() : mail;
    })
    .then(email => {
      let plainTextBody = HtmlDisassembler.getInstance().stripHtml(email.html);
      let annotations = []; // TODO call async java service
      let indexedAnnotations = HtmlDisassembler.getInstance().addAnnotationIndices(annotations, email.html);
      let annotationsRanges = HtmlDisassembler.getInstance().addAnnotationRanges(indexedAnnotations, email.html);
    })
    .then(email => {
      res.status(200).send(email);
    })
    .catch((err) => {
      res.status(400).send(err);
    });
}

// Inline attachments URL and tokens are changed in the front-end
// they have the form AttachmentURL/attachmentId?token=JWTToken
function replaceInlineAttachmentsSrc(email, user) {
  email.attachments.forEach((a) => {
    if (a.contentDispositionInline) {
      email.html = email.html.replace(`cid:${a.contentId}`, `ATTACHMENT_POINT/${a._id}?token=TOKEN_POINT`);
    }
  })

  return email;
}

/** Stores an email in the database and
 *  pushs upates via socket to the client */
// TODO
function storeEmail(mail) {
  return new Promise((resolve, reject) => {
    Email.updateAndGetOldAndUpdated(mail)
      .spread((emailOld, boxOld, emailUpdated, boxUpdated) => {
        // TODO new box numbers do not work properly
        console.log('inside storeEmail...');
        Socket.pushEmailUpdateToClient(emailOld, boxOld, emailUpdated, boxUpdated);
        resolve(emailUpdated);
      })
      .catch(err => {
        reject(err);
      });
  });
}

exports.storeEmail = storeEmail;

/**
 * @api {get} /email/search Search Emails
 * @apiDescription Search emails either for box or custom search
 * @apiName SearchEmail
 * @apiGroup Email
 * @apiParam {String} [boxId] Box unique ID.
 * @apiParam {String{'ASC','DESC'}} [sort='ASC'] Result sort order.
 * @apiParam {String} [search] Search string.
 * @apiParam {String} [lastEmailDate] Date of last email loaded.
 * @apiSuccessExample Success-Response:
 * //TODO
 *     {
 *       "firstname": "John",
 *       "lastname": "Doe"
 *     }
 */
exports.searchEmails = (req, res) => {

  const options = {
    boxId: req.query.boxId,
    sort: req.query.sort, // ASC or DESC
    search: req.query.search,
    lastEmailDate: new Date(req.query.lastEmailDate)
  };

  Email.search(req.user._id, options)
    .then(emails => {
      return Email.filterNonTrash(req.user, req.query.boxId, emails);
    })
    .then(emails => {
      res.status(200).send(emails);
    })
    .catch(err => {
      res.status(400).send(err);
    });
}

/** Creates autocomplete suggestions for email addresses */
exports.autocomplete = (req, res) => {
  Email.autocomplete(req.user._id)
    .then(suggestions => {
      res.status(200).send(suggestions);
    })
    .catch(err => {
      res.status(500).send(err);
    });
}

exports.appendEnron = (req, res) => {

  const emailConnector = req.user.createIMAPConnector();

  // find enron user e.g Allen
  User.findOne({ username: 'allen-p' })
    .then((user) => {
      console.log('this is the user');
      console.log(user);

      // get all emails for this user;
      Email.find({ user: user }).limit(3)
        .then((emails) => {

          // in which box to store these emails?

          // agregate boxes according to the folder names

          Box.findOne({ name: EWSConnector.staticBoxNames.inbox, user: req.user })
            .then(box => {

              // filter emails without ewsItemId (exchange)
              const emailsToAppend = emails.filter(email => !email.ewsItemId);
              //console.log(emailsToAppend);

              Promise.each(emails, (email) => {

                req.body.boxId = box._id;
                req.body.subject = email.subject;
                req.body.msgData = email.text;
                req.body.to = email.to;

                return emailConnector.append(req.body, box.ewsId)
                  .then(result => {
                    // Pair ewsItemId to the Enron email, already in DB
                    console.log('resultado...');
                    console.log(JSON.stringify(result));

                    email.box = box._id;
                    email.boxes = [box._id];
                    email.ewsItemId = result.Id;
                    email.ewsChangeKey = result.ChangeKey;
                  })
                  .then(() => {
                    return storeEmail(email);
                  });
              })
                .then(() => {
                  res.status(200).send({ msgData: 'ok' });
                })
                .catch((err) => {
                  console.log(err);
                  res.status(400).send(err);
                });

            });

        });

    });

}

<commit_msg>WIP: added missing return statement<commit_after>import Promise from 'bluebird';
import Email from '../models/email.model';
import Box from '../models/box.model';
import config from '../../config/env';
import User from '../models/user.model';
import Analyzer from '../core/engine/analyzer';
import Socket from '../routes/socket';
import HtmlDisassembler from '../core/analysis/HtmlDisassembler'
import GmailConnector from '../core/mail/GmailConnector';
import EWSConnector from '../core/mail/EWSConnector';

exports.sendEmail = (req, res) => {

  if (req.user.isExchangeProvider()) {

    const emailConnector = req.user.createIMAPConnector();
    emailConnector.sendMail(req.body)
      .then(result => {
        return Box.findOne({ name: EWSConnector.staticBoxNames.send, user: req.user });
      })
      .then(box => {
        return req.user.createIMAPConnector().fetchBoxes(storeEmail, [box]);
      })
      .then(() => {
        res.status(200).send({ message: 'Finished fetching' });
      })
      .catch((err) => {
        console.log(err);
        res.status(400).send(err);
      });

  } else {

    req.user.createSMTPConnector().sendMail(req.body)
      .then(result => {
        return Box.findOne({ name: GmailConnector.staticBoxNames.send, user: req.user });
      })
      .then(box => {
        return req.user.createIMAPConnector().fetchBoxes(storeEmail, [box]);
      })
      .then(() => {
        res.status(200).send({ message: 'Finished fetching' });
      })
      .catch((err) => {
        console.log(err);
        res.status(400).send(err);
      });

  }
}

exports.append = (req, res) => {
  const user = req.user;
  const boxId = req.body.boxId;
  const emailConnector = user.createIMAPConnector();

  if (req.user.isExchangeProvider()) {

    // Box.findOne({name: EWSConnector.staticBoxNames.draft, user: user})
    Box.findOne({ _id: boxId, user: user })
      .then(box => {
        return [box, emailConnector.append(req.body, box.ewsId)]
      })
      .spread((box, msgData) => {
        return [msgData, emailConnector.fetchBoxes(storeEmail, [box])]
      })
      .spread((msgData, result) => {
        res.status(200).send({ msgData: 'ok' });
      })
      .catch((err) => {
        console.log(err);
        res.status(400).send(err);
      });

  } else {

    // Box.findOne({name: GmailConnector.staticBoxNames.draft, user: user})
    Box.findOne({ _id: boxId, user: user })
      .then(box => {
        return [box, emailConnector.append(box.name, user.email, req.body.to, req.body.subject, req.body.msgData)]
      })
      .spread((box, msgData) => {
        return [msgData, emailConnector.fetchBoxes(storeEmail, [box])]
      })
      .spread((msgData, result) => {
        res.status(200).send({ msgData: msgData });
      })
      .catch((err) => {
        console.log(err);
        res.status(400).send(err);
      });

  }
}

exports.move = (req, res) => {
  const emailId = req.params.id;
  const newBoxId = req.body.newBoxId;
  const user = req.user;

  const emailConnector = user.createIMAPConnector();

  if (req.user.isExchangeProvider()) {

    Email.findOne({ _id: emailId }).populate('boxes')
      .then(email => {
        return [email, Box.findOne({ _id: newBoxId, user: user })]
      })
      .spread((email, destBox) => {
        const srcBox = email.boxes[0];
        return [srcBox, destBox, emailConnector.move(email, destBox.ewsId)]
      })
      .spread((srcBox, destBox, msgId) => {
        return emailConnector.fetchBoxes(storeEmail, [srcBox, destBox])
      })
      .then((messages) => {
        res.status(200).send({ messages: messages });
      })
      .catch(err => {
        console.log(err);
        res.status(400).send(err);
      });

  } else {

    Email.findOne({ _id: emailId }).populate('boxes')
      .then(email => {
        return [email, Box.findOne({ _id: newBoxId, user: user })]
      })
      // TODO boxId from parameter
      .spread((email, destBox) => {
        const srcBox = email.boxes[0];
        return [srcBox, destBox, emailConnector.move(email.uid, srcBox.name, destBox.name)]
      })
      .spread((srcBox, destBox, msgId) => {
        return emailConnector.fetchBoxes(storeEmail, [srcBox, destBox])
      })
      .then((messages) => {
        res.status(200).send({ messages: messages });
      })
      .catch(err => {
        console.log(err);
        res.status(400).send(err);
      });
  }
}

exports.moveToTrash = (req, res) => {
  if (req.user.isExchangeProvider()) {
    Box.findOne({ name: EWSConnector.staticBoxNames.deleted, user: req.user })
      .then(box => {
        req.body.newBoxId = box._id;
        exports.move(req, res);
      });
  } else if (req.user.isGMailProvider()) {
    Box.findOne({ name: GmailConnector.staticBoxNames.deleted, user: req.user })
      .then(box => {
        req.body.newBoxId = box._id;
        exports.move(req, res);
      });
  }
}

/**
 * @api {post} /email/:id/flags Add Email Flags
 * @apiDescription Add the specified flags to this email.
 * @apiName AddEmailFlags
 * @apiGroup Email
 * @apiParam {String} id Email unique ID.
 * @apiParam {String[]} flags Email flags.
 * @apiSuccessExample Success-Response:
 * //TODO
 *     {
 *       "firstname": "John",
 *       "lastname": "Doe"
 *     }
 */
exports.addFlags = (req, res) => {
  const emailId = req.params.id;
  const flags = req.body.flags;
  const user = req.user;
  const emailConnector = req.user.createIMAPConnector();
  let email = null;

  if (req.user.isExchangeProvider()) {

    Email.findById(emailId)//.populate('box')
      .then(mail => {
        email = mail;
        return emailConnector.addFlags(mail, flags);
      })
      .then(() => {
        email.flags = email.flags.concat(flags);
        return email.save();
      })
      .then(() => {
        res.status(200).send({ message: 'Successfully added Flags' });
      })
      .catch(err => {
        res.status(400).send(err);
      });

  } else {

    Email.findById(emailId).populate('boxes')
      .then(mail => {
        email = mail;
        return emailConnector.addFlags(mail.uid, flags, email.boxes[0].name);
      })
      .then(() => {
        email.flags = email.flags.concat(flags);
        return email.save();
      })
      .then(() => {
        res.status(200).send({ message: 'Successfully added Flags' });
      })
      .catch(err => {
        res.status(400).send(err);
      });
  }
}

/**
 * @api {delete} /email/:id/flags Remove Email Flags
 * @apiDescription Removes the specified flags from this email.
 * @apiName RemoveEmailFlags
 * @apiGroup Email
 * @apiParam {String} id Email unique ID.
 * @apiParam {String[]} flags Email flags.
 * @apiSuccessExample Success-Response:
 * //TODO
 *     {
 *       "firstname": "John",
 *       "lastname": "Doe"
 *     }
 */
exports.delFlags = (req, res) => {
  const emailId = req.params.id;
  const flags = req.body.flags;
  const user = req.user;
  const emailConnector = req.user.createIMAPConnector();
  let email = null;

  if (req.user.isExchangeProvider()) {

    Email.findById(emailId)//.populate('box')
      .then(mail => {
        email = mail;
        return emailConnector.delFlags(mail, flags);
      })
      .then(() => {
        flags.forEach(f => {
          const index = email.flags.indexOf(f);
          if (index > -1)
            email.flags.splice(index, 1);
        });
        return email.save()
      })
      .then(() => {
        res.status(200).send({ message: 'Successfully deleted Flags' });
      })
      .catch((err) => {
        res.status(400).send(err);
      });

  } else {

    Email.findById(emailId).populate('boxes')
      .then(mail => {
        email = mail;
        return emailConnector.delFlags(mail.uid, flags, mail.boxes[0].name);
      })
      .then(() => {
        flags.forEach(f => {
          const index = email.flags.indexOf(f);
          if (index > -1)
            email.flags.splice(index, 1);
        });
        return email.save()
      })
      .then(() => {
        res.status(200).send({ message: 'Successfully deleted Flags' });
      })
      .catch((err) => {
        res.status(400).send(err);
      });

  }
}

/**
 * @api {get} /email/:id Get Email
 * @apiDescription Returns one single mail with all details
 * @apiName GetEmail
 * @apiGroup Email
 * @apiParam {String} id Email unique ID.
 * @apiSuccessExample Success-Response:
 * //TODO
 *     {
 *       "firstname": "John",
 *       "lastname": "Doe"
 *     }
 */
exports.getSingleMail = (req, res) => {
  const emailId = req.params.id;
  Email.findOne({ _id: emailId }).populate('attachments')
    .lean()
    .then((mail) => {
      console.log('retrieving email id...');
      console.log(mail);
      mail = replaceInlineAttachmentsSrc(mail, req.user);
      return (mail && (req.user.trello || req.user.sociocortex)) ? new Analyzer(mail, req.user).getEmailTasks() : mail;
    })
    .then(email => {
      let plainTextBody = HtmlDisassembler.getInstance().stripHtml(email.html);
      let annotations = []; // TODO call async java service
      let indexedAnnotations = HtmlDisassembler.getInstance().addAnnotationIndices(annotations, email.html);
      email['annotations'] = HtmlDisassembler.getInstance().addAnnotationRanges(indexedAnnotations, email.html);
      return email;
    })
    .then(email => {
      res.status(200).send(email);
    })
    .catch((err) => {
      res.status(400).send(err);
    });
}

// Inline attachments URL and tokens are changed in the front-end
// they have the form AttachmentURL/attachmentId?token=JWTToken
function replaceInlineAttachmentsSrc(email, user) {
  email.attachments.forEach((a) => {
    if (a.contentDispositionInline) {
      email.html = email.html.replace(`cid:${a.contentId}`, `ATTACHMENT_POINT/${a._id}?token=TOKEN_POINT`);
    }
  })

  return email;
}

/** Stores an email in the database and
 *  pushs upates via socket to the client */
// TODO
function storeEmail(mail) {
  return new Promise((resolve, reject) => {
    Email.updateAndGetOldAndUpdated(mail)
      .spread((emailOld, boxOld, emailUpdated, boxUpdated) => {
        // TODO new box numbers do not work properly
        console.log('inside storeEmail...');
        Socket.pushEmailUpdateToClient(emailOld, boxOld, emailUpdated, boxUpdated);
        resolve(emailUpdated);
      })
      .catch(err => {
        reject(err);
      });
  });
}

exports.storeEmail = storeEmail;

/**
 * @api {get} /email/search Search Emails
 * @apiDescription Search emails either for box or custom search
 * @apiName SearchEmail
 * @apiGroup Email
 * @apiParam {String} [boxId] Box unique ID.
 * @apiParam {String{'ASC','DESC'}} [sort='ASC'] Result sort order.
 * @apiParam {String} [search] Search string.
 * @apiParam {String} [lastEmailDate] Date of last email loaded.
 * @apiSuccessExample Success-Response:
 * //TODO
 *     {
 *       "firstname": "John",
 *       "lastname": "Doe"
 *     }
 */
exports.searchEmails = (req, res) => {

  const options = {
    boxId: req.query.boxId,
    sort: req.query.sort, // ASC or DESC
    search: req.query.search,
    lastEmailDate: new Date(req.query.lastEmailDate)
  };

  Email.search(req.user._id, options)
    .then(emails => {
      return Email.filterNonTrash(req.user, req.query.boxId, emails);
    })
    .then(emails => {
      res.status(200).send(emails);
    })
    .catch(err => {
      res.status(400).send(err);
    });
}

/** Creates autocomplete suggestions for email addresses */
exports.autocomplete = (req, res) => {
  Email.autocomplete(req.user._id)
    .then(suggestions => {
      res.status(200).send(suggestions);
    })
    .catch(err => {
      res.status(500).send(err);
    });
}

exports.appendEnron = (req, res) => {

  const emailConnector = req.user.createIMAPConnector();

  // find enron user e.g Allen
  User.findOne({ username: 'allen-p' })
    .then((user) => {
      console.log('this is the user');
      console.log(user);

      // get all emails for this user;
      Email.find({ user: user }).limit(3)
        .then((emails) => {

          // in which box to store these emails?

          // agregate boxes according to the folder names

          Box.findOne({ name: EWSConnector.staticBoxNames.inbox, user: req.user })
            .then(box => {

              // filter emails without ewsItemId (exchange)
              const emailsToAppend = emails.filter(email => !email.ewsItemId);
              //console.log(emailsToAppend);

              Promise.each(emails, (email) => {

                req.body.boxId = box._id;
                req.body.subject = email.subject;
                req.body.msgData = email.text;
                req.body.to = email.to;

                return emailConnector.append(req.body, box.ewsId)
                  .then(result => {
                    // Pair ewsItemId to the Enron email, already in DB
                    console.log('resultado...');
                    console.log(JSON.stringify(result));

                    email.box = box._id;
                    email.boxes = [box._id];
                    email.ewsItemId = result.Id;
                    email.ewsChangeKey = result.ChangeKey;
                  })
                  .then(() => {
                    return storeEmail(email);
                  });
              })
                .then(() => {
                  res.status(200).send({ msgData: 'ok' });
                })
                .catch((err) => {
                  console.log(err);
                  res.status(400).send(err);
                });

            });

        });

    });

}

<|endoftext|>


<commit_before>var fs = require('fs');

function reloadCustomAvatars() {
	var path = require('path');
	var newCustomAvatars = {};
	fs.readdirSync('./config/avatars').forEach(function (file) {
		var ext = path.extname(file);
		if (ext !== '.png' && ext !== '.gif')
			return;

		var user = toId(path.basename(file, ext));
		newCustomAvatars[user] = file;
		delete Config.customavatars[user];
	});

	// Make sure the manually entered avatars exist
	for (var a in Config.customavatars)
		if (typeof Config.customavatars[a] === 'number')
			newCustomAvatars[a] = Config.customavatars[a];
		else
			fs.exists('./config/avatars/' + Config.customavatars[a], function (user, file, isExists) {
				if (isExists)
					Config.customavatars[user] = file;
			}.bind(null, a, Config.customavatars[a]));

	Config.customavatars = newCustomAvatars;
}
reloadCustomAvatars();
global.Gold.reloadCustomAvatars = reloadCustomAvatars;

if (Config.watchConfig) {
	fs.watchFile('./config/config.js', function (curr, prev) {
		if (curr.mtime <= prev.mtime) return;
		reloadCustomAvatars();
	});
}

const script = function () {
/*
	FILENAME=`mktemp`
	function cleanup {
		rm -f $FILENAME
	}
	trap cleanup EXIT

	set -xe

	timeout 10 wget "$1" -nv -O $FILENAME

	FRAMES=`identify $FILENAME | wc -l`
	if [ $FRAMES -gt 1 ]; then
		EXT=".gif"
	else
		EXT=".png"
	fi

	timeout 10 convert $FILENAME -layers TrimBounds -coalesce -adaptive-resize 80x80\> -background transparent -gravity center -extent 80x80 "$2$EXT"
*/
}.toString().match(/[^]*\/\*([^]*)\*\//)[1];

var pendingAdds = {};

exports.commands = {
	sca: 'customavatar',
	customavatars: 'customavatar',
	customavatar: function (target, room, user) {
		var parts = target.split(',');
		var cmd = parts[0].trim().toLowerCase();

		if (cmd in {'':1, show:1, view:1, display:1}) {
			var message = "";
			for (var a in Config.customavatars)
				message += "" + Tools.escapeHTML(a) + ": " + Tools.escapeHTML(Config.customavatars[a]) + "
";
			return this.sendReplyBox(message);
		}

		if (!this.can('pban') && !Gold.hasBadge(toId(user.name),'vip')) return false;

		switch (cmd) {
			case 'set':
				var userid = toId(parts[1]);
				var targetUser = Users.getExact(userid);
				var avatar = parts.slice(2).join(',').trim();
				if (!this.can('pban') && Gold.hasBadge(toId(user.name),'vip') && userid !== user.userid) return false;

				if (!userid) return this.sendReply("You didn't specify a user.");
				if (Config.customavatars[userid]) return this.sendReply(userid + " already has a custom avatar.");

				var hash = require('crypto').createHash('sha512').update(userid + '\u0000' + avatar).digest('hex').slice(0, 8);
				pendingAdds[hash] = {userid: userid, avatar: avatar};
				parts[1] = hash;

				if (!targetUser) {
					this.sendReply("Warning: " + userid + " is not online.");
					this.sendReply("If you want to continue, use: /customavatar forceset, " + hash);
					return;
				}
				break;
			case 'reload':
				if (!this.can('hotpatch')) return this.errorReply("Access denied.");
				reloadCustomAvatars();
				for (var u in Users.users) {
					if (Config.customavatars[u]) Users.users[u].avatar = Config.customavatars[u];
				}
				this.sendReply("Avatars have been reloaded.");
				break;
				/* falls through */
			case 'forceset':
				if (user.avatarCooldown && !this.can('pban')) {
					var milliseconds = (Date.now() - user.avatarCooldown);
					var seconds = ((milliseconds / 1000) % 60);
					var minutes = ((seconds / 60) % 60);
					var remainingTime = Math.round(seconds - (5 * 60));
					if (((Date.now() - user.avatarCooldown) <= 5 * 60 * 1000)) return this.sendReply("You must wait " + (remainingTime - remainingTime * 2) + " seconds before setting another avatar.");
				}
				user.avatarCooldown = Date.now();

				var hash = parts[1].trim();
				if (!pendingAdds[hash]) return this.sendReply("Invalid hash.");

				var userid = pendingAdds[hash].userid;
				var avatar = pendingAdds[hash].avatar;
				delete pendingAdds[hash];

				require('child_process').execFile('bash', ['-c', script, '-', avatar, './config/avatars/' + userid], function (e, out, err) {
					if (e) {
						this.sendReply(userid + "'s custom avatar failed to be set. Script output:");
						(out + err).split('\n').forEach(this.sendReply.bind(this));
						return;
					}

					reloadCustomAvatars();

					var targetUser = Users.getExact(userid);
					if (targetUser) targetUser.avatar = Config.customavatars[userid];

					this.sendReply(userid + "'s custom avatar has been set.");
					//Users.messageSeniorStaff(userid+' has received a custom avatar from '+user.name);
					Rooms.rooms.staff.add(userid+' has received a custom avatar from '+user.name);
					room.update();
				}.bind(this));
				break;

			case 'remove':
			case 'delete':
				var userid = toId(parts[1]);
				if (!this.can('pban') && Gold.hasBadge(toId(user.name),'vip') && userid !== user.userid) return false;
				if (!Config.customavatars[userid]) return this.sendReply(userid + " does not have a custom avatar.");

				if (Config.customavatars[userid].toString().split('.').slice(0, -1).join('.') !== userid)
					return this.sendReply(userid + "'s custom avatar (" + Config.customavatars[userid] + ") cannot be removed with this script.");

				var targetUser = Users.getExact(userid);
				if (targetUser) targetUser.avatar = 1;

				fs.unlink('./config/avatars/' + Config.customavatars[userid], function (e) {
					if (e) return this.sendReply(userid + "'s custom avatar (" + Config.customavatars[userid] + ") could not be removed: " + e.toString());

					delete Config.customavatars[userid];
					this.sendReply(userid + "'s custom avatar removed successfully");
				}.bind(this));
				break;

			default:
				return this.sendReply("Invalid command. Valid commands are `/customavatar set, user, avatar` and `/customavatar delete, user`.");
		}
	}
};
<commit_msg>Fix a crash in sca<commit_after>var fs = require('fs');

function reloadCustomAvatars() {
	var path = require('path');
	var newCustomAvatars = {};
	fs.readdirSync('./config/avatars').forEach(function (file) {
		var ext = path.extname(file);
		if (ext !== '.png' && ext !== '.gif')
			return;

		var user = toId(path.basename(file, ext));
		newCustomAvatars[user] = file;
		delete Config.customavatars[user];
	});

	// Make sure the manually entered avatars exist
	for (var a in Config.customavatars)
		if (typeof Config.customavatars[a] === 'number')
			newCustomAvatars[a] = Config.customavatars[a];
		else
			fs.exists('./config/avatars/' + Config.customavatars[a], function (user, file, isExists) {
				if (isExists)
					Config.customavatars[user] = file;
			}.bind(null, a, Config.customavatars[a]));

	Config.customavatars = newCustomAvatars;
}
reloadCustomAvatars();

if (Config.watchConfig) {
	fs.watchFile('./config/config.js', function (curr, prev) {
		if (curr.mtime <= prev.mtime) return;
		reloadCustomAvatars();
	});
}

const script = function () {
/*
	FILENAME=`mktemp`
	function cleanup {
		rm -f $FILENAME
	}
	trap cleanup EXIT

	set -xe

	timeout 10 wget "$1" -nv -O $FILENAME

	FRAMES=`identify $FILENAME | wc -l`
	if [ $FRAMES -gt 1 ]; then
		EXT=".gif"
	else
		EXT=".png"
	fi

	timeout 10 convert $FILENAME -layers TrimBounds -coalesce -adaptive-resize 80x80\> -background transparent -gravity center -extent 80x80 "$2$EXT"
*/
}.toString().match(/[^]*\/\*([^]*)\*\//)[1];

var pendingAdds = {};

exports.commands = {
	sca: 'customavatar',
	customavatars: 'customavatar',
	customavatar: function (target, room, user) {
		var parts = target.split(',');
		var cmd = parts[0].trim().toLowerCase();

		if (cmd in {'':1, show:1, view:1, display:1}) {
			var message = "";
			for (var a in Config.customavatars)
				message += "" + Tools.escapeHTML(a) + ": " + Tools.escapeHTML(Config.customavatars[a]) + "
";
			return this.sendReplyBox(message);
		}

		if (!this.can('pban') && !Gold.hasBadge(toId(user.name),'vip')) return false;

		switch (cmd) {
			case 'set':
				var userid = toId(parts[1]);
				var targetUser = Users.getExact(userid);
				var avatar = parts.slice(2).join(',').trim();
				if (!this.can('pban') && Gold.hasBadge(toId(user.name),'vip') && userid !== user.userid) return false;

				if (!userid) return this.sendReply("You didn't specify a user.");
				if (Config.customavatars[userid]) return this.sendReply(userid + " already has a custom avatar.");

				var hash = require('crypto').createHash('sha512').update(userid + '\u0000' + avatar).digest('hex').slice(0, 8);
				pendingAdds[hash] = {userid: userid, avatar: avatar};
				parts[1] = hash;

				if (!targetUser) {
					this.sendReply("Warning: " + userid + " is not online.");
					this.sendReply("If you want to continue, use: /customavatar forceset, " + hash);
					return;
				}
				break;
			case 'reload':
				if (!this.can('hotpatch')) return this.errorReply("Access denied.");
				reloadCustomAvatars();
				for (var u in Users.users) {
					if (Config.customavatars[u]) Users.users[u].avatar = Config.customavatars[u];
				}
				this.sendReply("Avatars have been reloaded.");
				break;
				/* falls through */
			case 'forceset':
				if (user.avatarCooldown && !this.can('pban')) {
					var milliseconds = (Date.now() - user.avatarCooldown);
					var seconds = ((milliseconds / 1000) % 60);
					var minutes = ((seconds / 60) % 60);
					var remainingTime = Math.round(seconds - (5 * 60));
					if (((Date.now() - user.avatarCooldown) <= 5 * 60 * 1000)) return this.sendReply("You must wait " + (remainingTime - remainingTime * 2) + " seconds before setting another avatar.");
				}
				user.avatarCooldown = Date.now();

				var hash = parts[1].trim();
				if (!pendingAdds[hash]) return this.sendReply("Invalid hash.");

				var userid = pendingAdds[hash].userid;
				var avatar = pendingAdds[hash].avatar;
				delete pendingAdds[hash];

				require('child_process').execFile('bash', ['-c', script, '-', avatar, './config/avatars/' + userid], function (e, out, err) {
					if (e) {
						this.sendReply(userid + "'s custom avatar failed to be set. Script output:");
						(out + err).split('\n').forEach(this.sendReply.bind(this));
						return;
					}

					reloadCustomAvatars();

					var targetUser = Users.getExact(userid);
					if (targetUser) targetUser.avatar = Config.customavatars[userid];

					this.sendReply(userid + "'s custom avatar has been set.");
					//Users.messageSeniorStaff(userid+' has received a custom avatar from '+user.name);
					Rooms.rooms.staff.add(userid+' has received a custom avatar from '+user.name);
					room.update();
				}.bind(this));
				break;

			case 'remove':
			case 'delete':
				var userid = toId(parts[1]);
				if (!this.can('pban') && Gold.hasBadge(toId(user.name),'vip') && userid !== user.userid) return false;
				if (!Config.customavatars[userid]) return this.sendReply(userid + " does not have a custom avatar.");

				if (Config.customavatars[userid].toString().split('.').slice(0, -1).join('.') !== userid)
					return this.sendReply(userid + "'s custom avatar (" + Config.customavatars[userid] + ") cannot be removed with this script.");

				var targetUser = Users.getExact(userid);
				if (targetUser) targetUser.avatar = 1;

				fs.unlink('./config/avatars/' + Config.customavatars[userid], function (e) {
					if (e) return this.sendReply(userid + "'s custom avatar (" + Config.customavatars[userid] + ") could not be removed: " + e.toString());

					delete Config.customavatars[userid];
					this.sendReply(userid + "'s custom avatar removed successfully");
				}.bind(this));
				break;

			default:
				return this.sendReply("Invalid command. Valid commands are `/customavatar set, user, avatar` and `/customavatar delete, user`.");
		}
	}
};
<|endoftext|>


<commit_before>/**
* @license Apache-2.0
*
* Copyright (c) 2018 The Stdlib Authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
*    http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

'use strict';

// MODULES //

var bench = require( '@stdlib/bench' );
var randu = require( '@stdlib/random/base/randu' );
var isnan = require( '@stdlib/math/base/assert/is-nan' );
var hypot = require( '@stdlib/math/base/special/hypot' );
var pkg = require( './../package.json' ).name;

// MAIN //

bench( pkg, function benchmark( b ) {
	var x;
	var y;
	var z;
	var i;

	b.tic();
	for ( i = 0; i < b.iterations; i++ ) {
		x = ( randu()*100.0 ) - 50.0;
		y = ( randu()*100.0 ) - 50.0;
		z = hypot( x, y );
		if ( isnan( z ) ) {
			b.fail( 'should not return NaN' );
		}
	}
	b.toc();
	if ( isnan( z ) ) {
		b.fail( 'should not return NaN' );
	}
	b.pass( 'benchmark finished' );
	b.end();
});
<commit_msg>Revert accidental overwrite of benchmark file<commit_after>/**
* @license Apache-2.0
*
* Copyright (c) 2018 The Stdlib Authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
*    http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

'use strict';

// MODULES //

var bench = require( '@stdlib/bench' );
var isnan = require( '@stdlib/math/base/assert/is-nan' );
var pkg = require( './../package.json' ).name;
var sizeOf = require( './../lib' );

// MAIN //

bench( pkg, function benchmark( b ) {
	var values;
	var s;
	var i;

	values = [
		'float64',
		'float32',
		'float16',
		'int32',
		'uint32',
		'int16',
		'uint16',
		'int8',
		'uint8',
		'uint8c',
		'complex128',
		'complex64'
	];
	b.tic();
	for ( i = 0; i < b.iterations; i++ ) {
		s = sizeOf( values[ i%values.length ] );
		if ( isnan( s ) ) {
			b.fail( 'should not return NaN' );
		}
	}
	b.toc();
	if ( isnan( s ) ) {
		b.fail( 'should not return NaN' );
	}
	b.pass( 'benchmark finished' );
	b.end();
});
<|endoftext|>


<commit_before>/**
 * Intro.js v0.1.0
 * https://github.com/usablica/intro.js
 * MIT licensed
 *
 * Copyright (C) 2013 usabli.ca - A weekend project by Afshin Mehrabani (@afshinmeh)
 */

(function () {

  //Default config/variables
  var VERSION = "0.1.0";

  /**
   * IntroJs main class
   *
   * @class IntroJs
   */
  function IntroJs(obj) {
    this._targetElement = obj;
  }

  /**
   * Initiate a new introduction/guide from an element in the page
   *
   * @api private
   * @method _introForElement
   * @param {Object} targetElm
   * @returns {Boolean} Success or not?
   */
  function _introForElement(targetElm) {
    var allIntroSteps = targetElm.querySelectorAll("*[data-intro]"),
        introItems = [],
        self = this;

    //if there's no element to intro
    if(allIntroSteps.length < 1) {
      return false;
    }

    for (var i = 0, elmsLength = allIntroSteps.length; i < elmsLength; i++) {
      var currentElement = allIntroSteps[i];
      introItems.push({
        element: currentElement,
        intro: currentElement.getAttribute("data-intro"),
        step: parseInt(currentElement.getAttribute("data-step"), 10),
        position: currentElement.getAttribute("data-position") || 'bottom'
      });
    }

    //Ok, sort all items with given steps
    introItems.sort(function (a, b) {
      return a.step - b.step;
    });

    //set it to the introJs object
    self._introItems = introItems;

    //add overlay layer to the page
    if(_addOverlayLayer.call(self, targetElm)) {
      //then, start the show
      _nextStep.call(self);

      var skipButton = targetElm.querySelector(".introjs-skipbutton"),
          nextStepButton = targetElm.querySelector(".introjs-nextbutton");

      window.onkeydown = function(e) {
        if (e.keyCode == 27) {
          //escape key pressed, exit the intro
          _exitIntro.call(self, targetElm);
        } else if(e.keyCode == 37) {
          //left arrow
          _previousStep.call(self);
        } else if (e.keyCode == 39 || e.keyCode == 13) {
          //right arrow or enter
          _nextStep.call(self);
        }
      };
    }
    return false;
  }

  /**
   * Go to next step on intro
   *
   * @api private
   * @method _nextStep
   */
  function _nextStep() {
    if (typeof(this._currentStep) === 'undefined') {
      this._currentStep = 0;
    } else {
      ++this._currentStep;
    }

    if((this._introItems.length) <= this._currentStep) {
      //end of the intro
      //check if any callback is defined
      if (this._introCompleteCallback != undefined) {
        this._introCompleteCallback.call(this);
      }
      _exitIntro.call(this, this._targetElement);
      return;
    }

    _showElement.call(this, this._introItems[this._currentStep].element);
  }

  /**
   * Go to previous step on intro
   *
   * @api private
   * @method _nextStep
   */
  function _previousStep() {
    if (this._currentStep == 0) {
      return false;
    }

    _showElement.call(this, this._introItems[--this._currentStep].element);
  }

  /**
   * Exit from intro
   *
   * @api private
   * @method _exitIntro
   * @param {Object} targetElement
   */
  function _exitIntro(targetElement) {
    //remove overlay layer from the page
    var overlayLayer = targetElement.querySelector(".introjs-overlay");
    //for fade-out animation
    overlayLayer.style.opacity = 0;
    setTimeout(function () {
      if (overlayLayer.parentNode) {
        overlayLayer.parentNode.removeChild(overlayLayer);
      }
    }, 500);
    //remove all helper layers
    var helperLayer = targetElement.querySelector(".introjs-helperLayer");
    if (helperLayer) {
      helperLayer.parentNode.removeChild(helperLayer);
    }
    //remove `introjs-showElement` class from the element
    var showElement = document.querySelector(".introjs-showElement");
    if (showElement) {
      showElement.className = showElement.className.replace(/introjs-[a-zA-Z]+/g, '').trim();
    }
    //clean listeners
    targetElement.onkeydown = null;
    //set the step to zero
    this._currentStep = undefined;
    //check if any callback is defined
    if (this._introExitCallback != undefined) {
      this._introExitCallback.call(this);
    }
  }

  /**
   * Render tooltip box in the page
   *
   * @api private
   * @method _placeTooltip
   * @param {Object} targetElement
   * @param {Object} tooltipLayer
   * @param {Object} arrowLayer
   */
  function _placeTooltip(targetElement, tooltipLayer, arrowLayer) {
    var tooltipLayerPosition = _getOffset(tooltipLayer);
    //reset the old style
    tooltipLayer.style.top = null;
    tooltipLayer.style.right = null;
    tooltipLayer.style.bottom = null;
    tooltipLayer.style.left = null;
    switch (targetElement.getAttribute('data-position')) {
      case 'top':
        tooltipLayer.style.left = "15px";
        tooltipLayer.style.top = "-" + (tooltipLayerPosition.height + 10) + "px";
        arrowLayer.className = 'introjs-arrow bottom';
        break;
      case 'right':
        console.log(tooltipLayerPosition);
        tooltipLayer.style.right = "-" + (tooltipLayerPosition.width + 10) + "px";
        arrowLayer.className = 'introjs-arrow left';
        break;
      case 'left':
        tooltipLayer.style.top = "15px";
        tooltipLayer.style.left = "-" + (tooltipLayerPosition.width + 10) + "px";
        arrowLayer.className = 'introjs-arrow right';
        break;
      case 'bottom':
      default:
        tooltipLayer.style.bottom = "-" + (tooltipLayerPosition.height + 10) + "px";
        arrowLayer.className = 'introjs-arrow top';
        break;
    }
  }

  /**
   * Show an element on the page
   *
   * @api private
   * @method _showElement
   * @param {Object} targetElement
   */
  function _showElement(targetElement) {

    var self = this,
        oldHelperLayer = document.querySelector(".introjs-helperLayer"),
        elementPosition = _getOffset(targetElement);

    if(oldHelperLayer != null) {
      var oldHelperNumberLayer = oldHelperLayer.querySelector(".introjs-helperNumberLayer"),
          oldtooltipLayer = oldHelperLayer.querySelector(".introjs-tooltiptext"),
          oldArrowLayer = oldHelperLayer.querySelector(".introjs-arrow"),
          oldtooltipContainer = oldHelperLayer.querySelector(".introjs-tooltip")

      //set new position to helper layer
      oldHelperLayer.setAttribute("style", "width: " + (elementPosition.width + 10)  + "px; " +
                                           "height:" + (elementPosition.height + 10) + "px; " +
                                           "top:"    + (elementPosition.top - 5)     + "px;" +
                                           "left: "  + (elementPosition.left - 5)    + "px;");
      //set current step to the label
      oldHelperNumberLayer.innerHTML = targetElement.getAttribute("data-step");
      //set current tooltip text
      oldtooltipLayer.innerHTML = targetElement.getAttribute("data-intro");
      var oldShowElement = document.querySelector(".introjs-showElement");
      oldShowElement.className = oldShowElement.className.replace(/introjs-[a-zA-Z]+/g, '').trim();
      _placeTooltip(targetElement, oldtooltipContainer, oldArrowLayer);
    } else {
      var helperLayer = document.createElement("div"),
          helperNumberLayer = document.createElement("span"),
          arrowLayer = document.createElement("div"),
          tooltipLayer = document.createElement("div");

      helperLayer.className = "introjs-helperLayer";
      helperLayer.setAttribute("style", "width: " + (elementPosition.width + 10)  + "px; " +
                                        "height:" + (elementPosition.height + 10) + "px; " +
                                        "top:"    + (elementPosition.top - 5)     + "px;" +
                                        "left: "  + (elementPosition.left - 5)    + "px;");

      //add helper layer to target element
      this._targetElement.appendChild(helperLayer);

      helperNumberLayer.className = "introjs-helperNumberLayer";
      arrowLayer.className = 'introjs-arrow';
      tooltipLayer.className = "introjs-tooltip";

      helperNumberLayer.innerHTML = targetElement.getAttribute("data-step");
      tooltipLayer.innerHTML = "
" + targetElement.getAttribute("data-intro") + "

";
      helperLayer.appendChild(helperNumberLayer);
      tooltipLayer.appendChild(arrowLayer);
      helperLayer.appendChild(tooltipLayer);

      var skipTooltipButton = document.createElement("a");
      skipTooltipButton.className = "introjs-skipbutton";
      skipTooltipButton.href = "javascript:void(0);";
      skipTooltipButton.innerHTML = "Skip";

      var nextTooltipButton = document.createElement("a");

      nextTooltipButton.onclick = function() {
        _nextStep.call(self);
      };

      nextTooltipButton.className = "introjs-nextbutton";
      nextTooltipButton.href = "javascript:void(0);";
      nextTooltipButton.innerHTML = "Next →";

      skipTooltipButton.onclick = function() {
        _exitIntro.call(self, self._targetElement);
      };

      var tooltipButtonsLayer = tooltipLayer.querySelector('.introjs-tooltipbuttons');
      tooltipButtonsLayer.appendChild(skipTooltipButton);
      tooltipButtonsLayer.appendChild(nextTooltipButton);

      //set proper position
      _placeTooltip(targetElement, tooltipLayer, arrowLayer);
    }

    //add target element position style
    targetElement.className += " introjs-showElement";

    //Thanks to JavaScript Kit: http://www.javascriptkit.com/dhtmltutors/dhtmlcascade4.shtml
    var currentElementPosition = "";
    if (targetElement.currentStyle) { //IE
      currentElementPosition = targetElement.currentStyle["position"];
    } else if (document.defaultView && document.defaultView.getComputedStyle) { //Firefox
      currentElementPosition = document.defaultView.getComputedStyle(targetElement, null).getPropertyValue("position");
    }

    //I don't know is this necessary or not, but I clear the position for better comparing
    currentElementPosition = currentElementPosition.toLowerCase();
    if (currentElementPosition != "absolute" && currentElementPosition != "relative") {
      //change to new intro item
      targetElement.className += " introjs-relativePosition";
    }

    //scroll the page to the element position
    if (typeof(targetElement.scrollIntoViewIfNeeded) === "function") {
      //awesome method guys: https://bugzilla.mozilla.org/show_bug.cgi?id=403510
      //but I think this method has some problems with IE < 7.0, I should find a proper failover way
      targetElement.scrollIntoViewIfNeeded();
    }
  }

  /**
   * Add overlay layer to the page
   *
   * @api private
   * @method _addOverlayLayer
   * @param {Object} targetElm
   */
  function _addOverlayLayer(targetElm) {
    var overlayLayer = document.createElement("div"),
        styleText = "",
        self = this;

    //set css class name
    overlayLayer.className = "introjs-overlay";

    //check if the target element is body, we should calculate the size of overlay layer in a better way
    if (targetElm.tagName.toLowerCase() == "body") {
      styleText += "top: 0;bottom: 0; left: 0;right: 0;position: fixed;";
      overlayLayer.setAttribute("style", styleText);
    } else {
      //set overlay layer position
      var elementPosition = _getOffset(targetElm);
      if(elementPosition) {
        styleText += "width: " + elementPosition.width + "px; height:" + elementPosition.height + "px; top:" + elementPosition.top + "px;left: " + elementPosition.left + "px;";
        overlayLayer.setAttribute("style", styleText);
      }
    }

    targetElm.appendChild(overlayLayer);

    overlayLayer.onclick = function() {
      _exitIntro.call(self, targetElm);
    };

    setTimeout(function() {
      styleText += "opacity: .5;";
      overlayLayer.setAttribute("style", styleText);
    }, 10);
    return true;
  }

  /**
   * Get an element position on the page
   * Thanks to `meouw`: http://stackoverflow.com/a/442474/375966
   *
   * @api private
   * @method _getOffset
   * @param {Object} element
   * @returns Element's position info
   */
  function _getOffset(element) {
    var elementPosition = {};

    //set width
    elementPosition.width = element.offsetWidth;

    //set height
    elementPosition.height = element.offsetHeight;

    //calculate element top and left
    var _x = 0;
    var _y = 0;
    while(element && !isNaN(element.offsetLeft) && !isNaN(element.offsetTop)) {
      _x += element.offsetLeft;
      _y += element.offsetTop;
      element = element.offsetParent;
    }
    //set top
    elementPosition.top = _y;
    //set left
    elementPosition.left = _x;

    return elementPosition;
  }

  var introJs = function (targetElm) {
    if (typeof (targetElm) === "object") {
      //Ok, create a new instance
      return new IntroJs(targetElm);

    } else if (typeof (targetElm) === "string") {
      //select the target element with query selector
      var targetElement = document.querySelector(targetElm);

      if (targetElement) {
        return new IntroJs(targetElement);
      } else {
        throw new Error("There's no element with given selector.");
      }
    } else {
      return new IntroJs(document.body);
    }
  };

  /**
   * Current IntroJs version
   *
   * @property version
   * @type String
   */
  introJs.version = VERSION;

  //Prototype
  introJs.fn = IntroJs.prototype = {
    clone: function () {
      return new IntroJs(this);
    },
    start: function () {
      _introForElement.call(this, this._targetElement);
      return this;
    },
    oncomplete: function(providedCallback) {
      if (typeof (providedCallback) === "function") {
        this._introCompleteCallback = providedCallback;
      } else {
        throw new Error("Provided callback for oncomplete was not a function.");
      }
      return this;
    },
    onexit: function(providedCallback) {
      if (typeof (providedCallback) === "function") {
        this._introExitCallback = providedCallback;
      } else {
        throw new Error("Provided callback for onexit was not a function.");
      }
      return this;
    }
  };

  window['introJs'] = introJs;
})();
<commit_msg>Adds new scrolling functionality<commit_after>/**
 * Intro.js v0.1.0
 * https://github.com/usablica/intro.js
 * MIT licensed
 *
 * Copyright (C) 2013 usabli.ca - A weekend project by Afshin Mehrabani (@afshinmeh)
 */

(function () {

  //Default config/variables
  var VERSION = "0.1.0";

  /**
   * IntroJs main class
   *
   * @class IntroJs
   */
  function IntroJs(obj) {
    this._targetElement = obj;
  }

  /**
   * Initiate a new introduction/guide from an element in the page
   *
   * @api private
   * @method _introForElement
   * @param {Object} targetElm
   * @returns {Boolean} Success or not?
   */
  function _introForElement(targetElm) {
    var allIntroSteps = targetElm.querySelectorAll("*[data-intro]"),
        introItems = [],
        self = this;

    //if there's no element to intro
    if(allIntroSteps.length < 1) {
      return false;
    }

    for (var i = 0, elmsLength = allIntroSteps.length; i < elmsLength; i++) {
      var currentElement = allIntroSteps[i];
      introItems.push({
        element: currentElement,
        intro: currentElement.getAttribute("data-intro"),
        step: parseInt(currentElement.getAttribute("data-step"), 10),
        position: currentElement.getAttribute("data-position") || 'bottom'
      });
    }

    //Ok, sort all items with given steps
    introItems.sort(function (a, b) {
      return a.step - b.step;
    });

    //set it to the introJs object
    self._introItems = introItems;

    //add overlay layer to the page
    if(_addOverlayLayer.call(self, targetElm)) {
      //then, start the show
      _nextStep.call(self);

      var skipButton = targetElm.querySelector(".introjs-skipbutton"),
          nextStepButton = targetElm.querySelector(".introjs-nextbutton");

      window.onkeydown = function(e) {
        if (e.keyCode == 27) {
          //escape key pressed, exit the intro
          _exitIntro.call(self, targetElm);
        } else if(e.keyCode == 37) {
          //left arrow
          _previousStep.call(self);
        } else if (e.keyCode == 39 || e.keyCode == 13) {
          //right arrow or enter
          _nextStep.call(self);
        }
      };
    }
    return false;
  }

  /**
   * Go to next step on intro
   *
   * @api private
   * @method _nextStep
   */
  function _nextStep() {
    if (typeof(this._currentStep) === 'undefined') {
      this._currentStep = 0;
    } else {
      ++this._currentStep;
    }

    if((this._introItems.length) <= this._currentStep) {
      //end of the intro
      //check if any callback is defined
      if (this._introCompleteCallback != undefined) {
        this._introCompleteCallback.call(this);
      }
      _exitIntro.call(this, this._targetElement);
      return;
    }

    _showElement.call(this, this._introItems[this._currentStep].element);
  }

  /**
   * Go to previous step on intro
   *
   * @api private
   * @method _nextStep
   */
  function _previousStep() {
    if (this._currentStep == 0) {
      return false;
    }

    _showElement.call(this, this._introItems[--this._currentStep].element);
  }

  /**
   * Exit from intro
   *
   * @api private
   * @method _exitIntro
   * @param {Object} targetElement
   */
  function _exitIntro(targetElement) {
    //remove overlay layer from the page
    var overlayLayer = targetElement.querySelector(".introjs-overlay");
    //for fade-out animation
    overlayLayer.style.opacity = 0;
    setTimeout(function () {
      if (overlayLayer.parentNode) {
        overlayLayer.parentNode.removeChild(overlayLayer);
      }
    }, 500);
    //remove all helper layers
    var helperLayer = targetElement.querySelector(".introjs-helperLayer");
    if (helperLayer) {
      helperLayer.parentNode.removeChild(helperLayer);
    }
    //remove `introjs-showElement` class from the element
    var showElement = document.querySelector(".introjs-showElement");
    if (showElement) {
      showElement.className = showElement.className.replace(/introjs-[a-zA-Z]+/g, '').trim();
    }
    //clean listeners
    targetElement.onkeydown = null;
    //set the step to zero
    this._currentStep = undefined;
    //check if any callback is defined
    if (this._introExitCallback != undefined) {
      this._introExitCallback.call(this);
    }
  }

  /**
   * Render tooltip box in the page
   *
   * @api private
   * @method _placeTooltip
   * @param {Object} targetElement
   * @param {Object} tooltipLayer
   * @param {Object} arrowLayer
   */
  function _placeTooltip(targetElement, tooltipLayer, arrowLayer) {
    var tooltipLayerPosition = _getOffset(tooltipLayer);
    //reset the old style
    tooltipLayer.style.top = null;
    tooltipLayer.style.right = null;
    tooltipLayer.style.bottom = null;
    tooltipLayer.style.left = null;
    switch (targetElement.getAttribute('data-position')) {
      case 'top':
        tooltipLayer.style.left = "15px";
        tooltipLayer.style.top = "-" + (tooltipLayerPosition.height + 10) + "px";
        arrowLayer.className = 'introjs-arrow bottom';
        break;
      case 'right':
        console.log(tooltipLayerPosition);
        tooltipLayer.style.right = "-" + (tooltipLayerPosition.width + 10) + "px";
        arrowLayer.className = 'introjs-arrow left';
        break;
      case 'left':
        tooltipLayer.style.top = "15px";
        tooltipLayer.style.left = "-" + (tooltipLayerPosition.width + 10) + "px";
        arrowLayer.className = 'introjs-arrow right';
        break;
      case 'bottom':
      default:
        tooltipLayer.style.bottom = "-" + (tooltipLayerPosition.height + 10) + "px";
        arrowLayer.className = 'introjs-arrow top';
        break;
    }
  }

  /**
   * Show an element on the page
   *
   * @api private
   * @method _showElement
   * @param {Object} targetElement
   */
  function _showElement(targetElement) {

    var self = this,
        oldHelperLayer = document.querySelector(".introjs-helperLayer"),
        elementPosition = _getOffset(targetElement);

    if(oldHelperLayer != null) {
      var oldHelperNumberLayer = oldHelperLayer.querySelector(".introjs-helperNumberLayer"),
          oldtooltipLayer = oldHelperLayer.querySelector(".introjs-tooltiptext"),
          oldArrowLayer = oldHelperLayer.querySelector(".introjs-arrow"),
          oldtooltipContainer = oldHelperLayer.querySelector(".introjs-tooltip")

      //set new position to helper layer
      oldHelperLayer.setAttribute("style", "width: " + (elementPosition.width + 10)  + "px; " +
                                           "height:" + (elementPosition.height + 10) + "px; " +
                                           "top:"    + (elementPosition.top - 5)     + "px;" +
                                           "left: "  + (elementPosition.left - 5)    + "px;");
      //set current step to the label
      oldHelperNumberLayer.innerHTML = targetElement.getAttribute("data-step");
      //set current tooltip text
      oldtooltipLayer.innerHTML = targetElement.getAttribute("data-intro");
      var oldShowElement = document.querySelector(".introjs-showElement");
      oldShowElement.className = oldShowElement.className.replace(/introjs-[a-zA-Z]+/g, '').trim();
      _placeTooltip(targetElement, oldtooltipContainer, oldArrowLayer);
    } else {
      var helperLayer = document.createElement("div"),
          helperNumberLayer = document.createElement("span"),
          arrowLayer = document.createElement("div"),
          tooltipLayer = document.createElement("div");

      helperLayer.className = "introjs-helperLayer";
      helperLayer.setAttribute("style", "width: " + (elementPosition.width + 10)  + "px; " +
                                        "height:" + (elementPosition.height + 10) + "px; " +
                                        "top:"    + (elementPosition.top - 5)     + "px;" +
                                        "left: "  + (elementPosition.left - 5)    + "px;");

      //add helper layer to target element
      this._targetElement.appendChild(helperLayer);

      helperNumberLayer.className = "introjs-helperNumberLayer";
      arrowLayer.className = 'introjs-arrow';
      tooltipLayer.className = "introjs-tooltip";

      helperNumberLayer.innerHTML = targetElement.getAttribute("data-step");
      tooltipLayer.innerHTML = "
" + targetElement.getAttribute("data-intro") + "

";
      helperLayer.appendChild(helperNumberLayer);
      tooltipLayer.appendChild(arrowLayer);
      helperLayer.appendChild(tooltipLayer);

      var skipTooltipButton = document.createElement("a");
      skipTooltipButton.className = "introjs-skipbutton";
      skipTooltipButton.href = "javascript:void(0);";
      skipTooltipButton.innerHTML = "Skip";

      var nextTooltipButton = document.createElement("a");

      nextTooltipButton.onclick = function() {
        _nextStep.call(self);
      };

      nextTooltipButton.className = "introjs-nextbutton";
      nextTooltipButton.href = "javascript:void(0);";
      nextTooltipButton.innerHTML = "Next →";

      skipTooltipButton.onclick = function() {
        _exitIntro.call(self, self._targetElement);
      };

      var tooltipButtonsLayer = tooltipLayer.querySelector('.introjs-tooltipbuttons');
      tooltipButtonsLayer.appendChild(skipTooltipButton);
      tooltipButtonsLayer.appendChild(nextTooltipButton);

      //set proper position
      _placeTooltip(targetElement, tooltipLayer, arrowLayer);
    }

    //add target element position style
    targetElement.className += " introjs-showElement";

    //Thanks to JavaScript Kit: http://www.javascriptkit.com/dhtmltutors/dhtmlcascade4.shtml
    var currentElementPosition = "";
    if (targetElement.currentStyle) { //IE
      currentElementPosition = targetElement.currentStyle["position"];
    } else if (document.defaultView && document.defaultView.getComputedStyle) { //Firefox
      currentElementPosition = document.defaultView.getComputedStyle(targetElement, null).getPropertyValue("position");
    }

    //I don't know is this necessary or not, but I clear the position for better comparing
    currentElementPosition = currentElementPosition.toLowerCase();
    if (currentElementPosition != "absolute" && currentElementPosition != "relative") {
      //change to new intro item
      targetElement.className += " introjs-relativePosition";
    }

    if (!elementInViewport(targetElement)) {
      var rect = targetElement.getBoundingClientRect()
          top = rect.bottom-rect.height,
          bottom = rect.bottom-window.innerHeight;

      // Scroll up
      if (top < 0) {
        window.scrollBy(0, top-30); // 30px padding from edge to look nice

      // Scroll down
      } else {
        window.scrollBy(0, bottom+100); // 70px + 30px padding from edge to look nice
      }
    }
  }

  // http://stackoverflow.com/questions/123999/how-to-tell-if-a-dom-element-is-visible-in-the-current-viewport
  function elementInViewport(el) {
    var rect = el.getBoundingClientRect();

    return (
      rect.top >= 0 &&
      rect.left >= 0 &&
      (rect.bottom+80) <= window.innerHeight && // add 80 to get the text right
      rect.right <= window.innerWidth
    );
  }

  /**
   * Add overlay layer to the page
   *
   * @api private
   * @method _addOverlayLayer
   * @param {Object} targetElm
   */
  function _addOverlayLayer(targetElm) {
    var overlayLayer = document.createElement("div"),
        styleText = "",
        self = this;

    //set css class name
    overlayLayer.className = "introjs-overlay";

    //check if the target element is body, we should calculate the size of overlay layer in a better way
    if (targetElm.tagName.toLowerCase() == "body") {
      styleText += "top: 0;bottom: 0; left: 0;right: 0;position: fixed;";
      overlayLayer.setAttribute("style", styleText);
    } else {
      //set overlay layer position
      var elementPosition = _getOffset(targetElm);
      if(elementPosition) {
        styleText += "width: " + elementPosition.width + "px; height:" + elementPosition.height + "px; top:" + elementPosition.top + "px;left: " + elementPosition.left + "px;";
        overlayLayer.setAttribute("style", styleText);
      }
    }

    targetElm.appendChild(overlayLayer);

    overlayLayer.onclick = function() {
      _exitIntro.call(self, targetElm);
    };

    setTimeout(function() {
      styleText += "opacity: .5;";
      overlayLayer.setAttribute("style", styleText);
    }, 10);
    return true;
  }

  /**
   * Get an element position on the page
   * Thanks to `meouw`: http://stackoverflow.com/a/442474/375966
   *
   * @api private
   * @method _getOffset
   * @param {Object} element
   * @returns Element's position info
   */
  function _getOffset(element) {
    var elementPosition = {};

    //set width
    elementPosition.width = element.offsetWidth;

    //set height
    elementPosition.height = element.offsetHeight;

    //calculate element top and left
    var _x = 0;
    var _y = 0;
    while(element && !isNaN(element.offsetLeft) && !isNaN(element.offsetTop)) {
      _x += element.offsetLeft;
      _y += element.offsetTop;
      element = element.offsetParent;
    }
    //set top
    elementPosition.top = _y;
    //set left
    elementPosition.left = _x;

    return elementPosition;
  }

  var introJs = function (targetElm) {
    if (typeof (targetElm) === "object") {
      //Ok, create a new instance
      return new IntroJs(targetElm);

    } else if (typeof (targetElm) === "string") {
      //select the target element with query selector
      var targetElement = document.querySelector(targetElm);

      if (targetElement) {
        return new IntroJs(targetElement);
      } else {
        throw new Error("There's no element with given selector.");
      }
    } else {
      return new IntroJs(document.body);
    }
  };

  /**
   * Current IntroJs version
   *
   * @property version
   * @type String
   */
  introJs.version = VERSION;

  //Prototype
  introJs.fn = IntroJs.prototype = {
    clone: function () {
      return new IntroJs(this);
    },
    start: function () {
      _introForElement.call(this, this._targetElement);
      return this;
    },
    oncomplete: function(providedCallback) {
      if (typeof (providedCallback) === "function") {
        this._introCompleteCallback = providedCallback;
      } else {
        throw new Error("Provided callback for oncomplete was not a function.");
      }
      return this;
    },
    onexit: function(providedCallback) {
      if (typeof (providedCallback) === "function") {
        this._introExitCallback = providedCallback;
      } else {
        throw new Error("Provided callback for onexit was not a function.");
      }
      return this;
    }
  };

  window['introJs'] = introJs;
})();
<|endoftext|>


<commit_before>#!/usr/bin/env node

var LogLevel = 'info';
var Logger = require('f5-cloud-libs').logger;
var logger = Logger.getLogger({logLevel: LogLevel, fileName: '/var/tmp/azureFailover.log'});

var util = require('f5-cloud-libs').util;
var fs = require('fs');

if (fs.existsSync('/config/cloud/azCredentials')) {
    var credentialsFile = JSON.parse(fs.readFileSync('/config/cloud/azCredentials', 'utf8'));
}
else {
     logger.info('Credentials file not found');
     return;
}

var subscriptionId = credentialsFile.subscriptionId;
var clientId = credentialsFile.clientId;
var tenantId = credentialsFile.tenantId;
var secret = credentialsFile.secret;
var resourceGroup = credentialsFile.resourceGroup;

var msRestAzure = require('ms-rest-azure');
var credentials = new msRestAzure.ApplicationTokenCredentials(clientId, tenantId, secret);

var networkManagementClient = require('azure-arm-network');
var networkClient = new networkManagementClient(credentials, subscriptionId);

if (fs.existsSync('/config/cloud/managedRoutes')) {
     var routeFilter = fs.readFileSync('/config/cloud/managedRoutes', 'utf8').replace(/(\r\n|\n|\r)/gm,"").split(',');
}
else {
     logger.info('Managed routes file not found');
}

if (fs.existsSync('/config/cloud/routeTableTag')) {
     var routeTableTags = fs.readFileSync('/config/cloud/routeTableTag', 'utf8').replace(/(\r\n|\n|\r)/gm,"").split('\n');
}
else {
     logger.info('Route table tag file not found');
}

var extIpName = '-ext-pip';
var extIpConfigName = '-ext-ipconfig';
var selfIpConfigName = '-self-ipconfig';

var BigIp;
var bigip;
BigIp = require('f5-cloud-libs').bigIp;
bigip = new BigIp({logger: logger});

bigip.init(
     'localhost',
     'admin',
     'file:///config/cloud/passwd',
     {
          passwordIsUrl: true,
          port: '8443'
     }
)
.then(function() {
     Promise.all([
          listRouteTables(),
          bigip.list('/tm/net/self/self_3nic'),
     ])
     .then((results) => {
          matchRoutes(results[0], results[1].address);
     })
     .catch(err => {
          logger.info('Error: ', err);
     });
     Promise.all([
          listAzNics(resourceGroup),
          listPublicIPs(resourceGroup),
          bigip.list('/tm/net/self/self_2nic'),
     ])
     .then((results) => {
          matchNics(results[0], results[1], results[2].address);
     })
     .catch(err => {
          logger.info('Error: ', err);
     });
});

/**
 * Lists all route tables in the subscription
 *
 * @returns {Promise} A promise which can be resolved with a non-error response from Azure REST API
 *
 */
function listRouteTables() {
     return new Promise(
     function (resolve, reject) {
          networkClient.routeTables.listAll(
          (error, data) => {
               if (error) {
                    reject(error);
               }
               else {
                    resolve(data);
               }
          });
     });
}

/**
 * Updates specified Azure user defined routes
 *
 * @param {String} routeTableGroup - Name of the route table resource group
 * @param {String} routeTableName - Name of the route table
 * @param {String} routeName - Name of the route to update
 * @param {Array} routeParams - New route parameters
 *
 * @returns {Promise} A promise which can be resolved with a non-error response from Azure REST API
 *
 */
function updateRoutes(routeTableGroup, routeTableName, routeName, routeParams) {
     return new Promise(
     function (resolve, reject) {
          networkClient.routes.beginCreateOrUpdate(routeTableGroup, routeTableName, routeName, routeParams,
          (error, data) => {
               if (error) {
                    reject(error);
               }
               else {
                    resolve(data);
               }
          });
     });
}

/**
 * Determines which routes to update
 *
 * @param {Object} routeTables - All of the route tables in the subscription
 * @param {String} self - The internal self IP address of this BIG-IP
 *
 * @returns {Promise} A promise which can be resolved with a non-error response from Azure REST API
 *
 */
function matchRoutes(routeTables, self) {
     var fields = self.split('/');
     var selfIp = fields[0];

     var t;
     var tag;
     var routeTableGroup;
     var routeTableName;
     var routes;
     var r;
     var routeName;
     var routeParams;
     var routeArr = [];

     var retryRoutes = function(routeTableGroup, routeTableName, routeName, routeParams) {
         return new Promise (
             function(resolve, reject) {
                 updateRoutes(routeTableGroup, routeTableName, routeName, routeParams)
                     .then(function(result) {
                         logger.info("Update route result: ", result);
                         resolve();
                     })
                     .catch(function(error) {
                          logger.info("Update route error: ", error);
                         if (error.response.statusCode == "429") {
                             reject();
                         }
                         else {
                             reject(error);
                         }
                     });
             });
     };

     for (t in routeTables) {
          if (routeTables[t].tags && routeTables[t].tags.f5_ha) {
               tag = routeTables[t].tags.f5_ha;

               if (routeTableTags.indexOf(tag) !== -1) {
                    routeTableGroup = routeTables[t].id.split("/")[4];
                    routeTableName = routeTables[t].name;
                    routes = routeTables[t].routes

                    for (r in routes) {
                         if (routeFilter.indexOf(routes[r].addressPrefix) !== -1) {
                              routeName = routes[r].name;
                              routes[r].nextHopType = 'VirtualAppliance';
                              routes[r].nextHopIpAddress = selfIp;
                              routeParams = routes[r];
                              routeArr = [routeTableGroup, routeTableName, routeName, routeParams];

                              util.tryUntil(this, {maxRetries: 4, retryIntervalMs: 15000}, retryRoutes, routeArr);
                         }
                    }
               }
          }
     }
}

/**
 * Lists all network interface configurations in this resource group
 *
 * @param {String} resourceGroup - Name of the resource group
 *
 * @returns {Promise} A promise which can be resolved with a non-error response from Azure REST API
 *
 */
function listAzNics(resourceGroup) {
     return new Promise(
     function (resolve, reject) {
          networkClient.networkInterfaces.list(resourceGroup,
          (error, data) => {
               if (error) {
                    reject(error);
               }
               else {
                    resolve(data);
               }
          });
     });
}

/**
 * Lists all public IP addresses in this resource group
 *
 * @param {String} resourceGroup - Name of the resource group
 *
 * @returns {Promise} A promise which can be resolved with a non-error response from Azure REST API
 *
*/
function listPublicIPs(resourceGroup) {
     return new Promise(
     function (resolve, reject) {
          networkClient.publicIPAddresses.list(resourceGroup,
          (error, data) => {
               if (error) {
                    reject(error);
               }
               else {
                    resolve(data);
               }
          });
     });
}

/**
 * Returns a network interface IP configuration
 *
 * @param {Object} ipConfig - The full Azure IP configuration
 *
 * @returns {Array} An array of IP configuration parameters
 *
*/
function getNicConfig(ipConfig) {
     return {
          name: ipConfig.name,
          privateIPAllocationMethod: ipConfig.privateIPAllocationMethod,
          privateIPAddress: ipConfig.privateIPAddress,
          primary: ipConfig.primary,
          publicIPAddress: ipConfig.publicIPAddress,
          subnet: ipConfig.subnet
     }
}

/**
 * Determines which IP configurations to move to and from network interfaces
 *
 * @param {Object} nics - The network interface configurations
 * @param {Object} pips - The public IP address configurations
 * @param {String} self - The external self IP address of this BIG-IP
 *
 * @returns {Promise} A promise which can be resolved with a non-error response from Azure REST API
 *
 */
function matchNics(nics, pips, self) {
     var fields = self.split('/');
     var selfIp = fields[0];

     var i;
     var ipConfigurations;
     var p;
     var myNicName;
     var myNicConfig;
     var theirNicName;
     var theirNicConfig;

     var orphanedPipsArr = [];
     var pip;
     var pipName;
     var name;
     var pipPrivate;
     var subnet;

     var c;
     var theirNicArr = [];
     var theirName;
     var theirPrivateIpMethod;
     var theirPrivateIp;
     var theirPrimary;
     var theirSubnetId;
     var theirPublicIpId;

     var myNicArr = [];
     var myName;
     var myPrivateIpMethod;
     var myPrivateIp;
     var myPrimary;
     var mySubnetId;
     var myPublicIpId;

     var ourLocation;
     var theirNicParams;
     var myNicParams;

     for (i in nics) {
          ipConfigurations = nics[i].ipConfigurations;
          for (p in ipConfigurations) {
               if (ipConfigurations[p].privateIPAddress === selfIp) {
                    myNicName = nics[i].name;
                    myNicConfig = nics[i];
               }
               else if (ipConfigurations[p].privateIPAddress !== selfIp && ipConfigurations[p].id.includes(selfIpConfigName)) {
                    theirNicName = nics[i].name;
                    theirNicConfig = nics[i];
               }
          }
     }

     for (p in pips) {
          if (pips[p].tags.f5_privateIp && pips[p].tags.f5_extSubnetId && pips[p].name.includes(extIpName)) {
               pip = {};
               pip.id = pips[p].id;
               pipName = pips[p].name;
               name = pipName.replace(extIpName, extIpConfigName);
               pipPrivate = pips[p].tags.f5_privateIp;
               subnet = {};
               subnet.id = pips[p].tags.f5_extSubnetId;

               if (!pips[p].ipConfiguration) {
                    orphanedPipsArr.push({
                         'name': name,
                         'privateIPAllocationMethod': 'Static',
                         'privateIPAddress': pipPrivate,
                         'primary': false,
                         'publicIPAddress': pip,
                         'subnet': subnet
                    });
               }
          }
     }

     for (c in theirNicConfig.ipConfigurations) {
          theirNicArr.push(getNicConfig(theirNicConfig.ipConfigurations[c]));
     }

     for (c in myNicConfig.ipConfigurations) {
          myNicArr.push(getNicConfig(myNicConfig.ipConfigurations[c]));
     }

     for (i=theirNicArr.length-1; i>=0; i--) {
          if (theirNicArr[i].name.includes(extIpConfigName)) {
               myNicArr.push({
                    'name': theirNicArr[i].name,
                    'privateIPAllocationMethod': theirNicArr[i].privateIPAllocationMethod,
                    'privateIPAddress': theirNicArr[i].privateIPAddress,
                    'primary': theirNicArr[i].primary,
                    'publicIPAddress': theirNicArr[i].publicIPAddress,
                    'subnet': theirNicArr[i].subnet
               });
               theirNicArr.splice(i, 1);
          }
     }

     for (i=orphanedPipsArr.length-1; i>=0; i--) {
          myNicArr.push({
               'name': orphanedPipsArr[i].name,
               'privateIPAllocationMethod': orphanedPipsArr[i].privateIPAllocationMethod,
               'privateIPAddress': orphanedPipsArr[i].privateIPAddress,
               'primary': orphanedPipsArr[i].primary,
               'publicIPAddress': orphanedPipsArr[i].publicIPAddress,
               'subnet': orphanedPipsArr[i].subnet
          });
     }

     ourLocation = myNicConfig.location;
     theirNicParams = { location: ourLocation, ipConfigurations:theirNicArr };
     myNicParams = { location: ourLocation, ipConfigurations:myNicArr };

     disassociateNics(resourceGroup, theirNicName, theirNicParams)
     .then(function (result) {
          logger.info("Disassociate NICs result: ", result);
          associateNics(resourceGroup, myNicName, myNicParams);
     })
     .then(function (result) {
          logger.info("Associate NICs result: ", result);
     })
     .catch(function (error) {
          logger.info('Error: ', error);
     });
}

/**
 * Removes specified IP configurations from the remote network interface
 *
 * @param {String} resourceGroup - Name of the resource group
 * @param {String} theirNicName - Name of the network interface to update
 * @param {Array} theirNicParams - Network interface parameters
 *
 * @returns {Promise} A promise which can be resolved with a non-error response from Azure REST API
 *
 */
function disassociateNics(resourceGroup, theirNicName, theirNicParams) {
     return new Promise(
     function (resolve, reject) {
          networkClient.networkInterfaces.createOrUpdate(resourceGroup, theirNicName, theirNicParams,
          (error, data) => {
               if (error) {
                    reject(error);
               }
               else {
                    resolve(data);
               }
          });
     });
}

/**
 * Adds specified IP configurations to the local network interface
 *
 * @param {String} resourceGroup - Name of the resource group
 * @param {String} myNicName - Name of the network interface to update
 * @param {Array} myNicParams - Network interface parameters
 *
 * @returns {Promise} A promise which can be resolved with a non-error response from Azure REST API
 *
 */
function associateNics(resourceGroup, myNicName, myNicParams) {
     return new Promise(
     function (resolve, reject) {
          networkClient.networkInterfaces.createOrUpdate(resourceGroup, myNicName, myNicParams,
          (error, data) => {
               if (error) {
                    reject(error);
               }
               else {
                    resolve(data);
               }
          });
     });
}<commit_msg>failover provider content<commit_after>#!/usr/bin/env node

var fs = require('fs');
var credentialsFile = JSON.parse(fs.readFileSync('/config/cloud/azCredentials', 'utf8'));

var subscriptionId = credentialsFile.subscriptionId;
var clientId = credentialsFile.clientId;
var tenantId = credentialsFile.tenantId;
var secret = credentialsFile.secret;
var resourceGroup = credentialsFile.resourceGroup;

var msRestAzure = require('ms-rest-azure');
var credentials = new msRestAzure.ApplicationTokenCredentials(clientId, tenantId, secret);

var networkManagementClient = require('azure-arm-network');
var networkClient = new networkManagementClient(credentials, subscriptionId);

var Logger = require('/config/cloud/node_modules/f5-cloud-libs/lib/logger');
var logger = Logger.getLogger({logLevel: 'debug', fileName: '/var/tmp/azureFailover.log'});

//I want to use bigIp.js here instead, but I still don't quite get how to do it
var iControl = require('icontrol');

//will I need the BIG-IP admin password using bigIp.js?
var bigip = new iControl({
     host: 'localhost',
     proto: 'https',
     port: 8443,
     strict: false,
     debug: false
});

var routeFilter = fs.readFileSync('/config/cloud/managedRoutes', 'utf8').replace(/(\r\n|\n|\r)/gm,"").split(',');
var routeTableTags = fs.readFileSync('/config/cloud/routeTableTag', 'utf8').replace(/(\r\n|\n|\r)/gm,"").split('\n');

var extIpName = '-ext-pip';
var extIpConfigName = '-ext-ipconfig';
var selfIpConfigName = '-self-ipconfig';

//update routes
function listRouteTablesPromisified() {
     return new Promise(
     function (resolve, reject) {
          networkClient.routeTables.listAll(
          (error, data) => {
               if (error) {
                    reject(error);
                    } else {
                    resolve(data);
               }
          });
     });
}

function listIntNicPromisified() {
     return new Promise(
     function (resolve, reject) {
          bigip.list('/net/self/self_3nic',
          (error, data) => {
               if (error) {
                    reject(error);
                    } else {
                    resolve(data);
               }
          });
     });
}

function updateRoutesPromisified(routeTableGroup, routeTableName, routeName, routeParams) {
     return new Promise(
     function (resolve, reject) {
          networkClient.routes.beginCreateOrUpdate(routeTableGroup, routeTableName, routeName, routeParams,
          (error, data) => {
               if (error) {
                    reject(error);
                    } else {
                    resolve(data);
               }
          });
     });
}

function matchRoutes(routeTables, self) {
     var self = self.address;
     var fields = self.split('/');
     var selfIp = fields[0];

     for ( var t in routeTables ) {
          if ( routeTables[t].tags && routeTables[t].tags.f5_ha ) {
               var tag = routeTables[t].tags.f5_ha;

               if ( routeTableTags.indexOf(tag) !== -1 ) {
                    var routeTableGroup = routeTables[t].id.split("/")[4];
                    var routeTableName = routeTables[t].name;
                    var routes = routeTables[t].routes

                    for ( var r in routes ) {
                         if ( routeFilter.indexOf(routes[r].addressPrefix) !== -1 ) {
                              var routeName = routes[r].name;
                              routes[r].nextHopType = 'VirtualAppliance';
                              routes[r].nextHopIpAddress = selfIp;
                              var routeParams = routes[r];

                              //do this for each route that needs updating
                              //if the HTTP response error.statusCode is 429 for updating each route, want to retry every 15 seconds for 4 retries
                              updateRoutesPromisified(routeTableGroup, routeTableName, routeName, routeParams)
                              .then(function (result) {
                                   logger.debug("Update route result: ", result);
                              })
                              .catch(function (error) {
                                   logger.debug('Error: ', error);
                              });
                         }
                    }
               }
          }
     }
}

Promise.all([
     listRouteTablesPromisified(),
     listIntNicPromisified(),
])
.then((results) => {
     matchRoutes(results[0], results[1]);
})
.catch(err => {
     logger.debug('Error: ', err);
});

//update NICs
function listAzNicsPromisified(resourceGroup) {
     return new Promise(
     function (resolve, reject) {
          networkClient.networkInterfaces.list(resourceGroup,
          (error, data) => {
               if (error) {
                    reject(error);
                    } else {
                    resolve(data);
               }
          });
     });
}

function listPublicIPsPromisified(resourceGroup) {
     return new Promise(
     function (resolve, reject) {
          networkClient.publicIPAddresses.list(resourceGroup,
          (error, data) => {
               if (error) {
                    reject(error);
                    } else {
                    resolve(data);
               }
          });
     });
}

function listExtNicPromisified() {
     return new Promise(
     function (resolve, reject) {
          bigip.list('/net/self/self_2nic',
          (error, data) => {
               if (error) {
                    reject(error);
                    } else {
                    resolve(data);
               }
          });
     });
}

function matchNics(nics, pips, self) {
     var self = self.address;
     var fields = self.split('/');
     var selfIp = fields[0];

     for ( var i in nics ) {
          var ipConfigurations = nics[i].ipConfigurations;
          for ( var p in ipConfigurations ) {
               if ( ipConfigurations[p].privateIPAddress === selfIp ) {
                    var myNicName = nics[i].name;
                    var myNicConfig = nics[i];
               }
               if ( ipConfigurations[p].privateIPAddress !== selfIp && ipConfigurations[p].id.includes(selfIpConfigName) ) {
                    var theirNicName = nics[i].name;
                    var theirNicConfig = nics[i];
               }
          }
     }

     var orphanedPipsArr = [];

     for ( var p in pips ) {
          if ( pips[p].tags.f5_privateIp && pips[p].tags.f5_extSubnetId && pips[p].name.includes(extIpName) ) {
               var pip = {};
               pip.id = pips[p].id;
               var pipName = pips[p].name;
               var name = pipName.replace(extIpName, extIpConfigName);
               var pipPrivate = pips[p].tags.f5_privateIp;
               var subnet = {};
               subnet.id = pips[p].tags.f5_extSubnetId;

               if ( !pips[p].ipConfiguration ) {
                    orphanedPipsArr.push({
                         'name': name,
                         'privateIPAllocationMethod': 'Static',
                         'privateIPAddress': pipPrivate,
                         'primary': false,
                         'publicIPAddress': pip,
                         'subnet': subnet
                    });
               }
          }
     }

     var ourLocation = myNicConfig.location;
     var theirNicArr = [];
     var myNicArr = [];

     for ( var c in theirNicConfig.ipConfigurations ) {
          var theirName = theirNicConfig.ipConfigurations[c].name;
          var theirPrivateIpMethod = theirNicConfig.ipConfigurations[c].privateIPAllocationMethod;
          var theirPrivateIp = theirNicConfig.ipConfigurations[c].privateIPAddress;
          var theirPrimary = theirNicConfig.ipConfigurations[c].primary;
          var theirSubnetId = theirNicConfig.ipConfigurations[c].subnet;
          var theirPublicIpId = theirNicConfig.ipConfigurations[c].publicIPAddress;
          theirNicArr.push({
               'name': theirName,
               'privateIPAllocationMethod': theirPrivateIpMethod,
               'privateIPAddress': theirPrivateIp,
               'primary': theirPrimary,
               'publicIPAddress': theirPublicIpId,
               'subnet': theirSubnetId
          });
     }

     for ( var c in myNicConfig.ipConfigurations ) {
          var myName = myNicConfig.ipConfigurations[c].name;
          var myPrivateIpMethod = myNicConfig.ipConfigurations[c].privateIPAllocationMethod;
          var myPrivateIp = myNicConfig.ipConfigurations[c].privateIPAddress;
          var myPrimary = myNicConfig.ipConfigurations[c].primary;
          var mySubnetId = myNicConfig.ipConfigurations[c].subnet;
          var myPublicIpId = myNicConfig.ipConfigurations[c].publicIPAddress;
          myNicArr.push({
               'name': myName,
               'privateIPAllocationMethod': myPrivateIpMethod,
               'privateIPAddress': myPrivateIp,
               'primary': myPrimary,
               'publicIPAddress': myPublicIpId,
               'subnet': mySubnetId
          });
     }

     for ( var i=theirNicArr.length-1; i>=0; i-- ) {
          if ( theirNicArr[i].name.includes(extIpConfigName) ) {
               myNicArr.push({
                    'name': theirNicArr[i].name,
                    'privateIPAllocationMethod': theirNicArr[i].privateIPAllocationMethod,
                    'privateIPAddress': theirNicArr[i].privateIPAddress,
                    'primary': theirNicArr[i].primary,
                    'publicIPAddress': theirNicArr[i].publicIPAddress,
                    'subnet': theirNicArr[i].subnet
               });
               theirNicArr.splice(i, 1);
          }
     }

     for ( var i=orphanedPipsArr.length-1; i>=0; i-- ) {
          myNicArr.push({
               'name': orphanedPipsArr[i].name,
               'privateIPAllocationMethod': orphanedPipsArr[i].privateIPAllocationMethod,
               'privateIPAddress': orphanedPipsArr[i].privateIPAddress,
               'primary': orphanedPipsArr[i].primary,
               'publicIPAddress': orphanedPipsArr[i].publicIPAddress,
               'subnet': orphanedPipsArr[i].subnet
          });
     }

     var theirNicParams = { location: ourLocation, ipConfigurations:theirNicArr };
     var myNicParams = { location: ourLocation, ipConfigurations:myNicArr };

     //if the HTTP response error.statusCode is 429 for associate or disassociate, want to retry every 15 seconds for 4 retries
     //is this the right way to nest my function calls?
     disassociateNicsPromisified(resourceGroup, theirNicName, theirNicParams)
     .then(function (result) {
          associateNicsPromisified(resourceGroup, myNicName, myNicParams);
     })
     .then(function (result) {
          logger.debug("Associate NICs result: ", result);
     })
     .catch(function (error) {
          logger.debug('Error: ', error);
     });
}

function disassociateNicsPromisified(resourceGroup, theirNicName, theirNicParams) {
     return new Promise(
     function (resolve, reject) {
          networkClient.networkInterfaces.createOrUpdate(resourceGroup, theirNicName, theirNicParams,
          (error, data) => {
               if (error) {
                    reject(error);
                    } else {
                    resolve(data);
               }
          });
     });
}

function associateNicsPromisified(resourceGroup, myNicName, myNicParams) {
     return new Promise(
     function (resolve, reject) {
          networkClient.networkInterfaces.createOrUpdate(resourceGroup, myNicName, myNicParams,
          (error, data) => {
               if (error) {
                    reject(error);
                    } else {
                    resolve(data);
               }
          });
     });
}

Promise.all([
     listAzNicsPromisified(resourceGroup),
     listPublicIPsPromisified(resourceGroup),
     listExtNicPromisified(),
])
.then((results) => {
     matchNics(results[0], results[1], results[2]);
})
.catch(err => {
     logger.debug('Error: ', err);
});<|endoftext|>