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

/*
 * How this works
 *
 * First of all, this scans all our packages and gets the current versions.
 * It then looks to see which packages have changed and therefore need releasing.
 *
 * It parses UNRELEASED.yaml to find out the semver change of each package for release.
 * If a changed package is not shown in UNRELEASED.yaml it will assume `patch`.
 *
 * It will then summarise all publishable changes for you, and ask you to confirm.
 * Once you confirm, it will update every package.json, update CHANGELOG.md, reset UNRELEASED.yaml, and push everything to git.
 *
 * Each package will also be published to NPM.
 *
 * Finally, it will generate a message for you to copy-paste to #backpack to inform consumers of published changes.
 */

/* @flow */

/* eslint-disable no-console */

const fs = require('fs');
const { execSync } = require('child_process');
const readline = require('readline');

const YAML = require('yaml');
const colors = require('colors');
const semver = require('semver');

const SLACK_PLATFORM_EMOJIS = `:backpack: :safari: :firefox_quantum: :chrome:`;
const PLATFORM = 'web';
const REPO = 'backpack';

const YES_REGEX = /Y(es)?/i;
const YES_NO_REGEX = /Y(es)?|N(o)?/i;
const CHANGELOG_PATH = 'CHANGELOG.md';
const UNRELEASED_PATH = 'UNRELEASED.yaml';
const UNRELEASED_PATH_CLEAN = 'scripts/npm/UNRELEASED_CLEAN.yaml';
const UNRELEASED_FILE_SPLIT_POINT_1 = '> Place your changes below this line.';
const UNRELEASED_FILE_SPLIT_POINT_2 = '> Place your changes above this line.';
const RELEASE_MODE_PATCH = 'patch';
const RELEASE_MODE_MINOR = 'minor';
const RELEASE_MODE_MAJOR = 'major';
const TITLE_PATCH = '**Fixed:**';
const TITLE_MINOR = '**Added:**';
const TITLE_MAJOR = '**Breaking:**';
const YAML_SECTION_PATCH = 'FIXED';
const YAML_SECTION_MINOR = 'ADDED';
const YAML_SECTION_MAJOR = 'BREAKING';

const verbose = process.argv.includes(`--verbose`);
const testing = process.argv.includes(`--testing`);

const logVerbose = (...args) => {
  console.log(colors.cyan(...args));
};
const logOk = (...args) => {
  console.log(colors.green(...args));
};
const logWarn = (...args) => {
  console.log(colors.yellow(...args));
};
const logError = (...args) => {
  console.error(colors.red(...args));
};

const arrayInsert = (arr, index, item) => arr.splice(index, 0, item);

const assert = (condition, message) => {
  if (condition) {
    return;
  }

  logError(message);
  process.exit(1);
};

const getLernaChanges = () => {
  const lernaChanges = JSON.parse(
    execSync(`npx lerna changed -l --json`).toString(),
  );
  assert(lernaChanges, 'Could not retrieve lerna changes.');
  return lernaChanges.map(c => c.name);
};

const getCurrentPackageMeta = () => {
  const packages = JSON.parse(execSync(`npx lerna ls -l --json`).toString());
  assert(packages && packages.map, 'Failed to get current package meta');
  return packages.map(p => ({
    name: p.name,
    private: p.private,
    path: p.location,
    currentVersion: p.version,
  }));
};

const applyNewVersions = (
  currentPackageMeta,
  documentedChanges,
  lernaChanges,
) => {
  let result = currentPackageMeta.filter(p => lernaChanges.includes(p.name));
  result = result.map(p => {
    const pkg = JSON.parse(JSON.stringify(p));
    const correspondingChange = documentedChanges.find(
      r => r.name === pkg.name,
    );
    let releaseMode = RELEASE_MODE_PATCH;
    if (correspondingChange) {
      if (correspondingChange.mode) {
        releaseMode = correspondingChange.mode;
      }
      if (correspondingChange.description) {
        pkg.description = correspondingChange.description;
      }
    }
    pkg.releaseMode = releaseMode;
    assert(
      semver.valid(pkg.currentVersion),
      `Package ${pkg.name} has invalid version ${pkg.currentVersion}`,
    );
    pkg.newVersion = semver.inc(pkg.currentVersion, releaseMode);
    return pkg;
  });
  return result;
};

const extractYaml = text => {
  assert(
    text.includes(UNRELEASED_FILE_SPLIT_POINT_1) &&
      text.includes(UNRELEASED_FILE_SPLIT_POINT_2),
    `UNRELEASED.yaml changes should be added between the following lines\n\t${UNRELEASED_FILE_SPLIT_POINT_1}\n\t${UNRELEASED_FILE_SPLIT_POINT_2}`,
  );
  let yamlBlock = text.split(UNRELEASED_FILE_SPLIT_POINT_1)[1];
  [yamlBlock] = yamlBlock.split(UNRELEASED_FILE_SPLIT_POINT_2);
  return yamlBlock;
};

const validateUnreleasedEntries = (yaml, currentPackageMeta) => {
  if (!yaml) {
    return false;
  }
  if (verbose) {
    logVerbose(`Validating yaml`, yaml);
  }
  Object.keys(yaml).forEach(key => {
    if (
      key !== YAML_SECTION_MAJOR &&
      key !== YAML_SECTION_MINOR &&
      key !== YAML_SECTION_PATCH
    ) {
      logError(
        `Section in ${UNRELEASED_PATH} should be one of ${YAML_SECTION_PATCH}, ${YAML_SECTION_MINOR}, or ${YAML_SECTION_MAJOR}. Found ${key}`,
      );
      return false;
    }
    yaml[key].forEach(entry => {
      const entryName = Object.keys(entry)[0];
      if (verbose) {
        logVerbose(`Validating entry`, entryName);
      }
      const correspondingPackage = currentPackageMeta.find(
        p => p.name === entryName,
      );
      if (!entryName || !correspondingPackage) {
        logError(
          `${UNRELEASED_PATH} contains an entry for package ${entryName} but no such package exists in the project.`,
        );
      }
    });
    return true;
  });
  return true;
};

const getModeFromYamlSection = yamlSection => {
  if (yamlSection === YAML_SECTION_MINOR) {
    return RELEASE_MODE_MINOR;
  }
  if (yamlSection === YAML_SECTION_MAJOR) {
    return RELEASE_MODE_MAJOR;
  }
  return RELEASE_MODE_PATCH;
};

const processUnreleasedYaml = yaml => {
  const result = [];
  if (!yaml) {
    return result;
  }
  const yamlKeys = Object.keys(yaml);
  yamlKeys.forEach(k => {
    const entries = yaml[k];
    entries.forEach((e, eIndex) => {
      const entryKeys = Object.keys(e);
      entryKeys.forEach(ek => {
        if (verbose) {
          console.log(`entryKeys`, entryKeys);
        }
        const changes = entries[eIndex][ek];
        const newResult = {
          name: ek,
          mode: getModeFromYamlSection(k),
          description: changes,
        };
        result.push(newResult);
      });
    });
  });

  return result;
};

const parseUnreleasedFile = currentPackageMeta => {
  const unreleasedContent = fs.readFileSync(UNRELEASED_PATH).toString();
  assert(unreleasedContent, `Failed to read ${UNRELEASED_PATH}`);
  const unreleasedYaml = YAML.parse(extractYaml(unreleasedContent));
  if (verbose) {
    logVerbose(`unreleasedYaml`);
    logVerbose(YAML.stringify(unreleasedYaml));
  }
  assert(unreleasedYaml, `Failed to parse ${UNRELEASED_PATH}`);
  assert(
    validateUnreleasedEntries(unreleasedYaml, currentPackageMeta),
    `${UNRELEASED_PATH} is not valid. Please fix it and try again.`,
  );

  return processUnreleasedYaml(unreleasedYaml);
};

const printOutSlackUpdate = (changes, changeSummary, publishTitle) => {
  const publishTitleURL = publishTitle
    .toLowerCase()
    .split(' ')
    .join('-')
    .split('`')
    .join('');

  let slackUpdate = `${SLACK_PLATFORM_EMOJIS} ${changeSummary}\n`;

  changes.forEach(c => {
    if (c.name && c.description && c.name.includes(`bpk-component-`)) {
      const componentName = c.name.split(`bpk-component-`)[1];
      slackUpdate += `:information_source: https://backpack.github.io/components/${componentName}/?platform=${PLATFORM}\n`;
    }
  });
  slackUpdate += `:clipboard: https://github.com/Skyscanner/${REPO}/blob/master/${CHANGELOG_PATH}#${publishTitleURL}`;

  execSync(`echo "${slackUpdate}" | pbcopy`);
  logOk(
    `\nNow paste this message in #backpack:\n${slackUpdate}. FYI it's already on your clipboard 😉`,
  );
};

const getDate = () => {
  const today = new Date();
  return today.toISOString().split('T')[0];
};

const getTitleForMode = mode => {
  if (mode === RELEASE_MODE_MINOR) {
    return TITLE_MINOR;
  }
  if (mode === RELEASE_MODE_MAJOR) {
    return TITLE_MAJOR;
  }
  return TITLE_PATCH;
};

const formatDescription = description => {
  if (!description || description.length === 0) {
    return '';
  }

  const result = [];
  description.forEach(d => {
    if (d && !d.match(/^\s+$/)) {
      result.push(`   - ${d}`);
    }
  });
  return result.join('\n');
};
const generateChangelogSection = (publishTitle, changes) => {
  let section = `${publishTitle}`;
  let currentMode = null;
  changes.forEach(c => {
    if (c.name && c.releaseMode && c.description) {
      if (c.releaseMode !== currentMode) {
        currentMode = c.releaseMode;
        section += `\n\n${getTitleForMode(c.releaseMode)}`;
      }
      section += `\n\n - ${c.name}: ${c.currentVersion} => ${c.newVersion}\n`;
      section += formatDescription(c.description);
    }
  });
  section += '\n\n';
  return section;
};

const addChangelogEntry = changelogEntry => {
  const changelogContent = fs.readFileSync(CHANGELOG_PATH).toString();
  const newContent = changelogContent.split('\n');
  arrayInsert(newContent, 4, changelogEntry);

  fs.writeFileSync(CHANGELOG_PATH, newContent.join('\n'), 'utf8');
};

const resetUnreleased = () => {
  execSync(`cp ${UNRELEASED_PATH_CLEAN} ${UNRELEASED_PATH}`);
};

const updatePackageJsonFiles = changes => {
  changes.forEach(c => {
    const packageJsonFile = `${c.path}/package.json`;
    const packageJsonData = JSON.parse(
      fs.readFileSync(packageJsonFile).toString(),
    );
    packageJsonData.version = c.newVersion;
    fs.writeFileSync(
      packageJsonFile,
      `${JSON.stringify(packageJsonData, null, '  ')}\n`,
    );
  });
};

const createGitTags = changes => {
  changes.forEach(c => {
    const tagMessage = `${c.name}@${c.newVersion}`;
    execSync(`git tag -a ${tagMessage} -m ${tagMessage}`);
  });
};

const performPublish = (changes, changeSummary) => {
  const publishTitle = `${getDate()} - ${changeSummary}`;
  const newChangelogEntry = generateChangelogSection(publishTitle, changes);
  if (verbose) {
    logVerbose(`newChangelogEntry`);
    logVerbose(newChangelogEntry);
  }
  addChangelogEntry(newChangelogEntry);
  resetUnreleased();
  updatePackageJsonFiles(changes);
  execSync(`npm run fix-bpk-dependencies`);
  if (!testing) {
    execSync(`git add . && git commit -m "Publish" --no-verify`);
    createGitTags(changes);
    execSync(`npx lerna publish from-package`);
    execSync(`git add . && git commit --amend --no-edit --no-verify`);
    execSync(`git push`);
    execSync(`git push --tags`);
  }
  logOk(`All good 👍`);
  printOutSlackUpdate(changes, changeSummary, publishTitle);
  process.exit(0);
};

const cancelPublish = () => {
  logOk(`Nothing has been changed!`);
  logOk(`See you later 👋`);
  process.exit(0);
};

const getConfirmation = () =>
  new Promise(resolve => {
    const rl = readline.createInterface({
      input: process.stdin,
      output: process.stdout,
    });

    rl.question(
      'PLEASE CONFIRM THAT THE ABOVE CHANGES ARE CORRECT AND YOU WOULD LIKE TO CONTINUE\nEnter Y(es) or N(o)\n',
      answer => {
        if (!answer.match(YES_NO_REGEX)) {
          rl.close();
          resolve(getConfirmation());
          return;
        }

        let result = false;
        if (answer.match(YES_REGEX)) {
          result = true;
        }

        rl.close();
        resolve(result);
      },
    );
  });

