<commit_before>'use strict';

// setup the headerless localStorage
if (global.window === undefined) {
    require('mock-local-storage');
    global.window = {
        localStorage: global.localStorage
    }
}

// setup the dependencies
const Store = require('../src/store.js');
const test  = require('tape');

// define test variables
var name = 'John Doe';
var age  = 33;
var wife = {
    name: 'Jane Doe',
    age: 31
}

var timeout = 2000;

// test the store initialization
test('new Store', assert => {
    assert.plan(1);
    var expected = 'object';

    try {
        var sessionStore = new Store('session');
        var actual = typeof(sessionStore);
        assert.equal(actual, expected, 'Store instance should be created');
    } catch(error) {
        assert.fail(error.message);
    }
});

// test the set method
test('Store.set', assert => {
    var userStore = new Store('userStore');

    assert.plan(3);
    assert.true(userStore.set('name', name));
    assert.true(userStore.set('age', age));
    assert.true(userStore.set('wife', wife));
});

// test the get method
test('Store.get', assert => {
    var userStore = new Store('userStore');

    assert.plan(4);
    assert.equal(userStore.get('name'), name);
    assert.equal(userStore.get('age'), age);
    assert.deepEqual(userStore.get('wife'), wife);
    assert.deepEqual(userStore.get(), {
        name: name,
        age: age,
        wife: wife
    });
});

// test the isset method
test('Store.isset', assert => {
    var userStore = new Store('userStore');

    assert.plan(4);
    assert.true(userStore.get('name'));
    assert.true(userStore.get('age'));
    assert.true(userStore.get('wife'));
    assert.false(userStore.get('randomError'));
});

// test the remove method
test('Store.remove', assert => {
    var userStore = new Store('userStore');

    var expected = 1;

    assert.plan(1);

    userStore.remove('name');
    userStore.remove('age');

    assert.equal(localStorage.length, expected);
});

// test the flush method
test('Store.flush', assert => {
    var otherStore = new Store('otherStore');
    otherStore.set('1', 1);
    otherStore.set('2', 1);
    otherStore.set('3', 1);

    var userStore = new Store('userStore');

    var expected = 3;

    assert.plan(1);

    userStore.flush();

    assert.equal(localStorage.length, expected);
});

// test the statioc flushExpired method
test('<static> Store.flushExpired', assert => {
    var flushStore = new Store('flushStatic');
    var otherStore = new Store('otherFlushStore');

    flushStore.set('1', 1, Date.now() + timeout - 2000);
    flushStore.set('2', 1, Date.now() + timeout - 100);
    flushStore.set('3', 1, Date.now() + timeout - 1000);
    flushStore.set('4', 1, Date.now() + timeout + 6000);

    otherStore.set('1', 1, Date.now() + timeout - 200);
    otherStore.set('2', 1, Date.now() + timeout - 50);
    otherStore.set('3', 1, Date.now() + timeout + 1000);
    otherStore.set('4', 1, Date.now() + timeout + 10000);

    setTimeout(function() {
        assert.plan(2);

        Store.flushExpired();

        assert.equal(Object.keys(flushStore.get()).length, 1);
        assert.equal(Object.keys(otherStore.get()).length, 2);
    }, timeout);
});

// test the static flush method
test('<static> Store.flush', assert => {
    var expected = 0;

    assert.plan(1);

    Store.flush();

    assert.equal(localStorage.length, expected);
});

// the the auto flush method on Store initialization
test('new Store ; auto flush expired values', assert => {
    var flushStore = new Store('flush');
    var expected   = 2;

    flushStore.set('1', 1, Date.now() + timeout - 1000);
    flushStore.set('2', 2, Date.now() + timeout - 100);
    flushStore.set('3', 3, Date.now() + timeout - 50);
    flushStore.set('4', 4, Date.now() + timeout + 1000);
    flushStore.set('5', 5, Date.now() + timeout + 5000);

    setTimeout(function() {
        assert.plan(1);

        var flushStore = new Store('flush');

        assert.equal(Object.keys(flushStore.get()).length, expected);
    }, timeout);
});
<commit_msg>added suppress auto expire test<commit_after>'use strict';

// setup the headerless localStorage
if (global.window === undefined) {
    require('mock-local-storage');
    global.window = {
        localStorage: global.localStorage
    }
}

// setup the dependencies
const Store = require('../src/store.js');
const test  = require('tape');

// define test variables
var name = 'John Doe';
var age  = 33;
var wife = {
    name: 'Jane Doe',
    age: 31
}

var timeout = 2000;
localStorage.clear();

// test the store initialization
test('new Store', assert => {
    assert.plan(1);
    var expected = 'object';

    try {
        var sessionStore = new Store('session');
        var actual = typeof(sessionStore);
        assert.equal(actual, expected, 'Store instance should be created');
    } catch(error) {
        assert.fail(error.message);
    }
});

// test the set method
test('Store.set', assert => {
    var userStore = new Store('userStore');

    assert.plan(3);
    assert.true(userStore.set('name', name));
    assert.true(userStore.set('age', age));
    assert.true(userStore.set('wife', wife));
});

// test the get method
test('Store.get', assert => {
    var userStore = new Store('userStore');

    assert.plan(4);
    assert.equal(userStore.get('name'), name);
    assert.equal(userStore.get('age'), age);
    assert.deepEqual(userStore.get('wife'), wife);
    assert.deepEqual(userStore.get(), {
        name: name,
        age: age,
        wife: wife
    });
});

// test the isset method
test('Store.isset', assert => {
    var userStore = new Store('userStore');

    assert.plan(4);
    assert.true(userStore.get('name'));
    assert.true(userStore.get('age'));
    assert.true(userStore.get('wife'));
    assert.false(userStore.get('randomError'));
});

// test the remove method
test('Store.remove', assert => {
    var userStore = new Store('userStore');

    var expected = 1;

    assert.plan(1);

    userStore.remove('name');
    userStore.remove('age');

    assert.equal(localStorage.length, expected);
});

// test the flush method
test('Store.flush', assert => {
    var otherStore = new Store('otherStore');
    otherStore.set('1', 1);
    otherStore.set('2', 1);
    otherStore.set('3', 1);

    var userStore = new Store('userStore');

    var expected = 3;

    assert.plan(1);

    userStore.flush();

    assert.equal(localStorage.length, expected);
});

// test the static flushExpired method
test('<static> Store.flushExpired', assert => {
    var flushStore = new Store('flushStatic');
    var otherStore = new Store('otherFlushStore');

    flushStore.set('1', 1, Date.now() + timeout - 2000);
    flushStore.set('2', 1, Date.now() + timeout - 100);
    flushStore.set('3', 1, Date.now() + timeout - 1000);
    flushStore.set('4', 1, Date.now() + timeout + 6000);

    otherStore.set('1', 1, Date.now() + timeout - 200);
    otherStore.set('2', 1, Date.now() + timeout - 50);
    otherStore.set('3', 1, Date.now() + timeout + 1000);
    otherStore.set('4', 1, Date.now() + timeout + 10000);

    setTimeout(function() {
        assert.plan(2);

        Store.flushExpired();

        assert.equal(Object.keys(flushStore.get()).length, 1);
        assert.equal(Object.keys(otherStore.get()).length, 2);
    }, timeout);
});

// test the static flush method
test('<static> Store.flush', assert => {
    var expected = 0;

    assert.plan(1);

    Store.flush();

    assert.equal(localStorage.length, expected);
});

// the the auto flush method on Store initialization
test('new Store ; auto flush expired values', assert => {
    var flushStore = new Store('flush');
    var expected   = 2;

    flushStore.set('1', 1, Date.now() + timeout - 1000);
    flushStore.set('2', 2, Date.now() + timeout - 100);
    flushStore.set('3', 3, Date.now() + timeout - 50);
    flushStore.set('4', 4, Date.now() + timeout + 1000);
    flushStore.set('5', 5, Date.now() + timeout + 5000);

    setTimeout(function() {
        assert.plan(1);

        var flushStore = new Store('flush');

        assert.equal(Object.keys(flushStore.get()).length, expected);
    }, timeout);
});

// the the auto flush method on Store initialization
test('new Store ; suppress auto flush expired values', assert => {
    var flushStore = new Store('suppressFlush');
    var expected   = 5;

    flushStore.set('1', 1, Date.now() + timeout - 1000);
    flushStore.set('2', 2, Date.now() + timeout - 100);
    flushStore.set('3', 3, Date.now() + timeout - 50);
    flushStore.set('4', 4, Date.now() + timeout + 1000);
    flushStore.set('5', 5, Date.now() + timeout + 5000);

    setTimeout(function() {
        assert.plan(1);

        var flushStore = new Store('suppressFlush', false);

        assert.equal(Object.keys(flushStore.get()).length, expected);
    }, timeout);
});
<|endoftext|>


<commit_before>'use strict'

const Web3 = require('web3')
const web3 = new Web3()
const hdkey = require('ethereumjs-wallet/hdkey')
const Tx = require('ethereumjs-tx')
const util = require('ethereumjs-util')
const pify = require('pify')
const BN = require('../../../bn')

const NAME = 'geth'
exports.SUPPORTED_MODULES = ['wallet']

const paymentPrefixPath = "m/44'/60'/0'/0'"
const defaultPrefixPath = "m/44'/60'/1'/0'"
let lastUsedNonces = {}

module.exports = {
  NAME,
  balance,
  sendCoins,
  newAddress,
  getStatus,
  sweep,
  defaultAddress,
  supportsHd: true,
  newFunding,
  privateKey,
  isStrictAddress,
  connect
}

function connect (url) {
  if (!web3.isConnected()) {
    web3.setProvider(new web3.providers.HttpProvider(url))
  }
}

const hex = bigNum => '0x' + bigNum.truncated().toString(16)

function privateKey (account) {
  return defaultWallet(account).getPrivateKey()
}

function isStrictAddress (cryptoCode, toAddress) {
  return cryptoCode === 'ETH' && util.isValidChecksumAddress(toAddress)
}

function sendCoins (account, toAddress, cryptoAtoms, cryptoCode) {
  return generateTx(toAddress, defaultWallet(account), cryptoAtoms, false)
    .then(pify(web3.eth.sendRawTransaction))
    .then(txid => {
      return pify(web3.eth.getTransaction)(txid)
        .then(tx => {
          if (!tx) return { txid }

          const fee = BN(tx.gas).multipliedBy(BN(tx.gasPrice)).round()

          return { txid, fee }
        })
    })
}

function checkCryptoCode (cryptoCode) {
  if (cryptoCode === 'ETH') return Promise.resolve()
  return Promise.reject(new Error('cryptoCode must be ETH'))
}

function balance (account, cryptoCode) {
  return checkCryptoCode(cryptoCode)
    .then(() => pendingBalance(defaultAddress(account)))
}

const pendingBalance = address => _balance(true, address)
const confirmedBalance = address => _balance(false, address)

function _balance (includePending, address) {
  const block = includePending ? 'pending' : undefined

  return pify(web3.eth.getBalance)(address.toLowerCase(), block)
}

