<commit_before>/**
 * Component for drawing and editing of vector data.
 * Uses popup, measure.
 * Screen position of buttons determined by css in html file.
 */

// import templates
import tpl from './draw.htm.js';
import Component from './component.js';
var component = new Component(tpl, 'draw');

import ol from '../../ol.js';
import olMap from '../../olMap.js';
var map = olMap.get();
import utils from '../../utils.js';
var $ = utils.$;
// make sure toolbar initialised
import './toolbar.js';

// add drawtemplate to map
// use ol.control to prevent event propagation
map.addControl(new ol.control.Control({element: component.getTemplate('draw')}));

import select from '../../select.js';
import vectors from '../../vectors.js';
import measure from '../../measure.js';
import popup from './popup.js';
var overlay = popup.getOverlay();

var joinKey;
// FIXME English
var DRAWID = 'Drawn';

// add a vector layer/source
// FIXME enable drawing on existing layers
vectors.add({vectors: [{id: DRAWID}]});
var source = vectors.getLayers().item(vectors.getLayers().get('length') - 1).get('source');

// create and add draw/modify interactions
var features = select.get().getFeatures();
var interactions = {
  point: new ol.interaction.Draw({
    source: source,
    type: 'Point'
  }),
  ls: new ol.interaction.Draw({
    source: source,
    type: 'LineString'
  }),
  poly: new ol.interaction.Draw({
    source: source,
    type: 'Polygon'
  }),
  modify: new ol.interaction.Modify({
    features: features
  }),
  split: new ol.interaction.Modify({
    features: features,
    deleteCondition: function() { return false; }
  })
};

var listener;
var form = component.getTemplate('featureform');

function plusFunc() {
  var temp = this.parentNode.querySelector('template');
  var clone = temp.content.firstElementChild.cloneNode(true);
  $('#featureformAtts').insertBefore(clone, temp);
  var i = $('#featureformAtts').querySelectorAll('input');
  i[i.length - 2].focus(); // focus on 1st new input field
}

var endCallback = function(e) {
  var el = overlay.get('element');
  // may be featuredisplay present
  while (el.firstChild) {
    el.removeChild(el.firstChild);
  }
  el.appendChild(form.cloneNode(true));
  // initialise displayed atts
  $('#featureformid').value = '';
  $('#plusbutton').onclick = plusFunc;
  // use onclick rather than addEventListener so only 1 active
  $('#featurebutton').onclick = function() {
    if ($('#featureformid').value) {
      e.feature.setId($('#featureformid').value);
    }
    var divs = $('#featureformAtts').querySelectorAll('div');
    for (var i = 0; i < divs.length; i++) {
      var inputs = divs[i].querySelectorAll('input');
      // only set attribute if there is a key
      if (inputs[0].value) {
        e.feature.set(inputs[0].value, inputs[1].value);
      }
    }
    el.style.display = 'none';
  };
  overlay.set('position', map.get('view').get('center'));
  el.style.display = 'block';
  $('#featureformid').focus();
  if (listener) {
    ol.Observable.unByKey(listener);
  }
};

interactions.point.on('drawend', endCallback);
interactions.ls.on('drawend', endCallback);
interactions.poly.on('drawend', endCallback);

interactions.ls.on('drawstart', function(evt) {
  var tooltipCoord = evt.coordinate;
  listener = evt.feature.getGeometry().on('change', function(evt) {
    tooltipCoord = evt.target.getLastCoordinate();
    var length = measure.getLength(evt.target, map.get('view').getProjection());
    overlay.get('element').innerHTML = (Math.round(length * 0.1) / 100) + ' ' + 'km';
    overlay.set('position', tooltipCoord);
    overlay.get('element').style.display = 'block';
  });
});
interactions.poly.on('drawstart', function(evt) {
  var tooltipCoord = evt.coordinate;
  listener = evt.feature.getGeometry().on('change', function(evt) {
    tooltipCoord = evt.target.getInteriorPoint().getCoordinates();
    var area = measure.getArea(evt.target, map.get('view').getProjection());
    overlay.get('element').innerHTML = (Math.round(area / 1000000 * 100) / 100) +
        ' ' + 'km2';
    overlay.set('position', tooltipCoord);
    overlay.get('element').style.display = 'block';
  });
});

interactions.modify.on('modifyend', function(evt) {
  // move modified feature to draw layer if not already there
  // FIXME: this is not very satisfactory, as is triggered on every vertex move/delete/add
  var f = evt.features.item(0);
  var layer = select.get().getLayer(f);
  if (layer.get('id') !== DRAWID) {
    layer.get('source').removeFeature(f);
    source.addFeatures([f]);
  }
});

var i;
// interactions inactive until clicked
for (i in interactions) {
  map.addInteraction(interactions[i]);
  interactions[i].set('active', false);
}

var wasDelete = false, wasModAtts = false, evtKey;

$('#drawtype').onchange = changeFunction;

function changeFunction(e) {
  // clear any selected features
  features.clear();
  // set any previously active interactions inactive
  for (var i in interactions) {
    interactions[i].set('active', false);
  }
  // ... and clicked one active
  if (interactions[e.target.value]) {
    interactions[e.target.value].set('active', true);
  }

  // if delete, add listener to delete from source and select when selected
  if (e.target.value == 'deleet') {
    evtKey = features.on('add', function(e) {
      select.get().getLayer(e.element).get('source').removeFeature(e.element);
      features.clear();
    });
    wasDelete = true;
  } else if (wasDelete) {
    ol.Observable.unByKey(evtKey);
    wasDelete = false;
  }

  if (e.target.value == 'hide' || e.target.value == 'choose') {
    select.drawOff();
    if (e.target.value == 'hide') {
      this.style.display = 'none';
      $('#drawoption').style.display = 'block';
      $('#drawoption').value = 'redraw';
      $('#drawtype').value = 'choose';
    }
  } else {
    select.drawOn();
  }

  if (['point', 'ls', 'poly'].indexOf(e.target.value) == -1) {
    select.get().set('active', true);
  } else {
    // no select while drawing
    select.get().set('active', false);
  }

  if (e.target.value == 'modAtts') {
    evtKey = features.on('add', function(e) {
      var el = overlay.get('element');
      // may be featuredisplay present
      while (el.firstChild) {
        el.removeChild(el.firstChild);
      }
      el.appendChild(form.cloneNode(true));

      var feature = e.element;
      $('#featureformid').value = feature.getId();
      // display existing atts
      var atts = feature.getProperties();
      var attsDiv = $('#featureformAtts');
      for (var att in atts) {
        if (att != 'geometry') {
          var clone = attsDiv.querySelector('template').content
              .firstElementChild.cloneNode(true);
          var inputs = clone.querySelectorAll('input');
          inputs[0].value = att;
          inputs[1].value = atts[att];
          attsDiv.insertBefore(clone, attsDiv.firstElementChild);
        }
      }
      // use onclick rather than addEventListener so only 1 active
      $('#plusbutton').onclick = plusFunc;
      $('#featurebutton').onclick = function() {
        feature.setId($('#featureformid').value);
        var divs = $('#featureformAtts').querySelectorAll('div');
        for (var i = 0; i < divs.length; i++) {
          var inputs = divs[i].querySelectorAll('input');
          if (inputs[1].value) {
            feature.set(inputs[0].value, inputs[1].value);
          } else {
            // delete attribute if value cleared
            feature.unset(inputs[0].value);
          }
        }
        el.style.display = 'none';
        // move feature to draw layer if not already there
        var layer = select.get().getLayer(feature);
        if (layer.get('id') !== DRAWID) {
          layer.get('source').removeFeature(feature);
          source.addFeatures([feature]);
        }
      };
      overlay.set('position', map.get('view').get('center'));
      el.style.display = 'block';
    });
    wasModAtts = true;
  } else if (wasModAtts) {
    ol.Observable.unByKey(evtKey);
    wasModAtts = false;
  }

  if (e.target.value == 'save' || e.target.value == 'saveall') {
    System.importModule('registry/components/serialise.js')
    .then(function(m) {
      m.default(e.target.value, DRAWID);
    });
  }

  if (e.target.value == 'split') {
    var origCoords;
    select.get().once('select', function(evt) {
      // this bit can be removed once modify event returns modified vertex
      if (evt.selected[0]) {
        origCoords = evt.selected[0].getGeometry().getCoordinates();
      }
    });
    interactions.split.once('modifyend', function(evt) {
      var f = evt.features.item(0);
      var modCoords = f.getGeometry().getCoordinates();
      // return true/false whether val is in arr
      function check(arr, val) {
        return arr.some(function(arrval) {
          // have to convert to strings as arrays won't be equal
          return val.toString() == arrval.toString();
        });
      }
      var i = 0;
      // compare each new coord with original until find non-match
      while (i < modCoords.length && check(origCoords, modCoords[i]) === true) {
        i++;
      }
      var part1 = modCoords.slice(0, i + 1); // include the new coord in both parts
      var part2 = modCoords.slice(i);
      var atts = f.getProperties();
      atts.geometry = new ol.geom.LineString(part1);
      var f1 = new ol.Feature(atts);
      atts.geometry = new ol.geom.LineString(part2);
      var f2 = new ol.Feature(atts);
      // effectively, this moves a split feature on layer other than draw layer to the draw layer
      source.addFeatures([f1, f2]);
      select.get().getLayer(f).get('source').removeFeature(f);
      features.clear();
      e.target.value = 'choose';
      changeFunction(e);
    });
  }

  if (e.target.value == 'join') {
    joinKey = select.get().on('select', function(evt) {
      if (features.get('length') == 2) {
        var f1 = features.item(0);
        var f2 = features.item(1);
        var atts = f1.getProperties();
        atts.geometry = new ol.geom.LineString(
          f1.getGeometry().getCoordinates().concat(f2.getGeometry().getCoordinates().slice(1))
        );
        var f3 = new ol.Feature(atts);
        features.clear();
        // effectively, this moves the joined feature on layer other than draw layer to the draw layer
        select.get().getLayer(f2).get('source').removeFeature(f2);
        select.get().getLayer(f1).get('source').removeFeature(f1);
        source.addFeatures([f3]);
      }
    });
  } else if (joinKey) {
    ol.Observable.unByKey(joinKey);
    joinKey = null;
  }

  if (e.target.value == 'clear') {
    source.clear();
  }

  this.blur();
}

// add helptemplate to toolbar
$('#help-content').appendChild(component.getTemplate('drawhelp'));
// add toggle listener to helptitle div
$('#draw-title').addEventListener('click', function() {
  // toggle display of next, i.e. content, element
  var el = this.nextElementSibling;
  el.style.display = (el.style.display == 'block') ? 'none' : 'block';
});
<commit_msg>Draw: fix blur on split<commit_after>/**
 * Component for drawing and editing of vector data.
 * Uses popup, measure.
 * Screen position of buttons determined by css in html file.
 */

// import templates
import tpl from './draw.htm.js';
import Component from './component.js';
var component = new Component(tpl, 'draw');

import ol from '../../ol.js';
import olMap from '../../olMap.js';
var map = olMap.get();
import utils from '../../utils.js';
var $ = utils.$;
// make sure toolbar initialised
import './toolbar.js';