const getChangeSummary = () =>
  new Promise(resolve => {
    const rl = readline.createInterface({
      input: process.stdin,
      output: process.stdout,
    });

    rl.question('Please summarise the changes you are publishing\n', answer => {
      rl.close();
      resolve(answer);
    });
  });

const printOutChanges = (changes, logFn) => {
  logOk(`\n`);
  changes.forEach(c => {
    logFn(
      `${c.releaseMode} change to package ${c.name}: ${c.currentVersion} => ${
        c.newVersion
      }`,
    );
    logOk(`${formatDescription(c.description)}`);
    logOk(``);
  });
};

const printOutChangeSummary = changes => {
  const undocumentedChanges = changes.filter(c => !c.description);
  const documentedChanges = changes.filter(c => !!c.description);
  if (undocumentedChanges.length > 0) {
    logWarn(`\n`);
    logWarn(
      `The following changes will be published with NO CHANGELOG ENTRY! 🙀`,
    );
    printOutChanges(undocumentedChanges, logWarn);
  }
  if (documentedChanges.length > 0) {
    console.log(``);
    logOk(`\n`);
    logOk(
      `The following changes will be published and reflected in CHANGELOG.md 😎`,
    );
    printOutChanges(documentedChanges, logOk);
  }
};

const cli = async () => {
  const currentPackageMeta = getCurrentPackageMeta();
  if (verbose) {
    logVerbose(`currentPackageMeta`);
    logVerbose(currentPackageMeta);
  }
  const documentedChanges = parseUnreleasedFile(currentPackageMeta);
  if (verbose) {
    logVerbose(`documentedChanges`);
    logVerbose(documentedChanges);
  }
  const lernaChanges = getLernaChanges();
  if (verbose) {
    logVerbose(`lernaChanges`);
    logVerbose(lernaChanges);
  }

  const changesReadyForPublish = applyNewVersions(
    currentPackageMeta,
    documentedChanges,
    lernaChanges,
  );
  if (verbose) {
    logVerbose('changesReadyForPublish');
    logVerbose(changesReadyForPublish);
  }

  printOutChangeSummary(changesReadyForPublish);

  const confirmation = await getConfirmation();

  if (confirmation) {
    const changeSummary = await getChangeSummary();
    performPublish(changesReadyForPublish, changeSummary);
  } else {
    cancelPublish();
  }
};

cli().then(null);
<commit_msg>[NO-JIRA] Fix publish script tagging<commit_after>/*
 * Backpack - Skyscanner's Design System
 *
 * Copyright 2016-2019 Skyscanner Ltd
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *   http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

/*
 * How this works
 *
 * First of all, this scans all our packages and gets the current versions.
 * It then looks to see which packages have changed and therefore need releasing.
 *
 * It parses UNRELEASED.yaml to find out the semver change of each package for release.
 * If a changed package is not shown in UNRELEASED.yaml it will assume `patch`.
 *
 * It will then summarise all publishable changes for you, and ask you to confirm.
 * Once you confirm, it will update every package.json, update CHANGELOG.md, reset UNRELEASED.yaml, and push everything to git.
 *
 * Each package will also be published to NPM.
 *
 * Finally, it will generate a message for you to copy-paste to #backpack to inform consumers of published changes.
 */

/* @flow */

/* eslint-disable no-console */

const fs = require('fs');
const { execSync } = require('child_process');
const readline = require('readline');

const YAML = require('yaml');
const colors = require('colors');
const semver = require('semver');

const SLACK_PLATFORM_EMOJIS = `:backpack: :safari: :firefox_quantum: :chrome:`;
const PLATFORM = 'web';
const REPO = 'backpack';

const YES_REGEX = /Y(es)?/i;
const YES_NO_REGEX = /Y(es)?|N(o)?/i;
const CHANGELOG_PATH = 'CHANGELOG.md';
const UNRELEASED_PATH = 'UNRELEASED.yaml';
const UNRELEASED_PATH_CLEAN = 'scripts/npm/UNRELEASED_CLEAN.yaml';
const UNRELEASED_FILE_SPLIT_POINT_1 = '> Place your changes below this line.';
const UNRELEASED_FILE_SPLIT_POINT_2 = '> Place your changes above this line.';
const RELEASE_MODE_PATCH = 'patch';
const RELEASE_MODE_MINOR = 'minor';
const RELEASE_MODE_MAJOR = 'major';
const TITLE_PATCH = '**Fixed:**';
const TITLE_MINOR = '**Added:**';
const TITLE_MAJOR = '**Breaking:**';
const YAML_SECTION_PATCH = 'FIXED';
const YAML_SECTION_MINOR = 'ADDED';
const YAML_SECTION_MAJOR = 'BREAKING';

const verbose = process.argv.includes(`--verbose`);
const testing = process.argv.includes(`--testing`);

const logVerbose = (...args) => {
  console.log(colors.cyan(...args));
};
const logOk = (...args) => {
  console.log(colors.green(...args));
};
const logWarn = (...args) => {
  console.log(colors.yellow(...args));
};
const logError = (...args) => {
  console.error(colors.red(...args));
};

const arrayInsert = (arr, index, item) => arr.splice(index, 0, item);

const assert = (condition, message) => {
  if (condition) {
    return;
  }

  logError(message);
  process.exit(1);
};

const getLernaChanges = () => {
  const lernaChanges = JSON.parse(
    execSync(`npx lerna changed -l --json`).toString(),
  );
  assert(lernaChanges, 'Could not retrieve lerna changes.');
  return lernaChanges.map(c => c.name);
};

const getCurrentPackageMeta = () => {
  const packages = JSON.parse(execSync(`npx lerna ls -l --json`).toString());
  assert(packages && packages.map, 'Failed to get current package meta');
  return packages.map(p => ({
    name: p.name,
    private: p.private,
    path: p.location,
    currentVersion: p.version,
  }));
};

const applyNewVersions = (
  currentPackageMeta,
  documentedChanges,
  lernaChanges,
) => {
  let result = currentPackageMeta.filter(p => lernaChanges.includes(p.name));
  result = result.map(p => {
    const pkg = JSON.parse(JSON.stringify(p));
    const correspondingChange = documentedChanges.find(
      r => r.name === pkg.name,
    );
    let releaseMode = RELEASE_MODE_PATCH;
    if (correspondingChange) {
      if (correspondingChange.mode) {
        releaseMode = correspondingChange.mode;
      }
      if (correspondingChange.description) {
        pkg.description = correspondingChange.description;
      }
    }
    pkg.releaseMode = releaseMode;
    assert(
      semver.valid(pkg.currentVersion),
      `Package ${pkg.name} has invalid version ${pkg.currentVersion}`,
    );
    pkg.newVersion = semver.inc(pkg.currentVersion, releaseMode);
    return pkg;
  });
  return result;
};

const extractYaml = text => {
  assert(
    text.includes(UNRELEASED_FILE_SPLIT_POINT_1) &&
      text.includes(UNRELEASED_FILE_SPLIT_POINT_2),
    `UNRELEASED.yaml changes should be added between the following lines\n\t${UNRELEASED_FILE_SPLIT_POINT_1}\n\t${UNRELEASED_FILE_SPLIT_POINT_2}`,
  );
  let yamlBlock = text.split(UNRELEASED_FILE_SPLIT_POINT_1)[1];
  [yamlBlock] = yamlBlock.split(UNRELEASED_FILE_SPLIT_POINT_2);
  return yamlBlock;
};

const validateUnreleasedEntries = (yaml, currentPackageMeta) => {
  if (!yaml) {
    return false;
  }
  if (verbose) {
    logVerbose(`Validating yaml`, yaml);
  }
  Object.keys(yaml).forEach(key => {
    if (
      key !== YAML_SECTION_MAJOR &&
      key !== YAML_SECTION_MINOR &&
      key !== YAML_SECTION_PATCH
    ) {
      logError(
        `Section in ${UNRELEASED_PATH} should be one of ${YAML_SECTION_PATCH}, ${YAML_SECTION_MINOR}, or ${YAML_SECTION_MAJOR}. Found ${key}`,
      );
      return false;
    }
    yaml[key].forEach(entry => {
      const entryName = Object.keys(entry)[0];
      if (verbose) {
        logVerbose(`Validating entry`, entryName);
      }
      const correspondingPackage = currentPackageMeta.find(
        p => p.name === entryName,
      );
      if (!entryName || !correspondingPackage) {
        logError(
          `${UNRELEASED_PATH} contains an entry for package ${entryName} but no such package exists in the project.`,
        );
      }
    });
    return true;
  });
  return true;
};

const getModeFromYamlSection = yamlSection => {
  if (yamlSection === YAML_SECTION_MINOR) {
    return RELEASE_MODE_MINOR;
  }
  if (yamlSection === YAML_SECTION_MAJOR) {
    return RELEASE_MODE_MAJOR;
  }
  return RELEASE_MODE_PATCH;
};

const processUnreleasedYaml = yaml => {
  const result = [];
  if (!yaml) {
    return result;
  }
  const yamlKeys = Object.keys(yaml);
  yamlKeys.forEach(k => {
    const entries = yaml[k];
    entries.forEach((e, eIndex) => {
      const entryKeys = Object.keys(e);
      entryKeys.forEach(ek => {
        if (verbose) {
          console.log(`entryKeys`, entryKeys);
        }
        const changes = entries[eIndex][ek];
        const newResult = {
          name: ek,
          mode: getModeFromYamlSection(k),
          description: changes,
        };
        result.push(newResult);
      });
    });
  });

  return result;
};

const parseUnreleasedFile = currentPackageMeta => {
  const unreleasedContent = fs.readFileSync(UNRELEASED_PATH).toString();
  assert(unreleasedContent, `Failed to read ${UNRELEASED_PATH}`);
  const unreleasedYaml = YAML.parse(extractYaml(unreleasedContent));
  if (verbose) {
    logVerbose(`unreleasedYaml`);
    logVerbose(YAML.stringify(unreleasedYaml));
  }
  assert(unreleasedYaml, `Failed to parse ${UNRELEASED_PATH}`);
  assert(
    validateUnreleasedEntries(unreleasedYaml, currentPackageMeta),
    `${UNRELEASED_PATH} is not valid. Please fix it and try again.`,
  );

  return processUnreleasedYaml(unreleasedYaml);
};

const printOutSlackUpdate = (changes, changeSummary, publishTitle) => {
  const publishTitleURL = publishTitle
    .toLowerCase()
    .split(' ')
    .join('-')
    .split('`')
    .join('');

  let slackUpdate = `${SLACK_PLATFORM_EMOJIS} ${changeSummary}\n`;

  changes.forEach(c => {
    if (c.name && c.description && c.name.includes(`bpk-component-`)) {
      const componentName = c.name.split(`bpk-component-`)[1];
      slackUpdate += `:information_source: https://backpack.github.io/components/${componentName}/?platform=${PLATFORM}\n`;
    }
  });
  slackUpdate += `:clipboard: https://github.com/Skyscanner/${REPO}/blob/master/${CHANGELOG_PATH}#${publishTitleURL}`;

  execSync(`echo "${slackUpdate}" | pbcopy`);
  logOk(
    `\nNow paste this message in #backpack:\n${slackUpdate}. FYI it's already on your clipboard 😉`,
  );
};

const getDate = () => {
  const today = new Date();
  return today.toISOString().split('T')[0];
};

const getTitleForMode = mode => {
  if (mode === RELEASE_MODE_MINOR) {
    return TITLE_MINOR;
  }
  if (mode === RELEASE_MODE_MAJOR) {
    return TITLE_MAJOR;
  }
  return TITLE_PATCH;
};

const formatDescription = description => {
  if (!description || description.length === 0) {
    return '';
  }

  const result = [];
  description.forEach(d => {
    if (d && !d.match(/^\s+$/)) {
      result.push(`   - ${d}`);
    }
  });
  return result.join('\n');
};
const generateChangelogSection = (publishTitle, changes) => {
  let section = `${publishTitle}`;
  let currentMode = null;
  changes.forEach(c => {
    if (c.name && c.releaseMode && c.description) {
      if (c.releaseMode !== currentMode) {
        currentMode = c.releaseMode;
        section += `\n\n${getTitleForMode(c.releaseMode)}`;
      }
      section += `\n\n - ${c.name}: ${c.currentVersion} => ${c.newVersion}\n`;
      section += formatDescription(c.description);
    }
  });
  section += '\n\n';
  return section;
};

