<commit_before>/* global spacesRegex oneWeekAgo performance historyInMemoryCache calculateHistoryScore */

/* depends on placesWorker.js */

function searchPlaces (searchText, callback, options) {
  function processSearchItem (item) {
    if (limitToBookmarks && !item.isBookmarked) {
      return
    }
    // if the text does not contain the first search word, it can't possibly be a match, so don't do any processing
    let itext = item.url.split('?')[0].replace('http://', '').replace('https://', '').replace('www.', '')

    if (item.url !== item.title) {
      itext += ' ' + item.title
    }

    itext = itext.toLowerCase().replace(spacesRegex, ' ')

    const tindex = itext.indexOf(st)

    // if the url contains the search string, count as a match
    // prioritize matches near the beginning of the url
    if (tindex === 0) {
      item.boost = itemStartBoost // large amount of boost for this
      matches.push(item)
    } else if (tindex !== -1) {
      item.boost = exactMatchBoost
      matches.push(item)
    } else {
      // if all of the search words (split by spaces, etc) exist in the url, count it as a match, even if they are out of order

      if (substringSearchEnabled) {
        let substringMatch = true

        // check if the search text matches but is out of order
        for (let i = 0; i < swl; i++) {
          if (itext.indexOf(searchWords[i]) === -1) {
            substringMatch = false
            break
          }
        }

        if (substringMatch) {
          item.boost = 0.125 * swl + (0.02 * stl)
          matches.push(item)
          return
        }
      }

      if (item.visitCount !== 1 || item.lastVisit > oneWeekAgo) { // if the item has been visited more than once, or has been visited in the last week, we should calculate the fuzzy score. Otherwise, it is ignored. This reduces the number of bad results and increases search speed.
        const score = itext.score(st, 0)

        if (score > 0.4 + (0.00075 * itext.length)) {
          item.boost = score * 0.5

          if (score > 0.62) {
            item.boost += 0.33
          }

          matches.push(item)
        }
      }
    }
  }

  const matches = []
  const st = searchText.replace(spacesRegex, ' ').split('?')[0].replace('http://', '').replace('https://', '').replace('www.', '')
  const stl = searchText.length
  const searchWords = st.split(' ')
  const swl = searchWords.length
  let substringSearchEnabled = false
  const itemStartBoost = Math.min(2.5 * stl, 10)
  const exactMatchBoost = 0.4 + (0.075 * stl)
  const limitToBookmarks = options && options.searchBookmarks
  const resultsLimit = options && options.limit || 100

  if (searchText.indexOf(' ') !== -1) {
    substringSearchEnabled = true
  }

  for (let i = 0; i < historyInMemoryCache.length; i++) {
    if (matches.length > (resultsLimit * 2)) {
      break
    }
    processSearchItem(historyInMemoryCache[i])
  }

  matches.sort(function (a, b) { // we have to re-sort to account for the boosts applied to the items
    return calculateHistoryScore(b) - calculateHistoryScore(a)
  })

  callback(matches.slice(0, resultsLimit))
}
<commit_msg>only use item URL for fuzzy search<commit_after>/* global spacesRegex oneWeekAgo performance historyInMemoryCache calculateHistoryScore */

/* depends on placesWorker.js */

function searchPlaces (searchText, callback, options) {
  function processSearchItem (item) {
    if (limitToBookmarks && !item.isBookmarked) {
      return
    }
    let itext
    let itextURL = item.url.split('?')[0].replace('http://', '').replace('https://', '').replace('www.', '').toLowerCase().replace(spacesRegex, ' ')

    if (item.url === item.title) {
      itext = itextURL
    } else {
      itext = itextURL + ' ' + item.title.toLowerCase().replace(spacesRegex, ' ')
    }

    const tindex = itext.indexOf(st)

    // if the url contains the search string, count as a match
    // prioritize matches near the beginning of the url
    if (tindex === 0) {
      item.boost = itemStartBoost // large amount of boost for this
      matches.push(item)
    } else if (tindex !== -1) {
      item.boost = exactMatchBoost
      matches.push(item)
    } else {
      // if all of the search words (split by spaces, etc) exist in the url, count it as a match, even if they are out of order

      if (substringSearchEnabled) {
        let substringMatch = true

        // check if the search text matches but is out of order
        for (let i = 0; i < swl; i++) {
          if (itext.indexOf(searchWords[i]) === -1) {
            substringMatch = false
            break
          }
        }

        if (substringMatch) {
          item.boost = 0.125 * swl + (0.02 * stl)
          matches.push(item)
          return
        }
      }

      if (item.visitCount !== 1 || item.lastVisit > oneWeekAgo) { // if the item has been visited more than once, or has been visited in the last week, we should calculate the fuzzy score. Otherwise, it is ignored. This reduces the number of bad results and increases search speed.
        const score = itextURL.score(st, 0)

        if (score > 0.4 + (0.00075 * itextURL.length)) {
          item.boost = score * 0.5

          if (score > 0.62) {
            item.boost += 0.33
          }

          matches.push(item)
        }
      }
    }
  }

  const matches = []
  const st = searchText.replace(spacesRegex, ' ').split('?')[0].replace('http://', '').replace('https://', '').replace('www.', '')
  const stl = searchText.length
  const searchWords = st.split(' ')
  const swl = searchWords.length
  let substringSearchEnabled = false
  const itemStartBoost = Math.min(2.5 * stl, 10)
  const exactMatchBoost = 0.4 + (0.075 * stl)
  const limitToBookmarks = options && options.searchBookmarks
  const resultsLimit = options && options.limit || 100

  if (searchText.indexOf(' ') !== -1) {
    substringSearchEnabled = true
  }

  for (let i = 0; i < historyInMemoryCache.length; i++) {
    if (matches.length > (resultsLimit * 2)) {
      break
    }
    processSearchItem(historyInMemoryCache[i])
  }

  matches.sort(function (a, b) { // we have to re-sort to account for the boosts applied to the items
    return calculateHistoryScore(b) - calculateHistoryScore(a)
  })

  callback(matches.slice(0, resultsLimit))
}
<|endoftext|>


<commit_before>// Copyright 2018, University of Colorado Boulder

/**
 * GQEquationFactory creates Nodes that display various equations needed by this sim.
 * Equations are reduced so that they don't contain terms that evaluate to zero,
 * and coefficients are displayed as positive decimal numbers.
 *
 * @author Chris Malley (PixelZoom, Inc.)
 */
define( require => {
  'use strict';

  // modules
  const GQColors = require( 'GRAPHING_QUADRATICS/common/GQColors' );
  const GQConstants = require( 'GRAPHING_QUADRATICS/common/GQConstants' );
  const GQSymbols = require( 'GRAPHING_QUADRATICS/common/GQSymbols' );
  const graphingQuadratics = require( 'GRAPHING_QUADRATICS/graphingQuadratics' );
  const MathSymbols = require( 'SCENERY_PHET/MathSymbols' );
  const Node = require( 'SCENERY/nodes/Node' );
  const PhetFont = require( 'SCENERY_PHET/PhetFont' );
  const Rectangle = require( 'SCENERY/nodes/Rectangle' );
  const RichText = require( 'SCENERY/nodes/RichText' );
  const Util = require( 'DOT/Util' );

  const GQEquationFactory = {

    /**
     * Creates an equation in standard form, y = ax^2 + bx + c
     * @param {Quadratic} quadratic
     * @param {Object} [options]
     * @public
     */
    createStandardForm( quadratic, options ) {

      options = _.extend( {
        font: new PhetFont( GQConstants.GRAPH_EQUATION_FONT_SIZE ),
        aDecimals: GQConstants.EXPLORE_DECIMALS_A,
        bDecimals: GQConstants.EXPLORE_DECIMALS_B,
        cDecimals: GQConstants.EXPLORE_DECIMALS_C
      }, options );

      // use toFixedNumber so we don't have trailing zeros
      const a = Util.toFixedNumber( quadratic.a, options.aDecimals );
      const b = Util.toFixedNumber( quadratic.b, options.bDecimals );
      const c = Util.toFixedNumber( quadratic.c, options.cDecimals );

      // y =
      let equationString = GQSymbols.y + ' ' + MathSymbols.EQUAL_TO + ' ';

      if ( a === 0 && b === 0 && c === 0 ) {

        // y = 0
        equationString += '0';
      }
      else {

        // ax^2 term
        if ( a !== 0 ) {

          if ( a === -1 ) {
            equationString += MathSymbols.UNARY_MINUS; // -x^2
          }
          else if ( a !== 1 ) {
            equationString += a; // ax^2
          }

          equationString += GQSymbols.xSquared;

          if ( b !== 0 || c !== 0 ) {
            equationString += ' ';
          }
        }

        // bx term
        if ( b !== 0 ) {

          if ( a === 0 ) {
            if ( b === -1 ) {
              equationString += MathSymbols.UNARY_MINUS; // -x
            }
            else if ( b !== 1 ) {
              equationString += b; // bx
            }
            equationString += GQSymbols.x;
          }
          else {
            equationString += ( b > 0 ) ? MathSymbols.PLUS : MathSymbols.MINUS;
            equationString += ' ';
            if ( Math.abs( b ) !== 1 ) {
              equationString += Math.abs( b );
            }
            equationString += GQSymbols.x;
          }

          if ( c !== 0 ) {
            equationString += ' ';
          }
        }

        // c term
        if ( c !== 0 ) {
          if ( a === 0 && b === 0 ) {
            equationString += c;
          }
          else {
            equationString += ( c > 0 ) ? MathSymbols.PLUS : MathSymbols.MINUS;
            equationString += ' ' + Math.abs( c );
          }
        }
      }

      const equationNode = new RichText( equationString, {
        fill: quadratic.color,
        font: options.font
      } );

      return withBackground( equationNode );
    },

    /**
     * Creates an equation in vertex form, y = a(x - h)^2 + k
     * @param {Quadratic} quadratic
     * @param {Object} [options]
     * @public
     */
    createVertexForm( quadratic, options ) {

      options = _.extend( {
        font: new PhetFont( GQConstants.GRAPH_EQUATION_FONT_SIZE ),
        aDecimals: GQConstants.FOCUS_AND_DIRECTRIX_DECIMALS_A,
        hDecimals: GQConstants.FOCUS_AND_DIRECTRIX_DECIMALS_H,
        kDecimals: GQConstants.FOCUS_AND_DIRECTRIX_DECIMALS_K
      }, options );

      // use toFixedNumber so we don't have trailing zeros
      const a = Util.toFixedNumber( quadratic.a, options.aDecimals );
      const h = Util.toFixedNumber( quadratic.h, options.hDecimals );
      const k = Util.toFixedNumber( quadratic.k, options.kDecimals );

      // y =
      let equationString = GQSymbols.y + ' ' + MathSymbols.EQUAL_TO + ' ';

      if ( a === 0 && k === 0 ) {

        // y = 0
        equationString += '0';
      }
      else if ( a === 0 ) {

        // y = c
        equationString += Util.toFixedNumber( quadratic.c, options.kDecimals );
      }
      else {

        // a(x - h)^2 term
        if ( a === -1 ) {
          equationString += MathSymbols.UNARY_MINUS;
        }
        else if ( a !== 1 ) {
          equationString += a;
        }

        if ( h === 0 ) {
          equationString += GQSymbols.xSquared;
        }
        else {
          equationString += '(' + GQSymbols.x + ' ';
          equationString += ( h > 0 ) ? MathSymbols.MINUS : MathSymbols.PLUS;
          equationString += ' ' + Math.abs( h );
          equationString += ')2';
        }

        if ( k !== 0 ) {
          equationString += ' ';
        }

        // k term
        if ( k !== 0 ) {
          if ( a === 0 ) {
            equationString += k;
          }
          else {
            equationString += ( k > 0 ) ? MathSymbols.PLUS : MathSymbols.MINUS;
            equationString += ' ' + Math.abs( k );
          }
        }
      }

      const equationNode = new RichText( equationString, {
        fill: quadratic.color,
        font: options.font
      } );

      return withBackground( equationNode );
    },

    /**
     * Creates the directrix equation.
     * @param {number} directrix
     * @public
     */
    createDirectrix( directrix ) {

      // y = N
      const equationString = GQSymbols.y + ' ' + MathSymbols.EQUAL_TO + ' ' +
                             Util.toFixedNumber( directrix, GQConstants.DIRECTRIX_DECIMALS );

      const equationNode = new RichText( equationString, {
        font: new PhetFont( GQConstants.GRAPH_EQUATION_FONT_SIZE ),
        fill: GQColors.DIRECTRIX
      } );

      return withBackground( equationNode );
    },

    /**
     * Creates the axis of symmetry equation.
     * @param {number} axisOfSymmetry
     * @public
     */
    createAxisOfSymmetry( axisOfSymmetry ) {

      // x = N
      const equationString = GQSymbols.x + ' ' + MathSymbols.EQUAL_TO + ' ' +
                             Util.toFixedNumber( axisOfSymmetry, GQConstants.AXIS_OF_SYMMETRY_DECIMALS );

      const equationNode = new RichText( equationString, {
        font: new PhetFont( GQConstants.GRAPH_EQUATION_FONT_SIZE ),
        fill: GQColors.AXIS_OF_SYMMETRY,
        rotation: Math.PI / 2
      } );

      return withBackground( equationNode );
    }
  };

  /**
   * Common method to put an equation on a translucent background.
   * @param {Node} node
   * @returns {Node}
   */
  function withBackground( node ) {

    // translucent background, sized to fit
    const backgroundNode = new Rectangle( 0, 0, node.width + 4, node.height + 2, {
      fill: 'white',
      opacity: 0.75,
      center: node.center
    } );

    return new Node( { children: [ backgroundNode, node ] } );
  }

  return graphingQuadratics.register( 'GQEquationFactory', GQEquationFactory );
} )
;<commit_msg>add review comment, see https://github.com/phetsims/graphing-quadratics/issues/43<commit_after>// Copyright 2018, University of Colorado Boulder

