<commit_before>/**
 * Using Rails-like standard naming convention for endpoints.
 * GET     /api/submissions              ->  index
 * POST    /api/submissions              ->  create
 * GET     /api/submissions/:id          ->  show
 * PUT     /api/submissions/:id          ->  update
 * DELETE  /api/submissions/:id          ->  destroy
 */

'use strict';

var _ = require('lodash');
var Submission = require('./submission.model');
var SectionEvent = require('../sectionevent/sectionevent.model');
var Section = require('../section/section.model');
var multiparty = require('multiparty');
var User = require('../user/user.model');
var fs = require('fs');
var mkdirp = require('mkdirp');
var config = require('../../config/environment');
var mongoose = require('bluebird').promisifyAll(require('mongoose'));
var path = require('path');
import async from 'async';

function handleError(res, statusCode) {
  statusCode = statusCode || 500;
  return function(err) {
    res.status(statusCode).send(err);
  };
}

function responseWithResult(res, statusCode) {
  statusCode = statusCode || 200;
  return function(entity) {
    if (entity) {
      res.status(statusCode).json(entity);
    }
  };
}

function handleEntityNotFound(res) {
  return function(entity) {
    if (!entity) {
      res.status(404).end();
      return null;
    }
    return entity;
  };
}

function saveUpdates(updates) {
  return function(entity) {
    var updated = _.merge(entity, updates);
    return updated.saveAsync()
      .spread(function(updated) {
        return updated;
      });
  };
}

function removeEntity(res) {
  return function(entity) {
    if (entity) {
      return entity.removeAsync()
        .then(function() {
          res.status(204).end();
        });
    }
  };
}

function saveSubmissionImage(files, fields){
  var imagePaths = [];
  if (!files){return imagePaths;}
  files.files.forEach(function(file) {
    var name = Date.now().toString()+ '_' + file.path.substring(file.path.lastIndexOf('/')).substring(1);
    var path = config.imageUploadPath  + fields.userId + '/' + fields.eventId;
    var destPath = path + '/' + name;
    imagePaths.push("/api/submissions/image?imgPath=" + destPath);
    if(!fs.existsSync(path)){
      mkdirp.sync(path);
    }
    var is = fs.createReadStream(file.path);
    var os = fs.createWriteStream(destPath);
    is.pipe(os);
    is.on('end', function() {
        fs.unlinkSync(file.path);
    });
  });
  return imagePaths;
}

function withDefault(queryString, defaultValue){
  console.log(queryString, queryString == "true");
  if (queryString === undefined) return defaultValue;
  else return queryString=="true";
}

// Gets a list of Submissions
exports.index = function(req, res) {
  var withStudents = withDefault(req.query.withStudents, true);
  var withSectionEvent = withDefault(req.query.withSectionEvent, true);
  var withEventInfo = withDefault(req.query.withEventInfo, withSectionEvent);

  function respond(query){
    if (withStudents){
      query.populate("authors");
      query.populate("submitter");
    }
    if (withSectionEvent){
      if (withEventInfo){
        query.populate({
          path: 'sectionEvent',
          model: 'SectionEvent',
          populate: {
            path: 'info',
            model: 'EventInfo'
          }
        });
      }else query.populate("sectionEvent");
    }

    query.execAsync()
      .then(responseWithResult(res))
      .catch(handleError(res));
  }

  if (req.query.onlyInstructor){
    var instructorId = req.query.onlyInstructor == "me" ? req.user._id : req.query.onlyInstructor;
    Section.findAsync({instructors: instructorId})
      .then((instructorSections) => {
        var instructorSectionIds = instructorSections.map((sec) => sec._id);
        return SectionEvent.findAsync({section: {$in: instructorSectionIds}});
      })
      .then((sectionEvents) => {
        var sectionEventIds = sectionEvents.map((evnt) => evnt._id);
        respond(Submission.find({sectionEvent: {$in: sectionEventIds}}))
      });

  }else if (req.query.onlySection){
    SectionEvent.findAsync({section: req.query.onlySection})
      .then((sectionEvents) => {
        var sectionEventIds = sectionEvents.map((evnt) => evnt._id);
        respond(Submission.find({sectionEvent: {$in: sectionEventIds}}));
      });

  }else if (req.query.onlyStudent){
    var studentId = req.query.onlyStudent == 'me' ? req.user._id : req.query.onlyStudent;
    var search = { $or: [{ submitter: studentId}, { authors: {$in: [studentId]} } ]};
    if (req.query.onlySectionEvent) search.sectionEvent = req.query.onlySectionEvent;
    respond(Submission.find(search));

  }else if (req.query.onlySectionEvent){
    respond(Submission.find({sectionEvent: req.query.onlySectionEvent}));

  }else{
    respond(Submission.find());
  }
};

exports.image = function(req, res){
  var imgPath = path.join(__dirname, "../../../", req.query.imgPath);
  res.sendFile(imgPath);
};

// Gets a single Submission from the DB
exports.show = function(req, res) {
  Submission.findByIdAsync(req.params.id)
    .then(handleEntityNotFound(res))
    .then(responseWithResult(res))
    .catch(handleError(res));
};

// Creates a new Submission in the DB
exports.create = function(req, res) {
  // TODO: add back after auth
  // if (req.user._id)
  //   {
  //     req.body.userId = req.user._id;
  //   }
  var imagePaths = saveSubmissionImage(req.files, req.body);
  var submit = {
    images : imagePaths,
    submitter : req.body.userId,
    authors : req.body.authors,
    sectionEvent : req.body.eventId,
    location : {
      geo: {
        coordinates : req.body.coordinates
      }
    },
    time: Date.now(),
    content: req.body.content
  };

  Submission.create(submit, (err, submission) => {
    if (err) return handleError(res);
    return res.json(submission);
  })
};

// Updates an existing Submission in the DB
exports.update = function(req, res) {
  if (req.body._id) {
    delete req.body._id;
  }
  Submission.findByIdAsync(req.params.id)
    .then(handleEntityNotFound(res))
    .then(saveUpdates(req.body))
    .then(responseWithResult(res))
    .catch(handleError(res));
};

// Deletes a Submission from the DB
exports.destroy = function(req, res) {
  Submission.findByIdAsync(req.params.id)
    .then(handleEntityNotFound(res))
    .then(removeEntity(res))
    .catch(handleError(res));
};
<commit_msg>Removed Console.log<commit_after>/**
 * Using Rails-like standard naming convention for endpoints.
 * GET     /api/submissions              ->  index
 * POST    /api/submissions              ->  create
 * GET     /api/submissions/:id          ->  show
 * PUT     /api/submissions/:id          ->  update
 * DELETE  /api/submissions/:id          ->  destroy
 */

'use strict';

var _ = require('lodash');
var Submission = require('./submission.model');
var SectionEvent = require('../sectionevent/sectionevent.model');
var Section = require('../section/section.model');
var multiparty = require('multiparty');
var User = require('../user/user.model');
var fs = require('fs');
var mkdirp = require('mkdirp');
var config = require('../../config/environment');
var mongoose = require('bluebird').promisifyAll(require('mongoose'));
var path = require('path');
import async from 'async';

function handleError(res, statusCode) {
  statusCode = statusCode || 500;
  return function(err) {
    res.status(statusCode).send(err);
  };
}

function responseWithResult(res, statusCode) {
  statusCode = statusCode || 200;
  return function(entity) {
    if (entity) {
      res.status(statusCode).json(entity);
    }
  };
}

function handleEntityNotFound(res) {
  return function(entity) {
    if (!entity) {
      res.status(404).end();
      return null;
    }
    return entity;
  };
}

function saveUpdates(updates) {
  return function(entity) {
    var updated = _.merge(entity, updates);
    return updated.saveAsync()
      .spread(function(updated) {
        return updated;
      });
  };
}

function removeEntity(res) {
  return function(entity) {
    if (entity) {
      return entity.removeAsync()
        .then(function() {
          res.status(204).end();
        });
    }
  };
}

function saveSubmissionImage(files, fields){
  var imagePaths = [];
  if (!files){return imagePaths;}
  files.files.forEach(function(file) {
    var name = Date.now().toString()+ '_' + file.path.substring(file.path.lastIndexOf('/')).substring(1);
    var path = config.imageUploadPath  + fields.userId + '/' + fields.eventId;
    var destPath = path + '/' + name;
    imagePaths.push("/api/submissions/image?imgPath=" + destPath);
    if(!fs.existsSync(path)){
      mkdirp.sync(path);
    }
    var is = fs.createReadStream(file.path);
    var os = fs.createWriteStream(destPath);
    is.pipe(os);
    is.on('end', function() {
        fs.unlinkSync(file.path);
    });
  });
  return imagePaths;
}

function withDefault(queryString, defaultValue){
  if (queryString === undefined) return defaultValue;
  else return queryString=="true";
}

// Gets a list of Submissions
exports.index = function(req, res) {
  var withStudents = withDefault(req.query.withStudents, true);
  var withSectionEvent = withDefault(req.query.withSectionEvent, true);
  var withEventInfo = withDefault(req.query.withEventInfo, withSectionEvent);

  function respond(query){
    if (withStudents){
      query.populate("authors");
      query.populate("submitter");
    }
    if (withSectionEvent){
      if (withEventInfo){
        query.populate({
          path: 'sectionEvent',
          model: 'SectionEvent',
          populate: {
            path: 'info',
            model: 'EventInfo'
          }
        });
      }else query.populate("sectionEvent");
    }

    query.execAsync()
      .then(responseWithResult(res))
      .catch(handleError(res));
  }

  if (req.query.onlyInstructor){
    var instructorId = req.query.onlyInstructor == "me" ? req.user._id : req.query.onlyInstructor;
    Section.findAsync({instructors: instructorId})
      .then((instructorSections) => {
        var instructorSectionIds = instructorSections.map((sec) => sec._id);
        return SectionEvent.findAsync({section: {$in: instructorSectionIds}});
      })
      .then((sectionEvents) => {
        var sectionEventIds = sectionEvents.map((evnt) => evnt._id);
        respond(Submission.find({sectionEvent: {$in: sectionEventIds}}))
      });

  }else if (req.query.onlySection){
    SectionEvent.findAsync({section: req.query.onlySection})
      .then((sectionEvents) => {
        var sectionEventIds = sectionEvents.map((evnt) => evnt._id);
        respond(Submission.find({sectionEvent: {$in: sectionEventIds}}));
      });

  }else if (req.query.onlyStudent){
    var studentId = req.query.onlyStudent == 'me' ? req.user._id : req.query.onlyStudent;
    var search = { $or: [{ submitter: studentId}, { authors: {$in: [studentId]} } ]};
    if (req.query.onlySectionEvent) search.sectionEvent = req.query.onlySectionEvent;
    respond(Submission.find(search));

  }else if (req.query.onlySectionEvent){
    respond(Submission.find({sectionEvent: req.query.onlySectionEvent}));

  }else{
    respond(Submission.find());
  }
};

exports.image = function(req, res){
  var imgPath = path.join(__dirname, "../../../", req.query.imgPath);
  res.sendFile(imgPath);
};

// Gets a single Submission from the DB
exports.show = function(req, res) {
  Submission.findByIdAsync(req.params.id)
    .then(handleEntityNotFound(res))
    .then(responseWithResult(res))
    .catch(handleError(res));
};