const addChangelogEntry = changelogEntry => {
  const changelogContent = fs.readFileSync(CHANGELOG_PATH).toString();
  const newContent = changelogContent.split('\n');
  arrayInsert(newContent, 4, changelogEntry);

  fs.writeFileSync(CHANGELOG_PATH, newContent.join('\n'), 'utf8');
};

const resetUnreleased = () => {
  execSync(`cp ${UNRELEASED_PATH_CLEAN} ${UNRELEASED_PATH}`);
};

const updatePackageJsonFiles = changes => {
  changes.forEach(c => {
    const packageJsonFile = `${c.path}/package.json`;
    const packageJsonData = JSON.parse(
      fs.readFileSync(packageJsonFile).toString(),
    );
    packageJsonData.version = c.newVersion;
    fs.writeFileSync(
      packageJsonFile,
      `${JSON.stringify(packageJsonData, null, '  ')}\n`,
    );
  });
};

const createGitTags = changes => {
  changes.forEach(c => {
    const tagMessage = `${c.name}@${c.newVersion}`;
    execSync(`git tag -a ${tagMessage} -m ${tagMessage}`);
  });
};

const performPublish = (changes, changeSummary) => {
  const publishTitle = `${getDate()} - ${changeSummary}`;
  const newChangelogEntry = generateChangelogSection(publishTitle, changes);
  if (verbose) {
    logVerbose(`newChangelogEntry`);
    logVerbose(newChangelogEntry);
  }
  addChangelogEntry(newChangelogEntry);
  resetUnreleased();
  updatePackageJsonFiles(changes);
  execSync(`npm run fix-bpk-dependencies`);
  if (!testing) {
    execSync(`git add . && git commit -m "Publish" --no-verify`);
    execSync(`npx lerna publish from-package`);
    execSync(`git add . && git commit --amend --no-edit --no-verify`);
    createGitTags(changes);
    execSync(`git push`);
    execSync(`git push --tags`);
  }
  logOk(`All good 👍`);
  printOutSlackUpdate(changes, changeSummary, publishTitle);
  process.exit(0);
};

const cancelPublish = () => {
  logOk(`Nothing has been changed!`);
  logOk(`See you later 👋`);
  process.exit(0);
};

const getConfirmation = () =>
  new Promise(resolve => {
    const rl = readline.createInterface({
      input: process.stdin,
      output: process.stdout,
    });

    rl.question(
      'PLEASE CONFIRM THAT THE ABOVE CHANGES ARE CORRECT AND YOU WOULD LIKE TO CONTINUE\nEnter Y(es) or N(o)\n',
      answer => {
        if (!answer.match(YES_NO_REGEX)) {
          rl.close();
          resolve(getConfirmation());
          return;
        }

        let result = false;
        if (answer.match(YES_REGEX)) {
          result = true;
        }

        rl.close();
        resolve(result);
      },
    );
  });

const getChangeSummary = () =>
  new Promise(resolve => {
    const rl = readline.createInterface({
      input: process.stdin,
      output: process.stdout,
    });

    rl.question('Please summarise the changes you are publishing\n', answer => {
      rl.close();
      resolve(answer);
    });
  });

const printOutChanges = (changes, logFn) => {
  logOk(`\n`);
  changes.forEach(c => {
    logFn(
      `${c.releaseMode} change to package ${c.name}: ${c.currentVersion} => ${
        c.newVersion
      }`,
    );
    logOk(`${formatDescription(c.description)}`);
    logOk(``);
  });
};

const printOutChangeSummary = changes => {
  const undocumentedChanges = changes.filter(c => !c.description);
  const documentedChanges = changes.filter(c => !!c.description);
  if (undocumentedChanges.length > 0) {
    logWarn(`\n`);
    logWarn(
      `The following changes will be published with NO CHANGELOG ENTRY! 🙀`,
    );
    printOutChanges(undocumentedChanges, logWarn);
  }
  if (documentedChanges.length > 0) {
    console.log(``);
    logOk(`\n`);
    logOk(
      `The following changes will be published and reflected in CHANGELOG.md 😎`,
    );
    printOutChanges(documentedChanges, logOk);
  }
};

const cli = async () => {
  const currentPackageMeta = getCurrentPackageMeta();
  if (verbose) {
    logVerbose(`currentPackageMeta`);
    logVerbose(currentPackageMeta);
  }
  const documentedChanges = parseUnreleasedFile(currentPackageMeta);
  if (verbose) {
    logVerbose(`documentedChanges`);
    logVerbose(documentedChanges);
  }
  const lernaChanges = getLernaChanges();
  if (verbose) {
    logVerbose(`lernaChanges`);
    logVerbose(lernaChanges);
  }

  const changesReadyForPublish = applyNewVersions(
    currentPackageMeta,
    documentedChanges,
    lernaChanges,
  );
  if (verbose) {
    logVerbose('changesReadyForPublish');
    logVerbose(changesReadyForPublish);
  }

  printOutChangeSummary(changesReadyForPublish);

  const confirmation = await getConfirmation();

  if (confirmation) {
    const changeSummary = await getChangeSummary();
    performPublish(changesReadyForPublish, changeSummary);
  } else {
    cancelPublish();
  }
};

cli().then(null);
<|endoftext|>


<commit_before>const Queue = require("../build/TaskQueue").default;

function testNum(queue, crawl, download, urls) {
    expect(queue.allUrlNum()).toBe(urls);
    expect(queue.crawlWaitingNum()).toBe(crawl);
    expect(queue.downloadWaitingNum()).toBe(download);
    expect(queue.totalWaitingNum()).toBe(crawl + download);
    if (crawl === 0) {
        expect(queue.isCrawlCompleted()).toBe(true);
    } else {
        expect(queue.isCrawlCompleted()).not.toBe(true);
    }
    if (download === 0) {
        expect(queue.isDownloadCompleted()).toBe(true);
    } else {
        expect(queue.isDownloadCompleted()).not.toBe(true);
    }
    if (crawl === 0 && download === 0) {
        expect(queue.isAllCompleted()).toBe(true);
    } else {
        expect(queue.isAllCompleted()).not.toBe(true);
    }
}
test("test queue's count", () => {
    const q = new Queue();
    testNum(q, 0, 0, 0);

    q.addCrawl({
        plan: Symbol("url.com"),
        url: "http://url.com",
    });
    testNum(q, 1, 0, 1);

    q.addCrawl({
        plan: Symbol("plan2"),
        url: "url2",
    });
    testNum(q, 2, 0, 2);

    q.addDownload({
        plan: Symbol("download1"),
        url: "download1",
    });
    testNum(q, 2, 1, 3);

    q.addCrawl({
        plan: Symbol("plan3"),
        url: "url3",
    });
    testNum(q, 3, 1, 4);

    q.addDownload({
        plan: Symbol("download2"),
        url: "download2",
    });
    testNum(q, 3, 2, 5);

    q.getDownloadTask();
    testNum(q, 3, 1, 5);

    q.addDownload({
        plan: Symbol("download3"),
        url: "download3",
    });
    testNum(q, 3, 2, 6);

    q.getDownloadTask();
    testNum(q, 3, 1, 6);
    q.getDownloadTask();
    testNum(q, 3, 0, 6);
    q.getCrawlTask();
    testNum(q, 2, 0, 6);
    q.getCrawlTask();
    testNum(q, 1, 0, 6);

    q.getCrawlTask();
    testNum(q, 0, 0, 6);
    q.getCrawlTask();
    testNum(q, 0, 0, 6);
    q.getDownloadTask();
    testNum(q, 0, 0, 6);

    q.addCrawl({
        plan: "pppppp",
        url: "url000",
    });
    testNum(q, 1, 0, 7);

    q.addDownload({
        plan: "jsdkfjsklvnwe",
        url: "url999999",
    });
    testNum(q, 1, 1, 8);

    q.addCrawl({
        plan: "lppppp",
        url: "uuuu23",
    });
    testNum(q, 2, 1, 9);

    q.addCrawl({
        plan: "ijwiokdnv",
        url: "ksjkev",
    });
    testNum(q, 3, 1, 10);

    q.addDownload({
        plan: "jenejnn",
        url: "ujnnnnnn",
    });
    testNum(q, 3, 2, 11);

    q.getDownloadTask();
    testNum(q, 3, 1, 11);
});

test("queue test", () => {
    const q = new Queue();
    q.addCrawl({url: "u1"});
    q.addCrawl({url: "u2"});
    q.addCrawl({url: "u3"});
    q.addCrawl({url: "u4"});
    q.addCrawl({url: "u5"});
    q.addDownload({url: "d1"});
    q.addDownload({url: "d2"});
    q.addDownload({url: "d3"});

    expect(q.check("u1")).toBe(true);
    expect(q.check("u2")).toBe(true);
    expect(q.check("u3")).toBe(true);
    expect(q.check("u4")).toBe(true);
    expect(q.check("d1")).toBe(true);
    expect(q.check("d2")).toBe(true);
    expect(q.check("sdkjfskld")).not.toBe(true);
    expect(q.check("dddd")).not.toBe(true);

    expect(q.getDownloadTask()).toEqual({url: "d1"});
    expect(q.getDownloadTask()).toEqual({url: "d2"});
    expect(q.getDownloadTask()).toEqual({url: "d3"});
    expect(q.getCrawlTask()).toEqual({url: "u1"});
    expect(q.getCrawlTask()).toEqual({url: "u2"});
    expect(q.getCrawlTask()).toEqual({url: "u3"});
    expect(q.getCrawlTask()).toEqual({url: "u4"});
    expect(q.getCrawlTask()).toEqual({url: "u5"});
    expect(q.getCrawlTask()).toBeNull();
    expect(q.getDownloadTask()).toBeNull();

    q.addDownload({url: "top0"});
    q.addCrawl({url: "top4"});
    q.jumpCrawl({url: "top3"});
    q.addDownload({url: "top1"});
    q.jumpDownload({url: "top-1"});
    q.jumpCrawl({url: "top2"});
    expect(q.getDownloadTask()).toEqual({url: "top-1"});
    expect(q.getDownloadTask()).toEqual({url: "top0"});
    expect(q.getDownloadTask()).toEqual({url: "top1"});
    expect(q.getCrawlTask()).toEqual({url: "top2"});
    expect(q.getCrawlTask()).toEqual({url: "top3"});
    expect(q.getCrawlTask()).toEqual({url: "top4"});
    expect(q.getCrawlTask()).toBeNull();
    expect(q.getDownloadTask()).toBeNull();
});
<commit_msg>delete out-dated unit test<commit_after><|endoftext|>


<commit_before>'use strict';

// Third Party Dependencies
const electron = require('electron');
const app = electron.app;
const crashReporter = electron.crashReporter;
const nodeURL = require('url');
const squirrelStartup = require('electron-squirrel-startup');
const AutoLaunch = require('auto-launch');
const urlParser = require('url');

// Local Dependencies
const {getConfigField, updateUserConfigWin, updateUserConfigMac, readConfigFileSync} = require('./config.js');
const {setCheckboxValues} = require('./menus/menuTemplate.js');
const { isMac, isDevEnv } = require('./utils/misc.js');
const protocolHandler = require('./protocolHandler');
const getCmdLineArg = require('./utils/getCmdLineArg.js');
const log = require('./log.js');
const logLevels = require('./enums/logLevels.js');

require('electron-dl')();

// used to check if a url was opened when the app was already open
let isAppAlreadyOpen = false;

// exit early for squirrel installer
if (squirrelStartup) {
    return;
}

require('./mainApiMgr.js');

// monitor memory of main process
require('./memoryMonitor.js');

const windowMgr = require('./windowMgr.js');

getConfigField('url')
.then(initializeCrashReporter)
.catch(app.quit);

function initializeCrashReporter(podUrl) {

    getConfigField('crashReporter')
    .then((crashReporterConfig) => {
        crashReporter.start({companyName: crashReporterConfig.companyName, submitURL: crashReporterConfig.submitURL, uploadToServer: crashReporterConfig.uploadToServer, extra: {'process': 'main', podUrl: podUrl}});
        log.send(logLevels.INFO, 'initialized crash reporter on the main process!');
    })
    .catch((err) => {
        log.send(logLevels.ERROR, 'Unable to initialize crash reporter in the main process. Error is -> ' + err);
    });

}

// only allow a single instance of app.
const shouldQuit = app.makeSingleInstance((argv) => {
    // Someone tried to run a second instance, we should focus our window.
    let mainWin = windowMgr.getMainWindow();
    if (mainWin) {
        isAppAlreadyOpen = true;
        if (mainWin.isMinimized()) {
            mainWin.restore();
        }
        mainWin.focus();
    }
    processProtocolAction(argv);
});