/**
 * GQEquationFactory creates Nodes that display various equations needed by this sim.
 * Equations are reduced so that they don't contain terms that evaluate to zero,
 * and coefficients are displayed as positive decimal numbers.
 *
 * @author Chris Malley (PixelZoom, Inc.)
 */
define( require => {
  'use strict';

  // modules
  const GQColors = require( 'GRAPHING_QUADRATICS/common/GQColors' );
  const GQConstants = require( 'GRAPHING_QUADRATICS/common/GQConstants' );
  const GQSymbols = require( 'GRAPHING_QUADRATICS/common/GQSymbols' );
  const graphingQuadratics = require( 'GRAPHING_QUADRATICS/graphingQuadratics' );
  const MathSymbols = require( 'SCENERY_PHET/MathSymbols' );
  const Node = require( 'SCENERY/nodes/Node' );
  const PhetFont = require( 'SCENERY_PHET/PhetFont' );
  const Rectangle = require( 'SCENERY/nodes/Rectangle' );
  const RichText = require( 'SCENERY/nodes/RichText' );
  const Util = require( 'DOT/Util' );

  const GQEquationFactory = {

    /**
     * Creates an equation in standard form, y = ax^2 + bx + c
     * @param {Quadratic} quadratic
     * @param {Object} [options]
     * @public
     */
    createStandardForm( quadratic, options ) {

      options = _.extend( {
        font: new PhetFont( GQConstants.GRAPH_EQUATION_FONT_SIZE ),
        aDecimals: GQConstants.EXPLORE_DECIMALS_A,
        bDecimals: GQConstants.EXPLORE_DECIMALS_B,
        cDecimals: GQConstants.EXPLORE_DECIMALS_C
      }, options );

      // use toFixedNumber so we don't have trailing zeros
      const a = Util.toFixedNumber( quadratic.a, options.aDecimals );
      const b = Util.toFixedNumber( quadratic.b, options.bDecimals );
      const c = Util.toFixedNumber( quadratic.c, options.cDecimals );

      //REVIEW: Looks like this same string is built on line 142, should it be a constant for this file?
      // y =
      let equationString = GQSymbols.y + ' ' + MathSymbols.EQUAL_TO + ' ';

      if ( a === 0 && b === 0 && c === 0 ) {

        // y = 0
        equationString += '0';
      }
      else {

        // ax^2 term
        if ( a !== 0 ) {

          if ( a === -1 ) {
            equationString += MathSymbols.UNARY_MINUS; // -x^2
          }
          else if ( a !== 1 ) {
            equationString += a; // ax^2
          }

          equationString += GQSymbols.xSquared;

          if ( b !== 0 || c !== 0 ) {
            equationString += ' ';
          }
        }

        // bx term
        if ( b !== 0 ) {

          if ( a === 0 ) {
            if ( b === -1 ) {
              equationString += MathSymbols.UNARY_MINUS; // -x
            }
            else if ( b !== 1 ) {
              equationString += b; // bx
            }
            equationString += GQSymbols.x;
          }
          else {
            equationString += ( b > 0 ) ? MathSymbols.PLUS : MathSymbols.MINUS;
            equationString += ' ';
            if ( Math.abs( b ) !== 1 ) {
              equationString += Math.abs( b );
            }
            equationString += GQSymbols.x;
          }

          if ( c !== 0 ) {
            equationString += ' ';
          }
        }

        // c term
        if ( c !== 0 ) {
          if ( a === 0 && b === 0 ) {
            equationString += c;
          }
          else {
            equationString += ( c > 0 ) ? MathSymbols.PLUS : MathSymbols.MINUS;
            equationString += ' ' + Math.abs( c );
          }
        }
      }

      const equationNode = new RichText( equationString, {
        fill: quadratic.color,
        font: options.font
      } );

      return withBackground( equationNode );
    },

    /**
     * Creates an equation in vertex form, y = a(x - h)^2 + k
     * @param {Quadratic} quadratic
     * @param {Object} [options]
     * @public
     */
    createVertexForm( quadratic, options ) {

      options = _.extend( {
        font: new PhetFont( GQConstants.GRAPH_EQUATION_FONT_SIZE ),
        aDecimals: GQConstants.FOCUS_AND_DIRECTRIX_DECIMALS_A,
        hDecimals: GQConstants.FOCUS_AND_DIRECTRIX_DECIMALS_H,
        kDecimals: GQConstants.FOCUS_AND_DIRECTRIX_DECIMALS_K
      }, options );

      // use toFixedNumber so we don't have trailing zeros
      const a = Util.toFixedNumber( quadratic.a, options.aDecimals );
      const h = Util.toFixedNumber( quadratic.h, options.hDecimals );
      const k = Util.toFixedNumber( quadratic.k, options.kDecimals );

      // y =
      let equationString = GQSymbols.y + ' ' + MathSymbols.EQUAL_TO + ' ';

      if ( a === 0 && k === 0 ) {

        // y = 0
        equationString += '0';
      }
      else if ( a === 0 ) {

        // y = c
        equationString += Util.toFixedNumber( quadratic.c, options.kDecimals );
      }
      else {

        // a(x - h)^2 term
        if ( a === -1 ) {
          equationString += MathSymbols.UNARY_MINUS;
        }
        else if ( a !== 1 ) {
          equationString += a;
        }

        if ( h === 0 ) {
          equationString += GQSymbols.xSquared;
        }
        else {
          equationString += '(' + GQSymbols.x + ' ';
          equationString += ( h > 0 ) ? MathSymbols.MINUS : MathSymbols.PLUS;
          equationString += ' ' + Math.abs( h );
          equationString += ')2';
        }

        if ( k !== 0 ) {
          equationString += ' ';
        }

        // k term
        if ( k !== 0 ) {
          if ( a === 0 ) {
            equationString += k;
          }
          else {
            equationString += ( k > 0 ) ? MathSymbols.PLUS : MathSymbols.MINUS;
            equationString += ' ' + Math.abs( k );
          }
        }
      }

      const equationNode = new RichText( equationString, {
        fill: quadratic.color,
        font: options.font
      } );

      return withBackground( equationNode );
    },

    /**
     * Creates the directrix equation.
     * @param {number} directrix
     * @public
     */
    createDirectrix( directrix ) {

      // y = N
      const equationString = GQSymbols.y + ' ' + MathSymbols.EQUAL_TO + ' ' +
                             Util.toFixedNumber( directrix, GQConstants.DIRECTRIX_DECIMALS );

      const equationNode = new RichText( equationString, {
        font: new PhetFont( GQConstants.GRAPH_EQUATION_FONT_SIZE ),
        fill: GQColors.DIRECTRIX
      } );

      return withBackground( equationNode );
    },

    /**
     * Creates the axis of symmetry equation.
     * @param {number} axisOfSymmetry
     * @public
     */
    createAxisOfSymmetry( axisOfSymmetry ) {

      // x = N
      const equationString = GQSymbols.x + ' ' + MathSymbols.EQUAL_TO + ' ' +
                             Util.toFixedNumber( axisOfSymmetry, GQConstants.AXIS_OF_SYMMETRY_DECIMALS );

      const equationNode = new RichText( equationString, {
        font: new PhetFont( GQConstants.GRAPH_EQUATION_FONT_SIZE ),
        fill: GQColors.AXIS_OF_SYMMETRY,
        rotation: Math.PI / 2
      } );

      return withBackground( equationNode );
    }
  };

  /**
   * Common method to put an equation on a translucent background.
   * @param {Node} node
   * @returns {Node}
   */
  function withBackground( node ) {

    // translucent background, sized to fit
    const backgroundNode = new Rectangle( 0, 0, node.width + 4, node.height + 2, {
      fill: 'white',
      opacity: 0.75,
      center: node.center
    } );

    return new Node( { children: [ backgroundNode, node ] } );
  }

  return graphingQuadratics.register( 'GQEquationFactory', GQEquationFactory );
} )
;<|endoftext|>


