<commit_before>/*global nodeca, _*/

"use strict";

// stdlib
var crypto    = require('crypto');
var os        = require('os');
var fs        = require('fs');
var path      = require('path');
var execFile  = require('child_process').execFile;

// 3rd-party
var neuron  = require('neuron');
var async   = require('nlib').Vendor.Async;
var fstools = require('nlib').Vendor.FsTools;

// internal
var stats   = require('../../lib/stats');

////////////////////////////////////////////////////////////////////////////////

var TMP_DIR             = '/tmp/fontello';
var APP_ROOT            = _.first(nodeca.runtime.apps).root;
var DOWNLOAD_DIR        = path.join(APP_ROOT, 'public/download/');
var DOWNLOAD_URL_PREFIX = "/download/";
var GENERATOR_BIN       = path.join(APP_ROOT, 'bin/generate_font.sh');
var CONFIG              = nodeca.config.fontomas;

////////////////////////////////////////////////////////////////////////////////

// return font configuration
var font_configs = null;
function get_embedded_font(name) {
  if (null === font_configs) {
    font_configs = {};
    nodeca.shared.fontomas.embedded_fonts.forEach(function (config) {
      font_configs[config.font.fontname] = config;
    });
  }

  return name ? font_configs[name] : font_configs;
}

var source_fonts;
function get_source_fonts() {
  var fonts_dir;

  if (!source_fonts) {
    source_fonts  = {};
    fonts_dir     = path.join(APP_ROOT, 'assets/embedded_fonts');

    _.each(get_embedded_font(), function (config, name) {
      source_fonts[name] = path.join(fonts_dir, name + '.ttf');
    });
  }

  return source_fonts;
}

// return valid glyphs configuration
function get_glyphs_config(params) {
  var glyphs = [];

  if (!_.isArray(params.glyphs)) {
    return glyphs;
  }

  _.each(params.glyphs, function (g) {
    var font = get_embedded_font(g.src), glyph;

    if (!font) {
      // unknown glyph source font
      return;
    }

    glyph = _.find(font.glyphs, function (config) {
      return config.code === g.from;
    });

    if (!glyph) {
      // unknown glyph code
      return;
    }

    glyphs.push({
      css:  g.css || glyph.css,
      src:  g.src,
      from: g.from,
      code: g.code || g.from
    });
  });

  if (0 === glyphs.length) {
    // at least one glyph is required
    return null;
  }

  // return glyphs config sorted by original codes
  return _.sortBy(glyphs, function (g) { return g.from; });
}

function format_timestring(d) {
  function pad(n) {
    return n < 10 ? ('0' + n) : n;
  }

  return  d.getUTCFullYear() + pad(d.getUTCMonth()+1) + pad(d.getUTCDate()) +
          pad(d.getUTCHours()) + pad(d.getUTCMinutes()) + pad(d.getUTCSeconds());
}

function format_font_fullname(name) {
  var parts = [];

  name.toLowerCase().split(/[\-_]/).forEach(function (p) {
    if (p) {
      parts.push(p.substr(0, 1).toUpperCase() + p.substr(1));
    }
  });

  return parts.join(' ');
}

function filter_fontname(str) {
  str = _.isString(str) ? str : '';
  return str.replace(/[^a-z0-9\-_]+/g, '-').trimLeft().trimRight();
}

function get_font_config(params) {
  var glyphs_config, timestamp, fontname;

  if (!_.isObject(params)) {
    return null;
  }

  glyphs_config = get_glyphs_config(params);
  timestamp     = format_timestring(new Date);
  fontname      = filter_fontname(params.name) || ('fontello-' + timestamp);

  return {
    font: {
      version:    timestamp,
      fontname:   fontname.toLowerCase(),
      fullname:   format_font_fullname(fontname),
      familyname: format_font_fullname(fontname).split(' ').shift(),
      copyright:  "Copyright (C) 2012 by original authors @ fontello.com",
      ascent:     800,
      descent:    200,
      weight:     "Medium"
    },
    meta: {
      columns: 4,
      css_prefix: 'icon-',
    },
    glyphs:     glyphs_config,
    src_fonts:  get_source_fonts()
  };
}

// returns unique ID for requested list of glyphs
function get_download_id(font) {
  var hash = crypto.createHash('md5');

  hash.update('fontello' + nodeca.runtime.version);
  hash.update(JSON.stringify(font));

  return hash.digest('hex');
}

function get_download_path(font_id) {
  var a, b;

  a = font_id.substr(0, 2);
  b = font_id.substr(2, 2);

  return [a, b, 'fontello-' + font_id].join("/") + ".zip";
}

function get_download_url(font_id) {
  return DOWNLOAD_URL_PREFIX + get_download_path(font_id);
}

// status of jobs by their ids
var jobs = {};

// returns instance of job (searches on FS if needed)
function get_job(font_id, callback) {
  var job = jobs[font_id],
      log_prefix = '[font::' + font_id + '] ',
      file;

  // return not-finished jobs as they are
  if (job) {
    nodeca.logger.debug(log_prefix + 'Job found in the cache: ' + JSON.stringify(job));
    callback(job);
    return;
  }

  file = path.join(DOWNLOAD_DIR, get_download_path(font_id));
  path.exists(file, function (result) {
    if (!result) {
      nodeca.logger.debug(log_prefix + 'Job not found and file not exists.');
      callback(/* undefined - job not found */);
      return;
    }

    nodeca.logger.debug(log_prefix + 'Job not found but download file exists.');
    callback({status: 'finished', url: get_download_url(font_id)});
  });
}

// define queue and jobs
var job_mgr = new (neuron.JobManager)();
job_mgr.addJob('generate-font', {
  dirname: '/tmp',
  concurrency: (CONFIG.builder_concurrency || os.cpus().length),
  work: function (font_id, config) {
    var self        = this,
        log_prefix  = '[font::' + font_id + '] ',
        tmp_dir     = path.join(TMP_DIR, "fontello-" + font_id),
        zipball     = path.join(DOWNLOAD_DIR, get_download_path(font_id)),
        times       = [jobs[font_id].start];

    nodeca.logger.info(log_prefix + 'Start generation: ' + JSON.stringify(config));

    // FIXME: after server restart this might become "undefined"
    //
    //        I'm still unsure WHAT might cause such behavior, as after restart
    //        manager should have empty stack.
    //
    //        Possible reason is that job removed by get_job_data().
    if (!jobs[font_id]) {
      jobs[font_id] = {start: Date.now(), status: 'enqueued'};
      times         = [jobs[font_id].start];

      nodeca.logger.error(log_prefix + "Unexpected absence of job.");
    }

    // push timer checkpoint
    times.push(Date.now());

    async.series([
      async.apply(fstools.remove, tmp_dir),
      async.apply(fstools.mkdir, tmp_dir),
      async.apply(fs.writeFile, path.join(tmp_dir, 'config.json'), JSON.stringify(config), 'utf8'),
      async.apply(execFile, GENERATOR_BIN, [config.font.fontname, tmp_dir, zipball], {cwd: APP_ROOT}),
      async.apply(fstools.remove, tmp_dir)
    ], function (err) {
      if (err) {
        nodeca.logger.error(log_prefix + (err.stack || err.message || err.toString()));

        jobs[font_id].status  = 'error';
        jobs[font_id].error   = (err.message || err.toString());
      } else {
        // remove job from the cache as we check filesystem
        // to decide whenever job is done or not
        delete jobs[font_id];
      }

      // push final checkpoint
      times.push(Date.now());

      // log some statistical info
      nodeca.logger.info(log_prefix + "Generated in " +
                         ((times[2] - times[0]) / 1000) + "ms " +
                         "(real: " + ((times[1] - times[0]) / 1000) + "ms)");

      stats.push({
        glyphs: config.glyphs.length,
        time:   (times[2] - times[0]) / 1000,
      });

      self.finished = true;
    });
  }
});

function get_job_data(id, job) {
  var data = {id: id, status: job.status};

  if ('error' === job.status) {
    data.error = job.error;
    // as long as user got info about error
    // remove the job from the cache
    delete jobs[id];
  }

  if ('enqueued' === job.status) {
    data.position = job_mgr.getPosition('generate-font', job.worker_id);

    if (-1 === data.position) {
      data.status = 'processing';
      delete data.position;
    }
  }

  if ('finished' === job.status) {
    data.url = job.url;
  }

  return data;
}

// request font generation status
module.exports.status = function (params, callback) {
  var self = this;

  get_job(params.id, function (job) {
    if (!job) {
      this.response.error = {
        code:     'UNKNOWN_FONT_ID',
        message:  "Unknown font id."
      };

      callback();
      return;
    }

    self.response.data = get_job_data(params.id, job);
    callback();
  });
};

// request font generation
module.exports.generate = function (params, callback) {
  var self = this, font = get_font_config(params), font_id, errmsg;

  if (!font || 0 >= font.glyphs.length) {
    callback("Invalid request");
    return;
  }

  if (CONFIG.max_glyphs && CONFIG.max_glyphs < font.glyphs.length) {
    errmsg = 'Too many icons requested: ' + font.glyphs.length +
             ' of ' + CONFIG.max_glyphs + ' allowed.';

    this.response.error = {
      code:     'MAX_GLYPHS_LIMIT',
      message:  errmsg
    };

    nodeca.logger.warn(errmsg);
    callback();
    return;
  }

  font_id = get_download_id(font);

  get_job(font_id, function (job) {
    // enqueue new unique job
    if (!job) {
      job = jobs[font_id] = {
        start:      Date.now(),
        status:     'enqueued',
        worker_id:  job_mgr.enqueue('generate-font', font_id, font)
      };

      nodeca.logger.info("New 'generate-font' job created: " + font_id);
    }

    self.response.data = get_job_data(font_id, job);
    callback();
  });
};
<commit_msg>Add info about jobs queue length<commit_after>/*global nodeca, _*/

"use strict";

// stdlib
var crypto    = require('crypto');
var os        = require('os');
var fs        = require('fs');
var path      = require('path');
var execFile  = require('child_process').execFile;

// 3rd-party
var neuron  = require('neuron');
var async   = require('nlib').Vendor.Async;
var fstools = require('nlib').Vendor.FsTools;

// internal
var stats   = require('../../lib/stats');

////////////////////////////////////////////////////////////////////////////////

var TMP_DIR             = '/tmp/fontello';
var APP_ROOT            = _.first(nodeca.runtime.apps).root;
var DOWNLOAD_DIR        = path.join(APP_ROOT, 'public/download/');
var DOWNLOAD_URL_PREFIX = "/download/";
var GENERATOR_BIN       = path.join(APP_ROOT, 'bin/generate_font.sh');
var CONFIG              = nodeca.config.fontomas;

////////////////////////////////////////////////////////////////////////////////

// return font configuration
var font_configs = null;
function get_embedded_font(name) {
  if (null === font_configs) {
    font_configs = {};
    nodeca.shared.fontomas.embedded_fonts.forEach(function (config) {
      font_configs[config.font.fontname] = config;
    });
  }

  return name ? font_configs[name] : font_configs;
}

var source_fonts;
function get_source_fonts() {
  var fonts_dir;

  if (!source_fonts) {
    source_fonts  = {};
    fonts_dir     = path.join(APP_ROOT, 'assets/embedded_fonts');

    _.each(get_embedded_font(), function (config, name) {
      source_fonts[name] = path.join(fonts_dir, name + '.ttf');
    });
  }

  return source_fonts;
}

// return valid glyphs configuration
function get_glyphs_config(params) {
  var glyphs = [];

  if (!_.isArray(params.glyphs)) {
    return glyphs;
  }

  _.each(params.glyphs, function (g) {
    var font = get_embedded_font(g.src), glyph;

    if (!font) {
      // unknown glyph source font
      return;
    }

    glyph = _.find(font.glyphs, function (config) {
      return config.code === g.from;
    });

    if (!glyph) {
      // unknown glyph code
      return;
    }

    glyphs.push({
      css:  g.css || glyph.css,
      src:  g.src,
      from: g.from,
      code: g.code || g.from
    });
  });

  if (0 === glyphs.length) {
    // at least one glyph is required
    return null;
  }

  // return glyphs config sorted by original codes
  return _.sortBy(glyphs, function (g) { return g.from; });
}

