<commit_before>/*jslint indent: 2, nomen: true, maxlen: 120 */
/*global module, require, exports */

////////////////////////////////////////////////////////////////////////////////
/// @brief Foxx Request Context
///
/// @file
///
/// DISCLAIMER
///
/// Copyright 2013 triagens GmbH, Cologne, Germany
///
/// Licensed under the Apache License, Version 2.0 (the "License");
/// you may not use this file except in compliance with the License.
/// You may obtain a copy of the License at
///
///     http://www.apache.org/licenses/LICENSE-2.0
///
/// Unless required by applicable law or agreed to in writing, software
/// distributed under the License is distributed on an "AS IS" BASIS,
/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
/// See the License for the specific language governing permissions and
/// limitations under the License.
///
/// Copyright holder is triAGENS GmbH, Cologne, Germany
///
/// @author Lucas Dohmen
/// @author Copyright 2013, triAGENS GmbH, Cologne, Germany
////////////////////////////////////////////////////////////////////////////////

var RequestContext,
  RequestContextBuffer,
  SwaggerDocs,
  _ = require("underscore"),
  extend = _.extend,
  internal = require("org/arangodb/foxx/internals"),
  is = require("org/arangodb/is"),
  UnauthorizedError = require("org/arangodb/foxx/authentication").UnauthorizedError,
  createErrorBubbleWrap,
  createBodyParamBubbleWrap,
  addCheck;

createBodyParamBubbleWrap = function (handler, paramName, Proto) {
  'use strict';
  return function (req, res) {
    req.parameters[paramName] = new Proto(req.body());
    handler(req, res);
  };
};

createErrorBubbleWrap = function (handler, errorClass, code, reason, errorHandler) {
  'use strict';
  if (is.notExisty(errorHandler)) {
    errorHandler = function () {
      return { error: reason };
    };
  }

  return function (req, res) {
    try {
      handler(req, res);
    } catch (e) {
      if (e instanceof errorClass) {
        res.status(code);
        res.json(errorHandler(e));
      } else {
        throw e;
      }
    }
  };
};

addCheck = function (handler, check) {
  'use strict';
  return function (req, res) {
    check(req);
    handler(req, res);
  };
};

// Wraps the docs object of a route to add swagger compatible documentation
SwaggerDocs = function (docs, models) {
  'use strict';
  this.docs = docs;
  this.models = models;
};

extend(SwaggerDocs.prototype, {
  addNickname: function (httpMethod, match) {
    'use strict';
    this.docs.nickname = internal.constructNickname(httpMethod, match);
  },

  addPathParam: function (paramName, description, dataType) {
    'use strict';
    this.docs.parameters.push(internal.constructPathParamDoc(paramName, description, dataType));
  },

  addQueryParam: function (paramName, description, dataType, required, allowMultiple) {
    'use strict';
    this.docs.parameters.push(internal.constructQueryParamDoc(
      paramName,
      description,
      dataType,
      required,
      allowMultiple
    ));
  },

  addBodyParam: function (paramName, description, jsonSchema) {
    'use strict';
    this.models[jsonSchema.id] = jsonSchema;

    this.docs.parameters.push({
      name: paramName,
      paramType: "body",
      description: description,
      dataType: jsonSchema.id
    });
  },

  addSummary: function (summary) {
    'use strict';
    this.docs.summary = summary;
  },

  addNotes: function (notes) {
    'use strict';
    this.docs.notes = notes;
  }
});

////////////////////////////////////////////////////////////////////////////////
/// @fn JSF_foxx_RequestContext_initializer
/// @brief Context of a Request Definition
///
/// Used for documenting and constraining the routes.
////////////////////////////////////////////////////////////////////////////////

RequestContext = function (executionBuffer, models, route) {
  'use strict';
  this.route = route;
  this.typeToRegex = {
    "int": "/[0-9]+/",
    "string": "/.+/"
  };

  this.docs = new SwaggerDocs(this.route.docs, models);
  this.docs.addNickname(route.docs.httpMethod, route.url.match);

  executionBuffer.applyEachFunction(this);
};

extend(RequestContext.prototype, {

////////////////////////////////////////////////////////////////////////////////
/// @fn JSF_foxx_RequestContext_pathParam
/// @brief Describe a path parameter
///
/// If you defined a route "/foxx/:id", you can constrain which format a path
/// parameter (`/foxx/12`) can have by giving it a type.  We currently support
/// the following types:
///
/// * int
/// * string
///
/// You can also provide a description of this parameter.
///
/// @EXAMPLES
///
/// @code
///     app.get("/foxx/:id", function {
///       // Do something
///     }).pathParam("id", {
///       description: "Id of the Foxx",
///       type: "int"
///     });
/// @endcode
////////////////////////////////////////////////////////////////////////////////

  pathParam: function (paramName, attributes) {
    'use strict';
    var url = this.route.url,
      docs = this.route.docs,
      constraint = url.constraint || {};

    constraint[paramName] = this.typeToRegex[attributes.type];
    this.route.url = internal.constructUrlObject(url.match, constraint, url.methods[0]);
    this.docs.addPathParam(paramName, attributes.description, attributes.type);
    return this;
  },

////////////////////////////////////////////////////////////////////////////////
/// @fn JSF_foxx_RequestContext_queryParam
/// @brief Describe a Query Parameter
///
/// @FUN{FoxxController::queryParam(@FA{id}, @FA{options})}
///
/// Describe a query parameter:
///
/// If you defined a route "/foxx", you can constrain which format a query
/// parameter (`/foxx?a=12`) can have by giving it a type.  We currently support
/// the following types:
///
/// * int
/// * string
///
/// You can also provide a description of this parameter, if it is required and
/// if you can provide the parameter multiple times.
///
/// @EXAMPLES
///
/// @code
///     app.get("/foxx", function {
///       // Do something
///     }).queryParam("id", {
///       description: "Id of the Foxx",
///       type: "int",
///       required: true,
///       allowMultiple: false
///     });
/// @endcode
////////////////////////////////////////////////////////////////////////////////

  queryParam: function (paramName, attributes) {
    'use strict';
    this.docs.addQueryParam(
      paramName,
      attributes.description,
      attributes.type,
      attributes.required,
      attributes.allowMultiple
    );
    return this;
  },

////////////////////////////////////////////////////////////////////////////////
/// @fn JSF_foxx_RequestContext_bodyParam
/// @brief Define a parameter for the body of the request
///
/// @FUN{FoxxController::bodyParam(@FA{paramName}, @FA{description}, @FA{Model})}
///
/// Expect the body of the request to be a JSON with the attributes you annotated
/// in your model. It will appear alongside the provided description in your
/// Documentation.
/// This will initialize a `Model` with the data and provide it to you via the
/// params as `paramName`.
/// For information about how to annotate your models, see the Model section.
////////////////////////////////////////////////////////////////////////////////

  bodyParam: function (paramName, description, Proto) {
    'use strict';
    var handler = this.route.action.callback;

    this.docs.addBodyParam(paramName, description, Proto.toJSONSchema(paramName));
    this.route.action.callback = createBodyParamBubbleWrap(handler, paramName, Proto);

    return this;
  },

////////////////////////////////////////////////////////////////////////////////
/// @fn JSF_foxx_RequestContext_summary
/// @brief Set the summary for this route in the documentation
///
/// @FUN{FoxxController::summary(@FA{description})}
///
/// Set the summary for this route in the documentation. Can't be longer than 60.
/// characters
////////////////////////////////////////////////////////////////////////////////

  summary: function (summary) {
    'use strict';
    if (summary.length > 60) {
      throw new Error("Summary can't be longer than 60 characters");
    }
    this.docs.addSummary(summary);
    return this;
  },

////////////////////////////////////////////////////////////////////////////////
/// @fn JSF_foxx_RequestContext_notes
/// @brief Set the notes for this route in the documentation
///
/// @FUN{FoxxController::notes(@FA{description})}
///
/// Set the notes for this route in the documentation
////////////////////////////////////////////////////////////////////////////////

  notes: function (notes) {
    'use strict';
    this.docs.addNotes(notes);
    return this;
  },

////////////////////////////////////////////////////////////////////////////////
/// @fn JSF_foxx_RequestContext_errorResponse
/// @brief Define an error response
///
/// @FUN{FoxxController::errorResponse(@FA{errorClass}, @FA{code}, @FA{description})}
///
/// Define a reaction to a thrown error for this route: If your handler throws an error
/// of the defined errorClass, it will be caught and the response will have the given
/// status code and a JSON with error set to your description as the body.
///
/// If you want more control over the returned JSON, you can give an optional fourth
/// parameter in form of a function. It gets the error as an argument, the return
/// value will transformed into JSON and then be used as the body.
/// The status code will be used as described above. The description will be used for
/// the documentation.
///
/// It also adds documentation for this error response to the generated documentation.
///
/// @EXAMPLES
///
/// @code
///     app.get("/foxx", function {
///       throw new FoxxyError();
///     }).errorResponse(FoxxyError, 303, "This went completely wrong. Sorry!");
///
///     app.get("/foxx", function {
///       throw new FoxxyError();
///     }).errorResponse(FoxxyError, 303, "This went completely wrong. Sorry!", function (e) {
///       return {
///         code: 123,
///         desc: e.description
///       };
///     });
/// @endcode
////////////////////////////////////////////////////////////////////////////////
  errorResponse: function (errorClass, code, reason, errorHandler) {
    'use strict';
    var handler = this.route.action.callback;
    this.route.action.callback = createErrorBubbleWrap(handler, errorClass, code, reason, errorHandler);
    this.route.docs.errorResponses.push(internal.constructErrorResponseDoc(code, reason));
    return this;
  },

////////////////////////////////////////////////////////////////////////////////
/// @fn JSF_foxx_RequestContext_onlyIf
/// @brief Only let the request get through if a condition holds
///
/// @FUN{FoxxController::onlyIf(@FA{check})}
///
/// Provide it with a function that throws an exception if the normal processing should
/// not be executed. Provide an `errorResponse` to define the behavior in this case.
/// This can be used for authentication or authorization for example.
///
/// @EXAMPLES
///
/// @code
///     app.get("/foxx", function {
///       // Do something
///     }).onlyIf(aFunction).errorResponse(ErrorClass, 303, "This went completely wrong. Sorry!");
/// @endcode
////////////////////////////////////////////////////////////////////////////////
  onlyIf: function (check, ErrorClass) {
    'use strict';
    var handler = this.route.action.callback;
    this.route.action.callback = addCheck(handler, check);
    return this;
  },

////////////////////////////////////////////////////////////////////////////////
/// @fn JSF_foxx_RequestContext_onlyIfAuthenticated
/// @brief Only let the request get through if the user is logged in
///
/// @FUN{FoxxController::onlyIf(@FA{code}, @FA{reason})}
///
/// Please activate authentification for this app if you want to use this function.
/// If the user is logged in, it will do nothing. Otherwise it will respond with
/// the status code and the reason you provided (the route handler won't be called).
/// This will also add the according documentation for this route.
///
/// @EXAMPLES
///
/// @code
///     app.get("/foxx", function {
///       // Do something
///     }).onlyIfAuthenticated(401, "You need to be authenticated");
/// @endcode
////////////////////////////////////////////////////////////////////////////////
  onlyIfAuthenticated: function (code, reason) {
    'use strict';
    var handler = this.route.action.callback,
      check;

    check = function (req) {
      if (!(req.user && req.currentSession)) {
        throw new UnauthorizedError();
      }
    };

    if (is.notExisty(code)) {
      code = 401;
    }
    if (is.notExisty(reason)) {
      reason = "Not authorized";
    }

    this.onlyIf(check);
    this.errorResponse(UnauthorizedError, code, reason);

    return this;
  }
});

