<commit_before>
/**
 * Module dependencies.
 */

var mongoose = require('mongoose');
var Imager = require('imager');
var config = require('../../config/config');

var imagerConfig = require(config.root + '/config/imager.js');
var utils = require('../../lib/utils');

var Schema = mongoose.Schema;

/**
 * Getters
 */

var getTags = function (tags) {
  return tags.join(',');
};

/**
 * Setters
 */

var setTags = function (tags) {
  return tags.split(',');
};

/**
 * Article Schema
 */

var ArticleSchema = new Schema({
  title: {type : String, default : '', trim : true},
  body: {type : String, default : '', trim : true},
  user: {type : Schema.ObjectId, ref : 'User'},
  comments: [{
    body: { type : String, default : '' },
    user: { type : Schema.ObjectId, ref : 'User' },
    createdAt: { type : Date, default : Date.now }
  }],
  tags: {type: [], get: getTags, set: setTags},
  image: {
    cdnUri: String,
    files: []
  },
  createdAt  : {type : Date, default : Date.now}
});

/**
 * Validations
 */

ArticleSchema.path('title').required(true, 'Article title cannot be blank');
ArticleSchema.path('body').required(true, 'Article body cannot be blank');

/**
 * Pre-remove hook
 */

ArticleSchema.pre('remove', function (next) {
  var imager = new Imager(imagerConfig, 'S3');
  var files = this.image.files;

  // if there are files associated with the item, remove from the cloud too
  imager.remove(files, function (err) {
    if (err) return next(err);
  }, 'article');

  next();
});

/**
 * Methods
 */

ArticleSchema.methods = {

  /**
   * Save article and upload image
   *
   * @param {Object} images
   * @param {Function} cb
   * @api private
   */

  uploadAndSave: function (images, cb) {
    if (!images || !images.length) return this.save(cb)

    var imager = new Imager(imagerConfig, 'S3');
    var self = this;

    this.validate(function (err) {
      if (err) return cb(err);
      imager.upload(images, function (err, cdnUri, files) {
        if (err) return cb(err);
        if (files.length) {
          self.image = { cdnUri : cdnUri, files : files };
        }
        self.save(cb);
      }, 'article');
    });
  },

  /**
   * Add comment
   *
   * @param {User} user
   * @param {Object} comment
   * @param {Function} cb
   * @api private
   */

  addComment: function (user, comment, cb) {
    var notify = require('../mailer');

    this.comments.push({
      body: comment.body,
      user: user._id
    });

    if (!this.user.email) this.user.email = 'email@product.com';
    notify.comment({
      article: this,
      currentUser: user,
      comment: comment.body
    });

    this.save(cb);
  },

  /**
   * Remove comment
   *
   * @param {commentId} String
   * @param {Function} cb
   * @api private
   */

  removeComment: function (commentId, cb) {
    var index = utils.indexof(this.comments, { id: commentId });
    if (~index) this.comments.splice(index, 1);
    else return cb('not found');
    this.save(cb);
  }
}

/**
 * Statics
 */

//ArticleSchema.statics = {
//
//  /**
//   * Find article by id
//   *
//   * @param {ObjectId} id
//   * @param {Function} cb
//   * @api private
//   */
//
//  load: function (id, cb) {
//    this.findOne({ _id : id })
//      .populate('user', 'name email username')
//      .populate('comments.user')
//      .exec(cb);
//  },
//
//  /**
//   * List articles
//   *
//   * @param {Object} options
//   * @param {Function} cb
//   * @api private
//   */
//
//  list: function (options, cb) {
//    var criteria = options.criteria || {}
//
//    this.find(criteria)
//      .populate('user', 'name username')
//      .sort({'createdAt': -1}) // sort by date
//      .limit(options.perPage)
//      .skip(options.perPage * options.page)
//      .exec(cb);
//  }
//}

mongoose.model('Article', ArticleSchema);
<commit_msg>remove article stats<commit_after>
/**
 * Module dependencies.
 */

var mongoose = require('mongoose');
var Imager = require('imager');
var config = require('../../config/config');

var imagerConfig = require(config.root + '/config/imager.js');
var utils = require('../../lib/utils');

var Schema = mongoose.Schema;

/**
 * Getters
 */

var getTags = function (tags) {
  return tags.join(',');
};

/**
 * Setters
 */

var setTags = function (tags) {
  return tags.split(',');
};

/**
 * Article Schema
 */

var ArticleSchema = new Schema({
  title: {type : String, default : '', trim : true},
  body: {type : String, default : '', trim : true},
  user: {type : Schema.ObjectId, ref : 'User'},
  comments: [{
    body: { type : String, default : '' },
    user: { type : Schema.ObjectId, ref : 'User' },
    createdAt: { type : Date, default : Date.now }
  }],
  tags: {type: [], get: getTags, set: setTags},
  image: {
    cdnUri: String,
    files: []
  },
  createdAt  : {type : Date, default : Date.now}
});

/**
 * Validations
 */

ArticleSchema.path('title').required(true, 'Article title cannot be blank');
ArticleSchema.path('body').required(true, 'Article body cannot be blank');

/**
 * Pre-remove hook
 */

ArticleSchema.pre('remove', function (next) {
  var imager = new Imager(imagerConfig, 'S3');
  var files = this.image.files;

  // if there are files associated with the item, remove from the cloud too
  imager.remove(files, function (err) {
    if (err) return next(err);
  }, 'article');

  next();
});

/**
 * Methods
 */

ArticleSchema.methods = {

  /**
   * Save article and upload image
   *
   * @param {Object} images
   * @param {Function} cb
   * @api private
   */

  uploadAndSave: function (images, cb) {
    if (!images || !images.length) return this.save(cb)

    var imager = new Imager(imagerConfig, 'S3');
    var self = this;

    this.validate(function (err) {
      if (err) return cb(err);
      imager.upload(images, function (err, cdnUri, files) {
        if (err) return cb(err);
        if (files.length) {
          self.image = { cdnUri : cdnUri, files : files };
        }
        self.save(cb);
      }, 'article');
    });
  },

  /**
   * Add comment
   *
   * @param {User} user
   * @param {Object} comment
   * @param {Function} cb
   * @api private
   */

  addComment: function (user, comment, cb) {
    var notify = require('../mailer');

    this.comments.push({
      body: comment.body,
      user: user._id
    });

    if (!this.user.email) this.user.email = 'email@product.com';
    notify.comment({
      article: this,
      currentUser: user,
      comment: comment.body
    });

    this.save(cb);
  },

  /**
   * Remove comment
   *
   * @param {commentId} String
   * @param {Function} cb
   * @api private
   */

  removeComment: function (commentId, cb) {
    var index = utils.indexof(this.comments, { id: commentId });
    if (~index) this.comments.splice(index, 1);
    else return cb('not found');
    this.save(cb);
  }
}

/**
 * Statics
 */

ArticleSchema.statics = {

  /**
   * Find article by id
   *
   * @param {ObjectId} id
   * @param {Function} cb
   * @api private
   */

  load: function (id, cb) {
    this.findOne({ _id : id })
      .populate('user', 'name email username')
      .populate('comments.user')
      .exec(cb);
  },

  /**
   * List articles
   *
   * @param {Object} options
   * @param {Function} cb
   * @api private
   */

  list: function (options, cb) {
//    var criteria = options.criteria || {}
//
//    this.find(criteria)
//      .populate('user', 'name username')
//      .sort({'createdAt': -1}) // sort by date
//      .limit(options.perPage)
//      .skip(options.perPage * options.page)
//      .exec(cb);
  }
}

mongoose.model('Article', ArticleSchema);
<|endoftext|>


<commit_before>import store from './storage';
import range from 'lodash/range';

function localstorageprop(key, initialValue) {
  return function() {
    if (arguments.length) store.set(key, arguments[0]);
    var ret = store.get(key);
    return (ret !== null) ? ret : initialValue;
  };
}

function tupleOf(x) {
  return [x.toString(), x.toString()];
}

