<commit_before>module.exports = {
  "extends": [
    "ash-nazg/sauron-node",
    "plugin:qunit/recommended", "plugin:testcafe/recommended"
  ],
  "parserOptions": {
    "sourceType": "module"
  },
  // Need to make explicit here for processing by jsdoc/check-examples
  "plugins": ["qunit"],
  "env": {
    "browser": true
  },
  "settings": {
    "polyfills": ["url", "promises", "fetch", "queryselector"],
    "jsdoc": {
      "additionalTagNames": {
        // In case we need to extend
        "customTags": []
      },
      "tagNamePreference": {
        "arg": "param",
        "return": "returns"
      },
      "allowOverrideWithoutParam": true,
      "allowImplementsWithoutParam": true,
      "allowAugmentsExtendsWithoutParam": true,
      // For `jsdoc/check-examples` in `ash-nazg`
      "matchingFileName": "dummy.md",
      "rejectExampleCodeRegex": "^`",
    }
  },
  "overrides": [
    // These would otherwise currently break because of these issues:
    //  1. `event:` https://github.com/eslint/doctrine/issues/221 and https://github.com/Kuniwak/jsdoctypeparser/pull/49 with https://github.com/Kuniwak/jsdoctypeparser/issues/47
    //  1. `@implements`/`@augments`/`@extends`/`@override`: https://github.com/eslint/doctrine/issues/222
    {
      "files": [
        "test/utilities_test.js", "editor/svg-editor.js", "editor/svgcanvas.js",
        "editor/coords.js",
        "editor/extensions/ext-eyedropper.js", "editor/extensions/ext-webappfind.js"
      ],
      "rules": {
        "jsdoc/valid-types": "off",
        "valid-jsdoc": "off"
      }
    },
    // Locales have no need for importing outside of SVG-Edit
    {
      "files": [
        "editor/locale/lang.*.js", "editor/extensions/ext-locale/**",
        "docs/tutorials/ExtensionDocs.md"
      ],
      "rules": {
        "import/no-anonymous-default-export": ["off"]
      }
    },
    // For extensions, `this` is generally assigned to be the more
    //   descriptive `svgEditor`; they also have no need for importing outside
    //   of SVG-Edit
    {
      "files": ["editor/extensions/**/ext-*.js"],
      "rules": {
        "consistent-this": ["error", "svgEditor"],
        "import/no-anonymous-default-export": ["off"]
      }
    },
    // These browser files don't do importing or requiring
    {
      "files": [
        "editor/svgpathseg.js", "editor/touch.js", "editor/typedefs.js",
        "editor/redirect-on-no-module-support.js",
        "editor/extensions/imagelib/index.js",
        "editor/external/dom-polyfill/dom-polyfill.js",
        "test/all_tests.js", "screencasts/svgopen2010/script.js",
        "opera-widget/handlers.js",
        "firefox-extension/handlers.js",
        "firefox-extension/content/svg-edit-overlay.js"
      ],
      "rules": {
        "import/unambiguous": ["off"]
      }
    },
    // Our Markdown rules (and used for JSDoc examples as well, by way of
    //   our use of `matchingFileName` in conjunction with
    //   `jsdoc/check-examples` within `ash-nazg`)
    {
      "files": ["**/*.md"],
      "rules": {
        "eol-last": ["off"],
        "no-console": ["off"],
        "no-undef": ["off"],
        "no-unused-vars": ["warn"],
        "padded-blocks": ["off"],
        "import/unambiguous": ["off"],
        "import/no-unresolved": ["off"],
        "node/no-missing-import": ["off"]
      }
    },
    // Dis-apply Node rules mistakenly giving errors with browser files
    {
      "files": ["editor/**", "test/**"],
      "rules": {
        "node/no-unsupported-features/node-builtins": ["off"]
      }
    },
    // We want console in tests!
    {
      "files": ["test/**"],
      "rules": {
        "no-console": ["off"]
      }
    },
    {
      // Node files
      "files": [
        "docs/jsdoc-config.js",
        "build-html.js", "jsdoc-check-overly-generic-types.js",
        "rollup.config.js", "rollup-config.config.js"
      ],
      "env": {
        "node": true,
      },
      "rules": {
        "node/no-unpublished-import": ["off"],
        "node/no-unsupported-features/es-syntax": ["off"]
      }
    },
    {
      // As consumed by jsdoc, cannot be expressed as ESM
      "files": ["docs/jsdoc-config.js"],
      "parserOptions": {
        "sourceType": "script"
      },
      "rules": {
        "import/no-commonjs": "off"
      }
    }
  ],
  "rules": {
    // Override these `ash-nazg/sauron` rules which are difficult for us
    //   to apply at this time
    "capitalized-comments": ["off"],
    "complexity": ["off"],
    "default-case": ["off"],
    "require-unicode-regexp": ["off"],
    "no-magic-numbers": ["off"],
    "no-warning-comments": ["off"],
    "max-len": ["off", {
      "ignoreUrls": true,
      "ignoreRegExpLiterals": true
    }]
  }
};
<commit_msg>- Linting (ESLint): Avoid JSON quoting<commit_after>module.exports = {
  extends: [
    "ash-nazg/sauron-node",
    "plugin:qunit/recommended", "plugin:testcafe/recommended"
  ],
  parserOptions: {
    sourceType: "module"
  },
  // Need to make explicit here for processing by jsdoc/check-examples
  plugins: ["qunit"],
  env: {
    browser: true
  },
  settings: {
    polyfills: ["url", "promises", "fetch", "queryselector"],
    jsdoc: {
      additionalTagNames: {
        // In case we need to extend
        customTags: []
      },
      tagNamePreference: {
        arg: "param",
        return: "returns"
      },
      allowOverrideWithoutParam: true,
      allowImplementsWithoutParam: true,
      allowAugmentsExtendsWithoutParam: true,
      // For `jsdoc/check-examples` in `ash-nazg`
      matchingFileName: "dummy.md",
      rejectExampleCodeRegex: "^`",
    }
  },
  overrides: [
    // These would otherwise currently break because of these issues:
    //  1. `event:` https://github.com/eslint/doctrine/issues/221 and https://github.com/Kuniwak/jsdoctypeparser/pull/49 with https://github.com/Kuniwak/jsdoctypeparser/issues/47
    //  1. `@implements`/`@augments`/`@extends`/`@override`: https://github.com/eslint/doctrine/issues/222
    {
      files: [
        "test/utilities_test.js", "editor/svg-editor.js", "editor/svgcanvas.js",
        "editor/coords.js",
        "editor/extensions/ext-eyedropper.js", "editor/extensions/ext-webappfind.js"
      ],
      rules: {
        "jsdoc/valid-types": "off",
        "valid-jsdoc": "off"
      }
    },
    // Locales have no need for importing outside of SVG-Edit
    {
      files: [
        "editor/locale/lang.*.js", "editor/extensions/ext-locale/**",
        "docs/tutorials/ExtensionDocs.md"
      ],
      rules: {
        "import/no-anonymous-default-export": ["off"]
      }
    },
    // For extensions, `this` is generally assigned to be the more
    //   descriptive `svgEditor`; they also have no need for importing outside
    //   of SVG-Edit
    {
      files: ["editor/extensions/**/ext-*.js"],
      rules: {
        "consistent-this": ["error", "svgEditor"],
        "import/no-anonymous-default-export": ["off"]
      }
    },
    // These browser files don't do importing or requiring
    {
      files: [
        "editor/svgpathseg.js", "editor/touch.js", "editor/typedefs.js",
        "editor/redirect-on-no-module-support.js",
        "editor/extensions/imagelib/index.js",
        "editor/external/dom-polyfill/dom-polyfill.js",
        "test/all_tests.js", "screencasts/svgopen2010/script.js",
        "opera-widget/handlers.js",
        "firefox-extension/handlers.js",
        "firefox-extension/content/svg-edit-overlay.js"
      ],
      rules: {
        "import/unambiguous": ["off"]
      }
    },
    // Our Markdown rules (and used for JSDoc examples as well, by way of
    //   our use of `matchingFileName` in conjunction with
    //   `jsdoc/check-examples` within `ash-nazg`)
    {
      files: ["**/*.md"],
      rules: {
        "eol-last": ["off"],
        "no-console": ["off"],
        "no-undef": ["off"],
        "no-unused-vars": ["warn"],
        "padded-blocks": ["off"],
        "import/unambiguous": ["off"],
        "import/no-unresolved": ["off"],
        "node/no-missing-import": ["off"]
      }
    },
    // Dis-apply Node rules mistakenly giving errors with browser files
    {
      files: ["editor/**", "test/**"],
      rules: {
        "node/no-unsupported-features/node-builtins": ["off"]
      }
    },
    // We want console in tests!
    {
      files: ["test/**"],
      rules: {
        "no-console": ["off"]
      }
    },
    {
      // Node files
      files: [
        "docs/jsdoc-config.js",
        "build-html.js", "jsdoc-check-overly-generic-types.js",
        "rollup.config.js", "rollup-config.config.js"
      ],
      env: {
        node: true,
      },
      rules: {
        "node/no-unpublished-import": ["off"],
        "node/no-unsupported-features/es-syntax": ["off"]
      }
    },
    {
      // As consumed by jsdoc, cannot be expressed as ESM
      files: ["docs/jsdoc-config.js"],
      parserOptions: {
        sourceType: "script"
      },
      rules: {
        "import/no-commonjs": "off"
      }
    }
  ],
  rules: {
    // Override these `ash-nazg/sauron` rules which are difficult for us
    //   to apply at this time
    "capitalized-comments": ["off"],
    complexity: ["off"],
    "default-case": ["off"],
    "require-unicode-regexp": ["off"],
    "no-magic-numbers": ["off"],
    "no-warning-comments": ["off"],
    "max-len": ["off", {
      ignoreUrls: true,
      ignoreRegExpLiterals: true
    }]
  }
};
<|endoftext|>


<commit_before>module.exports = {
  "env": {
    "es6": true,
    "node": true
  },
  "extends": "eslint:recommended",
  "parserOptions": {
    "sourceType": "module"
  },
  "rules": {
    "indent": [
      "error",
      2
    ],
    "linebreak-style": [
      "error",
      "unix"
    ],
    "quotes": [
      "error",
      "single"
    ],
    "semi": [
      "error",
      "always"
    ]
  }
}
<commit_msg>Code climate doesn't like 'error'<commit_after>module.exports = {
  "env": {
    "es6": true,
    "node": true
  },
  "extends": "eslint:recommended",
  "parserOptions": {
    "sourceType": "module"
  },
  "rules": {
    "indent": [
      2,
      2
    ],
    "linebreak-style": [
      2,
      "unix"
    ],
    "quotes": [
      1,
      "single"
    ],
    "semi": [
      2,
      "always"
    ]
  }
}
<|endoftext|>


<commit_before>const EventEmitter = require('events').EventEmitter;
const parser = require('cron-parser');
const moment = require('moment');
const uuid = require('uuid');
const find = require('lodash/find');
const pull = require('lodash/pull');

const DEFAULT_THRESHOLD = 100;

class SimpleCron extends EventEmitter {
  constructor (options) {
    const opts = options || {};
    if (typeof opts !== 'object') throw new Error('Invalid parameter. Object expected.');
    super();
    this.running = false;
    this.break = false;
    this.jobs = [];
    this.lastRun = null;
    this.threshold = opts.threshold || DEFAULT_THRESHOLD;
  }

  run () {
    if (this.running === true) throw new Error('SimpleCron is already running.');
    this.emit('run');
    this.running = true;
    const loop = () => {
      // Stop the loop if requested
      if (this.break === true) {
        this.break = false;
        this.running = false;
        this.emit('stop');
        return;
      }

      // If time has changed more than 3 hours, don't run
      // missed jobs. Reset job interval for all jobs.
      // (will re-run jobs if clock turned back)
      if (this.lastRun && Math.abs(this.lastRun.diff(moment(), 'hours')) >= 3) {
        this.jobs.forEach(job => {
          const interval = parser.parseExpression(job.expression);
          job.interval = interval;
          job.nextInvocation = null;
        });
      }

      // Iterate over scheduled jobs and execute if necessary
      this.jobs.forEach(job => {
        // If we haven't encountered this job yet, set up next invocation time
        if (job.nextInvocation === null) {
          job.nextInvocation = moment(job.interval.next()._date);
        }

        // Run job if it's time.
        // If the time has changed (less than 3 hours)
        // All missed jobs will be run immediately.
        // In the case of time turning back, jobs will not
        // be re-run.
        if (job.nextInvocation.isSameOrBefore(moment())) {
          console.log(job.nextInvocation);
          job.fn();
          this.emit('invoke', job.id);
          job.nextInvocation = moment(job.interval.next()._date);
        }
      });
      this.lastRun = moment();
      setTimeout(loop, this.threshold);
    };
    loop();
  }