// add drawtemplate to map
// use ol.control to prevent event propagation
map.addControl(new ol.control.Control({element: component.getTemplate('draw')}));

import select from '../../select.js';
import vectors from '../../vectors.js';
import measure from '../../measure.js';
import popup from './popup.js';
var overlay = popup.getOverlay();

var joinKey;
// FIXME English
var DRAWID = 'Drawn';

// add a vector layer/source
// FIXME enable drawing on existing layers
vectors.add({vectors: [{id: DRAWID}]});
var source = vectors.getLayers().item(vectors.getLayers().get('length') - 1).get('source');

// create and add draw/modify interactions
var features = select.get().getFeatures();
var interactions = {
  point: new ol.interaction.Draw({
    source: source,
    type: 'Point'
  }),
  ls: new ol.interaction.Draw({
    source: source,
    type: 'LineString'
  }),
  poly: new ol.interaction.Draw({
    source: source,
    type: 'Polygon'
  }),
  modify: new ol.interaction.Modify({
    features: features
  }),
  split: new ol.interaction.Modify({
    features: features,
    deleteCondition: function() { return false; }
  })
};

var listener;
var form = component.getTemplate('featureform');

function plusFunc() {
  var temp = this.parentNode.querySelector('template');
  var clone = temp.content.firstElementChild.cloneNode(true);
  $('#featureformAtts').insertBefore(clone, temp);
  var i = $('#featureformAtts').querySelectorAll('input');
  i[i.length - 2].focus(); // focus on 1st new input field
}

var endCallback = function(e) {
  var el = overlay.get('element');
  // may be featuredisplay present
  while (el.firstChild) {
    el.removeChild(el.firstChild);
  }
  el.appendChild(form.cloneNode(true));
  // initialise displayed atts
  $('#featureformid').value = '';
  $('#plusbutton').onclick = plusFunc;
  // use onclick rather than addEventListener so only 1 active
  $('#featurebutton').onclick = function() {
    if ($('#featureformid').value) {
      e.feature.setId($('#featureformid').value);
    }
    var divs = $('#featureformAtts').querySelectorAll('div');
    for (var i = 0; i < divs.length; i++) {
      var inputs = divs[i].querySelectorAll('input');
      // only set attribute if there is a key
      if (inputs[0].value) {
        e.feature.set(inputs[0].value, inputs[1].value);
      }
    }
    el.style.display = 'none';
  };
  overlay.set('position', map.get('view').get('center'));
  el.style.display = 'block';
  $('#featureformid').focus();
  if (listener) {
    ol.Observable.unByKey(listener);
  }
};

interactions.point.on('drawend', endCallback);
interactions.ls.on('drawend', endCallback);
interactions.poly.on('drawend', endCallback);

interactions.ls.on('drawstart', function(evt) {
  var tooltipCoord = evt.coordinate;
  listener = evt.feature.getGeometry().on('change', function(evt) {
    tooltipCoord = evt.target.getLastCoordinate();
    var length = measure.getLength(evt.target, map.get('view').getProjection());
    overlay.get('element').innerHTML = (Math.round(length * 0.1) / 100) + ' ' + 'km';
    overlay.set('position', tooltipCoord);
    overlay.get('element').style.display = 'block';
  });
});
interactions.poly.on('drawstart', function(evt) {
  var tooltipCoord = evt.coordinate;
  listener = evt.feature.getGeometry().on('change', function(evt) {
    tooltipCoord = evt.target.getInteriorPoint().getCoordinates();
    var area = measure.getArea(evt.target, map.get('view').getProjection());
    overlay.get('element').innerHTML = (Math.round(area / 1000000 * 100) / 100) +
        ' ' + 'km2';
    overlay.set('position', tooltipCoord);
    overlay.get('element').style.display = 'block';
  });
});

interactions.modify.on('modifyend', function(evt) {
  // move modified feature to draw layer if not already there
  // FIXME: this is not very satisfactory, as is triggered on every vertex move/delete/add
  var f = evt.features.item(0);
  var layer = select.get().getLayer(f);
  if (layer.get('id') !== DRAWID) {
    layer.get('source').removeFeature(f);
    source.addFeatures([f]);
  }
});

var i;
// interactions inactive until clicked
for (i in interactions) {
  map.addInteraction(interactions[i]);
  interactions[i].set('active', false);
}

var wasDelete = false, wasModAtts = false, evtKey;

$('#drawtype').onchange = changeFunction;

function changeFunction(e) {
  // clear any selected features
  features.clear();
  // set any previously active interactions inactive
  for (var i in interactions) {
    interactions[i].set('active', false);
  }
  // ... and clicked one active
  if (interactions[e.target.value]) {
    interactions[e.target.value].set('active', true);
  }

  // if delete, add listener to delete from source and select when selected
  if (e.target.value == 'deleet') {
    evtKey = features.on('add', function(e) {
      select.get().getLayer(e.element).get('source').removeFeature(e.element);
      features.clear();
    });
    wasDelete = true;
  } else if (wasDelete) {
    ol.Observable.unByKey(evtKey);
    wasDelete = false;
  }

  if (e.target.value == 'hide' || e.target.value == 'choose') {
    select.drawOff();
    if (e.target.value == 'hide') {
      this.style.display = 'none';
      $('#drawoption').style.display = 'block';
      $('#drawoption').value = 'redraw';
      $('#drawtype').value = 'choose';
    }
  } else {
    select.drawOn();
  }

  if (['point', 'ls', 'poly'].indexOf(e.target.value) == -1) {
    select.get().set('active', true);
  } else {
    // no select while drawing
    select.get().set('active', false);
  }

  if (e.target.value == 'modAtts') {
    evtKey = features.on('add', function(e) {
      var el = overlay.get('element');
      // may be featuredisplay present
      while (el.firstChild) {
        el.removeChild(el.firstChild);
      }
      el.appendChild(form.cloneNode(true));

      var feature = e.element;
      $('#featureformid').value = feature.getId();
      // display existing atts
      var atts = feature.getProperties();
      var attsDiv = $('#featureformAtts');
      for (var att in atts) {
        if (att != 'geometry') {
          var clone = attsDiv.querySelector('template').content
              .firstElementChild.cloneNode(true);
          var inputs = clone.querySelectorAll('input');
          inputs[0].value = att;
          inputs[1].value = atts[att];
          attsDiv.insertBefore(clone, attsDiv.firstElementChild);
        }
      }
      // use onclick rather than addEventListener so only 1 active
      $('#plusbutton').onclick = plusFunc;
      $('#featurebutton').onclick = function() {
        feature.setId($('#featureformid').value);
        var divs = $('#featureformAtts').querySelectorAll('div');
        for (var i = 0; i < divs.length; i++) {
          var inputs = divs[i].querySelectorAll('input');
          if (inputs[1].value) {
            feature.set(inputs[0].value, inputs[1].value);
          } else {
            // delete attribute if value cleared
            feature.unset(inputs[0].value);
          }
        }
        el.style.display = 'none';
        // move feature to draw layer if not already there
        var layer = select.get().getLayer(feature);
        if (layer.get('id') !== DRAWID) {
          layer.get('source').removeFeature(feature);
          source.addFeatures([feature]);
        }
      };
      overlay.set('position', map.get('view').get('center'));
      el.style.display = 'block';
    });
    wasModAtts = true;
  } else if (wasModAtts) {
    ol.Observable.unByKey(evtKey);
    wasModAtts = false;
  }

  if (e.target.value == 'save' || e.target.value == 'saveall') {
    System.importModule('registry/components/serialise.js')
    .then(function(m) {
      m.default(e.target.value, DRAWID);
    });
  }

  if (e.target.value == 'split') {
    var origCoords;
    select.get().once('select', function(evt) {
      // this bit can be removed once modify event returns modified vertex
      if (evt.selected[0]) {
        origCoords = evt.selected[0].getGeometry().getCoordinates();
      }
    });
    interactions.split.once('modifyend', function(evt) {
      var f = evt.features.item(0);
      var modCoords = f.getGeometry().getCoordinates();
      // return true/false whether val is in arr
      function check(arr, val) {
        return arr.some(function(arrval) {
          // have to convert to strings as arrays won't be equal
          return val.toString() == arrval.toString();
        });
      }
      var i = 0;
      // compare each new coord with original until find non-match
      while (i < modCoords.length && check(origCoords, modCoords[i]) === true) {
        i++;
      }
      var part1 = modCoords.slice(0, i + 1); // include the new coord in both parts
      var part2 = modCoords.slice(i);
      var atts = f.getProperties();
      atts.geometry = new ol.geom.LineString(part1);
      var f1 = new ol.Feature(atts);
      atts.geometry = new ol.geom.LineString(part2);
      var f2 = new ol.Feature(atts);
      // effectively, this moves a split feature on layer other than draw layer to the draw layer
      source.addFeatures([f1, f2]);
      select.get().getLayer(f).get('source').removeFeature(f);
      features.clear();
      e.target.value = 'choose';
      changeFunction(e);
    });
  }

  if (e.target.value == 'join') {
    joinKey = select.get().on('select', function(evt) {
      if (features.get('length') == 2) {
        var f1 = features.item(0);
        var f2 = features.item(1);
        var atts = f1.getProperties();
        atts.geometry = new ol.geom.LineString(
          f1.getGeometry().getCoordinates().concat(f2.getGeometry().getCoordinates().slice(1))
        );
        var f3 = new ol.Feature(atts);
        features.clear();
        // effectively, this moves the joined feature on layer other than draw layer to the draw layer
        select.get().getLayer(f2).get('source').removeFeature(f2);
        select.get().getLayer(f1).get('source').removeFeature(f1);
        source.addFeatures([f3]);
      }
    });
  } else if (joinKey) {
    ol.Observable.unByKey(joinKey);
    joinKey = null;
  }

  if (e.target.value == 'clear') {
    source.clear();
  }

  $('#drawtype').blur();
}

// add helptemplate to toolbar
$('#help-content').appendChild(component.getTemplate('drawhelp'));
// add toggle listener to helptitle div
$('#draw-title').addEventListener('click', function() {
  // toggle display of next, i.e. content, element
  var el = this.nextElementSibling;
  el.style.display = (el.style.display == 'block') ? 'none' : 'block';
});
<|endoftext|>


<commit_before>/**
 @module resources/wds/Workspaces
 The Twilio WDS "Workspaces" Resource.
 */
var _ = require('underscore');
var generate = require('../generate');

