<commit_before>import _ from 'lodash';
// We have to remove node_modules/react to avoid having multiple copies loaded.
// eslint-disable-next-line import/no-unresolved
import React, { PropTypes } from 'react';
import Route from 'react-router-dom/Route';
import queryString from 'query-string';
import fetch from 'isomorphic-fetch';

import Pane from '@folio/stripes-components/lib/Pane';
import Paneset from '@folio/stripes-components/lib/Paneset';
import PaneMenu from '@folio/stripes-components/lib/PaneMenu';
import Button from '@folio/stripes-components/lib/Button';
import Icon from '@folio/stripes-components/lib/Icon';
import MultiColumnList from '@folio/stripes-components/lib/MultiColumnList';
import FilterPaneSearch from '@folio/stripes-components/lib/FilterPaneSearch';
import FilterControlGroup from '@folio/stripes-components/lib/FilterControlGroup';
import Layer from '@folio/stripes-components/lib/Layer';
import FilterGroups, { initialFilterState, onChangeFilter as commonChangeFilter } from '@folio/stripes-components/lib/FilterGroups';

import transitionToParams from '@folio/stripes-components/util/transitionToParams';
import makeQueryFunction from '@folio/stripes-components/util/makeQueryFunction';
import IfPermission from '@folio/stripes-components/lib/IfPermission';

import UserForm from './UserForm';
import ViewUser from './ViewUser';

import contactTypes from './data/contactTypes';

const INITIAL_RESULT_COUNT = 30;
const RESULT_COUNT_INCREMENT = 30;

const filterConfig = [
  {
    label: 'User status',
    name: 'active',
    cql: 'active',
    values: [
      { name: 'Active', cql: 'true' },
      { name: 'Inactive', cql: 'false' },
    ],
  },
  {
    label: 'Patron group',
    name: 'pg',
    cql: 'patronGroup',
    values: [], // will be filled in by componentWillUpdate
  },
];

class Users extends React.Component {
  static propTypes = {
    stripes: PropTypes.shape({
      logger: PropTypes.shape({
        log: PropTypes.func.isRequired,
      }).isRequired,
      connect: PropTypes.func.isRequired,
      hasPerm: PropTypes.func.isRequired,
    }).isRequired,
    data: PropTypes.object.isRequired,
    resources: PropTypes.shape({
      users: PropTypes.shape({
        hasLoaded: PropTypes.bool.isRequired,
        other: PropTypes.shape({
          totalRecords: PropTypes.number,
          total_records: PropTypes.number,
        }).isRequired,
        isPending: PropTypes.bool.isPending,
      }),
    }).isRequired,
    history: PropTypes.shape({
      push: PropTypes.func.isRequired,
    }).isRequired,
    location: PropTypes.shape({
      pathname: PropTypes.string.isRequired,
      search: PropTypes.string,
    }).isRequired,
    match: PropTypes.shape({
      path: PropTypes.string.isRequired,
    }).isRequired,
    mutator: PropTypes.shape({
      addUserMode: PropTypes.shape({
        replace: PropTypes.func,
      }),
      userCount: PropTypes.shape({
        replace: PropTypes.func,
      }),
      users: PropTypes.shape({
        POST: PropTypes.func,
      }),
    }).isRequired,
    okapi: PropTypes.shape({
      url: PropTypes.string.isRequired,
      tenant: PropTypes.string.isRequired,
      token: PropTypes.string.isRequired,
    }).isRequired,
  };

  static manifest = Object.freeze({
    addUserMode: {},
    userCount: { initialValue: INITIAL_RESULT_COUNT },
    users: {
      type: 'okapi',
      records: 'users',
      recordsRequired: '${userCount}',
      perRequest: RESULT_COUNT_INCREMENT,
      path: 'users',
      GET: {
        params: {
          query: makeQueryFunction(
            'username=*',
            'username="$QUERY*" or personal.firstName="$QUERY*" or personal.lastName="$QUERY*"',
            {
              Active: 'active',
              Name: 'personal.lastName personal.firstName',
              'Patron Group': 'patronGroup',
              'User ID': 'username',
              Email: 'personal.email',
            },
            filterConfig,
          ),
        },
        staticFallback: { params: {} },
      },
    },
    patronGroups: {
      type: 'okapi',
      path: 'groups',
      records: 'usergroups',
    },
  });

  constructor(props) {
    super(props);

    const query = props.location.search ? queryString.parse(props.location.search) : {};
    this.state = {
      filters: initialFilterState(filterConfig, query.filters),
      selectedItem: {},
      searchTerm: query.query || '',
      sortOrder: query.sort || '',
    };

    this.okapi = props.okapi;

    this.commonChangeFilter = commonChangeFilter.bind(this);
    this.transitionToParams = transitionToParams.bind(this);
    this.connectedViewUser = props.stripes.connect(ViewUser);
    const logger = props.stripes.logger;
    this.log = logger.log.bind(logger);
  }

  componentWillMount() {
    if (_.isEmpty(this.props.data.addUserMode)) this.props.mutator.addUserMode.replace({ mode: false });
  }

  componentWillUpdate() {
    const pg = this.props.data.patronGroups;
    if (pg && pg.length) {
      filterConfig[1].values = pg.map(rec => ({ name: rec.group, cql: rec.id }));
    }
  }

  onClearSearch = () => {
    this.log('action', 'cleared search');
    this.setState({ searchTerm: '' });
    this.props.history.push(this.props.location.pathname);
  }

  onSort = (e, meta) => {
    const sortOrder = meta.alias;
    this.log('action', `sorted by ${sortOrder}`);
    this.setState({ sortOrder });
    this.transitionToParams({ sort: sortOrder });
  }

  onSelectRow = (e, meta) => {
    const userId = meta.id;
    const username = meta.username;
    this.log('action', `clicked ${userId}, selected user =`, meta);
    this.setState({ selectedItem: meta });
    this.props.history.push(`/users/view/${userId}/${username}${this.props.location.search}`);
  }

  onClickAddNewUser = (e) => {
    if (e) e.preventDefault();
    this.log('action', 'clicked "add new user"');
    this.props.mutator.addUserMode.replace({ mode: true });
  }

  onClickCloseNewUser = (e) => {
    if (e) e.preventDefault();
    this.log('action', 'clicked "close new user"');
    this.props.mutator.addUserMode.replace({ mode: false });
  }

  onChangeFilter = (e) => {
    this.props.mutator.userCount.replace(INITIAL_RESULT_COUNT);
    this.commonChangeFilter(e);
  }

  onChangeSearch = (e) => {
    const query = e.target.value;
    this.props.mutator.userCount.replace(INITIAL_RESULT_COUNT);
    this.setState({ searchTerm: query });
    this.performSearch(query);
  }

  onNeedMore = () => {
    this.props.mutator.userCount.replace(this.props.data.userCount + RESULT_COUNT_INCREMENT);
  }

  performSearch = _.debounce((query) => {
    this.log('action', `searched for '${query}'`);
    this.transitionToParams({ query });
  }, 150);

  updateFilters = (filters) => { // provided for onChangeFilter
    this.transitionToParams({ filters: Object.keys(filters).filter(key => filters[key]).join(',') });
  }

  create = (data) => {
    // extract creds object from user object
    const creds = Object.assign({}, data.creds, { username: data.username });
    if (data.creds) delete data.creds; // eslint-disable-line no-param-reassign
    // POST user record
    this.props.mutator.users.POST(data);
    // POST credentials, permission-user, permissions;
    this.postCreds(data.username, creds);
    this.onClickCloseNewUser();
  }

  postCreds = (username, creds) => {
    this.log('xhr', `POST credentials for new user '${username}':`, creds);
    fetch(`${this.okapi.url}/authn/credentials`, {
      method: 'POST',
      headers: Object.assign({}, { 'X-Okapi-Tenant': this.okapi.tenant, 'X-Okapi-Token': this.okapi.token, 'Content-Type': 'application/json' }),
      body: JSON.stringify(creds),
    }).then((response) => {
      if (response.status >= 400) {
        this.log('xhr', 'Users. POST of creds failed.');
      } else {
        this.postPerms(username, [
          'users.collection.get',       // so the user can search for his own user record after login
          'perms.permissions.get',      // so the user can fetch his own permissions after login
        ]);
      }
    });
  }

  postPerms = (username, perms) => {
    this.log('xhr', `POST permissions for new user '${username}':`, perms);
    fetch(`${this.okapi.url}/perms/users`, {
      method: 'POST',
      headers: Object.assign({}, { 'X-Okapi-Tenant': this.okapi.tenant, 'X-Okapi-Token': this.okapi.token, 'Content-Type': 'application/json' }),
      body: JSON.stringify({ username, permissions: perms }),
    }).then((response) => {
      if (response.status >= 400) {
        this.log('xhr', 'Users. POST of users permissions failed.');
      } else {
        // nothing to do
      }
    });
  }

  collapseDetails = () => {
    this.setState({
      selectedItem: {},
    });
    this.props.history.push(`${this.props.match.path}${this.props.location.search}`);
  }

  render() {
    const { data, stripes } = this.props;
    const users = data.users || [];

    /* searchHeader is a 'custom pane header'*/
    const searchHeader = <FilterPaneSearch id="SearchField" onChange={this.onChangeSearch} onClear={this.onClearSearch} value={this.state.searchTerm} />;
    const resultMenu = <PaneMenu><button><Icon icon="bookmark" /></button></PaneMenu>;

    const resultsFormatter = {
      Active: user => user.active,
      Name: user => `${_.get(user, ['personal', 'lastName'], '')}, ${_.get(user, ['personal', 'firstName'], '')}`,
      'Patron Group': (user) => {
        const pg = data.patronGroups.filter(g => g.id === user.patronGroup)[0];
        return pg ? pg.group : '?';
      },
      'User ID': user => user.username,
      Email: user => _.get(user, ['personal', 'email']),
    };

    const detailsPane = (
      this.props.stripes.hasPerm('users.item.get') ?
        (<Route
          path={`${this.props.match.path}/view/:userid/:username`}
          render={props => <this.connectedViewUser stripes={stripes} okapi={this.okapi} paneWidth="44%" onClose={this.collapseDetails} {...props} />}
        />) :
        (

Permission Error

Sorry - your user permissions do not allow access to this page.

));

    const resource = this.props.resources.users;
    return (
      <Paneset>
        {/* Filter Pane */}
        <Pane defaultWidth="16%" header={searchHeader}>
          <FilterGroups config={filterConfig} filters={this.state.filters} onChangeFilter={this.onChangeFilter} />
          <FilterControlGroup label="Actions">
            <IfPermission perm="users.item.post">
              <IfPermission perm="login.item.post">
                <IfPermission perm="perms.users.item.post">
                  <Button fullWidth onClick={this.onClickAddNewUser}>New user</Button>
                </IfPermission>
              </IfPermission>
            </IfPermission>
          </FilterControlGroup>
        </Pane>
        {/* Results Pane */}
        <Pane
          defaultWidth="fill"
          paneTitle={

              Results

                {resource && resource.hasLoaded ? (resource.other.totalRecords || resource.other.total_records) : ''} Result{users.length === 1 ? '' : 's'} Found

          }
          lastMenu={resultMenu}
        >
          <MultiColumnList
            contentData={users}
            selectedRow={this.state.selectedItem}
            rowMetadata={['id', 'username']}
            formatter={resultsFormatter}
            onRowClick={this.onSelectRow}
            onHeaderClick={this.onSort}
            onNeedMoreData={this.onNeedMore}
            visibleColumns={['Active', 'Name', 'Patron Group', 'User ID', 'Email']}
            sortOrder={this.state.sortOrder}
            isEmptyMessage={`No results found for "${this.state.searchTerm}". Please check your spelling and filters.`}
            columnMapping={{ 'User ID': 'username' }}
            loading={resource ? resource.isPending : false}
            autosize
            virtualize
          />
        </Pane>

        {detailsPane}
        <Layer isOpen={data.addUserMode ? data.addUserMode.mode : false} label="Add New User Dialog">
          <UserForm
            initialValues={{ active: true, personal: { preferredContactTypeId: '002' } }}
            onSubmit={(record) => { this.create(record); }}
            onCancel={this.onClickCloseNewUser}
            okapi={this.okapi}
            optionLists={{ patronGroups: this.props.data.patronGroups, contactTypes }}
          />
        </Layer>
      </Paneset>
    );
  }
}

