<commit_before>'use strict';
const AWS = require('aws-sdk');
const __ = require('lodash');
const uuid = require('uuid');
const BluebirdPromise = require('bluebird');

const BaseClient = require('./baseClient');

AWS.config.setPromisesDependency(BluebirdPromise);

class CloudFrontClient extends BaseClient {

  constructor(accessKey = '', secretKey = '') {
    // Calls super and sets the region to null
    super(accessKey, secretKey, null);
  }

  get _awsCloudFrontClient() {

    if(!this._internalCloudFrontClient) {
      const cloudfrontParams = {
        apiVersion: '2016-01-28',
        accessKeyId: this._accessKey,
        secretAccessKey: this._secretKey
      };
      this._internalCloudFrontClient = new AWS.CloudFront(cloudfrontParams);
    }

    return this._internalCloudFrontClient;
  }

  /**
   *
   * @param params
   * @return {Promise.<D>}
   */
  createOrUpdateCloudFrontDistribution(params) {
    return this._getDistributionByCName(params.cname).then(distribution => {
      if(!__.isEmpty(distribution) && !__.isNil(distribution)) {
        if(!this._isDistributionOutOfDate(distribution, params)) {
          this.logMessage(`CloudFront Distribution already exist. No action taken. [Cname: ${params.cname}]`);
          return distribution;
        } else {
          this.logMessage(`CloudFront distribution already exist, but out of date.  Updating Cloudfront. [Cname: ${params.cname}]`);
          return this._updateCloudFrontDistribution(distribution, params);
        }
      } else {
        return this._createCloudFrontDistribution(params);
      }
    });
  }

  /**
   *
   * @param params
   * @return {Promise<D>}
   * @private
   */
  _createCloudFrontDistribution(params) {
    this.logMessage(`Creating Cloud Front Distribution. [Cname: ${params.cname}]`);

    const cloudFrontParams = this._buildDistributionConfig(params);

    let distribution;
    let createDistributionPromise = this._awsCloudFrontClient.createDistribution(cloudFrontParams).promise();
    return createDistributionPromise.then(result => {

      distribution = result.Distribution;

      const waitForParams = {
        Id: distribution.Id
      };

      this.logMessage(`Waiting for CloudFront Distribution to deploy. [CloudFront Id: ${distribution.Id}] [Cname: ${params.cname}]`);
      return this._awsCloudFrontClient.waitFor('distributionDeployed', waitForParams).promise().catch(err => {
        this.logMessage(`First waitFor failed for [CloudFront Id: ${distribution.Id}] TRYING AGAIN!`);
        return this._awsCloudFrontClient.waitFor('distributionDeployed', waitForParams).promise();
      });
    }).then(() => {
      this.logMessage(`Distribution deployed! [CloudFront Id: ${distribution.Id}] [Cname: ${params.cname}]`);
      return distribution;
    });
  }

  /**
   *
   * @param distribution
   * @param params
   * @return {Promise.<TResult>}
   * @private
   */
  _updateCloudFrontDistribution(distribution, params) {
    this.logMessage(`Updating Cloud Front Distribution. [Cname: ${params.cname}]`);

    let cloudFrontParams = this._buildDistributionConfig(params);
    cloudFrontParams.Id = distribution.Id;
    cloudFrontParams.IfMatch = distribution.ETag;

    let updatedDistribution;
    let updateDistributionPromise = this._awsCloudFrontClient.updateDistribution(cloudFrontParams).promise();
    return updateDistributionPromise.then(result => {

      updatedDistribution = result.Distribution;

      const waitForParams = {
        Id: updatedDistribution.Id
      };

      this.logMessage(`Waiting for CloudFront Distribution to deploy. [CloudFront Id: ${updatedDistribution.Id}] [Cname: ${params.cname}]`);
      return this._awsCloudFrontClient.waitFor('distributionDeployed', waitForParams).promise().catch(err => {
        this.logMessage(`First waitFor failed for [CloudFront Id: ${updatedDistribution.Id}] TRYING AGAIN!`);
        return this._awsCloudFrontClient.waitFor('distributionDeployed', waitForParams).promise();
      });
    }).then(() => {
      this.logMessage(`Distribution deployed! [CloudFront Id: ${updatedDistribution.Id}] [Cname: ${params.cname}]`);
      return updatedDistribution;
    });
  }

  /**
   *
   * @param cname
   * @return {Promise.<CloudFrontDistribution>}
   * @private
   */
  _getDistributionByCName(cname) {
    this.logMessage(`Executing getDistributionByCName. [Cname: ${cname}]`);
    const params = {};

    let listDistributionsPromise = this._awsCloudFrontClient.listDistributions(params).promise();

    return listDistributionsPromise.then(data => {
      let distribution = {};
      if(data && data.DistributionList && data.DistributionList.Items && data.DistributionList.Items.length > 0) {
        const distributionList = data.DistributionList.Items;
        distribution = distributionList.find(obj => {
          return obj.Aliases.Quantity > 0 && __.includes(obj.Aliases.Items, cname);
        });
      }

      if(!__.isNil(distribution) && !__.isEmpty(distribution)) {
        this.logMessage(`Distribution found! [Cname: ${cname}]`);
        let getDistributionParams = {
          Id: distribution.Id
        };

        let getDistributionPromise = this._awsCloudFrontClient.getDistribution(getDistributionParams).promise();

        return getDistributionPromise.then(getDistributionResult => {

          //we add the ETag to the distribution
          let localDistribution = getDistributionResult.Distribution;
          localDistribution.ETag = getDistributionResult.ETag;
          return localDistribution;
        });
      } else {
        this.logMessage(`Distribution not found! [Cname: ${cname}]`);
        return {};
      }
    });
  }

  /**
   *
   * @param distribution
   * @param params
   * @return {boolean}
   * @private
   */
  _isDistributionOutOfDate(distribution, params) {

    const {cname, acmCertArn, comment, originName, originDomainName, originPath, pathPattern, originProtocolPolicy} = params;

    let computedOriginProtocolPolicy = originProtocolPolicy || 'match-viewer';

    //cname
    let foundAliasIndex = __.indexOf(distribution.DistributionConfig.Aliases.Items, cname);
    if(foundAliasIndex < 0) {
      return true;
    }

    //acmCertArn
    if(distribution.DistributionConfig.ViewerCertificate.ACMCertificateArn !== acmCertArn) {
      return true;
    }

    //comment
    if(distribution.DistributionConfig.Comment !== comment) {
      return true;
    }

    //originName

    //originDomainName
    let foundOrigin = __.find(distribution.DistributionConfig.Origins.Items, {Id: originName});
    if(!foundOrigin || foundOrigin.DomainName !== originDomainName) {
      return true;
    }

    //originPath
    if(!foundOrigin || foundOrigin.OriginPath !== originPath) {
      return true;
    }

    //originProtocolPolicy
    if(!foundOrigin || foundOrigin.CustomOriginConfig.OriginProtocolPolicy !== computedOriginProtocolPolicy) {
      return true;
    }

    //pathPattern

    return false;
  }

  _buildDistributionConfig(params) {
    const {cname, acmCertArn, comment, originName, originDomainName, originPath, pathPattern, originProtocolPolicy} = params;

    let computedOriginProtocolPolicy = originProtocolPolicy || 'match-viewer';

    const cloudFrontParams = {
      DistributionConfig: { /* required */
        CallerReference: uuid(), /* required */
        Comment: comment, /* required */
        DefaultCacheBehavior: { /* required */
          ForwardedValues: { /* required */
            Cookies: { /* required */
              Forward: 'none', /* required */
              WhitelistedNames: {
                Quantity: 0, /* required */
                Items: []
              }
            },
            QueryString: false, /* required */
            Headers: {
              Quantity: 4, /* required */
              Items: [
                'x-***REMOVED***-version',
                'x-***REMOVED***-test',
                'Content-Type',
                'authorization'
              ]
            }
          },
          MinTTL: 0, /* required */
          TargetOriginId: originName, /* required */
          TrustedSigners: { /* required */
            Enabled: false, /* required */
            Quantity: 0, /* required */
            Items: []
          },
          ViewerProtocolPolicy: 'allow-all', /* required */
          AllowedMethods: {
            Items: [ /* required */
              'POST',
              'HEAD',
              'PATCH',
              'DELETE',
              'PUT',
              'GET',
              'OPTIONS'
            ],
            Quantity: 7, /* required */
            CachedMethods: {
              Items: [ /* required */
                'HEAD',
                'GET',
                'OPTIONS'
              ],
              Quantity: 3 /* required */
            }
          },
          Compress: false,
          DefaultTTL: 0,
          MaxTTL: 0,
          SmoothStreaming: false
        },
        Enabled: true, /* required */
        Origins: { /* required */
          Quantity: 1, /* required */
          Items: [
            {
              DomainName: originDomainName, /* required */
              Id: originName, /* required */
              CustomHeaders: {
                Quantity: 0, /* required */
                Items: []
              },
              CustomOriginConfig: {
                HTTPPort: 80, /* required */
                HTTPSPort: 443, /* required */
                OriginProtocolPolicy: computedOriginProtocolPolicy, /* required */
                OriginSslProtocols: {
                  Items: [ /* required */
                    'TLSv1',
                    'TLSv1.1',
                    'TLSv1.2'
                  ],
                  Quantity: 3 /* required */
                }
              },
              OriginPath: originPath
            }
          ]
        },
        Aliases: {
          Quantity: 1, /* required */
          Items: [
            cname
          ]
        },
        CacheBehaviors: {
          Quantity: 1, /* required */
          Items: [
            {
              ForwardedValues: { /* required */
                Cookies: { /* required */
                  Forward: 'none', /* required */
                  WhitelistedNames: {
                    Quantity: 0, /* required */
                    Items: []
                  }
                },
                QueryString: false, /* required */
                Headers: {
                  Quantity: 0, /* required */
                  Items: []
                }
              },
              MinTTL: 0, /* required */
              PathPattern: pathPattern, /* required */
              TargetOriginId: originName, /* required */
              TrustedSigners: { /* required */
                Enabled: false, /* required */
                Quantity: 0, /* required */
                Items: []
              },
              ViewerProtocolPolicy: 'allow-all', /* required */
              AllowedMethods: {
                Items: [ /* required */
                  'POST',
                  'HEAD',
                  'PATCH',
                  'DELETE',
                  'PUT',
                  'GET',
                  'OPTIONS'
                ],
                Quantity: 7, /* required */
                CachedMethods: {
                  Items: [ /* required */
                    'HEAD',
                    'GET',
                    'OPTIONS'
                  ],
                  Quantity: 3 /* required */
                }
              },
              Compress: true,
              DefaultTTL: 0,
              MaxTTL: 0,
              SmoothStreaming: false
            }
          ]
        },
        CustomErrorResponses: {
          Quantity: 0, /* required */
          Items: []
        },
        DefaultRootObject: '',
        HttpVersion: 'http2',
        IsIPV6Enabled: true,
        Logging: {
          Bucket: '', /* required */
          Enabled: false, /* required */
          IncludeCookies: false, /* required */
          Prefix: '' /* required */
        },
        PriceClass: 'PriceClass_All',
        WebACLId: ''
      }
    };

    if(acmCertArn) {
      cloudFrontParams.DistributionConfig.ViewerCertificate = {
        ACMCertificateArn: acmCertArn,
        CertificateSource: 'acm',
        MinimumProtocolVersion: 'TLSv1',
        SSLSupportMethod: 'sni-only'
      };
    }

    return cloudFrontParams;
  }
}