<commit_before>import React   from 'react';
import numeral from 'numeral';
import Skycons from 'react-skycons';

import GarmentSuggestion from './garment_suggestion';

import translateSkyconIconName from './../utils/translate_skycon_icon_name';

const {
  Component,
} = React;

class CurrentConditions extends Component {
  skyconIconName() {
    if (this.props.icon === undefined) {
      return null;
    }
    let value = this.props.icon.toUpperCase();
    return value.replace(/-/g, '_');
  }

  tempTrend() {
    if (this.props.nextTemp === this.props.temperature) {
      return 'stagnating';
    }
    else if (this.props.nextTemp > this.props.temperature) {
      return 'raising';
    }
    else {
      return 'falling'
    }
  }

  render () {
    // Don't print until props are passed down
    if (this.props.temperature !== undefined) {
      return (

            <Skycons className = 'skycon'
                     color     = 'black'
                     icon      = {translateSkyconIconName(this.props.icon)} />

                  {Math.round(this.props.temperature)}

                  °

                and
                {' '}
                {this.tempTrend()}

            {this.props.summary}

            Wind:
            {' '}

              {numeral(this.props.windSpeed).format('0.0')}
              {' '}
              mph (S)

            What to wear
            <GarmentSuggestion
                temperature = {this.props.temperature}
                activity    = {this.props.summary} />

      );
    }
    else {
      return null;
    }
  }
};

export default CurrentConditions;
<commit_msg>Use Math.round when doing temp comparisons for the trending data point on the CurrentConditions component<commit_after>import React   from 'react';
import numeral from 'numeral';
import Skycons from 'react-skycons';

import GarmentSuggestion from './garment_suggestion';

import translateSkyconIconName from './../utils/translate_skycon_icon_name';

const {
  Component,
} = React;

class CurrentConditions extends Component {
  skyconIconName() {
    if (this.props.icon === undefined) {
      return null;
    }
    let value = this.props.icon.toUpperCase();
    return value.replace(/-/g, '_');
  }

  tempTrend() {
    if (Math.round(this.props.nextTemp) === Math.round(this.props.temperature)) {
      return 'stagnating';
    }
    else if (Math.round(this.props.nextTemp) > Math.round(this.props.temperature)) {
      return 'raising';
    }
    else {
      return 'falling'
    }
  }

  render () {
    // Don't print until props are passed down
    if (this.props.temperature !== undefined) {
      return (

            <Skycons className = 'skycon'
                     color     = 'black'
                     icon      = {translateSkyconIconName(this.props.icon)} />

                  {Math.round(this.props.temperature)}

                  °

                and
                {' '}
                {this.tempTrend()}

            {this.props.summary}

            Wind:
            {' '}

              {numeral(this.props.windSpeed).format('0.0')}
              {' '}
              mph (S)

            What to wear
            <GarmentSuggestion
                temperature = {this.props.temperature}
                activity    = {this.props.summary} />

      );
    }
    else {
      return null;
    }
  }
};

export default CurrentConditions;
<|endoftext|>


<commit_before>/*!
 * SigTrello
 *
 * Copyright (C) 2014 Signal Studios
 * Released under the MIT license
 * See LICENSE.txt
 */

var MutationObserver = window.MutationObserver || window.WebKitMutationObserver || window.MozMutationObserver || null;

function error( message ) {
	//alert( message );
}

var spamLimitCounter = 1000; // Safety
function spamLimit( ) {
	//return --limit < 0;
	return false;
}

function getBestByNameIndex( name, index, items ) {
	if( items.length > index && items[ index ].name == name )
		return items[ index ];

	var matchingName = $.grep( items, function(i) { return i.name == name });
	if( matchingName.length == 1 )
		return matchingName[ 0 ];

	return null; // Ambiguous due to move of duplicately named list item, or no such item.
}

function authorize( ) {
	Trello.authorize({
		type: "popup",
		name: "SigTrello",
		persist: true,
		interactive: true,
		scope: { read: true, write: true, account: false },
		expiration: "never"
	});
}

function replaceWithLink( ) {
	var toLink = this;
	if( !Trello.authorized() ) {
		authorize( );
		if( !Trello.authorized() )
			return;
	}

	var $this				= $(this);

	var $checklistItem		= $this.parent('.checklist-item');
	var $checklistItemText 	= $checklistItem.find('.checklist-item-details-text')
	var hasLinks			= $checklistItemText.find('a').length > 0;
	if( hasLinks )
		return;

	var baseURI				= window.document.baseURI;
	var cardShortId			= baseURI.replace( /^http[s]?:\/\/trello.com\/c\/([^\/]+)\/.*/, "$1" ); // Parse "mbdChsJG" out of "https://trello.com/c/mbdChsJG/185-test"
	var $checklistItemsList	= $checklistItem.parent('.checklist-items-list');
	var $checklist			= $checklistItemsList.parent('.checklist');
	var $checklistList		= $checklist.parent('.checklist-list');

	var checklistItemTitle	= $checklistItemText.text( );
	var checklistItemIndex	= $checklistItemsList.find('.checklist-item').index($checklistItem);
	var checklistTitle		= $checklist.find('.checklist-title h3').text( );
	var checklistIndex		= $checklistList.find('.checklist').index($checklist);

	// TODO: Learn how to tame JavaScript callback hell.

	// 1.  Identify the trello list
	Trello
        .get( "cards/"+cardShortId+"/list", { fields: "" } )
        .done( function( list ) {

			// 2. Identify the checklist item being replaced.
			Trello
				.get( "cards/"+cardShortId+"/checklists" )
				.done( function( cardInfo ) {
					var cardChecklist = getBestByNameIndex( checklistTitle, checklistIndex, cardInfo );
					if( cardChecklist == null ) { error( "Checklist ambiguous or missing!" ); return; }

					var cardChecklistItem = getBestByNameIndex( checklistItemTitle, checklistItemIndex, cardChecklist.checkItems );
					if( cardChecklistItem == null ) { error( "Checklist item ambiguous or missing!" ); return; }

					// 3.  Post new card to the list
					Trello
						.post( "lists/"+list.id+"/cards", { name: checklistItemTitle, desc: "Parent: "+baseURI+" "+checklistTitle, due: null } )
						.done( function( newCard ) {

							// 4.  Replace checklist item with new card
							Trello
								.put( "cards/"+cardShortId+"/checklist/"+cardChecklist.id+"/checkItem/"+cardChecklistItem.id+"/name", { value: newCard.url } );
						});
				});
		});

	return false;
}

var newChecklistsObserver = new MutationObserver(function(mutations) {
	$checklistItemsList = $(".checklist-items-list .checklist-item");
	for( var i = 0; i < $checklistItemsList.length; ++i ) {
		showConvertToCardButton( $checklistItemsList.get(i) );
	}

    $checklistEditControls = $(".checklist-item-details .edit-controls");
    if( $checklistEditControls.length > 0 ) {
        showConvertToCardLink( $checklistEditControls.get(0) );
    }
});

newChecklistsObserver.observe( document.body, { childList: true, characterData: false, attributes: false, subtree: true } );

function showConvertToCardButton(location) {
	if($(location).find('.ctcButtonImg').length) return; // Don't double add
	if( spamLimit( ) ) return;

	// Space the checkbox farther from the linkify icon
	$(location).find( '.checklist-item-checkbox' ).css( 'left', '16px' );

	// Add clickable image to linkify checkbox
	$("")
		.appendTo( location )
		.click( replaceWithLink )
		;
}

function showConvertToCardLink(location) {
	if($(location).find('.js-convert-item-to-link').length) return; // Don't double add
	if( spamLimit( ) ) return;

	// Add link to checkbox additional options
	$("Convert to Link")
		.insertAfter( $(location).find('.js-delete-item').get(0) )
		.click( replaceWithLink )
		;
}
<commit_msg>Allow the link to work if the card isn't simply a single URL already.  Fixes #6.<commit_after>/*!
 * SigTrello
 *
 * Copyright (C) 2014 Signal Studios
 * Released under the MIT license
 * See LICENSE.txt
 */

var MutationObserver = window.MutationObserver || window.WebKitMutationObserver || window.MozMutationObserver || null;

function error( message ) {
	//alert( message );
}

var spamLimitCounter = 1000; // Safety
function spamLimit( ) {
	//return --limit < 0;
	return false;
}

function getBestByNameIndex( name, index, items ) {
	if( items.length > index && items[ index ].name == name )
		return items[ index ];

	var matchingName = $.grep( items, function(i) { return i.name == name });
	if( matchingName.length == 1 )
		return matchingName[ 0 ];

	return null; // Ambiguous due to move of duplicately named list item, or no such item.
}

function authorize( ) {
	Trello.authorize({
		type: "popup",
		name: "SigTrello",
		persist: true,
		interactive: true,
		scope: { read: true, write: true, account: false },
		expiration: "never"
	});
}

function getFlatName( $node ) {
	var $elements = $node.contents( );
	var s = "";
	for( var i = 0; i < $elements.length; ++i ) {
		var element = $elements.get(i);
		if( element.nodeName == 'A' ) {
			s += $(element).attr("href");
		} else {
			s += $(element).text( );
		}
	}
	return s;
}

