<commit_before>/*
Handles the requests by executing stuff and replying to the client. Uses promises to get stuff done.
*/

'use strict';

const boom = require('boom'), //Boom gives us some predefined http codes and proper responses
  commentDB = require('../database/commentDatabase'), //Database functions specific for comments
  co = require('../common');

const Microservices = require('../configs/microservices');
// let http = require('http');
let rp = require('request-promise-native');
//Send request to insert new activity
function createActivity(comment) {
  let myPromise = new Promise((resolve, reject) => {
    const activityType = (comment.parent_comment === undefined) ? 'comment' : 'reply';
    const commentId = comment._id ? comment._id : comment.id;//co.rewriteID(comment) might be executing at the same time

    let data = JSON.stringify({
      activity_type: activityType,
      user_id: comment.user_id,
      content_id: comment.content_id,
      content_kind: comment.content_kind,
      content_name: comment.content_name,
      content_owner_id: comment.content_owner_id,
      comment_info: {
        comment_id: commentId,
        text: comment.title
      }
    });

    rp.post({uri: Microservices.activities.uri + '/activity/new', body:data}).then((res) => {
      try {
        let newActivity = JSON.parse(res);
        resolve(newActivity);
      } catch(e) {
        console.log(e);
        reject(e);
      }
    }).catch((err) => {
      console.log('Error', err);
      reject(e);
    });
  });

  return myPromise;
}

module.exports = {
  //Get Comment from database or return NOT FOUND
  getComment: function(request, reply) {
    return commentDB.get(encodeURIComponent(request.params.id)).then((comment) => {
      if (co.isEmpty(comment))
        reply(boom.notFound());
      else {
        return insertAuthor(comment).then((comment) => {
          reply(co.rewriteID(comment));
        }).catch((error) => {
          tryRequestLog(request, 'error', error);
          reply(boom.badImplementation());
        });
      }
    }).catch((error) => {
      tryRequestLog(request, 'error', error);
      reply(boom.badImplementation());
    });
  },

  //Create Comment with new id and payload or return INTERNAL_SERVER_ERROR
  newComment: function(request, reply) {
    return findContentTitleAndOwner(request.payload)
      .then((contentTitleAndOwner) => {
        let contentIdParts = request.payload.content_id.split('-');
        if (contentIdParts.length === 1) {//there is no revision id
          request.payload.content_id += '-' + contentTitleAndOwner.revisionId;
        }
        commentDB.insert(request.payload).then((inserted) => {
          if (co.isEmpty(inserted.ops) || co.isEmpty(inserted.ops[0]))
            throw inserted;
          else {
            inserted.ops[0].content_name = contentTitleAndOwner.title;
            inserted.ops[0].content_owner_id = contentTitleAndOwner.ownerId;
            if (inserted.ops[0].is_activity === undefined || inserted.ops[0].is_activity === true) {//insert activity if not test initiated
              createActivity(inserted.ops[0]);
            }
            return insertAuthor(inserted.ops[0]).then((comment) => {
              reply(co.rewriteID(comment));
            }).catch((error) => {
              tryRequestLog(request, 'error', error);
              reply(boom.badImplementation());
            });
          }
        }).catch((error) => {
          tryRequestLog(request, 'error', error);
          reply(boom.badImplementation());
        });
      }).catch((error) => {
        tryRequestLog(request, 'error', error);
        reply(boom.badImplementation());
      });
  },

  //Update Comment with id id and payload or return INTERNAL_SERVER_ERROR
  updateComment: function(request, reply) {
    return commentDB.replace(encodeURIComponent(request.params.id), request.payload).then((replaced) => {
      if (co.isEmpty(replaced.value))
        throw replaced;
      else
        reply(replaced.value);
    }).catch((error) => {
      tryRequestLog(request, 'error', error);
      reply(boom.badImplementation());
    });
  },

  //Delete Comment with id id
  deleteComment: function(request, reply) {
    return commentDB.delete(encodeURIComponent(request.payload.id)).then(() =>
      reply({'msg': 'comment is successfully deleted...'})
    ).catch((error) => {
      tryRequestLog(request, 'error', error);
      reply(boom.badImplementation());
    });
  },

  //Delete Discussions with content id id
  deleteDiscussion: function(request, reply) {
    return commentDB.deleteAllWithContentID(encodeURIComponent(request.payload.content_id)).then(() =>
      reply({'msg': 'discussion is successfully deleted...'})
    ).catch((error) => {
      tryRequestLog(request, 'error', error);
      reply(boom.badImplementation());
    });
  },

  //Get All Comments from database for the id in the request
  getDiscussion: function(request, reply) {
    let content_kind = request.params.content_kind;
    if (content_kind === undefined) {// this is just to serve requests from old front-end version
      content_kind = 'slide';
    }

    return addContentRevisionIdIfMissing(content_kind, request.params.id)
      .then((contentId) => {
        commentDB.getAll(content_kind, encodeURIComponent(contentId))
        .then((comments) => {
          // if (co.isEmpty(comments))
          //   reply(boom.notFound());
          // else {
          comments.forEach((comment) => {
            co.rewriteID(comment);
          });

          let replies = [];
          let arrayOfAuthorPromisses = [];
          comments.forEach((comment, index) => {
            let promise = insertAuthor(comment);
            arrayOfAuthorPromisses.push(promise);

            //move replies to their places
            let parent_comment_id = comment.parent_comment;
            if (parent_comment_id !== undefined) {
              let parentComment = findComment(comments, parent_comment_id);
              if (parentComment !== null) {//found parent comment
                if (parentComment.replies === undefined) {//first reply
                  parentComment.replies = [];
                }
                parentComment.replies.push(comment);
                replies.push(index);//remember index, to remove it later
              }
            }
          });
          Promise.all(arrayOfAuthorPromisses).then(() => {
            //remove comments which were inserted as replies
            replies.reverse();
            replies.forEach((i) => {
              comments.splice(i, 1);
            });

            let jsonReply = JSON.stringify(comments);
            reply(jsonReply);

          }).catch((error) => {
            tryRequestLog(request, 'error', error);
            reply(boom.badImplementation());
          });
        }).catch((error) => {
          tryRequestLog(request, 'error', error);
          reply(boom.badImplementation());
        });
      }).catch((error) => {
        tryRequestLog(request, 'error', error);
        reply(boom.badImplementation());
      });
  },

  //Get All Comments from database
  getAllDiscussions: function(request, reply) {
    return commentDB.getAllFromCollection()
      .then((comments) => {
        comments.forEach((comment) => {
          co.rewriteID(comment);
        });

        let replies = [];
        let arrayOfAuthorPromisses = [];
        comments.forEach((comment, index) => {
          let promise = insertAuthor(comment);
          arrayOfAuthorPromisses.push(promise);

          //move replies to their places
          let parent_comment_id = comment.parent_comment;
          if (parent_comment_id !== undefined) {
            let parentComment = findComment(comments, parent_comment_id);
            if (parentComment !== null) {//found parent comment
              if (parentComment.replies === undefined) {//first reply
                parentComment.replies = [];
              }
              parentComment.replies.push(comment);
              replies.push(index);//remember index, to remove it later
            }
          }
        });
        Promise.all(arrayOfAuthorPromisses).then(() => {
          //remove comments which were inserted as replies
          replies.reverse();
          replies.forEach((i) => {
            comments.splice(i, 1);
          });

          let jsonReply = JSON.stringify(comments);
          reply(jsonReply);

        }).catch((error) => {
          tryRequestLog(request, 'error', error);
          reply(boom.badImplementation());
        });
      }).catch((error) => {
        tryRequestLog(request, 'error', error);
        reply(boom.badImplementation());
      });
  },

  //Get the number of comments from database for the id in the request
  getDiscussionCount: function(request, reply) {
    return addContentRevisionIdIfMissing(request.params.content_kind, request.params.id)
      .then((contentId) => {
        commentDB.getCount(request.params.content_kind, encodeURIComponent(contentId))
          .then((count) => {
            reply (count);
          }).catch((error) => {
            tryRequestLog(request, 'error', error);
            reply(boom.badImplementation());
          });
      }).catch((error) => {
        tryRequestLog(request, 'error', error);
        reply(boom.badImplementation());
      });
  }
};

//Find comment in a tree with id = identifier
function findComment(array, identifier) {
  for(let i = 0; i < array.length; i++) {
    let comment = array[i];
    if (String(comment.id) === String(identifier)) {
      return comment;
    } else if (comment.replies !== undefined) {
      let commentFound = findComment(comment.replies, identifier);
      if (commentFound !== null)
        return commentFound;
    }
  }
  return null;
}

//insert author data using user microservice
function insertAuthor(comment) {
  let myPromise = new Promise((resolve, reject) => {

    let username = 'unknown';
    let avatar = '';
    rp.get({uri: Microservices.user.uri + '/user/' + comment.user_id}).then((res) => {
      try {
        let parsed = JSON.parse(res);
        username = parsed.username;
        avatar = parsed.picture;
      } catch(e) {
        console.log(e);
      }

      comment.author = {
        id: comment.user_id,
        username: username,
        avatar: avatar
      };
      resolve(comment);
    }).catch((err) => {
      console.log('Error', err);
      comment.author = {
        id: comment.user_id,
        username: username,
        avatar: avatar
      };
      resolve(comment);
    });
  });

  return myPromise;
}