  stop () {
    if (this.running === true) {
      this.break = true;
      return new Promise((resolve, reject) => {
        this.on('stop', () => {
          resolve();
        });
      });
    }
    throw new Error('SimpleCron is not running.');
  }

  schedule (expression, fn) {
    const interval = parser.parseExpression(expression);
    const id = uuid();
    this.jobs.push({
      id,
      expression,
      interval,
      fn,
      nextInvocation: null
    });
    this.emit('schedule', id);
    return id;
  }

  cancel (id) {
    const job = find(this.jobs, { id });
    if (!job) throw new Error(`Invalid job id '${id}'.`);
    pull(this.jobs, job);
    this.emit('cancel', id);
  }
}

module.exports = SimpleCron;

<commit_msg>refactor stop function<commit_after>const EventEmitter = require('events').EventEmitter;
const parser = require('cron-parser');
const moment = require('moment');
const uuid = require('uuid');
const find = require('lodash/find');
const pull = require('lodash/pull');

const DEFAULT_THRESHOLD = 100;

class SimpleCron extends EventEmitter {
  constructor (options) {
    const opts = options || {};
    if (typeof opts !== 'object') throw new Error('Invalid parameter. Object expected.');
    super();
    this.running = false;
    this.break = false;
    this.jobs = [];
    this.lastRun = null;
    this.threshold = opts.threshold || DEFAULT_THRESHOLD;
  }

  run () {
    if (this.running === true) throw new Error('SimpleCron is already running.');
    this.emit('run');
    this.running = true;
    const loop = () => {
      // Stop the loop if requested
      if (this.break === true) {
        this.break = false;
        this.running = false;
        this.emit('stop');
        return;
      }

      // If time has changed more than 3 hours, don't run
      // missed jobs. Reset job interval for all jobs.
      // (will re-run jobs if clock turned back)
      if (this.lastRun && Math.abs(this.lastRun.diff(moment(), 'hours')) >= 3) {
        this.jobs.forEach(job => {
          const interval = parser.parseExpression(job.expression);
          job.interval = interval;
          job.nextInvocation = null;
        });
      }

      // Iterate over scheduled jobs and execute if necessary
      this.jobs.forEach(job => {
        // If we haven't encountered this job yet, set up next invocation time
        if (job.nextInvocation === null) {
          job.nextInvocation = moment(job.interval.next()._date);
        }

        // Run job if it's time.
        // If the time has changed (less than 3 hours)
        // All missed jobs will be run immediately.
        // In the case of time turning back, jobs will not
        // be re-run.
        if (job.nextInvocation.isSameOrBefore(moment())) {
          console.log(job.nextInvocation);
          job.fn();
          this.emit('invoke', job.id);
          job.nextInvocation = moment(job.interval.next()._date);
        }
      });
      this.lastRun = moment();
      setTimeout(loop, this.threshold);
    };
    loop();
  }

  stop () {
    if (!this.running) throw new Error('SimpleCron is not running.');
    this.break = true;
    return new Promise((resolve, reject) => {
      this.once('stop', resolve);
    });
  }

  schedule (expression, fn) {
    const interval = parser.parseExpression(expression);
    const id = uuid();
    this.jobs.push({
      id,
      expression,
      interval,
      fn,
      nextInvocation: null
    });
    this.emit('schedule', id);
    return id;
  }

  cancel (id) {
    const job = find(this.jobs, { id });
    if (!job) throw new Error(`Invalid job id '${id}'.`);
    pull(this.jobs, job);
    this.emit('cancel', id);
  }
}

module.exports = SimpleCron;

<|endoftext|>


<commit_before>const path = require('path');

module.exports = {
  root: true,
  parser: '@typescript-eslint/parser',
  extends: [
    'eslint:recommended',
    'plugin:import/errors',
    'plugin:import/typescript',
    'plugin:qunit/recommended',
    'plugin:prettier/recommended',
  ],
  plugins: ['ember-internal', 'import', 'qunit', 'disable-features'],
  rules: {
    'no-console': 'error',
    'no-implicit-coercion': 'error',
    'no-new-wrappers': 'error',
    'no-unused-vars': 'error',
    'no-throw-literal': 'error',
    'no-var': 'error',

    'qunit/no-assert-equal': 'off',
    'qunit/no-commented-tests': 'off',
    'qunit/require-expect': 'off',

    'disable-features/disable-async-await': 'error',
    'disable-features/disable-generator-functions': 'error',
  },

  settings: {
    'import/core-modules': ['require', 'backburner', 'router', '@glimmer/interfaces'],
    'import/resolver': {
      node: {
        extensions: ['.js', '.ts', '.d.ts'],
        paths: [path.resolve('./packages/')],
      },
    },
  },

  overrides: [
    {
      files: ['*.ts'],

      extends: ['plugin:@typescript-eslint/recommended'],

      parserOptions: {
        sourceType: 'module',
        project: './tsconfig.json',
        tsconfigRootDir: __dirname,
      },

      rules: {
        '@typescript-eslint/ban-ts-comment': 'warn',
        '@typescript-eslint/ban-types': 'off',
        '@typescript-eslint/no-empty-function': 'off',
        '@typescript-eslint/no-this-alias': 'off',
        '@typescript-eslint/no-var-requires': 'warn',

        // TODO: Enable and fix these rules
        // Typescript provides better types with these rules enabled
        'prefer-spread': 'off',
        'prefer-const': 'off',
        'prefer-rest-params': 'off',
      },
    },
    {
      // TODO: files: ['packages/**/*.[jt]s'],
      files: ['packages/**/*.js'],

      parserOptions: {
        ecmaVersion: 2017,
        sourceType: 'module',
      },

      globals: {
        // A safe subset of 'browser:true':
        window: true,
        document: true,
        setTimeout: true,
        clearTimeout: true,
        setInterval: true,
        clearInterval: true,
        console: true,
        Map: true,
        Set: true,
        Symbol: true,
        WeakMap: true,
        Event: true,
      },

      rules: {
        'ember-internal/require-yuidoc-access': 'error',
        'ember-internal/no-const-outside-module-scope': 'error',
      },
    },
    {
      files: [
        'packages/*/tests/**/*.[jt]s',
        'packages/@ember/*/tests/**/*.[jt]s',
        'packages/@ember/-internals/*/tests/**/*.[jt]s',
        'packages/internal-test-helpers/**/*.[jt]s',
      ],
      env: {
        qunit: true,
      },
      globals: {
        expectAssertion: true,
        expectDeprecation: true,
        expectDeprecationAsync: true,
        expectNoDeprecation: true,
        expectWarning: true,
        expectNoWarning: true,
        ignoreAssertion: true,
        ignoreDeprecation: true,
      },
      rules: {
        'disable-features/disable-async-await': 'off',
        'disable-features/disable-generator-functions': 'off',
      },
    },
    {
      // matches all node-land files
      files: [
        '.eslintrc.js',
        'node-tests/**/*.js',
        'tests/node/**/*.js',
        'blueprints/**/*.js',
        'bin/**/*.js',
        'tests/docs/*.js',
        'config/**/*.js',
        'lib/**/*.js',
        'server/**/*.js',
        'testem.js',
        'testem.ci-browsers.js',
        'testem.browserstack.js',
        'd8-runner.js',
        'broccoli/**/*.js',
        'ember-cli-build.js',
        'rollup.config.js',
      ],

      parserOptions: {
        ecmaVersion: 2018,
        sourceType: 'script',
      },

      env: {
        node: true,
        es6: true,
      },

      plugins: ['node'],
      rules: Object.assign({}, require('eslint-plugin-node').configs.recommended.rules, {
        // add your custom rules and overrides for node files here
        'no-process-exit': 'off',
        'no-throw-literal': 'error',
        'disable-features/disable-async-await': 'off',
        'disable-features/disable-generator-functions': 'off',
      }),
    },
    {
      files: ['node-tests/**/*.js'],

      env: {
        mocha: true,
      },
    },
    {
      files: ['tests/docs/**/*.js', 'tests/node/**/*.js'],

      env: {
        qunit: true,
      },
    },
  ],
};
<commit_msg>add reportUnusedDisableDirectives to eslint config for warnings<commit_after>const path = require('path');

module.exports = {
  root: true,
  parser: '@typescript-eslint/parser',
  reportUnusedDisableDirectives: true,
  extends: [
    'eslint:recommended',
    'plugin:import/errors',
    'plugin:import/typescript',
    'plugin:qunit/recommended',
    'plugin:prettier/recommended',
  ],
  plugins: ['ember-internal', 'import', 'qunit', 'disable-features'],
  rules: {
    'no-console': 'error',
    'no-implicit-coercion': 'error',
    'no-new-wrappers': 'error',
    'no-unused-vars': 'error',
    'no-throw-literal': 'error',
    'no-var': 'error',

    'qunit/no-assert-equal': 'off',
    'qunit/no-commented-tests': 'off',
    'qunit/require-expect': 'off',

    'disable-features/disable-async-await': 'error',
    'disable-features/disable-generator-functions': 'error',
  },

  settings: {
    'import/core-modules': ['require', 'backburner', 'router', '@glimmer/interfaces'],
    'import/resolver': {
      node: {
        extensions: ['.js', '.ts', '.d.ts'],
        paths: [path.resolve('./packages/')],
      },
    },
  },

  overrides: [
    {
      files: ['*.ts'],

      extends: ['plugin:@typescript-eslint/recommended'],

      parserOptions: {
        sourceType: 'module',
        project: './tsconfig.json',
        tsconfigRootDir: __dirname,
      },

      rules: {
        '@typescript-eslint/ban-ts-comment': 'warn',
        '@typescript-eslint/ban-types': 'off',
        '@typescript-eslint/no-empty-function': 'off',
        '@typescript-eslint/no-this-alias': 'off',
        '@typescript-eslint/no-var-requires': 'warn',

        // TODO: Enable and fix these rules
        // Typescript provides better types with these rules enabled
        'prefer-spread': 'off',
        'prefer-const': 'off',
        'prefer-rest-params': 'off',
      },
    },
    {
      // TODO: files: ['packages/**/*.[jt]s'],
      files: ['packages/**/*.js'],

      parserOptions: {
        ecmaVersion: 2017,
        sourceType: 'module',
      },

      globals: {
        // A safe subset of 'browser:true':
        window: true,
        document: true,
        setTimeout: true,
        clearTimeout: true,
        setInterval: true,
        clearInterval: true,
        console: true,
        Map: true,
        Set: true,
        Symbol: true,
        WeakMap: true,
        Event: true,
      },

      rules: {
        'ember-internal/require-yuidoc-access': 'error',
        'ember-internal/no-const-outside-module-scope': 'error',
      },
    },
    {
      files: [
        'packages/*/tests/**/*.[jt]s',
        'packages/@ember/*/tests/**/*.[jt]s',
        'packages/@ember/-internals/*/tests/**/*.[jt]s',
        'packages/internal-test-helpers/**/*.[jt]s',
      ],
      env: {
        qunit: true,
      },
      globals: {
        expectAssertion: true,
        expectDeprecation: true,
        expectDeprecationAsync: true,
        expectNoDeprecation: true,
        expectWarning: true,
        expectNoWarning: true,
        ignoreAssertion: true,
        ignoreDeprecation: true,
      },
      rules: {
        'disable-features/disable-async-await': 'off',
        'disable-features/disable-generator-functions': 'off',
      },
    },
    {
      // matches all node-land files
      files: [
        '.eslintrc.js',
        'node-tests/**/*.js',
        'tests/node/**/*.js',
        'blueprints/**/*.js',
        'bin/**/*.js',
        'tests/docs/*.js',
        'config/**/*.js',
        'lib/**/*.js',
        'server/**/*.js',
        'testem.js',
        'testem.ci-browsers.js',
        'testem.browserstack.js',
        'd8-runner.js',
        'broccoli/**/*.js',
        'ember-cli-build.js',
        'rollup.config.js',
      ],

      parserOptions: {
        ecmaVersion: 2018,
        sourceType: 'script',
      },

      env: {
        node: true,
        es6: true,
      },

      plugins: ['node'],
      rules: Object.assign({}, require('eslint-plugin-node').configs.recommended.rules, {
        // add your custom rules and overrides for node files here
        'no-process-exit': 'off',
        'no-throw-literal': 'error',
        'disable-features/disable-async-await': 'off',
        'disable-features/disable-generator-functions': 'off',
      }),
    },
    {
      files: ['node-tests/**/*.js'],

      env: {
        mocha: true,
      },
    },
    {
      files: ['tests/docs/**/*.js', 'tests/node/**/*.js'],

      env: {
        qunit: true,
      },
    },
  ],
};
<|endoftext|>