RequestContextBuffer = function () {
  'use strict';
  this.applyChain = [];
};

extend(RequestContextBuffer.prototype, {
  applyEachFunction: function (target) {
    'use strict';
    _.each(this.applyChain, function (x) {
      target[x.functionName].apply(target, x.argumentList);
    });
  }
});

_.each([
////////////////////////////////////////////////////////////////////////////////
/// @fn JSF_foxx_RequestContextBuffer_errorResponse
/// @brief Defines a controller-wide error response
///
/// @FUN{RequestContextBuffer::errorResponse(@FA{errorClass}, @FA{code}, @FA{description})}
///
/// Defines an `errorResponse` for all routes of this controller. For details on
/// `errorResponse` see the according method on routes.
///
/// @EXAMPLES
///
/// @code
///     app.allroutes.errorResponse(FoxxyError, 303, "This went completely wrong. Sorry!");
///
///     app.get("/foxx", function {
///       // Do something
///     });
/// @endcode
////////////////////////////////////////////////////////////////////////////////
  "errorResponse",

////////////////////////////////////////////////////////////////////////////////
/// @fn JSF_foxx_RequestContextBuffer_onlyIf
/// @brief Defines a controller-wide onlyIf
///
/// @FUN{RequestContextBuffer::onlyIf(@FA{code}, @FA{reason})}
///
/// Defines an `onlyIf` for all routes of this controller. For details on
/// `onlyIf` see the according method on routes.
///
/// @EXAMPLES
///
/// @code
///     app.allroutes.onlyIf(myPersonalCheck);
///
///     app.get("/foxx", function {
///       // Do something
///     });
/// @endcode
////////////////////////////////////////////////////////////////////////////////
  "onlyIf",

////////////////////////////////////////////////////////////////////////////////
/// @fn JSF_foxx_RequestContextBuffer_onlyIfAuthenticated
/// @brief Defines a controller-wide onlyIfAuthenticated
///
/// @FUN{RequestContextBuffer::errorResponse(@FA{errorClass}, @FA{code}, @FA{description})}
///
/// Defines an `onlyIfAuthenticated` for all routes of this controller. For details on
/// `onlyIfAuthenticated` see the according method on routes.
///
/// @EXAMPLES
///
/// @code
///     app.allroutes.onlyIfAuthenticated(401, "You need to be authenticated");
///
///     app.get("/foxx", function {
///       // Do something
///     });
/// @endcode
////////////////////////////////////////////////////////////////////////////////
  "onlyIfAuthenticated"
], function (functionName) {
  'use strict';
  extend(RequestContextBuffer.prototype[functionName] = function () {
    this.applyChain.push({
      functionName: functionName,
      argumentList: arguments
    });
  });
});

exports.RequestContext = RequestContext;
exports.RequestContextBuffer = RequestContextBuffer;

// -----------------------------------------------------------------------------
// --SECTION--                                                       END-OF-FILE
// -----------------------------------------------------------------------------

/// Local Variables:
/// mode: outline-minor
/// outline-regexp: "/// @brief\\|/// @addtogroup\\|/// @page\\|// --SECTION--\\|/// @\\}\\|/\\*jslint"
/// End:
<commit_msg>issue #595<commit_after>/*jslint indent: 2, nomen: true, maxlen: 120 */
/*global module, require, exports */

////////////////////////////////////////////////////////////////////////////////
/// @brief Foxx Request Context
///
/// @file
///
/// DISCLAIMER
///
/// Copyright 2013 triagens GmbH, Cologne, Germany
///
/// Licensed under the Apache License, Version 2.0 (the "License");
/// you may not use this file except in compliance with the License.
/// You may obtain a copy of the License at
///
///     http://www.apache.org/licenses/LICENSE-2.0
///
/// Unless required by applicable law or agreed to in writing, software
/// distributed under the License is distributed on an "AS IS" BASIS,
/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
/// See the License for the specific language governing permissions and
/// limitations under the License.
///
/// Copyright holder is triAGENS GmbH, Cologne, Germany
///
/// @author Lucas Dohmen
/// @author Copyright 2013, triAGENS GmbH, Cologne, Germany
////////////////////////////////////////////////////////////////////////////////

var RequestContext,
  RequestContextBuffer,
  SwaggerDocs,
  _ = require("underscore"),
  extend = _.extend,
  internal = require("org/arangodb/foxx/internals"),
  is = require("org/arangodb/is"),
  UnauthorizedError = require("org/arangodb/foxx/authentication").UnauthorizedError,
  createErrorBubbleWrap,
  createBodyParamBubbleWrap,
  addCheck;

createBodyParamBubbleWrap = function (handler, paramName, Proto) {
  'use strict';
  return function (req, res) {
    req.parameters[paramName] = new Proto(req.body());
    handler(req, res);
  };
};

createErrorBubbleWrap = function (handler, errorClass, code, reason, errorHandler) {
  'use strict';
  if (is.notExisty(errorHandler)) {
    errorHandler = function () {
      return { error: reason };
    };
  }

  return function (req, res) {
    try {
      handler(req, res);
    } catch (e) {
      if (e instanceof errorClass) {
        res.status(code);
        res.json(errorHandler(e));
      } else {
        throw e;
      }
    }
  };
};

addCheck = function (handler, check) {
  'use strict';
  return function (req, res) {
    check(req);
    handler(req, res);
  };
};

// Wraps the docs object of a route to add swagger compatible documentation
SwaggerDocs = function (docs, models) {
  'use strict';
  this.docs = docs;
  this.models = models;
};

extend(SwaggerDocs.prototype, {
  addNickname: function (httpMethod, match) {
    'use strict';
    this.docs.nickname = internal.constructNickname(httpMethod, match);
  },

  addPathParam: function (paramName, description, dataType) {
    'use strict';
    this.docs.parameters.push(internal.constructPathParamDoc(paramName, description, dataType));
  },

  addQueryParam: function (paramName, description, dataType, required, allowMultiple) {
    'use strict';
    this.docs.parameters.push(internal.constructQueryParamDoc(
      paramName,
      description,
      dataType,
      required,
      allowMultiple
    ));
  },

  addBodyParam: function (paramName, description, jsonSchema) {
    'use strict';
    this.models[jsonSchema.id] = jsonSchema;

    this.docs.parameters.push({
      name: paramName,
      paramType: "body",
      description: description,
      dataType: jsonSchema.id
    });
  },

  addSummary: function (summary) {
    'use strict';
    this.docs.summary = summary;
  },

  addNotes: function (notes) {
    'use strict';
    this.docs.notes = notes;
  }
});

////////////////////////////////////////////////////////////////////////////////
/// @fn JSF_foxx_RequestContext_initializer
/// @brief Context of a Request Definition
///
/// Used for documenting and constraining the routes.
////////////////////////////////////////////////////////////////////////////////

RequestContext = function (executionBuffer, models, route) {
  'use strict';
  this.route = route;
  this.typeToRegex = {
    "int": "/[0-9]+/",
    "string": "/.+/"
  };

  this.docs = new SwaggerDocs(this.route.docs, models);
  this.docs.addNickname(route.docs.httpMethod, route.url.match);

  executionBuffer.applyEachFunction(this);
};

