<commit_before>/**
 * Copyright 2016 Marius Runde
 *
 * Constants of the Weather component.
 */
'use strict';

// OpenWeatherMap API Key
const API_KEY = 'YOUR_API_KEY';

// Customized OpenWeatherMap URLs to request current and forecast weather data
const CURRENT_WEATHER_URL = 'http://api.openweathermap.org/data/2.5/weather?APPID=' + API_KEY + '&q=';
const FORECAST_WEATHER_URL = 'api.openweathermap.org/data/2.5/forecast?APPID=' + API_KEY + '&q=';

// General URL for the OpenWeatherMap icons
const WEATHER_ICON_URL = 'http://openweathermap.org/img/w/';

module.exports = {
  CURRENT_WEATHER_URL,
  FORECAST_WEATHER_URL,
  WEATHER_ICON_URL
};<commit_msg>Fixed FORECAST_WEATHER_URL in weather constants<commit_after>/**
 * Copyright 2016 Marius Runde
 *
 * Constants of the Weather component.
 */
'use strict';

// OpenWeatherMap API Key
const API_KEY = 'YOUR_API_KEY';

// Customized OpenWeatherMap URLs to request current and forecast weather data
const CURRENT_WEATHER_URL = 'http://api.openweathermap.org/data/2.5/weather?APPID=' + API_KEY + '&q=';
const FORECAST_WEATHER_URL = 'http://api.openweathermap.org/data/2.5/forecast?APPID=' + API_KEY + '&q=';

// General URL for the OpenWeatherMap icons
const WEATHER_ICON_URL = 'http://openweathermap.org/img/w/';

module.exports = {
  CURRENT_WEATHER_URL,
  FORECAST_WEATHER_URL,
  WEATHER_ICON_URL
};<|endoftext|>


<commit_before>/* eslint-disable no-unused-vars*/
import React from 'react';
/* eslint-enable no-unused-vars*/
import { Router, Route, IndexRoute } from 'react-router';
import createBrowserHistory from 'history/lib/createBrowserHistory';

import Home from 'containers/Home/';
import App from 'containers/App/';

const Routes = () => (
  <Router history={createBrowserHistory()}>
    <Route path="/" component={App}>
      <IndexRoute component={Home} />
    </Route>
  </Router>
);

export default Routes;
<commit_msg>Spacing<commit_after>/* eslint-disable no-unused-vars*/
import React from 'react';
/* eslint-enable no-unused-vars*/
import { Router, Route, IndexRoute } from 'react-router';
import createBrowserHistory from 'history/lib/createBrowserHistory';

import App from 'containers/App/';
import Home from 'containers/Home/';

const Routes = () => (
  <Router history={createBrowserHistory()}>
    <Route path="/" component={App}>
      <IndexRoute component={Home} />
    </Route>
  </Router>
);

export default Routes;
<|endoftext|>


<commit_before>var browseRoute = require('./browse');
var store = require('../lib/store');

var log = require('../lib/log')('routes:index');

module.exports = function(routes) {
  routes.addRoute('/', handleIndexRoute);
};

function handleIndexRoute(state, events) {
  // TODO(aghassemi) What's the prod address?, do we even want to point to
  // v.io by default?
  var index = '/ns.dev.v.io:8101';
  store.getValue('index').then(function(storedIndex) {
    if (storedIndex) {
      index = storedIndex;
    }

    // Redirect to browse
    events.navigation.navigate({
      path: browseRoute.createUrl(state.browse, {
        namespace: index
      })
    });
  }).catch(function(err) {
    log.warn('Unable to access stored index', err);

    // Redirect to browse
    events.navigation.navigate({
      path: browseRoute.createUrl(state.browse, {
        namespace: index
      })
    });
  });
}
<commit_msg>viz: Update index routes to observe mercury state.<commit_after>var browseRoute = require('./browse');
var store = require('../lib/store');

var log = require('../lib/log')('routes:index');

module.exports = function(routes) {
  routes.addRoute('/', handleIndexRoute);
};

function handleIndexRoute(state, events) {
  // TODO(aghassemi) What's the prod address?, do we even want to point to
  // v.io by default?
  var index = '/ns.dev.v.io:8101';
  store.getValue('index').then(function(storedIndex) {
    if (storedIndex) {
      index = storedIndex;
    }

    // Redirect to browse
    events.navigation.navigate({
      path: browseRoute.createUrl(state.browse(), {
        namespace: index
      })
    });
  }).catch(function(err) {
    log.warn('Unable to access stored index', err);

    // Redirect to browse
    events.navigation.navigate({
      path: browseRoute.createUrl(state.browse(), {
        namespace: index
      })
    });
  });
}
<|endoftext|>


<commit_before>'use strict';

var template       = require('es5-ext/string/#/template')
  , mano           = require('mano')
  , tryRequire     = require('mano/lib/utils/try-require').bind(require)
  , resolve        = require('path').resolve
  , resolveTrigger = require('./_resolve-trigger')

  , forEach = Array.prototype.forEach
  , StatusLog = mano.db.StatusLog
  , configure;

exports = module.exports = [];

configure = function (conf) {
	conf.trigger = resolveTrigger(conf.trigger, conf.triggerValue);
	conf.text = template.call(conf.text, conf.variables);
	delete conf.triggerValue;
	exports.push(conf);
};

mano.apps.forEach(function (app) {
	var conf = tryRequire(resolve(app.root, 'server/status-log'));
	if (!conf) return;
	forEach.call(conf, configure);
});

exports.forEach(function (conf) {
	conf.trigger.on('add', function (user) {
		user.statusLog.add(new StatusLog({
			label: conf.label,
			time: new Date(),
			text: conf.text(user)
		}));
	});
});
<commit_msg>Support official field<commit_after>'use strict';

var template       = require('es5-ext/string/#/template')
  , mano           = require('mano')
  , tryRequire     = require('mano/lib/utils/try-require').bind(require)
  , resolve        = require('path').resolve
  , resolveTrigger = require('./_resolve-trigger')

  , forEach = Array.prototype.forEach
  , StatusLog = mano.db.StatusLog
  , configure;

exports = module.exports = [];

configure = function (conf) {
	conf.trigger = resolveTrigger(conf.trigger, conf.triggerValue);
	conf.text = template.call(conf.text, conf.variables);
	delete conf.triggerValue;
	exports.push(conf);
};

mano.apps.forEach(function (app) {
	var conf = tryRequire(resolve(app.root, 'server/status-log'));
	if (!conf) return;
	forEach.call(conf, configure);
});

exports.forEach(function (conf) {
	conf.trigger.on('add', function (user) {
		user.statusLog.add(new StatusLog({
			label: conf.label,
			official: conf.official ? user[conf.official] : null,
			time: new Date(),
			text: conf.text(user)
		}));
	});
});
<|endoftext|>


<commit_before>import { createStore, applyMiddleware, compose } from 'redux'
import { persistState } from 'redux-devtools'
import persistState from 'redux-localstorage'
import thunk from 'redux-thunk'
import rootReducer from '../reducers'
import DevTools from '../containers/DevTools'

const finalCreateStore = compose(
  persistState({ paths: 'servers' }),
  applyMiddleware(thunk),
  DevTools.instrument(),
  persistState(
    window.location.href.match(
      /[?&]debug_session=([^&]+)\b/
    )
  )
)(createStore)

export default function configureStore(initialState) {
  const store = finalCreateStore(rootReducer, initialState)

  if (module.hot) {
    module.hot.accept('../reducers', () =>
      store.replaceReducer(require('../reducers'))
    )
  }

  return store
}
<commit_msg>Remove persistence from dev<commit_after>import { createStore, applyMiddleware, compose } from 'redux'
import { persistState } from 'redux-devtools'
import thunk from 'redux-thunk'
import rootReducer from '../reducers'
import DevTools from '../containers/DevTools'

const finalCreateStore = compose(
  applyMiddleware(thunk),
  DevTools.instrument(),
  persistState(
    window.location.href.match(
      /[?&]debug_session=([^&]+)\b/
    )
  )
)(createStore)

export default function configureStore(initialState) {
  const store = finalCreateStore(rootReducer, initialState)

  if (module.hot) {
    module.hot.accept('../reducers', () =>
      store.replaceReducer(require('../reducers'))
    )
  }

  return store
}
<|endoftext|>


<commit_before>'use strict';

const rethinkDB = require('rethinkdb');
const labyrinthDB = require('./labyrinthdb');

// https://github.com/websockets/ws/
const WebSocketServer = require('ws').Server;

/**
 *
 * @param message
 */
function log(message) {
  console.log(`${Date.now() / 1000}: ${message}`);
}

class UserDB extends labyrinthDB.LabyrinthDB {
  runDB () {
    this.locationMap = [];
    const _this = this;

    rethinkDB
      .table('startLocation', {readMode: 'outdated'})
      .run(this.conn, function(err, cursor) {
        if (err) throw err;

        cursor.toArray(function (err, res) {
          if (err) throw err;

          let i =0;
          for (; i < res.length; i++) {
            let e = res[i];

            if (_this.locationMap[e.y] === undefined) _this.locationMap[e.y] = [];
            _this.locationMap[e.y][e.x] = e.type;
          }

          log(`Map buffer is ready, ${i} elements.`);

          _this.startWebSocketServer();
          _this.readChanges();
        });
      });
  }

  readChanges () {
    const _this = this;

    rethinkDB
      .table('startLocation', {readMode: 'outdated'})
      .changes()
      .run(this.conn, function(err, cursor) {
        if (err) throw err;

        cursor.each(function(err, res) {
          if (err) throw err;

          if (res.old_val.type != res.new_val.type) {
            _this.locationMap[res.new_val.y][res.new_val.x] = res.new_val.type;
            log(`Inserted element ${JSON.stringify(res.new_val)}`);
          }
        });
      });
  }

  startWebSocketServer () {
    this.webAPI = new WebAPI(this);
  }
}

class WebAPI {
  constructor (cdb) {
    this.cdb = cdb;
    this.connPool = {};
    this.clientId = 0;

    // dictionary for variant of offset
    this.offsets = {
      'up': {'x': 0, 'y': -1},
      'down': {'x': 0, 'y': 1},
      'left': {'x': -1, 'y': 0},
      'right': {'x': 1, 'y': 0}
    };

    this.wss = new WebSocketServer({ port: 8081 });

    const _this = this;

    this.wss.broadcast = function broadcast(data) {
      if (_this.wss.clients.length) {
        data = JSON.stringify(data);
        log(`Send broadcast: ${data}`);
      }

      _this.wss.clients.forEach(function each(client) {
        client.send(data);
      });
    };

    this.wss.on('connection', function(ws) {
      // increment id counter
      const thisId = ++_this.clientId;
      // set up structure for this connection
      _this.connPool[thisId] = {};

      // we accept message from user!
      ws.on('message', function(rawMessage) {
        log(`Received: ${rawMessage}`);

        const message = JSON.parse(rawMessage);
        _this.connPool[thisId]['login'] = message.login;

        if (message.direction && 'position' in _this.connPool[thisId]) {
          // user would like changes his position and he has old position
          const new_position = _this.getNewPosition(_this.connPool[thisId]['position'], message.direction);

          if (new_position) {
            _this.connPool[thisId]['position'] = new_position;

            let resp = {'changePosition': new_position};
            resp.changePosition.login = message.login;

            _this.wss.broadcast(resp);
          }
        } else {
          /**
          * user would like changes his position and he has not old position
          * because is connecting at server just now
          */
          log(`New user!! ${message.login} ${thisId}`);

          const position = _this.searchStartPosition();
          _this.connPool[thisId]['position'] = position;

          let resp = {
            allMap: _this.cdb.locationMap,
            changePosition: {
              x: position.x,
              y: position.y,
              login: message.login
            }
          };

          resp = JSON.stringify(resp);

          log(`Send: ${resp}`);
          ws.send(resp);
        }
      });

      ws.on('close', this.close(thisId));

      ws.on('error', function(e) {
        log(`Client ${_this.connPool[thisId]['login']} error: ${e.message}`);
      });
    });

    log('Web API started');
  }

  close (userId) {
    log(`Client disconnected: ${this.connPool[userId]['login']}`);
    delete this.connPool[thisId];
  }

  /**
  * search start position for new user
  * @returns {{y: number, x: number}}
  */
  searchStartPosition () {
    for (let i = 0; i < this.cdb.locationMap.length; i++) {
      for (let ii = 0; ii < this.cdb.locationMap[i].length; ii++) {
        if (this.cdb.locationMap[i][ii] === 0) {
          return {'y': i, 'x': ii};
        }
      }
    }
  }