<commit_before>"use strict";

var url = require('url');
var https = require('https');
var rp = require('request-promise');
const Slackdown = require('Slackdown');
const substitutions = require("./substitutions");
const log = require("matrix-appservice-bridge").Logging.get("SlackGhost");

// How long in milliseconds to cache user info lookups.
var USER_CACHE_TIMEOUT = 10 * 60 * 1000;  // 10 minutes

function SlackGhost(opts) {
    this._main = opts.main;

    this._user_id = opts.user_id;
    this._display_name = opts.display_name;
    this._avatar_url = opts.avatar_url;

    this._intent = opts.intent;

    this._atime = null; // last activity time in epoch seconds
}

SlackGhost.fromEntry = function(main, entry, intent) {
    return new SlackGhost({
        main: main,

        user_id: entry.id,
        display_name: entry.display_name,
        avatar_url: entry.avatar_url,

        intent: intent,
    });
};

SlackGhost.prototype.toEntry = function() {
    var entry = {
        id: this._user_id,
        display_name: this._display_name,
        avatar_url: this._avatar_url,
    };

    return entry;
};

SlackGhost.prototype.getIntent = function() {
    return this._intent;
};

SlackGhost.prototype.update = function(message, room) {
    log.info("Updating user information for " + message.user_id);
    return Promise.all([
        this.updateDisplayname(message, room).catch((e) => {
            log.error("Failed to update ghost displayname:", e);
        }),
        this.updateAvatar(message, room).catch((e) => {
            log.error("Failed to update ghost avatar:", e);
        }),
    ]);
};

SlackGhost.prototype.updateDisplayname = function(message, room) {
    var display_name = message.user_name;

    var getDisplayName;
    if (!display_name) {
        getDisplayName = this.lookupUserInfo(message.user_id, room.getAccessToken())
            .then(user => {
                if (user && user.profile) {
                    return user.profile.display_name || user.profile.real_name;
                }
            });
    } else {
        getDisplayName = Promise.resolve(display_name);
    }

    return getDisplayName.then(display_name => {
        if (!display_name || this._display_name === display_name) return Promise.resolve();

        return this.getIntent().setDisplayName(display_name).then(() => {
            this._display_name = display_name;
            return this._main.putUserToStore(this);
        });
    })
};

SlackGhost.prototype.lookupAvatarUrl = function(user_id, token) {
    return this.lookupUserInfo(user_id, token).then((user) => {
        if (!user || !user.profile) return;
        var profile = user.profile;

        // Pick the original image if we can, otherwise pick the largest image
        // that is defined
        var avatar_url = profile.image_original ||
            profile.image_1024 || profile.image_512 || profile.image_192 ||
            profile.image_72 || profile.image_48;

        return avatar_url;
    });
};

SlackGhost.prototype.lookupUserInfo = function(user_id, token) {
    if (this._user_info_cache) return Promise.resolve(this._user_info_cache);
    if (this._loading_user) return this._loading_user;
    if (!token) return Promise.resolve();

    this._main.incRemoteCallCounter("users.info");
    this._loading_user = rp({
        uri: 'https://slack.com/api/users.info',
        qs: {
            token: token,
            user: user_id,
        },
        json: true,
    }).then((response) => {
        if (!response.user || !response.user.profile) {
            log.error("Failed to get user profile", response);
            return;
        };

        this._user_info_cache = response.user;
        setTimeout(() => { this._user_info_cache = null }, USER_CACHE_TIMEOUT);

        delete this._loading_user;
        return response.user;
    });

    return this._loading_user;
};

SlackGhost.prototype.updateAvatar = function(message, room) {
    var token = room.getAccessToken();
    if (!token) return Promise.resolve();

    return this.lookupAvatarUrl(message.user_id, token).then((avatar_url) => {
        if (this._avatar_url === avatar_url) return;

        var shortname = avatar_url.match(/\/([^\/]+)$/)[1];

        return rp({
            uri: avatar_url,
            resolveWithFullResponse: true,
            encoding: null,
        }).then((response) => {
            return this.uploadContent({
                _content: response.body,
                title: shortname,
                mimetype: response.headers["content-type"],
            });
        }).then((content_uri) => {
            this.getIntent().setAvatarUrl(content_uri);
        }).then(() => {
            this._avatar_url = avatar_url;
            this._main.putUserToStore(this);
        });
    });
};

SlackGhost.prototype.sendText = function(room_id, text) {
    // TODO: Slack's markdown is their own thing that isn't really markdown,
    // but the only parser we have for it is slackdown. However, Matrix expects
    // a variant of markdown that is in the realm of sanity. Currently text
    // will be slack's markdown until we've got a slack -> markdown parser.
    const content = {
        body: text,
        msgtype: "m.text",
        formatted_body: slackdown.parse(text),
        format: "org.matrix.custom.html"
    };
    return this.getIntent().sendMessage(room_id, content).then(() => {
        this._main.incCounter("sent_messages", {side: "matrix"});
    });
};

SlackGhost.prototype.sendMessage = function(room_id, msg) {
    return this.getIntent().sendMessage(room_id, msg).then(() => {
        this._main.incCounter("sent_messages", {side: "matrix"});
    });
};

SlackGhost.prototype.uploadContentFromURI = function(file, uri, token) {
    return rp({
        uri: uri,
        headers: {
            Authorization: `Bearer ${token}`,
        },
        encoding: null, // Because we expect a binary
    }).then((buffer) => {
        file._content = buffer;
        return this.uploadContent(file);
    }).then((contentUri) => {
        return contentUri;
    }).catch((reason) => {
        log.error("Failed to upload content:\n%s", reason);
        throw reason;
    });
};

SlackGhost.prototype.uploadContent = function(file) {
    return this.getIntent().getClient().uploadContent({
            stream: new Buffer(file._content, "binary"),
            name: file.title,
            type: file.mimetype,
    }).then((response) => {
        var content_uri = JSON.parse(response).content_uri;

        log.debug("Media uploaded to " + content_uri);
        return content_uri;
    });
};

SlackGhost.prototype.getATime = function() {
    return this._atime;
};

SlackGhost.prototype.bumpATime = function() {
    this._atime = Date.now() / 1000;
};

module.exports = SlackGhost;
<commit_msg>Capitalisation<commit_after>"use strict";

var url = require('url');
var https = require('https');
var rp = require('request-promise');
const Slackdown = require('Slackdown');
const substitutions = require("./substitutions");
const log = require("matrix-appservice-bridge").Logging.get("SlackGhost");

// How long in milliseconds to cache user info lookups.
var USER_CACHE_TIMEOUT = 10 * 60 * 1000;  // 10 minutes

function SlackGhost(opts) {
    this._main = opts.main;

    this._user_id = opts.user_id;
    this._display_name = opts.display_name;
    this._avatar_url = opts.avatar_url;

    this._intent = opts.intent;

    this._atime = null; // last activity time in epoch seconds
}

SlackGhost.fromEntry = function(main, entry, intent) {
    return new SlackGhost({
        main: main,

        user_id: entry.id,
        display_name: entry.display_name,
        avatar_url: entry.avatar_url,

        intent: intent,
    });
};

SlackGhost.prototype.toEntry = function() {
    var entry = {
        id: this._user_id,
        display_name: this._display_name,
        avatar_url: this._avatar_url,
    };

    return entry;
};

SlackGhost.prototype.getIntent = function() {
    return this._intent;
};

SlackGhost.prototype.update = function(message, room) {
    log.info("Updating user information for " + message.user_id);
    return Promise.all([
        this.updateDisplayname(message, room).catch((e) => {
            log.error("Failed to update ghost displayname:", e);
        }),
        this.updateAvatar(message, room).catch((e) => {
            log.error("Failed to update ghost avatar:", e);
        }),
    ]);
};

SlackGhost.prototype.updateDisplayname = function(message, room) {
    var display_name = message.user_name;

    var getDisplayName;
    if (!display_name) {
        getDisplayName = this.lookupUserInfo(message.user_id, room.getAccessToken())
            .then(user => {
                if (user && user.profile) {
                    return user.profile.display_name || user.profile.real_name;
                }
            });
    } else {
        getDisplayName = Promise.resolve(display_name);
    }

    return getDisplayName.then(display_name => {
        if (!display_name || this._display_name === display_name) return Promise.resolve();

        return this.getIntent().setDisplayName(display_name).then(() => {
            this._display_name = display_name;
            return this._main.putUserToStore(this);
        });
    })
};

SlackGhost.prototype.lookupAvatarUrl = function(user_id, token) {
    return this.lookupUserInfo(user_id, token).then((user) => {
        if (!user || !user.profile) return;
        var profile = user.profile;

        // Pick the original image if we can, otherwise pick the largest image
        // that is defined
        var avatar_url = profile.image_original ||
            profile.image_1024 || profile.image_512 || profile.image_192 ||
            profile.image_72 || profile.image_48;

        return avatar_url;
    });
};

SlackGhost.prototype.lookupUserInfo = function(user_id, token) {
    if (this._user_info_cache) return Promise.resolve(this._user_info_cache);
    if (this._loading_user) return this._loading_user;
    if (!token) return Promise.resolve();

    this._main.incRemoteCallCounter("users.info");
    this._loading_user = rp({
        uri: 'https://slack.com/api/users.info',
        qs: {
            token: token,
            user: user_id,
        },
        json: true,
    }).then((response) => {
        if (!response.user || !response.user.profile) {
            log.error("Failed to get user profile", response);
            return;
        };

        this._user_info_cache = response.user;
        setTimeout(() => { this._user_info_cache = null }, USER_CACHE_TIMEOUT);

        delete this._loading_user;
        return response.user;
    });

    return this._loading_user;
};

SlackGhost.prototype.updateAvatar = function(message, room) {
    var token = room.getAccessToken();
    if (!token) return Promise.resolve();

    return this.lookupAvatarUrl(message.user_id, token).then((avatar_url) => {
        if (this._avatar_url === avatar_url) return;

        var shortname = avatar_url.match(/\/([^\/]+)$/)[1];

        return rp({
            uri: avatar_url,
            resolveWithFullResponse: true,
            encoding: null,
        }).then((response) => {
            return this.uploadContent({
                _content: response.body,
                title: shortname,
                mimetype: response.headers["content-type"],
            });
        }).then((content_uri) => {
            this.getIntent().setAvatarUrl(content_uri);
        }).then(() => {
            this._avatar_url = avatar_url;
            this._main.putUserToStore(this);
        });
    });
};

SlackGhost.prototype.sendText = function(room_id, text) {
    // TODO: Slack's markdown is their own thing that isn't really markdown,
    // but the only parser we have for it is slackdown. However, Matrix expects
    // a variant of markdown that is in the realm of sanity. Currently text
    // will be slack's markdown until we've got a slack -> markdown parser.
    const content = {
        body: text,
        msgtype: "m.text",
        formatted_body: Slackdown.parse(text),
        format: "org.matrix.custom.html"
    };
    return this.getIntent().sendMessage(room_id, content).then(() => {
        this._main.incCounter("sent_messages", {side: "matrix"});
    });
};

SlackGhost.prototype.sendMessage = function(room_id, msg) {
    return this.getIntent().sendMessage(room_id, msg).then(() => {
        this._main.incCounter("sent_messages", {side: "matrix"});
    });
};

SlackGhost.prototype.uploadContentFromURI = function(file, uri, token) {
    return rp({
        uri: uri,
        headers: {
            Authorization: `Bearer ${token}`,
        },
        encoding: null, // Because we expect a binary
    }).then((buffer) => {
        file._content = buffer;
        return this.uploadContent(file);
    }).then((contentUri) => {
        return contentUri;
    }).catch((reason) => {
        log.error("Failed to upload content:\n%s", reason);
        throw reason;
    });
};

SlackGhost.prototype.uploadContent = function(file) {
    return this.getIntent().getClient().uploadContent({
            stream: new Buffer(file._content, "binary"),
            name: file.title,
            type: file.mimetype,
    }).then((response) => {
        var content_uri = JSON.parse(response).content_uri;

        log.debug("Media uploaded to " + content_uri);
        return content_uri;
    });
};

SlackGhost.prototype.getATime = function() {
    return this._atime;
};

SlackGhost.prototype.bumpATime = function() {
    this._atime = Date.now() / 1000;
};

module.exports = SlackGhost;
<|endoftext|>


<commit_before>var API_BASE = "https://bugzilla.mozilla.org/rest/";

// change for each release