extend(RequestContext.prototype, {

////////////////////////////////////////////////////////////////////////////////
/// @fn JSF_foxx_RequestContext_pathParam
/// @brief Describe a path parameter
///
/// If you defined a route "/foxx/:id", you can constrain which format a path
/// parameter (`/foxx/12`) can have by giving it a type.  We currently support
/// the following types:
///
/// * int
/// * string
///
/// You can also provide a description of this parameter.
///
/// @EXAMPLES
///
/// @code
///     app.get("/foxx/:id", function {
///       // Do something
///     }).pathParam("id", {
///       description: "Id of the Foxx",
///       type: "int"
///     });
/// @endcode
////////////////////////////////////////////////////////////////////////////////

  pathParam: function (paramName, attributes) {
    'use strict';
    var url = this.route.url,
      docs = this.route.docs,
      constraint = url.constraint || {};

    constraint[paramName] = this.typeToRegex[attributes.type];
    this.route.url = internal.constructUrlObject(url.match, constraint, url.methods[0]);
    this.docs.addPathParam(paramName, attributes.description, attributes.type);
    return this;
  },

////////////////////////////////////////////////////////////////////////////////
/// @fn JSF_foxx_RequestContext_queryParam
/// @brief Describe a Query Parameter
///
/// @FUN{FoxxController::queryParam(@FA{id}, @FA{options})}
///
/// Describe a query parameter:
///
/// If you defined a route "/foxx", you can constrain which format a query
/// parameter (`/foxx?a=12`) can have by giving it a type.  We currently support
/// the following types:
///
/// * int
/// * string
///
/// You can also provide a description of this parameter, if it is required and
/// if you can provide the parameter multiple times.
///
/// @EXAMPLES
///
/// @code
///     app.get("/foxx", function {
///       // Do something
///     }).queryParam("id", {
///       description: "Id of the Foxx",
///       type: "int",
///       required: true,
///       allowMultiple: false
///     });
/// @endcode
////////////////////////////////////////////////////////////////////////////////

  queryParam: function (paramName, attributes) {
    'use strict';
    this.docs.addQueryParam(
      paramName,
      attributes.description,
      attributes.type,
      attributes.required,
      attributes.allowMultiple
    );
    return this;
  },

////////////////////////////////////////////////////////////////////////////////
/// @fn JSF_foxx_RequestContext_bodyParam
/// @brief Define a parameter for the body of the request
///
/// @FUN{FoxxController::bodyParam(@FA{paramName}, @FA{description}, @FA{Model})}
///
/// Expect the body of the request to be a JSON with the attributes you annotated
/// in your model. It will appear alongside the provided description in your
/// Documentation.
/// This will initialize a `Model` with the data and provide it to you via the
/// params as `paramName`.
/// For information about how to annotate your models, see the Model section.
////////////////////////////////////////////////////////////////////////////////

  bodyParam: function (paramName, description, Proto) {
    'use strict';
    var handler = this.route.action.callback;

    this.docs.addBodyParam(paramName, description, Proto.toJSONSchema(paramName));
    this.route.action.callback = createBodyParamBubbleWrap(handler, paramName, Proto);

    return this;
  },

////////////////////////////////////////////////////////////////////////////////
/// @fn JSF_foxx_RequestContext_summary
/// @brief Set the summary for this route in the documentation
///
/// @FUN{FoxxController::summary(@FA{description})}
///
/// Set the summary for this route in the documentation. Can't be longer than 60.
/// characters
////////////////////////////////////////////////////////////////////////////////

  summary: function (summary) {
    'use strict';
    if (summary.length > 60) {
      throw new Error("Summary can't be longer than 60 characters");
    }
    this.docs.addSummary(summary);
    return this;
  },

////////////////////////////////////////////////////////////////////////////////
/// @fn JSF_foxx_RequestContext_notes
/// @brief Set the notes for this route in the documentation
///
/// @FUN{FoxxController::notes(@FA{description})}
///
/// Set the notes for this route in the documentation
////////////////////////////////////////////////////////////////////////////////

  notes: function (notes) {
    'use strict';
    this.docs.addNotes(notes);
    return this;
  },

////////////////////////////////////////////////////////////////////////////////
/// @fn JSF_foxx_RequestContext_errorResponse
/// @brief Define an error response
///
/// @FUN{FoxxController::errorResponse(@FA{errorClass}, @FA{code}, @FA{description})}
///
/// Define a reaction to a thrown error for this route: If your handler throws an error
/// of the defined errorClass, it will be caught and the response will have the given
/// status code and a JSON with error set to your description as the body.
///
/// If you want more control over the returned JSON, you can give an optional fourth
/// parameter in form of a function. It gets the error as an argument, the return
/// value will transformed into JSON and then be used as the body.
/// The status code will be used as described above. The description will be used for
/// the documentation.
///
/// It also adds documentation for this error response to the generated documentation.
///
/// @EXAMPLES
///
/// @code
///     app.get("/foxx", function {
///       throw new FoxxyError();
///     }).errorResponse(FoxxyError, 303, "This went completely wrong. Sorry!");
///
///     app.get("/foxx", function {
///       throw new FoxxyError();
///     }).errorResponse(FoxxyError, 303, "This went completely wrong. Sorry!", function (e) {
///       return {
///         code: 123,
///         desc: e.message
///       };
///     });
/// @endcode
////////////////////////////////////////////////////////////////////////////////
  errorResponse: function (errorClass, code, reason, errorHandler) {
    'use strict';
    var handler = this.route.action.callback;
    this.route.action.callback = createErrorBubbleWrap(handler, errorClass, code, reason, errorHandler);
    this.route.docs.errorResponses.push(internal.constructErrorResponseDoc(code, reason));
    return this;
  },

////////////////////////////////////////////////////////////////////////////////
/// @fn JSF_foxx_RequestContext_onlyIf
/// @brief Only let the request get through if a condition holds
///
/// @FUN{FoxxController::onlyIf(@FA{check})}
///
/// Provide it with a function that throws an exception if the normal processing should
/// not be executed. Provide an `errorResponse` to define the behavior in this case.
/// This can be used for authentication or authorization for example.
///
/// @EXAMPLES
///
/// @code
///     app.get("/foxx", function {
///       // Do something
///     }).onlyIf(aFunction).errorResponse(ErrorClass, 303, "This went completely wrong. Sorry!");
/// @endcode
////////////////////////////////////////////////////////////////////////////////
  onlyIf: function (check, ErrorClass) {
    'use strict';
    var handler = this.route.action.callback;
    this.route.action.callback = addCheck(handler, check);
    return this;
  },

////////////////////////////////////////////////////////////////////////////////
/// @fn JSF_foxx_RequestContext_onlyIfAuthenticated
/// @brief Only let the request get through if the user is logged in
///
/// @FUN{FoxxController::onlyIf(@FA{code}, @FA{reason})}
///
/// Please activate authentification for this app if you want to use this function.
/// If the user is logged in, it will do nothing. Otherwise it will respond with
/// the status code and the reason you provided (the route handler won't be called).
/// This will also add the according documentation for this route.
///
/// @EXAMPLES
///
/// @code
///     app.get("/foxx", function {
///       // Do something
///     }).onlyIfAuthenticated(401, "You need to be authenticated");
/// @endcode
////////////////////////////////////////////////////////////////////////////////
  onlyIfAuthenticated: function (code, reason) {
    'use strict';
    var handler = this.route.action.callback,
      check;

    check = function (req) {
      if (!(req.user && req.currentSession)) {
        throw new UnauthorizedError();
      }
    };

    if (is.notExisty(code)) {
      code = 401;
    }
    if (is.notExisty(reason)) {
      reason = "Not authorized";
    }

    this.onlyIf(check);
    this.errorResponse(UnauthorizedError, code, reason);

    return this;
  }
});

RequestContextBuffer = function () {
  'use strict';
  this.applyChain = [];
};

extend(RequestContextBuffer.prototype, {
  applyEachFunction: function (target) {
    'use strict';
    _.each(this.applyChain, function (x) {
      target[x.functionName].apply(target, x.argumentList);
    });
  }
});

_.each([
////////////////////////////////////////////////////////////////////////////////
/// @fn JSF_foxx_RequestContextBuffer_errorResponse
/// @brief Defines a controller-wide error response
///
/// @FUN{RequestContextBuffer::errorResponse(@FA{errorClass}, @FA{code}, @FA{description})}
///
/// Defines an `errorResponse` for all routes of this controller. For details on
/// `errorResponse` see the according method on routes.
///
/// @EXAMPLES
///
/// @code
///     app.allroutes.errorResponse(FoxxyError, 303, "This went completely wrong. Sorry!");
///
///     app.get("/foxx", function {
///       // Do something
///     });
/// @endcode
////////////////////////////////////////////////////////////////////////////////
  "errorResponse",

////////////////////////////////////////////////////////////////////////////////
/// @fn JSF_foxx_RequestContextBuffer_onlyIf
/// @brief Defines a controller-wide onlyIf
///
/// @FUN{RequestContextBuffer::onlyIf(@FA{code}, @FA{reason})}
///
/// Defines an `onlyIf` for all routes of this controller. For details on
/// `onlyIf` see the according method on routes.
///
/// @EXAMPLES
///
/// @code
///     app.allroutes.onlyIf(myPersonalCheck);
///
///     app.get("/foxx", function {
///       // Do something
///     });
/// @endcode
////////////////////////////////////////////////////////////////////////////////
  "onlyIf",

////////////////////////////////////////////////////////////////////////////////
/// @fn JSF_foxx_RequestContextBuffer_onlyIfAuthenticated
/// @brief Defines a controller-wide onlyIfAuthenticated
///
/// @FUN{RequestContextBuffer::errorResponse(@FA{errorClass}, @FA{code}, @FA{description})}
///
/// Defines an `onlyIfAuthenticated` for all routes of this controller. For details on
/// `onlyIfAuthenticated` see the according method on routes.
///
/// @EXAMPLES
///
/// @code
///     app.allroutes.onlyIfAuthenticated(401, "You need to be authenticated");
///
///     app.get("/foxx", function {
///       // Do something
///     });
/// @endcode
////////////////////////////////////////////////////////////////////////////////
  "onlyIfAuthenticated"
], function (functionName) {
  'use strict';
  extend(RequestContextBuffer.prototype[functionName] = function () {
    this.applyChain.push({
      functionName: functionName,
      argumentList: arguments
    });
  });
});