  /**
  *
  * @param curPosition {y, x} current user position
  * @param direction 'up', or 'down', or 'left', or 'right'
  * @returns {{y: *, x: *, direction: *}} new position and direction
  *          or undefined if block ahead
  * @constructor
  */
  getNewPosition(curPosition, direction) {
    const offset = this.offsets[direction];

    const newY = curPosition.y + offset.y;
    const newX = curPosition.x + offset.x;

    if (newX >= 0 && newX < this.cdb.locationMap.length && newY >= 0 && newY < this.cdb.locationMap.length) {
      if (this.cdb.locationMap[newY][newX] === 0) {
        return {
        'y': newY,
        'x': newX,
        'direction': direction};
      }
    }
  }
}

if (require.main === module) {
  rethinkDB.connect( {host: 'localhost', port: 28015}, function(err, conn) {
    if (err) throw err;

    const cdb = new UserDB(conn, 'labyrinth', ['userPosition']);
    cdb.initDB();
  });
}<commit_msg>revert<commit_after>'use strict';

const rethinkDB = require('rethinkdb');
const labyrinthDB = require('./labyrinthdb');

// https://github.com/websockets/ws/
const WebSocketServer = require('ws').Server;

/**
 *
 * @param message
 */
function log(message) {
  console.log(`${Date.now() / 1000}: ${message}`);
}

class UserDB extends labyrinthDB.LabyrinthDB {
  runDB () {
    this.locationMap = [];
    const _this = this;

    rethinkDB
      .table('startLocation', {readMode: 'outdated'})
      .run(this.conn, function(err, cursor) {
        if (err) throw err;

        cursor.toArray(function (err, res) {
          if (err) throw err;

          let i =0;
          for (; i < res.length; i++) {
            let e = res[i];

            if (_this.locationMap[e.y] === undefined) _this.locationMap[e.y] = [];
            _this.locationMap[e.y][e.x] = e.type;
          }

          log(`Map buffer is ready, ${i} elements.`);

          _this.startWebSocketServer();
          _this.readChanges();
        });
      });
  }

  readChanges () {
    const _this = this;

    rethinkDB
      .table('startLocation', {readMode: 'outdated'})
      .changes()
      .run(this.conn, function(err, cursor) {
        if (err) throw err;

        cursor.each(function(err, res) {
          if (err) throw err;

          if (res.old_val.type != res.new_val.type) {
            _this.locationMap[res.new_val.y][res.new_val.x] = res.new_val.type;
            log(`Inserted element ${JSON.stringify(res.new_val)}`);
          }
        });
      });
  }

  startWebSocketServer () {
    this.webAPI = new WebAPI(this);
  }
}

class WebAPI {
  constructor (cdb) {
    this.cdb = cdb;
    this.connPool = {};
    this.clientId = 0;

    // dictionary for variant of offset
    this.offsets = {
      'up': {'x': 0, 'y': -1},
      'down': {'x': 0, 'y': 1},
      'left': {'x': -1, 'y': 0},
      'right': {'x': 1, 'y': 0}
    };

    this.wss = new WebSocketServer({ port: 8081 });

    const _this = this;

    this.wss.broadcast = function broadcast(data) {
      if (_this.wss.clients.length) {
        data = JSON.stringify(data);
        log(`Send broadcast: ${data}`);
      }

      _this.wss.clients.forEach(function each(client) {
        client.send(data);
      });
    };

    this.wss.on('connection', function(ws) {
      // increment id counter
      const thisId = ++_this.clientId;
      // set up structure for this connection
      _this.connPool[thisId] = {};

      // we accept message from user!
      ws.on('message', function(rawMessage) {
        log(`Received: ${rawMessage}`);

        const message = JSON.parse(rawMessage);
        _this.connPool[thisId]['login'] = message.login;

        if (message.direction && 'position' in _this.connPool[thisId]) {
          // user would like changes his position and he has old position
          const new_position = _this.getNewPosition(_this.connPool[thisId]['position'], message.direction);

          if (new_position) {
            _this.connPool[thisId]['position'] = new_position;

            let resp = {'changePosition': new_position};
            resp.changePosition.login = message.login;

            _this.wss.broadcast(resp);
          }
        } else {
          /**
          * user would like changes his position and he has not old position
          * because is connecting at server just now
          */
          log(`New user!! ${message.login} ${thisId}`);

          const position = _this.searchStartPosition();
          _this.connPool[thisId]['position'] = position;

          let resp = {
            allMap: _this.cdb.locationMap,
            changePosition: {
              x: position.x,
              y: position.y,
              login: message.login
            }
          };

          resp = JSON.stringify(resp);

          log(`Send: ${resp}`);
          ws.send(resp);
        }
      });

      ws.on('close', function () {
        log(`Client disconnected: ${_this.connPool[thisId]['login']}`);
        delete _this.connPool[thisId];
      });

      ws.on('error', function(e) {
        log(`Client ${_this.connPool[thisId]['login']} error: ${e.message}`);
      });
    });

    log('Web API started');
  }

  /**
  * search start position for new user
  * @returns {{y: number, x: number}}
  */
  searchStartPosition () {
    for (let i = 0; i < this.cdb.locationMap.length; i++) {
      for (let ii = 0; ii < this.cdb.locationMap[i].length; ii++) {
        if (this.cdb.locationMap[i][ii] === 0) {
          return {'y': i, 'x': ii};
        }
      }
    }
  }

  /**
  *
  * @param curPosition {y, x} current user position
  * @param direction 'up', or 'down', or 'left', or 'right'
  * @returns {{y: *, x: *, direction: *}} new position and direction
  *          or undefined if block ahead
  * @constructor
  */
  getNewPosition(curPosition, direction) {
    const offset = this.offsets[direction];

    const newY = curPosition.y + offset.y;
    const newX = curPosition.x + offset.x;

    if (newX >= 0 && newX < this.cdb.locationMap.length && newY >= 0 && newY < this.cdb.locationMap.length) {
      if (this.cdb.locationMap[newY][newX] === 0) {
        return {
        'y': newY,
        'x': newX,
        'direction': direction};
      }
    }
  }
}

if (require.main === module) {
  rethinkDB.connect( {host: 'localhost', port: 28015}, function(err, conn) {
    if (err) throw err;

    const cdb = new UserDB(conn, 'labyrinth', ['userPosition']);
    cdb.initDB();
  });
}<|endoftext|>


<commit_before>import elo from 'elo-rank'

import {LGBadRequestError} from 'src/server/util/error'

import {
  avg,
  toPercent,
  roundDecimal,
  range,
  attrCompareFn,
} from 'src/common/util'
import {
  STAT_DESCRIPTORS,
  RELEVANT_EXTERNAL_REVIEW_COUNT,
  MIN_EXTERNAL_REVIEW_COUNT_FOR_ACCURACY,
} from 'src/common/models/stat'

export const LIKERT_SCORE_NA = 0
export const LIKERT_SCORE_MIN = 1
export const LIKERT_SCORE_MAX = 7

const {
  CULTURE_CONTRIBUTION,
  ESTIMATION_ACCURACY,
  EXPERIENCE_POINTS,
  ELO,
  LEVEL,
  TEAM_PLAY,
  TECHNICAL_HEALTH,
  PROJECT_REVIEW_EXPERIENCE,
  PROJECT_REVIEW_ACCURACY,
  EXTERNAL_PROJECT_REVIEW_COUNT,
  INTERNAL_PROJECT_REVIEW_COUNT,
  PROJECT_QUALITY,
  PROJECT_COMPLETENESS,
} = STAT_DESCRIPTORS

export function relativeContributionAggregateCycles(numPlayers, numBuildCycles = 1) {
  if (numPlayers === null || numBuildCycles === null || isNaN(numPlayers) || isNaN(numBuildCycles)) {
    return null
  }
  return numPlayers * numBuildCycles
}

export function relativeContribution(playerRCScoresById, playerEstimationAccuraciesById) {
  if (_scoresShouldBeAveraged(playerRCScoresById, playerEstimationAccuraciesById)) {
    return Math.round(avg(Array.from(playerRCScoresById.values())))
  }

  let highestAccuracyPlayerId
  let highestAccuracy = 0
  for (const [playerId, accuracy] of playerEstimationAccuraciesById.entries()) {
    if (accuracy > highestAccuracy) {
      highestAccuracy = accuracy
      highestAccuracyPlayerId = playerId
    }
  }

  return playerRCScoresById.get(highestAccuracyPlayerId)
}

function _scoresShouldBeAveraged(playerRCScoresById, playerEstimationAccuraciesById) {
  return (
    !playerEstimationAccuraciesById ||
    playerEstimationAccuraciesById.size === 0 ||
    playerEstimationAccuraciesById.size < playerRCScoresById.size ||
    _mapContainsFalseyValue(playerEstimationAccuraciesById) ||
    _mapValuesAreEqual(playerEstimationAccuraciesById)
  )
}

function _mapValuesAreEqual(map) {
  const values = Array.from(map.values())
  if (!values || values.length === 0) {
    return true
  }
  const first = values[0]
  return values.every(value => first === value)
}

function _mapContainsFalseyValue(map) {
  const values = Array.from(map.values())
  return !values.every(value => Boolean(value))
}

export function relativeContributionExpected(playerHours, teamHours) {
  if (playerHours === null || isNaN(playerHours) || isNaN(teamHours)) {
    return null
  }
  if (teamHours === 0) {
    return 0
  }
  return Math.round(toPercent(playerHours / teamHours))
}

export function relativeContributionDelta(relativeContributionExpected, relativeContribution) {
  if (relativeContributionExpected === null || relativeContribution === null || isNaN(relativeContributionExpected) || isNaN(relativeContribution)) {
    return null
  }
  return relativeContribution - relativeContributionExpected
}

export function relativeContributionEffectiveCycles(relativeContributionAggregateCycles, relativeContribution) {
  if (relativeContributionAggregateCycles === null || relativeContribution === null || isNaN(relativeContributionAggregateCycles) || isNaN(relativeContribution)) {
    return null
  }
  return relativeContributionAggregateCycles * relativeContribution
}

export const technicalHealth = likert7Average
export const cultureContribution = likert7Average
export const cultureContributionStructure = likert7Average
export const cultureContributionSafety = likert7Average
export const cultureContributionTruth = likert7Average
export const cultureContributionChallenge = likert7Average
export const cultureContributionSupport = likert7Average
export const cultureContributionEngagement = likert7Average
export const cultureContributionEnjoyment = likert7Average
export const teamPlay = likert7Average
export const teamPlayReceptiveness = likert7Average
export const teamPlayFlexibleLeadership = likert7Average
export const teamPlayResultsFocus = likert7Average
export const teamPlayFrictionReduction = likert7Average

export function likert7Average(scores) {
  return averageScoreInRange(LIKERT_SCORE_MIN, LIKERT_SCORE_MAX, scores)
}

export function averageScoreInRange(minScore, maxScore, scores) {
  if (isNaN(minScore)) {
    throw new LGBadRequestError('Invalid score range min')
  }
  if (isNaN(maxScore)) {
    throw new LGBadRequestError('Invalid score range max')
  }
  if (minScore > maxScore) {
    throw new LGBadRequestError('Min score must be less than or equal to max score')
  }
  if (!Array.isArray(scores)) {
    return null
  }
  // exclude scores outside of valid range
  // shift score values down by min to produce range 0...n
  const adjusted = scores
                    .filter(n => (n >= minScore && n <= maxScore))
                    .map(n => n - minScore)
  const adjustedAvg = avg(adjusted)
  const range = maxScore - minScore
  return Math.round(toPercent(adjustedAvg / range))
}

export function experiencePoints(teamHours, relativeContribution) {
  return roundDecimal(teamHours * (relativeContribution / 100))
}

/**
 * params:
 *   players (obj array) -> [playerA, playerB]
 *     - rating
 *     - score (game score)
 *     - kFactor
 *
 * returns:
 *   result (int array) -> [newRatingA, newRatingB]
 */
export function eloRatings([playerA, playerB]) {
  _validatePlayer(playerA)
  _validatePlayer(playerB)

  const {rating: ratingA, score: scoreA, kFactor: kFactorA} = playerA
  const {rating: ratingB, score: scoreB, kFactor: kFactorB} = playerB

  const eloA = elo(kFactorA)
  const eloB = elo(kFactorB)

  const expectedMarginA = eloA.getExpected(ratingA, ratingB)
  const expectedMarginB = eloB.getExpected(ratingB, ratingA)

  const [actualMarginA, actualMarginB] = scoreMargins([scoreA, scoreB])

  const newRatingA = eloA.updateRating(expectedMarginA, actualMarginA, ratingA)
  const newRatingB = eloB.updateRating(expectedMarginB, actualMarginB, ratingB)

  return [newRatingA, newRatingB]
}