// Creates a new Submission in the DB
exports.create = function(req, res) {
  // TODO: add back after auth
  // if (req.user._id)
  //   {
  //     req.body.userId = req.user._id;
  //   }
  var imagePaths = saveSubmissionImage(req.files, req.body);
  var submit = {
    images : imagePaths,
    submitter : req.body.userId,
    authors : req.body.authors,
    sectionEvent : req.body.eventId,
    location : {
      geo: {
        coordinates : req.body.coordinates
      }
    },
    time: Date.now(),
    content: req.body.content
  };

  Submission.create(submit, (err, submission) => {
    if (err) return handleError(res);
    return res.json(submission);
  })
};

// Updates an existing Submission in the DB
exports.update = function(req, res) {
  if (req.body._id) {
    delete req.body._id;
  }
  Submission.findByIdAsync(req.params.id)
    .then(handleEntityNotFound(res))
    .then(saveUpdates(req.body))
    .then(responseWithResult(res))
    .catch(handleError(res));
};

// Deletes a Submission from the DB
exports.destroy = function(req, res) {
  Submission.findByIdAsync(req.params.id)
    .then(handleEntityNotFound(res))
    .then(removeEntity(res))
    .catch(handleError(res));
};
<|endoftext|>


<commit_before>'use strict'

var async = require('async'),
  _ = require('lodash'),
  natural = require('natural'),
  tokenizer = new natural.WordTokenizer();

module.exports = (root) => {
  return {
    smsRegisterTransaction: (req, res) => {
      root.child('transactions').once('value',
        snap => {
          let transactions = snap.val();
          if(req.query && !_.isEmpty(req.query)) {
            let payload = req.query;
            let mId = root.child('messages').push();
            mId.set(payload,
              err => {
                if(err)
                  res.json({'status': 'An error occured'});

                parseMessage(payload.msisdn, payload.text,
                  decoupled => {
                    if(decoupled) {
                      let status = false;
                      _.forEach(transactions,
                        (transaction, id) => {
                          let amount = sanitizeNumber(decoupled.amount);
                          let diff = Math.abs(transaction.amount - amount);
                          if(compare(transaction.name, decoupled.depositor) && diff < 100) {
                            decoupled.mId = mId.toString();
                            decoupled.date = Date.now();
                            root.child('transactions').child(id).child('history').push(decoupled, function(err) {
                              if(err)
                                res.json({'status': 'An error occured'});
                              res.json({'status': 'Success'});
                            });
                          }
                        }
                      );
                    } else {
                      res.json({'status': 'Does not require my attention'});
                    }
                  }
                );
              }
            );
          } else {
            res.json({'status': 'Invalid Request'});
          }
        }
      );
    }
  };
};

const FORMATS = {
  diamond: {
    sender: 'DIAMOND',
    pattern: /Credit Alert!\s+Acc#:.+\s+Amt:(.+)\s+Desc:(.+)\s+Time:(.+)\s+[.\s,:\w]+/
  },
  stanbic: {
    sender: 'STANBIC',
    pattern: /Credit Alert!/
  }
};

const compare = (phraseA, phraseB) => {
  // Reorder the phrases to follow alphabetical order for optimal search
  phraseA = tokenizer.tokenize(phraseA).sort().join(" ");
  phraseB = tokenizer.tokenize(phraseB).sort().join(" ");

  // Compare both phrases using Jaro Winkler Distance algorithm to get p between 0 and 1
  let p = natural.JaroWinklerDistance(phraseA, phraseB);

  // Require p > 0.8 to succeed
  console.log(p);
  return p > 0.8 ? true : false;
};

const parseMessage = (sender, message, complete) => {
  let banks = _.keys(FORMATS);
  async.eachSeries(banks,
    (bank, next) => {
      // Ensure the sender you got is the sender being expected
      if(FORMATS[bank].sender.toLowerCase() === sender.toLowerCase()) {
        // Run messages against regular expression
        let res = FORMATS[bank].pattern.exec(message);
        if(res) {
          // Return some information about the message you found
          complete({depositor: res[2], rawDate: res[3], amount: res[1], bank: bank});
        }
        next();
      }
    },
    err => {
      if(err)
        console.log("Something went wrong!");
      complete(false);
    }
  );
};

const sanitizeNumber = function(formattedNumber) {
  formattedNumber = formattedNumber.replace(",", "");
  return parseFloat(formattedNumber)
}<commit_msg>Chore: Refactored controller structure to be less annoying<commit_after>'use strict'

var async = require('async'),
  _ = require('lodash'),
  natural = require('natural'),
  tokenizer = new natural.WordTokenizer();

module.exports = (root) => {
  return {
    smsRegisterTransaction: (req, res) => {
      root.child('transactions').once('value',
        snap => {
          let transactions = snap.val();

          if(!req.query || _.isEmpty(req.query)) {
            res.json({'status': 'Invalid Request'});
          }

          let payload = req.query;
          let mId = root.child('messages').push();
          mId.set(payload,
            err => {
              if(err)
                res.json({'status': 'An error occured'});

              parseMessage(payload.msisdn, payload.text,
                decoupled => {
                  if(!decoupled) {
                    res.json({'status': 'Does not require my attention'});
                  }

                  let status = false;

                  _.forEach(transactions,
                    (transaction, id) => {
                      let amount = sanitizeNumber(decoupled.amount);
                      let diff = Math.abs(transaction.amount - amount);

                      if(compare(transaction.name, decoupled.depositor) && diff < 100) {
                        decoupled.mId = mId.toString();
                        decoupled.date = Date.now();
                        root.child('transactions').child(id).child('history').push(decoupled, function(err) {
                          if(err)
                            res.json({'status': 'An error occured'});
                          res.json({'status': 'Success'});
                        });
                      } else {
                        res.json({'status': 'Might not require my attention'});
                      }
                    }
                  );
                }
              );
            }
          );
        }
      );
    }
  };
};

const FORMATS = {
  diamond: {
    sender: 'DIAMOND',
    pattern: /Credit Alert!\s+Acc#:.+\s+Amt:(.+)\s+Desc:(.+)\s+Time:(.+)\s+[.\s,:\w]+/
  },
  stanbic: {
    sender: 'STANBIC',
    pattern: /Credit Alert!/
  }
};

const compare = (phraseA, phraseB) => {
  // Reorder the phrases to follow alphabetical order for optimal search
  phraseA = tokenizer.tokenize(phraseA).sort().join(" ");
  phraseB = tokenizer.tokenize(phraseB).sort().join(" ");

  // Compare both phrases using Jaro Winkler Distance algorithm to get p between 0 and 1
  let p = natural.JaroWinklerDistance(phraseA, phraseB);

  // Require p > 0.8 to succeed
  console.log(p);
  return p > 0.8 ? true : false;
};

const parseMessage = (sender, message, complete) => {
  let banks = _.keys(FORMATS);
  async.eachSeries(banks,
    (bank, next) => {
      // Ensure the sender you got is the sender being expected
      if(FORMATS[bank].sender.toLowerCase() === sender.toLowerCase()) {
        // Run messages against regular expression
        let res = FORMATS[bank].pattern.exec(message);
        if(res) {
          // Return some information about the message you found
          complete({depositor: res[2], rawDate: res[3], amount: res[1], bank: bank});
        }
        next();
      }
    },
    err => {
      if(err)
        console.log("Something went wrong!");
      complete(false);
    }
  );
};

const sanitizeNumber = function(formattedNumber) {
  formattedNumber = formattedNumber.replace(",", "");
  return parseFloat(formattedNumber)
}<|endoftext|>


<commit_before>/**
 * The global instance of Botpress, which is the main object
 * your bot will use to interact with Botpress.
 * @var {Botpress} bp
 * @example
 * // File: index.js
 * // All bots are passed an instance of `bp` upon start
 * // This is an example of an empty bot
 * module.exports = (bp) => { ... }
 */

/**
 * @namespace Botpress
 * @property {DialogEngine}  dialogEngine APIs to create and manipulate conversation flows
 * @property {KVS}  kvs Convenient, high-level storage mechanism
 * @property {ContentManager}  contentManager APIs to manage the content programmatically
 * @property {ContentRenderer}  renderers Change the look and feel of the
 * Content Elements (messages) on the different channels
 * @property {Database}  db (Advanced) Access to the internal Botpress Database
 * @property {Users}  users Store and manipulate data about users
 * @property {DialogStateManager}  dialogEngine.stateManager APIs to manipulate conversation states
 * @property {Logger}  logger Logging utility
 * @property {Botfile}  botfile The current botfile of the running bot
 */

import 'source-map-support/register'

import chalk from 'chalk'
import path from 'path'
import fs from 'fs'
import _ from 'lodash'
import cluster from 'cluster'
import dotenv from 'dotenv'
import ms from 'ms'

import createMiddlewares from './middlewares'
import createLogger from './logger'
import createSecurity from './security'
import createNotifications from './notifications'
import createHearMiddleware from './hear'
import createFallbackMiddleware from './fallback'
import createDatabase from './database'
import createGhostManager from './ghost-content'
import createMediaManager from './media-manager'
import createLicensing from './licensing'
import createAbout from './about'
import createModules from './modules'
import createCloud from './cloud'
import createRenderers from './renderers'
import createUsers from './users'
import createContentManager from './content/service'
import defaultGetItemProviders from './content/getItemProviders'
import createHelpers from './helpers'
import createJanitor from './janitor'
import stats from './stats'

import EventBus from './bus'
import ConfigurationManager from './config-manager'
import FlowProvider from './dialog/provider'
import StateManager from './dialog/state'
import DialogEngine from './dialog/engine'
import DialogProcessors from './dialog/processors'
import DialogJanitor from './dialog/janitor'
import SkillsManager from './skills'
import Queue from './queues/memory'

import packageJson from '../package.json'

import createServer from './server'

import { getDataLocation, getBotpressVersion } from './util'

import { isDeveloping, print } from './util'

const RESTART_EXIT_CODE = 107

const mkdirIfNeeded = (path, logger) => {
  if (!fs.existsSync(path)) {
    logger.info(`Creating data directory: ${path}`)

    try {
      fs.mkdirSync(path)
    } catch (err) {
      logger.error(`[FATAL] Error creating directory: ${err.message}`)
      process.exit(1)
    }
  }
}

const REQUIRED_PROPS = ['botUrl']

const validateBotfile = botfile => {
  if ('disableFileLogs' in botfile || _.get(botfile, 'log.file')) {
    console.log(
      `
      You're using the old logs configuration format.
      Since v11 botpress has stopped storing logs in files and
      has moved them to the database.

      Please update your botfile.

      Old configuration format:
        /*
          By default logs are enabled and available in dataDir
        */
        disableFileLogs: false,
        log: {
          file: 'bot.log',
          maxSize: 1e6 // 1mb
        }

      New format:
        /*
          By default logs are enabled and stored in the DB for 30 days
        */
        logs: {
          enabled: true,
          keepDays: 30
        }
      `
    )
    throw new Error('Outdated botfile format')
  }

  for (const prop of REQUIRED_PROPS) {
    if (!(prop in botfile)) {
      throw new Error(`Missing required botpress setting: ${prop}`)
    }
  }
}