export default Users;
<commit_msg>propType for 'other' within resource is no longer reqired.<commit_after>import _ from 'lodash';
// We have to remove node_modules/react to avoid having multiple copies loaded.
// eslint-disable-next-line import/no-unresolved
import React, { PropTypes } from 'react';
import Route from 'react-router-dom/Route';
import queryString from 'query-string';
import fetch from 'isomorphic-fetch';

import Pane from '@folio/stripes-components/lib/Pane';
import Paneset from '@folio/stripes-components/lib/Paneset';
import PaneMenu from '@folio/stripes-components/lib/PaneMenu';
import Button from '@folio/stripes-components/lib/Button';
import Icon from '@folio/stripes-components/lib/Icon';
import MultiColumnList from '@folio/stripes-components/lib/MultiColumnList';
import FilterPaneSearch from '@folio/stripes-components/lib/FilterPaneSearch';
import FilterControlGroup from '@folio/stripes-components/lib/FilterControlGroup';
import Layer from '@folio/stripes-components/lib/Layer';
import FilterGroups, { initialFilterState, onChangeFilter as commonChangeFilter } from '@folio/stripes-components/lib/FilterGroups';

import transitionToParams from '@folio/stripes-components/util/transitionToParams';
import makeQueryFunction from '@folio/stripes-components/util/makeQueryFunction';
import IfPermission from '@folio/stripes-components/lib/IfPermission';

import UserForm from './UserForm';
import ViewUser from './ViewUser';

import contactTypes from './data/contactTypes';

const INITIAL_RESULT_COUNT = 30;
const RESULT_COUNT_INCREMENT = 30;

const filterConfig = [
  {
    label: 'User status',
    name: 'active',
    cql: 'active',
    values: [
      { name: 'Active', cql: 'true' },
      { name: 'Inactive', cql: 'false' },
    ],
  },
  {
    label: 'Patron group',
    name: 'pg',
    cql: 'patronGroup',
    values: [], // will be filled in by componentWillUpdate
  },
];

class Users extends React.Component {
  static propTypes = {
    stripes: PropTypes.shape({
      logger: PropTypes.shape({
        log: PropTypes.func.isRequired,
      }).isRequired,
      connect: PropTypes.func.isRequired,
      hasPerm: PropTypes.func.isRequired,
    }).isRequired,
    data: PropTypes.object.isRequired,
    resources: PropTypes.shape({
      users: PropTypes.shape({
        hasLoaded: PropTypes.bool.isRequired,
        other: PropTypes.shape({
          totalRecords: PropTypes.number,
          total_records: PropTypes.number,
        }),
        isPending: PropTypes.bool.isPending,
      }),
    }).isRequired,
    history: PropTypes.shape({
      push: PropTypes.func.isRequired,
    }).isRequired,
    location: PropTypes.shape({
      pathname: PropTypes.string.isRequired,
      search: PropTypes.string,
    }).isRequired,
    match: PropTypes.shape({
      path: PropTypes.string.isRequired,
    }).isRequired,
    mutator: PropTypes.shape({
      addUserMode: PropTypes.shape({
        replace: PropTypes.func,
      }),
      userCount: PropTypes.shape({
        replace: PropTypes.func,
      }),
      users: PropTypes.shape({
        POST: PropTypes.func,
      }),
    }).isRequired,
    okapi: PropTypes.shape({
      url: PropTypes.string.isRequired,
      tenant: PropTypes.string.isRequired,
      token: PropTypes.string.isRequired,
    }).isRequired,
  };

  static manifest = Object.freeze({
    addUserMode: {},
    userCount: { initialValue: INITIAL_RESULT_COUNT },
    users: {
      type: 'okapi',
      records: 'users',
      recordsRequired: '${userCount}',
      perRequest: RESULT_COUNT_INCREMENT,
      path: 'users',
      GET: {
        params: {
          query: makeQueryFunction(
            'username=*',
            'username="$QUERY*" or personal.firstName="$QUERY*" or personal.lastName="$QUERY*"',
            {
              Active: 'active',
              Name: 'personal.lastName personal.firstName',
              'Patron Group': 'patronGroup',
              'User ID': 'username',
              Email: 'personal.email',
            },
            filterConfig,
          ),
        },
        staticFallback: { params: {} },
      },
    },
    patronGroups: {
      type: 'okapi',
      path: 'groups',
      records: 'usergroups',
    },
  });

  constructor(props) {
    super(props);

    const query = props.location.search ? queryString.parse(props.location.search) : {};
    this.state = {
      filters: initialFilterState(filterConfig, query.filters),
      selectedItem: {},
      searchTerm: query.query || '',
      sortOrder: query.sort || '',
    };

    this.okapi = props.okapi;

    this.commonChangeFilter = commonChangeFilter.bind(this);
    this.transitionToParams = transitionToParams.bind(this);
    this.connectedViewUser = props.stripes.connect(ViewUser);
    const logger = props.stripes.logger;
    this.log = logger.log.bind(logger);
  }

  componentWillMount() {
    if (_.isEmpty(this.props.data.addUserMode)) this.props.mutator.addUserMode.replace({ mode: false });
  }

  componentWillUpdate() {
    const pg = this.props.data.patronGroups;
    if (pg && pg.length) {
      filterConfig[1].values = pg.map(rec => ({ name: rec.group, cql: rec.id }));
    }
  }

  onClearSearch = () => {
    this.log('action', 'cleared search');
    this.setState({ searchTerm: '' });
    this.props.history.push(this.props.location.pathname);
  }

  onSort = (e, meta) => {
    const sortOrder = meta.alias;
    this.log('action', `sorted by ${sortOrder}`);
    this.setState({ sortOrder });
    this.transitionToParams({ sort: sortOrder });
  }

  onSelectRow = (e, meta) => {
    const userId = meta.id;
    const username = meta.username;
    this.log('action', `clicked ${userId}, selected user =`, meta);
    this.setState({ selectedItem: meta });
    this.props.history.push(`/users/view/${userId}/${username}${this.props.location.search}`);
  }

  onClickAddNewUser = (e) => {
    if (e) e.preventDefault();
    this.log('action', 'clicked "add new user"');
    this.props.mutator.addUserMode.replace({ mode: true });
  }

  onClickCloseNewUser = (e) => {
    if (e) e.preventDefault();
    this.log('action', 'clicked "close new user"');
    this.props.mutator.addUserMode.replace({ mode: false });
  }

  onChangeFilter = (e) => {
    this.props.mutator.userCount.replace(INITIAL_RESULT_COUNT);
    this.commonChangeFilter(e);
  }

  onChangeSearch = (e) => {
    const query = e.target.value;
    this.props.mutator.userCount.replace(INITIAL_RESULT_COUNT);
    this.setState({ searchTerm: query });
    this.performSearch(query);
  }

  onNeedMore = () => {
    this.props.mutator.userCount.replace(this.props.data.userCount + RESULT_COUNT_INCREMENT);
  }

  performSearch = _.debounce((query) => {
    this.log('action', `searched for '${query}'`);
    this.transitionToParams({ query });
  }, 150);

  updateFilters = (filters) => { // provided for onChangeFilter
    this.transitionToParams({ filters: Object.keys(filters).filter(key => filters[key]).join(',') });
  }

  create = (data) => {
    // extract creds object from user object
    const creds = Object.assign({}, data.creds, { username: data.username });
    if (data.creds) delete data.creds; // eslint-disable-line no-param-reassign
    // POST user record
    this.props.mutator.users.POST(data);
    // POST credentials, permission-user, permissions;
    this.postCreds(data.username, creds);
    this.onClickCloseNewUser();
  }

  postCreds = (username, creds) => {
    this.log('xhr', `POST credentials for new user '${username}':`, creds);
    fetch(`${this.okapi.url}/authn/credentials`, {
      method: 'POST',
      headers: Object.assign({}, { 'X-Okapi-Tenant': this.okapi.tenant, 'X-Okapi-Token': this.okapi.token, 'Content-Type': 'application/json' }),
      body: JSON.stringify(creds),
    }).then((response) => {
      if (response.status >= 400) {
        this.log('xhr', 'Users. POST of creds failed.');
      } else {
        this.postPerms(username, [
          'users.collection.get',       // so the user can search for his own user record after login
          'perms.permissions.get',      // so the user can fetch his own permissions after login
        ]);
      }
    });
  }

  postPerms = (username, perms) => {
    this.log('xhr', `POST permissions for new user '${username}':`, perms);
    fetch(`${this.okapi.url}/perms/users`, {
      method: 'POST',
      headers: Object.assign({}, { 'X-Okapi-Tenant': this.okapi.tenant, 'X-Okapi-Token': this.okapi.token, 'Content-Type': 'application/json' }),
      body: JSON.stringify({ username, permissions: perms }),
    }).then((response) => {
      if (response.status >= 400) {
        this.log('xhr', 'Users. POST of users permissions failed.');
      } else {
        // nothing to do
      }
    });
  }

  collapseDetails = () => {
    this.setState({
      selectedItem: {},
    });
    this.props.history.push(`${this.props.match.path}${this.props.location.search}`);
  }

  render() {
    const { data, stripes } = this.props;
    const users = data.users || [];

    /* searchHeader is a 'custom pane header'*/
    const searchHeader = <FilterPaneSearch id="SearchField" onChange={this.onChangeSearch} onClear={this.onClearSearch} value={this.state.searchTerm} />;
    const resultMenu = <PaneMenu><button><Icon icon="bookmark" /></button></PaneMenu>;

    const resultsFormatter = {
      Active: user => user.active,
      Name: user => `${_.get(user, ['personal', 'lastName'], '')}, ${_.get(user, ['personal', 'firstName'], '')}`,
      'Patron Group': (user) => {
        const pg = data.patronGroups.filter(g => g.id === user.patronGroup)[0];
        return pg ? pg.group : '?';
      },
      'User ID': user => user.username,
      Email: user => _.get(user, ['personal', 'email']),
    };

    const detailsPane = (
      this.props.stripes.hasPerm('users.item.get') ?
        (<Route
          path={`${this.props.match.path}/view/:userid/:username`}
          render={props => <this.connectedViewUser stripes={stripes} okapi={this.okapi} paneWidth="44%" onClose={this.collapseDetails} {...props} />}
        />) :
        (

Permission Error

Sorry - your user permissions do not allow access to this page.

));

    const resource = this.props.resources.users;
    return (
      <Paneset>
        {/* Filter Pane */}
        <Pane defaultWidth="16%" header={searchHeader}>
          <FilterGroups config={filterConfig} filters={this.state.filters} onChangeFilter={this.onChangeFilter} />
          <FilterControlGroup label="Actions">
            <IfPermission perm="users.item.post">
              <IfPermission perm="login.item.post">
                <IfPermission perm="perms.users.item.post">
                  <Button fullWidth onClick={this.onClickAddNewUser}>New user</Button>
                </IfPermission>
              </IfPermission>
            </IfPermission>
          </FilterControlGroup>
        </Pane>
        {/* Results Pane */}
        <Pane
          defaultWidth="fill"
          paneTitle={

              Results

                {resource && resource.hasLoaded ? (resource.other.totalRecords || resource.other.total_records) : ''} Result{users.length === 1 ? '' : 's'} Found

          }
          lastMenu={resultMenu}
        >
          <MultiColumnList
            contentData={users}
            selectedRow={this.state.selectedItem}
            rowMetadata={['id', 'username']}
            formatter={resultsFormatter}
            onRowClick={this.onSelectRow}
            onHeaderClick={this.onSort}
            onNeedMoreData={this.onNeedMore}
            visibleColumns={['Active', 'Name', 'Patron Group', 'User ID', 'Email']}
            sortOrder={this.state.sortOrder}
            isEmptyMessage={`No results found for "${this.state.searchTerm}". Please check your spelling and filters.`}
            columnMapping={{ 'User ID': 'username' }}
            loading={resource ? resource.isPending : false}
            autosize
            virtualize
          />
        </Pane>

        {detailsPane}
        <Layer isOpen={data.addUserMode ? data.addUserMode.mode : false} label="Add New User Dialog">
          <UserForm
            initialValues={{ active: true, personal: { preferredContactTypeId: '002' } }}
            onSubmit={(record) => { this.create(record); }}
            onCancel={this.onClickCloseNewUser}
            okapi={this.okapi}
            optionLists={{ patronGroups: this.props.data.patronGroups, contactTypes }}
          />
        </Layer>
      </Paneset>
    );
  }
}

