<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')

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

  /**
   * 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
   * @returns {Object} The normalized query object
   * @private
   */
  function normalize(query) {
    // 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 = 10
    } else {
      query.limit = Number(query.limit)
    }

    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
   */
  function params(parameters) {
    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
    })
  }

  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)

      // 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))

        // 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)

      // 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)

      // 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)

    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: extract normalize and params method and add to export<commit_after>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')

/**
 * 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 = 10
  } else {
    query.limit = Number(query.limit)
  }

  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

  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
  })
}
<|endoftext|>


<commit_before>// AGPLv3 or later
// Copyright (c) 2014 Genome Research Limited

var mongo  = require('mongodb').MongoClient,
    exists = {'$exists': true}
    noid   = {'_id': false};

// Error handling
var ServerError = function(status, message) {
  this.status  = status;
  this.message = message;
};

ServerError.prototype = Object.create(Error.prototype);
ServerError.prototype.constructor = ServerError;
ServerError.prototype.name = 'ServerError';

// Expand as necessary...
// http://en.wikipedia.org/wiki/List_of_HTTP_status_codes
ServerError.prototype.httpStatus = {
  '404': 'Not Found',
  '500': 'Internal Server Error',
  '501': 'Not Implemented',
  '502': 'Bad Gateway'
};

// Don't throw, we want to keep the server up
ServerError.prototype.handle = function(req, res) {
  var message = this.httpStatus[this.status] || 'Unknown Error';
  if (this.message) { message += ': ' + this.message; }

  res.status(this.status).send(message);
};

// 501 Lazy Developer
var notDoneYet = function(req, res) {
  var excuse = (function() {
    var excuses = [
      'Rome wasn\'t built in a day',
      'Give me a chance!',
      'Whip the lazy developer',
      '...',
      'It\'s too late for me. Save yourselves!',
      'I\'m waiting to be motivated'
    ];

    return excuses[Math.floor(Math.random() * excuses.length)];
  })();

  (new ServerError(501, excuse)).handle(req, res);
};

// Connection pool of client gateways
var clientGateway = (function() {
  var gateways = {};

  // Access client database
  // name :: Client name
  // callback :: function(err, Client database)
  var main = function(name, callback) {
    if (!gateways.hasOwnProperty(name)) {
      // No such client
      callback(new ServerError(404, 'No such client \'' + name + '\''), null);

    } else {
      if (!gateways[name].db) {
        mongo.connect(gateways[name].url, function(err, db) {
          if (err) {
            callback(new ServerError(502, err.message), null);
          } else {
            console.log('Connected to %s client database', name);
            gateways[name].db = db;
            db.on('close', function() {
              console.log('Connection to %s client database closed', name);
              gateways[name].db = null;
            });

            callback(null, gateways[name].db);
          }
        });
      } else {
        callback(null, gateways[name].db);
      }
    }
  };

  // Initialise/refresh client connection pool
  // db :: Mongo DB
  // callback :: function(err, Client array)
  main.init = function(db, callback) {
    // Attempt to open the _client collection
    db.collection('_client', {strict: true}, function(err, clients) {
      if (err && !clients) {
        callback(new ServerError(500, err.message), null);
      } else {
        clients.find({name: exists, url: exists}, noid).toArray(function(err, clientData) {
          if (err) {
            callback(new ServerError(500, err.message), null);

          } else {
            // Update cache
            clientData.forEach(function(client) {
              var refreshConnection = false;

              // Check if we need to do anything...
              if (gateways.hasOwnProperty(client.name)) {
                if (gateways[client.name].url != client.url) {
                  refreshConnection = true;
                }
              } else {
                gateways[client.name] = {};
                refreshConnection = true;
              }

              // ...and if we do
              if (refreshConnection) {
                var gateway = gateways[client.name];
                gateway.url = client.url;
                if (gateway.db) { gateway.db.close(); }
              }
            });

            // Return list of clients
            callback(null, clientData);
          }
        });
      }
    });
  };

  // Close connection pool on SIGINT
  process.on('SIGINT', function() {
    for (client in gateways) {
      if (gateways.hasOwnProperty(client) && gateways[client].db) {
        gateways[client].db.close();
      }
    }
  });

  return main;
})();

// OK, now we can actually do the route handlers!
module.exports = {
  root: {
    get: function(req, res) {
      // Get list of registered clients
      clientGateway.init(req.db, function(err, clients) {
        if (err) {
          err.handle(req, res);

        } else {
          // TODO Hypermedia content
          res.type('json');
          res.send(clients);
        }
      });
    },

    post:   notDoneYet, // Create new client gateway
    put:    notDoneYet, // Update client gateway
    delete: notDoneYet  // Delete client gateway
  },

  id: {
    get: function(req, res) {
      clientGateway.init(req.db, function(err, clients) {
        if (err) {
          err.handle(req, res);

        } else {
          var clientID = req.params.id;
          clientGateway(clientID, function(err, clientDB) {
            if (err) {
              err.handle(req, res);

            } else {
              clientDB.collectionNames(function(err, collections) {
                var clientCollections = collections
                  .map(function(c) {
                    // Strip database name
                    c.name = c.name.replace(clientDB.databaseName + '.', '');
                    return c;
                  })
                  .filter(function(c) {
                    // Remove system collections
                    return !/^system\./.test(c.name);
                  });

                res.send(clientCollections);
              });
            }
          });
        }
      });
    },

    post:   notDoneYet, // Create new client collection
    delete: notDoneYet  // Delete client collection
  },

  data: {
    get: function(req, res) {
      var clientID = req.params.id,
          dataID   = req.params.data,
          subPath  = req.params[0] ? req.params[0].substr(1).split('/') : [],
          dataObj;

      if (objAt(clients, [clientID])) {
        if (dataObj = objAt(clients, [clientID, dataID].concat(subPath))) {
          // TODO Hypermedia content
          res.type('json');
          res.send(dataObj);

        } else {
          // Not cool
          var dataKey = dataID + req.params[0];
          res.status(404).send('\'' + clientID + '\' has no \'' + dataKey + '\' data resource');
        }
      } else {
        // Not cool
        res.status(404).send('No such client \'' + clientID + '\'');
      }
    },

    post:   notDoneYet, // Create client collection data
    put:    notDoneYet, // Update client collection data
    patch:  notDoneYet, // Patch client collection data
    delete: notDoneYet  // Delete client collection data
  }
};
<commit_msg>Formatting<commit_after>// AGPLv3 or later
// Copyright (c) 2014 Genome Research Limited

var mongo  = require('mongodb').MongoClient,
    exists = {'$exists': true}
    noid   = {'_id': false};

// Error handling
var ServerError = function(status, message) {
  this.status  = status;
  this.message = message;
};

ServerError.prototype = Object.create(Error.prototype);
ServerError.prototype.constructor = ServerError;
ServerError.prototype.name = 'ServerError';

// Expand as necessary...
// http://en.wikipedia.org/wiki/List_of_HTTP_status_codes
ServerError.prototype.httpStatus = {
  '404': 'Not Found',
  '500': 'Internal Server Error',
  '501': 'Not Implemented',
  '502': 'Bad Gateway'
};

// Don't throw, we want to keep the server up
ServerError.prototype.handle = function(req, res) {
  var message = this.httpStatus[this.status] || 'Unknown Error';
  if (this.message) { message += ': ' + this.message; }

  res.status(this.status).send(message);
};

// 501 Lazy Developer
var notDoneYet = function(req, res) {
  var excuse = (function() {
    var excuses = [
      'Rome wasn\'t built in a day',
      'Give me a chance!',
      'Whip the lazy developer',
      '...',
      'It\'s too late for me. Save yourselves!',
      'I\'m waiting to be motivated'
    ];

    return excuses[Math.floor(Math.random() * excuses.length)];
  })();

  (new ServerError(501, excuse)).handle(req, res);
};

// Connection pool of client gateways
var clientGateway = (function() {
  var gateways = {};

  // Access client database
  // name :: Client name
  // callback :: function(err, Client database)
  var main = function(name, callback) {
    if (!gateways.hasOwnProperty(name)) {
      // No such client
      callback(new ServerError(404, 'No such client \'' + name + '\''), null);

    } else {
      if (!gateways[name].db) {
        mongo.connect(gateways[name].url, function(err, db) {
          if (err) {
            callback(new ServerError(502, err.message), null);

          } else {
            console.log('Connected to %s client database', name);
            gateways[name].db = db;
            db.on('close', function() {
              console.log('Connection to %s client database closed', name);
              gateways[name].db = null;
            });

            callback(null, gateways[name].db);
          }
        });

      } else {
        callback(null, gateways[name].db);
      }
    }
  };

  // Initialise/refresh client connection pool
  // db :: Mongo DB
  // callback :: function(err, Client array)
  main.init = function(db, callback) {
    // Attempt to open the _client collection
    db.collection('_client', {strict: true}, function(err, clients) {
      if (err && !clients) {
        callback(new ServerError(500, err.message), null);

      } else {
        clients.find({name: exists, url: exists}, noid).toArray(function(err, clientData) {
          if (err) {
            callback(new ServerError(500, err.message), null);

          } else {
            // Update cache
            clientData.forEach(function(client) {
              var refreshConnection = false;

              // Check if we need to do anything...
              if (gateways.hasOwnProperty(client.name)) {
                if (gateways[client.name].url != client.url) {
                  refreshConnection = true;
                }

              } else {
                gateways[client.name] = {};
                refreshConnection = true;
              }

              // ...and if we do
              if (refreshConnection) {
                var gateway = gateways[client.name];
                gateway.url = client.url;
                if (gateway.db) { gateway.db.close(); }
              }
            });

            // Return list of clients
            callback(null, clientData);
          }
        });
      }
    });
  };

  // Close connection pool on SIGINT
  process.on('SIGINT', function() {
    for (client in gateways) {
      if (gateways.hasOwnProperty(client) && gateways[client].db) {
        gateways[client].db.close();
      }
    }
  });

  return main;
})();

// OK, now we can actually do the route handlers!
module.exports = {
  root: {
    get: function(req, res) {
      // Get list of registered clients
      clientGateway.init(req.db, function(err, clients) {
        if (err) {
          err.handle(req, res);

        } else {
          // TODO Hypermedia content
          res.type('json');
          res.send(clients);
        }
      });
    },

    post:   notDoneYet, // Create new client gateway
    put:    notDoneYet, // Update client gateway
    delete: notDoneYet  // Delete client gateway
  },

  id: {
    get: function(req, res) {
      clientGateway.init(req.db, function(err, clients) {
        if (err) {
          err.handle(req, res);

        } else {
          var clientID = req.params.id;
          clientGateway(clientID, function(err, clientDB) {
            if (err) {
              err.handle(req, res);

            } else {
              clientDB.collectionNames(function(err, collections) {
                var clientCollections = collections
                  .map(function(c) {
                    // Strip database name
                    c.name = c.name.replace(clientDB.databaseName + '.', '');
                    return c;
                  })
                  .filter(function(c) {
                    // Remove system collections
                    return !/^system\./.test(c.name);
                  });

                res.send(clientCollections);
              });
            }
          });
        }
      });
    },

    post:   notDoneYet, // Create new client collection
    delete: notDoneYet  // Delete client collection
  },

  data: {
    get: notDoneYet,

    // get: function(req, res) {
    //   var clientID = req.params.id,
    //       dataID   = req.params.data,
    //       subPath  = req.params[0] ? req.params[0].substr(1).split('/') : [],
    //       dataObj;

    //   if (objAt(clients, [clientID])) {
    //     if (dataObj = objAt(clients, [clientID, dataID].concat(subPath))) {
    //       // TODO Hypermedia content
    //       res.type('json');
    //       res.send(dataObj);

    //     } else {
    //       // Not cool
    //       var dataKey = dataID + req.params[0];
    //       res.status(404).send('\'' + clientID + '\' has no \'' + dataKey + '\' data resource');
    //     }
    //   } else {
    //     // Not cool
    //     res.status(404).send('No such client \'' + clientID + '\'');
    //   }
    // },

    post:   notDoneYet, // Create client collection data
    put:    notDoneYet, // Update client collection data
    patch:  notDoneYet, // Patch client collection data
    delete: notDoneYet  // Delete client collection data
  }
};
<|endoftext|>


<commit_before>/** @jsx element */