class botpress {
  constructor({ botfile, options = {} }) {
    this.version = getBotpressVersion()
    /**
     * The project location, which is the folder where botfile.js located
     */
    this.projectLocation = path.dirname(botfile)

    /**
     * Setup env with dotenv *before* requiring the botfile config
     */
    this._setupEnv()

    /**
     * The botfile config object
     */
    // eslint-disable-next-line no-eval
    this.botfile = eval('require')(botfile)
    validateBotfile(this.botfile)

    this.stats = stats(this.botfile)

    this.interval = null

    /*
      Check --inspect flag
    */

    const opts = options.opts()

    this.hasInspectMode = opts.inspect || opts.i
  }

  /**
   * Start the bot instance
   *
   * It will do the following initiation steps:
   *
   * 1. setup logger
   * 2. resolve paths (dataLocation)
   * 3. inject security functions
   * 4. load modules
   * @private
   */
  async _start() {
    this.stats.track('bot', 'started')

    if (!this.interval) {
      this.interval = setInterval(() => {
        this.stats.track('bot', 'running')
      }, 30 * 1000)
    }

    this.botpressPath = path.join(__dirname, '../')

    const { projectLocation, botfile } = this

    const isFirstRun = fs.existsSync(path.join(projectLocation, '.welcome'))
    const dataLocation = getDataLocation(botfile.dataDir, projectLocation)
    const configLocation = getDataLocation(botfile.modulesConfigDir, projectLocation)
    const dbLocation = path.join(dataLocation, 'db.sqlite')
    const version = packageJson.version

    const logger = createLogger(botfile.logs)
    mkdirIfNeeded(dataLocation, logger)
    mkdirIfNeeded(configLocation, logger)

    const db = createDatabase({
      sqlite: { location: dbLocation },
      postgres: botfile.postgres,
      logger,
      botpressPath: this.botpressPath
    })

    await db.get() // Running migrations

    const janitor = createJanitor({ db, logger })

    logger.enableDbStorageIfNeeded({ db, janitor })
    logger.info(`Starting botpress version ${version}`)

    janitor.start()

    const kvs = db._kvs

    const cloud = await createCloud({ projectLocation, botfile, logger })

    if (!!botfile.login.useCloud && (await cloud.isPaired())) {
      setInterval(() => cloud.updateRemoteEnv(), ms('10m'))
      cloud.updateRemoteEnv() // async on purpose
    }

    const configManager = new ConfigurationManager({ configLocation, botfile, logger })

    const security = await createSecurity({
      dataLocation,
      securityConfig: botfile.login,
      projectLocation,
      db,
      cloud,
      logger
    })

    const modules = createModules(logger, projectLocation, dataLocation, configManager)

    const moduleDefinitions = modules._scan()

    const events = new EventBus()

    const notifications = createNotifications({
      knex: await db.get(),
      modules: moduleDefinitions,
      logger,
      events
    })
    const about = createAbout(projectLocation)
    const licensing = createLicensing({
      logger,
      projectLocation,
      version,
      db,
      botfile,
      bp: this
    })
    const middlewares = createMiddlewares(this, dataLocation, projectLocation, logger)
    const { hear, middleware: hearMiddleware } = createHearMiddleware()
    const { middleware: fallbackMiddleware } = createFallbackMiddleware(this)

    const users = createUsers({ db })
    const ghostManager = createGhostManager({
      projectLocation,
      logger,
      db,
      enabled: !!_.get(botfile, 'ghostContent.enabled')
    })
    const contentManager = await createContentManager({
      logger,
      projectLocation,
      botfile,
      ghostManager
    })
    const mediaManager = await createMediaManager({
      botfile,
      logger,
      ghostManager,
      projectLocation
    })

    // Register the built-in item providers such as "-random()"
    Object.keys(defaultGetItemProviders).forEach(provider => {
      contentManager.registerGetItemProvider(provider, defaultGetItemProviders[provider])
    })

    const renderers = createRenderers({
      logger,
      middlewares,
      db,
      contentManager,
      botfile
    })

    const stateManager = StateManager({ db })
    const flowProvider = new FlowProvider({ logger, projectLocation, botfile, ghostManager })
    const dialogJanitor = new DialogJanitor({ db, middlewares, botfile })
    const dialogEngine = new DialogEngine({ flowProvider, stateManager, logger })

    const skillsManager = new SkillsManager({ logger })

    dialogEngine.onError(({ message }) =>
      notifications.create({ message: `DialogEngine: ${message}`, level: 'error', redirectUrl: '/logs' })
    )

    // Registers the default output processor, which sends messages to the user
    dialogEngine.registerOutputProcessor(DialogProcessors['default'])
    dialogJanitor.install()

    const incomingQueue = new Queue('Incoming', logger, {
      redis: botfile.redis
    })
    incomingQueue.subscribe(job => middlewares.sendIncomingImmediately(job.event))

    const outgoingQueue = new Queue('Outgoing', logger, {
      redis: botfile.redis
    })
    outgoingQueue.subscribe(job => middlewares.sendOutgoingImmediately(job.event))

    const messages = {
      in: {
        enqueue: event => incomingQueue.enqueue({ event }),
        cancelAll: event => incomingQueue.cancelAll({ event }),
        peek: event => incomingQueue.peek({ event })
      },
      out: {
        enqueue: event => outgoingQueue.enqueue({ event }),
        cancelAll: event => outgoingQueue.cancelAll({ event }),
        peek: event => outgoingQueue.peek({ event })
      }
    }

    middlewares.register(renderers.incomingMiddleware)
    middlewares.register(hearMiddleware)
    middlewares.register(fallbackMiddleware)

    _.assign(this, {
      dataLocation,
      isFirstRun,
      version,
      logger,
      security, // login, authenticate, getSecret
      events,
      notifications, // load, save, send
      about,
      middlewares,
      hear,
      licensing,
      modules,
      db,
      janitor,
      kvs,
      configManager,
      cloud,
      renderers,
      users,
      ghostManager,
      contentManager,
      mediaManager,
      dialogEngine,
      dialogJanitor,
      messages,
      skills: skillsManager
    })

    Object.defineProperty(this, 'umm', {
      get() {
        logger.warn('DEPRECATION NOTICE – bp.umm is deprecated and will be removed – Please see bp.renderers instead.')
        return renderers
      }
    })

    const loadedModules = await modules._load(moduleDefinitions, this)

    this.stats.track('bot', 'modules', 'loaded', loadedModules.length)

    _.assign(this, {
      _loadedModules: loadedModules
    })

    skillsManager.registerSkillsFromModules(_.values(loadedModules))
    await contentManager.init()

    notifications._bindEvents()

    const server = createServer(this)
    server.start().then(srv => {
      this.stopServer = srv && srv.stop

      if (this.hasInspectMode) {
        const serverPID = process.pid
        const inspectSignal = 'SIGUSR1'

        if (process.platform === 'win32') {
          process._debugProcess(serverPID)
        } else {
          process.kill(serverPID, inspectSignal)
        }
      }

      events.emit('ready')
      for (const mod of _.values(loadedModules)) {
        mod.handlers.ready && mod.handlers.ready(this, mod.configuration, createHelpers)
      }

      const { botUrl } = botfile
      logger.info(chalk.green.bold(`Bot launched. Visit: ${botUrl}`))
    })

    const middlewareAutoLoading = _.get(botfile, 'middleware.autoLoading')
    if (!_.isNil(middlewareAutoLoading) && middlewareAutoLoading === false) {
      logger.debug('Middleware Auto Loading was disabled. Call bp.middlewares.load() manually.')
    } else {
      middlewares.load()
    }

    // eslint-disable-next-line no-eval
    const projectEntry = eval('require')(projectLocation)
    if (typeof projectEntry === 'function') {
      projectEntry.call(projectEntry, this)
    } else {
      logger.error('[FATAL] The bot entry point must be a function that takes an instance of bp')
      process.exit(1)
    }

    process.on('uncaughtException', err => {
      logger.error('[FATAL] An unhandled exception occurred in your bot', err)
      if (isDeveloping) {
        logger.error(err.stack)
      }

      this.stats.trackException(err.message)
      process.exit(1)
    })

    process.on('unhandledRejection', (reason, p) => {
      logger.error('Unhandled Rejection in Promise: ', p, 'Reason:', reason)

      this.stats.trackException(reason)
      if (isDeveloping && reason && reason.stack) {
        logger.error(reason.stack)
      }
    })
  }

  start = () => {
    if (cluster.isMaster) {
      let firstWorkerHasStartedAlready = false

      const quit = (code = 0) => {
        if (this.stopServer) {
          this.stopServer()
        }

        process.exit(code)
      }

      const receiveMessageFromWorker = message => {
        if (message && message.workerStatus === 'starting') {
          if (!firstWorkerHasStartedAlready) {
            firstWorkerHasStartedAlready = true
          } else {
            print('info', '*** restarted worker process ***')
            this.stats.track('bot', 'restarted')
          }
        } else if (message.type === 'exit') {
          quit()
        }
      }

      cluster.on('exit', (worker, code /* , signal */) => {
        if (code === RESTART_EXIT_CODE) {
          cluster.fork().on('message', receiveMessageFromWorker)
        } else {
          quit(code)
        }
      })

      cluster.fork().on('message', receiveMessageFromWorker)
    }

    if (cluster.isWorker) {
      process.send({ workerStatus: 'starting' })
      this._start().catch(err => {
        print('error', 'Error starting botpress: ', err.message, err.stack)
      })
    }
  }

  restart(interval = 0) {
    setTimeout(() => {
      process.exit(RESTART_EXIT_CODE)
    }, interval)
  }

  _setupEnv() {
    const envPath = path.resolve(this.projectLocation, '.env')
    if (fs.existsSync(envPath)) {
      const envConfig = dotenv.parse(fs.readFileSync(envPath))
      for (const k in envConfig) {
        if (_.isNil(process.env[k]) || process.env.ENV_OVERLOAD) {
          process.env[k] = envConfig[k]
        }
      }
    }
  }
}

module.exports = botpress
<commit_msg>fix: options not always defined across multiple botpress envs<commit_after>/**
 * The global instance of Botpress, which is the main object
 * your bot will use to interact with Botpress.
 * @var {Botpress} bp
 * @example
 * // File: index.js
 * // All bots are passed an instance of `bp` upon start
 * // This is an example of an empty bot
 * module.exports = (bp) => { ... }
 */

/**
 * @namespace Botpress
 * @property {DialogEngine}  dialogEngine APIs to create and manipulate conversation flows
 * @property {KVS}  kvs Convenient, high-level storage mechanism
 * @property {ContentManager}  contentManager APIs to manage the content programmatically
 * @property {ContentRenderer}  renderers Change the look and feel of the
 * Content Elements (messages) on the different channels
 * @property {Database}  db (Advanced) Access to the internal Botpress Database
 * @property {Users}  users Store and manipulate data about users
 * @property {DialogStateManager}  dialogEngine.stateManager APIs to manipulate conversation states
 * @property {Logger}  logger Logging utility
 * @property {Botfile}  botfile The current botfile of the running bot
 */

import 'source-map-support/register'

import chalk from 'chalk'
import path from 'path'
import fs from 'fs'
import _ from 'lodash'
import cluster from 'cluster'
import dotenv from 'dotenv'
import ms from 'ms'