export default Users;
<|endoftext|>


<commit_before>/**
 * Remote channel service for frontend server.
 * Receive push request from backend servers and push it to clients.
 */
var utils = require('../../../util/utils');
var logger = require('pomelo-logger').getLogger('pomelo', __filename);

module.exports = function(app) {
  return new Remote(app);
};

var Remote = function(app) {
  this.app = app;
};

/**
 * Push message to client by uids.
 *
 * @param  {String}   route route string of message
 * @param  {Object}   msg   message
 * @param  {Array}    uids  user ids that would receive the message
 * @param  {Object}   opts  push options
 * @param  {Function} cb    callback function
 */
Remote.prototype.pushMessage = function(route, msg, uids, opts, cb) {
  if(!msg){
    logger.error('Can not send empty message! route : %j, compressed msg : %j',
        route, msg);
    utils.invokeCallback(cb, new Error('can not send empty message.'));
    return;
  }

  var connector = this.app.components.__connector__;
  var sessionService = this.app.get('sessionService');
  var fails = [], sids = [], sessions, j, k;
  for(var i=0, l=uids.length; i<l; i++) {
    sessions = sessionService.getByUid(uids[i]);
    if(!sessions) {
      fails.push(uids[i]);
    } else {
      for(j=0, k=sessions.length; j<k; j++) {
        var session = sessions[j];
        var pushPacketStatus = session.get('pushPacketStatus');
        if(pushPacketStatus){
            var data = {};
            data.route = route;
            data.msgID = session.get('pushRequestID');
            msg.msgID = data.msgID;
            data.reqBody = msg;//未encode的数据
            data.resBody = {};
            data.status = 1;
            session.set('pushRequestID',data.msgID+1);
            pushPacketStatus[data.msgID] = data;
        }
        sids.push(session.id);
      }
    }
  }
  logger.debug('[%s] pushMessage uids: %j, msg: %j, sids: %j', this.app.serverId, uids, msg, sids);
  if(sids.length>0){
      connector.send(null, route, msg, sids, opts, function(err) {
          utils.invokeCallback(cb, err, fails);
      });
  }else{
      utils.invokeCallback(cb, new Error('not get session'));
  }
};

/**
 * Broadcast to all the client connectd with current frontend server.
 *
 * @param  {String}    route  route string
 * @param  {Object}    msg    message
 * @param  {Boolean}   opts   broadcast options.
 * @param  {Function}  cb     callback function
 */
Remote.prototype.broadcast = function(route, msg, opts, cb) {
  var connector = this.app.components.__connector__;

  connector.send(null, route, msg, null, opts, cb);
};
<commit_msg>push 优化<commit_after>/**
 * Remote channel service for frontend server.
 * Receive push request from backend servers and push it to clients.
 */
var utils = require('../../../util/utils');
var logger = require('pomelo-logger').getLogger('pomelo', __filename);

module.exports = function(app) {
  return new Remote(app);
};

var Remote = function(app) {
  this.app = app;
};

/**
 * Push message to client by uids.
 *
 * @param  {String}   route route string of message
 * @param  {Object}   msg   message
 * @param  {Array}    uids  user ids that would receive the message
 * @param  {Object}   opts  push options
 * @param  {Function} cb    callback function
 */
Remote.prototype.pushMessage = function(route, msg, uids, opts, cb) {
  if(!msg){
    logger.error('Can not send empty message! route : %j, compressed msg : %j',
        route, msg);
    utils.invokeCallback(cb, new Error('can not send empty message.'));
    return;
  }

  var connector = this.app.components.__connector__;
  var sessionService = this.app.get('sessionService');
  var fails = [], sids = [], sessions, j, k;
  for(var i=0, l=uids.length; i<l; i++) {
    sessions = sessionService.getByUid(uids[i]);
    if(!sessions) {
      fails.push(uids[i]);
    } else {
      for(j=0, k=sessions.length; j<k; j++) {
        var session = sessions[j];
        var pushPacketStatus = session.get('pushPacketStatus');
        if(pushPacketStatus){
            var data = {};
            data.route = route;
            data.msgID = session.get('pushRequestID');
            msg.msgID = data.msgID;
            data.reqBody = msg;//未encode的数据
            data.resBody = {};
            data.status = 1;
            session.set('pushRequestID',data.msgID+1);
            pushPacketStatus[data.msgID] = data;
        }
        sids.push(session.id);
      }
    }
  }
  logger.debug('[%s] pushMessage uids: %j, msg: %j, sids: %j', this.app.serverId, uids, msg, sids);

      connector.send(null, route, msg, sids, opts, function(err) {
          utils.invokeCallback(cb, err, fails);
      });

};

/**
 * Broadcast to all the client connectd with current frontend server.
 *
 * @param  {String}    route  route string
 * @param  {Object}    msg    message
 * @param  {Boolean}   opts   broadcast options.
 * @param  {Function}  cb     callback function
 */
Remote.prototype.broadcast = function(route, msg, opts, cb) {
  var connector = this.app.components.__connector__;

  connector.send(null, route, msg, null, opts, cb);
};
<|endoftext|>


<commit_before>describe("GuessSpec", function() {

  //单元测试
  describe("GuessSpec_Unit", function() {
    var guess_num;
    var guess;
    var answer_generator;
    var compare_number;

    beforeEach(function() {
      answer_generator = {
        getNumber: null
      };
      spyOn(answer_generator, "getNumber").and.returnValue("1234");
    });

    it("1234 & 1234 should be output 4A0B", function() {
      compare_number = {
        checkGuess: null
      }
      spyOn(compare_number, "checkGuess").and.returnValue("4A0B");
      guess_num = "1234";
      guess = new Guess(answer_generator, compare_number);
      expect(guess.setNumber(guess_num)).toEqual("4A0B");
    });

  });

  //集成测试
  describe("GuessSpec_Integration", function() {
    var guess_num;
    var guess;
    var math;

    beforeEach(function() {
      my_math = {
        register : 0,
        random : function () {
          this.register += 0.1;
          return this.register;
        }
      };
    });

    it("1234 & 1234 should be output 4A0B", function() {
      guess_num = "1234";
      guess = new Guess(new AnswerGenerator(my_math), new CompareNumber());
      expect(guess.setNumber(guess_num)).toEqual("4A0B");
    });

  });
});
<commit_msg>修改第三问测试<commit_after>describe("GuessSpec", function() {

  //单元测试
  describe("GuessSpec_Unit", function() {
    var guess_num;
    var guess;
    var answer_generator;
    var compare_number;

    beforeEach(function() {
      answer_generator = {
        getNumber: null
      };
      spyOn(answer_generator, "getNumber").and.returnValue("1234");
    });

    it("1234 & 1234 should be output 4A0B", function() {
      compare_number = {
        checkGuess: null
      }
      spyOn(compare_number, "checkGuess").and.returnValue("4A0B");
      guess_num = "1234";
      guess = new Guess(answer_generator, compare_number);
      expect(guess.setNumber(guess_num)).toEqual("4A0B");
    });

  });

  //集成测试
  describe("GuessSpec_Integration", function() {
    var guess_num;
    var guess;
    var math;

    beforeEach(function() {
      math = {
        register : 0,
        random : function () {
          this.register += 0.1;
          return this.register;
        }
      };
    });

    it("1234 & 1234 should be output 4A0B", function() {
      guess_num = "1234";
      guess = new Guess(new AnswerGenerator(math), new CompareNumber());
      expect(guess.setNumber(guess_num)).toEqual("4A0B");
    });

  });
});
<|endoftext|>


<commit_before>import _ from 'lodash'
import dbg from 'debug'
import async from 'async'
import filter from './odata/filter'
import * as errors from 'netiam-errors'

const debug = dbg('netiam:rest:resource')

export const MANY_TO_ONE = Symbol('many-to-one')
export const ONE_TO_MANY = Symbol('one-to-many')

let itemsPerPage = 10

/**
 * Normalize the request query. The resource routes do access certain query
 * parameters, neither they are set or not.
 * @param {Object} query The original query object
 * @param {String} idField The ID field
 * @returns {Object} The normalized query object
 * @private
 */
export function normalize(query, idField) {
  // Clone query object
  query = _.clone(query)

  // Filter
  if (!query.filter) {
    query.filter = ''
  }

  // Property expansion
  if (_.isString(query.expand)) {
    query.expand = query.expand.split(',')
  } else {
    query.expand = []
  }

  // Order
  if (!query.sort) {
    query.sort = idField
  }

  // Pagination
  if (!query.offset) {
    query.offset = 0
  } else {
    query.offset = Number(query.offset)
  }

  if (!query.limit) {
    query.limit = itemsPerPage
  } else {
    query.limit = Number(query.limit)
  }

  if (query.page) {
    query.page = query.page ? Number(query.page) : 1
    query.limit = itemsPerPage
    query.offset = Math.max(0, (query.page - 1) * itemsPerPage)
  }

  return query
}

/**
 * Maps request parameters to a properties hash object which can be
 * used to create a filter expression for database queries. This method
 * is also used to constraint requests to subresources.
 *
 * The following example tries to fetch messages from a specific user.
 * In order to reduce the messages within the database, there must be some
 * kind of 1:n relationship between a user and its messages.
 *
 * The mapping allows you to define a dynamic relationship between a parameter
 * and a specific document property. Internal this mapping is used to reduce
 * the resultset of documents by adding a $where statement to the database
 * query.
 *
 * Example:
 * // Request
 * GET /users/:user/messages
 *
 * // Mapping
 * {
   *  'owner': ':user'
   * }
 *
 * @param {Object} parameters
 * @returns {Object}
 * @private
 */
export function params(parameters, map) {
  const newMap = _.clone(map)

  return _.forOwn(newMap, (val, key, obj) => {
    // handle route params
    if (val.charAt(0) === ':') {
      obj[key] = parameters[val.substring(1)]
      return
    }

    // Static value
    obj[key] = val
  })
}

