<commit_before>define(['jquery', 'rz_core', 'view/selection'],
function($, rz_core, selection) {

function get_graph_view(element)
{
    var dict = rz_core.root_element_id_to_graph_view;

    while (undefined !== element && null !== element && undefined === dict[element.id]) {
        element = element.parentElement;
    }
    return element !== undefined && element !== null ? dict[element.id] : undefined;
}

function install() {
    document.body.onkeydown = function(e) {
        var key = ((e.key && String(e.key))
                   || (e.charCode && String.fromCharCode(e.charCode))
                   || (e.which && String.fromCharCode(e.which))).toLowerCase(),
            handled = false,
            graph_view = get_graph_view(e.target);

        if (e.altKey && e.ctrlKey && 'i' === key) {
            $('#textanalyser').focus();
        }
        if (e.altKey && e.ctrlKey && 'o' === key) {
            search.focus();
        }
        if (e.ctrlKey && 'a' === key && e.target === document.body) {
            selection.update(rz_core.main_graph.nodes(), false);
            handled = true;
        }
        if (e.ctrlKey && 'z' === key && e.target.nodeName !== 'INPUT') {
            // TODO: rz_core.main_graph.undo();
        }
        // SVG elements cannot handle any keys directly - pass the key to them in this case
        if (undefined !== graph_view) {
            graph_view.keyboard_handler(e);
        }
        if (handled) {
            e.preventDefault();
            e.stopPropagation();
        }
    };
}
return {
        install: install
       };
}
);
<commit_msg>client: fix ctrl-A breakage since selection signatures changed<commit_after>define(['jquery', 'rz_core', 'view/selection'],
function($, rz_core, selection) {

function get_graph_view(element)
{
    var dict = rz_core.root_element_id_to_graph_view;

    while (undefined !== element && null !== element && undefined === dict[element.id]) {
        element = element.parentElement;
    }
    return element !== undefined && element !== null ? dict[element.id] : undefined;
}

function install() {
    document.body.onkeydown = function(e) {
        var key = ((e.key && String(e.key))
                   || (e.charCode && String.fromCharCode(e.charCode))
                   || (e.which && String.fromCharCode(e.which))).toLowerCase(),
            handled = false,
            graph_view = get_graph_view(e.target);

        if (e.altKey && e.ctrlKey && 'i' === key) {
            $('#textanalyser').focus();
        }
        if (e.altKey && e.ctrlKey && 'o' === key) {
            search.focus();
        }
        if (e.ctrlKey && 'a' === key && e.target === document.body) {
            selection.select_nodes(rz_core.main_graph.nodes());
            handled = true;
        }
        if (e.ctrlKey && 'z' === key && e.target.nodeName !== 'INPUT') {
            // TODO: rz_core.main_graph.undo();
        }
        // SVG elements cannot handle any keys directly - pass the key to them in this case
        if (undefined !== graph_view) {
            graph_view.keyboard_handler(e);
        }
        if (handled) {
            e.preventDefault();
            e.stopPropagation();
        }
    };
}
return {
        install: install
       };
}
);
<|endoftext|>


<commit_before>var request = require('supertest');
var JSONAPIValidator = require('jsonapi-validator').Validator;

validateJSONapi = function(res) {
  var validator = new JSONAPIValidator();

  validator.validate(res.body);
}

describe('Error handling', function() {
  describe('GET /fake', function() {
    it('Should return 404', function(done) {
      request(sails.hooks.http.app)
        .get('/fake')
        .expect(404)
        .expect(validateJSONapi)
        .expect({
          'errors': [
            {
              status: "404",
              title: 'Resource not found'
            }
          ]
        })
        .end(done);
    });
  });

  describe('GET /users/42', function() {
    it('Should return 404', function(done) {
      request(sails.hooks.http.app)
        .get('/users/42')
        .expect(404)
        .expect(validateJSONapi)
        .expect({
          'errors': [
            {
              status: "404",
              title: 'Resource not found',
              detail: 'No record found with the specified id.'
            }
          ]
        })
        .end(done);
    });
  });

  describe('GET categories?invalid=true', function() {
    it('Should return 400', function(done) {
      request(sails.hooks.http.app)
        .get('/categories?invalid=true')
        .expect(400)
        .expect(validateJSONapi)
        .expect({
          'errors': [
            {
              status: "400",
              title: 'Bad request',
              detail: "Something is rotten in the state of denmark"
            }
          ]
        })
        .end(done);
    });
  });

  describe('GET categories?forbidden=true', function() {
    it('Should return 403', function(done) {
      request(sails.hooks.http.app)
        .get('/categories?forbidden=true')
        .expect(403)
        .expect(validateJSONapi)
        .expect({
          'errors': [
            {
              status: "403",
              title: 'Forbidden',
              detail: "You don't wanna do that"
            }
          ]
        })
        .end(done);
    });
  });

  describe('GET categories?error=true', function() {
    it('Should return 500', function(done) {
      request(sails.hooks.http.app)
        .get('/categories?error=true')
        .expect(500)
        .expect(validateJSONapi)
        .expect({
          'errors': [
            {
              status: "500",
              title: 'Internal server error',
              detail: 'A super massive black hole just swallowed the server'
            }
          ]
        })
        .end(done);
    });
  });

  describe('POST /users with invalid attributes', function() {
    it('Should return 400 with error details', function (done) {

      var userToCreate = {
        'data': {
          'attributes': {
            'email': 'test@jsonapi.com',
            'first-name':'a', // This is invalid. Minimum length is 3
            'last-name':'api'
          },
          'type':'users'
        }
      };

      request(sails.hooks.http.app)
        .post('/users')
        .send(userToCreate)
        .expect(400)
        .expect(validateJSONapi)
        .expect({
          "errors": [
            {
              "detail": "user.firstName.minLength",
              "source": {
                "pointer": "data/attributes/first-name"
              }
            }
          ]
        })
        .end(done);
    });
  });

  describe("POST /categories with invalid attributes", function() {
    it('Should return human readable errors from sails-hook-validation', function (done) {

      var categoryToCreate = {
        'data': {
          'attributes': {
            name: "a" // Invalid because minLength is 2
          },
          'type':'categories'
        }
      };

      request(sails.hooks.http.app)
        .post('/categories')
        .send(categoryToCreate)
        .expect(400)
        .expect(validateJSONapi)
        .expect({
          errors: [
            {
              detail: "Minimum length is 2",
              source: {
                pointer: "data/attributes/name"
              }
            }
            ]
        })
        .end(done);
    });
  });
});
<commit_msg>Expect Bad request (400) to be sent if input is not a valid JSON API<commit_after>var request = require('supertest');
var JSONAPIValidator = require('jsonapi-validator').Validator;

validateJSONapi = function(res) {
  var validator = new JSONAPIValidator();

  validator.validate(res.body);
}

describe('Error handling', function() {
  describe('GET /fake', function() {
    it('Should return 404', function(done) {
      request(sails.hooks.http.app)
        .get('/fake')
        .expect(404)
        .expect(validateJSONapi)
        .expect({
          'errors': [
            {
              status: "404",
              title: 'Resource not found'
            }
          ]
        })
        .end(done);
    });
  });

  describe('GET /users/42', function() {
    it('Should return 404', function(done) {
      request(sails.hooks.http.app)
        .get('/users/42')
        .expect(404)
        .expect(validateJSONapi)
        .expect({
          'errors': [
            {
              status: "404",
              title: 'Resource not found',
              detail: 'No record found with the specified id.'
            }
          ]
        })
        .end(done);
    });
  });

  describe('GET categories?invalid=true', function() {
    it('Should return 400', function(done) {
      request(sails.hooks.http.app)
        .get('/categories?invalid=true')
        .expect(400)
        .expect(validateJSONapi)
        .expect({
          'errors': [
            {
              status: "400",
              title: 'Bad request',
              detail: "Something is rotten in the state of denmark"
            }
          ]
        })
        .end(done);
    });
  });

  describe('GET categories?forbidden=true', function() {
    it('Should return 403', function(done) {
      request(sails.hooks.http.app)
        .get('/categories?forbidden=true')
        .expect(403)
        .expect(validateJSONapi)
        .expect({
          'errors': [
            {
              status: "403",
              title: 'Forbidden',
              detail: "You don't wanna do that"
            }
          ]
        })
        .end(done);
    });
  });

  describe('GET categories?error=true', function() {
    it('Should return 500', function(done) {
      request(sails.hooks.http.app)
        .get('/categories?error=true')
        .expect(500)
        .expect(validateJSONapi)
        .expect({
          'errors': [
            {
              status: "500",
              title: 'Internal server error',
              detail: 'A super massive black hole just swallowed the server'
            }
          ]
        })
        .end(done);
    });
  });

  describe('POST /users with invalid attributes', function() {
    it('Should return 400 with error details', function (done) {

      var userToCreate = {
        'data': {
          'attributes': {
            'email': 'test@jsonapi.com',
            'first-name':'a', // This is invalid. Minimum length is 3
            'last-name':'api'
          },
          'type':'users'
        }
      };

      request(sails.hooks.http.app)
        .post('/users')
        .send(userToCreate)
        .expect(400)
        .expect(validateJSONapi)
        .expect({
          "errors": [
            {
              "detail": "user.firstName.minLength",
              "source": {
                "pointer": "data/attributes/first-name"
              }
            }
          ]
        })
        .end(done);
    });
  });

  describe("POST /categories with invalid attributes", function() {
    it('Should return human readable errors from sails-hook-validation', function (done) {

      var categoryToCreate = {
        'data': {
          'attributes': {
            name: "a" // Invalid because minLength is 2
          },
          'type':'categories'
        }
      };

      request(sails.hooks.http.app)
        .post('/categories')
        .send(categoryToCreate)
        .expect(400)
        .expect(validateJSONapi)
        .expect({
          errors: [
            {
              detail: "Minimum length is 2",
              source: {
                pointer: "data/attributes/name"
              }
            }
            ]
        })
        .end(done);
    });
  });
});

describe('Submit invalid data', function() {
  describe('POST invalid /users ', function() {
    it('Should return 400', function (done) {

      request(sails.hooks.http.app)
        .post('/users')
        .send({
          data: {
            foo: {

            }
          }
        })
        .expect(400)
        .expect(validateJSONapi)
        .expect({
          'errors': [
            {
              status: "400",
              title: 'Bad request',
              detail: 'Invalid JSON API data',
              links: {
                self: 'http://jsonapi.org/format/'
              }
            }
          ]
        })
        .end(done)
    });
  });
});
<|endoftext|>


