<commit_before>var searchAddrTemplate = '{{#if meta.total_count}}\

Resultados
\

\

\
            {{#each results}}\

\
              \

\

{{formatted_address}}
\

\
              \

\
            {{/each}}\

\

\
        {{else}}\

\
          Não foram encontrados resultados\

\
        {{/if}}';

horarioTemplate = '
\

watch_later
\

\
              <fieldset class="clean-fieldset">\

\
                  <input name="id" type="hidden">\

<input class="bottom-border" name="from_hour" type="time">
\

<input class="bottom-border" name="to_hour" type="time">
\

\
                    \
                      <select name="weekday" multiple="multiple">\
                        <option value=1 data-display-as="Seg">Segunda</option>\
                        <option value=2 data-display-as="Ter">Terça</option>\
                        <option value=3 data-display-as="Qua">Quarta</option>\
                        <option value=4 data-display-as="Qui">Quinta</option>\
                        <option value=5 data-display-as="Sex">Sexta</option>\
                        <option value=6 data-display-as="Sáb">Sábado</option>\
                        <option value=7 data-display-as="Dom">Domingo</option>\
                      </select>\

\
                    \

\

\
              </fieldset>\

remove_circle_outline
\

\

';

var resourcize = function(name, resource) {
  //return '/api/' + resource + '/' + encodeURIComponent(name) + '/';
  return '/api/' + resource + '/' + name + '/';
};

Frm7.onPageInit('adicionar', function(page) {
  addForm.page = page;

  // input mask para telefone
  $('#add-telefone').inputmask({
    'mask': '(99) [X]9999-9999',
    'greedy': false,
    'autoUnmask': true,
    'definitions': {
      'X': {
        validator: '[9]',
        cardinality: 1,
        casing: 'upper'
      }
    }
  });

  //limpa local no mapa quando endereço muda
  $('#add-endereco').change(function() {
    addForm.invalidaMapa();
  });

  // atualiza valor visual de slider
  $$('input[type="range"]').on('input change', function(){
    $('#badge-'+this.name).html(this.value);
  });

  // form validation
  $.validator.addMethod("notNull", function(value, element) {
    return value != null;
  }, "Este campo é requerido.");

  $('#add-form').validate({
    ignore: false,
    rules: {
      nome: 'required',
      endereco: 'required',
      lat: 'required',
      long: 'required',
      telefone: {
        required: false,
        minlength: 10,
      },
      comida: {
        notNull: true,
      },
      sinopse: 'required',
    },
    errorPlacement: function(error, element) {
      var placement = $(element).data('error');
      if ('#'+placement) {
        $('#'+placement).append(error)
      } else {
        error.insertAfter(element);
      }
    },
    focusInvalid: false,
    invalidHandler: function(form, validator) { // scroll para primeiro campo invláido
      if (!validator.numberOfInvalids()) return;
      var elem = $(validator.errorList[0].element).parent().parent().parent().parent();
      console.log(elem);
      $$('.page-content').scrollTop(
        elem.position().top, // posição do primeiro erro
        500 // tempo em milis
      );
    }
  });

  addForm.init(page);
});

Frm7.onPageInit('busca-end', function(page) {
    templates.searchAddr = Template7.compile(searchAddrTemplate);
});

var addForm = {
  init: function(page) {
    addForm.data = {};
    addForm.results = [];
    addForm.mapaValido = false;
    addForm.horarioPatch = {
      objects: [],
      deleted_objects: []
    };

    // limpa form
    $('#add-form').trigger('reset');

    // carrega objeto do restaurante se for editar
    if(page.context.id != undefined) {
      addForm.id = page.context.id;
      addForm.editar = true;
      // clona objeto pra evitar alterações
      var obj = JSON.parse(JSON.stringify(data.objects[addForm.id]));
      obj.origem = obj.origem.nome;
      Frm7.formFromData("#add-form", obj);

      var horarios = obj.horarios;
      for(key in horarios) {
        addForm.horarioAdd(horarios[key]);
      }

    } else {
      addForm.id = null;
      addForm.editar = false;
    }
  },

  openStreetSearch: function() {
    if(!$("#add-endereco").valid()) return;
    var search = $("#add-endereco").val();
    mainView.router.load({
      url: 'busca-end.html'
    });
    addForm.searchAddress(search);
  },

  // busca por endereço
  parseSearchAddress: function(json) {
    var obj = json;
    addForm.results = json.results;
    obj.meta = {
      total_count: json.results.length,
    };
    obj.objects = json;
    var html = templates.searchAddr(obj);
    $("#search-addr-results").html(html);

    Frm7.hideIndicator();
  },

  // busca por endereço
  searchAddress: function(search) {
    Frm7.showIndicator();
    var api = "https://maps.googleapis.com/maps/api/geocode/json?region=br";
    var key = "&key=" + keys.gmaps;
    if(!search) search = $("#search-addr-input").val();
    var query = "&address=" + search;
    var url = api + key + query;
    $.getJSON(url, addForm.parseSearchAddress, data.fail);
  },

  // seleciona resultado e coloca no form
  resultAddress: function(id) {
    var obj = addForm.results[id];
    console.log(obj);
    try {
      var rua = $.grep(obj.address_components, function(e){ return e.types.indexOf('route') >= 0; })[0].long_name;
      var num = $.grep(obj.address_components, function(e){ return e.types.indexOf('street_number') >= 0; })[0].long_name;
      var bairro = $.grep(obj.address_components, function(e){ return e.types.indexOf('sublocality') >= 0; })[0].long_name;
      var formatted_address = rua + ", " + num + " - " + bairro;
    } catch (e) {
      var formatted_address = obj.formatted_address;
    }
    formData = {
      'lat': obj.geometry.location.lat,
      'long': obj.geometry.location.lng,
    };
    Frm7.formFromData("#add-form", formData);
    addForm.validaMapa();
    mainView.router.back();
  },

  // usuário escolheu resultado no gmaps
  validaMapa: function() {
    $('#add-endereco-button').removeClass('button-fail');
    $('#add-endereco-button').addClass('button-ok');
    addForm.mapaValido = true;
  },

  // usuário mudou endereço, gmaps não é mais válido
  invalidaMapa: function() {
    $('#add-endereco-button').removeClass('button-ok');
    $('#add-endereco-button').addClass('button-fail');
    var formData = {
      'lat': 0,
      'long': 0,
    };
    Frm7.formFromData("#add-form", formData);
    addForm.mapaValido = false;
  },

  // POST
  formSend: function() {
    if (!$('#add-form').valid()) return false;
    if (!addForm.mapaValido) { // validação dos campos ocultos lat long
      $$('.page-content').scrollTop(
        $('#add-endereco-button').position().top, // posição do botão de endereço
        500 // tempo em milis
      );
      return false;
    }

    var api = "/api/restaurante/";
    if(addForm.editar) {
      var type = 'PUT';
      api = api + addForm.id + '/';
    }
    else { var type = 'POST'; }
    var url = SERVER + api;

    var data = Frm7.formToData('#add-form');
    data.origem = resourcize(data.origem, 'origem');
    data.regiao = resourcize(data.regiao, 'regiao');
    data.comida.forEach(function(part, index, arr) {
      arr[index] = resourcize(part, 'comida');
    });
    data.flags.forEach(function(part, index, arr) {
      arr[index] = resourcize(part, 'flag');
    });
    if(data.preco_min == "") data.preco_min = 0;
    if(data.preco_max == "") data.preco_max = 0;

    console.log(data);
    addForm.data = data;

    /*$.post(url, data,
      function(data) {
        console.log("success");
        console.log(data)
      },
      "json"
    );*/

    $.ajax({
      url: url,
      type: type,
      contentType: 'application/json',
      data: JSON.stringify(data),
      dataType: 'json',
      processData: false,
      complete: addForm.formCheck,
      xhrFields: {
          withCredentials: true
        },
        beforeSend: function(xhr, settings) {
            xhr.setRequestHeader("X-CSRFToken", user.profile.csrf_token);
        }
    })
  },

  formCheck: function(d) {
    if (d.status == 201) { // created
      // aqui deve chamar o patch para horário
      addForm.horarioSend();
      //alert("O restaurante " + addForm.data.nome + " foi enviado com sucesso e aguarda moderação.");
      //mainView.router.back();
    } else if (d.status == 204) { // edited
      // aqui deve chamar o patch para horário
      addForm.horarioSend();
      //alert("O restaurante " + addForm.data.nome + "foi editado com sucesso.");
      //delete data.objects[addForm.id];
      //data.downloadSingle(addForm.id);
    } else { // qualquer outro código
      alert("Ocorreu um erro ao tentar enviar o restaurante!");
    }
  },

  // novo horário na lista
  horarioAdd: function(data) {
    var pai = document.getElementById('list-horarios');
    var elem = document.createElement('li');
    elem.innerHTML = horarioTemplate;
    pai.appendChild(elem);
    // popula com horario
    if(data) {
      $(elem).find('input[name$="id"]').val(data.id);
      $(elem).find('input[name$="from_hour"]').val(data.from_hour);
      $(elem).find('input[name$="to_hour"]').val(data.to_hour);
      $(elem).find('select[name$="weekday"]').val(data.weekday);
      Frm7.initSmartSelects(elem); // isso atualiza o texto no select
    }
  },

  // remove aquele horário da lista
  horarioRemove: function(elem) {
    // isto pega o
 referente ao botão pressionado
    var li = $(elem).parent().parent().parent().parent();
    // se formset deste elemento tem id, adiciona para lista de ids removidos
    var id = li.find('input[name$="id"]').val();
    if(id) addForm.horarioPatch.deleted_objects.push(resourcize(id,'horario'));
    li.remove();
  },

  horarioSend: function() {
    var horario_list = $('#list-horarios').find('fieldset');
    for(var key = 0; key < horario_list.length; key++) {
      var fields = $(horario_list[key]);
      // se não tem id, ou seja, se não é novo
      if(!fields.find('input[name$="id"]').val()) {
        // encontramos todos os dias selecionados
        weekdaylist = fields.find('select[name$="weekday"]').val()
        for (wd in weekdaylist) {
          // criamos o objeto para cada dia na linha de horário
          var obj = {
            from_hour: fields.find('input[name$="from_hour"]').val(),
            to_hour: fields.find('input[name$="to_hour"]').val(),
            weekday: weekdaylist[wd],
            restaurante : '/api/restaurante/9/', //ESTA HARDCODED CAGADO - TROCAR URGENTE
          }
          // e adicionamos ao patch
          addForm.horarioPatch.objects.push(obj);
        }

      }
    }
    console.log(addForm.horarioPatch);

    var api = "/api/horario/";
    var url = SERVER + api;

    $.ajax({
      url: url,
      type: 'PATCH',
      contentType: 'application/json',
      data: JSON.stringify(addForm.horarioPatch),
      dataType: 'json',
      processData: false,
      complete: addForm.horarioCheck,
      xhrFields: {
          withCredentials: true
        },
        beforeSend: function(xhr, settings) {
            xhr.setRequestHeader("X-CSRFToken", user.profile.csrf_token);
        }
    })
  },

  horarioCheck: function(d) {
    if (d.status == 202) { // patch com sucesso
      if(addForm.editar) {
        alert("O restaurante " + addForm.data.nome + " foi editado com sucesso.");
        delete data.objects[addForm.id];
        data.downloadSingle(addForm.id);
      } else {
        alert("O restaurante " + addForm.data.nome + " foi enviado com sucesso e aguarda moderação.");
        mainView.router.back();
      }
    } else { // qualquer outro código
      alert("Ocorreu um erro ao tentar enviar o restaurante!");
    }
  },
};
<commit_msg>envio de horários passa URI do restaurante<commit_after>var searchAddrTemplate = '{{#if meta.total_count}}\

Resultados
\

\

\
            {{#each results}}\

\
              \

\

{{formatted_address}}
\

\
              \

\
            {{/each}}\

\

\
        {{else}}\

\
          Não foram encontrados resultados\

\
        {{/if}}';

horarioTemplate = '
\

watch_later
\

\
              <fieldset class="clean-fieldset">\

\
                  <input name="id" type="hidden">\

<input class="bottom-border" name="from_hour" type="time">
\

<input class="bottom-border" name="to_hour" type="time">
\

\
                    \
                      <select name="weekday" multiple="multiple">\
                        <option value=1 data-display-as="Seg">Segunda</option>\
                        <option value=2 data-display-as="Ter">Terça</option>\
                        <option value=3 data-display-as="Qua">Quarta</option>\
                        <option value=4 data-display-as="Qui">Quinta</option>\
                        <option value=5 data-display-as="Sex">Sexta</option>\
                        <option value=6 data-display-as="Sáb">Sábado</option>\
                        <option value=7 data-display-as="Dom">Domingo</option>\
                      </select>\

\
                    \

\

\
              </fieldset>\

remove_circle_outline
\

\

';

var resourcize = function(name, resource) {
  //return '/api/' + resource + '/' + encodeURIComponent(name) + '/';
  return '/api/' + resource + '/' + name + '/';
};

Frm7.onPageInit('adicionar', function(page) {
  addForm.page = page;

  // input mask para telefone
  $('#add-telefone').inputmask({
    'mask': '(99) [X]9999-9999',
    'greedy': false,
    'autoUnmask': true,
    'definitions': {
      'X': {
        validator: '[9]',
        cardinality: 1,
        casing: 'upper'
      }
    }
  });

  //limpa local no mapa quando endereço muda
  $('#add-endereco').change(function() {
    addForm.invalidaMapa();
  });

  // atualiza valor visual de slider
  $$('input[type="range"]').on('input change', function(){
    $('#badge-'+this.name).html(this.value);
  });

  // form validation
  $.validator.addMethod("notNull", function(value, element) {
    return value != null;
  }, "Este campo é requerido.");

  $('#add-form').validate({
    ignore: false,
    rules: {
      nome: 'required',
      endereco: 'required',
      lat: 'required',
      long: 'required',
      telefone: {
        required: false,
        minlength: 10,
      },
      comida: {
        notNull: true,
      },
      sinopse: 'required',
    },
    errorPlacement: function(error, element) {
      var placement = $(element).data('error');
      if ('#'+placement) {
        $('#'+placement).append(error)
      } else {
        error.insertAfter(element);
      }
    },
    focusInvalid: false,
    invalidHandler: function(form, validator) { // scroll para primeiro campo invláido
      if (!validator.numberOfInvalids()) return;
      var elem = $(validator.errorList[0].element).parent().parent().parent().parent();
      console.log(elem);
      $$('.page-content').scrollTop(
        elem.position().top, // posição do primeiro erro
        500 // tempo em milis
      );
    }
  });

  addForm.init(page);
});

Frm7.onPageInit('busca-end', function(page) {
    templates.searchAddr = Template7.compile(searchAddrTemplate);
});

var addForm = {
  init: function(page) {
    addForm.data = {};
    addForm.results = [];
    addForm.mapaValido = false;
    addForm.horarioPatch = {
      objects: [],
      deleted_objects: []
    };

    // limpa form
    $('#add-form').trigger('reset');

    // carrega objeto do restaurante se for editar
    if(page.context.id != undefined) {
      addForm.id = page.context.id;
      addForm.editar = true;
      // clona objeto pra evitar alterações
      var obj = JSON.parse(JSON.stringify(data.objects[addForm.id]));
      obj.origem = obj.origem.nome;
      Frm7.formFromData("#add-form", obj);

      var horarios = obj.horarios;
      for(key in horarios) {
        addForm.horarioAdd(horarios[key]);
      }

    } else {
      addForm.id = null;
      addForm.editar = false;
    }
  },

  openStreetSearch: function() {
    if(!$("#add-endereco").valid()) return;
    var search = $("#add-endereco").val();
    mainView.router.load({
      url: 'busca-end.html'
    });
    addForm.searchAddress(search);
  },

  // busca por endereço
  parseSearchAddress: function(json) {
    var obj = json;
    addForm.results = json.results;
    obj.meta = {
      total_count: json.results.length,
    };
    obj.objects = json;
    var html = templates.searchAddr(obj);
    $("#search-addr-results").html(html);

    Frm7.hideIndicator();
  },

  // busca por endereço
  searchAddress: function(search) {
    Frm7.showIndicator();
    var api = "https://maps.googleapis.com/maps/api/geocode/json?region=br";
    var key = "&key=" + keys.gmaps;
    if(!search) search = $("#search-addr-input").val();
    var query = "&address=" + search;
    var url = api + key + query;
    $.getJSON(url, addForm.parseSearchAddress, data.fail);
  },

  // seleciona resultado e coloca no form
  resultAddress: function(id) {
    var obj = addForm.results[id];
    console.log(obj);
    try {
      var rua = $.grep(obj.address_components, function(e){ return e.types.indexOf('route') >= 0; })[0].long_name;
      var num = $.grep(obj.address_components, function(e){ return e.types.indexOf('street_number') >= 0; })[0].long_name;
      var bairro = $.grep(obj.address_components, function(e){ return e.types.indexOf('sublocality') >= 0; })[0].long_name;
      var formatted_address = rua + ", " + num + " - " + bairro;
    } catch (e) {
      var formatted_address = obj.formatted_address;
    }
    formData = {
      'lat': obj.geometry.location.lat,
      'long': obj.geometry.location.lng,
    };
    Frm7.formFromData("#add-form", formData);
    addForm.validaMapa();
    mainView.router.back();
  },

  // usuário escolheu resultado no gmaps
  validaMapa: function() {
    $('#add-endereco-button').removeClass('button-fail');
    $('#add-endereco-button').addClass('button-ok');
    addForm.mapaValido = true;
  },

  // usuário mudou endereço, gmaps não é mais válido
  invalidaMapa: function() {
    $('#add-endereco-button').removeClass('button-ok');
    $('#add-endereco-button').addClass('button-fail');
    var formData = {
      'lat': 0,
      'long': 0,
    };
    Frm7.formFromData("#add-form", formData);
    addForm.mapaValido = false;
  },

  // POST
  formSend: function() {
    if (!$('#add-form').valid()) return false;
    if (!addForm.mapaValido) { // validação dos campos ocultos lat long
      $$('.page-content').scrollTop(
        $('#add-endereco-button').position().top, // posição do botão de endereço
        500 // tempo em milis
      );
      return false;
    }

    var api = "/api/restaurante/";
    if(addForm.editar) {
      var type = 'PUT';
      api = api + addForm.id + '/';
    }
    else { var type = 'POST'; }
    var url = SERVER + api;

    var data = Frm7.formToData('#add-form');
    data.origem = resourcize(data.origem, 'origem');
    data.regiao = resourcize(data.regiao, 'regiao');
    data.comida.forEach(function(part, index, arr) {
      arr[index] = resourcize(part, 'comida');
    });
    data.flags.forEach(function(part, index, arr) {
      arr[index] = resourcize(part, 'flag');
    });
    if(data.preco_min == "") data.preco_min = 0;
    if(data.preco_max == "") data.preco_max = 0;

    console.log(data);
    addForm.data = data;

    /*$.post(url, data,
      function(data) {
        console.log("success");
        console.log(data)
      },
      "json"
    );*/

    $.ajax({
      url: url,
      type: type,
      contentType: 'application/json',
      data: JSON.stringify(data),
      dataType: 'json',
      processData: false,
      complete: addForm.formCheck,
      xhrFields: {
        withCredentials: true
      },
      beforeSend: function(xhr, settings) {
        xhr.setRequestHeader("X-CSRFToken", user.profile.csrf_token);
      }
    })
  },

  formCheck: function(d) {
    if (d.status == 201) { // created
      // se criado, URI é dada no header Location
      addForm.restauranteURI = d.getResponseHeader('Location');
      // aqui deve chamar o patch para horário
      addForm.horarioSend();
      //alert("O restaurante " + addForm.data.nome + " foi enviado com sucesso e aguarda moderação.");
      //mainView.router.back();
    } else if (d.status == 204) { // edited
      // se editado, URI é gerada a partir do id sabido
      addForm.restauranteURI = resourcize(addForm.id,'restaurante');
      // aqui deve chamar o patch para horário
      addForm.horarioSend();
      //alert("O restaurante " + addForm.data.nome + "foi editado com sucesso.");
      //delete data.objects[addForm.id];
      //data.downloadSingle(addForm.id);
    } else { // qualquer outro código
      alert("Ocorreu um erro ao tentar enviar o restaurante!");
    }
  },

  // novo horário na lista
  horarioAdd: function(data) {
    var pai = document.getElementById('list-horarios');
    var elem = document.createElement('li');
    elem.innerHTML = horarioTemplate;
    pai.appendChild(elem);
    // popula com horario
    if(data) {
      $(elem).find('input[name$="id"]').val(data.id);
      $(elem).find('input[name$="from_hour"]').val(data.from_hour);
      $(elem).find('input[name$="to_hour"]').val(data.to_hour);
      $(elem).find('select[name$="weekday"]').val(data.weekday);
      Frm7.initSmartSelects(elem); // isso atualiza o texto no select
    }
  },

  // remove aquele horário da lista
  horarioRemove: function(elem) {
    // isto pega o
 referente ao botão pressionado
    var li = $(elem).parent().parent().parent().parent();
    // se formset deste elemento tem id, adiciona para lista de ids removidos
    var id = li.find('input[name$="id"]').val();
    if(id) addForm.horarioPatch.deleted_objects.push(resourcize(id,'horario'));
    li.remove();
  },

  horarioSend: function() {
    var horario_list = $('#list-horarios').find('fieldset');
    for(var key = 0; key < horario_list.length; key++) {
      var fields = $(horario_list[key]);
      // se não tem id, ou seja, se não é novo
      if(!fields.find('input[name$="id"]').val()) {
        // encontramos todos os dias selecionados
        weekdaylist = fields.find('select[name$="weekday"]').val()
        for (wd in weekdaylist) {
          // criamos o objeto para cada dia na linha de horário
          var obj = {
            from_hour: fields.find('input[name$="from_hour"]').val(),
            to_hour: fields.find('input[name$="to_hour"]').val(),
            weekday: weekdaylist[wd],
            restaurante: addForm.restauranteURI
          }
          // e adicionamos ao patch
          addForm.horarioPatch.objects.push(obj);
        }

      }
    }
    console.log(addForm.horarioPatch);

    var api = "/api/horario/";
    var url = SERVER + api;

    $.ajax({
      url: url,
      type: 'PATCH',
      contentType: 'application/json',
      data: JSON.stringify(addForm.horarioPatch),
      dataType: 'json',
      processData: false,
      complete: addForm.horarioCheck,
      xhrFields: {
        withCredentials: true
      },
      beforeSend: function(xhr, settings) {
        xhr.setRequestHeader("X-CSRFToken", user.profile.csrf_token);
      }
    })
  },

  horarioCheck: function(d) {
    if (d.status == 202) { // patch com sucesso
      if(addForm.editar) {
        alert("O restaurante " + addForm.data.nome + " foi editado com sucesso.");
        delete data.objects[addForm.id];
        data.downloadSingle(addForm.id);
      } else {
        alert("O restaurante " + addForm.data.nome + " foi enviado com sucesso e aguarda moderação.");
        mainView.router.back();
      }
    } else { // qualquer outro código
      alert("Ocorreu um erro ao tentar enviar o restaurante!");
    }
  },
};
<|endoftext|>


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

/* global Config, Log */
'use strict';

const args = require('yargs')
  .usage('Usage: $0 [args]')
  .option('c', {
    alias: 'config',
    describe: 'Load configuration from file',
    type: 'string'
  })
  .help('help')
  .argv;

const express = require('express');
const http = require('http');
const Path = require('path');
const Logger = require('../lib/logger');
const app = express();

// Load nconf into the global namespace
global.Config = require('nconf')
    .env()
    .argv();

if (args.c) {
  global.Config.file(Path.resolve(process.cwd(), args.c));
}
global.Config.defaults(require('../config/defaults.json'));

// Set up logging
global.Log = Logger.attach(global.Config.get('log:level'), global.Config.get('log:filename'));

// Add request logging middleware
const accessLog = Logger.attach(global.Config.get('log:access:level'), global.Config.get('log:access:filename'));

app.use(Logger.logRequests((message) => accessLog.verbose(message)));

// Initialize the Plugin Manager and storage layer
const PluginManager = require('../lib/plugin-manager');
const storage = new (require('../lib/storage'))();
const manager = new PluginManager(storage, {});

manager.on('error', (err) => global.Log.error(err));
manager.initialize();

// Register endpoints
require('../lib/control/v1/core').attach(app, storage, manager);
require('../lib/control/v1/conqueso').attach(app, storage);

// Instantiate server and start it
const host = Config.get('service:hostname');
const port = Config.get('service:port');
const server = http.createServer(app);

server.on('error', (err) => global.Log.error(err));

server.listen(port, host, () => {
  Log.info(`Listening on ${host}:${port}`);
});
<commit_msg>The access log now logs at the level declared in the config file.<commit_after>#!/usr/bin/env node

/* global Config, Log */
'use strict';

const args = require('yargs')
  .usage('Usage: $0 [args]')
  .option('c', {
    alias: 'config',
    describe: 'Load configuration from file',
    type: 'string'
  })
  .help('help')
  .argv;

const express = require('express');
const http = require('http');
const Path = require('path');
const Logger = require('../lib/logger');
const app = express();

// Load nconf into the global namespace
global.Config = require('nconf')
    .env()
    .argv();

if (args.c) {
  global.Config.file(Path.resolve(process.cwd(), args.c));
}
global.Config.defaults(require('../config/defaults.json'));

// Set up logging
global.Log = Logger.attach(global.Config.get('log:level'), global.Config.get('log:filename'));

// Add request logging middleware
const accessLog = Logger.attach(global.Config.get('log:access:level'), global.Config.get('log:access:filename'));

app.use(Logger.logRequests((message) => accessLog.log(global.Config.get('log:access:level'), message)));

// Initialize the Plugin Manager and storage layer
const PluginManager = require('../lib/plugin-manager');
const storage = new (require('../lib/storage'))();
const manager = new PluginManager(storage, {});

manager.on('error', (err) => global.Log.error(err));
manager.initialize();

// Register endpoints
require('../lib/control/v1/core').attach(app, storage, manager);
require('../lib/control/v1/conqueso').attach(app, storage);

// Instantiate server and start it
const host = Config.get('service:hostname');
const port = Config.get('service:port');
const server = http.createServer(app);

server.on('error', (err) => global.Log.error(err));

server.listen(port, host, () => {
  Log.info(`Listening on ${host}:${port}`);
});
<|endoftext|>


<commit_before>var WebSocketServer = require('ws').Server;
var http = require('http');
var express = require('express');

var app = express();
var port = process.env.PORT || 5000;
var wsPath = process.env.WS_PATH || '/api';

app.get('/', function(req, res, next) {
  if (req.query.dev === undefined) return res.send("under construction...");
  next();
});
app.use(express.static('web'));

var server = http.createServer(app);
server.listen(port);

console.log('http server listening on %d', port);

var wss = new WebSocketServer({ server: server, path: wsPath });
console.log('websocket server created on %d', wsPath);

wss.on('connection', function(ws) {
  // [Todo] handle new websocket connection.
  console.log('new websocket connection');

  ws.on('message', function(msg) {
    ws.send(msg);
  });
  ws.on('close', function() {
    console.log('websocket connection closed');
  });
});
<commit_msg>small fix<commit_after>var WebSocketServer = require('ws').Server;
var http = require('http');
var express = require('express');

var app = express();
var port = process.env.PORT || 5000;
var wsPath = process.env.WS_PATH || '/api';

app.get('/', function(req, res, next) {
  if (req.query.dev === undefined) return res.send("under construction...");
  next();
});
app.use(express.static('web'));

var server = http.createServer(app);
server.listen(port);

console.log('http server listening on %d', port);

var wss = new WebSocketServer({ server: server, path: wsPath });
console.log('websocket server created on %s', wsPath);

wss.on('connection', function(ws) {
  // [Todo] handle new websocket connection.
  console.log('new websocket connection');

  ws.on('message', function(msg) {
    ws.send(msg);
  });
  ws.on('close', function() {
    console.log('websocket connection closed');
  });
});
<|endoftext|>


<commit_before>#!/usr/bin/env node
'use strict';

var bodyParser = require('body-parser');
var bunyan = require('bunyan');
var express = require('express');
var octokat = require('octokat');
var superagent = require('superagent-promise')(require('superagent'), Promise);

// ignore errors for git's SSL certificate
process.env.NODE_TLS_REJECT_UNAUTHORIZED = 0;

var log = bunyan.createLogger({ name: 'app' });
var app = express();
app.use(bodyParser.json());
var github = new octokat({
  token: process.env.GITHUB_TOKEN,
  rootURL: 'https://git/api/v3'
})

log.info('Starting');

app.get('/',  function (req, res) {
  res.send('leeroy-pull-request-builder');
});

app.post('/event_handler', function (req, res) {
  var gitHubEvent = req.headers['x-github-event'];
  log.info('Received event: ' + gitHubEvent);
  if (gitHubEvent === 'ping') {
    res.status(204).send();
  } else if (gitHubEvent === 'pull_request') {
    if (req.body.action === 'opened' || req.body.action === 'reopened' || req.body.action === 'synchronize') {
      var pr = req.body.pull_request;
      processPullRequest(pr)
        .then(null, function (e) {
          log.error(e);
          setStatus(pr.base.repo.owner.login, pr.base.repo.name, pr.head.sha, 'error', 'Error creating CI build');
        });
      res.status(204).send();
    }
  } else {
    res.status(400).send();
  }
});

app.post('/jenkins', function (req, res) {
  if (req.body && req.body.build) {
    var pr = activeBuilds[req.body.build.parameters.sha1];
    if (pr) {
      switch (req.body.build.phase) {
        case 'STARTED':
          setStatus(pr.baseUser, pr.baseRepo, pr.sha, 'pending', 'Building with Jenkins', req.body.build.full_url);
          break;
        case 'COMPLETED':
          setStatus(pr.baseUser, pr.baseRepo, pr.sha,
            req.body.build.status == 'SUCCESS' ? 'success' : 'failure',
            'Jenkins build status: ' + req.body.build.status,
            req.body.build.full_url);
          break;
      }
    }
    res.status(204).send();
  } else {
    res.status(400).send();
  }
});

app.listen(3000);

/**
 * When a pull_request event is received, creates a new commit in the Build repo that references the
 * PR's commit (in a submodule) and starts a build.
 */
function processPullRequest(pullRequest) {
  var pr = {
    baseUser: pullRequest.base.repo.owner.login,
    baseRepo: pullRequest.base.repo.name,
    baseBranch: pullRequest.base.ref,
    headUser: pullRequest.head.repo.owner.login,
    headRepo: pullRequest.head.repo.name,
    sha: pullRequest.head.sha,
  };
  return leeroyBranches.then(function (lb) {
    var key = pr.baseUser + '/' + pr.baseRepo + '/' + pr.baseBranch;
    log.info('Received pull_request event for ' + key + ': SHA = ' + pr.sha);
    if (lb[key]) {
      return setStatus(pr.baseUser, pr.baseRepo, pr.sha, 'pending', 'Preparing build')
        .then(function () {
          return Promise.all(lb[key].map(function (leeroyConfig) {
            var buildUserRepo = leeroyConfig.repoUrl.match(buildRepoUrl);
            var build = {
              config: leeroyConfig,
              repo: github.repos(buildUserRepo[1], buildUserRepo[2])
            };
            return getHeadCommit(pr, build)
              .then(function (commit) {
                return createNewCommit(pr, build, commit);
              })
              .then(function (newCommit) {
                return createRef(pr, build, newCommit)
                  .then(function() {
                    activeBuilds[newCommit.sha] = pr;
                    return Promise.all(build.config.pullRequestBuildUrls.map(function (prBuildUrl) {
                      log.info('Starting a build at ' + prBuildUrl);
                      return superagent
                        .get(prBuildUrl)
                        .query({ sha1: newCommit.sha });
                    }));
                  });
              });
          }));
        });
    }
  });
}

/**
 * Calls the GitHub Status API to set the state for 'sha'.
 * See https://developer.github.com/v3/repos/statuses/#create-a-status for parameter descriptions.
 */
function setStatus(user, repo, sha, state, description, targetUrl) {
  return github.repos(user, repo).statuses(sha).create({
    state: state,
    description: description,
    target_url: targetUrl,
    context: 'leeroy-pull-request-builder'
  });
}

/**
 * Returns a promise for the SHA of the head of the build repo branch specified by
 * the Leeroy config in 'build.config'.
 */
function getHeadCommit(pr, build) {
  return build.repo.git.refs('heads', build.config.branch).fetch()
    .then(function (ref) {
      log.info('Repo ' + build.config.repoUrl + ' is at commit ' + ref.object.sha);
      return build.repo.git.commits(ref.object.sha).fetch();
    });
}

/**
 * Returns a promise for a new build repo commit that updates the specified 'commit'
 * with updated submodules for 'pr'.
 */
function createNewCommit(pr, build, commit) {
  return build.repo.git.trees(commit.tree.sha).fetch()
    .then(function(tree) {
      return createNewTree(pr, build, tree);
    })
    .then(function (newTree) {
      log.info('newTree = ' + newTree.sha);
      return build.repo.git.commits.create({
        message: 'PR test commit',
        tree: newTree.sha,
        parents: [ commit.sha ]
      });
    });
}

/**
 * Returns a promise for a new tree that updates .gitmodules and the submodules
 * in 'tree' with the updated submodules for 'pr'.
 */
function createNewTree(pr, build, tree) {
  // find the submodule that needs to be changed and update its SHA
  var newItems = tree.tree.filter(function (treeItem) {
    if (treeItem.mode === '160000' && treeItem.path == pr.baseRepo) {
      treeItem.sha = pr.sha;
      return true;
    }
    return false;
  });

  // find the .gitmodules file
  var gitmodulesItem = tree.tree.filter(function (treeItem) {
    return treeItem.path === '.gitmodules';
  })[0];

  // get the contents of .gitmodules
  return build.repo.git.blobs(gitmodulesItem.sha).fetch()
    .then(function (blob) {
      // update .gitmodules with the repo URL the PR is coming from (because it has the commit we need)
      var gitmodules = new Buffer(blob.content, 'base64').toString('utf-8')
        .replace('git@git:' + pr.baseUser + '/' + pr.baseRepo + '.git', 'git@git:' + pr.headUser + '/' + pr.headRepo + '.git');
      return build.repo.git.blobs.create({
        content: gitmodules
      });
    })
    .then(function (newBlob) {
      // create a new tree with updated submodules and .gitmodules
      gitmodulesItem.sha = newBlob.sha;
      newItems.push(gitmodulesItem);
      return build.repo.git.trees.create({
        base_tree: tree.sha,
        tree: newItems
      });
    })
}

/**
 * Updates the 'lprb' (Leeroy Pull Request Builder) branch in 'build' to point at the specified commit SHA.
 */
function createRef(pr, build, newCommit) {
  log.info('New commit is ' + newCommit.sha + '; updating ref.');
  var refName = 'heads/lprb';
  return build.repo.git.refs(refName).fetch()
    .then(function () {
      return build.repo.git.refs(refName).update({
        sha: newCommit.sha,
        force: true
      });
    }, function() {
      return build.repo.git.refs.create({
        ref: 'refs/' + refName,
        sha: newCommit.sha
      });
  });
}

/**
 * Gets all the repos+branches that have pullRequestBuildUrls set in their Leeroy configs.
 */
function getLeeroyBranches() {
  return github.repos('Build', 'Configuration').contents.fetch()
    .then(function (contents) {
      log.info('Contents has ' + contents.length + ' files');
      var jsonFiles = contents.filter(function (elem) {
        return elem.path.indexOf('.json') === elem.path.length - 5;
      });
      return Promise.all(jsonFiles.map(function (elem) {
        return github.repos('Build', 'Configuration').contents(elem.path).read()
          .then(function (contents) {
            try {
              return JSON.parse(contents);
            }
            catch (e) {
              return null;
            }
          });
      }));
    })
    .then(function (files) {
      var enabledFiles = files.filter(function (f) {
        return f && !f.disabled && f.submodules && f.pullRequestBuildUrls && buildRepoUrl.test(f.repoUrl);
      });
      log.info('there are ' + enabledFiles.length + ' enabled files with submodules and PR build URLs.');
      var repos = { };
      enabledFiles.forEach(function (file) {
        for (var submodule in file.submodules) {
          var key = submodule + '/' + file.submodules[submodule];
          repos[key] = repos[key] || [];
          repos[key].push(file);
        }
      });
      return repos;
    })
    .then(null, function(err) {
      log.error(err);
    });
}

var leeroyBranches = getLeeroyBranches();
var activeBuilds = {};
var buildRepoUrl = /^git@git:([^/]+)\/([^.]+).git$/;
<commit_msg>Log Jenkins requests.<commit_after>#!/usr/bin/env node
'use strict';

var bodyParser = require('body-parser');
var bunyan = require('bunyan');
var express = require('express');
var octokat = require('octokat');
var superagent = require('superagent-promise')(require('superagent'), Promise);

// ignore errors for git's SSL certificate
process.env.NODE_TLS_REJECT_UNAUTHORIZED = 0;

var log = bunyan.createLogger({ name: 'app' });
var app = express();
app.use(bodyParser.json());
var github = new octokat({
  token: process.env.GITHUB_TOKEN,
  rootURL: 'https://git/api/v3'
})

log.info('Starting');

app.get('/',  function (req, res) {
  res.send('leeroy-pull-request-builder');
});

app.post('/event_handler', function (req, res) {
  var gitHubEvent = req.headers['x-github-event'];
  log.info('Received GitHub event: ' + gitHubEvent);
  if (gitHubEvent === 'ping') {
    res.status(204).send();
  } else if (gitHubEvent === 'pull_request') {
    if (req.body.action === 'opened' || req.body.action === 'reopened' || req.body.action === 'synchronize') {
      var pr = req.body.pull_request;
      processPullRequest(pr)
        .then(null, function (e) {
          log.error(e);
          setStatus(pr.base.repo.owner.login, pr.base.repo.name, pr.head.sha, 'error', 'Error creating CI build');
        });
      res.status(204).send();
    }
  } else {
    res.status(400).send();
  }
});

app.post('/jenkins', function (req, res) {
  log.info('Received Jenkins notification.');
  if (req.body && req.body.build) {
    var pr = activeBuilds[req.body.build.parameters.sha1];
    if (pr) {
      switch (req.body.build.phase) {
        case 'STARTED':
          setStatus(pr.baseUser, pr.baseRepo, pr.sha, 'pending', 'Building with Jenkins', req.body.build.full_url);
          break;
        case 'COMPLETED':
          setStatus(pr.baseUser, pr.baseRepo, pr.sha,
            req.body.build.status == 'SUCCESS' ? 'success' : 'failure',
            'Jenkins build status: ' + req.body.build.status,
            req.body.build.full_url);
          break;
      }
    }
    res.status(204).send();
  } else {
    res.status(400).send();
  }
});

app.listen(3000);

/**
 * When a pull_request event is received, creates a new commit in the Build repo that references the
 * PR's commit (in a submodule) and starts a build.
 */
function processPullRequest(pullRequest) {
  var pr = {
    baseUser: pullRequest.base.repo.owner.login,
    baseRepo: pullRequest.base.repo.name,
    baseBranch: pullRequest.base.ref,
    headUser: pullRequest.head.repo.owner.login,
    headRepo: pullRequest.head.repo.name,
    sha: pullRequest.head.sha,
  };
  return leeroyBranches.then(function (lb) {
    var key = pr.baseUser + '/' + pr.baseRepo + '/' + pr.baseBranch;
    log.info('Received pull_request event for ' + key + ': SHA = ' + pr.sha);
    if (lb[key]) {
      return setStatus(pr.baseUser, pr.baseRepo, pr.sha, 'pending', 'Preparing build')
        .then(function () {
          return Promise.all(lb[key].map(function (leeroyConfig) {
            var buildUserRepo = leeroyConfig.repoUrl.match(buildRepoUrl);
            var build = {
              config: leeroyConfig,
              repo: github.repos(buildUserRepo[1], buildUserRepo[2])
            };
            return getHeadCommit(pr, build)
              .then(function (commit) {
                return createNewCommit(pr, build, commit);
              })
              .then(function (newCommit) {
                return createRef(pr, build, newCommit)
                  .then(function() {
                    activeBuilds[newCommit.sha] = pr;
                    return Promise.all(build.config.pullRequestBuildUrls.map(function (prBuildUrl) {
                      log.info('Starting a build at ' + prBuildUrl);
                      return superagent
                        .get(prBuildUrl)
                        .query({ sha1: newCommit.sha });
                    }));
                  });
              });
          }));
        });
    }
  });
}

/**
 * Calls the GitHub Status API to set the state for 'sha'.
 * See https://developer.github.com/v3/repos/statuses/#create-a-status for parameter descriptions.
 */
function setStatus(user, repo, sha, state, description, targetUrl) {
  return github.repos(user, repo).statuses(sha).create({
    state: state,
    description: description,
    target_url: targetUrl,
    context: 'leeroy-pull-request-builder'
  });
}

/**
 * Returns a promise for the SHA of the head of the build repo branch specified by
 * the Leeroy config in 'build.config'.
 */
function getHeadCommit(pr, build) {
  return build.repo.git.refs('heads', build.config.branch).fetch()
    .then(function (ref) {
      log.info('Repo ' + build.config.repoUrl + ' is at commit ' + ref.object.sha);
      return build.repo.git.commits(ref.object.sha).fetch();
    });
}

/**
 * Returns a promise for a new build repo commit that updates the specified 'commit'
 * with updated submodules for 'pr'.
 */
function createNewCommit(pr, build, commit) {
  return build.repo.git.trees(commit.tree.sha).fetch()
    .then(function(tree) {
      return createNewTree(pr, build, tree);
    })
    .then(function (newTree) {
      log.info('newTree = ' + newTree.sha);
      return build.repo.git.commits.create({
        message: 'PR test commit',
        tree: newTree.sha,
        parents: [ commit.sha ]
      });
    });
}

/**
 * Returns a promise for a new tree that updates .gitmodules and the submodules
 * in 'tree' with the updated submodules for 'pr'.
 */
function createNewTree(pr, build, tree) {
  // find the submodule that needs to be changed and update its SHA
  var newItems = tree.tree.filter(function (treeItem) {
    if (treeItem.mode === '160000' && treeItem.path == pr.baseRepo) {
      treeItem.sha = pr.sha;
      return true;
    }
    return false;
  });

  // find the .gitmodules file
  var gitmodulesItem = tree.tree.filter(function (treeItem) {
    return treeItem.path === '.gitmodules';
  })[0];

  // get the contents of .gitmodules
  return build.repo.git.blobs(gitmodulesItem.sha).fetch()
    .then(function (blob) {
      // update .gitmodules with the repo URL the PR is coming from (because it has the commit we need)
      var gitmodules = new Buffer(blob.content, 'base64').toString('utf-8')
        .replace('git@git:' + pr.baseUser + '/' + pr.baseRepo + '.git', 'git@git:' + pr.headUser + '/' + pr.headRepo + '.git');
      return build.repo.git.blobs.create({
        content: gitmodules
      });
    })
    .then(function (newBlob) {
      // create a new tree with updated submodules and .gitmodules
      gitmodulesItem.sha = newBlob.sha;
      newItems.push(gitmodulesItem);
      return build.repo.git.trees.create({
        base_tree: tree.sha,
        tree: newItems
      });
    })
}

/**
 * Updates the 'lprb' (Leeroy Pull Request Builder) branch in 'build' to point at the specified commit SHA.
 */
function createRef(pr, build, newCommit) {
  log.info('New commit is ' + newCommit.sha + '; updating ref.');
  var refName = 'heads/lprb';
  return build.repo.git.refs(refName).fetch()
    .then(function () {
      return build.repo.git.refs(refName).update({
        sha: newCommit.sha,
        force: true
      });
    }, function() {
      return build.repo.git.refs.create({
        ref: 'refs/' + refName,
        sha: newCommit.sha
      });
  });
}

/**
 * Gets all the repos+branches that have pullRequestBuildUrls set in their Leeroy configs.
 */
function getLeeroyBranches() {
  return github.repos('Build', 'Configuration').contents.fetch()
    .then(function (contents) {
      log.info('Contents has ' + contents.length + ' files');
      var jsonFiles = contents.filter(function (elem) {
        return elem.path.indexOf('.json') === elem.path.length - 5;
      });
      return Promise.all(jsonFiles.map(function (elem) {
        return github.repos('Build', 'Configuration').contents(elem.path).read()
          .then(function (contents) {
            try {
              return JSON.parse(contents);
            }
            catch (e) {
              return null;
            }
          });
      }));
    })
    .then(function (files) {
      var enabledFiles = files.filter(function (f) {
        return f && !f.disabled && f.submodules && f.pullRequestBuildUrls && buildRepoUrl.test(f.repoUrl);
      });
      log.info('there are ' + enabledFiles.length + ' enabled files with submodules and PR build URLs.');
      var repos = { };
      enabledFiles.forEach(function (file) {
        for (var submodule in file.submodules) {
          var key = submodule + '/' + file.submodules[submodule];
          repos[key] = repos[key] || [];
          repos[key].push(file);
        }
      });
      return repos;
    })
    .then(null, function(err) {
      log.error(err);
    });
}

var leeroyBranches = getLeeroyBranches();
var activeBuilds = {};
var buildRepoUrl = /^git@git:([^/]+)\/([^.]+).git$/;
<|endoftext|>


<commit_before>var request = require('request');
var bunyan = require('bunyan');
var rereddit = require('rereddit');
var config = require('config');
var mongoose = require('mongoose');
var async = require('async');
var _ = require('lodash');
var Schema = mongoose.Schema;
var ObjectId = mongoose.Types.ObjectId;

// Connect to mongodb
var connect = function () {
  var options = { server: { socketOptions: { keepAlive: 1 } } };
  mongoose.connect(config.db, options);
};
connect();

mongoose.connection.on('error', console.log);
mongoose.connection.on('disconnected', connect);

// Get subscription model
var mongooseSubscription = require('../app/models/subscription');
var Subscription = mongoose.model('Subscription');

var mongooseUser = require('../app/models/user');
var User = mongoose.model('User');

var mongooseArticle = require('../app/models/article');
var Article = mongoose.model('Article');

// Connect to SendGrid
var sendgrid  = require('sendgrid')('chronicle-app', 'no11pfds');

// Run with node bin/worker.js | ./node_modules/bunyan/bin/bunyan
// or on heroku with http://stackoverflow.com/questions/13345664/using-heroku-scheduler-with-node-js

var log = bunyan.createLogger({name: "chronicle-worker"});

function findSubscriptions(users, cb) {
  var subs = [];
  async.each(users, function(user, callback) {
    console.log(user);
    Subscription.find({user: ObjectId(user._id)}, function(err, subscriptions) {
      console.log(subscriptions);
      Array.prototype.push.apply(subs, subscriptions);
      callback();
    });
  }, function(err) {
    cb(err, subs);
  });
}

function buildEmails(subscriptions, cb) {

  async.each(subscriptions, function(subscription, callback) {
    subscription.substitutions = [];
    async.each(subscription.subscriptions, function(sub, callback2) {
      Article.find({feed: ObjectId(sub.feed)}, function(err, articles) {
        // Sort them by score
        //console.log(articles);
        articles = _.sortBy(articles, function(article) {
          return article.score;
        });
        // Trim down to specified quantity
        articles = articles.slice(0, sub.quantity-1);

        // Create list elements for each article
        articles = articles.map(function(article) {
          return '
' + article.title + '
'
        });
        // Add to substititions
        Array.prototype.push.apply(subscription.substitutions, articles);
        // Execute callback
        callback2();
      });
    }, function(err) {
      callback();
    });
  }, function(err) {
    //console.log(subscriptions)
    cb(err, subscriptions);
  });
}

function sendEmail(subscriptions, cb) {

  async.each(subscriptions, function(subscription, callback) {
    var payload,
        email,
        user,
        substitutions = [];
    // Loop through subscriptions within hash
    async.each(subscription.subscriptions, function(sub, callback2) {
      // Find all articles associated with the subscription's feed
      Article.find({feed: sub.feed}, function(articles) {
        // Sort them by score
        //console.log(articles);
        articles = _.sortBy(articles, function(article) {
          return article.score;
        });
        // Trim down to specified quantity
        articles = articles.slice(0, sub.quantity-1);

        // Create list elements for each article
        articles = articles.map(function(article) {
          return '
' + article.title + '
'
        });
        // Add to substititions
        Array.prototype.push.apply(substitutions, articles);
        // Execute callback
        callback2();
      }, function(err) {
        callback();
      });
    });

    // Grab the user from the subscription hash
    user = User.findById(subscription.user);
    // Define payload
    payload   = {
      to      : user.email,
      from    : 'zach@chronicle.io',
      subject : 'Your Digest',
      text    : 'Here is your digest:'
    }
    // Email settings
    email = new sendgrid.Email(payload);
    email.addFilter('templates', 'enable', 1);
    email.addFilter('templates', 'template_id', '08715756-7bbc-4d51-add2-c655bf3e1d04');

    // Fill in email template
    email.setSubstitutions({articles: substitutions.toString()});

    sendgrid.send(email, function(err, json) {
      if (err) { console.error(err); }
      // Alert Dead Man's Snitch
      success = true;
      request.get('https://nosnch.in/81ff490627');
      console.log(json);
      callback();
    });
  }, function(err){
    cb(err, success)
  });

}

function sendEmails(subscriptions, cb) {
    var email, success;
    // Grab the user from the subscription hash
    async.each(subscriptions, function(subscription, callback) {
      //var user = User.findById(subscription.user);
      console.log(subscription);
      User.findOne({_id: subscription.user}, function (err, user) {
        // Define payload
        payload   = {
          to      : user.email,
          from    : 'zach@chronicle.io',
          subject : 'Your Digest',
          text    : 'Here is your digest:'
        }

        // Email settings
        email = new sendgrid.Email(payload);
        email.setHtml(subscription.substitutions.join("\n"));
        // email.addFilter('templates', 'enable', 1);
        // email.addFilter('templates', 'template_id', '08715756-7bbc-4d51-add2-c655bf3e1d04');

        // Fill in email template
        //email.setSubstitutions({articles: subscription.substitutions.join("\n")});

        sendgrid.send(email, function(err, json) {
          if (err) { console.error(err); }
          // Alert Dead Man's Snitch
          request.get('https://nosnch.in/81ff490627');
          console.log(json);
          callback();
        });
      });
    },
    function(err){
      cb(err, "success");
    });
}

async.waterfall([
  function(cb) {
    // Get all users
    User.find({}, cb);
  },
  function(users, cb) {
    // Get all subscriptions
    findSubscriptions(users, cb);
  },
  function(subscriptions, cb) {
    // Organize user subscriptions
    subscriptions = _.chain(subscriptions)
    .groupBy("user")
    .pairs()
    .map(function(sub) {
        return _.object(_.zip(["user", "subscriptions"], sub));
    })
    .value();
    // Build emails
    buildEmails(subscriptions, cb);
  },
  function(subscriptions, cb) {
    sendEmails(subscriptions, cb);
  }
], function(err) {
  // Done
  process.exit(0);
});

// // Todo: Use handlebars or swig template to send in here
// email.setSubstitutions({articles: ['
Girly Things

Buttons

Girly Things
']});
<commit_msg>update worker<commit_after>var request = require('request');
var bunyan = require('bunyan');
var config = require('config');
var mongoose = require('mongoose');
var async = require('async');
var _ = require('lodash');
var Schema = mongoose.Schema;
var ObjectId = mongoose.Types.ObjectId;

// Connect to mongodb
var connect = function () {
  var options = { server: { socketOptions: { keepAlive: 1 } } };
  mongoose.connect(config.db, options);
};
connect();

mongoose.connection.on('error', console.log);
mongoose.connection.on('disconnected', connect);

// Get subscription model
var mongooseSubscription = require('../app/models/subscription');
var Subscription = mongoose.model('Subscription');

var mongooseUser = require('../app/models/user');
var User = mongoose.model('User');

var mongooseArticle = require('../app/models/article');
var Article = mongoose.model('Article');

// Connect to SendGrid
var sendgrid  = require('sendgrid')('chronicle-app', 'no11pfds');

// Run with node bin/worker.js | ./node_modules/bunyan/bin/bunyan
// or on heroku with http://stackoverflow.com/questions/13345664/using-heroku-scheduler-with-node-js

var log = bunyan.createLogger({name: "chronicle-worker"});

function findSubscriptions(users, cb) {
  var subs = [];
  async.each(users, function(user, callback) {
    console.log(user);
    Subscription.find({user: ObjectId(user._id)}, function(err, subscriptions) {
      console.log(subscriptions);
      Array.prototype.push.apply(subs, subscriptions);
      callback();
    });
  }, function(err) {
    cb(err, subs);
  });
}

function buildEmails(subscriptions, cb) {

  async.each(subscriptions, function(subscription, callback) {
    subscription.substitutions = [];
    async.each(subscription.subscriptions, function(sub, callback2) {
      Article.find({feed: ObjectId(sub.feed)}, function(err, articles) {
        // Sort them by score
        //console.log(articles);
        articles = _.sortBy(articles, function(article) {
          return article.score;
        });
        // Trim down to specified quantity
        articles = articles.slice(0, sub.quantity-1);

        // Create list elements for each article
        articles = articles.map(function(article) {
          return '
' + article.title + '
'
        });
        // Add to substititions
        Array.prototype.push.apply(subscription.substitutions, articles);
        // Execute callback
        callback2();
      });
    }, function(err) {
      callback();
    });
  }, function(err) {
    //console.log(subscriptions)
    cb(err, subscriptions);
  });
}

function sendEmail(subscriptions, cb) {

  async.each(subscriptions, function(subscription, callback) {
    var payload,
        email,
        user,
        substitutions = [];
    // Loop through subscriptions within hash
    async.each(subscription.subscriptions, function(sub, callback2) {
      // Find all articles associated with the subscription's feed
      Article.find({feed: sub.feed}, function(articles) {
        // Sort them by score
        //console.log(articles);
        articles = _.sortBy(articles, function(article) {
          return article.score;
        });
        // Trim down to specified quantity
        articles = articles.slice(0, sub.quantity-1);

        // Create list elements for each article
        articles = articles.map(function(article) {
          return '
' + article.title + '
'
        });
        // Add to substititions
        Array.prototype.push.apply(substitutions, articles);
        // Execute callback
        callback2();
      }, function(err) {
        callback();
      });
    });

    // Grab the user from the subscription hash
    user = User.findById(subscription.user);
    // Define payload
    payload   = {
      to      : user.email,
      from    : 'zach@chronicle.io',
      subject : 'Your Digest',
      text    : 'Here is your digest:'
    }
    // Email settings
    email = new sendgrid.Email(payload);
    email.addFilter('templates', 'enable', 1);
    email.addFilter('templates', 'template_id', '08715756-7bbc-4d51-add2-c655bf3e1d04');

    // Fill in email template
    email.setSubstitutions({articles: substitutions.toString()});

    sendgrid.send(email, function(err, json) {
      if (err) { console.error(err); }
      // Alert Dead Man's Snitch
      success = true;
      request.get('https://nosnch.in/81ff490627');
      console.log(json);
      callback();
    });
  }, function(err){
    cb(err, success)
  });

}

function sendEmails(subscriptions, cb) {
    var email, success;
    // Grab the user from the subscription hash
    async.each(subscriptions, function(subscription, callback) {
      //var user = User.findById(subscription.user);
      console.log(subscription);
      User.findOne({_id: subscription.user}, function (err, user) {
        // Define payload
        payload   = {
          to      : user.email,
          from    : 'zach@chronicle.io',
          subject : 'Your Digest',
          text    : 'Here is your digest:'
        }

        // Email settings
        email = new sendgrid.Email(payload);
        email.setHtml(subscription.substitutions.join("\n"));
        // email.addFilter('templates', 'enable', 1);
        // email.addFilter('templates', 'template_id', '08715756-7bbc-4d51-add2-c655bf3e1d04');

        // Fill in email template
        //email.setSubstitutions({articles: subscription.substitutions.join("\n")});

        sendgrid.send(email, function(err, json) {
          if (err) { console.error(err); }
          // Alert Dead Man's Snitch
          request.get('https://nosnch.in/81ff490627');
          console.log(json);
          callback();
        });
      });
    },
    function(err){
      cb(err, "success");
    });
}

async.waterfall([
  function(cb) {
    // Get all users
    User.find({}, cb);
  },
  function(users, cb) {
    // Get all subscriptions
    findSubscriptions(users, cb);
  },
  function(subscriptions, cb) {
    // Organize user subscriptions
    subscriptions = _.chain(subscriptions)
    .groupBy("user")
    .pairs()
    .map(function(sub) {
        return _.object(_.zip(["user", "subscriptions"], sub));
    })
    .value();
    // Build emails
    buildEmails(subscriptions, cb);
  },
  function(subscriptions, cb) {
    sendEmails(subscriptions, cb);
  }
], function(err) {
  // Done
  process.exit(0);
});

// // Todo: Use handlebars or swig template to send in here
// email.setSubstitutions({articles: ['
Girly Things

Buttons

Girly Things
']});
<|endoftext|>


<commit_before>'use strict';

// app package deps
const pify = require('util').promisify;
const csvParse = require('csv-parse');
const mkdirp = pify(require('mkdirp'));
const fs = require('fs');

const unlinkAsync = pify(fs.unlink);
const linkAsync = pify(fs.link);
const statAsync = pify(fs.stat);
const readdirAsync = pify(fs.readdir);

const path = require('path');
const winston = require('winston');
// set w/ config etc post release
process.LOGLEVEL = 'silly';

const { Logger, transports: { Console } } = winston;
const DockerManager = require('coinstac-docker-manager');
const PipelineManager = require('coinstac-pipeline');

/**
 * Create a user client for COINSTAC
 * @example
construction and initialization

 *
 * @class
 * @constructor
 * @param {object} opts
 * @property {string} [opts.appDirectory] Path to on-disk storage. Primarily
 * used for testing.
 * @param {Winston} [opts.logger] permit user injected logger vs default logger
 * @param {string} [opts.logLevel] npm log levels, e.g. 'verbose', 'error', etc
 * @param {string} [opts.userId] Currently logged-in user
 * @property {Winston} logger
 * @property {string} appDirectory
 * @property {Auth} auth
 * @property {Project} project
 */
class CoinstacClient {
  constructor(opts) {
    if (!opts || !(opts instanceof Object)) {
      throw new TypeError('coinstac-client requires configuration opts');
    }
    this.options = opts;
    this.logger = opts.logger || new Logger({ transports: [new Console()] });
    this.appDirectory = opts.appDirectory;

    this.dockerManager = DockerManager;
    this.dockerManager.setLogger(this.logger);

    /* istanbul ignore if */
    if (opts.logLevel) {
      this.logger.level = opts.logLevel;
    }

    this.clientId = opts.userId;
  }

  initialize() {
    return PipelineManager.create({
      mode: 'local',
      clientId: this.options.userId,
      logger: this.logger,
      operatingDirectory: path.join(this.appDirectory),
      remotePort: this.options.fileServer.port,
      remoteProtocol: this.options.fileServer.protocol,
      remotePathname: this.options.fileServer.pathname,
      remoteURL: this.options.fileServer.hostname,
      mqttRemotePort: this.options.mqttServer.port,
      mqttRemoteProtocol: this.options.mqttServer.protocol,
      mqttRemoteURL: this.options.mqttServer.hostname,
    }).then((manager) => {
      this.pipelineManager = manager;
      return manager;
    });
  }

  /**
   * Get a metadata CSV's contents.
   *
   * @param {string} filename Full file path to CSV
   * @returns {Promise<Project>}
   */
  static getCSV(filename) {
    return pify(fs.readFile)(filename)
      .then(data => pify(csvParse)(data.toString()))
      .then(JSON.stringify);
  }

  /**
   * Get array index of metadata file column
   *
   * @param {array} arr metadata array set
   * @returns {index}
   */
  static getFileIndex(arr) {
    let key = '';
    arr.shift();
    arr[0].forEach((str, i) => {
      if (str && typeof str === 'string') {
        const match = str.match(/(?:\.([a-zA-Z]+))?$/);
        if (match[0] !== '' && match[1] !== 'undefined') {
          key = i;
        }
      }
    });
    return key;
  }

  /**
   * Parse metadata and shift data column if need be.
   *
   * @param {Array[]} metaFile Metadata CSV's contents
   * @returns {Array[]} metaFile Metadata CSV's contents
   */
  static parseMetaFile(metaFile) {
    const filesKey = this.getFileIndex([...metaFile]);
    if (filesKey !== 0) {
      metaFile = metaFile.map((row) => {
        const r = [...row];
        const data = r[filesKey];
        r.splice(filesKey, 1);
        r.unshift(data);
        return r;
      });
    }
    return metaFile;
  }

  /**
   * Load a metadata CSV file.
   *
   * @param {string} metaFilePath Path to metadata CSV
   * @param {Array[]} metaFile Metadata CSV's contents
   * @returns {File[]} Collection of files
   */
  static getFilesFromMetadata(metaFilePath, metaFile) {
    const files = this.parseMetaFile(metaFile).map((filecol) => {
      const file = filecol[0];
      return path.isAbsolute(file)
        ? file
        : path.resolve(path.join(path.dirname(metaFilePath), file));
    });
    files.shift();
    return files;
  }

  /**
   * Get JSON schema contents.
   *
   * @param {string} filename Full file path to JSON Schema
   * @returns {Promise<Project>}
   */
  static getJSONSchema(filename) {
    return pify(fs.readFile)(filename)
      .then(data => JSON.parse(data.toString()));
  }

  /**
    * Get array of file paths recursively
    *
    * @param {object} group
    * @param {array} group.paths the paths to traverse
    * @param {string} group.parentDir parent directory if diving into subdir
    * @param {string} group.error present if error found
    */
  static async getSubPathsAndGroupExtension(group, multext) {
    let pathsArray = [];
    let extension = null;

    // Empty subdirectory
    if (group.paths.length === 0) {
      return null;
    }

    // Return error
    if (group.error) {
      return group;
    }

    // Iterate through all paths
    await Promise.all(group.paths.map(async (filePath) => {
      let p = filePath;
      // Combine path with parent dir to get absolute path
      if (group.parentDir) {
        p = path.join(group.parentDir, p);
      }

      const stats = await statAsync(p);

      if (stats.isDirectory()) {
        const dirs = await readdirAsync(p);
        const paths = [...dirs.filter(item => !(/(^|\/)\.[^/.]/g).test(item))];
        // Recursively retrieve path contents of directory
        const subGroup = this.getSubPathsAndGroupExtension({
          paths: [...fs.readdirSync(p).filter(item => !(/(^|\/)\.[^\/\.]/g).test(item))], // eslint-disable-line no-useless-escape
          extension: group.extension,
          parentDir: p,
        });
        if (subGroup) {
          if (subGroup.error) {
            return subGroup;
          }

          if (!multext && extension && subGroup.extension && extension !== subGroup.extension) {
            return { error: `Group contains multiple extensions - ${extension} & ${subGroup.extension}.` };
          }

          extension = subGroup.extension; // eslint-disable-line prefer-destructuring
          pathsArray = pathsArray.concat(subGroup.paths);
        }
      } else {
        const thisExtension = path.extname(p);

        if ((!multext && group.extension && thisExtension !== group.extension)
             || (!multext && extension && extension !== thisExtension)) {
          return { error: `Group contains multiple extensions - ${thisExtension} & ${group.extension ? group.extension : extension}.` };
        }

        extension = thisExtension;
        pathsArray.push(p);
      }
    }));
    return { paths: pathsArray, extension };
  }

  /**
   * Validate client pipeline against run pipeline snapshot before link data files
   *
   * TODO: Create cleanup function to unlink files after run
   *
   * @param {string} consortiumId The id of the consortium running this pipeline
   * @param {*} clientPipeline The client's copy of the consortium's active pipeline
   * @param {*} filesArray An array of all the files used by the client's data mapping
   *                        for this pipeline
   * @param {*} runId The id if this particular pipeline run
   * @param {*} runPipeline The run's copy of the current pipeline
   */
  startPipeline(
    clients,
    consortiumId,
    clientPipeline,
    filesArray,
    runId,
    runPipeline // eslint-disable-line no-unused-vars
  ) {
    return mkdirp(path.join(this.appDirectory, 'input', this.clientId, runId))
      .then(() => {
      // TODO: validate runPipeline against clientPipeline
        const linkPromises = [];

        const e = new RegExp(/[-\/\\^$*+?.()|[\]{}]/g); // eslint-disable-line no-useless-escape
        const escape = (string) => {
          return string.replace(e, '\\$&');
        };

        for (let i = 0; i < filesArray.length; i += 1) {
          const pathsep = new RegExp(`${escape(path.sep)}|:`, 'g');
          linkPromises.push(
            linkAsync(filesArray[i], path.resolve(this.appDirectory, 'input', this.clientId, runId, filesArray[i].replace(pathsep, '-')))
              .catch((e) => {
              // permit dupes
                if (e.code && e.code !== 'EEXIST') {
                  throw e;
                }
              })
          );
        }

        const runObj = { spec: clientPipeline, runId, timeout: clientPipeline.timeout };
        if (clients) {
          runObj.clients = clients;
        }

        return Promise.all(linkPromises)
          .then(() => this.pipelineManager.startPipeline(runObj));
      });
  }

  /**
   * Requests a pipeline stop by notifying the pipeline controller
   * the pipeline will then stop via an error event and terminate as if an error
   * were thrown
   * @param {string} pipelineId The id of the pipeline running
   * @param {string} runId The id of the pipeline run
   */
  requestPipelineStop(pipelineId, runId) {
    this.pipelineManager.stopPipeline(pipelineId, runId);
  }

  unlinkFiles(runId) {
    const fullPath = path.join(this.appDirectory, 'input', this.clientId, runId);

    return statAsync(fullPath).then((stats) => {
      return stats.isDirectory();
    })
      .catch((err) => {
        if (err.code === 'ENOENT') return false;
        throw err;
      })
      .then((exists) => {
        if (!exists) {
          return [];
        }

        return new Promise((res, rej) => {
          return fs.readdir(fullPath,
            (error, filesArray) => {
              if (error) {
                rej(error);
              } else {
                res(filesArray);
              }
            });
        });
      })
      .then((filesArray) => {
        const unlinkPromises = [];
        for (let i = 0; i < filesArray.length; i += 1) {
          unlinkPromises.push(
            unlinkAsync(path.resolve(fullPath, filesArray[i]))
          );
        }
        return Promise.all(unlinkPromises);
      });
  }
}

module.exports = CoinstacClient;
<commit_msg>fix merge revert<commit_after>'use strict';

// app package deps
const pify = require('util').promisify;
const csvParse = require('csv-parse');
const mkdirp = pify(require('mkdirp'));
const fs = require('fs');

const unlinkAsync = pify(fs.unlink);
const linkAsync = pify(fs.link);
const statAsync = pify(fs.stat);
const readdirAsync = pify(fs.readdir);

const path = require('path');
const winston = require('winston');
// set w/ config etc post release
process.LOGLEVEL = 'silly';

const { Logger, transports: { Console } } = winston;
const DockerManager = require('coinstac-docker-manager');
const PipelineManager = require('coinstac-pipeline');

/**
 * Create a user client for COINSTAC
 * @example
construction and initialization

 *
 * @class
 * @constructor
 * @param {object} opts
 * @property {string} [opts.appDirectory] Path to on-disk storage. Primarily
 * used for testing.
 * @param {Winston} [opts.logger] permit user injected logger vs default logger
 * @param {string} [opts.logLevel] npm log levels, e.g. 'verbose', 'error', etc
 * @param {string} [opts.userId] Currently logged-in user
 * @property {Winston} logger
 * @property {string} appDirectory
 * @property {Auth} auth
 * @property {Project} project
 */
class CoinstacClient {
  constructor(opts) {
    if (!opts || !(opts instanceof Object)) {
      throw new TypeError('coinstac-client requires configuration opts');
    }
    this.options = opts;
    this.logger = opts.logger || new Logger({ transports: [new Console()] });
    this.appDirectory = opts.appDirectory;

    this.dockerManager = DockerManager;
    this.dockerManager.setLogger(this.logger);

    /* istanbul ignore if */
    if (opts.logLevel) {
      this.logger.level = opts.logLevel;
    }

    this.clientId = opts.userId;
  }

  initialize() {
    return PipelineManager.create({
      mode: 'local',
      clientId: this.options.userId,
      logger: this.logger,
      operatingDirectory: path.join(this.appDirectory),
      remotePort: this.options.fileServer.port,
      remoteProtocol: this.options.fileServer.protocol,
      remotePathname: this.options.fileServer.pathname,
      remoteURL: this.options.fileServer.hostname,
      mqttRemotePort: this.options.mqttServer.port,
      mqttRemoteProtocol: this.options.mqttServer.protocol,
      mqttRemoteURL: this.options.mqttServer.hostname,
    }).then((manager) => {
      this.pipelineManager = manager;
      return manager;
    });
  }

  /**
   * Get a metadata CSV's contents.
   *
   * @param {string} filename Full file path to CSV
   * @returns {Promise<Project>}
   */
  static getCSV(filename) {
    return pify(fs.readFile)(filename)
      .then(data => pify(csvParse)(data.toString()))
      .then(JSON.stringify);
  }

  /**
   * Get array index of metadata file column
   *
   * @param {array} arr metadata array set
   * @returns {index}
   */
  static getFileIndex(arr) {
    let key = '';
    arr.shift();
    arr[0].forEach((str, i) => {
      if (str && typeof str === 'string') {
        const match = str.match(/(?:\.([a-zA-Z]+))?$/);
        if (match[0] !== '' && match[1] !== 'undefined') {
          key = i;
        }
      }
    });
    return key;
  }

  /**
   * Parse metadata and shift data column if need be.
   *
   * @param {Array[]} metaFile Metadata CSV's contents
   * @returns {Array[]} metaFile Metadata CSV's contents
   */
  static parseMetaFile(metaFile) {
    const filesKey = this.getFileIndex([...metaFile]);
    if (filesKey !== 0) {
      metaFile = metaFile.map((row) => {
        const r = [...row];
        const data = r[filesKey];
        r.splice(filesKey, 1);
        r.unshift(data);
        return r;
      });
    }
    return metaFile;
  }

  /**
   * Load a metadata CSV file.
   *
   * @param {string} metaFilePath Path to metadata CSV
   * @param {Array[]} metaFile Metadata CSV's contents
   * @returns {File[]} Collection of files
   */
  static getFilesFromMetadata(metaFilePath, metaFile) {
    const files = this.parseMetaFile(metaFile).map((filecol) => {
      const file = filecol[0];
      return path.isAbsolute(file)
        ? file
        : path.resolve(path.join(path.dirname(metaFilePath), file));
    });
    files.shift();
    return files;
  }

  /**
   * Get JSON schema contents.
   *
   * @param {string} filename Full file path to JSON Schema
   * @returns {Promise<Project>}
   */
  static getJSONSchema(filename) {
    return pify(fs.readFile)(filename)
      .then(data => JSON.parse(data.toString()));
  }

  /**
    * Get array of file paths recursively
    *
    * @param {object} group
    * @param {array} group.paths the paths to traverse
    * @param {string} group.parentDir parent directory if diving into subdir
    * @param {string} group.error present if error found
    */
  static async getSubPathsAndGroupExtension(group, multext) {
    let pathsArray = [];
    let extension = null;

    // Empty subdirectory
    if (group.paths.length === 0) {
      return null;
    }

    // Return error
    if (group.error) {
      return group;
    }

    // Iterate through all paths
    await Promise.all(group.paths.map(async (filePath) => {
      let p = filePath;
      // Combine path with parent dir to get absolute path
      if (group.parentDir) {
        p = path.join(group.parentDir, p);
      }

      const stats = await statAsync(p);

      if (stats.isDirectory()) {
        const dirs = await readdirAsync(p);
        const paths = [...dirs.filter(item => !(/(^|\/)\.[^/.]/g).test(item))];
        // Recursively retrieve path contents of directory
        const subGroup = await this.getSubPathsAndGroupExtension({
          path,
          extension: group.extension,
          parentDir: p,
        });
        if (subGroup) {
          if (subGroup.error) {
            return subGroup;
          }

          if (!multext && extension && subGroup.extension && extension !== subGroup.extension) {
            return { error: `Group contains multiple extensions - ${extension} & ${subGroup.extension}.` };
          }

          extension = subGroup.extension; // eslint-disable-line prefer-destructuring
          pathsArray = pathsArray.concat(subGroup.paths);
        }
      } else {
        const thisExtension = path.extname(p);

        if ((!multext && group.extension && thisExtension !== group.extension)
             || (!multext && extension && extension !== thisExtension)) {
          return { error: `Group contains multiple extensions - ${thisExtension} & ${group.extension || extension}.` };
        }

        extension = thisExtension;
        pathsArray.push(p);
      }
    }));
    return { paths: pathsArray, extension };
  }

  /**
   * Validate client pipeline against run pipeline snapshot before link data files
   *
   * TODO: Create cleanup function to unlink files after run
   *
   * @param {string} consortiumId The id of the consortium running this pipeline
   * @param {*} clientPipeline The client's copy of the consortium's active pipeline
   * @param {*} filesArray An array of all the files used by the client's data mapping
   *                        for this pipeline
   * @param {*} runId The id if this particular pipeline run
   * @param {*} runPipeline The run's copy of the current pipeline
   */
  startPipeline(
    clients,
    consortiumId,
    clientPipeline,
    filesArray,
    runId,
    runPipeline // eslint-disable-line no-unused-vars
  ) {
    return mkdirp(path.join(this.appDirectory, 'input', this.clientId, runId))
      .then(() => {
      // TODO: validate runPipeline against clientPipeline
        const linkPromises = [];

        const e = new RegExp(/[-\/\\^$*+?.()|[\]{}]/g); // eslint-disable-line no-useless-escape
        const escape = (string) => {
          return string.replace(e, '\\$&');
        };

        for (let i = 0; i < filesArray.length; i += 1) {
          const pathsep = new RegExp(`${escape(path.sep)}|:`, 'g');
          linkPromises.push(
            linkAsync(filesArray[i], path.resolve(this.appDirectory, 'input', this.clientId, runId, filesArray[i].replace(pathsep, '-')))
              .catch((e) => {
              // permit dupes
                if (e.code && e.code !== 'EEXIST') {
                  throw e;
                }
              })
          );
        }

        const runObj = { spec: clientPipeline, runId, timeout: clientPipeline.timeout };
        if (clients) {
          runObj.clients = clients;
        }

        return Promise.all(linkPromises)
          .then(() => this.pipelineManager.startPipeline(runObj));
      });
  }

  /**
   * Requests a pipeline stop by notifying the pipeline controller
   * the pipeline will then stop via an error event and terminate as if an error
   * were thrown
   * @param {string} pipelineId The id of the pipeline running
   * @param {string} runId The id of the pipeline run
   */
  requestPipelineStop(pipelineId, runId) {
    this.pipelineManager.stopPipeline(pipelineId, runId);
  }

  unlinkFiles(runId) {
    const fullPath = path.join(this.appDirectory, 'input', this.clientId, runId);

    return statAsync(fullPath).then((stats) => {
      return stats.isDirectory();
    })
      .catch((err) => {
        if (err.code === 'ENOENT') return false;
        throw err;
      })
      .then((exists) => {
        if (!exists) {
          return [];
        }

        return new Promise((res, rej) => {
          return fs.readdir(fullPath,
            (error, filesArray) => {
              if (error) {
                rej(error);
              } else {
                res(filesArray);
              }
            });
        });
      })
      .then((filesArray) => {
        const unlinkPromises = [];
        for (let i = 0; i < filesArray.length; i += 1) {
          unlinkPromises.push(
            unlinkAsync(path.resolve(fullPath, filesArray[i]))
          );
        }
        return Promise.all(unlinkPromises);
      });
  }
}

module.exports = CoinstacClient;
<|endoftext|>


<commit_before>/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this file,
 * You can obtain one at http://mozilla.org/MPL/2.0/. */

// See http://dygraphs.com/ for graphs documentation.

var gBody, gGraph, gSelID, gBranchSelect, gADICheckbox, gDataIssueDays,
    gRawData, gUseADI = true;

var gDataPath = "../../";
// for local debugging
//gDataPath = "../socorro/";

var gBranches = {
  fxrel: {
    title: "Firefox release channel",
    datafile: "Firefox-release-bytype.json",
    annotationfile: "Firefox-release-annotations.json",
    plugins: true,
    sumContent: true,
    maxRate: 3,
    maxCrashes: 2.2e6,
  },
  fxbeta: {
    title: "Firefox beta channel",
    datafile: "Firefox-beta-bytype.json",
    annotationfile: "Firefox-beta-annotations.json",
    plugins: true,
    sumContent: true,
    maxRate: 3,
    maxCrashes: 80e3,
  },
  andrel: {
    title: "Firefox for Android release channel",
    datafile: "FennecAndroid-release-bytype.json",
    annotationfile: "FennecAndroid-release-annotations.json",
    plugins: false,
    sumContent: false,
    maxRate: 4,
    maxCrashes: 100e3,
  },
  andbeta: {
    title: "Firefox for Android beta channel",
    datafile: "FennecAndroid-beta-bytype.json",
    annotationfile: "FennecAndroid-beta-annotations.json",
    plugins: false,
    sumContent: false,
    maxRate: 11,
    maxCrashes: 10e3,
  },
  andaurora: {
    title: "Firefox for Android Aurora channel",
    datafile: "FennecAndroid-aurora-bytype.json",
    annotationfile: "FennecAndroid-aurora-annotations.json",
    plugins: false,
    sumContent: false,
    maxRate: 20,
    maxCrashes: 10e3,
  },
  andnightly: {
    title: "Firefox for Android Nightly channel",
    datafile: "FennecAndroid-nightly-bytype.json",
    annotationfile: "FennecAndroid-nightly-annotations.json",
    plugins: false,
    sumContent: false,
    maxRate: 20,
    maxCrashes: 10e3,
  },
}

window.onload = function() {
  // Get data to graph.
  gBody = document.getElementsByTagName("body")[0];
  gBranchSelect = document.getElementById("branch");
  gBranchSelect.onchange = function() {
    location.href = '?' + gBranchSelect.value;
  }
  var option;
  for (var branchID in gBranches) {
    option = document.createElement("option");
    option.value = branchID;
    option.text = gBranches[branchID].title;
    gBranchSelect.add(option);
  }
  gADICheckbox = document.getElementById("adiCheckbox");
  gADICheckbox.checked = gUseADI;
  gADICheckbox.onchange = function(aCheckbox) {
    gUseADI = gADICheckbox.checked;
    graphData(gRawData);
  }

  if (location.search) {
    var urlAnchor = location.search.substr(1); // Cut off the ? sign.
    if (urlAnchor in gBranches) {
      gSelID = urlAnchor;
      gBranchSelect.value = urlAnchor;
    }
  }
  else {
    gSelID = "fxrel";
  }
  fetchFile(gDataPath + "dataissue-days.json", "json",
    function(aData) {
      if (aData) {
        gDataIssueDays = aData;
      }
      else {
        console.log("Error loading data issue days.");
      }
    }
  );

  fetchFile(gDataPath + gBranches[gSelID].datafile, "json", graphData);
}

window.onresize = function() {
  gGraph.resize(
    gBody.clientWidth,
    gBody.clientHeight - graphDiv.offsetTop
  );
}

function graphData(aData) {
  var graphDiv = document.getElementById("graphdiv");
  gRawData = aData;
  if (aData) {
    var graphData = [], dataArray, browserCrashes;
    var crUnit = (gBranches[gSelID].maxCrashes > 1e6) ? "M" : "k";
    var crUnitNum = (gBranches[gSelID].maxCrashes > 1e6) ? 1e6 : 1e3;
    var yAxisMax = gUseADI ? gBranches[gSelID].maxRate : gBranches[gSelID].maxCrashes / crUnitNum;
    var yAxisDecimals = (yAxisMax > 20) ? 0 : 1;
    // add elements in the following format: [ new Date("2009-07-12"), 100, 200 ]
    for (var day in aData) {
      dataArray = [ new Date(day) ];
      if (gBranches[gSelID].sumContent) {
        browserCrashes = 0;
        if (aData[day].crashes["Browser"]) { browserCrashes += aData[day].crashes["Browser"]; }
        if (aData[day].crashes["Content"]) { browserCrashes += aData[day].crashes["Content"]; }
        dataArray.push(browserCrashes * (gUseADI ? (100 / aData[day].adi) : 1 / crUnitNum));
      }
      else {
        dataArray.push(aData[day].crashes["Browser"] * (gUseADI ? (100 / aData[day].adi) : 1 / crUnitNum));
      }
      if (gBranches[gSelID].plugins) {
        dataArray.push(aData[day].crashes["OOP Plugin"] * (gUseADI ? (100 / aData[day].adi) : 1 / crUnitNum));
        dataArray.push(aData[day].crashes["Hang Plugin"] * (gUseADI ? (100 / aData[day].adi) : 1 / crUnitNum));
      }
      graphData.push(dataArray);
    }

    var colors = ["#004080"], labels = ["date"];
    if (gBranches[gSelID].sumContent) {
      labels.push("browser+content crashes");
    }
    else {
      labels.push("browser crashes");
    }
    if (gBranches[gSelID].plugins) {
      labels.push("plugin crashes");
      labels.push("plugin hangs");
      colors.push("#FF8000");
      colors.push("#FFCC00");
    }
    var graphOptions = {
      title: gBranches[gSelID].title,
      ylabel: gUseADI ? "crashes / 100 ADI" : "crashes",
      valueRange: [0, yAxisMax + .01],
      axes: {
        x: {
          axisLabelFormatter: function(aDate) {
            return (aDate.getUTCMonth() + 1) + "/" + aDate.getUTCFullYear();
          },
          valueFormatter: function(aMilliseconds) {
            var dateValue = new Date(aMilliseconds);
            return dateValue.getUTCFullYear() + "-" +
              (dateValue.getUTCMonth() < 9 ? "0" : "") + (dateValue.getUTCMonth() + 1 ) + "-" +
              (dateValue.getUTCDate() < 10 ? "0" : "") + dateValue.getUTCDate();
          },
        },
        y: {
          axisLabelFormatter: function(aNumber) {
            return aNumber.toFixed(yAxisDecimals) + (gUseADI ? "" : crUnit);
          },
          valueFormatter: function(aNumber) {
            return aNumber.toFixed(2) + (gUseADI ? "" : crUnit);
          },
        },
      },
      colors: colors,
      strokeWidth: 2,
      legend: 'always',
      labels: labels,
      labelsSeparateLines: true,
      labelsShowZeroValues: true,
      width: gBody.clientWidth,
      height: gBody.clientHeight - graphDiv.offsetTop,
    };

    gGraph = new Dygraph(graphDiv, graphData, graphOptions);
    gGraph.ready(function() {
      fetchFile(gDataPath + gBranches[gSelID].annotationfile, "json",
        function(aData) {
          if (aData) {
            // Convert dates to Date objects so they can be displayed.
            for (var i = 0; i < aData.length; i++) {
              aData[i]["x"] = Date.parse(aData[i]["x"]);
            }
            if (gDataIssueDays) {
              for (var i = 0; i < gDataIssueDays.length; i++) {
                aData.push({
                  series: labels[labels.length - 1],
                  x: Date.parse(gDataIssueDays[i]),
                  shortText: "D",
                  text: "Data Issue (missing ADI or crashes)",
                  attachAtBottom: true,
                  cssClass: "dataissue",
                });
              }
            }
            gGraph.setAnnotations(aData);
          }
          else {
            console.log("Error loading annotation data.");
          }
        }
      );
      console.log("foo");
    });
  }
  else {
    // ERROR! We're screwed!
    graphDiv.textContent = "Error loading JSON data.";
  }
}

function fetchFile(aURL, aFormat, aCallback) {
  var XHR = new XMLHttpRequest();
  XHR.onreadystatechange = function() {
    if (XHR.readyState == 4) {/*
      gLog.appendChild(document.createElement("li"))
          .appendChild(document.createTextNode(aURL + " - " + XHR.status +
                                               " " + XHR.statusText));*/
    }
    if (XHR.readyState == 4 && XHR.status == 200) {
      // so far so good
      if (XHR.responseXML != null && aFormat == "xml" &&
          XHR.responseXML.getElementById('test').firstChild.data)
        aCallback(aXHR.responseXML.getElementById('test').firstChild.data);
      else if (XHR.responseText != null && aFormat == "json")
        aCallback(JSON.parse(XHR.responseText));
      else
        aCallback(XHR.responseText);
    } else if (XHR.readyState == 4 && XHR.status != 200) {
      // fetched the wrong page or network error...
      aCallback(null);
    }
  };
  XHR.open("GET", aURL);
  if (aFormat == "json") { XHR.setRequestHeader("Accept", "application/json"); }
  else if (aFormat == "xml") { XHR.setRequestHeader("Accept", "application/xml"); }
  try {
    XHR.send();
  }
  catch (e) {
    aCallback(null);
  }
}
<commit_msg>Gather data on aurora/nightly with same algorithm (even though it's imperfect)<commit_after>/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this file,
 * You can obtain one at http://mozilla.org/MPL/2.0/. */

// See http://dygraphs.com/ for graphs documentation.

var gBody, gGraph, gSelID, gBranchSelect, gADICheckbox, gDataIssueDays,
    gRawData, gUseADI = true;

var gDataPath = "../../";
// for local debugging
//gDataPath = "../socorro/";

var gBranches = {
  fxrel: {
    title: "Firefox release channel",
    datafile: "Firefox-release-bytype.json",
    annotationfile: "Firefox-release-annotations.json",
    plugins: true,
    sumContent: true,
    maxRate: 3,
    maxCrashes: 2.2e6,
  },
  fxbeta: {
    title: "Firefox beta channel",
    datafile: "Firefox-beta-bytype.json",
    annotationfile: "Firefox-beta-annotations.json",
    plugins: true,
    sumContent: true,
    maxRate: 3,
    maxCrashes: 80e3,
  },
  andaurora: {
    title: "Firefox Aurora / DevEdition channel",
    datafile: "Firefox-aurora-bytype.json",
    annotationfile: "Firefox-aurora-annotations.json",
    plugins: false,
    sumContent: false,
    maxRate: 20,
    maxCrashes: 10e3,
  },
  andnightly: {
    title: "Firefox Nightly channel",
    datafile: "Firefox-nightly-bytype.json",
    annotationfile: "Firefox-nightly-annotations.json",
    plugins: false,
    sumContent: false,
    maxRate: 30,
    maxCrashes: 10e3,
  },
  andrel: {
    title: "Firefox for Android release channel",
    datafile: "FennecAndroid-release-bytype.json",
    annotationfile: "FennecAndroid-release-annotations.json",
    plugins: false,
    sumContent: false,
    maxRate: 4,
    maxCrashes: 100e3,
  },
  andbeta: {
    title: "Firefox for Android beta channel",
    datafile: "FennecAndroid-beta-bytype.json",
    annotationfile: "FennecAndroid-beta-annotations.json",
    plugins: false,
    sumContent: false,
    maxRate: 11,
    maxCrashes: 1e3,
  },
  andaurora: {
    title: "Firefox for Android Aurora channel",
    datafile: "FennecAndroid-aurora-bytype.json",
    annotationfile: "FennecAndroid-aurora-annotations.json",
    plugins: false,
    sumContent: false,
    maxRate: 20,
    maxCrashes: 1e3,
  },
  andnightly: {
    title: "Firefox for Android Nightly channel",
    datafile: "FennecAndroid-nightly-bytype.json",
    annotationfile: "FennecAndroid-nightly-annotations.json",
    plugins: false,
    sumContent: false,
    maxRate: 30,
    maxCrashes: 1e3,
  },
}

window.onload = function() {
  // Get data to graph.
  gBody = document.getElementsByTagName("body")[0];
  gBranchSelect = document.getElementById("branch");
  gBranchSelect.onchange = function() {
    location.href = '?' + gBranchSelect.value;
  }
  var option;
  for (var branchID in gBranches) {
    option = document.createElement("option");
    option.value = branchID;
    option.text = gBranches[branchID].title;
    gBranchSelect.add(option);
  }
  gADICheckbox = document.getElementById("adiCheckbox");
  gADICheckbox.checked = gUseADI;
  gADICheckbox.onchange = function(aCheckbox) {
    gUseADI = gADICheckbox.checked;
    graphData(gRawData);
  }

  if (location.search) {
    var urlAnchor = location.search.substr(1); // Cut off the ? sign.
    if (urlAnchor in gBranches) {
      gSelID = urlAnchor;
      gBranchSelect.value = urlAnchor;
    }
  }
  else {
    gSelID = "fxrel";
  }
  fetchFile(gDataPath + "dataissue-days.json", "json",
    function(aData) {
      if (aData) {
        gDataIssueDays = aData;
      }
      else {
        console.log("Error loading data issue days.");
      }
    }
  );

  fetchFile(gDataPath + gBranches[gSelID].datafile, "json", graphData);
}

window.onresize = function() {
  gGraph.resize(
    gBody.clientWidth,
    gBody.clientHeight - graphDiv.offsetTop
  );
}

function graphData(aData) {
  var graphDiv = document.getElementById("graphdiv");
  gRawData = aData;
  if (aData) {
    var graphData = [], dataArray, browserCrashes;
    var crUnit = (gBranches[gSelID].maxCrashes > 1e6) ? "M" : "k";
    var crUnitNum = (gBranches[gSelID].maxCrashes > 1e6) ? 1e6 : 1e3;
    var yAxisMax = gUseADI ? gBranches[gSelID].maxRate : gBranches[gSelID].maxCrashes / crUnitNum;
    var yAxisDecimals = (yAxisMax > 20) ? 0 : 1;
    // add elements in the following format: [ new Date("2009-07-12"), 100, 200 ]
    for (var day in aData) {
      dataArray = [ new Date(day) ];
      if (gBranches[gSelID].sumContent) {
        browserCrashes = 0;
        if (aData[day].crashes["Browser"]) { browserCrashes += aData[day].crashes["Browser"]; }
        if (aData[day].crashes["Content"]) { browserCrashes += aData[day].crashes["Content"]; }
        dataArray.push(browserCrashes * (gUseADI ? (100 / aData[day].adi) : 1 / crUnitNum));
      }
      else {
        dataArray.push(aData[day].crashes["Browser"] * (gUseADI ? (100 / aData[day].adi) : 1 / crUnitNum));
      }
      if (gBranches[gSelID].plugins) {
        dataArray.push(aData[day].crashes["OOP Plugin"] * (gUseADI ? (100 / aData[day].adi) : 1 / crUnitNum));
        dataArray.push(aData[day].crashes["Hang Plugin"] * (gUseADI ? (100 / aData[day].adi) : 1 / crUnitNum));
      }
      graphData.push(dataArray);
    }

    var colors = ["#004080"], labels = ["date"];
    if (gBranches[gSelID].sumContent) {
      labels.push("browser+content crashes");
    }
    else {
      labels.push("browser crashes");
    }
    if (gBranches[gSelID].plugins) {
      labels.push("plugin crashes");
      labels.push("plugin hangs");
      colors.push("#FF8000");
      colors.push("#FFCC00");
    }
    var graphOptions = {
      title: gBranches[gSelID].title,
      ylabel: gUseADI ? "crashes / 100 ADI" : "crashes",
      valueRange: [0, yAxisMax + .01],
      axes: {
        x: {
          axisLabelFormatter: function(aDate) {
            return (aDate.getUTCMonth() + 1) + "/" + aDate.getUTCFullYear();
          },
          valueFormatter: function(aMilliseconds) {
            var dateValue = new Date(aMilliseconds);
            return dateValue.getUTCFullYear() + "-" +
              (dateValue.getUTCMonth() < 9 ? "0" : "") + (dateValue.getUTCMonth() + 1 ) + "-" +
              (dateValue.getUTCDate() < 10 ? "0" : "") + dateValue.getUTCDate();
          },
        },
        y: {
          axisLabelFormatter: function(aNumber) {
            return aNumber.toFixed(yAxisDecimals) + (gUseADI ? "" : crUnit);
          },
          valueFormatter: function(aNumber) {
            return aNumber.toFixed(2) + (gUseADI ? "" : crUnit);
          },
        },
      },
      colors: colors,
      strokeWidth: 2,
      legend: 'always',
      labels: labels,
      labelsSeparateLines: true,
      labelsShowZeroValues: true,
      width: gBody.clientWidth,
      height: gBody.clientHeight - graphDiv.offsetTop,
    };

    gGraph = new Dygraph(graphDiv, graphData, graphOptions);
    gGraph.ready(function() {
      fetchFile(gDataPath + gBranches[gSelID].annotationfile, "json",
        function(aData) {
          if (aData) {
            // Convert dates to Date objects so they can be displayed.
            for (var i = 0; i < aData.length; i++) {
              aData[i]["x"] = Date.parse(aData[i]["x"]);
            }
            if (gDataIssueDays) {
              for (var i = 0; i < gDataIssueDays.length; i++) {
                aData.push({
                  series: labels[labels.length - 1],
                  x: Date.parse(gDataIssueDays[i]),
                  shortText: "D",
                  text: "Data Issue (missing ADI or crashes)",
                  attachAtBottom: true,
                  cssClass: "dataissue",
                });
              }
            }
            gGraph.setAnnotations(aData);
          }
          else {
            console.log("Error loading annotation data.");
          }
        }
      );
      console.log("foo");
    });
  }
  else {
    // ERROR! We're screwed!
    graphDiv.textContent = "Error loading JSON data.";
  }
}

function fetchFile(aURL, aFormat, aCallback) {
  var XHR = new XMLHttpRequest();
  XHR.onreadystatechange = function() {
    if (XHR.readyState == 4) {/*
      gLog.appendChild(document.createElement("li"))
          .appendChild(document.createTextNode(aURL + " - " + XHR.status +
                                               " " + XHR.statusText));*/
    }
    if (XHR.readyState == 4 && XHR.status == 200) {
      // so far so good
      if (XHR.responseXML != null && aFormat == "xml" &&
          XHR.responseXML.getElementById('test').firstChild.data)
        aCallback(aXHR.responseXML.getElementById('test').firstChild.data);
      else if (XHR.responseText != null && aFormat == "json")
        aCallback(JSON.parse(XHR.responseText));
      else
        aCallback(XHR.responseText);
    } else if (XHR.readyState == 4 && XHR.status != 200) {
      // fetched the wrong page or network error...
      aCallback(null);
    }
  };
  XHR.open("GET", aURL);
  if (aFormat == "json") { XHR.setRequestHeader("Accept", "application/json"); }
  else if (aFormat == "xml") { XHR.setRequestHeader("Accept", "application/xml"); }
  try {
    XHR.send();
  }
  catch (e) {
    aCallback(null);
  }
}
<|endoftext|>