export default function resource(spec) {
  const {collection} = spec
  const {relationshipField} = spec
  const {relationshipCollection} = spec
  const {map} = spec
  let {relationship} = spec
  let {idParam} = spec
  let {idField} = spec

  idParam = idParam || 'id'
  idField = idField || '_id'
  relationship = relationship || ONE_TO_MANY
  itemsPerPage = spec.itemsPerPage ? spec.itemsPerPage : itemsPerPage

  function listHandle(q, query, resolve, reject) {
    // populate
    if (query.expand.length > 0) {
      q = q.populate(query.expand.join(' '))
    }

    // sort
    q = q.sort(query.sort)

    // pagination
    q = q.skip(query.offset)
    if (query.limit && query.limit !== -1) {
      q = q.limit(query.limit)
    }

    // execute
    q.exec((err, documents) => {
      if (err) {
        debug(err)
        return reject(errors.internalServerError(err.message))
      }

      if (!_.isArray(documents)) {
        return resolve([])
      }

      resolve(_.map(documents, document => {
        return document.toObject()
      }))
    })
  }

  /**
   * Fetches database for a list of documents. As all public resource methods,
   * this one provides several predefined parameters someone can use to
   * manipulate the resultset.
   *
   * Example:
   * GET /users?filter=email EQ
   * 'box@mail.tld'&sort=email-&limit=10&offset=1&expand=messages
   *
   * @param {Object} req The request object
   * @returns {Promise}
   */
  function list(req) {
    return new Promise((resolve, reject) => {
      // normalize
      const query = normalize(req.query, idField)

      // filter
      let f = filter(query.filter)

      // handle relationships
      if (relationship === MANY_TO_ONE &&
        relationshipField &&
        relationshipCollection &&
        map) {

        const key = Object.keys(map)[0]
        const val = req.params[map[key].substr(1)]

        collection
          .findOne({[key]: val})
          .select(relationshipField)
          .exec((err, doc) => {
            if (err) {
              debug(err)
              return reject(err)
            }

            if (!doc) {
              return resolve([])
            }

            // query
            let q
            try {
              q = relationshipCollection.find(f.toObject())
            } catch (err) {
              debug(err)
              reject(errors.badRequest(err.message))
            }

            // select only related
            q = q.where('_id').in(doc[relationshipField])

            // handle
            listHandle(q, query, resolve, reject)
          })

        return
      }

      if (relationship === ONE_TO_MANY) {
        f = f.where(params(req.params, map))

        // query
        let q
        try {
          q = collection.find(f.toObject())
        } catch (err) {
          debug(err)
          reject(errors.badRequest(err.message))
        }

        // handle
        return listHandle(q, query, resolve, reject)
      }
    })
  }

  function readHandle(q, query, resolve, reject) {
    // populate
    if (query.expand.length > 0) {
      q = q.populate(query.expand.join(' '))
    }

    // execute
    q.exec((err, document) => {
      if (err) {
        debug(err)
        return reject(errors.internalServerError(err.message))
      }

      if (!document) {
        return reject(errors.notFound('Document not found'))
      }

      resolve(document.toObject())
    })
  }

  /**
   * Fetches a single document from database.
   *
   * @param {Object} req The request object
   * @returns {Promise}
   */
  function read(req) {
    return new Promise((resolve, reject) => {
      // normalize
      const query = normalize(req.query, idField)

      // handle relationships
      if (relationship === MANY_TO_ONE &&
        relationshipField &&
        relationshipCollection &&
        map) {

        const key = Object.keys(map)[0]
        const val = req.params[map[key].substr(1)]

        collection
          .findOne({[key]: val})
          .select(relationshipField)
          .exec((err, doc) => {
            if (err) {
              debug(err)
              return reject(err)
            }

            if (!doc) {
              return reject(errors.notFound('Document not found'))
            }

            // query options
            const qo = {[idField]: req.params[idParam]}

            // query
            const q = relationshipCollection.findOne(qo)

            // handle
            readHandle(q, query, resolve, reject)
          })

        return
      }

      if (relationship === ONE_TO_MANY) {
        // query options
        const qo = {[idField]: req.params[idParam]}

        // query
        const q = collection.findOne(qo)

        // handle
        return readHandle(q, query, resolve, reject)
      }

    })
  }

  /**
   * Creates a new document and saves it to the database.
   *
   * @param {Object} req The request object
   * @returns {Promise}
   */
  function create(req) {
    return new Promise((resolve, reject) => {
      // normalize
      const query = normalize(req.query, idField)

      // create model
      collection
        .create(req.body, (err, document) => {
          if (err) {
            debug(err)
            if (err.code === 11000) {
              return reject(errors.badRequest(err.message))
            }

            return reject(errors.internalServerError(err.message))
          }

          if (!document) {
            return reject(errors.internalServerError('Document could not be created'))
          }

          // populate
          if (query.expand.length > 0) {
            document.populate(query.expand.join(' '), err => {
              if (err) {
                debug(err)
                return reject(errors.internalServerError(err.message))
              }

              resolve(document.toJSON())
            })
          } else {
            resolve(document.toJSON())
          }
        })
    })
  }

  function updateExpandedPath(document, path, payload) {
    return new Promise((resolve, reject) => {
      const data = document[path]
      const update = payload[path]

      if (_.isArray(data)) {
        async.each(
          data,
          (node, done) => {
            const index = data.indexOf(node)
            node
              .merge(update[index])
              .save(done)
          },
          (err) => {
            if (err) {
              debug(err)
              return reject(err)
            }

            return resolve()
          })
        return
      }

      if (_.isObject(data)) {
        data
          .merge(update)
          .save(err => {
            if (err) {
              return reject(err)
            }

            resolve()
          })
      }
    })
  }

  /**
   * Modifies a single and existing document
   *
   * @param {Object} req The request object
   * @returns {Promise}
   */
  function update(req) {
    // normalize
    const query = normalize(req.query, idField)

    function updateExpanded(document) {
      if (!document) {
        throw errors.notFound('Document not found')
      }
      const ops = _.map(query.expand, path => {
        return updateExpandedPath(document, path, req.body)
      })

      return Promise
        .all(ops)
        .then(() => {
          return document
        })
    }

    return new Promise((resolve, reject) => {
      // handle relationships
      if (relationship === MANY_TO_ONE &&
        relationshipField &&
        relationshipCollection &&
        map) {

        const key = Object.keys(map)[0]
        const val = req.params[map[key].substr(1)]

        collection
          .findOne({[key]: val})
          .select(relationshipField).exec()
          .then(doc => {
            if (!doc) {
              throw errors.notFound('Document not found')
            }

            // query options
            const qo = {[idField]: req.params[idParam]}

            // query
            let q = relationshipCollection.findOne(qo)

            // populate
            if (query.expand.length > 0) {
              q = q.populate(query.expand.join(' '))
            }

            return q.exec()
          })
          .then(updateExpanded)
          .then(document => {
            return document
              .merge(req.body)
              .save()
          })
          .then(document => {
            if (req.query.expand) {
              return document
                .populate(query.expand.join(' '), err => {
                  if (err) {
                    return reject(err)
                  }

                  resolve(document.toJSON())
                })
            }
            resolve(document.toJSON())
          })
          .then(null, err => {
            debug(err)
            reject(err)
          })
        return
      }

      if (relationship === ONE_TO_MANY) {
        // query
        const qo = {[idField]: req.params[idParam]}

        // Build query
        const q = collection.findOne(qo)

        // Execute query
        q.exec((err, document) => {
          if (err) {
            debug(err)
            return reject(errors.internalServerError(err.message))
          }
          if (!document) {
            return reject(errors.notFound('Document not found'))
          }

          document
            .merge(req.body)
            .save(err => {
              if (err) {
                debug(err)
                return reject(errors.internalServerError(err.message))
              }

              // populate
              if (req.query.expand) {
                document.populate(query.expand.join(' '), err => {
                  if (err) {
                    return reject(errors.internalServerError(err.message))
                  }

                  resolve(document.toJSON())
                })
              } else {
                resolve(document.toJSON())
              }
            })
        })
      }

    })
  }

  /**
   * Deletes an existing document from database
   *
   * @param {Object} req The request object
   * @returns {Promise}
   */
  function remove(req) {
    return new Promise((resolve, reject) => {
      // query options
      const qo = {[idField]: req.params[idParam]}

      collection
        .findOneAndRemove(qo)
        .exec((err, documents) => {
          if (err) {
            debug(err)
            return reject(errors.internalServerError(err.message))
          }
          if (!documents) {
            return reject(errors.notFound('Document not found'))
          }

          resolve()
        })

    })
  }

  return Object.freeze({
    list,
    read,
    create,
    update,
    remove
  })
}
<commit_msg>refactor: use query module instead of builtin methods<commit_after>import _ from 'lodash'
import dbg from 'debug'
import async from 'async'
import {params, normalize} from './query'
import filter from './odata/filter'
import * as errors from 'netiam-errors'

const debug = dbg('netiam:rest:resource')

export const MANY_TO_ONE = Symbol('many-to-one')
export const ONE_TO_MANY = Symbol('one-to-many')

let itemsPerPage = 10