function generateTx (_toAddress, wallet, amount, includesFee) {
  const fromAddress = '0x' + wallet.getAddress().toString('hex')
  const toAddress = _toAddress.toLowerCase()

  const txTemplate = {
    from: fromAddress,
    to: toAddress,
    value: amount
  }

  const promises = [
    pify(web3.eth.estimateGas)(txTemplate),
    pify(web3.eth.getGasPrice)(),
    pify(web3.eth.getTransactionCount)(fromAddress)
  ]

  return Promise.all(promises)
    .then(arr => {
      const gas = arr[0]
      const gasPrice = arr[1]
      const txCount = arr[2] <= lastUsedNonces[fromAddress]
        ? lastUsedNonces[fromAddress] + 1
        : arr[2]

      lastUsedNonces[fromAddress] = txCount

      const toSend = includesFee
        ? amount.minus(gasPrice.times(gas))
        : amount

      const rawTx = {
        nonce: txCount,
        gasPrice: hex(gasPrice),
        gasLimit: gas,
        to: toAddress,
        from: fromAddress,
        value: hex(toSend)
      }

      const tx = new Tx(rawTx)
      const privateKey = wallet.getPrivateKey()

      tx.sign(privateKey)

      return '0x' + tx.serialize().toString('hex')
    })
}

function defaultWallet (account) {
  return defaultHdNode(account).deriveChild(0).getWallet()
}

function defaultAddress (account) {
  return defaultWallet(account).getChecksumAddressString()
}

function sweep (account, cryptoCode, hdIndex) {
  const wallet = paymentHdNode(account).deriveChild(hdIndex).getWallet()
  const fromAddress = wallet.getChecksumAddressString()

  return confirmedBalance(fromAddress)
    .then(r => {
      if (r.eq(0)) return

      return generateTx(defaultAddress(account), wallet, r, true)
        .then(signedTx => pify(web3.eth.sendRawTransaction)(signedTx))
    })
}

function newAddress (account, info) {
  const childNode = paymentHdNode(account).deriveChild(info.hdIndex)
  return Promise.resolve(childNode.getWallet().getChecksumAddressString())
}

function getStatus (account, toAddress, cryptoAtoms, cryptoCode) {
  return checkCryptoCode(cryptoCode)
    .then(() => confirmedBalance(toAddress))
    .then(confirmed => {
      if (confirmed.gte(cryptoAtoms)) return {status: 'confirmed'}

      return pendingBalance(toAddress)
        .then(pending => {
          if (pending.gte(cryptoAtoms)) return {status: 'published'}
          if (pending.gt(0)) return {status: 'insufficientFunds'}
          return {status: 'notSeen'}
        })
    })
}

function paymentHdNode (account) {
  const masterSeed = account.seed
  if (!masterSeed) throw new Error('No master seed!')
  const key = hdkey.fromMasterSeed(masterSeed)
  return key.derivePath(paymentPrefixPath)
}

function defaultHdNode (account) {
  const masterSeed = account.seed
  if (!masterSeed) throw new Error('No master seed!')
  const key = hdkey.fromMasterSeed(masterSeed)
  return key.derivePath(defaultPrefixPath)
}

function newFunding (account, cryptoCode) {
  return checkCryptoCode(cryptoCode)
    .then(() => {
      const fundingAddress = defaultAddress(account)

      const promises = [
        pendingBalance(fundingAddress),
        confirmedBalance(fundingAddress)
      ]

      return Promise.all(promises)
        .then(([fundingPendingBalance, fundingConfirmedBalance]) => ({
          fundingPendingBalance,
          fundingConfirmedBalance,
          fundingAddress
        }))
    })
}
<commit_msg>Fix ETH sending following miner fee logging<commit_after>'use strict'

const Web3 = require('web3')
const web3 = new Web3()
const hdkey = require('ethereumjs-wallet/hdkey')
const Tx = require('ethereumjs-tx')
const util = require('ethereumjs-util')
const pify = require('pify')
const BN = require('../../../bn')

const NAME = 'geth'
exports.SUPPORTED_MODULES = ['wallet']

const paymentPrefixPath = "m/44'/60'/0'/0'"
const defaultPrefixPath = "m/44'/60'/1'/0'"
let lastUsedNonces = {}

module.exports = {
  NAME,
  balance,
  sendCoins,
  newAddress,
  getStatus,
  sweep,
  defaultAddress,
  supportsHd: true,
  newFunding,
  privateKey,
  isStrictAddress,
  connect
}

function connect (url) {
  if (!web3.isConnected()) {
    web3.setProvider(new web3.providers.HttpProvider(url))
  }
}

const hex = bigNum => '0x' + bigNum.truncated().toString(16)

function privateKey (account) {
  return defaultWallet(account).getPrivateKey()
}

function isStrictAddress (cryptoCode, toAddress) {
  return cryptoCode === 'ETH' && util.isValidChecksumAddress(toAddress)
}

function sendCoins (account, toAddress, cryptoAtoms, cryptoCode) {
  return generateTx(toAddress, defaultWallet(account), cryptoAtoms, false)
    .then(pify(web3.eth.sendRawTransaction))
    .then(txid => {
      return pify(web3.eth.getTransaction)(txid)
        .then(tx => {
          if (!tx) return { txid }

          const fee = BN(tx.gas).mul(BN(tx.gasPrice)).round()

          return { txid, fee }
        })
    })
}

function checkCryptoCode (cryptoCode) {
  if (cryptoCode === 'ETH') return Promise.resolve()
  return Promise.reject(new Error('cryptoCode must be ETH'))
}

function balance (account, cryptoCode) {
  return checkCryptoCode(cryptoCode)
    .then(() => pendingBalance(defaultAddress(account)))
}

const pendingBalance = address => _balance(true, address)
const confirmedBalance = address => _balance(false, address)

function _balance (includePending, address) {
  const block = includePending ? 'pending' : undefined

  return pify(web3.eth.getBalance)(address.toLowerCase(), block)
}

function generateTx (_toAddress, wallet, amount, includesFee) {
  const fromAddress = '0x' + wallet.getAddress().toString('hex')
  const toAddress = _toAddress.toLowerCase()

  const txTemplate = {
    from: fromAddress,
    to: toAddress,
    value: amount
  }

  const promises = [
    pify(web3.eth.estimateGas)(txTemplate),
    pify(web3.eth.getGasPrice)(),
    pify(web3.eth.getTransactionCount)(fromAddress)
  ]

  return Promise.all(promises)
    .then(arr => {
      const gas = arr[0]
      const gasPrice = arr[1]
      const txCount = arr[2] <= lastUsedNonces[fromAddress]
        ? lastUsedNonces[fromAddress] + 1
        : arr[2]

      lastUsedNonces[fromAddress] = txCount

      const toSend = includesFee
        ? amount.minus(gasPrice.times(gas))
        : amount

      const rawTx = {
        nonce: txCount,
        gasPrice: hex(gasPrice),
        gasLimit: gas,
        to: toAddress,
        from: fromAddress,
        value: hex(toSend)
      }

      const tx = new Tx(rawTx)
      const privateKey = wallet.getPrivateKey()

      tx.sign(privateKey)

      return '0x' + tx.serialize().toString('hex')
    })
}

function defaultWallet (account) {
  return defaultHdNode(account).deriveChild(0).getWallet()
}

function defaultAddress (account) {
  return defaultWallet(account).getChecksumAddressString()
}

function sweep (account, cryptoCode, hdIndex) {
  const wallet = paymentHdNode(account).deriveChild(hdIndex).getWallet()
  const fromAddress = wallet.getChecksumAddressString()

  return confirmedBalance(fromAddress)
    .then(r => {
      if (r.eq(0)) return

      return generateTx(defaultAddress(account), wallet, r, true)
        .then(signedTx => pify(web3.eth.sendRawTransaction)(signedTx))
    })
}

function newAddress (account, info) {
  const childNode = paymentHdNode(account).deriveChild(info.hdIndex)
  return Promise.resolve(childNode.getWallet().getChecksumAddressString())
}

function getStatus (account, toAddress, cryptoAtoms, cryptoCode) {
  return checkCryptoCode(cryptoCode)
    .then(() => confirmedBalance(toAddress))
    .then(confirmed => {
      if (confirmed.gte(cryptoAtoms)) return {status: 'confirmed'}

      return pendingBalance(toAddress)
        .then(pending => {
          if (pending.gte(cryptoAtoms)) return {status: 'published'}
          if (pending.gt(0)) return {status: 'insufficientFunds'}
          return {status: 'notSeen'}
        })
    })
}

function paymentHdNode (account) {
  const masterSeed = account.seed
  if (!masterSeed) throw new Error('No master seed!')
  const key = hdkey.fromMasterSeed(masterSeed)
  return key.derivePath(paymentPrefixPath)
}

function defaultHdNode (account) {
  const masterSeed = account.seed
  if (!masterSeed) throw new Error('No master seed!')
  const key = hdkey.fromMasterSeed(masterSeed)
  return key.derivePath(defaultPrefixPath)
}

function newFunding (account, cryptoCode) {
  return checkCryptoCode(cryptoCode)
    .then(() => {
      const fundingAddress = defaultAddress(account)

      const promises = [
        pendingBalance(fundingAddress),
        confirmedBalance(fundingAddress)
      ]

      return Promise.all(promises)
        .then(([fundingPendingBalance, fundingConfirmedBalance]) => ({
          fundingPendingBalance,
          fundingConfirmedBalance,
          fundingAddress
        }))
    })
}
<|endoftext|>


<commit_before>import React from 'react';
import PropTypes from 'prop-types';
import uuid from 'uuid';
import _ from 'lodash';
// eslint-disable-next-line import/no-extraneous-dependencies
import moment from 'moment';

import ChargeForm from './ChargeForm';

class Charge extends React.Component {
  static manifest = Object.freeze({
    feefines: {
      type: 'okapi',
      records: 'feefines',
      GET: {
        path: 'feefines?query=(ownerId=%{activeRecord.ownerId} or ownerId=%{activeRecord.shared})&limit=100',
      },
    },
    feefineactions: {
      type: 'okapi',
      records: 'feefineactions',
      path: 'feefineactions',
    },
    owners: {
      type: 'okapi',
      records: 'owners',
      path: 'owners?limit=100',
    },
    accounts: {
      type: 'okapi',
      records: 'accounts',
      path: 'accounts',
    },
    instances: {
      type: 'okapi',
      records: 'instances',
      path: 'instance-storage/instances?query=%{activeRecord.queryInstance}',
    },
    locations: {
      type: 'okapi',
      record: 'shelflocations',
      path: 'shelf-locations',
    },
    material: {
      type: 'okapi',
      record: 'mtypes',
      path: 'material-types',
    },
    barcodes: {
      type: 'okapi',
      record: 'items',
      path: 'item-storage/items?query=%{activeRecord.queryItem}',
    },
    holdings: {
      type: 'okapi',
      record: 'holdingsRecords',
      path: 'holdings-storage/holdings?query=%{activeRecord.query}',
    },
    activeRecord: {},
  });

  static propTypes = {
    resources: PropTypes.shape({
      feefines: PropTypes.shape({
        records: PropTypes.arrayOf(PropTypes.object),
      }),
      owners: PropTypes.shape({
        records: PropTypes.arrayOf(PropTypes.object),
      }),
      chargeitem: PropTypes.shape({
        records: PropTypes.arrayOf(PropTypes.object),
      }),
    }).isRequired,
    mutator: PropTypes.shape({
      accounts: PropTypes.shape({
        POST: PropTypes.func.isRequired,
      }),
      feefineactions: PropTypes.shape({
        POST: PropTypes.func.isRequired,
      }),
      activeRecord: PropTypes.shape({
        update: PropTypes.func,
      }),
    }).isRequired,
    stripes: PropTypes.object,
    onCloseChargeFeeFine: PropTypes.func.isRequired,
    handleAddRecords: PropTypes.func,
    okapi: PropTypes.object,
    selectedLoan: PropTypes.object,
    user: PropTypes.object,
  };