//find content title and ownerId using deck microservice
function findContentTitleAndOwner(comment) {
  let myPromise = new Promise((resolve, reject) => {
    let title = '';
    let ownerId = 0;

    let contentIdParts = comment.content_id.split('-');
    let contentRevisionId = (contentIdParts.length > 1) ? contentIdParts[contentIdParts.length - 1] : undefined;
    rp.get({uri: Microservices.deck.uri + '/' + comment.content_kind + '/' + comment.content_id}).then((res) => {

      try {
        let parsed = JSON.parse(res);
        if (parsed.user) {
          ownerId = parsed.user;
        }
        if (parsed.revisions !== undefined && parsed.revisions.length > 0 && parsed.revisions[0] !== null) {
          //get title from result

          let contentRevision = (contentRevisionId !== undefined) ? parsed.revisions.find((revision) =>  String(revision.id) ===  String(contentRevisionId)) : undefined;

          if (contentRevision !== undefined) {
            ownerId = contentRevision.user;
            title = contentRevision.title;
          } else {//if revision from content_id is not found take data from active revision
            const activeRevisionId = parsed.active;
            let activeRevision = parsed.revisions[parsed.revisions.length - 1];//if active is not defined take the last revision in array
            if (activeRevisionId !== undefined) {
              activeRevision = parsed.revisions.find((revision) =>  String(revision.id) ===  String(activeRevisionId));
            }
            if (activeRevision !== undefined) {
              title = activeRevision.title;
              if (contentRevisionId === undefined) {
                contentRevisionId = activeRevision.id;
              }
            }
          }
        }
      } catch(e) {
        console.log(e);
      }
      resolve({title: title, ownerId: String(ownerId), revisionId: contentRevisionId});

    }).catch((err) => {
      console.log('Error', err);
      resolve({title: title, ownerId: String(ownerId), revisionId: contentRevisionId});
    });
  });

  return myPromise;
}

function addContentRevisionIdIfMissing(contentKind, contentId) {
  let myPromise = new Promise((resolve, reject) => {
    let contentIdParts = contentId.split('-');
    if (contentIdParts.length > 1) {//there is a revision id
      resolve(contentId);
    } else {
      rp.get({uri: Microservices.deck.uri + '/' + contentKind + '/' + contentId}).then((res) => {
        try {
          let parsed = JSON.parse(res);
          let revisionId = 1;
          if (parsed.revisions !== undefined && parsed.revisions.length > 0 && parsed.revisions[0] !== null) {
            revisionId = (parsed.active) ? parsed.active : (parsed.revisions.length - 1);
          }
          resolve(contentId + '-' + revisionId);
        } catch(e) {
          console.log(e);
          resolve(contentId);
        }
      }).catch((err) => {
        console.log('Error', err);
        resolve(contentId);
      });
    }
  });

  return myPromise;
}

//This function tries to use request log and uses console.log if this doesnt work - this is the case in unit tests
function tryRequestLog(request, message, _object) {
  try {
    request.log(message, _object);
  } catch (e) {
    console.log(message, _object);
  }
}
<commit_msg>Moved finding title and owner to the activity service<commit_after>/*
Handles the requests by executing stuff and replying to the client. Uses promises to get stuff done.
*/

'use strict';

const boom = require('boom'), //Boom gives us some predefined http codes and proper responses
  commentDB = require('../database/commentDatabase'), //Database functions specific for comments
  co = require('../common');

const Microservices = require('../configs/microservices');
// let http = require('http');
let rp = require('request-promise-native');
//Send request to insert new activity
function createActivity(comment) {
  let myPromise = new Promise((resolve, reject) => {
    const activityType = (comment.parent_comment === undefined) ? 'comment' : 'reply';
    const commentId = comment._id ? comment._id : comment.id;//co.rewriteID(comment) might be executing at the same time

    let data = JSON.stringify({
      activity_type: activityType,
      user_id: comment.user_id,
      content_id: comment.content_id,
      content_kind: comment.content_kind,
      comment_info: {
        comment_id: commentId,
        text: comment.title
      }
    });

    rp.post({uri: Microservices.activities.uri + '/activity/new', body:data}).then((res) => {
      try {
        let newActivity = JSON.parse(res);
        resolve(newActivity);
      } catch(e) {
        console.log(e);
        reject(e);
      }
    }).catch((err) => {
      console.log('Error', err);
      reject(e);
    });
  });

  return myPromise;
}

module.exports = {
  //Get Comment from database or return NOT FOUND
  getComment: function(request, reply) {
    return commentDB.get(encodeURIComponent(request.params.id)).then((comment) => {
      if (co.isEmpty(comment))
        reply(boom.notFound());
      else {
        return insertAuthor(comment).then((comment) => {
          reply(co.rewriteID(comment));
        }).catch((error) => {
          tryRequestLog(request, 'error', error);
          reply(boom.badImplementation());
        });
      }
    }).catch((error) => {
      tryRequestLog(request, 'error', error);
      reply(boom.badImplementation());
    });
  },

  //Create Comment with new id and payload or return INTERNAL_SERVER_ERROR
  newComment: function(request, reply) {
    return addContentRevisionIfMissing(request.payload)
      .then((comment) => {
        commentDB.insert(comment).then((inserted) => {
          if (co.isEmpty(inserted.ops) || co.isEmpty(inserted.ops[0]))
            throw inserted;
          else {
            if (inserted.ops[0].is_activity === undefined || inserted.ops[0].is_activity === true) {//insert activity if not test initiated
              createActivity(inserted.ops[0]);
            }
            return insertAuthor(inserted.ops[0]).then((comment) => {
              reply(co.rewriteID(comment));
            }).catch((error) => {
              tryRequestLog(request, 'error', error);
              reply(boom.badImplementation());
            });
          }
        }).catch((error) => {
          tryRequestLog(request, 'error', error);
          reply(boom.badImplementation());
        });
      }).catch((error) => {
        tryRequestLog(request, 'error', error);
        reply(boom.badImplementation());
      });
  },

  //Update Comment with id id and payload or return INTERNAL_SERVER_ERROR
  updateComment: function(request, reply) {
    return commentDB.replace(encodeURIComponent(request.params.id), request.payload).then((replaced) => {
      if (co.isEmpty(replaced.value))
        throw replaced;
      else
        reply(replaced.value);
    }).catch((error) => {
      tryRequestLog(request, 'error', error);
      reply(boom.badImplementation());
    });
  },

  //Delete Comment with id id
  deleteComment: function(request, reply) {
    return commentDB.delete(encodeURIComponent(request.payload.id)).then(() =>
      reply({'msg': 'comment is successfully deleted...'})
    ).catch((error) => {
      tryRequestLog(request, 'error', error);
      reply(boom.badImplementation());
    });
  },

  //Delete Discussions with content id id
  deleteDiscussion: function(request, reply) {
    return commentDB.deleteAllWithContentID(encodeURIComponent(request.payload.content_id)).then(() =>
      reply({'msg': 'discussion is successfully deleted...'})
    ).catch((error) => {
      tryRequestLog(request, 'error', error);
      reply(boom.badImplementation());
    });
  },

  //Get All Comments from database for the id in the request
  getDiscussion: function(request, reply) {
    let content_kind = request.params.content_kind;
    if (content_kind === undefined) {// this is just to serve requests from old front-end version
      content_kind = 'slide';
    }

    return addContentRevisionIdIfMissing(content_kind, request.params.id)
      .then((contentId) => {
        commentDB.getAll(content_kind, encodeURIComponent(contentId))
        .then((comments) => {
          // if (co.isEmpty(comments))
          //   reply(boom.notFound());
          // else {
          comments.forEach((comment) => {
            co.rewriteID(comment);
          });

          let replies = [];
          let arrayOfAuthorPromisses = [];
          comments.forEach((comment, index) => {
            let promise = insertAuthor(comment);
            arrayOfAuthorPromisses.push(promise);

            //move replies to their places
            let parent_comment_id = comment.parent_comment;
            if (parent_comment_id !== undefined) {
              let parentComment = findComment(comments, parent_comment_id);
              if (parentComment !== null) {//found parent comment
                if (parentComment.replies === undefined) {//first reply
                  parentComment.replies = [];
                }
                parentComment.replies.push(comment);
                replies.push(index);//remember index, to remove it later
              }
            }
          });
          Promise.all(arrayOfAuthorPromisses).then(() => {
            //remove comments which were inserted as replies
            replies.reverse();
            replies.forEach((i) => {
              comments.splice(i, 1);
            });

            let jsonReply = JSON.stringify(comments);
            reply(jsonReply);

          }).catch((error) => {
            tryRequestLog(request, 'error', error);
            reply(boom.badImplementation());
          });
        }).catch((error) => {
          tryRequestLog(request, 'error', error);
          reply(boom.badImplementation());
        });
      }).catch((error) => {
        tryRequestLog(request, 'error', error);
        reply(boom.badImplementation());
      });
  },

  //Get All Comments from database
  getAllDiscussions: function(request, reply) {
    return commentDB.getAllFromCollection()
      .then((comments) => {
        comments.forEach((comment) => {
          co.rewriteID(comment);
        });

        let replies = [];
        let arrayOfAuthorPromisses = [];
        comments.forEach((comment, index) => {
          let promise = insertAuthor(comment);
          arrayOfAuthorPromisses.push(promise);

          //move replies to their places
          let parent_comment_id = comment.parent_comment;
          if (parent_comment_id !== undefined) {
            let parentComment = findComment(comments, parent_comment_id);
            if (parentComment !== null) {//found parent comment
              if (parentComment.replies === undefined) {//first reply
                parentComment.replies = [];
              }
              parentComment.replies.push(comment);
              replies.push(index);//remember index, to remove it later
            }
          }
        });
        Promise.all(arrayOfAuthorPromisses).then(() => {
          //remove comments which were inserted as replies
          replies.reverse();
          replies.forEach((i) => {
            comments.splice(i, 1);
          });

          let jsonReply = JSON.stringify(comments);
          reply(jsonReply);

        }).catch((error) => {
          tryRequestLog(request, 'error', error);
          reply(boom.badImplementation());
        });
      }).catch((error) => {
        tryRequestLog(request, 'error', error);
        reply(boom.badImplementation());
      });
  },

  //Get the number of comments from database for the id in the request
  getDiscussionCount: function(request, reply) {
    return addContentRevisionIdIfMissing(request.params.content_kind, request.params.id)
      .then((contentId) => {
        commentDB.getCount(request.params.content_kind, encodeURIComponent(contentId))
          .then((count) => {
            reply (count);
          }).catch((error) => {
            tryRequestLog(request, 'error', error);
            reply(boom.badImplementation());
          });
      }).catch((error) => {
        tryRequestLog(request, 'error', error);
        reply(boom.badImplementation());
      });
  }
};