exports.RequestContext = RequestContext;
exports.RequestContextBuffer = RequestContextBuffer;

// -----------------------------------------------------------------------------
// --SECTION--                                                       END-OF-FILE
// -----------------------------------------------------------------------------

/// Local Variables:
/// mode: outline-minor
/// outline-regexp: "/// @brief\\|/// @addtogroup\\|/// @page\\|// --SECTION--\\|/// @\\}\\|/\\*jslint"
/// End:
<|endoftext|>


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

  var view;
  beforeEach(function() {
    var table = TestUtil.createTable('test');
    var infowindow = new cdb.geo.ui.InfowindowModel({ });
    var map = new cdb.admin.Map();
    map.layers.add(new cdb.geo.MapLayer());
    map.layers.add(new cdb.geo.MapLayer());

    view = new cdb.admin.MapTab({
      model: map,
      table: table,
      infowindow: infowindow,
      menu: new cdb.admin.RightMenu({})
    });
    // view.baseLayerChooser = {
    //   render: function() {
    //     return {
    //       "el": "dummy"
    //     }
    //   }
    // }
    // view._bindDataLayer(map.get('dataLayer').cid);

  });

  it("should render", function() {
    view.render();
    expect(view.$('#map').length).toEqual(1);
    expect(view.$('.base_maps').length).toEqual(1);
    expect(view.$('.share').length).toEqual(1);
  });

  xit("should refresh the layer when a change is made on the table", function() {
    var called = false;
    view.render();
    view.layerDataView.reload = function() {
      called = true;
    }

    table._data.trigger('change');

    expect(called).toBeTruthy();
  })

});
<commit_msg>tests for no-georef dialog 'memory'<commit_after>
describe("mapview", function() {

  var view;
  beforeEach(function() {
    var table = TestUtil.createTable('test');
    var infowindow = new cdb.geo.ui.InfowindowModel({ });
    var map = new cdb.admin.Map();
    map.layers.add(new cdb.geo.MapLayer());
    map.layers.add(new cdb.geo.MapLayer());

    view = new cdb.admin.MapTab({
      model: map,
      table: table,
      infowindow: infowindow,
      menu: new cdb.admin.RightMenu({})
    });
    // view.baseLayerChooser = {
    //   render: function() {
    //     return {
    //       "el": "dummy"
    //     }
    //   }
    // }
    // view._bindDataLayer(map.get('dataLayer').cid);

  });

  afterEach(function() {
    localStorage.clear();
  })

  it("should render", function() {
    view.render();
    expect(view.$('#map').length).toEqual(1);
    expect(view.$('.base_maps').length).toEqual(1);
    expect(view.$('.share').length).toEqual(1);
  });

  it("should trigger the georef warning when there's no geom in the table", function() {
    localStorage.clear();
    view.bindGeoRefCheck();
    view.render();
    view.options.table.trigger("dataLoaded");
    expect(view.noGeoRefDialog).toBeTruthy()
  })

  it("should trigger the georef warning when there's no data in the table", function() {
    view.options.table = TestUtil.createTable('test', [['the_geom', 'geometry']]);
    view.bindGeoRefCheck();
    view.render();
    view.options.table.trigger("dataLoaded");
    expect(view.noGeoRefDialog).toBeTruthy()
  })

  it("should NOT trigger the georef warning when there the_geom and data in the table", function() {
    view.options.table = TestUtil.createTable('test', [['the_geom', 'geometry']]);
    view.options.table._data.reset([{'the_geom':'{"type":"Point","coordinates":["1","1"]}'}])
    view.bindGeoRefCheck();
    view.render();
    view.options.table.trigger("dataLoaded");
    expect(view.noGeoRefDialog).toBeFalsy()
  })

  it("should trigger the georef warning only once when there's no geom in the table", function() {
    view.bindGeoRefCheck();
    view.render();
    view.options.table.trigger("dataLoaded");
    view.noGeoRefDialog = undefined;
    view.options.table.trigger("dataLoaded");
    expect(view.noGeoRefDialog).toBeFalsy()
  })

  it("should trigger the georef only once warning when there's no data in the table", function() {
    view.options.table = TestUtil.createTable('test', [['the_geom', 'geometry']]);
    view.bindGeoRefCheck();
    view.render();
    view.options.table.trigger("dataLoaded");
    view.noGeoRefDialog = undefined;
    view.options.table.trigger("dataLoaded");
    expect(view.noGeoRefDialog).toBeFalsy()
  })

  it("should trigger the georef again when formerly there were any data and now there are data but not georeferenced", function() {

    view.options.table = TestUtil.createTable('test', [['the_geom', 'geometry'], ['name','string']]);
    view.bindGeoRefCheck();
    view.render();
    view.options.table.trigger("dataLoaded");
    view.noGeoRefDialog = undefined;
    view.options.table._data.reset([{'name':'tralará'}])
    view.options.table.trigger("dataLoaded");
    expect(view.noGeoRefDialog).toBeTruthy()
  })

});
<|endoftext|>


<commit_before>/**
 * Copyright 2015-2022 OpenCB
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

import {LitElement, html} from "lit";
import "./clinical-analysis-browser.js";
import "../commons/tool-header.js";

export default class ClinicalAnalysisPortal extends LitElement {

    createRenderRoot() {
        return this;
    }

    static get properties() {
        return {
            opencgaSession: {
                type: Object
            },
            settings: {
                type: Object
            },
        };
    }

    renderToolbarButtons() {
        return html`

                <button class="btn btn-info active">
                    Case Explorer
                </button>
                <button class="btn btn-info">
                    Disease Panel Explorer
                </button>

                <button class="btn btn-default" style="margin-left:16px;">

                    New Case
                </button>

        `;
    }

    render() {
        if (!this.opencgaSession) {
            return html`

No public projects available to browse. Please login to continue

            `;
        }

        return html`
            <tool-header
                .title="${this.settings?.title || "Case Portal"}"
                .icon="${this.settings?.icon || ""}"
                .rhs="${this.renderToolbarButtons()}">
            </tool-header>

Case Explorer

            <clinical-analysis-browser
                .opencgaSession="${this.opencgaSession}"
                .settings="${this.settings}">
            </clinical-analysis-browser>
        `;
    }

}

customElements.define("clinical-analysis-portal", ClinicalAnalysisPortal);
<commit_msg>wc: add views system to case portal and add disease panels explorer #TASK-993<commit_after>/**
 * Copyright 2015-2022 OpenCB
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

import {LitElement, html} from "lit";
import "./clinical-analysis-browser.js";
import "../disease-panel/disease-panel-browser.js";
import "../commons/tool-header.js";

export default class ClinicalAnalysisPortal extends LitElement {

    constructor() {
        super();
        this.#init();
    }

    createRenderRoot() {
        return this;
    }

    static get properties() {
        return {
            opencgaSession: {
                type: Object
            },
            settings: {
                type: Object
            },
        };
    }

    #init() {
        this.currentView = "case";
    }

    #onViewChange(newView) {
        this.currentView = newView;
        this.requestUpdate();
    }

    renderToolbarButtons() {
        return html`

                <button
                    class="${`btn btn-info ${this.currentView === "case" ? "active" : ""}`}"
                    @click="${() => this.#onViewChange("case")}">
                    Case Explorer
                </button>
                <button
                    class="${`btn btn-info ${this.currentView === "panel" ? "active" : ""}`}"
                    @click="${() => this.#onViewChange("panel")}">
                    Disease Panel Explorer
                </button>
                <button class="btn btn-default" style="margin-left:16px;">

                    New Case
                </button>

        `;
    }

    render() {
        if (!this.opencgaSession) {
            return html`

No public projects available to browse. Please login to continue

            `;
        }

        return html`
            <tool-header
                .title="${this.settings?.title || "Case Portal"}"
                .icon="${this.settings?.icon || ""}"
                .rhs="${this.renderToolbarButtons()}">
            </tool-header>

                <div role="tabpanel" class="${`tab-pane ${this.currentView === "case" ? "active" : ""}`}">

Case Explorer

                    <clinical-analysis-browser
                        .opencgaSession="${this.opencgaSession}"
                        .settings="${this.settings}"
                        .config="${{
                            showHeader: false,
                        }}">
                    </clinical-analysis-browser>

                <div role="tabpanel" class="${`tab-pane ${this.currentView === "panel" ? "active" : ""}`}">

Disease Panel Explorer

                    <disease-panel-browser
                        .opencgaSession="${this.opencgaSession}"
                        .config="${{
                            showHeader: false,
                        }}">
                    </disease-panel-browser>

        `;
    }

}

customElements.define("clinical-analysis-portal", ClinicalAnalysisPortal);
<|endoftext|>


<commit_before>// Copyright 2009 The Closure Library Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//      http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS-IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

/**
 * @fileoverview provides a reusable mp3 UI component given a mp3 URL.
 *
 * goog.ui.media.Mp3 is actually a {@link goog.ui.ControlRenderer}, a stateless
 * class - that could/should be used as a Singleton with the static method
 * {@code goog.ui.media.Mp3.getInstance} -, that knows how to render Mp3s. It is
 * designed to be used with a {@link goog.ui.Control}, which will actually
 * control the media renderer and provide the {@link goog.ui.Component} base.
 * This design guarantees that all different types of medias will behave alike
 * but will look different.
 *
 * goog.ui.media.Mp3 expects mp3 urls on {@code goog.ui.Control.getModel} as
 * data models, and render a flash object that will play that URL.
 *
 * Example of usage:
 *
 *

 *   goog.ui.media.Mp3.newControl('http://hostname/file.mp3').render();
 *

 *
 * Mp3 medias currently support the following states:
 *
 *

 *
 {@link goog.ui.Component.State.DISABLED}: shows 'flash not available'
 *
 {@link goog.ui.Component.State.HOVER}: mouse cursor is over the mp3
 *
 {@link goog.ui.Component.State.SELECTED}: mp3 is playing
 *

 *
 * Which can be accessed by
 *
 *

 *   mp3.setEnabled(true);
 *   mp3.setHighlighted(true);
 *   mp3.setSelected(true);
 *

 *
 *
 *
 * @supported IE6, FF2+, Safari. Requires flash to actually work.
 *
 * TODO(user): test on other browsers
 */