let allowMultiInstance = getCmdLineArg(process.argv, '--multiInstance', true) || isDevEnv;

// quit if another instance is already running, ignore for dev env or if app was started with multiInstance flag
if (!allowMultiInstance && shouldQuit) {
    app.quit();
}

let symphonyAutoLauncher;

if (isMac) {
    symphonyAutoLauncher = new AutoLaunch({
        name: 'Symphony',
        mac: {
            useLaunchAgent: true,
        },
        path: process.execPath,
    });
} else {
    symphonyAutoLauncher = new AutoLaunch({
        name: 'Symphony',
        path: process.execPath,
    });
}

function setChromeFlags() {

    log.send(logLevels.INFO, 'checking if we need to set custom chrome flags!');

    let config = readConfigFileSync();

    if (config && config !== null && config.customFlags) {

        log.send(logLevels.INFO, 'Chrome flags config found!');

        if (config.customFlags.authServerWhitelist && config.customFlags.authServerWhitelist !== "") {
            log.send(logLevels.INFO, 'Setting auth server whitelist flag');
            app.commandLine.appendSwitch('auth-server-whitelist', config.customFlags.authServerWhitelist);
        }

        if (config.customFlags.authNegotiateDelegateWhitelist && config.customFlags.authNegotiateDelegateWhitelist !== "") {
            log.send(logLevels.INFO, 'Setting auth negotiate delegate whitelist flag');
            app.commandLine.appendSwitch('auth-negotiate-delegate-whitelist', config.customFlags.authNegotiateDelegateWhitelist);
        }

    }
}

setChromeFlags();

/**
 * This method will be called when Electron has finished
 * initialization and is ready to create browser windows.
 * Some APIs can only be used after this event occurs.
 */
app.on('ready', readConfigThenOpenMainWindow);

/**
 * Is triggered when all the windows are closed
 * In which case we quit the app
 */
app.on('window-all-closed', function() {
    app.quit();
});

/**
 * Is triggered when the app is up & running
 */
app.on('activate', function() {
    if (windowMgr.isMainWindow(null)) {
        setupThenOpenMainWindow();
    } else {
        windowMgr.showMainWindow();
    }
});

// adds 'symphony' as a protocol
// in the system. plist file in macOS
// and registry keys in windows
app.setAsDefaultProtocolClient('symphony');

/**
 * This event is emitted only on macOS
 * at this moment, support for windows
 * is in pipeline (https://github.com/electron/electron/pull/8052)
 */
app.on('open-url', function(event, url) {
    handleProtocolAction(url);
});

/**
 * Reads the config fields that are required for the menu items
 * then opens the main window
 *
 * This is a workaround for the issue where the menu template was returned
 * even before the config data was populated
 * https://perzoinc.atlassian.net/browse/ELECTRON-154
 */
function readConfigThenOpenMainWindow() {
    setCheckboxValues()
        .then(setupThenOpenMainWindow)
        .catch(setupThenOpenMainWindow)
}

/**
 * Sets up the app (to handle various things like config changes, protocol handling etc.)
 * and opens the main window
 */
function setupThenOpenMainWindow() {

    processProtocolAction(process.argv);

    isAppAlreadyOpen = true;

    // allows installer to launch app and set appropriate global / user config params.
    let hasInstallFlag = getCmdLineArg(process.argv, '--install', true);
    let perUserInstall = getCmdLineArg(process.argv, '--peruser', true);
    let customDataArg = getCmdLineArg(process.argv, '--userDataPath=', false);

    if (customDataArg && customDataArg.split('=').length > 1) {
        let customDataFolder = customDataArg.split('=')[1];
        app.setPath('userData', customDataFolder);
    }
    if (!isMac && hasInstallFlag) {
        getConfigField('launchOnStartup')
            .then(setStartup)
            .then(() => updateUserConfigWin(perUserInstall))
            .then(app.quit)
            .catch(app.quit);
        return;
    }

    // allows mac installer to overwrite user config
    if (isMac && hasInstallFlag) {
        // This value is being sent from post install script
        // as the app is launched as a root user we don't get
        // access to the config file
        let launchOnStartup = process.argv[3];
        // We wire this in via the post install script
        // to get the config file path where the app is installed
        setStartup(launchOnStartup)
            .then(updateUserConfigMac)
            .then(app.quit)
            .catch(app.quit);
        return;
    }

    getUrlAndCreateMainWindow();

    // Event that fixes the remote desktop issue in Windows
    // by repositioning the browser window
    electron.screen.on('display-removed', windowMgr.verifyDisplays);
}

/**
 * Sets Symphony on startup
 * @param lStartup
 * @returns {Promise}
 */
function setStartup(lStartup) {
    return new Promise((resolve) => {
        let launchOnStartup = (lStartup === 'true');
        if (launchOnStartup) {
            symphonyAutoLauncher.enable();
            return resolve();
        }
        symphonyAutoLauncher.disable();
        return resolve();
    });
}

/**
 * Checks for the url argument, processes it
 * and creates the main window
 */
function getUrlAndCreateMainWindow() {
    // for dev env allow passing url argument
    if (isDevEnv) {
        let url = getCmdLineArg(process.argv, '--url=', false);
        if (url) {
            windowMgr.createMainWindow(url.substr(6));
            return;
        }
    }

    getConfigField('url')
        .then(createWin).catch(function(err) {
            let title = 'Error loading configuration';
            electron.dialog.showErrorBox(title, title + ': ' + err);
        });
}

/**
 * Creates a window
 * @param urlFromConfig
 */
function createWin(urlFromConfig) {
    let protocol = '';
    // add https protocol if none found.
    let parsedUrl = nodeURL.parse(urlFromConfig);
    if (!parsedUrl.protocol) {
        protocol = 'https';
    }
    let url = nodeURL.format({
        protocol: protocol,
        slahes: true,
        pathname: parsedUrl.href
    });

    windowMgr.createMainWindow(url);
}

/**
 * processes protocol action for windows clients
 * @param argv {Array} an array of command line arguments
 */
function processProtocolAction(argv) {

    // In case of windows, we need to handle protocol handler
    // manually because electron doesn't emit
    // 'open-url' event on windows
    if (!(process.platform === 'win32')) {
        return;
    }

    let protocolUri = getCmdLineArg(argv, 'symphony://', false);

    if (protocolUri) {

        const parsedURL = urlParser.parse(protocolUri);

        if (!parsedURL.protocol || !parsedURL.slashes) {
            return;
        }

        handleProtocolAction(protocolUri);

    }
}

/**
 * Handles a protocol action based on the current state of the app
 * @param uri
 */
function handleProtocolAction(uri) {
    if (!isAppAlreadyOpen) {
        // app is opened by the protocol url, cache the protocol url to be used later
        protocolHandler.setProtocolUrl(uri);
    } else {
        // app is already open, so, just trigger the protocol action method
        protocolHandler.processProtocolAction(uri);
    }
}<commit_msg>electron-194: added code comments<commit_after>'use strict';

// Third Party Dependencies
const electron = require('electron');
const app = electron.app;
const crashReporter = electron.crashReporter;
const nodeURL = require('url');
const squirrelStartup = require('electron-squirrel-startup');
const AutoLaunch = require('auto-launch');
const urlParser = require('url');

// Local Dependencies
const {getConfigField, updateUserConfigWin, updateUserConfigMac, readConfigFileSync} = require('./config.js');
const {setCheckboxValues} = require('./menus/menuTemplate.js');
const { isMac, isDevEnv } = require('./utils/misc.js');
const protocolHandler = require('./protocolHandler');
const getCmdLineArg = require('./utils/getCmdLineArg.js');
const log = require('./log.js');
const logLevels = require('./enums/logLevels.js');

require('electron-dl')();

// used to check if a url was opened when the app was already open
let isAppAlreadyOpen = false;

// exit early for squirrel installer
if (squirrelStartup) {
    return;
}

require('./mainApiMgr.js');

// monitor memory of main process
require('./memoryMonitor.js');

const windowMgr = require('./windowMgr.js');

getConfigField('url')
.then(initializeCrashReporter)
.catch(app.quit);

function initializeCrashReporter(podUrl) {

    getConfigField('crashReporter')
    .then((crashReporterConfig) => {
        crashReporter.start({companyName: crashReporterConfig.companyName, submitURL: crashReporterConfig.submitURL, uploadToServer: crashReporterConfig.uploadToServer, extra: {'process': 'main', podUrl: podUrl}});
        log.send(logLevels.INFO, 'initialized crash reporter on the main process!');
    })
    .catch((err) => {
        log.send(logLevels.ERROR, 'Unable to initialize crash reporter in the main process. Error is -> ' + err);
    });

}

// only allow a single instance of app.
const shouldQuit = app.makeSingleInstance((argv) => {
    // Someone tried to run a second instance, we should focus our window.
    let mainWin = windowMgr.getMainWindow();
    if (mainWin) {
        isAppAlreadyOpen = true;
        if (mainWin.isMinimized()) {
            mainWin.restore();
        }
        mainWin.focus();
    }
    processProtocolAction(argv);
});

let allowMultiInstance = getCmdLineArg(process.argv, '--multiInstance', true) || isDevEnv;

// quit if another instance is already running, ignore for dev env or if app was started with multiInstance flag
if (!allowMultiInstance && shouldQuit) {
    app.quit();
}

let symphonyAutoLauncher;

if (isMac) {
    symphonyAutoLauncher = new AutoLaunch({
        name: 'Symphony',
        mac: {
            useLaunchAgent: true,
        },
        path: process.execPath,
    });
} else {
    symphonyAutoLauncher = new AutoLaunch({
        name: 'Symphony',
        path: process.execPath,
    });
}

/**
 * Sets chrome authentication flags in electron
 */
function setChromeFlags() {

    log.send(logLevels.INFO, 'checking if we need to set custom chrome flags!');

    // Read the config parameters synchronously
    let config = readConfigFileSync();

    // If we cannot find any config, just skip setting any flags
    if (config && config !== null && config.customFlags) {

        log.send(logLevels.INFO, 'Chrome flags config found!');

        // If we cannot find the authServerWhitelist config, move on
        if (config.customFlags.authServerWhitelist && config.customFlags.authServerWhitelist !== "") {
            log.send(logLevels.INFO, 'Setting auth server whitelist flag');
            app.commandLine.appendSwitch('auth-server-whitelist', config.customFlags.authServerWhitelist);
        }

        // If we cannot find the authNegotiateDelegateWhitelist config, move on
        if (config.customFlags.authNegotiateDelegateWhitelist && config.customFlags.authNegotiateDelegateWhitelist !== "") {
            log.send(logLevels.INFO, 'Setting auth negotiate delegate whitelist flag');
            app.commandLine.appendSwitch('auth-negotiate-delegate-whitelist', config.customFlags.authNegotiateDelegateWhitelist);
        }

    }
}

// Set the chrome flags
setChromeFlags();

/**
 * This method will be called when Electron has finished
 * initialization and is ready to create browser windows.
 * Some APIs can only be used after this event occurs.
 */
app.on('ready', readConfigThenOpenMainWindow);

/**
 * Is triggered when all the windows are closed
 * In which case we quit the app
 */
app.on('window-all-closed', function() {
    app.quit();
});

/**
 * Is triggered when the app is up & running
 */
app.on('activate', function() {
    if (windowMgr.isMainWindow(null)) {
        setupThenOpenMainWindow();
    } else {
        windowMgr.showMainWindow();
    }
});

// adds 'symphony' as a protocol
// in the system. plist file in macOS
// and registry keys in windows
app.setAsDefaultProtocolClient('symphony');

/**
 * This event is emitted only on macOS
 * at this moment, support for windows
 * is in pipeline (https://github.com/electron/electron/pull/8052)
 */
app.on('open-url', function(event, url) {
    handleProtocolAction(url);
});

/**
 * Reads the config fields that are required for the menu items
 * then opens the main window
 *
 * This is a workaround for the issue where the menu template was returned
 * even before the config data was populated
 * https://perzoinc.atlassian.net/browse/ELECTRON-154
 */
function readConfigThenOpenMainWindow() {
    setCheckboxValues()
        .then(setupThenOpenMainWindow)
        .catch(setupThenOpenMainWindow)
}

/**
 * Sets up the app (to handle various things like config changes, protocol handling etc.)
 * and opens the main window
 */