module.exports = CloudFrontClient;
<commit_msg>added queryStringCacheKeys<commit_after>'use strict';
const AWS = require('aws-sdk');
const __ = require('lodash');
const uuid = require('uuid');
const BluebirdPromise = require('bluebird');

const BaseClient = require('./baseClient');

AWS.config.setPromisesDependency(BluebirdPromise);

class CloudFrontClient extends BaseClient {

  constructor(accessKey = '', secretKey = '') {
    // Calls super and sets the region to null
    super(accessKey, secretKey, null);
  }

  get _awsCloudFrontClient() {

    if(!this._internalCloudFrontClient) {
      const cloudfrontParams = {
        apiVersion: '2016-01-28',
        accessKeyId: this._accessKey,
        secretAccessKey: this._secretKey
      };
      this._internalCloudFrontClient = new AWS.CloudFront(cloudfrontParams);
    }

    return this._internalCloudFrontClient;
  }

  /**
   *
   * @param params
   * @return {Promise.<D>}
   */
  createOrUpdateCloudFrontDistribution(params) {
    return this._getDistributionByCName(params.cname).then(distribution => {
      if(!__.isEmpty(distribution) && !__.isNil(distribution)) {
        if(!this._isDistributionOutOfDate(distribution, params)) {
          this.logMessage(`CloudFront Distribution already exist. No action taken. [Cname: ${params.cname}]`);
          return distribution;
        } else {
          this.logMessage(`CloudFront distribution already exist, but out of date.  Updating Cloudfront. [Cname: ${params.cname}]`);
          return this._updateCloudFrontDistribution(distribution, params);
        }
      } else {
        return this._createCloudFrontDistribution(params);
      }
    });
  }

  /**
   *
   * @param params
   * @return {Promise<D>}
   * @private
   */
  _createCloudFrontDistribution(params) {
    this.logMessage(`Creating Cloud Front Distribution. [Cname: ${params.cname}]`);

    const cloudFrontParams = this._buildDistributionConfig(params);

    let distribution;
    let createDistributionPromise = this._awsCloudFrontClient.createDistribution(cloudFrontParams).promise();
    return createDistributionPromise.then(result => {

      distribution = result.Distribution;

      const waitForParams = {
        Id: distribution.Id
      };

      this.logMessage(`Waiting for CloudFront Distribution to deploy. [CloudFront Id: ${distribution.Id}] [Cname: ${params.cname}]`);
      return this._awsCloudFrontClient.waitFor('distributionDeployed', waitForParams).promise().catch(err => {
        this.logMessage(`First waitFor failed for [CloudFront Id: ${distribution.Id}] TRYING AGAIN!`);
        return this._awsCloudFrontClient.waitFor('distributionDeployed', waitForParams).promise();
      });
    }).then(() => {
      this.logMessage(`Distribution deployed! [CloudFront Id: ${distribution.Id}] [Cname: ${params.cname}]`);
      return distribution;
    });
  }

  /**
   *
   * @param distribution
   * @param params
   * @return {Promise.<TResult>}
   * @private
   */
  _updateCloudFrontDistribution(distribution, params) {
    this.logMessage(`Updating Cloud Front Distribution. [Cname: ${params.cname}]`);

    let cloudFrontParams = this._buildDistributionConfig(params);
    cloudFrontParams.Id = distribution.Id;
    cloudFrontParams.IfMatch = distribution.ETag;

    let updatedDistribution;
    let updateDistributionPromise = this._awsCloudFrontClient.updateDistribution(cloudFrontParams).promise();
    return updateDistributionPromise.then(result => {

      updatedDistribution = result.Distribution;

      const waitForParams = {
        Id: updatedDistribution.Id
      };

      this.logMessage(`Waiting for CloudFront Distribution to deploy. [CloudFront Id: ${updatedDistribution.Id}] [Cname: ${params.cname}]`);
      return this._awsCloudFrontClient.waitFor('distributionDeployed', waitForParams).promise().catch(err => {
        this.logMessage(`First waitFor failed for [CloudFront Id: ${updatedDistribution.Id}] TRYING AGAIN!`);
        return this._awsCloudFrontClient.waitFor('distributionDeployed', waitForParams).promise();
      });
    }).then(() => {
      this.logMessage(`Distribution deployed! [CloudFront Id: ${updatedDistribution.Id}] [Cname: ${params.cname}]`);
      return updatedDistribution;
    });
  }

  /**
   *
   * @param cname
   * @return {Promise.<CloudFrontDistribution>}
   * @private
   */
  _getDistributionByCName(cname) {
    this.logMessage(`Executing getDistributionByCName. [Cname: ${cname}]`);
    const params = {};

    let listDistributionsPromise = this._awsCloudFrontClient.listDistributions(params).promise();

    return listDistributionsPromise.then(data => {
      let distribution = {};
      if(data && data.DistributionList && data.DistributionList.Items && data.DistributionList.Items.length > 0) {
        const distributionList = data.DistributionList.Items;
        distribution = distributionList.find(obj => {
          return obj.Aliases.Quantity > 0 && __.includes(obj.Aliases.Items, cname);
        });
      }

      if(!__.isNil(distribution) && !__.isEmpty(distribution)) {
        this.logMessage(`Distribution found! [Cname: ${cname}]`);
        let getDistributionParams = {
          Id: distribution.Id
        };

        let getDistributionPromise = this._awsCloudFrontClient.getDistribution(getDistributionParams).promise();

        return getDistributionPromise.then(getDistributionResult => {

          //we add the ETag to the distribution
          let localDistribution = getDistributionResult.Distribution;
          localDistribution.ETag = getDistributionResult.ETag;
          return localDistribution;
        });
      } else {
        this.logMessage(`Distribution not found! [Cname: ${cname}]`);
        return {};
      }
    });
  }

  /**
   *
   * @param distribution
   * @param params
   * @return {boolean}
   * @private
   */
  _isDistributionOutOfDate(distribution, params) {

    const {cname, acmCertArn, comment, originName, originDomainName, originPath, pathPattern, originProtocolPolicy} = params;

    let computedOriginProtocolPolicy = originProtocolPolicy || 'match-viewer';

    //cname
    let foundAliasIndex = __.indexOf(distribution.DistributionConfig.Aliases.Items, cname);
    if(foundAliasIndex < 0) {
      return true;
    }

    //acmCertArn
    if(distribution.DistributionConfig.ViewerCertificate.ACMCertificateArn !== acmCertArn) {
      return true;
    }

    //comment
    if(distribution.DistributionConfig.Comment !== comment) {
      return true;
    }

    //originName

    //originDomainName
    let foundOrigin = __.find(distribution.DistributionConfig.Origins.Items, {Id: originName});
    if(!foundOrigin || foundOrigin.DomainName !== originDomainName) {
      return true;
    }

    //originPath
    if(!foundOrigin || foundOrigin.OriginPath !== originPath) {
      return true;
    }

    //originProtocolPolicy
    if(!foundOrigin || foundOrigin.CustomOriginConfig.OriginProtocolPolicy !== computedOriginProtocolPolicy) {
      return true;
    }

    //pathPattern

    return false;
  }