export default {
  general: {
    lang: localstorageprop('settings.lang'),
    sound: localstorageprop('settings.sound', true),
    theme: {
      background: localstorageprop('settings.bgTheme', 'dark'),
      availableBoardThemes: [
        ['boardThemeBrown', 'brown'],
        ['boardThemeBlue', 'blue'],
        ['boardThemeGreen', 'green'],
        ['boardThemeGrey', 'grey'],
        ['boardThemePurple', 'purple'],
        ['boardThemeWood', 'wood'],
        ['boardThemeWood2', 'wood2'],
        ['boardThemeWood3', 'wood3'],
        ['boardThemeMaple', 'maple'],
        ['boardThemeBlue2', 'blue3'],
        ['boardThemeCanvas', 'canvas'],
        ['boardThemeMetal', 'metal']
      ],
      availablePieceThemes: [
        'cburnett',
        'merida',
        'pirouetti',
        'alpha',
        'spatial',
        'reilly',
        'fantasy',
        'shapes',
        'chessnut',
        'chess7',
        'symmetric'
      ],
      board: localstorageprop('settings.theme.board', 'brown'),
      piece: localstorageprop('settings.theme.piece', 'cburnett')
    },
    analytics: localstorageprop('settings.analytics', true),
    vibrateOnGameEvents: localstorageprop('settings.vibrateOnGameEvents', false),
    notifications: {
      allow: localstorageprop('settings.notifications', true),
      vibrate: localstorageprop('settings.notifications.vibrate', true),
      sound: localstorageprop('settings.notifications.sound', true)
    }
  },

  game: {
    supportedVariants: ['standard', 'chess960', 'antichess', 'fromPosition',
      'kingOfTheHill', 'threeCheck', 'atomic', 'horde', 'racingKings', 'crazyhouse'
    ],
    animations: localstorageprop('settings.gameAnimations', true),
    highlights: localstorageprop('settings.boardHighlights', true),
    pieceDestinations: localstorageprop('settings.pieceDestinations', true),
    coords: localstorageprop('settings.coords', true),
    magnified: localstorageprop('settings.pieceMagnified', true)
  },

  analyse: {
    supportedVariants: ['standard', 'chess960', 'antichess', 'fromPosition',
      'kingOfTheHill', 'threeCheck', 'atomic', 'horde', 'racingKings', 'crazyhouse'
    ],
    enableCeval: localstorageprop('settings.analyse.enableCeval', false),
    showBestMove: localstorageprop('settings.analyse.showBestMove', true),
    showComments: localstorageprop('settings.analyse.showComments', true),
    explorer: {
      db: localstorageprop('settings.analyse.explorer.db', 'lichess'),
      availableRatings: [1600, 1800, 2000, 2200, 2500],
      rating: localstorageprop('settings.analyse.explorer.rating', [1600, 1800, 2000, 2200, 2500]),
      availableSpeeds: ['bullet', 'blitz', 'classical'],
      speed: localstorageprop('settings.analyse.explorer.speed', ['bullet', 'blitz', 'classical'])
    }
  },

  ai: {
    availableOpponents: [
      ['Stockfish', '1'],
      ['Stockfish', '2'],
      ['Stockfish', '3'],
      ['Stockfish', '4'],
      ['Stockfish', '5'],
      ['Stockfish', '6'],
      ['Stockfish', '7'],
      ['Stockfish', '8']
    ],
    color: localstorageprop('settings.ai.color', 'white'),
    opponent: localstorageprop('settings.ai.opponent', '1'),
    variant: localstorageprop('settings.ai.variant', 'standard'),
    availableVariants: [
      ['Standard', 'standard'],
      ['Chess960', 'chess960'],
      ['King of the Hill', 'kingOfTheHill'],
      ['Three-check', 'threeCheck']
    ]
  },

  otb: {
    flipPieces: localstorageprop('settings.otb.flipPieces', false),
    useSymmetric: localstorageprop('settings.otb.useSymmetric', true),
    variant: localstorageprop('settings.otb.variant', 'standard'),
    availableVariants: [
      ['Standard', 'standard'],
      ['Crazyhouse', 'crazyhouse'],
      ['Chess960', 'chess960'],
      ['King of the Hill', 'kingOfTheHill'],
      ['Three-check', 'threeCheck'],
      ['Antichess', 'antichess'],
      ['Atomic', 'atomic'],
      ['Horde', 'horde'],
      ['Racing Kings', 'racingKings']
    ]
  },

  clock: {
    availableClocks: [
      ['Sudden Death', 'simple'],
      ['Increment', 'increment'],
      ['Simple Delay', 'delay'],
      ['Bronstein Delay', 'bronstein'],
      ['Hourglass', 'hourglass'],
      ['Stage', 'stage']
    ],

    clockType: localstorageprop('settings.clock.clockType', 'simple'),

    simple: {
      time: localstorageprop('settings.clock.simple.time', '5')
    },

    increment: {
      time: localstorageprop('settings.clock.increment.time', '3'),
      increment: localstorageprop('settings.clock.increment.increment', '2')
    },

    delay: {
      time: localstorageprop('settings.clock.delay.time', '3'),
      increment: localstorageprop('settings.clock.delay.increment', '2')
    },

    bronstein: {
      time: localstorageprop('settings.clock.bronstein.time', '3'),
      increment: localstorageprop('settings.clock.bronstein.increment', '2')
    },

    hourglass: {
      time: localstorageprop('settings.clock.hourglass.time', '5')
    },

    stage: {
      stages: localstorageprop('settings.clock.stage.stages', [{time: '120', moves: '40'}, {time: '60', moves: null}]),
      increment: localstorageprop('settings.clock.stage.increment', '30')
    },

    availableTimes: [['0', '0'], ['½', '0.5'], ['¾', '0.75'], ['1', '1'], ['2', '2'], ['3', '3'], ['4', '4'], ['5', '5'], ['6', '6'], ['7', '7'], ['8', '8'], ['9', '9'], ['10', '10'], ['15', '15'], ['20', '20'], ['25', '25'], ['30', '30'], ['45', '45'], ['60', '60'], ['90', '90'], ['120', '120'], ['150', '150'], ['180', '180']
    ],

    availableIncrements: ['0', '1', '2', '3', '4', '5', '6', '7', '8', '9',
      '10', '15', '20', '25', '30', '45', '60', '90', '120', '150', '180'
    ],

    availableMoves: ['5', '10', '15', '20', '25', '30', '35', '40', '45', '50', '55', '60'
    ]
  },

  gameSetup: {
    availableTimes: [['0', '0'], ['½', '0.5'], ['¾', '0.75'], ['1', '1'], ['2', '2'], ['3', '3'], ['4', '4'], ['5', '5'], ['6', '6'], ['7', '7'], ['8', '8'], ['9', '9'], ['10', '10'], ['15', '15'], ['20', '20'], ['25', '25'], ['30', '30'], ['45', '45'], ['60', '60'], ['90', '90'], ['120', '120'], ['150', '150'], ['180', '180']
    ],
    availableIncrements: ['0', '1', '2', '3', '4', '5', '6', '7', '8', '9',
      '10', '15', '20', '25', '30', '45', '60', '90', '120', '150', '180'
    ],
    availableDays: ['1', '2', '3', '5', '7', '10', '14'],
    isTimeValid: function(gameSettings) {
      return gameSettings.timeMode() !== '1' ||
        gameSettings.time() !== '0' ||
        gameSettings.increment() !== '0';
    },

    ai: {
      color: localstorageprop('settings.game.ai.color', 'random'),
      availableVariants: [
        ['Standard', '1'],
        ['Crazyhouse', '10'],
        ['Chess960', '2'],
        ['King of the Hill', '4'],
        ['Three-check', '5'],
        ['Atomic', '7'],
        ['Horde', '8'],
        ['Racing Kings', '9'],
        ['From Position', '3']
      ],
      variant: localstorageprop('settings.game.ai.variant', '1'),
      availableTimeModes: [
        ['unlimited', '0'],
        ['realTime', '1']
      ],
      timeMode: localstorageprop('settings.game.ai.clock', '1'),
      time: localstorageprop('settings.game.ai.time', '10'),
      increment: localstorageprop('settings.game.ai.increment', '0'),
      days: localstorageprop('settings.game.ai.days', '2'),
      level: localstorageprop('settings.game.ai.aiLevel', '3')
    },

    human: {
      availableVariants: [
        ['Standard', '1'],
        ['Crazyhouse', '10'],
        ['Chess960', '2'],
        ['King of the Hill', '4'],
        ['Three-check', '5'],
        ['Antichess', '6'],
        ['Atomic', '7'],
        ['Horde', '8'],
        ['Racing Kings', '9']
      ],
      availableRatingRanges: {
        min: range(800, 2900, 100).map(tupleOf),
        max: range(900, 3000, 100).map(tupleOf)
      },
      ratingMin: localstorageprop('settings.game.human.rating.min', '800'),
      ratingMax: localstorageprop('settings.game.human.rating.max', '2900'),
      color: localstorageprop('settings.game.human.color', 'random'),
      variant: localstorageprop('settings.game.human.variant', '1'),
      availableTimeModes: [
        ['realTime', '1'],
        ['correspondence', '2'],
        ['unlimited', '0']
      ],
      timeMode: localstorageprop('settings.game.human.clock', '1'),
      time: localstorageprop('settings.game.human.time', '5'),
      increment: localstorageprop('settings.game.human.increment', '0'),
      days: localstorageprop('settings.game.human.days', '2'),
      mode: localstorageprop('settings.game.human.mode', '0'),
      membersOnly: localstorageprop('settings.game.human.membersOnly', false)
    },

    challenge: {
      availableVariants: [
        ['Standard', '1'],
        ['Crazyhouse', '10'],
        ['Chess960', '2'],
        ['King of the Hill', '4'],
        ['Three-check', '5'],
        ['Antichess', '6'],
        ['Atomic', '7'],
        ['Horde', '8'],
        ['Racing Kings', '9'],
        ['From Position', '3']
      ],
      color: localstorageprop('settings.game.invite.color', 'random'),
      variant: localstorageprop('settings.game.invite.variant', '1'),
      availableTimeModes: [
        ['realTime', '1'],
        ['correspondence', '2'],
        ['unlimited', '0']
      ],
      timeMode: localstorageprop('settings.game.invite.clock', '1'),
      time: localstorageprop('settings.game.invite.time', '5'),
      increment: localstorageprop('settings.game.invite.increment', '0'),
      days: localstorageprop('settings.game.invite.days', '2'),
      mode: localstorageprop('settings.game.invite.mode', '0')
    }
  },

  tv: {
    channel: localstorageprop('settings.tv.channel', 'best')
  }
};
<commit_msg>Add 3 more variants ai offline<commit_after>import store from './storage';
import range from 'lodash/range';

function localstorageprop(key, initialValue) {
  return function() {
    if (arguments.length) store.set(key, arguments[0]);
    var ret = store.get(key);
    return (ret !== null) ? ret : initialValue;
  };
}

function tupleOf(x) {
  return [x.toString(), x.toString()];
}