//Find comment in a tree with id = identifier
function findComment(array, identifier) {
  for(let i = 0; i < array.length; i++) {
    let comment = array[i];
    if (String(comment.id) === String(identifier)) {
      return comment;
    } else if (comment.replies !== undefined) {
      let commentFound = findComment(comment.replies, identifier);
      if (commentFound !== null)
        return commentFound;
    }
  }
  return null;
}

//insert author data using user microservice
function insertAuthor(comment) {
  let myPromise = new Promise((resolve, reject) => {

    let username = 'unknown';
    let avatar = '';
    rp.get({uri: Microservices.user.uri + '/user/' + comment.user_id}).then((res) => {
      try {
        let parsed = JSON.parse(res);
        username = parsed.username;
        avatar = parsed.picture;
      } catch(e) {
        console.log(e);
      }

      comment.author = {
        id: comment.user_id,
        username: username,
        avatar: avatar
      };
      resolve(comment);
    }).catch((err) => {
      console.log('Error', err);
      comment.author = {
        id: comment.user_id,
        username: username,
        avatar: avatar
      };
      resolve(comment);
    });
  });

  return myPromise;
}

//find content revision using deck microservice
function addContentRevisionIfMissing(comment) {
  let myPromise = new Promise((resolve, reject) => {
    let contentIdParts = comment.content_id.split('-');
    if (contentIdParts.length === 1) {
      rp.get({uri: Microservices.deck.uri + '/' + comment.content_kind + '/' + comment.content_id}).then((res) => {
        try {
          let parsed = JSON.parse(res);
          comment.content_id += '-' + parsed.active;
        } catch(e) {
          console.log(e);
        }
        resolve({revisionId: contentRevisionId});
      }).catch((err) => {
        console.log('Error', err);
        resolve(comment);
      });
    } else {
      resolve(comment);
    }
  });

  return myPromise;
}

function addContentRevisionIdIfMissing(contentKind, contentId) {
  let myPromise = new Promise((resolve, reject) => {
    let contentIdParts = contentId.split('-');
    if (contentIdParts.length > 1) {//there is a revision id
      resolve(contentId);
    } else {
      rp.get({uri: Microservices.deck.uri + '/' + contentKind + '/' + contentId}).then((res) => {
        try {
          let parsed = JSON.parse(res);
          let revisionId = 1;
          if (parsed.revisions !== undefined && parsed.revisions.length > 0 && parsed.revisions[0] !== null) {
            revisionId = (parsed.active) ? parsed.active : (parsed.revisions.length - 1);
          }
          resolve(contentId + '-' + revisionId);
        } catch(e) {
          console.log(e);
          resolve(contentId);
        }
      }).catch((err) => {
        console.log('Error', err);
        resolve(contentId);
      });
    }
  });

  return myPromise;
}

//This function tries to use request log and uses console.log if this doesnt work - this is the case in unit tests
function tryRequestLog(request, message, _object) {
  try {
    request.log(message, _object);
  } catch (e) {
    console.log(message, _object);
  }
}
<|endoftext|>


<commit_before>import express from 'express';
import twilio from 'twilio';

import describeImage from './describe-image';

// eslint-disable-next-line new-cap
const router = express.Router();
const notImpl = (twiml) => {
  twiml.say('Functionality not implemented. Goodbye.');
  twiml.hangup();
};
const viewPost = (twiml) => {
  twiml.gather({
    action: '/ivr/instagram_actions',
    numDigits: '1',
    method: 'POST',
  }, (node) => {
    node.say('At LOCATION on DATE at TIME, USERNAME took a photo of DESCRIPTION. '
    + 'To like this photo, please press 1. '
    + 'To comment on this photo, please press 2. '
    + 'To share this photo, please press 3. '
    + 'To view next photo, please press 4. '
    + 'To view this user\'s profile, please press 5. '
    + 'To view this photo again please press 6. '
    + 'To repeat these options, please press 9. '
    + 'Please press 0 to speak to a representative. ',
    { voice: 'alice', language: 'en-GB' });
  });
  return twiml;
};

const likePost = (twiml) => {
  twiml.gather({
    action: '/ivr/instagram_actions',
    numDigits: '1',
    method: 'POST',
  }, (node) => {
    node.say('Liked photo of DESCRIPTION At LOCATION on DATE at TIME by USERNAME. '
    + 'To comment on this photo, please press 2. '
    + 'To share this photo, please press 3. '
    + 'To view next photo, please press 4. '
    + 'To view this user\'s profile, please press 5. '
    + 'To view this photo again please press 6. '
    + 'To repeat these options, please press 9. '
    + 'Please press 0 to speak to a representative. ',
    { voice: 'alice', language: 'en-GB' });
  });
  return twiml;
};

const commentOnPost = (twiml) => {
  twiml.gather({
    action: '/ivr/instagram_actions',
    numDigits: '1',
    method: 'POST',
  }, (node) => {
    node.say('Recording a comment '
    + 'on photo of DESCRIPTION At LOCATION on DATE at TIME by USERNAME, '
    + 'after the beep. To end comment press the pound sign. ',
    { voice: 'alice', language: 'en-GB' });
    node.record({
      transcribe: true,
      transcribeCallback: '/ivr/save_comment',
      playBeep: true,
    });
    node.say('Comment saved.'
    + 'To like this photo, please press 1. '
    + 'To comment again, please press 2. '
    + 'To share this photo, please press 3. '
    + 'To view next photo, please press 4. '
    + 'To view this user\'s profile, please press 5. '
    + 'To view this photo again please press 6. '
    + 'To repeat these options, please press 9. '
    + 'Please press 0 to speak to a representative. ',
    { voice: 'alice', language: 'en-GB' });
  });
  return twiml;
};

const repeatPostOptions = (twiml) => {
  twiml.gather({
    action: '/ivr/instagram_actions',
    numDigits: '1',
    method: 'POST',
  }, (node) => {
    node.say('To like this photo, please press 1. '
    + 'To comment on this photo, please press 2. '
    + 'To share this photo, please press 3. '
    + 'To view next photo, please press 4. '
    + 'To view this user\'s profile, please press 5. '
    + 'To view this photo again please press 6. '
    + 'To repeat these options, please press 9. '
    + 'Please press 0 to speak to a representative. ',
    { voice: 'alice', language: 'en-GB' });
  });
  return twiml;
};

const operator = (twiml) => {
  twiml.dial('+12063312167');
};
const redirectWelcome = () => {
  const twiml = new twilio.TwimlResponse();
  twiml.say('Returning to the main menu', { voice: 'alice', language: 'en-GB' });
  twiml.redirect('/ivr/welcome');
  return twiml;
};

// POST: '/ivr/welcome'
router.post('/welcome', twilio.webhook({ validate: false }), (request, response) => {
  const twiml = new twilio.TwimlResponse();
  twiml.gather({
    action: '/ivr/menu',
    numDigits: '10',
    method: 'POST',
  }, (node) => {
    node.say('Welcome to Instagram. Capture and Share the World\'s Moments, ' +
      'Brought to you by Analogue Social; ' +
      'surfing the information superhighway at the pace of yesterday. ' +
      'To view your Instagram feed, please press 1. ' +
      'If you are on a rotary telephone please hold for an operator.');
  });
  response.send(twiml);
});

// POST: '/ivr/menu'
router.post('/menu', twilio.webhook({ validate: false }), (request, response) => {
  const selectedOption = request.body.Digits;
  const optionActions = {
    1: viewPost,
  };

  if (optionActions[selectedOption]) {
    const twiml = new twilio.TwimlResponse();
    optionActions[selectedOption](twiml);
    response.send(twiml);
  }
  response.send(redirectWelcome());
});

// POST: '/ivr/instagram_actions'
router.post('/instagram_actions', twilio.webhook({ validate: false }), (request, response) => {
  const selectedOption = request.body.Digits;
  const optionActions = {
    1: likePost,
    2: commentOnPost,
    3: notImpl, // sharePost
    4: notImpl, // nextPost
    5: notImpl, // profilePhoto
    6: viewPost,
    9: repeatPostOptions,
    0: operator,
  };

  if (optionActions[selectedOption]) {
    const twiml = new twilio.TwimlResponse();
    twiml.dial(optionActions[selectedOption]);
    response.send(twiml);
  }
  response.send(redirectWelcome());
});

export default router;
<commit_msg>Refactoring<commit_after>import express from 'express';
import twilio from 'twilio';

import describeImage from './describe-image';

// eslint-disable-next-line new-cap
const router = express.Router();
const notImpl = (twiml) => {
  twiml.say('Functionality not implemented. Goodbye.');
  twiml.hangup();
};