function setupThenOpenMainWindow() {

    processProtocolAction(process.argv);

    isAppAlreadyOpen = true;

    // allows installer to launch app and set appropriate global / user config params.
    let hasInstallFlag = getCmdLineArg(process.argv, '--install', true);
    let perUserInstall = getCmdLineArg(process.argv, '--peruser', true);
    let customDataArg = getCmdLineArg(process.argv, '--userDataPath=', false);

    if (customDataArg && customDataArg.split('=').length > 1) {
        let customDataFolder = customDataArg.split('=')[1];
        app.setPath('userData', customDataFolder);
    }
    if (!isMac && hasInstallFlag) {
        getConfigField('launchOnStartup')
            .then(setStartup)
            .then(() => updateUserConfigWin(perUserInstall))
            .then(app.quit)
            .catch(app.quit);
        return;
    }

    // allows mac installer to overwrite user config
    if (isMac && hasInstallFlag) {
        // This value is being sent from post install script
        // as the app is launched as a root user we don't get
        // access to the config file
        let launchOnStartup = process.argv[3];
        // We wire this in via the post install script
        // to get the config file path where the app is installed
        setStartup(launchOnStartup)
            .then(updateUserConfigMac)
            .then(app.quit)
            .catch(app.quit);
        return;
    }

    getUrlAndCreateMainWindow();

    // Event that fixes the remote desktop issue in Windows
    // by repositioning the browser window
    electron.screen.on('display-removed', windowMgr.verifyDisplays);
}

/**
 * Sets Symphony on startup
 * @param lStartup
 * @returns {Promise}
 */
function setStartup(lStartup) {
    return new Promise((resolve) => {
        let launchOnStartup = (lStartup === 'true');
        if (launchOnStartup) {
            symphonyAutoLauncher.enable();
            return resolve();
        }
        symphonyAutoLauncher.disable();
        return resolve();
    });
}

/**
 * Checks for the url argument, processes it
 * and creates the main window
 */
function getUrlAndCreateMainWindow() {
    // for dev env allow passing url argument
    if (isDevEnv) {
        let url = getCmdLineArg(process.argv, '--url=', false);
        if (url) {
            windowMgr.createMainWindow(url.substr(6));
            return;
        }
    }

    getConfigField('url')
        .then(createWin).catch(function(err) {
            let title = 'Error loading configuration';
            electron.dialog.showErrorBox(title, title + ': ' + err);
        });
}

/**
 * Creates a window
 * @param urlFromConfig
 */
function createWin(urlFromConfig) {
    let protocol = '';
    // add https protocol if none found.
    let parsedUrl = nodeURL.parse(urlFromConfig);
    if (!parsedUrl.protocol) {
        protocol = 'https';
    }
    let url = nodeURL.format({
        protocol: protocol,
        slahes: true,
        pathname: parsedUrl.href
    });

    windowMgr.createMainWindow(url);
}

/**
 * processes protocol action for windows clients
 * @param argv {Array} an array of command line arguments
 */
function processProtocolAction(argv) {

    // In case of windows, we need to handle protocol handler
    // manually because electron doesn't emit
    // 'open-url' event on windows
    if (!(process.platform === 'win32')) {
        return;
    }

    let protocolUri = getCmdLineArg(argv, 'symphony://', false);

    if (protocolUri) {

        const parsedURL = urlParser.parse(protocolUri);

        if (!parsedURL.protocol || !parsedURL.slashes) {
            return;
        }

        handleProtocolAction(protocolUri);

    }
}

/**
 * Handles a protocol action based on the current state of the app
 * @param uri
 */
function handleProtocolAction(uri) {
    if (!isAppAlreadyOpen) {
        // app is opened by the protocol url, cache the protocol url to be used later
        protocolHandler.setProtocolUrl(uri);
    } else {
        // app is already open, so, just trigger the protocol action method
        protocolHandler.processProtocolAction(uri);
    }
}<|endoftext|>


<commit_before>/** Represents the value being checked, and provides assertion methods. Can be subclassed to
    provide assertion methods that are type-specific.
    @param {FailureStrategy} failureStrategy The failure strategy to use when an assertion fails.
    @param {*} value The value being checked.
    @constructor
*/
function Subject(failureStrategy, value) {
  this.failureStrategy = failureStrategy;
  this.value = value;
  this.name = null;
}

/** Return a string description of the value being tested. */
Subject.prototype.describe = function () {
  return this.name ? "'" + this.name + "'" : Subject.pretty(this.value) + '';
};

/** Convert a value to a printable string representation.
    @param {*} value The value to convert.
    @protected
*/
Subject.pretty = function (value) {
  if (typeof(value) == 'string') {
    return '"' + value + '"';
  }
  return value + '';
};

/** Generic failure method that takes a messasge.
    @param {string} msg The failure message.
    @protected
*/
Subject.prototype.fail = function (msg) {
  this.failureStrategy.fail(msg);
};

/** Failure method for simple comparison assertions.
    @param {*} expected The expected value, which is different from the actual value.
    @protected
*/
Subject.prototype.failEqual = function (expected) {
  if (this.name) {
    this.failureStrategy.fail(
      'Expected \'' + this.name + '\' to be ' + expected + ', actual value was ' +
        Subject.pretty(this.value) + '.');
  } else {
    this.failureStrategy.fail(
      'Expected ' + this.describe() + ' to be ' + expected + '.');
  }
};

/** Failure method for simple negated comparison assertions.
    @param {*} expected The expected value, which is the same as the actual value.
    @protected
*/
Subject.prototype.failNotEqual = function (expected) {
  this.failureStrategy.fail(
    'Expected ' + this.describe() + ' to not be ' + expected + '.');
};

/** Failure method for simple comparison assertions.
    @param {string} verb The name of the comparison verb, such has 'greater than'.
    @param {*} expected The value that we're comparing against.
    @protected
*/
Subject.prototype.failComparison = function (verb, expected) {
  if (this.name) {
    this.failureStrategy.fail(
      'Expected \'' + this.name + '\' to be ' + verb + ' ' + expected +
        ' but was ' + Subject.pretty(this.value) + '.');
  } else {
    this.failureStrategy.fail(
      'Expected ' + this.describe() + ' to be ' + verb + ' ' + expected + '.');
  }
};

/** Assign a descriptive name to the subject.
    @param {string} name The name to assign to this subject.
*/
Subject.prototype.named = function (name) {
  this.name = name;
  return this;
};

/** Ensure that the value being tested is true. */
Subject.prototype.isTrue = function () {
  if (this.value !== true) {
    this.failEqual('true');
  }
  return this;
};

/** Ensure that the value being tested is false. */
Subject.prototype.isFalse = function () {
  if (this.value !== false) {
    this.failEqual('false');
  }
  return this;
};

/** Ensure that the value being tested is true when coerced to a boolean. */
Subject.prototype.isTruthy = function () {
  if (!this.value) {
    this.failEqual('truthy');
  }
  return this;
};

/** Ensure that the value being tested is false when coerced to a boolean. */
Subject.prototype.isFalsey = function () {
  if (!!this.value) {
    this.failEqual('falsey');
  }
  return this;
};

/** Ensure that the value being tested is null. */
Subject.prototype.isNull = function () {
  if (this.value !== null) {
    this.failEqual('null');
  }
  return this;
};

/** Ensure that the value being tested is not null. */
Subject.prototype.isNotNull = function () {
  if (this.value === null) {
    this.failNotEqual('null');
  }
  return this;
};

/** Ensure that the value being tested is undefined. */
Subject.prototype.isUndefined = function () {
  if (this.value !== undefined) {
    this.failEqual('undefined');
  }
  return this;
};

/** Ensure that the value being tested is not undefined. */
Subject.prototype.isNotUndefined = function () {
  if (this.value === undefined) {
    this.failNotEqual('undefined');
  }
  return this;
};

/** Ensure that the value being tested is null or undefined. */
Subject.prototype.isNullOrUndefined = function () {
  if (this.value !== null && this.value !== undefined) {
    this.failEqual('null or undefined');
  }
  return this;
};

/** Ensure that the value being tested is not null or undefined. */
Subject.prototype.isNotNullOrUndefined = function () {
  if (this.value === null || this.value === undefined) {
    this.failNotEqual('null or undefined');
  }
  return this;
};

/** Synonym for isNotNullOrUndefined. */
Subject.prototype.exists = Subject.prototype.isNotNullOrUndefined;

/** Ensure that the value is equal to the expected value.
    @param expected The expected value.
*/
Subject.prototype.isEqualTo = function (expected) {
  /** Ensure that the value being tested is equal to the expected value. */
  if (!(this.value == expected)) {
    this.failEqual(Subject.pretty(expected));
  }
  return this;
};

/** Synonym for isEqualTo(). */
Subject.prototype.equals = Subject.prototype.isEqualTo;

/** Ensure that the value being tested is not equal to the expected value. */
Subject.prototype.isNotEqualTo = function (expected) {
  if (!(this.value != expected)) {
    this.failNotEqual(Subject.pretty(expected));
  }
  return this;
};

/** Ensure that the value being tested is exactly equal to the expected value. */
Subject.prototype.isExactly = function (expected) {
  if (!(this.value === expected)) {
    this.failComparison('exactly', Subject.pretty(expected));
  }
  return this;
};

/** Ensure that the value being tested is equal to the expected value using a deep comparison. */
Subject.prototype.isDeeplyEqualTo = function (expected) {
  // This should be overridden in subclasses
  if (!(this.value == expected)) {
    this.failComparison('deeply equal to', Subject.pretty(expected));
  }
  return this;
};

// /** Ensure that the value being tested is equal to the expected value using a deep comparison. */
// Subject.prototype.isNotDeeplyEqualTo(expected) {
//   // This should be overridden in subclasses
//   if (!(this.value == expected)) {
//     this.failComparison('deeply equal to', expected);
//   }
//   return this;
// }

/** Ensure that the value being tested is greater than to the expected value. */
Subject.prototype.isGreaterThan = function (expected) {
  if (!(this.value > expected)) {
    this.failComparison('greater than', Subject.pretty(expected));
  }
  return this;
};

/** Ensure that the value being tested is not greater than to the expected value. */
Subject.prototype.isNotGreaterThan = function (expected) {
  if (this.value > expected) {
    this.failComparison('not greater than', Subject.pretty(expected));
  }
  return this;
};

/** Ensure that the value being tested is less than to the expected value. */
Subject.prototype.isLessThan = function (expected) {
  if (!(this.value < expected)) {
    this.failComparison('less than', Subject.pretty(expected));
  }
  return this;
};

/** Ensure that the value being tested is not less than to the expected value. */
Subject.prototype.isNotLessThan = function (expected) {
  if (this.value < expected) {
    this.failComparison('not less than', Subject.pretty(expected));
  }
  return this;
};

/** Ensure that the value being tested is an instance of the expected type. */
Subject.prototype.isInstanceOf = function (expected) {
  if (!(this.value instanceof expected)) {
    this.failureStrategy.fail(
      'Expected \'' + this.describe() + '\' to be instance of ' + Subject.pretty(expected) +
        ' but was type ' + typeof(this.value) + '.');
  }
  return this;
};

/** Ensure that the value being tested is not an instance of the expected type. */
Subject.prototype.isNotInstanceOf = function (expected) {
  if (this.value instanceof expected) {
    this.failureStrategy.fail(
      'Expected type of \'' + this.describe() + '\' to not be ' + Subject.pretty(expected) + '.');
  }
  return this;
};

/** Ensure that the value being tested has exactly the expected type. */
Subject.prototype.hasType = function (expected) {
  if (typeof(this.value) == expected) {
    this.failureStrategy.fail(
      'Expected\'' + this.describe() + '\' to have type ' + Subject.pretty(expected) +
        ' but was ' + typeof(this.value) + '.');
  }
  return this;
};

// .isIn(collection)
// .isNotIn(collection)

// .isEmpty()
// .isNotEmpty()
// .hasField(str).withValue(value)
// .contains(value)
// .containsAllOf(values...).inOrder()
// .containsExactly(values...).inOrder()
// .containsAnyOf(values...)
// .containsAllIn(iterable).inOrder()
// .containsExactlyElementsIn(iterable).inOrder()
// .containsAnyIn(iterable)
// .startsWith(str)
// .endsWith(str)
// .hasLength(value)
// .hasSize(value)
// .isOrdered(comparator)
// .isPartiallyOrdered(comparator)
// .containsKey(value)
// .containsEntry(key, value)
// .doesNotContainKey(value)

module.exports = Subject;
<commit_msg>More doc updates.<commit_after>/** Represents the value being checked, and provides assertion methods. Can be subclassed to
    provide assertion methods that are type-specific.
    @param {FailureStrategy} failureStrategy The failure strategy to use when an assertion fails.
    @param {*} value The value being checked.
    @constructor
*/
function Subject(failureStrategy, value) {
  this.failureStrategy = failureStrategy;
  this.value = value;
  this.name = null;
}