import createMiddlewares from './middlewares'
import createLogger from './logger'
import createSecurity from './security'
import createNotifications from './notifications'
import createHearMiddleware from './hear'
import createFallbackMiddleware from './fallback'
import createDatabase from './database'
import createGhostManager from './ghost-content'
import createMediaManager from './media-manager'
import createLicensing from './licensing'
import createAbout from './about'
import createModules from './modules'
import createCloud from './cloud'
import createRenderers from './renderers'
import createUsers from './users'
import createContentManager from './content/service'
import defaultGetItemProviders from './content/getItemProviders'
import createHelpers from './helpers'
import createJanitor from './janitor'
import stats from './stats'

import EventBus from './bus'
import ConfigurationManager from './config-manager'
import FlowProvider from './dialog/provider'
import StateManager from './dialog/state'
import DialogEngine from './dialog/engine'
import DialogProcessors from './dialog/processors'
import DialogJanitor from './dialog/janitor'
import SkillsManager from './skills'
import Queue from './queues/memory'

import packageJson from '../package.json'

import createServer from './server'

import { getDataLocation, getBotpressVersion } from './util'

import { isDeveloping, print } from './util'

const RESTART_EXIT_CODE = 107

const mkdirIfNeeded = (path, logger) => {
  if (!fs.existsSync(path)) {
    logger.info(`Creating data directory: ${path}`)

    try {
      fs.mkdirSync(path)
    } catch (err) {
      logger.error(`[FATAL] Error creating directory: ${err.message}`)
      process.exit(1)
    }
  }
}

const REQUIRED_PROPS = ['botUrl']

const validateBotfile = botfile => {
  if ('disableFileLogs' in botfile || _.get(botfile, 'log.file')) {
    console.log(
      `
      You're using the old logs configuration format.
      Since v11 botpress has stopped storing logs in files and
      has moved them to the database.

      Please update your botfile.

      Old configuration format:
        /*
          By default logs are enabled and available in dataDir
        */
        disableFileLogs: false,
        log: {
          file: 'bot.log',
          maxSize: 1e6 // 1mb
        }

      New format:
        /*
          By default logs are enabled and stored in the DB for 30 days
        */
        logs: {
          enabled: true,
          keepDays: 30
        }
      `
    )
    throw new Error('Outdated botfile format')
  }

  for (const prop of REQUIRED_PROPS) {
    if (!(prop in botfile)) {
      throw new Error(`Missing required botpress setting: ${prop}`)
    }
  }
}

class botpress {
  constructor({ botfile, options = {} }) {
    this.version = getBotpressVersion()
    /**
     * The project location, which is the folder where botfile.js located
     */
    this.projectLocation = path.dirname(botfile)

    /**
     * Setup env with dotenv *before* requiring the botfile config
     */
    this._setupEnv()

    /**
     * The botfile config object
     */
    // eslint-disable-next-line no-eval
    this.botfile = eval('require')(botfile)
    validateBotfile(this.botfile)

    this.stats = stats(this.botfile)

    this.interval = null

    /*
      Check --inspect flag
    */

    const opts = options && options.opts()

    this.hasInspectMode = opts.inspect || opts.i
  }

  /**
   * Start the bot instance
   *
   * It will do the following initiation steps:
   *
   * 1. setup logger
   * 2. resolve paths (dataLocation)
   * 3. inject security functions
   * 4. load modules
   * @private
   */
  async _start() {
    this.stats.track('bot', 'started')

    if (!this.interval) {
      this.interval = setInterval(() => {
        this.stats.track('bot', 'running')
      }, 30 * 1000)
    }

    this.botpressPath = path.join(__dirname, '../')

    const { projectLocation, botfile } = this

    const isFirstRun = fs.existsSync(path.join(projectLocation, '.welcome'))
    const dataLocation = getDataLocation(botfile.dataDir, projectLocation)
    const configLocation = getDataLocation(botfile.modulesConfigDir, projectLocation)
    const dbLocation = path.join(dataLocation, 'db.sqlite')
    const version = packageJson.version

    const logger = createLogger(botfile.logs)
    mkdirIfNeeded(dataLocation, logger)
    mkdirIfNeeded(configLocation, logger)

    const db = createDatabase({
      sqlite: { location: dbLocation },
      postgres: botfile.postgres,
      logger,
      botpressPath: this.botpressPath
    })

    await db.get() // Running migrations

    const janitor = createJanitor({ db, logger })

    logger.enableDbStorageIfNeeded({ db, janitor })
    logger.info(`Starting botpress version ${version}`)

    janitor.start()

    const kvs = db._kvs

    const cloud = await createCloud({ projectLocation, botfile, logger })

    if (!!botfile.login.useCloud && (await cloud.isPaired())) {
      setInterval(() => cloud.updateRemoteEnv(), ms('10m'))
      cloud.updateRemoteEnv() // async on purpose
    }

    const configManager = new ConfigurationManager({ configLocation, botfile, logger })

    const security = await createSecurity({
      dataLocation,
      securityConfig: botfile.login,
      projectLocation,
      db,
      cloud,
      logger
    })

    const modules = createModules(logger, projectLocation, dataLocation, configManager)

    const moduleDefinitions = modules._scan()

    const events = new EventBus()

    const notifications = createNotifications({
      knex: await db.get(),
      modules: moduleDefinitions,
      logger,
      events
    })
    const about = createAbout(projectLocation)
    const licensing = createLicensing({
      logger,
      projectLocation,
      version,
      db,
      botfile,
      bp: this
    })
    const middlewares = createMiddlewares(this, dataLocation, projectLocation, logger)
    const { hear, middleware: hearMiddleware } = createHearMiddleware()
    const { middleware: fallbackMiddleware } = createFallbackMiddleware(this)

    const users = createUsers({ db })
    const ghostManager = createGhostManager({
      projectLocation,
      logger,
      db,
      enabled: !!_.get(botfile, 'ghostContent.enabled')
    })
    const contentManager = await createContentManager({
      logger,
      projectLocation,
      botfile,
      ghostManager
    })
    const mediaManager = await createMediaManager({
      botfile,
      logger,
      ghostManager,
      projectLocation
    })

    // Register the built-in item providers such as "-random()"
    Object.keys(defaultGetItemProviders).forEach(provider => {
      contentManager.registerGetItemProvider(provider, defaultGetItemProviders[provider])
    })

    const renderers = createRenderers({
      logger,
      middlewares,
      db,
      contentManager,
      botfile
    })

    const stateManager = StateManager({ db })
    const flowProvider = new FlowProvider({ logger, projectLocation, botfile, ghostManager })
    const dialogJanitor = new DialogJanitor({ db, middlewares, botfile })
    const dialogEngine = new DialogEngine({ flowProvider, stateManager, logger })

    const skillsManager = new SkillsManager({ logger })

    dialogEngine.onError(({ message }) =>
      notifications.create({ message: `DialogEngine: ${message}`, level: 'error', redirectUrl: '/logs' })
    )

    // Registers the default output processor, which sends messages to the user
    dialogEngine.registerOutputProcessor(DialogProcessors['default'])
    dialogJanitor.install()

    const incomingQueue = new Queue('Incoming', logger, {
      redis: botfile.redis
    })
    incomingQueue.subscribe(job => middlewares.sendIncomingImmediately(job.event))

    const outgoingQueue = new Queue('Outgoing', logger, {
      redis: botfile.redis
    })
    outgoingQueue.subscribe(job => middlewares.sendOutgoingImmediately(job.event))

    const messages = {
      in: {
        enqueue: event => incomingQueue.enqueue({ event }),
        cancelAll: event => incomingQueue.cancelAll({ event }),
        peek: event => incomingQueue.peek({ event })
      },
      out: {
        enqueue: event => outgoingQueue.enqueue({ event }),
        cancelAll: event => outgoingQueue.cancelAll({ event }),
        peek: event => outgoingQueue.peek({ event })
      }
    }

    middlewares.register(renderers.incomingMiddleware)
    middlewares.register(hearMiddleware)
    middlewares.register(fallbackMiddleware)

    _.assign(this, {
      dataLocation,
      isFirstRun,
      version,
      logger,
      security, // login, authenticate, getSecret
      events,
      notifications, // load, save, send
      about,
      middlewares,
      hear,
      licensing,
      modules,
      db,
      janitor,
      kvs,
      configManager,
      cloud,
      renderers,
      users,
      ghostManager,
      contentManager,
      mediaManager,
      dialogEngine,
      dialogJanitor,
      messages,
      skills: skillsManager
    })

    Object.defineProperty(this, 'umm', {
      get() {
        logger.warn('DEPRECATION NOTICE – bp.umm is deprecated and will be removed – Please see bp.renderers instead.')
        return renderers
      }
    })

    const loadedModules = await modules._load(moduleDefinitions, this)

    this.stats.track('bot', 'modules', 'loaded', loadedModules.length)

    _.assign(this, {
      _loadedModules: loadedModules
    })

    skillsManager.registerSkillsFromModules(_.values(loadedModules))
    await contentManager.init()

    notifications._bindEvents()

    const server = createServer(this)
    server.start().then(srv => {
      this.stopServer = srv && srv.stop

      if (this.hasInspectMode) {
        const serverPID = process.pid
        const inspectSignal = 'SIGUSR1'

        if (process.platform === 'win32') {
          process._debugProcess(serverPID)
        } else {
          process.kill(serverPID, inspectSignal)
        }
      }

      events.emit('ready')
      for (const mod of _.values(loadedModules)) {
        mod.handlers.ready && mod.handlers.ready(this, mod.configuration, createHelpers)
      }

      const { botUrl } = botfile
      logger.info(chalk.green.bold(`Bot launched. Visit: ${botUrl}`))
    })

    const middlewareAutoLoading = _.get(botfile, 'middleware.autoLoading')
    if (!_.isNil(middlewareAutoLoading) && middlewareAutoLoading === false) {
      logger.debug('Middleware Auto Loading was disabled. Call bp.middlewares.load() manually.')
    } else {
      middlewares.load()
    }

    // eslint-disable-next-line no-eval
    const projectEntry = eval('require')(projectLocation)
    if (typeof projectEntry === 'function') {
      projectEntry.call(projectEntry, this)
    } else {
      logger.error('[FATAL] The bot entry point must be a function that takes an instance of bp')
      process.exit(1)
    }

    process.on('uncaughtException', err => {
      logger.error('[FATAL] An unhandled exception occurred in your bot', err)
      if (isDeveloping) {
        logger.error(err.stack)
      }

      this.stats.trackException(err.message)
      process.exit(1)
    })

    process.on('unhandledRejection', (reason, p) => {
      logger.error('Unhandled Rejection in Promise: ', p, 'Reason:', reason)

      this.stats.trackException(reason)
      if (isDeveloping && reason && reason.stack) {
        logger.error(reason.stack)
      }
    })
  }

  start = () => {
    if (cluster.isMaster) {
      let firstWorkerHasStartedAlready = false

      const quit = (code = 0) => {
        if (this.stopServer) {
          this.stopServer()
        }

        process.exit(code)
      }

      const receiveMessageFromWorker = message => {
        if (message && message.workerStatus === 'starting') {
          if (!firstWorkerHasStartedAlready) {
            firstWorkerHasStartedAlready = true
          } else {
            print('info', '*** restarted worker process ***')
            this.stats.track('bot', 'restarted')
          }
        } else if (message.type === 'exit') {
          quit()
        }
      }

      cluster.on('exit', (worker, code /* , signal */) => {
        if (code === RESTART_EXIT_CODE) {
          cluster.fork().on('message', receiveMessageFromWorker)
        } else {
          quit(code)
        }
      })

      cluster.fork().on('message', receiveMessageFromWorker)
    }

    if (cluster.isWorker) {
      process.send({ workerStatus: 'starting' })
      this._start().catch(err => {
        print('error', 'Error starting botpress: ', err.message, err.stack)
      })
    }
  }