const sayInstagramActions = () => {
  // TODO: like or unlike
  // TODO: comment again perhaps
  const actions = [
    'To like this photo, please press 1',
    'To comment on this photo, please press 2',
    'To share this photo, please press 3',
    'To view next photo, please press 4',
    'To view this user\'s profile, please press 5',
    'To view this photo again please press 6',
    'To repeat these options, please press 9',
    'Please press 0 to speak to a representative',
  ];
  return actions.join('. ');
};
const viewPost = (twiml) => {
  twiml.gather({
    action: '/ivr/instagram_actions',
    numDigits: '1',
    method: 'POST',
  }, (node) => {
    node.say('At LOCATION on DATE at TIME, USERNAME took a photo of DESCRIPTION. '
    + sayInstagramActions(),
    { voice: 'alice', language: 'en-GB' });
  });
  return twiml;
};

const likePost = (twiml) => {
  twiml.gather({
    action: '/ivr/instagram_actions',
    numDigits: '1',
    method: 'POST',
  }, (node) => {
    node.say('Liked photo of DESCRIPTION At LOCATION on DATE at TIME by USERNAME. '
    + sayInstagramActions(),
    { voice: 'alice', language: 'en-GB' });
  });
  return twiml;
};

const commentOnPost = (twiml) => {
  twiml.gather({
    action: '/ivr/instagram_actions',
    numDigits: '1',
    method: 'POST',
  }, (node) => {
    node.say('Recording a comment '
    + 'on photo of DESCRIPTION At LOCATION on DATE at TIME by USERNAME, '
    + 'after the beep. To end comment press the pound sign. ',
    { voice: 'alice', language: 'en-GB' });
    node.record({
      transcribe: true,
      transcribeCallback: '/ivr/save_comment',
      playBeep: true,
    });
    node.say('Comment saved. ' + sayInstagramActions(),
    { voice: 'alice', language: 'en-GB' });
  });
  return twiml;
};

const repeatPostOptions = (twiml) => {
  twiml.gather({
    action: '/ivr/instagram_actions',
    numDigits: '1',
    method: 'POST',
  }, (node) => {
    node.say(sayInstagramActions(),
    { voice: 'alice', language: 'en-GB' });
  });
  return twiml;
};

const operator = (twiml) => {
  twiml.dial('+12063312167');
};
const redirectWelcome = () => {
  const twiml = new twilio.TwimlResponse();
  twiml.say('Returning to the main menu', { voice: 'alice', language: 'en-GB' });
  twiml.redirect('/ivr/welcome');
  return twiml;
};

// POST: '/ivr/welcome'
router.post('/welcome', twilio.webhook({ validate: false }), (request, response) => {
  const twiml = new twilio.TwimlResponse();
  twiml.gather({
    action: '/ivr/menu',
    numDigits: '10',
    method: 'POST',
  }, (node) => {
    node.say('Welcome to Instagram. Capture and Share the World\'s Moments, ' +
      'Brought to you by Analogue Social; ' +
      'surfing the information superhighway at the pace of yesterday. ' +
      'To view your Instagram feed, please press 1. ' +
      'If you are on a rotary telephone please hold for an operator.');
  });
  response.send(twiml);
});

// POST: '/ivr/menu'
router.post('/menu', twilio.webhook({ validate: false }), (request, response) => {
  const selectedOption = request.body.Digits;
  const optionActions = {
    1: viewPost,
  };

  if (optionActions[selectedOption]) {
    const twiml = new twilio.TwimlResponse();
    optionActions[selectedOption](twiml);
    response.send(twiml);
  }
  response.send(redirectWelcome());
});

// POST: '/ivr/instagram_actions'
router.post('/instagram_actions', twilio.webhook({ validate: false }), (request, response) => {
  const selectedOption = request.body.Digits;
  const optionActions = {
    1: likePost,
    2: commentOnPost,
    3: notImpl, // sharePost
    4: notImpl, // nextPost
    5: notImpl, // profilePhoto
    6: viewPost,
    9: repeatPostOptions,
    0: operator,
  };

  if (optionActions[selectedOption]) {
    const twiml = new twilio.TwimlResponse();
    twiml.dial(optionActions[selectedOption]);
    response.send(twiml);
  }
  response.send(redirectWelcome());
});

export default router;
<|endoftext|>


<commit_before>const winston = require('winston');
const { format } = winston;
// const { combine, label, json, cli } = format;
// load the config file
var nconf = require('nconf');
var conf_file = './config/config.json';
nconf.file({file: conf_file});
nconf.load();

var loglevel = nconf.get('global:loglevel');

winston.loggers.add('pagermon', {
    format: format.combine(
        format.colorize(),
        format.label({ label: '[pmon]' }),
        format.timestamp({format:"YYYY-MM-DD HH:MM:SS"}),
        format.prettyPrint(),
        format.printf(
            info => `${info.label}  ${info.timestamp}  ${info.level} : ${info.message}`
        )
    ),
    transports: [
        new winston.transports.File({
            level: loglevel,
            filename: './logs/pagermon.log',
            handleExceptions: true,
            maxsize: 10485760,
            maxFiles: 5
        }),
        new winston.transports.Console({
            level: loglevel,
            handleExceptions: true
        })
    ]
});

winston.loggers.add('http', {
    format: format.combine(
        format.colorize(),
        format.label({ label: '[http]' }),
        format.timestamp({format:"YYYY-MM-DD HH:MM:SS"}),
        format.printf(
            info => `${info.message}`
        )
    ),
    transports: [
        new winston.transports.File({
            level: 'debug',
            filename: './logs/http.log',
            handleExceptions: true,
            maxsize: 10485760,
            maxFiles: 5
        }),
        new winston.transports.Console({
            level: loglevel,
            handleExceptions: true
        })
    ]
});

module.exports = {
    main: winston.loggers.get('pagermon'),
    http: winston.loggers.get('http')
}
module.exports.http.stream = {
    write: function(message, encoding){
        var httpLog = winston.loggers.get('http');
        httpLog.debug(message.substring(0,message.lastIndexOf('\n')));
    }
};<commit_msg>Fix Winston<commit_after>const winston = require('winston');
const { format } = winston;
// const { combine, label, json, cli } = format;
// load the config file
var nconf = require('nconf');
var conf_file = './config/config.json';
nconf.file({file: conf_file});
nconf.load();

var loglevel = nconf.get('global:loglevel');

winston.loggers.add('pagermon', {
    format: format.combine(
        format.colorize(),
        format.label({ label: '[pmon]' }),
        format.timestamp({format:"YYYY-MM-DD HH:mm:ss"}),
        format.prettyPrint(),
        format.printf(
            info => `${info.label}  ${info.timestamp}  ${info.level} : ${info.message}`
        )
    ),
    transports: [
        new winston.transports.File({
            level: loglevel,
            filename: './logs/pagermon.log',
            handleExceptions: true,
            maxsize: 10485760,
            maxFiles: 5
        }),
        new winston.transports.Console({
            level: loglevel,
            handleExceptions: true
        })
    ]
});

winston.loggers.add('http', {
    format: format.combine(
        format.colorize(),
        format.label({ label: '[http]' }),
        format.timestamp({format:"YYYY-MM-DD HH:MM:SS"}),
        format.printf(
            info => `${info.message}`
        )
    ),
    transports: [
        new winston.transports.File({
            level: 'debug',
            filename: './logs/http.log',
            handleExceptions: true,
            maxsize: 10485760,
            maxFiles: 5
        }),
        new winston.transports.Console({
            level: loglevel,
            handleExceptions: true
        })
    ]
});

module.exports = {
    main: winston.loggers.get('pagermon'),
    http: winston.loggers.get('http')
}
module.exports.http.stream = {
    write: function(message, encoding){
        var httpLog = winston.loggers.get('http');
        httpLog.debug(message.substring(0,message.lastIndexOf('\n')));
    }
};
<|endoftext|>


<commit_before>const winston = require('winston');

let Log = {};

winston.setLevels(winston.config.syslog.levels);
winston.remove(winston.transports.Console);
winston.add(winston.transports.Console, {
  colorize: true,
  level: (() => {
    const levels = ['emerg', 'alert', 'crit', 'error', 'warning', 'notice', 'info', 'debug'];

    for (const level of levels) {
      Log[level] = (message, ...placeholders) => {
        winston.log(level, message, ...placeholders);
      };
    }

    let desiredLogLevel = (process.env.LOG_LEVEL || '').toLowerCase() || 'debug';

    if (!levels.includes(desiredLogLevel)) {
      process.emitWarning(`Invalid log level: ${desiredLogLevel}`, {
        code: 'SETUP_WARNING',
        detail: `Permitted log levels are: ${levels.join(', ')}. The application will log at debug level now.`
      });
    }
  })()
});

Log = Object.freeze(Log);
module.exports = Log;<commit_msg>rm log colours<commit_after>const winston = require('winston');

let Log = {};

winston.setLevels(winston.config.syslog.levels);
winston.remove(winston.transports.Console);
winston.add(winston.transports.Console, {
  colorize: false,
  level: (() => {
    const levels = ['emerg', 'alert', 'crit', 'error', 'warning', 'notice', 'info', 'debug'];

    for (const level of levels) {
      Log[level] = (message, ...placeholders) => {
        winston.log(level, message, ...placeholders);
      };
    }

    let desiredLogLevel = (process.env.LOG_LEVEL || '').toLowerCase() || 'debug';

    if (!levels.includes(desiredLogLevel)) {
      process.emitWarning(`Invalid log level: ${desiredLogLevel}`, {
        code: 'SETUP_WARNING',
        detail: `Permitted log levels are: ${levels.join(', ')}. The application will log at debug level now.`
      });
    }
  })()
});

Log = Object.freeze(Log);
module.exports = Log;<|endoftext|>