<commit_before>import React, { PropTypes } from "react";
import { PropTypes as CustomPropTypes, TextSize, Helpers } from "../victory-util/index";
import { default as VictoryLabel } from "../victory-label/victory-label";
import { default as VictoryTheme } from "../victory-theme/victory-theme";
import { Flyout } from "../victory-primitives/index";
import { default as VictoryPortal} from "../victory-portal/victory-portal";
import { assign, defaults } from "lodash";

const fallbackProps = {
  cornerRadius: 5,
  pointerLength: 10,
  pointerWidth: 10
};

export default class VictoryTooltip extends React.Component {
  static displayName = "VictoryTooltip";

  static propTypes = {
    active: PropTypes.oneOfType([
      PropTypes.bool,
      PropTypes.func
    ]),
    datum: PropTypes.object,
    data: PropTypes.array,
    events: PropTypes.object,
    text: PropTypes.oneOfType([
      PropTypes.string,
      PropTypes.number,
      PropTypes.func
    ]),
    style: PropTypes.object,
    flyoutStyle: PropTypes.object,
    x: PropTypes.number,
    y: PropTypes.number,
    dx: PropTypes.oneOfType([
      PropTypes.number,
      PropTypes.func
    ]),
    dy: PropTypes.oneOfType([
      PropTypes.number,
      PropTypes.func
    ]),
    width: PropTypes.oneOfType([
      CustomPropTypes.nonNegative,
      PropTypes.func
    ]),
    height: PropTypes.oneOfType([
      CustomPropTypes.nonNegative,
      PropTypes.func
    ]),
    orientation: PropTypes.oneOfType([
      PropTypes.oneOf(["top", "bottom", "left", "right"]),
      PropTypes.func
    ]),
    pointerLength: PropTypes.oneOfType([
      CustomPropTypes.nonNegative,
      PropTypes.func
    ]),
    pointerWidth: PropTypes.oneOfType([
      CustomPropTypes.nonNegative,
      PropTypes.func
    ]),
    cornerRadius: PropTypes.oneOfType([
      CustomPropTypes.nonNegative,
      PropTypes.func
    ]),
    horizontal: PropTypes.bool,
    labelComponent: PropTypes.element,
    flyoutComponent: PropTypes.element,
    groupComponent: PropTypes.element,
    index: PropTypes.oneOfType([PropTypes.number, PropTypes.string]),
    renderInPortal: PropTypes.bool,
    theme: PropTypes.object
  };

  static defaultProps = {
    theme: VictoryTheme.grayscale,
    active: false,
    renderInPortal: true,
    labelComponent: <VictoryLabel/>,
    flyoutComponent: <Flyout/>,
    groupComponent: <g/>
  };

  static defaultEvents = [{
    target: "data",
    eventHandlers: {
      onMouseOver: () => {
        return [
          {
            target: "labels",
            mutation: () => ({ active: true })
          }, {
            target: "data",
            mutation: () => ({ active: true })
          }
        ];
      },
      onMouseOut: () => {
        return [
          {
            target: "labels",
            mutation: () => ({ active: false })
          }, {
            target: "data",
            mutation: () => ({ active: false })
          }
        ];
      }
    }
  }];

  getEvaluatedProps(props) {
    const {
      horizontal, datum, pointerLength, pointerWidth, cornerRadius,
      width, height, orientation, dx, dy, text, active
    } = props;

    const style = Helpers.evaluateStyle(props.style, datum, active);
    const flyoutStyle = Helpers.evaluateStyle(props.flyoutStyle, datum, active);
    const padding = flyoutStyle && flyoutStyle.padding || 0;
    const defaultDx = horizontal ? padding : 0;
    const defaultDy = horizontal ? 0 : padding;
    const getDefaultOrientation = () => {
      const positive = horizontal ? "right" : "top";
      const negative = horizontal ? "left" : "bottom";
      return datum && datum.y < 0 ? negative : positive;
    };
    return assign(
      {},
      props,
      {
        style,
        flyoutStyle,
        dx: dx !== undefined ? Helpers.evaluateProp(dx, datum, active) : defaultDx,
        dy: dy !== undefined ? Helpers.evaluateProp(dy, datum, active) : defaultDy,
        cornerRadius: Helpers.evaluateProp(cornerRadius, datum, active),
        pointerLength: Helpers.evaluateProp(pointerLength, datum, active),
        pointerWidth: Helpers.evaluateProp(pointerWidth, datum, active),
        orientation: Helpers.evaluateProp(orientation, datum, active) || getDefaultOrientation(),
        width: Helpers.evaluateProp(width, datum, active),
        height: Helpers.evaluateProp(height, datum, active),
        active: Helpers.evaluateProp(active, datum, active),
        text: Helpers.evaluateProp(text, datum, active)
      }
    );
  }

  getCalculatedValues(props) {
    const { style, text, datum, theme } = props;
    const defaultLabelStyles = theme && theme.tooltip && theme.tooltip.style ?
      theme.tooltip.style : {};
    const baseLabelStyle = style ?
      defaults({}, style, defaultLabelStyles) : defaultLabelStyles;
    const defaultFlyoutStyles = theme && theme.tooltip && theme.tooltip.flyoutStyle ?
      theme.tooltip.flyoutStyle : {};
    const flyoutStyle = props.flyoutStyle ?
      defaults({}, props.flyoutStyle, defaultFlyoutStyles) : defaultFlyoutStyles;
    const labelStyle = Helpers.evaluateStyle(baseLabelStyle, datum);
    const labelSize = TextSize.approximateTextSize(text, labelStyle);
    const flyoutDimensions = this.getDimensions(props, labelSize, labelStyle);
    const flyoutCenter = this.getFlyoutCenter(props, flyoutDimensions);
    return {labelStyle, flyoutStyle, labelSize, flyoutDimensions, flyoutCenter};
  }

  getFlyoutCenter(props, dimensions) {
    const {x, y, dx, dy, pointerLength, orientation} = props;
    const {height, width} = dimensions;
    const sign = orientation === "right" || orientation === "top" ? 1 : -1;
    return {
      x: orientation === "left" || orientation === "right" ?
        x + sign * (pointerLength + (width / 2) + dx) : x + sign * dx,
      y: orientation === "top" || orientation === "bottom" ?
        y - sign * (pointerLength + (height / 2) + dy) : y - sign * dy
    };
  }

  getDimensions(props, labelSize, labelStyle) {
    const { orientation, cornerRadius, pointerLength, pointerWidth } = props;
    const padding = labelStyle && labelStyle.padding || 0;
    const getHeight = () => {
      const calculatedHeight = labelSize.height + padding;
      const minHeight = orientation === "top" || orientation === "bottom" ?
        2 * cornerRadius : 2 * cornerRadius + pointerWidth;
      return Math.max(minHeight, calculatedHeight);
    };
    const getWidth = () => {
      const calculatedWidth = labelSize.width + padding;
      const minWidth = orientation === "left" || orientation === "right" ?
        2 * cornerRadius + pointerLength : 2 * cornerRadius;
      return Math.max(minWidth, calculatedWidth);
    };
    return {
      height: props.height || getHeight(props, labelSize, orientation) + (padding / 2),
      width: props.width || getWidth(props, labelSize, orientation) + padding
    };
  }

  getLabelProps(props, calculatedValues) {
    const { flyoutCenter, labelStyle, labelSize, flyoutDimensions } = calculatedValues;
    const { text, datum, labelComponent, index } = props;
    const textAnchor = labelStyle.textAnchor || "middle";
    const getLabelX = () => {
      const sign = textAnchor === "end" ? -1 : 1;
      return flyoutCenter.x - sign * (flyoutDimensions.width - labelSize.width);
    };
    return defaults(
      {},
      labelComponent.props,
      {
        key: `label-${index}`,
        text, datum, textAnchor,
        style: labelStyle,
        x: !labelStyle.textAnchor || labelStyle.textAnchor === "middle" ?
          flyoutCenter.x : getLabelX(),
        y: flyoutCenter.y,
        verticalAnchor: "middle",
        angle: labelStyle.angle
      }
    );
  }

  getFlyoutProps(props, calculatedValues) {
    const {flyoutDimensions, flyoutStyle} = calculatedValues;
    const {
      x, y, dx, dy, orientation, pointerLength, pointerWidth, cornerRadius,
      events, flyoutComponent, index
    } = props;
    return defaults(
      {},
      flyoutComponent.props,
      {
        x, y, dx, dy, orientation, pointerLength, pointerWidth, cornerRadius, events,
        key: `flyout-${index}`,
        width: flyoutDimensions.width,
        height: flyoutDimensions.height,
        style: flyoutStyle
      }
    );
  }

  renderTooltip(props) {
    const evaluatedProps = this.getEvaluatedProps(props);
    const { flyoutComponent, labelComponent, groupComponent } = evaluatedProps;
    const calculatedValues = this.getCalculatedValues(evaluatedProps);
    const children = [
      React.cloneElement(flyoutComponent, this.getFlyoutProps(evaluatedProps, calculatedValues)),
      React.cloneElement(labelComponent, this.getLabelProps(evaluatedProps, calculatedValues))
    ];
    return React.cloneElement(groupComponent, { role: "presentation" }, children);
  }

  // Overridden in victory-core-native
  render() {
    const props = Helpers.modifyProps((this.props), fallbackProps, "tooltip");
    const { active, renderInPortal } = props;
    const tooltip = active ? this.renderTooltip(props) : null;
    return renderInPortal ? <VictoryPortal>{tooltip}</VictoryPortal> : tooltip;
  }
}
<commit_msg>clear changes rather than manually setting active false<commit_after>import React, { PropTypes } from "react";
import { PropTypes as CustomPropTypes, TextSize, Helpers } from "../victory-util/index";
import { default as VictoryLabel } from "../victory-label/victory-label";
import { default as VictoryTheme } from "../victory-theme/victory-theme";
import { Flyout } from "../victory-primitives/index";
import { default as VictoryPortal} from "../victory-portal/victory-portal";
import { assign, defaults } from "lodash";