var FIRST_NIGHTLY_CURRENT = '2020-03-09'; // first nightly of current release
var FIRST_NIGHTLY_NEXT = '2020-04-06'; // first nightly of next version at release
var NIGHTLY = '78';
var BETA    = '77';
var RELEASE = '76';
var ESR     = '68';
var STATUS_RELEASE_VERSION = 'cf_status_firefox' + RELEASE;
var STATUS_BETA_VERSION = 'cf_status_firefox' + BETA;
var STATUS_NIGHTLY_VERSION = 'cf_status_firefox' + NIGHTLY;
var STATUS_ESR_VERSION = 'cf_status_firefox_esr' + ESR;

/**
 * @returns d3.request
 */
function make_api_request(path, params, data, method) {
  var uri = API_BASE + path;
  if (params) {
    uri += "?" + params.toString();
  }
  var r = d3.json(uri);

  method = "GET";
  data = null;

  return r.send(method, data);
}

var gComponents;

function get_components() {
  $("#component-loading").progressbar({ value: false });
  return fetch("components-min.json")
    .then(function(r) { return r.json(); })
    .then(function(r) {
      gComponents = r;
      selected_from_url();
      $("#component-loading").hide();
    });
}

function selected_from_url() {
  var sp = new URLSearchParams(window.location.search);
  var components = new Set(sp.getAll("component"));
  gComponents.forEach(function(c) {
    var test = c.product_name + ":" + c.component_name;
    c.selected = components.has(test);
  });
  setup_queries();
}

// Takes an array of bugs and returns only those for which there is
// at least one flag set that's a needinfo where the requestee and
// setter are not the same
function filter_self_needinfos(bugs) {
  return bugs.filter(function(bug) {
    return bug.flags.some(function(flag) {
      return flag.name == "needinfo" && flag.requestee != flag.setter;
    });
  });
}

$(function() {
  $(".badge").hide();
  $("#tabs").tabs({ heightStyle: "fill", active: 1 });
  $("#stale-inner").accordion({ heightStyle: "content", collapsible: true, active: false });

  get_components().then(setup_components).then(() => {
    var selected = gComponents.filter(function(c) { return c.selected; });
    if (selected.length) {
      // Select the "stale" tab
      $("#tabs").tabs("option", "active", 2);
    }
  });
  d3.select("#filter").on("input", function() {
    setup_components();
  });
  window.addEventListener("popstate", function() {
    selected_from_url();
    setup_components();
  });
});

function setup_components() {
  var search = d3.select("#filter").property("value").toLowerCase().split(/\s+/).filter(function(w) { return w.length > 0; });
  var filtered;
  if (search.length == 0) {
    filtered = gComponents;
  } else {
    filtered = gComponents.filter(function(c) {
      var search_name = (c.product_name + ": " + c.component_name + " " + c.component_description).toLowerCase();
      var found = true;
      search.forEach(function(w) {
        if (search_name.indexOf(w) == -1) {
          found = false;
        }
      });
      return found;
    });
  }
  var rows = d3.select("#components tbody").selectAll("tr")
    .data(filtered, function(c) { return c.product_id + "_" + c.component_id; });
  var new_rows = rows.enter().append("tr");
  new_rows.on("click", function(d) {
    d.selected = !d.selected;
    d3.select(this).select("input").property("checked", d.selected);
    navigate_url();
    setup_queries();
  });
  new_rows.append("th").append("input")
    .attr("type", "checkbox");
  new_rows.append("th").text(function(d) {
    return d.product_name + ": " + d.component_name;
  });
  new_rows.append("td").text(function(d) {
    return d.component_description;
  });
  rows.exit().remove();
  rows.selectAll("input").property("checked", function(d) { return !!d.selected; });
  document.getElementById('filter').removeAttribute('disabled');
}

var gPendingQueries = new Set();

function setup_queries() {
  gPendingQueries.forEach(function(r) {
    r.abort();
  });
  gPendingQueries.clear();

  var selected = gComponents.filter(function(c) { return c.selected; });
  var products = new Set();
  var components = new Set();
  selected.forEach(function(c) {
    products.add(c.product_name);
    components.add(c.component_name);
  });

  var common_params = new URLSearchParams();
  Array.from(products.values()).forEach(function(p) {
    common_params.append("product", p);
  });
  Array.from(components.values()).forEach(function(c) {
    common_params.append("component", c);
  });

  var to_triage = make_search({
    email1: "intermittent-bug-filer%40mozilla.bugs",
    emailreporter1: "1",
    emailtype1: "notequals",
    email2: "wptsync%40mozilla.bugs",
    emailreporter2: "1",
    emailtype2: "notequals",
    resolution: "---",
    f1: "bug_type",
    o1: "equals",
    v1: "defect",
    f2: "flagtypes.name",
    o2: "notsubstring",
    v2: "needinfo%3F",
    f3: "OP",
    f4: "bug_severity",
    o4: "anyexact",
    v4: "--",
    f5: "component",
    o5: "equals",
    v5: "untriaged",
    f6: "CP",
    j3: "OR",
    limit: "0",
    chfield: "[Bug creation]",
    chfieldto: "Now",
    chfieldfrom: FIRST_NIGHTLY_CURRENT, // Change to first nightly of current release
  }, common_params);
  document.getElementById("triage-list").href = "https://bugzilla.mozilla.org/buglist.cgi?" + to_triage.toString();
  populate_table($("#need-decision"), to_triage, $("#need-decision-marker"), !!selected.length);

  var stale_needinfo = make_search({
    f1: "flagtypes.name",
    o1: "substring",
    v1: "needinfo",
    f2: "delta_ts",
    o2: "lessthan", // means "older than"
    v2: "14d",
    resolution: "---",
    query_format: "advanced",
  }, common_params);
  document.getElementById("stuck-list").href = "https://bugzilla.mozilla.org/buglist.cgi?" + stale_needinfo.toString();
  populate_table($("#needinfo-stale"), stale_needinfo, $("#needinfo-stale-marker"), !!selected.length, filter_self_needinfos);

  var stale_review = make_search({
    f1: "flagtypes.name",
    o1: "regexp",
    v1: "^(review|superreview|ui-review|feedback|a11y-review)\\?",
    resolution: "---",
    f2: "delta_ts",
    o2: "lessthan", // means "older than"
    v2: "5d",
    query_format: "advanced",
  }, common_params);
  document.getElementById("review-list").href = "https://bugzilla.mozilla.org/buglist.cgi?" + stale_review.toString();
  populate_table($("#review-stale"), stale_review, $("#review-stale-marker"), !!selected.length);

  var stale_decision = make_search({
    keywords: "regression",
    keywords_type: "allwords",
    v1: "affected,unaffected,fixed,verified,disabled,verified disabled,wontfix,fix-optional",
    chfieldto: "Now",
    o1: "nowords",
    chfield: "[Bug creation]",
    chfieldfrom: FIRST_NIGHTLY_NEXT, // change to date of first nightly of next version at release
    f1: STATUS_BETA_VERSION, // change to next version at release
    resolution: "---",
    query_format: "advanced"
  }, common_params);

  document.getElementById("decision-list").href = "https://bugzilla.mozilla.org/buglist.cgi?" + stale_decision.toString();
  populate_table($("#decision-stale"), stale_decision, $("#decision-stale-marker"), !!selected.length);

  var stale_range = make_search({
    chfield: "[Bug creation]",
    chfieldfrom: FIRST_NIGHTLY_NEXT, // change to date of first nightly of next version at release
    chfieldto: "Now",
    f1: STATUS_RELEASE_VERSION, // increment version numbers at release
    f2: STATUS_BETA_VERSION,
    j_top: "OR",
    keywords: "regression",
    keywords_type: "allwords",
    o1: "nowords",
    o2: "nowords",
    query_format: "advanced",
    resolution: "---",
    v1: "affected,unaffected,fixed,verified,disabled,verified disabled,wontfix,fix-optional",
    v2: "affected,unaffected,fixed,verified,disabled,verified disabled,wontfix,fix-optional"
  }, common_params);
  document.getElementById("range-list").href = "https://bugzilla.mozilla.org/buglist.cgi?" + stale_range.toString();
  populate_table($("#range-stale"), stale_range, $("#range-stale-marker"), !!selected.length);
}

function navigate_url() {
  var u = new URL(window.location.href);
  var sp = u.searchParams;
  sp.delete("component");
  var selected = gComponents.filter(function(c) { return c.selected; });
  selected.forEach(function(c) {
    sp.append("component", c.product_name + ":" + c.component_name);
  });
  window.history.pushState(undefined, undefined, u.href);
}

function make_search(o, base) {
  var s = new URLSearchParams(base);
  Object.keys(o).forEach(function(k) {
    var v = o[k];
    if (v instanceof Array) {
      v.forEach(function(v2) {
        s.append(k, v2);
      });
    } else {
      s.append(k, v);
    }
  });
  return s;
}

function bug_component(d) {
  return d.product + ": " + d.component;
}

function bug_type(d) {
  return d.type;
}

function bug_description(d) {
  var s = d.summary;
  if (d.keywords.length) {
    s += " " + d.keywords.join(",");
  }
  return s;
}

function bug_users(d) {
  var s = "Owner: " + d.assigned_to;
  s += " Reporter: " + d.creator;
  return s;
}

function bug_created(d) {
  return d3.time.format("%Y-%m-%d %H:%M")(new Date(d.creation_time));
}

function bug_priority(d) {
    var priority = '';
    switch (d.priority.toLowerCase()) {
        case '--':
            priority = 'No Priority';
            break;
        case 'p1':
            priority = 'P1: This Release/Iteration';
            break;
        case 'p2':
            priority = 'P2: Next Release/Iteration';
            break;
        case 'p3':
            priority = 'P3: Backlog';
            break;
        case 'p4':
            priority = 'P4: Bot Managed';
            break;
        case 'p5':
            priority = 'P5: Won\'t fix but will accept a patch';
            break;
        default:
            priority = 'Undefined (this shouldn\'t happen; contact the maintainer)';
    }
    return priority;
}

function bug_severity(d) {
    var severity = '';
    switch(d.severity.toLowerCase()) {
      case '--':
        severity = 'No Severity Set';
        break;
      case 's1':
        severity = 'S1: (Catastrophic)';
        break;
      case 's2':
        severity = 'S2: (Serious)';
        break;
      case 's3':
        severity = 'S3: (Normal)';
        break;
      case 's4':
        severity = 'S4: (Trivial)';
        break;
      case 'n/a':
        severity = 'Triage problem: Defects cannot have a severity of N/A';
        break;
      case 'normal':
        severity = 'Normal (this is the old default value; and should be reviewed)';
        break;
      default:
        severity = d.severity + ' (This should be updated to the new, correct value)';
      }
      return severity;
}

function populate_table(s, params, marker, some_selected, filter_fn) {
  if (!some_selected) {
    $(".p", s).hide();
    d3.select(s[0]).selectAll('.bugtable > tbody > tr').remove();
    return;
  }
  $(".p", s).progressbar({ value: false }).off("click");
  var r = make_api_request("bug", params).on("load", function(data) {
    gPendingQueries.delete(r);
    $(".p", s)
      .button({ icons: { primary: 'ui-icon-refresh' }, label: 'Refresh', text: false })
      .on("click", function() { populate_table(s, params, marker, true); });
    var bugs = filter_fn ? filter_fn(data.bugs) : data.bugs;
    if (!bugs.length) {
      marker.text("(none!)").removeClass("pending");
    } else {
      marker.text("(" + bugs.length + ")").addClass("pending");
    }
    bugs.sort(function(a, b) { return d3.ascending(a.id, b.id); });
    var rows = d3.select(s[0]).select('.bugtable > tbody').selectAll('tr')
      .data(bugs, function(d) { return d.id; });
    rows.exit().remove();
    var new_rows = rows.enter().append("tr");
    new_rows.append("th").append("a")
      .attr("href", function(d) { return "https://bugzilla.mozilla.org/show_bug.cgi?id=" + d.id; })
      .attr("target", "_blank").text(function(d) { return d.id; });
    new_rows.append("td").classed("bugtype", true);
    new_rows.append("td").classed("bugseverity", true);
    new_rows.append("td").classed("bugpriority", true);
    new_rows.append("td").classed("bugdescription", true);
    new_rows.append("td").classed("bugcomponent", true);
    new_rows.append("td").classed("bugusers", true);
    new_rows.append("td").classed("bugcreated", true);
    rows.select(".bugtype").text(bug_type);
    rows.select(".bugseverity").text(bug_severity);
    rows.select(".bugpriority ").text(bug_priority);
    rows.select(".bugdescription").text(bug_description);
    rows.select(".bugcomponent").text(bug_component);
    rows.select(".bugusers").text(bug_users);
    rows.select(".bugcreated").text(bug_created);
    rows.order();
  }).on("error", function(e) {
    console.log("XHR error", r, e, this);
    gPendingQueries.delete(r);
  });
  gPendingQueries.add(r);
}