  _buildDistributionConfig(params) {
    const {cname, acmCertArn, comment, originName, originDomainName, originPath, pathPattern, originProtocolPolicy} = params;

    let computedOriginProtocolPolicy = originProtocolPolicy || 'match-viewer';

    const cloudFrontParams = {
      DistributionConfig: { /* required */
        CallerReference: uuid(), /* required */
        Comment: comment, /* required */
        DefaultCacheBehavior: { /* required */
          ForwardedValues: { /* required */
            Cookies: { /* required */
              Forward: 'none', /* required */
              WhitelistedNames: {
                Quantity: 0, /* required */
                Items: []
              }
            },
            QueryString: false, /* required */
            Headers: {
              Quantity: 4, /* required */
              Items: [
                'x-***REMOVED***-version',
                'x-***REMOVED***-test',
                'Content-Type',
                'authorization'
              ]
            },
            QueryStringCacheKeys: {
              Quantity: 0, /* required */
              Items: []
            }
          },
          MinTTL: 0, /* required */
          TargetOriginId: originName, /* required */
          TrustedSigners: { /* required */
            Enabled: false, /* required */
            Quantity: 0, /* required */
            Items: []
          },
          ViewerProtocolPolicy: 'allow-all', /* required */
          AllowedMethods: {
            Items: [ /* required */
              'POST',
              'HEAD',
              'PATCH',
              'DELETE',
              'PUT',
              'GET',
              'OPTIONS'
            ],
            Quantity: 7, /* required */
            CachedMethods: {
              Items: [ /* required */
                'HEAD',
                'GET',
                'OPTIONS'
              ],
              Quantity: 3 /* required */
            }
          },
          Compress: false,
          DefaultTTL: 0,
          MaxTTL: 0,
          SmoothStreaming: false
        },
        Enabled: true, /* required */
        Origins: { /* required */
          Quantity: 1, /* required */
          Items: [
            {
              DomainName: originDomainName, /* required */
              Id: originName, /* required */
              CustomHeaders: {
                Quantity: 0, /* required */
                Items: []
              },
              CustomOriginConfig: {
                HTTPPort: 80, /* required */
                HTTPSPort: 443, /* required */
                OriginProtocolPolicy: computedOriginProtocolPolicy, /* required */
                OriginSslProtocols: {
                  Items: [ /* required */
                    'TLSv1',
                    'TLSv1.1',
                    'TLSv1.2'
                  ],
                  Quantity: 3 /* required */
                }
              },
              OriginPath: originPath
            }
          ]
        },
        Aliases: {
          Quantity: 1, /* required */
          Items: [
            cname
          ]
        },
        CacheBehaviors: {
          Quantity: 1, /* required */
          Items: [
            {
              ForwardedValues: { /* required */
                Cookies: { /* required */
                  Forward: 'none', /* required */
                  WhitelistedNames: {
                    Quantity: 0, /* required */
                    Items: []
                  }
                },
                QueryString: false, /* required */
                Headers: {
                  Quantity: 0, /* required */
                  Items: []
                }
              },
              MinTTL: 0, /* required */
              PathPattern: pathPattern, /* required */
              TargetOriginId: originName, /* required */
              TrustedSigners: { /* required */
                Enabled: false, /* required */
                Quantity: 0, /* required */
                Items: []
              },
              ViewerProtocolPolicy: 'allow-all', /* required */
              AllowedMethods: {
                Items: [ /* required */
                  'POST',
                  'HEAD',
                  'PATCH',
                  'DELETE',
                  'PUT',
                  'GET',
                  'OPTIONS'
                ],
                Quantity: 7, /* required */
                CachedMethods: {
                  Items: [ /* required */
                    'HEAD',
                    'GET',
                    'OPTIONS'
                  ],
                  Quantity: 3 /* required */
                }
              },
              Compress: true,
              DefaultTTL: 0,
              MaxTTL: 0,
              SmoothStreaming: false
            }
          ]
        },
        CustomErrorResponses: {
          Quantity: 0, /* required */
          Items: []
        },
        DefaultRootObject: '',
        HttpVersion: 'http2',
        IsIPV6Enabled: true,
        Logging: {
          Bucket: '', /* required */
          Enabled: false, /* required */
          IncludeCookies: false, /* required */
          Prefix: '' /* required */
        },
        PriceClass: 'PriceClass_All',
        WebACLId: ''
      }
    };

    if(acmCertArn) {
      cloudFrontParams.DistributionConfig.ViewerCertificate = {
        ACMCertificateArn: acmCertArn,
        CertificateSource: 'acm',
        MinimumProtocolVersion: 'TLSv1',
        SSLSupportMethod: 'sni-only'
      };
    }

    return cloudFrontParams;
  }
}

module.exports = CloudFrontClient;
<|endoftext|>


<commit_before>/* Module: Logger -- Event logging for environments and modules; log searches. */

var Module = require('./Module.js');
var winston = require('winston');
var moment = require('moment');
var fs = require('fs');
var cp = require('child_process');

class ModLogger extends Module {

    get requiredParams() { return [
        'basePath',             //Path to the logs directory
        'logs'                  //List of: {outputFile: TEMPLATE, channels: [LOGCHANNEL, ...]} where the outputFiles are moment templates relative to basePath (must end in .log)
    ]; }

    get optionalParams() { return [
        'templateJoin',         //Template for join event logs. Placeholders: %(MOMENT_FORMAT)% %env% %userid% %user% %channelid% %channel%
        'templatePart',         //Template for part event logs. Placeholders: %(MOMENT_FORMAT)% %env% %userid% %user% %channelid% %channel% %reason%
        'templateMessage',      //Template for message event logs. Placeholders: %(MOMENT_FORMAT)% %env% %userid% %user% %channelid% %channel% %type% %message%
        'maxResults'            //Maximum results per search
    ]; }

    get requiredModules() { return [
        'Commands'
    ]; }

    constructor(name) {
        super('Logger', name);

        this._params['templateJoin'] = '%(YYYY-MM-DD HH:mm:ss)% {%env%} [%channelid%] * Joins: %userid%';
        this._params['templatePart'] = '%(YYYY-MM-DD HH:mm:ss)% {%env%} [%channelid%] * Parts: %userid% (%reason%)';
        this._params['templateMessage'] = '%(YYYY-MM-DD HH:mm:ss)% {%env%} [%channelid%] <%userid%> %message%';
        this._params['maxResults'] = 5;

        this._logs = [];  //Initialized with param('logs') but each item also contains logger (points to winston logger).
        this._channels = {};
    }

    initialize(envs, mods, moduleRequest) {
        if (!super.initialize(envs, mods, moduleRequest)) return false;

        //Index channels

        this._logs = this.param('logs');
        for (let log of this._logs) {
            if (!log.outputFile || !log.outputFile.match(/\.log\]$/)) continue;
            if (!log.channels || !log.channels.length) continue;

            log.logger = null;
            log.openPath = null;

            for (let channel of log.channels) {
                if (!this._channels[channel]) {
                    this._channels[channel] = [];
                }
                this._channels[channel].push(log);
            }
        }

        //Register callbacks

        for (var envname in envs) {
            envs[envname].registerOnJoin(this.onJoin, this);
            envs[envname].registerOnPart(this.onPart, this);
            envs[envname].registerOnMessage(this.onMessage, this);
        }

        this.mod('Commands').registerCommand('grep', {
            description: "Search the event logs.",
            args: ["pattern", "results", "filepattern"],
            minArgs: 1,
            permissions: ["administrator", "moderator", "trusted"]
        }, (env, type, userid, command, args, handle, reply) => {

            var filepattern = null;
            if (args.filepattern) {
                filepattern = args.filepattern;
                let m = filepattern.match(/^\/(.*)\/$/);
                if (m) {
                    filepattern = m[1];
                } else {
                    filepattern = '^.*' + filepattern.replace(/ /g, '.*') + '.*$';
                }
                filepattern = RegExp(filepattern);
            }

            var pattern = args.pattern;
            let m = pattern.match(/^\/(.*)\/$/);
            if (m) {
                pattern = m[1];
            } else {
                pattern = '^.*' + pattern.replace(/ /g, '.*') + '.*$';
            }

            var maxResults = this.param('maxResults');
            if (args.results) maxResults = Math.min(maxResults, args.results);

            var results = 0;

            for (let loginfo of this.getLogPaths(filepattern)) {
                let logpath = loginfo[0];
                let logname = loginfo[1];

                let lines = "";
                try {
                    lines = cp.execSync('grep -E ' + escapeShell(pattern) + ' -- ' + escapeShell(logpath) + ' 2>/dev/null');
                } catch (e) {}
                lines = lines.toString().split("\n");

                for (let line of lines) {
                    if (!line.trim()) continue;

                    reply(logname + ' ' + line);

                    results += 1;
                    if (maxResults && results >= maxResults) break;
                }

                if (maxResults && results >= maxResults) break;
            }

            if (results) {
                reply('Found ' + results + ' result' + (results != 1 ? 's' : '') + (results == maxResults ? ' (max)' : '') + '.');
            } else {
                reply('Found nothing.');
            }

            return true;
        });

        return true;
    };

    // # Module code below this line #

    //Write to log; call this from dependent modules to log custom events

    write(logchannel, message) {
        if (!logchannel || !this._channels[logchannel]) return 0;

        var writes = 0;
        for (let log of this._channels[logchannel]) {

            if (!this.ready(log)) {
                if (!log.warned) {
                    this.log('warning', 'Unable to open event log with template "' + log.outputFile + '" for channel "' + logchannel + '".');
                    log.warned = true;
                }
                continue;
            }

            log.logger.info(message);
            writes += 1;
        }

        return writes;
    }

    //Write to log using a template and placeholder values

    templateWrite(logchannel, template, fields) {
        if (!fields) fields = {};
        if (!template) template = "";
        var message = template;

        message = message.replace(/%\(([^)]+)\)%/g, (match, format) => {
            return moment().format(format);
        });

        message = message.replace(/%([^%]+)%/g, (match, placeholder) => {
            if (fields[placeholder]) return fields[placeholder];
            return "";
        });

        return this.write(logchannel, message);
    }

    //Write to log using a template informally defined in this module's config; call from dependent modules if desired

    templateNameWrite(logchannel, templatename, fields) {
        if (!templatename) return false;

        var param = 'template' + templatename.charAt(0).toUpperCase() + templatename.slice(1);
        if (!this.param(param)) return false;

        return this.templateWrite(logchannel, this.param(param), fields);
    }

    //Event handlers

    onJoin(env, authorid, channelid, rawobj) {
        this.templateWrite("join", this.param("templateJoin"), {env: env.name, userid: authorid, user: env.idToDisplayName(authorid), channelid: channelid, channel: env.channelIdToDisplayName(channelid)});
    }

    onPart(env, authorid, channelid, reason, rawobj) {
        var reasonstr = reason;
        if (typeof reason == "object") {
            reasonstr = reason[0];
            if (reason[2]) reasonstr += ' by ' + reason[2];
            if (reason[1]) reasonstr += ' (' + reason[1] + ')';
        }
        this.templateWrite("part", this.param("templatePart"), {env: env.name, userid: authorid, user: env.idToDisplayName(authorid), channelid: channelid, channel: env.channelIdToDisplayName(channelid), reason: reasonstr});
    }

    onMessage(env, type, message, authorid, channelid, rawobj) {
        var channel = 'default';
        if (type == "regular" || type == "action") channel = 'public';
        if (type == "private" || type == "privateaction") channel = 'private';
        this.templateWrite(channel, this.param("templateMessage"), {env: env.name, userid: authorid, user: env.idToDisplayName(authorid), channelid: channelid, channel: env.channelIdToDisplayName(channelid), type: type, message: env.normalizeFormatting(message)});
    }

    //Auxiliary - Open or reopen a logger

    ready(log) {
        if (!log.outputFile) return false;

        var desiredPath = this.param('basePath') + '/' + moment().format(log.outputFile);
        if (!log.logger || log.openPath != desiredPath) {
            log.openPath = desiredPath;
            this.log('Log open: ' + desiredPath);

            log.logger = new (winston.Logger)({
                transports: [
                    new (winston.transports.File)({
                        filename: log.openPath,
                        json: false,
                        timestamp: () => "",
                        prettyPrint: true,
                        formatter: (args) => args.message
                    })
                ]
            });
        }

        return !!log.logger;
    }

    //Auxiliary - List existing log files

    getLogPaths(filter) {
        var result = [];
        var paths = [this.param('basePath')];
        var path;
        while (path = paths.shift()) {
            for (let file of fs.readdirSync(path)) {
                if (file.match(/^\./)) continue;
                let filepath = path + '/' + file;
                if (filter && !filter.exec(filepath)) continue;
                if (fs.statSync(filepath).isDirectory()) {
                    paths.push(filepath);
                } else if (file.match(/\.log$/)) {
                    result.push([filepath, file]);
                }
            }
        }
        return result;
    };

}