export default function resource(spec) {
  const {collection} = spec
  const {relationshipField} = spec
  const {relationshipCollection} = spec
  const {map} = spec
  let {relationship} = spec
  let {idParam} = spec
  let {idField} = spec

  idParam = idParam || 'id'
  idField = idField || '_id'
  relationship = relationship || ONE_TO_MANY
  itemsPerPage = spec.itemsPerPage ? spec.itemsPerPage : itemsPerPage

  function listHandle(q, query, resolve, reject) {
    // populate
    if (query.expand.length > 0) {
      q = q.populate(query.expand.join(' '))
    }

    // sort
    q = q.sort(query.sort)

    // pagination
    q = q.skip(query.offset)
    if (query.limit && query.limit !== -1) {
      q = q.limit(query.limit)
    }

    // execute
    q.exec((err, documents) => {
      if (err) {
        debug(err)
        return reject(errors.internalServerError(err.message))
      }

      if (!_.isArray(documents)) {
        return resolve([])
      }

      resolve(_.map(documents, document => {
        return document.toObject()
      }))
    })
  }

  /**
   * Fetches database for a list of documents. As all public resource methods,
   * this one provides several predefined parameters someone can use to
   * manipulate the resultset.
   *
   * Example:
   * GET /users?filter=email EQ
   * 'box@mail.tld'&sort=email-&limit=10&offset=1&expand=messages
   *
   * @param {Object} req The request object
   * @returns {Promise}
   */
  function list(req) {
    return new Promise((resolve, reject) => {
      const query = normalize({
        req,
        idField,
        itemsPerPage
      })

      // filter
      let f = filter(query.filter)

      // handle relationships
      if (relationship === MANY_TO_ONE &&
        relationshipField &&
        relationshipCollection &&
        map) {

        const key = Object.keys(map)[0]
        const val = req.params[map[key].substr(1)]

        collection
          .findOne({[key]: val})
          .select(relationshipField)
          .exec((err, doc) => {
            if (err) {
              debug(err)
              return reject(err)
            }

            if (!doc) {
              return resolve([])
            }

            // query
            let q
            try {
              q = relationshipCollection.find(f.toObject())
            } catch (err) {
              debug(err)
              reject(errors.badRequest(err.message))
            }

            // select only related
            q = q.where('_id').in(doc[relationshipField])

            // handle
            listHandle(q, query, resolve, reject)
          })

        return
      }

      if (relationship === ONE_TO_MANY) {
        f = f.where(params({
          req,
          map
        }))

        // query
        let q
        try {
          q = collection.find(f.toObject())
        } catch (err) {
          debug(err)
          reject(errors.badRequest(err.message))
        }

        // handle
        return listHandle(q, query, resolve, reject)
      }
    })
  }

  function readHandle(q, query, resolve, reject) {
    // populate
    if (query.expand.length > 0) {
      q = q.populate(query.expand.join(' '))
    }

    // execute
    q.exec((err, document) => {
      if (err) {
        debug(err)
        return reject(errors.internalServerError(err.message))
      }

      if (!document) {
        return reject(errors.notFound('Document not found'))
      }

      resolve(document.toObject())
    })
  }

  /**
   * Fetches a single document from database.
   *
   * @param {Object} req The request object
   * @returns {Promise}
   */
  function read(req) {
    return new Promise((resolve, reject) => {
      const query = normalize({
        req,
        idField,
        itemsPerPage
      })

      // handle relationships
      if (relationship === MANY_TO_ONE &&
        relationshipField &&
        relationshipCollection &&
        map) {

        const key = Object.keys(map)[0]
        const val = req.params[map[key].substr(1)]

        collection
          .findOne({[key]: val})
          .select(relationshipField)
          .exec((err, doc) => {
            if (err) {
              debug(err)
              return reject(err)
            }

            if (!doc) {
              return reject(errors.notFound('Document not found'))
            }

            // query options
            const qo = {[idField]: req.params[idParam]}

            // query
            const q = relationshipCollection.findOne(qo)

            // handle
            readHandle(q, query, resolve, reject)
          })

        return
      }

      if (relationship === ONE_TO_MANY) {
        // query options
        const qo = {[idField]: req.params[idParam]}

        // query
        const q = collection.findOne(qo)

        // handle
        return readHandle(q, query, resolve, reject)
      }

    })
  }

  /**
   * Creates a new document and saves it to the database.
   *
   * @param {Object} req The request object
   * @returns {Promise}
   */
  function create(req) {
    return new Promise((resolve, reject) => {
      // normalize
      const query = normalize({
        req,
        idField,
        itemsPerPage
      })

      // create model
      collection
        .create(req.body, (err, document) => {
          if (err) {
            debug(err)
            if (err.code === 11000) {
              return reject(errors.badRequest(err.message))
            }

            return reject(errors.internalServerError(err.message))
          }

          if (!document) {
            return reject(errors.internalServerError('Document could not be created'))
          }

          // populate
          if (query.expand.length > 0) {
            document.populate(query.expand.join(' '), err => {
              if (err) {
                debug(err)
                return reject(errors.internalServerError(err.message))
              }

              resolve(document.toJSON())
            })
          } else {
            resolve(document.toJSON())
          }
        })
    })
  }

  function updateExpandedPath(document, path, payload) {
    return new Promise((resolve, reject) => {
      const data = document[path]
      const update = payload[path]

      if (_.isArray(data)) {
        async.each(
          data,
          (node, done) => {
            const index = data.indexOf(node)
            node
              .merge(update[index])
              .save(done)
          },
          (err) => {
            if (err) {
              debug(err)
              return reject(err)
            }

            return resolve()
          })
        return
      }

      if (_.isObject(data)) {
        data
          .merge(update)
          .save(err => {
            if (err) {
              return reject(err)
            }

            resolve()
          })
      }
    })
  }

  /**
   * Modifies a single and existing document
   *
   * @param {Object} req The request object
   * @returns {Promise}
   */
  function update(req) {
    const query = normalize({
      req,
      idField,
      itemsPerPage
    })

    function updateExpanded(document) {
      if (!document) {
        throw errors.notFound('Document not found')
      }
      const ops = _.map(query.expand, path => {
        return updateExpandedPath(document, path, req.body)
      })

      return Promise
        .all(ops)
        .then(() => {
          return document
        })
    }

    return new Promise((resolve, reject) => {
      // handle relationships
      if (relationship === MANY_TO_ONE &&
        relationshipField &&
        relationshipCollection &&
        map) {

        const key = Object.keys(map)[0]
        const val = req.params[map[key].substr(1)]

        collection
          .findOne({[key]: val})
          .select(relationshipField).exec()
          .then(doc => {
            if (!doc) {
              throw errors.notFound('Document not found')
            }

            // query options
            const qo = {[idField]: req.params[idParam]}

            // query
            let q = relationshipCollection.findOne(qo)

            // populate
            if (query.expand.length > 0) {
              q = q.populate(query.expand.join(' '))
            }

            return q.exec()
          })
          .then(updateExpanded)
          .then(document => {
            return document
              .merge(req.body)
              .save()
          })
          .then(document => {
            if (req.query.expand) {
              return document
                .populate(query.expand.join(' '), err => {
                  if (err) {
                    return reject(err)
                  }

                  resolve(document.toJSON())
                })
            }
            resolve(document.toJSON())
          })
          .then(null, err => {
            debug(err)
            reject(err)
          })
        return
      }

      if (relationship === ONE_TO_MANY) {
        // query
        const qo = {[idField]: req.params[idParam]}

        // Build query
        const q = collection.findOne(qo)

        // Execute query
        q.exec((err, document) => {
          if (err) {
            debug(err)
            return reject(errors.internalServerError(err.message))
          }
          if (!document) {
            return reject(errors.notFound('Document not found'))
          }

          document
            .merge(req.body)
            .save(err => {
              if (err) {
                debug(err)
                return reject(errors.internalServerError(err.message))
              }

              // populate
              if (req.query.expand) {
                document.populate(query.expand.join(' '), err => {
                  if (err) {
                    return reject(errors.internalServerError(err.message))
                  }

                  resolve(document.toJSON())
                })
              } else {
                resolve(document.toJSON())
              }
            })
        })
      }

    })
  }

  /**
   * Deletes an existing document from database
   *
   * @param {Object} req The request object
   * @returns {Promise}
   */
  function remove(req) {
    return new Promise((resolve, reject) => {
      // query options
      const qo = {[idField]: req.params[idParam]}

      collection
        .findOneAndRemove(qo)
        .exec((err, documents) => {
          if (err) {
            debug(err)
            return reject(errors.internalServerError(err.message))
          }
          if (!documents) {
            return reject(errors.notFound('Document not found'))
          }

          resolve()
        })

    })
  }

  return Object.freeze({
    list,
    read,
    create,
    update,
    remove
  })
}
<|endoftext|>


<commit_before>var http = require("http"),
    express = require("express"),
    browserify  = require('browserify-middleware'),
    app = express();

browserify.settings({
    transform: ['reactify'],
    extensions: ['.js', '.jsx'],
    grep: /\.jsx?$/
});

// serve client code via browserify and reactify
app.get('/bundle.js', browserify('./boot.jsx'));

// html
app.get('*.html', function (req, res) {
    res.sendFile(__dirname+req.path);
});

// css
app.get('*.css', function (req, res) {
    res.sendFile(__dirname+"/react-datepicker.css");
});

// Everything else
app.get('*', function (req, res) {
    res.sendFile(__dirname+"/index.html");
});

app.listen("8000",function(){
    console.log("Server running on port 8000");
});
<commit_msg>Added _dirname to server<commit_after>var http = require("http"),
    express = require("express"),
    browserify  = require('browserify-middleware'),
    app = express();

browserify.settings({
    transform: ['reactify'],
    extensions: ['.js', '.jsx'],
    grep: /\.jsx?$/
});

// serve client code via browserify and reactify
app.get('/bundle.js', browserify(__dirname+'/boot.jsx'));

// html
app.get('*.html', function (req, res) {
    res.sendFile(__dirname+req.path);
});

// css
app.get('*.css', function (req, res) {
    res.sendFile(__dirname+"/react-datepicker.css");
});

// Everything else
app.get('*', function (req, res) {
    res.sendFile(__dirname+"/index.html");
});

app.listen("8000",function(){
    console.log("Server running on port 8000");
});
<|endoftext|>


<commit_before>import mongoose from 'mongoose';
import mongoosePaginate from 'mongoose-paginate';
import Promise from 'bluebird';
import Box from './box.model';
const ObjectId = mongoose.Schema.Types.ObjectId;
const Mixed = mongoose.Schema.Types.Mixed;

const EmailSchema = new mongoose.Schema({
  user: {type: ObjectId, ref: 'User'},
  messageId: {type: String, index: true},
  uid: Number,
  box: {type: ObjectId, ref: 'Box'},
  thrid: String,
  attrs: {
    'x-gm-thrid': String,
    'x-gm-msgid': String,
    modseq: String,
    uid: Number,
    flags: [String],
    date: Date,
    struct: Mixed,
    /*[{
     language: mongoose.Schema.Types.Mixed,
     disposition: mongoose.Schema.Types.Mixed,
     md5: mongoose.Schema.Types.Mixed,
     lines: Number,
     size: Number,
     encoding: String,
     description: mongoose.Schema.Types.Mixed,
     id: mongoose.Schema.Types.Mixed,
     params: mongoose.Schema.Types.Mixed,
     subtype: String,
     type: String,
     partID: String,
     }]*/
    'x-gm-labels': [String]
  },
  from: [{
    address: String,
    name: String
  }],
  to: [{
    address: String,
    name: String
  }],
  cc: [{
    address: String,
    name: String
  }],
  bcc: [{
    address: String,
    name: String
  }],
  subject: String,
  html: String,
  text: String,
  date: {type: Date, index: true},
  flags: [String],
  labels: [String]
}, {
  timestamps: true
});

EmailSchema.plugin(mongoosePaginate);

EmailSchema.index({
  text: 'text',
  subject: 'text'
});

EmailSchema.statics.isUnseen = (email) => {
  return email.flags.indexOf("\\Seen") == -1;
}

EmailSchema.statics.isSeenUnseenChanged = (emailOld, emailNew) => {
  return emailOld.flags != null && emailNew.flags != null
    && Email.isUnseen(emailOld) != Email.isUnseen(emailNew);
}

/**
 * Updates or creates an email and
 * returns the old- and updated email
 * @param email
 * @return Promise resolve([oldMail, oldBox, updatedMail, , updatedBox])
 */
EmailSchema.statics.updateAndGetOldAndUpdated = (mail) => {
  return new Promise((resolve, reject) => {
    const res = [];
    Email.findOne({messageId: mail.messageId})
      .then(emailOld => {
        res.push(emailOld);
        return emailOld != null ? Box.findWithUnseenCountById(emailOld.box) : Box.findOne({_id: mail.box});
      })
      .then(boxOld => {
        res.push(boxOld);
        return Email.findOneAndUpdate({
          messageId: mail.messageId
        }, mail, {
          new: true, // returns new doc
          upsert: true,
          setDefaultsOnInsert: true
        });
      })
      .then(emailUpdated => {
        res.push(emailUpdated);
        return emailUpdated != null ? Box.findWithUnseenCountById(emailUpdated.box) : Promise.resolve(null);
      })
      .then(boxUpdated => {
        res.push(boxUpdated);
        resolve(res);
      })
      .catch(err => {
        reject(err);
      });
  });
}

/**
 * Returns autocomplete suggestions for email addresses
 * @param userId only consider emails of this user
 */
EmailSchema.statics.autocomplete = (userId) => {
  return Email.getCollection('emails').aggregate([
    {$match: {user: userId}},
    {$project: {address: {$setUnion: ["$from", "$to", "$cc", "$bcc"]}}},
    {$unwind: '$address'},
    {$group: {_id: {address: {$toLower: '$address.address'}, name: '$address.name'}}},
    {$project: {_id: 0, address: '$_id.address', name: '$_id.name'}},
    //{$match: {$or:[{address: /+search+/},{address: /fe/}]}} //TODO add vars
  ]);
}

/**
 * Returns the searched email, this is meant to return also a simple mail list for every box
 * @param userId
 * @param opt.sort fild that will be used to sort e.g. ASC or DESC
 * @param opt.boxId
 * @param opt.search string to searchs
 * @param opt.lastEmailDate
 */