import ModalMessage from '../components/ModalMessage'
import Controls from '../components/Controls'
import element from 'vdux/element'
import {Block} from 'vdux-ui'
import Output from '../components/Output'
import omit from '@f/omit'
import createAction from '@f/create-action'

const changeTab = createAction('<Home/>: CHANGE_TAB')

function initialState ({props, local}) {
  return {
    tab: 'sandbox',
    actions: {
      tabChanged: local((name) => changeTab(name))
    }
  }
}

function render ({props, state}) {
  const {
    selectedLine,
    activeLine,
    running,
    active,
    hasRun,
    game,
    left
  } = props

  const {actions, tab} = state

  const {
    animals,
    inputType
  } = game

  const size = '400px'

  const outputProps = {
    inputType,
    animals: animals.map((animal) => omit('sequence', animal)),
    running,
    active,
    size
  }

  return (
    <Block bgColor='background' relative>
      <Block
        relative
        display='flex'
        left='0'
        minHeight='100%'
        wide>
        <Output
          size={size}
          tabs={['sandbox', 'options']}
          handleTabClick={actions.tabChanged}
          tab={tab}
          options
          hasRun={hasRun}
          {...game}
          {...outputProps}
        />
        <Controls
          selectedLine={selectedLine}
          activeLine={activeLine}
          inputType={inputType}
          running={running}
          hasRun={hasRun}
          active={active}
          animals={animals}/>
      </Block>
    </Block>
  )
}