function escapeShell(cmd) {
    return '"' + cmd.replace(/(["\s'$`\\])/g, '\\$1') + '"';
};

module.exports = ModLogger;
<commit_msg>!grep returns newest results instead of oldest<commit_after>/* Module: Logger -- Event logging for environments and modules; log searches. */

var Module = require('./Module.js');
var winston = require('winston');
var moment = require('moment');
var fs = require('fs');
var cp = require('child_process');

class ModLogger extends Module {

    get requiredParams() { return [
        'basePath',             //Path to the logs directory
        'logs'                  //List of: {outputFile: TEMPLATE, channels: [LOGCHANNEL, ...]} where the outputFiles are moment templates relative to basePath (must end in .log)
    ]; }

    get optionalParams() { return [
        'templateJoin',         //Template for join event logs. Placeholders: %(MOMENT_FORMAT)% %env% %userid% %user% %channelid% %channel%
        'templatePart',         //Template for part event logs. Placeholders: %(MOMENT_FORMAT)% %env% %userid% %user% %channelid% %channel% %reason%
        'templateMessage',      //Template for message event logs. Placeholders: %(MOMENT_FORMAT)% %env% %userid% %user% %channelid% %channel% %type% %message%
        'maxResults'            //Maximum results per search
    ]; }

    get requiredModules() { return [
        'Commands'
    ]; }

    constructor(name) {
        super('Logger', name);

        this._params['templateJoin'] = '%(YYYY-MM-DD HH:mm:ss)% {%env%} [%channelid%] * Joins: %userid%';
        this._params['templatePart'] = '%(YYYY-MM-DD HH:mm:ss)% {%env%} [%channelid%] * Parts: %userid% (%reason%)';
        this._params['templateMessage'] = '%(YYYY-MM-DD HH:mm:ss)% {%env%} [%channelid%] <%userid%> %message%';
        this._params['maxResults'] = 5;

        this._logs = [];  //Initialized with param('logs') but each item also contains logger (points to winston logger).
        this._channels = {};
    }

    initialize(envs, mods, moduleRequest) {
        if (!super.initialize(envs, mods, moduleRequest)) return false;

        //Index channels

        this._logs = this.param('logs');
        for (let log of this._logs) {
            if (!log.outputFile || !log.outputFile.match(/\.log\]$/)) continue;
            if (!log.channels || !log.channels.length) continue;

            log.logger = null;
            log.openPath = null;

            for (let channel of log.channels) {
                if (!this._channels[channel]) {
                    this._channels[channel] = [];
                }
                this._channels[channel].push(log);
            }
        }

        //Register callbacks

        for (var envname in envs) {
            envs[envname].registerOnJoin(this.onJoin, this);
            envs[envname].registerOnPart(this.onPart, this);
            envs[envname].registerOnMessage(this.onMessage, this);
        }

        this.mod('Commands').registerCommand('grep', {
            description: "Search the event logs.",
            args: ["pattern", "results", "filepattern"],
            minArgs: 1,
            permissions: ["administrator", "moderator", "trusted"]
        }, (env, type, userid, command, args, handle, reply) => {

            var filepattern = null;
            if (args.filepattern) {
                filepattern = args.filepattern;
                let m = filepattern.match(/^\/(.*)\/$/);
                if (m) {
                    filepattern = m[1];
                } else {
                    filepattern = '^.*' + filepattern.replace(/ /g, '.*') + '.*$';
                }
                filepattern = RegExp(filepattern);
            }

            var pattern = args.pattern;
            let m = pattern.match(/^\/(.*)\/$/);
            if (m) {
                pattern = m[1];
            } else {
                pattern = '^.*' + pattern.replace(/ /g, '.*') + '.*$';
            }

            var maxResults = this.param('maxResults');
            if (args.results) maxResults = Math.min(maxResults, args.results);

            var results = 0;

            for (let loginfo of this.getLogPaths(filepattern)) {
                let logpath = loginfo[0];
                let logname = loginfo[1];

                let lines = "";
                try {
                    lines = cp.execSync('grep -E ' + escapeShell(pattern) + ' -- ' + escapeShell(logpath) + ' 2>/dev/null');
                } catch (e) {}
                lines = lines.toString().split("\n").reverse();

                for (let line of lines) {
                    if (!line.trim()) continue;

                    reply(logname + ' ' + line);

                    results += 1;
                    if (maxResults && results >= maxResults) break;
                }

                if (maxResults && results >= maxResults) break;
            }

            if (results) {
                reply('Found ' + results + ' result' + (results != 1 ? 's' : '') + (results == maxResults ? ' (max)' : '') + '.');
            } else {
                reply('Found nothing.');
            }

            return true;
        });

        return true;
    };

    // # Module code below this line #

    //Write to log; call this from dependent modules to log custom events

    write(logchannel, message) {
        if (!logchannel || !this._channels[logchannel]) return 0;

        var writes = 0;
        for (let log of this._channels[logchannel]) {

            if (!this.ready(log)) {
                if (!log.warned) {
                    this.log('warning', 'Unable to open event log with template "' + log.outputFile + '" for channel "' + logchannel + '".');
                    log.warned = true;
                }
                continue;
            }

            log.logger.info(message);
            writes += 1;
        }

        return writes;
    }

    //Write to log using a template and placeholder values

    templateWrite(logchannel, template, fields) {
        if (!fields) fields = {};
        if (!template) template = "";
        var message = template;

        message = message.replace(/%\(([^)]+)\)%/g, (match, format) => {
            return moment().format(format);
        });

        message = message.replace(/%([^%]+)%/g, (match, placeholder) => {
            if (fields[placeholder]) return fields[placeholder];
            return "";
        });

        return this.write(logchannel, message);
    }

    //Write to log using a template informally defined in this module's config; call from dependent modules if desired

    templateNameWrite(logchannel, templatename, fields) {
        if (!templatename) return false;

        var param = 'template' + templatename.charAt(0).toUpperCase() + templatename.slice(1);
        if (!this.param(param)) return false;

        return this.templateWrite(logchannel, this.param(param), fields);
    }

    //Event handlers

    onJoin(env, authorid, channelid, rawobj) {
        this.templateWrite("join", this.param("templateJoin"), {env: env.name, userid: authorid, user: env.idToDisplayName(authorid), channelid: channelid, channel: env.channelIdToDisplayName(channelid)});
    }

    onPart(env, authorid, channelid, reason, rawobj) {
        var reasonstr = reason;
        if (typeof reason == "object") {
            reasonstr = reason[0];
            if (reason[2]) reasonstr += ' by ' + reason[2];
            if (reason[1]) reasonstr += ' (' + reason[1] + ')';
        }
        this.templateWrite("part", this.param("templatePart"), {env: env.name, userid: authorid, user: env.idToDisplayName(authorid), channelid: channelid, channel: env.channelIdToDisplayName(channelid), reason: reasonstr});
    }

    onMessage(env, type, message, authorid, channelid, rawobj) {
        var channel = 'default';
        if (type == "regular" || type == "action") channel = 'public';
        if (type == "private" || type == "privateaction") channel = 'private';
        this.templateWrite(channel, this.param("templateMessage"), {env: env.name, userid: authorid, user: env.idToDisplayName(authorid), channelid: channelid, channel: env.channelIdToDisplayName(channelid), type: type, message: env.normalizeFormatting(message)});
    }

    //Auxiliary - Open or reopen a logger

    ready(log) {
        if (!log.outputFile) return false;

        var desiredPath = this.param('basePath') + '/' + moment().format(log.outputFile);
        if (!log.logger || log.openPath != desiredPath) {
            log.openPath = desiredPath;
            this.log('Log open: ' + desiredPath);

            log.logger = new (winston.Logger)({
                transports: [
                    new (winston.transports.File)({
                        filename: log.openPath,
                        json: false,
                        timestamp: () => "",
                        prettyPrint: true,
                        formatter: (args) => args.message
                    })
                ]
            });
        }

        return !!log.logger;
    }

    //Auxiliary - List existing log files

    getLogPaths(filter) {
        var result = [];
        var paths = [this.param('basePath')];
        var path;
        while (path = paths.shift()) {
            for (let file of fs.readdirSync(path)) {
                if (file.match(/^\./)) continue;
                let filepath = path + '/' + file;
                if (filter && !filter.exec(filepath)) continue;
                if (fs.statSync(filepath).isDirectory()) {
                    paths.push(filepath);
                } else if (file.match(/\.log$/)) {
                    result.push([filepath, file]);
                }
            }
        }
        return result.reverse();
    };

}

function escapeShell(cmd) {
    return '"' + cmd.replace(/(["\s'$`\\])/g, '\\$1') + '"';
};

module.exports = ModLogger;
<|endoftext|>


<commit_before>define(function(require) {

  var $ = require('jquery');
  var _ = require('underscore');
  var Backbone = require('backbone');
  var Doc = require('models/doc');
  var remoteStorage = require('remotestorage');

  var remoteStorageDocuments = require('remotestorage-documents');
  var rsSync = require('rs-adapter');
  var welcome = require('text!templates/welcome.html');

  var Docs = Backbone.Collection.extend({

    model: Doc,

    sync: rsSync,

    initialize: function(models, options) {

      _.bindAll(this, 'sort', 'save', 'welcome', 'rsChange');

      this
        .on('change:lastEdited', this.sort)
        .on('change:lastEdited', this.save);

      this.once('sync', this.welcome);

      this.initRemotestorage();

    },

    addNew: _.throttle(function(options) {
      return this.add( _.defaults(options || {}, {
        id: this.remote.uuid(),
        lastEdited: Date.now()
      }) );
    }, 1000, { leading: true }),

    // Sort by 'lastEdited'
    comparator: function(first, second) {
      return first.get('lastEdited') > second.get('lastEdited') ? -1 : 1 ;
    },

    save: function(doc) {
      doc.throttledSave();
    },

    before: function(id) {
      return this.at( this.indexOf( this.get(id) ) - 1 );
    },

    after: function(id) {
      return this.at( this.indexOf( this.get(id) ) + 1 );
    },

    welcome: function () {
      var data = { content: welcome };
      if (this.isEmpty()) this.addNew(data);
    },

    remote: null,

    initRemotestorage: function() {
      var docs = this;

      var origHash = document.location.hash;

      remoteStorage.on('disconnect', function() {
        docs.reset().welcome();
      });

      remoteStorage.access.claim('documents', 'rw');

      docs.remote = remoteStorageDocuments.privateList('notes');
      docs.remote.on('change', docs.rsChange);

      setTimeout(function() {
        var md = origHash.match(/access_token=([^&]+)/);
        if ( md && (! remoteStorage.getBearerToken()) ) {
          // backbone stole our access token
          remoteStorage.setBearerToken(md[1]);
        }
      }, 0);
    },

    events: [],

    rsChange: function (event) {
      this.events.push(event);
      this.handleEvents();
    },

    handleEvents: _.debounce(function() {
      _.each(this.events, function(event) {
        if (event.origin !== 'window') {
          // TODO: normally this should work
          // but I completely disabled deletion of doc on remote events
          // because right now sometimes randomly docs disappear
          // after syncing different devices.
          // this is really annoying -.-
          //
          // if (event.oldValue && !event.newValue) return this.remove(event.oldValue);
          var existingDoc = this.get(event.newValue.id);
          if (!existingDoc) return this.add(event.newValue);
          var isNew = event.newValue.lastEdited > existingDoc.get('lastEdited');
          if (isNew) this.set(event.newValue, { remove: false });
        }
      }, this);
      this.events = [];
    }, 400)

  });

  return Docs;

});
<commit_msg>unique welcome doc (by using id = "welcome"). fixes #166.<commit_after>define(function(require) {

  var $ = require('jquery');
  var _ = require('underscore');
  var Backbone = require('backbone');
  var Doc = require('models/doc');
  var remoteStorage = require('remotestorage');

  var remoteStorageDocuments = require('remotestorage-documents');
  var rsSync = require('rs-adapter');
  var welcome = require('text!templates/welcome.html');

  var Docs = Backbone.Collection.extend({

    model: Doc,

    sync: rsSync,

    initialize: function(models, options) {

      _.bindAll(this, 'sort', 'save', 'welcome', 'rsChange', 'uniqueWelcomeDoc');

      this
        .on('change:lastEdited', this.sort)
        .on('change:lastEdited', this.save)
        .on('add', this.uniqueWelcomeDoc);

      this.once('sync', this.welcome);

      this.initRemotestorage();

    },

    addNew: _.throttle(function(options) {
      return this.add( _.defaults(options || {}, {
        id: this.remote.uuid(),
        lastEdited: Date.now()
      }) );
    }, 1000, { leading: true }),

    // Sort by 'lastEdited'
    comparator: function(first, second) {
      return first.get('lastEdited') > second.get('lastEdited') ? -1 : 1 ;
    },

    save: function(doc) {
      doc.throttledSave();
    },

    before: function(id) {
      return this.at( this.indexOf( this.get(id) ) - 1 );
    },

    after: function(id) {
      return this.at( this.indexOf( this.get(id) ) + 1 );
    },

    welcome: function () {
      var data = { id: 'welcome', content: welcome };
      if (this.isEmpty()) this.addNew(data);
    },

    remote: null,

    initRemotestorage: function() {
      var docs = this;

      var origHash = document.location.hash;

      remoteStorage.on('disconnect', function() {
        docs.reset().welcome();
      });

      remoteStorage.access.claim('documents', 'rw');

      docs.remote = remoteStorageDocuments.privateList('notes');
      docs.remote.on('change', docs.rsChange);

      setTimeout(function() {
        var md = origHash.match(/access_token=([^&]+)/);
        if ( md && (! remoteStorage.getBearerToken()) ) {
          // backbone stole our access token
          remoteStorage.setBearerToken(md[1]);
        }
      }, 0);
    },

    events: [],

    rsChange: function (event) {
      this.events.push(event);
      this.handleEvents();
    },

    handleEvents: _.debounce(function() {
      _.each(this.events, function(event) {
        if (event.origin !== 'window') {
          // TODO: normally this should work
          // but I completely disabled deletion of doc on remote events
          // because right now sometimes randomly docs disappear
          // after syncing different devices.
          // this is really annoying -.-
          //
          // if (event.oldValue && !event.newValue) return this.remove(event.oldValue);
          var existingDoc = this.get(event.newValue.id);
          if (!existingDoc) return this.add(event.newValue);
          var isNew = event.newValue.lastEdited > existingDoc.get('lastEdited');
          if (isNew) this.set(event.newValue, { remove: false });
        }
      }, this);
      this.events = [];
    }, 400),

    uniqueWelcomeDoc: function() {
      var welcomes = this.where({ id: 'welcome' });
      if (welcomes.length > 1) this.remove(welcomes[0]);
    }

  });

  return Docs;

});
<|endoftext|>


<commit_before>/*
 * Licensed to Jasig under one or more contributor license
 * agreements. See the NOTICE file distributed with this work
 * for additional information regarding copyright ownership.
 * Jasig licenses this file to you 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.
 */

/**
 * PortalWindowView.js contains the main home view, controlled
 * by PortalWindowController.js
 */

/**
* @constructor
*/
var PortalWindowView = function (facade) {
    var app = facade, init, _self = this, Styles, UI, LocalDictionary, Device, WindowManager, Portal, SettingsWindow, PortalWindow, GridView,
    portlets, isGuestLayout,
    win, contentLayer, gridView,
    titleBar, activityIndicator, guestNotificationView,
    createWindow, createContentLayer, createGridView, drawChrome, addGuestLayoutIndicator;

    init = function () {
        Styles = app.styles;
        UI = app.UI;
        Portal = app.models.portalProxy;
        Device = app.models.deviceProxy;
        LocalDictionary = app.localDictionary;
        WindowManager = app.models.windowManager;
        SettingsWindow = app.controllers.settingsWindowController;
        PortalWindow = app.controllers.portalWindowController;

        Ti.App.addEventListener('updatestylereference', function (e) {
            Styles = app.styles;
        });

        Ti.App.addEventListener('dimensionchanges', function (e) {
            if (contentLayer) {
                contentLayer.width = Styles.portalContentLayer.width;
                contentLayer.height = Styles.portalContentLayer.height;

            }
            guestNotificationView.top = win.height - Styles.titleBar.height - Styles.homeGuestNote.height;
        });
    };

    this.open = function (modules, options) {
        if (!GridView) {
            Ti.include('js/views/PortalGridView.js');
            GridView = new PortalGridView(app);
        }
        portlets = modules;

        createWindow();
        createContentLayer();

        if (options.isGuestLayout) {
            Ti.API.debug("Is guest layout");
            isGuestLayout = true;
            addGuestLayoutIndicator();
        }
        else {
            Ti.API.debug("Not guest layout");
            isGuestLayout = false;
        }

        createGridView();
        drawChrome();

        if (options.firstLoad) {
            _self.showActivityIndicator(LocalDictionary.gettingPortlets);
        }
        else {
            Ti.API.debug("This isn't the first time we're loading");
        }
    };

    this.close = function () {
        if (win && (Device.isIOS())) {
            win.close();
        }
        else if (win) {
            win.hide();
        }
    };

    this.updateModules = function (modules, options) {
        Ti.API.debug("updateModules() in PortalWindowView");
        if (options.isGuestLayout) {
            isGuestLayout = true;
        }
        else {
            isGuestLayout = false;
        }
        portlets = modules;
        if (WindowManager.getCurrentWindow() === PortalWindow.key) {
            createGridView();
        }
        if (isGuestLayout) {
            addGuestLayoutIndicator();
        }
    };

    this.showActivityIndicator = function (message) {
        if (activityIndicator) {
            if (message) {
                activityIndicator.setLoadingMessage(message);
            }
            activityIndicator.show();
        }
        else {
            Ti.API.error("Activity Indicator isn't defined.");
        }
    };

    this.hideActivityIndicator = function () {
        Ti.API.debug("hideActivityIndicator() in PortalWindowView");
        if (activityIndicator) {
            activityIndicator.hide();
        }
        else {
            Ti.API.debug("activityIndicator not defined.");
        }
    };

    this.alert = function (title, message) {
        Titanium.UI.createAlertDialog({ title: title,
            message: message, buttonNames: [LocalDictionary.OK]
            }).show();
    };

    createWindow = function () {
        if (!win || Device.isIOS()) {
            Ti.API.debug("Create and open the portal window");
            //We want to create a new window and redraw the whole UI each time on iOS
            win = Ti.UI.createWindow(Styles.portalWindow);
            win.open();
        }
        else if (win && !win.visible) {
            Ti.API.debug("Just show the portal window");
            win.show();
        }
    };

    createContentLayer = function () {
        contentLayer = Ti.UI.createView(Styles.portalContentLayer);
        win.add(contentLayer);
    };

    createGridView = function () {
        Ti.API.debug("Preparing to iterate through portlets in drawHomeGrid: " + portlets.length);

        contentLayer.add(GridView.getGridView({isGuestLayout: isGuestLayout }));
        GridView.updateGrid(portlets);
        _self.hideActivityIndicator();
    };

    drawChrome = function (options) {
        Ti.API.debug("drawChrome() in PortalWindowView");
        titleBar = UI.createTitleBar({
    	    title: LocalDictionary.homeTitle,
    	    settingsButton: true,
    	    homeButton: false
    	});
        win.add(titleBar);

    	activityIndicator = UI.createActivityIndicator();
        win.add(activityIndicator);
        activityIndicator.hide();
    };

    addGuestLayoutIndicator = function () {
        var guestNotificationLabel;

        guestNotificationView = Ti.UI.createView(Styles.homeGuestNote);
        guestNotificationView.top = win.height - Styles.titleBar.height - Styles.homeGuestNote.height;

        guestNotificationLabel = Ti.UI.createLabel(Styles.homeGuestNoteLabel);
        guestNotificationLabel.text = LocalDictionary.viewingGuestLayout;
        guestNotificationView.add(guestNotificationLabel);

        contentLayer.add(guestNotificationView);

        guestNotificationView.addEventListener('click', function (e){
            Ti.API.info("Clicked guest notification, opening settings");
            WindowManager.openWindow(SettingsWindow.key);
        });

    };

    init();
};<commit_msg>UMOBILE-93 added conditional to check for existence of guestlayoutview in portalwindowview orientation change handler.<commit_after>/*
 * Licensed to Jasig under one or more contributor license
 * agreements. See the NOTICE file distributed with this work
 * for additional information regarding copyright ownership.
 * Jasig licenses this file to you 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.
 */

/**
 * PortalWindowView.js contains the main home view, controlled
 * by PortalWindowController.js
 */

/**
* @constructor
*/
var PortalWindowView = function (facade) {
    var app = facade, init, _self = this, Styles, UI, LocalDictionary, Device, WindowManager, Portal, SettingsWindow, PortalWindow, GridView,
    portlets, isGuestLayout,
    win, contentLayer, gridView,
    titleBar, activityIndicator, guestNotificationView,
    createWindow, createContentLayer, createGridView, drawChrome, addGuestLayoutIndicator;

    init = function () {
        Styles = app.styles;
        UI = app.UI;
        Portal = app.models.portalProxy;
        Device = app.models.deviceProxy;
        LocalDictionary = app.localDictionary;
        WindowManager = app.models.windowManager;
        SettingsWindow = app.controllers.settingsWindowController;
        PortalWindow = app.controllers.portalWindowController;

        Ti.App.addEventListener('updatestylereference', function (e) {
            Styles = app.styles;
        });

        Ti.App.addEventListener('dimensionchanges', function (e) {
            if (contentLayer) {
                contentLayer.width = Styles.portalContentLayer.width;
                contentLayer.height = Styles.portalContentLayer.height;

            }
            if (guestNotificationView) {
                guestNotificationView.top = win.height - Styles.titleBar.height - Styles.homeGuestNote.height;
            }
        });
    };

    this.open = function (modules, options) {
        if (!GridView) {
            Ti.include('js/views/PortalGridView.js');
            GridView = new PortalGridView(app);
        }
        portlets = modules;

        createWindow();
        createContentLayer();

        if (options.isGuestLayout) {
            Ti.API.debug("Is guest layout");
            isGuestLayout = true;
            addGuestLayoutIndicator();
        }
        else {
            Ti.API.debug("Not guest layout");
            isGuestLayout = false;
        }

        createGridView();
        drawChrome();

        if (options.firstLoad) {
            _self.showActivityIndicator(LocalDictionary.gettingPortlets);
        }
        else {
            Ti.API.debug("This isn't the first time we're loading");
        }
    };

    this.close = function () {
        if (win && (Device.isIOS())) {
            win.close();
        }
        else if (win) {
            win.hide();
        }
    };

    this.updateModules = function (modules, options) {
        Ti.API.debug("updateModules() in PortalWindowView");
        if (options.isGuestLayout) {
            isGuestLayout = true;
        }
        else {
            isGuestLayout = false;
        }
        portlets = modules;
        if (WindowManager.getCurrentWindow() === PortalWindow.key) {
            createGridView();
        }
        if (isGuestLayout) {
            addGuestLayoutIndicator();
        }
    };

    this.showActivityIndicator = function (message) {
        if (activityIndicator) {
            if (message) {
                activityIndicator.setLoadingMessage(message);
            }
            activityIndicator.show();
        }
        else {
            Ti.API.error("Activity Indicator isn't defined.");
        }
    };

    this.hideActivityIndicator = function () {
        Ti.API.debug("hideActivityIndicator() in PortalWindowView");
        if (activityIndicator) {
            activityIndicator.hide();
        }
        else {
            Ti.API.debug("activityIndicator not defined.");
        }
    };

    this.alert = function (title, message) {
        Titanium.UI.createAlertDialog({ title: title,
            message: message, buttonNames: [LocalDictionary.OK]
            }).show();
    };

    createWindow = function () {
        if (!win || Device.isIOS()) {
            Ti.API.debug("Create and open the portal window");
            //We want to create a new window and redraw the whole UI each time on iOS
            win = Ti.UI.createWindow(Styles.portalWindow);
            win.open();
        }
        else if (win && !win.visible) {
            Ti.API.debug("Just show the portal window");
            win.show();
        }
    };

    createContentLayer = function () {
        contentLayer = Ti.UI.createView(Styles.portalContentLayer);
        win.add(contentLayer);
    };

    createGridView = function () {
        Ti.API.debug("Preparing to iterate through portlets in drawHomeGrid: " + portlets.length);

        contentLayer.add(GridView.getGridView({isGuestLayout: isGuestLayout }));
        GridView.updateGrid(portlets);
        _self.hideActivityIndicator();
    };

    drawChrome = function (options) {
        Ti.API.debug("drawChrome() in PortalWindowView");
        titleBar = UI.createTitleBar({
    	    title: LocalDictionary.homeTitle,
    	    settingsButton: true,
    	    homeButton: false
    	});
        win.add(titleBar);

    	activityIndicator = UI.createActivityIndicator();
        win.add(activityIndicator);
        activityIndicator.hide();
    };

    addGuestLayoutIndicator = function () {
        var guestNotificationLabel;

        guestNotificationView = Ti.UI.createView(Styles.homeGuestNote);
        guestNotificationView.top = win.height - Styles.titleBar.height - Styles.homeGuestNote.height;

        guestNotificationLabel = Ti.UI.createLabel(Styles.homeGuestNoteLabel);
        guestNotificationLabel.text = LocalDictionary.viewingGuestLayout;
        guestNotificationView.add(guestNotificationLabel);

        contentLayer.add(guestNotificationView);

        guestNotificationView.addEventListener('click', function (e){
            Ti.API.info("Clicked guest notification, opening settings");
            WindowManager.openWindow(SettingsWindow.key);
        });

    };

    init();
};<|endoftext|>


<commit_before>/*
 * The MIT License (MIT)
 *
 * Copyright (c) 2014 Apigee Corporation
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 * THE SOFTWARE.
 */

'use strict';

var _ = require('lodash');
var async = require('async');
var bp = require('body-parser');
var cHelpers = require('../lib/helpers');
var debug = require('debug')('swagger-tools:middleware:metadata');
var mHelpers = require('./helpers');
var parseurl = require('parseurl');
var pathToRegexp = require('path-to-regexp');

// Upstream middlewares
var jsonBodyParser = bp.json();
var parseQueryString = mHelpers.parseQueryString;
var queryParser = function (req, res, next) {
  if (!req.query) {
    req.query = parseQueryString(req);
  }

  return next();
};
var urlEncodedBodyParser = bp.urlencoded({extended: false});
var bodyParser = function (req, res, callback) {
  urlEncodedBodyParser(req, res, function (err) {
    if (err) {
      callback(err);
    } else {
      jsonBodyParser(req, res, callback);
    }
  });
};

// Helper functions
var expressStylePath = function expressStylePath (basePath, apiPath) {
  basePath = parseurl({url: basePath || '/'}).pathname || '/';

  // Make sure the base path starts with '/'
  if (basePath.charAt(0) !== '/') {
    basePath = '/' + basePath;
  }

  // Make sure the base path ends with '/'
  if (basePath.charAt(basePath.length - 1) !== '/') {
    basePath = basePath + '/';
  }

  // Make sure the api path does not start with '/' since the base path will end with '/'
  if (apiPath.charAt(0) === '/') {
    apiPath = apiPath.substring(1);
  }

  // Replace Swagger syntax for path parameters with Express' version (All Swagger path parameters are required)
  return (basePath + apiPath).replace(/{/g, ':').replace(/}/g, '');
};
var getParameterType = function getParameterType (schema) {
  var type = schema.type;

  if (!type && schema.schema) {
    type = schema.type;
  }

  if (!type) {
    type = 'object';
  }

  return type;
};
var convertValue = function convertValue (value, schema, type) {
  if (_.isUndefined(type)) {
    type = getParameterType(schema);
  }

  // If there is no value, do not convert it
  if (_.isUndefined(value)) {
    return value;
  }

  switch (type) {
  case 'array':
    value = _.map(value, function (item) {
      return convertValue(item, _.isArray(schema.items) ? schema.items[0] : schema.items);
    });

    break;

  case 'boolean':
    if (!_.isBoolean(value)) {
      value = value === 'true' || value === true ? true : false;
    }

    break;

  case 'integer':
    if (!_.isNumber(value)) {
      value = parseInt(value, 10);
    }

    break;

  case 'number':
    if (!_.isNumber(value)) {
      value = parseFloat(value);
    }

    break;

  case 'string':
    if (['date', 'date-time'].indexOf(schema.format) > -1 && !_.isDate(value)) {
      value = new Date(value);
    }

    break;

  }

  return value;
};
var processOperationParameters = function processOperationParameters (version, pathKeys, pathMatch, req, res, next) {
  var swaggerMetadata = req.swagger;
  var parameters = !_.isUndefined(swaggerMetadata) ?
                     (version === '1.2' ? swaggerMetadata.operation.parameters : swaggerMetadata.operationParameters) :
                     undefined;

  if (!parameters) {
    return next();
  }

  debug('  Processing Parameters');

  async.map(_.reduce(parameters, function (requestParsers, parameter) {
    var paramType = version === '1.2' ? parameter.paramType : parameter.schema.in;
    var parser;

    switch (paramType) {
    case 'body':
    case 'form':
    case 'formData':
      parser = bodyParser;

      break;

    case 'query':
      parser = queryParser;

      break;
    }

    if (parser && requestParsers.indexOf(parser) === -1) {
      requestParsers.push(parser);
    }

    return requestParsers;
  }, []), function (parser, callback) {
    parser(req, res, callback);
  }, function (err) {
    if (err) {
      return next(err);
    }

    _.each(parameters, function (parameterOrMetadata, index) {
      var parameter = version === '1.2' ? parameterOrMetadata : parameterOrMetadata.schema;
      var pType = getParameterType(parameter);
      var oVal;
      var value;

      debug('    %s', parameter.name);
      debug('      Type: %s%s', pType, !_.isUndefined(parameter.format) ? ' (format: ' + parameter.format + ')': '');

      // Located here to make the debug output pretty
      oVal = mHelpers.getParameterValue(version, parameter, pathKeys, pathMatch, req, debug);
      value = convertValue(oVal, parameter, pType);

      debug('      Value: %s', value);

      swaggerMetadata.params[parameter.name] = {
        path: version === '1.2' ?
                swaggerMetadata.operationPath.concat(['parameters', index.toString()]) :
                parameterOrMetadata.path,
        schema: parameter,
        originalValue: oVal,
        value: value
      };
    });

    return next();
  });
};
var processSwaggerDocuments = function processSwaggerDocuments (rlOrSO, apiDeclarations) {
  if (_.isUndefined(rlOrSO)) {
    throw new Error('rlOrSO is required');
  } else if (!_.isPlainObject(rlOrSO)) {
    throw new TypeError('rlOrSO must be an object');
  }

  var spec = cHelpers.getSpec(cHelpers.getSwaggerVersion(rlOrSO), true);
  var apiCache = {};
  var composeParameters = function composeParameters (apiPath, method, path, operation) {
    var cParams = [];
    var seenParams = [];

    _.each(operation.parameters, function (parameter, index) {
      cParams.push({
        path: apiPath.concat([method, 'parameters', index.toString()]),
        schema: parameter
      });

      seenParams.push(parameter.name + ':' + parameter.in);
    });

    _.each(path.parameters, function (parameter, index) {
      if (seenParams.indexOf(parameter.name + ':' + parameter.in) === -1) {
        cParams.push({
          path: apiPath.concat(['parameters', index.toString()]),
          schema: parameter
        });
      }
    });

    return cParams;
  };
  var createCacheEntry = function createCacheEntry (adOrSO, apiOrPath, indexOrName, indent) {
    var apiPath = spec.version === '1.2' ? apiOrPath.path : indexOrName;
    var expressPath = expressStylePath(adOrSO.basePath, spec.version === '1.2' ? apiOrPath.path: indexOrName);
    var keys = [];
    var handleSubPaths = !(rlOrSO.paths && rlOrSO.paths[apiPath]['x-swagger-router-handle-subpaths']);
    var re = pathToRegexp(expressPath, keys, { end: handleSubPaths });
    var cacheKey = re.toString();
    var cacheEntry;

    // This is an absolute path, use it as the cache key
    if (expressPath.indexOf('{') === -1) {
      cacheKey = expressPath;
    }

    debug(new Array(indent + 1).join(' ') + 'Found %s: %s',
          (spec.version === '1.2' ? 'API' : 'Path'),
          apiPath);

    cacheEntry = apiCache[cacheKey] = spec.version === '1.2' ?
      {
        api: apiOrPath,
        apiDeclaration: adOrSO,
        apiIndex: indexOrName,
        keys: keys,
        params: {},
        re: re,
        operations: {},
        resourceListing: rlOrSO
      } :
      {
        apiPath: indexOrName,
        path: apiOrPath,
        keys: keys,
        re: re,
        operations: {},
        swaggerObject: {
          original: rlOrSO,
          resolved: adOrSO
        }
      };

    return cacheEntry;
  };

  debug('  Identified Swagger version: %s', spec.version);

  if (spec.version === '1.2') {
    if (_.isUndefined(apiDeclarations)) {
      throw new Error('apiDeclarations is required');
    } else if (!_.isArray(apiDeclarations)) {
      throw new TypeError('apiDeclarations must be an array');
    }

    debug('  Number of API Declarations: %d', apiDeclarations.length);

    _.each(apiDeclarations, function (apiDeclaration, adIndex) {
      debug('  Processing API Declaration %d', adIndex);

      _.each(apiDeclaration.apis, function (api, apiIndex) {
        var cacheEntry = createCacheEntry(apiDeclaration, api, apiIndex, 4);

        cacheEntry.resourceIndex = adIndex;

        _.each(api.operations, function (operation, operationIndex) {
          cacheEntry.operations[operation.method.toLowerCase()] = {
            operation: operation,
            operationPath: ['apis', apiIndex.toString(), 'operations', operationIndex.toString()],
            operationParameters: operation.parameters
          };
        });
      });
    });
  } else {
    // To avoid running into issues with references throughout the Swagger object we will use the resolved version.
    // Getting the resolved version is an asynchronous process but since initializeMiddleware caches the resolved document
    // this is a synchronous action at this point.
    spec.resolve(rlOrSO, function (err, resolved) {
      // Gather the paths, their path regex patterns and the corresponding operations
      _.each(resolved.paths, function (path, pathName) {
        var cacheEntry = createCacheEntry(resolved, path, pathName, 2);

        _.each(['get', 'put', 'post', 'delete', 'options', 'head', 'patch'], function (method) {
          var operation = path[method];

          if (!_.isUndefined(operation)) {
            cacheEntry.operations[method] = {
              operation: operation,
              operationPath: ['paths', pathName, method],
              // Required since we have to compose parameters based on the operation and the path
              operationParameters: composeParameters(['paths', pathName], method, path, operation)
            };
          }
        });
      });
    });
  }

  return apiCache;
};

/**
 * Middleware for providing Swagger information to downstream middleware and request handlers.  For all requests that
 * match a Swagger path, 'req.swagger' will be provided with pertinent Swagger details.  Since Swagger 1.2 and 2.0
 * differ a bit, the structure of this object will change so please view the documentation below for more details:
 *
 *     https://github.com/apigee-127/swagger-tools/blob/master/docs/Middleware.md#swagger-metadata
 *
 * @param {object} rlOrSO - The Resource Listing (Swagger 1.2) or Swagger Object (Swagger 2.0)
 * @param {object[]} apiDeclarations - The array of API Declarations (Swagger 1.2)
 *
 * @returns the middleware function
 */
exports = module.exports = function swaggerMetadataMiddleware (rlOrSO, apiDeclarations) {
  debug('Initializing swagger-metadata middleware');

  var apiCache = processSwaggerDocuments(rlOrSO, apiDeclarations);
  var swaggerVersion = cHelpers.getSwaggerVersion(rlOrSO);

  if (_.isUndefined(rlOrSO)) {
    throw new Error('rlOrSO is required');
  } else if (!_.isPlainObject(rlOrSO)) {
    throw new TypeError('rlOrSO must be an object');
  }

  if (swaggerVersion === '1.2') {
    if (_.isUndefined(apiDeclarations)) {
      throw new Error('apiDeclarations is required');
    } else if (!_.isArray(apiDeclarations)) {
      throw new TypeError('apiDeclarations must be an array');
    }
  }

  return function swaggerMetadata (req, res, next) {
    var method = req.method.toLowerCase();
    var path = parseurl(req).pathname;
    var cacheEntry;
    var match;
    var metadata;

    try {
      cacheEntry = apiCache[path] || _.find(apiCache, function (metadata) {
        match = metadata.re.exec(path);
        return _.isArray(match);
      });

      debug('%s %s', req.method, req.url);
      debug('  Is a Swagger path: %s', !_.isUndefined(cacheEntry));

      // Request does not match an API defined in the Swagger document(s)
      if (!cacheEntry) {
        return next();
      }

      metadata = swaggerVersion === '1.2' ?
        {
          api: cacheEntry.api,
          apiDeclaration: cacheEntry.apiDeclaration,
          apiIndex: cacheEntry.apiIndex,
          params: {},
          resourceIndex: cacheEntry.resourceIndex,
          resourceListing: cacheEntry.resourceListing
        } :
        {
          apiPath : cacheEntry.apiPath,
          path: cacheEntry.path,
          params: {},
          swaggerObject: cacheEntry.swaggerObject.resolved
        };

      if (_.isPlainObject(cacheEntry.operations[method])) {
        metadata.operation = cacheEntry.operations[method].operation;
        metadata.operationPath = cacheEntry.operations[method].operationPath;

        if (swaggerVersion === '1.2') {
          metadata.authorizations = metadata.operation.authorizations || cacheEntry.apiDeclaration.authorizations;
        } else {
          metadata.operationParameters = cacheEntry.operations[method].operationParameters;
          metadata.security = metadata.operation.security || metadata.swaggerObject.security || [];
        }
      }

      metadata.swaggerVersion = swaggerVersion;

      req.swagger = metadata;

      debug('  Is a Swagger operation: %s', !_.isUndefined(metadata.operation));

      if (metadata.operation) {
        // Process the operation parameters
        return processOperationParameters(swaggerVersion, cacheEntry.keys, match, req, res, next, debug);
      } else {
        return next();
      }
    } catch (err) {
      mHelpers.debugError(err, debug);

      return next(err);
    }
  };
};
<commit_msg>Remove the try/catch block that could cause next to be called more than once in swagger-metadata<commit_after>/*
 * The MIT License (MIT)
 *
 * Copyright (c) 2014 Apigee Corporation
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 * THE SOFTWARE.
 */

'use strict';

var _ = require('lodash');
var async = require('async');
var bp = require('body-parser');
var cHelpers = require('../lib/helpers');
var debug = require('debug')('swagger-tools:middleware:metadata');
var mHelpers = require('./helpers');
var parseurl = require('parseurl');
var pathToRegexp = require('path-to-regexp');

// Upstream middlewares
var jsonBodyParser = bp.json();
var parseQueryString = mHelpers.parseQueryString;
var queryParser = function (req, res, next) {
  if (!req.query) {
    req.query = parseQueryString(req);
  }

  return next();
};
var urlEncodedBodyParser = bp.urlencoded({extended: false});
var bodyParser = function (req, res, callback) {
  urlEncodedBodyParser(req, res, function (err) {
    if (err) {
      callback(err);
    } else {
      jsonBodyParser(req, res, callback);
    }
  });
};

// Helper functions
var expressStylePath = function expressStylePath (basePath, apiPath) {
  basePath = parseurl({url: basePath || '/'}).pathname || '/';

  // Make sure the base path starts with '/'
  if (basePath.charAt(0) !== '/') {
    basePath = '/' + basePath;
  }

  // Make sure the base path ends with '/'
  if (basePath.charAt(basePath.length - 1) !== '/') {
    basePath = basePath + '/';
  }

  // Make sure the api path does not start with '/' since the base path will end with '/'
  if (apiPath.charAt(0) === '/') {
    apiPath = apiPath.substring(1);
  }

  // Replace Swagger syntax for path parameters with Express' version (All Swagger path parameters are required)
  return (basePath + apiPath).replace(/{/g, ':').replace(/}/g, '');
};
var getParameterType = function getParameterType (schema) {
  var type = schema.type;

  if (!type && schema.schema) {
    type = schema.type;
  }

  if (!type) {
    type = 'object';
  }

  return type;
};
var convertValue = function convertValue (value, schema, type) {
  if (_.isUndefined(type)) {
    type = getParameterType(schema);
  }

  // If there is no value, do not convert it
  if (_.isUndefined(value)) {
    return value;
  }

  switch (type) {
  case 'array':
    value = _.map(value, function (item) {
      return convertValue(item, _.isArray(schema.items) ? schema.items[0] : schema.items);
    });

    break;

  case 'boolean':
    if (!_.isBoolean(value)) {
      value = value === 'true' || value === true ? true : false;
    }

    break;

  case 'integer':
    if (!_.isNumber(value)) {
      value = parseInt(value, 10);
    }

    break;

  case 'number':
    if (!_.isNumber(value)) {
      value = parseFloat(value);
    }

    break;

  case 'string':
    if (['date', 'date-time'].indexOf(schema.format) > -1 && !_.isDate(value)) {
      value = new Date(value);
    }

    break;

  }

  return value;
};
var processOperationParameters = function processOperationParameters (version, pathKeys, pathMatch, req, res, next) {
  var swaggerMetadata = req.swagger;
  var parameters = !_.isUndefined(swaggerMetadata) ?
                     (version === '1.2' ? swaggerMetadata.operation.parameters : swaggerMetadata.operationParameters) :
                     undefined;

  if (!parameters) {
    return next();
  }

  debug('  Processing Parameters');

  async.map(_.reduce(parameters, function (requestParsers, parameter) {
    var paramType = version === '1.2' ? parameter.paramType : parameter.schema.in;
    var parser;

    switch (paramType) {
    case 'body':
    case 'form':
    case 'formData':
      parser = bodyParser;

      break;

    case 'query':
      parser = queryParser;

      break;
    }

    if (parser && requestParsers.indexOf(parser) === -1) {
      requestParsers.push(parser);
    }

    return requestParsers;
  }, []), function (parser, callback) {
    parser(req, res, callback);
  }, function (err) {
    if (err) {
      return next(err);
    }

    _.each(parameters, function (parameterOrMetadata, index) {
      var parameter = version === '1.2' ? parameterOrMetadata : parameterOrMetadata.schema;
      var pType = getParameterType(parameter);
      var oVal;
      var value;

      debug('    %s', parameter.name);
      debug('      Type: %s%s', pType, !_.isUndefined(parameter.format) ? ' (format: ' + parameter.format + ')': '');

      // Located here to make the debug output pretty
      oVal = mHelpers.getParameterValue(version, parameter, pathKeys, pathMatch, req, debug);
      value = convertValue(oVal, parameter, pType);

      debug('      Value: %s', value);

      swaggerMetadata.params[parameter.name] = {
        path: version === '1.2' ?
                swaggerMetadata.operationPath.concat(['parameters', index.toString()]) :
                parameterOrMetadata.path,
        schema: parameter,
        originalValue: oVal,
        value: value
      };
    });

    return next();
  });
};
var processSwaggerDocuments = function processSwaggerDocuments (rlOrSO, apiDeclarations) {
  if (_.isUndefined(rlOrSO)) {
    throw new Error('rlOrSO is required');
  } else if (!_.isPlainObject(rlOrSO)) {
    throw new TypeError('rlOrSO must be an object');
  }

  var spec = cHelpers.getSpec(cHelpers.getSwaggerVersion(rlOrSO), true);
  var apiCache = {};
  var composeParameters = function composeParameters (apiPath, method, path, operation) {
    var cParams = [];
    var seenParams = [];

    _.each(operation.parameters, function (parameter, index) {
      cParams.push({
        path: apiPath.concat([method, 'parameters', index.toString()]),
        schema: parameter
      });

      seenParams.push(parameter.name + ':' + parameter.in);
    });

    _.each(path.parameters, function (parameter, index) {
      if (seenParams.indexOf(parameter.name + ':' + parameter.in) === -1) {
        cParams.push({
          path: apiPath.concat(['parameters', index.toString()]),
          schema: parameter
        });
      }
    });

    return cParams;
  };
  var createCacheEntry = function createCacheEntry (adOrSO, apiOrPath, indexOrName, indent) {
    var apiPath = spec.version === '1.2' ? apiOrPath.path : indexOrName;
    var expressPath = expressStylePath(adOrSO.basePath, spec.version === '1.2' ? apiOrPath.path: indexOrName);
    var keys = [];
    var handleSubPaths = !(rlOrSO.paths && rlOrSO.paths[apiPath]['x-swagger-router-handle-subpaths']);
    var re = pathToRegexp(expressPath, keys, { end: handleSubPaths });
    var cacheKey = re.toString();
    var cacheEntry;

    // This is an absolute path, use it as the cache key
    if (expressPath.indexOf('{') === -1) {
      cacheKey = expressPath;
    }

    debug(new Array(indent + 1).join(' ') + 'Found %s: %s',
          (spec.version === '1.2' ? 'API' : 'Path'),
          apiPath);

    cacheEntry = apiCache[cacheKey] = spec.version === '1.2' ?
      {
        api: apiOrPath,
        apiDeclaration: adOrSO,
        apiIndex: indexOrName,
        keys: keys,
        params: {},
        re: re,
        operations: {},
        resourceListing: rlOrSO
      } :
      {
        apiPath: indexOrName,
        path: apiOrPath,
        keys: keys,
        re: re,
        operations: {},
        swaggerObject: {
          original: rlOrSO,
          resolved: adOrSO
        }
      };

    return cacheEntry;
  };

  debug('  Identified Swagger version: %s', spec.version);

  if (spec.version === '1.2') {
    if (_.isUndefined(apiDeclarations)) {
      throw new Error('apiDeclarations is required');
    } else if (!_.isArray(apiDeclarations)) {
      throw new TypeError('apiDeclarations must be an array');
    }

    debug('  Number of API Declarations: %d', apiDeclarations.length);

    _.each(apiDeclarations, function (apiDeclaration, adIndex) {
      debug('  Processing API Declaration %d', adIndex);

      _.each(apiDeclaration.apis, function (api, apiIndex) {
        var cacheEntry = createCacheEntry(apiDeclaration, api, apiIndex, 4);

        cacheEntry.resourceIndex = adIndex;

        _.each(api.operations, function (operation, operationIndex) {
          cacheEntry.operations[operation.method.toLowerCase()] = {
            operation: operation,
            operationPath: ['apis', apiIndex.toString(), 'operations', operationIndex.toString()],
            operationParameters: operation.parameters
          };
        });
      });
    });
  } else {
    // To avoid running into issues with references throughout the Swagger object we will use the resolved version.
    // Getting the resolved version is an asynchronous process but since initializeMiddleware caches the resolved document
    // this is a synchronous action at this point.
    spec.resolve(rlOrSO, function (err, resolved) {
      // Gather the paths, their path regex patterns and the corresponding operations
      _.each(resolved.paths, function (path, pathName) {
        var cacheEntry = createCacheEntry(resolved, path, pathName, 2);

        _.each(['get', 'put', 'post', 'delete', 'options', 'head', 'patch'], function (method) {
          var operation = path[method];

          if (!_.isUndefined(operation)) {
            cacheEntry.operations[method] = {
              operation: operation,
              operationPath: ['paths', pathName, method],
              // Required since we have to compose parameters based on the operation and the path
              operationParameters: composeParameters(['paths', pathName], method, path, operation)
            };
          }
        });
      });
    });
  }

  return apiCache;
};

/**
 * Middleware for providing Swagger information to downstream middleware and request handlers.  For all requests that
 * match a Swagger path, 'req.swagger' will be provided with pertinent Swagger details.  Since Swagger 1.2 and 2.0
 * differ a bit, the structure of this object will change so please view the documentation below for more details:
 *
 *     https://github.com/apigee-127/swagger-tools/blob/master/docs/Middleware.md#swagger-metadata
 *
 * @param {object} rlOrSO - The Resource Listing (Swagger 1.2) or Swagger Object (Swagger 2.0)
 * @param {object[]} apiDeclarations - The array of API Declarations (Swagger 1.2)
 *
 * @returns the middleware function
 */
exports = module.exports = function swaggerMetadataMiddleware (rlOrSO, apiDeclarations) {
  debug('Initializing swagger-metadata middleware');

  var apiCache = processSwaggerDocuments(rlOrSO, apiDeclarations);
  var swaggerVersion = cHelpers.getSwaggerVersion(rlOrSO);

  if (_.isUndefined(rlOrSO)) {
    throw new Error('rlOrSO is required');
  } else if (!_.isPlainObject(rlOrSO)) {
    throw new TypeError('rlOrSO must be an object');
  }

  if (swaggerVersion === '1.2') {
    if (_.isUndefined(apiDeclarations)) {
      throw new Error('apiDeclarations is required');
    } else if (!_.isArray(apiDeclarations)) {
      throw new TypeError('apiDeclarations must be an array');
    }
  }

  return function swaggerMetadata (req, res, next) {
    var method = req.method.toLowerCase();
    var path = parseurl(req).pathname;
    var cacheEntry;
    var match;
    var metadata;

    cacheEntry = apiCache[path] || _.find(apiCache, function (metadata) {
      match = metadata.re.exec(path);
      return _.isArray(match);
    });

    debug('%s %s', req.method, req.url);
    debug('  Is a Swagger path: %s', !_.isUndefined(cacheEntry));

    // Request does not match an API defined in the Swagger document(s)
    if (!cacheEntry) {
      return next();
    }

    metadata = swaggerVersion === '1.2' ?
      {
        api: cacheEntry.api,
        apiDeclaration: cacheEntry.apiDeclaration,
        apiIndex: cacheEntry.apiIndex,
        params: {},
        resourceIndex: cacheEntry.resourceIndex,
        resourceListing: cacheEntry.resourceListing
      } :
    {
      apiPath : cacheEntry.apiPath,
      path: cacheEntry.path,
      params: {},
      swaggerObject: cacheEntry.swaggerObject.resolved
    };

    if (_.isPlainObject(cacheEntry.operations[method])) {
      metadata.operation = cacheEntry.operations[method].operation;
      metadata.operationPath = cacheEntry.operations[method].operationPath;

      if (swaggerVersion === '1.2') {
        metadata.authorizations = metadata.operation.authorizations || cacheEntry.apiDeclaration.authorizations;
      } else {
        metadata.operationParameters = cacheEntry.operations[method].operationParameters;
        metadata.security = metadata.operation.security || metadata.swaggerObject.security || [];
      }
    }

    metadata.swaggerVersion = swaggerVersion;

    req.swagger = metadata;

    debug('  Is a Swagger operation: %s', !_.isUndefined(metadata.operation));

    if (metadata.operation) {
      // Process the operation parameters
      return processOperationParameters(swaggerVersion, cacheEntry.keys, match, req, res, next, debug);
    } else {
      return next();
    }
  };
};
<|endoftext|>