module.exports = function (client) {
    var baseResourceUrl = '/Accounts/' + client.accountSid + '/Workspaces';

//    //Define sub-resources on the accounts resource, with the given account SID
//    function mixinResources(obj, workspaceSid) {
//        var subresources = {
//            statistics:require('./Statistics')(client, workspaceSid)
//        };
//
//        _.extend(obj, subresources);
//    }

    //Instance requests
    function Workspaces(sid) {
        var resourceApi = {};

        //Add standard instance resource functions
        generate.restFunctions(resourceApi, client, ['DELETE', 'GET', 'POST', {update: 'POST'}], baseResourceUrl + '/' + sid);

        //Add activities sub-resource
        resourceApi.activities = function(activitySid) {
            var activityResourceApi = {
                get: generate(client, 'GET', baseResourceUrl + '/' + sid + '/Activities/' + activitySid),
                post: generate(client, 'POST', baseResourceUrl + '/' + sid + '/Activities/' + activitySid),
                delete: generate(client, 'DELETE', baseResourceUrl + '/' + sid + '/Activities/' + activitySid)
            };

            //Aliases
            activityResourceApi.update = activityResourceApi.post;

            return activityResourceApi;
        };

        resourceApi.activities.get = generate(client, 'GET', baseResourceUrl + '/' + sid + '/Activities');
        resourceApi.activities.post = generate(client, 'POST', baseResourceUrl + '/' + sid + '/Activities');

        //Aliases
        resourceApi.activities.list = resourceApi.activities.get;
        resourceApi.activities.create = resourceApi.activities.post;

        //Add tasks sub-resource
        resourceApi.tasks = function(taskSid) {
            var taskResourceApi = {
                get: generate(client, 'GET', baseResourceUrl + '/' + sid + '/Tasks/' + taskSid),
                post: generate(client, 'POST', baseResourceUrl + '/' + sid + '/Tasks/' + taskSid),
                delete: generate(client, 'DELETE', baseResourceUrl + '/' + sid + '/Tasks/' + taskSid)
            };

            //Aliases
            taskResourceApi.update = taskResourceApi.post;

            // Add reservation sub-resource
            taskResourceApi.reservations = function(reservationSid) {
                var reservationResourceApi = {
                    get: generate(client, 'GET', baseResourceUrl + '/' + sid + '/Tasks/' + taskSid + '/Reservations/' + reservationSid),
                    post: generate(client, 'POST', baseResourceUrl + '/' + sid + '/Tasks/' + taskSid + '/Reservations/' + reservationSid)
                };

                //Aliases
                reservationResourceApi.update = reservationResourceApi.post;

                return reservationResourceApi;
            };

            taskResourceApi.reservations.get = generate(client, 'GET', baseResourceUrl + '/' + sid + '/Tasks/' + taskSid + '/Reservations');

            //Aliases
            taskResourceApi.reservations.list = taskResourceApi.reservations.get;

            return taskResourceApi;
        };

        resourceApi.tasks.get = generate(client, 'GET', baseResourceUrl + '/' + sid + '/Tasks');
        resourceApi.tasks.post = generate(client, 'POST', baseResourceUrl + '/' + sid + '/Tasks');

        //Aliases
        resourceApi.tasks.list = resourceApi.tasks.get;
        resourceApi.tasks.create = resourceApi.tasks.post;

        //Add taskQueues sub-resource
        resourceApi.taskQueues = function(taskQueueSid) {
            var taskQueueResourceApi = {
                get: generate(client, 'GET', baseResourceUrl + '/' + sid + '/TaskQueues/' + taskQueueSid),
                post: generate(client, 'POST', baseResourceUrl + '/' + sid + '/TaskQueues/' + taskQueueSid),
                delete: generate(client, 'DELETE', baseResourceUrl + '/' + sid + '/TaskQueues/' + taskQueueSid)
            };

            //Aliases
            taskQueueResourceApi.update = taskQueueResourceApi.post;

            // Statistics
            taskQueueResourceApi.statistics = function() {
                return {};
            }

            taskQueueResourceApi.statistics.get = generate(client, 'GET', baseResourceUrl + '/' + sid + '/Statistics/TaskQueues/' + taskQueueSid);

            return taskQueueResourceApi;
        };

        resourceApi.taskQueues.statistics = function() {
            return {};
        }

        resourceApi.taskQueues.statistics.get = generate(client, 'GET', baseResourceUrl + '/' + sid + '/Statistics/TaskQueues');
        resourceApi.taskQueues.statistics.list = resourceApi.taskQueues.statistics.get;

        resourceApi.taskQueues.get = generate(client, 'GET', baseResourceUrl + '/' + sid + '/TaskQueues');
        resourceApi.taskQueues.post = generate(client, 'POST', baseResourceUrl + '/' + sid + '/TaskQueues');

        //Aliases
        resourceApi.taskQueues.list = resourceApi.taskQueues.get;
        resourceApi.taskQueues.create = resourceApi.taskQueues.post;

        //Add workers sub-resource
        resourceApi.workers = function(workerSid) {
            var workerResourceApi = {
                get: generate(client, 'GET', baseResourceUrl + '/' + sid + '/Workers/' + workerSid),
                post: generate(client, 'POST', baseResourceUrl + '/' + sid + '/Workers/' + workerSid),
                delete: generate(client, 'DELETE', baseResourceUrl + '/' + sid + '/Workers/' + workerSid)
            };

            //Aliases
            workerResourceApi.update = workerResourceApi.post;

            // Statistics
            workerResourceApi.statistics = function() {
                return {};
            }

            workerResourceApi.statistics.get = generate(client, 'GET', baseResourceUrl + '/' + sid + '/Statistics/Workers/' + workerSid);

            return workerResourceApi;
        };

        resourceApi.workers.statistics = function() {
            return {};
        }

        resourceApi.workers.statistics.get = generate(client, 'GET', baseResourceUrl + '/' + sid + '/Statistics/Workers');
        resourceApi.workers.statistics.list = resourceApi.workers.statistics.get;

        resourceApi.workers.get = generate(client, 'GET', baseResourceUrl + '/' + sid + '/Workers');
        resourceApi.workers.post = generate(client, 'POST', baseResourceUrl + '/' + sid + '/Workers');

        //Aliases
        resourceApi.workers.list = resourceApi.workers.get;
        resourceApi.workers.create = resourceApi.workers.post;

        //Add workflows sub-resource
        resourceApi.workflows = function(workflowSid) {
            var workflowResourceApi = {
                get: generate(client, 'GET', baseResourceUrl + '/' + sid + '/Workflows/' + workflowSid),
                post: generate(client, 'POST', baseResourceUrl + '/' + sid + '/Workflows/' + workflowSid),
                delete: generate(client, 'DELETE', baseResourceUrl + '/' + sid + '/Workflows/' + workflowSid)
            };

            //Aliases
            workflowResourceApi.update = workflowResourceApi.post;

            workflowResourceApi.statistics = function() {
                return {};
            };

            workflowResourceApi.statistics.get = generate(client, 'GET', baseResourceUrl + '/' + sid + '/Statistics/Workflows/' + workflowSid);

            return workflowResourceApi;
        };

        resourceApi.workflows.get = generate(client, 'GET', baseResourceUrl + '/' + sid + '/Workflows');
        resourceApi.workflows.post = generate(client, 'POST', baseResourceUrl + '/' + sid + '/Workflows');

        //Aliases
        resourceApi.workflows.list = resourceApi.workflows.get;
        resourceApi.workflows.create = resourceApi.workflows.post;

        // Statistics
        resourceApi.statistics = function() {
            return {};
        }

        resourceApi.statistics.get = generate(client, 'GET', baseResourceUrl + '/' + sid + '/Statistics');

        return resourceApi;
    }

    //List requests
    generate.restFunctions(Workspaces, client, ['GET', 'POST', {create: 'POST'}], baseResourceUrl);

    //Define other sub-resources of Workspaces
//    mixinResources(Workspaces, client.workspaceSid);

    return Workspaces;
};
<commit_msg>Clean-up<commit_after>/**
 @module resources/wds/Workspaces
 The Twilio WDS "Workspaces" Resource.
 */
var generate = require('../generate');