const fallbackProps = {
  cornerRadius: 5,
  pointerLength: 10,
  pointerWidth: 10
};

export default class VictoryTooltip extends React.Component {
  static displayName = "VictoryTooltip";

  static propTypes = {
    active: PropTypes.oneOfType([
      PropTypes.bool,
      PropTypes.func
    ]),
    datum: PropTypes.object,
    data: PropTypes.array,
    events: PropTypes.object,
    text: PropTypes.oneOfType([
      PropTypes.string,
      PropTypes.number,
      PropTypes.func
    ]),
    style: PropTypes.object,
    flyoutStyle: PropTypes.object,
    x: PropTypes.number,
    y: PropTypes.number,
    dx: PropTypes.oneOfType([
      PropTypes.number,
      PropTypes.func
    ]),
    dy: PropTypes.oneOfType([
      PropTypes.number,
      PropTypes.func
    ]),
    width: PropTypes.oneOfType([
      CustomPropTypes.nonNegative,
      PropTypes.func
    ]),
    height: PropTypes.oneOfType([
      CustomPropTypes.nonNegative,
      PropTypes.func
    ]),
    orientation: PropTypes.oneOfType([
      PropTypes.oneOf(["top", "bottom", "left", "right"]),
      PropTypes.func
    ]),
    pointerLength: PropTypes.oneOfType([
      CustomPropTypes.nonNegative,
      PropTypes.func
    ]),
    pointerWidth: PropTypes.oneOfType([
      CustomPropTypes.nonNegative,
      PropTypes.func
    ]),
    cornerRadius: PropTypes.oneOfType([
      CustomPropTypes.nonNegative,
      PropTypes.func
    ]),
    horizontal: PropTypes.bool,
    labelComponent: PropTypes.element,
    flyoutComponent: PropTypes.element,
    groupComponent: PropTypes.element,
    index: PropTypes.oneOfType([PropTypes.number, PropTypes.string]),
    renderInPortal: PropTypes.bool,
    theme: PropTypes.object
  };

  static defaultProps = {
    theme: VictoryTheme.grayscale,
    active: false,
    renderInPortal: true,
    labelComponent: <VictoryLabel/>,
    flyoutComponent: <Flyout/>,
    groupComponent: <g/>
  };

  static defaultEvents = [{
    target: "data",
    eventHandlers: {
      onMouseOver: () => {
        return [
          {
            target: "labels",
            mutation: () => ({ active: true })
          }, {
            target: "data",
            mutation: () => ({ active: true })
          }
        ];
      },
      onMouseOut: () => {
        return [
          {
            target: "labels",
            mutation: () => null
          }, {
            target: "data",
            mutation: () => null
          }
        ];
      }
    }
  }];

  getEvaluatedProps(props) {
    const {
      horizontal, datum, pointerLength, pointerWidth, cornerRadius,
      width, height, orientation, dx, dy, text, active
    } = props;

    const style = Helpers.evaluateStyle(props.style, datum, active);
    const flyoutStyle = Helpers.evaluateStyle(props.flyoutStyle, datum, active);
    const padding = flyoutStyle && flyoutStyle.padding || 0;
    const defaultDx = horizontal ? padding : 0;
    const defaultDy = horizontal ? 0 : padding;
    const getDefaultOrientation = () => {
      const positive = horizontal ? "right" : "top";
      const negative = horizontal ? "left" : "bottom";
      return datum && datum.y < 0 ? negative : positive;
    };
    return assign(
      {},
      props,
      {
        style,
        flyoutStyle,
        dx: dx !== undefined ? Helpers.evaluateProp(dx, datum, active) : defaultDx,
        dy: dy !== undefined ? Helpers.evaluateProp(dy, datum, active) : defaultDy,
        cornerRadius: Helpers.evaluateProp(cornerRadius, datum, active),
        pointerLength: Helpers.evaluateProp(pointerLength, datum, active),
        pointerWidth: Helpers.evaluateProp(pointerWidth, datum, active),
        orientation: Helpers.evaluateProp(orientation, datum, active) || getDefaultOrientation(),
        width: Helpers.evaluateProp(width, datum, active),
        height: Helpers.evaluateProp(height, datum, active),
        active: Helpers.evaluateProp(active, datum, active),
        text: Helpers.evaluateProp(text, datum, active)
      }
    );
  }

  getCalculatedValues(props) {
    const { style, text, datum, theme } = props;
    const defaultLabelStyles = theme && theme.tooltip && theme.tooltip.style ?
      theme.tooltip.style : {};
    const baseLabelStyle = style ?
      defaults({}, style, defaultLabelStyles) : defaultLabelStyles;
    const defaultFlyoutStyles = theme && theme.tooltip && theme.tooltip.flyoutStyle ?
      theme.tooltip.flyoutStyle : {};
    const flyoutStyle = props.flyoutStyle ?
      defaults({}, props.flyoutStyle, defaultFlyoutStyles) : defaultFlyoutStyles;
    const labelStyle = Helpers.evaluateStyle(baseLabelStyle, datum);
    const labelSize = TextSize.approximateTextSize(text, labelStyle);
    const flyoutDimensions = this.getDimensions(props, labelSize, labelStyle);
    const flyoutCenter = this.getFlyoutCenter(props, flyoutDimensions);
    return {labelStyle, flyoutStyle, labelSize, flyoutDimensions, flyoutCenter};
  }

  getFlyoutCenter(props, dimensions) {
    const {x, y, dx, dy, pointerLength, orientation} = props;
    const {height, width} = dimensions;
    const sign = orientation === "right" || orientation === "top" ? 1 : -1;
    return {
      x: orientation === "left" || orientation === "right" ?
        x + sign * (pointerLength + (width / 2) + dx) : x + sign * dx,
      y: orientation === "top" || orientation === "bottom" ?
        y - sign * (pointerLength + (height / 2) + dy) : y - sign * dy
    };
  }

  getDimensions(props, labelSize, labelStyle) {
    const { orientation, cornerRadius, pointerLength, pointerWidth } = props;
    const padding = labelStyle && labelStyle.padding || 0;
    const getHeight = () => {
      const calculatedHeight = labelSize.height + padding;
      const minHeight = orientation === "top" || orientation === "bottom" ?
        2 * cornerRadius : 2 * cornerRadius + pointerWidth;
      return Math.max(minHeight, calculatedHeight);
    };
    const getWidth = () => {
      const calculatedWidth = labelSize.width + padding;
      const minWidth = orientation === "left" || orientation === "right" ?
        2 * cornerRadius + pointerLength : 2 * cornerRadius;
      return Math.max(minWidth, calculatedWidth);
    };
    return {
      height: props.height || getHeight(props, labelSize, orientation) + (padding / 2),
      width: props.width || getWidth(props, labelSize, orientation) + padding
    };
  }

  getLabelProps(props, calculatedValues) {
    const { flyoutCenter, labelStyle, labelSize, flyoutDimensions } = calculatedValues;
    const { text, datum, labelComponent, index } = props;
    const textAnchor = labelStyle.textAnchor || "middle";
    const getLabelX = () => {
      const sign = textAnchor === "end" ? -1 : 1;
      return flyoutCenter.x - sign * (flyoutDimensions.width - labelSize.width);
    };
    return defaults(
      {},
      labelComponent.props,
      {
        key: `label-${index}`,
        text, datum, textAnchor,
        style: labelStyle,
        x: !labelStyle.textAnchor || labelStyle.textAnchor === "middle" ?
          flyoutCenter.x : getLabelX(),
        y: flyoutCenter.y,
        verticalAnchor: "middle",
        angle: labelStyle.angle
      }
    );
  }

  getFlyoutProps(props, calculatedValues) {
    const {flyoutDimensions, flyoutStyle} = calculatedValues;
    const {
      x, y, dx, dy, orientation, pointerLength, pointerWidth, cornerRadius,
      events, flyoutComponent, index
    } = props;
    return defaults(
      {},
      flyoutComponent.props,
      {
        x, y, dx, dy, orientation, pointerLength, pointerWidth, cornerRadius, events,
        key: `flyout-${index}`,
        width: flyoutDimensions.width,
        height: flyoutDimensions.height,
        style: flyoutStyle
      }
    );
  }

  renderTooltip(props) {
    const evaluatedProps = this.getEvaluatedProps(props);
    const { flyoutComponent, labelComponent, groupComponent } = evaluatedProps;
    const calculatedValues = this.getCalculatedValues(evaluatedProps);
    const children = [
      React.cloneElement(flyoutComponent, this.getFlyoutProps(evaluatedProps, calculatedValues)),
      React.cloneElement(labelComponent, this.getLabelProps(evaluatedProps, calculatedValues))
    ];
    return React.cloneElement(groupComponent, { role: "presentation" }, children);
  }

  // Overridden in victory-core-native
  render() {
    const props = Helpers.modifyProps((this.props), fallbackProps, "tooltip");
    const { active, renderInPortal } = props;
    const tooltip = active ? this.renderTooltip(props) : null;
    return renderInPortal ? <VictoryPortal>{tooltip}</VictoryPortal> : tooltip;
  }
}
<|endoftext|>