function reducer (state, action) {
  switch (action.type) {
    case changeTab.type:
      return {
        ...state,
        tab: action.payload
      }
  }
  return state
}

export default {
  initialState,
  reducer,
  render
}
<commit_msg>layout on sandbox<commit_after>/** @jsx element */

import ModalMessage from '../components/ModalMessage'
import Controls from '../components/Controls'
import element from 'vdux/element'
import {Block} from 'vdux-ui'
import Output from '../components/Output'
import omit from '@f/omit'
import createAction from '@f/create-action'

const changeTab = createAction('<Home/>: CHANGE_TAB')

function initialState ({props, local}) {
  return {
    tab: 'sandbox',
    actions: {
      tabChanged: local((name) => changeTab(name))
    }
  }
}

function render ({props, state}) {
  const {
    selectedLine,
    activeLine,
    running,
    active,
    hasRun,
    game,
    left
  } = props

  const {actions, tab} = state

  const {
    animals,
    inputType
  } = game

  const size = '400px'

  const outputProps = {
    inputType,
    animals: animals.map((animal) => omit('sequence', animal)),
    running,
    active,
    size
  }

  return (
    <Block tall wide bgColor='background' relative>
      <Block
      relative
        relative
        display='flex'
        left='0'
        minHeight='100%'
        h='100%'
        wide>
        <Output
          size={size}
          tabs={['sandbox', 'options']}
          handleTabClick={actions.tabChanged}
          tab={tab}
          options
          hasRun={hasRun}
          {...game}
          {...outputProps}
        />
        <Controls
          selectedLine={selectedLine}
          activeLine={activeLine}
          inputType={inputType}
          running={running}
          hasRun={hasRun}
          active={active}
          animals={animals}/>
      </Block>
    </Block>
  )
}

function reducer (state, action) {
  switch (action.type) {
    case changeTab.type:
      return {
        ...state,
        tab: action.payload
      }
  }
  return state
}

export default {
  initialState,
  reducer,
  render
}
<|endoftext|>


<commit_before>import React from 'react';

import SPELLS from 'common/SPELLS';
import SpellIcon from 'common/SpellIcon';
import SpellLink from 'common/SpellLink';
import { formatPercentage, formatThousands } from 'common/format';

import Module from 'Parser/Core/Module';

/**
 * Second Sunrise does not cause any special events aside from additional Light of Dawn heals. These heals occur after a static amount of time, this looks to be about 700ms. Initial LoD heals always occur within 100ms, so this is a save middle where the SS delay is more likely to vary than the initial heal delay.
 */
const SECOND_SUNRISE_MINIMAL_DELAY = 300;
const SECOND_SUNRISE_PROC_CHANCE = 0.05;