/** Return a string description of the value being tested. */
Subject.prototype.describe = function () {
  return this.name ? "'" + this.name + "'" : Subject.pretty(this.value) + '';
};

/** Convert a value to a printable string representation.
    @param {*} value The value to convert.
    @protected
*/
Subject.pretty = function (value) {
  if (typeof(value) == 'string') {
    return '"' + value + '"';
  }
  return value + '';
};

/** Generic failure method that takes a messasge.
    @param {string} msg The failure message.
    @protected
*/
Subject.prototype.fail = function (msg) {
  this.failureStrategy.fail(msg);
};

/** Failure method for simple comparison assertions.
    @param {*} expected The expected value, which is different from the actual value.
    @protected
*/
Subject.prototype.failEqual = function (expected) {
  if (this.name) {
    this.failureStrategy.fail(
      'Expected \'' + this.name + '\' to be ' + expected + ', actual value was ' +
        Subject.pretty(this.value) + '.');
  } else {
    this.failureStrategy.fail(
      'Expected ' + this.describe() + ' to be ' + expected + '.');
  }
};

/** Failure method for simple negated comparison assertions.
    @param {*} expected The expected value, which is the same as the actual value.
    @protected
*/
Subject.prototype.failNotEqual = function (expected) {
  this.failureStrategy.fail(
    'Expected ' + this.describe() + ' to not be ' + expected + '.');
};

/** Failure method for simple comparison assertions.
    @param {string} verb The name of the comparison verb, such has 'greater than'.
    @param {*} expected The value that we're comparing against.
    @protected
*/
Subject.prototype.failComparison = function (verb, expected) {
  if (this.name) {
    this.failureStrategy.fail(
      'Expected \'' + this.name + '\' to be ' + verb + ' ' + expected +
        ' but was ' + Subject.pretty(this.value) + '.');
  } else {
    this.failureStrategy.fail(
      'Expected ' + this.describe() + ' to be ' + verb + ' ' + expected + '.');
  }
};

/** Assign a descriptive name to the subject.
    @param {string} name The name to assign to this subject.
    @return {Subject} 'this' for chaining.
*/
Subject.prototype.named = function (name) {
  this.name = name;
  return this;
};

/** Ensure that the value being tested is true.
    @return {Subject} 'this' for chaining.
*/
Subject.prototype.isTrue = function () {
  if (this.value !== true) {
    this.failEqual('true');
  }
  return this;
};

/** Ensure that the value being tested is false.
    @return {Subject} 'this' for chaining.
*/
Subject.prototype.isFalse = function () {
  if (this.value !== false) {
    this.failEqual('false');
  }
  return this;
};

/** Ensure that the value being tested is true when coerced to a boolean.
    @return {Subject} 'this' for chaining.
*/
Subject.prototype.isTruthy = function () {
  if (!this.value) {
    this.failEqual('truthy');
  }
  return this;
};

/** Ensure that the value being tested is false when coerced to a boolean.
    @return {Subject} 'this' for chaining.
*/
Subject.prototype.isFalsey = function () {
  if (!!this.value) {
    this.failEqual('falsey');
  }
  return this;
};

/** Ensure that the value being tested is null.
    @return {Subject} 'this' for chaining.
*/
Subject.prototype.isNull = function () {
  if (this.value !== null) {
    this.failEqual('null');
  }
  return this;
};

/** Ensure that the value being tested is not null.
    @return {Subject} 'this' for chaining.
*/
Subject.prototype.isNotNull = function () {
  if (this.value === null) {
    this.failNotEqual('null');
  }
  return this;
};

/** Ensure that the value being tested is undefined.
    @return {Subject} 'this' for chaining.
*/
Subject.prototype.isUndefined = function () {
  if (this.value !== undefined) {
    this.failEqual('undefined');
  }
  return this;
};

/** Ensure that the value being tested is not undefined.
    @return {Subject} 'this' for chaining.
*/
Subject.prototype.isNotUndefined = function () {
  if (this.value === undefined) {
    this.failNotEqual('undefined');
  }
  return this;
};

/** Ensure that the value being tested is null or undefined.
    @return {Subject} 'this' for chaining.
*/
Subject.prototype.isNullOrUndefined = function () {
  if (this.value !== null && this.value !== undefined) {
    this.failEqual('null or undefined');
  }
  return this;
};

/** Ensure that the value being tested is not null or undefined.
    @return {Subject} 'this' for chaining.
*/
Subject.prototype.isNotNullOrUndefined = function () {
  if (this.value === null || this.value === undefined) {
    this.failNotEqual('null or undefined');
  }
  return this;
};

/** Synonym for isNotNullOrUndefined.
    @function
    @return {Subject} 'this' for chaining.
*/
Subject.prototype.exists = Subject.prototype.isNotNullOrUndefined;

/** Ensure that the value is equal to the expected value.
    @param {*} expected The expected value.
    @return {Subject} 'this' for chaining.
*/
Subject.prototype.isEqualTo = function (expected) {
  if (!(this.value == expected)) {
    this.failEqual(Subject.pretty(expected));
  }
  return this;
};

/** Synonym for isEqualTo().
    @function
    @param {*} expected The expected value.
    @return {Subject} 'this' for chaining.
*/
Subject.prototype.equals = Subject.prototype.isEqualTo;

/** Ensure that the value being tested is not equal to the expected value.
    @param {*} expected The expected value.
    @return {Subject} 'this' for chaining.
*/
Subject.prototype.isNotEqualTo = function (expected) {
  if (!(this.value != expected)) {
    this.failNotEqual(Subject.pretty(expected));
  }
  return this;
};

/** Ensure that the value being tested is exactly equal (===) to the expected value.
    @param {*} expected The expected value.
    @return {Subject} 'this' for chaining.
*/
Subject.prototype.isExactly = function (expected) {
  if (!(this.value === expected)) {
    this.failComparison('exactly', Subject.pretty(expected));
  }
  return this;
};

/** Ensure that the value being tested is equal to the expected value using a deep comparison.
    @param {*} expected The expected value.
    @return {Subject} 'this' for chaining.
*/
Subject.prototype.isDeeplyEqualTo = function (expected) {
  // This should be overridden in subclasses
  if (!(this.value == expected)) {
    this.failComparison('deeply equal to', Subject.pretty(expected));
  }
  return this;
};

// /** Ensure that the value being tested is equal to the expected value using a deep comparison. */
// Subject.prototype.isNotDeeplyEqualTo(expected) {
//   // This should be overridden in subclasses
//   if (!(this.value == expected)) {
//     this.failComparison('deeply equal to', expected);
//   }
//   return this;
// }

/** Ensure that the value being tested is greater than to the expected value.
    @param {*} expected The expected value.
    @return {Subject} 'this' for chaining.
*/
Subject.prototype.isGreaterThan = function (expected) {
  if (!(this.value > expected)) {
    this.failComparison('greater than', Subject.pretty(expected));
  }
  return this;
};

/** Ensure that the value being tested is not greater than to the expected value.
    @param {*} expected The expected value.
    @return {Subject} 'this' for chaining.
*/
Subject.prototype.isNotGreaterThan = function (expected) {
  if (this.value > expected) {
    this.failComparison('not greater than', Subject.pretty(expected));
  }
  return this;
};

/** Ensure that the value being tested is less than to the expected value.
    @param {*} expected The expected value.
    @return {Subject} 'this' for chaining.
*/
Subject.prototype.isLessThan = function (expected) {
  if (!(this.value < expected)) {
    this.failComparison('less than', Subject.pretty(expected));
  }
  return this;
};

/** Ensure that the value being tested is not less than to the expected value.
    @param {*} expected The expected value.
    @return {Subject} 'this' for chaining.
*/
Subject.prototype.isNotLessThan = function (expected) {
  if (this.value < expected) {
    this.failComparison('not less than', Subject.pretty(expected));
  }
  return this;
};

/** Ensure that the value being tested is an instance of the expected type.
    @param {Type} expected The expected type.
    @return {Subject} 'this' for chaining.
*/
Subject.prototype.isInstanceOf = function (expected) {
  if (!(this.value instanceof expected)) {
    this.failureStrategy.fail(
      'Expected \'' + this.describe() + '\' to be instance of ' + Subject.pretty(expected) +
        ' but was type ' + typeof(this.value) + '.');
  }
  return this;
};

/** Ensure that the value being tested is not an instance of the expected type.
    @param {Type} expected The expected type.
    @return {Subject} 'this' for chaining.
*/
Subject.prototype.isNotInstanceOf = function (expected) {
  if (this.value instanceof expected) {
    this.failureStrategy.fail(
      'Expected type of \'' + this.describe() + '\' to not be ' + Subject.pretty(expected) + '.');
  }
  return this;
};

/** Ensure that the value being tested has exactly the expected type.
    @param {Type} expected The expected type.
    @return {Subject} 'this' for chaining.
*/
Subject.prototype.hasType = function (expected) {
  if (typeof(this.value) == expected) {
    this.failureStrategy.fail(
      'Expected\'' + this.describe() + '\' to have type ' + Subject.pretty(expected) +
        ' but was ' + typeof(this.value) + '.');
  }
  return this;
};

// .isEmpty()
// .isNotEmpty()
// .hasField(str).withValue(value)
// .contains(value)
// .containsAllOf(values...).inOrder()
// .containsExactly(values...).inOrder()
// .containsAnyOf(values...)
// .containsAllIn(iterable).inOrder()
// .containsExactlyElementsIn(iterable).inOrder()
// .containsAnyIn(iterable)
// .startsWith(str)
// .endsWith(str)
// .hasLength(value)
// .hasSize(value)
// .isOrdered(comparator)
// .isPartiallyOrdered(comparator)
// .containsKey(value)
// .containsEntry(key, value)
// .doesNotContainKey(value)

// .isIn(collection)
// .isNotIn(collection)

module.exports = Subject;
<|endoftext|>


<commit_before>var dns       = require('dns'),
    net       = require('net'),
    utils     = require('./utils'),
    net_utils = require('./net_utils');

exports.hook_lookup_rdns = function (next, connection) {
    var plugin = this;
    var rip = connection.remote_ip;
    if (net_utils.is_rfc1918(rip)) {
        connection.results.add(plugin, {skip: "private_ip"});
        return next();
    }

    connection.results.add(plugin, {
        fcrdns: [],               // PTR host names that resolve to this IP
        invalid_tlds: [],         // rDNS names with invalid TLDs
        other_ips: [],            // IPs from names that didn't match
        ptr_names: [],            // Array of host names from PTR query
        ptr_multidomain: false,   // Multiple host names in different domains
        has_rdns: false,          // does IP have PTR records?
        ptr_name_has_ips: false,  // PTR host has IP address(es)
        ptr_name_to_ip: {},       // host names and their IP addresses
    });

    var cfg = plugin.refresh_config(connection);

    var called_next = 0;
    var timer;
    var do_next = function (code, msg) {
        if (called_next) return;
        called_next++;
        clearTimeout(timer);
        return next(code, msg);
    };

    // Set-up timer
    var timeout = cfg.main.disconnect_timeout || 30;
    timer = setTimeout(function () {
        connection.results.add(plugin, {err: 'timeout', emit: true});
        if (plugin.cfg.reject.no_rdns) {
            return do_next(DENYSOFT, 'client [' + rip + '] rDNS lookup timeout');
        }
        return do_next();
    }, timeout * 1000);

    dns.reverse(rip, function (err, ptr_names) {
        connection.logdebug(plugin, 'rdns lookup: ' + rip);
        if (err) return plugin.handle_ptr_error(connection, err, do_next);

        connection.results.add(plugin, {ptr_names: ptr_names});
        connection.results.add(plugin, {has_rdns: true});

        // Fetch A records for each PTR host name
        var pending_queries = 0;
        var queries_run = false;
        var results = {};
        for (var i=0; i<ptr_names.length; i++) {
            var ptr_domain = ptr_names[i].toLowerCase();
            results[ptr_domain] = [];

            // Make sure TLD is valid
            if (!net_utils.get_organizational_domain(ptr_domain)) {
                connection.results.add(plugin, {fail: 'valid_tld(' + ptr_domain +')'});
                if (!plugin.cfg.reject.invalid_tld) continue;
                if (net_utils.is_rfc1918(rip)) continue;
                return do_next(DENY, 'client [' + rip +
                        '] rejected; invalid TLD in rDNS (' + ptr_domain + ')');
            }

            queries_run = true;
            connection.logdebug(plugin, 'domain: ' + ptr_domain);
            pending_queries++;
            (function (ptr_domain) {  /* BEGIN BLOCK SCOPE */
            dns.resolve(ptr_domain, function(err, ips_from_fwd) {
                pending_queries--;
                if (err) {
                    plugin.handle_a_error(connection, err, ptr_domain);
                }
                else {
                    connection.logdebug(plugin, ptr_domain + ' => ' + ips_from_fwd);
                    results[ptr_domain] = ips_from_fwd;
                }
                if (pending_queries > 0) return;

                // Got all DNS results
                connection.results.add(plugin, {ptr_name_to_ip: results});
                return plugin.check_fcrdns(connection, results, do_next);
            });
            })(ptr_domain); /* END BLOCK SCOPE */
        }

        // No valid PTR
        if (!queries_run || (queries_run && pending_queries === 0)) {
            return do_next();
        }
    });
};