<commit_before>var async = require('async'),
    config = require('./config'),
    child_process = require('child_process'),
    db = require('./db'),
    formidable = require('formidable'),
    fs = require('fs'),
    im = require('imagemagick'),
    path = require('path'),
    util = require('util');

function get_thumb_specs(w, h, pinky) {
	var QUALITY = config[pinky ? 'PINKY_QUALITY' : 'THUMB_QUALITY'];
	var bound = config[pinky ? 'PINKY_DIMENSIONS' : 'THUMB_DIMENSIONS'];
	var r = Math.max(w / bound[0], h / bound[1], 1);
	return {dims: [Math.round(w/r), Math.round(h/r)], quality: QUALITY};
}

exports.ImageUpload = function (clients, allocate_post, status) {
	this.clients = clients;
	this.allocate_post = allocate_post;
	this.status = status;
};

var IU = exports.ImageUpload.prototype;

var validFields = ['client_id', 'alloc'];

IU.handle_request = function (req, resp) {
	this.resp = resp;
	var form = new formidable.IncomingForm();
	form.maxFieldsSize = 2048;
	form.onPart = function (part) {
		if (part.filename && part.name == 'image')
			form.handlePart(part);
		else if (!part.filename && validFields.indexOf(part.name) >= 0)
			form.handlePart(part);
		else
			this._error('Superfluous field.');
	};
	form.parse(req, this.parse_form.bind(this));
};

IU.parse_form = function (err, fields, files) {
	if (err) {
		console.error("Upload error: " + err);
		return this.failure('Invalid upload.');
	}
	var image = files.image;
	if (!image)
		return this.failure('No image.');
	this.image = image;
	var client = this.clients[fields.client_id];
	if (!client)
		return this.failure('Invalid client id.');
	this.client = client;

	if (client.uploading) {
		this.failure('Already uploading.');
		/* previous line negated client.uploading, so restore it */
		client.uploading = true;
		return;
	}
	client.uploading = true;
	if (client.post && client.post.image)
		return this.failure('Image already exists.');
	image.ext = path.extname(image.filename).toLowerCase();
	if (image.ext == '.jpeg')
		image.ext = '.jpg';
	if (['.png', '.jpg', '.gif'].indexOf(image.ext) < 0)
		return this.failure('Invalid image format.');
	if (fields.alloc) {
		try {
			this.alloc = JSON.parse(fields.alloc);
		}
		catch (e) {
			return this.failure('Bad alloc.');
		}
	}
	else if (!client.post)
		return this.failure('Missing alloc.');
	image.imgnm = image.filename.substr(0, 256);
	client.db.check_throttle(client.ip, this.process.bind(this));
}

IU.process = function (err) {
	if (err)
		return this.failure(err);
	this.status('Verifying...');
	var image = this.image;
	var tagged_path = image.ext.replace('.', '') + ':' + image.path;
	var self = this;
	async.parallel({
		MD5: MD5_file.bind(null, image.path),
		stat: fs.stat.bind(fs, image.path),
		dims: im.identify.bind(im, tagged_path)
	}, function (err, rs) {
		if (err) {
			console.error(err);
			return self.failure('Bad image.');
		}
		image.MD5 = rs.MD5;
		image.size = rs.stat.size;
		image.dims = [rs.dims.width, rs.dims.height];
		self.client.db.check_duplicate(image.MD5,
	function (err) {
		if (err)
			return self.failure(err);
		var w = rs.dims.width, h = rs.dims.height;
		if (!w || !h)
			return self.failure('Invalid image dimensions.');
		if (w > config.IMAGE_WIDTH_MAX)
			return self.failure('Image is too wide.');
		if (h > config.IMAGE_HEIGHT_MAX)
			return self.failure('Image is too tall.');
		image.thumb_path = image.path + '_thumb';
		self.status('Thumbnailing...');
		var pinky = (self.client.post && self.client.post.op) ||
				(self.alloc && self.alloc.op);
		var specs = get_thumb_specs(w, h, pinky);
		image.dims = [w, h].concat(specs.dims);
		self.resize_image(tagged_path, image.ext, image.thumb_path,
				specs.dims, specs.quality,
	function () {
		self.status('Publishing...');
		var time = new Date().getTime();
		image.src = time + image.ext;
		image.thumb = time + '.jpg';
		var dest = path.join(config.MEDIA_DIR, 'src', image.src);
		var nail = path.join(config.MEDIA_DIR, 'thumb', image.thumb);
		async.parallel([mv_file.bind(null, image.path, dest),
				mv_file.bind(null, image.thumb_path, nail)],
				function (err, rs) {
			if (err) {
				console.error(err);
				return self.failure("Distro failure.");
			}
			image.path = dest;
			image.thumb_path = nail;
			self.publish();
		});

	});
	});
	});
}

IU.read_image_filesize = function (callback) {
	var self = this;
	fs.stat(this.image.path, function (err, stat) {
		if (err) {
			console.error(err);
			callback('Internal filesize error.');
		}
		else if (stat.size > config.IMAGE_FILESIZE_MAX)
			callback('File is too large.');
		else
			callback(null, stat.size);
	});
};

function MD5_file(path, callback) {
	child_process.exec('md5sum -b ' + path, function (err, stdout, stderr) {
		if (!err) {
			var m = stdout.match(/^([\da-f]{32})/);
			if (m)
				return callback(null, m[1]);
		}
		console.log(stdout);
		console.error(stderr);
		return callback('Hashing error.');
	});
};

function mv_file(src, dest, callback) {
	mv = child_process.spawn('/bin/mv', ['-n', src, dest]);
	mv.on('error', callback);
	mv.stderr.on('data', function (buf) {
		process.stderr.write(buf);
	});
	mv.on('exit', function (code) {
		callback(code ? 'mv error' : null);
	});
};

exports.bury_image = function (src, thumb, callback) {
	/* Just in case */
	var m = /^\d+\.\w+$/;
	if (!src.match(m) || !thumb.match(m))
		return callback('Invalid images.');
	async.parallel([mv.bind(null, 'src', src),
			mv.bind(null, 'thumb', thumb)], callback);
	function mv(p, nm, cb) {
		mv_file(path.join(config.MEDIA_DIR, p, nm),
			path.join(config.DEAD_DIR, p, nm), cb);
	}
};

IU.resize_image = function (src, ext, dest, dims, quality, callback) {
	var self = this;
	var args = [];
	if (ext == '.jpg')
		args.push('-define', 'jpeg:size=' + (dims[0] * 2) + 'x' +
				(dims[1] * 2));
	dims = dims[0] + 'x' + dims[1];
	args.push(src + '[0]', '-gamma', '0.454545', '-filter', 'box',
			'-resize', dims + '!', '-gamma', '2.2', '-strip',
			'-background', 'white', '-mosaic', '+matte',
			'-quality', ''+quality, 'jpg:' + dest);
	im.convert(args, function (err, stdout, stderr) {
		if (err) {
			console.error(stderr);
			return self.failure('Conversion error.');
		}
		if (config.DEBUG)
			setTimeout(callback, 1000);
		else
			callback();
	});
};

IU.failure = function (err_desc) {
	this.iframe_call('postForm.upload_error', err_desc);
	var image = this.image;
	if (image) {
		if (image.path)
			fs.unlink(image.path);
		if (image.thumb_path)
			fs.unlink(image.thumb_path);
	}
	if (this.client)
		this.client.uploading = false;
};

exports.image_attrs = ['src', 'thumb', 'dims', 'size', 'MD5', 'imgnm'];

IU.publish = function () {
	var client = this.client;
	var view = {};
	var self = this;
	exports.image_attrs.forEach(function (key) {
		view[key] = self.image[key];
	});
	if (client.post) {
		/* Text beat us here, discard alloc (if any) */
		client.db.add_image(client.post, view, function (err) {
			if (err || !client.post)
				return self.failure("Publishing failure.");
			client.post.image = view;
			client.uploading = false;
			self.iframe_call('postForm.upload_complete', view);
		});
		return;
	}
	self.allocate_post(self.alloc, view, client, function (err, alloc) {
		if (err) {
			console.error(err);
			return self.failure('Bad post.');
		}
		client.uploading = false;
		self.iframe_call('postForm.on_allocation', alloc);
	});
};

IU.iframe_call = function (func, param) {
	var resp = this.resp;
	param = param ? JSON.stringify(param) : '';
	resp.writeHead(200, {'Content-Type': 'text/html; charset=UTF-8'});
	resp.end('<!doctype html>\n<title></title>\n<script>'
		+ 'parent.' + func + '(' + param + ');</script>');
};
<commit_msg>Match thumbnail background to post background<commit_after>var async = require('async'),
    config = require('./config'),
    child_process = require('child_process'),
    db = require('./db'),
    formidable = require('formidable'),
    fs = require('fs'),
    im = require('imagemagick'),
    path = require('path'),
    util = require('util');

function get_thumb_specs(w, h, pinky) {
	var QUALITY = config[pinky ? 'PINKY_QUALITY' : 'THUMB_QUALITY'];
	var bound = config[pinky ? 'PINKY_DIMENSIONS' : 'THUMB_DIMENSIONS'];
	var r = Math.max(w / bound[0], h / bound[1], 1);
	var bg = pinky ? '#d6daf0' : '#eef2ff';
	return {dims: [Math.round(w/r), Math.round(h/r)], quality: QUALITY,
			bg_color: bg};
}

exports.ImageUpload = function (clients, allocate_post, status) {
	this.clients = clients;
	this.allocate_post = allocate_post;
	this.status = status;
};

var IU = exports.ImageUpload.prototype;

var validFields = ['client_id', 'alloc'];