export function scoreMargins([scoreA, scoreB]) {
  if (scoreA === 0 && scoreB === 0) {
    return [0, 0]
  }

  return [
    roundDecimal(_applyStretchFactor(scoreA / (scoreA + scoreB))),
    roundDecimal(_applyStretchFactor(scoreB / (scoreB + scoreA))),
  ]
}

const STRETCH_FACTOR = 3
function _applyStretchFactor(unstretchedScore) {
  const stretchedScore = ((unstretchedScore - 0.5) * STRETCH_FACTOR) + 0.5
  if (stretchedScore > 1) {
    return 1
  }
  if (stretchedScore < 0) {
    return 0
  }

  return stretchedScore
}

// see: https://playbook.learnersguild.org/Game_Manual/Levels_and_Roles.html#level-requirements
export const LEVELS = [{
  [LEVEL]: 0,
  requirements: {
    [EXPERIENCE_POINTS]: 0,
    [ELO]: 0,
    [ESTIMATION_ACCURACY]: 0,
  },
}, {
  [LEVEL]: 1,
  requirements: {
    [EXPERIENCE_POINTS]: 0,
    [ELO]: 900,
    [ESTIMATION_ACCURACY]: 70,
  },
}, {
  [LEVEL]: 2,
  requirements: {
    [EXPERIENCE_POINTS]: 150,
    [ELO]: 990,
    [ESTIMATION_ACCURACY]: 91,
  },
}, {
  [LEVEL]: 3,
  requirements: {
    [EXPERIENCE_POINTS]: 400,
    [ELO]: 1020,
    [ESTIMATION_ACCURACY]: 92,
  },
}, {
  [LEVEL]: 4,
  requirements: {
    [EXPERIENCE_POINTS]: 600,
    [ELO]: 1050,
    [ESTIMATION_ACCURACY]: 93,
  }
}, {
  [LEVEL]: 5,
  requirements: {
    [EXPERIENCE_POINTS]: 800,
    [ELO]: 1150,
    [ESTIMATION_ACCURACY]: 94,
  },
}]

export function computePlayerLevel(player) {
  const stats = _playerLevelStats(player)

  const levelsDescending = LEVELS.slice().reverse()
  for (const {level, requirements} of levelsDescending) {
    const playerMeetsRequirements = Object.keys(requirements).every(stat => stats[stat] >= requirements[stat])
    if (playerMeetsRequirements) {
      return level
    }
  }

  throw new LGBadRequestError(`Could not place this player in ANY level! ${player.id}`)
}

export const floatStatFormatter = value => parseFloat(Number(value).toFixed(2))
export const intStatFormatter = value => parseInt(value, 10)
export function getPlayerStat(player, statName, formatter = floatStatFormatter) {
  const statParts = statName.split('.')
  const statValue = statParts.reduce((statValue, statPart, i) => {
    if (i === statParts.length - 1) {
      return statValue[statPart] || 0
    }
    return statValue[statPart] || {}
  }, player.stats || {})

  return formatter(statValue)
}

function _playerLevelStats(player) {
  return {
    [ELO]: getPlayerStat(player, 'elo.rating', intStatFormatter),
    [EXPERIENCE_POINTS]: getPlayerStat(player, EXPERIENCE_POINTS, intStatFormatter),
    [CULTURE_CONTRIBUTION]: getPlayerStat(player, `weightedAverages.${CULTURE_CONTRIBUTION}`),
    [TEAM_PLAY]: getPlayerStat(player, `weightedAverages.${TEAM_PLAY}`),
    [TECHNICAL_HEALTH]: getPlayerStat(player, `weightedAverages.${TECHNICAL_HEALTH}`),
    [ESTIMATION_ACCURACY]: getPlayerStat(player, `weightedAverages.${ESTIMATION_ACCURACY}`),
  }
}

function _validatePlayer(player) {
  if (!player) {
    throw new LGBadRequestError('Invalid player object')
  }
  if (isNaN(player.rating)) {
    throw new LGBadRequestError('Invalid player rating')
  }
  if (isNaN(player.score)) {
    throw new LGBadRequestError('Invalid player score')
  }
  if (isNaN(player.kFactor)) {
    throw new LGBadRequestError('Invalid player kFactor')
  }
}

export function findValueForReponseQuestionStat(responseArr, statDescriptor) {
  if (!Array.isArray(responseArr) || !statDescriptor) {
    return
  }
  return (responseArr.find(response => (
    ((response.question || {}).stat || {}).descriptor === statDescriptor
  )) || {}).value
}

export function calculateProjectReviewStats(project, projectReviews) {
  const isExternal = review => !project.playerIds.includes(review.player.id)

  const mostAccurateExternalReview = projectReviews
    .filter(isExternal)
    .sort(_compareByMostExperiencedReviewer)[0]

  return mostAccurateExternalReview ?
    mostAccurateExternalReview.responses :
    {[PROJECT_QUALITY]: null, [PROJECT_COMPLETENESS]: null}
}

function _compareByMostExperiencedReviewer(a, b) {
  return (
    b.player.stats[PROJECT_REVIEW_EXPERIENCE] - a.player.stats[PROJECT_REVIEW_EXPERIENCE] ||
    b.player.stats[PROJECT_REVIEW_ACCURACY] - a.player.stats[PROJECT_REVIEW_ACCURACY] ||
    b.player.id.localeCompare(a.player.id)
  )
}

export function calculateProjectReviewStatsForPlayer(player, projectReviewInfoList) {
  const statNames = [PROJECT_COMPLETENESS, PROJECT_QUALITY]
  const isExternal = reviewInfo => !reviewInfo.project.playerIds.includes(player.id)
  const externalReviewInfoList = projectReviewInfoList.filter(isExternal)
  const compareClosedAt = attrCompareFn('closedAt')
  const recentExternalReviewInfoList = externalReviewInfoList
    .sort(({project: a}, {project: b}) => compareClosedAt(a, b))
    .reverse()
    .slice(0, RELEVANT_EXTERNAL_REVIEW_COUNT)

  const baseline = player.statsBaseline || {}
  const internalCountBaseline = baseline[INTERNAL_PROJECT_REVIEW_COUNT] || 0
  const externalCountBaseline = baseline[EXTERNAL_PROJECT_REVIEW_COUNT] || 0
  const reviewAccuracyBaseline = baseline[PROJECT_REVIEW_ACCURACY] || 0

  const stats = {}
  stats[EXTERNAL_PROJECT_REVIEW_COUNT] = externalReviewInfoList.length + externalCountBaseline
  stats[INTERNAL_PROJECT_REVIEW_COUNT] = (projectReviewInfoList.length - externalReviewInfoList.length) + internalCountBaseline

  if (stats[EXTERNAL_PROJECT_REVIEW_COUNT] >= MIN_EXTERNAL_REVIEW_COUNT_FOR_ACCURACY) {
    const externalReviewAccuracies =
      recentExternalReviewInfoList.map(({project, projectReviews}) => {
        const thisPlayersReview = projectReviews.find(_ => _.player.id === player.id)
        const statDeltas = statNames.map(stat => Math.abs(thisPlayersReview.responses[stat] - project.stats[stat]))
        return avg(statDeltas)
      })
      .map(delta => 100 - delta)

    stats[PROJECT_REVIEW_ACCURACY] = avg([
      ...externalReviewAccuracies,
      ...range(1, externalCountBaseline).map(_ => reviewAccuracyBaseline)
    ])
  } else {
    stats[PROJECT_REVIEW_ACCURACY] = (((player.stats || {})[ELO] || {}).rating || 0) / 100
  }

  stats[PROJECT_REVIEW_EXPERIENCE] = stats[PROJECT_REVIEW_ACCURACY] + (stats[EXTERNAL_PROJECT_REVIEW_COUNT] / 20)

  return stats
}
<commit_msg>consider only 20 recent ext. reviews when calculating review accuracy<commit_after>import elo from 'elo-rank'

import {LGBadRequestError} from 'src/server/util/error'

import {
  avg,
  toPercent,
  roundDecimal,
  range,
  attrCompareFn,
} from 'src/common/util'
import {
  STAT_DESCRIPTORS,
  RELEVANT_EXTERNAL_REVIEW_COUNT,
  MIN_EXTERNAL_REVIEW_COUNT_FOR_ACCURACY,
} from 'src/common/models/stat'

export const LIKERT_SCORE_NA = 0
export const LIKERT_SCORE_MIN = 1
export const LIKERT_SCORE_MAX = 7

const {
  CULTURE_CONTRIBUTION,
  ESTIMATION_ACCURACY,
  EXPERIENCE_POINTS,
  ELO,
  LEVEL,
  TEAM_PLAY,
  TECHNICAL_HEALTH,
  PROJECT_REVIEW_EXPERIENCE,
  PROJECT_REVIEW_ACCURACY,
  EXTERNAL_PROJECT_REVIEW_COUNT,
  INTERNAL_PROJECT_REVIEW_COUNT,
  PROJECT_QUALITY,
  PROJECT_COMPLETENESS,
} = STAT_DESCRIPTORS

export function relativeContributionAggregateCycles(numPlayers, numBuildCycles = 1) {
  if (numPlayers === null || numBuildCycles === null || isNaN(numPlayers) || isNaN(numBuildCycles)) {
    return null
  }
  return numPlayers * numBuildCycles
}

export function relativeContribution(playerRCScoresById, playerEstimationAccuraciesById) {
  if (_scoresShouldBeAveraged(playerRCScoresById, playerEstimationAccuraciesById)) {
    return Math.round(avg(Array.from(playerRCScoresById.values())))
  }

  let highestAccuracyPlayerId
  let highestAccuracy = 0
  for (const [playerId, accuracy] of playerEstimationAccuraciesById.entries()) {
    if (accuracy > highestAccuracy) {
      highestAccuracy = accuracy
      highestAccuracyPlayerId = playerId
    }
  }

  return playerRCScoresById.get(highestAccuracyPlayerId)
}

function _scoresShouldBeAveraged(playerRCScoresById, playerEstimationAccuraciesById) {
  return (
    !playerEstimationAccuraciesById ||
    playerEstimationAccuraciesById.size === 0 ||
    playerEstimationAccuraciesById.size < playerRCScoresById.size ||
    _mapContainsFalseyValue(playerEstimationAccuraciesById) ||
    _mapValuesAreEqual(playerEstimationAccuraciesById)
  )
}

function _mapValuesAreEqual(map) {
  const values = Array.from(map.values())
  if (!values || values.length === 0) {
    return true
  }
  const first = values[0]
  return values.every(value => first === value)
}

function _mapContainsFalseyValue(map) {
  const values = Array.from(map.values())
  return !values.every(value => Boolean(value))
}

export function relativeContributionExpected(playerHours, teamHours) {
  if (playerHours === null || isNaN(playerHours) || isNaN(teamHours)) {
    return null
  }
  if (teamHours === 0) {
    return 0
  }
  return Math.round(toPercent(playerHours / teamHours))
}

export function relativeContributionDelta(relativeContributionExpected, relativeContribution) {
  if (relativeContributionExpected === null || relativeContribution === null || isNaN(relativeContributionExpected) || isNaN(relativeContribution)) {
    return null
  }
  return relativeContribution - relativeContributionExpected
}

export function relativeContributionEffectiveCycles(relativeContributionAggregateCycles, relativeContribution) {
  if (relativeContributionAggregateCycles === null || relativeContribution === null || isNaN(relativeContributionAggregateCycles) || isNaN(relativeContribution)) {
    return null
  }
  return relativeContributionAggregateCycles * relativeContribution
}

export const technicalHealth = likert7Average
export const cultureContribution = likert7Average
export const cultureContributionStructure = likert7Average
export const cultureContributionSafety = likert7Average
export const cultureContributionTruth = likert7Average
export const cultureContributionChallenge = likert7Average
export const cultureContributionSupport = likert7Average
export const cultureContributionEngagement = likert7Average
export const cultureContributionEnjoyment = likert7Average
export const teamPlay = likert7Average
export const teamPlayReceptiveness = likert7Average
export const teamPlayFlexibleLeadership = likert7Average
export const teamPlayResultsFocus = likert7Average
export const teamPlayFrictionReduction = likert7Average