module.exports = function (client) {
    var baseResourceUrl = '/Accounts/' + client.accountSid + '/Workspaces';

    //Instance requests
    function Workspaces(sid) {
        var resourceApi = {};

        //Add standard instance resource functions
        generate.restFunctions(resourceApi, client, ['DELETE', 'GET', 'POST', {update: 'POST'}], baseResourceUrl + '/' + sid);

        //Add activities sub-resource
        resourceApi.activities = function(activitySid) {
            var activityResourceApi = {
                get: generate(client, 'GET', baseResourceUrl + '/' + sid + '/Activities/' + activitySid),
                post: generate(client, 'POST', baseResourceUrl + '/' + sid + '/Activities/' + activitySid),
                delete: generate(client, 'DELETE', baseResourceUrl + '/' + sid + '/Activities/' + activitySid)
            };

            //Aliases
            activityResourceApi.update = activityResourceApi.post;

            return activityResourceApi;
        };

        resourceApi.activities.get = generate(client, 'GET', baseResourceUrl + '/' + sid + '/Activities');
        resourceApi.activities.post = generate(client, 'POST', baseResourceUrl + '/' + sid + '/Activities');

        //Aliases
        resourceApi.activities.list = resourceApi.activities.get;
        resourceApi.activities.create = resourceApi.activities.post;

        //Add tasks sub-resource
        resourceApi.tasks = function(taskSid) {
            var taskResourceApi = {
                get: generate(client, 'GET', baseResourceUrl + '/' + sid + '/Tasks/' + taskSid),
                post: generate(client, 'POST', baseResourceUrl + '/' + sid + '/Tasks/' + taskSid),
                delete: generate(client, 'DELETE', baseResourceUrl + '/' + sid + '/Tasks/' + taskSid)
            };

            //Aliases
            taskResourceApi.update = taskResourceApi.post;

            // Add reservation sub-resource
            taskResourceApi.reservations = function(reservationSid) {
                var reservationResourceApi = {
                    get: generate(client, 'GET', baseResourceUrl + '/' + sid + '/Tasks/' + taskSid + '/Reservations/' + reservationSid),
                    post: generate(client, 'POST', baseResourceUrl + '/' + sid + '/Tasks/' + taskSid + '/Reservations/' + reservationSid)
                };

                //Aliases
                reservationResourceApi.update = reservationResourceApi.post;

                return reservationResourceApi;
            };

            taskResourceApi.reservations.get = generate(client, 'GET', baseResourceUrl + '/' + sid + '/Tasks/' + taskSid + '/Reservations');

            //Aliases
            taskResourceApi.reservations.list = taskResourceApi.reservations.get;

            return taskResourceApi;
        };

        resourceApi.tasks.get = generate(client, 'GET', baseResourceUrl + '/' + sid + '/Tasks');
        resourceApi.tasks.post = generate(client, 'POST', baseResourceUrl + '/' + sid + '/Tasks');

        //Aliases
        resourceApi.tasks.list = resourceApi.tasks.get;
        resourceApi.tasks.create = resourceApi.tasks.post;

        //Add taskQueues sub-resource
        resourceApi.taskQueues = function(taskQueueSid) {
            var taskQueueResourceApi = {
                get: generate(client, 'GET', baseResourceUrl + '/' + sid + '/TaskQueues/' + taskQueueSid),
                post: generate(client, 'POST', baseResourceUrl + '/' + sid + '/TaskQueues/' + taskQueueSid),
                delete: generate(client, 'DELETE', baseResourceUrl + '/' + sid + '/TaskQueues/' + taskQueueSid)
            };

            //Aliases
            taskQueueResourceApi.update = taskQueueResourceApi.post;

            // Statistics
            taskQueueResourceApi.statistics = function() {
                return {};
            }

            taskQueueResourceApi.statistics.get = generate(client, 'GET', baseResourceUrl + '/' + sid + '/Statistics/TaskQueues/' + taskQueueSid);

            return taskQueueResourceApi;
        };

        resourceApi.taskQueues.statistics = function() {
            return {};
        }

        resourceApi.taskQueues.statistics.get = generate(client, 'GET', baseResourceUrl + '/' + sid + '/Statistics/TaskQueues');
        resourceApi.taskQueues.statistics.list = resourceApi.taskQueues.statistics.get;

        resourceApi.taskQueues.get = generate(client, 'GET', baseResourceUrl + '/' + sid + '/TaskQueues');
        resourceApi.taskQueues.post = generate(client, 'POST', baseResourceUrl + '/' + sid + '/TaskQueues');

        //Aliases
        resourceApi.taskQueues.list = resourceApi.taskQueues.get;
        resourceApi.taskQueues.create = resourceApi.taskQueues.post;

        //Add workers sub-resource
        resourceApi.workers = function(workerSid) {
            var workerResourceApi = {
                get: generate(client, 'GET', baseResourceUrl + '/' + sid + '/Workers/' + workerSid),
                post: generate(client, 'POST', baseResourceUrl + '/' + sid + '/Workers/' + workerSid),
                delete: generate(client, 'DELETE', baseResourceUrl + '/' + sid + '/Workers/' + workerSid)
            };

            //Aliases
            workerResourceApi.update = workerResourceApi.post;

            // Statistics
            workerResourceApi.statistics = function() {
                return {};
            }

            workerResourceApi.statistics.get = generate(client, 'GET', baseResourceUrl + '/' + sid + '/Statistics/Workers/' + workerSid);

            return workerResourceApi;
        };

        resourceApi.workers.statistics = function() {
            return {};
        }

        resourceApi.workers.statistics.get = generate(client, 'GET', baseResourceUrl + '/' + sid + '/Statistics/Workers');
        resourceApi.workers.statistics.list = resourceApi.workers.statistics.get;

        resourceApi.workers.get = generate(client, 'GET', baseResourceUrl + '/' + sid + '/Workers');
        resourceApi.workers.post = generate(client, 'POST', baseResourceUrl + '/' + sid + '/Workers');

        //Aliases
        resourceApi.workers.list = resourceApi.workers.get;
        resourceApi.workers.create = resourceApi.workers.post;

        //Add workflows sub-resource
        resourceApi.workflows = function(workflowSid) {
            var workflowResourceApi = {
                get: generate(client, 'GET', baseResourceUrl + '/' + sid + '/Workflows/' + workflowSid),
                post: generate(client, 'POST', baseResourceUrl + '/' + sid + '/Workflows/' + workflowSid),
                delete: generate(client, 'DELETE', baseResourceUrl + '/' + sid + '/Workflows/' + workflowSid)
            };

            //Aliases
            workflowResourceApi.update = workflowResourceApi.post;

            workflowResourceApi.statistics = function() {
                return {};
            };

            workflowResourceApi.statistics.get = generate(client, 'GET', baseResourceUrl + '/' + sid + '/Statistics/Workflows/' + workflowSid);

            return workflowResourceApi;
        };

        resourceApi.workflows.get = generate(client, 'GET', baseResourceUrl + '/' + sid + '/Workflows');
        resourceApi.workflows.post = generate(client, 'POST', baseResourceUrl + '/' + sid + '/Workflows');

        //Aliases
        resourceApi.workflows.list = resourceApi.workflows.get;
        resourceApi.workflows.create = resourceApi.workflows.post;

        // Statistics
        resourceApi.statistics = function() {
            return {};
        }

        resourceApi.statistics.get = generate(client, 'GET', baseResourceUrl + '/' + sid + '/Statistics');

        return resourceApi;
    }

    //List requests
    generate.restFunctions(Workspaces, client, ['GET', 'POST', {create: 'POST'}], baseResourceUrl);

    return Workspaces;
};
<|endoftext|>


<commit_before>var _ = require('lodash'),

    // total number of replays allowed
    MAX_REPLAY_COUNT = 3,

    ReplayController;

/**
 * Handles replay logic with replayState from context.
 * Makes sure request replays do not go into an infinite loop.
 *
 * @param {ReplayState} replayState
 * @param {Run} run
 *
 * @constructor
 */
ReplayController = function ReplayController (replayState, run) {
    // store state
    this.count = replayState ? replayState.count : 0;
    this.run = run;
};

_.assign(ReplayController.prototype, /** @lends ReplayController.prototype */{
    /**
     * Sends a request in the item. This takes care of limiting the total number of replays for a request.
     *
     * @param {Object} context
     * @param {Request} item
     * @param {Object} desiredPayload a partial payload to use for the replay request
     * @param {Function} success this callback is invoked when replay controller sent the request
     * @param {Function} failure this callback is invoked when replay controller decided not to send the request
     */
    requestReplay: function (context, item, desiredPayload, success, failure) {
        // max retries exceeded
        if (this.count >= MAX_REPLAY_COUNT) {
            return failure(new Error('runtime: maximum intermediate request limit exceeded'));
        }

        // update replay count state
        this.count++;

        // update replay state to context
        context.replayState = this.getReplayState();

        // set item parent
        !item.parent() && item.setParent(context.originalItem);

        // construct payload for request
        var payload = _.merge({}, desiredPayload, context, {
            item: item,
            // abortOnError makes sure request command bubbles errors
            // so we can pass it on to the callback
            abortOnError: true
        });

        this.run.immediate('httprequest', payload)
            .done(function (response) {
                success(null, response);
            })
            .catch(success);
    },

    /**
     * Returns a serialized version of current ReplayController
     *
     * @returns {ReplayState}
     */
    getReplayState: function () {
        /**
         * Defines the current replay state of a request.
         *
         * By replay state, we mean the number of requests sent
         * as part of one Collection requests. It can be intermediate requests,
         * or replays of the same collection requests.
         *
         * @typedef {Object} ReplayState
         *
         * @property {Number} count total number of requests, including Collection requests and replays
         */
        return {
            count: this.count
        };
    }
});

module.exports = ReplayController;
<commit_msg>Remove `item.setParent` in replay-controller<commit_after>var _ = require('lodash'),

    // total number of replays allowed
    MAX_REPLAY_COUNT = 3,

    ReplayController;

/**
 * Handles replay logic with replayState from context.
 * Makes sure request replays do not go into an infinite loop.
 *
 * @param {ReplayState} replayState
 * @param {Run} run
 *
 * @constructor
 */
ReplayController = function ReplayController (replayState, run) {
    // store state
    this.count = replayState ? replayState.count : 0;
    this.run = run;
};

_.assign(ReplayController.prototype, /** @lends ReplayController.prototype */{
    /**
     * Sends a request in the item. This takes care of limiting the total number of replays for a request.
     *
     * @param {Object} context
     * @param {Request} item
     * @param {Object} desiredPayload a partial payload to use for the replay request
     * @param {Function} success this callback is invoked when replay controller sent the request
     * @param {Function} failure this callback is invoked when replay controller decided not to send the request
     */
    requestReplay: function (context, item, desiredPayload, success, failure) {
        // max retries exceeded
        if (this.count >= MAX_REPLAY_COUNT) {
            return failure(new Error('runtime: maximum intermediate request limit exceeded'));
        }

        // update replay count state
        this.count++;

        // update replay state to context
        context.replayState = this.getReplayState();

        // construct payload for request
        var payload = _.merge({}, desiredPayload, context, {
            item: item,
            // abortOnError makes sure request command bubbles errors
            // so we can pass it on to the callback
            abortOnError: true
        });

        this.run.immediate('httprequest', payload)
            .done(function (response) {
                success(null, response);
            })
            .catch(success);
    },

    /**
     * Returns a serialized version of current ReplayController
     *
     * @returns {ReplayState}
     */
    getReplayState: function () {
        /**
         * Defines the current replay state of a request.
         *
         * By replay state, we mean the number of requests sent
         * as part of one Collection requests. It can be intermediate requests,
         * or replays of the same collection requests.
         *
         * @typedef {Object} ReplayState
         *
         * @property {Number} count total number of requests, including Collection requests and replays
         */
        return {
            count: this.count
        };
    }
});

module.exports = ReplayController;
<|endoftext|>


<commit_before>import {createSelector} from 'reselect'

import selectModificationRoute from './modification-route'

export default createSelector(
  selectModificationRoute,
  (route) => route ? route.patterns : []
)
<commit_msg>fix(patterns): Ensure patterns exist in selector.<commit_after>import {createSelector} from 'reselect'

import selectModificationRoute from './modification-route'

export default createSelector(
  selectModificationRoute,
  (route) => route && route.patterns ? route.patterns : []
)
<|endoftext|>


<commit_before>var gcm = require("node-gcm");

function sendNotificationEndpoint(req, res) {
    var recipients = req.body.to;

    if(!recipients) {
        res.status(403);
        return res.send({
            success: false,
            message: "Missing argument required `to`."
        });
    }

    var message = new gcm.Message(req.body);

    var apiKey = (req.vars && req.vars.apiKey) || req.body.apiKey;

    if(!apiKey) {
        res.status(403);
        return res.send({
            success: false,
            message: "Missing required argument `apiKey`."
        });
    }

    var sender = new gcm.Sender(apiKey);

    sender.send(message, recipients, function(error, result) {
        if(error) {
            console.error("Failed to send message", error);
            res.status(500);
            return res.send({
                success: false,
                message: "Sending failed",
                error: error
            });
        }
        res.send({
            success: true,
            message: "Message sent.",
            result: result
        });
    });
}

module.exports = sendNotificationEndpoint;
<commit_msg>word ordering<commit_after>var gcm = require("node-gcm");

function sendNotificationEndpoint(req, res) {
    var recipients = req.body.to;

    if(!recipients) {
        res.status(403);
        return res.send({
            success: false,
            message: "Missing required argument `to`."
        });
    }

    var message = new gcm.Message(req.body);

    var apiKey = (req.vars && req.vars.apiKey) || req.body.apiKey;

    if(!apiKey) {
        res.status(403);
        return res.send({
            success: false,
            message: "Missing required argument `apiKey`."
        });
    }

    var sender = new gcm.Sender(apiKey);

    sender.send(message, recipients, function(error, result) {
        if(error) {
            console.error("Failed to send message", error);
            res.status(500);
            return res.send({
                success: false,
                message: "Sending failed",
                error: error
            });
        }
        res.send({
            success: true,
            message: "Message sent.",
            result: result
        });
    });
}

module.exports = sendNotificationEndpoint;
<|endoftext|>


<commit_before>var _ = require('lodash/dist/lodash.underscore');
var path = require('path');
var Wreck = require('wreck');

var hoodiejs = require('../../../helpers/pack_hoodie');
var plugins = require('../../../helpers/plugin_api');