<commit_msg>Firefox 79<commit_after>var API_BASE = "https://bugzilla.mozilla.org/rest/";

// change for each release

var FIRST_NIGHTLY_CURRENT = '2020-04-06'; // first nightly of current release
var FIRST_NIGHTLY_NEXT = '2020-05-04'; // first nightly of next version at release
var NIGHTLY = '79';
var BETA    = '78';
var RELEASE = '77';
var ESR     = '68';
var STATUS_RELEASE_VERSION = 'cf_status_firefox' + RELEASE;
var STATUS_BETA_VERSION = 'cf_status_firefox' + BETA;
var STATUS_NIGHTLY_VERSION = 'cf_status_firefox' + NIGHTLY;
var STATUS_ESR_VERSION = 'cf_status_firefox_esr' + ESR;

/**
 * @returns d3.request
 */
function make_api_request(path, params, data, method) {
  var uri = API_BASE + path;
  if (params) {
    uri += "?" + params.toString();
  }
  var r = d3.json(uri);

  method = "GET";
  data = null;

  return r.send(method, data);
}

var gComponents;

function get_components() {
  $("#component-loading").progressbar({ value: false });
  return fetch("components-min.json")
    .then(function(r) { return r.json(); })
    .then(function(r) {
      gComponents = r;
      selected_from_url();
      $("#component-loading").hide();
    });
}

function selected_from_url() {
  var sp = new URLSearchParams(window.location.search);
  var components = new Set(sp.getAll("component"));
  gComponents.forEach(function(c) {
    var test = c.product_name + ":" + c.component_name;
    c.selected = components.has(test);
  });
  setup_queries();
}

// Takes an array of bugs and returns only those for which there is
// at least one flag set that's a needinfo where the requestee and
// setter are not the same
function filter_self_needinfos(bugs) {
  return bugs.filter(function(bug) {
    return bug.flags.some(function(flag) {
      return flag.name == "needinfo" && flag.requestee != flag.setter;
    });
  });
}

$(function() {
  $(".badge").hide();
  $("#tabs").tabs({ heightStyle: "fill", active: 1 });
  $("#stale-inner").accordion({ heightStyle: "content", collapsible: true, active: false });

  get_components().then(setup_components).then(() => {
    var selected = gComponents.filter(function(c) { return c.selected; });
    if (selected.length) {
      // Select the "stale" tab
      $("#tabs").tabs("option", "active", 2);
    }
  });
  d3.select("#filter").on("input", function() {
    setup_components();
  });
  window.addEventListener("popstate", function() {
    selected_from_url();
    setup_components();
  });
});

function setup_components() {
  var search = d3.select("#filter").property("value").toLowerCase().split(/\s+/).filter(function(w) { return w.length > 0; });
  var filtered;
  if (search.length == 0) {
    filtered = gComponents;
  } else {
    filtered = gComponents.filter(function(c) {
      var search_name = (c.product_name + ": " + c.component_name + " " + c.component_description).toLowerCase();
      var found = true;
      search.forEach(function(w) {
        if (search_name.indexOf(w) == -1) {
          found = false;
        }
      });
      return found;
    });
  }
  var rows = d3.select("#components tbody").selectAll("tr")
    .data(filtered, function(c) { return c.product_id + "_" + c.component_id; });
  var new_rows = rows.enter().append("tr");
  new_rows.on("click", function(d) {
    d.selected = !d.selected;
    d3.select(this).select("input").property("checked", d.selected);
    navigate_url();
    setup_queries();
  });
  new_rows.append("th").append("input")
    .attr("type", "checkbox");
  new_rows.append("th").text(function(d) {
    return d.product_name + ": " + d.component_name;
  });
  new_rows.append("td").text(function(d) {
    return d.component_description;
  });
  rows.exit().remove();
  rows.selectAll("input").property("checked", function(d) { return !!d.selected; });
  document.getElementById('filter').removeAttribute('disabled');
}

var gPendingQueries = new Set();

function setup_queries() {
  gPendingQueries.forEach(function(r) {
    r.abort();
  });
  gPendingQueries.clear();

  var selected = gComponents.filter(function(c) { return c.selected; });
  var products = new Set();
  var components = new Set();
  selected.forEach(function(c) {
    products.add(c.product_name);
    components.add(c.component_name);
  });

  var common_params = new URLSearchParams();
  Array.from(products.values()).forEach(function(p) {
    common_params.append("product", p);
  });
  Array.from(components.values()).forEach(function(c) {
    common_params.append("component", c);
  });

  var to_triage = make_search({
    email1: "intermittent-bug-filer%40mozilla.bugs",
    emailreporter1: "1",
    emailtype1: "notequals",
    email2: "wptsync%40mozilla.bugs",
    emailreporter2: "1",
    emailtype2: "notequals",
    resolution: "---",
    f1: "bug_type",
    o1: "equals",
    v1: "defect",
    f2: "flagtypes.name",
    o2: "notsubstring",
    v2: "needinfo%3F",
    f3: "OP",
    f4: "bug_severity",
    o4: "anyexact",
    v4: "--",
    f5: "component",
    o5: "equals",
    v5: "untriaged",
    f6: "CP",
    j3: "OR",
    limit: "0",
    chfield: "[Bug creation]",
    chfieldto: "Now",
    chfieldfrom: FIRST_NIGHTLY_CURRENT, // Change to first nightly of current release
  }, common_params);
  document.getElementById("triage-list").href = "https://bugzilla.mozilla.org/buglist.cgi?" + to_triage.toString();
  populate_table($("#need-decision"), to_triage, $("#need-decision-marker"), !!selected.length);

  var stale_needinfo = make_search({
    f1: "flagtypes.name",
    o1: "substring",
    v1: "needinfo",
    f2: "delta_ts",
    o2: "lessthan", // means "older than"
    v2: "14d",
    resolution: "---",
    query_format: "advanced",
  }, common_params);
  document.getElementById("stuck-list").href = "https://bugzilla.mozilla.org/buglist.cgi?" + stale_needinfo.toString();
  populate_table($("#needinfo-stale"), stale_needinfo, $("#needinfo-stale-marker"), !!selected.length, filter_self_needinfos);

  var stale_review = make_search({
    f1: "flagtypes.name",
    o1: "regexp",
    v1: "^(review|superreview|ui-review|feedback|a11y-review)\\?",
    resolution: "---",
    f2: "delta_ts",
    o2: "lessthan", // means "older than"
    v2: "5d",
    query_format: "advanced",
  }, common_params);
  document.getElementById("review-list").href = "https://bugzilla.mozilla.org/buglist.cgi?" + stale_review.toString();
  populate_table($("#review-stale"), stale_review, $("#review-stale-marker"), !!selected.length);

  var stale_decision = make_search({
    keywords: "regression",
    keywords_type: "allwords",
    v1: "affected,unaffected,fixed,verified,disabled,verified disabled,wontfix,fix-optional",
    chfieldto: "Now",
    o1: "nowords",
    chfield: "[Bug creation]",
    chfieldfrom: FIRST_NIGHTLY_NEXT, // change to date of first nightly of next version at release
    f1: STATUS_BETA_VERSION, // change to next version at release
    resolution: "---",
    query_format: "advanced"
  }, common_params);

  document.getElementById("decision-list").href = "https://bugzilla.mozilla.org/buglist.cgi?" + stale_decision.toString();
  populate_table($("#decision-stale"), stale_decision, $("#decision-stale-marker"), !!selected.length);

  var stale_range = make_search({
    chfield: "[Bug creation]",
    chfieldfrom: FIRST_NIGHTLY_NEXT, // change to date of first nightly of next version at release
    chfieldto: "Now",
    f1: STATUS_RELEASE_VERSION, // increment version numbers at release
    f2: STATUS_BETA_VERSION,
    j_top: "OR",
    keywords: "regression",
    keywords_type: "allwords",
    o1: "nowords",
    o2: "nowords",
    query_format: "advanced",
    resolution: "---",
    v1: "affected,unaffected,fixed,verified,disabled,verified disabled,wontfix,fix-optional",
    v2: "affected,unaffected,fixed,verified,disabled,verified disabled,wontfix,fix-optional"
  }, common_params);
  document.getElementById("range-list").href = "https://bugzilla.mozilla.org/buglist.cgi?" + stale_range.toString();
  populate_table($("#range-stale"), stale_range, $("#range-stale-marker"), !!selected.length);
}

function navigate_url() {
  var u = new URL(window.location.href);
  var sp = u.searchParams;
  sp.delete("component");
  var selected = gComponents.filter(function(c) { return c.selected; });
  selected.forEach(function(c) {
    sp.append("component", c.product_name + ":" + c.component_name);
  });
  window.history.pushState(undefined, undefined, u.href);
}

function make_search(o, base) {
  var s = new URLSearchParams(base);
  Object.keys(o).forEach(function(k) {
    var v = o[k];
    if (v instanceof Array) {
      v.forEach(function(v2) {
        s.append(k, v2);
      });
    } else {
      s.append(k, v);
    }
  });
  return s;
}

function bug_component(d) {
  return d.product + ": " + d.component;
}

function bug_type(d) {
  return d.type;
}

function bug_description(d) {
  var s = d.summary;
  if (d.keywords.length) {
    s += " " + d.keywords.join(",");
  }
  return s;
}

function bug_users(d) {
  var s = "Owner: " + d.assigned_to;
  s += " Reporter: " + d.creator;
  return s;
}

function bug_created(d) {
  return d3.time.format("%Y-%m-%d %H:%M")(new Date(d.creation_time));
}

function bug_priority(d) {
    var priority = '';
    switch (d.priority.toLowerCase()) {
        case '--':
            priority = 'No Priority';
            break;
        case 'p1':
            priority = 'P1: This Release/Iteration';
            break;
        case 'p2':
            priority = 'P2: Next Release/Iteration';
            break;
        case 'p3':
            priority = 'P3: Backlog';
            break;
        case 'p4':
            priority = 'P4: Bot Managed';
            break;
        case 'p5':
            priority = 'P5: Won\'t fix but will accept a patch';
            break;
        default:
            priority = 'Undefined (this shouldn\'t happen; contact the maintainer)';
    }
    return priority;
}

function bug_severity(d) {
    var severity = '';
    switch(d.severity.toLowerCase()) {
      case '--':
        severity = 'No Severity Set';
        break;
      case 's1':
        severity = 'S1: (Catastrophic)';
        break;
      case 's2':
        severity = 'S2: (Serious)';
        break;
      case 's3':
        severity = 'S3: (Normal)';
        break;
      case 's4':
        severity = 'S4: (Trivial)';
        break;
      case 'n/a':
        severity = 'Triage problem: Defects cannot have a severity of N/A';
        break;
      case 'normal':
        severity = 'Normal (this is the old default value; and should be reviewed)';
        break;
      default:
        severity = d.severity + ' (This should be updated to the new, correct value)';
      }
      return severity;
}

function populate_table(s, params, marker, some_selected, filter_fn) {
  if (!some_selected) {
    $(".p", s).hide();
    d3.select(s[0]).selectAll('.bugtable > tbody > tr').remove();
    return;
  }
  $(".p", s).progressbar({ value: false }).off("click");
  var r = make_api_request("bug", params).on("load", function(data) {
    gPendingQueries.delete(r);
    $(".p", s)
      .button({ icons: { primary: 'ui-icon-refresh' }, label: 'Refresh', text: false })
      .on("click", function() { populate_table(s, params, marker, true); });
    var bugs = filter_fn ? filter_fn(data.bugs) : data.bugs;
    if (!bugs.length) {
      marker.text("(none!)").removeClass("pending");
    } else {
      marker.text("(" + bugs.length + ")").addClass("pending");
    }
    bugs.sort(function(a, b) { return d3.ascending(a.id, b.id); });
    var rows = d3.select(s[0]).select('.bugtable > tbody').selectAll('tr')
      .data(bugs, function(d) { return d.id; });
    rows.exit().remove();
    var new_rows = rows.enter().append("tr");
    new_rows.append("th").append("a")
      .attr("href", function(d) { return "https://bugzilla.mozilla.org/show_bug.cgi?id=" + d.id; })
      .attr("target", "_blank").text(function(d) { return d.id; });
    new_rows.append("td").classed("bugtype", true);
    new_rows.append("td").classed("bugseverity", true);
    new_rows.append("td").classed("bugpriority", true);
    new_rows.append("td").classed("bugdescription", true);
    new_rows.append("td").classed("bugcomponent", true);
    new_rows.append("td").classed("bugusers", true);
    new_rows.append("td").classed("bugcreated", true);
    rows.select(".bugtype").text(bug_type);
    rows.select(".bugseverity").text(bug_severity);
    rows.select(".bugpriority ").text(bug_priority);
    rows.select(".bugdescription").text(bug_description);
    rows.select(".bugcomponent").text(bug_component);
    rows.select(".bugusers").text(bug_users);
    rows.select(".bugcreated").text(bug_created);
    rows.order();
  }).on("error", function(e) {
    console.log("XHR error", r, e, this);
    gPendingQueries.delete(r);
  });
  gPendingQueries.add(r);
}