export function likert7Average(scores) {
  return averageScoreInRange(LIKERT_SCORE_MIN, LIKERT_SCORE_MAX, scores)
}

export function averageScoreInRange(minScore, maxScore, scores) {
  if (isNaN(minScore)) {
    throw new LGBadRequestError('Invalid score range min')
  }
  if (isNaN(maxScore)) {
    throw new LGBadRequestError('Invalid score range max')
  }
  if (minScore > maxScore) {
    throw new LGBadRequestError('Min score must be less than or equal to max score')
  }
  if (!Array.isArray(scores)) {
    return null
  }
  // exclude scores outside of valid range
  // shift score values down by min to produce range 0...n
  const adjusted = scores
                    .filter(n => (n >= minScore && n <= maxScore))
                    .map(n => n - minScore)
  const adjustedAvg = avg(adjusted)
  const range = maxScore - minScore
  return Math.round(toPercent(adjustedAvg / range))
}

export function experiencePoints(teamHours, relativeContribution) {
  return roundDecimal(teamHours * (relativeContribution / 100))
}

/**
 * params:
 *   players (obj array) -> [playerA, playerB]
 *     - rating
 *     - score (game score)
 *     - kFactor
 *
 * returns:
 *   result (int array) -> [newRatingA, newRatingB]
 */
export function eloRatings([playerA, playerB]) {
  _validatePlayer(playerA)
  _validatePlayer(playerB)

  const {rating: ratingA, score: scoreA, kFactor: kFactorA} = playerA
  const {rating: ratingB, score: scoreB, kFactor: kFactorB} = playerB

  const eloA = elo(kFactorA)
  const eloB = elo(kFactorB)

  const expectedMarginA = eloA.getExpected(ratingA, ratingB)
  const expectedMarginB = eloB.getExpected(ratingB, ratingA)

  const [actualMarginA, actualMarginB] = scoreMargins([scoreA, scoreB])

  const newRatingA = eloA.updateRating(expectedMarginA, actualMarginA, ratingA)
  const newRatingB = eloB.updateRating(expectedMarginB, actualMarginB, ratingB)

  return [newRatingA, newRatingB]
}

export function scoreMargins([scoreA, scoreB]) {
  if (scoreA === 0 && scoreB === 0) {
    return [0, 0]
  }

  return [
    roundDecimal(_applyStretchFactor(scoreA / (scoreA + scoreB))),
    roundDecimal(_applyStretchFactor(scoreB / (scoreB + scoreA))),
  ]
}

const STRETCH_FACTOR = 3
function _applyStretchFactor(unstretchedScore) {
  const stretchedScore = ((unstretchedScore - 0.5) * STRETCH_FACTOR) + 0.5
  if (stretchedScore > 1) {
    return 1
  }
  if (stretchedScore < 0) {
    return 0
  }

  return stretchedScore
}

// see: https://playbook.learnersguild.org/Game_Manual/Levels_and_Roles.html#level-requirements
export const LEVELS = [{
  [LEVEL]: 0,
  requirements: {
    [EXPERIENCE_POINTS]: 0,
    [ELO]: 0,
    [ESTIMATION_ACCURACY]: 0,
  },
}, {
  [LEVEL]: 1,
  requirements: {
    [EXPERIENCE_POINTS]: 0,
    [ELO]: 900,
    [ESTIMATION_ACCURACY]: 70,
  },
}, {
  [LEVEL]: 2,
  requirements: {
    [EXPERIENCE_POINTS]: 150,
    [ELO]: 990,
    [ESTIMATION_ACCURACY]: 91,
  },
}, {
  [LEVEL]: 3,
  requirements: {
    [EXPERIENCE_POINTS]: 400,
    [ELO]: 1020,
    [ESTIMATION_ACCURACY]: 92,
  },
}, {
  [LEVEL]: 4,
  requirements: {
    [EXPERIENCE_POINTS]: 600,
    [ELO]: 1050,
    [ESTIMATION_ACCURACY]: 93,
  }
}, {
  [LEVEL]: 5,
  requirements: {
    [EXPERIENCE_POINTS]: 800,
    [ELO]: 1150,
    [ESTIMATION_ACCURACY]: 94,
  },
}]

export function computePlayerLevel(player) {
  const stats = _playerLevelStats(player)

  const levelsDescending = LEVELS.slice().reverse()
  for (const {level, requirements} of levelsDescending) {
    const playerMeetsRequirements = Object.keys(requirements).every(stat => stats[stat] >= requirements[stat])
    if (playerMeetsRequirements) {
      return level
    }
  }

  throw new LGBadRequestError(`Could not place this player in ANY level! ${player.id}`)
}

export const floatStatFormatter = value => parseFloat(Number(value).toFixed(2))
export const intStatFormatter = value => parseInt(value, 10)
export function getPlayerStat(player, statName, formatter = floatStatFormatter) {
  const statParts = statName.split('.')
  const statValue = statParts.reduce((statValue, statPart, i) => {
    if (i === statParts.length - 1) {
      return statValue[statPart] || 0
    }
    return statValue[statPart] || {}
  }, player.stats || {})

  return formatter(statValue)
}

function _playerLevelStats(player) {
  return {
    [ELO]: getPlayerStat(player, 'elo.rating', intStatFormatter),
    [EXPERIENCE_POINTS]: getPlayerStat(player, EXPERIENCE_POINTS, intStatFormatter),
    [CULTURE_CONTRIBUTION]: getPlayerStat(player, `weightedAverages.${CULTURE_CONTRIBUTION}`),
    [TEAM_PLAY]: getPlayerStat(player, `weightedAverages.${TEAM_PLAY}`),
    [TECHNICAL_HEALTH]: getPlayerStat(player, `weightedAverages.${TECHNICAL_HEALTH}`),
    [ESTIMATION_ACCURACY]: getPlayerStat(player, `weightedAverages.${ESTIMATION_ACCURACY}`),
  }
}

function _validatePlayer(player) {
  if (!player) {
    throw new LGBadRequestError('Invalid player object')
  }
  if (isNaN(player.rating)) {
    throw new LGBadRequestError('Invalid player rating')
  }
  if (isNaN(player.score)) {
    throw new LGBadRequestError('Invalid player score')
  }
  if (isNaN(player.kFactor)) {
    throw new LGBadRequestError('Invalid player kFactor')
  }
}

export function findValueForReponseQuestionStat(responseArr, statDescriptor) {
  if (!Array.isArray(responseArr) || !statDescriptor) {
    return
  }
  return (responseArr.find(response => (
    ((response.question || {}).stat || {}).descriptor === statDescriptor
  )) || {}).value
}

export function calculateProjectReviewStats(project, projectReviews) {
  const isExternal = review => !project.playerIds.includes(review.player.id)

  const mostAccurateExternalReview = projectReviews
    .filter(isExternal)
    .sort(_compareByMostExperiencedReviewer)[0]

  return mostAccurateExternalReview ?
    mostAccurateExternalReview.responses :
    {[PROJECT_QUALITY]: null, [PROJECT_COMPLETENESS]: null}
}

function _compareByMostExperiencedReviewer(a, b) {
  return (
    b.player.stats[PROJECT_REVIEW_EXPERIENCE] - a.player.stats[PROJECT_REVIEW_EXPERIENCE] ||
    b.player.stats[PROJECT_REVIEW_ACCURACY] - a.player.stats[PROJECT_REVIEW_ACCURACY] ||
    b.player.id.localeCompare(a.player.id)
  )
}

export function calculateProjectReviewStatsForPlayer(player, projectReviewInfoList) {
  const statNames = [PROJECT_COMPLETENESS, PROJECT_QUALITY]
  const isExternal = reviewInfo => !reviewInfo.project.playerIds.includes(player.id)
  const externalReviewInfoList = projectReviewInfoList.filter(isExternal)
  const compareClosedAt = attrCompareFn('closedAt')
  const recentExternalReviewInfoList = externalReviewInfoList
    .sort(({project: a}, {project: b}) => compareClosedAt(a, b))
    .reverse()
    .slice(0, RELEVANT_EXTERNAL_REVIEW_COUNT)

  const baseline = player.statsBaseline || {}
  const internalCountBaseline = baseline[INTERNAL_PROJECT_REVIEW_COUNT] || 0
  const externalCountBaseline = baseline[EXTERNAL_PROJECT_REVIEW_COUNT] || 0
  const reviewAccuracyBaseline = baseline[PROJECT_REVIEW_ACCURACY] || 0

  const stats = {}
  stats[EXTERNAL_PROJECT_REVIEW_COUNT] = externalReviewInfoList.length + externalCountBaseline
  stats[INTERNAL_PROJECT_REVIEW_COUNT] = (projectReviewInfoList.length - externalReviewInfoList.length) + internalCountBaseline

  if (stats[EXTERNAL_PROJECT_REVIEW_COUNT] >= MIN_EXTERNAL_REVIEW_COUNT_FOR_ACCURACY) {
    const externalReviewAccuracies =
      recentExternalReviewInfoList.map(({project, projectReviews}) => {
        const thisPlayersReview = projectReviews.find(_ => _.player.id === player.id)
        const statDeltas = statNames.map(stat => Math.abs(thisPlayersReview.responses[stat] - project.stats[stat]))
        return avg(statDeltas)
      })
      .map(delta => 100 - delta)
    const consideredExternalReviewAccuracies = [
      ...externalReviewAccuracies,
      ...range(1, externalCountBaseline).map(_ => reviewAccuracyBaseline)
    ].slice(0, RELEVANT_EXTERNAL_REVIEW_COUNT)

    stats[PROJECT_REVIEW_ACCURACY] = avg(consideredExternalReviewAccuracies)
  } else {
    stats[PROJECT_REVIEW_ACCURACY] = (((player.stats || {})[ELO] || {}).rating || 0) / 100
  }

  stats[PROJECT_REVIEW_EXPERIENCE] = stats[PROJECT_REVIEW_ACCURACY] + (stats[EXTERNAL_PROJECT_REVIEW_COUNT] / 20)

  return stats
}
<|endoftext|>


<commit_before>const ldap = require('ldapjs');
const yapi = require('../yapi.js');
const util = require('util');

exports.ldapQuery = (username, password) => {
  // const deferred = Q.defer();

  return new Promise((resolve, reject) => {
    const { ldapLogin } = yapi.WEBCONFIG;

    //  使用ldapjs库创建一个LDAP客户端
    const client = ldap.createClient({
      url: ldapLogin.server
    });

    client.once('error', err => {
      if (err) {
        let msg = {
          type: false,
          message: `once: ${err}`
        };
        reject(msg);
      }
    });
    // 注册事件处理函数
    const ldapSearch = (err, search) => {
      const users = [];
      if (err) {
        let msg = {
          type: false,
          message: `ldapSearch: ${err}`
        };
        reject(msg);
      }
      // 查询结果事件响应
      search.on('searchEntry', entry => {
        if (entry) {
          // 获取查询对象
          users.push(entry.object);
        }
      });
      // 查询错误事件
      search.on('error', e => {
        if (e) {
          let msg = {
            type: false,
            message: `searchErr: ${e}`
          };
          reject(msg);
        }
      });

      search.on('searchReference', referral => {
        // if (referral) {
        //   let msg = {
        //     type: false,
        //     message: `searchReference: ${referral}`
        //   };
        //   reject(msg);
        // }
        console.log('referral: ' + referral.uris.join());
      });
      // 查询结束
      search.on('end', () => {
        if (users.length > 0) {
          client.bind(users[0].dn, password, e => {
            if (e) {
              let msg = {
                type: false,
                message: `用户名或密码不正确: ${e}`
              };
              reject(msg);
            } else {
              let msg = {
                type: true,
                message: `验证成功`,
                info: users[0]
              };
              resolve(msg);
            }
            client.unbind();
          });
        } else {
          let msg = {
            type: false,
            message: `用户名不存在`
          };
          reject(msg);
          client.unbind();
        }
      });
    };
    // 将client绑定LDAP Server
    // 第一个参数： 是用户，必须是从根结点到用户节点的全路径
    // 第二个参数： 用户密码
    return new Promise((resolve, reject) => {
      if (ldapLogin.bindPassword) {
        client.bind(ldapLogin.baseDn, ldapLogin.bindPassword, err => {
          if (err) {
            let msg = {
              type: false,
              message: `LDAP server绑定失败: ${err}`
            };
            reject(msg);
          }

          resolve();
        });
      } else {
        resolve();
      }
    }).then(() => {
      const searchDn = ldapLogin.searchDn;
      const searchStandard = ldapLogin.searchStandard;
      // 处理可以自定义filter
      let customFilter;
      if (/^&/gi.test(searchStandard)) {
        customFilter = util.format(searchStandard, username);
      } else {
        customFilter = `${searchStandard}=${username}`;
      }
      const opts = {
        // filter: `(${searchStandard}=${username})`,
        filter: `(${customFilter})`,
        scope: 'sub'
      };

      // 开始查询
      // 第一个参数： 查询基础路径，代表在查询用户信息将在这个路径下进行，该路径由根结点开始
      // 第二个参数： 查询选项
      client.search(searchDn, opts, ldapSearch);
    });
  });
};
<commit_msg>Update ldap.js<commit_after>const ldap = require('ldapjs');
const yapi = require('../yapi.js');
const util = require('util');