function formatDuration(duration) {
  const sumSeconds = duration / 1000;
  const seconds = (sumSeconds % 60);
  const minutes = Math.floor(sumSeconds / 60);
  return `${minutes < 10 ? `0${minutes}` : minutes}:${seconds < 10 ? `0${seconds.toFixed(3)}` : seconds.toFixed(3)}`;
}

/**
 * Second Sunrise (Artifact Trait)
 * Light of Dawn has a 5% chance to cast a second time for no additional mana cost.
 */
class SecondSunrise extends Module {
  rank = 0;
  healing = 0;

  get healingFactorOfOnePoint() {
    return SECOND_SUNRISE_PROC_CHANCE / (this.rank * SECOND_SUNRISE_PROC_CHANCE);
  }

  on_initialized() {
    this.rank = this.owner.selectedCombatant.traitsBySpellId[SPELLS.SECOND_SUNRISE.id];
    this.active = this.rank > 0;
  }

  _lastCast = null;
  on_byPlayer_cast(event) {
    if (event.ability.guid !== SPELLS.LIGHT_OF_DAWN_CAST.id) {
      return;
    }
    this._lastCast = event.timestamp;
  }
  on_byPlayer_heal(event) {
    if (event.ability.guid !== SPELLS.LIGHT_OF_DAWN_HEAL.id || this._lastCast === null) {
      return;
    }
    const timeSinceLastCast = event.timestamp - this._lastCast;
    if (timeSinceLastCast > SECOND_SUNRISE_MINIMAL_DELAY) {
      this.healing += (event.amount + (event.absorbed || 0));
      console.log(formatDuration(event.timestamp - this.owner.fight.start_time));
    }
  }
  on_beacon_heal(beaconTransferEvent, healEvent) {
    if (healEvent.ability.guid !== SPELLS.LIGHT_OF_DAWN_HEAL.id) {
      return;
    }
    const timeSinceLastCast = healEvent.timestamp - this._lastCast;
    if (timeSinceLastCast > SECOND_SUNRISE_MINIMAL_DELAY) {
      this.healing += (beaconTransferEvent.amount + (beaconTransferEvent.absorbed || 0));
      console.log(formatDuration(beaconTransferEvent.timestamp - this.owner.fight.start_time));
    }
  }

  subStatistic() {
    return (

          <SpellLink id={SPELLS.SECOND_SUNRISE.id}>
            <SpellIcon id={SPELLS.SECOND_SUNRISE.id} noLink /> Second Sunrise
          </SpellLink>

          <dfn data-tip={`For each Light of Dawn cast you had a ${formatPercentage(this.rank * SECOND_SUNRISE_PROC_CHANCE, 0)}% chance to proc Second Sunrise. One point giving 5% proc chance is ${formatPercentage(this.healingFactorOfOnePoint, 0)}% of this ${formatPercentage(this.rank * SECOND_SUNRISE_PROC_CHANCE, 0)}% chance, so the assumption is made that ${formatPercentage(this.healingFactorOfOnePoint, 0)}% of the total healing by Second Sunrise (which was ${formatThousands(this.healing)} healing) was due to one point. This gets us a simplified average but this may be completely off since Second Sunrise is a completely random proc with a low proc rate and there's no telling whether the 5% increased chance helped this fight or not.`}>
            ≈{formatPercentage(this.owner.getPercentageOfTotalHealingDone(this.healing * this.healingFactorOfOnePoint))} %
          </dfn>

    );
  }
}

export default SecondSunrise;
<commit_msg>Remove debug code<commit_after>import React from 'react';

import SPELLS from 'common/SPELLS';
import SpellIcon from 'common/SpellIcon';
import SpellLink from 'common/SpellLink';
import { formatPercentage, formatThousands } from 'common/format';

import Module from 'Parser/Core/Module';

/**
 * Second Sunrise does not cause any special events aside from additional Light of Dawn heals. These heals occur after a static amount of time, this looks to be about 700ms. Initial LoD heals always occur within 100ms, so this is a save middle where the SS delay is more likely to vary than the initial heal delay.
 */
const SECOND_SUNRISE_MINIMAL_DELAY = 300;
const SECOND_SUNRISE_PROC_CHANCE = 0.05;

/**
 * Second Sunrise (Artifact Trait)
 * Light of Dawn has a 5% chance to cast a second time for no additional mana cost.
 */
class SecondSunrise extends Module {
  rank = 0;
  healing = 0;

  get healingFactorOfOnePoint() {
    return SECOND_SUNRISE_PROC_CHANCE / (this.rank * SECOND_SUNRISE_PROC_CHANCE);
  }

  on_initialized() {
    this.rank = this.owner.selectedCombatant.traitsBySpellId[SPELLS.SECOND_SUNRISE.id];
    this.active = this.rank > 0;
  }

  _lastCast = null;
  on_byPlayer_cast(event) {
    if (event.ability.guid !== SPELLS.LIGHT_OF_DAWN_CAST.id) {
      return;
    }
    this._lastCast = event.timestamp;
  }
  on_byPlayer_heal(event) {
    if (event.ability.guid !== SPELLS.LIGHT_OF_DAWN_HEAL.id || this._lastCast === null) {
      return;
    }
    const timeSinceLastCast = event.timestamp - this._lastCast;
    if (timeSinceLastCast > SECOND_SUNRISE_MINIMAL_DELAY) {
      this.healing += (event.amount + (event.absorbed || 0));
    }
  }
  on_beacon_heal(beaconTransferEvent, healEvent) {
    if (healEvent.ability.guid !== SPELLS.LIGHT_OF_DAWN_HEAL.id) {
      return;
    }
    const timeSinceLastCast = healEvent.timestamp - this._lastCast;
    if (timeSinceLastCast > SECOND_SUNRISE_MINIMAL_DELAY) {
      this.healing += (beaconTransferEvent.amount + (beaconTransferEvent.absorbed || 0));
    }
  }