function replaceWithLink( ) {
	var toLink = this;
	if( !Trello.authorized() ) {
		authorize( );
		if( !Trello.authorized() )
			return;
	}

	var $this				= $(this);

	var $checklistItem		= $this.parent('.checklist-item');
	var $checklistItemText 	= $checklistItem.find('.checklist-item-details-text')
	var $links				= $checklistItemText.find('a');
	if( $links.length == 1 && $links.text == $this.text )
		return;

	var baseURI				= window.document.baseURI;
	var cardShortId			= baseURI.replace( /^http[s]?:\/\/trello.com\/c\/([^\/]+)\/.*/, "$1" ); // Parse "mbdChsJG" out of "https://trello.com/c/mbdChsJG/185-test"
	var $checklistItemsList	= $checklistItem.parent('.checklist-items-list');
	var $checklist			= $checklistItemsList.parent('.checklist');
	var $checklistList		= $checklist.parent('.checklist-list');

	var checklistItemTitle	= $checklistItemText.text( );
	var checklistItemName	= getFlatName( $checklistItemText );
	var checklistItemIndex	= $checklistItemsList.find('.checklist-item').index($checklistItem);
	var checklistTitle		= $checklist.find('.checklist-title h3').text( );
	var checklistIndex		= $checklistList.find('.checklist').index($checklist);

	// TODO: Learn how to tame JavaScript callback hell.

	// 1.  Identify the trello list
	Trello
        .get( "cards/"+cardShortId+"/list", { fields: "" } )
        .done( function( list ) {

			// 2. Identify the checklist item being replaced.
			Trello
				.get( "cards/"+cardShortId+"/checklists" )
				.done( function( cardInfo ) {
					var cardChecklist = getBestByNameIndex( checklistTitle, checklistIndex, cardInfo );
					if( cardChecklist == null ) { error( "Checklist ambiguous or missing!" ); return; }

					var cardChecklistItem = getBestByNameIndex( checklistItemName, checklistItemIndex, cardChecklist.checkItems );
					if( cardChecklistItem == null ) { error( "Checklist item ambiguous or missing!" ); return; }

					// 3.  Post new card to the list
					Trello
						.post( "lists/"+list.id+"/cards", { name: checklistItemTitle, desc: "Parent: "+baseURI+" "+checklistTitle, due: null } )
						.done( function( newCard ) {

							// 4.  Replace checklist item with new card
							Trello
								.put( "cards/"+cardShortId+"/checklist/"+cardChecklist.id+"/checkItem/"+cardChecklistItem.id+"/name", { value: newCard.url } );
						});
				});
		});

	return false;
}

var newChecklistsObserver = new MutationObserver(function(mutations) {
	$checklistItemsList = $(".checklist-items-list .checklist-item");
	for( var i = 0; i < $checklistItemsList.length; ++i ) {
		showConvertToCardButton( $checklistItemsList.get(i) );
	}

    $checklistEditControls = $(".checklist-item-details .edit-controls");
    if( $checklistEditControls.length > 0 ) {
        showConvertToCardLink( $checklistEditControls.get(0) );
    }
});

newChecklistsObserver.observe( document.body, { childList: true, characterData: false, attributes: false, subtree: true } );

function showConvertToCardButton(location) {
	if($(location).find('.ctcButtonImg').length) return; // Don't double add
	if( spamLimit( ) ) return;

	// Space the checkbox farther from the linkify icon
	$(location).find( '.checklist-item-checkbox' ).css( 'left', '16px' );

	// Add clickable image to linkify checkbox
	$("")
		.appendTo( location )
		.click( replaceWithLink )
		;
}

function showConvertToCardLink(location) {
	if($(location).find('.js-convert-item-to-link').length) return; // Don't double add
	if( spamLimit( ) ) return;

	// Add link to checkbox additional options
	$("Convert to Link")
		.insertAfter( $(location).find('.js-delete-item').get(0) )
		.click( replaceWithLink )
		;
}
<|endoftext|>


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

  var BaseController = require("BaseController"),
      _ = require("lodash");

  var multiSelectors = {
    playPause: ["#play-pause", "#play", "[title='Pause'],[title='Play']"],
    playNext: ["#next", "#next", "[title='Next']"],
    playPrev: ["#previous", "#previous", "[title='Previous']"],
    playState: ["#play-pause.playing", "#play.playing", "[title='Pause']"],
    iframe: ["#app-player", "#main", null],
    like: [".thumb.up", ".thumb.up", null],
    dislike: [".thumb.down", ".thumb.down", null],
    song: ["#track-name", ".caption .track", ".now-playing-bar div div [href*='/album/']"],
    artist: ["#track-artist", ".caption .artist", ".now-playing-bar div div [href*='/artist/']"]
  };

  var controller = new BaseController({
    siteName: "Spotify"
  });

  controller.checkPlayer = function() {
    var that = this;
    var selectorIndex;

    if (window.location.hostname === "open.spotify.com") {
      selectorIndex = 2;
    } else {
      if (document.querySelector(multiSelectors.iframe[0])) { selectorIndex = 0; }
      if (document.querySelector(multiSelectors.iframe[1])) { selectorIndex = 1; }
    }

    _.each(multiSelectors, function(value, key) {
      that.selectors[key] = value[selectorIndex];
    });
  };
})();
<commit_msg>Fix old spotify player & use class names instead of title for new player.<commit_after>;(function() {
  "use strict";

  var BaseController = require("BaseController"),
      _ = require("lodash");

  var multiSelectors = {
    play: [null, ".now-playing-bar .spoticon-play-32"],
    pause: [null, ".now-playing-bar .spoticon-pause-32"],
    playPause: ["#play-pause", null],
    playNext: ["#next", ".now-playing-bar .spoticon-skip-forward-24"],
    playPrev: ["#previous", ".now-playing-bar .spoticon-skip-back-24"],
    playState: ["#play-pause.playing", ".now-playing-bar .spoticon-pause-32"],
    iframe: ["#app-player", null],
    like: [".thumb.up", null],
    dislike: [".thumb.down", null],
    song: ["#track-name", ".now-playing-bar div div [href*='/album/']"],
    artist: ["#track-artist", ".now-playing-bar div div [href*='/artist/']"]
  };

  var controller = new BaseController({
    siteName: "Spotify"
  });

  controller.checkPlayer = function() {
    var that = this;
    var selectorIndex = window.location.hostname === "open.spotify.com" ? 1 : 0;

    _.each(multiSelectors, function(value, key) {
      that.selectors[key] = value[selectorIndex];
    });
  };
})();
<|endoftext|>


<commit_before>const fs = require('fs');
const qs = require('qs');
const https = require('https');
const url = require('url');
const request = require('request');
const passport = require('passport');
const config = require('./config');
const bodyParser = require('body-parser');
const sgMail = require('@sendgrid/mail');
const requestUtils = require('./client/src/shared/requestUtils');
const notifications = require('./notifications');
const crypto = require('crypto');
const compare = require('secure-compare');