exports.ldapQuery = (username, password) => {
  // const deferred = Q.defer();

  return new Promise((resolve, reject) => {
    const { ldapLogin } = yapi.WEBCONFIG;

    //  使用ldapjs库创建一个LDAP客户端
    const client = ldap.createClient({
      url: ldapLogin.server
    });

    client.once('error', err => {
      if (err) {
        let msg = {
          type: false,
          message: `once: ${err}`
        };
        reject(msg);
      }
    });
    // 注册事件处理函数
    const ldapSearch = (err, search) => {
      const users = [];
      if (err) {
        let msg = {
          type: false,
          message: `ldapSearch: ${err}`
        };
        reject(msg);
      }
      // 查询结果事件响应
      search.on('searchEntry', entry => {
        if (entry) {
          // 获取查询对象
          users.push(entry.object);
        }
      });
      // 查询错误事件
      search.on('error', e => {
        if (e) {
          let msg = {
            type: false,
            message: `searchErr: ${e}`
          };
          reject(msg);
        }
      });

      search.on('searchReference', referral => {
        // if (referral) {
        //   let msg = {
        //     type: false,
        //     message: `searchReference: ${referral}`
        //   };
        //   reject(msg);
        // }
        console.log('referral: ' + referral.uris.join());
      });
      // 查询结束
      search.on('end', () => {
        if (users.length > 0) {
          client.bind(users[0].dn, password, e => {
            if (e) {
              let msg = {
                type: false,
                message: `用户名或密码不正确: ${e}`
              };
              reject(msg);
            } else {
              let msg = {
                type: true,
                message: `验证成功`,
                info: users[0]
              };
              resolve(msg);
            }
            client.unbind();
          });
        } else {
          let msg = {
            type: false,
            message: `用户名不存在`
          };
          reject(msg);
          client.unbind();
        }
      });
    };
    // 将client绑定LDAP Server
    // 第一个参数： 是用户，必须是从根结点到用户节点的全路径
    // 第二个参数： 用户密码
    return new Promise((resolve, reject) => {
      if (ldapLogin.bindPassword) {
        client.bind(ldapLogin.baseDn, ldapLogin.bindPassword, err => {
          if (err) {
            let msg = {
              type: false,
              message: `LDAP server绑定失败: ${err}`
            };
            reject(msg);
          }

          resolve();
        });
      } else {
        resolve();
      }
    }).then(() => {
      const searchDn = ldapLogin.searchDn;
      const searchStandard = ldapLogin.searchStandard;
      // 处理可以自定义filter
      let customFilter;
      if (/^(&|\|)/gi.test(searchStandard)) {
        customFilter = searchStandard.replace(/%s/g,username);
      } else {
        customFilter = `${searchStandard}=${username}`;
      }
      const opts = {
        // filter: `(${searchStandard}=${username})`,
        filter: `(${customFilter})`,
        scope: 'sub'
      };

      // 开始查询
      // 第一个参数： 查询基础路径，代表在查询用户信息将在这个路径下进行，该路径由根结点开始
      // 第二个参数： 查询选项
      client.search(searchDn, opts, ldapSearch);
    });
  });
};
<|endoftext|>


<commit_before>var fs = require('fs'),
    xml = require('xml-object-stream'),
    enums = require('../shared/enums'),
    _ = require('lodash'),
    moment = require('moment'),
    utils = require('../shared/utils')

exports.readPrograms = function (body, callback) {
  var stream = xml.parse(body)
  var programs = []

  stream.each('KUVAOHJELMA', function(xml) {
    programs.push(validateProgram(xml))
  })

  stream.on('end', function() {
    return callback(null, programs)
  })
}

var format = flatMap(requiredAttr('TYPE', 'type'), function(p) {
  var cls = _.curry(node)('LUOKITTELU', 'classification')
  if (p.type == '08') {
    return map(cls([and(required('PELIFORMAATTI'), valueInList('PELIFORMAATTI', enums.gameFormat, 'gameFormat'))]), function(p) {
      return {gameFormat: p.classification.gameFormat}
    })
  } else {
    return cls([and(required('FORMAATTI'), valueInList('FORMAATTI', enums.format, 'format'))])
  }
})

var validateProgram = map(compose([
  and(requiredAttr('TYPE', 'type'), function(xml) {
    var type = xml.$.TYPE
    if (_.has(enums.legacyProgramTypes, type)) return ok({ programType: enums.legacyProgramTypes[type] })
    else return error("Virheellinen attribuutti: TYPE")
  }),
  required('ASIAKKAANTUNNISTE', 'externalId'),
  required('ALKUPERAINENNIMI', 'name'),
  flatMap(requiredAttr('TYPE', 'type'), function(p) {
    var allButTvOrOther = ['01','02','03','04','06','07','08','10','11']
    if (_.contains(allButTvOrOther, p.type)) return required('SUOMALAINENNIMI', 'nameFi')
    else return optional('SUOMALAINENNIMI', 'nameFi')
  }),
  optional('RUOTSALAINENNIMI', 'nameSv'),
  optional('MUUNIMI', 'nameOther'),
  optional('JULKAISUVUOSI', 'year'),
  optional('VALMISTUMISVUOSI', 'year'),
  flatMap(optional('MAAT'), function(p) {
    var countries = p.MAAT ? p.MAAT.split(' ') : []
    return function(xml) {
      if (_.all(countries, _.curry(_.has)(enums.countries))) return ok({country: countries})
      else return error('Virheellinen MAAT kenttä: ' + countries)
    }
  }),
  map(childrenByNameTo('TUOTANTOYHTIO', 'productionCompanies'), function(p) { return { productionCompanies: p.productionCompanies.map(text) } }),
  required('SYNOPSIS', 'synopsis'),
  optional('TUOTANTOKAUSI', 'season'),
  optional('OSA', 'episode'),
  flatMap(requiredAttr('TYPE', 'type'), function(p) {
    return p.type == '03' ? required('ISANTAOHJELMA', 'parentTvSeriesName') : optional('ISANTAOHJELMA', 'parentTvSeriesName')
  }),
  map(compose([
    valuesInEnum('LAJIT', enums.legacyGenres),
    valuesInEnum('TELEVISIO-OHJELMALAJIT', enums.legacyTvGenres),
    valuesInList('PELINLAJIT', enums.legacyGameGenres)
  ]), function(p) {
    return { legacyGenre: p.LAJIT.concat(p['TELEVISIO-OHJELMALAJIT']).concat(p.PELINLAJIT) }
  }),
  map(childrenByNameTo('OHJAAJA', 'directors'), function(p) { return {directors: p.directors.map(fullname) }}),
  map(childrenByNameTo('NAYTTELIJA', 'actors'), function(p) { return {actors: p.actors.map(fullname) }}),
  map(required('LUOKITTELIJA', 'author'), function(p) { return { classification: { author: { name: p.author } } }}),
  format,
  node('LUOKITTELU', 'classification', [
    and(requiredAttr('REKISTEROINTIPAIVA', 'registrationDate'), function(xml) {
      var d = moment(xml.$.REKISTEROINTIPAIVA, "DD.MM.YYYY HH:mm:ss")
      if (d.isValid()) return ok({ registrationDate: d.toDate() })
      else return error("Virheellinen aikaformaatti: " + 'REKISTEROINTIPAIVA')
    }),
    and(required('KESTO'), test('KESTO', utils.isValidDuration, "Virheellinen kesto", 'duration')),
    flatMap(childrenByNameTo('VALITTUTERMI', 'criteria'), function(p) {
      var validCriteria = enums.classificationCriteria.map(function(x) { return x.id })
      var errors = _.flatten(p.criteria.map(function(c) {
        return and(requiredAttr('KRITEERI'), attrInList('KRITEERI', validCriteria))(c).errors
      }))
      if (errors.length > 0) return function() { return {program: {}, errors: errors } }
      var criteriaComments = _.object(p.criteria.map(function (c) {
        return [parseInt(c.$.KRITEERI), c.$.KOMMENTTI]
      }))
      return function () { return ok({safe: _.isEmpty(criteriaComments), criteria: _.keys(criteriaComments), criteriaComments: criteriaComments}) }
    })
  ])
]), function(p) { p.classifications = [p.classification]; delete p.classification; return p})

// validator = Xml -> Result
// validation = validator -> (program -> validator) -> validator
function flatMap(validator, f) {
  return function(xml) {
    var res = validator(xml)
    var res2 = f(res.program)(xml)
    var errors = _(res.errors.concat(res2.errors)).flatten().uniq().value()
    return {program: _.merge(_.cloneDeep(res.program), _.cloneDeep(res2.program)), errors: errors}
  }
}

function map(validator, f) {
  return function(xml) {
    var res = validator(xml)
    if (res.errors.length > 0) return res
    else return {program: f(res.program), errors: []}
  }
}

function mapError(v, f) {
  return function(xml) {
    var res = v(xml)
    if (res.errors.length > 0) return error([f(res.errors)])
    else return res
  }
}

function compose(xs) {
  return _.reduce(xs, function(acc, f) {
    return flatMap(acc, function(_) { return f })
  }, ret({}))
}

function and(v1, v2) {
  return function(xml) {
    var res = v1(xml)
    if (res.errors.length == 0) {
      return v2(xml)
    } else {
      return res
    }
  }
}

function or(v1, v2) {
  return function(xml) {
    var res = v1(xml)
    if (res.errors.length > 0) {
      return v2(xml)
    } else {
      return res
    }
  }
}

function ok(program) {
  return {program: program, errors: []}
}
function error(msg) {
  return {program: {}, errors: [msg]}
}

function ret(program) {
  return function(xml) {
    return {program: program, errors: []}
  }
}

function required(name, toField) {
  return function(xml) {
    toField = toField || name
    var val = xml[name]
    if (val && val.$text !== '' && val.$text !== undefined) return ok(utils.keyValue(toField, xml[name].$text))
    else return error(["Pakollinen kenttä puuttuu: " + name])
  }
}

function requiredNode(name, toField) {
  return function(xml) {
    toField = toField || name
    if (xml[name]) return ok(utils.keyValue(toField, xml[name].$text))
    else return error(["Pakollinen elementti puuttuu: " + name])
  }
}

function requiredAttr(name, toField) {
  return function(xml) {
    if (xml.$[name]) return ok(utils.keyValue(toField, xml.$[name]))
    else return error("Pakollinen attribuutti puuttuu: " + name)
  }
}

function optional(field, toField) {
  return function(xml) {
    toField = toField || field
    if (!xml[field] || !xml[field].$text || xml[field].$text.length == 0) return {program: utils.keyValue(toField, undefined), errors: []}
    else return {program: utils.keyValue(toField, xml[field].$text), errors: []}
  }
}

function childrenByNameTo(field, toField) {
  return function(xml) {
    return {
      program: utils.keyValue(toField, childrenByName(xml, field)),
      errors: []
    }
  }
}

function valuesInEnum(field, _enum) {
  return function(xml) {
    var values = optionListToArray(xml[field]).map(function(g) { return _enum[g] })
    if (_.all(values, function(v) { return v !== undefined })) return ok(utils.keyValue(field, values))
    else return error("Virheellinen kenttä " + field)
  }
}

function valuesInList(field, list) {
  return function(xml) {
    var values = optionListToArray(xml[field])
    var exists = values.map(_.curry(_.contains)(list))
    if (_.all(exists)) return ok(utils.keyValue(field, values))
    else return error("Virheellinen kenttä " + field)
  }
}

function valueInList(field, list, toField) {
  return function(xml) {
    toField = toField || field
    var value = xml[field].$text
    if (_.contains(list, value)) return ok(utils.keyValue(toField, value))
    else return error("Virheellinen kenttä " + field)
  }
}

function attrInList(attr, list) {
  return function(xml) {
    var value = xml.$[attr]
    if (_.contains(list.map(function(x) { return x.toString() }), value)) return ok(utils.keyValue(attr, value))
    else return error("Virheellinen arvo atribuutille " + attr + ": " + value)
  }
}