  restart(interval = 0) {
    setTimeout(() => {
      process.exit(RESTART_EXIT_CODE)
    }, interval)
  }

  _setupEnv() {
    const envPath = path.resolve(this.projectLocation, '.env')
    if (fs.existsSync(envPath)) {
      const envConfig = dotenv.parse(fs.readFileSync(envPath))
      for (const k in envConfig) {
        if (_.isNil(process.env[k]) || process.env.ENV_OVERLOAD) {
          process.env[k] = envConfig[k]
        }
      }
    }
  }
}

module.exports = botpress
<|endoftext|>


<commit_before>/* global appendChartID, coordsFromTranslate, loadDateFixture */
describe('dc.legend', function () {
    var id, chart, dateDimension, dateValueSumGroup, dateIdSumGroup;

    beforeEach(function () {
        var data = crossfilter(loadDateFixture());
        dateDimension = data.dimension(function (d) { return d3.time.day.utc(d.dd); });
        dateValueSumGroup = dateDimension.group().reduceSum(function (d) { return d.value; });
        dateIdSumGroup = dateDimension.group().reduceSum(function (d) { return d.id; });

        id = 'legend-chart';
        appendChartID(id);
        chart = dc.lineChart('#' + id);

        chart
            .dimension(dateDimension)
            .group(dateIdSumGroup, 'Id Sum')
            .stack(dateValueSumGroup, 'Value Sum')
            .stack(dateValueSumGroup, 'Fixed', function () {})
            .x(d3.time.scale.utc().domain([new Date(2012, 4, 20), new Date(2012, 7, 15)]))
            .legend(dc.legend().x(400).y(10).itemHeight(13).gap(5));
    });

    describe('rendering the legend', function () {
        beforeEach(function () {
            chart.render();
        });

        it('should generate a legend', function () {
            expect(chart.select('g.dc-legend').empty()).toBeFalsy();
        });

        it('should place the legend using the provided x and y values', function () {
            expect(chart.select('g.dc-legend').attr('transform')).toMatchTranslate(400, 10);
        });

        it('should generate a legend item for each stacked line', function () {
            expect(chart.select('g.dc-legend').selectAll('g.dc-legend-item').size()).toBe(3);
        });

        it('should generate legend item boxes', function () {
            expect(legendIcon(0).attr('width')).toBeWithinDelta(13, 2);
            expect(legendIcon(0).attr('height')).toBeWithinDelta(13, 2);
        });

        it('should color the legend item boxes using the chart line colors', function () {
            expect(legendIcon(0).attr('fill')).toMatch(/#1f77b4/i);
            expect(legendIcon(1).attr('fill')).toMatch(/#ff7f0e/i);
            expect(legendIcon(2).attr('fill')).toMatch(/#2ca02c/i);
        });

        it('should generate a legend label for each chart line', function () {
            expect(chart.selectAll('g.dc-legend g.dc-legend-item text').size()).toBe(3);
        });

        it('should position the legend labels', function () {
            expect(legendLabel(0).attr('x')).toBeWithinDelta(15, 2);
            expect(legendLabel(0).attr('y')).toBeWithinDelta(11.75, 2);
            expect(legendLabel(1).attr('x')).toBeWithinDelta(15, 2);
            expect(legendLabel(1).attr('y')).toBeWithinDelta(11.75, 2);
            expect(legendLabel(2).attr('x')).toBeWithinDelta(15, 2);
            expect(legendLabel(2).attr('y')).toBeWithinDelta(11.75, 2);
        });

        it('should label the legend items with the names of their associated stacks', function () {
            expect(legendLabel(0).text()).toBe('Id Sum');
            expect(legendLabel(1).text()).toBe('Value Sum');
            expect(legendLabel(2).text()).toBe('Fixed');
        });

        it('not allow hiding stacks be default', function () {
            legendItem(0).on('click').call(legendItem(0)[0][0], legendItem(0).datum());
            expect(chart.selectAll('path.line').size()).toBe(3);
        });

        describe('without .horizontal(true)', function () {
            it('should place legend items vertically', function () {
                expect(coordsFromTranslate(legendItem(0).attr('transform')).y).toBeWithinDelta(0, 1);
                expect(coordsFromTranslate(legendItem(1).attr('transform')).y).toBeWithinDelta(18, 2);
                expect(coordsFromTranslate(legendItem(2).attr('transform')).y).toBeWithinDelta(36, 4);
            });
        });

        describe('with .horizontal(true)', function () {
            beforeEach(function () {
                chart.legend(dc.legend().horizontal(true));
                chart.render();
            });

            it('should place legend items horizontally', function () {
                expect(coordsFromTranslate(legendItem(0).attr('transform')).x).toBeWithinDelta(0, 1);
                expect(coordsFromTranslate(legendItem(1).attr('transform')).x).toBeWithinDelta(65, 5);
                expect(coordsFromTranslate(legendItem(2).attr('transform')).x).toBeWithinDelta(155, 15);
            });
        });

        describe('with .horizontal(true) and defined legendWidth and itemWidth', function () {
            var legendCoords;
            beforeEach(function () {
                chart.legend(dc.legend().horizontal(true).legendWidth(60).itemWidth(30));
                chart.render();
                legendCoords = d3.range(3).map(function (i) {
                    return coordsFromTranslate(legendItem(i).attr('transform'));
                });
            });

            it('should place legend items in two columns. third item is new row', function () {
                expect(legendCoords[0].x).toBeWithinDelta(0, 1);
                expect(legendCoords[0].y).toBeWithinDelta(0, 1);
                expect(legendCoords[1].x).toBeWithinDelta(30, 5);
                expect(legendCoords[1].y).toBeWithinDelta(0, 1);
                expect(legendCoords[2].x).toBeWithinDelta(0, 1);
                expect(legendCoords[2].y).toBeWithinDelta(13, 5);
            });
        });

        describe('with .autoItemWidth not called', function () {
            beforeEach(function () {
                chart.legend(dc.legend());
            });

            it('_autoItemWidth should be false', function () {
                expect(chart.legend().autoItemWidth()).toBe(false);
            });
        });

        describe('with .autoItemWidth(false)', function () {
            beforeEach(function () {
                chart.legend(dc.legend().autoItemWidth(false));
            });

            it('_autoItemWidth should be false', function () {
                expect(chart.legend().autoItemWidth()).toBe(false);
            });
        });

        describe('with .autoItemWidth(true)', function () {
            beforeEach(function () {
                chart.legend(dc.legend().autoItemWidth(true));
            });
            it('_autoItemWidth should be true', function () {
                expect(chart.legend().autoItemWidth()).toBe(true);
            });
        });

        describe('with .horizontal(true) and .autoItemWidth(true)', function () {

            var
                autoWidthOffset1, fixedWidthOffset1,
                autoWidthOffset2, fixedWidthOffset2;

            beforeEach(function () {
                chart.legend(dc.legend().horizontal(true).itemWidth(30).autoItemWidth(false));
                chart.render();
                fixedWidthOffset1 = coordsFromTranslate(legendItem(1).attr('transform')).x;
                fixedWidthOffset2 = coordsFromTranslate(legendItem(2).attr('transform')).x;
                chart.legend(dc.legend().horizontal(true).itemWidth(30).autoItemWidth(true));
                chart.render();
                autoWidthOffset1  = coordsFromTranslate(legendItem(1).attr('transform')).x;
                autoWidthOffset2  = coordsFromTranslate(legendItem(2).attr('transform')).x;
            });

            it('autoWidth x offset should be greater than fixedWidth x offset for some legend items', function () {
                expect(autoWidthOffset1).toBeGreaterThan(fixedWidthOffset1);
                expect(autoWidthOffset2).toBeGreaterThan(fixedWidthOffset2);
            });
        });

        describe('with .legendText()', function () {
            beforeEach(function () {
                chart.legend(dc.legend().legendText(function (d, i) {
                    var _i = i + 1;

                    return _i + '. ' + d.name;
                }));
                chart.render();
            });

            it('should label the legend items with the names of their associated stacks', function () {
                expect(legendLabel(0).text()).toBe('1. Id Sum');
                expect(legendLabel(1).text()).toBe('2. Value Sum');
                expect(legendLabel(2).text()).toBe('3. Fixed');
            });
        });

        describe('with .maxItems(2)', function () {
            beforeEach(function () {
                chart.legend()
                    .maxItems(2);
                chart.render();
            });
            it('should display two items', function () {
                expect(chart.select('g.dc-legend').selectAll('g.dc-legend-item').size()).toBe(2);
            });
        });

        describe('with invalid .maxItems', function () {
            beforeEach(function () {
                chart.legend()
                    .maxItems('foo');
                chart.render();
            });
            it('should display three items', function () {
                expect(chart.select('g.dc-legend').selectAll('g.dc-legend-item').size()).toBe(3);
            });
        });
    });

    describe('legends with dashed lines', function () {
        beforeEach(function () {
            id = 'legend-chart-dashed';
            appendChartID(id);
            chart = dc.compositeChart('#' + id);

            var subChart1 = dc.lineChart(chart);
            subChart1
                .dimension(dateDimension)
                .group(dateIdSumGroup, 'Id Sum')
                .dashStyle([10,1]);

            var subChart2 = dc.lineChart(chart);
            subChart2
                .dimension(dateDimension)
                .group(dateValueSumGroup, 'Value Sum')
                .dashStyle([2,1]);

            chart
                .x(d3.scale.linear().domain([0,20]))
                .legend(dc.legend().x(400).y(10).itemHeight(13).gap(5))
                .compose([subChart1, subChart2])
                .render();
        });

        it('should style legend line correctly', function () {
            expect(legendLine(0).attr('stroke-dasharray')).toEqualIntList('10,1');
            expect(legendLine(1).attr('stroke-dasharray')).toEqualIntList('2,1');
        });
    });

    describe('legends with hidable stacks', function () {
        beforeEach(function () {
            chart.hidableStacks(true).render();
        });

        describe('clicking on a legend item', function () {
            beforeEach(function () {
                legendItem(0).on('click').call(legendItem(0)[0][0], legendItem(0).datum());
            });

            it('should fade out the legend item', function () {
                expect(legendItem(0).classed('fadeout')).toBeTruthy();
            });

            it('should hide its associated stack', function () {
                expect(chart.selectAll('path.line').size()).toEqual(2);
            });

            it('disable hover highlighting for that legend item', function () {
                legendItem(0).on('mouseover')(legendItem(0).datum());
                expect(d3.select(chart.selectAll('path.line')[0][1]).classed('fadeout')).toBeFalsy();
            });

            describe('clicking on a faded out legend item', function () {
                beforeEach(function () {
                    legendItem(0).on('click').call(legendItem(0)[0][0], legendItem(0).datum());
                });

                it('should unfade the legend item', function () {
                    expect(legendItem(0).classed('fadeout')).toBeFalsy();
                });

                it('should unfade its associated stack', function () {
                    expect(chart.selectAll('path.line').size()).toEqual(3);
                });
            });
        });
    });

    function legendItem (n) {
        return d3.select(chart.selectAll('g.dc-legend g.dc-legend-item')[0][n]);
    }
    function legendLabel (n) {
        return d3.select(chart.selectAll('g.dc-legend g.dc-legend-item text')[0][n]);
    }
    function legendIcon (n) {
        return d3.select(chart.selectAll('g.dc-legend g.dc-legend-item rect')[0][n]);
    }
    function legendLine (n) {
        return d3.select(chart.selectAll('g.dc-legend g.dc-legend-item line')[0][n]);
    }
});

<commit_msg>test that legend wraps at correct place<commit_after>/* global appendChartID, coordsFromTranslate, loadDateFixture */
describe('dc.legend', function () {
    var id, chart, dateDimension, dateValueSumGroup, dateIdSumGroup;

    beforeEach(function () {
        var data = crossfilter(loadDateFixture());
        dateDimension = data.dimension(function (d) { return d3.time.day.utc(d.dd); });
        dateValueSumGroup = dateDimension.group().reduceSum(function (d) { return d.value; });
        dateIdSumGroup = dateDimension.group().reduceSum(function (d) { return d.id; });

        id = 'legend-chart';
        appendChartID(id);
        chart = dc.lineChart('#' + id);

        chart
            .dimension(dateDimension)
            .group(dateIdSumGroup, 'Id Sum')
            .stack(dateValueSumGroup, 'Value Sum')
            .stack(dateValueSumGroup, 'Fixed', function () {})
            .x(d3.time.scale.utc().domain([new Date(2012, 4, 20), new Date(2012, 7, 15)]))
            .legend(dc.legend().x(400).y(10).itemHeight(13).gap(5));
    });

    describe('rendering the legend', function () {
        beforeEach(function () {
            chart.render();
        });

        it('should generate a legend', function () {
            expect(chart.select('g.dc-legend').empty()).toBeFalsy();
        });

        it('should place the legend using the provided x and y values', function () {
            expect(chart.select('g.dc-legend').attr('transform')).toMatchTranslate(400, 10);
        });

        it('should generate a legend item for each stacked line', function () {
            expect(chart.select('g.dc-legend').selectAll('g.dc-legend-item').size()).toBe(3);
        });

        it('should generate legend item boxes', function () {
            expect(legendIcon(0).attr('width')).toBeWithinDelta(13, 2);
            expect(legendIcon(0).attr('height')).toBeWithinDelta(13, 2);
        });

        it('should color the legend item boxes using the chart line colors', function () {
            expect(legendIcon(0).attr('fill')).toMatch(/#1f77b4/i);
            expect(legendIcon(1).attr('fill')).toMatch(/#ff7f0e/i);
            expect(legendIcon(2).attr('fill')).toMatch(/#2ca02c/i);
        });

        it('should generate a legend label for each chart line', function () {
            expect(chart.selectAll('g.dc-legend g.dc-legend-item text').size()).toBe(3);
        });

        it('should position the legend labels', function () {
            expect(legendLabel(0).attr('x')).toBeWithinDelta(15, 2);
            expect(legendLabel(0).attr('y')).toBeWithinDelta(11.75, 2);
            expect(legendLabel(1).attr('x')).toBeWithinDelta(15, 2);
            expect(legendLabel(1).attr('y')).toBeWithinDelta(11.75, 2);
            expect(legendLabel(2).attr('x')).toBeWithinDelta(15, 2);
            expect(legendLabel(2).attr('y')).toBeWithinDelta(11.75, 2);
        });

        it('should label the legend items with the names of their associated stacks', function () {
            expect(legendLabel(0).text()).toBe('Id Sum');
            expect(legendLabel(1).text()).toBe('Value Sum');
            expect(legendLabel(2).text()).toBe('Fixed');
        });

        it('not allow hiding stacks be default', function () {
            legendItem(0).on('click').call(legendItem(0)[0][0], legendItem(0).datum());
            expect(chart.selectAll('path.line').size()).toBe(3);
        });

        describe('without .horizontal(true)', function () {
            it('should place legend items vertically', function () {
                expect(coordsFromTranslate(legendItem(0).attr('transform')).y).toBeWithinDelta(0, 1);
                expect(coordsFromTranslate(legendItem(1).attr('transform')).y).toBeWithinDelta(18, 2);
                expect(coordsFromTranslate(legendItem(2).attr('transform')).y).toBeWithinDelta(36, 4);
            });
        });

        describe('with .horizontal(true)', function () {
            beforeEach(function () {
                chart.legend(dc.legend().horizontal(true));
                chart.render();
            });

            it('should place legend items horizontally', function () {
                expect(coordsFromTranslate(legendItem(0).attr('transform')).x).toBeWithinDelta(0, 1);
                expect(coordsFromTranslate(legendItem(1).attr('transform')).x).toBeWithinDelta(65, 5);
                expect(coordsFromTranslate(legendItem(2).attr('transform')).x).toBeWithinDelta(155, 15);
            });
        });

        describe('with .horizontal(true) and defined legendWidth and itemWidth', function () {
            var legendCoords;
            beforeEach(function () {
                chart.legend(dc.legend().horizontal(true).legendWidth(60).itemWidth(30));
                chart.render();
                legendCoords = d3.range(3).map(function (i) {
                    return coordsFromTranslate(legendItem(i).attr('transform'));
                });
            });

            it('should place legend items in two columns. third item is new row', function () {
                expect(legendCoords[0].x).toBeWithinDelta(0, 1);
                expect(legendCoords[0].y).toBeWithinDelta(0, 1);
                expect(legendCoords[1].x).toBeWithinDelta(30, 5);
                expect(legendCoords[1].y).toBeWithinDelta(0, 1);
                expect(legendCoords[2].x).toBeWithinDelta(0, 1);
                expect(legendCoords[2].y).toBeWithinDelta(13, 5);
            });
        });

        describe('with .autoItemWidth not called', function () {
            beforeEach(function () {
                chart.legend(dc.legend());
            });

            it('_autoItemWidth should be false', function () {
                expect(chart.legend().autoItemWidth()).toBe(false);
            });
        });

        describe('with .autoItemWidth(false)', function () {
            beforeEach(function () {
                chart.legend(dc.legend().autoItemWidth(false));
            });

            it('_autoItemWidth should be false', function () {
                expect(chart.legend().autoItemWidth()).toBe(false);
            });
        });

        describe('with .autoItemWidth(true)', function () {
            beforeEach(function () {
                chart.legend(dc.legend().autoItemWidth(true));
            });
            it('_autoItemWidth should be true', function () {
                expect(chart.legend().autoItemWidth()).toBe(true);
            });
        });

        describe('with .horizontal(true) and .autoItemWidth(true)', function () {

            var fixedWidthOffset1, fixedWidthOffset2,
                autoWidthCoords;

            beforeEach(function () {
                chart.legend(dc.legend().horizontal(true).itemWidth(30).autoItemWidth(false));
                chart.render();
                fixedWidthOffset1 = coordsFromTranslate(legendItem(1).attr('transform')).x;
                fixedWidthOffset2 = coordsFromTranslate(legendItem(2).attr('transform')).x;
                chart.legend(dc.legend().horizontal(true).itemWidth(30).autoItemWidth(true).legendWidth(200));
                chart.render();
                autoWidthCoords = d3.range(3).map(function (i) {
                    return coordsFromTranslate(legendItem(i).attr('transform'));
                });
            });

            it('autoWidth x offset should be greater than fixedWidth x offset for the second item', function () {
                expect(autoWidthCoords[1].x).toBeGreaterThan(fixedWidthOffset1);
            });
            it('should wrap the third item based on actual widths', function () {
                expect(autoWidthCoords[2].x).toBe(0);
            });
        });

        describe('with .legendText()', function () {
            beforeEach(function () {
                chart.legend(dc.legend().legendText(function (d, i) {
                    var _i = i + 1;

                    return _i + '. ' + d.name;
                }));
                chart.render();
            });

            it('should label the legend items with the names of their associated stacks', function () {
                expect(legendLabel(0).text()).toBe('1. Id Sum');
                expect(legendLabel(1).text()).toBe('2. Value Sum');
                expect(legendLabel(2).text()).toBe('3. Fixed');
            });
        });

        describe('with .maxItems(2)', function () {
            beforeEach(function () {
                chart.legend()
                    .maxItems(2);
                chart.render();
            });
            it('should display two items', function () {
                expect(chart.select('g.dc-legend').selectAll('g.dc-legend-item').size()).toBe(2);
            });
        });

        describe('with invalid .maxItems', function () {
            beforeEach(function () {
                chart.legend()
                    .maxItems('foo');
                chart.render();
            });
            it('should display three items', function () {
                expect(chart.select('g.dc-legend').selectAll('g.dc-legend-item').size()).toBe(3);
            });
        });
    });

    describe('legends with dashed lines', function () {
        beforeEach(function () {
            id = 'legend-chart-dashed';
            appendChartID(id);
            chart = dc.compositeChart('#' + id);

            var subChart1 = dc.lineChart(chart);
            subChart1
                .dimension(dateDimension)
                .group(dateIdSumGroup, 'Id Sum')
                .dashStyle([10,1]);

            var subChart2 = dc.lineChart(chart);
            subChart2
                .dimension(dateDimension)
                .group(dateValueSumGroup, 'Value Sum')
                .dashStyle([2,1]);

            chart
                .x(d3.scale.linear().domain([0,20]))
                .legend(dc.legend().x(400).y(10).itemHeight(13).gap(5))
                .compose([subChart1, subChart2])
                .render();
        });

        it('should style legend line correctly', function () {
            expect(legendLine(0).attr('stroke-dasharray')).toEqualIntList('10,1');
            expect(legendLine(1).attr('stroke-dasharray')).toEqualIntList('2,1');
        });
    });

    describe('legends with hidable stacks', function () {
        beforeEach(function () {
            chart.hidableStacks(true).render();
        });

        describe('clicking on a legend item', function () {
            beforeEach(function () {
                legendItem(0).on('click').call(legendItem(0)[0][0], legendItem(0).datum());
            });

            it('should fade out the legend item', function () {
                expect(legendItem(0).classed('fadeout')).toBeTruthy();
            });

            it('should hide its associated stack', function () {
                expect(chart.selectAll('path.line').size()).toEqual(2);
            });

            it('disable hover highlighting for that legend item', function () {
                legendItem(0).on('mouseover')(legendItem(0).datum());
                expect(d3.select(chart.selectAll('path.line')[0][1]).classed('fadeout')).toBeFalsy();
            });

            describe('clicking on a faded out legend item', function () {
                beforeEach(function () {
                    legendItem(0).on('click').call(legendItem(0)[0][0], legendItem(0).datum());
                });

                it('should unfade the legend item', function () {
                    expect(legendItem(0).classed('fadeout')).toBeFalsy();
                });

                it('should unfade its associated stack', function () {
                    expect(chart.selectAll('path.line').size()).toEqual(3);
                });
            });
        });
    });

    function legendItem (n) {
        return d3.select(chart.selectAll('g.dc-legend g.dc-legend-item')[0][n]);
    }
    function legendLabel (n) {
        return d3.select(chart.selectAll('g.dc-legend g.dc-legend-item text')[0][n]);
    }
    function legendIcon (n) {
        return d3.select(chart.selectAll('g.dc-legend g.dc-legend-item rect')[0][n]);
    }
    function legendLine (n) {
        return d3.select(chart.selectAll('g.dc-legend g.dc-legend-item line')[0][n]);
    }
});

<|endoftext|>


<commit_before>(function () {
  'use strict';

  var hostRule = /^(www\.)?adf\.(ly|acb\.im|sazlina\.com)|[jq]\.gs|go\.(phpnulledscripts|nicoblog-games)\.com|ay\.gy|(chathu|alien)\.apkmania\.co|ksn\.mx|goto\.adflytutor\.com|dl\.apkpro\.net|adfly\.itsrinaldo\.net|.*\.gamecopyworld\.com$/;

  $.register({
    rule: {
      host: hostRule,
      path: /\/locked$/,
      query: /url=([^&]+)/,
    },
    start: function (m) {
      $.resetCookies();
      $.openLink('/' + m.query[1]);
    },
  });

  $.register({
    rule: {
      host: hostRule,
      // FIXME this pattern is not stable
      path: /^\/([a-z\/]{2,})?$/,
    },
  });

  $.register({
    rule: {
      host: hostRule,
    },
    ready: function () {
      $.removeNodes('iframe');

      var h = unsafeWindow.eu, b64 = unsafeWindow.Base64;
      if (!h) {
        h = $('#adfly_bar');
        unsafeWindow.close_bar();
        return;
      }
      var a = h.indexOf('!HiTommy'), b = '';
      if (a >= 0) {
        h = h.substring(0, a);
      }
      a = '';
      for (var i = 0; i < h.length; ++i) {
        if (i % 2 === 0) {
          a = a + h.charAt(i);
        } else {
          b = h.charAt(i) + b;
        }
      }
      h = b64.decode(a + b);
      h = h.substr(2);
      if (location.hash) {
        h += location.hash;
      }
      $.openLink(h);
    },
  });

  $.register({
    rule: 'http://ad7.biz/*',
    ready: function () {
      $.removeNodes('iframe');
      $.resetCookies();

      var script = $.$$('script').find(function (v) {
        if (v.innerHTML.indexOf('var r_url') < 0) {
          return _.nop;
        }
        return v.innerHTML;
      });
      var url = script.payload.match(/&url=([^&]+)/);
      url = url[1];
      $.openLink(url);
    },
  });

})();

// ex: ts=2 sts=2 sw=2 et
// sublime: tab_size 2; translate_tabs_to_spaces true; detect_indentation false; use_tab_stops true;
// kate: space-indent on; indent-width 2;
<commit_msg>ad7.biz<commit_after>(function () {
  'use strict';

  var hostRule = /^(www\.)?adf\.(ly|acb\.im|sazlina\.com)|[jq]\.gs|go\.(phpnulledscripts|nicoblog-games)\.com|ay\.gy|(chathu|alien)\.apkmania\.co|ksn\.mx|goto\.adflytutor\.com|dl\.apkpro\.net|adfly\.itsrinaldo\.net|.*\.gamecopyworld\.com$/;

  $.register({
    rule: {
      host: hostRule,
      path: /\/locked$/,
      query: /url=([^&]+)/,
    },
    start: function (m) {
      $.resetCookies();
      $.openLink('/' + m.query[1]);
    },
  });

  $.register({
    rule: {
      host: hostRule,
      // FIXME this pattern is not stable
      path: /^\/([a-z\/]{2,})?$/,
    },
  });

  $.register({
    rule: {
      host: hostRule,
    },
    ready: function () {
      $.removeNodes('iframe');

      var h = unsafeWindow.eu, b64 = unsafeWindow.Base64;
      if (!h) {
        h = $('#adfly_bar');
        unsafeWindow.close_bar();
        return;
      }
      var a = h.indexOf('!HiTommy'), b = '';
      if (a >= 0) {
        h = h.substring(0, a);
      }
      a = '';
      for (var i = 0; i < h.length; ++i) {
        if (i % 2 === 0) {
          a = a + h.charAt(i);
        } else {
          b = h.charAt(i) + b;
        }
      }
      h = b64.decode(a + b);
      h = h.substr(2);
      if (location.hash) {
        h += location.hash;
      }
      $.openLink(h);
    },
  });

  $.register({
    rule: 'http://ad7.biz/*.php',
  });

  $.register({
    rule: 'http://ad7.biz/*',
    ready: function () {
      $.removeNodes('iframe');
      $.resetCookies();

      var script = $.$$('script').find(function (v) {
        if (v.innerHTML.indexOf('var r_url') < 0) {
          return _.nop;
        }
        return v.innerHTML;
      });
      var url = script.payload.match(/&url=([^&]+)/);
      url = url[1];
      $.openLink(url);
    },
  });

})();

// ex: ts=2 sts=2 sw=2 et
// sublime: tab_size 2; translate_tabs_to_spaces true; detect_indentation false; use_tab_stops true;
// kate: space-indent on; indent-width 2;
<|endoftext|>


<commit_before>import jsonop from 'jsonop';
import winston from 'winston';
import Counter from '../../lib/counter';
import * as pg from '../../lib/pg';
import { TABLES, TYPES } from '../../lib/schema';
import queryHandler from './query';
import entityHandler from './entity';
import { bus, cache, config } from '../../core-server';
import * as Types from './../../models/models';
import './cache-updater';
const channel = 'heyneighbor';

const TYPE_SEGMENT = `case \
when tableoid = 'notes'::regclass then 'note' \
when tableoid = 'privs'::regclass then 'privs' \
when tableoid = 'roomrels'::regclass then 'roomrels' \
when tableoid = 'rooms'::regclass then 'room' \
when tableoid = 'textrels'::regclass then 'textrel' \
when tableoid = 'texts'::regclass then 'text' \
when tableoid = 'threadrels'::regclass then 'threadrel' \
when tableoid = 'threads'::regclass then 'thread' \
when tableoid = 'topicrels'::regclass then 'topicrel' \
when tableoid = 'topics'::regclass then 'topic' \
when tableoid = 'users'::regclass then 'user' \
end as type`;

function broadcast (entity) {
	pg.notify(config.connStr, channel, entity);
}

cache.onChange((changes) => {
	const cb = (key, range, err, results) => {
		if (err) {
			winston.error(err);
			return;
		}
		bus.emit('change', {
			knowledge: { [key]: [ range ] },
			indexes: { [key]: results },
			source: 'postgres'
		});
	};

	if (changes.queries) {
		for (const key in changes.queries) {
			if (key === 'entities') {
				const ids = Object.keys(changes.queries.entities),
					typeToEntities = {
						item: [],
						user: []
					};

				ids.forEach((id) => {
					const _split = id.split('_');
					let type;

					if (_split.length === 3) return;
					else if (_split.length === 2) return;
					else if (id.length >= 36) type = 'item';
					else type = 'user';
					typeToEntities[type].push(id);
				});

				// TODO: move to diff file.
				for (const i in typeToEntities) {
					if (!typeToEntities[i].length) continue;

					pg.read(config.connStr, {
						$: `select *, ${TYPE_SEGMENT} from "${TABLES[TYPES[i]]}" where id in (&(ids))`,
						ids: typeToEntities[i]
					}, (err, r) => {
						if (err) {
							winston.error(err.message);
							return;
						}

						const state = {
							entities: {},
							source: 'postgres'
						};

						r.map((entity) => {
							state.entities[entity.id] = new Types[entity.type](entity);
						});

						const missingIds = ids.filter(itemID => !state.entities[itemID]);

						missingIds.forEach(id => {
							state.entities[id] = null;
						});

						bus.emit('change', state);
					});
				}
			} else {
				for (const range of changes.queries[key]) {
					pg.read(
						config.connStr,
						queryHandler(cache.keyToSlice(key), range),
						cb.bind(null, key, range)
					);
				}
			}
		}
	}
});

pg.listen(config.connStr, channel, (payload) => {
	bus.emit('postchange', payload);
});

bus.on('change', (changes, next) => {
	const counter = new Counter(), response = changes.response = {};

	if (!response.entities) response.entities = {};
	if (changes.source === 'postgres') {
		next();
		return;
	}

	if (changes.entities) {
		const sql = [];

		for (const id in changes.entities) {
			sql.push(entityHandler(changes.entities[id]));
		}

		winston.info('sql', sql);
		counter.inc();
		pg.write(config.connStr, sql, (err, results) => {
			if (err) {
				counter.err(err);
				return;
			}

			winston.info('PgWrite Results', results[0].rows);
			results.forEach((result) => {
				response.entities[result.rows[0].id] = result.rows[0];
				broadcast(result.rows[0]);
			});

			counter.dec();
		});
	}

	if (changes.queries) {
		const cb = (key, err, results) => {
				if (err) { jsonop(response, { state: { error: err } }); }
				jsonop(response, { indexes: { [key]: results } });
				counter.dec();
			},
			entityCallback = (err, result) => {
				if (err) { jsonop(response, { state: { error: err } }); }
				if (result && result.id) {
					response.entities = response.entities ? response.entities : {};
					response.entities[result.id] = result;
				}

				counter.dec();
			};

		for (const key in changes.queries) {
			if (key === 'entities') {
				for (const entity in changes.queries[key]) {
					counter.inc();
					cache.getEntity(entity, entityCallback);
				}
			} else {
				for (const range of changes.queries[key]) {
					counter.inc();
					cache.query(key, range, cb.bind(null, key));
				}
			}
		}
	}

	counter.then(next);
});
<commit_msg>Fix response from server<commit_after>import jsonop from 'jsonop';
import winston from 'winston';
import Counter from '../../lib/counter';
import * as pg from '../../lib/pg';
import { TABLES, TYPES } from '../../lib/schema';
import queryHandler from './query';
import entityHandler from './entity';
import { bus, cache, config } from '../../core-server';
import * as Types from './../../models/models';
import './cache-updater';
const channel = 'heyneighbor';

const TYPE_SEGMENT = `case \
when tableoid = 'notes'::regclass then 'note' \
when tableoid = 'privs'::regclass then 'privs' \
when tableoid = 'roomrels'::regclass then 'roomrels' \
when tableoid = 'rooms'::regclass then 'room' \
when tableoid = 'textrels'::regclass then 'textrel' \
when tableoid = 'texts'::regclass then 'text' \
when tableoid = 'threadrels'::regclass then 'threadrel' \
when tableoid = 'threads'::regclass then 'thread' \
when tableoid = 'topicrels'::regclass then 'topicrel' \
when tableoid = 'topics'::regclass then 'topic' \
when tableoid = 'users'::regclass then 'user' \
end as type`;

function broadcast (entity) {
	pg.notify(config.connStr, channel, entity);
}

cache.onChange((changes) => {
	const cb = (key, range, err, results) => {
		if (err) {
			winston.error(err);
			return;
		}
		bus.emit('change', {
			knowledge: { [key]: [ range ] },
			indexes: { [key]: results },
			source: 'postgres'
		});
	};

	if (changes.queries) {
		for (const key in changes.queries) {
			if (key === 'entities') {
				const ids = Object.keys(changes.queries.entities),
					typeToEntities = {
						item: [],
						user: []
					};

				ids.forEach((id) => {
					const _split = id.split('_');
					let type;

					if (_split.length === 3) return;
					else if (_split.length === 2) return;
					else if (id.length >= 36) type = 'item';
					else type = 'user';
					typeToEntities[type].push(id);
				});

				// TODO: move to diff file.
				for (const i in typeToEntities) {
					if (!typeToEntities[i].length) continue;

					pg.read(config.connStr, {
						$: `select *, ${TYPE_SEGMENT} from "${TABLES[TYPES[i]]}" where id in (&(ids))`,
						ids: typeToEntities[i]
					}, (err, r) => {
						if (err) {
							winston.error(err.message);
							return;
						}

						const state = {
							entities: {},
							source: 'postgres'
						};

						r.map((entity) => {
							state.entities[entity.id] = new Types[entity.type](entity);
						});

						const missingIds = ids.filter(itemID => !state.entities[itemID]);

						missingIds.forEach(id => {
							state.entities[id] = null;
						});

						bus.emit('change', state);
					});
				}
			} else {
				for (const range of changes.queries[key]) {
					pg.read(
						config.connStr,
						queryHandler(cache.keyToSlice(key), range),
						cb.bind(null, key, range)
					);
				}
			}
		}
	}
});

pg.listen(config.connStr, channel, (payload) => {
	bus.emit('postchange', payload);
});

bus.on('change', (changes, next) => {
	const counter = new Counter(), response = changes.response = changes.response || {};

	if (!response.entities) response.entities = {};
	if (changes.source === 'postgres') {
		next();
		return;
	}

	if (changes.entities) {
		const sql = [];

		for (const id in changes.entities) {
			sql.push(entityHandler(changes.entities[id]));
		}

		winston.info('sql', sql);
		counter.inc();
		pg.write(config.connStr, sql, (err, results) => {
			if (err) {
				counter.err(err);
				return;
			}

			winston.info('PgWrite Results', results[0].rows);
			results.forEach((result) => {
				response.entities[result.rows[0].id] = result.rows[0];
				broadcast(result.rows[0]);
			});

			counter.dec();
		});
	}

	if (changes.queries) {
		const cb = (key, err, results) => {
				if (err) { jsonop(response, { state: { error: err } }); }
				jsonop(response, { indexes: { [key]: results } });
				counter.dec();
			},
			entityCallback = (err, result) => {
				if (err) { jsonop(response, { state: { error: err } }); }
				if (result && result.id) {
					response.entities = response.entities ? response.entities : {};
					response.entities[result.id] = result;
				}

				counter.dec();
			};

		for (const key in changes.queries) {
			if (key === 'entities') {
				for (const entity in changes.queries[key]) {
					counter.inc();
					cache.getEntity(entity, entityCallback);
				}
			} else {
				for (const range of changes.queries[key]) {
					counter.inc();
					cache.query(key, range, cb.bind(null, key));
				}
			}
		}
	}

	counter.then(next);
});
<|endoftext|>


<commit_before>/*
 matrix、attributes
 TODO:？？？ path  转换path描述？？？
 Todo: 多个canvas同时执行动画 test
 Todo: 中断动画 test

    animator = new Animator();

    matrix属性: scale, rotate, translate, transform

    attr属性:

    //group
    el.animate(object, number, function);

    el.animate({
      scale: [x, y],
      rotate: [a, x, y],
      opacity: value
    }, step, function(){});

    //callback
    el.animate({}, function(){});

 */
var Animator = new Class;
Animator.include({
  init: function(){
    this.amtArr = [];
    this.stamp = null;
    this.timer = null;
    this.timeDiff = 1;
  },
  add: function(el, arr){
    //arr = [am, hl, cb]
    var self = this,
      amtArr = self.amtArr,
      oldStatus = amtArr.length,
      hl = arr[1]||500,
      cfg = el.cfg,
      am = extend(true, {}, arr[0]),
      cb = arr[2] || function(){};
    forEach(am, function(v, k){
      if(k == 'translate'){
        am[k] = {
          st: [v[0]/hl, v[1]/hl],
          src: [0, 0],
          tar: v
        };
      }else if(k == 'rotate'){
        v[1]= v[1] || 0;
        v[2] = v[2] || 0;
        am[k] = {
          st: [v[0]/hl, 0, 0],
          src: [0, v[1], v[2]],
          tar: v
        };
      }else if(k == 'scale'){
        v[1] = v[1] || v[0];
        v[2] = v[2] || 0;
        v[3] = v[3] || 0;
        am[k] = {
          st: [ (v[0]-1)/hl, (v[1]-1)/hl, 0, 0 ],
          src: [1, 1, v[2], v[3]],
          tar: v
        };
      }else if(k == 'fillStyle' || k == 'strokeStyle'){
        cfg[k] = hex2num(cfg[k] || '#fff');
        v = hex2num(v);
        am[k] = {
          src: cfg[k],
          st: [(v[0] - cfg[k][0])/hl, (v[1] - cfg[k][1])/hl, (v[2] - cfg[k][2])/hl],
          tar: v
        };
      }else{
        cfg[k] = cfg[k] || 0;
        am[k] = {
          src: cfg[k],
          st: [(v[0] - cfg[k][0])/hl, (v[1] - cfg[k][1])/hl, (v[2] - cfg[k][2])/hl],
          tar: v
        };
      }
    });
    amtArr.push([el, am, hl, cb, +new Date()]);
    if(oldStatus == 0){ //不为0则有action在执行
      self.action();
    }
  },
  abort: function(){
    var self = this;
    self.init();
  },
  action: function(){
    var self = this,
      amtArr = self.amtArr,
      td = self.timeDiff,
      el,
      am,
      hl,
      cb,
      src,
      st,
      tar,
      tem,
      pt,
      done;
    self.stamp = +new Date();
    amtArr.forEach(function(v, i){
      el = v[0];
      am = v[1];
      hl = v[2];
      cb = v[3];
      stime = v[4];
      pt = (self.stamp - stime);
      done = (hl <= pt);
      el.matrix = [1, 0, 0, 1, 0, 0];
      forEach(am, function(val, k){
        src = val.src;
        st = val.st;
        tar = val.tar;
        if(k.search(/scale|rotate|translate|transform/) != -1){
          if(done){
            el[k].apply(el, tar);
          }else{
            el[k].apply(el, src);
          }
          src.forEach(function(o, n, aa){
            aa[n] += (st[n] * td);
          });
        }else if(k.search(/fillStyle|strokeStyle|shadowColor/) != -1){
          tem = {};
          tem[k] = rgb2hex.apply(this, src);
          el.attr(tem);
          src.forEach(function(o, n, aa){
            aa[n] += (st[n] * td);
          });
        }else{
          tem = {};
          tem[k] = src;
          el.attr(tem);
          val.src += (st * td);
        }
      });
      if(done){
        amtArr.splice(i, 1);
        cb();
      }
    });
    window.cancelAnimationFrame(self.timer);
    self.timer = window.requestAnimationFrame(function(){
      self.timeDiff = (+new Date() - self.stamp);
      self.checkStatus();
    });
  },
  checkStatus: function(){
    var self = this;
    if(self.amtArr.length !== 0){
      self.action();
    }else{
      self.init();
    }
  }
});

//主动画控制器
var Sanimator = new Animator();<commit_msg>增加todo<commit_after>/*
 matrix、attributes
 TODO:？？？ path  转换path描述？？？
 Todo: 多个canvas同时执行动画 test
 Todo: 中断动画 test

    animator = new Animator();

    matrix属性: scale, rotate, translate, transform

    attr属性:

    //group
    el.animate(object, number, function);

    el.animate({
      scale: [x, y],
      rotate: [a, x, y],
      opacity: value
    }, step, function(){});

    //callback
    el.animate({}, function(){});

 */
var Animator = new Class;
Animator.include({
  init: function(){
    this.amtArr = [];
    this.stamp = null;
    this.timer = null;
    this.timeDiff = 1;
  },
  // Todo: 增加一组元素的动画，只设置一个CallBack
  add: function(el, arr){
    //arr = [am, hl, cb]
    var self = this,
      amtArr = self.amtArr,
      oldStatus = amtArr.length,
      hl = arr[1]||500,
      cfg = el.cfg,
      am = extend(true, {}, arr[0]),
      cb = arr[2] || function(){};
    forEach(am, function(v, k){
      if(k == 'translate'){
        am[k] = {
          st: [v[0]/hl, v[1]/hl],
          src: [0, 0],
          tar: v
        };
      }else if(k == 'rotate'){
        v[1]= v[1] || 0;
        v[2] = v[2] || 0;
        am[k] = {
          st: [v[0]/hl, 0, 0],
          src: [0, v[1], v[2]],
          tar: v
        };
      }else if(k == 'scale'){
        v[1] = v[1] || v[0];
        v[2] = v[2] || 0;
        v[3] = v[3] || 0;
        am[k] = {
          st: [ (v[0]-1)/hl, (v[1]-1)/hl, 0, 0 ],
          src: [1, 1, v[2], v[3]],
          tar: v
        };
      }else if(k == 'fillStyle' || k == 'strokeStyle'){
        cfg[k] = hex2num(cfg[k] || '#fff');
        v = hex2num(v);
        am[k] = {
          src: cfg[k],
          st: [(v[0] - cfg[k][0])/hl, (v[1] - cfg[k][1])/hl, (v[2] - cfg[k][2])/hl],
          tar: v
        };
      }else{
        cfg[k] = cfg[k] || 0;
        am[k] = {
          src: cfg[k],
          st: [(v[0] - cfg[k][0])/hl, (v[1] - cfg[k][1])/hl, (v[2] - cfg[k][2])/hl],
          tar: v
        };
      }
    });
    amtArr.push([el, am, hl, cb, +new Date()]);
    if(oldStatus == 0){ //不为0则有action在执行
      self.action();
    }
  },
  abort: function(){
    var self = this;
    self.init();
  },
  action: function(){
    var self = this,
      amtArr = self.amtArr,
      td = self.timeDiff,
      el,
      am,
      hl,
      cb,
      src,
      st,
      tar,
      tem,
      pt,
      done;
    self.stamp = +new Date();
    amtArr.forEach(function(v, i){
      el = v[0];
      am = v[1];
      hl = v[2];
      cb = v[3];
      stime = v[4];
      pt = (self.stamp - stime);
      done = (hl <= pt);
      el.matrix = [1, 0, 0, 1, 0, 0];
      forEach(am, function(val, k){
        src = val.src;
        st = val.st;
        tar = val.tar;
        if(k.search(/scale|rotate|translate|transform/) != -1){
          if(done){
            el[k].apply(el, tar);
          }else{
            el[k].apply(el, src);
          }
          src.forEach(function(o, n, aa){
            aa[n] += (st[n] * td);
          });
        }else if(k.search(/fillStyle|strokeStyle|shadowColor/) != -1){
          tem = {};
          tem[k] = rgb2hex.apply(this, src);
          el.attr(tem);
          src.forEach(function(o, n, aa){
            aa[n] += (st[n] * td);
          });
        }else{
          tem = {};
          tem[k] = src;
          el.attr(tem);
          val.src += (st * td);
        }
      });
      if(done){
        amtArr.splice(i, 1);
        cb();
      }
    });
    window.cancelAnimationFrame(self.timer);
    self.timer = window.requestAnimationFrame(function(){
      self.timeDiff = (+new Date() - self.stamp);
      self.checkStatus();
    });
  },
  checkStatus: function(){
    var self = this;
    if(self.amtArr.length !== 0){
      self.action();
    }else{
      self.init();
    }
  }
});

//主动画控制器
var Sanimator = new Animator();<|endoftext|>