<commit_before>@Route = (@api, @path, @options, @endpoints) ->
  # Check if options were provided
  if not @endpoints
    @endpoints = @options
    @options = null

@Route.prototype.addToApi =  ->
  self = this

  # Throw an error if a route has already been added at this path
  # TODO: Check for collisions with paths that follow same pattern with different parameter names
  if _.contains @api.config.paths, @path
    throw new Error "Cannot add a route at an existing path: #{@path}"

  # Append the path to the base API path
  fullPath = @api.config.apiPath + @path

  # Setup endpoints on route using Iron Router
  Router.route fullPath,
    where: 'server'
    action: ->
      # Flatten parameters in the URL and request body (and give them better names)
      # TODO: Decide whether or not to nullify the copied objects. Makes sense to do it, right?
      @urlParams = @params
      @queryParams = @params.query
      @bodyParams = @request.body

      # Respond to the requested HTTP method if an endpoint has been provided for it
      method = @request.method
      if method is 'GET' and self.endpoints.get
        responseData = callEndpoint.call(this, self, self.endpoints.get)
      else if method is 'POST' and self.endpoints.post
        responseData = callEndpoint.call(this, self, self.endpoints.post)
      else if method is 'PUT' and self.endpoints.put
        responseData = callEndpoint.call(this, self, self.endpoints.put)
      else if method is 'PATCH' and self.endpoints.patch
        responseData = callEndpoint.call(this, self, self.endpoints.patch)
      else if method is 'DELETE' and self.endpoints.delete
        responseData = callEndpoint.call(this, self, self.endpoints.delete)
      else
        responseData = {statusCode: 404, body: {success: false, message:'API endpoint not found'}}

      # Generate and return the http response, handling the different endpoint response types
      if responseData.body and (responseData.statusCode or responseData.headers)
        responseData.statusCode or= 200
        responseData.headers or= {'Content-Type': 'text/json'}
        respond.call this, responseData.body, responseData.statusCode, responseData.headers
      else
        respond.call this, responseData

  # Add the path to our list of existing paths
  @api.config.paths.push @path


###
  Authenticate an endpoint if required, and return the result of calling it

  @context: IronRouter.Router.route()
###
callEndpoint = (route, endpoint) ->
  endpoint = resolveEndpoint endpoint
  authenticateIfRequired.call this, route, endpoint
  endpoint.action.call this

###
  Convert the given endpoint into our expected endpoint object if it is a bare function
###
resolveEndpoint = (endpoint) ->
  if _.isFunction(endpoint)
    endpoint = {action: endpoint}
  endpoint

###
  Authenticate the given method if required

  Once it's globally configured in the API, authentication can be required on an entire route or individual
  endpoints. If required on an entire endpoint, that serves as the default. If required in any individual endpoints, that
  will override the default.

  @context: IronRouter.Router.route()
###
authenticateIfRequired = (route, endpoint) ->
  # Authenticate the request if necessary
  if route.api.config.useAuth
    if endpoint.authRequired is undefined
      authenticate.call(this) if route.options?.authRequired
    else if endpoint.authRequired
      authenticate.call this


###
  Verify the request is being made by an actively logged in user

  @context: IronRouter.Router.route()
###
authenticate = ->
  # Get the auth info from header
  userId = @request.headers['x-user-id']
  loginToken = @request.headers['x-auth-token']

  # Get the user from the database
  if userId and loginToken
    user = Meteor.users.findOne {'_id': userId, 'services.resume.loginTokens.token': loginToken}

  # Return an error if the login token does not match any belonging to the user
  if not user
    respond.call this, {success: false, message: "You must be logged in to do this."}, 401

  @user = user


###
  Respond to an HTTP request

  @context: IronRouter.Router.route()
###
respond = (body, statusCode=200, headers={'Content-Type':'text/json'}) ->
  @response.writeHead statusCode, headers
  @response.write JSON.stringify body
  @response.end()<commit_msg>Allow cross-domain requests to API from browsers<commit_after>@Route = (@api, @path, @options, @endpoints) ->
  # Check if options were provided
  if not @endpoints
    @endpoints = @options
    @options = null