  constructor(props) {
    super(props);
    this.state = {
      ownerId: '0',
    };
    this.onClickCharge = this.onClickCharge.bind(this);
    this.onClickSelectItem = this.onClickSelectItem.bind(this);
    this.onChangeOwner = this.onChangeOwner.bind(this);
    this.onFindShared = this.onFindShared.bind(this);
    this.onSubmit = this.onSubmit.bind(this);
    this.item = {};
  }

  componentDidMount() {
    this.item.load = true;
    if (this.props.selectedLoan.id) {
      this.props.mutator.activeRecord.update({ item: this.props.selectedLoan.item.barcode });
    } else {
      this.props.mutator.activeRecord.update({ item: 0 });
    }
  }

  shouldComponentUpdate(nextProps) {
    const bar = _.get(this.props.resources, ['barcodes', 'records', 0, 'items'], []);
    const nextBar = _.get(nextProps.resources, ['barcodes', 'records', 0, 'items'], []);
    const title = _.get(this.props.resources, ['instances', 'records'], []);
    const nextTitle = _.get(nextProps.resources, ['instances', 'records'], []);
    const holdings = _.get(this.props.resources, ['holdings', 'records', 0, 'holdingsRecords'], []);
    const nextHoldings = _.get(nextProps.resources, ['holdings', 'records', 0, 'holdingsRecords'], []);

    if (bar !== nextBar && nextBar.length === 1) {
      if (!this.item.barcode) {
        this.item.barcode = nextBar[0].barcode;
        this.item.itemStatus = (nextBar[0].status || {}).name || '';
        this.props.mutator.activeRecord.update({ query: 'id=' + nextBar[0].holdingsRecordId });
        return false;
      }
      this.item.barcode = nextBar[0].barcode;
      this.item.itemStatus = (nextBar[0].status || {}).name || '';
    } else if (title !== nextTitle && nextTitle.length === 1) {
      if (!this.item.instance) {
        this.item.instance = nextTitle[0].title;
        this.props.mutator.activeRecord.update({ query: 'instanceId=' + nextTitle[0].id });
        return false;
      }
      this.item.instance = nextTitle[0].title;
    } else if (holdings !== nextHoldings) {
      if (!this.item.barcode && nextHoldings.length === 1) {
        this.item.barcode = nextHoldings[0].id;
        this.props.mutator.activeRecord.update({ queryItem: 'holdingsRecordId=' + nextHoldings[0].id });
        return false;
      } else if (!this.item.instance && nextHoldings.length === 1) {
        this.item.instance = nextHoldings[0].instanceId;
        this.props.mutator.activeRecord.update({ queryInstance: 'id=' + nextHoldings[0].instanceId });
        return false;
      }
    }
    return true;
  }

  componentWillUnmount() {
    this.props.mutator.activeRecord.update({
      query: 'id=0',
      queryItem: 'id=0',
      queryInstance: 'id=0',
    });
  }

  onClickCharge(type) {
    const instances = _.get(this.props.resources, ['instances', 'records', 0], {});
    const locations = _.get(this.props.resources, ['locations', 'records', 0, 'shelflocations'], []);
    const barcodes = _.get(this.props.resources, ['barcodes', 'records', 0, 'items', 0], {});
    const material = _.get(this.props.resources, ['material', 'records', 0, 'mtypes'], []);
    const holdings = _.get(this.props.resources, ['holdings', 'records', 0, 'holdingsRecords', 0], {});

    const owners = _.get(this.props.resources, ['owners', 'records'], []);
    const feefines = _.get(this.props.resources, ['feefines', 'records'], []);

    const item = {
      title: instances.title || '',
      barcode: barcodes.barcode || '',
      itemStatus: (barcodes.status || {}).name || '',
      callNumber: holdings.callNumber || '',
      location: (locations.find(l => l.id === holdings.permanentLocationId) || {}).name || '',
      type: (material.find(m => m.id === barcodes.materialTypeId) || {}).name || '',
      materialTypeId: (material.find(m => m.id === barcodes.materialTypeId) || {}).id || '0'
    };
    type.feeFineType = feefines.find(f => f.id === type.feeFineId).feeFineType || '';
    type.feeFineOwner = owners.find(o => o.id === type.ownerId).owner || '';
    type.title = item.title;
    type.barcode = item.barcode;
    type.paymentStatus = {
      name: item.itemStatus,
    };
    type.callNumber = item.callNumber;
    type.location = item.location;
    type.materialType = item.type;

    const date = new Date();
    const action = {};
    type.id = uuid();
    type.loanId = this.props.selectedLoan.id || '0';
    type.userId = this.props.user.id;
    type.itemId = this.item.id || '0';
    type.materialTypeId = item.materialTypeId;
    const source = `${this.props.okapi.currentUser.lastName}, ${this.props.okapi.currentUser.firstName}`;
    action.id = uuid();
    action.accountId = type.id;
    action.userId = type.userId;
    action.dateAction = moment(date);
    action.typeAction = (feefines.find(f => f.id === type.feeFineId) || {}).feeFineType || '';
    action.source = source;
    action.transactionNumber = 0;
    action.createdAt = 'Main Library';
    if (type.comments) {
      action.comments = type.comments;
    }
    action.amountAction = type.amount;
    action.balance = type.remaining;
    delete type.comments;
    this.props.mutator.accounts.POST(type)
      .then(() => this.props.mutator.feefineactions.POST(action));
    this.props.handleAddRecords();
    setTimeout(this.props.onCloseChargeFeeFine, 350);
  }

  onSubmit(data) {
    this.log('xhr', data);
  }

  onClickSelectItem(search) {
    this.item = {};
    this.item.load = false;
    if (search === '' || search === '0') {
      this.item.load = true;
    } else if (Number.isNaN(Number(search))) {
      this.props.mutator.activeRecord.update({ queryInstance: 'title=' + search });
    } else {
      this.props.mutator.activeRecord.update({ queryItem: 'barcode=' + search });
    }
  }

  onChangeOwner(e) {
    const ownerId = e.target.value;
    this.props.mutator.activeRecord.update({ ownerId });
    this.setState({
      ownerId,
    });
  }

  onFindShared(id) {
    this.props.mutator.activeRecord.update({ shared: id });
  }

  render() {
    const resources = this.props.resources;
    const owners = _.get(resources, ['owners', 'records'], []);
    const feefines = (this.state.ownerId !== '0') ? (resources.feefines || {}).records || [] : [];
    const instances = _.get(this.props.resources, ['instances', 'records', 0], {});
    const locations = _.get(this.props.resources, ['locations', 'records', 0, 'shelflocations'], []);
    const barcodes = _.get(this.props.resources, ['barcodes', 'records', 0, 'items', 0], {});
    const material = _.get(this.props.resources, ['material', 'records', 0, 'mtypes'], []);
    const holdings = _.get(this.props.resources, ['holdings', 'records', 0, 'holdingsRecords', 0], {});

    const item = (!this.item.load) ? {
      instance: instances.title || '',
      barcode: barcodes.barcode || '',
      itemStatus: (barcodes.status || {}).name || '',
      callNumber: holdings.callNumber || '',
      location: (locations.find(l => l.id === holdings.permanentLocationId) || {}).name || '',
      type: (material.find(m => m.id === barcodes.materialTypeId) || {}).name || '',
    } : {};

    return (
      <ChargeForm
        onClickCancel={this.props.onCloseChargeFeeFine}
        onClickCharge={this.onClickCharge}
        onSubmit={data => this.onSubmit(data)}
        user={this.props.user}
        owners={owners}
        ownerId={this.state.ownerId}
        feefines={feefines}
        item={item}
        onFindShared={this.onFindShared}
        onChangeOwner={this.onChangeOwner}
        onClickSelectItem={this.onClickSelectItem}
        stripes={this.props.stripes}
      />
    );
  }
}

export default Charge;

<commit_msg>Add condition for loan history<commit_after>import React from 'react';
import PropTypes from 'prop-types';
import uuid from 'uuid';
import _ from 'lodash';
// eslint-disable-next-line import/no-extraneous-dependencies
import moment from 'moment';

import ChargeForm from './ChargeForm';

class Charge extends React.Component {
  static manifest = Object.freeze({
    feefines: {
      type: 'okapi',
      records: 'feefines',
      GET: {
        path: 'feefines?query=(ownerId=%{activeRecord.ownerId} or ownerId=%{activeRecord.shared})&limit=100',
      },
    },
    feefineactions: {
      type: 'okapi',
      records: 'feefineactions',
      path: 'feefineactions',
    },
    owners: {
      type: 'okapi',
      records: 'owners',
      path: 'owners?limit=100',
    },
    accounts: {
      type: 'okapi',
      records: 'accounts',
      path: 'accounts',
    },
    instances: {
      type: 'okapi',
      records: 'instances',
      path: 'instance-storage/instances?query=%{activeRecord.queryInstance}',
    },
    locations: {
      type: 'okapi',
      record: 'shelflocations',
      path: 'shelf-locations',
    },
    material: {
      type: 'okapi',
      record: 'mtypes',
      path: 'material-types',
    },
    barcodes: {
      type: 'okapi',
      record: 'items',
      path: 'item-storage/items?query=%{activeRecord.queryItem}',
    },
    holdings: {
      type: 'okapi',
      record: 'holdingsRecords',
      path: 'holdings-storage/holdings?query=%{activeRecord.query}',
    },
    activeRecord: {},
  });

  static propTypes = {
    resources: PropTypes.shape({
      feefines: PropTypes.shape({
        records: PropTypes.arrayOf(PropTypes.object),
      }),
      owners: PropTypes.shape({
        records: PropTypes.arrayOf(PropTypes.object),
      }),
      chargeitem: PropTypes.shape({
        records: PropTypes.arrayOf(PropTypes.object),
      }),
    }).isRequired,
    mutator: PropTypes.shape({
      accounts: PropTypes.shape({
        POST: PropTypes.func.isRequired,
      }),
      feefineactions: PropTypes.shape({
        POST: PropTypes.func.isRequired,
      }),
      activeRecord: PropTypes.shape({
        update: PropTypes.func,
      }),
    }).isRequired,
    stripes: PropTypes.object,
    onCloseChargeFeeFine: PropTypes.func.isRequired,
    handleAddRecords: PropTypes.func,
    okapi: PropTypes.object,
    selectedLoan: PropTypes.object,
    user: PropTypes.object,
  };

  constructor(props) {
    super(props);
    this.state = {
      ownerId: '0',
    };
    this.onClickCharge = this.onClickCharge.bind(this);
    this.onClickSelectItem = this.onClickSelectItem.bind(this);
    this.onChangeOwner = this.onChangeOwner.bind(this);
    this.onFindShared = this.onFindShared.bind(this);
    this.onSubmit = this.onSubmit.bind(this);
    this.item = {};
  }

  componentDidMount() {
    this.item.load = true;
    if (this.props.selectedLoan.id) {
      this.props.mutator.activeRecord.update({ item: this.props.selectedLoan.item.barcode });
    } else {
      this.props.mutator.activeRecord.update({ item: 0 });
    }
  }