export default {
  general: {
    lang: localstorageprop('settings.lang'),
    sound: localstorageprop('settings.sound', true),
    theme: {
      background: localstorageprop('settings.bgTheme', 'dark'),
      availableBoardThemes: [
        ['boardThemeBrown', 'brown'],
        ['boardThemeBlue', 'blue'],
        ['boardThemeGreen', 'green'],
        ['boardThemeGrey', 'grey'],
        ['boardThemePurple', 'purple'],
        ['boardThemeWood', 'wood'],
        ['boardThemeWood2', 'wood2'],
        ['boardThemeWood3', 'wood3'],
        ['boardThemeMaple', 'maple'],
        ['boardThemeBlue2', 'blue3'],
        ['boardThemeCanvas', 'canvas'],
        ['boardThemeMetal', 'metal']
      ],
      availablePieceThemes: [
        'cburnett',
        'merida',
        'pirouetti',
        'alpha',
        'spatial',
        'reilly',
        'fantasy',
        'shapes',
        'chessnut',
        'chess7',
        'symmetric'
      ],
      board: localstorageprop('settings.theme.board', 'brown'),
      piece: localstorageprop('settings.theme.piece', 'cburnett')
    },
    analytics: localstorageprop('settings.analytics', true),
    vibrateOnGameEvents: localstorageprop('settings.vibrateOnGameEvents', false),
    notifications: {
      allow: localstorageprop('settings.notifications', true),
      vibrate: localstorageprop('settings.notifications.vibrate', true),
      sound: localstorageprop('settings.notifications.sound', true)
    }
  },

  game: {
    supportedVariants: ['standard', 'chess960', 'antichess', 'fromPosition',
      'kingOfTheHill', 'threeCheck', 'atomic', 'horde', 'racingKings', 'crazyhouse'
    ],
    animations: localstorageprop('settings.gameAnimations', true),
    highlights: localstorageprop('settings.boardHighlights', true),
    pieceDestinations: localstorageprop('settings.pieceDestinations', true),
    coords: localstorageprop('settings.coords', true),
    magnified: localstorageprop('settings.pieceMagnified', true)
  },

  analyse: {
    supportedVariants: ['standard', 'chess960', 'antichess', 'fromPosition',
      'kingOfTheHill', 'threeCheck', 'atomic', 'horde', 'racingKings', 'crazyhouse'
    ],
    enableCeval: localstorageprop('settings.analyse.enableCeval', false),
    showBestMove: localstorageprop('settings.analyse.showBestMove', true),
    showComments: localstorageprop('settings.analyse.showComments', true),
    explorer: {
      db: localstorageprop('settings.analyse.explorer.db', 'lichess'),
      availableRatings: [1600, 1800, 2000, 2200, 2500],
      rating: localstorageprop('settings.analyse.explorer.rating', [1600, 1800, 2000, 2200, 2500]),
      availableSpeeds: ['bullet', 'blitz', 'classical'],
      speed: localstorageprop('settings.analyse.explorer.speed', ['bullet', 'blitz', 'classical'])
    }
  },

  ai: {
    availableOpponents: [
      ['Stockfish', '1'],
      ['Stockfish', '2'],
      ['Stockfish', '3'],
      ['Stockfish', '4'],
      ['Stockfish', '5'],
      ['Stockfish', '6'],
      ['Stockfish', '7'],
      ['Stockfish', '8']
    ],
    color: localstorageprop('settings.ai.color', 'white'),
    opponent: localstorageprop('settings.ai.opponent', '1'),
    variant: localstorageprop('settings.ai.variant', 'standard'),
    availableVariants: [
      ['Standard', 'standard'],
      ['Chess960', 'chess960'],
      ['King of the Hill', 'kingOfTheHill'],
      ['Three-check', 'threeCheck'],
      ['Atomic', 'atomic'],
      ['Horde', 'horde'],
      ['Racing Kings', 'racingKings']
    ]
  },

  otb: {
    flipPieces: localstorageprop('settings.otb.flipPieces', false),
    useSymmetric: localstorageprop('settings.otb.useSymmetric', true),
    variant: localstorageprop('settings.otb.variant', 'standard'),
    availableVariants: [
      ['Standard', 'standard'],
      ['Crazyhouse', 'crazyhouse'],
      ['Chess960', 'chess960'],
      ['King of the Hill', 'kingOfTheHill'],
      ['Three-check', 'threeCheck'],
      ['Antichess', 'antichess'],
      ['Atomic', 'atomic'],
      ['Horde', 'horde'],
      ['Racing Kings', 'racingKings']
    ]
  },

  clock: {
    availableClocks: [
      ['Sudden Death', 'simple'],
      ['Increment', 'increment'],
      ['Simple Delay', 'delay'],
      ['Bronstein Delay', 'bronstein'],
      ['Hourglass', 'hourglass'],
      ['Stage', 'stage']
    ],

    clockType: localstorageprop('settings.clock.clockType', 'simple'),

    simple: {
      time: localstorageprop('settings.clock.simple.time', '5')
    },

    increment: {
      time: localstorageprop('settings.clock.increment.time', '3'),
      increment: localstorageprop('settings.clock.increment.increment', '2')
    },

    delay: {
      time: localstorageprop('settings.clock.delay.time', '3'),
      increment: localstorageprop('settings.clock.delay.increment', '2')
    },

    bronstein: {
      time: localstorageprop('settings.clock.bronstein.time', '3'),
      increment: localstorageprop('settings.clock.bronstein.increment', '2')
    },

    hourglass: {
      time: localstorageprop('settings.clock.hourglass.time', '5')
    },

    stage: {
      stages: localstorageprop('settings.clock.stage.stages', [{time: '120', moves: '40'}, {time: '60', moves: null}]),
      increment: localstorageprop('settings.clock.stage.increment', '30')
    },

    availableTimes: [['0', '0'], ['½', '0.5'], ['¾', '0.75'], ['1', '1'], ['2', '2'], ['3', '3'], ['4', '4'], ['5', '5'], ['6', '6'], ['7', '7'], ['8', '8'], ['9', '9'], ['10', '10'], ['15', '15'], ['20', '20'], ['25', '25'], ['30', '30'], ['45', '45'], ['60', '60'], ['90', '90'], ['120', '120'], ['150', '150'], ['180', '180']
    ],

    availableIncrements: ['0', '1', '2', '3', '4', '5', '6', '7', '8', '9',
      '10', '15', '20', '25', '30', '45', '60', '90', '120', '150', '180'
    ],

    availableMoves: ['5', '10', '15', '20', '25', '30', '35', '40', '45', '50', '55', '60'
    ]
  },

  gameSetup: {
    availableTimes: [['0', '0'], ['½', '0.5'], ['¾', '0.75'], ['1', '1'], ['2', '2'], ['3', '3'], ['4', '4'], ['5', '5'], ['6', '6'], ['7', '7'], ['8', '8'], ['9', '9'], ['10', '10'], ['15', '15'], ['20', '20'], ['25', '25'], ['30', '30'], ['45', '45'], ['60', '60'], ['90', '90'], ['120', '120'], ['150', '150'], ['180', '180']
    ],
    availableIncrements: ['0', '1', '2', '3', '4', '5', '6', '7', '8', '9',
      '10', '15', '20', '25', '30', '45', '60', '90', '120', '150', '180'
    ],
    availableDays: ['1', '2', '3', '5', '7', '10', '14'],
    isTimeValid: function(gameSettings) {
      return gameSettings.timeMode() !== '1' ||
        gameSettings.time() !== '0' ||
        gameSettings.increment() !== '0';
    },

    ai: {
      color: localstorageprop('settings.game.ai.color', 'random'),
      availableVariants: [
        ['Standard', '1'],
        ['Crazyhouse', '10'],
        ['Chess960', '2'],
        ['King of the Hill', '4'],
        ['Three-check', '5'],
        ['Atomic', '7'],
        ['Horde', '8'],
        ['Racing Kings', '9'],
        ['From Position', '3']
      ],
      variant: localstorageprop('settings.game.ai.variant', '1'),
      availableTimeModes: [
        ['unlimited', '0'],
        ['realTime', '1']
      ],
      timeMode: localstorageprop('settings.game.ai.clock', '1'),
      time: localstorageprop('settings.game.ai.time', '10'),
      increment: localstorageprop('settings.game.ai.increment', '0'),
      days: localstorageprop('settings.game.ai.days', '2'),
      level: localstorageprop('settings.game.ai.aiLevel', '3')
    },

    human: {
      availableVariants: [
        ['Standard', '1'],
        ['Crazyhouse', '10'],
        ['Chess960', '2'],
        ['King of the Hill', '4'],
        ['Three-check', '5'],
        ['Antichess', '6'],
        ['Atomic', '7'],
        ['Horde', '8'],
        ['Racing Kings', '9']
      ],
      availableRatingRanges: {
        min: range(800, 2900, 100).map(tupleOf),
        max: range(900, 3000, 100).map(tupleOf)
      },
      ratingMin: localstorageprop('settings.game.human.rating.min', '800'),
      ratingMax: localstorageprop('settings.game.human.rating.max', '2900'),
      color: localstorageprop('settings.game.human.color', 'random'),
      variant: localstorageprop('settings.game.human.variant', '1'),
      availableTimeModes: [
        ['realTime', '1'],
        ['correspondence', '2'],
        ['unlimited', '0']
      ],
      timeMode: localstorageprop('settings.game.human.clock', '1'),
      time: localstorageprop('settings.game.human.time', '5'),
      increment: localstorageprop('settings.game.human.increment', '0'),
      days: localstorageprop('settings.game.human.days', '2'),
      mode: localstorageprop('settings.game.human.mode', '0'),
      membersOnly: localstorageprop('settings.game.human.membersOnly', false)
    },

    challenge: {
      availableVariants: [
        ['Standard', '1'],
        ['Crazyhouse', '10'],
        ['Chess960', '2'],
        ['King of the Hill', '4'],
        ['Three-check', '5'],
        ['Antichess', '6'],
        ['Atomic', '7'],
        ['Horde', '8'],
        ['Racing Kings', '9'],
        ['From Position', '3']
      ],
      color: localstorageprop('settings.game.invite.color', 'random'),
      variant: localstorageprop('settings.game.invite.variant', '1'),
      availableTimeModes: [
        ['realTime', '1'],
        ['correspondence', '2'],
        ['unlimited', '0']
      ],
      timeMode: localstorageprop('settings.game.invite.clock', '1'),
      time: localstorageprop('settings.game.invite.time', '5'),
      increment: localstorageprop('settings.game.invite.increment', '0'),
      days: localstorageprop('settings.game.invite.days', '2'),
      mode: localstorageprop('settings.game.invite.mode', '0')
    }
  },

  tv: {
    channel: localstorageprop('settings.tv.channel', 'best')
  }
};
<|endoftext|>