@Route.prototype.addToApi =  ->
  self = this

  # Throw an error if a route has already been added at this path
  # TODO: Check for collisions with paths that follow same pattern with different parameter names
  if _.contains @api.config.paths, @path
    throw new Error "Cannot add a route at an existing path: #{@path}"

  # Append the path to the base API path
  fullPath = @api.config.apiPath + @path

  # Setup endpoints on route using Iron Router
  Router.route fullPath,
    where: 'server'
    action: ->
      # Flatten parameters in the URL and request body (and give them better names)
      # TODO: Decide whether or not to nullify the copied objects. Makes sense to do it, right?
      @urlParams = @params
      @queryParams = @params.query
      @bodyParams = @request.body

      # Respond to the requested HTTP method if an endpoint has been provided for it
      method = @request.method
      if method is 'GET' and self.endpoints.get
        responseData = callEndpoint.call(this, self, self.endpoints.get)
      else if method is 'POST' and self.endpoints.post
        responseData = callEndpoint.call(this, self, self.endpoints.post)
      else if method is 'PUT' and self.endpoints.put
        responseData = callEndpoint.call(this, self, self.endpoints.put)
      else if method is 'PATCH' and self.endpoints.patch
        responseData = callEndpoint.call(this, self, self.endpoints.patch)
      else if method is 'DELETE' and self.endpoints.delete
        responseData = callEndpoint.call(this, self, self.endpoints.delete)
      else
        responseData = {statusCode: 404, body: {success: false, message:'API endpoint not found'}}

      # Generate and return the http response, handling the different endpoint response types
      if responseData.body and (responseData.statusCode or responseData.headers)
        responseData.statusCode or= 200
        responseData.headers or= {'Content-Type': 'text/json'}
        respond.call this, responseData.body, responseData.statusCode, responseData.headers
      else
        respond.call this, responseData

  # Add the path to our list of existing paths
  @api.config.paths.push @path


###
  Authenticate an endpoint if required, and return the result of calling it

  @context: IronRouter.Router.route()
###
callEndpoint = (route, endpoint) ->
  endpoint = resolveEndpoint endpoint
  authenticateIfRequired.call this, route, endpoint
  endpoint.action.call this

###
  Convert the given endpoint into our expected endpoint object if it is a bare function
###
resolveEndpoint = (endpoint) ->
  if _.isFunction(endpoint)
    endpoint = {action: endpoint}
  endpoint

###
  Authenticate the given method if required

  Once it's globally configured in the API, authentication can be required on an entire route or individual
  endpoints. If required on an entire endpoint, that serves as the default. If required in any individual endpoints, that
  will override the default.

  @context: IronRouter.Router.route()
###
authenticateIfRequired = (route, endpoint) ->
  # Authenticate the request if necessary
  if route.api.config.useAuth
    if endpoint.authRequired is undefined
      authenticate.call(this) if route.options?.authRequired
    else if endpoint.authRequired
      authenticate.call this


###
  Verify the request is being made by an actively logged in user

  @context: IronRouter.Router.route()
###
authenticate = ->
  # Get the auth info from header
  userId = @request.headers['x-user-id']
  authToken = @request.headers['x-auth-token']

  # Get the user from the database
  if userId and authToken
    user = Meteor.users.findOne {'_id': userId, 'services.resume.loginTokens.token': authToken}

  # Return an error if the login token does not match any belonging to the user
  if not user
    respond.call this, {success: false, message: "You must be logged in to do this."}, 401

  @user = user


###
  Respond to an HTTP request

  @context: IronRouter.Router.route()
###
respond = (body, statusCode=200, headers) ->
  # Allow cross-domain requests to be made from the browser
  @response.setHeader 'Access-Control-Allow-Origin', '*'

  # Ensure that a content type is set (will be overridden if also included in given headers)
  # TODO: Consider enforcing a text/json-only content type (override any user-defined content-type)
  @response.setHeader 'Content-Type', 'text/json'

  # Send response
  @response.writeHead statusCode, headers
  @response.write JSON.stringify(body)
  @response.end()
{'fraction_non_alphanumeric': 0.08674939064923554, 'fraction_numerical': 0.002658985153999557, 'mean_word_length': 3.5293527345709985, 'pattern_counts': {'":': 0, '<': 3, '<?xml version=': 0, '>': 19, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '4392399', 'n_tokens_mistral': 2511, 'n_tokens_neox': 2434, 'n_words': 967}