function node(name, toField, validators) {
  return flatMap(requiredNode(name, toField), function(p) {
    return function (xml) {
      return _.reduce(validators, function(acc, f) {
        return flatMap(acc, function(_) { return map(f, function(p) { return utils.keyValue(toField, p)}) })
      }, ret(p))(xml[name])
    }
  })
}

function test(field, f, msg, toField) {
  return function(xml) {
    var text = xml[field].$text
    if (f(text)) return ok(utils.keyValue(toField || field, text))
    else return error(msg + " " + text)
  }
}
function text(node) {
  return node.$text
}
function fullname(node) {
  return node.ETUNIMI.$text + ' ' + node.SUKUNIMI.$text
}
function childrenByName(root, name) {
  return root.$children.filter(function(e) { return e.$name == name })
}

function optionListToArray(field, sep) {
  sep = sep || ' '
  if (!field || field.$text.length == 0) return []
  var arr = field.$text.split(sep).map(function(s) { return s.replace(/[\^\s]/g, '')} )
  return _(arr).compact().uniq().value()
}

<commit_msg>Parse KRITEERI as int before validating<commit_after>var fs = require('fs'),
    xml = require('xml-object-stream'),
    enums = require('../shared/enums'),
    _ = require('lodash'),
    moment = require('moment'),
    utils = require('../shared/utils')

exports.readPrograms = function (body, callback) {
  var stream = xml.parse(body)
  var programs = []

  stream.each('KUVAOHJELMA', function(xml) {
    programs.push(validateProgram(xml))
  })

  stream.on('end', function() {
    return callback(null, programs)
  })
}

var format = flatMap(requiredAttr('TYPE', 'type'), function(p) {
  var cls = _.curry(node)('LUOKITTELU', 'classification')
  if (p.type == '08') {
    return map(cls([and(required('PELIFORMAATTI'), valueInList('PELIFORMAATTI', enums.gameFormat, 'gameFormat'))]), function(p) {
      return {gameFormat: p.classification.gameFormat}
    })
  } else {
    return cls([and(required('FORMAATTI'), valueInList('FORMAATTI', enums.format, 'format'))])
  }
})

var validateProgram = map(compose([
  and(requiredAttr('TYPE', 'type'), function(xml) {
    var type = xml.$.TYPE
    if (_.has(enums.legacyProgramTypes, type)) return ok({ programType: enums.legacyProgramTypes[type] })
    else return error("Virheellinen attribuutti: TYPE")
  }),
  required('ASIAKKAANTUNNISTE', 'externalId'),
  required('ALKUPERAINENNIMI', 'name'),
  flatMap(requiredAttr('TYPE', 'type'), function(p) {
    var allButTvOrOther = ['01','02','03','04','06','07','08','10','11']
    if (_.contains(allButTvOrOther, p.type)) return required('SUOMALAINENNIMI', 'nameFi')
    else return optional('SUOMALAINENNIMI', 'nameFi')
  }),
  optional('RUOTSALAINENNIMI', 'nameSv'),
  optional('MUUNIMI', 'nameOther'),
  optional('JULKAISUVUOSI', 'year'),
  optional('VALMISTUMISVUOSI', 'year'),
  flatMap(optional('MAAT'), function(p) {
    var countries = p.MAAT ? p.MAAT.split(' ') : []
    return function(xml) {
      if (_.all(countries, _.curry(_.has)(enums.countries))) return ok({country: countries})
      else return error('Virheellinen MAAT kenttä: ' + countries)
    }
  }),
  map(childrenByNameTo('TUOTANTOYHTIO', 'productionCompanies'), function(p) { return { productionCompanies: p.productionCompanies.map(text) } }),
  required('SYNOPSIS', 'synopsis'),
  optional('TUOTANTOKAUSI', 'season'),
  optional('OSA', 'episode'),
  flatMap(requiredAttr('TYPE', 'type'), function(p) {
    return p.type == '03' ? required('ISANTAOHJELMA', 'parentTvSeriesName') : optional('ISANTAOHJELMA', 'parentTvSeriesName')
  }),
  map(compose([
    valuesInEnum('LAJIT', enums.legacyGenres),
    valuesInEnum('TELEVISIO-OHJELMALAJIT', enums.legacyTvGenres),
    valuesInList('PELINLAJIT', enums.legacyGameGenres)
  ]), function(p) {
    return { legacyGenre: p.LAJIT.concat(p['TELEVISIO-OHJELMALAJIT']).concat(p.PELINLAJIT) }
  }),
  map(childrenByNameTo('OHJAAJA', 'directors'), function(p) { return {directors: p.directors.map(fullname) }}),
  map(childrenByNameTo('NAYTTELIJA', 'actors'), function(p) { return {actors: p.actors.map(fullname) }}),
  map(required('LUOKITTELIJA', 'author'), function(p) { return { classification: { author: { name: p.author } } }}),
  format,
  node('LUOKITTELU', 'classification', [
    and(requiredAttr('REKISTEROINTIPAIVA', 'registrationDate'), function(xml) {
      var d = moment(xml.$.REKISTEROINTIPAIVA, "DD.MM.YYYY HH:mm:ss")
      if (d.isValid()) return ok({ registrationDate: d.toDate() })
      else return error("Virheellinen aikaformaatti: " + 'REKISTEROINTIPAIVA')
    }),
    and(required('KESTO'), test('KESTO', utils.isValidDuration, "Virheellinen kesto", 'duration')),
    flatMap(childrenByNameTo('VALITTUTERMI', 'criteria'), function(p) {
      var validCriteria = enums.classificationCriteria.map(function(x) { return x.id })
      var errors = _.flatten(p.criteria.map(function(c) {
        return and(requiredAttr('KRITEERI'), function(xml) {
          var criteria = parseInt(xml.$.KRITEERI)
          if (_.contains(validCriteria, criteria)) return ok({})
          else return error('Virheellinen attribuutti KRITEERI ' + criteria)
        })(c).errors
      }))
      if (errors.length > 0) return function() { return {program: {}, errors: errors } }
      var criteriaComments = _.object(p.criteria.map(function (c) {
        return [parseInt(c.$.KRITEERI), c.$.KOMMENTTI]
      }))
      return function () { return ok({safe: _.isEmpty(criteriaComments), criteria: _.keys(criteriaComments), criteriaComments: criteriaComments}) }
    })
  ])
]), function(p) { p.classifications = [p.classification]; delete p.classification; return p})

// validator = Xml -> Result
// validation = validator -> (program -> validator) -> validator
function flatMap(validator, f) {
  return function(xml) {
    var res = validator(xml)
    var res2 = f(res.program)(xml)
    var errors = _(res.errors.concat(res2.errors)).flatten().uniq().value()
    return {program: _.merge(_.cloneDeep(res.program), _.cloneDeep(res2.program)), errors: errors}
  }
}

function map(validator, f) {
  return function(xml) {
    var res = validator(xml)
    if (res.errors.length > 0) return res
    else return {program: f(res.program), errors: []}
  }
}

function mapError(v, f) {
  return function(xml) {
    var res = v(xml)
    if (res.errors.length > 0) return error([f(res.errors)])
    else return res
  }
}

function compose(xs) {
  return _.reduce(xs, function(acc, f) {
    return flatMap(acc, function(_) { return f })
  }, ret({}))
}

function and(v1, v2) {
  return function(xml) {
    var res = v1(xml)
    if (res.errors.length == 0) {
      return v2(xml)
    } else {
      return res
    }
  }
}

function or(v1, v2) {
  return function(xml) {
    var res = v1(xml)
    if (res.errors.length > 0) {
      return v2(xml)
    } else {
      return res
    }
  }
}

function ok(program) {
  return {program: program, errors: []}
}
function error(msg) {
  return {program: {}, errors: [msg]}
}

function ret(program) {
  return function(xml) {
    return {program: program, errors: []}
  }
}

function required(name, toField) {
  return function(xml) {
    toField = toField || name
    var val = xml[name]
    if (val && val.$text !== '' && val.$text !== undefined) return ok(utils.keyValue(toField, xml[name].$text))
    else return error(["Pakollinen kenttä puuttuu: " + name])
  }
}

function requiredNode(name, toField) {
  return function(xml) {
    toField = toField || name
    if (xml[name]) return ok(utils.keyValue(toField, xml[name].$text))
    else return error(["Pakollinen elementti puuttuu: " + name])
  }
}

function requiredAttr(name, toField) {
  return function(xml) {
    if (xml.$[name]) return ok(utils.keyValue(toField, xml.$[name]))
    else return error("Pakollinen attribuutti puuttuu: " + name)
  }
}

function optional(field, toField) {
  return function(xml) {
    toField = toField || field
    if (!xml[field] || !xml[field].$text || xml[field].$text.length == 0) return {program: utils.keyValue(toField, undefined), errors: []}
    else return {program: utils.keyValue(toField, xml[field].$text), errors: []}
  }
}

function childrenByNameTo(field, toField) {
  return function(xml) {
    return {
      program: utils.keyValue(toField, childrenByName(xml, field)),
      errors: []
    }
  }
}

function valuesInEnum(field, _enum) {
  return function(xml) {
    var values = optionListToArray(xml[field]).map(function(g) { return _enum[g] })
    if (_.all(values, function(v) { return v !== undefined })) return ok(utils.keyValue(field, values))
    else return error("Virheellinen kenttä " + field)
  }
}

function valuesInList(field, list) {
  return function(xml) {
    var values = optionListToArray(xml[field])
    var exists = values.map(_.curry(_.contains)(list))
    if (_.all(exists)) return ok(utils.keyValue(field, values))
    else return error("Virheellinen kenttä " + field)
  }
}

function valueInList(field, list, toField) {
  return function(xml) {
    toField = toField || field
    var value = xml[field].$text
    if (_.contains(list, value)) return ok(utils.keyValue(toField, value))
    else return error("Virheellinen kenttä " + field)
  }
}

function attrInList(attr, list) {
  return function(xml) {
    var value = xml.$[attr]
    if (_.contains(list.map(function(x) { return x.toString() }), value)) return ok(utils.keyValue(attr, value))
    else return error("Virheellinen arvo atribuutille " + attr + ": " + value)
  }
}

function node(name, toField, validators) {
  return flatMap(requiredNode(name, toField), function(p) {
    return function (xml) {
      return _.reduce(validators, function(acc, f) {
        return flatMap(acc, function(_) { return map(f, function(p) { return utils.keyValue(toField, p)}) })
      }, ret(p))(xml[name])
    }
  })
}

function test(field, f, msg, toField) {
  return function(xml) {
    var text = xml[field].$text
    if (f(text)) return ok(utils.keyValue(toField || field, text))
    else return error(msg + " " + text)
  }
}
function text(node) {
  return node.$text
}
function fullname(node) {
  return node.ETUNIMI.$text + ' ' + node.SUKUNIMI.$text
}
function childrenByName(root, name) {
  return root.$children.filter(function(e) { return e.$name == name })
}

function optionListToArray(field, sep) {
  sep = sep || ' '
  if (!field || field.$text.length == 0) return []
  var arr = field.$text.split(sep).map(function(s) { return s.replace(/[\^\s]/g, '')} )
  return _(arr).compact().uniq().value()
}

<|endoftext|>


<commit_before>/**
 * @file 数据类
 * @author errorrik(errorrik@gmail.com)
 */

var ExprType = require('../parser/expr-type');
var evalExpr = require('./eval-expr');
var DataChangeType = require('./data-change-type');
var createAccessor = require('../parser/create-accessor');
var parseExpr = require('../parser/parse-expr');
var guid = require('../util/guid');
var dataCache = require('./data-cache');

/**
 * 数据类
 *
 * @class
 * @param {Object?} data 初始数据
 * @param {Model?} parent 父级数据容器
 */
function Data(data, parent) {
    this.id = guid();
    this.parent = parent;
    this.raw = data || {};
    this.listeners = [];
}

// #[begin] error
// 以下两个函数只在开发模式下可用，在生产模式下不存在
/**
 * DataTypes 检测
 */
Data.prototype.checkDataTypes = function () {
    if (this.typeChecker) {
        this.typeChecker(this.raw);
    }
};

/**
 * 设置 type checker
 *
 * @param  {Function} typeChecker 类型校验器
 */
Data.prototype.setTypeChecker = function (typeChecker) {
    this.typeChecker = typeChecker;
};

// #[end]

/**
 * 添加数据变更的事件监听器
 *
 * @param {Function} listener 监听函数
 */