<commit_before>/* globals state, createObj, findObjs, getObj, getAttrByName, sendChat, on, log, Campaign, playerIsGM, spawnFx,
 spawnFxBetweenPoints, filterObjs, randomInteger, setDefaultTokenForCharacter */
'use strict';
const _ = require('underscore');

// noinspection JSUnusedLocalSymbols
module.exports = class Roll20Wrapper {

  getState(module) {
    if (!state[module]) {
      state[module] = {};
    }
    return state[module];
  }

  createObj(type, attributes) {
    return createObj(type, attributes);
  }

  findObjs(attributes) {
    return findObjs(attributes);
  }

  filterObjs(attributes) {
    return filterObjs(attributes);
  }

  getObj(type, id) {
    return getObj(type, id);
  }

  getOrCreateObj(type, attributes) {
    const newAttributes = _.extend(_.clone(attributes), { type });
    const existing = this.findObjs(newAttributes);
    switch (existing.length) {
      case 0:
        return this.createObj(type, newAttributes);
      case 1:
        return existing[0];
      default:
        throw new Error(`Asked for a single ${type} but more than 1 was found matching attributes: ` +
          `${JSON.stringify(attributes)}`);
    }
  }

  getAttrByName(character, attrName, valueType) {
    return getAttrByName(character, attrName, valueType);
  }

  checkCharacterFlag(character, flag) {
    const value = this.getAttrByName(character, flag);
    switch (typeof value) {
      case 'boolean':
        return value;
      case 'number':
        return !!value;
      default:
        return value === '1' || value === 'on';
    }
  }

  getAttrObjectByName(character, attrName) {
    const attr = this.findObjs({ type: 'attribute', characterid: character, name: attrName });
    return attr && attr.length > 0 ? attr[0] : null;
  }

  getOrCreateAttr(characterId, attrName) {
    return this.getOrCreateObj('attribute', { characterid: characterId, name: attrName });
  }

  setAttrByName(characterId, attrName, value) {
    this.getOrCreateAttr(characterId, attrName).set('current', value);
  }

  processAttrValue(characterId, attrName, cb) {
    const attribute = this.getOrCreateAttr(characterId, attrName);
    const newVal = cb(attribute.get('current'));
    attribute.set('current', newVal);
    return newVal;
  }

  getRepeatingSectionAttrs(characterId, sectionName) {
    const prefix = `repeating_${sectionName}`;
    return _.filter(this.findObjs({ type: 'attribute', characterid: characterId }),
      attr => attr.get('name').indexOf(prefix) === 0);
  }

  getRepeatingSectionItemIdsByName(characterId, sectionName) {
    const re = new RegExp(`repeating_${sectionName}_([^_]+)_name$`);
    return _.reduce(this.getRepeatingSectionAttrs(characterId, sectionName),
      (lookup, attr) => {
        const match = attr.get('name').match(re);
        if (match) {
          lookup[attr.get('current').toLowerCase()] = match[1];
        }
        return lookup;
      }, {});
  }

  getCurrentPage(playerId) {
    let pageId;
    if (this.playerIsGM(playerId)) {
      pageId = this.getObj('player', playerId).get('lastpage');
    }
    else {
      pageId = this.getCampaign().get('playerspecificpages')[playerId] || this.getCampaign().get('playerpageid');
    }

    return pageId ? this.getObj('page', pageId) : null;
  }

  spawnFx(pointsArray, fxType, pageId) {
    switch (pointsArray.length) {
      case 1:
        spawnFx(pointsArray[0].x, pointsArray[0].y, fxType, pageId);
        break;
      case 2:
        spawnFxBetweenPoints(pointsArray[0], pointsArray[1], fxType, pageId);
        break;
      default:
        throw new Error('Wrong number of points supplied to spawnFx: $$$', pointsArray);
    }
  }

  playerIsGM(playerId) {
    return playerIsGM(playerId);
  }

  getCampaign() {
    return Campaign(); // eslint-disable-line
  }

  sendChat(sendAs, message, callback, options) {
    return sendChat(sendAs, message, callback, options);
  }

  on(event, callback) {
    return on(event, callback);
  }

  randomInteger(max) {
    return randomInteger(max);
  }

  log(msg) {
    return log(msg);
  }

  setDefaultTokenForCharacter(character, token) {
    return setDefaultTokenForCharacter(character, token);
  }
};

<commit_msg>Add setWithWorker method<commit_after>/* globals state, createObj, findObjs, getObj, getAttrByName, sendChat, on, log, Campaign, playerIsGM, spawnFx,
 spawnFxBetweenPoints, filterObjs, randomInteger, setDefaultTokenForCharacter, onSheetWorkerCompleted */
'use strict';
const _ = require('underscore');

// noinspection JSUnusedLocalSymbols
module.exports = class Roll20Wrapper {

  getState(module) {
    if (!state[module]) {
      state[module] = {};
    }
    return state[module];
  }

  createObj(type, attributes) {
    return createObj(type, attributes);
  }

  findObjs(attributes) {
    return findObjs(attributes);
  }

  filterObjs(attributes) {
    return filterObjs(attributes);
  }

  getObj(type, id) {
    return getObj(type, id);
  }

  getOrCreateObj(type, attributes) {
    const newAttributes = _.extend(_.clone(attributes), { type });
    const existing = this.findObjs(newAttributes);
    switch (existing.length) {
      case 0:
        return this.createObj(type, newAttributes);
      case 1:
        return existing[0];
      default:
        throw new Error(`Asked for a single ${type} but more than 1 was found matching attributes: ` +
            `${JSON.stringify(attributes)}`);
    }
  }

  getAttrByName(character, attrName, valueType) {
    return getAttrByName(character, attrName, valueType);
  }

  checkCharacterFlag(character, flag) {
    const value = this.getAttrByName(character, flag);
    switch (typeof value) {
      case 'boolean':
        return value;
      case 'number':
        return !!value;
      default:
        return value === '1' || value === 'on';
    }
  }

  getAttrObjectByName(character, attrName) {
    const attr = this.findObjs({ type: 'attribute', characterid: character, name: attrName });
    return attr && attr.length > 0 ? attr[0] : null;
  }

  getOrCreateAttr(characterId, attrName) {
    return this.getOrCreateObj('attribute', { characterid: characterId, name: attrName });
  }

  setAttrByName(characterId, attrName, value) {
    this.getOrCreateAttr(characterId, attrName).set('current', value);
  }

  processAttrValue(characterId, attrName, cb) {
    const attribute = this.getOrCreateAttr(characterId, attrName);
    const newVal = cb(attribute.get('current'));
    attribute.set('current', newVal);
    return newVal;
  }

  getRepeatingSectionAttrs(characterId, sectionName) {
    const prefix = `repeating_${sectionName}`;
    return _.filter(this.findObjs({ type: 'attribute', characterid: characterId }),
        attr => attr.get('name').indexOf(prefix) === 0);
  }

  getRepeatingSectionItemIdsByName(characterId, sectionName) {
    const re = new RegExp(`repeating_${sectionName}_([^_]+)_name$`);
    return _.reduce(this.getRepeatingSectionAttrs(characterId, sectionName),
        (lookup, attr) => {
          const match = attr.get('name').match(re);
          if (match) {
            lookup[attr.get('current').toLowerCase()] = match[1];
          }
          return lookup;
        }, {});
  }

  getCurrentPage(playerId) {
    let pageId;
    if (this.playerIsGM(playerId)) {
      pageId = this.getObj('player', playerId).get('lastpage');
    }
    else {
      pageId = this.getCampaign().get('playerspecificpages')[playerId] || this.getCampaign().get('playerpageid');
    }

    return pageId ? this.getObj('page', pageId) : null;
  }

  spawnFx(pointsArray, fxType, pageId) {
    switch (pointsArray.length) {
      case 1:
        spawnFx(pointsArray[0].x, pointsArray[0].y, fxType, pageId);
        break;
      case 2:
        spawnFxBetweenPoints(pointsArray[0], pointsArray[1], fxType, pageId);
        break;
      default:
        throw new Error('Wrong number of points supplied to spawnFx: $$$', pointsArray);
    }
  }

  playerIsGM(playerId) {
    return playerIsGM(playerId);
  }

  getCampaign() {
    return Campaign(); // eslint-disable-line
  }

  sendChat(sendAs, message, callback, options) {
    return sendChat(sendAs, message, callback, options);
  }

  on(event, callback) {
    return on(event, callback);
  }

  randomInteger(max) {
    return randomInteger(max);
  }

  log(msg) {
    return log(msg);
  }

  setDefaultTokenForCharacter(character, token) {
    return setDefaultTokenForCharacter(character, token);
  }

  setAttrWithWorker(character, attrName, attrValue, cb) {
    const attr = this.getOrCreateAttr(character, attrName);
    onSheetWorkerCompleted(cb);
    attr.setWithWorker({ current: attrValue });
  }
};

<|endoftext|>


<commit_before>var restify = require('restify');
var fs = require('fs');
var log = require('./log/log.js');

/* Load database configuration variables */
var config = require('config');
var restifyConfig = config.get('Server.restify');

var commentRoutes = require('./controllers/comment.js');
var userRoutes = require('./controllers/user.js');