  subStatistic() {
    return (

          <SpellLink id={SPELLS.SECOND_SUNRISE.id}>
            <SpellIcon id={SPELLS.SECOND_SUNRISE.id} noLink /> Second Sunrise
          </SpellLink>

          <dfn data-tip={`For each Light of Dawn cast you had a ${formatPercentage(this.rank * SECOND_SUNRISE_PROC_CHANCE, 0)}% chance to proc Second Sunrise. One point giving 5% proc chance is ${formatPercentage(this.healingFactorOfOnePoint, 0)}% of this ${formatPercentage(this.rank * SECOND_SUNRISE_PROC_CHANCE, 0)}% chance, so the assumption is made that ${formatPercentage(this.healingFactorOfOnePoint, 0)}% of the total healing by Second Sunrise (which was ${formatThousands(this.healing)} healing) was due to one point. This gets us a simplified average but this may be completely off since Second Sunrise is a completely random proc with a low proc rate and there's no telling whether the 5% increased chance helped this fight or not.`}>
            ≈{formatPercentage(this.owner.getPercentageOfTotalHealingDone(this.healing * this.healingFactorOfOnePoint))} %
          </dfn>

    );
  }
}

export default SecondSunrise;
<|endoftext|>


<commit_before>var antiglobal = require('../');

antiglobal.log = false;

module.exports = {
	'call without global additions': function(test) {
		test.ok(antiglobal());

		test.done();
	},

	'call after a global addition': function(test) {
		global.AAA = 'aaa';

		test.ok(antiglobal('AAA'));

		test.done();
	},

	'call after two global additions': function(test) {
		global.BBB = 'bbb';
		global.CCC = 'ccc';

		test.ok(antiglobal('BBB', 'CCC'));

		test.done();
	},

	'antiglobal.throw=true works': function(test) {
		antiglobal.throw = true;

		global.DDD = 'ddd';
		global.EEE = 'eee';

		test.throws(function() {
			antiglobal('DDD');
		}, Error);

		antiglobal.throw = false;
		test.done();
	},

	'final call without global addition': function(test) {
		test.ok(antiglobal());

		test.done();
	}
};
<commit_msg>Polish code.<commit_after>var antiglobal = require('../');

antiglobal.log = false;

module.exports = {
	'call without global additions': function(test) {
		test.ok(antiglobal());
		test.done();
	},

	'call after a global addition': function(test) {
		global.AAA = 'aaa';

		test.ok(antiglobal('AAA'));
		test.done();
	},

	'call after two global additions': function(test) {
		global.BBB = 'bbb';
		global.CCC = 'ccc';

		test.ok(antiglobal('BBB', 'CCC'));
		test.done();
	},

	'TODO: should fail coño': function(test) {
		console.log(global.AAA);
		delete global.AAA;

		test.ok(antiglobal());
		test.done();
	},

	'antiglobal.throw=true works': function(test) {
		antiglobal.throw = true;

		global.DDD = 'ddd';
		global.EEE = 'eee';

		test.throws(function() {
			antiglobal('DDD');
		}, Error);

		antiglobal.throw = false;
		test.done();
	},

	'final call without global addition': function(test) {
		test.ok(antiglobal());
		test.done();
	}
};
<|endoftext|>


<commit_before>'use strict';

var fs = require('fs');

var lrSnippet = require('grunt-contrib-livereload/lib/utils').livereloadSnippet;

var mountFolder = function (connect, dir) {
  return connect.static(require('path').resolve(dir));
};

var fallbackToTest = function (connect) {
  return connect().use(function (req, res, next) {
    fs.exists(__dirname+req.url, function (exists) {
      if(exists) {
        fs.createReadStream(req.url).pipe(res);
      } else {
        fs.createReadStream(__dirname+'/test/e2e/test-index.html').pipe(res);
      }
    });
  });
}

var fallbackToIndex = function (connect, index, file) {
  return connect().use(function (req, res, next) {
    if(req.url === file) {
      return next();
    }
    res.end( fs.readFileSync(index) );
  });
};