module.exports = function(app) {
  const GITHUB_API_ROOT = 'https://api.github.com';

  const getlocalAppRootUrl = request => {
    return process.env.NODE_ENV == 'production'
      ? url.format({
          protocol: 'https', // request.protocol returns http for now since the node server itself is only using http. However the api is used over https thanks to azure / IIS
          host: request.hostname,
          // port: request.port,
          pathname: ''
        })
      : 'https://localhost:3000'; // hard coded value in development because the request came through the webpack dev server on a different port and via https.
  };

  function loadAppData() {
    return JSON.parse(fs.readFileSync(`${__dirname}/${config.app.groupConfigPath}`, 'utf-8'));
  }

  const appData = loadAppData();

  // Utility functions
  const sendMail = (to, title, body) => {
    sgMail.setApiKey(process.env.SENDGRID_API_KEY);
    const msg = {
      to: to,
      from: config.app.emailSender,
      subject: title,
      html: body,
    };
    return sgMail.send(msg);
  };

  const getProxyRequestOptions = url => ({
    url: GITHUB_API_ROOT + url.replace('/api', ''),
    headers: {
      Authorization: `token ${config.github.botToken}`
      //,"user-agent": "Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/65.0.3325.181 Safari/537.36"
      ,"accept-encoding": "identity",
    }
  });

  const rewriteResponseHeaders = (request, response) => {
    if (response.headers.link) {
      response.headers.link = response.headers.link.replace(GITHUB_API_ROOT, getlocalAppRootUrl(request) + '/api');
      console.log(`Request App: Rewritten response.headers.link: ${JSON.stringify(response.headers.link)}`);
    } else {
      console.log(`Request App: response.headers.link falsy, nothing to rewrite`);
    }
  };

  const genericErrorHandler = (error, response, body) => {
    if (error) {
      if (error.code === 'ECONNREFUSED') {
        console.error('Refused connection');
      } else if (error.code === 'ETIMEDOUT') {
        console.error('Connection timed out');
      } else {
        throw error;
      }
    }
  };

  const validateRepository = (organisation, repo, projects) => {
    if (projects.filter(p => p.organisation == organisation && p.repository == repo).length == 0) {
      console.error(`Request App: Repository not found : ${organisation}/${repo}`);
      return false;
    } else {
      console.log(`Request App: Valid repository: ${organisation}/${repo}`);
      return true;
    }
  };

  // App end points
  app.get('/api/projects', passport.authenticate('oauth-bearer', { session: false }), function(req, res) {
    res.json(appData);
  });

  app.get('/api/authenticate/github/:code', passport.authenticate('oauth-bearer', { session: false }), function(req, res) {
    const r = request.post(
      {
        url: 'https://github.com/login/oauth/access_token',
        json: {
          client_id: config.github.clientID,
          client_secret: config.github.clientSecret,
          code: req.params.code
        }
      },
      genericErrorHandler
    );
    r.pipe(res);
  });

  // proxy to github api end points
  app.get('/api/repos/:organisation/:repo/issues', passport.authenticate('oauth-bearer', { session: false }), function(req, res) {
    console.log('Request App received request to: ', req.url);

    if (!validateRepository(req.params.organisation, req.params.repo, appData.projects))
      res.status(403).send({ error: 'Invalid repository name' });

    console.log(`Request App: Listing issues on repository ${req.params.organisation}/${req.params.repo}`);

    // this actually does a request to github, which is not required
    const gitHubRequest = request(getProxyRequestOptions(req.url)); // add the authorization that github wants

    console.log('Request App: Proxying request to: ', getProxyRequestOptions(req.url).url);

    req
    .pipe(gitHubRequest, genericErrorHandler) // send to git hub
    .on('response', response => rewriteResponseHeaders(req, response)) // response from github, monkey with the headers for some reason. Looking at the logs it doesn't seem to do anything
    .pipe(res); // pipe response from github back to response from this route / function
  });

  app.get('/api/repositories/:repoId/issues', passport.authenticate('oauth-bearer', { session: false }), function(req, res) {
    console.log(`going through pages on repository ${req.params.repoId}`);
    const r = request(getProxyRequestOptions(req.url));
    console.log('Proxied request to: ', getProxyRequestOptions(req.url).url);
    req.pipe(r, genericErrorHandler).on('response', response => rewriteResponseHeaders(req, response)).pipe(res);
  });

  app.get('/api/repos/:organisation/:repo/issues/:issueId', passport.authenticate('oauth-bearer', { session: false }), function(req, res) {
    if (!validateRepository(req.params.organisation, req.params.repo, appData.projects))
      res.status(403).send({ error: 'Invalid repository name' });

    const r = request(getProxyRequestOptions(req.url));
    console.log('Proxied request to: ', getProxyRequestOptions(req.url).url);
    req.pipe(r, genericErrorHandler).pipe(res);
  });

  app.post('/api/repos/:organisation/:repo/issues', passport.authenticate('oauth-bearer', { session: false }), function(req, res) {
    if (!validateRepository(req.params.organisation, req.params.repo, appData.projects))
      res.status(403).send({ error: 'Invalid repository name' });

    console.log(`creating issue on repository ${req.params.organisation}/${req.params.repo}`);
    const r = request.post(getProxyRequestOptions(req.url));
    req.pipe(r, genericErrorHandler).pipe(res);
  });

  app.get('/api/repos/:organisation/:repo/issues/:issueId/comments', passport.authenticate('oauth-bearer', { session: false }), function(req, res) {
    if (!validateRepository(req.params.organisation, req.params.repo, appData.projects))
      res.status(403).send({ error: 'Invalid repository name' });

    const r = request(getProxyRequestOptions(req.url));
    console.log('Proxied request to: ', getProxyRequestOptions(req.url).url);
    req.pipe(r, genericErrorHandler).pipe(res);
  });

  app.post('/api/repos/:organisation/:repo/issues/:issueId/comments', passport.authenticate('oauth-bearer', { session: false }), function(req, res) {
    if (!validateRepository(req.params.organisation, req.params.repo, appData.projects))
      res.status(403).send({ error: 'Invalid repository name' });

    console.log(`adding comment on issue in repository ${req.params.organisation}/${req.params.repo}`);
    const r = request.post(getProxyRequestOptions(req.url));
    req.pipe(r, genericErrorHandler).pipe(res);
  });

  // Calculate the X-Hub-Signature header value.
  function getSignature(buf) {
    var hmac = crypto.createHmac("sha1", config.github.webHookSecret);
    hmac.update(buf, "utf-8");
    return "sha1=" + hmac.digest("hex");
  }

  // Verify function compatible with body-parser to retrieve the request payload.
  // Read more: https://github.com/expressjs/body-parser#verify
  function verifyRequest(req, res, buf, encoding) {
    var expected = req.headers['x-hub-signature'];
    var calculated = getSignature(buf);
    console.log("X-Hub-Signature:", expected, "Content:", "-" + buf.toString('utf8') + "-");
    if (!compare(expected,calculated)) {
      throw new Error("Invalid signature.");
    } else {
      console.log("Valid signature!");
    }
  }

  function userLoginFromGitHubRequestBody(body){
    return (body && body.issue.user) ? body.issue.user.login : "";
  }

  function isGitHubWebHookRequestValid(req)  {
    return ['issues', 'issue_comment'].includes(req.headers['x-github-event']) && userLoginFromGitHubRequestBody(req.body) === config.github.botLogin;
  }

  function handleInvalidGitHubWebHookRequest(req, res)  {
    console.log('RequestApp: The following github webhook was not valid:');
    console.log(req.headers['x-github-event']);
    console.log(config.github.botLogin);
    console.log(userLoginFromGitHubRequestBody(req.body));

    res.status(422).send({
      message: 'The github webhook request is not valid'
    });
  }

  function sendEmailsForGitHubWebHook(req, res)  {
    try {
      trySendEmailsForGitHubWebHook(req, res);
    } catch (error) {
      handleErrorForEmailsForGitHubWebHook(req, res, error);
    }
  }

  function trySendEmailsForGitHubWebHook(req, res)  {
    const { email } = requestUtils.getCreator(req.body.issue);
    const project = notifications.findProject(req.body, appData.projects);
    const requestUrl = notifications.getRequestUrl(req.body.issue.number, project, getlocalAppRootUrl(req));
    const eventType = req.headers['x-github-event'];
    const { subject, content } = notifications.getNotificationText(eventType, req.body, project.name, requestUrl);

    console.log(`Request app: Email notification starting`);

    if (email && subject && content) {
      sendMail(email, subject, content)
        .then(response => {
          console.log(response.statusCode, `Email notification sent successfully to ${email}`);
        })
        .catch(error => {
          //error is an instance of SendGridError
          //The full response is attached to error.response
          console.log(error.response);
        });
    }

    console.log(`Request app: Email notification returning response`);
    res.json({ message: 'web hook processed' });
  }

  function handleErrorForEmailsForGitHubWebHook(req, res, error)  {
    console.log('RequestApp: An error occcured while attempting to process the following github webhook:');
    console.log(error);

    res.status(500).json({
      message: 'An error occcured while attempting to process a github webhook',
    });
  }

  // handle github web hooks
  app.post('/api/github-webhooks', [bodyParser.json({verify:verifyRequest})], function(req, res) {

    // this message is unsecured because github doesn't have a token to call us
    // instead we check that this message was generated by our github instance
    // by checking that the signature of the body was created with the correct secret
    console.log(`RequestApp: received web hook action: ${req.headers['x-github-event']}`);
    console.log(JSON.stringify(req.headers));
    console.log(JSON.stringify(req.body));

    //const valid = isGitHubWebHookRequestValid(req);
    console.log(isGitHubWebHookRequestValid(req));

    if (isGitHubWebHookRequestValid(req)) {
      sendEmailsForGitHubWebHook(req, res)
    } else {
      handleInvalidGitHubWebHookRequest(req, res);
    }
  });
};
<commit_msg>Remove status code from log, which is undefined, I think because it is from an email sending thing<commit_after>const fs = require('fs');
const qs = require('qs');
const https = require('https');
const url = require('url');
const request = require('request');
const passport = require('passport');
const config = require('./config');
const bodyParser = require('body-parser');
const sgMail = require('@sendgrid/mail');
const requestUtils = require('./client/src/shared/requestUtils');
const notifications = require('./notifications');
const crypto = require('crypto');
const compare = require('secure-compare');