IU.handle_request = function (req, resp) {
	this.resp = resp;
	var form = new formidable.IncomingForm();
	form.maxFieldsSize = 2048;
	form.onPart = function (part) {
		if (part.filename && part.name == 'image')
			form.handlePart(part);
		else if (!part.filename && validFields.indexOf(part.name) >= 0)
			form.handlePart(part);
		else
			this._error('Superfluous field.');
	};
	form.parse(req, this.parse_form.bind(this));
};

IU.parse_form = function (err, fields, files) {
	if (err) {
		console.error("Upload error: " + err);
		return this.failure('Invalid upload.');
	}
	var image = files.image;
	if (!image)
		return this.failure('No image.');
	this.image = image;
	var client = this.clients[fields.client_id];
	if (!client)
		return this.failure('Invalid client id.');
	this.client = client;

	if (client.uploading) {
		this.failure('Already uploading.');
		/* previous line negated client.uploading, so restore it */
		client.uploading = true;
		return;
	}
	client.uploading = true;
	if (client.post && client.post.image)
		return this.failure('Image already exists.');
	image.ext = path.extname(image.filename).toLowerCase();
	if (image.ext == '.jpeg')
		image.ext = '.jpg';
	if (['.png', '.jpg', '.gif'].indexOf(image.ext) < 0)
		return this.failure('Invalid image format.');
	if (fields.alloc) {
		try {
			this.alloc = JSON.parse(fields.alloc);
		}
		catch (e) {
			return this.failure('Bad alloc.');
		}
	}
	else if (!client.post)
		return this.failure('Missing alloc.');
	image.imgnm = image.filename.substr(0, 256);
	client.db.check_throttle(client.ip, this.process.bind(this));
}

IU.process = function (err) {
	if (err)
		return this.failure(err);
	this.status('Verifying...');
	var image = this.image;
	var tagged_path = image.ext.replace('.', '') + ':' + image.path;
	var self = this;
	async.parallel({
		MD5: MD5_file.bind(null, image.path),
		stat: fs.stat.bind(fs, image.path),
		dims: im.identify.bind(im, tagged_path)
	}, function (err, rs) {
		if (err) {
			console.error(err);
			return self.failure('Bad image.');
		}
		image.MD5 = rs.MD5;
		image.size = rs.stat.size;
		image.dims = [rs.dims.width, rs.dims.height];
		self.client.db.check_duplicate(image.MD5,
	function (err) {
		if (err)
			return self.failure(err);
		var w = rs.dims.width, h = rs.dims.height;
		if (!w || !h)
			return self.failure('Invalid image dimensions.');
		if (w > config.IMAGE_WIDTH_MAX)
			return self.failure('Image is too wide.');
		if (h > config.IMAGE_HEIGHT_MAX)
			return self.failure('Image is too tall.');
		image.thumb_path = image.path + '_thumb';
		self.status('Thumbnailing...');
		var pinky = (self.client.post && self.client.post.op) ||
				(self.alloc && self.alloc.op);
		var specs = get_thumb_specs(w, h, pinky);
		image.dims = [w, h].concat(specs.dims);
		self.resize_image(tagged_path, image.ext, image.thumb_path,
				specs.dims, specs.quality, specs.bg_color,
	function () {
		self.status('Publishing...');
		var time = new Date().getTime();
		image.src = time + image.ext;
		image.thumb = time + '.jpg';
		var dest = path.join(config.MEDIA_DIR, 'src', image.src);
		var nail = path.join(config.MEDIA_DIR, 'thumb', image.thumb);
		async.parallel([mv_file.bind(null, image.path, dest),
				mv_file.bind(null, image.thumb_path, nail)],
				function (err, rs) {
			if (err) {
				console.error(err);
				return self.failure("Distro failure.");
			}
			image.path = dest;
			image.thumb_path = nail;
			self.publish();
		});

	});
	});
	});
}

IU.read_image_filesize = function (callback) {
	var self = this;
	fs.stat(this.image.path, function (err, stat) {
		if (err) {
			console.error(err);
			callback('Internal filesize error.');
		}
		else if (stat.size > config.IMAGE_FILESIZE_MAX)
			callback('File is too large.');
		else
			callback(null, stat.size);
	});
};

function MD5_file(path, callback) {
	child_process.exec('md5sum -b ' + path, function (err, stdout, stderr) {
		if (!err) {
			var m = stdout.match(/^([\da-f]{32})/);
			if (m)
				return callback(null, m[1]);
		}
		console.log(stdout);
		console.error(stderr);
		return callback('Hashing error.');
	});
};

function mv_file(src, dest, callback) {
	mv = child_process.spawn('/bin/mv', ['-n', src, dest]);
	mv.on('error', callback);
	mv.stderr.on('data', function (buf) {
		process.stderr.write(buf);
	});
	mv.on('exit', function (code) {
		callback(code ? 'mv error' : null);
	});
};

exports.bury_image = function (src, thumb, callback) {
	/* Just in case */
	var m = /^\d+\.\w+$/;
	if (!src.match(m) || !thumb.match(m))
		return callback('Invalid images.');
	async.parallel([mv.bind(null, 'src', src),
			mv.bind(null, 'thumb', thumb)], callback);
	function mv(p, nm, cb) {
		mv_file(path.join(config.MEDIA_DIR, p, nm),
			path.join(config.DEAD_DIR, p, nm), cb);
	}
};

IU.resize_image = function (src, ext, dest, dims, qual, bg, callback) {
	var self = this;
	var args = [];
	if (ext == '.jpg')
		args.push('-define', 'jpeg:size=' + (dims[0] * 2) + 'x' +
				(dims[1] * 2));
	dims = dims[0] + 'x' + dims[1];
	args.push(src + '[0]', '-gamma', '0.454545', '-filter', 'box',
			'-resize', dims + '!', '-gamma', '2.2', '-strip',
			'-background', bg, '-mosaic', '+matte',
			'-quality', ''+qual, 'jpg:' + dest);
	im.convert(args, function (err, stdout, stderr) {
		if (err) {
			console.error(stderr);
			return self.failure('Conversion error.');
		}
		if (config.DEBUG)
			setTimeout(callback, 1000);
		else
			callback();
	});
};

IU.failure = function (err_desc) {
	this.iframe_call('postForm.upload_error', err_desc);
	var image = this.image;
	if (image) {
		if (image.path)
			fs.unlink(image.path);
		if (image.thumb_path)
			fs.unlink(image.thumb_path);
	}
	if (this.client)
		this.client.uploading = false;
};

exports.image_attrs = ['src', 'thumb', 'dims', 'size', 'MD5', 'imgnm'];

IU.publish = function () {
	var client = this.client;
	var view = {};
	var self = this;
	exports.image_attrs.forEach(function (key) {
		view[key] = self.image[key];
	});
	if (client.post) {
		/* Text beat us here, discard alloc (if any) */
		client.db.add_image(client.post, view, function (err) {
			if (err || !client.post)
				return self.failure("Publishing failure.");
			client.post.image = view;
			client.uploading = false;
			self.iframe_call('postForm.upload_complete', view);
		});
		return;
	}
	self.allocate_post(self.alloc, view, client, function (err, alloc) {
		if (err) {
			console.error(err);
			return self.failure('Bad post.');
		}
		client.uploading = false;
		self.iframe_call('postForm.on_allocation', alloc);
	});
};

IU.iframe_call = function (func, param) {
	var resp = this.resp;
	param = param ? JSON.stringify(param) : '';
	resp.writeHead(200, {'Content-Type': 'text/html; charset=UTF-8'});
	resp.end('<!doctype html>\n<title></title>\n<script>'
		+ 'parent.' + func + '(' + param + ');</script>');
};
<|endoftext|>


<commit_before>import React, { Component } from 'react';
import { Link, hashHistory } from 'react-router';
import { connect } from 'react-redux';
import styles from './Sidebar.css';
import Project_List from './Project_List';
import { fetchUserProjects } from '../reducers/projects_list';
import { logUserOut } from '../reducers/login';
import * as fs from 'fs';
import * as FEActions from '../../utilities/vcfrontend';
import axios from 'axios';

export class Sidebar extends Component {
  constructor(props) {
    super(props)
    this.localLogUserOut = this.localLogUserOut.bind(this);
    this.linkToHomeView = this.linkToHomeView.bind(this);
    this.onClickArchiveUpdate = this.onClickArchiveUpdate.bind(this);
    this.onClickAddFile = this.onClickAddFile.bind(this);
    this.onClickAddArchive = this.onClickAddArchive.bind(this);
  }

  onClickAddArchive(event) {
    console.log('adding archive to local machine');
    const project = this.props.currents && this.props.currents.currentProject
      ? this.props.currents.currentProject : undefined;
    project && axios.get(`http://localhost:3000/api/vcontrol/${project.id}`)
      .then(project => {
        const projectData = project.data[0];
        // Note the object structure
        // project.commits[0].blob.files[0];

        // create the directory if it doesn't already exist
        const dirPath = `./${projectData.name}`;
        try {
          fs.statSync(dirPath).isDirectory()
        } catch (err) {
          fs.mkdirSync(dirPath);
        }
        // create the file if it doesn't already exist
        // NOTE: WILL CONTAIN MOST RECENT DATA
        const commits = projectData.commits;
        const fileData = commits[commits.length - 1].blob.files[0];
        const filePath = `${dirPath}/${fileData.file_name}.txt`
        fs.writeFileSync(filePath, fileData.file_contents, 'utf-8');

        // then create the .archive directory
        try {
          fs.statSync(`${dirPath}/.archive`).isDirectory()
        } catch (err) {
          FEActions.initNewProject(dirPath);
        }

        // then create all the .archive files
        projectData.commits.forEach((commit, index) => {
          // commitFileChanges(filePath, message, mergeHash, date)
          const fileHash = commit.blob.hash;
          const fileContent = commit.blob.files[0].file_contents;
          FEActions.commitFileChanges(filePath, commit.message, undefined, commit.date, fileHash, fileContent);
        })
      })
  }