EmailSchema.statics.search = (userId, opt) => {
  const boxId = opt.boxId;
  const sort = opt.sort;
  const search = opt.search;
  const lastEmailDate = opt.lastEmailDate;
  const query = {user: userId, date: {$lt: lastEmailDate}};
  const select = {}; // only necessary
  const options = {limit: 15, sort: {date: sort == 'DESC' ? -1 : 1}};

  if (boxId != 'NONE') {
    query.box = boxId;
  }

  if (search != null && search != '') {
    // von:"mySubject" searchTerm
    // von: "mySubject" searchTerm
    // from:"mySubject" searchTerm
    // from: "mySubject" searchTerm
    // an:"mySubject" searchTerm
    // an: "mySubject" searchTerm
    // to:"mySubject" searchTerm
    // to: "mySubject" searchTerm
    const parsedSearch = search.match(/(\bfrom\b|\bvon\b|\bto\b|\ban\b): ?"([a-zA-Z\u00C0-\u017F0-9 ]*)"([a-zA-Z\u00C0-\u017F0-9 ]*)/);
    console.log('this is my search..');
    console.log(parsedSearch);

    if (parsedSearch != null) {
      const from = (parsedSearch[1] == 'from' || parsedSearch[1] == 'von') ? parsedSearch[2] : null;
      const to = (parsedSearch[1] == 'to' || parsedSearch[1] == 'an') ? parsedSearch[2] : null;
      const searchTerm = parsedSearch[3];

      if (from != null) {
        query['from.name'] = new RegExp('.*' + from + '.*', "i")
      }
      if (to != null) {
        query['to.name'] = new RegExp('.*' + to + '.*', "i")
      }
      if (searchTerm != null && searchTerm != ' ' && searchTerm != '') {
        query.$text = {$search: searchTerm};
      }
    } else {
      query.$text = {$search: search};
    }
  }
  return Email.find(query, select, options)
}

let Email = mongoose.model('Email', EmailSchema)
export default Email;
<commit_msg>Fix boxId in case is 0<commit_after>import mongoose from 'mongoose';
import mongoosePaginate from 'mongoose-paginate';
import Promise from 'bluebird';
import Box from './box.model';
const ObjectId = mongoose.Schema.Types.ObjectId;
const Mixed = mongoose.Schema.Types.Mixed;

const EmailSchema = new mongoose.Schema({
  user: {type: ObjectId, ref: 'User'},
  messageId: {type: String, index: true},
  uid: Number,
  box: {type: ObjectId, ref: 'Box'},
  thrid: String,
  attrs: {
    'x-gm-thrid': String,
    'x-gm-msgid': String,
    modseq: String,
    uid: Number,
    flags: [String],
    date: Date,
    struct: Mixed,
    /*[{
     language: mongoose.Schema.Types.Mixed,
     disposition: mongoose.Schema.Types.Mixed,
     md5: mongoose.Schema.Types.Mixed,
     lines: Number,
     size: Number,
     encoding: String,
     description: mongoose.Schema.Types.Mixed,
     id: mongoose.Schema.Types.Mixed,
     params: mongoose.Schema.Types.Mixed,
     subtype: String,
     type: String,
     partID: String,
     }]*/
    'x-gm-labels': [String]
  },
  from: [{
    address: String,
    name: String
  }],
  to: [{
    address: String,
    name: String
  }],
  cc: [{
    address: String,
    name: String
  }],
  bcc: [{
    address: String,
    name: String
  }],
  subject: String,
  html: String,
  text: String,
  date: {type: Date, index: true},
  flags: [String],
  labels: [String]
}, {
  timestamps: true
});

EmailSchema.plugin(mongoosePaginate);

EmailSchema.index({
  text: 'text',
  subject: 'text'
});

EmailSchema.statics.isUnseen = (email) => {
  return email.flags.indexOf("\\Seen") == -1;
}

EmailSchema.statics.isSeenUnseenChanged = (emailOld, emailNew) => {
  return emailOld.flags != null && emailNew.flags != null
    && Email.isUnseen(emailOld) != Email.isUnseen(emailNew);
}

/**
 * Updates or creates an email and
 * returns the old- and updated email
 * @param email
 * @return Promise resolve([oldMail, oldBox, updatedMail, , updatedBox])
 */
EmailSchema.statics.updateAndGetOldAndUpdated = (mail) => {
  return new Promise((resolve, reject) => {
    const res = [];
    Email.findOne({messageId: mail.messageId})
      .then(emailOld => {
        res.push(emailOld);
        return emailOld != null ? Box.findWithUnseenCountById(emailOld.box) : Box.findOne({_id: mail.box});
      })
      .then(boxOld => {
        res.push(boxOld);
        return Email.findOneAndUpdate({
          messageId: mail.messageId
        }, mail, {
          new: true, // returns new doc
          upsert: true,
          setDefaultsOnInsert: true
        });
      })
      .then(emailUpdated => {
        res.push(emailUpdated);
        return emailUpdated != null ? Box.findWithUnseenCountById(emailUpdated.box) : Promise.resolve(null);
      })
      .then(boxUpdated => {
        res.push(boxUpdated);
        resolve(res);
      })
      .catch(err => {
        reject(err);
      });
  });
}

/**
 * Returns autocomplete suggestions for email addresses
 * @param userId only consider emails of this user
 */
EmailSchema.statics.autocomplete = (userId) => {
  return Email.getCollection('emails').aggregate([
    {$match: {user: userId}},
    {$project: {address: {$setUnion: ["$from", "$to", "$cc", "$bcc"]}}},
    {$unwind: '$address'},
    {$group: {_id: {address: {$toLower: '$address.address'}, name: '$address.name'}}},
    {$project: {_id: 0, address: '$_id.address', name: '$_id.name'}},
    //{$match: {$or:[{address: /+search+/},{address: /fe/}]}} //TODO add vars
  ]);
}

/**
 * Returns the searched email, this is meant to return also a simple mail list for every box
 * @param userId
 * @param opt.sort fild that will be used to sort e.g. ASC or DESC
 * @param opt.boxId
 * @param opt.search string to searchs
 * @param opt.lastEmailDate
 */
EmailSchema.statics.search = (userId, opt) => {
  const boxId = opt.boxId;
  const sort = opt.sort;
  const search = opt.search;
  const lastEmailDate = opt.lastEmailDate;
  const query = {user: userId, date: {$lt: lastEmailDate}};
  const select = {}; // only necessary
  const options = {limit: 15, sort: {date: sort == 'DESC' ? -1 : 1}};

  if (boxId != 'NONE' && boxId != 0) {
    query.box = boxId;
  }

  if (search != null && search != '') {
    // von:"mySubject" searchTerm
    // von: "mySubject" searchTerm
    // from:"mySubject" searchTerm
    // from: "mySubject" searchTerm
    // an:"mySubject" searchTerm
    // an: "mySubject" searchTerm
    // to:"mySubject" searchTerm
    // to: "mySubject" searchTerm
    const parsedSearch = search.match(/(\bfrom\b|\bvon\b|\bto\b|\ban\b): ?"([a-zA-Z\u00C0-\u017F0-9 ]*)"([a-zA-Z\u00C0-\u017F0-9 ]*)/);
    console.log('this is my search..');
    console.log(parsedSearch);

    if (parsedSearch != null) {
      const from = (parsedSearch[1] == 'from' || parsedSearch[1] == 'von') ? parsedSearch[2] : null;
      const to = (parsedSearch[1] == 'to' || parsedSearch[1] == 'an') ? parsedSearch[2] : null;
      const searchTerm = parsedSearch[3];

      if (from != null) {
        query['from.name'] = new RegExp('.*' + from + '.*', "i")
      }
      if (to != null) {
        query['to.name'] = new RegExp('.*' + to + '.*', "i")
      }
      if (searchTerm != null && searchTerm != ' ' && searchTerm != '') {
        query.$text = {$search: searchTerm};
      }
    } else {
      query.$text = {$search: search};
    }
  }
  return Email.find(query, select, options)
}

let Email = mongoose.model('Email', EmailSchema)
export default Email;
<|endoftext|>


<commit_before>const Endless = imports.gi.Endless;
const EosKnowledge = imports.gi.EosKnowledge;
const EosKnowledgeSearch = imports.EosKnowledgeSearch;
const Gio = imports.gi.Gio;
const GObject = imports.gi.GObject;
const Lang = imports.lang;

const TESTDIR = Endless.getCurrentFileDir() + '/..';
const MOCK_ARTICLE_PATH = TESTDIR + '/test-content/mexico.jsonld';

const MockEngine = new Lang.Class({
    Name: 'MockEngine',
    Extends: GObject.Object,

    _init: function () {
        this.parent();
        this.host = 'localhost';
        this.port = 3003;
    },

    ping: function () {},
    get_object_by_id: function () {},
    get_ekn_id: function () {},
    get_objects_by_query: function () {},
});

const MockView = new Lang.Class({
    Name: 'MockView',
    Extends: GObject.Object,
    Signals: {
        'new-view-transitioned': {}
    },

    _init: function (props) {
        this.parent(props);
        this.toc = {
            connect: function () {},
        }
    },

    switch_in_content_view: function (view, animation_type) {
        this.emit('new-view-transitioned');
    },
});

describe('Article Presenter', function () {
    let presenter;
    let view;
    let mockArticleData;
    let articleObject;
    let engine;
    let webview;

    beforeEach(function (done) {
        let file = Gio.file_new_for_path(MOCK_ARTICLE_PATH);

        let [success, data] = file.load_contents(null);
        mockArticleData = JSON.parse(data);

        articleObject = new EosKnowledgeSearch.ArticleObjectModel.new_from_json_ld(mockArticleData);

        view = new MockView();
        view.connect_after('new-view-transitioned', done);

        engine = new MockEngine();
        spyOn(engine, 'get_object_by_id');

        presenter = new EosKnowledge.ArticlePresenter({
            article_view: view,
            engine: engine
        });
        presenter.load_article(articleObject, EosKnowledge.LoadingAnimationType.NONE);
    });

    it('can be constructed', function () {});

    it('can set title and subtitle on view', function () {
        expect(view.title).toBe(articleObject.title);
    });

    it('can set toc section list', function () {
        let labels = [];
        for (let obj of mockArticleData['tableOfContents']) {
            if (!('hasParent' in obj)) {
                labels.push(obj['hasLabel']);
            }
        }
        expect(view.toc.section_list).toEqual(labels);
    });

    it('emits signal when webview navigates to media object', function (done) {
        let dummy_page = '
Frango frango frango
';
        presenter.connect('media-object-clicked', function (widget, media_object) {
            expect(media_object.ekn_id).toEqual('mock_model_id');
            done();
        }.bind());
        engine.get_object_by_id.and.callFake(function (d, i, callback) {
            callback(undefined, new EosKnowledgeSearch.MediaObjectModel({
                ekn_id: 'mock_model_id'
            }));
        });
        presenter._webview.load_html(dummy_page, null);
    });

    it('emits signal when webview navigates to article object', function (done) {
        let dummy_page = '
Frango frango frango
';
        presenter.connect('article-object-clicked', function (widget, article_object) {
            expect(article_object.ekn_id).toEqual('mock_model_id');
            done();
        }.bind());
        engine.get_object_by_id.and.callFake(function (d, i, callback) {
            callback(undefined, new EosKnowledgeSearch.ArticleObjectModel({
                ekn_id: 'mock_model_id'
            }));
        });
        presenter._webview.load_html(dummy_page, null);
    });
});
<commit_msg>Ensure done() can only be called once in tests<commit_after>const Endless = imports.gi.Endless;
const EosKnowledge = imports.gi.EosKnowledge;
const EosKnowledgeSearch = imports.EosKnowledgeSearch;
const Gio = imports.gi.Gio;
const GObject = imports.gi.GObject;
const Lang = imports.lang;