function format_timestring(d) {
  function pad(n) {
    return n < 10 ? ('0' + n) : n;
  }

  return  d.getUTCFullYear() + pad(d.getUTCMonth()+1) + pad(d.getUTCDate()) +
          pad(d.getUTCHours()) + pad(d.getUTCMinutes()) + pad(d.getUTCSeconds());
}

function format_font_fullname(name) {
  var parts = [];

  name.toLowerCase().split(/[\-_]/).forEach(function (p) {
    if (p) {
      parts.push(p.substr(0, 1).toUpperCase() + p.substr(1));
    }
  });

  return parts.join(' ');
}

function filter_fontname(str) {
  str = _.isString(str) ? str : '';
  return str.replace(/[^a-z0-9\-_]+/g, '-').trimLeft().trimRight();
}

function get_font_config(params) {
  var glyphs_config, timestamp, fontname;

  if (!_.isObject(params)) {
    return null;
  }

  glyphs_config = get_glyphs_config(params);
  timestamp     = format_timestring(new Date);
  fontname      = filter_fontname(params.name) || ('fontello-' + timestamp);

  return {
    font: {
      version:    timestamp,
      fontname:   fontname.toLowerCase(),
      fullname:   format_font_fullname(fontname),
      familyname: format_font_fullname(fontname).split(' ').shift(),
      copyright:  "Copyright (C) 2012 by original authors @ fontello.com",
      ascent:     800,
      descent:    200,
      weight:     "Medium"
    },
    meta: {
      columns: 4,
      css_prefix: 'icon-',
    },
    glyphs:     glyphs_config,
    src_fonts:  get_source_fonts()
  };
}

// returns unique ID for requested list of glyphs
function get_download_id(font) {
  var hash = crypto.createHash('md5');

  hash.update('fontello' + nodeca.runtime.version);
  hash.update(JSON.stringify(font));

  return hash.digest('hex');
}

function get_download_path(font_id) {
  var a, b;

  a = font_id.substr(0, 2);
  b = font_id.substr(2, 2);

  return [a, b, 'fontello-' + font_id].join("/") + ".zip";
}

function get_download_url(font_id) {
  return DOWNLOAD_URL_PREFIX + get_download_path(font_id);
}

// status of jobs by their ids
var jobs = {};

// returns instance of job (searches on FS if needed)
function get_job(font_id, callback) {
  var job = jobs[font_id],
      log_prefix = '[font::' + font_id + '] ',
      file;

  // return not-finished jobs as they are
  if (job) {
    nodeca.logger.debug(log_prefix + 'Job found in the cache: ' + JSON.stringify(job));
    callback(job);
    return;
  }

  file = path.join(DOWNLOAD_DIR, get_download_path(font_id));
  path.exists(file, function (result) {
    if (!result) {
      nodeca.logger.debug(log_prefix + 'Job not found and file not exists.');
      callback(/* undefined - job not found */);
      return;
    }

    nodeca.logger.debug(log_prefix + 'Job not found but download file exists.');
    callback({status: 'finished', url: get_download_url(font_id)});
  });
}

// define queue and jobs
var job_mgr = new (neuron.JobManager)();
job_mgr.addJob('generate-font', {
  dirname: '/tmp',
  concurrency: (CONFIG.builder_concurrency || os.cpus().length),
  work: function (font_id, config) {
    var self        = this,
        log_prefix  = '[font::' + font_id + '] ',
        tmp_dir     = path.join(TMP_DIR, "fontello-" + font_id),
        zipball     = path.join(DOWNLOAD_DIR, get_download_path(font_id)),
        times       = [jobs[font_id].start];

    nodeca.logger.info(log_prefix + 'Start generation: ' + JSON.stringify(config));

    // FIXME: after server restart this might become "undefined"
    //
    //        I'm still unsure WHAT might cause such behavior, as after restart
    //        manager should have empty stack.
    //
    //        Possible reason is that job removed by get_job_data().
    if (!jobs[font_id]) {
      jobs[font_id] = {start: Date.now(), status: 'enqueued'};
      times         = [jobs[font_id].start];

      nodeca.logger.error(log_prefix + "Unexpected absence of job.");
    }

    // push timer checkpoint
    times.push(Date.now());

    async.series([
      async.apply(fstools.remove, tmp_dir),
      async.apply(fstools.mkdir, tmp_dir),
      async.apply(fs.writeFile, path.join(tmp_dir, 'config.json'), JSON.stringify(config), 'utf8'),
      async.apply(execFile, GENERATOR_BIN, [config.font.fontname, tmp_dir, zipball], {cwd: APP_ROOT}),
      async.apply(fstools.remove, tmp_dir)
    ], function (err) {
      if (err) {
        nodeca.logger.error(log_prefix + (err.stack || err.message || err.toString()));

        jobs[font_id].status  = 'error';
        jobs[font_id].error   = (err.message || err.toString());
      } else {
        // remove job from the cache as we check filesystem
        // to decide whenever job is done or not
        delete jobs[font_id];
      }

      // push final checkpoint
      times.push(Date.now());

      // log some statistical info
      nodeca.logger.info(log_prefix + "Generated in " +
                         ((times[2] - times[0]) / 1000) + "ms " +
                         "(real: " + ((times[1] - times[0]) / 1000) + "ms)");

      stats.push({
        glyphs: config.glyphs.length,
        time:   (times[2] - times[0]) / 1000,
      });

      self.finished = true;
    });
  }
});

function get_job_data(id, job) {
  var data = {id: id, status: job.status};

  if ('error' === job.status) {
    data.error = job.error;
    // as long as user got info about error
    // remove the job from the cache
    delete jobs[id];
  }

  if ('enqueued' === job.status) {
    data.position = job_mgr.getPosition('generate-font', job.worker_id);

    if (-1 === data.position) {
      data.status = 'processing';
      delete data.position;
    }
  }

  if ('finished' === job.status) {
    data.url = job.url;
  }

  return data;
}

// request font generation status
module.exports.status = function (params, callback) {
  var self = this;

  get_job(params.id, function (job) {
    if (!job) {
      this.response.error = {
        code:     'UNKNOWN_FONT_ID',
        message:  "Unknown font id."
      };

      callback();
      return;
    }

    self.response.data = get_job_data(params.id, job);
    callback();
  });
};

// request font generation
module.exports.generate = function (params, callback) {
  var self = this, font = get_font_config(params), font_id, errmsg;

  if (!font || 0 >= font.glyphs.length) {
    callback("Invalid request");
    return;
  }

  if (CONFIG.max_glyphs && CONFIG.max_glyphs < font.glyphs.length) {
    errmsg = 'Too many icons requested: ' + font.glyphs.length +
             ' of ' + CONFIG.max_glyphs + ' allowed.';

    this.response.error = {
      code:     'MAX_GLYPHS_LIMIT',
      message:  errmsg
    };

    nodeca.logger.warn(errmsg);
    callback();
    return;
  }

  font_id = get_download_id(font);

  get_job(font_id, function (job) {
    // enqueue new unique job
    if (!job) {
      job = jobs[font_id] = {
        start:      Date.now(),
        status:     'enqueued',
        worker_id:  job_mgr.enqueue('generate-font', font_id, font)
      };

      nodeca.logger.info("New 'generate-font' job created: " + JSON.stringify({
        font_id:      font_id,
        jobs_length:  _.keys(jobs).length
      }));
    }

    self.response.data = get_job_data(font_id, job);
    callback();
  });
};
<|endoftext|>


<commit_before>"use strict";

/*global nodeca, _*/

var NLib = require('nlib');
var Async = NLib.Vendor.Async;

var Section = nodeca.models.forum.Section;
var Thread = nodeca.models.forum.Thread;

var forum_breadcrumbs = require('../../lib/forum_breadcrumbs.js');
var to_tree = require('../../lib/to_tree.js');

var threads_in_fields = {
  '_id': 1,
  'id': 1,
  'title': 1,
  'prefix': 1,
  'forum_id': 1,
  'cache': 1
};

var subforums_in_fields = {
  '_id' : 1,
  'id' : 1,
  'title' : 1,
  'description' : 1,
  'parent' : 1,
  'parent_list' : 1,
  'moderator_list' : 1,
  'display_order' : 1,
  'cache' : 1
};

var forum_info_out_fields = [
  'id',
  'title',
  'description',
  'is_category'
];

// Prefetch forum to simplify permisson check.
// Check that forum exists.
//
nodeca.filters.before('@', function (params, next) {
  var env = this;

  env.extras.puncher.start('Forum info prefetch');

  Section.findOne({id: params.id}).setOptions({ lean: true })
      .exec(function(err, forum) {

    if (err) {
      next(err);
      return;
    }

    // No forum -> "Not Found" status
    if (!forum) {
      next({ statusCode: 404 });
      return;
    }

    env.data.section = forum;

    env.extras.puncher.stop();

    next(err);
  });
});

//
// fetch and prepare threads and sub-forums(only on first page)
//
// ##### params
//
// - `id`   forum id
//
module.exports = function (params, next) {
  var env = this;
  var paginate_by;
  var sort = {};
  var ts_from = null;
  var ts_to = null;

  // fetch and prepare threads
  env.extras.puncher.start('Get threads');

  var max_threads = nodeca.settings.global.get('max_threads_per_page');

  var start = (params.page - 1) * max_threads;
  var end   = params.page * max_threads;

  if (env.session.hb) {
    paginate_by = 'cache.hb.last_ts';
  }
  else {
    paginate_by = 'cache.real.last_ts';
  }
  sort[paginate_by] = -1;

  // FIXME add state condition only visible thread
  var query = { forum_id: params.id };

  Async.series([
    // get start bourder
    function(callback){
      Thread.find(query).select('cache').sort(sort).skip(start)
          .limit(1).setOptions({ lean: true }).exec(function(err, docs) {

        // No page -> "Not Found" status
        if (!docs.length) {
          next({ statusCode: 404 });
          return;
        }

        if (env.session.hb) {
          ts_from = docs[0].cache.hb.last_ts;
        }
        else {
          ts_from = docs[0].cache.real.last_ts;
        }
        callback();
      });
    },
    // get end bourder
    function(callback){
      Thread.find(query).select('cache').sort(sort).skip(end)
          .limit(1).setOptions({ lean: true }).exec(function(err, docs) {
        if (docs.length) {
          if (env.session.hb) {
            ts_to = docs[0].cache.hb.last_ts;
          }
          else {
            ts_to = docs[0].cache.real.last_ts;
          }
        }
        callback();
      });
    },
    // fetch threads
    function(callback) {
      // FIXME modify state condition (deleted and etc) if user has permission
      if (!!ts_to) {
        query[paginate_by] = { $lt: ts_from, $gte: ts_to };
      }
      else {
        query[paginate_by] = { $lt: ts_from };
      }

      Thread.find(query).select(threads_in_fields).sort(sort)
          .setOptions({ lean: true }).exec(function(err, threads){
        if (err) {
          callback(err);
          return;
        }

        env.data.threads = threads;

        env.extras.puncher.stop(_.isArray(threads) ? { count: threads.length } : null);

        callback();
      });
    },
    // fetch sub-forums
    function(callback){
      env.data.sections = [];
      // subforums fetched only on first page
      if (params.page > 1) {
        callback();
        return;
      }
      env.extras.puncher.start('Get subforums');

      var max_level = env.data.section.level + 2; // need two next levels
      var query = {
        level: { $lte: max_level },
        parent_list: env.data.section._id
      };

      // FIXME add permissions check
      Section.find(query).select(subforums_in_fields).sort('display_order')
          .setOptions({ lean: true }).exec(function(err, sections){
        if (err) {
          callback(err);
          return;
        }
        env.data.sections = sections;

        env.extras.puncher.stop({ count: sections.length });

        callback();
      });
    },
  ], next);

};