var internals = {
  uiKitPath: '/node_modules/hoodie-server/node_modules/hoodie-admin-dashboard-uikit/dist/',
  mapProxyPath: function (request, callback) {
    //use the bearer token as the cookie AuthSession for couchdb:
    if (request.headers.authorization && request.headers.authorization.substring(0, 'Bearer '.length) === 'Bearer ') {
      request.headers.cookie = 'AuthSession=' + request.headers.authorization.substring('Bearer '.length);
    } else {
      delete request.headers.cookie;
    }
    request.headers.host = [internals.couchCfg.host, internals.couchCfg.port].join(':');
    callback(null, internals.couchCfg.url + request.url.path.substr('/_api'.length), request.headers);
  },
  notFound: function (request, reply) {
    reply({
      'error': 'not found'
    }).code(404);
  },
  extractToken: function (cookieHeader) {
    var result = (/AuthSession=(.*); Version(.*)/).exec(cookieHeader[0]);
    if (Array.isArray(result)) {
      return result[1];
    }
  },
  addCorsAndBearerToken: function (err, res, request, reply) {

    if (err) {
      reply(err).code(500);
      return;
    }

    Wreck.read(res, {
      json: true
    }, function (err, data) {
      var resp;
      var allowedHeaders = [
        'authorization',
        'content-length',
        'content-type',
        'if-match',
        'if-none-match',
        'origin',
        'x-requested-with'
      ];

      function addAllowedHeaders(arr) {
        for (var i = 0; i < arr.length; i++) {
          if (allowedHeaders.indexOf(arr[i].trim().toLowerCase()) === -1) {
            allowedHeaders.push(arr[i].trim().toLowerCase());
          }
        }
      }

      if (err) {
        reply(err).code(500);
        return;
      }

      if (Array.isArray(res.headers['set-cookie'])) {
        data.bearerToken = internals.extractToken(res.headers['set-cookie']);
        delete res.headers['set-cookie'];
      }

      addAllowedHeaders(Object.keys(request.headers));

      if (request.method === 'options') {
        res.statusCode = 200;
        if (request.headers['Allow-Control-Request-Headers']) {
          addAllowedHeaders(request.headers['Allow-Control-Request-Headers'].split(','));
        }
      }

      // hapi eats newlines. We like newlines. For POSIX and such.
      // data = data + '\n';

      resp = reply(data).code(res.statusCode).hold();
      resp.headers = res.headers;
      resp.headers['content-length'] = data.length;
      resp.headers['access-control-allow-origin'] = request.headers.origin || '*';
      resp.headers['access-control-allow-headers'] = allowedHeaders.join(', ');
      resp.headers['access-control-expose-headers'] = 'content-type, content-length, etag';
      resp.headers['access-control-allow-methods'] = 'GET, PUT, POST, DELETE';
      resp.headers['access-control-allow-credentials'] = 'true';
      resp.send();
    });
  }
};

exports.register = function (plugin, options, next) {
  // FIXME: have to define these three internals here,
  //     because they rely on `options` being defined.
  //     This means we can't write unit tests for them,
  //     and it's also in itself ugly. Is there a nicer
  //     way of doing this?
  internals.couchCfg = options.app.couch;
  internals.handlePluginRequest = function (request, reply) {

    var hooks = options.app.hooks;
    var pluginName = request.params.name;
    if (!pluginName) {
      internals.notFound(request, reply);
      return;
    }

    var ran_hook = hooks.runDynamicForPlugin(pluginName, 'server.api.plugin-request', [request, reply]);

    if (!ran_hook) {
      internals.notFound(request, reply);
    }
  };
  internals.getHoodiePath = function (request, reply) {
    reply(hoodiejs(options.app))
    .type('application/javascript')
    .header(
      'Pragma', 'no-cache'
    );
  };

  plugin.route([
    {
      method: 'OPTIONS',
      path: '/_api/{p*}',
      handler: {
        proxy: {
          passThrough: true,
          mapUri: internals.mapProxyPath,
          onResponse: internals.addCorsAndBearerToken
        }
      }
    },
    {
      method: 'GET',
      path: '/_api/_all_dbs',
      handler: internals.notFound
    },
    {
      method: 'GET',
      path: '/_api/{p*}',
      handler: {
        proxy: {
          passThrough: true,
          mapUri: internals.mapProxyPath,
          onResponse: internals.addCorsAndBearerToken
        }
      }
    },
    {
      method: 'PUT',
      path: '/_api/{p*}',
      handler: {
        proxy: {
          passThrough: true,
          mapUri: internals.mapProxyPath,
          onResponse: internals.addCorsAndBearerToken
        }
      }
    },
    {
      method: 'POST',
      path: '/_api/{p*}',
      handler: {
        proxy: {
          passThrough: true,
          mapUri: internals.mapProxyPath,
          onResponse: internals.addCorsAndBearerToken
        }
      }
    },
    {
      method: 'DELETE',
      path: '/_api/{p*}',
      handler: {
        proxy: {
          passThrough: true,
          mapUri: internals.mapProxyPath,
          onResponse: internals.addCorsAndBearerToken
        }
      }
    },
    {
      method: 'GET',
      path: '/_api/_plugins',
      handler: function (req, res) {
        res(plugins.metadata(options.app));
      }
    },
    {
      method: 'GET',
      path: '/_api/_plugins/{name}',
      handler: function (request, reply) {
        var metaData = _.find(plugins.metadata(options.app), function (doc) {
          return doc.name === request.params.name;
        });

        if (!metaData) {
          reply({
            'error': 'not found'
          }).code(404);
        } else {
          reply(metaData);
        }
      }
    },
    {
      method: 'HEAD',
      path: '/_api/_plugins/{name}/_api/{p*}',
      handler: internals.handlePluginRequest
    },
    {
      method: 'GET',
      path: '/_api/_plugins/{name}/_api/{p*}',
      handler: internals.handlePluginRequest
    },
    {
      method: 'PUT',
      path: '/_api/_plugins/{name}/_api/{p*}',
      handler: internals.handlePluginRequest
    },
    {
      method: 'POST',
      path: '/_api/_plugins/{name}/_api/{p*}',
      handler: internals.handlePluginRequest
    },
    {
      method: 'DELETE',
      path: '/_api/_plugins/{name}/_api/{p*}',
      handler: internals.handlePluginRequest
    },
    {
      method: 'GET',
      path: '/_api/_plugins/{name}/admin-dashboard',
      handler: function (req, res) {
        res.redirect('/');
      }
    },
    {
      method: 'GET',
      path: '/_api/_plugins/{name}/admin-dashboard/{path*}',
      handler: {
        directory: {
          path: function (request) {
            return plugins.admin_dashboards(options.app)[request.params.name];
          },
          listing: false,
          index: true
        }
      }
    },
    {
      method: 'GET',
      path: '/_api/_plugins/_assets/{path*}',
      handler: {
        directory: {
          path: function () {
            var projectDir = options.app.project_dir;
            var uiKitPath = internals.uiKitPath;
            if (!options.app.is_in_app) {
              // we are running inside hoodie-server, e.g. in dev and test mode
              uiKitPath = uiKitPath.replace('/node_modules/hoodie-server', '');
            }
            var p = path.join(projectDir, uiKitPath);
            return p;
          },
          listing: true,
          index: false
        }
      }
    },
    {
      method: 'GET',
      path: '/_api/_files/hoodie.js',
      handler: internals.getHoodiePath
    }
  ]);

  return next();
};

exports.register.attributes = {
  pkg: require('./package.json')
};

//for unit tests:
exports.internals = internals;
<commit_msg>fix(hooks): dynamic hooks do not return a boolean<commit_after>var _ = require('lodash/dist/lodash.underscore');
var path = require('path');
var Wreck = require('wreck');

var hoodiejs = require('../../../helpers/pack_hoodie');
var plugins = require('../../../helpers/plugin_api');

var internals = {
  uiKitPath: '/node_modules/hoodie-server/node_modules/hoodie-admin-dashboard-uikit/dist/',
  mapProxyPath: function (request, callback) {
    //use the bearer token as the cookie AuthSession for couchdb:
    if (request.headers.authorization && request.headers.authorization.substring(0, 'Bearer '.length) === 'Bearer ') {
      request.headers.cookie = 'AuthSession=' + request.headers.authorization.substring('Bearer '.length);
    } else {
      delete request.headers.cookie;
    }
    request.headers.host = [internals.couchCfg.host, internals.couchCfg.port].join(':');
    callback(null, internals.couchCfg.url + request.url.path.substr('/_api'.length), request.headers);
  },
  notFound: function (request, reply) {
    reply({
      'error': 'not found'
    }).code(404);
  },
  extractToken: function (cookieHeader) {
    var result = (/AuthSession=(.*); Version(.*)/).exec(cookieHeader[0]);
    if (Array.isArray(result)) {
      return result[1];
    }
  },
  addCorsAndBearerToken: function (err, res, request, reply) {

    if (err) {
      reply(err).code(500);
      return;
    }

    Wreck.read(res, {
      json: true
    }, function (err, data) {
      var resp;
      var allowedHeaders = [
        'authorization',
        'content-length',
        'content-type',
        'if-match',
        'if-none-match',
        'origin',
        'x-requested-with'
      ];

      function addAllowedHeaders(arr) {
        for (var i = 0; i < arr.length; i++) {
          if (allowedHeaders.indexOf(arr[i].trim().toLowerCase()) === -1) {
            allowedHeaders.push(arr[i].trim().toLowerCase());
          }
        }
      }

      if (err) {
        reply(err).code(500);
        return;
      }

      if (Array.isArray(res.headers['set-cookie'])) {
        data.bearerToken = internals.extractToken(res.headers['set-cookie']);
        delete res.headers['set-cookie'];
      }

      addAllowedHeaders(Object.keys(request.headers));

      if (request.method === 'options') {
        res.statusCode = 200;
        if (request.headers['Allow-Control-Request-Headers']) {
          addAllowedHeaders(request.headers['Allow-Control-Request-Headers'].split(','));
        }
      }

      // hapi eats newlines. We like newlines. For POSIX and such.
      // data = data + '\n';

      resp = reply(data).code(res.statusCode).hold();
      resp.headers = res.headers;
      resp.headers['content-length'] = data.length;
      resp.headers['access-control-allow-origin'] = request.headers.origin || '*';
      resp.headers['access-control-allow-headers'] = allowedHeaders.join(', ');
      resp.headers['access-control-expose-headers'] = 'content-type, content-length, etag';
      resp.headers['access-control-allow-methods'] = 'GET, PUT, POST, DELETE';
      resp.headers['access-control-allow-credentials'] = 'true';
      resp.send();
    });
  }
};