  onClickArchiveUpdate(event) {
  }

  onClickAddFile(event) {
    // Hardcode this to the current filePath since we're only doing one file
    const project = this.props.currents && this.props.currents.currentProject
      ? this.props.currents.currentProject : undefined;
    const dirPath = `./${project.name}`;
    const fileData = project ? project.commits[project.commits.length - 1].blob.files[0] : undefined;
    console.log(fileData);
    const filePath = project && fileData
      ? `./${project.name}/${fileData.file_name}.txt` : undefined;
    if (filePath) {
      // Check to make sure the file exists first
      try {
        fs.statSync(filePath).isFile();
      } catch (err) {
        console.log(`file ${filePath} does not exist!`);
        return false;
      }

      const commitFileContents = fileData.file_contents;
      const localFileContents = fs.readFileSync(filePath, 'utf-8');

      // Check to make sure there are changes to be sent to server
      if (commitFileContents === localFileContents) {
        console.log('no changes made!');
        return false;
      }

      const project = this.props.currents && this.props.currents.currentProject
        ? this.props.currents.currentProject : undefined;

      const newCommit = {
        previousCommit: fs.readFileSync(`./${dirPath}/.archive/refs/${fileData.file_name}`, 'utf-8'),
        // hard coded message
        date: new Date(),
        message: 'merging file updates to server',
        committer: `${this.props.loginUser.first_name} ${this.props.loginUser.last_name}`,
        projectId: project.id,
        file_name: fileData.file_name,
        file_contents: localFileContents
      }
      // Need to create the new objs for commit and new file
      newCommit.hash = FEActions.getSha1Hash(`${newCommit.file_name}${newCommit.file_contents}${newCommit.message}`);
      newCommit.fileHash = FEActions.getSha1Hash(`${newCommit.file_name}${newCommit.file_contents}`);
      FEActions.commitFileChanges(filePath, newCommit.message, undefined,
        newCommit.date, newCommit.fileHash, newCommit.file_contents);

      project && axios.post(`http://localhost:3000/api/vcontrol/${project.id}`, newCommit)
        .then(res => console.log(res))
        .catch(err => console.error(err));

      // Need to trigger the project_list to re-render the latest commit
    }
  }

  componentDidUpdate() {
    if (this.props.loginUser.id && !Object.keys(this.props.projects).length) {
      this.props.onLoadProjects(this.props.loginUser.id);
    }
  }

  linkToHomeView() {
    hashHistory.push('/mainHome');
  }

  localLogUserOut() {
    this.props.logMeOut();
  }

  render() {
    return (

            <Link>
               hashHistory.push('/info')}>
                info

            </Link>

              <button className="btn-floating btn-large waves-effect waves-light pink accent-1 left"
                onClick={this.onClickAddArchive}>
                queue
              </button>

              <button className="btn-floating btn-large waves-effect waves-light light-green accent-3 left"
                onClick={this.onClickArchiveUpdate}>
                play_for_work
              </button>

              <button className="btn-floating btn-large waves-effect waves-light light-blue accent-2 left"
                onClick={this.onClickAddFile}>
                trending_up
              </button>

            <Link onClick={this.linkToHomeView}>

Welcome, {this.props.loginUser.first_name}

              person_pin
            </Link>

            <Link to={'/'}>

Logout

            </Link>

            <Project_List />

    );
  }
}

function mapStateToProps(state) {
  return {
    loginUser: state.login,
    projects: state.projects,
    currents: state.currents
  }
}

function mapDispatchToProps(dispatch) {
  return {
    onLoadProjects: function (user) {
      dispatch(fetchUserProjects(user));
    },
    logMeOut: function () {
      dispatch(logUserOut());
    }
  }
}

export default connect(
  mapStateToProps,
  mapDispatchToProps
)(Sidebar);
<commit_msg>project_list now rerenders on server update<commit_after>import React, { Component } from 'react';
import { Link, hashHistory } from 'react-router';
import { connect } from 'react-redux';
import styles from './Sidebar.css';
import Project_List from './Project_List';
import { fetchUserProjects } from '../reducers/projects_list';
import { logUserOut } from '../reducers/login';
import * as fs from 'fs';
import * as FEActions from '../../utilities/vcfrontend';
import axios from 'axios';

export class Sidebar extends Component {
  constructor(props) {
    super(props)
    this.localLogUserOut = this.localLogUserOut.bind(this);
    this.linkToHomeView = this.linkToHomeView.bind(this);
    this.onClickArchiveUpdate = this.onClickArchiveUpdate.bind(this);
    this.onClickAddFile = this.onClickAddFile.bind(this);
    this.onClickAddArchive = this.onClickAddArchive.bind(this);
  }

  onClickAddArchive(event) {
    console.log('adding archive to local machine');
    const project = this.props.currents && this.props.currents.currentProject
      ? this.props.currents.currentProject : undefined;
    project && axios.get(`http://localhost:3000/api/vcontrol/${project.id}`)
      .then(project => {
        const projectData = project.data[0];
        // Note the object structure
        // project.commits[0].blob.files[0];

        // create the directory if it doesn't already exist
        const dirPath = `./${projectData.name}`;
        try {
          fs.statSync(dirPath).isDirectory()
        } catch (err) {
          fs.mkdirSync(dirPath);
        }
        // create the file if it doesn't already exist
        // NOTE: WILL CONTAIN MOST RECENT DATA
        const commits = projectData.commits;
        const fileData = commits[commits.length - 1].blob.files[0];
        const filePath = `${dirPath}/${fileData.file_name}.txt`
        fs.writeFileSync(filePath, fileData.file_contents, 'utf-8');

        // then create the .archive directory
        try {
          fs.statSync(`${dirPath}/.archive`).isDirectory()
        } catch (err) {
          FEActions.initNewProject(dirPath);
        }

        // then create all the .archive files
        projectData.commits.forEach((commit, index) => {
          // commitFileChanges(filePath, message, mergeHash, date)
          const fileHash = commit.blob.hash;
          const fileContent = commit.blob.files[0].file_contents;
          FEActions.commitFileChanges(filePath, commit.message, undefined, commit.date, fileHash, fileContent);
        })
      })
  }

  onClickArchiveUpdate(event) {
  }

  onClickAddFile(event) {
    // Hardcode this to the current filePath since we're only doing one file
    const project = this.props.currents && this.props.currents.currentProject
      ? this.props.currents.currentProject : undefined;
    const dirPath = `./${project.name}`;
    const fileData = project ? project.commits[project.commits.length - 1].blob.files[0] : undefined;
    console.log(fileData);
    const filePath = project && fileData
      ? `./${project.name}/${fileData.file_name}.txt` : undefined;
    if (filePath) {
      // Check to make sure the file exists first
      try {
        fs.statSync(filePath).isFile();
      } catch (err) {
        console.log(`file ${filePath} does not exist!`);
        return false;
      }

      const commitFileContents = fileData.file_contents;
      const localFileContents = fs.readFileSync(filePath, 'utf-8');

      // Check to make sure there are changes to be sent to server
      if (commitFileContents === localFileContents) {
        console.log('no changes made!');
        return false;
      }

      const project = this.props.currents && this.props.currents.currentProject
        ? this.props.currents.currentProject : undefined;

      const newCommit = {
        previousCommit: fs.readFileSync(`./${dirPath}/.archive/refs/${fileData.file_name}`, 'utf-8'),
        // hard coded message
        date: new Date(),
        message: 'merging file updates to server',
        committer: `${this.props.loginUser.first_name} ${this.props.loginUser.last_name}`,
        projectId: project.id,
        file_name: fileData.file_name,
        file_contents: localFileContents
      }
      // Need to create the new objs for commit and new file
      newCommit.hash = FEActions.getSha1Hash(`${newCommit.file_name}${newCommit.file_contents}${newCommit.message}`);
      newCommit.fileHash = FEActions.getSha1Hash(`${newCommit.file_name}${newCommit.file_contents}`);
      FEActions.commitFileChanges(filePath, newCommit.message, undefined,
        newCommit.date, newCommit.fileHash, newCommit.file_contents);

      project && axios.post(`http://localhost:3000/api/vcontrol/${project.id}`, newCommit)
        .then(res => console.log(res))
        .catch(err => console.error(err));

      // Need to trigger the project_list to re-render the latest commit
      this.props.fetchProjects(this.props.loginUser.id);
    }
  }

  componentDidUpdate() {
    if (this.props.loginUser.id && !Object.keys(this.props.projects).length) {
      this.props.onLoadProjects(this.props.loginUser.id);
    }
  }

  linkToHomeView() {
    hashHistory.push('/mainHome');
  }

  localLogUserOut() {
    this.props.logMeOut();
  }

  render() {
    return (

            <Link>
               hashHistory.push('/info')}>
                info

            </Link>

              <button className="btn-floating btn-large waves-effect waves-light pink accent-1 left"
                onClick={this.onClickAddArchive}>
                queue
              </button>

              <button className="btn-floating btn-large waves-effect waves-light light-green accent-3 left"
                onClick={this.onClickArchiveUpdate}>
                play_for_work
              </button>

              <button className="btn-floating btn-large waves-effect waves-light light-blue accent-2 left"
                onClick={this.onClickAddFile}>
                trending_up
              </button>

            <Link onClick={this.linkToHomeView}>

Welcome, {this.props.loginUser.first_name}

              person_pin
            </Link>

            <Link to={'/'}>

Logout

            </Link>

            <Project_List />

    );
  }
}