module.exports = function (grunt) {
  // load all grunt tasks
  require('matchdep').filterDev('grunt-*').forEach(grunt.loadNpmTasks);

  // configurable paths
  var yeomanConfig = {
    app: 'app',
    dist: 'dist'
  };

  try {
    yeomanConfig.app = require('./bower.json').appPath || yeomanConfig.app;
  } catch (e) {}

  grunt.initConfig({
    yeoman: yeomanConfig,
    docular: {
      baseUrl: 'http://localhost:8000',
      showAngularDocs: false,
      showDocularDocs: false,
      docAPIOrder : ['doc', 'angular'],
      groups: [
        {
          groupTitle: 'CleverStack Seed',
          groupId: 'cleverstack',
          groupIcon: 'icon-book',
          sections: [
            {
              id: "api",
              title: "API",
              scripts: [
                "app/scripts/init.js",
                "app/scripts/routes.js",
                "app/scripts/services",
                "app/scripts/filters",
                "app/scripts/directives"
              ]
            }
          ]
        }
      ] //groups of documentation to parse
    },
    watch: {
      livereload: {
        files: [
          '<%= yeoman.app %>/components/bootstrap/{,*/}*.css',
          '<%= yeoman.app %>/styles/{,*/}*.css',
          '<%= yeoman.app %>/{,*/}*.html',
          '{.tmp,<%= yeoman.app %>}/styles/{,*/}*.css',
          '{.tmp,<%= yeoman.app %>}/views/{,*/}*.html',
          '{.tmp,<%= yeoman.app %>}/scripts/{,*/}*.js',
          '<%= yeoman.app %>/images/{,*/}*.{png,jpg,jpeg,gif,webp,svg}'
        ],
        tasks: ['livereload']
      },
      less: {
        files: [
          '<%= yeoman.app %>/components/bootstrap/less/{,*/}*.less',
          '<%= yeoman.app %>/styles/less/{,*/}*.less'
        ],
        tasks: ['less']
      }
    },
    connect: {
      options: {
        port: 9000,
        // Change this to '0.0.0.0' to access the server from outside.
        hostname: '0.0.0.0'
      },
      livereload: {
        options: {
          middleware: function (connect) {
            return [
              lrSnippet,
              mountFolder(connect, '.tmp'),
              mountFolder(connect, yeomanConfig.app),
              fallbackToIndex(connect, 'app/index.html', '/index.html')
            ];
          }
        }
      },
      test: {
        options: {
          port: 9090,
          base: __dirname,
          middleware: function (connect) {
            return [
              mountFolder(connect, '.'),
              fallbackToTest(connect)
            ]
          }
        }
      }
    },
    open: {
      server: {
        url: 'http://localhost:<%= connect.options.port %>'
      },
      test: {
        url: 'http://localhost:<%= connect.test.options.port %>/test/e2e/runner.html'
      },
      docs: {
        url: 'docs/index.html'
      }
    },
    clean: {
      dist: {
        files: [{
          dot: true,
          src: [
            '.tmp',
            '<%= yeoman.dist %>/*',
            '!<%= yeoman.dist %>/.git*'
          ]
        }]
      },
      server: '.tmp'
    },
    jshint: {
      options: {
        jshintrc: '.jshintrc'
      },
      all: [
        'Gruntfile.js',
        '<%= yeoman.app %>/scripts/{,*/}*.js'
      ]
    },
    concat: {
      dist: {
        files: {
          '<%= yeoman.dist %>/scripts/scripts.js': [
            '.tmp/scripts/{,*/}*.js',
            '<%= yeoman.app %>/scripts/{,*/}*.js'
          ]
        }
      }
    },
    useminPrepare: {
      html: '<%= yeoman.app %>/index.html',
      options: {
        dest: '<%= yeoman.dist %>'
      }
    },
    usemin: {
      html: ['<%= yeoman.dist %>/{,*/}*.html'],
      css: ['<%= yeoman.dist %>/styles/{,*/}*.css'],
      options: {
        dirs: ['<%= yeoman.dist %>']
      }
    },
    imagemin: {
      dist: {
        files: [{
          expand: true,
          cwd: '<%= yeoman.app %>/images',
          src: '**/*.{png,jpg,jpeg}',
          dest: '<%= yeoman.dist %>/images'
        }]
      }
    },
    less: {
      development: {
        options: {
          paths: ['app/styles']
        },
        files: [
          { '<%= yeoman.app %>/styles/application.css': '<%= yeoman.app %>/styles/less/application.less' },
          { '<%= yeoman.app %>/styles/bootstrap.css': '<%= yeoman.app %>/components/bootstrap/less/bootstrap.less' }
        ]
      },
      production: {
        options: {
          paths: ['app/styles']
        },
        files: [
          { '<%= yeoman.app %>/styles/application.css': '<%= yeoman.app %>/styles/less/application.less' },
          { '<%= yeoman.app %>/styles/bootstrap.css': '<%= yeoman.app %>/components/bootstrap/less/bootstrap.less' }
        ]
      },
    },
    cssmin: {
      dist: {
        files: {
          '<%= yeoman.dist %>/styles/screen.css': [
            '.tmp/styles/{,*/}*.css',
            '<%= yeoman.app %>/styles/{,*/}*.css'
          ]
        }
      }
    },
    htmlmin: {
      dist: {
        options: {
          /*removeCommentsFromCDATA: true,
          // https://github.com/yeoman/grunt-usemin/issues/44
          //collapseWhitespace: true,
          collapseBooleanAttributes: true,
          removeAttributeQuotes: true,
          removeRedundantAttributes: true,
          useShortDoctype: true,
          removeEmptyAttributes: true,
          removeOptionalTags: true*/
        },
        files: [{
          expand: true,
          cwd: '<%= yeoman.app %>',
          src: ['*.html', 'views/**/*.html', 'views/**/partials/*.html'],
          dest: '<%= yeoman.dist %>'
        }]
      }
    },
    ngmin: {
      dist: {
        files: [{
          expand: true,
          cwd: '<%= yeoman.dist %>/scripts',
          src: '*.js',
          dest: '<%= yeoman.dist %>/scripts'
        }]
      }
    },
    uglify: {
      dist: {
        files: {
          '<%= yeoman.dist %>/scripts/scripts.js': [
            '<%= yeoman.dist %>/scripts/scripts.js'
          ]
        }
      }
    },
    requirejs: {
      compile: {
        options: {
          baseUrl: 'app/scripts',
          mainConfigFile: "app/scripts/main.js",
          out: "<%=yeoman.dist %>/scripts/scripts.js",
          uglify: {
            beautify: false,
            'no-copyright': true,
            'no-seqs': true,
            'lift-vars': true
          }
        }
      }
    },
    rev: {
      dist: {
        files: {
          src: [
            '<%= yeoman.dist %>/scripts/{,*/}*.js',
            '<%= yeoman.dist %>/styles/{,*/}*.css',
            '<%= yeoman.dist %>/images/{,*/}*.{png,jpg,jpeg,gif,webp,svg}',
            '<%= yeoman.dist %>/styles/fonts/*'
          ]
        }
      }
    },
    copy: {
      dist: {
        files: [{
          expand: true,
          dot: true,
          cwd: '<%= yeoman.app %>',
          dest: '<%= yeoman.dist %>',
          src: [
            '*.{ico,txt}',
            '.htaccess',
            'components/**/*.{js,css,eot,svg,ttf,woff,png,jpg,jpeg,gif,webp}',
            'images/{,*/}*.{gif,webp, png}',
            'images/**/*.{gif,webp, png}',
            'styles/fonts/**/*',
            'fonts/**/*',
            'home/**/*'
          ]
        }]
      }
    }
  });

  grunt.renameTask('regarde', 'watch');

  grunt.registerTask('docs', ['docular']);

  grunt.registerTask('server', [
    'clean:server',
    'livereload-start',
    'connect:livereload',
    'connect:test',
    'watch'
  ]);

  grunt.registerTask('build', [
    'clean:dist',
    // 'jshint',
    'useminPrepare',
    'imagemin',
    'less',
    'cssmin',
    'htmlmin',
    // 'concat',
    'copy',
    'ngmin',
    'requirejs',
    // 'uglify',
    'rev',
    'usemin',
    'docs'
  ]);

  grunt.registerTask('default', ['build']);
};
<commit_msg>fix(server): Fix EISDIR error when hitting the test server<commit_after>'use strict';