// Build response:
//  - forums list -> filtered tree
//  - collect users ids (last posters / moderators / threads authors + last)
//  - threads
//
nodeca.filters.after('@', function (params, next) {
  var env = this;

  env.extras.puncher.start('Post-process forums/threads/users');

  //
  // Process sections
  //

  if (env.session.hb) {
    this.data.sections = this.data.sections.map(function(doc) {
      doc.cache.real = doc.cache.hb;
      return doc;
    });
  }

  var root = this.data.section._id;
  this.response.data.sections = to_tree(this.data.sections, root);

  env.data.users = env.data.users || [];

  // collect users from subforums
  this.data.sections.forEach(function(doc){
    if (!!doc.moderator_list) {
      doc.moderator_list.forEach(function(user) {
        env.data.users.push(user);
      });
    }
    if (doc.cache.real.last_user) {
      env.data.users.push(doc.cache.real.last_user);
    }
  });

  //
  // Process threads
  //

  if (env.session.hb) {
    this.data.threads = this.data.threads.map(function(doc) {
      doc.cache.real = doc.cache.hb;
      return doc;
    });
  }

  this.response.data.threads = this.data.threads;

  // collect users from threads
  this.data.threads.forEach(function(doc) {
    if (doc.cache.real.first_user) {
      env.data.users.push(doc.cache.real.first_user);
    }
    if (doc.cache.real.last_user) {
      env.data.users.push(doc.cache.real.last_user);
    }
  });

  env.extras.puncher.stop();

  next();
});

// Fill head meta & fetch/fill breadcrumbs
//
nodeca.filters.after('@', function (params, next) {
  var env = this;
  var data = this.response.data;
  var forum = this.data.section;

  if (this.session.hb) {
    forum.cache.real = forum.cache.hb;
  }
  // prepare page title
  data.head.title = forum.title;
  if (params.page > 1) {
    data.head.title += ' - ' + env.helpers.t('forum.head.page') + ' ' + params.page;
  }

  // prepare forum info
  data.forum = _.pick(forum, forum_info_out_fields);

  // prepare pagination data
  var max_threads = nodeca.settings.global.get('max_threads_per_page');
  data.max_page = Math.ceil(forum.cache.real.thread_count / max_threads);

  // fetch breadcrumbs data
  var query = { _id: { $in: forum.parent_list } };
  var fields = { '_id': 1, 'id': 1, 'title': 1 };

  env.extras.puncher.start('Build breadcrumbs');

  Section.find(query).select(fields).sort({ 'level': 1 })
      .setOptions({lean:true}).exec(function(err, parents){
    if (err) {
      next(err);
      return;
    }

    parents.push(forum);
    data.widgets.breadcrumbs = forum_breadcrumbs(env, parents);

    env.extras.puncher.stop();

    next();
  });
});
<commit_msg>Move sub-forums fetching to separate filter<commit_after>"use strict";

/*global nodeca, _*/

var NLib = require('nlib');
var Async = NLib.Vendor.Async;

var Section = nodeca.models.forum.Section;
var Thread = nodeca.models.forum.Thread;

var forum_breadcrumbs = require('../../lib/forum_breadcrumbs.js');
var to_tree = require('../../lib/to_tree.js');

var threads_in_fields = {
  '_id': 1,
  'id': 1,
  'title': 1,
  'prefix': 1,
  'forum_id': 1,
  'cache': 1
};

var subforums_in_fields = {
  '_id' : 1,
  'id' : 1,
  'title' : 1,
  'description' : 1,
  'parent' : 1,
  'parent_list' : 1,
  'moderator_list' : 1,
  'display_order' : 1,
  'cache' : 1
};

var forum_info_out_fields = [
  'id',
  'title',
  'description',
  'is_category'
];

// Prefetch forum to simplify permisson check.
// Check that forum exists.
//
nodeca.filters.before('@', function (params, next) {
  var env = this;

  env.extras.puncher.start('Forum info prefetch');

  Section.findOne({id: params.id}).setOptions({ lean: true })
      .exec(function(err, forum) {

    if (err) {
      next(err);
      return;
    }

    // No forum -> "Not Found" status
    if (!forum) {
      next({ statusCode: 404 });
      return;
    }

    env.data.section = forum;

    env.extras.puncher.stop();

    next(err);
  });
});

//
// fetch and prepare threads
//
// ##### params
//
// - `id`   forum id
//
module.exports = function (params, next) {
  var env = this;
  var paginate_by;
  var sort = {};
  var ts_from = null;
  var ts_to = null;

  // fetch and prepare threads
  env.extras.puncher.start('Get threads');

  var max_threads = nodeca.settings.global.get('max_threads_per_page');

  var start = (params.page - 1) * max_threads;
  var end   = params.page * max_threads;

  if (env.session.hb) {
    paginate_by = 'cache.hb.last_ts';
  }
  else {
    paginate_by = 'cache.real.last_ts';
  }
  sort[paginate_by] = -1;

  // FIXME add state condition only visible thread
  var query = { forum_id: params.id };

  Async.series([
    // get start bourder
    function(callback){
      Thread.find(query).select('cache').sort(sort).skip(start)
          .limit(1).setOptions({ lean: true }).exec(function(err, docs) {

        // No page -> "Not Found" status
        if (!docs.length) {
          next({ statusCode: 404 });
          return;
        }

        if (env.session.hb) {
          ts_from = docs[0].cache.hb.last_ts;
        }
        else {
          ts_from = docs[0].cache.real.last_ts;
        }
        callback();
      });
    },
    // get end bourder
    function(callback){
      Thread.find(query).select('cache').sort(sort).skip(end)
          .limit(1).setOptions({ lean: true }).exec(function(err, docs) {
        if (docs.length) {
          if (env.session.hb) {
            ts_to = docs[0].cache.hb.last_ts;
          }
          else {
            ts_to = docs[0].cache.real.last_ts;
          }
        }
        callback();
      });
    },
    // fetch threads
    function(callback) {
      // FIXME modify state condition (deleted and etc) if user has permission
      if (!!ts_to) {
        query[paginate_by] = { $lt: ts_from, $gte: ts_to };
      }
      else {
        query[paginate_by] = { $lt: ts_from };
      }

      Thread.find(query).select(threads_in_fields).sort(sort)
          .setOptions({ lean: true }).exec(function(err, threads){
        if (err) {
          callback(err);
          return;
        }

        env.data.threads = threads;

        env.extras.puncher.stop(_.isArray(threads) ? { count: threads.length } : null);

        callback();
      });
    },
    // fetch sub-forums
  ], next);

};

// fetch sub-forums (only on first page)
nodeca.filters.after('@', function (params, next) {
  var env = this;
  env.data.sections = [];
  // subforums fetched only on first page
  if (params.page > 1) {
    next();
    return;
  }
  env.extras.puncher.start('Get subforums');

  var max_level = env.data.section.level + 2; // need two next levels
  var query = {
    level: { $lte: max_level },
    parent_list: env.data.section._id
  };

  // FIXME add permissions check
  Section.find(query).select(subforums_in_fields).sort('display_order')
      .setOptions({ lean: true }).exec(function(err, sections){
    if (err) {
      next(err);
      return;
    }
    env.data.sections = sections;

    env.extras.puncher.stop({ count: sections.length });
    next();
  });
});

// Build response:
//  - forums list -> filtered tree
//  - collect users ids (last posters / moderators / threads authors + last)
//  - threads
//
nodeca.filters.after('@', function (params, next) {
  var env = this;

  env.extras.puncher.start('Post-process forums/threads/users');

  //
  // Process sections
  //

  if (env.session.hb) {
    this.data.sections = this.data.sections.map(function(doc) {
      doc.cache.real = doc.cache.hb;
      return doc;
    });
  }

  var root = this.data.section._id;
  this.response.data.sections = to_tree(this.data.sections, root);

  env.data.users = env.data.users || [];

  // collect users from subforums
  this.data.sections.forEach(function(doc){
    if (!!doc.moderator_list) {
      doc.moderator_list.forEach(function(user) {
        env.data.users.push(user);
      });
    }
    if (doc.cache.real.last_user) {
      env.data.users.push(doc.cache.real.last_user);
    }
  });

  //
  // Process threads
  //

  if (env.session.hb) {
    this.data.threads = this.data.threads.map(function(doc) {
      doc.cache.real = doc.cache.hb;
      return doc;
    });
  }

  this.response.data.threads = this.data.threads;

  // collect users from threads
  this.data.threads.forEach(function(doc) {
    if (doc.cache.real.first_user) {
      env.data.users.push(doc.cache.real.first_user);
    }
    if (doc.cache.real.last_user) {
      env.data.users.push(doc.cache.real.last_user);
    }
  });

  env.extras.puncher.stop();

  next();
});

// Fill head meta & fetch/fill breadcrumbs
//
nodeca.filters.after('@', function (params, next) {
  var env = this;
  var data = this.response.data;
  var forum = this.data.section;

  if (this.session.hb) {
    forum.cache.real = forum.cache.hb;
  }
  // prepare page title
  data.head.title = forum.title;
  if (params.page > 1) {
    data.head.title += ' - ' + env.helpers.t('forum.head.page') + ' ' + params.page;
  }

  // prepare forum info
  data.forum = _.pick(forum, forum_info_out_fields);

  // prepare pagination data
  var max_threads = nodeca.settings.global.get('max_threads_per_page');
  data.max_page = Math.ceil(forum.cache.real.thread_count / max_threads);

  // fetch breadcrumbs data
  var query = { _id: { $in: forum.parent_list } };
  var fields = { '_id': 1, 'id': 1, 'title': 1 };

  env.extras.puncher.start('Build breadcrumbs');

  Section.find(query).select(fields).sort({ 'level': 1 })
      .setOptions({lean:true}).exec(function(err, parents){
    if (err) {
      next(err);
      return;
    }

    parents.push(forum);
    data.widgets.breadcrumbs = forum_breadcrumbs(env, parents);

    env.extras.puncher.stop();

    next();
  });
});
<|endoftext|>


<commit_before>// (C) Copyright 2014-2015 Hewlett-Packard Development Company, L.P.

var path = require('path');
var __path__ = path.join(__dirname, '../../src/js/components/RadioButton');

var GrommetTestUtils = require('../../src/utils/test/GrommetTestUtils');

describe('Grommet RadioButton', function() {
  it('loads a basic RadioButton', function() {
    var Component = GrommetTestUtils.getComponent(__path__, undefined, {
      id: 'radio-id',
      label: 'Test Label'
    });

    GrommetTestUtils.componentShouldExist(Component, 'radio-button');
    GrommetTestUtils.componentShouldExist(Component,
      'radio-button__label', 'Test Label');
  });

});
<commit_msg>Added tests for RadioButton Component<commit_after>// (C) Copyright 2014-2015 Hewlett-Packard Development Company, L.P.

var path = require('path');
var __path__ = path.join(__dirname, '../../src/js/components/RadioButton');

var GrommetTestUtils = require('../../src/utils/test/GrommetTestUtils');

describe('Grommet RadioButton', function() {
  it('loads a basic RadioButton', function() {
    var Component = GrommetTestUtils.getComponent(__path__, undefined, {
      id: 'radio-id',
      label: 'Test Label'
    });

    GrommetTestUtils.componentShouldExist(Component, 'radio-button');
    GrommetTestUtils.componentShouldExist(Component,
      'radio-button__label', 'Test Label');
  });

  it('loads a disabled RadioButton', function () {
    var Component = GrommetTestUtils.getComponent(__path__, undefined, {
      id: 'radio-id',
      label: 'Test Label',
      disabled: true
    });

    GrommetTestUtils.componentShouldExist(Component, 'radio-button');
    GrommetTestUtils.componentShouldExist(Component,
      'radio-button__label', 'Test Label');
    GrommetTestUtils.componentShouldExist(Component, 'radio-button--disabled');
  });

  it('loads a basic RadioButton with an additional class', function() {
    var Component = GrommetTestUtils.getComponent(__path__, undefined, {
      id: 'radio-id',
      label: 'Test Label',
      className: 'test'
    });

    GrommetTestUtils.componentShouldExist(Component, 'radio-button');
    GrommetTestUtils.componentShouldExist(Component, 'test');
    GrommetTestUtils.componentShouldExist(Component,
      'radio-button__label', 'Test Label');
  });

});
<|endoftext|>


<commit_before>// ==UserScript==
// @name            Phim4400 Helper
// @name:vi         Phim4400 Helper
// @namespace       https://lelinhtinh.github.io
// @description     Xem online và tải phim trực tiếp tại Phim4400, bỏ qua quảng cáo.
// @description:vi  Xem online và tải phim trực tiếp tại Phim4400, bỏ qua quảng cáo.
// @version         1.0.2
// @icon            https://i.imgur.com/wRRkkqr.png
// @author          lelinhtinh
// @oujs:author     baivong
// @license         MIT; https://baivong.mit-license.org/license.txt
// @match           https://phim440.cf/*
// @match           https://phim4400.cf/*
// @match           https://phim4400.tv/*
// @noframes
// @supportURL      https://github.com/lelinhtinh/Userscript/issues
// @run-at          document-idle
// @grant           none
// ==/UserScript==