goog.provide('goog.ui.media.Mp3');

goog.require('goog.string');
goog.require('goog.ui.media.FlashObject');
goog.require('goog.ui.media.Media');
goog.require('goog.ui.media.MediaRenderer');

/**
 * Subclasses a goog.ui.media.MediaRenderer to provide a Mp3 specific media
 * renderer.
 *
 * This class knows how to parse mp3 URLs, and render the DOM structure
 * of mp3 flash players. This class is meant to be used as a singleton static
 * stateless class, that takes {@code goog.ui.media.Media} instances and renders
 * it. It expects {@code goog.ui.media.Media.getModel} to return a well formed,
 * previously checked, mp3 URL {@see goog.ui.media.PicasaAlbum.parseUrl},
 * which is the data model this renderer will use to construct the DOM
 * structure. {@see goog.ui.media.PicasaAlbum.newControl} for an example of
 * constructing a control with this renderer.
 *
 * This design is patterned after http://go/closure_control_subclassing
 *
 * It uses {@link goog.ui.media.FlashObject} to embed the flash object.
 *
 * @constructor
 * @extends {goog.ui.media.MediaRenderer}
 */
goog.ui.media.Mp3 = function() {
  goog.ui.media.MediaRenderer.call(this);
};
goog.inherits(goog.ui.media.Mp3, goog.ui.media.MediaRenderer);
goog.addSingletonGetter(goog.ui.media.Mp3);

/**
 * Flash player arguments. We expect that {@code flashUrl_} will contain a flash
 * movie that takes an audioUrl parameter on its URL, containing the URL of the
 * mp3 to be played.
 *
 * @type {string}
 * @private
 */
goog.ui.media.Mp3.PLAYER_ARGUMENTS_ = 'audioUrl=%s';

/**
 * Default CSS class to be applied to the root element of components rendered
 * by this renderer.
 *
 * @type {string}
 */
goog.ui.media.Mp3.CSS_CLASS = goog.getCssName('goog-ui-media-mp3');

/**
 * Flash player URL. Uses Google Reader's mp3 flash player by default.
 *
 * @type {string}
 * @private
 */
goog.ui.media.Mp3.flashUrl_ =
    'http://www.google.com/reader/ui/3247397568-audio-player.swf';

/**
 * Regular expression to check if a given URL is a valid mp3 URL.
 *
 * Copied from http://go/markdownlite.js.

 *
 * NOTE(user): although it would be easier to use goog.string.endsWith('.mp3'),
 * in the future, we want to provide media inlining, which is basically getting
 * a text and replacing all mp3 references with an mp3 player, so it makes sense
 * to share the same regular expression to match everything.
 *
 * @type {RegExp}
 */