<commit_before>(function RawParsingServiceModule() {
	"use strict";

	angular.module('translate.app')
		.factory('rawParsingService', RawParsingService);

	RawParsingService.$inject = [];

	var delimiter = '。';

	function RawParsingService() {
		return {
			parse: function (text) {
				return parsePlainText(text);
			}
		};

		function parsePlainText(text) {
			var paragraphs = text.trim().split(/[\r?\n]{2}/);
			var phrases = [];
			for (var i = 0; i < paragraphs.length; i++) {
				var paragraph = paragraphs[i].trim();
				if (paragraph === '') {
					continue;
				}

				var subphrases = paragraph.split(delimiter);
				for (var j = 0; j < subphrases.length; j++) {
					var phrase = subphrases[j];
					if (phrase.trim() === '') {
						continue;
					}
					phrases.push({
						phrase: phrase + delimiter,
						paragraphOffset: i,
						translation: ''
					});
				}
			}

			return phrases;
		}

		function parseFromTranslationAssitant(text) {

		}
	}
})();<commit_msg>Fixed a bug where a delimiter is tagged onto the end of raw<commit_after>(function RawParsingServiceModule() {
	"use strict";

	angular.module('translate.app')
		.factory('rawParsingService', RawParsingService);

	RawParsingService.$inject = [];

	var delimiter = '。';

	function RawParsingService() {
		return {
			parse: function (text) {
				return parsePlainText(text);
			}
		};

		function parsePlainText(text) {
			var paragraphs = text.trim().split(/[\r?\n]{2}/);
			var phrases = [];
			for (var i = 0; i < paragraphs.length; i++) {
				var paragraph = paragraphs[i].trim();
				if (paragraph === '') {
					continue;
				}

				var subphrases = paragraph.split(delimiter);
				for (var j = 0; j < subphrases.length; j++) {
					var phrase = subphrases[j];
					if (phrase.trim() === '') {
						continue;
					} else if (j < subphrases.length - 1) {
						phrase += delimiter;
					}
					phrases.push({
						phrase: phrase,
						paragraphOffset: i,
						translation: ''
					});
				}
			}

			return phrases;
		}

		function parseFromTranslationAssitant(text) {

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


<commit_before>describe('controller: FeatureProposalController', function () {

    var controller, scope, fps;

    beforeEach(function() {
        module('core');
        module('app');
        module('mock.idea');
        module('mock.ideaRepo');
        module('mock.projectService');
        module('mock.featureProposal');
        module('mock.featureProposalRepo');
        module('mock.serviceRepo');

        inject(function ($controller, $rootScope, _Idea_, _IdeaRepo_, IdeaState, _FeatureProposal_, _FeatureProposalRepo_, FeatureProposalState, _ProjectService_, _ServiceRepo_) {
            installPromiseMatchers();
            scope = $rootScope.$new();
            fps = FeatureProposalState;
            controller = $controller('FeatureProposalController', {
                $scope: scope,
                Idea: _Idea_,
                IdeaRepo: _IdeaRepo_,
                IdeaState: IdeaState,
                FeatureProposal: _FeatureProposal_,
                FeatureProposalRepo: _FeatureProposalRepo_,
                FeatureProposalState: FeatureProposalState,
                ProjectService: _ProjectService_,
                ServiceRepo: _ServiceRepo_
            });
        });
    });

    describe('Is the controller defined', function () {
        it('should be defined', function () {
            expect(controller).toBeDefined();
        });
    });

    describe('Are the scope methods defined', function () {
        it('editFeatureProposal should be defined', function () {
            expect(scope.editFeatureProposal).toBeDefined();
            expect(typeof scope.editFeatureProposal).toEqual("function");
        });
        it('removeIdea should be defined', function () {
            expect(scope.removeIdea).toBeDefined();
            expect(typeof scope.removeIdea).toEqual("function");
        });
        it('updateFeatureProposal should be defined', function () {
            expect(scope.updateFeatureProposal).toBeDefined();
            expect(typeof scope.updateFeatureProposal).toEqual("function");
        });
        it('select should be defined', function () {
            expect(scope.select).toBeDefined();
            expect(typeof scope.select).toEqual("function");
        });
        it('submitFeatureProposal should be defined', function () {
            expect(scope.submitFeatureProposal).toBeDefined();
            expect(typeof scope.submitFeatureProposal).toEqual("function");
        });
        it('confirmDeleteFp should be defined', function () {
            expect(scope.confirmDeleteFp).toBeDefined();
            expect(typeof scope.confirmDeleteFp).toEqual("function");
        });
        it('deleteFp should be defined', function () {
            expect(scope.deleteFp).toBeDefined();
            expect(typeof scope.deleteFp).toEqual("function");
        });
        it('hasState should be defined', function () {
            expect(scope.hasState).toBeDefined();
            expect(typeof scope.hasState).toEqual("function");
        });
        it('getStateSummary should be defined', function () {
            expect(scope.getStateSummary).toBeDefined();
            expect(typeof scope.getStateSummary).toEqual("function");
        });
    });

    describe('Are the scope methods working as expected', function () {
        it('hasState should return a boolean', function () {
            var state = "IN_PROGRESS";
            var fp = {
                state: state
            };
            expect(typeof scope.hasState(state, fp)).toEqual("boolean");
        });
    });

    describe('Are the scope methods working as expected', function () {
        it('hasState should return a boolean', function () {
            var state;
            var fp = {
                state: state
            };

            angular.forEach(fps, function(v, k) {
                state = v.value;
                fp.state = state;
                expect(typeof scope.hasState(state, fp)).toEqual("boolean");
            });
        });

        it('getStateSummary should return a string', function () {
            var state;
            angular.forEach(fps, function(v, k) {
                state = v.value;
                expect(typeof scope.getStateSummary(state)).toEqual("string");
            });
        });
    });
});
<commit_msg>Add existence test for initCreateFeatureProposal() in FeatureProposalController<commit_after>describe('controller: FeatureProposalController', function () {

    var controller, scope, fps;

    beforeEach(function() {
        module('core');
        module('app');
        module('mock.idea');
        module('mock.ideaRepo');
        module('mock.projectService');
        module('mock.featureProposal');
        module('mock.featureProposalRepo');
        module('mock.serviceRepo');

        inject(function ($controller, $rootScope, _Idea_, _IdeaRepo_, IdeaState, _FeatureProposal_, _FeatureProposalRepo_, FeatureProposalState, _ProjectService_, _ServiceRepo_) {
            installPromiseMatchers();
            scope = $rootScope.$new();
            fps = FeatureProposalState;
            controller = $controller('FeatureProposalController', {
                $scope: scope,
                Idea: _Idea_,
                IdeaRepo: _IdeaRepo_,
                IdeaState: IdeaState,
                FeatureProposal: _FeatureProposal_,
                FeatureProposalRepo: _FeatureProposalRepo_,
                FeatureProposalState: FeatureProposalState,
                ProjectService: _ProjectService_,
                ServiceRepo: _ServiceRepo_
            });
        });
    });

    describe('Is the controller defined', function () {
        it('should be defined', function () {
            expect(controller).toBeDefined();
        });
    });

    describe('Are the scope methods defined', function () {
        it('editFeatureProposal should be defined', function () {
            expect(scope.editFeatureProposal).toBeDefined();
            expect(typeof scope.editFeatureProposal).toEqual("function");
        });
        it('removeIdea should be defined', function () {
            expect(scope.removeIdea).toBeDefined();
            expect(typeof scope.removeIdea).toEqual("function");
        });
        it('updateFeatureProposal should be defined', function () {
            expect(scope.updateFeatureProposal).toBeDefined();
            expect(typeof scope.updateFeatureProposal).toEqual("function");
        });
        it('select should be defined', function () {
            expect(scope.select).toBeDefined();
            expect(typeof scope.select).toEqual("function");
        });
        it('submitFeatureProposal should be defined', function () {
            expect(scope.submitFeatureProposal).toBeDefined();
            expect(typeof scope.submitFeatureProposal).toEqual("function");
        });
        it('confirmDeleteFp should be defined', function () {
            expect(scope.confirmDeleteFp).toBeDefined();
            expect(typeof scope.confirmDeleteFp).toEqual("function");
        });
        it('deleteFp should be defined', function () {
            expect(scope.deleteFp).toBeDefined();
            expect(typeof scope.deleteFp).toEqual("function");
        });
        it('hasState should be defined', function () {
            expect(scope.hasState).toBeDefined();
            expect(typeof scope.hasState).toEqual("function");
        });
        it('getStateSummary should be defined', function () {
            expect(scope.getStateSummary).toBeDefined();
            expect(typeof scope.getStateSummary).toEqual("function");
        });
        it('initCreateFeatureProposal should be defined', function () {
            expect(scope.initCreateFeatureProposal).toBeDefined();
            expect(typeof scope.initCreateFeatureProposal).toEqual("function");
        });
    });

    describe('Are the scope methods working as expected', function () {
        it('hasState should return a boolean', function () {
            var state = "IN_PROGRESS";
            var fp = {
                state: state
            };
            expect(typeof scope.hasState(state, fp)).toEqual("boolean");
        });
    });

    describe('Are the scope methods working as expected', function () {
        it('hasState should return a boolean', function () {
            var state;
            var fp = {
                state: state
            };

            angular.forEach(fps, function(v, k) {
                state = v.value;
                fp.state = state;
                expect(typeof scope.hasState(state, fp)).toEqual("boolean");
            });
        });

        it('getStateSummary should return a string', function () {
            var state;
            angular.forEach(fps, function(v, k) {
                state = v.value;
                expect(typeof scope.getStateSummary(state)).toEqual("string");
            });
        });
    });
});
<|endoftext|>


<commit_before>/**
 * Shows info for a workspace object.
 *
 */
(function( $, undefined ) {
	$.KBWidget({
		name: "KBaseObjectMeta",
		parent: "kbaseWidget",
		version: "1.0.0",

		options: {
			workspaceID: null,
			objectId: null,
			loadingImage: "assets/img/loading.gif",
			kbCache:{}

		},

		workspaceURL: "https://kbase.us/services/workspace",

		//cdmiURL: "https://kbase.us/services/cdmi_api",

		/**
		 * Initialize the widget.
		 */
		init: function(options) {
			this._super(options);
			var self = this;

			console.log("here!");
			if (this.options.objectID === null) {
				this.renderError({ error: "No object id: " +
                                              this.options.workspaceID + "/" +
                                              this.options.objectId });
				return this;
			}

			if (options.wsUrl) {
			self.wsUrl = options.wsUrl;
			}

			var kbws;

			if (self.options.kbCache.ws_url) {
			    self.wsUrl = self.options.kbCache.ws_url;
			}
			if (self.options.kbCache.token) {
			    kbws = new Workspace(self.wsUrl, {token: self.options.kbCache.token});
			} else {
			    kbws = new Workspace(self.wsUrl);
			}

			self.objName = options.objNameOrId;
			self.wsName = options.wsNameOrId;

			self.$elem.append('

finding all data that references this object...

');
			self.$elem.append('
')

			 this.$infoPanel = $("
");
			 this.$infoTable = $("
")
                              .addClass("table table-striped table-bordered");
			 this.$infoPanel.append(this.$infoTable)
                           .append(this.$buttonPanel);

			this.$elem.append(this.$infoPanel);

			  var objectIdentity = self.getObjectIdentity(options.wsNameOrId, options.objNameOrId, options.objVer);
			 kbws.list_referencing_objects([objectIdentity], function(data) {

			 $.when(wsobject).fail($.proxy(function(error) {
                this.renderError(error);
                console.log(error);
            }, this));

            $.when(wsobject).done($.proxy(function(wsobject) {
                if (wsobject) {

                        this.$infoTable.empty();

			//0:obj_id objid, 1:obj_name name, 2:type_string type,3:timestamp save_date, 4:int version, 5:username saved_by,
                            //6:ws_id wsid, 7:ws_name workspace, 8:string chsum, 9:int size, 10:usermeta meta>

			this.$infoTable.append(this.makeRow("Name", wsobject[2]));
			this.$infoTable.append(this.makeRow("Id", wsobject[1]));
			this.$infoTable.append(this.makeRow("Last save", wsobject[3]));
			this.$infoTable.append(this.makeRow("Version", wsobject[4]));
                        this.$infoTable.append(this.makeRow("Owner", wsobject[5]));
			this.$infoTable.append(this.makeRow("Workspace Id", wsobject[6]));
			this.$infoTable.append(this.makeRow("Workspace name", wsobject[7]));
			this.$infoTable.append(this.makeRow("Checksum", wsobject[8]));
			this.$infoTable.append(this.makeRow("Size", wsobject[9]));
			this.$infoTable.append(this.makeRow("User metadata", wsobject[10]));

                }
                else {
                    this.renderError({ error: "No object id: " +
                                              this.options.wsNameOrId + "/" +
                                              this.options.objNameOrId });
                }

                this.hideMessage();
                this.$infoPanel.show();
            }, this));

        } , function(err) {
                    self.$elem.find('#loading-mssg').remove();
                    self.$elem.append("
No workspace or object found with these ids.");
                    console.error("No workspace or object found with these ids.");
                    console.error(err);
                });
	return this;
        },

		/**
		 *Returns the full workspace identified, even with version.
		 */
		getObjectIdentity: function(wsNameOrId, objNameOrId, objVer) {
			if (objVer) { return {ref:wsNameOrId+"/"+objNameOrId +"/"+objVer}; }
			return {ref:wsNameOrId+"/"+objNameOrId } ;
		},

	});
})( jQuery );
<commit_msg>added object mappings<commit_after>/**
 * Shows info for a workspace object.
 *
 */
(function( $, undefined ) {
	$.KBWidget({
		name: "KBaseObjectMeta",
		parent: "kbaseWidget",
		version: "1.0.0",

		options: {
			objNameOrId: null,
			wsNameOrId: null,
			loadingImage: "assets/img/loading.gif",
			kbCache:{},
			objVer: null,

		},

		objName:"",
		wsName:"",

		workspaceURL: "https://kbase.us/services/workspace",

		//cdmiURL: "https://kbase.us/services/cdmi_api",

		/**
		 * Initialize the widget.
		 */
		init: function(options) {
			this._super(options);
			var self = this;

			console.log(this.options.objNameOrId+"   "+this.options.wsNameOrId);

			/*
			console.log("here!");
			if (this.options.objNameOrId === null) {
				this.renderError({ error: "No object id: " +
                                              this.options.objNameOrId + "/" +
                                              this.options.wsNameOrId });
				return this;
			}

			if (options.wsUrl) {
			self.wsUrl = options.wsUrl;
			}
			*/

			var kbws;

			if (self.options.kbCache.ws_url) {
			    self.wsUrl = self.options.kbCache.ws_url;
			}
			if (self.options.kbCache.token) {
			    kbws = new Workspace(self.wsUrl, {token: self.options.kbCache.token});
			} else {
			    kbws = new Workspace(self.wsUrl);
			}

			self.objName = options.objNameOrId;
			self.wsName = options.wsNameOrId;

			self.$elem.append('

Finding metadata for this object...

');
			self.$elem.append('
')

			 this.$infoPanel = $("
");
			 this.$infoTable = $("
")
                              .addClass("table table-striped table-bordered");
			 this.$infoPanel.append(this.$infoTable)
                           .append(this.$buttonPanel);

			this.$elem.append(this.$infoPanel);

			  var objectIdentity = self.getObjectIdentity(options.wsNameOrId, options.objNameOrId, options.objVer);
			 kbws.get_objects([objectIdentity], function(data) {

		console.log(data[0].length);
		console.log(data.length);
		if (data[0].length == 0) {
                        self.$elem.append("
There no workspaces or objects with this id.");
                    } else {
                        var refList = {};

                        for(var i = 0; i < data[0].length; i++) {
                            var objInfo = data[0][i];
                            //0:obj_id objid, 1:obj_name name, 2:type_string type,3:timestamp save_date, 4:int version, 5:username saved_by,
                            //6:ws_id wsid, 7:ws_name workspace, 8:string chsum, 9:int size, 10:usermeta meta>
                            //object_info;
                            if (!(objInfo[6]+"/"+objInfo[1] in refList)) {
                                refList[objInfo[6]+"/"+objInfo[1]] = objInfo;

                            } else {
                                if (refList[objInfo[6]+"/"+objInfo[1]][4] < objInfo[4]) {
                                    refList[objInfo[6]+"/"+objInfo[1]] = objInfo;

                            }
                        }

                        // load the data into a data object
                        var refTableData = [];
                        for(var ref in refList) {
                            var objInfo = refList[ref];
                            var savedate = new Date(objInfo[3]);
                                    refTableData.push({
                                            na:objInfo[1]+ " ("+objInfo[6]+"/"+objInfo[0]+"/"+objInfo[4]+")",
                                            ty:objInfo[2],
                                            de:"saved by "+objInfo[5]+" on "+self.monthLookup[savedate.getMonth()]+" "+savedate.getDate()+", "+savedate.getFullYear()
                                        })
                                }
                            //self.$elem.append(""+objInfo[1]+" "+objInfo[2]+" ("+objInfo[6]+"/"+objInfo[0]+"/"+objInfo[4]+", saved by "
                            //            +objInfo[5]+" on "+self.monthLookup[savedate.getMonth()]+" "+savedate.getDate()+", "+savedate.getFullYear()+")
")
                        }

                        self.$elem.find('#loading-mssg').remove();

                        var $maindiv = self.$elem.find('#mainview');
                        $maindiv.append('
');

            		var tblSettings = {
            				"sPaginationType": "full_numbers",
            				"iDisplayLength": 10,
                                        "sDom": 't<flip>',
            				"aoColumns": [
            				              {sTitle: "Object Name (reference)", mData: "na", sWidth:"30%"},
            				              {sTitle: "Type", mData: "ty"},
            				              {sTitle: "Details", mData: "de"}
            				              ],
            				              "aaData": refTableData
            		};
            		var refTable = self.$elem.find('#ref-table').dataTable(tblSettings);
                    }

	/*
			 $.when(wsobject).fail($.proxy(function(error) {
                this.renderError(error);
                console.log(error);
            }, this));

            $.when(wsobject).done($.proxy(function(wsobject) {
                if (wsobject) {

                        this.$infoTable.empty();

			//0:obj_id objid, 1:obj_name name, 2:type_string type,3:timestamp save_date, 4:int version, 5:username saved_by,
                            //6:ws_id wsid, 7:ws_name workspace, 8:string chsum, 9:int size, 10:usermeta meta>

			this.$infoTable.append(this.makeRow("Name", wsobject[2]));
			this.$infoTable.append(this.makeRow("Id", wsobject[1]));
			this.$infoTable.append(this.makeRow("Last save", wsobject[3]));
			this.$infoTable.append(this.makeRow("Version", wsobject[4]));
                        this.$infoTable.append(this.makeRow("Owner", wsobject[5]));
			this.$infoTable.append(this.makeRow("Workspace Id", wsobject[6]));
			this.$infoTable.append(this.makeRow("Workspace name", wsobject[7]));
			this.$infoTable.append(this.makeRow("Checksum", wsobject[8]));
			this.$infoTable.append(this.makeRow("Size", wsobject[9]));
			this.$infoTable.append(this.makeRow("User metadata", wsobject[10]));

                }
                else {
                    this.renderError({ error: "No object id: " +
                                              this.options.wsNameOrId + "/" +
                                              this.options.objNameOrId });
                }

                this.hideMessage();
                this.$infoPanel.show();
            }, this));
	    */

        } , function(err) {
                    self.$elem.find('#loading-mssg').remove();
                    self.$elem.append("
No workspace or object found with these ids.");
                    console.error("No workspace or object found with these ids.");
                    console.error(err);
                });
	return this;
        },

		  getData: function() {
            return {title:"Metadata for :",id:this.objName, workspace:this.wsName};
        },

		/**
		 *Returns the full workspace identified, even with version.
		 */
		getObjectIdentity: function(wsNameOrId, objNameOrId, objVer) {
			if (objVer) { return {ref:wsNameOrId+"/"+objNameOrId +"/"+objVer}; }
			return {ref:wsNameOrId+"/"+objNameOrId } ;
		},

	});
})( jQuery );
<|endoftext|>


<commit_before>var _ = require('lodash');
var async = require('async');
var fs = require('fs');
var path = require('path');
var http = require('http');
var watch = require('watch');
var url = require('url');
var browserSync = require('browser-sync');
var RunListHelper = require('../src/RunListHelper');
var CmdHelper = require('../src/CmdHelper');

module.exports = {
  name: 'cdn',
  usage: '[-r <repoPattern>] [-w <repoPattern>] [<minify>]',
  description: 'Aggregates all the static assets across all microservices and serves them via a pseudo local CDN url',
  requiresNvm: true,
  options: [{
    name: 'tag',
    alias: 't',
    type: 'string',
    desc: 'Filter by a tag defined within bosco-service.json',
  },
  {
    name: 'watch',
    alias: 'w',
    type: 'string',
    desc: 'Filter by a regex of services to watch (similar to -r in run)',
  }],
};

function cmd(bosco, args) {
  var minify = _.includes(args, 'minify');
  var port = bosco.config.get('cdn:port') || 7334;
  var repoPattern = bosco.options.repo;
  var repoRegex = new RegExp(repoPattern);
  var watchPattern = bosco.options.watch || '$a';
  var watchRegex = new RegExp(watchPattern);
  var repoTag = bosco.options.tag;
  var bs = browserSync.create();
  var repos;

  bosco.log('Starting pseudo CDN on port: ' + (port + '').blue);

  if (bosco.options.list) {
    repos = bosco.options.list.split(',');
  } else {
    if (CmdHelper.checkInService(bosco)) {
      bosco.options.watch = bosco.options.watch || new RegExp(path.basename(process.cwd()));
      watchRegex = new RegExp(bosco.options.watch);
    }

    repos = bosco.getRepos();
  }

  if (!repos) return bosco.error('You are repo-less :( You need to initialise bosco first, try \'bosco clone\'.');

  function getRunList(next) {
    RunListHelper.getRunList(bosco, repos, repoRegex, watchRegex, repoTag, next);
  }

  function startServer(staticAssets, staticRepos, serverPort) {
    var isWatchedFile = function(assetPath) {
      var watched;
      try {
        watched = assetPath && !fs.lstatSync(assetPath).isDirectory() && assetPath.match(watchRegex);
      } catch (ex) {
        watched = false;
      }
      return watched;
    };

    function getAsset(assetUrl) {
      var key = assetUrl.replace('/', '');
      return _.find(staticAssets, {assetKey: key});
    }

    var server = http.createServer(function(request, response) {
      if (request.method === 'OPTIONS') {
        response.writeHead(200, {
          'Access-Control-Allow-Origin': '*',
          'Access-Control-Allow-Methods': 'POST, GET, PUT, DELETE, OPTIONS',
          'Access-Control-Allow-Credentials': true,
          'Access-Control-Max-Age': '86400', // 24 hours
          'Access-Control-Allow-Headers': 'X-Requested-With, Access-Control-Allow-Origin, X-HTTP-Method-Override, Content-Type, Authorization, Accept',
        });
        return response.end();
      }

      var pathname = url.parse(request.url).pathname;
      if (pathname === '/repos') {
        response.writeHead(200, {'Content-Type': 'text/html'});
        return response.end(staticRepos.formattedRepos);
      }

      var asset = getAsset(pathname);
      if (!asset) {
        response.writeHead(404, {'Content-Type': 'text/html'});
        return response.end(staticAssets.formattedAssets);
      }

      response.writeHead(200, {
        'Content-Type': asset.mimeType,
        'Cache-Control': 'no-cache, must-revalidate',
        'Pragma': 'no-cache',
        'Expires': 'Sat, 21 May 1952 00:00:00 GMT',
        'Access-Control-Allow-Origin': '*',
      });

      if (isWatchedFile(asset.path)) {
        fs.readFile(asset.path, function(err, content) {
          response.end(content);
        });
      } else {
        response.end(asset.data || asset.content);
      }
    });

    server.listen(serverPort);

    if (bosco.options['browser-sync']) {
      var assets = _.filter(_.map(staticAssets, 'path'), isWatchedFile);
      var extraFiles = _.filter(_.uniq(_.flattenDeep(_.map(staticAssets, 'extraFiles'))));
      var assetsToWatch = _.union(assets, extraFiles);
      bs.init({
        proxy: bosco.options['browser-sync-proxy'],
        files: assetsToWatch,
        reloadDelay: bosco.options['browser-sync-delay'],
      });
    }

    bosco.log('Server is listening on ' + serverPort);
  }

  function startMonitor(staticAssets) {
    var watchSet = {};
    var reloading = {};

    _.forEach(staticAssets, function(asset) {
      if (asset.repo && !asset.repo.match(watchRegex)) return;

      if (minify && asset.extname === '.manifest') {
        asset.files.forEach(function(file) {
          if (file) watchSet[file.path] = asset.tag;
        });
        return;
      }

      if (asset.path) watchSet[asset.path] = asset.assetKey;
    });

    function filterFn(f, stat) {
      return f.match(watchRegex) && stat.isDirectory() || watchSet[f];
    }

    function getIndexForKey(assetList, fileKey) {
      var find = (_.isObject(assetList)) ? _.findKey : _.findIndex;
      return find(assetList, 'assetKey', fileKey);
    }

    function reloadFile(fileKey) {
      if (!fileKey) return;

      if (!minify) {
        var assetIndex = getIndexForKey(staticAssets, fileKey);
        if (!assetIndex) {
          bosco.error('Unable to locate asset with key: ' + fileKey);
          return;
        }
        fs.readFile(staticAssets[assetIndex].path, function(err, data) {
          if (err) {
            bosco.log('Error reloading ' + fileKey);
            bosco.log(err.toString());
            return;
          }
          staticAssets[assetIndex].data = data;
          staticAssets[assetIndex].content = data.toString();
          bosco.log('Reloaded ' + fileKey);
          reloading[fileKey] = false;
        });
        return;
      }

      bosco.log('Recompiling tag ' + fileKey.blue);
      var staticAssetOptions = {
        repos: repos,
        minify: minify,
        buildNumber: 'local',
        tagFilter: fileKey,
        watchBuilds: false,
        reloadOnly: true,
      };
      bosco.staticUtils.getStaticAssets(staticAssetOptions, function(err, updatedAssets) {
        _.forEach(updatedAssets, function(value) {
          var index = getIndexForKey(staticAssets, value.assetKey);
          staticAssets[index] = value;
        });
        bosco.log('Reloaded minified assets for tag ' + fileKey.blue);
        reloading[fileKey] = false;
      });
    }

    watch.createMonitor(bosco.getOrgPath(), {filter: filterFn, ignoreDotFiles: true, ignoreUnreadableDir: true, ignoreDirectoryPattern: /node_modules|\.git|coverage/, interval: 1000}, function(monitor) {
      bosco.log('Watching ' + _.keys(monitor.files).length + ' files ...');

      function onChange(f) {
        var fileKey = watchSet[f];

        if (reloading[fileKey]) return;
        reloading[fileKey] = true;
        reloadFile(fileKey);
      }

      monitor.on('changed', onChange);
      monitor.on('created', onChange);
    });
  }

  if (minify) bosco.log('Minifying front end assets, this can take some time ...');

  getRunList(function(err, repoList) {
    var repoNames = _.map(repoList, 'name');
    var options = {
      repos: repoNames,
      buildNumber: 'local',
      minify: minify,
      tagFilter: null,
      watchBuilds: true,
      reloadOnly: false,
      ignoreFailure: true,
      watchRegex: watchRegex,
      repoRegex: repoRegex,
      repoTag: repoTag,
    };

    var executeAsync = {
      staticAssets: bosco.staticUtils.getStaticAssets.bind(null, options),
      staticRepos: bosco.staticUtils.getStaticRepos.bind(null, options),
    };

    async.parallel(executeAsync, function(err, results) {
      startServer(results.staticAssets, results.staticRepos, port);
      startMonitor(results.staticAssets);
    });
  });
}

module.exports.cmd = cmd;
<commit_msg>Compatibility fix for lodash<commit_after>var _ = require('lodash');
var async = require('async');
var fs = require('fs');
var path = require('path');
var http = require('http');
var watch = require('watch');
var url = require('url');
var browserSync = require('browser-sync');
var RunListHelper = require('../src/RunListHelper');
var CmdHelper = require('../src/CmdHelper');

module.exports = {
  name: 'cdn',
  usage: '[-r <repoPattern>] [-w <repoPattern>] [<minify>]',
  description: 'Aggregates all the static assets across all microservices and serves them via a pseudo local CDN url',
  requiresNvm: true,
  options: [{
    name: 'tag',
    alias: 't',
    type: 'string',
    desc: 'Filter by a tag defined within bosco-service.json',
  },
  {
    name: 'watch',
    alias: 'w',
    type: 'string',
    desc: 'Filter by a regex of services to watch (similar to -r in run)',
  }],
};

function cmd(bosco, args) {
  var minify = _.includes(args, 'minify');
  var port = bosco.config.get('cdn:port') || 7334;
  var repoPattern = bosco.options.repo;
  var repoRegex = new RegExp(repoPattern);
  var watchPattern = bosco.options.watch || '$a';
  var watchRegex = new RegExp(watchPattern);
  var repoTag = bosco.options.tag;
  var bs = browserSync.create();
  var repos;

  bosco.log('Starting pseudo CDN on port: ' + (port + '').blue);

  if (bosco.options.list) {
    repos = bosco.options.list.split(',');
  } else {
    if (CmdHelper.checkInService(bosco)) {
      bosco.options.watch = bosco.options.watch || new RegExp(path.basename(process.cwd()));
      watchRegex = new RegExp(bosco.options.watch);
    }

    repos = bosco.getRepos();
  }

  if (!repos) return bosco.error('You are repo-less :( You need to initialise bosco first, try \'bosco clone\'.');

  function getRunList(next) {
    RunListHelper.getRunList(bosco, repos, repoRegex, watchRegex, repoTag, next);
  }

  function startServer(staticAssets, staticRepos, serverPort) {
    var isWatchedFile = function(assetPath) {
      var watched;
      try {
        watched = assetPath && !fs.lstatSync(assetPath).isDirectory() && assetPath.match(watchRegex);
      } catch (ex) {
        watched = false;
      }
      return watched;
    };

    function getAsset(assetUrl) {
      var key = assetUrl.replace('/', '');
      return _.find(staticAssets, {assetKey: key});
    }

    var server = http.createServer(function(request, response) {
      if (request.method === 'OPTIONS') {
        response.writeHead(200, {
          'Access-Control-Allow-Origin': '*',
          'Access-Control-Allow-Methods': 'POST, GET, PUT, DELETE, OPTIONS',
          'Access-Control-Allow-Credentials': true,
          'Access-Control-Max-Age': '86400', // 24 hours
          'Access-Control-Allow-Headers': 'X-Requested-With, Access-Control-Allow-Origin, X-HTTP-Method-Override, Content-Type, Authorization, Accept',
        });
        return response.end();
      }

      var pathname = url.parse(request.url).pathname;
      if (pathname === '/repos') {
        response.writeHead(200, {'Content-Type': 'text/html'});
        return response.end(staticRepos.formattedRepos);
      }

      var asset = getAsset(pathname);
      if (!asset) {
        response.writeHead(404, {'Content-Type': 'text/html'});
        return response.end(staticAssets.formattedAssets);
      }

      response.writeHead(200, {
        'Content-Type': asset.mimeType,
        'Cache-Control': 'no-cache, must-revalidate',
        'Pragma': 'no-cache',
        'Expires': 'Sat, 21 May 1952 00:00:00 GMT',
        'Access-Control-Allow-Origin': '*',
      });

      if (isWatchedFile(asset.path)) {
        fs.readFile(asset.path, function(err, content) {
          response.end(content);
        });
      } else {
        response.end(asset.data || asset.content);
      }
    });

    server.listen(serverPort);

    if (bosco.options['browser-sync']) {
      var assets = _.filter(_.map(staticAssets, 'path'), isWatchedFile);
      var extraFiles = _.filter(_.uniq(_.flattenDeep(_.map(staticAssets, 'extraFiles'))));
      var assetsToWatch = _.union(assets, extraFiles);
      bs.init({
        proxy: bosco.options['browser-sync-proxy'],
        files: assetsToWatch,
        reloadDelay: bosco.options['browser-sync-delay'],
      });
    }

    bosco.log('Server is listening on ' + serverPort);
  }

  function startMonitor(staticAssets) {
    var watchSet = {};
    var reloading = {};

    _.forEach(staticAssets, function(asset) {
      if (asset.repo && !asset.repo.match(watchRegex)) return;

      if (minify && asset.extname === '.manifest') {
        asset.files.forEach(function(file) {
          if (file) watchSet[file.path] = asset.tag;
        });
        return;
      }

      if (asset.path) watchSet[asset.path] = asset.assetKey;
    });

    function filterFn(f, stat) {
      return f.match(watchRegex) && stat.isDirectory() || watchSet[f];
    }

    function getIndexForKey(assetList, fileKey) {
      return _.findIndex(assetList, {assetKey: fileKey});
    }

    function reloadFile(fileKey) {
      if (!fileKey) return;

      if (!minify) {
        var assetIndex = getIndexForKey(staticAssets, fileKey);
        if (!assetIndex) {
          bosco.error('Unable to locate asset with key: ' + fileKey);
          return;
        }
        fs.readFile(staticAssets[assetIndex].path, function(err, data) {
          if (err) {
            bosco.log('Error reloading ' + fileKey);
            bosco.log(err.toString());
            return;
          }
          staticAssets[assetIndex].data = data;
          staticAssets[assetIndex].content = data.toString();
          bosco.log('Reloaded ' + fileKey);
          reloading[fileKey] = false;
        });
        return;
      }

      bosco.log('Recompiling tag ' + fileKey.blue);
      var staticAssetOptions = {
        repos: repos,
        minify: minify,
        buildNumber: 'local',
        tagFilter: fileKey,
        watchBuilds: false,
        reloadOnly: true,
      };
      bosco.staticUtils.getStaticAssets(staticAssetOptions, function(err, updatedAssets) {
        _.forEach(updatedAssets, function(value) {
          var index = getIndexForKey(staticAssets, value.assetKey);
          staticAssets[index] = value;
        });
        bosco.log('Reloaded minified assets for tag ' + fileKey.blue);
        reloading[fileKey] = false;
      });
    }

    watch.createMonitor(bosco.getOrgPath(), {filter: filterFn, ignoreDotFiles: true, ignoreUnreadableDir: true, ignoreDirectoryPattern: /node_modules|\.git|coverage/, interval: 1000}, function(monitor) {
      bosco.log('Watching ' + _.keys(monitor.files).length + ' files ...');

      function onChange(f) {
        var fileKey = watchSet[f];

        if (reloading[fileKey]) return;
        reloading[fileKey] = true;
        reloadFile(fileKey);
      }

      monitor.on('changed', onChange);
      monitor.on('created', onChange);
    });
  }

  if (minify) bosco.log('Minifying front end assets, this can take some time ...');

  getRunList(function(err, repoList) {
    var repoNames = _.map(repoList, 'name');
    var options = {
      repos: repoNames,
      buildNumber: 'local',
      minify: minify,
      tagFilter: null,
      watchBuilds: true,
      reloadOnly: false,
      ignoreFailure: true,
      watchRegex: watchRegex,
      repoRegex: repoRegex,
      repoTag: repoTag,
    };

    var executeAsync = {
      staticAssets: bosco.staticUtils.getStaticAssets.bind(null, options),
      staticRepos: bosco.staticUtils.getStaticRepos.bind(null, options),
    };

    async.parallel(executeAsync, function(err, results) {
      startServer(results.staticAssets, results.staticRepos, port);
      startMonitor(results.staticAssets);
    });
  });
}

module.exports.cmd = cmd;
<|endoftext|>


<commit_before>'use strict';

var _ = require('lodash'),
  uuid = require('node-uuid');

var Router = require('koa-router'),
  body = require('koa-better-body');

module.exports = function BikeshedsController (helpers) {
  var middleware = helpers.middleware,
    auth = middleware.auth(),
    loadBikeshed = middleware.load('Bikeshed'),
    authLoadBike = middleware.load('Bike', {parent: 'Bikeshed'}),
    authLoadBikeshed = middleware.load('Bikeshed', {parent: 'User'});

  var jsonBody = body(),
    multiBody = body({
      multipart: true,
      formidable: {
        multiples: false
      }
    });

  var bikeshedRoutes = new Router()

    // public
    .get('/bikesheds', index)
    .get('/bikesheds/:bikeshed', loadBikeshed, get)
    .get('/bikesheds/:bikeshed/bikes', loadBikeshed, score)

    // private
    .post('/bikesheds', auth, jsonBody, create)
    .put('/bikesheds/:bikeshed', auth, authLoadBikeshed, update)
    .del('/bikesheds/:bikeshed', auth, authLoadBikeshed, destroy)
    .post('/bikesheds/:bikeshed', auth, authLoadBikeshed, multiBody, add)
    .post('/bikesheds/:bikeshed/bikes', auth, loadBikeshed, jsonBody, rate)
    .del('/bikesheds/:bikeshed/bikes/:bike', auth, authLoadBikeshed, authLoadBike, remove);

  return bikeshedRoutes.middleware();
};

/**
 * GET /bikesheds
 * Public
 * Parameters: sortBy, direction, page, per
 *  per: default 12, [1..96]
 *  page: default 1, [1..]
 *  sortBy: default id, [id, name, createdAt, updatedAt]
 *  direction: default DESC, [DESC, ASC]
 */
function* index () {
  var Bikeshed = this.models.Bikeshed,
    query = this.request.query,
    page = _.parseInt(query.page),
    per = _.parseInt(query.per),
    sortBy = query.sortBy,
    direction = (query.direction || '').toUpperCase();

  page = page >= 1 ? page : 1;
  per = per >= 1 ? (per < 96 ? per : 96) : 12;
  direction = _.contains(['ASC', 'DESC'], direction) ? direction : 'DESC';
  sortBy = _.contains(['id', 'name', 'createdAt', 'updatedAt'], sortBy) ? sortBy : 'id';

  var result = yield Bikeshed.findAndCountAll({
    where: {status: {ne: 'incomplete'}},
    order: [[sortBy, direction]],
    offset: (page - 1) * per,
    limit: per
  });

  this.body = {
    list: result.rows,
    count: result.count,
    page: page,
    pages: Math.ceil(result.count / per) || 1,
    per: per,
    sortBy: sortBy,
    direction: direction
  };
}

/**
 * POST /bikesheds
 * Protected
 * Body: {name: string, body: string}
 */
function* create () {
  var fields = _.assign({UserId: this.state.user.id},
    _.pick(this.request.body.fields, ['name', 'body']));

  var bikeshed = this.models.Bikeshed.build(fields),
    bikeshedValidation = yield bikeshed.validate();
  if (bikeshedValidation) {
    this.body = bikeshedValidation;
    this.status = 422;
  } else {
    this.body = yield bikeshed.save();
    this.status = 201;
  }
}

/**
 * GET /bikesheds/:bikeshed
 * Public
 */
function* get () {
  this.body = this.state.bikeshed;
}

/**
 * PUT /biksheds/:bikeshed
 * Protected
 * Body: {status: string}
 */
function* update () {
  this.body = 503;
}

/**
 * DELETE /bikeshed/:bikeshed
 * Protected
 */
function* destroy () {
  yield this.state.bikeshed.destroy();
  this.status = 204;
}

/**
 * POST /bikesheds/:bikeshed
 * Protected
 * Body: {name: string, body: string, image: file}
 */
function* add () {
  var bikeshed = this.state.bikeshed;
  if (bikeshed.status !== 'incomplete') {
    this.throw(403, 'can only add bikes to incomplete bikeshed');
  } else if (bikeshed.size >= 5) {
    this.throw(403, 'cannot insert over 5 bikes per bikeshed');
  }

  var image = _.values(this.request.body.files)[0],
    fields = _.assign({
      BikeshedId: this.state.bikeshed.id, imageType: image && image.type
    }, _.pick(this.request.body.fields, ['name', 'body']));

  var bike = this.models.Bike.build(fields),
    bikeValidation = yield bike.validate();
  if (bikeValidation) {
    this.body = bikeValidation;
    if (_.where(bikeValidation.errors, {path: 'imageType'})) {
      this.throw(415);
    } else {
      this.throw(422);
    }
  }

  var s3Options = {};
  if (image) {
    bike.imageName = uuid.v4();
    s3Options.upload = {
      localFile: image.path,
      s3Params: {
        ACL: 'public-read',
        ContentType: bike.imageType,
        Bucket: bike.bucket,
        Key: bike.key
      }
    };
    s3Options.destroy = {
      Delete: {Objects: [{Key: bike.key}]},
      Bucket: bike.bucket
    };
  }

  var t = yield this.models.sequelize.transaction();
  try {
    bike = yield bike.save({transaction: t});
    if (image) yield this.s3.attemptUpload(s3Options);
    yield t.commit();
  } catch (err) {
    yield t.rollback();
    if (err.message === 'cannot insert over 5 bikes per bikeshed') {
      this.throw(403, err.message);
    } else if (err.message === 'could not serialize access due to concurrent update') {
      this.throw(409, err.message);
    } else {
      this.throw(503);
    }
  }
  this.body = bike;
  this.status = 201;
}

/**
 * GET /bikesheds/:bikeshed/bikes
 * Public
 */
function* score () {
  this.body = yield this.models.Bikes.findAll({where: {BikeshedId: this.state.bikeshed.id}});
}

/**
 * DELETE /bikesheds/:bikeshed/bikes/:bike
 * Protected
 */
function* remove () {
  if (this.state.bikeshed.status !== 'incomplete') {
    this.throw(403, 'can only remove bikes from incomplete bikesheds');
  }

  yield this.state.bike.destroy();
  this.status = 204;
}

/**
 * PUT /bikesheds/:bikeshed/bikes
 * Protected
 * Body: {}
 */
function* change () {
  this.body = 503;
}

/**
 * POST /bikesheds/:bikeshed/bikes
 * Protected
 * Body: {[BikeId]: {score: number}}
 * Example: {1:{score:0}, 2:{score:1}, 3:{score:2}}
 */
function* rate () {
  var {Vote, Bike} = this.models, {bikeshed, user} = this.state;

  if (bikeshed.status !== 'open') {
    this.throw(403, 'bikeshed must be open');
  } else if (yield Vote.count({where: {BikeshedId: bikeshed.id, UserId: user.id}})) {
    this.throw(403, 'already voted');
  }

  var bikes = yield Bike.findAll({where: {BikeshedId: bikeshed.id}});
  var votes = _.pairs(this.request.body.fields)
  .map(function (pair) {
    var BikeId = _.parseInt(pair[0]), {score} = _.pick(pair[1], ['score']),
      BikeshedId = bikeshed.id, UserId = user.id;
    return Vote.build({BikeshedId, UserId, BikeId, score});
  });

  if (bikes.length !== votes.length) {
    this.throw(422, 'must vote for each bike');
  } else if (_.difference(_.pluck(votes, 'BikeId'), _.pluck(bikes, 'id')).length) {
    this.throw(422, 'can only vote once per bike');
  } else if (_.difference(_.pluck(votes, 'score'), _.range(votes.length)).length) {
    this.throw(422, 'must have unique score per bike');
  }

  var validations = yield votes.map(vote => vote.validate()).filter(vote => vote);
  if (validations.length) {
    this.body = validations;
    this.throw(422);
  }

  var t = yield this.models.sequelize.transaction();
  try {
    votes = yield votes.map(vote => vote.save({transaction: t}));
    yield t.commit();
  } catch (err) {
    yield t.rollback();
    this.throw(409, 'voting conflict');
  }

  this.body = votes;
  this.status = 201;
}
<commit_msg>rename get to show<commit_after>'use strict';

var _ = require('lodash'),
  uuid = require('node-uuid');

var Router = require('koa-router'),
  body = require('koa-better-body');

module.exports = function BikeshedsController (helpers) {
  var middleware = helpers.middleware,
    auth = middleware.auth(),
    loadBikeshed = middleware.load('Bikeshed'),
    authLoadBike = middleware.load('Bike', {parent: 'Bikeshed'}),
    authLoadBikeshed = middleware.load('Bikeshed', {parent: 'User'});

  var jsonBody = body(),
    multiBody = body({
      multipart: true,
      formidable: {
        multiples: false
      }
    });

  var bikeshedRoutes = new Router()

    // public
    .get('/bikesheds', index)
    .get('/bikesheds/:bikeshed', loadBikeshed, show)
    .get('/bikesheds/:bikeshed/bikes', loadBikeshed, score)

    // private
    .post('/bikesheds', auth, jsonBody, create)
    .put('/bikesheds/:bikeshed', auth, authLoadBikeshed, update)
    .del('/bikesheds/:bikeshed', auth, authLoadBikeshed, destroy)
    .post('/bikesheds/:bikeshed', auth, authLoadBikeshed, multiBody, add)
    .post('/bikesheds/:bikeshed/bikes', auth, loadBikeshed, jsonBody, rate)
    .del('/bikesheds/:bikeshed/bikes/:bike', auth, authLoadBikeshed, authLoadBike, remove);

  return bikeshedRoutes.middleware();
};

/**
 * GET /bikesheds
 * Public
 * Parameters: sortBy, direction, page, per
 *  per: default 12, [1..96]
 *  page: default 1, [1..]
 *  sortBy: default id, [id, name, createdAt, updatedAt]
 *  direction: default DESC, [DESC, ASC]
 */
function* index () {
  var Bikeshed = this.models.Bikeshed,
    query = this.request.query,
    page = _.parseInt(query.page),
    per = _.parseInt(query.per),
    sortBy = query.sortBy,
    direction = (query.direction || '').toUpperCase();

  page = page >= 1 ? page : 1;
  per = per >= 1 ? (per < 96 ? per : 96) : 12;
  direction = _.contains(['ASC', 'DESC'], direction) ? direction : 'DESC';
  sortBy = _.contains(['id', 'name', 'createdAt', 'updatedAt'], sortBy) ? sortBy : 'id';

  var result = yield Bikeshed.findAndCountAll({
    where: {status: {ne: 'incomplete'}},
    order: [[sortBy, direction]],
    offset: (page - 1) * per,
    limit: per
  });

  this.body = {
    list: result.rows,
    count: result.count,
    page: page,
    pages: Math.ceil(result.count / per) || 1,
    per: per,
    sortBy: sortBy,
    direction: direction
  };
}

/**
 * POST /bikesheds
 * Protected
 * Body: {name: string, body: string}
 */
function* create () {
  var fields = _.assign({UserId: this.state.user.id},
    _.pick(this.request.body.fields, ['name', 'body']));

  var bikeshed = this.models.Bikeshed.build(fields),
    bikeshedValidation = yield bikeshed.validate();
  if (bikeshedValidation) {
    this.body = bikeshedValidation;
    this.status = 422;
  } else {
    this.body = yield bikeshed.save();
    this.status = 201;
  }
}

/**
 * GET /bikesheds/:bikeshed
 * Public
 */
function* show () {
  this.body = this.state.bikeshed;
}

/**
 * PUT /biksheds/:bikeshed
 * Protected
 * Body: {status: string}
 */
function* update () {
  this.body = 503;
}

/**
 * DELETE /bikeshed/:bikeshed
 * Protected
 */
function* destroy () {
  yield this.state.bikeshed.destroy();
  this.status = 204;
}

/**
 * POST /bikesheds/:bikeshed
 * Protected
 * Body: {name: string, body: string, image: file}
 */
function* add () {
  var bikeshed = this.state.bikeshed;
  if (bikeshed.status !== 'incomplete') {
    this.throw(403, 'can only add bikes to incomplete bikeshed');
  } else if (bikeshed.size >= 5) {
    this.throw(403, 'cannot insert over 5 bikes per bikeshed');
  }

  var image = _.values(this.request.body.files)[0],
    fields = _.assign({
      BikeshedId: this.state.bikeshed.id, imageType: image && image.type
    }, _.pick(this.request.body.fields, ['name', 'body']));

  var bike = this.models.Bike.build(fields),
    bikeValidation = yield bike.validate();
  if (bikeValidation) {
    this.body = bikeValidation;
    if (_.where(bikeValidation.errors, {path: 'imageType'})) {
      this.throw(415);
    } else {
      this.throw(422);
    }
  }

  var s3Options = {};
  if (image) {
    bike.imageName = uuid.v4();
    s3Options.upload = {
      localFile: image.path,
      s3Params: {
        ACL: 'public-read',
        ContentType: bike.imageType,
        Bucket: bike.bucket,
        Key: bike.key
      }
    };
    s3Options.destroy = {
      Delete: {Objects: [{Key: bike.key}]},
      Bucket: bike.bucket
    };
  }

  var t = yield this.models.sequelize.transaction();
  try {
    bike = yield bike.save({transaction: t});
    if (image) yield this.s3.attemptUpload(s3Options);
    yield t.commit();
  } catch (err) {
    yield t.rollback();
    if (err.message === 'cannot insert over 5 bikes per bikeshed') {
      this.throw(403, err.message);
    } else if (err.message === 'could not serialize access due to concurrent update') {
      this.throw(409, err.message);
    } else {
      this.throw(503);
    }
  }
  this.body = bike;
  this.status = 201;
}

/**
 * GET /bikesheds/:bikeshed/bikes
 * Public
 */
function* score () {
  this.body = yield this.models.Bikes.findAll({where: {BikeshedId: this.state.bikeshed.id}});
}

/**
 * DELETE /bikesheds/:bikeshed/bikes/:bike
 * Protected
 */
function* remove () {
  if (this.state.bikeshed.status !== 'incomplete') {
    this.throw(403, 'can only remove bikes from incomplete bikesheds');
  }

  yield this.state.bike.destroy();
  this.status = 204;
}

/**
 * PUT /bikesheds/:bikeshed/bikes
 * Protected
 * Body: {}
 */
function* change () {
  this.body = 503;
}

/**
 * POST /bikesheds/:bikeshed/bikes
 * Protected
 * Body: {[BikeId]: {score: number}}
 * Example: {1:{score:0}, 2:{score:1}, 3:{score:2}}
 */
function* rate () {
  var {Vote, Bike} = this.models, {bikeshed, user} = this.state;

  if (bikeshed.status !== 'open') {
    this.throw(403, 'bikeshed must be open');
  } else if (yield Vote.count({where: {BikeshedId: bikeshed.id, UserId: user.id}})) {
    this.throw(403, 'already voted');
  }

  var bikes = yield Bike.findAll({where: {BikeshedId: bikeshed.id}});
  var votes = _.pairs(this.request.body.fields)
  .map(function (pair) {
    var BikeId = _.parseInt(pair[0]), {score} = _.pick(pair[1], ['score']),
      BikeshedId = bikeshed.id, UserId = user.id;
    return Vote.build({BikeshedId, UserId, BikeId, score});
  });

  if (bikes.length !== votes.length) {
    this.throw(422, 'must vote for each bike');
  } else if (_.difference(_.pluck(votes, 'BikeId'), _.pluck(bikes, 'id')).length) {
    this.throw(422, 'can only vote once per bike');
  } else if (_.difference(_.pluck(votes, 'score'), _.range(votes.length)).length) {
    this.throw(422, 'must have unique score per bike');
  }

  var validations = yield votes.map(vote => vote.validate()).filter(vote => vote);
  if (validations.length) {
    this.body = validations;
    this.throw(422);
  }

  var t = yield this.models.sequelize.transaction();
  try {
    votes = yield votes.map(vote => vote.save({transaction: t}));
    yield t.commit();
  } catch (err) {
    yield t.rollback();
    this.throw(409, 'voting conflict');
  }

  this.body = votes;
  this.status = 201;
}
<|endoftext|>