  shouldComponentUpdate(nextProps) {
    const bar = _.get(this.props.resources, ['barcodes', 'records', 0, 'items'], []);
    const nextBar = _.get(nextProps.resources, ['barcodes', 'records', 0, 'items'], []);
    const title = _.get(this.props.resources, ['instances', 'records'], []);
    const nextTitle = _.get(nextProps.resources, ['instances', 'records'], []);
    const holdings = _.get(this.props.resources, ['holdings', 'records', 0, 'holdingsRecords'], []);
    const nextHoldings = _.get(nextProps.resources, ['holdings', 'records', 0, 'holdingsRecords'], []);

    if (bar !== nextBar && nextBar.length === 1) {
      if (!this.item.barcode) {
        this.item.barcode = nextBar[0].barcode;
        this.item.itemStatus = (nextBar[0].status || {}).name || '';
        this.props.mutator.activeRecord.update({ query: 'id=' + nextBar[0].holdingsRecordId });
        return false;
      }
      this.item.barcode = nextBar[0].barcode;
      this.item.itemStatus = (nextBar[0].status || {}).name || '';
    } else if (title !== nextTitle && nextTitle.length === 1) {
      if (!this.item.instance) {
        this.item.instance = nextTitle[0].title;
        this.props.mutator.activeRecord.update({ query: 'instanceId=' + nextTitle[0].id });
        return false;
      }
      this.item.instance = nextTitle[0].title;
    } else if (holdings !== nextHoldings) {
      if (!this.item.barcode && nextHoldings.length === 1) {
        this.item.barcode = nextHoldings[0].id;
        this.props.mutator.activeRecord.update({ queryItem: 'holdingsRecordId=' + nextHoldings[0].id });
        return false;
      } else if (!this.item.instance && nextHoldings.length === 1) {
        this.item.instance = nextHoldings[0].instanceId;
        this.props.mutator.activeRecord.update({ queryInstance: 'id=' + nextHoldings[0].instanceId });
        return false;
      }
    }
    return true;
  }

  componentWillUnmount() {
    this.props.mutator.activeRecord.update({
      query: 'id=0',
      queryItem: 'id=0',
      queryInstance: 'id=0',
    });
  }

  onClickCharge(type) {
    const instances = _.get(this.props.resources, ['instances', 'records', 0], {});
    const locations = _.get(this.props.resources, ['locations', 'records', 0, 'shelflocations'], []);
    const barcodes = _.get(this.props.resources, ['barcodes', 'records', 0, 'items', 0], {});
    const material = _.get(this.props.resources, ['material', 'records', 0, 'mtypes'], []);
    const holdings = _.get(this.props.resources, ['holdings', 'records', 0, 'holdingsRecords', 0], {});

    const owners = _.get(this.props.resources, ['owners', 'records'], []);
    const feefines = _.get(this.props.resources, ['feefines', 'records'], []);
    const { selectedLoan } = this.props;

    const item = {
      title: instances.title || '',
      barcode: barcodes.barcode || '',
      itemStatus: (barcodes.status || {}).name || '',
      callNumber: holdings.callNumber || '',
      location: (locations.find(l => l.id === holdings.permanentLocationId) || {}).name || '',
      type: (material.find(m => m.id === barcodes.materialTypeId) || {}).name || '',
      materialTypeId: (material.find(m => m.id === barcodes.materialTypeId) || {}).id || '0'
    };
    type.feeFineType = feefines.find(f => f.id === type.feeFineId).feeFineType || '';
    type.feeFineOwner = owners.find(o => o.id === type.ownerId).owner || '';
    if (!selectedLoan.id) {
      type.title = item.title;
      type.barcode = item.barcode;
      type.callNumber = item.callNumber;
      type.location = item.location;
      type.materialType = item.type;
      type.materialTypeId = item.materialTypeId;
    } else {
      type.title = selectedLoan.item.title;
      type.barcode = selectedLoan.item.barcode;
      type.callNumber = selectedLoan.item.callNumber;
      type.location = selectedLoan.item.location.name;
      type.materialType = selectedLoan.item.materialType.name;
      type.materialTypeId = '0';
    }
    const date = new Date();
    const action = {};
    type.id = uuid();
    type.loanId = this.props.selectedLoan.id || '0';
    type.userId = this.props.user.id;
    type.itemId = this.item.id || '0';
    const source = `${this.props.okapi.currentUser.lastName}, ${this.props.okapi.currentUser.firstName}`;
    action.id = uuid();
    action.accountId = type.id;
    action.userId = type.userId;
    action.dateAction = moment(date);
    action.typeAction = (feefines.find(f => f.id === type.feeFineId) || {}).feeFineType || '';
    action.source = source;
    action.transactionNumber = 0;
    action.createdAt = 'Main Library';
    if (type.comments) {
      action.comments = type.comments;
    }
    action.amountAction = type.amount;
    action.balance = type.remaining;
    delete type.comments;
    this.props.mutator.accounts.POST(type)
      .then(() => this.props.mutator.feefineactions.POST(action));
    this.props.handleAddRecords();
    setTimeout(this.props.onCloseChargeFeeFine, 350);
  }

  onSubmit(data) {
    this.log('xhr', data);
  }

  onClickSelectItem(search) {
    this.item = {};
    this.item.load = false;
    if (search === '' || search === '0') {
      this.item.load = true;
    } else if (Number.isNaN(Number(search))) {
      this.props.mutator.activeRecord.update({ queryInstance: 'title=' + search });
    } else {
      this.props.mutator.activeRecord.update({ queryItem: 'barcode=' + search });
    }
  }

  onChangeOwner(e) {
    const ownerId = e.target.value;
    this.props.mutator.activeRecord.update({ ownerId });
    this.setState({
      ownerId,
    });
  }

  onFindShared(id) {
    this.props.mutator.activeRecord.update({ shared: id });
  }

  render() {
    const resources = this.props.resources;
    const owners = _.get(resources, ['owners', 'records'], []);
    const feefines = (this.state.ownerId !== '0') ? (resources.feefines || {}).records || [] : [];
    const instances = _.get(this.props.resources, ['instances', 'records', 0], {});
    const locations = _.get(this.props.resources, ['locations', 'records', 0, 'shelflocations'], []);
    const barcodes = _.get(this.props.resources, ['barcodes', 'records', 0, 'items', 0], {});
    const material = _.get(this.props.resources, ['material', 'records', 0, 'mtypes'], []);
    const holdings = _.get(this.props.resources, ['holdings', 'records', 0, 'holdingsRecords', 0], {});

    const item = (!this.item.load) ? {
      instance: instances.title || '',
      barcode: barcodes.barcode || '',
      itemStatus: (barcodes.status || {}).name || '',
      callNumber: holdings.callNumber || '',
      location: (locations.find(l => l.id === holdings.permanentLocationId) || {}).name || '',
      type: (material.find(m => m.id === barcodes.materialTypeId) || {}).name || '',
    } : {};

    return (
      <ChargeForm
        onClickCancel={this.props.onCloseChargeFeeFine}
        onClickCharge={this.onClickCharge}
        onSubmit={data => this.onSubmit(data)}
        user={this.props.user}
        owners={owners}
        ownerId={this.state.ownerId}
        feefines={feefines}
        item={item}
        onFindShared={this.onFindShared}
        onChangeOwner={this.onChangeOwner}
        onClickSelectItem={this.onClickSelectItem}
        stripes={this.props.stripes}
        {...this.props}
      />
    );
  }
}

export default Charge;

<|endoftext|>