<|endoftext|>


<commit_before>// https://eslint.org/docs/user-guide/configuring

module.exports = {
  root: true,
  parserOptions: {
    parser: 'babel-eslint'
  },
  env: {
    browser: true,
  },
  extends: [
    // https://github.com/vuejs/eslint-plugin-vue#priority-a-essential-error-prevention
    // consider switching to `plugin:vue/strongly-recommended` or `plugin:vue/recommended` for stricter rules.
    'plugin:vue/essential',
    // https://github.com/standard/standard/blob/master/docs/RULES-en.md
    'standard'
  ],
  // required to lint *.vue files
  plugins: [
    'eslint:recommended',
    'vue'
  ],
  // add your custom rules here
  rules: {
    // allow async-await
    'generator-star-spacing': 'off',
    // allow debugger during development
    'no-debugger': process.env.NODE_ENV === 'production' ? 'error' : 'off'
  }
}
<commit_msg>Remove recommended<commit_after>// https://eslint.org/docs/user-guide/configuring

module.exports = {
  root: true,
  parserOptions: {
    parser: 'babel-eslint'
  },
  env: {
    browser: true,
  },
  extends: [
    // https://github.com/vuejs/eslint-plugin-vue#priority-a-essential-error-prevention
    // consider switching to `plugin:vue/strongly-recommended` or `plugin:vue/recommended` for stricter rules.
    'plugin:vue/essential',
    // https://github.com/standard/standard/blob/master/docs/RULES-en.md
    'standard'
  ],
  // required to lint *.vue files
  plugins: [
    'vue'
  ],
  // add your custom rules here
  rules: {
    // allow async-await
    'generator-star-spacing': 'off',
    // allow debugger during development
    'no-debugger': process.env.NODE_ENV === 'production' ? 'error' : 'off'
  }
}
<|endoftext|>


<commit_before>module.exports = {
   extends: 'frost-standard'
}
<commit_msg>add ocd rule back until we upgrade to new version of ember-test-utils<commit_after>module.exports = {
   extends: 'frost-standard',
   rules: {
    'ocd/sort-import-declarations': [
      2,
      {
        'localPrefixes': [
          '../',
          './',
          'dummy/',
          'ember-prop-types'
        ]
      }
    ]
  }
}
<|endoftext|>


<commit_before>module.exports = {
  "parserOptions": {
    "sourceType": "module",
    "ecmaVersion": 6,
    "ecmaFeatures": {
      "jsxnec": true,
    },
  },
  "installedESLint": true,
  "extends": "eslint:recommended",
  "globals": [
    // browser
    "window", "document", "URL", "XMLHttpRequest",
    // browser / nodejs
    "console", "setTimeout", "setInterval", "clearInterval",
    // nodejs
    "global",
    // EcmaScript 6
    "Promise", "Map", "Set", "WeakMap",
    // commonjs
    "require", "module", "exports"
    //
  ].reduce((m, key)=>{m[key]=true;return m}, {}),
  "rules": {
    // 0 - off, 1 - warning, 2 - error
    "indent": ["error", 2, { "SwitchCase": 1 }],
    "semi": [0, "always"],
    "comma-dangle": [2, "only-multiline"],
    "no-cond-assign": 2,
    "no-console": [2, { allow: ["warn", "info", "error", "assert"] }],
    "no-constant-condition": 2,
    "no-control-regex": 2,
    "no-debugger": 2,
    "no-dupe-args": 2,
    "no-dupe-keys": 2,
    "no-duplicate-case": 2,
    "no-empty": 1,
    "no-empty-character-class": 2,
    "no-ex-assign": 2,
    "no-extra-boolean-cast": 2,
    "no-extra-parens": 0,
    "no-extra-semi": 2,
    "no-func-assign": 2,
    "no-inner-declarations": 2,
    "no-invalid-regexp": 2,
    "no-irregular-whitespace": 2,
    "no-negated-in-lhs": 2,
    "no-obj-calls": 2,
    // turned of as we want to be able to use this.hasOwnProperty() for instance
    "no-prototype-builtins": 0,
    "no-regex-spaces": 2,
    "no-restricted-globals": [2, "Document", "Node"],
    "no-sparse-arrays": 0,
    "no-unexpected-multiline": 2,
    "no-unreachable": 2,
    "no-unsafe-finally": 2,
    "use-isnan": 2,
    "valid-jsdoc": 0,
    "valid-typeof": 2,
    "strict": [2, "safe"],

    // Best practices
    "accessor-pairs": 0,
    "array-callback-return": 2,
    "block-scoped-var": 2,
    "complexity": [0, 10],
    "consistent-return": 0,
    "curly": [2, "multi-line"],
    "default-case": 2,
    "dot-location": [2, 'property'],
    "dot-notation": 0,
    "eqeqeq": 2,
    "guard-for-in": 2,
    "no-alert": 2,
    "no-caller": 2,
    "no-case-declarations": 2,
    "no-div-regex": 2,
    "no-else-return": 0,
    "no-empty-function": 0,
    // if you want to check for undefined or null use lodash/isNil
    "no-eq-null": 2,
    "no-eval": 2,
    "no-extend-native": 2,
    "no-extra-bind": 2,
    "no-extra-label": 2,
    "no-fallthrough": 2,
    "no-floating-decimal": 2,
    "no-implicit-coercion": 2,
    "no-implicit-globals": 2,
    "no-implied-eval": 2,
    "no-invalid-this": 2,
    "no-iterator": 2,
    "no-labels": 2,
    "no-lone-blocks": 0,
    "no-loop-func": 2,
    "no-magic-numbers": 0,
    "no-multi-spaces": 2,
    "no-multi-str": 0,
    "no-native-reassign": 2,
    "no-new": 0,
    "no-new-func": 0,
    "no-new-wrappers": 2,
    "no-octal": 2,
    "no-octal-escape": 2,
    "no-param-reassign": 0,
    "no-proto": 2,
    "no-redeclare": 2,
    "no-return-assign": 2,
    "no-script-url": 2,
    "no-self-assign": 2,
    "no-self-compare": 2,
    "no-sequences": 2,
    "no-throw-literal": 2,
    "no-unmodified-loop-condition": 2,
    "no-unused-expressions": 2,
    "no-unused-labels": 2,
    "no-useless-call": 2,
    "no-useless-concat": 2,
    "no-useless-escape": 2,
    "no-void": 2,
    "no-warning-comments": 0,
    "no-with": 2,
    "radix": 2,
    "vars-on-top": 0,
    "wrap-iife": 2,
    "yoda": 0,
    // variables
    "init-declarations": 0,
    "no-catch-shadow": 2,
    "no-delete-var": 2,
    "no-label-var": 2,
    "no-restricted-globals": 2,
    "no-shadow": 0,
    "no-shadow-restricted-names": 2,
    "no-undef": 2,
    "no-undef-init": 2,
    "no-undefined": 0,
    "no-unused-vars": 2,
    "no-use-before-define": [2, { "functions": false, "classes": false }]
  }
};<commit_msg>Remove jsxnec<commit_after>module.exports = {
  "parserOptions": {
    "sourceType": "module",
    "ecmaVersion": 6
  },
  "installedESLint": true,
  "extends": "eslint:recommended",
  "globals": [
    // browser
    "window", "document", "URL", "XMLHttpRequest",
    // browser / nodejs
    "console", "setTimeout", "setInterval", "clearInterval",
    // nodejs
    "global",
    // EcmaScript 6
    "Promise", "Map", "Set", "WeakMap",
    // commonjs
    "require", "module", "exports"
    //
  ].reduce((m, key)=>{m[key]=true;return m}, {}),
  "rules": {
    // 0 - off, 1 - warning, 2 - error
    "indent": ["error", 2, { "SwitchCase": 1 }],
    "semi": [0, "always"],
    "comma-dangle": [2, "only-multiline"],
    "no-cond-assign": 2,
    "no-console": [2, { allow: ["warn", "info", "error", "assert"] }],
    "no-constant-condition": 2,
    "no-control-regex": 2,
    "no-debugger": 2,
    "no-dupe-args": 2,
    "no-dupe-keys": 2,
    "no-duplicate-case": 2,
    "no-empty": 1,
    "no-empty-character-class": 2,
    "no-ex-assign": 2,
    "no-extra-boolean-cast": 2,
    "no-extra-parens": 0,
    "no-extra-semi": 2,
    "no-func-assign": 2,
    "no-inner-declarations": 2,
    "no-invalid-regexp": 2,
    "no-irregular-whitespace": 2,
    "no-negated-in-lhs": 2,
    "no-obj-calls": 2,
    // turned of as we want to be able to use this.hasOwnProperty() for instance
    "no-prototype-builtins": 0,
    "no-regex-spaces": 2,
    "no-restricted-globals": [2, "Document", "Node"],
    "no-sparse-arrays": 0,
    "no-unexpected-multiline": 2,
    "no-unreachable": 2,
    "no-unsafe-finally": 2,
    "use-isnan": 2,
    "valid-jsdoc": 0,
    "valid-typeof": 2,
    "strict": [2, "safe"],

    // Best practices
    "accessor-pairs": 0,
    "array-callback-return": 2,
    "block-scoped-var": 2,
    "complexity": [0, 10],
    "consistent-return": 0,
    "curly": [2, "multi-line"],
    "default-case": 2,
    "dot-location": [2, 'property'],
    "dot-notation": 0,
    "eqeqeq": 2,
    "guard-for-in": 2,
    "no-alert": 2,
    "no-caller": 2,
    "no-case-declarations": 2,
    "no-div-regex": 2,
    "no-else-return": 0,
    "no-empty-function": 0,
    // if you want to check for undefined or null use lodash/isNil
    "no-eq-null": 2,
    "no-eval": 2,
    "no-extend-native": 2,
    "no-extra-bind": 2,
    "no-extra-label": 2,
    "no-fallthrough": 2,
    "no-floating-decimal": 2,
    "no-implicit-coercion": 2,
    "no-implicit-globals": 2,
    "no-implied-eval": 2,
    "no-invalid-this": 2,
    "no-iterator": 2,
    "no-labels": 2,
    "no-lone-blocks": 0,
    "no-loop-func": 2,
    "no-magic-numbers": 0,
    "no-multi-spaces": 2,
    "no-multi-str": 0,
    "no-native-reassign": 2,
    "no-new": 0,
    "no-new-func": 0,
    "no-new-wrappers": 2,
    "no-octal": 2,
    "no-octal-escape": 2,
    "no-param-reassign": 0,
    "no-proto": 2,
    "no-redeclare": 2,
    "no-return-assign": 2,
    "no-script-url": 2,
    "no-self-assign": 2,
    "no-self-compare": 2,
    "no-sequences": 2,
    "no-throw-literal": 2,
    "no-unmodified-loop-condition": 2,
    "no-unused-expressions": 2,
    "no-unused-labels": 2,
    "no-useless-call": 2,
    "no-useless-concat": 2,
    "no-useless-escape": 2,
    "no-void": 2,
    "no-warning-comments": 0,
    "no-with": 2,
    "radix": 2,
    "vars-on-top": 0,
    "wrap-iife": 2,
    "yoda": 0,
    // variables
    "init-declarations": 0,
    "no-catch-shadow": 2,
    "no-delete-var": 2,
    "no-label-var": 2,
    "no-restricted-globals": 2,
    "no-shadow": 0,
    "no-shadow-restricted-names": 2,
    "no-undef": 2,
    "no-undef-init": 2,
    "no-undefined": 0,
    "no-unused-vars": 2,
    "no-use-before-define": [2, { "functions": false, "classes": false }]
  }
};<|endoftext|>