const TESTDIR = Endless.getCurrentFileDir() + '/..';
const MOCK_ARTICLE_PATH = TESTDIR + '/test-content/mexico.jsonld';

const MockEngine = new Lang.Class({
    Name: 'MockEngine',
    Extends: GObject.Object,

    _init: function () {
        this.parent();
        this.host = 'localhost';
        this.port = 3003;
    },

    ping: function () {},
    get_object_by_id: function () {},
    get_ekn_id: function () {},
    get_objects_by_query: function () {},
});

const MockView = new Lang.Class({
    Name: 'MockView',
    Extends: GObject.Object,
    Signals: {
        'new-view-transitioned': {}
    },

    _init: function (props) {
        this.parent(props);
        this.toc = {
            connect: function () {},
        }
    },

    switch_in_content_view: function (view, animation_type) {
        this.emit('new-view-transitioned');
    },
});

describe('Article Presenter', function () {
    let presenter;
    let view;
    let mockArticleData;
    let articleObject;
    let engine;
    let webview;

    beforeEach(function (done) {
        let file = Gio.file_new_for_path(MOCK_ARTICLE_PATH);

        let [success, data] = file.load_contents(null);
        mockArticleData = JSON.parse(data);

        articleObject = new EosKnowledgeSearch.ArticleObjectModel.new_from_json_ld(mockArticleData);

        view = new MockView();
        view.connect_after('new-view-transitioned', done);

        engine = new MockEngine();
        spyOn(engine, 'get_object_by_id');

        presenter = new EosKnowledge.ArticlePresenter({
            article_view: view,
            engine: engine
        });
        presenter.load_article(articleObject, EosKnowledge.LoadingAnimationType.NONE);
    });

    it('can be constructed', function () {});

    it('can set title and subtitle on view', function () {
        expect(view.title).toBe(articleObject.title);
    });

    it('can set toc section list', function () {
        let labels = [];
        for (let obj of mockArticleData['tableOfContents']) {
            if (!('hasParent' in obj)) {
                labels.push(obj['hasLabel']);
            }
        }
        expect(view.toc.section_list).toEqual(labels);
    });

    it('emits signal when webview navigates to media object', function (done) {
        let dummy_page = '
Frango frango frango
';
        let id = presenter.connect('media-object-clicked', function (widget, media_object) {
            expect(media_object.ekn_id).toEqual('mock_model_id');
            presenter.disconnect(id);
            done();
        }.bind());
        engine.get_object_by_id.and.callFake(function (d, i, callback) {
            callback(undefined, new EosKnowledgeSearch.MediaObjectModel({
                ekn_id: 'mock_model_id'
            }));
        });
        presenter._webview.load_html(dummy_page, null);
    });

    it('emits signal when webview navigates to article object', function (done) {
        let dummy_page = '
Frango frango frango
';
        let id = presenter.connect('article-object-clicked', function (widget, article_object) {
            expect(article_object.ekn_id).toEqual('mock_model_id');
            presenter.disconnect(id);
            done();
        }.bind());
        engine.get_object_by_id.and.callFake(function (d, i, callback) {
            callback(undefined, new EosKnowledgeSearch.ArticleObjectModel({
                ekn_id: 'mock_model_id'
            }));
        });
        presenter._webview.load_html(dummy_page, null);
    });
});
<|endoftext|>


<commit_before>/**
 * GET /
 * Itinerary Pages
 */
var secrets = require("../config/secrets");
var async = require("async");
var yelp = require("yelp").createClient(secrets.yelp);
var User = require("../models/User");

// var visited_businesses = [];
var nullLocation = {}
nullLocation["name"] = "Oops! Data does not seem to be available for this location.";
nullLocation["id"] = "no-data";
nullLocation["rating"] = 0;
nullLocation["location"] = ["Try to find a cool activity in a different city! We'll update our databases as new data becomes available. Check back soon!"];

function stripData(data) {
  var info = {};

  info["name"] = data["name"];
  info["id"] = data["id"];
  info["rating"] = data["rating"];
  info["phone"] = phoneFormat(data["phone"]);
  info["url"] = data["url"];
  info["snippet_text"] = data["snippet_text"];
  info["review_cnt"] = data["review_count"];
  info["categories"] = data["categories"];
  info["location"] = data["location"]["display_address"]

  return info;
};

function phoneFormat(phone) {
  if (typeof phone != 'undefined') {
    phone = phone.replace(/[^0-9]/g, '');
    phone = phone.replace(/(\d{3})(\d{3})(\d{4})/, "($1) $2-$3");
    return phone;
  } else {
    return "";
  }
}

function stripDataDetail(data) {
  var info = {};
  info["name"] = data["name"];
  info["id"] = data["id"];
  info["rating"] = data["rating"];
  info["phone"] = phoneFormat(data["phone"]);
  info["url"] = data["url"];
  info["snippet_text"] = data["snippet_text"];
  info["review_cnt"] = data["review_count"];
  info["categories"] = data["categories"];
  info["location"] = data["location"]["display_address"]

  return info;
};

function getBestBusiness(businesses, visited) {
  var scores = []
  for (var i=0; i < businesses.length; i++) {
   scores.push([scoreBusiness(businesses[i]), i]);
  }

  scores.sort(function(x, y) {
   return y[0] - x[0];
  });

  j = 0;
  if (scores == [] || typeof(scores[0]) == "undefined") {
    return nullLocation;
  }
  while (alreadyVisited(businesses[scores[j][1]], visited)) {
   j++;
  }
  return businesses[scores[j][1]];
};

// Probably requires profile data.
function scoreBusiness(business) {
  return business["rating"];
};

function alreadyVisited(business, visited) {
  var bool = 0;
  for (var i=0; i < visited.length; i++) {
  if (business["id"] == visited[i]["id"]) {
    bool++;
  }
  }
  return bool;
};

exports.getItinerary = function(req, res) {
  res.render('itinerary/itinerary', {
  title: 'Itinerary',
  searchTerm: "CITY"
  });
};

exports.getDetail = function(req, res) {
  var location = [];
  var locID = req.params.id
  var searchTerm = req.params.searchTerm

  yelp.business(locID, function(err, locationData) {
    // console.log(locationData);
    if (locID == "no-data") {
      location.push(nullLocation);
    }
    else {
      location.push(stripDataDetail(locationData));
    }
    res.render('itinerary/detail', {
      title: 'Detail Page',
      searchTerm: searchTerm,
      loc: location[0]

    });
  })
};

// currently lacks a button
exports.clearHistory = function(req, res) {
  User.findById(req.user.id, function(err, user) {
    user.user_history.brunches = [];
    user.user_history.events1 = [];
    user.user_history.events2 = [];
    user.user_history.dinners = [];
    user.user_history.nightlives = [];
  })
};

exports.searchYelp = function(req, res) {
  if (!(req.body.city && req.body.city.length > 0)) {
    return res.redirect("/");
  }

  var brunches = [];
  var events1 = [];
  var events2 = [];
  var dinners = [];
  var nightlives = [];

  var brunch, event1, event2, dinner, nightlife;

  var bquery;
  var e1query;
  var e2query;
  var dquery;
  var nlquery;

  if (req.user) {
    bquery = req.user.queries.brunch;
    e1query = req.user.queries.event1;
    e2query = req.user.queries.event2;
    dquery = req.user.queries.dinner;
    nlquery = req.user.queries.nightlife;
  }
  else {
    bquery = "lunch or brunch or breakfast";
    e1query = "park or zoo or hike";
    e2query = "museum or landmark or concert";
    dquery = "dinner";
    nlquery = "bar or nightlife or club";
  }

  // Brunch
  yelp.search({term: bquery, location: req.body.city}, function(err, brunchData) {
    brunchData.businesses.forEach(function(i) {
      brunches.push(stripData(i));
    });

    if (req.user) {
      User.findById(req.user.id, function(err, user) {
        brunch = getBestBusiness(brunches, user.user_history.brunches);
        user.user_history.brunches.unshift(brunch);
        if (user.user_history.brunches.length > 10) {
          user.user_history.brunches.pop();
        }
        user.save();
      });
    }
    else {
      brunch = getBestBusiness(brunches, []);
    }

    // Event ("outdoors")
    yelp.search({term: e1query, location: req.body.city}, function(err, eventsData) {
      eventsData.businesses.forEach(function(i) {
        events1.push(stripData(i));
      });

      if (req.user) {
        User.findById(req.user.id, function(err, user) {
          event1 = getBestBusiness(events1, user.user_history.events1);
          user.user_history.events1.unshift(event1);
          if (user.user_history.events1.length > 10) {
            user.user_history.events1.pop();
          }
          user.save();
        });
      }
      else {
        event1 = getBestBusiness(events1, []);
      }

      // Event ("indoors")
      yelp.search({term: e2query, location: req.body.city}, function(err, eventsData) {
        eventsData.businesses.forEach(function(i) {
          events2.push(stripData(i));
        });

        if (req.user) {
          User.findById(req.user.id, function(err, user) {
            event2 = getBestBusiness(events2, user.user_history.events2);
            user.user_history.events2.unshift(event2);
            if (user.user_history.events2.length > 10) {
              user.user_history.events2.pop();
            }
            user.save();
          });
        }
        else {
          event2 = getBestBusiness(events2, []);
        }

        // Dinner
        yelp.search({term: dquery, location: req.body.city}, function(err, dinnerData) {
          dinnerData.businesses.forEach(function(i) {
            dinners.push(stripData(i));
          });

          if (req.user) {
            User.findById(req.user.id, function(err, user) {
              dinner = getBestBusiness(dinners, user.user_history.dinners);
              user.user_history.dinners.unshift(dinner);
              if (user.user_history.dinners.length > 10) {
                user.user_history.dinners.pop();
              }
              user.save();
            });
          }
          else {
            dinner = getBestBusiness(dinners, []);
          }

          // Nightlife
          yelp.search({term: nlquery, location: req.body.city}, function(err, barsData) {
            barsData.businesses.forEach(function(i) {
              nightlives.push(stripData(i));
            });

            if (req.user) {
              User.findById(req.user.id, function(err, user) {
                nightlife = getBestBusiness(nightlives, user.user_history.nightlives);
                user.user_history.nightlives.unshift(nightlife);
                if (user.user_history.nightlives.length > 10) {
                  user.user_history.nightlives.pop();
                }
                user.save();
                res.render('itinerary/itinerary', {
                  searchTerm: req.body.city,
                  title: 'Itinerary',
                  brunchPlace: brunch,
                  eventPlace1: event1,
                  eventPlace2: event2,
                  dinnerPlace: dinner,
                  nightPlace: nightlife,
                });
              });
            }
            else {
              nightlife = getBestBusiness(nightlives, []);
              res.render('itinerary/itinerary', {
                searchTerm: req.body.city.charAt(0).toUpperCase() + req.body.city.slice(1).toLowerCase(),
                title: 'Itinerary',
                brunchPlace: brunch,
                eventPlace1: event1,
                eventPlace2: event2,
                dinnerPlace: dinner,
                nightPlace: nightlife,
              });
            }
          });
        });
      });
    });
  });
}<commit_msg>fixed empty response for yelp searches<commit_after>/**
 * GET /
 * Itinerary Pages
 */
var secrets = require("../config/secrets");
var async = require("async");
var yelp = require("yelp").createClient(secrets.yelp);
var User = require("../models/User");

// var visited_businesses = [];
var nullLocation = {}
nullLocation["name"] = "Oops! Data does not seem to be available for this location.";
nullLocation["id"] = "no-data";
nullLocation["rating"] = 0;
nullLocation["location"] = ["Try to find a cool activity in a different city! We'll update our databases as new data becomes available. Check back soon!"];