Data.prototype.listen = function (listener) {
    if (typeof listener === 'function') {
        this.listeners.push(listener);
    }
};

/**
 * 移除数据变更的事件监听器
 *
 * @param {Function} listener 监听函数
 */
Data.prototype.unlisten = function (listener) {
    var len = this.listeners.length;
    while (len--) {
        if (!listener || this.listeners[len] === listener) {
            this.listeners.splice(len, 1);
        }
    }
};

/**
 * 触发数据变更
 *
 * @param {Object} change 变更信息对象
 */
Data.prototype.fire = function (change) {
    if (change.option.silent || change.option.silence || change.option.quiet) {
        return;
    }

    for (var i = 0; i < this.listeners.length; i++) {
        this.listeners[i].call(this, change);
    }
};

/**
 * 获取数据项
 *
 * @param {string|Object?} expr 数据项路径
 * @param {Data?} callee 当前数据获取的调用环境
 * @return {*}
 */
Data.prototype.get = function (expr, callee) {
    var value = this.raw;
    if (!expr) {
        return value;
    }

    expr = parseExpr(expr);

    var paths = expr.paths;
    callee = callee || this;

    value = value[paths[0].value];

    if (value == null && this.parent) {
        value = this.parent.get(expr, callee);
    }
    else {
        for (var i = 1, l = paths.length; value != null && i < l; i++) {
            value = value[paths[i].value || evalExpr(paths[i], callee)];
        }
    }

    return value;
};

/**
 * 数据对象变更操作
 *
 * @inner
 * @param {Object|Array} source 要变更的源数据
 * @param {Array} exprPaths 属性路径
 * @param {*} value 变更属性值
 * @param {Data} data 对应的Data对象
 * @return {*} 变更后的新数据
 */
function immutableSet(source, exprPaths, pathsStart, pathsLen, value, data) {
    if (pathsStart >= pathsLen) {
        return value;
    }

    if (source == null) {
        source = {};
    }

    var pathExpr = exprPaths[pathsStart];
    var prop = evalExpr(pathExpr, data);
    var result = source;

    if (source instanceof Array) {
        var index = +prop;
        prop = isNaN(index) ? prop : index;

        result = source.slice(0);
        result[prop] = immutableSet(source[prop], exprPaths, pathsStart + 1, pathsLen, value, data);
    }
    else if (typeof source === 'object') {
        result = {};

        for (var key in source) {
            if (key !== prop) {
                result[key] = source[key];
            }
        }

        result[prop] = immutableSet(source[prop], exprPaths, pathsStart + 1, pathsLen, value, data);
    }

    if (pathExpr.value == null) {
        exprPaths[pathsStart] = {
            type: typeof prop === 'string' ? ExprType.STRING : ExprType.NUMBER,
            value: prop
        };
    }

    return result;
}

/**
 * 设置数据项
 *
 * @param {string|Object} expr 数据项路径
 * @param {*} value 数据值
 * @param {Object=} option 设置参数
 * @param {boolean} option.silent 静默设置，不触发变更事件
 */
Data.prototype.set = function (expr, value, option) {
    option = option || {};

    // #[begin] error
    var exprRaw = expr;
    // #[end]

    expr = parseExpr(expr);

    // #[begin] error
    if (expr.type !== ExprType.ACCESSOR) {
        throw new Error('[SAN ERROR] Invalid Expression in Data set: ' + exprRaw);
    }
    // #[end]

    if (this.get(expr) === value && !option.force) {
        return;
    }

    expr = {
        type: ExprType.ACCESSOR,
        paths: expr.paths.slice(0)
    };

    dataCache.clear();
    this.raw = immutableSet(this.raw, expr.paths, 0, expr.paths.length, value, this);
    this.fire({
        type: DataChangeType.SET,
        expr: expr,
        value: value,
        option: option
    });

    // #[begin] error
    this.checkDataTypes();
    // #[end]

};

/**
 * 合并更新数据项
 *
 * @param {string|Object} expr 数据项路径
 * @param {Object} source 待合并的数据值
 * @param {Object=} option 设置参数
 * @param {boolean} option.silent 静默设置，不触发变更事件
 */
Data.prototype.merge = function (expr, source, option) {
    option = option || {};

    // #[begin] error
    var exprRaw = expr;
    // #[end]

    expr = parseExpr(expr);

    // #[begin] error
    if (expr.type !== ExprType.ACCESSOR) {
        throw new Error('[SAN ERROR] Invalid Expression in Data merge: ' + exprRaw);
    }

    if (typeof this.get(expr) !== 'object') {
        throw new Error('[SAN ERROR] Merge Expects a Target of Type \'object\'; got ' + typeof oldValue);
    }

    if (typeof source !== 'object') {
        throw new Error('[SAN ERROR] Merge Expects a Source of Type \'object\'; got ' + typeof source);
    }
    // #[end]

    for (var key in source) { // eslint-disable-line
        this.set(
            createAccessor(
                expr.paths.concat(
                    [
                        {
                            type: ExprType.STRING,
                            value: key
                        }
                    ]
                )
            ),
            source[key],
            option
        );
    }
};

/**
 * 基于更新函数更新数据项
 *
 * @param {string|Object} expr 数据项路径
 * @param {Function} fn 数据处理函数
 * @param {Object=} option 设置参数
 * @param {boolean} option.silent 静默设置，不触发变更事件
 */
Data.prototype.apply = function (expr, fn, option) {
    // #[begin] error
    var exprRaw = expr;
    // #[end]

    expr = parseExpr(expr);

    // #[begin] error
    if (expr.type !== ExprType.ACCESSOR) {
        throw new Error('[SAN ERROR] Invalid Expression in Data apply: ' + exprRaw);
    }
    // #[end]

    var oldValue = this.get(expr);

    // #[begin] error
    if (typeof fn !== 'function') {
        throw new Error(
            '[SAN ERROR] Invalid Argument\'s Type in Data apply: '
            + 'Expected Function but got ' + typeof fn
        );
    }
    // #[end]

    this.set(expr, fn(oldValue), option);
};

/**
 * 数组数据项splice操作
 *
 * @param {string|Object} expr 数据项路径
 * @param {Array} args splice 接受的参数列表，数组项与Array.prototype.splice的参数一致
 * @param {Object=} option 设置参数
 * @param {boolean} option.silent 静默设置，不触发变更事件
 * @return {Array} 新数组
 */
Data.prototype.splice = function (expr, args, option) {
    option = option || {};
    // #[begin] error
    var exprRaw = expr;
    // #[end]

    expr = parseExpr(expr);

    // #[begin] error
    if (expr.type !== ExprType.ACCESSOR) {
        throw new Error('[SAN ERROR] Invalid Expression in Data splice: ' + exprRaw);
    }
    // #[end]

    var target = this.get(expr);
    var returnValue = [];

    if (target instanceof Array) {
        var index = args[0];
        var len = target.length;
        if (index > len) {
            index = len;
        }
        else if (index < 0) {
            index = len + index;
            if (index < 0) {
                index = 0;
            }
        }

        var newArray = target.slice(0);
        returnValue = newArray.splice.apply(newArray, args);

        expr = {
            type: ExprType.ACCESSOR,
            paths: expr.paths.slice(0)
        };

        dataCache.clear();
        this.raw = immutableSet(this.raw, expr.paths, 0, expr.paths.length, newArray, this);

        this.fire({
            expr: expr,
            type: DataChangeType.SPLICE,
            index: index,
            deleteCount: returnValue.length,
            value: returnValue,
            insertions: args.slice(2),
            option: option
        });
    }

    // #[begin] error
    this.checkDataTypes();
    // #[end]

    return returnValue;
};

/**
 * 数组数据项push操作
 *
 * @param {string|Object} expr 数据项路径
 * @param {*} item 要push的值
 * @param {Object=} option 设置参数
 * @param {boolean} option.silent 静默设置，不触发变更事件
 * @return {number} 新数组的length属性
 */
Data.prototype.push = function (expr, item, option) {
    var target = this.get(expr);

    if (target instanceof Array) {
        this.splice(expr, [target.length, 0, item], option);
        return target.length + 1;
    }
};

/**
 * 数组数据项pop操作
 *
 * @param {string|Object} expr 数据项路径
 * @param {Object=} option 设置参数
 * @param {boolean} option.silent 静默设置，不触发变更事件
 * @return {*}
 */
Data.prototype.pop = function (expr, option) {
    var target = this.get(expr);

    if (target instanceof Array) {
        var len = target.length;
        if (len) {
            return this.splice(expr, [len - 1, 1], option)[0];
        }
    }
};

/**
 * 数组数据项shift操作
 *
 * @param {string|Object} expr 数据项路径
 * @param {Object=} option 设置参数
 * @param {boolean} option.silent 静默设置，不触发变更事件
 * @return {*}
 */
Data.prototype.shift = function (expr, option) {
    return this.splice(expr, [0, 1], option)[0];
};

/**
 * 数组数据项unshift操作
 *
 * @param {string|Object} expr 数据项路径
 * @param {*} item 要unshift的值
 * @param {Object=} option 设置参数
 * @param {boolean} option.silent 静默设置，不触发变更事件
 * @return {number} 新数组的length属性
 */
Data.prototype.unshift = function (expr, item, option) {
    var target = this.get(expr);

    if (target instanceof Array) {
        this.splice(expr, [0, 0, item], option);
        return target.length + 1;
    }
};

/**
 * 数组数据项移除操作
 *
 * @param {string|Object} expr 数据项路径
 * @param {number} index 要移除项的索引
 * @param {Object=} option 设置参数
 * @param {boolean} option.silent 静默设置，不触发变更事件
 */
Data.prototype.removeAt = function (expr, index, option) {
    this.splice(expr, [index, 1], option);
};

/**
 * 数组数据项移除操作
 *
 * @param {string|Object} expr 数据项路径
 * @param {*} value 要移除的项
 * @param {Object=} option 设置参数
 * @param {boolean} option.silent 静默设置，不触发变更事件
 */
Data.prototype.remove = function (expr, value, option) {
    var target = this.get(expr);

    if (target instanceof Array) {
        var len = target.length;
        while (len--) {
            if (target[len] === value) {
                this.splice(expr, [len, 1], option);
                break;
            }
        }
    }
};

exports = module.exports = Data;
<commit_msg>use createAccessor<commit_after>/**
 * @file 数据类
 * @author errorrik(errorrik@gmail.com)
 */

var ExprType = require('../parser/expr-type');
var evalExpr = require('./eval-expr');
var DataChangeType = require('./data-change-type');
var createAccessor = require('../parser/create-accessor');
var parseExpr = require('../parser/parse-expr');
var guid = require('../util/guid');
var dataCache = require('./data-cache');

/**
 * 数据类
 *
 * @class
 * @param {Object?} data 初始数据
 * @param {Model?} parent 父级数据容器
 */
function Data(data, parent) {
    this.id = guid();
    this.parent = parent;
    this.raw = data || {};
    this.listeners = [];
}

// #[begin] error
// 以下两个函数只在开发模式下可用，在生产模式下不存在
/**
 * DataTypes 检测
 */
Data.prototype.checkDataTypes = function () {
    if (this.typeChecker) {
        this.typeChecker(this.raw);
    }
};

/**
 * 设置 type checker
 *
 * @param  {Function} typeChecker 类型校验器
 */
Data.prototype.setTypeChecker = function (typeChecker) {
    this.typeChecker = typeChecker;
};

// #[end]

/**
 * 添加数据变更的事件监听器
 *
 * @param {Function} listener 监听函数
 */
Data.prototype.listen = function (listener) {
    if (typeof listener === 'function') {
        this.listeners.push(listener);
    }
};

/**
 * 移除数据变更的事件监听器
 *
 * @param {Function} listener 监听函数
 */
Data.prototype.unlisten = function (listener) {
    var len = this.listeners.length;
    while (len--) {
        if (!listener || this.listeners[len] === listener) {
            this.listeners.splice(len, 1);
        }
    }
};

/**
 * 触发数据变更
 *
 * @param {Object} change 变更信息对象
 */
Data.prototype.fire = function (change) {
    if (change.option.silent || change.option.silence || change.option.quiet) {
        return;
    }

    for (var i = 0; i < this.listeners.length; i++) {
        this.listeners[i].call(this, change);
    }
};

/**
 * 获取数据项
 *
 * @param {string|Object?} expr 数据项路径
 * @param {Data?} callee 当前数据获取的调用环境
 * @return {*}
 */