<commit_before>module.exports = {
    // aliases: {
    //     $: 'third-party-libs/jquery',
    //     _: 'third-party-libs/underscore',
    // },
    // ignorePackagePrefixes: ['my-company-'],
    // mergableOptions: {
    //     aliases: true,
    //     coreModules: true,
    //     namedExports: true,
    //     globals: true,
    // }
    // importFunction: 'require',
    // environments: ['browser', 'node'],
    logLevel: 'debug',
    excludes: ['./e2e/node_modules/**', './*.js', './mockData/**', './routes/**', './coverage/**', './tests/**', './build-assets/**'],
    emptyLineBetweenGroups: false,
    sortImports: true,
    groupImports: true,
    importDevDependencies: true,
    danglingCommas: false,
    stripFileExtensions: ['.jsx', '.js'],
    maxLineLength: 120,
    tab: '    ',
    mergableOptions: { globals: false },
    globals: [ 'module' ],
    namedExports: {
        'prop-types': [ 'bool', 'number', 'string', 'object', 'array', 'func', 'element', 'any', 'oneOfType', 'oneOf', 'arrayOf', 'objectOf', 'shape' ],
        'immutable': [ 'fromJS', 'Map', 'List', 'OrderedMap', 'OrderedSet', 'Set', 'is', 'isImmutable' ],
        'reselect': [ 'createSelector' ],
        'redux-saga/effects': [ 'all', 'put', 'call', 'select', 'take', 'race', 'takeLatest', 'takeEvery', 'fork', 'cancel', 'spawn' ],
        'redux-saga': [ 'delay' ],
        'react-redux': [ 'connect' ],
        'redux': [ 'bindActionCreators' ]
    },
    useRelativePaths({ pathToImportedModule, pathToCurrentFile }) {
        // if (pathToCurrentFile.includes('app') || pathToCurrentFile.includes('e2e')) {
            return false
        // }

        // return true
    },
    declarationKeyword({ pathToImportedModule, pathToCurrentFile }) {
        // if (pathToCurrentFile.includes('app')) {
            return 'import'
        // }

        // return 'const'
    },
    moduleNameFormatter({ moduleName, pathToCurrentFile, pathToImportedModule }) {
        if (moduleName.startsWith('app/js/')) {
            return moduleName.replace('app/js/', '')
        }

        if (moduleName.startsWith('e2e/')) {
            return moduleName.replace('e2e/', '')
        }

        return moduleName
    },
    importStatementFormatter({ importStatement }) {
        return importStatement.replace(/;$/, '');
    }
}
<commit_msg>update<commit_after>module.exports = {
    // aliases: {
    //     $: 'third-party-libs/jquery',
    //     _: 'third-party-libs/underscore',
    // },
    // ignorePackagePrefixes: ['my-company-'],
    // mergableOptions: {
    //     aliases: true,
    //     coreModules: true,
    //     namedExports: true,
    //     globals: true,
    // }
    // importFunction: 'require',
    // environments: ['browser', 'node'],
    logLevel: 'debug',
    excludes: ['./e2e/node_modules/**', './*.js', './mockData/**', './routes/**', './coverage/**', './tests/**', './build-assets/**'],
    emptyLineBetweenGroups: false,
    sortImports: true,
    groupImports: true,
    importDevDependencies: true,
    danglingCommas: false,
    stripFileExtensions: ['.jsx', '.js'],
    maxLineLength: 120,
    tab: '    ',
    mergableOptions: { globals: false },
    globals: [ 'module' ],
    namedExports: {
        'prop-types': [ 'bool', 'number', 'string', 'object', 'array', 'func', 'element', 'any', 'oneOfType', 'oneOf', 'arrayOf', 'objectOf', 'shape' ],
        'react-immutable-proptypes': [ 'list', 'map' ],
        'immutable': [ 'fromJS', 'Map', 'List', 'OrderedMap', 'OrderedSet', 'Set', 'is', 'isImmutable' ],
        'reselect': [ 'createSelector' ],
        'redux-saga/effects': [ 'all', 'join', 'put', 'call', 'select', 'take', 'race', 'takeLatest', 'takeEvery', 'fork', 'cancel', 'spawn' ],
        'redux-saga': [ 'delay' ],
        'react-redux': [ 'connect' ],
        'redux': [ 'bindActionCreators' ]
    },
    useRelativePaths({ pathToImportedModule, pathToCurrentFile }) {
        // if (pathToCurrentFile.includes('app') || pathToCurrentFile.includes('e2e')) {
            return false
        // }

        // return true
    },
    declarationKeyword({ pathToImportedModule, pathToCurrentFile }) {
        // if (pathToCurrentFile.includes('app')) {
            return 'import'
        // }

        // return 'const'
    },
    moduleNameFormatter({ moduleName, pathToCurrentFile, pathToImportedModule }) {
        if (moduleName.startsWith('app/js/')) {
            return moduleName.replace('app/js/', '')
        }

        if (moduleName.startsWith('e2e/')) {
            return moduleName.replace('e2e/', '')
        }

        return moduleName
    },
    importStatementFormatter({ importStatement }) {
        return importStatement.replace(/;$/, '');
    }
}
<|endoftext|>


<commit_before>var tripcode = require('tripcode'),
	crypto = require('crypto'),
	uuid = require('node-uuid'),
	easyimg = require('easyimage'),
	path = require('path'),
	fs = require('fs-extra'),
	async = require('async'),
	timestamps = require('mongoose-timestamp'),
	autoIncrement = require('mongoose-auto-increment'),
	FFmpeg = require('fluent-ffmpeg'),
	ffprobe = FFmpeg.ffprobe,
	tripsalt = nconf.get('api:tripsalt');

const MAX_IMAGE_SIZE = 2 * 1024; /* 2 MB */
const MAX_VIDEO_SIZE = 3 * 1024; /* 3 MB */