exports.register = function (plugin, options, next) {
  // FIXME: have to define these three internals here,
  //     because they rely on `options` being defined.
  //     This means we can't write unit tests for them,
  //     and it's also in itself ugly. Is there a nicer
  //     way of doing this?
  internals.couchCfg = options.app.couch;
  internals.handlePluginRequest = function (request, reply) {

    var hooks = options.app.hooks;
    var pluginName = request.params.name;
    if (!pluginName) {
      internals.notFound(request, reply);
      return;
    }

    // the plugin is now responsible to call `reply()`
    hooks.runDynamicForPlugin(pluginName, 'server.api.plugin-request', [request, reply]);
  };
  internals.getHoodiePath = function (request, reply) {
    reply(hoodiejs(options.app))
    .type('application/javascript')
    .header(
      'Pragma', 'no-cache'
    );
  };

  plugin.route([
    {
      method: 'OPTIONS',
      path: '/_api/{p*}',
      handler: {
        proxy: {
          passThrough: true,
          mapUri: internals.mapProxyPath,
          onResponse: internals.addCorsAndBearerToken
        }
      }
    },
    {
      method: 'GET',
      path: '/_api/_all_dbs',
      handler: internals.notFound
    },
    {
      method: 'GET',
      path: '/_api/{p*}',
      handler: {
        proxy: {
          passThrough: true,
          mapUri: internals.mapProxyPath,
          onResponse: internals.addCorsAndBearerToken
        }
      }
    },
    {
      method: 'PUT',
      path: '/_api/{p*}',
      handler: {
        proxy: {
          passThrough: true,
          mapUri: internals.mapProxyPath,
          onResponse: internals.addCorsAndBearerToken
        }
      }
    },
    {
      method: 'POST',
      path: '/_api/{p*}',
      handler: {
        proxy: {
          passThrough: true,
          mapUri: internals.mapProxyPath,
          onResponse: internals.addCorsAndBearerToken
        }
      }
    },
    {
      method: 'DELETE',
      path: '/_api/{p*}',
      handler: {
        proxy: {
          passThrough: true,
          mapUri: internals.mapProxyPath,
          onResponse: internals.addCorsAndBearerToken
        }
      }
    },
    {
      method: 'GET',
      path: '/_api/_plugins',
      handler: function (req, res) {
        res(plugins.metadata(options.app));
      }
    },
    {
      method: 'GET',
      path: '/_api/_plugins/{name}',
      handler: function (request, reply) {
        var metaData = _.find(plugins.metadata(options.app), function (doc) {
          return doc.name === request.params.name;
        });

        if (!metaData) {
          reply({
            'error': 'not found'
          }).code(404);
        } else {
          reply(metaData);
        }
      }
    },
    {
      method: 'HEAD',
      path: '/_api/_plugins/{name}/_api/{p*}',
      handler: internals.handlePluginRequest
    },
    {
      method: 'GET',
      path: '/_api/_plugins/{name}/_api/{p*}',
      handler: internals.handlePluginRequest
    },
    {
      method: 'PUT',
      path: '/_api/_plugins/{name}/_api/{p*}',
      handler: internals.handlePluginRequest
    },
    {
      method: 'POST',
      path: '/_api/_plugins/{name}/_api/{p*}',
      handler: internals.handlePluginRequest
    },
    {
      method: 'DELETE',
      path: '/_api/_plugins/{name}/_api/{p*}',
      handler: internals.handlePluginRequest
    },
    {
      method: 'GET',
      path: '/_api/_plugins/{name}/admin-dashboard',
      handler: function (req, res) {
        res.redirect('/');
      }
    },
    {
      method: 'GET',
      path: '/_api/_plugins/{name}/admin-dashboard/{path*}',
      handler: {
        directory: {
          path: function (request) {
            return plugins.admin_dashboards(options.app)[request.params.name];
          },
          listing: false,
          index: true
        }
      }
    },
    {
      method: 'GET',
      path: '/_api/_plugins/_assets/{path*}',
      handler: {
        directory: {
          path: function () {
            var projectDir = options.app.project_dir;
            var uiKitPath = internals.uiKitPath;
            if (!options.app.is_in_app) {
              // we are running inside hoodie-server, e.g. in dev and test mode
              uiKitPath = uiKitPath.replace('/node_modules/hoodie-server', '');
            }
            var p = path.join(projectDir, uiKitPath);
            return p;
          },
          listing: true,
          index: false
        }
      }
    },
    {
      method: 'GET',
      path: '/_api/_files/hoodie.js',
      handler: internals.getHoodiePath
    }
  ]);

  return next();
};

exports.register.attributes = {
  pkg: require('./package.json')
};

//for unit tests:
exports.internals = internals;
<|endoftext|>


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

// Module dependencies.
var util = require('util');
var url = require('url');

var azureutil = require('../../util/util');

var ServiceClient = require('../serviceclient');
var ServiceBusServiceClient = require('../servicebusserviceclient');
var WrapTokenManager = require('./wraptokenmanager');

var WebResource = require('../../http/webresource');
var Constants = require('../../util/constants');
var QueryStringConstants = Constants.QueryStringConstants;
var HttpConstants = Constants.HttpConstants;
var HeaderConstants = Constants.HeaderConstants;

// Expose 'Wrap'.
exports = module.exports = Wrap;

/**
* Creates a new Wrap object.
*
* @param {string} acsnamespace            The access control namespace.
* @param {string} issuer                  The service bus issuer.
* @param {string} password                The service bus issuer password.
* @param {string} host                    The host for the service.
*/
function Wrap(acsnamespace, issuer, password, host) {
  this.wrapTokenManager = new WrapTokenManager(acsnamespace, issuer, password, host);
}

/**
* Signs a request with the Authentication header.
*
* @param {WebResource} The webresource to be signed.
* @return {Void}
*/
Wrap.prototype.signRequest = function (webResource, callback) {
  var parsedUrl = url.parse(webResource.requestUrl);
  parsedUrl.protocol = 'http:';
  delete parsedUrl.path;
  delete parsedUrl.pathname;
  delete parsedUrl.host;
  delete parsedUrl.port;
  var requestUrl = url.format(parsedUrl);

  this.wrapTokenManager.getAccessToken(requestUrl, function (error, accessToken) {
    if (!error) {
      webResource.addOptionalHeader(HeaderConstants.AUTHORIZATION,
        "WRAP access_token=\"" + accessToken['wrap_access_token'] + "\"");
    }

    callback (error);
  });
};<commit_msg>Fixing wrap authentication - missing path / querystring.<commit_after>/**
* Copyright 2011 Microsoft Corporation
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*   http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

// Module dependencies.
var util = require('util');
var url = require('url');

var azureutil = require('../../util/util');

var ServiceClient = require('../serviceclient');
var ServiceBusServiceClient = require('../servicebusserviceclient');
var WrapTokenManager = require('./wraptokenmanager');

var WebResource = require('../../http/webresource');
var Constants = require('../../util/constants');
var QueryStringConstants = Constants.QueryStringConstants;
var HttpConstants = Constants.HttpConstants;
var HeaderConstants = Constants.HeaderConstants;

// Expose 'Wrap'.
exports = module.exports = Wrap;

/**
* Creates a new Wrap object.
*
* @param {string} acsnamespace            The access control namespace.
* @param {string} issuer                  The service bus issuer.
* @param {string} password                The service bus issuer password.
* @param {string} host                    The host for the service.
*/
function Wrap(acsnamespace, issuer, password, host) {
  this.wrapTokenManager = new WrapTokenManager(acsnamespace, issuer, password, host);
}

/**
* Signs a request with the Authentication header.
*
* @param {WebResource} The webresource to be signed.
* @return {Void}
*/
Wrap.prototype.signRequest = function (webResource, callback) {
  var parsedUrl = url.parse(webResource.requestUrl);
  parsedUrl.protocol = 'http:';
  delete parsedUrl.path;
  delete parsedUrl.host;
  delete parsedUrl.port;
  var requestUrl = url.format(parsedUrl);
  this.wrapTokenManager.getAccessToken(requestUrl, function (error, accessToken) {
    if (!error) {
      webResource.addOptionalHeader(HeaderConstants.AUTHORIZATION,
        "WRAP access_token=\"" + accessToken['wrap_access_token'] + "\"");
    }

    callback(error);
  });
};<|endoftext|>


<commit_before>// Read configs, init loggers, init apps, fills N object.

"use strict";

// stdlib
var path = require('path');
var fs   = require('fs');

// 3rd-party
var _           = require('lodash');
var fstools     = require("fs-tools");
var log4js      = require("log4js");
var revalidator = require("revalidator");
var yaml        = require('js-yaml');

// internal
var Application = require("./application");
var Wire        = require("../wire");

////////////////////////////////////////////////////////////////////////////////

// merge configs, respecting `~override: true` instructions
function mergeConfigs(dst, src) {
  _.each(src || {}, function (value, key) {

    // if destination exists & already has `~override` flag, keep it intact
    if (_.isObject(dst[key]) && dst[key]['~override']) {
      return;
    }

    // if source has `~override` flag - override whole value in destination
    if (value && value['~override']) {
      dst[key] = value;
      return;
    }

    // if both nodes are objects - merge recursively
    if (_.isObject(value) && _.isObject(dst[key])) {
      mergeConfigs(dst[key], value);
      return;
    }

    // destination node does not exist - create
    // or both nodes are of different types - override.
    dst[key] = value;
    return;
  });

  return dst;
}

// reads all *.yml files from `dir` and merge resulting objects into single one
function loadConfigs(root) {
  var config = {}
    , files = [];

  fstools.walkSync(root, /[.]yml$/, function (file, stat) {
    if (stat.isFile()) {
      files.push(file);
    }
  });

  // files order can change, but we shuld return the same result always
  files = files.sort();

  _.each(files, function (file) {
    mergeConfigs(config, yaml.safeLoad(fs.readFileSync(file, 'utf8'), { filename: file }));
  });

  return config;
}

// Returns an object with keys:
//
//   `responderName` (string)
//   `splittedMethod` (array)
//
// Each one may be `null` which means 'any'.
//
//   'rpc@'             => { responderName: 'rpc',  splittedMethod: null }
//   'http@forum.index' => { responderName: 'http', splittedMethod: [ 'forum', 'index' ] }
//   'blogs'            => { responderName: null,   splittedMethod: [ 'blogs' ] }
//
function parseLoggerName(name) {
  var responderName, splittedMethod, parts = name.split('@');

  if (1 === parts.length) {
    responderName  = null;
    splittedMethod = name.split('.');

  } else if (2 === parts.length) {
    responderName  = parts[0];
    splittedMethod = parts[1].split('.');

  } else {
    // Bad name format. Only one @ symbol is allowed.
    return null;
  }

  if (0 === _.compact(splittedMethod).length) {
    splittedMethod = null;
  }

  return { responderName: responderName, splittedMethod: splittedMethod };
}

////////////////////////////////////////////////////////////////////////////////