Data.prototype.get = function (expr, callee) {
    var value = this.raw;
    if (!expr) {
        return value;
    }

    expr = parseExpr(expr);

    var paths = expr.paths;
    callee = callee || this;

    value = value[paths[0].value];

    if (value == null && this.parent) {
        value = this.parent.get(expr, callee);
    }
    else {
        for (var i = 1, l = paths.length; value != null && i < l; i++) {
            value = value[paths[i].value || evalExpr(paths[i], callee)];
        }
    }

    return value;
};

/**
 * 数据对象变更操作
 *
 * @inner
 * @param {Object|Array} source 要变更的源数据
 * @param {Array} exprPaths 属性路径
 * @param {*} value 变更属性值
 * @param {Data} data 对应的Data对象
 * @return {*} 变更后的新数据
 */
function immutableSet(source, exprPaths, pathsStart, pathsLen, value, data) {
    if (pathsStart >= pathsLen) {
        return value;
    }

    if (source == null) {
        source = {};
    }

    var pathExpr = exprPaths[pathsStart];
    var prop = evalExpr(pathExpr, data);
    var result = source;

    if (source instanceof Array) {
        var index = +prop;
        prop = isNaN(index) ? prop : index;

        result = source.slice(0);
        result[prop] = immutableSet(source[prop], exprPaths, pathsStart + 1, pathsLen, value, data);
    }
    else if (typeof source === 'object') {
        result = {};

        for (var key in source) {
            if (key !== prop) {
                result[key] = source[key];
            }
        }

        result[prop] = immutableSet(source[prop], exprPaths, pathsStart + 1, pathsLen, value, data);
    }

    if (pathExpr.value == null) {
        exprPaths[pathsStart] = {
            type: typeof prop === 'string' ? ExprType.STRING : ExprType.NUMBER,
            value: prop
        };
    }

    return result;
}

/**
 * 设置数据项
 *
 * @param {string|Object} expr 数据项路径
 * @param {*} value 数据值
 * @param {Object=} option 设置参数
 * @param {boolean} option.silent 静默设置，不触发变更事件
 */
Data.prototype.set = function (expr, value, option) {
    option = option || {};

    // #[begin] error
    var exprRaw = expr;
    // #[end]

    expr = parseExpr(expr);

    // #[begin] error
    if (expr.type !== ExprType.ACCESSOR) {
        throw new Error('[SAN ERROR] Invalid Expression in Data set: ' + exprRaw);
    }
    // #[end]

    if (this.get(expr) === value && !option.force) {
        return;
    }

    expr = createAccessor(expr.paths.slice(0));

    dataCache.clear();
    this.raw = immutableSet(this.raw, expr.paths, 0, expr.paths.length, value, this);
    this.fire({
        type: DataChangeType.SET,
        expr: expr,
        value: value,
        option: option
    });

    // #[begin] error
    this.checkDataTypes();
    // #[end]

};

/**
 * 合并更新数据项
 *
 * @param {string|Object} expr 数据项路径
 * @param {Object} source 待合并的数据值
 * @param {Object=} option 设置参数
 * @param {boolean} option.silent 静默设置，不触发变更事件
 */
Data.prototype.merge = function (expr, source, option) {
    option = option || {};

    // #[begin] error
    var exprRaw = expr;
    // #[end]

    expr = parseExpr(expr);

    // #[begin] error
    if (expr.type !== ExprType.ACCESSOR) {
        throw new Error('[SAN ERROR] Invalid Expression in Data merge: ' + exprRaw);
    }

    if (typeof this.get(expr) !== 'object') {
        throw new Error('[SAN ERROR] Merge Expects a Target of Type \'object\'; got ' + typeof oldValue);
    }

    if (typeof source !== 'object') {
        throw new Error('[SAN ERROR] Merge Expects a Source of Type \'object\'; got ' + typeof source);
    }
    // #[end]

    for (var key in source) { // eslint-disable-line
        this.set(
            createAccessor(
                expr.paths.concat(
                    [
                        {
                            type: ExprType.STRING,
                            value: key
                        }
                    ]
                )
            ),
            source[key],
            option
        );
    }
};

/**
 * 基于更新函数更新数据项
 *
 * @param {string|Object} expr 数据项路径
 * @param {Function} fn 数据处理函数
 * @param {Object=} option 设置参数
 * @param {boolean} option.silent 静默设置，不触发变更事件
 */
Data.prototype.apply = function (expr, fn, option) {
    // #[begin] error
    var exprRaw = expr;
    // #[end]

    expr = parseExpr(expr);

    // #[begin] error
    if (expr.type !== ExprType.ACCESSOR) {
        throw new Error('[SAN ERROR] Invalid Expression in Data apply: ' + exprRaw);
    }
    // #[end]

    var oldValue = this.get(expr);

    // #[begin] error
    if (typeof fn !== 'function') {
        throw new Error(
            '[SAN ERROR] Invalid Argument\'s Type in Data apply: '
            + 'Expected Function but got ' + typeof fn
        );
    }
    // #[end]

    this.set(expr, fn(oldValue), option);
};

/**
 * 数组数据项splice操作
 *
 * @param {string|Object} expr 数据项路径
 * @param {Array} args splice 接受的参数列表，数组项与Array.prototype.splice的参数一致
 * @param {Object=} option 设置参数
 * @param {boolean} option.silent 静默设置，不触发变更事件
 * @return {Array} 新数组
 */
Data.prototype.splice = function (expr, args, option) {
    option = option || {};
    // #[begin] error
    var exprRaw = expr;
    // #[end]

    expr = parseExpr(expr);

    // #[begin] error
    if (expr.type !== ExprType.ACCESSOR) {
        throw new Error('[SAN ERROR] Invalid Expression in Data splice: ' + exprRaw);
    }
    // #[end]

    var target = this.get(expr);
    var returnValue = [];

    if (target instanceof Array) {
        var index = args[0];
        var len = target.length;
        if (index > len) {
            index = len;
        }
        else if (index < 0) {
            index = len + index;
            if (index < 0) {
                index = 0;
            }
        }

        var newArray = target.slice(0);
        returnValue = newArray.splice.apply(newArray, args);

        expr = createAccessor(expr.paths.slice(0));

        dataCache.clear();
        this.raw = immutableSet(this.raw, expr.paths, 0, expr.paths.length, newArray, this);

        this.fire({
            expr: expr,
            type: DataChangeType.SPLICE,
            index: index,
            deleteCount: returnValue.length,
            value: returnValue,
            insertions: args.slice(2),
            option: option
        });
    }

    // #[begin] error
    this.checkDataTypes();
    // #[end]

    return returnValue;
};

/**
 * 数组数据项push操作
 *
 * @param {string|Object} expr 数据项路径
 * @param {*} item 要push的值
 * @param {Object=} option 设置参数
 * @param {boolean} option.silent 静默设置，不触发变更事件
 * @return {number} 新数组的length属性
 */
Data.prototype.push = function (expr, item, option) {
    var target = this.get(expr);

    if (target instanceof Array) {
        this.splice(expr, [target.length, 0, item], option);
        return target.length + 1;
    }
};

/**
 * 数组数据项pop操作
 *
 * @param {string|Object} expr 数据项路径
 * @param {Object=} option 设置参数
 * @param {boolean} option.silent 静默设置，不触发变更事件
 * @return {*}
 */
Data.prototype.pop = function (expr, option) {
    var target = this.get(expr);

    if (target instanceof Array) {
        var len = target.length;
        if (len) {
            return this.splice(expr, [len - 1, 1], option)[0];
        }
    }
};

/**
 * 数组数据项shift操作
 *
 * @param {string|Object} expr 数据项路径
 * @param {Object=} option 设置参数
 * @param {boolean} option.silent 静默设置，不触发变更事件
 * @return {*}
 */
Data.prototype.shift = function (expr, option) {
    return this.splice(expr, [0, 1], option)[0];
};

/**
 * 数组数据项unshift操作
 *
 * @param {string|Object} expr 数据项路径
 * @param {*} item 要unshift的值
 * @param {Object=} option 设置参数
 * @param {boolean} option.silent 静默设置，不触发变更事件
 * @return {number} 新数组的length属性
 */
Data.prototype.unshift = function (expr, item, option) {
    var target = this.get(expr);

    if (target instanceof Array) {
        this.splice(expr, [0, 0, item], option);
        return target.length + 1;
    }
};

/**
 * 数组数据项移除操作
 *
 * @param {string|Object} expr 数据项路径
 * @param {number} index 要移除项的索引
 * @param {Object=} option 设置参数
 * @param {boolean} option.silent 静默设置，不触发变更事件
 */
Data.prototype.removeAt = function (expr, index, option) {
    this.splice(expr, [index, 1], option);
};

/**
 * 数组数据项移除操作
 *
 * @param {string|Object} expr 数据项路径
 * @param {*} value 要移除的项
 * @param {Object=} option 设置参数
 * @param {boolean} option.silent 静默设置，不触发变更事件
 */
Data.prototype.remove = function (expr, value, option) {
    var target = this.get(expr);

    if (target instanceof Array) {
        var len = target.length;
        while (len--) {
            if (target[len] === value) {
                this.splice(expr, [len, 1], option);
                break;
            }
        }
    }
};

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


<commit_before>var files =
	[
	 	"lib/underscore.js",
	 	"lib/backbone.js",
	 	"src/bcapi.js",
	 	"src/helper.js",
	 	"src/config.js",
	 	"src/models.js",
	 	"src/webapp.js",
	 	"src/webapp_item.js",
	 	"src/webapp_customfield.js"
    ];

module.exports = files;<commit_msg>Added categories to the grunt loop<commit_after>var files =
	[
	 	"lib/underscore.js",
	 	"lib/backbone.js",
	 	"src/bcapi.js",
	 	"src/helper.js",
	 	"src/config.js",
	 	"src/models.js",
	 	"src/webapp.js",
	 	"src/webapp_item.js",
	 	"src/webapp_customfield.js",
        "src/category.js"
    ];

module.exports = files;<|endoftext|>


<commit_before>var CONTEXT_URI = process.env.CONTEXT_URI;

exports.requiresSession = function (req, res, next) {
    if (req.session.encodedUsername) {
        res.cookie('_eun', req.session.encodedUsername);
        next();
    } else {
        req.session.redirectUrl = CONTEXT_URI + req.originalUrl;
        if (req.url.split('?')[1] !== undefined) {
            var intent = "session.redirect";
            var redirectUrl = CONTEXT_URI + req.url;
            res.redirect(CONTEXT_URI + "/signup" + "?intent=" + intent + "&redirectUrl=" + encodeURIComponent(redirectUrl));
        } else {
            res.redirect(CONTEXT_URI + "/signup");
        }
    }
};

exports.setSession = function (req, res, user) {
    req.session.username = user.username;
    req.session.encodedUsername = user.encodedUsername;
    req.session.githubUsername = user.githubUser.username;
    req.session.avatarUrl = user.githubUser._json.avatar_url;
    res.cookie('_eun', req.session.encodedUsername);
};

exports.resetSession = function (req) {
    req.session.encodedUsername = null;
    req.session.auth = null;
    req.session.intent = null;
    req.session.oneselfUsername = null;
    req.session.username = null;
    req.session.githubUsername = null;
    req.session.avatarUrl = null;
};
<commit_msg>Destroy session redis way too.<commit_after>var CONTEXT_URI = process.env.CONTEXT_URI;

exports.requiresSession = function (req, res, next) {
    if (req.session.encodedUsername) {
        res.cookie('_eun', req.session.encodedUsername);
        next();
    } else {
        req.session.redirectUrl = CONTEXT_URI + req.originalUrl;
        if (req.url.split('?')[1] !== undefined) {
            var intent = "session.redirect";
            var redirectUrl = CONTEXT_URI + req.url;
            res.redirect(CONTEXT_URI + "/signup" + "?intent=" + intent + "&redirectUrl=" + encodeURIComponent(redirectUrl));
        } else {
            res.redirect(CONTEXT_URI + "/signup");
        }
    }
};

exports.setSession = function (req, res, user) {
    req.session.username = user.username;
    req.session.encodedUsername = user.encodedUsername;
    req.session.githubUsername = user.githubUser.username;
    req.session.avatarUrl = user.githubUser._json.avatar_url;
    res.cookie('_eun', req.session.encodedUsername);
};

exports.resetSession = function (req) {
    req.session.destory;
    req.session.encodedUsername = null;
    req.session.auth = null;
    req.session.intent = null;
    req.session.oneselfUsername = null;
    req.session.username = null;
    req.session.githubUsername = null;
    req.session.avatarUrl = null;
};
<|endoftext|>