function stripData(data) {
  var info = {};

  info["name"] = data["name"];
  info["id"] = data["id"];
  info["rating"] = data["rating"];
  info["phone"] = phoneFormat(data["phone"]);
  info["url"] = data["url"];
  info["snippet_text"] = data["snippet_text"];
  info["review_cnt"] = data["review_count"];
  info["categories"] = data["categories"];
  info["location"] = data["location"]["display_address"]

  return info;
};

function phoneFormat(phone) {
  if (typeof phone != 'undefined') {
    phone = phone.replace(/[^0-9]/g, '');
    phone = phone.replace(/(\d{3})(\d{3})(\d{4})/, "($1) $2-$3");
    return phone;
  } else {
    return "";
  }
}

function stripDataDetail(data) {
  var info = {};
  info["name"] = data["name"];
  info["id"] = data["id"];
  info["rating"] = data["rating"];
  info["phone"] = phoneFormat(data["phone"]);
  info["url"] = data["url"];
  info["snippet_text"] = data["snippet_text"];
  info["review_cnt"] = data["review_count"];
  info["categories"] = data["categories"];
  info["location"] = data["location"]["display_address"]

  return info;
};

function getBestBusiness(businesses, visited) {
  var scores = []
  for (var i=0; i < businesses.length; i++) {
   scores.push([scoreBusiness(businesses[i]), i]);
  }

  scores.sort(function(x, y) {
   return y[0] - x[0];
  });

  j = 0;
  if (scores == [] || typeof(scores[0]) == "undefined") {
    return nullLocation;
  }
  while (alreadyVisited(businesses[scores[j][1]], visited)) {
   j++;
  }
  return businesses[scores[j][1]];
};

// Probably requires profile data.
function scoreBusiness(business) {
  return business["rating"];
};

function alreadyVisited(business, visited) {
  var bool = 0;
  for (var i=0; i < visited.length; i++) {
  if (business["id"] == visited[i]["id"]) {
    bool++;
  }
  }
  return bool;
};

exports.getItinerary = function(req, res) {
  res.render('itinerary/itinerary', {
  title: 'Itinerary',
  searchTerm: "CITY"
  });
};

exports.getDetail = function(req, res) {
  var location = [];
  var locID = req.params.id
  var searchTerm = req.params.searchTerm

  yelp.business(locID, function(err, locationData) {
    // console.log(locationData);
    if (locID == "no-data") {
      location.push(nullLocation);
    }
    else {
      location.push(stripDataDetail(locationData));
    }
    res.render('itinerary/detail', {
      title: 'Detail Page',
      searchTerm: searchTerm,
      loc: location[0]

    });
  })
};

// currently lacks a button
exports.clearHistory = function(req, res) {
  User.findById(req.user.id, function(err, user) {
    user.user_history.brunches = [];
    user.user_history.events1 = [];
    user.user_history.events2 = [];
    user.user_history.dinners = [];
    user.user_history.nightlives = [];
  })
};

exports.searchYelp = function(req, res) {
  if (!(req.body.city && req.body.city.length > 0)) {
    return res.redirect("/");
  }

  var brunches = [];
  var events1 = [];
  var events2 = [];
  var dinners = [];
  var nightlives = [];

  var brunch, event1, event2, dinner, nightlife;

  var bquery;
  var e1query;
  var e2query;
  var dquery;
  var nlquery;

  if (req.user) {
    bquery = req.user.queries.brunch;
    e1query = req.user.queries.event1;
    e2query = req.user.queries.event2;
    dquery = req.user.queries.dinner;
    nlquery = req.user.queries.nightlife;
  }
  else {
    bquery = "lunch or brunch or breakfast";
    e1query = "park or zoo or hike";
    e2query = "museum or landmark or concert";
    dquery = "dinner";
    nlquery = "bar or nightlife or club";
  }

  // Brunch
  yelp.search({term: bquery, location: req.body.city}, function(err, brunchData) {
    if(!typeof(brunchData.businesses)==='undefined') {
      brunchData.businesses.forEach(function(i) {
        brunches.push(stripData(i));
      });
    }

    if (req.user) {
      User.findById(req.user.id, function(err, user) {
        brunch = getBestBusiness(brunches, user.user_history.brunches);
        user.user_history.brunches.unshift(brunch);
        if (user.user_history.brunches.length > 10) {
          user.user_history.brunches.pop();
        }
        user.save();
      });
    }
    else {
      brunch = getBestBusiness(brunches, []);
    }

    // Event ("outdoors")
    yelp.search({term: e1query, location: req.body.city}, function(err, eventsData) {
      if(!typeof(eventsData.businesses)==='undefined') {
        eventsData.businesses.forEach(function(i) {
          events1.push(stripData(i));
        });
      }

      if (req.user) {
        User.findById(req.user.id, function(err, user) {
          event1 = getBestBusiness(events1, user.user_history.events1);
          user.user_history.events1.unshift(event1);
          if (user.user_history.events1.length > 10) {
            user.user_history.events1.pop();
          }
          user.save();
        });
      }
      else {
        event1 = getBestBusiness(events1, []);
      }

      // Event ("indoors")
      yelp.search({term: e2query, location: req.body.city}, function(err, eventsData) {
        if(!typeof(eventsData.businesses)==='undefined') {
          eventsData.businesses.forEach(function(i) {
            events2.push(stripData(i));
          });
        }

        if (req.user) {
          User.findById(req.user.id, function(err, user) {
            event2 = getBestBusiness(events2, user.user_history.events2);
            user.user_history.events2.unshift(event2);
            if (user.user_history.events2.length > 10) {
              user.user_history.events2.pop();
            }
            user.save();
          });
        }
        else {
          event2 = getBestBusiness(events2, []);
        }

        // Dinner
        yelp.search({term: dquery, location: req.body.city}, function(err, dinnerData) {
          if(!typeof(dinnerData.businesses)==='undefined') {
            dinnerData.businesses.forEach(function(i) {
              dinners.push(stripData(i));
            });
          }

          if (req.user) {
            User.findById(req.user.id, function(err, user) {
              dinner = getBestBusiness(dinners, user.user_history.dinners);
              user.user_history.dinners.unshift(dinner);
              if (user.user_history.dinners.length > 10) {
                user.user_history.dinners.pop();
              }
              user.save();
            });
          }
          else {
            dinner = getBestBusiness(dinners, []);
          }

          // Nightlife
          yelp.search({term: nlquery, location: req.body.city}, function(err, barsData) {
            if(!typeof(barsData.businesses)==='undefined') {
              barsData.businesses.forEach(function(i) {
                nightlives.push(stripData(i));
              });
            }

            if (req.user) {
              User.findById(req.user.id, function(err, user) {
                nightlife = getBestBusiness(nightlives, user.user_history.nightlives);
                user.user_history.nightlives.unshift(nightlife);
                if (user.user_history.nightlives.length > 10) {
                  user.user_history.nightlives.pop();
                }
                user.save();
                res.render('itinerary/itinerary', {
                  searchTerm: req.body.city,
                  title: 'Itinerary',
                  brunchPlace: brunch,
                  eventPlace1: event1,
                  eventPlace2: event2,
                  dinnerPlace: dinner,
                  nightPlace: nightlife,
                });
              });
            }
            else {
              nightlife = getBestBusiness(nightlives, []);
              res.render('itinerary/itinerary', {
                searchTerm: req.body.city.charAt(0).toUpperCase() + req.body.city.slice(1).toLowerCase(),
                title: 'Itinerary',
                brunchPlace: brunch,
                eventPlace1: event1,
                eventPlace2: event2,
                dinnerPlace: dinner,
                nightPlace: nightlife,
              });
            }
          });
        });
      });
    });
  });
}<|endoftext|>


<commit_before>var expect = require('chai').expect,
    di = require('../../di');

describe('queue test', function() {

    'use strict';

    var q = di.getFactory('ds', 'queue')();

    describe('when the queue is empty', function() {

        it('.isEmpty() should return true', function() {
            expect(q.isEmpty()).to.be.equal(true);
        });

        it('.length() should return 0', function() {
            expect(q.length()).to.be.equal(0);
        });

        it('.front(), .back() and .dequeue() should return null', function() {
            expect(q.dequeue()).to.be.equal(null);
            expect(q.front()).to.be.equal(null);
            expect(q.back()).to.be.equal(null);
        });

        it('.enqueue() should queue elements', function() {
            q.enqueue(1);
            q.enqueue(8);
            q.enqueue(45);
            expect(q.isEmpty()).to.be.equal(false);
        });

    });

    describe('when the queue is not empty', function() {

        it('.isEmpty() should return false', function() {
            expect(q.isEmpty()).to.be.equal(false);
        });

        it('.length() should return a number bigger than 0', function() {
            expect(q.length()).to.be.equal(3);
        });

        it('.front() should retreive the front element', function(){
            expect(q.front()).to.be.equal(1);
        });

        it('.back() should retreive the back element', function(){
            expect(q.back()).to.be.equal(45);
        });

        it('.dequeue() should dequeue an element', function() {
            expect(q.dequeue()).to.be.equal(1);
            expect(q.front()).to.be.equal(8);
            expect(q.back()).to.be.equal(45);
        });

        it('clear() should empty the queue', function(){
            expect(q.length()).to.be.equal(2);
            q.clear();
            expect(q.length()).to.be.equal(0);
        });

        it('getElements() should return all elements', function(){
            q.enqueue(1);
            q.enqueue(8);
            q.enqueue(45);
            let elements = q.getElements();
            expect(elements[0]).to.be.equal(1);
            expect(elements[1]).to.be.equal(8);
            expect(elements[2]).to.be.equal(45);
        });

    });

});
<commit_msg>Cant use es6 here, so using var instead of let in this test.<commit_after>var expect = require('chai').expect,
    di = require('../../di');

describe('queue test', function() {

    'use strict';

    var q = di.getFactory('ds', 'queue')();

    describe('when the queue is empty', function() {

        it('.isEmpty() should return true', function() {
            expect(q.isEmpty()).to.be.equal(true);
        });

        it('.length() should return 0', function() {
            expect(q.length()).to.be.equal(0);
        });

        it('.front(), .back() and .dequeue() should return null', function() {
            expect(q.dequeue()).to.be.equal(null);
            expect(q.front()).to.be.equal(null);
            expect(q.back()).to.be.equal(null);
        });

        it('.enqueue() should queue elements', function() {
            q.enqueue(1);
            q.enqueue(8);
            q.enqueue(45);
            expect(q.isEmpty()).to.be.equal(false);
        });

    });

    describe('when the queue is not empty', function() {

        it('.isEmpty() should return false', function() {
            expect(q.isEmpty()).to.be.equal(false);
        });

        it('.length() should return a number bigger than 0', function() {
            expect(q.length()).to.be.equal(3);
        });

        it('.front() should retreive the front element', function(){
            expect(q.front()).to.be.equal(1);
        });

        it('.back() should retreive the back element', function(){
            expect(q.back()).to.be.equal(45);
        });

        it('.dequeue() should dequeue an element', function() {
            expect(q.dequeue()).to.be.equal(1);
            expect(q.front()).to.be.equal(8);
            expect(q.back()).to.be.equal(45);
        });

        it('clear() should empty the queue', function(){
            expect(q.length()).to.be.equal(2);
            q.clear();
            expect(q.length()).to.be.equal(0);
        });

        it('getElements() should return all elements', function(){
            q.enqueue(1);
            q.enqueue(8);
            q.enqueue(45);
            var elements = q.getElements();
            expect(elements[0]).to.be.equal(1);
            expect(elements[1]).to.be.equal(8);
            expect(elements[2]).to.be.equal(45);
        });

    });

});
<|endoftext|>