function mapStateToProps(state) {
  return {
    loginUser: state.login,
    projects: state.projects,
    currents: state.currents
  }
}

function mapDispatchToProps(dispatch) {
  return {
    onLoadProjects: function (user) {
      dispatch(fetchUserProjects(user));
    },
    fetchProjects: (userId) => {
      dispatch(fetchUserProjects(userId))
    },
    logMeOut: function () {
      dispatch(logUserOut());
    }
  }
}

export default connect(
  mapStateToProps,
  mapDispatchToProps
)(Sidebar);
<|endoftext|>


<commit_before>import {promisify} from 'util';
import path from 'path';
import {execSync} from 'child_process';
import {EventEmitter} from 'events';

import tryCatch from 'try-catch';
import versionio from 'version-io';
import changelog from 'changelog-io';
import rendy from 'rendy';
import series from 'async/series.js';
import minor from 'minor';
import jessy from 'jessy';
import {readPackageUp} from 'read-pkg-up';
import fullstore from 'fullstore';
import currify from 'currify';

import {validatePackage} from './validate-package.js';
import {parseCommitType} from './parse-commit-type.js';
import {release} from './release.js';

import _prepend from 'prepend';

const prepend = promisify(_prepend);

import runWisdom from './run-wisdom.js';
import getEnv from './get-env.js';

const isUndefined = (a) => typeof a === 'undefined';

const Cmd = [
    'git add --all',
    'git commit -m "chore{{ commitType }} v{{ version }}"',
    'git pull --rebase',
    'git push origin {{ branch }}',
].join('&&');

const CmdTag = [
    'git tag v{{ version }}',
    'git push origin v{{ version }}',
].join('&&');

const chlogStore = fullstore();
const InfoDirStore = fullstore();

const setInfoDir = (data) => {
    const dir = path.dirname(data.path);

    InfoDirStore(dir);

    return data;
};

const getPkg = (data) => data.packageJson;

export default (version) => {
    const emitter = new EventEmitter();

    const onError = (e) => {
        const error = Error(`Error reading package.json: ${e.message}`);
        emitter.emit('error', error);
    };

    const start = currify((version, emitter, info) => {
        if (validatePackage({version, emitter, info}))
            return;

        publish(version, info, emitter);
    });

    readPackageUp()
        .then(setInfoDir)
        .then(getPkg)
        .then(start(version, emitter))
        .catch(onError);

    return emitter;
};

function publish(version, info, emitter) {
    let type = '--';

    if (!version.indexOf('v')) {
        version = version.slice(1);
        type += 'v' + version;
    } else if (/major|minor|patch/.test(version)) {
        type += version;
        version = minor(version, info.version);
    }

    const cmd = rendy(Cmd, {
        version,
        commitType: parseCommitType(info.commitType),
        branch: info.branch || 'master',
    });

    const cmdTag = rendy(CmdTag, {
        version,
    });

    series([
        async function beforePublish() {
            const cmd = jessy('scripts.wisdom', info);

            if (!cmd)
                return;

            await runWisdom('wisdom', '', version, info, emitter);
        },

        async function beforePublishType() {
            const cmd = jessy('scripts.wisdom:type', info);

            if (!cmd)
                return;

            await runWisdom('wisdom:type', type, version, info, emitter);
        },

        async () => {
            if (!isUndefined(info.changelog) && !info.changelog) {
                emitter.emit('data', 'changelog: false\n');
                return;
            }

            const [error, data] = tryCatch(changelog, version);

            if (error) {
                error.message += '\n';
                throw error;
            }

            const name = path.join(InfoDirStore(), 'ChangeLog');

            await prepend(name, data);

            const value = rmLines(data, 2);
            chlogStore(value);
        },

        async () => {
            await versionio(version);
        },

        (callback) => {
            execute(cmd, version, emitter, callback);
        },

        function tag(callback) {
            const isNoTag = !info.tag && !isUndefined(info.tag);

            if (isNoTag) {
                emitter.emit('data', 'tag: false\n');
                return callback();
            }

            execute(cmdTag, version, emitter, callback);
        },

        async function releaseToGitHub(callback) {
            await release({
                version,
                info,
                chlog: chlogStore,
                emitter,
                count: info.releaseTriesCount || 10,
            }),

            callback();
        },

        function npmPublish(callback) {
            if (info.private)
                return callback();

            execute(`npm publish`, version, emitter, callback);
        },

        async function afterPublish() {
            const cmd = jessy('scripts.wisdom:done', info);

            if (!cmd)
                return;

            await runWisdom('wisdom:done', '', version, info, emitter);
        },
    ], (error) => {
        if (error)
            emitter.emit('error', error);

        emitter.emit('exit');
    });

    return emitter;
}

function execute(cmd, version, emitter, callback) {
    const stdio = [0, 1, 2, 'pipe'];

    const [e] = tryCatch(execSync, cmd, {
        env: getEnv(version),
        cwd: InfoDirStore(),
        stdio,
    });

    callback(e);
}

function rmLines(str, count) {
    return str
        .split('\n')
        .slice(count)
        .join('\n');
}

<commit_msg>fix: wisdom: rm callback<commit_after>import {promisify} from 'util';
import path from 'path';
import {execSync} from 'child_process';
import {EventEmitter} from 'events';

import tryCatch from 'try-catch';
import versionio from 'version-io';
import changelog from 'changelog-io';
import rendy from 'rendy';
import series from 'async/series.js';
import minor from 'minor';
import jessy from 'jessy';
import {readPackageUp} from 'read-pkg-up';
import fullstore from 'fullstore';
import currify from 'currify';

import {validatePackage} from './validate-package.js';
import {parseCommitType} from './parse-commit-type.js';
import {release} from './release.js';

import _prepend from 'prepend';

const prepend = promisify(_prepend);

import runWisdom from './run-wisdom.js';
import getEnv from './get-env.js';

const isUndefined = (a) => typeof a === 'undefined';

const Cmd = [
    'git add --all',
    'git commit -m "chore{{ commitType }} v{{ version }}"',
    'git pull --rebase',
    'git push origin {{ branch }}',
].join('&&');

const CmdTag = [
    'git tag v{{ version }}',
    'git push origin v{{ version }}',
].join('&&');

const chlogStore = fullstore();
const InfoDirStore = fullstore();

const setInfoDir = (data) => {
    const dir = path.dirname(data.path);

    InfoDirStore(dir);

    return data;
};

const getPkg = (data) => data.packageJson;

export default (version) => {
    const emitter = new EventEmitter();

    const onError = (e) => {
        const error = Error(`Error reading package.json: ${e.message}`);
        emitter.emit('error', error);
    };

    const start = currify((version, emitter, info) => {
        if (validatePackage({version, emitter, info}))
            return;

        publish(version, info, emitter);
    });

    readPackageUp()
        .then(setInfoDir)
        .then(getPkg)
        .then(start(version, emitter))
        .catch(onError);

    return emitter;
};

function publish(version, info, emitter) {
    let type = '--';

    if (!version.indexOf('v')) {
        version = version.slice(1);
        type += 'v' + version;
    } else if (/major|minor|patch/.test(version)) {
        type += version;
        version = minor(version, info.version);
    }

    const cmd = rendy(Cmd, {
        version,
        commitType: parseCommitType(info.commitType),
        branch: info.branch || 'master',
    });

    const cmdTag = rendy(CmdTag, {
        version,
    });

    series([
        async function beforePublish() {
            const cmd = jessy('scripts.wisdom', info);

            if (!cmd)
                return;

            await runWisdom('wisdom', '', version, info, emitter);
        },

        async function beforePublishType() {
            const cmd = jessy('scripts.wisdom:type', info);

            if (!cmd)
                return;

            await runWisdom('wisdom:type', type, version, info, emitter);
        },

        async () => {
            if (!isUndefined(info.changelog) && !info.changelog) {
                emitter.emit('data', 'changelog: false\n');
                return;
            }

            const [error, data] = tryCatch(changelog, version);

            if (error) {
                error.message += '\n';
                throw error;
            }

            const name = path.join(InfoDirStore(), 'ChangeLog');

            await prepend(name, data);

            const value = rmLines(data, 2);
            chlogStore(value);
        },

        async () => {
            await versionio(version);
        },

        (callback) => {
            execute(cmd, version, emitter, callback);
        },

        function tag(callback) {
            const isNoTag = !info.tag && !isUndefined(info.tag);

            if (isNoTag) {
                emitter.emit('data', 'tag: false\n');
                return callback();
            }

            execute(cmdTag, version, emitter, callback);
        },

        async function releaseToGitHub() {
            await release({
                version,
                info,
                chlog: chlogStore,
                emitter,
                count: info.releaseTriesCount || 10,
            });
        },

        function npmPublish(callback) {
            if (info.private)
                return callback();

            execute(`npm publish`, version, emitter, callback);
        },

        async function afterPublish() {
            const cmd = jessy('scripts.wisdom:done', info);

            if (!cmd)
                return;

            await runWisdom('wisdom:done', '', version, info, emitter);
        },
    ], (error) => {
        if (error)
            emitter.emit('error', error);

        emitter.emit('exit');
    });

    return emitter;
}

function execute(cmd, version, emitter, callback) {
    const stdio = [0, 1, 2, 'pipe'];

    const [e] = tryCatch(execSync, cmd, {
        env: getEnv(version),
        cwd: InfoDirStore(),
        stdio,
    });

    callback(e);
}

function rmLines(str, count) {
    return str
        .split('\n')
        .slice(count)
        .join('\n');
}

<|endoftext|>