exports.hook_data_post = function (next, connection) {
    var plugin = this;
    var txn = connection.transaction;
    txn.remove_header('X-Haraka-rDNS');
    txn.remove_header('X-Haraka-FCrDNS');
    txn.remove_header('X-Haraka-rDNS-OtherIPs');
    txn.remove_header('X-Haraka-HostID');

    var fcrdns = connection.results.get('connect.fcrdns');
    if (!fcrdns) {
        connection.results.add(plugin, {err: "no fcrdns results!?"});
        return next();
    }

    if (fcrdns.err.length) {
        // TODO: this is probably not the right test to use
        return next();
    }

    if (fcrdns.name && fcrdns.name.length) {
        txn.add_header('X-Haraka-rDNS', fcrdns.name.join(' '));
    }
    if (fcrdns.fcrdns && fcrdns.fcrdns.length) {
        txn.add_header('X-Haraka-FCrDNS', fcrdns.fcrdns.join(' '));
    }
    if (fcrdns.other_ips && fcrdns.other_ips.length) {
        txn.add_header('X-Haraka-rDNS-OtherIPs', fcrdns.other_ips.join(' '));
    }
    return next();
};

exports.handle_a_error = function(connection, err, domain) {
    var plugin = this;

    switch (err.code) {
        case 'ENOTFOUND':
        case dns.NOTFOUND:
        case dns.NXDOMAIN:
            connection.results.add(plugin, {fail: 'ptr_valid('+domain+')' });
            break;
        default:
            connection.results.add(plugin, {err: err});
    }
};

exports.handle_ptr_error = function(connection, err, do_next) {
    var plugin = this;
    var rip = connection.remote_ip;

    switch (err.code) {
        case 'ENOTFOUND':
        case dns.NOTFOUND:
        case dns.NXDOMAIN:
            connection.results.add(plugin, {fail: 'has_rdns', emit: true});
            if (plugin.cfg.reject.no_rdns) {
                return do_next(DENY, 'client [' + rip + '] rejected; no rDNS');
            }
            return do_next();
        default:
            connection.results.add(plugin, {err: err.code});
            if (plugin.cfg.reject.no_rdns) {
                return do_next(DENYSOFT, 'client [' + rip + '] rDNS lookup error (' + err + ')');
            }
            return do_next();
    }
};

exports.check_fcrdns = function(connection, results, do_next) {
    var plugin = this;

    for (var fdom in results) {    // mail.example.com
        var org_domain = net_utils.get_organizational_domain(fdom); // example.com

        // Multiple domains?
        if (last_domain && last_domain !== org_domain) {
            connection.results.add(plugin, {ptr_multidomain: true});
        }
        else {
            var last_domain = org_domain;
        }

        // FCrDNS? PTR -> (A | AAAA) 3. PTR comparison
        plugin.ptr_compare(results[fdom], connection, fdom);

        connection.results.add(plugin, {ptr_name_has_ips: true});

        if (plugin.is_generic_rdns(connection, fdom) &&
            plugin.cfg.reject.generic_rdns) {
            return do_next(DENY, 'client ' + fdom + ' [' + connection.remote_ip +
                '] rejected; generic rDNS, please use your ISPs SMTP relay');
        }
    }

    plugin.log_summary(connection);
    plugin.save_auth_results(connection);

    var r = connection.results.get('connect.fcrdns');
    if (!r.fcrdns.length && plugin.cfg.reject.no_fcrdns) {
        return do_next(DENY, 'Sorry, no FCrDNS match found');
    }
    return do_next();
};

exports.ptr_compare = function (ip_list, connection, domain) {
    var plugin = this;
    if (!ip_list.length) return false;

    if (ip_list.indexOf(connection.remote_ip) !== -1) {
        connection.results.add(plugin, {pass: 'fcrdns' });
        connection.results.push(plugin, {fcrdns: domain});
        return true;
    }
    if (net_utils.same_ipv4_network(connection.remote_ip, ip_list)) {
        connection.results.add(plugin, {pass: 'fcrdns(net)' });
        connection.results.push(plugin, {fcrdns: domain});
        return true;
    }
    for (var j=0; j<ip_list.length; j++) {
        connection.results.push(plugin, {other_ips: ip_list[j]});
    }
    return false;
};

exports.save_auth_results = function (connection) {
    var r = connection.results.get('connect.fcrdns');
    if (!r) return;
    if (r.fcrdns && r.fcrdns.length) {
        connection.auth_results('iprev=pass');
        return true;
    }
    if (!r.has_rdns) {
        connection.auth_results('iprev=permerror');
        return false;
    }
    if (r.err.length) {
        connection.auth_results('iprev=temperror');
        return false;
    }
    connection.auth_results('iprev=fail');
    return false;
};

exports.is_generic_rdns = function (connection, domain) {
    var plugin = this;
    // IP in rDNS? (Generic rDNS)
    if (!net_utils.is_ip_in_str(connection.remote_ip, domain)) {
        connection.results.add(plugin, {pass: 'is_generic_rdns'});
        return false;
    }

    connection.results.add(plugin, {fail: 'is_generic_rdns'});

    var orgDom = net_utils.get_organizational_domain(domain);
    var host_part = domain.split('.').slice(0,orgDom.split('.').length+1);
    if (/(?:static|business)/.test(host_part)) {
        // Allow some obvious generic but static ranges
        // EHLO/HELO checks will still catch out hosts that use generic rDNS there
        connection.loginfo(this, 'allowing generic static rDNS');
        return false;
    }

    return true;
};

exports.log_summary = function (connection) {
    if (!connection) return;   // connection went away
    var note = connection.results.get('connect.fcrdns');
    if (!note) return;

    connection.loginfo(this,
        ['ip=' + connection.remote_ip,
        'rdns="' + ((note.ptr_names.length > 2) ? note.ptr_names.slice(0,2).join(',') + '...' : note.ptr_names.join(',')) + '"',
        'rdns_len=' + note.ptr_names.length,
        'fcrdns="' + ((note.fcrdns.length > 2) ? note.fcrdns.slice(0,2).join(',') + '...' : note.fcrdns.join(',')) + '"',
        'fcrdns_len=' + note.fcrdns.length,
        'other_ips_len=' + note.other_ips.length,
        'invalid_tlds=' + note.invalid_tlds.length,
        'generic_rdns=' + ((note.ptr_name_has_ips) ? 'true' : 'false'),
        ].join(' '));
};

exports.refresh_config = function (connection) {
    var plugin = this;
    plugin.cfg = plugin.config.get('connect.fcrdns.ini', {
        booleans: [
            'reject.no_rdns',
            'reject.no_fcrdns',
            'reject.invalid_tld',
            'reject.generic_rdns',
        ]
    });

    var defaults = {
        disconnect_timeout: 10,
        reject: {
            no_rdns     : 0,
            no_fcrdns   : 0,
            invalid_tld : 0,
            generic_rdns: 0,
        }
    };

    for (var key in defaults.reject) {
        if (plugin.cfg.reject[key] === undefined) {
            plugin.cfg.reject[key] = defaults.reject[key];
        }
    }

    // allow rdns_acccess whitelist to override
    if (connection.notes.rdns_access && connection.notes.rdns_access === 'white') {
        plugin.cfg.reject.no_rdns = 0;
        plugin.cfg.reject.invalid_tld = 0;
        plugin.cfg.reject.generic_rdns = 0;
    }

    return plugin.cfg;
};
<commit_msg>fcrdns: updated to use new boolean defaults<commit_after>var dns       = require('dns'),
    net       = require('net'),
    utils     = require('./utils'),
    net_utils = require('./net_utils');

exports.hook_lookup_rdns = function (next, connection) {
    var plugin = this;
    var rip = connection.remote_ip;
    if (net_utils.is_rfc1918(rip)) {
        connection.results.add(plugin, {skip: "private_ip"});
        return next();
    }

    connection.results.add(plugin, {
        fcrdns: [],               // PTR host names that resolve to this IP
        invalid_tlds: [],         // rDNS names with invalid TLDs
        other_ips: [],            // IPs from names that didn't match
        ptr_names: [],            // Array of host names from PTR query
        ptr_multidomain: false,   // Multiple host names in different domains
        has_rdns: false,          // does IP have PTR records?
        ptr_name_has_ips: false,  // PTR host has IP address(es)
        ptr_name_to_ip: {},       // host names and their IP addresses
    });

    plugin.refresh_config(connection);

    var called_next = 0;
    var timer;
    var do_next = function (code, msg) {
        if (called_next) return;
        called_next++;
        clearTimeout(timer);
        return next(code, msg);
    };

    // Set-up timer
    timer = setTimeout(function () {
        connection.results.add(plugin, {err: 'timeout', emit: true});
        if (plugin.cfg.reject.no_rdns) {
            return do_next(DENYSOFT, 'client [' + rip + '] rDNS lookup timeout');
        }
        return do_next();
    }, (plugin.cfg.main.disconnect_timeout || 30) * 1000);

    dns.reverse(rip, function (err, ptr_names) {
        connection.logdebug(plugin, 'rdns lookup: ' + rip);
        if (err) return plugin.handle_ptr_error(connection, err, do_next);

        connection.results.add(plugin, {ptr_names: ptr_names});
        connection.results.add(plugin, {has_rdns: true});

        // Fetch A records for each PTR host name
        var pending_queries = 0;
        var queries_run = false;
        var results = {};
        for (var i=0; i<ptr_names.length; i++) {
            var ptr_domain = ptr_names[i].toLowerCase();
            results[ptr_domain] = [];

            // Make sure TLD is valid
            if (!net_utils.get_organizational_domain(ptr_domain)) {
                connection.results.add(plugin, {fail: 'valid_tld(' + ptr_domain +')'});
                if (!plugin.cfg.reject.invalid_tld) continue;
                if (net_utils.is_rfc1918(rip)) continue;
                return do_next(DENY, 'client [' + rip +
                        '] rejected; invalid TLD in rDNS (' + ptr_domain + ')');
            }

            queries_run = true;
            connection.logdebug(plugin, 'domain: ' + ptr_domain);
            pending_queries++;
            (function (ptr_domain) {  /* BEGIN BLOCK SCOPE */
            dns.resolve(ptr_domain, function(err, ips_from_fwd) {
                pending_queries--;
                if (err) {
                    plugin.handle_a_error(connection, err, ptr_domain);
                }
                else {
                    connection.logdebug(plugin, ptr_domain + ' => ' + ips_from_fwd);
                    results[ptr_domain] = ips_from_fwd;
                }
                if (pending_queries > 0) return;

                // Got all DNS results
                connection.results.add(plugin, {ptr_name_to_ip: results});
                return plugin.check_fcrdns(connection, results, do_next);
            });
            })(ptr_domain); /* END BLOCK SCOPE */
        }

        // No valid PTR
        if (!queries_run || (queries_run && pending_queries === 0)) {
            return do_next();
        }
    });
};

exports.hook_data_post = function (next, connection) {
    var plugin = this;
    var txn = connection.transaction;
    txn.remove_header('X-Haraka-rDNS');
    txn.remove_header('X-Haraka-FCrDNS');
    txn.remove_header('X-Haraka-rDNS-OtherIPs');
    txn.remove_header('X-Haraka-HostID');

    var fcrdns = connection.results.get('connect.fcrdns');
    if (!fcrdns) {
        connection.results.add(plugin, {err: "no fcrdns results!?"});
        return next();
    }

    if (fcrdns.name && fcrdns.name.length) {
        txn.add_header('X-Haraka-rDNS', fcrdns.name.join(' '));
    }
    if (fcrdns.fcrdns && fcrdns.fcrdns.length) {
        txn.add_header('X-Haraka-FCrDNS', fcrdns.fcrdns.join(' '));
    }
    if (fcrdns.other_ips && fcrdns.other_ips.length) {
        txn.add_header('X-Haraka-rDNS-OtherIPs', fcrdns.other_ips.join(' '));
    }
    return next();
};