<commit_before>(function(window, app, Backbone, jQuery, _, moment){
    "use strict";

    var $ = jQuery;

    app.GroupView = Backbone.View.extend({
        tagName: 'li',
        className: 'group',
        template: _.template(app.templates.group),

        initialize: function(){
            Backbone.View.prototype.initialize.apply(this, arguments);

            _.bindAll(this, 'updateCount', 'updateAllAnnotations', 'updateAnnotation', 'updateLastSeen',
                'updateResolved', 'updateHasSeen', 'renderSparkline', 'updateBookmarked',
                'render');

            this.model.on({
                'change:count': this.updateCount,
                'change:annotations': this.updateAllAnnotations,
                'change:lastSeen': this.updateLastSeen,
                'change:isBookmarked': this.updateBookmarked,
                'change:isResolved': this.updateResolved,
                'change:hasSeen': this.updateHasSeen,
                'change:historicalData': this.renderSparkline
            }, this);
        },

        render: function(){
            var data = this.model.toJSON();
            data.projectUrl = app.config.urlPrefix + '/' + app.config.teamId +
                '/' + data.project.slug + '/';
            data.loggerUrl = data.projectUrl + '?logger=' + data.logger;

            this.$el.html(this.template(data));
            this.$el.attr('data-id', this.model.id);
            this.$el.addClass(this.getLevelClassName());
            this.$el.find('a[data-action=resolve]').click(_.bind(function(e){
                e.preventDefault();
                if (this.model.get('isResolved')) {
                    this.unresolve();
                } else {
                    this.resolve();
                }
            }, this));
            this.$el.find('a[data-action=bookmark]').click(_.bind(function(e){
                e.preventDefault();
                this.bookmark();
            }, this));
            this.updateLastSeen();
            this.renderSparkline();
            this.updateResolved();
            this.updateHasSeen();
            this.updateBookmarked();
        },

        updateBookmarked: function(){
            if (this.model.get('isBookmarked')) {
                this.$el.find('a[data-action=bookmark]').addClass('checked');
            } else {
                this.$el.find('a[data-action=bookmark]').removeClass('checked');
            }
        },

        updateResolved: function(){
            if (this.model.get('isResolved')) {
                this.$el.addClass('resolved');
            } else {
                this.$el.removeClass('resolved');
            }
        },

        updateHasSeen: function(){
            if (this.model.get('hasSeen')) {
                this.$el.addClass('seen');
            } else {
                this.$el.removeClass('seen');
            }
        },

        renderSparkline: function(obj){
            var data = this.model.get('historicalData');
            if (!data || !data.length)
                return;

            this.$el.addClass('with-sparkline');

            app.charts.createSparkline(this.$el.find('.sparkline'), data);
        },

        resolve: function(){
            $.ajax({
                url: this.getResolveUrl(),
                type: 'post',
                dataType: 'json',
                success: _.bind(function(response) {
                    this.model.set('version', response.version);
                    this.model.set('isResolved', true);
                }, this)
            });
        },

        unresolve: function(){
            $.ajax({
                url: this.getUnresolveUrl(),
                type: 'post',
                dataType: 'json',
                success: _.bind(function(response) {
                    this.model.set('version', response.version);
                    this.model.set('isResolved', false);
                }, this)
            });
        },

        getResolveUrl: function(){
            return app.config.urlPrefix + '/api/' + app.config.teamId + '/' +
                    app.config.projectId + '/group/' + this.model.get('id') +
                    '/set/resolved/';
        },

        getUnresolveUrl: function(){
            return app.config.urlPrefix + '/api/' + app.config.teamId + '/' +
                    app.config.projectId + '/group/' + this.model.get('id') +
                    '/set/unresolved/';
        },

        getBookmarkUrl: function(){
            return app.config.urlPrefix + '/api/' + app.config.teamId + '/' + app.config.projectId + '/bookmark/';
        },

        bookmark: function(){
            $.ajax({
                url: this.getBookmarkUrl(),
                type: 'post',
                dataType: 'json',
                data: {
                    gid: this.model.get('id')
                },
                success: _.bind(function(response){
                    this.model.set('version', response.version);
                    this.model.set('isBookmarked', response.isBookmarked);
                }, this)
            });
        },

        getLevelClassName: function(){
            return 'level-' + this.model.get('levelName');
        },

        updateLastSeen: function(){
            var dt = moment(this.model.get('lastSeen'));
            this.$el.find('.last-seen')
                .text(dt.fromNow())
                .data('datetime', this.model.get('lastSeen'))
                .attr('title', dt.format('llll'));
        },

        updateCount: function(){
            var new_count = app.utils.formatNumber(this.model.get('count'));
            var counter = this.$el.find('.count');
            var digit = counter.find('span');

            if (digit.is(':animated'))
                return false;

            if (counter.data('count') == new_count) {
                // We are already showing this number
                return false;
            }

            counter.data('count', new_count);

            var replacement = $('', {
                css: {
                    top: '-2.1em',
                    opacity: 0
                },
                text: new_count
            });

            // The .static class is added when the animation
            // completes. This makes it run smoother.

            digit.before(replacement).animate({
                top: '2.5em',
                opacity: 0
            }, 'fast', function(){
                digit.remove();
            });

            replacement.delay(100).animate({
                top: 0,
                opacity: 1
            }, 'fast');
        },

        updateAnnotation: function(annotation){
            var value = annotation.count;
            if (value === null)
                return;
            var new_count = app.utils.formatNumber(value);
            var counter = this.$el.find('.annotation[data-tag="' + annotation.label + '"]');
            var digit = counter.find('span');

            if (digit.is(':animated'))
                return false;

            if (counter.data('count') == new_count) {
                // We are already showing this number
                return false;
            }

            counter.data('count', new_count);

            var replacement = $('', {
                css: {
                    top: '-2.1em',
                    opacity: 0
                },
                text: new_count
            });

            // The .static class is added when the animation
            // completes. This makes it run smoother.

            digit.before(replacement).animate({
                top: '2.5em',
                opacity: 0
            }, 'fast', function(){
                digit.remove();
            });

            replacement.delay(100).animate({
                top: 0,
                opacity: 1
            }, 'fast');
        },

        updateAllAnnotations: function(){
            var self = this;
            $.each(this.model.get('annotations'), function(index, annotation){
                self.updateAnnotation(annotation);
            });
        }

    });

    app.OrderedElementsView = Backbone.View.extend({

        emptyMessage: '

No events to show.

We\'ll notify you if that changes. In the meantime why not take a moment to become more familiar with Sentry.

Installation instructions Project settings

',
        loadingMessage: '
Loading...
',
        model: app.models.Group,

        defaults: {
            maxItems: 50,
            view: Backbone.View
        },

        initialize: function(data){
            if (_.isUndefined(data))
                data = {};

            var members = data.members;

            Backbone.View.prototype.initialize.apply(this, arguments);

            this.options = $.extend({}, this.defaults, this.options, data);

            this.$wrapper = $('#' + this.id);
            this.$parent = $('

');
            this.$empty = $('

');
            this.$wrapper.html(this.$parent);

            if (this.options.className)
                this.$parent.addClass(this.options.className);

            _.bindAll(this, 'renderMemberInContainer', 'unrenderMember', 'reSortMembers');

            this.collection = new app.ScoredList([], {
                model: data.model
            });
            this.collection.on('add', this.renderMemberInContainer, this);
            this.collection.on('remove', this.unrenderMember, this);
            this.collection.on('reset', this.reSortMembers, this);

            delete data.members;

            this.reset(members);
        },

        reset: function(members){
            this.$parent.empty();
            this.setEmpty();

            if (members === undefined) {
                this.$empty.html(this.loadingMessage);
                this.collection.reset();
                this.setEmpty();
                this.loaded = false;
            } else {
                this.$empty.html(this.emptyMessage);
                this.collection.reset(members);
                this.loaded = true;
            }
        },

        setEmpty: function(){
            this.$parent.html(this.$empty);
        },

        extend: function(data){
            for (var i=0; i<data.length; i++) {
                this.addMember(data[i]);
            }
        },

        addMember: function(member){
            if (!this.hasMember(member)) {
                if (this.collection.length >= this.options.maxItems) {
                    // bail early if the score is too low
                    if (member.score < this.collection.last().get('score'))
                        return;

                    // make sure we limit the number shown
                    while (this.collection.length >= this.options.maxItems)
                        this.collection.pop();
                }
            }
            this.collection.add(member, {merge: true});
        },

        reSortMembers: function(){
            this.collection.each(_.bind(function(member){
                this.renderMemberInContainer(member);
            }, this));
        },

        updateMember: function(member, options){
            if (_.isUndefined(options))
                options = {};

            var existing = this.collection.get(member.id);
            if (existing.get('version') > member.get('version'))
                return;

            this.collection.add(member, {
                merge: true,
                sort: options.sort !== false ? true : false
            });

        },

        hasMember: function(member){
            return (this.collection.get(member.id) ? true : false);
        },

        removeMember: function(member){
            this.collection.remove(member);
        },

        renderMemberInContainer: function(member){
            var new_pos = this.collection.indexOf(member),
                $el, $rel;

            this.$parent.find('li.empty').remove();

            $el = $('#' + this.id + member.id);

            if (!$el.length) {
                // create the element if it does not yet exist
                $el = this.renderMember(member);
            } else if ($el.index() === new_pos) {
                // if the row was already present, ensure it moved
                return;
            }

            // top item
            if (new_pos === 0) {
                this.$parent.prepend($el);
            } else {
                // find existing item at new position
                $rel = $('#' + this.id + this.collection.at(new_pos).id);
                if (!$rel.length) {
                    this.$parent.append($el);
                } else if ($el.id !== $rel.id) {
                    // TODO: why do we get here?
                    $el.insertBefore($rel);
                } else {

                    return;
                }
            }

            if (this.loaded)
                $el.css('background-color', '#eee').animate({backgroundColor: '#fff'}, 300);
        },

        renderMember: function(member){
            var view = new this.options.view({
                model: member,
                id: this.id + member.id
            });
            view.render();
            return view.$el;
        },

        unrenderMember: function(member){
            this.$parent.find('#' + this.id + member.id).remove();
            if (!this.$parent.find('li').length)
                this.setEmpty();
        }

    });

    app.GroupListView = app.OrderedElementsView.extend({

        defaults: {
            realtime: false,
            stream: false,
            pollUrl: null,
            pollTime: 1000,
            tickTime: 100
        },

        initialize: function(data){
            if (_.isUndefined(data))
                data = {};

            data.model = app.models.Group;
            data.view = app.GroupView;

            app.OrderedElementsView.prototype.initialize.call(this, data);

            this.options = $.extend({}, this.defaults, this.options, data);

            this.queue = new app.ScoredList([], {
                model: data.model
            });

            this.cursor = null;

            _.bindAll(this, 'poll', 'pollSuccess', 'pollFailure', 'tick');

            this.poll();

            window.setInterval(this.tick, this.options.tickTime);
        },

        tick: function(){
            if (!this.queue.length)
                return;

            var item = this.queue.pop();
            if (this.options.canStream){
                this.addMember(item);
            } else if (this.hasMember(item)) {
                this.updateMember(item, {
                    sort: false
                });
            }
        },

        pollSuccess: function(groups){
            if (!groups.length)
                return window.setTimeout(this.poll, this.options.pollTime * 5);

            this.cursor = groups[groups.length - 1].score;

            this.queue.add(groups, {merge: true});

            window.setTimeout(this.poll, this.options.pollTime);
        },

        pollFailure: function(jqXHR, textStatus, errorThrown){
            // if an error happened lets give the server a bit of time before we poll again
            window.setTimeout(this.poll, this.options.pollTime * 10);
        },

        poll: function(){
            var data;

            if (!this.options.realtime || !this.options.pollUrl)
                return window.setTimeout(this.poll, this.options.pollTime);

            data = app.utils.getQueryParams();
            data.cursor = this.cursor || undefined;

            $.ajax({
                url: this.options.pollUrl,
                type: 'GET',
                dataType: 'json',
                data: data,
                success: this.pollSuccess,
                error: this.pollFailure
            });
        }

    });

}(window, app, Backbone, jQuery, _, moment));
<commit_msg>Dirty hack to fix "I resolved this but it just reappeared as unresolved"<commit_after>(function(window, app, Backbone, jQuery, _, moment){
    "use strict";

    var $ = jQuery;

    app.GroupView = Backbone.View.extend({
        tagName: 'li',
        className: 'group',
        template: _.template(app.templates.group),

        initialize: function(){
            Backbone.View.prototype.initialize.apply(this, arguments);

            _.bindAll(this, 'updateCount', 'updateAllAnnotations', 'updateAnnotation', 'updateLastSeen',
                'updateResolved', 'updateHasSeen', 'renderSparkline', 'updateBookmarked',
                'render');

            this.model.on({
                'change:count': this.updateCount,
                'change:annotations': this.updateAllAnnotations,
                'change:lastSeen': this.updateLastSeen,
                'change:isBookmarked': this.updateBookmarked,
                'change:isResolved': this.updateResolved,
                'change:hasSeen': this.updateHasSeen,
                'change:historicalData': this.renderSparkline
            }, this);
        },

        render: function(){
            var data = this.model.toJSON();
            data.projectUrl = app.config.urlPrefix + '/' + app.config.teamId +
                '/' + data.project.slug + '/';
            data.loggerUrl = data.projectUrl + '?logger=' + data.logger;

            this.$el.html(this.template(data));
            this.$el.attr('data-id', this.model.id);
            this.$el.addClass(this.getLevelClassName());
            this.$el.find('a[data-action=resolve]').click(_.bind(function(e){
                e.preventDefault();
                if (this.model.get('isResolved')) {
                    this.unresolve();
                } else {
                    this.resolve();
                }
            }, this));
            this.$el.find('a[data-action=bookmark]').click(_.bind(function(e){
                e.preventDefault();
                this.bookmark();
            }, this));
            this.updateLastSeen();
            this.renderSparkline();
            this.updateResolved();
            this.updateHasSeen();
            this.updateBookmarked();
        },

        updateBookmarked: function(){
            if (this.model.get('isBookmarked')) {
                this.$el.find('a[data-action=bookmark]').addClass('checked');
            } else {
                this.$el.find('a[data-action=bookmark]').removeClass('checked');
            }
        },

        updateResolved: function(){
            if (this.model.get('isResolved')) {
                this.$el.addClass('resolved');
            } else {
                this.$el.removeClass('resolved');
            }
        },

        updateHasSeen: function(){
            if (this.model.get('hasSeen')) {
                this.$el.addClass('seen');
            } else {
                this.$el.removeClass('seen');
            }
        },

        renderSparkline: function(obj){
            var data = this.model.get('historicalData');
            if (!data || !data.length)
                return;

            this.$el.addClass('with-sparkline');

            app.charts.createSparkline(this.$el.find('.sparkline'), data);
        },

        resolve: function(){
            $.ajax({
                url: this.getResolveUrl(),
                type: 'post',
                dataType: 'json',
                success: _.bind(function(response) {
                    this.model.set('version', response.version + 1000);
                    this.model.set('isResolved', true);
                }, this)
            });
        },

        unresolve: function(){
            $.ajax({
                url: this.getUnresolveUrl(),
                type: 'post',
                dataType: 'json',
                success: _.bind(function(response) {
                    this.model.set('version', response.version + 1000);
                    this.model.set('isResolved', false);
                }, this)
            });
        },

        getResolveUrl: function(){
            return app.config.urlPrefix + '/api/' + app.config.teamId + '/' +
                    app.config.projectId + '/group/' + this.model.get('id') +
                    '/set/resolved/';
        },

        getUnresolveUrl: function(){
            return app.config.urlPrefix + '/api/' + app.config.teamId + '/' +
                    app.config.projectId + '/group/' + this.model.get('id') +
                    '/set/unresolved/';
        },

        getBookmarkUrl: function(){
            return app.config.urlPrefix + '/api/' + app.config.teamId + '/' + app.config.projectId + '/bookmark/';
        },

        bookmark: function(){
            $.ajax({
                url: this.getBookmarkUrl(),
                type: 'post',
                dataType: 'json',
                data: {
                    gid: this.model.get('id')
                },
                success: _.bind(function(response){
                    this.model.set('version', response.version);
                    this.model.set('isBookmarked', response.isBookmarked);
                }, this)
            });
        },

        getLevelClassName: function(){
            return 'level-' + this.model.get('levelName');
        },

        updateLastSeen: function(){
            var dt = moment(this.model.get('lastSeen'));
            this.$el.find('.last-seen')
                .text(dt.fromNow())
                .data('datetime', this.model.get('lastSeen'))
                .attr('title', dt.format('llll'));
        },

        updateCount: function(){
            var new_count = app.utils.formatNumber(this.model.get('count'));
            var counter = this.$el.find('.count');
            var digit = counter.find('span');

            if (digit.is(':animated'))
                return false;

            if (counter.data('count') == new_count) {
                // We are already showing this number
                return false;
            }

            counter.data('count', new_count);

            var replacement = $('', {
                css: {
                    top: '-2.1em',
                    opacity: 0
                },
                text: new_count
            });

            // The .static class is added when the animation
            // completes. This makes it run smoother.

            digit.before(replacement).animate({
                top: '2.5em',
                opacity: 0
            }, 'fast', function(){
                digit.remove();
            });

            replacement.delay(100).animate({
                top: 0,
                opacity: 1
            }, 'fast');
        },

        updateAnnotation: function(annotation){
            var value = annotation.count;
            if (value === null)
                return;
            var new_count = app.utils.formatNumber(value);
            var counter = this.$el.find('.annotation[data-tag="' + annotation.label + '"]');
            var digit = counter.find('span');

            if (digit.is(':animated'))
                return false;

            if (counter.data('count') == new_count) {
                // We are already showing this number
                return false;
            }

            counter.data('count', new_count);

            var replacement = $('', {
                css: {
                    top: '-2.1em',
                    opacity: 0
                },
                text: new_count
            });

            // The .static class is added when the animation
            // completes. This makes it run smoother.

            digit.before(replacement).animate({
                top: '2.5em',
                opacity: 0
            }, 'fast', function(){
                digit.remove();
            });

            replacement.delay(100).animate({
                top: 0,
                opacity: 1
            }, 'fast');
        },

        updateAllAnnotations: function(){
            var self = this;
            $.each(this.model.get('annotations'), function(index, annotation){
                self.updateAnnotation(annotation);
            });
        }

    });

    app.OrderedElementsView = Backbone.View.extend({

        emptyMessage: '

No events to show.

We\'ll notify you if that changes. In the meantime why not take a moment to become more familiar with Sentry.

Installation instructions Project settings

',
        loadingMessage: '
Loading...
',
        model: app.models.Group,

        defaults: {
            maxItems: 50,
            view: Backbone.View
        },

        initialize: function(data){
            if (_.isUndefined(data))
                data = {};

            var members = data.members;

            Backbone.View.prototype.initialize.apply(this, arguments);

            this.options = $.extend({}, this.defaults, this.options, data);

            this.$wrapper = $('#' + this.id);
            this.$parent = $('

');
            this.$empty = $('

');
            this.$wrapper.html(this.$parent);

            if (this.options.className)
                this.$parent.addClass(this.options.className);

            _.bindAll(this, 'renderMemberInContainer', 'unrenderMember', 'reSortMembers');

            this.collection = new app.ScoredList([], {
                model: data.model
            });
            this.collection.on('add', this.renderMemberInContainer, this);
            this.collection.on('remove', this.unrenderMember, this);
            this.collection.on('reset', this.reSortMembers, this);

            delete data.members;

            this.reset(members);
        },

        reset: function(members){
            this.$parent.empty();
            this.setEmpty();

            if (members === undefined) {
                this.$empty.html(this.loadingMessage);
                this.collection.reset();
                this.setEmpty();
                this.loaded = false;
            } else {
                this.$empty.html(this.emptyMessage);
                this.collection.reset(members);
                this.loaded = true;
            }
        },

        setEmpty: function(){
            this.$parent.html(this.$empty);
        },

        extend: function(data){
            for (var i=0; i<data.length; i++) {
                this.addMember(data[i]);
            }
        },

        addMember: function(member){
            if (!this.hasMember(member)) {
                if (this.collection.length >= this.options.maxItems) {
                    // bail early if the score is too low
                    if (member.score < this.collection.last().get('score'))
                        return;

                    // make sure we limit the number shown
                    while (this.collection.length >= this.options.maxItems)
                        this.collection.pop();
                }
            }
            this.collection.add(member, {merge: true});
        },

        reSortMembers: function(){
            this.collection.each(_.bind(function(member){
                this.renderMemberInContainer(member);
            }, this));
        },

        updateMember: function(member, options){
            if (_.isUndefined(options))
                options = {};

            var existing = this.collection.get(member.id);
            if (existing.get('version') > member.get('version'))
                return;

            this.collection.add(member, {
                merge: true,
                sort: options.sort !== false ? true : false
            });

        },

        hasMember: function(member){
            return (this.collection.get(member.id) ? true : false);
        },

        removeMember: function(member){
            this.collection.remove(member);
        },

        renderMemberInContainer: function(member){
            var new_pos = this.collection.indexOf(member),
                $el, $rel;

            this.$parent.find('li.empty').remove();

            $el = $('#' + this.id + member.id);

            if (!$el.length) {
                // create the element if it does not yet exist
                $el = this.renderMember(member);
            } else if ($el.index() === new_pos) {
                // if the row was already present, ensure it moved
                return;
            }

            // top item
            if (new_pos === 0) {
                this.$parent.prepend($el);
            } else {
                // find existing item at new position
                $rel = $('#' + this.id + this.collection.at(new_pos).id);
                if (!$rel.length) {
                    this.$parent.append($el);
                } else if ($el.id !== $rel.id) {
                    // TODO: why do we get here?
                    $el.insertBefore($rel);
                } else {

                    return;
                }
            }

            if (this.loaded)
                $el.css('background-color', '#eee').animate({backgroundColor: '#fff'}, 300);
        },

        renderMember: function(member){
            var view = new this.options.view({
                model: member,
                id: this.id + member.id
            });
            view.render();
            return view.$el;
        },

        unrenderMember: function(member){
            this.$parent.find('#' + this.id + member.id).remove();
            if (!this.$parent.find('li').length)
                this.setEmpty();
        }

    });

    app.GroupListView = app.OrderedElementsView.extend({

        defaults: {
            realtime: false,
            stream: false,
            pollUrl: null,
            pollTime: 1000,
            tickTime: 100
        },

        initialize: function(data){
            if (_.isUndefined(data))
                data = {};

            data.model = app.models.Group;
            data.view = app.GroupView;

            app.OrderedElementsView.prototype.initialize.call(this, data);

            this.options = $.extend({}, this.defaults, this.options, data);

            this.queue = new app.ScoredList([], {
                model: data.model
            });

            this.cursor = null;

            _.bindAll(this, 'poll', 'pollSuccess', 'pollFailure', 'tick');

            this.poll();

            window.setInterval(this.tick, this.options.tickTime);
        },

        tick: function(){
            if (!this.queue.length)
                return;

            var item = this.queue.pop();
            if (this.options.canStream){
                this.addMember(item);
            } else if (this.hasMember(item)) {
                this.updateMember(item, {
                    sort: false
                });
            }
        },

        pollSuccess: function(groups){
            if (!groups.length)
                return window.setTimeout(this.poll, this.options.pollTime * 5);

            this.cursor = groups[groups.length - 1].score;

            this.queue.add(groups, {merge: true});

            window.setTimeout(this.poll, this.options.pollTime);
        },

        pollFailure: function(jqXHR, textStatus, errorThrown){
            // if an error happened lets give the server a bit of time before we poll again
            window.setTimeout(this.poll, this.options.pollTime * 10);
        },

        poll: function(){
            var data;

            if (!this.options.realtime || !this.options.pollUrl)
                return window.setTimeout(this.poll, this.options.pollTime);

            data = app.utils.getQueryParams();
            data.cursor = this.cursor || undefined;

            $.ajax({
                url: this.options.pollUrl,
                type: 'GET',
                dataType: 'json',
                data: data,
                success: this.pollSuccess,
                error: this.pollFailure
            });
        }

    });

}(window, app, Backbone, jQuery, _, moment));
<|endoftext|>


<commit_before>/*!
 * OS.js - JavaScript Cloud/Web Desktop Platform
 *
 * Mysql Handler: Login screen and session/settings handling via database
 * PLEASE NOTE THAT THIS AN EXAMPLE ONLY, AND SHOUD BE MODIFIED BEFORE USAGE
 *
 * Copyright (c) 2011-2016, Anders Evenrud <andersevenrud@gmail.com>
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *
 * 1. Redistributions of source code must retain the above copyright notice, this
 *    list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright notice,
 *    this list of conditions and the following disclaimer in the documentation
 *    and/or other materials provided with the distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
 * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 *
 * @author  Anders Evenrud <andersevenrud@gmail.com>
 * @licence Simplified BSD License
 */
(function(mysql, bcrypt) {
  'use strict';

  var pool;

  function select(q, a, cb) {
    if ( !pool ) {
      cb('No mysql connection available');
      return;
    }

    pool.getConnection(function(err, connection) {
      if ( err ) {
        cb(err);
        return;
      }

      connection.query(q, a, function(err, row, fields) {
        cb(err, row, fields);
        connection.release();
      });
    });
  }

  /////////////////////////////////////////////////////////////////////////////
  // USER SESSION ABSTRACTION
  /////////////////////////////////////////////////////////////////////////////

  var APIUser = function() {};
  APIUser.login = function(login, request, response, callback, config, handler) {
    console.log('APIUser::login()');

    function complete(data) {
      handler.onLogin(request, response, {
        userData: {
          id : data.id,
          username : data.username,
          name : data.name,
          groups : data.groups
        },
        userSettings: data.settings
      }, callback);
    }

    function invalid() {
      callback('Invalid login credentials');
    }

    function onerror(err) {
      console.error(err.toString());
      callback(err.toString());
      return;
    }

    if ( !login ) {
      invalid();
      return;
    }

    function getUserInfo() {
      var q = 'SELECT `id`, `username`, `name`, `groups`, `settings` FROM `users` WHERE `username` = ? LIMIT 1;';
      var a = [login.username];

      select(q, a, function(err, rows, fields) {
        if ( err ) {
          onerror(err);
          return;
        }

        if ( rows[0] ) {
          var row = rows[0];
          var settings = {};
          var groups = [];

          try {
            settings = JSON.parse(row.settings);
          } catch ( e ) {
            console.log('failed to parse settings', e);
          }

          try {
            groups = JSON.parse(row.groups);
          } catch ( e ) {
            console.log('failed to parse groups', e);
          }

          complete({
            id: parseInt(row.id, 10),
            username: row.username,
            name: row.name,
            groups: groups,
            settings: settings
          });
          return;
        }
        invalid();
      });
    }

    var q = 'SELECT `password` FROM `users` WHERE `username` = ? LIMIT 1;';
    var a = [login.username];

    select(q, a, function(err, rows, fields) {
      if ( err ) {
        onerror(err);
        return;
      }

      if ( rows[0] ) {
        var row = rows[0];
        var hash = row.password.replace(/^\$2y(.+)$/i, '\$2a$1');
        bcrypt.compare(login.password, hash, function(err, res) {
          if ( err ) {
            onerror(err);
          } else {
            if ( res === true ) {
              getUserInfo();
            } else {
              invalid();
            }
          }
        });
        return;
      }

      invalid();
    });
  };

  APIUser.updateSettings = function(settings, request, response, callback) {
    var uname = request.cookies.get('username');

    var q = 'UPDATE `users` SET `settings` = ? WHERE `username` = ?;';
    var a = [JSON.stringify(settings), uname];

    select(q, a, function(err, rows, fields) {
      if ( err ) {
        onerror(err);
        return;
      }

      callback(false, true);
    });
  };

  /////////////////////////////////////////////////////////////////////////////
  // API
  /////////////////////////////////////////////////////////////////////////////

  var API = {
    login: function(args, callback, request, response, config, handler) {
      APIUser.login(args, request, response, function(error, result) {
        if ( error ) {
          callback(error);
          return;
        }

        handler.onLogin(request, response, result, function() {
          callback(false, result);
        });
      }, config, handler);
    },

    logout: function(args, callback, request, response, config, handler) {
      handler.onLogout(request, response, callback);
    },

    settings: function(args, callback, request, response, config, handler) {
      APIUser.updateSettings(args.settings, request, response, callback);
    }
  };

  /////////////////////////////////////////////////////////////////////////////
  // EXPORTS
  /////////////////////////////////////////////////////////////////////////////

  /**
   * @api handler.MysqlHandler
   * @see handler.Handler
   * @class
   */
  exports.register = function(instance, DefaultHandler) {
    function MysqlHandler() {
      DefaultHandler.call(this, instance, API);
    }

    MysqlHandler.prototype = Object.create(DefaultHandler.prototype);
    MysqlHandler.constructor = DefaultHandler;

    MysqlHandler.prototype.onServerStart = function(cb) {
      var cfg = instance.config.handlers.mysql;

      pool = mysql.createPool(cfg);

      cb();
    };

    MysqlHandler.prototype.onServerEnd = function(cb) {
      cb();
    };

    return new MysqlHandler();
  };

})(require('mysql'), require('bcryptjs'));
<commit_msg>mysql-handler: Renamed query method<commit_after>/*!
 * OS.js - JavaScript Cloud/Web Desktop Platform
 *
 * Mysql Handler: Login screen and session/settings handling via database
 * PLEASE NOTE THAT THIS AN EXAMPLE ONLY, AND SHOUD BE MODIFIED BEFORE USAGE
 *
 * Copyright (c) 2011-2016, Anders Evenrud <andersevenrud@gmail.com>
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *
 * 1. Redistributions of source code must retain the above copyright notice, this
 *    list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright notice,
 *    this list of conditions and the following disclaimer in the documentation
 *    and/or other materials provided with the distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
 * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 *
 * @author  Anders Evenrud <andersevenrud@gmail.com>
 * @licence Simplified BSD License
 */
(function(mysql, bcrypt) {
  'use strict';

  var pool;

  function query(q, a, cb) {
    if ( !pool ) {
      cb('No mysql connection available');
      return;
    }

    pool.getConnection(function(err, connection) {
      if ( err ) {
        cb(err);
        return;
      }

      connection.query(q, a, function(err, row, fields) {
        cb(err, row, fields);
        connection.release();
      });
    });
  }

  /////////////////////////////////////////////////////////////////////////////
  // USER SESSION ABSTRACTION
  /////////////////////////////////////////////////////////////////////////////

  var APIUser = function() {};
  APIUser.login = function(login, request, response, callback, config, handler) {
    console.log('APIUser::login()');

    function complete(data) {
      handler.onLogin(request, response, {
        userData: {
          id : data.id,
          username : data.username,
          name : data.name,
          groups : data.groups
        },
        userSettings: data.settings
      }, callback);
    }

    function invalid() {
      callback('Invalid login credentials');
    }

    function onerror(err) {
      console.error(err.toString());
      callback(err.toString());
      return;
    }

    if ( !login ) {
      invalid();
      return;
    }

    function getUserInfo() {
      var q = 'SELECT `id`, `username`, `name`, `groups`, `settings` FROM `users` WHERE `username` = ? LIMIT 1;';
      var a = [login.username];

      query(q, a, function(err, rows, fields) {
        if ( err ) {
          onerror(err);
          return;
        }

        if ( rows[0] ) {
          var row = rows[0];
          var settings = {};
          var groups = [];

          try {
            settings = JSON.parse(row.settings);
          } catch ( e ) {
            console.log('failed to parse settings', e);
          }

          try {
            groups = JSON.parse(row.groups);
          } catch ( e ) {
            console.log('failed to parse groups', e);
          }

          complete({
            id: parseInt(row.id, 10),
            username: row.username,
            name: row.name,
            groups: groups,
            settings: settings
          });
          return;
        }
        invalid();
      });
    }

    var q = 'SELECT `password` FROM `users` WHERE `username` = ? LIMIT 1;';
    var a = [login.username];

    query(q, a, function(err, rows, fields) {
      if ( err ) {
        onerror(err);
        return;
      }

      if ( rows[0] ) {
        var row = rows[0];
        var hash = row.password.replace(/^\$2y(.+)$/i, '\$2a$1');
        bcrypt.compare(login.password, hash, function(err, res) {
          if ( err ) {
            onerror(err);
          } else {
            if ( res === true ) {
              getUserInfo();
            } else {
              invalid();
            }
          }
        });
        return;
      }

      invalid();
    });
  };

  APIUser.updateSettings = function(settings, request, response, callback) {
    var uname = request.cookies.get('username');

    var q = 'UPDATE `users` SET `settings` = ? WHERE `username` = ?;';
    var a = [JSON.stringify(settings), uname];

    query(q, a, function(err, rows, fields) {
      if ( err ) {
        onerror(err);
        return;
      }

      callback(false, true);
    });
  };

  /////////////////////////////////////////////////////////////////////////////
  // API
  /////////////////////////////////////////////////////////////////////////////

  var API = {
    login: function(args, callback, request, response, config, handler) {
      APIUser.login(args, request, response, function(error, result) {
        if ( error ) {
          callback(error);
          return;
        }

        handler.onLogin(request, response, result, function() {
          callback(false, result);
        });
      }, config, handler);
    },

    logout: function(args, callback, request, response, config, handler) {
      handler.onLogout(request, response, callback);
    },

    settings: function(args, callback, request, response, config, handler) {
      APIUser.updateSettings(args.settings, request, response, callback);
    }
  };

  /////////////////////////////////////////////////////////////////////////////
  // EXPORTS
  /////////////////////////////////////////////////////////////////////////////

  /**
   * @api handler.MysqlHandler
   * @see handler.Handler
   * @class
   */
  exports.register = function(instance, DefaultHandler) {
    function MysqlHandler() {
      DefaultHandler.call(this, instance, API);
    }

    MysqlHandler.prototype = Object.create(DefaultHandler.prototype);
    MysqlHandler.constructor = DefaultHandler;

    MysqlHandler.prototype.onServerStart = function(cb) {
      var cfg = instance.config.handlers.mysql;

      pool = mysql.createPool(cfg);

      cb();
    };

    MysqlHandler.prototype.onServerEnd = function(cb) {
      cb();
    };

    return new MysqlHandler();
  };

})(require('mysql'), require('bcryptjs'));
<|endoftext|>


<commit_before>{
  "id":15,
  "name":"Apply for a Budgeting Loan",
  "description": "Lets users apply for a short-term cash loan so that they can afford to buy essential items or services.",
  "theme": "Working Age",
  "location": "Leeds",
  "phase":"beta",
  "sro":"Derek Hobbs",
  "service_man":"Zoe Gould",
  "phase-history":{
    "discovery": [
      {"label":"Completed", "date": "May 2015"}
    ],
    "alpha": [
      {"label":"Completed", "date": "November 2015"}
    ],
    "beta": [
      {"label":"Started", "date": "November 2015"},
      {"label":"Private Beta Predicted", "date": "January 2015"},
      {"label":"Public Beta Predicted", "date": "March 2016"}
    ]
  },
  "priority":"Low"
}
<commit_msg>Update budgeting-loans.js<commit_after>{
  "id":15,
  "name":"Apply for a Budgeting Loan",
  "description": "Lets users apply for a short-term cash loan so that they can afford to buy essential items or services.",
  "theme": "Working Age",
  "location": "Leeds",
  "phase":"beta",
  "sro":"Derek Hobbs",
  "service_man":"Zoe Gould",
  "phase-history":{
    "discovery": [
      {"label":"Completed", "date": "May 2015"}
    ],
    "alpha": [
      {"label":"Completed", "date": "November 2015"}
    ],
    "beta": [
      {"label":"Started", "date": "November 2015"},
      {"label":"Private beta predicted", "date": "January 2015"},
      {"label":"Public beta predicted", "date": "March 2016"}
    ]
  },
  "priority":"Low"
}
<|endoftext|>


<commit_before>const qs = require('qs')
const Mock = require('mockjs')

const dic = {
  1: {
    1: [1, 2],
    2: [1],
    3: [1, 2, 3, 4, 5],
    4: [1, 2, 3, 4, 5],
    5: [1, 2, 3, 4, 5],
    6: [1],
    7: [1, 2, 4],
    8: [1],
    9: [1, 2, 3, 4, 5],
    10: [1],
    11: [1, 2],
    12: [1],
    13: [1, 2],
    14: [1, 2],
    15: [1],
    16: [1, 2],
    17: [1, 2],
    18: [1, 2],
    19: [1, 2],
  },
  2: {
    1: [1],
    2: [1],
    3: [1],
    4: [1, 3, 4],
    5: [1, 2, 3, 4, 5],
    6: [1],
    7: [1, 4],
  },
  3: {
    1: [1, 2],
    2: [1],
    3: [1, 2],
    4: [1, 2],
    5: [1, 2],
    6: [1],
    7: [1, 2, 4],
    8: [1],
    9: [1, 2, 3, 4, 5],
    10: [1],
    11: [1, 2],
    12: [1, 2],
    13: [1, 2],
    14: [1, 2],
    15: [1],
    16: [1, 2],
    17: [1, 2],
    18: [1, 2],
    19: [1, 2],
  },
}

let roleList = Mock.mock({
  'data|3': [
    {
      'id|+1': 1,
      'name|+1': ['管理员', '教师', '学生'],
      'power|+1': [dic[1], dic[2], dic[3]],
    },
  ],
  page: {
    total: 3,
    current: 1,
  },
})

global.roleListData = roleList
global.powerList = dic

module.exports = {

  'GET /api/role': function (req, res) {
    const page = qs.parse(req.query)
    const pageSize = page.pageSize || 10
    const currentPage = page.page || 1

    let data
    let newPage

    let newData = roleList.data.concat()

    if (page.field) {
      const d = newData.filter((item) => {
        return item[page.field].indexOf(decodeURI(page.keyword)) > -1
      })

      data = d.slice((currentPage - 1) * pageSize, currentPage * pageSize)

      newPage = {
        current: currentPage * 1,
        total: d.length,
      }
    } else {
      data = roleList.data.slice((currentPage - 1) * pageSize, currentPage * pageSize)
      roleList.page.current = currentPage * 1
      newPage = roleList.page
    }
    res.json({ success: true, list: data, page: { ...newPage, pageSize: Number(pageSize) } })
  },

  'POST /api/role': function (req, res) {
    const curItem = req.body

    if (curItem.id) {
      roleList.data = roleList.data.map((item) => {
        if (item.id === curItem.id) {
          return { ...curItem, power: JSON.parse(curItem.power) }
        }
        return item
      })
    } else {
      curItem.id = roleList.data.length + 1
      roleList.data.push({ ...curItem, power: JSON.parse(curItem.power) })

      roleList.page.total = roleList.data.length
      roleList.page.current = 1
    }
    res.json({ success: true, data: roleList.data, page: roleList.page })
  },

  'DELETE /api/role': function (req, res) {
    const deleteItem = req.body

    roleList.data = roleList.data.filter((item) => {
      return item.id !== deleteItem.id
    })

    roleList.page.total = roleList.data.length

    res.json({ success: true, data: roleList.data, page: roleList.page })
  },

}
<commit_msg>fix mock power<commit_after>const qs = require('qs')
const Mock = require('mockjs')

const dic = {
  1: {
    1: [1, 2],
    2: [1],
    3: [1, 2, 3, 4, 5],
    4: [1, 2, 3, 4, 5],
    5: [1, 2, 3, 4, 5],
    6: [1],
    7: [1, 2, 4],
    8: [1],
    9: [1, 2, 3, 4, 5],
    10: [1],
    11: [1, 2],
    12: [1],
    13: [1, 2],
    14: [1, 2],
    15: [1],
    16: [1, 2],
    17: [1, 2],
    18: [1, 2],
    19: [1, 2],
  },
  2: {
    1: [1],
    2: [1],
    3: [1],
    4: [1, 3, 4],
    5: [1, 2, 3, 4, 5],
    6: [1],
    7: [1, 4],
  },
  3: {
    1: [1, 2],
    2: [1],
    3: [1, 2],
    4: [1, 2],
    5: [1, 2],
    6: [1],
    7: [1, 2, 4],
    8: [1],
    9: [1, 2, 3, 4, 5],
    10: [1],
    11: [1, 2],
    12: [1, 2],
    13: [1, 2],
    14: [1, 2],
    15: [1],
    16: [1, 2],
    17: [1, 2],
    18: [1, 2],
    19: [1, 2],
  },
}

let roleList = Mock.mock({
  'data|3': [
    {
      'id|+1': 1,
      'name|+1': ['管理员', '教师', '学生'],
      'power|+1': [dic[1], dic[2], dic[3]],
    },
  ],
  page: {
    total: 3,
    current: 1,
  },
})

global.powerList = dic

module.exports = {

  'GET /api/role': function (req, res) {
    const page = qs.parse(req.query)
    const pageSize = page.pageSize || 10
    const currentPage = page.page || 1

    let data
    let newPage

    let newData = roleList.data.concat()

    if (page.field) {
      const d = newData.filter((item) => {
        return item[page.field].indexOf(decodeURI(page.keyword)) > -1
      })

      data = d.slice((currentPage - 1) * pageSize, currentPage * pageSize)

      newPage = {
        current: currentPage * 1,
        total: d.length,
      }
    } else {
      data = roleList.data.slice((currentPage - 1) * pageSize, currentPage * pageSize)
      roleList.page.current = currentPage * 1
      newPage = roleList.page
    }
    res.json({ success: true, list: data, page: { ...newPage, pageSize: Number(pageSize) } })
  },

  'POST /api/role': function (req, res) {
    const curItem = req.body

    if (curItem.id) {
      roleList.data = roleList.data.map((item) => {
        if (item.id === curItem.id) {
          return { ...curItem, power: JSON.parse(curItem.power) }
        }
        return item
      })
    } else {
      curItem.id = roleList.data.length + 1
      roleList.data.push({ ...curItem, power: JSON.parse(curItem.power) })

      roleList.page.total = roleList.data.length
      roleList.page.current = 1
    }

    global.powerList = { ...global.powerList, [curItem.id]: JSON.parse(curItem.power) }
    res.json({ success: true, data: roleList.data, page: roleList.page })
  },

  'DELETE /api/role': function (req, res) {
    const deleteItem = req.body

    roleList.data = roleList.data.filter((item) => {
      return item.id !== deleteItem.id
    })

    roleList.page.total = roleList.data.length

    delete global.powerList[deleteItem.id]

    res.json({ success: true, data: roleList.data, page: roleList.page })
  },

}
<|endoftext|>


<commit_before>define(function(require, exports, module) {
    "use strict";

    var app = require("app");
    var AbstractView = require("modules/view/AbstractView");
    var DojoListItemView = require("modules/view/dojo/top/DojoListItemView");
    var DojoContentCollection = require("modules/collection/dojo/DojoContentCollection");
    var FeedListView = require("modules/view/news/FeedListView");
    var Super = FeedListView;
    var Code = require("modules/util/Code");

    /**
     * 道場アプリのコンテンツ一覧を表示するためのViewクラスを作成する。
     *
     * @class 道場アプリのトップ画面を表示するためのView
     * @exports DojoListView
     * @constructor
     */
    var DojoListView = FeedListView.extend({
        /**
         * このViewのテンプレートファイルパス
         * @memberOf DojoListView#
         */
        template : require("ldsh!templates/{mode}/top/dojoList"),

        /**
         * 記事一覧を表示する要素のセレクタ
         * @memberOf DojoListView#
         */
        listElementSelector : "#dojo-list",

        /**
         * テンプレートに渡す情報をシリアライズする
         * @return {Object}
         */
        serialize: function () {
            return _.extend({}, Super.prototype.serialize.call(this), {
                levels: this.extractLevels()
            });
        },

        /**
         * Viewの描画処理の開始前に呼び出されるコールバック関数。
         * <p>
         * 記事一覧の表示処理を開始する。
         * </p>
         * @memberOf DojoListView#
         */
        beforeRendered : function() {
            this.extractLevels();

            Super.prototype.beforeRendered.call(this);
        },

        /**
         * Viewの描画処理の終了後に呼び出されるコールバック関数。
         * @memberOf DojoListView#
         */
        afterRendered : function() {
            Super.prototype.afterRendered.call(this);
        },

        /**
         * 初期化
         * @memberOf DojoListView#
         */
        initialize : function() {
            console.assert(this.collection, "DojoListView should have a collection");

            Super.prototype.setFeedListItemViewClass.call(this, DojoListItemView);
        },

        /**
         * コレクション内のモデルの値から級の一覧を作る
         * @memberOf DojoListView#
         */
        extractLevels : function() {
            var levels = {};
            // 級の名称を収集し、重複を削除する
            var levelValues = this.collection.map(function(model) {
                return model.get("level");
            });
            levelValues = _.uniq(levelValues);

            // 「級の名称=>インデックス」の対応を格納する
            _.each(levelValues, function(levelValue, index) {
                //levels[levelValue] = index;
                levels[index] = levelValue;
            });

            return levels;
        },

        /**
         * 取得した動画一覧を描画する
         * @memberOf DojoListView#
         */
        setFeedList : function() {
            var self = this;
            var animationDeley = 0;
            // 選択されている級の文字列表現を取得する。この値は、dojo_movie#levelの文字列と同じ
            var levelValue = this.level.get("level");
            this.collection.models = _.sortBy(this.collection.models,function(model) {
                return model.get("sequence");
            });

            // 次に見るべき動画とグレーアウトする動画を判断するカウント変数
            var nextCount = 0;
            this.collection.each($.proxy(function(model) {
                if (model.get("level") === levelValue) {
                    var solvedItem = _.find(model.achievementModels,function(ach){
                        return ach.get("type") === "dojo_" + Code.DOJO_STATUS_SOLVED;
                    });
                    if (!solvedItem) {
                        nextCount++;
                    }
                    var ItemView = self.feedListItemViewClass;
                    this.insertView(this.listElementSelector, new ItemView({
                        model : model,
                        animationDeley : animationDeley,
                        parentView: this,
                        isNext: nextCount === 1,
                        isGrayedOut: nextCount > 1,

                    }));
                    animationDeley += 0.2;
                }

            }, this));
        }
    });

    module.exports = DojoListView;
});
<commit_msg>NAM-613 次のレベルのボタンは一つ前のレベルを全てクリアしてから表示<commit_after>define(function(require, exports, module) {
    "use strict";

    var app = require("app");
    var AbstractView = require("modules/view/AbstractView");
    var DojoListItemView = require("modules/view/dojo/top/DojoListItemView");
    var DojoContentCollection = require("modules/collection/dojo/DojoContentCollection");
    var FeedListView = require("modules/view/news/FeedListView");
    var Super = FeedListView;
    var Code = require("modules/util/Code");

    /**
     * 道場アプリのコンテンツ一覧を表示するためのViewクラスを作成する。
     *
     * @class 道場アプリのトップ画面を表示するためのView
     * @exports DojoListView
     * @constructor
     */
    var DojoListView = FeedListView.extend({
        /**
         * このViewのテンプレートファイルパス
         * @memberOf DojoListView#
         */
        template : require("ldsh!templates/{mode}/top/dojoList"),

        /**
         * 記事一覧を表示する要素のセレクタ
         * @memberOf DojoListView#
         */
        listElementSelector : "#dojo-list",

        /**
         * Viewの描画処理の終了後に呼び出されるコールバック関数。
         * @memberOf DojoListView#
         */
        afterRendered : function() {
            Super.prototype.afterRendered.call(this);
        },

        /**
         * 初期化
         * @memberOf DojoListView#
         */
        initialize : function() {
            console.assert(this.collection, "DojoListView should have a collection");

            Super.prototype.setFeedListItemViewClass.call(this, DojoListItemView);
        },

        /**
         * 取得した動画一覧を描画する
         * @memberOf DojoListView#
         */
        setFeedList : function() {
            var self = this;
            var animationDeley = 0;
            // 選択されている級の文字列表現を取得する。この値は、dojo_movie#levelの文字列と同じ
            var levelValue = this.level.get("level");
            this.collection.models = _.sortBy(this.collection.models,function(model) {
                return model.get("sequence");
            });

            // 次に見るべき動画とグレーアウトする動画を判断するカウント変数
            var nextCount = 0;
            this.collection.each($.proxy(function(model) {
                if (model.get("level") === levelValue) {
                    var solvedItem = _.find(model.achievementModels,function(ach){
                        return ach.get("type") === "dojo_" + Code.DOJO_STATUS_SOLVED;
                    });
                    if (!solvedItem) {
                        nextCount++;
                    }
                    var ItemView = self.feedListItemViewClass;
                    this.insertView(this.listElementSelector, new ItemView({
                        model : model,
                        animationDeley : animationDeley,
                        parentView: this,
                        isNext: nextCount === 1,
                        isGrayedOut: nextCount > 1,

                    }));
                    animationDeley += 0.2;
                }

            }, this));
        }
    });

    module.exports = DojoListView;
});
<|endoftext|>