var parseMetadata = function(metadata){
	if(metadata == null){
		return null;
	}

	if(!metadata.format){
		return null;
	}

	var data = {};
	metadata.streams.forEach(function(s){
		if(s.codec_type === 'video'){
			data.video = {
				'codec': s.codec_name,
				'resolution': {
					'w': s.width,
					'h': s.height
				}
			};
		}else if(s.codec_type === 'audio'){
			data.audio = {
				'codec': s.codec_name,
			};
		}
	});

	data.container = metadata.format.format_name.split(',')[0];
	data.bitrate = parseInt(metadata.format.bit_rate) / 1000;
	data.seconds = Math.ceil(parseFloat(metadata.format.duration));
	return data;
};
var validateVideo = function validateVideo(file, filename, _callback){
	//console.log('a', file, filename);
	var uploadPath = nconf.get('api:upload_path'),
		full = path.join(uploadPath, 'full', filename);

	ffprobe(file.path, function(err, metadata){
		var errors = [];
		if(!metadata){
			return _callback(new Error('Could not find metadata'), file, filename);
		}
		metadata = parseMetadata(metadata);

		if(metadata.video && metadata.video.resolution){
			var res = metadata.video.resolution;
			if(res.w > 2048 || res.h > 2048){
				errors.push('Max video resolution is 2048x2048');
			}
		}

		if(metadata.video && metadata.video.codec !== 'vp8'){
			errors.push('Video must be in WebM format');
		}

		if(parseFloat(metadata.seconds) > 120){
			errors.push('Max video duration is 120 sec');
		}

		if(metadata.audio){
			errors.push('Video can not contain an audio track')
		}

		if(errors.length > 0){
			return _callback(new Error(errors.join('\n')), file, filename);
		}else{
			fs.copy(file.path, full, function(err){
				_callback(err, file, filename);
			});
		}
	});
};
var generateVideoThumb = function generateVideoThumb(file, filename, _callback){
	var uploadPath = nconf.get('api:upload_path'),
		thumb = filename.replace('.webm', '');

	new FFmpeg({
		source: file.path
	})
	.withSize(nconf.get('video:thumbnail:width') + 'x' + nconf.get('video:thumbnail:height'))
	.on('error', function(err){
		_callback(err, filename, null);
	})
	.on('end', function(filenames){
		_callback(null, filename, filenames[0]);
	})
  .takeScreenshots({
		count: 1,
  	filename: thumb,
		fileextension: '.png'
	}, path.join(uploadPath, 'thumb'));
};
var uploadImage = function uploadImage(file, _callback){
	if(file && file.path && file.name){
		var filename = 'upload-' + uuid.v1() + '.png',
			uploadPath = nconf.get('api:upload_path'),
			full = path.join(uploadPath, 'full', filename),
			thumb = path.join(uploadPath, 'thumb', filename);

		if(!file.mimetype.match(/^image\//i)){
			return _callback(new Error('Invalid file format'));
		}
		if(file.size > parseInt(MAX_IMAGE_SIZE, 10) * 1024){
			return _callback(new Error('File too big'));
		}

		async.parallel([function(cb){
			easyimg.thumbnail({
				'src': file.path,
				'dst': thumb,
				'width': nconf.get('image:thumbnail:width'),
				'height': nconf.get('image:thumbnail:height'),
				'x': 0,
				'y': 0
			}, cb);
		}, function(cb){
			easyimg.convert({
				'src': file.path,
				'dst': full
			}, cb);
		}], function(err){
			_callback(err, filename);
		});
	}else{
		_callback(null);
	}
};
var uploadVideo = function uploadVideo(file, _callback){
	//return _callback(new Error('Video upload is not supported yet'))
	if(file && file.path && file.name){
		var filename = 'upload-' + uuid.v1() + '.webm';
			async.waterfall([
				function(cb){
					fs.readFile(file.path, function(err, buffer){
						if(file.mimetype !== 'video/webm'){
							return cb(new Error('Invalid file format'), null, null);
						}
						if(buffer.length > parseInt(MAX_VIDEO_SIZE, 10) * 1024){
							return cb(new Error('File too big'), null, null);
						}
						cb(null, file, filename);
					});
				},
				validateVideo,
				generateVideoThumb
			], _callback);
	}else{
		_callback(null);
	}
};
var uploadFile = function uploadFile(file, _callback){
	if(file && file.mimetype){
		if(file.mimetype.match(/^image\//i)){
			return uploadImage(file, _callback);
		}else if(file.mimetype == 'video/webm'){
			return uploadVideo(file, _callback);
		}else{
			_callback(null);
		}
	}else{
		_callback(null);
	}
};
var formatPost = function formatPost(post, _callback){
	var tripindex = post.name.indexOf('#');
	if(tripindex > -1){
		var trip = post.name.substr(tripindex + 1),
			secure = trip.indexOf('#') === 0;

		if(secure){
			trip = crypto.createHash('sha1')
			.update(trip.substr(1) + tripsalt)
			.digest('base64')
			.toString();
		}

		post.tripcode = (secure ? '!!' : '!') + tripcode(trip);
		post.name = post.name.slice(0, tripindex);
	}

	if(post.ext){
		post.type = post.ext == '.webm' ? 'video' : 'image';
	}else{
		post.type = 'text';
	}

	if(_callback){
		_callback(null, post);
	}

	return post;
};
module.exports = function(db){
	var Schema = db.Schema;
	var PostSchema = new Schema({
		'board': String,
		'parent': {type: Number, required: true, default: 0},
		'ip': String,
		'name': String,
		'tripcode': String,
		'email': String,
		'subject': String,
		'comment': String,
		'file': String,
		'ext': String,
		'time': {type: Date, default: Date.now},
		'type': String,
		'closed': 0
	}), Post;
	autoIncrement.initialize(db.connection);
	PostSchema.plugin(timestamps);
	PostSchema.plugin(autoIncrement.plugin, {
		'model': 'Post',
		'startAt': 1
	});
	PostSchema.set('redisCache', true);
	Post = db.model('Post', PostSchema);

	return {
		getIndexThreads: function getIndexThreads(board, page, _callback){
			var page = page ? parseInt(page, 10) : 1,
				perPage = nconf.get('board:threadsPerpage'),
				offset = (page - 1) * perPage,
				find = {};

			if(board){
				find['board'] = board;
			}
			find['parent'] = 0;

			Post.find(find)
			.sort({updatedAt: -1})
			.skip(offset)
			.limit(perPage)
			.lean()
			.exec(_callback);
		},
		getTotalThreads: function getTotalThreads(board, _callback){
			var find = {};

			if(board){
				find['board'] = board;
			}

			find['parent'] = 0;

			Post.count(find)
			.lean()
			.exec(function(err, total){
				if(!total){
					total = 0;
				}
				_callback(err, total);
			});
		},
		getThread: function getThread(id, page, _callback){
			var page = page ? parseInt(page, 10) : 1,
				perPage = nconf.get('board:threadsPerPage'),
				offset = (page - 1) * perPage,
				find = {};

			find['_id'] = id;
			find['parent'] = 0;

			Post.findOne(find)
			.sort({updatedAt: -1})
			.skip(offset)
			.limit(perPage)
			.lean()
			.exec(function(err, thread){
				_callback(err, thread);
			});
		},
		getThreadReplies: function getThreadReplies(id, limit, _callback){
			var find = {};

			find['parent'] = id;

			Post.find(find)
			.sort({updatedAt: 1})
			.limit(limit)
			.lean()
			.exec(function(err, replies){
				if(replies){
					_callback(err, replies);
				}else{
					_callback(err);
				}
			});
		},
		countThreadReplies: function countThreadReplies(id, _callback){
			var find = {};

			find['parent'] = id;

			Post.count(find)
			.lean()
			.exec(function(err, total){
				if(!total){
					total = 0;
				}
				_callback(err, total);
			});

		},
		newThread: function newThread(params, _callback){
			var p = {
				'board': params.board,
				'name': params.name,
				'tripcode': '',
				'email': params.email,
				'subject': params.subject,
				'comment': params.comment,
				'sticky': params.sticky,
				'ip': params.ip,
				'closed': params.closed
			};

			uploadFile(params.file, function(err, filename){
				if(err){
					return _callback(err);
				}

				if(filename){
					var ext = path.extname(filename) || '.png';
					p['ext'] = ext;
					p['file'] = path.basename(filename, ext);
				}
				var t = new Post(formatPost(p));

				t.save(_callback);
			});
		},
		newReply: function newReply(params, _callback){
			var p = {
				'parent': params.parent,
				'board': params.board,
				'name': params.name,
				'email': params.email,
				'comment': params.comment,
				'ip': params.ip
			};

			uploadFile(params.file, function(err, filename){
				if(err){
					return _callback(err);
				}

				if(filename){
					var ext = path.extname(filename) || '.png';
					p['ext'] = ext;
					p['file'] = path.basename(filename, ext);
				}
				var t = new Post(formatPost(p));

				t.save(_callback);
			});
		},
		clean: function(board, parent, _callback){
			var find = {},
				self = this,
				threads = [],
				offset = nconf.get('board:maxThreads');

			if(board){
				find['board'] = board;
			}

			if(parent){
				find['parent'] = parent;
			}else{
				find['parent'] = 0;
			}

			Post
			.find(find, null, {
				skip: !parent ? offset : 0,
				sort: {
					updatedAt: -1
				}
			})
			.exec(function(err, threads){
				if(threads.length > 0){
					threads.forEach(function(thread){
						Post.remove({_id: thread._id}, function(err){
							if(thread.file){
								fs.unlinkSync(path.join(nconf.get('api:upload_path'), thread.file));
							}
							self.clean(board, thread._id);
						});
					});
				}else{
					// This is a single post (reply?)
					Post.remove({_id: threads._id}, function(err){
							if(threads.file){
								fs.unlinkSync(path.join(nconf.get('api:upload_path'), threads.file));
							}
					});
				}
			});
			if(typeof _callback == 'function'){
				_callback(true);
			}
		}
	};
};
<commit_msg>More cleaning<commit_after>var tripcode = require('tripcode'),
	crypto = require('crypto'),
	uuid = require('node-uuid'),
	easyimg = require('easyimage'),
	path = require('path'),
	fs = require('fs-extra'),
	async = require('async'),
	timestamps = require('mongoose-timestamp'),
	autoIncrement = require('mongoose-auto-increment'),
	FFmpeg = require('fluent-ffmpeg'),
	ffprobe = FFmpeg.ffprobe,
	tripsalt = nconf.get('api:tripsalt');

const MAX_IMAGE_SIZE = 2 * 1024; /* 2 MB */
const MAX_VIDEO_SIZE = 3 * 1024; /* 3 MB */

var parseMetadata = function(metadata){
	if(metadata == null){
		return null;
	}

	if(!metadata.format){
		return null;
	}

	var data = {};
	metadata.streams.forEach(function(s){
		if(s.codec_type === 'video'){
			data.video = {
				'codec': s.codec_name,
				'resolution': {
					'w': s.width,
					'h': s.height
				}
			};
		}else if(s.codec_type === 'audio'){
			data.audio = {
				'codec': s.codec_name,
			};
		}
	});

	data.container = metadata.format.format_name.split(',')[0];
	data.bitrate = parseInt(metadata.format.bit_rate) / 1000;
	data.seconds = Math.ceil(parseFloat(metadata.format.duration));
	return data;
};
var validateVideo = function validateVideo(file, filename, _callback){
	//console.log('a', file, filename);
	var uploadPath = nconf.get('api:upload_path'),
		full = path.join(uploadPath, 'full', filename);

	ffprobe(file.path, function(err, metadata){
		var errors = [];
		if(!metadata){
			return _callback(new Error('Could not find metadata'), file, filename);
		}
		metadata = parseMetadata(metadata);

		if(metadata.video && metadata.video.resolution){
			var res = metadata.video.resolution;
			if(res.w > 2048 || res.h > 2048){
				errors.push('Max video resolution is 2048x2048');
			}
		}

		if(metadata.video && metadata.video.codec !== 'vp8'){
			errors.push('Video must be in WebM format');
		}

		if(parseFloat(metadata.seconds) > 120){
			errors.push('Max video duration is 120 sec');
		}

		if(metadata.audio){
			errors.push('Video can not contain an audio track')
		}

		if(errors.length > 0){
			return _callback(new Error(errors.join('\n')), file, filename);
		}else{
			fs.copy(file.path, full, function(err){
				_callback(err, file, filename);
			});
		}
	});
};
var generateVideoThumb = function generateVideoThumb(file, filename, _callback){
	var uploadPath = nconf.get('api:upload_path'),
		thumb = filename.replace('.webm', '');

	new FFmpeg({
		source: file.path
	})
	.withSize(nconf.get('video:thumbnail:width') + 'x' + nconf.get('video:thumbnail:height'))
	.on('error', function(err){
		_callback(err, filename, null);
	})
	.on('end', function(filenames){
		_callback(null, filename, filenames[0]);
	})
  .takeScreenshots({
		count: 1,
  	filename: thumb,
		fileextension: '.png'
	}, path.join(uploadPath, 'thumb'));
};
var uploadImage = function uploadImage(file, _callback){
	if(file && file.path && file.name){
		var filename = 'upload-' + uuid.v1() + '.png',
			uploadPath = nconf.get('api:upload_path'),
			full = path.join(uploadPath, 'full', filename),
			thumb = path.join(uploadPath, 'thumb', filename);

		if(!file.mimetype.match(/^image\//i)){
			return _callback(new Error('Invalid file format'));
		}
		if(file.size > parseInt(MAX_IMAGE_SIZE, 10) * 1024){
			return _callback(new Error('File too big'));
		}

		async.parallel([function(cb){
			easyimg.thumbnail({
				'src': file.path,
				'dst': thumb,
				'width': nconf.get('image:thumbnail:width'),
				'height': nconf.get('image:thumbnail:height'),
				'x': 0,
				'y': 0
			}, cb);
		}, function(cb){
			easyimg.convert({
				'src': file.path,
				'dst': full
			}, cb);
		}], function(err){
			_callback(err, filename);
		});
	}else{
		_callback(null);
	}
};
var uploadVideo = function uploadVideo(file, _callback){
	if(file && file.path && file.name){
		var filename = 'upload-' + uuid.v1() + '.webm';
			async.waterfall([
				function(cb){
					fs.readFile(file.path, function(err, buffer){
						if(file.mimetype !== 'video/webm'){
							return cb(new Error('Invalid file format'), null, null);
						}
						if(buffer.length > parseInt(MAX_VIDEO_SIZE, 10) * 1024){
							return cb(new Error('File too big'), null, null);
						}
						cb(null, file, filename);
					});
				},
				validateVideo,
				generateVideoThumb
			], _callback);
	}else{
		_callback(null);
	}
};
var uploadFile = function uploadFile(file, _callback){
	if(file && file.mimetype){
		if(file.mimetype.match(/^image\//i)){
			return uploadImage(file, _callback);
		}else if(file.mimetype == 'video/webm'){
			return uploadVideo(file, _callback);
		}else{
			_callback(null);
		}
	}else{
		_callback(null);
	}
};
var formatPost = function formatPost(post, _callback){
	var tripindex = post.name.indexOf('#');
	if(tripindex > -1){
		var trip = post.name.substr(tripindex + 1),
			secure = trip.indexOf('#') === 0;

		// If tripcode contains #, hash it with site-specific salt
		if(secure){
			trip = crypto.createHash('sha1')
			.update(trip.substr(1) + tripsalt)
			.digest('base64')
			.toString();
		}

		post.tripcode = (secure ? '!!' : '!') + tripcode(trip);
		post.name = post.name.slice(0, tripindex);
	}

	if(post.ext){
		post.type = post.ext == '.webm' ? 'video' : 'image';
	}else{
		post.type = 'text';
	}

	if(_callback){
		_callback(null, post);
	}

	return post;
};
module.exports = function(db){
	var Schema = db.Schema;
	var PostSchema = new Schema({
		'board': String,
		'parent': {type: Number, required: true, default: 0},
		'ip': String,
		'name': String,
		'tripcode': String,
		'email': String,
		'subject': String,
		'comment': String,
		'file': String,
		'ext': String,
		'time': {type: Date, default: Date.now},
		'type': String,
		'closed': 0
	}), Post;
	autoIncrement.initialize(db.connection);
	PostSchema.plugin(timestamps);
	PostSchema.plugin(autoIncrement.plugin, {
		'model': 'Post',
		'startAt': 1
	});
	PostSchema.set('redisCache', true);
	Post = db.model('Post', PostSchema);

	return {
		getIndexThreads: function getIndexThreads(board, page, _callback){
			var page = page ? parseInt(page, 10) : 1,
				perPage = nconf.get('board:threadsPerpage'),
				offset = (page - 1) * perPage,
				find = {};

			if(board){
				find['board'] = board;
			}
			find['parent'] = 0;

			Post.find(find)
			.sort({updatedAt: -1})
			.skip(offset)
			.limit(perPage)
			.lean()
			.exec(_callback);
		},
		getTotalThreads: function getTotalThreads(board, _callback){
			var find = {};

			if(board){
				find['board'] = board;
			}

			find['parent'] = 0;

			Post.count(find)
			.lean()
			.exec(function(err, total){
				if(!total){
					total = 0;
				}
				_callback(err, total);
			});
		},
		getThread: function getThread(id, page, _callback){
			var page = page ? parseInt(page, 10) : 1,
				perPage = nconf.get('board:threadsPerPage'),
				offset = (page - 1) * perPage,
				find = {};

			find['_id'] = id;
			find['parent'] = 0;

			Post.findOne(find)
			.sort({updatedAt: -1})
			.skip(offset)
			.limit(perPage)
			.lean()
			.exec(_callback);
		},
		getThreadReplies: function getThreadReplies(id, limit, _callback){
			var find = {};

			find['parent'] = id;

			Post.find(find)
			.sort({updatedAt: 1})
			.limit(limit)
			.lean()
			.exec(_callback);
		},
		countThreadReplies: function countThreadReplies(id, _callback){
			var find = {};

			find['parent'] = id;

			Post.count(find)
			.lean()
			.exec(function(err, total){
				if(!total){
					total = 0;
				}
				_callback(err, total);
			});

		},
		newThread: function newThread(params, _callback){
			var p = {
				'board': params.board,
				'name': params.name,
				'tripcode': '',
				'email': params.email,
				'subject': params.subject,
				'comment': params.comment,
				'sticky': params.sticky,
				'ip': params.ip,
				'closed': params.closed
			};

			uploadFile(params.file, function(err, filename){
				if(err){
					return _callback(err);
				}

				if(filename){
					var ext = path.extname(filename) || '.png';
					p['ext'] = ext;
					p['file'] = path.basename(filename, ext);
				}
				var t = new Post(formatPost(p));

				t.save(_callback);
			});
		},
		newReply: function newReply(params, _callback){
			var p = {
				'parent': params.parent,
				'board': params.board,
				'name': params.name,
				'email': params.email,
				'comment': params.comment,
				'ip': params.ip
			};

			uploadFile(params.file, function(err, filename){
				if(err){
					return _callback(err);
				}

				if(filename){
					var ext = path.extname(filename) || '.png';
					p['ext'] = ext;
					p['file'] = path.basename(filename, ext);
				}
				var t = new Post(formatPost(p));

				t.save(_callback);
			});
		},
		clean: function(board, parent, _callback){
			var find = {},
				self = this,
				threads = [],
				offset = nconf.get('board:maxThreads');

			if(board){
				find['board'] = board;
			}

			if(parent){
				find['parent'] = parent;
			}else{
				find['parent'] = 0;
			}

			Post
			.find(find, null, {
				skip: !parent ? offset : 0,
				sort: {
					updatedAt: -1
				}
			})
			.exec(function(err, threads){
				if(threads.length > 0){
					threads.forEach(function(thread){
						Post.remove({_id: thread._id}, function(err){
							if(thread.file){
								fs.unlinkSync(path.join(nconf.get('api:upload_path'), thread.file));
							}
							self.clean(board, thread._id);
						});
					});
				}else{
					// This is a single post (reply?)
					Post.remove({_id: threads._id}, function(err){
							if(threads.file){
								fs.unlinkSync(path.join(nconf.get('api:upload_path'), threads.file));
							}
					});
				}
			});
			if(typeof _callback == 'function'){
				_callback(true);
			}
		}
	};
};
<|endoftext|>