var server = restify.createServer({ log: log.logger });
server.use(restify.fullResponse());
server.use(restify.bodyParser());

server.pre(function(req, res, next) {
  req.log.debug(req.method + ' ' + req.url + ' from ' + req.connection.remoteAddress);
  next();
});

/* Comment endpoint handlers */
server.post('/comments', commentRoutes.createComment);
server.del('/comments/:id', commentRoutes.deleteComment);
server.get('/comments', commentRoutes.listComments);
server.get('/comments/:id', commentRoutes.viewComment);

/* User endpoint handlers */
server.post('/users', userRoutes.createUser);
server.del('/users/:username', userRoutes.deleteUser);
server.post('/users/:user1/follow/:user2', userRoutes.followUser);
server.get('/users', userRoutes.listUsers);
server.put('/users/:username', userRoutes.updateUser);
server.get('/users/:username', userRoutes.viewUser);

server.listen(restifyConfig.port, function() {
  log.logger.info('Restify server listening at port ' + restifyConfig.port);
});

if (process.env.NODE_ENV == 'production') {
  process.on('uncaughtException', function(err) {
    if (err.errno === 'EADDRINUSE') {
      log.logger.error('Can\'t start restify server: Address already in use');
      process.exit(1);
    } else {
      log.logger.error('Uncaught exception: ' + JSON.parse(JSON.stringify(err, ['stack', 'message', 'inner'], 2)));
    }
  });
}
<commit_msg>Add handlers for hug endpoints<commit_after>var restify = require('restify');
var fs = require('fs');
var log = require('./log/log.js');

/* Load database configuration variables */
var config = require('config');
var restifyConfig = config.get('Server.restify');

var commentRoutes = require('./controllers/comment.js');
var hugRoutes = require('./controllers/hug.js');
var userRoutes = require('./controllers/user.js');

var server = restify.createServer({ log: log.logger });
server.use(restify.fullResponse());
server.use(restify.bodyParser());

server.pre(function(req, res, next) {
  req.log.debug(req.method + ' ' + req.url + ' from ' + req.connection.remoteAddress);
  next();
});

/* Comment endpoint handlers */
server.post('/comments', commentRoutes.createComment);
server.del('/comments/:id', commentRoutes.deleteComment);
server.get('/comments', commentRoutes.listComments);
server.get('/comments/:id', commentRoutes.viewComment);

/* Hug endpoint handlers */
server.get('/hugs/:username', hugRoutes.countHugs);
server.post('/hugs', hugRoutes.createHug);
server.get('/hugs', hugRoutes.listHugs);

/* User endpoint handlers */
server.post('/users', userRoutes.createUser);
server.del('/users/:username', userRoutes.deleteUser);
server.post('/users/:user1/follow/:user2', userRoutes.followUser);
server.get('/users', userRoutes.listUsers);
server.put('/users/:username', userRoutes.updateUser);
server.get('/users/:username', userRoutes.viewUser);

server.listen(restifyConfig.port, function() {
  log.logger.info('Restify server listening at port ' + restifyConfig.port);
});

if (process.env.NODE_ENV == 'production') {
  process.on('uncaughtException', function(err) {
    if (err.errno === 'EADDRINUSE') {
      log.logger.error('Can\'t start restify server: Address already in use');
      process.exit(1);
    } else {
      log.logger.error('Uncaught exception: ' + JSON.parse(JSON.stringify(err, ['stack', 'message', 'inner'], 2)));
    }
  });
}
<|endoftext|>


<commit_before>/**
 * @author Exertis Micro-P
 */

Router.configure({
	layoutTemplate : 'layout',
	loadingTemplate : 'loading',
	waitOn : function() {/* return Meteor.subscribe('jobs'); */
	}
});

JobsListController = RouteController.extend({
	template : 'jobsList',
	increment : 5,
	limit : function() {
		return parseInt(this.params.jobsLimit) || this.increment;
	},
	findOptions : function() {
		return {
			sort : {
				job_title : 1
			},
			limit : this.limit()
		};
	},
	waitOn : function() {
		return Meteor.subscribe('jobs', this.findOptions());
	},
	data : function() {
		return {
			jobs : Jobs.find({}, this.findOptions()),
			nextPath : this.route.path({
				jobsLimit : this.limit() + this.increment
			})
		};
	}
});

Router.map(function() {

	this.route('jobsList', {
		path : '/:jobsLimit?',
		controller : JobsListController
	});

	this.route('jobPage', {
		path : '/jobs/:_id',
		waitOn : function() {
			return [Meteor.subscribe('singleJob', this.params._id)];
		},
		data : function() {
			return Jobs.findOne(this.params._id);
		}
	});
}); <commit_msg>Fixed Search<commit_after>/**
 * @author Exertis Micro-P
 */

Router.configure({
	layoutTemplate : 'layout',
	loadingTemplate : 'loading',
	waitOn : function() {/* return Meteor.subscribe('jobs'); */
	}
});

JobsListController = RouteController.extend({
	template : 'jobsList',
	increment : 5,
	limit : function() {
		return parseInt(this.params.jobsLimit) || this.increment;
	},
	findOptions : function() {
		return {
			sort : {
				job_title : 1
			},
			limit : this.limit()
		};
	},
	waitOn : function() {
		return Meteor.subscribe('jobs', this.findOptions());
	},
	data : function() {
		return {
			jobs : Jobs.find({job_title: {$regex: Session.get('searchfor'), $options: 'i' }}, this.findOptions()),
			nextPath : this.route.path({
				jobsLimit : this.limit() + this.increment
			})
		};
	}
});

Router.map(function() {

	this.route('jobsList', {
		path : '/:jobsLimit?',
		controller : JobsListController
	});

	this.route('jobPage', {
		path : '/jobs/:_id',
		waitOn : function() {
			return [Meteor.subscribe('singleJob', this.params._id)];
		},
		data : function() {
			return Jobs.findOne(this.params._id);
		}
	});
}); <|endoftext|>


<commit_before>// Copyright 2012 Mark Cavage, Inc.  All rights reserved.

var EventEmitter = require('events').EventEmitter;
var url = require('url');
var util = require('util');

var assert = require('assert-plus');
var clone = require('clone');
var LRU = require('lru-cache');
var semver = require('semver');

var errors = require('./errors');

///--- Globals

var DEF_CT = 'application/octet-stream';

var ALLOW_HEADERS = [
        'accept-version',
        'content-type',
        'request-id',
        'x-api-version',
        'x-request-id'
];

var maxSatisfying = semver.maxSatisfying;

var BadRequestError = errors.BadRequestError;
var InvalidArgumentError = errors.InvalidArgumentError;
var InvalidVersionError = errors.InvalidVersionError;
var MethodNotAllowedError = errors.MethodNotAllowedError;
var ResourceNotFoundError = errors.ResourceNotFoundError;
var UnsupportedMediaTypeError = errors.UnsupportedMediaTypeError;

///--- Helpers

function matchContentType(type1, type2) {
        var matches = true;

        try {
                var t1 = type1.split('/');
                var t2 = type2.split('/');

                t1[1] = t1[1].split(';');
                t2[1] = t2[1].split(';');

                if (t1[0] !== t2[0] && t1[0] !== '*') {
                        matches = false;
                } else  if (t1[1][0] !== t2[1][0] && t1[1][0] !== '*') {
                        matches = false;
                } else if (t1[1].length !== t2[1].length) {
                        matches = false;
                } else {
                        t1[1].sort();
                        t2[1].sort();
                        for (var i = 0; i < t1[1].length; i++) {
                                if (t1[1][i] !== t2[1][i] &&
                                    t1[1][i] !== '*') {
                                        matches = false;
                                        break;
                                }
                        }
                }
        } catch (e) {
                matches = false;
        }

        return (matches);
}

function matchURL(re, req) {
        var i = 0;
        var result = re.exec(req.path());
        var params = {};

        if (!result)
                return (false);

        // This means the user original specified a regexp match, not a url
        // string like /:foo/:bar
        if (!re.restifyParams) {
                for (i = 1; i < result.length; i++)
                        params[(i - 1)] = result[i];

                return (params);
        }

        // This was a static string, like /foo
        if (re.restifyParams.length === 0)
                return (params);

        // This was the "normal" case, of /foo/:id
        re.restifyParams.forEach(function (p) {
                if (++i < result.length)
                        params[p] = decodeURIComponent(result[i]);
        });

        return (params);
}

function compileURL(options) {
        if (options.url instanceof RegExp)
                return (options.url);
        assert.string(options.url, 'url');

        var params = [];
        var pattern = '^';
        var re;
        var _url = url.parse(options.url).pathname;
        _url.split('/').forEach(function (frag) {
                if (frag.length <= 0)
                        return (false);

                pattern += '\\/+';
                if (frag.charAt(0) === ':') {
                        if (options.urlParamPattern) {
                                pattern += '(' + options.urlParamPattern + ')';
                        } else {
                                // Strictly adhere to RFC3986
                                pattern += '([a-zA-Z0-9-_~\\.%@]+)';
                        }
                        params.push(frag.slice(1));
                } else {
                        pattern += frag;
                }

                return (true);
        });

        if (pattern === '^')
                pattern += '\\/';
        pattern += '$';

        re = new RegExp(pattern, options.flags);
        re.restifyParams = params;

        return (re);
}

///--- API