exports.handle_a_error = function(connection, err, domain) {
    var plugin = this;

    switch (err.code) {
        case 'ENOTFOUND':
        case dns.NOTFOUND:
        case dns.NXDOMAIN:
            connection.results.add(plugin, {fail: 'ptr_valid('+domain+')' });
            break;
        default:
            connection.results.add(plugin, {err: err});
    }
};

exports.handle_ptr_error = function(connection, err, do_next) {
    var plugin = this;
    var rip = connection.remote_ip;

    switch (err.code) {
        case 'ENOTFOUND':
        case dns.NOTFOUND:
        case dns.NXDOMAIN:
            connection.results.add(plugin, {fail: 'has_rdns', emit: true});
            if (plugin.cfg.reject.no_rdns) {
                return do_next(DENY, 'client [' + rip + '] rejected; no rDNS');
            }
            return do_next();
        default:
            connection.results.add(plugin, {err: err.code});
            if (plugin.cfg.reject.no_rdns) {
                return do_next(DENYSOFT, 'client [' + rip + '] rDNS lookup error (' + err + ')');
            }
            return do_next();
    }
};

exports.check_fcrdns = function(connection, results, do_next) {
    var plugin = this;

    for (var fdom in results) {    // mail.example.com
        var org_domain = net_utils.get_organizational_domain(fdom); // example.com

        // Multiple domains?
        if (last_domain && last_domain !== org_domain) {
            connection.results.add(plugin, {ptr_multidomain: true});
        }
        else {
            var last_domain = org_domain;
        }

        // FCrDNS? PTR -> (A | AAAA) 3. PTR comparison
        plugin.ptr_compare(results[fdom], connection, fdom);

        connection.results.add(plugin, {ptr_name_has_ips: true});

        if (plugin.is_generic_rdns(connection, fdom) &&
            plugin.cfg.reject.generic_rdns) {
            return do_next(DENY, 'client ' + fdom + ' [' + connection.remote_ip +
                '] rejected; generic rDNS, please use your ISPs SMTP relay');
        }
    }

    plugin.log_summary(connection);
    plugin.save_auth_results(connection);

    var r = connection.results.get('connect.fcrdns');
    if (!r.fcrdns.length && plugin.cfg.reject.no_fcrdns) {
        return do_next(DENY, 'Sorry, no FCrDNS match found');
    }
    return do_next();
};

exports.ptr_compare = function (ip_list, connection, domain) {
    var plugin = this;
    if (!ip_list.length) return false;

    if (ip_list.indexOf(connection.remote_ip) !== -1) {
        connection.results.add(plugin, {pass: 'fcrdns' });
        connection.results.push(plugin, {fcrdns: domain});
        return true;
    }
    if (net_utils.same_ipv4_network(connection.remote_ip, ip_list)) {
        connection.results.add(plugin, {pass: 'fcrdns(net)' });
        connection.results.push(plugin, {fcrdns: domain});
        return true;
    }
    for (var j=0; j<ip_list.length; j++) {
        connection.results.push(plugin, {other_ips: ip_list[j]});
    }
    return false;
};

exports.save_auth_results = function (connection) {
    var r = connection.results.get('connect.fcrdns');
    if (!r) return;
    if (r.fcrdns && r.fcrdns.length) {
        connection.auth_results('iprev=pass');
        return true;
    }
    if (!r.has_rdns) {
        connection.auth_results('iprev=permerror');
        return false;
    }
    if (r.err.length) {
        connection.auth_results('iprev=temperror');
        return false;
    }
    connection.auth_results('iprev=fail');
    return false;
};

exports.is_generic_rdns = function (connection, domain) {
    var plugin = this;
    // IP in rDNS? (Generic rDNS)
    if (!net_utils.is_ip_in_str(connection.remote_ip, domain)) {
        connection.results.add(plugin, {pass: 'is_generic_rdns'});
        return false;
    }

    connection.results.add(plugin, {fail: 'is_generic_rdns'});

    var orgDom = net_utils.get_organizational_domain(domain);
    var host_part = domain.split('.').slice(0,orgDom.split('.').length+1);
    if (/(?:static|business)/.test(host_part)) {
        // Allow some obvious generic but static ranges
        // EHLO/HELO checks will still catch out hosts that use generic rDNS there
        connection.loginfo(this, 'allowing generic static rDNS');
        return false;
    }

    return true;
};

exports.log_summary = function (connection) {
    if (!connection) return;   // connection went away
    var note = connection.results.get('connect.fcrdns');
    if (!note) return;

    connection.loginfo(this,
        ['ip=' + connection.remote_ip,
        'rdns="' + ((note.ptr_names.length > 2) ? note.ptr_names.slice(0,2).join(',') + '...' : note.ptr_names.join(',')) + '"',
        'rdns_len=' + note.ptr_names.length,
        'fcrdns="' + ((note.fcrdns.length > 2) ? note.fcrdns.slice(0,2).join(',') + '...' : note.fcrdns.join(',')) + '"',
        'fcrdns_len=' + note.fcrdns.length,
        'other_ips_len=' + note.other_ips.length,
        'invalid_tlds=' + note.invalid_tlds.length,
        'generic_rdns=' + ((note.ptr_name_has_ips) ? 'true' : 'false'),
        ].join(' '));
};

exports.refresh_config = function (connection) {
    var plugin = this;
    plugin.cfg = plugin.config.get('connect.fcrdns.ini', {
        booleans: [
            'reject.-no_rdns',
            'reject.-no_fcrdns',
            'reject.-invalid_tld',
            'reject.-generic_rdns',
        ]
    });

    var defaults = {
        disconnect_timeout: 10,
    };

    // allow rdns_acccess whitelist to override
    if (connection.notes.rdns_access && connection.notes.rdns_access === 'white') {
        plugin.cfg.reject.no_rdns = 0;
        plugin.cfg.reject.invalid_tld = 0;
        plugin.cfg.reject.generic_rdns = 0;
    }

    return plugin.cfg;
};
<|endoftext|>


<commit_before>'use strict';

var fs = require('fs'),
    _ = require('lodash'),
    data;

var toTitleCase = function(str){
  return str.replace(/([^\W_]+[^\s-]*) */g, function(txt){return txt.charAt(0).toUpperCase() + txt.substr(1).toLowerCase();});
};

var getSubRegions = function (index, parentId, indentation, isLast) {
  if(data[index]){
    var subregions = _.filter(data[index].items, function(subregion) {
      return parentId === subregion.parentId;
    });
    isLast = false;
    subregions.forEach(function(region, listNumber){
      var thisIndentation = indentation + '├ ';
      if(subregions.length === listNumber + 1){
        thisIndentation = indentation + '└ ';
        isLast = true;
      }
      console.log(thisIndentation + toTitleCase(region.name)+' ('+region.id+')');
      var subIndentation;
      if(isLast){
        subIndentation = indentation + '  ';
      } else {
        subIndentation = indentation + '| ';
      }
      getSubRegions(index + 1, region.id, subIndentation, isLast);
    });
  }
};

var iterateThroughRegions = function(){
  if(data[0]){
    data[0].items.forEach(function(region, listNumber){
      console.log(listNumber+1+'. '+ toTitleCase(region.name));
      getSubRegions(1, region.id, '   ', false);
    });
  }
};

var getUnderline = function (string) {
  var underline = '';
  while(underline.length < string.length){
    underline += '-';
  }
  console.log(underline);
};

if(process.argv.length === 2){
  console.log('Please add a country parameter, i.e. $node readable.js guinea');
} else {
  var dataString = fs.readFileSync('./json/'+process.argv[2]+'.json').toString();
  data = JSON.parse(dataString);
  var title = 'Administrative regions of '+ process.argv[2];
  getUnderline(title);
  console.log(title);
  getUnderline(title);
  iterateThroughRegions();
}

<commit_msg>Further improvements to indentation, also title case for all regions<commit_after>'use strict';

var fs = require('fs'),
    _ = require('lodash'),
    data;

var toTitleCase = function(str){
  return str.replace(/([^\W_]+[^\s-]*) */g, function(txt){return txt.charAt(0).toUpperCase() + txt.substr(1).toLowerCase();});
};

var getSubRegions = function (index, parentId, indentation, isLast) {
  if(data[index]){
    var subregions = _.filter(data[index].items, function(subregion) {
      return parentId === subregion.parentId;
    });
    isLast = false;
    subregions.forEach(function(region, listNumber){
      var thisIndentation = indentation + '├ ';
      if(subregions.length === listNumber + 1){
        thisIndentation = indentation + '└ ';
        isLast = true;
      }
      console.log(thisIndentation + toTitleCase(region.name)+' ('+region.id+')');
      var subIndentation;
      if(isLast){
        subIndentation = indentation + '  ';
      } else {
        subIndentation = indentation + '| ';
      }
      getSubRegions(index + 1, region.id, subIndentation, isLast);
    });
  }
};

var iterateThroughRegions = function(){
  if(data[0]){
    data[0].items.forEach(function(region, listNumber){
      console.log(listNumber+1+'. '+ toTitleCase(region.name) + '('+region.id+')');
      getSubRegions(1, region.id, '   ', false);
    });
  }
};

var getUnderline = function (string) {
  var underline = '';
  while(underline.length < string.length){
    underline += '-';
  }
  console.log(underline);
};

if(process.argv.length === 2){
  console.log('Please add a country parameter, i.e. $node readable.js guinea');
} else {
  var dataString = fs.readFileSync('./json/'+process.argv[2]+'.json').toString();
  data = JSON.parse(dataString);
  var title = 'Administrative regions of '+ process.argv[2];
  getUnderline(title);
  console.log(title);
  getUnderline(title);
  iterateThroughRegions();
}

<|endoftext|>


<commit_before>var express  = require('express'),
    router   = express.Router();

var Board = require('../models/board');

router.route('/boards')

  // GET /boards
  .get(function (req, res) {
    res.json({message: "Fake GET index route!"});
  })

  // POST /boards
  .post(function (req, res) {
    Board.findOne({ fbId: req.fbUser }, function (err, board) {
      if (err) {
        res.json(err);
      } else if (board) {
        // 409: request could not be completed due to a conflict with
        // the current state of the resource
        // res.status(409).json({ message: "Board already exists." });
        console.log('409??: ', board);
        // res.json(board)
      } else {
        var board = new Board();
        board.fbId = req.fbUser;
        board.save(function (err) {
          if (err) {
            res.send(err);
          } else {
            res.json(board);
          }
        });
      }
    });
  });

router.route('/boards/:boardId')

  // GET /boards/12345
  .get(function(req, res) {
    Board.findById(req.params.boardId, function (err, board) {
      if (err)
        res.send(err);

      res.json(board);
    });
  })

  // PUT /boards/12345
  .put(function (req, res) {
    Board.findById(req.params.boardId, function (err, board) {
      if (err)
        res.send(err)

      for (prop in req.body) {
        board[prop] = req.body[prop];
      }

      board.save(function (err) {
        if (err)
          res.send(err);

        res.json(board);
      });
    });
  });

module.exports = router;
<commit_msg>Return to original state<commit_after>var express  = require('express'),
    router   = express.Router();

var Board = require('../models/board');

router.route('/boards')

  // GET /boards
  .get(function (req, res) {
    res.json({message: "Fake GET index route!"});
  })

  // POST /boards
  .post(function (req, res) {
    Board.findOne({ fbId: req.fbUser }, function (err, board) {
      if (err) {
        res.json(err);
      } else if (board) {
        // 409: request could not be completed due to a conflict with
        // the current state of the resource
         res.status(409).json({ message: "Board already exists." });
      } else {
        var board = new Board();
        board.fbId = req.fbUser;
        board.save(function (err) {
          if (err) {
            res.send(err);
          } else {
            res.json(board);
          }
        });
      }
    });
  });

router.route('/boards/:boardId')

  // GET /boards/12345
  .get(function(req, res) {
    Board.findById(req.params.boardId, function (err, board) {
      if (err)
        res.send(err);

      res.json(board);
    });
  })

  // PUT /boards/12345
  .put(function (req, res) {
    Board.findById(req.params.boardId, function (err, board) {
      if (err)
        res.send(err)

      for (prop in req.body) {
        board[prop] = req.body[prop];
      }

      board.save(function (err) {
        if (err)
          res.send(err);

        res.json(board);
      });
    });
  });

module.exports = router;
<|endoftext|>