function initScope(N) {

  // provide app version
  N.runtime.version = N.runtime.mainApp.version;

  // provide some empty structures
  N.wire  = new Wire();
  N.client  = {};
  N.views   = {};

  // Storage for validation schemas
  // Each key is a `apiPath`, each value is a Schema object
  var validationSchemas = {};

  /**
   *  N.validate(apiPath, schema) -> Void
   *  N.validate(schema) -> Void
   *  - apiPath (String): server api path relative to the current api node
   *  - schema (Object): validation schema (for proprties only)
   *
   *  Add validation schema for params of apiPath.
   *
   *  ##### Schema
   *
   *  You can provide full JSON-Schema compatible object:
   *
   *      {
   *        properties: {
   *          id: { type: 'integer', minimal: 1 }
   *        },
   *        additionalProperties: false
   *      }
   *
   *  But for convenience we provide a syntax suger for this situation, so the
   *  above is long-hand syntax of:
   *
   *      {
   *        id: { type: 'integer', minimal: 1 }
   *      }
   *
   *
   *  ##### Example
   *
   *      // file: server/forum/thread.js
   *
   *      N.validate('server:forum.thread.show', {
   *        properties: {
   *          id: { type: 'integer', minimal: 1 }
   *        },
   *        additionalProperties: false
   *      });
   *
   *      module.exports.show = function (params, callback) {
   *        // ...
   *      };
   **/
  N.validate = function (apiPath, schema) {
    if (!schema || !schema.properties) {
      schema = {
        properties: schema,
        additionalProperties: false
      };
    }

    validationSchemas[apiPath] = schema;
  };

  /** internal
   *  N.validate.test(apiPath, params) -> Object|Null
   *
   *  Runs revalidate of apiPath for given params. Returns structure with
   *  `valid:Boolean` and `errors:Array` properties or `Null` if apiPath has no
   *  schema.
   **/
  N.validate.test = function (apiPath, params) {
    if (validationSchemas[apiPath]) {
      return revalidator.validate(params, validationSchemas[apiPath], {
        cast: false
      });
    }

    return null;
  };

  /**
   *  N.runtime.env -> String
   *
   *  Proxy to process.env['NODECA_ENV']
   **/
  N.runtime.env = process.env['NODECA_ENV'] || 'development';
}

function initConfig(N) {
  var mainRoot = N.runtime.mainApp.root
    , mainConfig  = {};

  //
  // Create empty object that we'll fill in a second
  //

  N.config = {};

  //
  // Start reading configs:
  // - Main app config stored into mainConfig
  // - Sub-apps configs got merged into N.config
  // - After all mainConfig got merged into N.config
  //

  // load main app cnfig
  mainConfig = loadConfigs(mainRoot + '/config') || {};

  // read configs of sub-applications
  if (mainConfig.applications && mainConfig.applications.length) {
    _.each(mainConfig.applications, function (appName) {
      var root;

      root = path.dirname(require.resolve(appName)) + '/config';

      mergeConfigs(N.config, loadConfigs(root));
    });
  }

  // merge in main config and resolve `per-environment` configs
  mergeConfigs(N.config, mainConfig);

  // expand environment-dependent configs
  _.each(N.config, function (val, key) {
    if ('^' === key[0]) {
      delete N.config[key];

      if (N.runtime.env === key.substr(1)) {
        mergeConfigs(N.config, val);
      }
    }
  });

  //
  // Post-process config.
  //
  N.config.options = N.config.options || {};

  // Normalize cache directory path.
  N.config.options.cache_dir = path.resolve(mainRoot, N.config.options.cache_dir || './.cache');

  /*
  if (!N.config.themes) {
    N.config.themes = {};
  }

  if (!N.config.themes.schemas) {
    N.config.themes.schemas = {};
  }

  // check whenever theme is enabled or not
  function isEnabled(id) {
    // when whitelist speciefied:
    // enable only those specified in whitelist
    if (N.config.themes.enabled) {
      return 0 <= N.config.themes.enabled.indexOf(id);
    }

    // when blacklist is given and there's no whitelist
    // enable only those, not specified in the blacklist
    if (N.config.themes.disabled) {
      return -1 === N.config.themes.disabled.indexOf(id);
    }

    // else, when no white/black lists are given
    // enable by default
    return true;
  }

  _.each(N.config.themes.schemas, function (opts, id) {
    opts.enabled = isEnabled(id);
  });
  */
}

function initLogger(N) {
  var mainRoot  = N.runtime.mainApp.root
    , config    = _.extend({}, N.config.logger)
    , options   = _.extend({file: {logSize: 10, backups: 5}}, config.options)
      // common logging level (minimal threshold)
    , baseLevel = log4js.levels.toLevel(options.level, log4js.levels.ALL)
      // cache of initialized appenders
    , appenders = {}
      // real loggers created for each entry in the config
    , loggers   = []
      // cache of met channels, maps full channel names to corresponding loggers
    , channels  = {};

  //
  // define system (general) logger
  //

  N.logger = log4js.getLogger('system');

  //
  // provide a wrapper to set global log level
  //

  N.logger.setLevel = function (level) {
    level = log4js.levels[level.toUpperCase()];
    log4js.setGlobalLogLevel(level);
  };

  //
  // provide getLogger wrapper
  //

  N.logger.getLogger = function (name) {
    if (channels[name]) {
      return channels[name];
    }

    var chosenLogger, inputInfo = parseLoggerName(name);

    if (!inputInfo) {
      N.logger.error('Unacceptable logger channel name <%s>. Using <system>.', name);
      return N.logger;
    }

    chosenLogger = _.find(loggers, function (logger) {
      var loggerInfo = parseLoggerName(logger.category);

      // If the both have specified responder names - that must be equal.
      if (inputInfo.responderName && loggerInfo.responderName &&
          inputInfo.responderName !== loggerInfo.responderName) {
        return false;
      }

      // If the both have specified methods - that must match.
      if (inputInfo.splittedMethod && loggerInfo.splittedMethod) {
        return _.every(loggerInfo.splittedMethod, function (part, index) {
          return part === inputInfo.splittedMethod[index];
        });
      } else {
        return true;
      }
    });

    if (!chosenLogger) {
      N.logger.warn('Logger <%s> not found. Using <system>.', name);
      chosenLogger = N.logger;
    }

    channels[name] = chosenLogger; // cache
    return chosenLogger;
  };

  //
  // Load supported appenders
  //

  log4js.loadAppender('file');
  log4js.loadAppender('console');
  log4js.loadAppender('logLevelFilter');

  //
  // Helper that returns thresholded appender
  // Resulting appender will log event with level => given `threshold` only
  //

  function thresholdedAppender(threshold, appender) {
    var level = baseLevel;

    if (threshold) {
      level = log4js.levels.toLevel(threshold, baseLevel);

      // get upper threshold
      level = level.isGreaterThanOrEqualTo(baseLevel) ? level : baseLevel;
    }

    // return thresholded appender
    return log4js.appenders.logLevelFilter(level, appender);
  }

  //
  // clear default loggers
  //

  log4js.clearAppenders();

  //
  // configure console logger for non-production environment only
  //

  if ('production' !== N.runtime.env) {
    log4js.addAppender(log4js.appenders.console());
  }

  //
  // leave only loggers (with appenders) configs, removing keywords
  //

  delete config.options;

  //
  // configure logger categories and appenders
  //

  _.each(config, function (loggerConfig, name) {
    var resultLogger;

    _.each(loggerConfig, function (appenderConfig) {
      var filename, appender;

      if (!appenders[appenderConfig.file]) {
        filename = path.resolve(mainRoot, appenderConfig.file);

        // make sure destination directory for log file exists
        fstools.mkdirSync(path.dirname(filename));

        appenders[appenderConfig.file] = log4js.appenders.file(
          filename,                             // filename
          null,                                 // layout
          options.file.logSize * 1024 * 1024,   // logSize
          options.file.backups                  // numBackups
        );
      }

      // prepare thresholded appender
      appender  = thresholdedAppender(
        appenderConfig.level, appenders[appenderConfig.file]);

      log4js.addAppender(appender, name);
    });

    if ('system' !== name) {
      resultLogger           = log4js.getLogger(name);
      resultLogger.getLogger = N.logger.getLogger;

      // register logger in the internal cache
      loggers.push(resultLogger);
    }
  });

  //
  // Ensure loggers are placed in order from most specific to most general.
  // e.g. 'http@forum.index' comes earlier than 'http@forum'.
  //

  loggers.sort(function (a, b) {
    a = parseLoggerName(a.category);
    b = parseLoggerName(b.category);

    if (a.splittedMethod && b.splittedMethod) {
      // Both loggers have a specified splittedMethod.

      if (a.splittedMethod.length < b.splittedMethod.length) {
        return 1;

      } else if (a.splittedMethod.length > b.splittedMethod.length) {
        return -1;

      } else {
        // Both loggers have the same splittedMethod length.

        if (a.responderName && b.responderName) {
          // Both loggers have a specified responderName.
          return 0;
        } else {
          // Logger which has a responderName is more specific.
          return a.responderName ? -1 : 1;
        }
      }
    } else {
      // Logger which has a splittedMethod is more specific.
      return a.splittedMethod ? -1 : 1;
    }
  });
}

// Just check, that you did not forgot to create config file
// Valid config MUST contain "configured: true" string
//
function checkConfig(N) {
  if (!N.config.configured) {
    throw new Error("No main configuration file (usually: config/application.yml)");
  }
}

// Run `init()` method for all registered apps.
// Usually, hooks loading is placed there
//
function initApps(N) {
  N.runtime.apps = [N.runtime.mainApp];

  // Try load each enabled application and push to the array of loaded apps
  _.each(N.config.applications, function (name) {
    N.runtime.apps.push(new Application(require(name)));
  });

  // Call init on each application
  _.each(N.runtime.apps, function (app) {
    app.init(N);
  });
}

////////////////////////////////////////////////////////////////////////////////

module.exports = function (N) {
  initScope(N);
  initConfig(N);
  initLogger(N);
  checkConfig(N);
  initApps(N);
};
<commit_msg>added `mongo` format shortcut to validator<commit_after>// Read configs, init loggers, init apps, fills N object.

"use strict";

// stdlib
var path = require('path');
var fs   = require('fs');

// 3rd-party
var _           = require('lodash');
var fstools     = require("fs-tools");
var log4js      = require("log4js");
var revalidator = require("revalidator");
var yaml        = require('js-yaml');

// internal
var Application = require("./application");
var Wire        = require("../wire");

////////////////////////////////////////////////////////////////////////////////

// merge configs, respecting `~override: true` instructions
function mergeConfigs(dst, src) {
  _.each(src || {}, function (value, key) {

    // if destination exists & already has `~override` flag, keep it intact
    if (_.isObject(dst[key]) && dst[key]['~override']) {
      return;
    }

    // if source has `~override` flag - override whole value in destination
    if (value && value['~override']) {
      dst[key] = value;
      return;
    }

    // if both nodes are objects - merge recursively
    if (_.isObject(value) && _.isObject(dst[key])) {
      mergeConfigs(dst[key], value);
      return;
    }

    // destination node does not exist - create
    // or both nodes are of different types - override.
    dst[key] = value;
    return;
  });

  return dst;
}

// reads all *.yml files from `dir` and merge resulting objects into single one
function loadConfigs(root) {
  var config = {}
    , files = [];

  fstools.walkSync(root, /[.]yml$/, function (file, stat) {
    if (stat.isFile()) {
      files.push(file);
    }
  });

  // files order can change, but we shuld return the same result always
  files = files.sort();

  _.each(files, function (file) {
    mergeConfigs(config, yaml.safeLoad(fs.readFileSync(file, 'utf8'), { filename: file }));
  });

  return config;
}