function Router(options) {
        assert.object(options, 'options');
        assert.object(options.log, 'options.log');

        EventEmitter.call(this);

        this.cache = LRU({max: 100});
        this.contentType = options.contentType || [];
        if (!Array.isArray(this.contentType))
                this.contentType = [this.contentType];
        assert.arrayOfString(this.contentType, 'options.contentType');

        this.log = options.log;
        this.mounts = {};
        this.name = 'RestifyRouter';

        // A list of methods to routes
        this.routes = {
                DELETE: [],
                GET: [],
                HEAD: [],
                OPTIONS: [],
                PATCH: [],
                POST: [],
                PUT: []
        };

        // So we can retrun 405 vs 404, we maintain a reverse mapping of URLs
        // to method
        this.reverse = {};

        this.version = options.version || [];
        if (!Array.isArray(this.version))
                this.version = [this.version];
        assert.arrayOfString(this.version, 'options.version');

        this.version.forEach(function (v) {
                if (semver.valid(v))
                        return (true);

                throw new InvalidArgumentError('%s is not a valid semver', v);
        });
        this.version.sort();

}
util.inherits(Router, EventEmitter);
module.exports = Router;

Router.prototype.mount = function mount(options) {
        assert.object(options, 'options');
        assert.string(options.method, 'options.method');
        assert.string(options.name, 'options.name');

        var exists;
        var name = options.name;
        var route;
        var routes = this.routes[options.method];
        var self = this;
        var type = options.contentType || self.contentType;
        var version = options.version || self.version;

        if (!Array.isArray(type))
                type = [type];
        type.sort();

        if (!Array.isArray(version))
                version = [version];
        version.sort();

        exists = routes.some(function (r) {
                return (r.name === name);
        });
        if (exists)
                return (false);

        route = {
                name: name,
                method: options.method,
                path: compileURL({
                        url: options.path || options.url,
                        flags: options.flags,
                        urlParamPattern: options.urlParamPattern
                }),
                type: type,
                version: version
        };
        routes.push(route);

        if (!this.reverse[route.path.source])
                this.reverse[route.path.source] = [];

        if (this.reverse[route.path.source].indexOf(route.method) === -1)
                this.reverse[route.path.source].push(route.method);

        this.mounts[route.name] = route;

        this.emit('mount',
                  route.method,
                  route.path,
                  route.type,
                  route.version);

        return (route.name);
};

Router.prototype.unmount = function unmount(name) {
        var route = this.mounts[name];
        if (!route) {
                this.log.warn('router.unmount(%s): route does not exist', name);
                return (false);
        }

        var reverse = this.reverse[route.path.source];
        var routes = this.routes[route.method];
        this.routes[route.method] = routes.filter(function (r) {
                return (r.name !== route.name);
        });

        this.reverse[route.path.source] = reverse.filter(function (r) {
                return (r !== route.method);
        });

        if (this.reverse[route.path.source].length === 0)
                delete this.reverse[route.path.source];

        delete this.mounts[name];

        return (name);
};

Router.prototype.find = function find(req, res, callback) {
        var ct = req.headers['content-type'] || DEF_CT;
        var cacheKey = req.method + req.url + req.version() + ct;
        var cacheVal;
        var params;
        var r;
        var routes = this.routes[req.method] || [];
        var typed;
        var ver;

        if ((cacheVal = this.cache.get(cacheKey))) {
                callback(null, cacheVal.name, clone(cacheVal.params));
                return;
        }

        for (var i = 0; i < routes.length; i++) {
                try {
                        params = matchURL(routes[i].path, req);
                } catch (e) {
                        this.log.trace({err: e}, 'error parsing URL');
                        callback(new BadRequestError(e.message));
                        return;
                }

                if (params === false)
                        continue;

                // Not common
                if (routes[i].type.length !== 0) {
                        switch (req.method) {
                        case 'POST':
                        case 'PUT':
                                // On uploads, check if the supplied
                                // content-type matches the type of the route
                                typed = routes[i].type.some(function (t) {
                                        return (matchContentType(t, ct));
                                });
                                break;

                        default:
                                break;
                        }

                        if (typed === false)
                                continue;
                }

                if (routes[i].version.length === 0 ||
                    req.version() === '*' ||
                    (ver = maxSatisfying(routes[i].version,
                                         req.version()) || false)) {
                        r = routes[i];
                        break;
                }
        }

        //
        // In order, we check if the route exists, in which case, we're good.
        // Otherwise we look to see if ver was set to false; that would tell us
        // we indeed did find a matching route (method+url), but the version
        // field didn't line up, so we return bad version.  If no route and no
        // version, we now need to go walk the reverse map and look at whether
        // we should return 405 or 404.  If it was an OPTIONS request, we need
        // to handle this having been a preflight request.
        //

        if (params && r) {
                cacheVal = {
                        name: r.name,
                        params: params
                };
                this.cache.set(cacheKey, cacheVal);
                callback(null, r.name, clone(params));
                return;
        }

        if (typed === false) {
                callback(new UnsupportedMediaTypeError(ct));
                return;
        }
        if (ver === false) {
                callback(new InvalidVersionError(req.getVersion()));
                return;
        }

        // This is a very generic preflight handler - it does
        // not handle requiring authentication, nor does it do
        // any special checking for extra user headers. The
        // user will need to defined their own .opts handler to
        // do that
        function preflight(methods) {
                var headers = req.headers['access-control-request-headers'];
                var method = req.headers['access-control-request-method'];
                var origin = req.headers['origin'];

                if (req.method !== 'OPTIONS' ||
                    !origin ||
                    !method ||
                    methods.indexOf(method) === -1) {
                        return (false);
                }
                // Last, check request-headers
                var ok = true;
                (headers || []).forEach(function (h) {
                        h = h.toLowerCase();
                        ok = ALLOW_HEADERS.indexOf(h) !== -1 && ok;
                });
                if (!ok)
                        return (false);

                res.setHeader('Access-Control-Allow-Origin', '*');
                res.setHeader('Access-Control-Allow-Methods',
                              methods.join(', '));
                res.setHeader('Access-Control-Allow-Headers',
                              ALLOW_HEADERS.join(', '));
                res.setHeader('Access-Control-Max-Age', 3600);

                return (true);
        }

        // Check for 405 instead of 404
        var urls = Object.keys(this.reverse);
        for (i = 0; i < urls.length; i++) {
                if (matchURL(new RegExp(urls[i]), req)) {
                        var _methods = this.reverse[urls[i]].slice();
                        res.setHeader('Allow', _methods.join(', '));
                        if (preflight(_methods)) {
                                callback(null, true);
                                return;
                        }
                        var err = new MethodNotAllowedError('%s is not allowed',
                                                            req.method);
                        callback(err);
                        return;
                }
        }

        callback(new ResourceNotFoundError('%s does not exist', req.url));
};

Router.prototype.toString = function toString() {
        var self = this;
        var str = this.name + ':\n';

        Object.keys(this.routes).forEach(function (k) {
                var routes = self.routes[k].map(function (r) {
                        return (r.name);
                });

                str += '\t\t' + k + ': [' + routes.join(', ') + ']\n';
        });

        return (str);
};
<commit_msg>Minor fixes for my own usage.<commit_after>// Copyright 2012 Mark Cavage, Inc.  All rights reserved.

var EventEmitter = require('events').EventEmitter;
var url = require('url');
var util = require('util');

var assert = require('assert-plus');
var clone = require('clone');
var LRU = require('lru-cache');
var semver = require('semver');

var errors = require('./errors');

///--- Globals

var DEF_CT = 'application/octet-stream';

var ALLOW_HEADERS = [
        'accept-version',
        'content-type',
        'request-id',
        'x-api-version',
        'x-request-id',
        'accept',
        'authorization',
        'origin'

];

var maxSatisfying = semver.maxSatisfying;

var BadRequestError = errors.BadRequestError;
var InvalidArgumentError = errors.InvalidArgumentError;
var InvalidVersionError = errors.InvalidVersionError;
var MethodNotAllowedError = errors.MethodNotAllowedError;
var ResourceNotFoundError = errors.ResourceNotFoundError;
var UnsupportedMediaTypeError = errors.UnsupportedMediaTypeError;

///--- Helpers

function matchContentType(type1, type2) {
        var matches = true;

        try {
                var t1 = type1.split('/');
                var t2 = type2.split('/');

                t1[1] = t1[1].split(';');
                t2[1] = t2[1].split(';');

                if (t1[0] !== t2[0] && t1[0] !== '*') {
                        matches = false;
                } else  if (t1[1][0] !== t2[1][0] && t1[1][0] !== '*') {
                        matches = false;
                } else if (t1[1].length !== t2[1].length) {
                        matches = false;
                } else {
                        t1[1].sort();
                        t2[1].sort();
                        for (var i = 0; i < t1[1].length; i++) {
                                if (t1[1][i] !== t2[1][i] &&
                                    t1[1][i] !== '*') {
                                        matches = false;
                                        break;
                                }
                        }
                }
        } catch (e) {
                matches = false;
        }

        return (matches);
}

function matchURL(re, req) {
        var i = 0;
        var result = re.exec(req.path());
        var params = {};

        if (!result)
                return (false);

        // This means the user original specified a regexp match, not a url
        // string like /:foo/:bar
        if (!re.restifyParams) {
                for (i = 1; i < result.length; i++)
                        params[(i - 1)] = result[i];

                return (params);
        }

        // This was a static string, like /foo
        if (re.restifyParams.length === 0)
                return (params);

        // This was the "normal" case, of /foo/:id
        re.restifyParams.forEach(function (p) {
                if (++i < result.length)
                        params[p] = decodeURIComponent(result[i]);
        });

        return (params);
}

function compileURL(options) {
        if (options.url instanceof RegExp)
                return (options.url);
        assert.string(options.url, 'url');

        var params = [];
        var pattern = '^';
        var re;
        var _url = url.parse(options.url).pathname;
        _url.split('/').forEach(function (frag) {
                if (frag.length <= 0)
                        return (false);

                pattern += '\\/+';
                if (frag.charAt(0) === ':') {
                        if (options.urlParamPattern) {
                                pattern += '(' + options.urlParamPattern + ')';
                        } else {
                                // Strictly adhere to RFC3986
                                pattern += '([a-zA-Z0-9-_~\\.%@]+)';
                        }
                        params.push(frag.slice(1));
                } else {
                        pattern += frag;
                }

                return (true);
        });

        if (pattern === '^')
                pattern += '\\/';
        pattern += '$';

        re = new RegExp(pattern, options.flags);
        re.restifyParams = params;

        return (re);
}