module.exports = function(app) {
  const GITHUB_API_ROOT = 'https://api.github.com';

  const getlocalAppRootUrl = request => {
    return process.env.NODE_ENV == 'production'
      ? url.format({
          protocol: 'https', // request.protocol returns http for now since the node server itself is only using http. However the api is used over https thanks to azure / IIS
          host: request.hostname,
          // port: request.port,
          pathname: ''
        })
      : 'https://localhost:3000'; // hard coded value in development because the request came through the webpack dev server on a different port and via https.
  };

  function loadAppData() {
    return JSON.parse(fs.readFileSync(`${__dirname}/${config.app.groupConfigPath}`, 'utf-8'));
  }

  const appData = loadAppData();

  // Utility functions
  const sendMail = (to, title, body) => {
    sgMail.setApiKey(process.env.SENDGRID_API_KEY);
    const msg = {
      to: to,
      from: config.app.emailSender,
      subject: title,
      html: body,
    };
    return sgMail.send(msg);
  };

  const getProxyRequestOptions = url => ({
    url: GITHUB_API_ROOT + url.replace('/api', ''),
    headers: {
      Authorization: `token ${config.github.botToken}`
      //,"user-agent": "Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/65.0.3325.181 Safari/537.36"
      ,"accept-encoding": "identity",
    }
  });

  const rewriteResponseHeaders = (request, response) => {
    if (response.headers.link) {
      response.headers.link = response.headers.link.replace(GITHUB_API_ROOT, getlocalAppRootUrl(request) + '/api');
      console.log(`Request App: Rewritten response.headers.link: ${JSON.stringify(response.headers.link)}`);
    } else {
      console.log(`Request App: response.headers.link falsy, nothing to rewrite`);
    }
  };

  const genericErrorHandler = (error, response, body) => {
    if (error) {
      if (error.code === 'ECONNREFUSED') {
        console.error('Refused connection');
      } else if (error.code === 'ETIMEDOUT') {
        console.error('Connection timed out');
      } else {
        throw error;
      }
    }
  };

  const validateRepository = (organisation, repo, projects) => {
    if (projects.filter(p => p.organisation == organisation && p.repository == repo).length == 0) {
      console.error(`Request App: Repository not found : ${organisation}/${repo}`);
      return false;
    } else {
      console.log(`Request App: Valid repository: ${organisation}/${repo}`);
      return true;
    }
  };

  // App end points
  app.get('/api/projects', passport.authenticate('oauth-bearer', { session: false }), function(req, res) {
    res.json(appData);
  });

  app.get('/api/authenticate/github/:code', passport.authenticate('oauth-bearer', { session: false }), function(req, res) {
    const r = request.post(
      {
        url: 'https://github.com/login/oauth/access_token',
        json: {
          client_id: config.github.clientID,
          client_secret: config.github.clientSecret,
          code: req.params.code
        }
      },
      genericErrorHandler
    );
    r.pipe(res);
  });

  // proxy to github api end points
  app.get('/api/repos/:organisation/:repo/issues', passport.authenticate('oauth-bearer', { session: false }), function(req, res) {
    console.log('Request App received request to: ', req.url);

    if (!validateRepository(req.params.organisation, req.params.repo, appData.projects))
      res.status(403).send({ error: 'Invalid repository name' });

    console.log(`Request App: Listing issues on repository ${req.params.organisation}/${req.params.repo}`);

    // this actually does a request to github, which is not required
    const gitHubRequest = request(getProxyRequestOptions(req.url)); // add the authorization that github wants

    console.log('Request App: Proxying request to: ', getProxyRequestOptions(req.url).url);

    req
    .pipe(gitHubRequest, genericErrorHandler) // send to git hub
    .on('response', response => rewriteResponseHeaders(req, response)) // response from github, monkey with the headers for some reason. Looking at the logs it doesn't seem to do anything
    .pipe(res); // pipe response from github back to response from this route / function
  });

  app.get('/api/repositories/:repoId/issues', passport.authenticate('oauth-bearer', { session: false }), function(req, res) {
    console.log(`going through pages on repository ${req.params.repoId}`);
    const r = request(getProxyRequestOptions(req.url));
    console.log('Proxied request to: ', getProxyRequestOptions(req.url).url);
    req.pipe(r, genericErrorHandler).on('response', response => rewriteResponseHeaders(req, response)).pipe(res);
  });

  app.get('/api/repos/:organisation/:repo/issues/:issueId', passport.authenticate('oauth-bearer', { session: false }), function(req, res) {
    if (!validateRepository(req.params.organisation, req.params.repo, appData.projects))
      res.status(403).send({ error: 'Invalid repository name' });

    const r = request(getProxyRequestOptions(req.url));
    console.log('Proxied request to: ', getProxyRequestOptions(req.url).url);
    req.pipe(r, genericErrorHandler).pipe(res);
  });

  app.post('/api/repos/:organisation/:repo/issues', passport.authenticate('oauth-bearer', { session: false }), function(req, res) {
    if (!validateRepository(req.params.organisation, req.params.repo, appData.projects))
      res.status(403).send({ error: 'Invalid repository name' });

    console.log(`creating issue on repository ${req.params.organisation}/${req.params.repo}`);
    const r = request.post(getProxyRequestOptions(req.url));
    req.pipe(r, genericErrorHandler).pipe(res);
  });

  app.get('/api/repos/:organisation/:repo/issues/:issueId/comments', passport.authenticate('oauth-bearer', { session: false }), function(req, res) {
    if (!validateRepository(req.params.organisation, req.params.repo, appData.projects))
      res.status(403).send({ error: 'Invalid repository name' });

    const r = request(getProxyRequestOptions(req.url));
    console.log('Proxied request to: ', getProxyRequestOptions(req.url).url);
    req.pipe(r, genericErrorHandler).pipe(res);
  });

  app.post('/api/repos/:organisation/:repo/issues/:issueId/comments', passport.authenticate('oauth-bearer', { session: false }), function(req, res) {
    if (!validateRepository(req.params.organisation, req.params.repo, appData.projects))
      res.status(403).send({ error: 'Invalid repository name' });

    console.log(`adding comment on issue in repository ${req.params.organisation}/${req.params.repo}`);
    const r = request.post(getProxyRequestOptions(req.url));
    req.pipe(r, genericErrorHandler).pipe(res);
  });

  // Calculate the X-Hub-Signature header value.
  function getSignature(buf) {
    var hmac = crypto.createHmac("sha1", config.github.webHookSecret);
    hmac.update(buf, "utf-8");
    return "sha1=" + hmac.digest("hex");
  }

  // Verify function compatible with body-parser to retrieve the request payload.
  // Read more: https://github.com/expressjs/body-parser#verify
  function verifyRequest(req, res, buf, encoding) {
    var expected = req.headers['x-hub-signature'];
    var calculated = getSignature(buf);
    console.log("X-Hub-Signature:", expected, "Content:", "-" + buf.toString('utf8') + "-");
    if (!compare(expected,calculated)) {
      throw new Error("Invalid signature.");
    } else {
      console.log("Valid signature!");
    }
  }

  function userLoginFromGitHubRequestBody(body){
    return (body && body.issue.user) ? body.issue.user.login : "";
  }

  function isGitHubWebHookRequestValid(req)  {
    return ['issues', 'issue_comment'].includes(req.headers['x-github-event']) && userLoginFromGitHubRequestBody(req.body) === config.github.botLogin;
  }

  function handleInvalidGitHubWebHookRequest(req, res)  {
    console.log('RequestApp: The following github webhook was not valid:');
    console.log(req.headers['x-github-event']);
    console.log(config.github.botLogin);
    console.log(userLoginFromGitHubRequestBody(req.body));

    res.status(422).send({
      message: 'The github webhook request is not valid'
    });
  }

  function sendEmailsForGitHubWebHook(req, res)  {
    try {
      trySendEmailsForGitHubWebHook(req, res);
    } catch (error) {
      handleErrorForEmailsForGitHubWebHook(req, res, error);
    }
  }

  function trySendEmailsForGitHubWebHook(req, res)  {
    const { email } = requestUtils.getCreator(req.body.issue);
    const project = notifications.findProject(req.body, appData.projects);
    const requestUrl = notifications.getRequestUrl(req.body.issue.number, project, getlocalAppRootUrl(req));
    const eventType = req.headers['x-github-event'];
    const { subject, content } = notifications.getNotificationText(eventType, req.body, project.name, requestUrl);

    console.log(`Request app: Email notification starting`);

    if (email && subject && content) {
      sendMail(email, subject, content)
        .then(response => {
          console.log(`Request app: Email notification sent successfully to ${email}`);
        })
        .catch(error => {
          //error is an instance of SendGridError
          //The full response is attached to error.response
          console.log(error.response);
        });
    }

    console.log(`Request app: Email notification returning response`);
    res.json({ message: 'web hook processed' });
  }

  function handleErrorForEmailsForGitHubWebHook(req, res, error)  {
    console.log('RequestApp: An error occcured while attempting to process the following github webhook:');
    console.log(error);

    res.status(500).json({
      message: 'An error occcured while attempting to process a github webhook',
    });
  }

  // handle github web hooks
  app.post('/api/github-webhooks', [bodyParser.json({verify:verifyRequest})], function(req, res) {

    // this message is unsecured because github doesn't have a token to call us
    // instead we check that this message was generated by our github instance
    // by checking that the signature of the body was created with the correct secret
    console.log(`RequestApp: received web hook action: ${req.headers['x-github-event']}`);
    console.log(JSON.stringify(req.headers));
    console.log(JSON.stringify(req.body));

    //const valid = isGitHubWebHookRequestValid(req);
    console.log(isGitHubWebHookRequestValid(req));

    if (isGitHubWebHookRequestValid(req)) {
      sendEmailsForGitHubWebHook(req, res)
    } else {
      handleInvalidGitHubWebHookRequest(req, res);
    }
  });
};
<|endoftext|>


<commit_before>var models = require('./model');
var uuid = require('node-uuid');
var VenueModel = models.venue;
var OpinionModel = models.opinion;

exports.api = function (req, res) {
    res.send({message: 'API is functional'});
};

exports.newVenue =  function (req, res) {
    var venue = new VenueModel({
        id: uuid.v4(),
        name: req.body.name,
        foursquare_id: req.body.foursquare_id,
        location: req.body.location,
        approve_count: 0,
        veto_count: 0
    });
    venue.save(function (err) {
        if(!err) {
            return res.send(venue.toClient());
        }
        else {
            // TODO better eror handling
            //console.log(err);
            return res.status(500).send({code: err.code, msg: err.err});
        }
    });
};

exports.listAllVenues = function (req, res) {
    var limit = req.query.limit || req.body.limit || 20;

    return VenueModel.find()
        .limit(limit)
        .exec(function (err, venues) {
            if(!err) {

                return res.send(venues.map(function (venue) {
                    return venue.toClient();
                }));
            }
            else {
                // TODO better eror handling
                return res.status(500).send({code: err.code, msg: 'Error Occurred'});
            }
        });
};

exports.showVenue = function (req, res) {
    return VenueModel.findOne({id: req.params.id}, function (err, venue) {
        if(!err) {
            if(venue) {
                return res.send(venue.toClient());
            }
            else {
                return res.status(404).send({msg: 'Resource Not Found'});
            }
        }
        else {
            // TODO better eror handling
            return res.status(500).send({code: err.code, msg: 'Error Occurred'});
        }
    });
};

// remove methods should probably be non-public
exports.removeVenue = function (req, res) {
    return res.send({});
};

// remove methods should probably be non-public
exports.removeAllVenues = function (req, res) {
    return res.send({});
};

// users can approve a boycott with or without an opinion
// for approve, just increase the approve_count by one.
// then, if there is a text, insert an opinion
exports.approveBoycott = function (req, res) {
    return VenueModel.findOneAndUpdate({id: req.body.id}, {$inc: {approve_count: 1 }}, function (err, venue) {
        if(!err) {
            if(venue) {
                if(req.body.opinion) {
                    var opinion = new OpinionModel({
                        id: uuid.v4(),
                        boycott_id: venue._id,
                        boycott_type: 'venue',
                        opinion: req.body.opinion
                    });

                    return opinion.save(function (err) {
                        if(!err) {
                            var combinedObj = venue.toClient();
                            combinedObj.opinion_id = opinion._id;
                            return res.send(combinedObj);
                        }
                        else {
                            var errObj = {code: err.code, msg: 'Error Occurred'};
                            // taking back the approve increment if an error occurs while saving the opinion.
                            VenueModel.update(req.body.id, {$dec: {approve_count: 1}}, function (err, rowsAffected, raw) {
                                return res.status(500).send(errObj);
                            });
                        }
                    });
                }
                else {
                    return res.send(venue.toClient());
                }
            }
            else {
                return res.status(404).send({msg: 'Resource Not Found'});
            }
        }
        else {
            // TODO better eror handling
            return res.status(500).send({code: err.code, msg: 'Error Occurred'});
        }
    });
};

// users can veto a boycott with or without an opinion
// for veto, just increase the veto_count by one.
// then, if there is a text, insert an opinion
exports.vetoBoycott = function (req, res) {
    return VenueModel.findOneAndUpdate({id: req.body.id}, {$inc: {veto_count: 1 }}, function (err, venue) {
        if(!err) {
            if(venue) {
                if(req.body.opinion) {
                    var opinion = new OpinionModel({
                        id: uuid.v4(),
                        boycott_id: venue._id,
                        boycott_type: 'venue',
                        opinion: req.body.opinion
                    });

                    return opinion.save(function (err) {
                        if(!err) {
                            var combinedObj = venue.toClient();
                            combinedObj.opinion_id = opinion._id;
                            return res.send(combinedObj);
                        }
                        else {
                            var errObj = {code: err.code, msg: 'Error Occurred'};
                            // taking back the veto increment if an error occurs while saving the opinion.
                            VenueModel.update(req.body.id, {$dec: {veto_count: 1}}, function (err, rowsAffected, raw) {
                                return res.status(500).send(errObj);
                            });
                        }
                    });
                }
                else {
                    return res.send(venue.toClient());
                }
            }
            else {
                return res.status(404).send({msg: 'Resource Not Found'});
            }
        }
        else {
            // TODO better eror handling
            return res.status(500).send({code: err.code, msg: 'Error Occurred'});
        }
    });
};

// users can agree with an opinion
// increase agree count and return the object
exports.agreeWithOpinion = function (req, res) {
    return OpinionModel.findOneAndUpdate({id: req.params.id}, {$inc: {agree_count: 1 }}, function (err, opinion) {
        if(!err) {
            if(opinion) {
                return res.send(opinion.toClient());
            }
            else {
                return res.status(404).send({msg: 'Resource Not Found'});
            }
        }
        else {
            // TODO better eror handling
            return res.status(500).send({code: err.code, msg: 'Error Occurred'});
        }
    });
};