/* global shortcut */
(function() {
  'use strict';

  document.oncontextmenu = null;
  if (shortcut) {
    shortcut.remove('Ctrl+U');
    shortcut.remove('F12');
    shortcut.remove('Ctrl+Shift+I');
    shortcut.remove('Ctrl+S');
    shortcut.remove('Ctrl+Shift+C');
  }

  var btn = document.querySelector('.button-phim'),
    link = btn.href;

  link = /(&|\?)url=([^&\s\n?#!]+)/.exec(link);
  if (!link) return;
  link = link[2];
  if (!link) return;
  link = atob(link);
  btn.href = link;
  btn.setAttribute('target', '_top');
})();
<commit_msg>Download VIP<commit_after>// ==UserScript==
// @name            Phim4400 Helper
// @name:vi         Phim4400 Helper
// @namespace       https://lelinhtinh.github.io
// @description     Xem online và tải phim trực tiếp tại Phim4400, bỏ qua quảng cáo.
// @description:vi  Xem online và tải phim trực tiếp tại Phim4400, bỏ qua quảng cáo.
// @version         1.1.0
// @icon            https://i.imgur.com/wRRkkqr.png
// @author          lelinhtinh
// @oujs:author     baivong
// @license         MIT; https://baivong.mit-license.org/license.txt
// @match           https://phim440.cf/*
// @match           https://phim4400.cf/*
// @match           https://phim4400.tv/*
// @noframes
// @supportURL      https://github.com/lelinhtinh/Userscript/issues
// @run-at          document-idle
// @grant           none
// ==/UserScript==

/* global shortcut */
(function() {
  'use strict';

  document.oncontextmenu = null;
  if (shortcut) {
    shortcut.remove('Ctrl+U');
    shortcut.remove('F12');
    shortcut.remove('Ctrl+Shift+I');
    shortcut.remove('Ctrl+S');
    shortcut.remove('Ctrl+Shift+C');
  }

  var btn = document.querySelector('.button-phim'),
    link = document.querySelector('.fb-comments');

  if (link === null) return;
  link = link.dataset.href;
  if (link.indexOf('/phim/') === -1) return;

  if (btn === null) {
    btn = document.createElement('A');
    btn.textContent = 'Tải Phim VIP';
    document.querySelector('.button-info').appendChild(btn);
  }

  btn.className = 'button-phim uk-button uk-button-secondary uk-dropdown-right';
  btn.setAttribute('target', '_top');

  btn.href = link;
})();
<|endoftext|>


<commit_before>var socketio = require('socket.io');
var Box2D = require('box2dweb');
var MathUtil = require('./math_helpers');

const ARTIFICIAL_LATENCY_FACTOR = 1; // Make it 1 for no fake latency
const UPDATE_INTERVAL = 1/60;
const STAGE_WIDTH = 1000; // px
const STAGE_HEIGHT = 900; // px
const PX_PER_METER = 100; // conversion
var state = {
	world: null,
	bodies: [] // instances of b2Body (from Box2D)
};
var definitions = {
	polyFixture: new Box2D.Dynamics.b2FixtureDef(),
	circleFixture: new Box2D.Dynamics.b2FixtureDef(),
	bodyDef: new Box2D.Dynamics.b2BodyDef()
};
var io = null;

var game = {
	init: function (server) {
		this.initNetwork(server);
		var gravity = new Box2D.Common.Math.b2Vec2(0, 0);
		state.world = new Box2D.Dynamics.b2World(gravity,  true);

		this.initFixtures();

		this.addShip({pos: {x: STAGE_WIDTH/2/PX_PER_METER, y: STAGE_HEIGHT/2/PX_PER_METER}});

		setInterval(this.step.bind(this), UPDATE_INTERVAL * 1000);
		setInterval(this.sync.bind(this), UPDATE_INTERVAL * 1000 * ARTIFICIAL_LATENCY_FACTOR);
	},
	initFixtures: function () {
		definitions.circleFixture.shape = new Box2D.Collision.Shapes.b2CircleShape();
		definitions.circleFixture.density = 1;
		definitions.circleFixture.restitution = 0.7;
	},
	initInputHandling: function (socket) {
		socket.on('move', function (vector) {
			state.bodies[0].ApplyForce(new Box2D.Common.Math.b2Vec2(vector.x * 10, vector.y * 10), state.bodies[0].GetWorldCenter());
		});
	},
	initNetwork: function (server) {
		var _g = this;
		io = socketio.listen(server);
		io.set('log level', 1);
		io.sockets.on('connection', function (socket) {
			console.log("Connection: ", socket.id);
			_g.initInputHandling(socket);
			socket.on('disconnect', function () {
				console.log("Disconnect: ", socket.id);
			});
		});
	},
	addShip: function (params) {
		params = params || {};
		var pos = params.pos || {x: 0, y: 0},
			body,
			size;

		definitions.bodyDef.type = Box2D.Dynamics.b2Body.b2_dynamicBody;
		definitions.bodyDef.position.Set(pos.x, pos.y);
		body = state.world.CreateBody(definitions.bodyDef);

		size = 50;
		definitions.circleFixture.shape.SetRadius(size / 2 / PX_PER_METER);
		body.CreateFixture(definitions.circleFixture);

		state.bodies.push(body);
	},
	sync: function () {
		var data = [];
		const n = state.bodies.length;
		for (var i = 0; i < n; i++) {
			var body  = state.bodies[i];
			var position = body.GetPosition();
			var velocity = body.GetLinearVelocity();
			var angular_velocity = body.GetAngularVelocity();
			data.push({ x: position.x,
									y: position.y,
									rot: body.GetAngle(),
									x_vel: velocity.x,
									y_vel: velocity.y,
									a_vel: angular_velocity });
		}
		io.sockets.emit('update', data);
	},
	step: function () {
		state.world.Step(UPDATE_INTERVAL, 3, 3);
		state.world.ClearForces();
	}
};

module.exports = game<commit_msg>Moved server to use object ids for bodies<commit_after>var socketio = require('socket.io');
var Box2D = require('box2dweb');
var MathUtil = require('./math_helpers');

const ARTIFICIAL_LATENCY_FACTOR = 1; // Make it 1 for no fake latency
const UPDATE_INTERVAL = 1/60;
const STAGE_WIDTH = 1000; // px
const STAGE_HEIGHT = 900; // px
const PX_PER_METER = 100; // conversion
var state = {
	world: null,
	bodies: {}, // instances of b2Body (from Box2D)
	players: {}
};
var definitions = {
	polyFixture: new Box2D.Dynamics.b2FixtureDef(),
	circleFixture: new Box2D.Dynamics.b2FixtureDef(),
	bodyDef: new Box2D.Dynamics.b2BodyDef()
};
var io = null;
var object_tracker = 0; // Increments every time a new object is added

var game = {
	init: function (server) {
		this.initNetwork(server);
		var gravity = new Box2D.Common.Math.b2Vec2(0, 0);
		state.world = new Box2D.Dynamics.b2World(gravity,  true);

		this.initFixtures();

		setInterval(this.step.bind(this), UPDATE_INTERVAL * 1000);
		setInterval(this.sync.bind(this), UPDATE_INTERVAL * 1000 * ARTIFICIAL_LATENCY_FACTOR);
	},
	initFixtures: function () {
		definitions.circleFixture.shape = new Box2D.Collision.Shapes.b2CircleShape();
		definitions.circleFixture.density = 1;
		definitions.circleFixture.restitution = 0.7;
	},
	initInputHandling: function (socket) {
		// socket.on('move', function (vector) {
		// 	var body = state.bodies[players[socket.id].ship_id];
		// 	body.ApplyForce(new Box2D.Common.Math.b2Vec2(vector.x * 10, vector.y * 10), body.GetWorldCenter());
		// });
		socket.on('set_destination', function (loc) {
			// var body = state.bodies[players[socket.id].ship_id];
			// body.ApplyForce(new Box2D.Common.Math.b2Vec2(vector.x * 10, vector.y * 10), body.GetWorldCenter());
			console.log('set dest: ', loc);
		});
	},
	initNetwork: function (server) {
		var _g = this;
		io = socketio.listen(server);
		io.set('log level', 1);
		io.sockets.on('connection', function (new_socket) {
			console.log("Connection: ", new_socket.id);
			_g.initInputHandling(new_socket);
			var ship_type = 'avenger';
			var other_objects = [];
			for (var s in _g.state.players) {
				if (_g.state.players.hasOwnProperty(s)) {
					var player = _g.state.players[s];
					other_objects.push({type: ship_type, id: player.ship_id});
				}
			}
			// To new player: create objects that exist on the server
			new_socket.emit('make_objects', other_objects);
			var ship_id = this.addShip();
			players[new_socket.id] = { socket: new_socket, type: ship_type, ship_id: ship_id };

			// To everyone: create a new ship for the new player
			sockets.emit('make_objects', [{type: ship_type, id: ship_id}]);

			// To new player: assign control of the new ship
			new_socket.emit('assign_ship', ship_id);
			new_socket.on('disconnect', function () {
				console.log("Disconnect: ", new_socket.id);
				delete players[new_socket.id];
			});
		});
	},
	addShip: function (params) {
		params = params || {};
		var pos = params.pos || {x: 0, y: 0},
			body,
			size;

		definitions.bodyDef.type = Box2D.Dynamics.b2Body.b2_dynamicBody;
		definitions.bodyDef.position.Set(pos.x, pos.y);
		body = state.world.CreateBody(definitions.bodyDef);

		size = 50;
		definitions.circleFixture.shape.SetRadius(size / 2 / PX_PER_METER);
		body.CreateFixture(definitions.circleFixture);

		var id = object_tracker;
		state.bodies[id] = body;
		object_tracker++;
		return id;
	},
	sync: function () {
		var data = [];
		for (var b_idx in state.bodies) {
			if (state.bodies.hasOwnProperty(b_idx)) {
				var body = state.bodies[b_idx];
				var position = body.GetPosition();
				var velocity = body.GetLinearVelocity();
				var angular_velocity = body.GetAngularVelocity();
				data.push({ id: b_idx,
										x: position.x,
										y: position.y,
										rot: body.GetAngle(),
										x_vel: velocity.x,
										y_vel: velocity.y,
										a_vel: angular_velocity });
			}
		}
		io.sockets.emit('update', data);
	},
	step: function () {
		state.world.Step(UPDATE_INTERVAL, 3, 3);
		state.world.ClearForces();
	}
};

module.exports = game<|endoftext|>


<commit_before>describe("A leapConfig Value", function () {
    beforeEach(module('angular-leap'));

    it("should contain a defaultTimeout value", inject(function (leapConfig) {
        expect(leapConfig.defaultTimeout).toBeDefined();
    }));

    it("should contain a defaultTimeout value of 650", inject(function (leapConfig) {
        expect(leapConfig.defaultTimeout).toBe(650);
    }));

});<commit_msg>add test for defaultGestureIntense<commit_after>describe("A leapConfig Value", function () {
    beforeEach(module('angular-leap'));

    it("should contain a defaultTimeout value", inject(function (leapConfig) {
        expect(leapConfig.defaultTimeout).toBeDefined();
    }));

    it("should contain a defaultTimeout value of 650", inject(function (leapConfig) {
        expect(leapConfig.defaultTimeout).toBe(650);
    }));

    it("should contain a defaultTimeout value", inject(function (leapConfig) {
        expect(leapConfig.defaultGestureIntense).toBeDefined();
    }));

    it("should contain a defaultTimeout value of 650", inject(function (leapConfig) {
        expect(leapConfig.defaultGestureIntense).toBe(0.5);
    }));
});<|endoftext|>


<commit_before>'use strict';

var bitcore = require('bitcore-lib');
var _ = bitcore.deps._;
var $ = bitcore.util.preconditions;
var common = require('./common');
var async = require('async');

var MAXINT = 0xffffffff; // Math.pow(2, 32) - 1;

function TxController(node) {
  this.node = node;
}

TxController.prototype.show = function(req, res) {
  if (req.transaction) {
    res.jsonp(req.transaction);
  }
};

/**
 * Find transaction by hash ...
 */
TxController.prototype.transaction = function(req, res, next, txid) {
  var self = this;

  this.node.getTransactionWithBlockInfo(txid, true, function(err, transaction) {
    if (err && err instanceof self.node.errors.Transaction.NotFound) {
      return common.handleErrors(null, res);
    } else if(err) {
      return common.handleErrors(err, res);
    }

    transaction.populateInputs(self.node.services.bitcoind, [], function(err) {
      if(err) {
        return res.send({
          error: err.toString()
        });
      }

      self.transformTransaction(transaction, function(err, transformedTransaction) {
        if (err) {
          return common.handleErrors(err, res);
        }
        req.transaction = transformedTransaction;
        next();
      });

    });
  });
};

TxController.prototype.transformTransaction = function(transaction, callback) {
  $.checkArgument(_.isFunction(callback));
  var self = this;
  var txid = transaction.id;
  var txObj = transaction.toObject();

  var confirmations = 0;
  if(transaction.__height >= 0) {
    confirmations = this.node.services.bitcoind.height - transaction.__height + 1;
  }

  var transformed = {
    txid: txObj.hash,
    version: txObj.version,
    locktime: txObj.nLockTime
  };

  if(transaction.isCoinbase()) {
    transformed.vin = [
      {
        coinbase: txObj.inputs[0].script,
        sequence: txObj.inputs[0].sequenceNumber,
        n: 0
      }
    ];
  } else {
    transformed.vin = txObj.inputs.map(this.transformInput.bind(this));
  }

  async.map(
    Object.keys(txObj.outputs),
    function(outputIndex, next) {
      outputIndex = parseInt(outputIndex);
      var output = txObj.outputs[outputIndex];
      self.transformOutput(txid, output, outputIndex, next);
    },
    function(err, vout) {
      if (err) {
        return callback(err);
      }

      transformed.vout = vout;

      transformed.blockhash = transaction.__blockHash;
      transformed.blockheight = transaction.__height;
      transformed.confirmations = confirmations;
      var time = transaction.__timestamp ? transaction.__timestamp : Math.round(Date.now() / 1000);
      transformed.time = time;
      if (transformed.confirmations) {
        transformed.blocktime = transformed.time;
      }

      if(transaction.isCoinbase()) {
        transformed.isCoinBase = true;
      }

      transformed.valueOut = transaction.outputAmount / 1e8;
      transformed.size = transaction.toBuffer().length;
      if(transaction.hasAllUtxoInfo()) {
        transformed.valueIn = transaction.inputAmount / 1e8;
        transformed.fees = transaction.getFee() / 1e8;
      }

      callback(null, transformed);

    }
  );

};

TxController.prototype.transformInput = function(input, index) {
  // Input scripts are validated and can be assumed to be valid
  var script = new bitcore.Script(input.script);
  var transformed = {
    txid: input.prevTxId,
    vout: input.outputIndex,
    scriptSig: {
      asm: script.toASM(),
      hex: input.script
    },
    sequence: input.sequenceNumber,
    n: index
  };

  if(input.output) {
    transformed.addr = bitcore.Script(input.output.script).toAddress(this.node.network).toString();
    transformed.valueSat = input.output.satoshis;
    transformed.value = input.output.satoshis / 1e8;
    transformed.doubleSpentTxID = null; // TODO
    //transformed.isConfirmed = null; // TODO
    //transformed.confirmations = null; // TODO
    //transformed.unconfirmedInput = null; // TODO
  }

  return transformed;
};

TxController.prototype.transformOutput = function(txid, output, index, callback) {
  var self = this;
  var transformed = {
    value: (output.satoshis / 1e8).toFixed(8),
    n: index,
    scriptPubKey: {
      hex: output.script,
      //reqSigs: null, // TODO
    }
    //spentTs: undefined // TODO
  };

  var script;
  try {
    // Output scripts can be invalid, so we need to try/catch
    script = new bitcore.Script(output.script);
  } catch (err) {
    script = false;
  }
  if (script) {
    transformed.scriptPubKey.asm = script.toASM();
    var address = script.toAddress(this.node.network);
    if (address) {
      transformed.scriptPubKey.addresses = [address.toString()];
      transformed.scriptPubKey.type = address.type;
    }
  }

  var options = {
    queryMempool: true
  };

  self.node.services.bitcoind.getInputForOutput(
    txid,
    index,
    options,
    function(err, inputResult) {
      if (err) {
        return callback(err);
      }
      if (inputResult) {
        transformed.spentTxId = inputResult.inputTxId;
        transformed.spentIndex = inputResult.inputIndex;
      }
      callback(null, transformed);
    }
  );
};

TxController.prototype.transformInvTransaction = function(transaction) {
  var self = this;

  var valueOut = 0;
  var vout = [];
  for (var i = 0; i < transaction.outputs.length; i++) {
    var output = transaction.outputs[i];
    valueOut += output.satoshis;
    if (output.script) {
      var address = output.script.toAddress(self.node.network);
      if (address) {
        var obj = {};
        obj[address.toString()] = output.satoshis;
        vout.push(obj);
      }
    }
  }

  var isRBF = _.any(_.pluck(transaction.inputs, 'sequenceNumber'), function(seq) {
    return seq < MAXINT - 1;
  });

  var transformed = {
    txid: transaction.hash,
    valueOut: valueOut / 1e8,
    vout: vout,
    isRBF: isRBF,
  };

  return transformed;
};

TxController.prototype.rawTransaction = function(req, res, next, txid) {
  var self = this;

  this.node.getTransaction(txid, true, function(err, transaction) {
    if (err && err instanceof self.node.errors.Transaction.NotFound) {
      return common.handleErrors(null, res);
    } else if(err) {
      return common.handleErrors(err, res);
    }

    req.rawTransaction = {
      'rawtx': transaction.toBuffer().toString('hex')
    };

    next();
  });
};

TxController.prototype.showRaw = function(req, res) {
  if (req.rawTransaction) {
    res.jsonp(req.rawTransaction);
  }
};

TxController.prototype.list = function(req, res) {
  var self = this;

  var blockHash = req.query.block;
  var address = req.query.address;
  var page = parseInt(req.query.pageNum) || 0;
  var pageLength = 10;
  var pagesTotal = 1;

  if(blockHash) {
    self.node.getBlock(blockHash, function(err, block) {
      if(err && err.message === 'Block not found.') {
        return common.handleErrors(null, res);
      } else if(err) {
        return common.handleErrors(err, res);
      }

      self.node.services.bitcoind.getBlockHeader(block.hash, function(err, blockInfo) {
        var txs = block.transactions;
        var totalTxs = txs.length;

        if(!_.isUndefined(page)) {
          txs = txs.splice(page * pageLength, pageLength);
          pagesTotal = Math.ceil(totalTxs / pageLength);
        }

        async.mapSeries(txs, function(tx, next) {
          tx.__blockHash = block.hash;
          tx.__height = blockInfo.height;
          tx.__timestamp = block.header.time;

          tx.populateInputs(self.node.services.bitcoind, [], function(err) {
            if(err) {
              return next(err);
            }
            self.transformTransaction(tx, next);
          });
        }, function(err, transformed) {
          if(err) {
            return common.handleErrors(err, res);
          }

          res.jsonp({
            pagesTotal: pagesTotal,
            txs: transformed
          });
        });

      });

    });
  } else if(address) {
    var options = {
      from: page * pageLength,
      to: (page + 1) * pageLength
    };

    self.node.getAddressHistory(address, options, function(err, result) {
      if(err) {
        return common.handleErrors(err, res);
      }

      var txs = result.items.map(function(info) {
        return info.tx;
      }).filter(function(value, index, self) {
        return self.indexOf(value) === index;
      });

      async.map(
        txs,
        function(tx, next) {
          self.transformTransaction(tx, next);
        },
        function(err, transformed) {
          if (err) {
            return common.handleErrors(err, res);
          }
          res.jsonp({
            pagesTotal: Math.ceil(result.totalCount / pageLength),
            txs: transformed
          });
        }
      );
    });
  } else {
    return common.handleErrors(new Error('Block hash or address expected'), res);
  }
};

TxController.prototype.send = function(req, res) {
  this.node.sendTransaction(req.body.rawtx, function(err, txid) {
    if(err) {
      // TODO handle specific errors
      return common.handleErrors(err, res);
    }

    res.json({'txid': txid});
  });
};

module.exports = TxController;
<commit_msg>fixed bug that was removing transactions from the cached block<commit_after>'use strict';

var bitcore = require('bitcore-lib');
var _ = bitcore.deps._;
var $ = bitcore.util.preconditions;
var common = require('./common');
var async = require('async');

var MAXINT = 0xffffffff; // Math.pow(2, 32) - 1;

function TxController(node) {
  this.node = node;
}

TxController.prototype.show = function(req, res) {
  if (req.transaction) {
    res.jsonp(req.transaction);
  }
};

/**
 * Find transaction by hash ...
 */
TxController.prototype.transaction = function(req, res, next, txid) {
  var self = this;

  this.node.getTransactionWithBlockInfo(txid, true, function(err, transaction) {
    if (err && err instanceof self.node.errors.Transaction.NotFound) {
      return common.handleErrors(null, res);
    } else if(err) {
      return common.handleErrors(err, res);
    }

    transaction.populateInputs(self.node.services.bitcoind, [], function(err) {
      if(err) {
        return res.send({
          error: err.toString()
        });
      }

      self.transformTransaction(transaction, function(err, transformedTransaction) {
        if (err) {
          return common.handleErrors(err, res);
        }
        req.transaction = transformedTransaction;
        next();
      });

    });
  });
};

TxController.prototype.transformTransaction = function(transaction, callback) {
  $.checkArgument(_.isFunction(callback));
  var self = this;
  var txid = transaction.id;
  var txObj = transaction.toObject();

  var confirmations = 0;
  if(transaction.__height >= 0) {
    confirmations = this.node.services.bitcoind.height - transaction.__height + 1;
  }

  var transformed = {
    txid: txObj.hash,
    version: txObj.version,
    locktime: txObj.nLockTime
  };

  if(transaction.isCoinbase()) {
    transformed.vin = [
      {
        coinbase: txObj.inputs[0].script,
        sequence: txObj.inputs[0].sequenceNumber,
        n: 0
      }
    ];
  } else {
    transformed.vin = txObj.inputs.map(this.transformInput.bind(this));
  }

  async.map(
    Object.keys(txObj.outputs),
    function(outputIndex, next) {
      outputIndex = parseInt(outputIndex);
      var output = txObj.outputs[outputIndex];
      self.transformOutput(txid, output, outputIndex, next);
    },
    function(err, vout) {
      if (err) {
        return callback(err);
      }

      transformed.vout = vout;

      transformed.blockhash = transaction.__blockHash;
      transformed.blockheight = transaction.__height;
      transformed.confirmations = confirmations;
      var time = transaction.__timestamp ? transaction.__timestamp : Math.round(Date.now() / 1000);
      transformed.time = time;
      if (transformed.confirmations) {
        transformed.blocktime = transformed.time;
      }

      if(transaction.isCoinbase()) {
        transformed.isCoinBase = true;
      }

      transformed.valueOut = transaction.outputAmount / 1e8;
      transformed.size = transaction.toBuffer().length;
      if(transaction.hasAllUtxoInfo()) {
        transformed.valueIn = transaction.inputAmount / 1e8;
        transformed.fees = transaction.getFee() / 1e8;
      }

      callback(null, transformed);

    }
  );

};

TxController.prototype.transformInput = function(input, index) {
  // Input scripts are validated and can be assumed to be valid
  var script = new bitcore.Script(input.script);
  var transformed = {
    txid: input.prevTxId,
    vout: input.outputIndex,
    scriptSig: {
      asm: script.toASM(),
      hex: input.script
    },
    sequence: input.sequenceNumber,
    n: index
  };

  if(input.output) {
    transformed.addr = bitcore.Script(input.output.script).toAddress(this.node.network).toString();
    transformed.valueSat = input.output.satoshis;
    transformed.value = input.output.satoshis / 1e8;
    transformed.doubleSpentTxID = null; // TODO
    //transformed.isConfirmed = null; // TODO
    //transformed.confirmations = null; // TODO
    //transformed.unconfirmedInput = null; // TODO
  }

  return transformed;
};

TxController.prototype.transformOutput = function(txid, output, index, callback) {
  var self = this;
  var transformed = {
    value: (output.satoshis / 1e8).toFixed(8),
    n: index,
    scriptPubKey: {
      hex: output.script,
      //reqSigs: null, // TODO
    }
    //spentTs: undefined // TODO
  };

  var script;
  try {
    // Output scripts can be invalid, so we need to try/catch
    script = new bitcore.Script(output.script);
  } catch (err) {
    script = false;
  }
  if (script) {
    transformed.scriptPubKey.asm = script.toASM();
    var address = script.toAddress(this.node.network);
    if (address) {
      transformed.scriptPubKey.addresses = [address.toString()];
      transformed.scriptPubKey.type = address.type;
    }
  }

  var options = {
    queryMempool: true
  };

  self.node.services.bitcoind.getInputForOutput(
    txid,
    index,
    options,
    function(err, inputResult) {
      if (err) {
        return callback(err);
      }
      if (inputResult) {
        transformed.spentTxId = inputResult.inputTxId;
        transformed.spentIndex = inputResult.inputIndex;
      }
      callback(null, transformed);
    }
  );
};

TxController.prototype.transformInvTransaction = function(transaction) {
  var self = this;

  var valueOut = 0;
  var vout = [];
  for (var i = 0; i < transaction.outputs.length; i++) {
    var output = transaction.outputs[i];
    valueOut += output.satoshis;
    if (output.script) {
      var address = output.script.toAddress(self.node.network);
      if (address) {
        var obj = {};
        obj[address.toString()] = output.satoshis;
        vout.push(obj);
      }
    }
  }

  var isRBF = _.any(_.pluck(transaction.inputs, 'sequenceNumber'), function(seq) {
    return seq < MAXINT - 1;
  });

  var transformed = {
    txid: transaction.hash,
    valueOut: valueOut / 1e8,
    vout: vout,
    isRBF: isRBF,
  };

  return transformed;
};

TxController.prototype.rawTransaction = function(req, res, next, txid) {
  var self = this;

  this.node.getTransaction(txid, true, function(err, transaction) {
    if (err && err instanceof self.node.errors.Transaction.NotFound) {
      return common.handleErrors(null, res);
    } else if(err) {
      return common.handleErrors(err, res);
    }

    req.rawTransaction = {
      'rawtx': transaction.toBuffer().toString('hex')
    };

    next();
  });
};

TxController.prototype.showRaw = function(req, res) {
  if (req.rawTransaction) {
    res.jsonp(req.rawTransaction);
  }
};

TxController.prototype.list = function(req, res) {
  var self = this;

  var blockHash = req.query.block;
  var address = req.query.address;
  var page = parseInt(req.query.pageNum) || 0;
  var pageLength = 10;
  var pagesTotal = 1;

  if(blockHash) {
    self.node.getBlock(blockHash, function(err, block) {
      if(err && err.message === 'Block not found.') {
        return common.handleErrors(null, res);
      } else if(err) {
        return common.handleErrors(err, res);
      }

      self.node.services.bitcoind.getBlockHeader(block.hash, function(err, blockInfo) {
        var totalTxs = block.transactions.length;
        var txs;

        if(!_.isUndefined(page)) {
          var start = page * pageLength;
          txs = block.transactions.slice(start, start + pageLength);
          pagesTotal = Math.ceil(totalTxs / pageLength);
        } else {
          txs = block.transactions;
        }

        async.mapSeries(txs, function(tx, next) {
          tx.__blockHash = block.hash;
          tx.__height = blockInfo.height;
          tx.__timestamp = block.header.time;

          tx.populateInputs(self.node.services.bitcoind, [], function(err) {
            if(err) {
              return next(err);
            }
            self.transformTransaction(tx, next);
          });
        }, function(err, transformed) {
          if(err) {
            return common.handleErrors(err, res);
          }

          res.jsonp({
            pagesTotal: pagesTotal,
            txs: transformed
          });
        });

      });

    });
  } else if(address) {
    var options = {
      from: page * pageLength,
      to: (page + 1) * pageLength
    };

    self.node.getAddressHistory(address, options, function(err, result) {
      if(err) {
        return common.handleErrors(err, res);
      }

      var txs = result.items.map(function(info) {
        return info.tx;
      }).filter(function(value, index, self) {
        return self.indexOf(value) === index;
      });

      async.map(
        txs,
        function(tx, next) {
          self.transformTransaction(tx, next);
        },
        function(err, transformed) {
          if (err) {
            return common.handleErrors(err, res);
          }
          res.jsonp({
            pagesTotal: Math.ceil(result.totalCount / pageLength),
            txs: transformed
          });
        }
      );
    });
  } else {
    return common.handleErrors(new Error('Block hash or address expected'), res);
  }
};

TxController.prototype.send = function(req, res) {
  this.node.sendTransaction(req.body.rawtx, function(err, txid) {
    if(err) {
      // TODO handle specific errors
      return common.handleErrors(err, res);
    }

    res.json({'txid': txid});
  });
};

module.exports = TxController;
<|endoftext|>


<commit_before>'use strict';

const path = require('path');
const koa = require('koa');
const fs = require('fs-extra');
const { post } = require('../../fetch');
const proxyquire = require('proxyquire').noPreserveCache();
const basePath = '/tmp/nfm-test';
const stubs = {
  '../config': {
    'fs.base': basePath,
    '@global': true
  }
};
const deployApi = proxyquire('../../../controllers/api/deploy', stubs);
const bodyParser = require('../../../lib/bodyParser');
const apiParser = require('../../../lib/apiParser');

const config = require('../../../config');
const deployDir = config['deploy.dir'];
const backupDir = config['backup.dir'];

const mocha = require('mocha');
const chai = require('chai');
const chaiAsPromised = require('chai-as-promised');
chai.should();
chai.use(chaiAsPromised);

const fsExists = p => fs.existsSync(path.join(basePath, p));

describe('deploy测试', function () {

  before('before deploy测试', function () {
    fs.ensureDirSync(basePath);
    fs.copySync(path.resolve(__dirname, '../../files'), `${basePath}`);
  });

  after('after deploy测试', function () {
    fs.removeSync(basePath);
  });

  beforeEach(function () {
    var app = koa();

    app.use(bodyParser);
    app.use(apiParser);
    app.use(deployApi.deploy);

    this.server = app.listen(8888);
  });

  afterEach(function () {
    this.server.close();
  });

  it('# 入参测试:path省略', function (done) {
    post('http://localhost:8888')
      .then(response => {
        response.message.should.be.equal('入参错误');
        done();
      });
  });

  it('# 入参测试:文件不存在', function (done) {
    var data = {
      path: '/aaaaaa.js'
    };

    post('http://localhost:8888', data)
      .then(response => {
        response.message.should.be.equal('文件不存在');
        done();
      });
  });

  it('# 入参测试:path是目录', function (done) {
    var data = {
      path: '/dir1'
    };

    post('http://localhost:8888', data)
      .then(response => {
        response.message.should.be.equal('文件不存在');
        done();
      });
  });

  it('# 入参测试:待发布文件不存在', function (done) {
    var data = {
      path: '/file1.js'
    };

    post('http://localhost:8888', data)
      .then(response => {
        response.message.should.be.equal('待发布文件不存在');
        done();
      });
  });

  it('# 入参测试:path安全', function (done) {
    var data = {
      path: '/dir/../../../../aaaaa.js'
    };

    post('http://localhost:8888', data)
      .then(response => {
        response.message.should.be.equal('文件不存在');
        done();
      });
  });

  it('# 发布', function (done) {
    var data = {
      path: '/deploy1.js'
    };

    post('http://localhost:8888', data)
      .then(() => {
        if (!fsExists(`/${deployDir}/deploy1.js`)
          && fsExists(`/${backupDir}/deploy1.js`)
          && fs.readdirSync(`/${basePath}/${backupDir}/deploy1.js`).length === 1) {
          done();
        }
      });
  });

  it('# 发布', function (done) {
    var data = {
      path: '/deploy_backup1.js'
    };

    post('http://localhost:8888', data)
      .then(() => {
        if (!fsExists(`/${deployDir}/deploy_backup1.js`)
          && fsExists(`/${backupDir}/deploy_backup1.js`)
          && fs.readdirSync(`/${basePath}/${backupDir}/deploy_backup1.js`).length === 3) {
          done();
        }
      });
  });

});<commit_msg>完善deploy测试<commit_after>'use strict';

const path = require('path');
const koa = require('koa');
const fs = require('fs-extra');
const { post } = require('../../fetch');
const proxyquire = require('proxyquire').noPreserveCache();
const basePath = '/tmp/nfm-test';
const stubs = {
  '../config': {
    'fs.base': basePath,
    '@global': true
  }
};
const deployApi = proxyquire('../../../controllers/api/deploy', stubs);
const bodyParser = require('../../../lib/bodyParser');
const apiParser = require('../../../lib/apiParser');

const config = require('../../../config');
const deployDir = config['deploy.dir'];
const backupDir = config['backup.dir'];

const mocha = require('mocha');
const chai = require('chai');
const chaiAsPromised = require('chai-as-promised');
chai.should();
chai.use(chaiAsPromised);

const fsExists = p => fs.existsSync(path.join(basePath, p));
const fsRead = p => fs.readFileSync(path.join(basePath, p)).toString();

describe('deploy测试', function () {

  before('before deploy测试', function () {
    fs.ensureDirSync(basePath);
    fs.copySync(path.resolve(__dirname, '../../files'), `${basePath}`);
  });

  after('after deploy测试', function () {
    fs.removeSync(basePath);
  });

  beforeEach(function () {
    var app = koa();

    app.use(bodyParser);
    app.use(apiParser);
    app.use(deployApi.deploy);

    this.server = app.listen(8888);
  });

  afterEach(function () {
    this.server.close();
  });

  it('# 入参测试:path省略', function (done) {
    post('http://localhost:8888')
      .then(response => {
        response.message.should.be.equal('入参错误');
        done();
      });
  });

  it('# 入参测试:文件不存在', function (done) {
    var data = {
      path: '/aaaaaa.js'
    };

    post('http://localhost:8888', data)
      .then(response => {
        response.message.should.be.equal('文件不存在');
        done();
      });
  });

  it('# 入参测试:path是目录', function (done) {
    var data = {
      path: '/dir1'
    };

    post('http://localhost:8888', data)
      .then(response => {
        response.message.should.be.equal('文件不存在');
        done();
      });
  });

  it('# 入参测试:待发布文件不存在', function (done) {
    var data = {
      path: '/file1.js'
    };

    post('http://localhost:8888', data)
      .then(response => {
        response.message.should.be.equal('待发布文件不存在');
        done();
      });
  });

  it('# 入参测试:path安全', function (done) {
    var data = {
      path: '/dir/../../../../aaaaa.js'
    };

    post('http://localhost:8888', data)
      .then(response => {
        response.message.should.be.equal('文件不存在');
        done();
      });
  });

  it('# 发布', function (done) {
    var data = {
      path: '/deploy1.js'
    };

    post('http://localhost:8888', data)
      .then(() => {
        if (!fsExists(`/${deployDir}/deploy1.js`) && fsExists(`/${backupDir}/deploy1.js`)) {
          var fileName = fs.readdirSync(`/${basePath}/${backupDir}/deploy1.js`)[0];
          if(fsRead(`/${backupDir}/deploy1.js/${fileName}`) === '//.nfm_deploy/deploy1.js') {
            done();
          }
        }
      });
  });

  it('# 发布', function (done) {
    var data = {
      path: '/deploy_backup1.js'
    };

    post('http://localhost:8888', data)
      .then(() => {
        if (!fsExists(`/${deployDir}/deploy_backup1.js`)
          && fsExists(`/${backupDir}/deploy_backup1.js`)
          && fs.readdirSync(`/${basePath}/${backupDir}/deploy_backup1.js`).length === 3) {
          done();
        }
      });
  });

});<|endoftext|>


<commit_before>import {badRequest, notFound} from 'boom'
import Joi from 'joi'
import Logger from '@modulus/logger'
import Slug from 'slug'

import Post from '../models/blog/post'

let logger = Logger('handlers/post')

export default {

  /////////// Post.create \\\\\\\\\\
  create: {
    validate: {
      payload: {
        title: Joi.string().required(),
        image: Joi.string(),
        caption: Joi.string(),
        summary: Joi.string().required(),
        content: Joi.string().required(),
        tags: Joi.array()
      }
    },
    handler: function (request, reply) {
      let {caption, content, image, summary, tags, title} = request.payload

      let newPost =  {
        title: title,
        slug: Slug(title, { lower: true }),
        image: image,
        caption: caption,
        summary: summary,
        content: content,
        tags: tags
      }

      new Post(newPost).save((err, created) => {
        if (err) {
          logger.error(`Post.create error: ${err.message}`)
          return reply(badRequest(err.message))
        }

        Post.update(
          { slug: { $ne: created.slug } },
          { latest: false },
          { multi: true },
          (err, count) => {
            if (err) {
              logger.error(`Post.update error: ${err.message}`)
              return reply(badRequest(err.message))
            }

            logger.debug(`Post.update updated latest`)
            logger.debug(`Post.create saved ${JSON.stringify(created)}`)

            reply(created)
          })
      })
    }
  },

  ////////// Post.get \\\\\\\\\\
  get: {
    validate: {
      params: {
        slug: Joi.string().required()
      }
    },
    handler: function (request, reply) {
      Post.findOne({ slug: request.params.slug }, (err, post) => {
        if (err) {
          logger.error(`Job.findOne error: ${err.message}`)
          return reply(badRequest(err.message))
        }

        logger.debug(`Post.findOne found ${JSON.stringify(post)}`)
        reply(post)
      })
    }
  },

  ////////// Post.getAll \\\\\\\\\\
  getAll: {
    validate: {
      query: {
        latest: Joi.boolean()
      }
    },
    handler: function (request, reply) {
      let query = request.query && request.query.latest ? { latest: true } : {}

      Post.find(query, (err, posts) => {
        if (err) {
          logger.error(`Post.find error: ${err.message}`)
          return reply(badRequest(err.message))
        }

        logger.debug(`Post.find found ${JSON.stringify(posts)}`)

        posts = posts.sort((a, b) => {
          a = new Date(a.createdAt)
          b = new Date(b.createdAt)

          if(a < b) return -1
          if(a > b) return 1

          return 0
        })

        reply(posts)
      })
    }
  },

  ////////// Post.remove \\\\\\\\\\
  remove: {
    validate: {
      params: {
        slug: Joi.string().required()
      }
    },
    handler: function (request, reply) {
      Post.remove({ slug: request.params.slug }, (err) => {
        if (err) {
          logger.error(`Post.remove error: ${err.message}`)
          return reply(badRequest(err.message))
        }

        logger.debug(`Post.remove removed ${request.params.slug}`)
        reply()
      })
    }
  },

  ////////// Post.update \\\\\\\\\\
  update: {
    validate: {
      params: {
        slug: Joi.string().required()
      },
      payload: {
        title: Joi.string(),
        image: Joi.string(),
        caption: Joi.string(),
        summary: Joi.string(),
        content: Joi.string(),
        tags: Joi.array(),
        latest: Joi.boolean()
      }
    },
    handler: function (request, reply) {
      let {params, payload} = request

      Post.update({ slug: params.slug }, payload, (err) => {
        if (err) {
          logger.error(`Post.update error: ${err.message}`)
          return reply(badRequest(err.message))
        }

        logger.debug(`Post.update updated ${params.slug}`)
        reply(params.slug)
      })
    }
  }
}
<commit_msg>Change ordering on returned posts<commit_after>import {badRequest, notFound} from 'boom'
import Joi from 'joi'
import Logger from '@modulus/logger'
import Slug from 'slug'

import Post from '../models/blog/post'

let logger = Logger('handlers/post')

export default {

  /////////// Post.create \\\\\\\\\\
  create: {
    validate: {
      payload: {
        title: Joi.string().required(),
        image: Joi.string(),
        caption: Joi.string(),
        summary: Joi.string().required(),
        content: Joi.string().required(),
        tags: Joi.array()
      }
    },
    handler: function (request, reply) {
      let {caption, content, image, summary, tags, title} = request.payload

      let newPost =  {
        title: title,
        slug: Slug(title, { lower: true }),
        image: image,
        caption: caption,
        summary: summary,
        content: content,
        tags: tags
      }

      new Post(newPost).save((err, created) => {
        if (err) {
          logger.error(`Post.create error: ${err.message}`)
          return reply(badRequest(err.message))
        }

        Post.update(
          { slug: { $ne: created.slug } },
          { latest: false },
          { multi: true },
          (err, count) => {
            if (err) {
              logger.error(`Post.update error: ${err.message}`)
              return reply(badRequest(err.message))
            }

            logger.debug(`Post.update updated latest`)
            logger.debug(`Post.create saved ${JSON.stringify(created)}`)

            reply(created)
          })
      })
    }
  },

  ////////// Post.get \\\\\\\\\\
  get: {
    validate: {
      params: {
        slug: Joi.string().required()
      }
    },
    handler: function (request, reply) {
      Post.findOne({ slug: request.params.slug }, (err, post) => {
        if (err) {
          logger.error(`Job.findOne error: ${err.message}`)
          return reply(badRequest(err.message))
        }

        logger.debug(`Post.findOne found ${JSON.stringify(post)}`)
        reply(post)
      })
    }
  },

  ////////// Post.getAll \\\\\\\\\\
  getAll: {
    validate: {
      query: {
        latest: Joi.boolean()
      }
    },
    handler: function (request, reply) {
      let query = request.query && request.query.latest ? { latest: true } : {}

      Post.find(query, (err, posts) => {
        if (err) {
          logger.error(`Post.find error: ${err.message}`)
          return reply(badRequest(err.message))
        }

        logger.debug(`Post.find found ${JSON.stringify(posts)}`)

        posts = posts.sort((a, b) => {
          a = new Date(a.createdAt)
          b = new Date(b.createdAt)

          if(a < b) return 1
          if(a > b) return -1

          return 0
        })

        reply(posts)
      })
    }
  },

  ////////// Post.remove \\\\\\\\\\
  remove: {
    validate: {
      params: {
        slug: Joi.string().required()
      }
    },
    handler: function (request, reply) {
      Post.remove({ slug: request.params.slug }, (err) => {
        if (err) {
          logger.error(`Post.remove error: ${err.message}`)
          return reply(badRequest(err.message))
        }

        logger.debug(`Post.remove removed ${request.params.slug}`)
        reply()
      })
    }
  },

  ////////// Post.update \\\\\\\\\\
  update: {
    validate: {
      params: {
        slug: Joi.string().required()
      },
      payload: {
        title: Joi.string(),
        image: Joi.string(),
        caption: Joi.string(),
        summary: Joi.string(),
        content: Joi.string(),
        tags: Joi.array(),
        latest: Joi.boolean()
      }
    },
    handler: function (request, reply) {
      let {params, payload} = request

      Post.update({ slug: params.slug }, payload, (err) => {
        if (err) {
          logger.error(`Post.update error: ${err.message}`)
          return reply(badRequest(err.message))
        }

        logger.debug(`Post.update updated ${params.slug}`)
        reply(params.slug)
      })
    }
  }
}
<|endoftext|>


<commit_before>$(document).ready(function() {
  tileListener();

})

function tileListener() {
  $(".arrow").on("click", function(){
    var arrow = this.attr("id");
    clickedArrow(arrow);
  });
}

function clickedArrow(arrow) {
  var divId = addPiece(arrow,color);
  if (divId) {
    console.log(arrow);
    // ajax call here
    if (checkForWin()){
      //ajax call to announce winner
    }
    changeColor();
  }

}

// BEGIN BOARD GAME LOGIC//

var color = "black"
var board = [["","","","","","",""],["","","","","","",""],["","","","","","",""],["","","","","","",""],["","","","","","",""],["","","","","","",""]];
var height = 6;
var width = 7;

function changeColor() {
  if (color === "black"){
    color = "red";
  } else if (color === "red"){
    color = "black";
  }
}

function addPiece(column, piece){
  if (board[0][column]){
    console.log("Cannot add a piece there");
    return false;
  }
  if (!board[5][column]){
    console.log(piece + " piece added to column " + column);
    board[5][column] = piece;
    var spaceToStr = ("5" + column.toString());
    return spaceToStr;
  }
  for (var row = 1; row < height; row ++){
    if (board[row][column]){
      console.log(piece + " piece added to column " + column);
      board[row-1][column] = piece;
      var spaceToStr = ((row * 10) + column).toString();
      if (spaceToStr.length < 2){
        spaceToStr = "0" + spaceToStr;
      }
      return spaceToStr;
    }
  }
}

function checkForWin(){
  for (var row = 0; row < height; row ++){
    for (var column = 0; column < width; column ++){
      var space = board[row][column];
      if (space){
        var checkRows = rowCheckMethod(space, row, column);
        var checkColumns = columnCheckMethod(space, row, column);
        var checkDiags = diagCheckMethod(space, row, column)
        if (checkRows || checkColumns || checkDiags){
          return winner(space);
        }else{
          return false;
        }

      }
    }
  }
}

function rowCheckMethod(space, row, column){
  if (row > 2){
    return false;
  }
  var checker = (board[row + 1][column] === space) && (board[row + 2][column] === space) && (board[row + 3][column] === space);
  return checker;
}

function columnCheckMethod(space, row, column){
  var checker = (board[row][column + 1] === space) && (board[row][column + 2] === space) && (board[row][column + 3] === space);
  return checker;
}

function diagCheckMethod(space, row, column){
  return (bckDiagCheckMethod(space, row, column) || fwdDiagCheckMethod(space, row, column));
}

function bckDiagCheckMethod(space, row, column){
  if (row > 2){
    return false
  }
  var backSlash = (board[row + 1][column + 1] === space) && (board[row + 2][column + 2] === space) && (board[row + 3][column + 3] === space);
  return backSlash;
}

function fwdDiagCheckMethod(space, row, column){
  if (row < 3){
    return false
  }
  var forwardSlash = (board[row - 1][column + 1] === space) && (board[row - 2][column + 2] === space) && (board[row - 3][column + 3] === space);
  return forwardSlash;

}

function winner(space){
  return true;
}

<commit_msg>More changes to game listeners<commit_after>$(document).ready(function() {
  tileListener();

})

function tileListener() {
  $(".arrow").on("click", function(){
    var arrow = $(this).attr("id");
    clickedArrow(arrow);
  });
}

function clickedArrow(arrow) {
  var divId = addPiece(arrow,color);
  if (divId) {
    $("#" + divId).css("color", color);
    if (checkForWin()){
      //announce winner
    }
    changeColor();
  }

}

// BEGIN BOARD GAME LOGIC//

var color = "black"
var board = [["","","","","","",""],["","","","","","",""],["","","","","","",""],["","","","","","",""],["","","","","","",""],["","","","","","",""]];
var height = 6;
var width = 7;

function changeColor() {
  if (color === "black"){
    color = "red";
  } else if (color === "red"){
    color = "black";
  }
}

function addPiece(column, piece){
  if (board[0][column]){
    console.log("Cannot add a piece there");
    return false;
  }
  if (!board[5][column]){
    console.log(piece + " piece added to column " + column);
    board[5][column] = piece;
    var spaceToStr = ("5" + column.toString());
    return spaceToStr;
  }
  for (var row = 1; row < height; row ++){
    if (board[row][column]){
      console.log(piece + " piece added to column " + column);
      board[row-1][column] = piece;
      var spaceToStr = ((row * 10) + column).toString();
      if (spaceToStr.length < 2){
        spaceToStr = "0" + spaceToStr;
      }
      return spaceToStr;
    }
  }
}

function checkForWin(){
  for (var row = 0; row < height; row ++){
    for (var column = 0; column < width; column ++){
      var space = board[row][column];
      if (space){
        var checkRows = rowCheckMethod(space, row, column);
        var checkColumns = columnCheckMethod(space, row, column);
        var checkDiags = diagCheckMethod(space, row, column)
        if (checkRows || checkColumns || checkDiags){
          return winner(space);
        }else{
          return false;
        }

      }
    }
  }
}

function rowCheckMethod(space, row, column){
  if (row > 2){
    return false;
  }
  var checker = (board[row + 1][column] === space) && (board[row + 2][column] === space) && (board[row + 3][column] === space);
  return checker;
}

function columnCheckMethod(space, row, column){
  var checker = (board[row][column + 1] === space) && (board[row][column + 2] === space) && (board[row][column + 3] === space);
  return checker;
}

function diagCheckMethod(space, row, column){
  return (bckDiagCheckMethod(space, row, column) || fwdDiagCheckMethod(space, row, column));
}

function bckDiagCheckMethod(space, row, column){
  if (row > 2){
    return false
  }
  var backSlash = (board[row + 1][column + 1] === space) && (board[row + 2][column + 2] === space) && (board[row + 3][column + 3] === space);
  return backSlash;
}

function fwdDiagCheckMethod(space, row, column){
  if (row < 3){
    return false
  }
  var forwardSlash = (board[row - 1][column + 1] === space) && (board[row - 2][column + 2] === space) && (board[row - 3][column + 3] === space);
  return forwardSlash;

}

function winner(space){
  return true;
}

<|endoftext|>


<commit_before>import React from 'react';
import chai from 'chai';
import chaiSubset from 'chai-subset';
import should from 'should';
import { createStore, applyMiddleware, combineReducers } from 'redux';
import { Provider, connect } from 'react-redux';
import thunk from 'redux-thunk';
import TestUtils from 'react-addons-test-utils';

chai.use(chaiSubset);

const { assert } = chai;

import {
  Field,
  createFieldClass,
  actions,
  createFormReducer,
  createModelReducer,
  controlPropsMap
} from '../lib';

describe('custom <Field /> components', () => {
  const CustomField = createFieldClass({
    'CustomText': (props) => ({
      customOnChange: props.onChange
    })
  });

  class CustomText extends React.Component {
    handleChange(e) {
      let { customOnChange } = this.props;
      let value = e.target.value.toUpperCase();

      customOnChange(value);
    }
    render() {
      let { customOnChange } = this.props;

      return (

          <input onChange={(e) => this.handleChange(e)} />

      );
    }
  }

  it('should handle custom prop mappings', () => {
    const store = applyMiddleware(thunk)(createStore)(combineReducers({
      testForm: createFormReducer('test'),
      test: createModelReducer('test', { foo: 'bar' })
    }));

    const field = TestUtils.renderIntoDocument(
      <Provider store={store}>
        <CustomField model="test.foo">
          <CustomText />
        </CustomField>
      </Provider>
    );

    const control = TestUtils.findRenderedDOMComponentWithTag(field, 'input');

    control.value = 'testing';

    TestUtils.Simulate.change(control);

    assert.equal(
      store.getState().test.foo,
      'TESTING');
  });
})
<commit_msg>Updating createFieldClass() unit tests<commit_after>import React from 'react';
import chai from 'chai';
import chaiSubset from 'chai-subset';
import should from 'should';
import { createStore, applyMiddleware, combineReducers } from 'redux';
import { Provider, connect } from 'react-redux';
import thunk from 'redux-thunk';
import TestUtils from 'react-addons-test-utils';

chai.use(chaiSubset);

const { assert } = chai;

import {
  Field,
  createFieldClass,
  actions,
  createFormReducer,
  createModelReducer,
  controlPropsMap
} from '../lib';

describe('custom <Field /> components with createFieldClass()', () => {
  const CustomField = createFieldClass({
    'CustomText': (props) => ({
      customOnChange: props.onChange
    })
  });

  class CustomText extends React.Component {
    handleChange(e) {
      let { customOnChange } = this.props;
      let value = e.target.value.toUpperCase();

      customOnChange(value);
    }
    render() {
      let { customOnChange } = this.props;

      return (

          <input onChange={(e) => this.handleChange(e)} />

      );
    }
  }

  it('should return a Field component class', () => {
    assert.equal(CustomField.constructor, Field.constructor);
  });

  it('should handle custom prop mappings', () => {
    const store = applyMiddleware(thunk)(createStore)(combineReducers({
      testForm: createFormReducer('test'),
      test: createModelReducer('test', { foo: 'bar' })
    }));

    const field = TestUtils.renderIntoDocument(
      <Provider store={store}>
        <CustomField model="test.foo">
          <CustomText />
        </CustomField>
      </Provider>
    );

    const control = TestUtils.findRenderedDOMComponentWithTag(field, 'input');

    control.value = 'testing';

    TestUtils.Simulate.change(control);

    assert.equal(
      store.getState().test.foo,
      'TESTING');
  });

  it('should continue to recognize native controls', () => {
    const store = applyMiddleware(thunk)(createStore)(combineReducers({
      testForm: createFormReducer('test'),
      test: createModelReducer('test', { foo: 'bar' })
    }));

    const field = TestUtils.renderIntoDocument(
      <Provider store={store}>
        <CustomField model="test.foo">
          <input type="text" />
        </CustomField>
      </Provider>
    );

    const control = TestUtils.findRenderedDOMComponentWithTag(field, 'input');

    control.value = 'testing';

    TestUtils.Simulate.change(control);

    assert.equal(
      store.getState().test.foo,
      'testing');
  });
});
<|endoftext|>


<commit_before>Meteor.methods({
  newFollowerNotification: function (followingId) { // initiated by the follower
    var follower = Meteor.user();

    var notificationData = {
      follower: { '_id': follower._id, 'name': follower.profile.name }
    };

    // notify user who follower is following
    if (Herald.userPreference(followingId, 'onsite', 'newFollower')) {
      Herald.createNotification(followingId, {
        courier: 'newFollower',
        data: notificationData,
        'duplicates': false,
        'aggregate': true,
        'aggregateAt': 5
      });
    }
  },
  newTopicNotification: function (topic) { // initiated by the topic creator
    var user = Meteor.user(); // topic owner

    if (!user.activity || !user.activity.followers) return;

    var notificationData = {
      author: { '_id': user._id, 'name': user.profile.name },
      topic: _.pick(topic, '_id', 'title')
    };

    // notify owner's followers
    _.each(user.activity.followers, function (followerId) {

      if (Herald.userPreference(followerId, 'onsite', 'newTopic')) {
        Herald.createNotification(followerId, {
          courier: 'newTopic',
          data: notificationData // don't aggregate new topics
        });
      }
    });
  },
  newCommentNotification: function (comment) { // initiated by the comment creator
    var topic = Topics.findOne(comment.topicId);
    var replyToId = comment.replyTo;
    var user = Meteor.user(); // comment owner
    var notified = [];

    var notificationData = {
      author: { '_id': user._id, 'name': user.profile.name },
      comment: _.pick(comment, '_id'),
      topic: _.pick(topic, '_id', 'title')
    };

    if (!!replyToId) { // comment reply
      var replyTo = Comments.findOne(replyToId);

      notificationData.replyTo = _.pick(replyTo, '_id', 'topicId');

      // notify replyTo owner
      // unless user is just replying to self
      if (replyTo.userId !== user._id && Herald.userPreference(replyTo.userId, 'onsite', 'newReply')) {

        Herald.createNotification(replyTo.userId, {
          courier: 'newReply',
          data: notificationData,
          'aggregate': true,
          'aggregateAt': 5,
          'aggregateUnder': 'replyTo' // combine notifications that share the same author
        });
        notified.push(replyTo.userId);
      }

    }

    // notify topic owner (if topic owner wants to be notified)
    // unless the comment owner is also the topic owner,
    // or the topic owner is the replyTo owner (in which case we already notified them with 'newReply')
    if (topic.userId !== user._id &&
        !_.contains(notified, topic.userId) &&
        Herald.userPreference(topic.userId, 'onsite', { courier: 'newComment.topicOwner' }))
    {
      Herald.createNotification(topic.userId, {
        courier: 'newComment',
        data: notificationData,
        'aggregate': true,
        'aggregateAt': 5,
        'aggregateUnder': 'topic'
      });
      notified.push(topic.userId);
    }

    // notify topic followers
    var topicFollowers = _.difference(topic.followers, notified);
    _.each(topicFollowers, function (followerId) {

      // in case user is following the topic
      if (followerId !== user._id &&
          Herald.userPreference(followerId, 'onsite', { courier: 'newComment.topicFollower' }))
      {
        Herald.createNotification(followerId, {
          courier: 'newComment',
          data: notificationData,
          'aggregate': true,
          'aggregateAt': 5,
          'aggregateUnder': 'topic'
        });
        notified.push(followerId);
      }
    });

    // notify commenter's followers
    var commenterFollowers = _.difference(user.activity.followers, notified);
    _.each(commenterFollowers, function (followerId) {

      if (Herald.userPreference(followerId, 'onsite', { courier: 'newComment.follower' })) {
        Herald.createNotification(followerId, {
          courier: 'newComment',
          data: notificationData,
          'aggregate': true,
          'aggregateAt': 5,
          'aggregateUnder': 'author'
        });
      }

      // notified.push(followerId);
    });
  }
});<commit_msg>Prevent duplicates for new comments for topic owners<commit_after>Meteor.methods({
  newFollowerNotification: function (followingId) { // initiated by the follower
    var follower = Meteor.user();

    var notificationData = {
      follower: { '_id': follower._id, 'name': follower.profile.name }
    };

    // notify user who follower is following
    if (Herald.userPreference(followingId, 'onsite', 'newFollower')) {
      Herald.createNotification(followingId, {
        courier: 'newFollower',
        data: notificationData,
        'duplicates': false,
        'aggregate': true,
        'aggregateAt': 5
      });
    }
  },
  newTopicNotification: function (topic) { // initiated by the topic creator
    var user = Meteor.user(); // topic owner

    if (!user.activity || !user.activity.followers) return;

    var notificationData = {
      author: { '_id': user._id, 'name': user.profile.name },
      topic: _.pick(topic, '_id', 'title')
    };

    // notify owner's followers
    _.each(user.activity.followers, function (followerId) {

      if (Herald.userPreference(followerId, 'onsite', 'newTopic')) {
        Herald.createNotification(followerId, {
          courier: 'newTopic',
          data: notificationData // don't aggregate new topics
        });
      }
    });
  },
  newCommentNotification: function (comment) { // initiated by the comment creator
    var topic = Topics.findOne(comment.topicId);
    var replyToId = comment.replyTo;
    var user = Meteor.user(); // comment owner
    var notified = [];

    var notificationData = {
      author: { '_id': user._id, 'name': user.profile.name },
      comment: _.pick(comment, '_id'),
      topic: _.pick(topic, '_id', 'title', 'userId')
    };

    if (!!replyToId) { // comment reply
      var replyTo = Comments.findOne(replyToId);

      notificationData.replyTo = _.pick(replyTo, '_id', 'topicId');

      // notify replyTo owner
      // unless user is just replying to self
      if (replyTo.userId !== user._id && Herald.userPreference(replyTo.userId, 'onsite', 'newReply')) {

        Herald.createNotification(replyTo.userId, {
          courier: 'newReply',
          data: notificationData,
          'aggregate': true,
          'aggregateAt': 5,
          'aggregateUnder': 'replyTo' // combine notifications that share the same author
        });
        notified.push(replyTo.userId);
      }

    }

    // notify topic owner (if topic owner wants to be notified)
    // unless the comment owner is also the topic owner,
    // or the topic owner is the replyTo owner (in which case we already notified them with 'newReply')
    if (topic.userId !== user._id &&
        !_.contains(notified, topic.userId) &&
        Herald.userPreference(topic.userId, 'onsite', { courier: 'newComment.topicOwner' }))
    {
      Herald.createNotification(topic.userId, {
        courier: 'newComment',
        data: notificationData,
        'duplicates': false,
        'aggregate': true,
        'aggregateAt': 5,
        'aggregateUnder': 'topic'
      });
      notified.push(topic.userId);
    }

    // notify topic followers
    var topicFollowers = _.difference(topic.followers, notified);
    _.each(topicFollowers, function (followerId) {

      // in case user is following the topic
      if (followerId !== user._id &&
          Herald.userPreference(followerId, 'onsite', { courier: 'newComment.topicFollower' }))
      {
        Herald.createNotification(followerId, {
          courier: 'newComment',
          data: notificationData,
          'aggregate': true,
          'aggregateAt': 5,
          'aggregateUnder': 'topic'
        });
        notified.push(followerId);
      }
    });

    // notify commenter's followers
    var commenterFollowers = _.difference(user.activity.followers, notified);
    _.each(commenterFollowers, function (followerId) {

      if (Herald.userPreference(followerId, 'onsite', { courier: 'newComment.follower' })) {
        Herald.createNotification(followerId, {
          courier: 'newComment',
          data: notificationData,
          'aggregate': true,
          'aggregateAt': 5,
          'aggregateUnder': 'topic'
        });
      }

      // notified.push(followerId);
    });
  }
});<|endoftext|>