///--- API

function Router(options) {
        assert.object(options, 'options');
        assert.object(options.log, 'options.log');

        EventEmitter.call(this);

        this.cache = LRU({max: 100});
        this.contentType = options.contentType || [];
        if (!Array.isArray(this.contentType))
                this.contentType = [this.contentType];
        assert.arrayOfString(this.contentType, 'options.contentType');

        this.log = options.log;
        this.mounts = {};
        this.name = 'RestifyRouter';

        // A list of methods to routes
        this.routes = {
                DELETE: [],
                GET: [],
                HEAD: [],
                OPTIONS: [],
                PATCH: [],
                POST: [],
                PUT: []
        };

        // So we can retrun 405 vs 404, we maintain a reverse mapping of URLs
        // to method
        this.reverse = {};

        this.version = options.version || [];
        if (!Array.isArray(this.version))
                this.version = [this.version];
        assert.arrayOfString(this.version, 'options.version');

        this.version.forEach(function (v) {
                if (semver.valid(v))
                        return (true);

                throw new InvalidArgumentError('%s is not a valid semver', v);
        });
        this.version.sort();

}
util.inherits(Router, EventEmitter);
module.exports = Router;

Router.prototype.mount = function mount(options) {
        assert.object(options, 'options');
        assert.string(options.method, 'options.method');
        assert.string(options.name, 'options.name');

        var exists;
        var name = options.name;
        var route;
        var routes = this.routes[options.method];
        var self = this;
        var type = options.contentType || self.contentType;
        var version = options.version || self.version;

        if (!Array.isArray(type))
                type = [type];
        type.sort();

        if (!Array.isArray(version))
                version = [version];
        version.sort();

        exists = routes.some(function (r) {
                return (r.name === name);
        });
        if (exists)
                return (false);

        route = {
                name: name,
                method: options.method,
                path: compileURL({
                        url: options.path || options.url,
                        flags: options.flags,
                        urlParamPattern: options.urlParamPattern
                }),
                type: type,
                version: version
        };
        routes.push(route);

        if (!this.reverse[route.path.source])
                this.reverse[route.path.source] = [];

        if (this.reverse[route.path.source].indexOf(route.method) === -1)
                this.reverse[route.path.source].push(route.method);

        this.mounts[route.name] = route;

        this.emit('mount',
                  route.method,
                  route.path,
                  route.type,
                  route.version);

        return (route.name);
};

Router.prototype.unmount = function unmount(name) {
        var route = this.mounts[name];
        if (!route) {
                this.log.warn('router.unmount(%s): route does not exist', name);
                return (false);
        }

        var reverse = this.reverse[route.path.source];
        var routes = this.routes[route.method];
        this.routes[route.method] = routes.filter(function (r) {
                return (r.name !== route.name);
        });

        this.reverse[route.path.source] = reverse.filter(function (r) {
                return (r !== route.method);
        });

        if (this.reverse[route.path.source].length === 0)
                delete this.reverse[route.path.source];

        delete this.mounts[name];

        return (name);
};

Router.prototype.find = function find(req, res, callback) {
        var ct = req.headers['content-type'] || DEF_CT;
        var cacheKey = req.method + req.url + req.version() + ct;
        var cacheVal;
        var params;
        var r;
        var routes = this.routes[req.method] || [];
        var typed;
        var ver;

        if ((cacheVal = this.cache.get(cacheKey))) {
                callback(null, cacheVal.name, clone(cacheVal.params));
                return;
        }

        for (var i = 0; i < routes.length; i++) {
                try {
                        params = matchURL(routes[i].path, req);
                } catch (e) {
                        this.log.trace({err: e}, 'error parsing URL');
                        callback(new BadRequestError(e.message));
                        return;
                }

                if (params === false)
                        continue;

                // Not common
                if (routes[i].type.length !== 0) {
                        switch (req.method) {
                        case 'POST':
                        case 'PUT':
                                // On uploads, check if the supplied
                                // content-type matches the type of the route
                                typed = routes[i].type.some(function (t) {
                                        return (matchContentType(t, ct));
                                });
                                break;

                        default:
                                break;
                        }

                        if (typed === false)
                                continue;
                }

                if (routes[i].version.length === 0 ||
                    req.version() === '*' ||
                    (ver = maxSatisfying(routes[i].version,
                                         req.version()) || false)) {
                        r = routes[i];
                        break;
                }
        }

        //
        // In order, we check if the route exists, in which case, we're good.
        // Otherwise we look to see if ver was set to false; that would tell us
        // we indeed did find a matching route (method+url), but the version
        // field didn't line up, so we return bad version.  If no route and no
        // version, we now need to go walk the reverse map and look at whether
        // we should return 405 or 404.  If it was an OPTIONS request, we need
        // to handle this having been a preflight request.
        //

        if (params && r) {
                cacheVal = {
                        name: r.name,
                        params: params
                };
                this.cache.set(cacheKey, cacheVal);
                callback(null, r.name, clone(params));
                return;
        }

        if (typed === false) {
                callback(new UnsupportedMediaTypeError(ct));
                return;
        }
        if (ver === false) {
                callback(new InvalidVersionError(req.getVersion()));
                return;
        }

        // This is a very generic preflight handler - it does
        // not handle requiring authentication, nor does it do
        // any special checking for extra user headers. The
        // user will need to defined their own .opts handler to
        // do that
        function preflight(methods) {
                var headers = req.headers['access-control-request-headers'];
                var method = req.headers['access-control-request-method'];
                var origin = req.headers['origin'];

                if (req.method !== 'OPTIONS' ||
                    !origin ||
                    !method ||
                    methods.indexOf(method) === -1) {
                        return (false);
                }
                // Last, check request-headers
                var ok = true;
                (headers || "").toLowerCase().split(", ").forEach(function (h) {
                        h = h.toLowerCase();
                        ok = ALLOW_HEADERS.indexOf(h) !== -1 && ok;
                });
                if (!ok)
                        return (false);

                res.setHeader('Access-Control-Allow-Origin', '*');
                res.setHeader('Access-Control-Allow-Methods',
                              methods.join(', '));
                res.setHeader('Access-Control-Allow-Headers',
                              ALLOW_HEADERS.join(', '));
                res.setHeader('Access-Control-Max-Age', 3600);

                return (true);
        }

        // Check for 405 instead of 404
        var urls = Object.keys(this.reverse);
        for (i = 0; i < urls.length; i++) {
                if (matchURL(new RegExp(urls[i]), req)) {
                        var _methods = this.reverse[urls[i]].slice();
                        res.setHeader('Allow', _methods.join(', '));
                        if (preflight(_methods)) {
                                callback(null, true);
                                return;
                        }
                        var err = new MethodNotAllowedError('%s is not allowed',
                                                            req.method);
                        callback(err);
                        return;
                }
        }

        callback(new ResourceNotFoundError('%s does not exist', req.url));
};

Router.prototype.toString = function toString() {
        var self = this;
        var str = this.name + ':\n';

        Object.keys(this.routes).forEach(function (k) {
                var routes = self.routes[k].map(function (r) {
                        return (r.name);
                });

                str += '\t\t' + k + ': [' + routes.join(', ') + ']\n';
        });

        return (str);
};
<|endoftext|>


<commit_before>var subscriptions = new SubsManager();

Router.configure({
	layoutTemplate: 'layout',
	loadingTemplate: 'loading',
	notFoundTemplate: 'notFound'
});

Router.route('/admin', {
	name: 'adminHome'
});

Router.route('/admin/addCompleteMangas', {
	name: 'addCompleteMangas'
});

Router.route('/admin/viewMangasToAdd', {
	name: 'viewMangasToAdd'
});

Router.route('/', {
	name: 'home',
	waitOn: function() {
		return subscriptions.subscribe('allMangasCover');
	},
	fastRender: true
});

Router.route('/addMangaForUser/step1', {
	name: 'addMangaForUserStep1',
	waitOn: function() {
		return subscriptions.subscribe('allMangasCover');
	}
});

Router.route('/addMangaForUser/step2/:_id', {
	name: 'addMangaForUserStep2',
	waitOn: function() {
		return [
			subscriptions.subscribe('oneMangasData', this.params._id),
			subscriptions.subscribe('allTomesForUser', Meteor.userId())
		];
	}
});

Router.route('/ownedMangas', {
	name: 'ownedMangas',
	waitOn: function() {
		return subscriptions.subscribe('allOwnedMangas', Meteor.userId());
	}
});

Router.route('/missingMangas', {
	name: 'missingMangas',
	waitOn: function() {
		return subscriptions.subscribe('allMissingMangas', Meteor.userId());
	}
});

Router.route('/:name/tome/:number/:_id', {
	name: 'tomeDetails',
	waitOn: function() {
		return [
			subscriptions.subscribe('tomeDetails', this.params._id),
			subscriptions.subscribe('allTomes', Meteor.userId(), this.params.name)
		];
	},
	data: function() {
		return Mangas.findOne(this.params._id);
	}
});

Router.route('/:author', {
	name: 'mangaka',
	waitOn: function() {
		return subscriptions.subscribe('mangaka', this.params.author);
	}
});
<commit_msg>Update subscriptions<commit_after>var subscriptions = new SubsManager();

Router.configure({
	layoutTemplate: 'layout',
	loadingTemplate: 'loading',
	notFoundTemplate: 'notFound'
});