// users can disagree with an opinion
// increase disagree count and return the object
exports.disagreeWithOpinion  = function (req, res) {
    return OpinionModel.findOneAndUpdate({id: req.params.id}, {$inc: {disagree_count: 1 }}, function (err, opinion) {
        if(!err) {
            if(opinion) {
                return res.send(opinion.toClient());
            }
            else {
                return res.status(404).send({msg: 'Resource Not Found'});
            }
        }
        else {
            // TODO better eror handling
            return res.status(500).send({code: err.code, msg: 'Error Occurred'});
        }
    });
};

exports.listOpinions = function (req, res) {
    var limit = req.param.limit || 50,
        filters = {};
    if(req.param.boycott_id) {
        filters['boycott_id'] = req.param.boycott_id;
    }

    return OpinionModel.find(filters).sort({_id: 1}).limit(limit)
        .exec(function (err, opinions) {
            if(!err) {
                return res.send(opinions.map(function (opinion) {
                    return opinion.toClient();
                }));
            }
            else {
                // TODO better eror handling
                return res.status(500).send({code: err.code, msg: 'Error Occurred'});
            }
        });
};

exports.searchVenues = function (req, res) {
    var radius = req.query.radius || req.body.radius || 500,
        lat = req.query.lat || req.body.lat || undefined,
        lng = req.query.lng || req.body.lng || undefined,
        lim = req.query.limit || req.body.limit || 20,
        filter = {};

    if(lat !== undefined && lng !== undefined) {
        filter.location = {$nearSphere: [lng, lat], $maxDistance: radius / 6371000};
    }

    var query = VenueModel.find(filter).limit(lim);
    return query.exec(function (err, venues) {
        if(!err) {

            return res.send(venues.map(function (venue) {
                return venue.toClient();
            }));
        }
        else {
            // TODO better eror handling
            return res.status(500).send({code: err.code, msg: 'Error Occurred'});
        }
    });
};<commit_msg>added max_date query to listAllVenues<commit_after>var models = require('./model');
var uuid = require('node-uuid');
var VenueModel = models.venue;
var OpinionModel = models.opinion;

exports.api = function (req, res) {
    res.send({message: 'API is functional'});
};

exports.newVenue =  function (req, res) {
    var venue = new VenueModel({
        id: uuid.v4(),
        name: req.body.name,
        foursquare_id: req.body.foursquare_id,
        location: req.body.location,
        approve_count: 0,
        veto_count: 0
    });
    venue.save(function (err) {
        if(!err) {
            return res.send(venue.toClient());
        }
        else {
            // TODO better eror handling
            //console.log(err);
            return res.status(500).send({code: err.code, msg: err.err});
        }
    });
};

exports.listAllVenues = function (req, res) {
    var limit = req.query.limit || req.body.limit || 20;
    var max_date = req.query.max_date || req.query.max_date;
    var query = {};

    if(max_date !== undefined) {
        query.date_added = {$lt: max_date};
    }

    return VenueModel.find(query)
        .sort({date_added: -1})
        .limit(limit)
        .exec(function (err, venues) {
            if(!err) {

                return res.send(venues.map(function (venue) {
                    return venue.toClient();
                }));
            }
            else {
                // TODO better eror handling
                return res.status(500).send({code: err.code, msg: 'Error Occurred'});
            }
        });
};

exports.showVenue = function (req, res) {
    return VenueModel.findOne({id: req.params.id}, function (err, venue) {
        if(!err) {
            if(venue) {
                return res.send(venue.toClient());
            }
            else {
                return res.status(404).send({msg: 'Resource Not Found'});
            }
        }
        else {
            // TODO better eror handling
            return res.status(500).send({code: err.code, msg: 'Error Occurred'});
        }
    });
};

// remove methods should probably be non-public
exports.removeVenue = function (req, res) {
    return res.send({});
};

// remove methods should probably be non-public
exports.removeAllVenues = function (req, res) {
    return res.send({});
};

// users can approve a boycott with or without an opinion
// for approve, just increase the approve_count by one.
// then, if there is a text, insert an opinion
exports.approveBoycott = function (req, res) {
    return VenueModel.findOneAndUpdate({id: req.body.id}, {$inc: {approve_count: 1 }}, function (err, venue) {
        if(!err) {
            if(venue) {
                if(req.body.opinion) {
                    var opinion = new OpinionModel({
                        id: uuid.v4(),
                        boycott_id: venue._id,
                        boycott_type: 'venue',
                        opinion: req.body.opinion
                    });

                    return opinion.save(function (err) {
                        if(!err) {
                            var combinedObj = venue.toClient();
                            combinedObj.opinion_id = opinion._id;
                            return res.send(combinedObj);
                        }
                        else {
                            var errObj = {code: err.code, msg: 'Error Occurred'};
                            // taking back the approve increment if an error occurs while saving the opinion.
                            VenueModel.update(req.body.id, {$dec: {approve_count: 1}}, function (err, rowsAffected, raw) {
                                return res.status(500).send(errObj);
                            });
                        }
                    });
                }
                else {
                    return res.send(venue.toClient());
                }
            }
            else {
                return res.status(404).send({msg: 'Resource Not Found'});
            }
        }
        else {
            // TODO better eror handling
            return res.status(500).send({code: err.code, msg: 'Error Occurred'});
        }
    });
};

// users can veto a boycott with or without an opinion
// for veto, just increase the veto_count by one.
// then, if there is a text, insert an opinion
exports.vetoBoycott = function (req, res) {
    return VenueModel.findOneAndUpdate({id: req.body.id}, {$inc: {veto_count: 1 }}, function (err, venue) {
        if(!err) {
            if(venue) {
                if(req.body.opinion) {
                    var opinion = new OpinionModel({
                        id: uuid.v4(),
                        boycott_id: venue._id,
                        boycott_type: 'venue',
                        opinion: req.body.opinion
                    });

                    return opinion.save(function (err) {
                        if(!err) {
                            var combinedObj = venue.toClient();
                            combinedObj.opinion_id = opinion._id;
                            return res.send(combinedObj);
                        }
                        else {
                            var errObj = {code: err.code, msg: 'Error Occurred'};
                            // taking back the veto increment if an error occurs while saving the opinion.
                            VenueModel.update(req.body.id, {$dec: {veto_count: 1}}, function (err, rowsAffected, raw) {
                                return res.status(500).send(errObj);
                            });
                        }
                    });
                }
                else {
                    return res.send(venue.toClient());
                }
            }
            else {
                return res.status(404).send({msg: 'Resource Not Found'});
            }
        }
        else {
            // TODO better eror handling
            return res.status(500).send({code: err.code, msg: 'Error Occurred'});
        }
    });
};

// users can agree with an opinion
// increase agree count and return the object
exports.agreeWithOpinion = function (req, res) {
    return OpinionModel.findOneAndUpdate({id: req.params.id}, {$inc: {agree_count: 1 }}, function (err, opinion) {
        if(!err) {
            if(opinion) {
                return res.send(opinion.toClient());
            }
            else {
                return res.status(404).send({msg: 'Resource Not Found'});
            }
        }
        else {
            // TODO better eror handling
            return res.status(500).send({code: err.code, msg: 'Error Occurred'});
        }
    });
};

// users can disagree with an opinion
// increase disagree count and return the object
exports.disagreeWithOpinion  = function (req, res) {
    return OpinionModel.findOneAndUpdate({id: req.params.id}, {$inc: {disagree_count: 1 }}, function (err, opinion) {
        if(!err) {
            if(opinion) {
                return res.send(opinion.toClient());
            }
            else {
                return res.status(404).send({msg: 'Resource Not Found'});
            }
        }
        else {
            // TODO better eror handling
            return res.status(500).send({code: err.code, msg: 'Error Occurred'});
        }
    });
};

exports.listOpinions = function (req, res) {
    var limit = req.param.limit || 50,
        filters = {};
    if(req.param.boycott_id) {
        filters['boycott_id'] = req.param.boycott_id;
    }

    return OpinionModel.find(filters).sort({date_added: -1}).limit(limit)
        .exec(function (err, opinions) {
            if(!err) {
                return res.send(opinions.map(function (opinion) {
                    return opinion.toClient();
                }));
            }
            else {
                // TODO better eror handling
                return res.status(500).send({code: err.code, msg: 'Error Occurred'});
            }
        });
};

exports.searchVenues = function (req, res) {
    var radius = req.query.radius || req.body.radius || 500,
        lat = req.query.lat || req.body.lat || undefined,
        lng = req.query.lng || req.body.lng || undefined,
        lim = req.query.limit || req.body.limit || 20,
        filter = {};

    if(lat !== undefined && lng !== undefined) {
        filter.location = {$nearSphere: [lng, lat], $maxDistance: radius / 6371000};
    }

    var query = VenueModel.find(filter).sort({date_added: -1}).limit(lim);
    return query.exec(function (err, venues) {
        if(!err) {

            return res.send(venues.map(function (venue) {
                return venue.toClient();
            }));
        }
        else {
            // TODO better eror handling
            return res.status(500).send({code: err.code, msg: 'Error Occurred'});
        }
    });
};<|endoftext|>