// Returns an object with keys:
//
//   `responderName` (string)
//   `splittedMethod` (array)
//
// Each one may be `null` which means 'any'.
//
//   'rpc@'             => { responderName: 'rpc',  splittedMethod: null }
//   'http@forum.index' => { responderName: 'http', splittedMethod: [ 'forum', 'index' ] }
//   'blogs'            => { responderName: null,   splittedMethod: [ 'blogs' ] }
//
function parseLoggerName(name) {
  var responderName, splittedMethod, parts = name.split('@');

  if (1 === parts.length) {
    responderName  = null;
    splittedMethod = name.split('.');

  } else if (2 === parts.length) {
    responderName  = parts[0];
    splittedMethod = parts[1].split('.');

  } else {
    // Bad name format. Only one @ symbol is allowed.
    return null;
  }

  if (0 === _.compact(splittedMethod).length) {
    splittedMethod = null;
  }

  return { responderName: responderName, splittedMethod: splittedMethod };
}

////////////////////////////////////////////////////////////////////////////////

function initScope(N) {

  // provide app version
  N.runtime.version = N.runtime.mainApp.version;

  // provide some empty structures
  N.wire  = new Wire();
  N.client  = {};
  N.views   = {};

  // Storage for validation schemas
  // Each key is a `apiPath`, each value is a Schema object
  var validationSchemas = {};

  // patch revalidator to add 'mongo' format shortcut
  revalidator.validate.formats.mongo = /^[0-9a-fA-F]{24}$/;

  /**
   *  N.validate(apiPath, schema) -> Void
   *  N.validate(schema) -> Void
   *  - apiPath (String): server api path relative to the current api node
   *  - schema (Object): validation schema (for proprties only)
   *
   *  Add validation schema for params of apiPath.
   *
   *  ##### Schema
   *
   *  You can provide full JSON-Schema compatible object:
   *
   *      {
   *        properties: {
   *          id: { type: 'integer', minimal: 1 }
   *        },
   *        additionalProperties: false
   *      }
   *
   *  But for convenience we provide a syntax suger for this situation, so the
   *  above is long-hand syntax of:
   *
   *      {
   *        id: { type: 'integer', minimal: 1 }
   *      }
   *
   *
   *  ##### Example
   *
   *      // file: server/forum/thread.js
   *
   *      N.validate('server:forum.thread.show', {
   *        properties: {
   *          id: { type: 'integer', minimal: 1 }
   *        },
   *        additionalProperties: false
   *      });
   *
   *      module.exports.show = function (params, callback) {
   *        // ...
   *      };
   **/
  N.validate = function (apiPath, schema) {
    if (!schema || !schema.properties) {
      schema = {
        properties: schema,
        additionalProperties: false
      };
    }

    validationSchemas[apiPath] = schema;
  };

  /** internal
   *  N.validate.test(apiPath, params) -> Object|Null
   *
   *  Runs revalidate of apiPath for given params. Returns structure with
   *  `valid:Boolean` and `errors:Array` properties or `Null` if apiPath has no
   *  schema.
   **/
  N.validate.test = function (apiPath, params) {
    if (validationSchemas[apiPath]) {
      return revalidator.validate(params, validationSchemas[apiPath], {
        cast: false
      });
    }

    return null;
  };

  /**
   *  N.runtime.env -> String
   *
   *  Proxy to process.env['NODECA_ENV']
   **/
  N.runtime.env = process.env['NODECA_ENV'] || 'development';
}

function initConfig(N) {
  var mainRoot = N.runtime.mainApp.root
    , mainConfig  = {};

  //
  // Create empty object that we'll fill in a second
  //

  N.config = {};

  //
  // Start reading configs:
  // - Main app config stored into mainConfig
  // - Sub-apps configs got merged into N.config
  // - After all mainConfig got merged into N.config
  //

  // load main app cnfig
  mainConfig = loadConfigs(mainRoot + '/config') || {};

  // read configs of sub-applications
  if (mainConfig.applications && mainConfig.applications.length) {
    _.each(mainConfig.applications, function (appName) {
      var root;

      root = path.dirname(require.resolve(appName)) + '/config';

      mergeConfigs(N.config, loadConfigs(root));
    });
  }

  // merge in main config and resolve `per-environment` configs
  mergeConfigs(N.config, mainConfig);

  // expand environment-dependent configs
  _.each(N.config, function (val, key) {
    if ('^' === key[0]) {
      delete N.config[key];

      if (N.runtime.env === key.substr(1)) {
        mergeConfigs(N.config, val);
      }
    }
  });

  //
  // Post-process config.
  //
  N.config.options = N.config.options || {};

  // Normalize cache directory path.
  N.config.options.cache_dir = path.resolve(mainRoot, N.config.options.cache_dir || './.cache');

  /*
  if (!N.config.themes) {
    N.config.themes = {};
  }

  if (!N.config.themes.schemas) {
    N.config.themes.schemas = {};
  }

  // check whenever theme is enabled or not
  function isEnabled(id) {
    // when whitelist speciefied:
    // enable only those specified in whitelist
    if (N.config.themes.enabled) {
      return 0 <= N.config.themes.enabled.indexOf(id);
    }

    // when blacklist is given and there's no whitelist
    // enable only those, not specified in the blacklist
    if (N.config.themes.disabled) {
      return -1 === N.config.themes.disabled.indexOf(id);
    }

    // else, when no white/black lists are given
    // enable by default
    return true;
  }

  _.each(N.config.themes.schemas, function (opts, id) {
    opts.enabled = isEnabled(id);
  });
  */
}

function initLogger(N) {
  var mainRoot  = N.runtime.mainApp.root
    , config    = _.extend({}, N.config.logger)
    , options   = _.extend({file: {logSize: 10, backups: 5}}, config.options)
      // common logging level (minimal threshold)
    , baseLevel = log4js.levels.toLevel(options.level, log4js.levels.ALL)
      // cache of initialized appenders
    , appenders = {}
      // real loggers created for each entry in the config
    , loggers   = []
      // cache of met channels, maps full channel names to corresponding loggers
    , channels  = {};

  //
  // define system (general) logger
  //

  N.logger = log4js.getLogger('system');

  //
  // provide a wrapper to set global log level
  //

  N.logger.setLevel = function (level) {
    level = log4js.levels[level.toUpperCase()];
    log4js.setGlobalLogLevel(level);
  };

  //
  // provide getLogger wrapper
  //

  N.logger.getLogger = function (name) {
    if (channels[name]) {
      return channels[name];
    }

    var chosenLogger, inputInfo = parseLoggerName(name);

    if (!inputInfo) {
      N.logger.error('Unacceptable logger channel name <%s>. Using <system>.', name);
      return N.logger;
    }

    chosenLogger = _.find(loggers, function (logger) {
      var loggerInfo = parseLoggerName(logger.category);

      // If the both have specified responder names - that must be equal.
      if (inputInfo.responderName && loggerInfo.responderName &&
          inputInfo.responderName !== loggerInfo.responderName) {
        return false;
      }

      // If the both have specified methods - that must match.
      if (inputInfo.splittedMethod && loggerInfo.splittedMethod) {
        return _.every(loggerInfo.splittedMethod, function (part, index) {
          return part === inputInfo.splittedMethod[index];
        });
      } else {
        return true;
      }
    });

    if (!chosenLogger) {
      N.logger.warn('Logger <%s> not found. Using <system>.', name);
      chosenLogger = N.logger;
    }

    channels[name] = chosenLogger; // cache
    return chosenLogger;
  };

  //
  // Load supported appenders
  //

  log4js.loadAppender('file');
  log4js.loadAppender('console');
  log4js.loadAppender('logLevelFilter');

  //
  // Helper that returns thresholded appender
  // Resulting appender will log event with level => given `threshold` only
  //

  function thresholdedAppender(threshold, appender) {
    var level = baseLevel;

    if (threshold) {
      level = log4js.levels.toLevel(threshold, baseLevel);

      // get upper threshold
      level = level.isGreaterThanOrEqualTo(baseLevel) ? level : baseLevel;
    }

    // return thresholded appender
    return log4js.appenders.logLevelFilter(level, appender);
  }

  //
  // clear default loggers
  //

  log4js.clearAppenders();

  //
  // configure console logger for non-production environment only
  //

  if ('production' !== N.runtime.env) {
    log4js.addAppender(log4js.appenders.console());
  }

  //
  // leave only loggers (with appenders) configs, removing keywords
  //

  delete config.options;

  //
  // configure logger categories and appenders
  //

  _.each(config, function (loggerConfig, name) {
    var resultLogger;

    _.each(loggerConfig, function (appenderConfig) {
      var filename, appender;

      if (!appenders[appenderConfig.file]) {
        filename = path.resolve(mainRoot, appenderConfig.file);

        // make sure destination directory for log file exists
        fstools.mkdirSync(path.dirname(filename));

        appenders[appenderConfig.file] = log4js.appenders.file(
          filename,                             // filename
          null,                                 // layout
          options.file.logSize * 1024 * 1024,   // logSize
          options.file.backups                  // numBackups
        );
      }

      // prepare thresholded appender
      appender  = thresholdedAppender(
        appenderConfig.level, appenders[appenderConfig.file]);

      log4js.addAppender(appender, name);
    });

    if ('system' !== name) {
      resultLogger           = log4js.getLogger(name);
      resultLogger.getLogger = N.logger.getLogger;

      // register logger in the internal cache
      loggers.push(resultLogger);
    }
  });

  //
  // Ensure loggers are placed in order from most specific to most general.
  // e.g. 'http@forum.index' comes earlier than 'http@forum'.
  //

  loggers.sort(function (a, b) {
    a = parseLoggerName(a.category);
    b = parseLoggerName(b.category);

    if (a.splittedMethod && b.splittedMethod) {
      // Both loggers have a specified splittedMethod.

      if (a.splittedMethod.length < b.splittedMethod.length) {
        return 1;

      } else if (a.splittedMethod.length > b.splittedMethod.length) {
        return -1;

      } else {
        // Both loggers have the same splittedMethod length.

        if (a.responderName && b.responderName) {
          // Both loggers have a specified responderName.
          return 0;
        } else {
          // Logger which has a responderName is more specific.
          return a.responderName ? -1 : 1;
        }
      }
    } else {
      // Logger which has a splittedMethod is more specific.
      return a.splittedMethod ? -1 : 1;
    }
  });
}

// Just check, that you did not forgot to create config file
// Valid config MUST contain "configured: true" string
//
function checkConfig(N) {
  if (!N.config.configured) {
    throw new Error("No main configuration file (usually: config/application.yml)");
  }
}

// Run `init()` method for all registered apps.
// Usually, hooks loading is placed there
//
function initApps(N) {
  N.runtime.apps = [N.runtime.mainApp];

  // Try load each enabled application and push to the array of loaded apps
  _.each(N.config.applications, function (name) {
    N.runtime.apps.push(new Application(require(name)));
  });

  // Call init on each application
  _.each(N.runtime.apps, function (app) {
    app.init(N);
  });
}

////////////////////////////////////////////////////////////////////////////////

module.exports = function (N) {
  initScope(N);
  initConfig(N);
  initLogger(N);
  checkConfig(N);
  initApps(N);
};
<|endoftext|>