goog.ui.media.Mp3.MATCHER =
    /(https?:\/\/[a-zA-Z0-9-_%&\/.=:#\+~\(\)]+\.(mp3)+(\?[a-zA-Z0-9-_%&\/.=:#\+~\(\)]+)?)/i;

/**
 * A static convenient method to construct a goog.ui.media.Media control out of
 * a mp3 URL. It checks the mp3 URL, sets it as the data model
 * goog.ui.media.Mp3 renderer uses, sets the states supported by the renderer,
 * and returns a Control that binds everything together. This is what you
 * should be using for constructing Mp3 videos, except if you need more fine
 * control over the configuration.
 *
 * @param {goog.ui.media.MediaModel} dataModel A media model that must contain
 *     an mp3 url on {@code dataModel.getUrl}.
 * @param {goog.dom.DomHelper=} opt_domHelper Optional DOM helper, used for
 *     document interaction.
 * @return {goog.ui.media.Media} A goog.ui.Control subclass with the mp3
 *     renderer.
 */
goog.ui.media.Mp3.newControl = function(dataModel, opt_domHelper) {
  var control = new goog.ui.media.Media(
      dataModel,
      goog.ui.media.Mp3.getInstance(),
      opt_domHelper);
  // mp3 ui doesn't have a non selected view: it shows the mp3 player by
  // default.
  control.setSelected(true);
  return control;
};

/**
 * A static method that sets which flash URL this class should use. Use this if
 * you want to host your own flash mp3 player.
 *
 * @param {string} flashUrl The URL of the flash mp3 player.
 */
goog.ui.media.Mp3.setFlashUrl = function(flashUrl) {
  goog.ui.media.Mp3.flashUrl_ = flashUrl;
};

/**
 * A static method that builds a URL that will contain the flash player that
 * will play the {@code mp3Url}.
 *
 * @param {string} mp3Url The URL of the mp3 music.
 * @return {string} An URL of a flash player that will know how to play the
 *     given {@code mp3Url}.
 */
goog.ui.media.Mp3.buildFlashUrl = function(mp3Url) {
  var flashUrl = goog.ui.media.Mp3.flashUrl_ + '?' + goog.string.subs(
      goog.ui.media.Mp3.PLAYER_ARGUMENTS_,
      goog.string.urlEncode(mp3Url));
  return flashUrl;
};

/**
 * Creates the initial DOM structure of a mp3 video, which is basically a
 * the flash object pointing to a flash mp3 player.
 *
 * @param {goog.ui.media.Media} control The media control.
 * @return {Element} A DOM structure that represents the control.
 */
goog.ui.media.Mp3.prototype.createDom = function(control) {
  var div = goog.ui.media.Mp3.superClass_.createDom.call(this, control);

  var dataModel =
      /** @type {goog.ui.media.MediaModel} */ (control.getDataModel());
  var flashUrl = goog.ui.media.Mp3.flashUrl_ + '?' + goog.string.subs(
      goog.ui.media.Mp3.PLAYER_ARGUMENTS_,
      goog.string.urlEncode(dataModel.getUrl()));
  var flash = new goog.ui.media.FlashObject(
      dataModel.getPlayer().getUrl(), control.getDomHelper());
  flash.setFlashVars('playerMode', 'embedded');
  flash.render(div);

  return div;
};

/**
 * Returns the CSS class to be applied to the root element of components
 * rendered using this renderer.
 * @return {string} Renderer-specific CSS class.
 */
goog.ui.media.Mp3.prototype.getCssClass = function() {
  return goog.ui.media.Mp3.CSS_CLASS;
};
<commit_msg><commit_after>// Copyright 2009 The Closure Library Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//      http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS-IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

/**
 * @fileoverview provides a reusable mp3 UI component given a mp3 URL.
 *
 * goog.ui.media.Mp3 is actually a {@link goog.ui.ControlRenderer}, a stateless
 * class - that could/should be used as a Singleton with the static method
 * {@code goog.ui.media.Mp3.getInstance} -, that knows how to render Mp3s. It is
 * designed to be used with a {@link goog.ui.Control}, which will actually
 * control the media renderer and provide the {@link goog.ui.Component} base.
 * This design guarantees that all different types of medias will behave alike
 * but will look different.
 *
 * goog.ui.media.Mp3 expects mp3 urls on {@code goog.ui.Control.getModel} as
 * data models, and render a flash object that will play that URL.
 *
 * Example of usage:
 *
 *

 *   goog.ui.media.Mp3.newControl('http://hostname/file.mp3').render();
 *

 *
 * Mp3 medias currently support the following states:
 *
 *

 *
 {@link goog.ui.Component.State.DISABLED}: shows 'flash not available'
 *
 {@link goog.ui.Component.State.HOVER}: mouse cursor is over the mp3
 *
 {@link goog.ui.Component.State.SELECTED}: mp3 is playing
 *

 *
 * Which can be accessed by
 *
 *

 *   mp3.setEnabled(true);
 *   mp3.setHighlighted(true);
 *   mp3.setSelected(true);
 *

 *
 *
 *
 * @supported IE6, FF2+, Safari. Requires flash to actually work.
 *
 * TODO(user): test on other browsers
 */

goog.provide('goog.ui.media.Mp3');

goog.require('goog.string');
goog.require('goog.ui.media.FlashObject');
goog.require('goog.ui.media.Media');
goog.require('goog.ui.media.MediaRenderer');

/**
 * Subclasses a goog.ui.media.MediaRenderer to provide a Mp3 specific media
 * renderer.
 *
 * This class knows how to parse mp3 URLs, and render the DOM structure
 * of mp3 flash players. This class is meant to be used as a singleton static
 * stateless class, that takes {@code goog.ui.media.Media} instances and renders
 * it. It expects {@code goog.ui.media.Media.getModel} to return a well formed,
 * previously checked, mp3 URL {@see goog.ui.media.PicasaAlbum.parseUrl},
 * which is the data model this renderer will use to construct the DOM
 * structure. {@see goog.ui.media.PicasaAlbum.newControl} for an example of
 * constructing a control with this renderer.
 *
 * This design is patterned after http://go/closure_control_subclassing
 *
 * It uses {@link goog.ui.media.FlashObject} to embed the flash object.
 *
 * @constructor
 * @extends {goog.ui.media.MediaRenderer}
 */
goog.ui.media.Mp3 = function() {
  goog.ui.media.MediaRenderer.call(this);
};
goog.inherits(goog.ui.media.Mp3, goog.ui.media.MediaRenderer);
goog.addSingletonGetter(goog.ui.media.Mp3);

/**
 * Flash player arguments. We expect that {@code flashUrl_} will contain a flash
 * movie that takes an audioUrl parameter on its URL, containing the URL of the
 * mp3 to be played.
 *
 * @type {string}
 * @private
 */
goog.ui.media.Mp3.PLAYER_ARGUMENTS_ = 'audioUrl=%s';

/**
 * Default CSS class to be applied to the root element of components rendered
 * by this renderer.
 *
 * @type {string}
 */
goog.ui.media.Mp3.CSS_CLASS = goog.getCssName('goog-ui-media-mp3');

/**
 * Flash player URL. Uses Google Reader's mp3 flash player by default.
 *
 * @type {string}
 * @private
 */
goog.ui.media.Mp3.flashUrl_ =
    'http://www.google.com/reader/ui/3523697345-audio-player.swf';

/**
 * Regular expression to check if a given URL is a valid mp3 URL.
 *
 * Copied from http://go/markdownlite.js.

 *
 * NOTE(user): although it would be easier to use goog.string.endsWith('.mp3'),
 * in the future, we want to provide media inlining, which is basically getting
 * a text and replacing all mp3 references with an mp3 player, so it makes sense
 * to share the same regular expression to match everything.
 *
 * @type {RegExp}
 */
goog.ui.media.Mp3.MATCHER =
    /(https?:\/\/[\w-%&\/.=:#\+~\(\)]+\.(mp3)+(\?[\w-%&\/.=:#\+~\(\)]+)?)/i;

/**
 * A static convenient method to construct a goog.ui.media.Media control out of
 * a mp3 URL. It checks the mp3 URL, sets it as the data model
 * goog.ui.media.Mp3 renderer uses, sets the states supported by the renderer,
 * and returns a Control that binds everything together. This is what you
 * should be using for constructing Mp3 videos, except if you need more fine
 * control over the configuration.
 *
 * @param {goog.ui.media.MediaModel} dataModel A media model that must contain
 *     an mp3 url on {@code dataModel.getUrl}.
 * @param {goog.dom.DomHelper=} opt_domHelper Optional DOM helper, used for
 *     document interaction.
 * @return {goog.ui.media.Media} A goog.ui.Control subclass with the mp3
 *     renderer.
 */
goog.ui.media.Mp3.newControl = function(dataModel, opt_domHelper) {
  var control = new goog.ui.media.Media(
      dataModel,
      goog.ui.media.Mp3.getInstance(),
      opt_domHelper);
  // mp3 ui doesn't have a non selected view: it shows the mp3 player by
  // default.
  control.setSelected(true);
  return control;
};

/**
 * A static method that sets which flash URL this class should use. Use this if
 * you want to host your own flash mp3 player.
 *
 * @param {string} flashUrl The URL of the flash mp3 player.
 */
goog.ui.media.Mp3.setFlashUrl = function(flashUrl) {
  goog.ui.media.Mp3.flashUrl_ = flashUrl;
};

/**
 * A static method that builds a URL that will contain the flash player that
 * will play the {@code mp3Url}.
 *
 * @param {string} mp3Url The URL of the mp3 music.
 * @return {string} An URL of a flash player that will know how to play the
 *     given {@code mp3Url}.
 */
goog.ui.media.Mp3.buildFlashUrl = function(mp3Url) {
  var flashUrl = goog.ui.media.Mp3.flashUrl_ + '?' + goog.string.subs(
      goog.ui.media.Mp3.PLAYER_ARGUMENTS_,
      goog.string.urlEncode(mp3Url));
  return flashUrl;
};

/**
 * Creates the initial DOM structure of a mp3 video, which is basically a
 * the flash object pointing to a flash mp3 player.
 *
 * @param {goog.ui.media.Media} control The media control.
 * @return {Element} A DOM structure that represents the control.
 */
goog.ui.media.Mp3.prototype.createDom = function(control) {
  var div = goog.ui.media.Mp3.superClass_.createDom.call(this, control);

  var dataModel =
      /** @type {goog.ui.media.MediaModel} */ (control.getDataModel());
  var flashUrl = goog.ui.media.Mp3.flashUrl_ + '?' + goog.string.subs(
      goog.ui.media.Mp3.PLAYER_ARGUMENTS_,
      goog.string.urlEncode(dataModel.getUrl()));
  var flash = new goog.ui.media.FlashObject(
      dataModel.getPlayer().getUrl(), control.getDomHelper());
  flash.setFlashVars('playerMode', 'embedded');
  flash.render(div);

  return div;
};

/**
 * Returns the CSS class to be applied to the root element of components
 * rendered using this renderer.
 * @return {string} Renderer-specific CSS class.
 */
goog.ui.media.Mp3.prototype.getCssClass = function() {
  return goog.ui.media.Mp3.CSS_CLASS;
};
<|endoftext|>


<commit_before>/**
 * Project Name: <%= projectName %>
 * Client:
 * Author: <%= devNames %>
 * Company:
 */

'use-strict';

// npm modules
import ready from 'lite-ready';<%

if (includeSwiftclick) {%>
import SwiftClick from 'swiftclick';<%
}

if (includeTrak) {%>
import trak from 'trak.js';<%
}

if (includeJquery) {%>
window.jQuery = window.$ = require('jquery');<% } %>

// Bundle global libs that don't return a value
import 'console';
import './utils/$$';

// Add your project-specific modules here
//import moduleTest from './modules/moduleTest';

// DOM ready code goes in here
ready(function () {<%
if (includeSwiftclick) {%>
	const swiftclick = SwiftClick.attach(document.body);
	swiftclick.useCssParser(true);<% }

	if (includeTrak) {%>
	trak.start();<% } %>

	// browserifyTest(); // this is a test, uncomment this line & the line above to try it
});
<commit_msg>refactor: merge latest from kickoff `next`<commit_after>/**
 * Project Name: <%= projectName %>
 * Client:
 * Author: <%= devNames %>
 * Company:
 */

'use-strict';

// npm modules
import ready from 'lite-ready';
import $$ from 'double-dollar';
window.$$ = $$;<%

if (includeSwiftclick) {%>
import SwiftClick from 'swiftclick';<%
}

if (includeTrak) {%>
import trak from 'trak.js';<%
}

if (includeJquery) {%>
import $ from 'jquery';
window.jQuery = window.$ = $;<% } %>

// Bundle global libs that don't return a value
import 'console';

// Add your project-specific modules here
//import moduleTest from './modules/moduleTest';

// DOM ready code goes in here
ready(function () {<%
if (includeSwiftclick) {%>
	const swiftclick = SwiftClick.attach(document.body);
	swiftclick.useCssParser(true);<% }

	if (includeTrak) {%>
	trak.start();<% } %>

	// moduleTest(); // this is a test, uncomment this line & the line above to try it
});
<|endoftext|>


<commit_before>// Append number with 0 if there is only 1 digit
function pad(num) {
    num = num.toString();
    if (num.length === 1) {
        num = '0' + num;
    }
    return num;
}

// Convert a decimal duration (0.0) to a string (0:00).
function durationToString(duration) {
    if (typeof(duration) === 'number') {
        let hours = Math.floor(duration);
        let minutes = Math.round((duration - hours) * 60);
        duration = hours + ':' + pad(minutes);
    }
    return duration;
}

// Convert a decimal duration (0.0) duration to a number (0) of seconds.
function durationToSeconds(duration) {
    if (typeof(duration) === 'number') {
        let hours = Math.floor(duration);
        let minutes = Math.round((duration - hours) * 60);
        duration = hours * 3600 + minutes * 60;
    }
    return duration;
}

// Convert a number (0) of seconds to a string (0:00).
function secondsToDurationString(duration) {
    if (typeof(duration) === 'number') {
        let hours = Math.floor(duration / 3600);
        let minutes = Math.floor(duration % 3600 / 60);
        duration = hours + ':' + pad(minutes);
    }
    return duration;
}

// Toggle disabled/enabled styles on form submit button(s).
function toggleButtonBusy(target) {
    let buttons = [];

    // The passed target may be a form object containing elements that include
    // the clicked submit button.
    if (typeof target.elements === 'object') {
        for (let i = 0; i < target.elements.length; i++) {
            if (target.elements.hasOwnProperty(i)
                && typeof target.elements[i].type !== 'undefined'
                && target.elements[i].type == 'submit') {
                buttons.push(target.elements[i]);
            }
        }
    }
    else {
        buttons.push(target);
    }

    for (let i = 0; i < buttons.length; i++) {
        if (buttons.hasOwnProperty(i)) {
            let button = buttons[i];
            if (typeof button.toggleClass !== 'undefined') {
                button.toggleClass('progress-bar-striped');
                button.toggleClass('progress-bar-animated');
                button.prop('disabled', function (i, val) {
                    return !val;
                });
            }
            else {
                if (!button.disabled) {
                    button.disabled = true;
                    button.classList.add('progress-bar-striped');
                    button.classList.add('progress-bar-animated');
                }
                else {
                    button.disabled = false;
                    button.classList.remove('progress-bar-striped');
                    button.classList.remove('progress-bar-animated');
                }
            }
        }
    }
}
<commit_msg>Remove toggleButtonBusy global function.<commit_after>// Append number with 0 if there is only 1 digit
function pad(num) {
    num = num.toString();
    if (num.length === 1) {
        num = '0' + num;
    }
    return num;
}

// Convert a decimal duration (0.0) to a string (0:00).
function durationToString(duration) {
    if (typeof(duration) === 'number') {
        let hours = Math.floor(duration);
        let minutes = Math.round((duration - hours) * 60);
        duration = hours + ':' + pad(minutes);
    }
    return duration;
}

// Convert a decimal duration (0.0) duration to a number (0) of seconds.
function durationToSeconds(duration) {
    if (typeof(duration) === 'number') {
        let hours = Math.floor(duration);
        let minutes = Math.round((duration - hours) * 60);
        duration = hours * 3600 + minutes * 60;
    }
    return duration;
}

// Convert a number (0) of seconds to a string (0:00).
function secondsToDurationString(duration) {
    if (typeof(duration) === 'number') {
        let hours = Math.floor(duration / 3600);
        let minutes = Math.floor(duration % 3600 / 60);
        duration = hours + ':' + pad(minutes);
    }
    return duration;
}
<|endoftext|>


<commit_before>var w = 960,
    h = 500,
    fill = d3.scale.category20();

var vis = d3.select("#chart")
  .append("svg:svg")
    .attr("width", w)
    .attr("height", h);

d3.json("miserables.json", function(json) {
  var force = d3.layout.force()
      .charge(-120)
      .linkDistance(30)
      .nodes(json.nodes)
      .links(json.links)
      .size([w, h])
      .start();

  var link = vis.selectAll("line.link")
      .data(json.links)
    .enter().append("svg:line")
      .attr("class", "link")
      .style("stroke-width", function(d) { return Math.sqrt(d.value); })
      .attr("x1", function(d) { return d.source.x; })
      .attr("y1", function(d) { return d.source.y; })
      .attr("x2", function(d) { return d.target.x; })
      .attr("y2", function(d) { return d.target.y; });

  var node = vis.selectAll("circle.node")
      .data(json.nodes)
    .enter().append("svg:circle")
      .attr("class", "node")
      .attr("cx", function(d) { return d.x; })
      .attr("cy", function(d) { return d.y; })
      .attr("r", 5)
      .style("fill", function(d) { return fill(d.group); })
      .call(force.drag);

  node.append("svg:title")
      .text(function(d) { return d.name; });

  vis.style("opacity", 1e-6)
    .transition()
      .duration(1000)
      .style("opacity", 1);

  force.on("tick", function() {
    link.attr("x1", function(d) { return d.source.x; })
        .attr("y1", function(d) { return d.source.y; })
        .attr("x2", function(d) { return d.target.x; })
        .attr("y2", function(d) { return d.target.y; });

    node.attr("cx", function(d) { return d.x; })
        .attr("cy", function(d) { return d.y; });
  });
});
<commit_msg>Tweak example parameters.<commit_after>var w = 960,
    h = 500,
    fill = d3.scale.category20();

var vis = d3.select("#chart")
  .append("svg:svg")
    .attr("width", w)
    .attr("height", h);

d3.json("miserables.json", function(json) {
  var force = d3.layout.force()
      .charge(-60)
      .nodes(json.nodes)
      .links(json.links)
      .size([w, h])
      .start();

  var link = vis.selectAll("line.link")
      .data(json.links)
    .enter().append("svg:line")
      .attr("class", "link")
      .style("stroke-width", function(d) { return Math.sqrt(d.value); })
      .attr("x1", function(d) { return d.source.x; })
      .attr("y1", function(d) { return d.source.y; })
      .attr("x2", function(d) { return d.target.x; })
      .attr("y2", function(d) { return d.target.y; });

  var node = vis.selectAll("circle.node")
      .data(json.nodes)
    .enter().append("svg:circle")
      .attr("class", "node")
      .attr("cx", function(d) { return d.x; })
      .attr("cy", function(d) { return d.y; })
      .attr("r", 5)
      .style("fill", function(d) { return fill(d.group); })
      .call(force.drag);

  node.append("svg:title")
      .text(function(d) { return d.name; });

  vis.style("opacity", 1e-6)
    .transition()
      .duration(1000)
      .style("opacity", 1);

  force.on("tick", function() {
    link.attr("x1", function(d) { return d.source.x; })
        .attr("y1", function(d) { return d.source.y; })
        .attr("x2", function(d) { return d.target.x; })
        .attr("y2", function(d) { return d.target.y; });

    node.attr("cx", function(d) { return d.x; })
        .attr("cy", function(d) { return d.y; });
  });
});
<|endoftext|>


<commit_before>require("dotenv").config({ path: "./env." + process.env.NODE_ENV });
var request = require("request-promise");
require("cheerio");
var robots = require("robots");
var parser = new robots.RobotsParser(null, {});
var jeopardyParser = require("../lib/JeopardyParser");
const Queries = require("../lib/Queries");

var MongoClient = require("mongodb").MongoClient,
	assert = require("assert");

var url = process.env.DB_URL;
var USER_AGENT = "Alex Pelan (alexpelan@gmail.com)";
var ROBOTS_URL = "http://www.j-archive.com/robots.txt";
const EARLIEST_SEASON = 30;

let requestOptions = {
	url: "",
	headers: {
		"User-Agent": USER_AGENT
	}
};

var seedGame = function(gameId, delay, db) {
	return new Promise(function(resolve) {
		setTimeout(function() {
			var url = process.env.GAME_URL + gameId;
			requestOptions.url = url;
			request(requestOptions).then(function(html) {
				var gameJson = jeopardyParser.parseJeopardyGame(html);
				var gamesCollection = db.collection("games");
				console.log("updating game ", gameId, " with ", gameJson);
				gamesCollection.updateOne({id: gameId}, {$set: gameJson}, function() {
					resolve();
				});
			});
		}, delay * 1000);
	});
};

// gamesJson is an array of arrays of games
var seedGames = function(gamesJson, delayPerRequest, db) {
	var counter = 0;
	var gamePromises = [];
	gamesJson.forEach(function(gamesFromSingleSeason) {

		gamesFromSingleSeason.forEach(function(game) {
			var delay = delayPerRequest * counter;
			counter = counter + 1;
			gamePromises.push(seedGame(game.id, delay, db));
		});

	});

	return gamePromises;
};

var seedSeason = function(seasonId, delay, db, checkForExisting=false) {
	return new Promise(function(resolve) {
		setTimeout(function() {
			var url = process.env.SEASON_URL + seasonId;
			requestOptions.url = url;
			request(requestOptions).then(function(html) {
				var gamesJson = jeopardyParser.parseJeopardySeason(html).games;
				gamesJson.forEach(function(game) {
					game.season = seasonId;
				});

				if (checkForExisting) {
					insertNewGamesMetadata(db, gamesJson).then((gamesToFinish) => resolve(gamesToFinish));
				} else {
					var gamesCollection = db.collection("games");
					gamesCollection.insertMany(gamesJson, function() {
						resolve(gamesJson);
					});
				}
			}).catch(function(error){
				console.log("error requesting ", error);
			});
		}, delay * 1000);
	});
};

var seedSeasons = function(seasonsJson, delayPerRequest, db) {
	var seasonPromises = [];
	seasonsJson.forEach(function(season, index){
		var delay = delayPerRequest * index;
		seasonPromises.push(seedSeason(season.id, delay, db));
	});

	return seasonPromises;
};

var seedListOfSeasons = function(db) {
	var url = process.env.LIST_SEASONS_URL;
	requestOptions.url = url;

	return new Promise(function (resolve) {
		request(requestOptions).then(function(html) {
			const seasonsJson = jeopardyParser.parseSeasonList(html).seasons;
			const filteredSeasonsJson = seasonsJson.filter((season) => {
				return parseInt(season.id) >= EARLIEST_SEASON;
			});
			var seasonsCollection = db.collection("seasons");
			// always do a complete removal
			seasonsCollection.removeMany({}).then(() => {
				seasonsCollection.insertMany(filteredSeasonsJson, function() {
					resolve(filteredSeasonsJson);
				});
			});
		}).catch(function(error){
			console.log("error requesting ", error);
		});
	});

};

const seedFullDatabase = function(db, delayInSeconds) {
	seedListOfSeasons(db).then(function(seasonsJson) {
		var promises = seedSeasons(seasonsJson, delayInSeconds, db);
		Promise.all(promises).then(function(gamesJson){
			Promise.all(seedGames(gamesJson, delayInSeconds, db)).then(function() {
				db.close();
			}).catch(function(error) {
				console.log("error fetching list of games", error);
			});
		}).catch(function(error){
			console.log("error fetching listOfSeasons ", error);
		});
	});
};

const insertNewGamesMetadata = function(db, gamesJson) {
	return new Promise(function(resolve) {
		///fetch all the games
		const gamesCollection = db.collection("games");
		gamesCollection.find({}).toArray().then((gamesInDb) => {
			// figure out which games aren't in the database.
			let gamesToInsert = gamesJson.filter((game) => {
				return !gamesInDb.some((gameInDb) => {
					return gameInDb.id === game.id;
				});
			});

			// insert the ones that aren't
			if (gamesToInsert.length > 0) {
				gamesCollection.insertMany(gamesToInsert, function() {
					resolve(gamesToInsert);
				});
			} else {
				resolve();
			}

		});

	});

};

const checkForNewSeason = function(db, delayInSeconds) {
	let foundAnySeason = false;
	var url = process.env.LIST_SEASONS_URL;
	requestOptions.url = url;

	return new Promise(function (resolve) {
		setTimeout(() => {
			request(requestOptions).then(function(html) {
				const seasonsJson = jeopardyParser.parseSeasonList(html).seasons;
				const filteredSeasonsJson = seasonsJson.filter((season) => {
					return parseInt(season.id) >= EARLIEST_SEASON;
				});
				var seasonsCollection = db.collection("seasons");
				seasonsCollection.find({}).toArray().then((seasonsInDb) => {
					filteredSeasonsJson.forEach((seasonFromPage) => {
						const foundNewSeason = !seasonsInDb.some((seasonInDb) => seasonInDb.id === seasonFromPage.id);
						if (foundNewSeason) {
							foundAnySeason = true;
							seedListOfSeasons(db).then(() => {
								seedSeason(seasonFromPage.id.toString(), delayInSeconds, db, false).then((gamesJson)  => {
									if (gamesJson) {
										Promise.all(seedGames([gamesJson], delayInSeconds, db)).then(() => {
											db.close();
											resolve();
										});
									}  else {
										resolve();
									}
								});
							});

						}
					});

					if (!foundAnySeason) {
						resolve();
					}
				});
			}).catch(function(error){
				console.log("error requesting ", error);
			});
		}, delayInSeconds * 1000);
	});
};

const checkForUpdates = function(db, delayInSeconds) {
	Queries.findMostRecentSeason(db).then((result) => {

		const checkForExisting = true;
		seedSeason(result.toString(), delayInSeconds, db, checkForExisting).then((gamesToFinish)  => {
			// if there are new games in the most recent season, then add those. otherwise, check for a new season
			if (gamesToFinish) {
				Promise.all(seedGames([gamesToFinish], delayInSeconds, db)).then(() => db.close());
			} else {
				// then, check for a new season, and if we find one, seed ALL games from that new season
				checkForNewSeason(db, delayInSeconds).then(() =>  {
					db.close();
				});
			}
		});
	});
};

const executeTask = function(taskFunction) {
	MongoClient.connect(url, function(err, db) {
		assert.equal(null, err);

		parser.setUrl(ROBOTS_URL, function(parser, success) {
			if(success) {
				var delayInSeconds = parser.getCrawlDelay(USER_AGENT);
				taskFunction(db, delayInSeconds);
			}
		});
	});
};

const firstTimeSetupEnvironment = function() {
	executeTask(seedFullDatabase);
};

const updateEnvironment = function() {
	executeTask(checkForUpdates);
};

module.exports = {
	seedGame,
	seedListOfSeasons,
	seedSeason,
	firstTimeSetupEnvironment,
	updateEnvironment,
};<commit_msg>Include bugsnag for scripts as well<commit_after>require("dotenv").config({ path: "./env." + process.env.NODE_ENV });
const bugsnag = require("bugsnag");
var request = require("request-promise");
require("cheerio");
var robots = require("robots");
var parser = new robots.RobotsParser(null, {});
var jeopardyParser = require("../lib/JeopardyParser");
const Queries = require("../lib/Queries");

var MongoClient = require("mongodb").MongoClient,
	assert = require("assert");

var url = process.env.DB_URL;
var USER_AGENT = "Alex Pelan (alexpelan@gmail.com)";
var ROBOTS_URL = "http://www.j-archive.com/robots.txt";
const EARLIEST_SEASON = 30;

let requestOptions = {
	url: "",
	headers: {
		"User-Agent": USER_AGENT
	}
};

if (process.env.NODE_ENV === "production") {
	bugsnag.register("8c6c3e8de424b622023527da88f508ee");
}

var seedGame = function(gameId, delay, db) {
	return new Promise(function(resolve) {
		setTimeout(function() {
			var url = process.env.GAME_URL + gameId;
			requestOptions.url = url;
			request(requestOptions).then(function(html) {
				var gameJson = jeopardyParser.parseJeopardyGame(html);
				var gamesCollection = db.collection("games");
				console.log("updating game ", gameId, " with ", gameJson);
				gamesCollection.updateOne({id: gameId}, {$set: gameJson}, function() {
					resolve();
				});
			});
		}, delay * 1000);
	});
};

// gamesJson is an array of arrays of games
var seedGames = function(gamesJson, delayPerRequest, db) {
	var counter = 0;
	var gamePromises = [];
	gamesJson.forEach(function(gamesFromSingleSeason) {

		gamesFromSingleSeason.forEach(function(game) {
			var delay = delayPerRequest * counter;
			counter = counter + 1;
			gamePromises.push(seedGame(game.id, delay, db));
		});

	});

	return gamePromises;
};

var seedSeason = function(seasonId, delay, db, checkForExisting=false) {
	return new Promise(function(resolve) {
		setTimeout(function() {
			var url = process.env.SEASON_URL + seasonId;
			requestOptions.url = url;
			request(requestOptions).then(function(html) {
				var gamesJson = jeopardyParser.parseJeopardySeason(html).games;
				gamesJson.forEach(function(game) {
					game.season = seasonId;
				});

				if (checkForExisting) {
					insertNewGamesMetadata(db, gamesJson).then((gamesToFinish) => resolve(gamesToFinish));
				} else {
					var gamesCollection = db.collection("games");
					gamesCollection.insertMany(gamesJson, function() {
						resolve(gamesJson);
					});
				}
			}).catch(function(error){
				console.log("error requesting ", error);
			});
		}, delay * 1000);
	});
};

var seedSeasons = function(seasonsJson, delayPerRequest, db) {
	var seasonPromises = [];
	seasonsJson.forEach(function(season, index){
		var delay = delayPerRequest * index;
		seasonPromises.push(seedSeason(season.id, delay, db));
	});

	return seasonPromises;
};

var seedListOfSeasons = function(db) {
	var url = process.env.LIST_SEASONS_URL;
	requestOptions.url = url;

	return new Promise(function (resolve) {
		request(requestOptions).then(function(html) {
			const seasonsJson = jeopardyParser.parseSeasonList(html).seasons;
			const filteredSeasonsJson = seasonsJson.filter((season) => {
				return parseInt(season.id) >= EARLIEST_SEASON;
			});
			var seasonsCollection = db.collection("seasons");
			// always do a complete removal
			seasonsCollection.removeMany({}).then(() => {
				seasonsCollection.insertMany(filteredSeasonsJson, function() {
					resolve(filteredSeasonsJson);
				});
			});
		}).catch(function(error){
			console.log("error requesting ", error);
		});
	});

};

const seedFullDatabase = function(db, delayInSeconds) {
	seedListOfSeasons(db).then(function(seasonsJson) {
		var promises = seedSeasons(seasonsJson, delayInSeconds, db);
		Promise.all(promises).then(function(gamesJson){
			Promise.all(seedGames(gamesJson, delayInSeconds, db)).then(function() {
				db.close();
			}).catch(function(error) {
				console.log("error fetching list of games", error);
			});
		}).catch(function(error){
			console.log("error fetching listOfSeasons ", error);
		});
	});
};

const insertNewGamesMetadata = function(db, gamesJson) {
	return new Promise(function(resolve) {
		///fetch all the games
		const gamesCollection = db.collection("games");
		gamesCollection.find({}).toArray().then((gamesInDb) => {
			// figure out which games aren't in the database.
			let gamesToInsert = gamesJson.filter((game) => {
				return !gamesInDb.some((gameInDb) => {
					return gameInDb.id === game.id;
				});
			});

			// insert the ones that aren't
			if (gamesToInsert.length > 0) {
				gamesCollection.insertMany(gamesToInsert, function() {
					resolve(gamesToInsert);
				});
			} else {
				resolve();
			}

		});

	});

};

const checkForNewSeason = function(db, delayInSeconds) {
	let foundAnySeason = false;
	var url = process.env.LIST_SEASONS_URL;
	requestOptions.url = url;

	return new Promise(function (resolve) {
		setTimeout(() => {
			request(requestOptions).then(function(html) {
				const seasonsJson = jeopardyParser.parseSeasonList(html).seasons;
				const filteredSeasonsJson = seasonsJson.filter((season) => {
					return parseInt(season.id) >= EARLIEST_SEASON;
				});
				var seasonsCollection = db.collection("seasons");
				seasonsCollection.find({}).toArray().then((seasonsInDb) => {
					filteredSeasonsJson.forEach((seasonFromPage) => {
						const foundNewSeason = !seasonsInDb.some((seasonInDb) => seasonInDb.id === seasonFromPage.id);
						if (foundNewSeason) {
							foundAnySeason = true;
							seedListOfSeasons(db).then(() => {
								seedSeason(seasonFromPage.id.toString(), delayInSeconds, db, false).then((gamesJson)  => {
									if (gamesJson) {
										Promise.all(seedGames([gamesJson], delayInSeconds, db)).then(() => {
											db.close();
											resolve();
										});
									}  else {
										resolve();
									}
								});
							});

						}
					});

					if (!foundAnySeason) {
						resolve();
					}
				});
			}).catch(function(error){
				console.log("error requesting ", error);
			});
		}, delayInSeconds * 1000);
	});
};

const checkForUpdates = function(db, delayInSeconds) {
	Queries.findMostRecentSeason(db).then((result) => {

		const checkForExisting = true;
		seedSeason(result.toString(), delayInSeconds, db, checkForExisting).then((gamesToFinish)  => {
			// if there are new games in the most recent season, then add those. otherwise, check for a new season
			if (gamesToFinish) {
				Promise.all(seedGames([gamesToFinish], delayInSeconds, db)).then(() => db.close());
			} else {
				// then, check for a new season, and if we find one, seed ALL games from that new season
				checkForNewSeason(db, delayInSeconds).then(() =>  {
					db.close();
				});
			}
		});
	});
};

const executeTask = function(taskFunction) {
	MongoClient.connect(url, function(err, db) {
		assert.equal(null, err);

		parser.setUrl(ROBOTS_URL, function(parser, success) {
			if(success) {
				var delayInSeconds = parser.getCrawlDelay(USER_AGENT);
				taskFunction(db, delayInSeconds);
			}
		});
	});
};

const firstTimeSetupEnvironment = function() {
	executeTask(seedFullDatabase);
};

const updateEnvironment = function() {
	executeTask(checkForUpdates);
};

module.exports = {
	seedGame,
	seedListOfSeasons,
	seedSeason,
	firstTimeSetupEnvironment,
	updateEnvironment,
};<|endoftext|>