Router.route('/admin', {
	name: 'adminHome'
});

Router.route('/admin/addCompleteMangas', {
	name: 'addCompleteMangas'
});

Router.route('/admin/viewMangasToAdd', {
	name: 'viewMangasToAdd'
});

Router.route('/', {
	name: 'home',
	waitOn() {
		return subscriptions.subscribe('allMangasCover');
	},
	fastRender: true
});

Router.route('/addMangaForUser/step1', {
	name: 'addMangaForUserStep1',
	waitOn() {
		return subscriptions.subscribe('allMangasCover');
	}
});

Router.route('/addMangaForUser/step2/:_id', {
	name: 'addMangaForUserStep2',
	waitOn() {
		return [
			subscriptions.subscribe('oneMangasData', this.params._id),
			subscriptions.subscribe('allTomesForUser', Meteor.userId())
		];
	}
});

Router.route('/ownedMangas', {
	name: 'ownedMangas',
	waitOn() {
		return subscriptions.subscribe('allOwnedMangas', Meteor.userId());
	}
});

Router.route('/missingMangas', {
	name: 'missingMangas',
	waitOn() {
		return subscriptions.subscribe('allMissingMangas', Meteor.userId());
	}
});

Router.route('/:name/tome/:number/:_id', {
	name: 'tomeDetails',
	waitOn() {
		return [
			subscriptions.subscribe('tomeDetails', this.params._id),
			subscriptions.subscribe('allTomes', Meteor.userId(), this.params.name)
		];
	},
	data() {
		return Mangas.findOne(this.params._id);
	}
});

Router.route('/:author', {
	name: 'mangaka',
	waitOn() {
		return subscriptions.subscribe('mangaka', this.params.author);
	}
});
<|endoftext|>


<commit_before>var express = require('express');
var router = express.Router();
var fs = require('fs');
var path = require('path');
var convertMarkdown = require('./markdown');

var mdDir, debug, hasSetting, docList, title, hasSwaggerDoc, swaggerDoc;
var mdFiles = {};
var swagger_cache = {};
var docEjs = path.join(__dirname, '../views/docs');

router.use('/asset/', express.static(path.join(__dirname, '../public')));
// for swagger
fs.readFile(path.join(__dirname, '../public/index.html'), function (err, html) {
    if (err) {throw err;}
    swagger_cache['index'] = html; //Buffer.from( String(html).replace('css/reset.css','') );
});

// routes
function routes(options) {
    "use strict";

    if (!options.mddir) {
        throw new Error("mddir is not specified");
    }

    mdDir = options.mddir;
    debug = options.debug || false;
    hasSetting = options.hasSetting || false;
    title = options.title || "";
    hasSwaggerDoc = options.swaggerDoc? true: false;
    swaggerDoc = options.swaggerDoc;

    // open md mddir
    fs.readdir(mdDir, function(err, files) {
        if (err) throw err;

        if (hasSetting) {
            fs.open(path.join(mdDir, 'settings.json'), 'r', function(err, fd) {
                if (err) {
                    if (err.code == "ENOENT") {
                        console.error("You set hasSetting true but ", mdDir, "/settings.json is not exist");
                    }

                    throw err;
                }

                // read mddir/settings.json
                fs.readFile(fd, 'utf8', (err, data) => {
                    var settings = JSON.parse(data);
                    if (settings.listorder) {
                        docList = settings.listorder;
                        if (debug) console.log("List up md files... ", docList);

                        // check file existance
                        docList.forEach(name => {
                            let wholename = name + ".md";
                            let file = files.find(filename => (wholename === filename));
                            if (file) {
                                mdFiles[name] = path.join(mdDir, file);
                            } else {
                                console.error("settings.json defines file: ", name, "but the file is not exist in ", mdDir);
                                throw new Error("File " + name + " is not exist");
                            }
                        });
                    }
                });
            });
        } else {
            // read alphabetical order
            files.forEach(function(filename) {
                if (filename.endsWith('.md')) {
                    mdFiles[filename.substring(0, filename.length - 3)] = path.join(mdDir, filename);
                }
            });
            docList = Object.keys(mdFiles);
            console.log("md file name list: ", docList);
            return;
        }
    });
    router.get('/', function(req, res) {
        let param = {
            title: title,
            content : null,
            hasSwaggerDoc: hasSwaggerDoc,
            baseUrl: req.baseUrl,
            list: docList
        };

        res.render(docEjs, param);
    });

    router.get('/swagger', function(req, res) {
        let param = {
            title: title,
            content : swagger_cache.index,
            hasSwaggerDoc: hasSwaggerDoc,
            baseUrl: req.baseUrl,
            list: docList
        };
        res.render(docEjs, param);
    });

    router.get('/swagger/apidoc', function(req, res) {
        res.status(200).send(swaggerDoc);
    });

    router.get('/:filename', function(req, res) {
        let filename = mdFiles[req.params.filename];
        if (filename) {
            console.log("File: ", filename);
            convertMarkdown(filename, function(err, html) {
                if (err) return res.status(500).json(err);
                let param = {
                    title: title,
                    mdfilename: req.params.filename,
                    content : html,
                    hasSwaggerDoc: hasSwaggerDoc,
                    baseUrl: req.baseUrl,
                    list: docList
                };
                res.render(docEjs, param);
            });
        } else {
            res.status(404).send();
        }
    });

    return router;
}

module.exports = routes;<commit_msg>Add cache for converted html string from md<commit_after>var express = require('express');
var router = express.Router();
var fs = require('fs');
var path = require('path');
var convertMarkdown = require('./markdown');

var mdDir, debug, hasSetting, docList, title, hasSwaggerDoc, swaggerDoc;
var mdFiles = {};
var swagger_cache = {};
var mdCache = {};
var docEjs = path.join(__dirname, '../views/docs');

router.use('/asset/', express.static(path.join(__dirname, '../public')));
// for swagger
fs.readFile(path.join(__dirname, '../public/index.html'), function (err, html) {
    if (err) {throw err;}
    swagger_cache['index'] = html; //Buffer.from( String(html).replace('css/reset.css','') );
});

// routes
function routes(options) {
    "use strict";

    if (!options.mddir) {
        throw new Error("mddir is not specified");
    }

    mdDir = options.mddir;
    debug = options.debug || false;
    hasSetting = options.hasSetting || false;
    title = options.title || "";
    hasSwaggerDoc = options.swaggerDoc? true: false;
    swaggerDoc = options.swaggerDoc;

    // open md mddir
    fs.readdir(mdDir, function(err, files) {
        if (err) throw err;

        if (hasSetting) {
            fs.open(path.join(mdDir, 'settings.json'), 'r', function(err, fd) {
                if (err) {
                    if (err.code == "ENOENT") {
                        console.error("You set hasSetting true but ", mdDir, "/settings.json is not exist");
                    }

                    throw err;
                }

                // read mddir/settings.json
                fs.readFile(fd, 'utf8', (err, data) => {
                    var settings = JSON.parse(data);
                    if (settings.listorder) {
                        docList = settings.listorder;
                        if (debug) console.log("List up md files... ", docList);

                        // check file existance
                        docList.forEach(name => {
                            let wholename = name + ".md";
                            let file = files.find(filename => (wholename === filename));
                            if (file) {
                                mdFiles[name] = path.join(mdDir, file);
                            } else {
                                console.error("settings.json defines file: ", name, "but the file is not exist in ", mdDir);
                                throw new Error("File " + name + " is not exist");
                            }
                        });
                    }
                });
            });
        } else {
            // read alphabetical order
            files.forEach(function(filename) {
                if (filename.endsWith('.md')) {
                    mdFiles[filename.substring(0, filename.length - 3)] = path.join(mdDir, filename);
                }
            });
            docList = Object.keys(mdFiles);
            if (debug) console.log("md file name list: ", docList);
            return;
        }
    });
    router.get('/', function(req, res) {
        let param = {
            title: title,
            content : null,
            hasSwaggerDoc: hasSwaggerDoc,
            baseUrl: req.baseUrl,
            list: docList
        };

        res.render(docEjs, param);
    });

    router.get('/swagger', function(req, res) {
        let param = {
            title: title,
            content : swagger_cache.index,
            hasSwaggerDoc: hasSwaggerDoc,
            baseUrl: req.baseUrl,
            list: docList
        };
        res.render(docEjs, param);
    });

    router.get('/swagger/apidoc', function(req, res) {
        res.status(200).send(swaggerDoc);
    });

    router.get('/:filename', function(req, res) {
        let filename = req.params.filename;
        let filepath = mdFiles[filename];
        if (filepath) {
            if (debug) console.log("File: ", filepath);
            // check cache
            fs.stat(filepath, function(err, stats) {
                if (mdCache[filename] && (mdCache[filename].mtime.getTime() === stats.mtime.getTime())) {
                    // use cache
                    if (debug) console.log("Using cached value");
                    let param = {
                        title: title,
                        mdfilename: filename,
                        content : mdCache[filename].html,
                        hasSwaggerDoc: hasSwaggerDoc,
                        baseUrl: req.baseUrl,
                        list: docList
                    };
                    res.render(docEjs, param);
                } else {
                    // read from file and add cache
                    convertMarkdown(filepath, function(err, html) {
                        if (err) return res.status(500).json(err);
                        // add cache
                        mdCache[filename] = {
                            mtime: stats.mtime,
                            html: html
                        };

                        // render page
                        let param = {
                            title: title,
                            mdfilename: filename,
                            content : html,
                            hasSwaggerDoc: hasSwaggerDoc,
                            baseUrl: req.baseUrl,
                            list: docList
                        };
                        res.render(docEjs, param);
                    });
                }
            });
        } else {
            res.status(404).send();
        }
    });

    return router;
}

module.exports = routes;
<|endoftext|>