var fs = require('fs');

var lrSnippet = require('grunt-contrib-livereload/lib/utils').livereloadSnippet;

var mountFolder = function (connect, dir) {
  return connect.static(require('path').resolve(dir));
};

var fallbackToTest = function (connect) {
  return connect().use(function (req, res, next) {
    fs.exists(__dirname+req.url, function (exists) {
      if(exists && req.url !== '/') {
        fs.createReadStream(req.url).pipe(res);
      } else {
        fs.createReadStream(__dirname+'/test/e2e/test-index.html').pipe(res);
      }
    });
  });
}

var fallbackToIndex = function (connect, index, file) {
  return connect().use(function (req, res, next) {
    if(req.url === file) {
      return next();
    }
    res.end( fs.readFileSync(index) );
  });
};

module.exports = function (grunt) {
  // load all grunt tasks
  require('matchdep').filterDev('grunt-*').forEach(grunt.loadNpmTasks);

  // configurable paths
  var yeomanConfig = {
    app: 'app',
    dist: 'dist'
  };

  try {
    yeomanConfig.app = require('./bower.json').appPath || yeomanConfig.app;
  } catch (e) {}

  grunt.initConfig({
    yeoman: yeomanConfig,
    docular: {
      baseUrl: 'http://localhost:8000',
      showAngularDocs: false,
      showDocularDocs: false,
      docAPIOrder : ['doc', 'angular'],
      groups: [
        {
          groupTitle: 'CleverStack Seed',
          groupId: 'cleverstack',
          groupIcon: 'icon-book',
          sections: [
            {
              id: "api",
              title: "API",
              scripts: [
                "app/scripts/init.js",
                "app/scripts/routes.js",
                "app/scripts/services",
                "app/scripts/filters",
                "app/scripts/directives"
              ]
            }
          ]
        }
      ] //groups of documentation to parse
    },
    watch: {
      livereload: {
        files: [
          '<%= yeoman.app %>/components/bootstrap/{,*/}*.css',
          '<%= yeoman.app %>/styles/{,*/}*.css',
          '<%= yeoman.app %>/{,*/}*.html',
          '{.tmp,<%= yeoman.app %>}/styles/{,*/}*.css',
          '{.tmp,<%= yeoman.app %>}/views/{,*/}*.html',
          '{.tmp,<%= yeoman.app %>}/scripts/{,*/}*.js',
          '<%= yeoman.app %>/images/{,*/}*.{png,jpg,jpeg,gif,webp,svg}'
        ],
        tasks: ['livereload']
      },
      less: {
        files: [
          '<%= yeoman.app %>/components/bootstrap/less/{,*/}*.less',
          '<%= yeoman.app %>/styles/less/{,*/}*.less'
        ],
        tasks: ['less']
      }
    },
    connect: {
      options: {
        port: 9000,
        // Change this to '0.0.0.0' to access the server from outside.
        hostname: '0.0.0.0'
      },
      livereload: {
        options: {
          middleware: function (connect) {
            return [
              lrSnippet,
              mountFolder(connect, '.tmp'),
              mountFolder(connect, yeomanConfig.app),
              fallbackToIndex(connect, 'app/index.html', '/index.html')
            ];
          }
        }
      },
      test: {
        options: {
          port: 9090,
          base: __dirname,
          middleware: function (connect) {
            return [
              mountFolder(connect, '.'),
              fallbackToTest(connect)
            ]
          }
        }
      }
    },
    open: {
      server: {
        url: 'http://localhost:<%= connect.options.port %>'
      },
      test: {
        url: 'http://localhost:<%= connect.test.options.port %>/test/e2e/runner.html'
      },
      docs: {
        url: 'docs/index.html'
      }
    },
    clean: {
      dist: {
        files: [{
          dot: true,
          src: [
            '.tmp',
            '<%= yeoman.dist %>/*',
            '!<%= yeoman.dist %>/.git*'
          ]
        }]
      },
      server: '.tmp'
    },
    jshint: {
      options: {
        jshintrc: '.jshintrc'
      },
      all: [
        'Gruntfile.js',
        '<%= yeoman.app %>/scripts/{,*/}*.js'
      ]
    },
    concat: {
      dist: {
        files: {
          '<%= yeoman.dist %>/scripts/scripts.js': [
            '.tmp/scripts/{,*/}*.js',
            '<%= yeoman.app %>/scripts/{,*/}*.js'
          ]
        }
      }
    },
    useminPrepare: {
      html: '<%= yeoman.app %>/index.html',
      options: {
        dest: '<%= yeoman.dist %>'
      }
    },
    usemin: {
      html: ['<%= yeoman.dist %>/{,*/}*.html'],
      css: ['<%= yeoman.dist %>/styles/{,*/}*.css'],
      options: {
        dirs: ['<%= yeoman.dist %>']
      }
    },
    imagemin: {
      dist: {
        files: [{
          expand: true,
          cwd: '<%= yeoman.app %>/images',
          src: '**/*.{png,jpg,jpeg}',
          dest: '<%= yeoman.dist %>/images'
        }]
      }
    },
    less: {
      development: {
        options: {
          paths: ['app/styles']
        },
        files: [
          { '<%= yeoman.app %>/styles/application.css': '<%= yeoman.app %>/styles/less/application.less' },
          { '<%= yeoman.app %>/styles/bootstrap.css': '<%= yeoman.app %>/components/bootstrap/less/bootstrap.less' }
        ]
      },
      production: {
        options: {
          paths: ['app/styles']
        },
        files: [
          { '<%= yeoman.app %>/styles/application.css': '<%= yeoman.app %>/styles/less/application.less' },
          { '<%= yeoman.app %>/styles/bootstrap.css': '<%= yeoman.app %>/components/bootstrap/less/bootstrap.less' }
        ]
      },
    },
    cssmin: {
      dist: {
        files: {
          '<%= yeoman.dist %>/styles/screen.css': [
            '.tmp/styles/{,*/}*.css',
            '<%= yeoman.app %>/styles/{,*/}*.css'
          ]
        }
      }
    },
    htmlmin: {
      dist: {
        options: {
          /*removeCommentsFromCDATA: true,
          // https://github.com/yeoman/grunt-usemin/issues/44
          //collapseWhitespace: true,
          collapseBooleanAttributes: true,
          removeAttributeQuotes: true,
          removeRedundantAttributes: true,
          useShortDoctype: true,
          removeEmptyAttributes: true,
          removeOptionalTags: true*/
        },
        files: [{
          expand: true,
          cwd: '<%= yeoman.app %>',
          src: ['*.html', 'views/**/*.html', 'views/**/partials/*.html'],
          dest: '<%= yeoman.dist %>'
        }]
      }
    },
    ngmin: {
      dist: {
        files: [{
          expand: true,
          cwd: '<%= yeoman.dist %>/scripts',
          src: '*.js',
          dest: '<%= yeoman.dist %>/scripts'
        }]
      }
    },
    uglify: {
      dist: {
        files: {
          '<%= yeoman.dist %>/scripts/scripts.js': [
            '<%= yeoman.dist %>/scripts/scripts.js'
          ]
        }
      }
    },
    requirejs: {
      compile: {
        options: {
          baseUrl: 'app/scripts',
          mainConfigFile: "app/scripts/main.js",
          out: "<%=yeoman.dist %>/scripts/scripts.js",
          uglify: {
            beautify: false,
            'no-copyright': true,
            'no-seqs': true,
            'lift-vars': true
          }
        }
      }
    },
    rev: {
      dist: {
        files: {
          src: [
            '<%= yeoman.dist %>/scripts/{,*/}*.js',
            '<%= yeoman.dist %>/styles/{,*/}*.css',
            '<%= yeoman.dist %>/images/{,*/}*.{png,jpg,jpeg,gif,webp,svg}',
            '<%= yeoman.dist %>/styles/fonts/*'
          ]
        }
      }
    },
    copy: {
      dist: {
        files: [{
          expand: true,
          dot: true,
          cwd: '<%= yeoman.app %>',
          dest: '<%= yeoman.dist %>',
          src: [
            '*.{ico,txt}',
            '.htaccess',
            'components/**/*.{js,css,eot,svg,ttf,woff,png,jpg,jpeg,gif,webp}',
            'images/{,*/}*.{gif,webp, png}',
            'images/**/*.{gif,webp, png}',
            'styles/fonts/**/*',
            'fonts/**/*',
            'home/**/*'
          ]
        }]
      }
    }
  });

  grunt.renameTask('regarde', 'watch');

  grunt.registerTask('docs', ['docular']);

  grunt.registerTask('server', [
    'clean:server',
    'livereload-start',
    'connect:livereload',
    'connect:test',
    'watch'
  ]);

  grunt.registerTask('build', [
    'clean:dist',
    // 'jshint',
    'useminPrepare',
    'imagemin',
    'less',
    'cssmin',
    'htmlmin',
    // 'concat',
    'copy',
    'ngmin',
    'requirejs',
    // 'uglify',
    'rev',
    'usemin',
    'docs'
  ]);

  grunt.registerTask('default', ['build']);
};
<|endoftext|>