<commit_before>var request = require('request');
var CSAS_API_KEY = process.env.CSAS_API_KEY;
// api calls
module.exports = {

  refreshAccounts: function(session, callback)
    {
        request({
            method: 'GET',
            url: 'https://api.csas.cz/sandbox/webapi/api/v3/netbanking/my/accounts?size=&page=&sort=&order=&type=',
            headers: {
                'WEB-API-key': CSAS_API_KEY,
                'Authorization': session.userData.access_token
            }}, function (error, response, body) {
                if(response.statusCode==403)
                {
                    session.userData.authorised = false;
                    callback();
                    return;
                }
                session.userData.accounts = JSON.parse(body);
                session.userData.authorised = true;
                callback();
            });
    },
    accountHistory: function(session, callback)
    {
        request({
            method: 'GET',
            url: 'https://api.csas.cz/sandbox/webapi/api/v1/netbanking/my/accounts/id/transactions?dateStart=2014-06-01T00%3A00%3A00%2B02%3A00&dateEnd=2014-06-30T00%3A00%3A00%2B02%3A00',
            headers: {
                'WEB-API-key': CSAS_API_KEY,
                'Authorization': session.userData.access_token
            }}, function (error, response, body) {
                if(response.statusCode == 403)
                {
                    session.userData.authorised = false;
                    callback();
                    return;
                }
                var history = JSON.parse(body);
                session.userData.accountHistory = JSON.parse(body);

                session.userData.authorised = true;
                callback();
                //session.send(histText);
                //session.replaceDialog('accountDialog');
        });

    },
    refreshCards: function(session, callback)
    {
        request({
            method: 'GET',
            url: 'https://api.csas.cz/sandbox/webapi/api/v3/netbanking/my/cards?size=&page=&sort=&order=',
            headers: {
                'WEB-API-key': CSAS_API_KEY,
                'Authorization': session.userData.access_token
            }}, function (error, response, body) {
                if(response.statusCode==403)
                {
                    session.userData.authorised = false;
                    callback();
                    return;
                }
                var objCards = JSON.parse(body);
                session.userData.cards = objCards;
                session.userData.authorised = true;
                callback();
            });
    },
    cardDetail: function(session, callback)
    {
        request({
            method: 'GET',
            url: 'https://api.csas.cz/sandbox/webapi/api/v3/netbanking/my/cards/' + session.userData.cards.cards[session.userData.cardIndex].id,
            headers: {
                'WEB-API-key': CSAS_API_KEY,
                'Authorization': session.userData.access_token
            }}, function (error, response, body) {
                if(response.statusCode == 403)
                {
                    session.userData.authorised = false;
                    callback();
                    return;
                }
                if(response.statusCode == 404)
                {
                    console.log('card not found: '+ session.userData.cards.cards[session.userData.cardIndex].id);
                    return;
                }
                var card = JSON.parse(body);
                session.userData.card = card;
                session.userData.authorised = true;
                callback();
                //session.send(histText);
                //session.replaceDialog('accountDialog');
        });

    }

};

<commit_msg>refactoring<commit_after>var request = require('request');
var CSAS_API_KEY = process.env.CSAS_API_KEY;
// api calls
module.exports = {

  refreshAccounts: function(session, callback)
    {
        request({
            method: 'GET',
            url: 'https://api.csas.cz/sandbox/webapi/api/v3/netbanking/my/accounts?size=&page=&sort=&order=&type=',
            headers: {
                'WEB-API-key': CSAS_API_KEY,
                'Authorization': session.userData.access_token
            }}, function (error, response, body) {
                if(response.statusCode==403)
                {
                    session.userData.authorised = false;
                    callback();
                    return;
                }
                session.userData.accounts = JSON.parse(body);
                session.userData.authorised = true;
                callback();
            });
    },
    accountHistory: function(session, callback)
    {
        request({
            method: 'GET',
            url: 'https://api.csas.cz/sandbox/webapi/api/v1/netbanking/my/accounts/id/transactions?dateStart=2014-06-01T00%3A00%3A00%2B02%3A00&dateEnd=2014-06-30T00%3A00%3A00%2B02%3A00',
            headers: {
                'WEB-API-key': CSAS_API_KEY,
                'Authorization': session.userData.access_token
            }}, function (error, response, body) {
                if(response.statusCode == 403)
                {
                    session.userData.authorised = false;
                    callback();
                    return;
                }
                var history = JSON.parse(body);
                session.userData.accountHistory = JSON.parse(body);

                session.userData.authorised = true;
                callback();
                //session.send(histText);
                //session.replaceDialog('accountDialog');
        });

    },
    refreshCards: function(session, callback)
    {
        request({
            method: 'GET',
            url: 'https://api.csas.cz/sandbox/webapi/api/v3/netbanking/my/cards?size=&page=&sort=&order=',
            headers: {
                'WEB-API-key': CSAS_API_KEY,
                'Authorization': session.userData.access_token
            }}, function (error, response, body) {
                if(response.statusCode==403)
                {
                    session.userData.authorised = false;
                    callback();
                    return;
                }

                var objCards = JSON.parse(body);
                session.userData.cards = objCards;
                session.userData.authorised = true;
                callback();
            });
    },
    cardDetail: function(session, callback)
    {
        request({
            method: 'GET',
            url: 'https://api.csas.cz/sandbox/webapi/api/v3/netbanking/my/cards/' + session.userData.cards.cards[session.userData.cardIndex].id,
            headers: {
                'WEB-API-key': CSAS_API_KEY,
                'Authorization': session.userData.access_token
            }}, function (error, response, body) {
                if(response.statusCode == 403)
                {
                    session.userData.authorised = false;
                    callback();
                    return;
                }
                if(response.statusCode == 404)
                {
                    console.log('API call, card not found: '+ session.userData.cards.cards[session.userData.cardIndex].id);
                    return;
                }
                var card = JSON.parse(body);
                session.userData.card = card;
                session.userData.authorised = true;
                callback();
                //session.send(histText);
                //session.replaceDialog('accountDialog');
        });

    }

};

<|endoftext|>


<commit_before>/* jshint
    esversion: 6,
    node: true
*/
(() => {
    "use strict";
    var jimp = require("jimp");
    var express = require("express");

    var router = express.Router();

    var imagePath = `${__dirname}/lemonysnicket.jpg`;
    var xPadding = 10;

    // This function is copied from the Jimp implmentation
    // (https://github.com/oliver-moran/jimp/blob/master/index.js#L2381)
    // License: https://github.com/oliver-moran/jimp/blob/master/LICENSE
    // Once https://github.com/oliver-moran/jimp/pull/199 has been merged, this can be removed
    function measureText(font, text) {
        var x = 0;
        for (var i = 0; i < text.length; i++) {
            if (font.chars[text[i]]) {
                x += font.chars[text[i]].xoffset
                    + (font.kernings[text[i]] && font.kernings[text[i]][text[i+1]] ? font.kernings[text[i]][text[i+1]] : 0)
                    + (font.chars[text[i]].xadvance || 0);
            }
        }
        return x;
    }

    function getLinesOfText(font, text, maxWidth) {
        var words = text.split(" ");
        var lines = [];
        var line = "";

        words.forEach((word) => {
            if (measureText(font, `${line} ${word}`) > maxWidth) {
                lines.push(line);
                line = word;
            } else {
                line = `${line} ${word}`;
            }
        });
        lines.push(line);

        return lines;
    }

    function writeTextToImage(image, font, text, maxWidth) {
        var lineHeight = font.common.lineHeight;
        var yPadding = 1.5 * lineHeight;
        var y = image.bitmap.height - yPadding - lineHeight;

        var lines = getLinesOfText(font, text, maxWidth);

        // print all lines starting with the last one so we know where the others are placed on the y-axis
        lines.reverse();
        lines.forEach((line) => {
            var x = (image.bitmap.width / 2) - (measureText(font, line) / 2) + xPadding;
            image.print(font, x, y, line);
            y -= lineHeight;
        });

        return image;
    }

    router.get("/:text64", (req, res) => {
        var text =  (req.params.text64 ? new Buffer(req.params.text64, "base64").toString() : "");

        res.contentType("image/jpeg");

        jimp.read(imagePath).then((image) => {
            jimp.loadFont(jimp.FONT_SANS_32_WHITE).then((font) => {
                var width = image.bitmap.width - (2 * xPadding);
                image = writeTextToImage(image, font, text, width);

                image.getBuffer(jimp.AUTO, (error, buffer) => {
                    if (!error) {
                        res.end(buffer);
                    } else {
                        console.error(`Error: ${error}`);
                        res.end();
                    }
                });
            })
            .catch((error) => {
                console.error(`Error: ${error}`);
                res.end();
            });
        })
        .catch((error) => {
            console.error(`Error: ${error}`)  ;
            res.end();
        });
    });

    module.exports = router;
})();<commit_msg>Load image and font one time and copy the image in-memory once per request<commit_after>/* jshint
    esversion: 6,
    node: true
*/
(() => {
    "use strict";
    var jimp = require("jimp");
    var express = require("express");

    var router = express.Router();

    var imagePath = `${__dirname}/lemonysnicket.jpg`;
    var xPadding = 10;

    var cleanImage;
    var captionFont;

    var imageLoaded = jimp.read(imagePath).then((image) => {
        cleanImage = image;
    });

    var fontLoaded = jimp.loadFont(jimp.FONT_SANS_32_WHITE).then((font) => {
        captionFont = font;
    });

    // This function is copied from the Jimp implmentation
    // (https://github.com/oliver-moran/jimp/blob/master/index.js#L2381)
    // License: https://github.com/oliver-moran/jimp/blob/master/LICENSE
    // Once https://github.com/oliver-moran/jimp/pull/199 has been merged, this can be removed
    function measureText(font, text) {
        var x = 0;
        for (var i = 0; i < text.length; i++) {
            if (font.chars[text[i]]) {
                x += font.chars[text[i]].xoffset
                    + (font.kernings[text[i]] && font.kernings[text[i]][text[i+1]] ? font.kernings[text[i]][text[i+1]] : 0)
                    + (font.chars[text[i]].xadvance || 0);
            }
        }
        return x;
    }

    function getLinesOfText(font, text, maxWidth) {
        var words = text.split(" ");
        var lines = [];
        var line = "";

        words.forEach((word) => {
            if (measureText(font, `${line} ${word}`) > maxWidth) {
                lines.push(line);
                line = word;
            } else {
                line = `${line} ${word}`;
            }
        });
        lines.push(line);

        return lines;
    }

    function writeTextToImage(image, font, text, maxWidth) {
        var lineHeight = font.common.lineHeight;
        var yPadding = 1.5 * lineHeight;
        var y = image.bitmap.height - yPadding - lineHeight;

        var lines = getLinesOfText(font, text, maxWidth);

        // print all lines starting with the last one so we know where the others are placed on the y-axis
        lines.reverse();
        lines.forEach((line) => {
            var x = (image.bitmap.width / 2) - (measureText(font, line) / 2) + xPadding;
            image.print(font, x, y, line);
            y -= lineHeight;
        });

        return image;
    }

    router.get("/:text64", (req, res) => {
        var text =  (req.params.text64 ? new Buffer(req.params.text64, "base64").toString() : "");

        res.contentType("image/jpeg");

        Promise.all([imageLoaded, fontLoaded]).then(() => {
            var image = new jimp(cleanImage);
            var width = image.bitmap.width - (2 * xPadding);
            image = writeTextToImage(image, captionFont, text, width);

            image.getBuffer(jimp.AUTO, (error, buffer) => {
                if (!error) {
                    res.end(buffer);
                } else {
                    console.error(`Error: ${error}`);
                    res.end();
                }
            });
        })
        .catch((error) => {
            console.error(error);
        });
    });

    module.exports = router;
})();<|endoftext|>