<commit_before>/*******************************************************************************
 * Base Model
 ******************************************************************************/

var path = require('path');

/**
 * Setup a consistent model system that interacts with an database
 * @param {Object} props
 */
function Model(Ceres, props) {
  // Import ORM
  var model;

  var type = props.type || Ceres.config.db.type;

  if (['bookshelf', 'rethinkdb'].indexOf(type.toLowerCase()) > -1) {
    var orm = require(path.resolve(__dirname, './types', type));
    model = orm.extend.call(Ceres, props);
  } else {
    throw new Error('Unknown model type: ' + type);
  }

  // Override defaults
  Object.assign(this, model, props);

  // Allow some user initialization code
  if (typeof this.init === 'function') {
    this.init.call(this);
  }
}

/**
 * Required methods for all Models
 * @type {Object}
 */
Model.prototype = {
  /**
   * Create model and return a promise
   */
  create: null,

  /**
   * Read all models or just a single one
   */
  read: null,

  /**
   * Update/Patch a single model
   */
  update: null,

  /**
   * Delete a model
   */
  del: null
};

/**
 * Helper function to create new models. Ensures the correct interface for
 * default rest controller
 *
 * @param     {Object}    props
 * @return    {Object}
 */
Model.extend = function extend(props) {
  return new Model(props);
};

module.exports = Model;
<commit_msg>fixed extend<commit_after>/*******************************************************************************
 * Base Model
 ******************************************************************************/

var path = require('path');

/**
 * Setup a consistent model system that interacts with an database
 * @param {Object} props
 */
function Model(Ceres, props) {
  // Import ORM
  var model;

  var type = props.type || Ceres.config.db.type;

  if (['bookshelf', 'rethinkdb'].indexOf(type.toLowerCase()) > -1) {
    var orm = require(path.resolve(__dirname, './types', type));
    model = orm.extend.call(Ceres, props);
  } else {
    throw new Error('Unknown model type: ' + type);
  }

  // Override defaults
  Object.assign(this, model, props);

  // Allow some user initialization code
  if (typeof this.init === 'function') {
    this.init.call(this);
  }
}

/**
 * Required methods for all Models
 * @type {Object}
 */
Model.prototype = {
  /**
   * Create model and return a promise
   */
  create: null,

  /**
   * Read all models or just a single one
   */
  read: null,

  /**
   * Update/Patch a single model
   */
  update: null,

  /**
   * Delete a model
   */
  del: null
};

/**
 * Helper function to create new models. Ensures the correct interface for
 * default rest controller
 *
 * @param     {Object}    props
 * @return    {Object}
 */
Model.extend = function extend(props) {
  return new Model(this, props);
};

module.exports = Model;
<|endoftext|>


<commit_before>const extname = require('path').extname

export default class Cover {
  constructor (path, stats) {
    this.path = path
    this.format = extname(path).slice(1)
    this.size = stats.size
    this.blockSize = stats.blksize
    this.blocks = stats.blocks
  }

  getSize (bs = 1) {
    return Math.ceil(this.size / bs)
  }
}
<commit_msg>models: Cover inherits from File<commit_after>import { extname } from 'path'

import File from './file.js'

export default class Cover extends File {
  constructor (path, stats) {
    const extension = extname(path)
    super(path, stats, extension)
    this.format = extension.slice(1)
  }
}
<|endoftext|>


<commit_before>module.exports = {
	Website: require('./Website'),
	Personal: require('./Personal'),
	Skills: require('./Skills'),
	Projects: require('./Projects')
};
<commit_msg>models/index.js: added ProjectInfo<commit_after>module.exports = {
	Website: require('./Website'),
	Personal: require('./Personal'),
	Skills: require('./Skills'),
	Projects: require('./Projects'),
	ProjectInfo: require('./ProjectInfo')
};
<|endoftext|>


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

module.exports = {
  entry: path.resolve(__dirname, 'app.js'),
  output: {
    path: path.resolve(__dirname, 'dist'),
    filename: 'bundle.js'
  },
  resolve: {
    // which extensions should be considered for resolution - automatically?
    // any extensions "referred" via files with these extensions are resolved as well.
    extensions: ['.ts', '.js', '.json'],
    // default - modules: ["node_modules"]
    modules: [path.resolve(__dirname, 'src'), 'node_modules'],
  },

  module: {
    rules: [
      {
        // required ONLY for -p (production) build of webpack
        test: /\.js$/,
        exclude: /(node_modules|bower_components)/,
        use: {
          loader: 'babel-loader',
          options: {
            cacheDirectory: path.resolve(__dirname, 'babelCache'),
            presets: [
              ['es2015', {modules: false}]
            ]
          }
        }
      },
      {
        test: /\.css$/,
        use: ['style-loader', 'css-loader'],
      },
      /* UNCOMMENT BELOW PORTION TO SEE ALL LOADERS WORKING */
      /* {
        test: /\.ts(x?)$/,
        exclude: /node_modules/,
        use: [
          {
            loader: 'babel-loader',
            options: {presets: ['es2015']}
          },
          {
            loader: 'ts-loader'
          }
        ]
      },
      {
        test: /\.json$/,
        use: 'json-loader'
      },
      {
        test: /\.scss$/,
        use: [
          {
            loader: "style-loader" // creates style nodes from JS strings
          }, {
            loader: "css-loader" // translates CSS into CommonJS
          }, {
            loader: "sass-loader" // compiles Sass to CSS
          }
        ],
      },
      {
        test: /\.(jpg|png|gif|svg)$/,
        use: {
          loader:'file-loader',
          options: {
            publicPath:'dist/'
          }
        }
      },
      {
        test: /\.woff(2)?(\?v=[0-9]\.[0-9]\.[0-9])?$/,
        use: {
          loader:'url-loader',
          options: {
            limit:10000,
            mimetype:'application/font-woff',
            publicPath:'dist/'
          }
        }
      },
      {
        test: /\.(ttf|eot)(\?v=[0-9]\.[0-9]\.[0-9])?$/,
        use: {
          loader:'file-loader',
          options: {
            publicPath:'dist/'
          }
        }
      }*/
    ]
  },
  stats: "verbose"
}<commit_msg>webpack compilation was failing due to file-loader for image extension was commented off.<commit_after>var path = require('path');

module.exports = {
  entry: path.resolve(__dirname, 'app.js'),
  output: {
    path: path.resolve(__dirname, 'dist'),
    filename: 'bundle.js'
  },
  resolve: {
    // which extensions should be considered for resolution - automatically?
    // any extensions "referred" via files with these extensions are resolved as well.
    extensions: ['.ts', '.js', '.json'],
    // default - modules: ["node_modules"]
    modules: [path.resolve(__dirname, 'src'), 'node_modules'],
  },

  module: {
    rules: [
      {
        // required ONLY for -p (production) build of webpack
        test: /\.js$/,
        exclude: /(node_modules|bower_components)/,
        use: {
          loader: 'babel-loader',
          options: {
            cacheDirectory: path.resolve(__dirname, 'babelCache'),
            presets: [
              ['es2015', {modules: false}]
            ]
          }
        }
      },
      {
        test: /\.css$/,
        use: ['style-loader', 'css-loader'],
      },
      {
        test: /\.(jpg|png|gif|svg)$/,
        use: {
          loader:'file-loader',
          options: {
            publicPath:'dist/'
          }
        }
      },
      /* UNCOMMENT BELOW PORTION TO SEE ALL LOADERS WORKING */
      /* {
        test: /\.ts(x?)$/,
        exclude: /node_modules/,
        use: [
          {
            loader: 'babel-loader',
            options: {presets: ['es2015']}
          },
          {
            loader: 'ts-loader'
          }
        ]
      },
      {
        test: /\.json$/,
        use: 'json-loader'
      },
      {
        test: /\.scss$/,
        use: [
          {
            loader: "style-loader" // creates style nodes from JS strings
          }, {
            loader: "css-loader" // translates CSS into CommonJS
          }, {
            loader: "sass-loader" // compiles Sass to CSS
          }
        ],
      },
      {
        test: /\.woff(2)?(\?v=[0-9]\.[0-9]\.[0-9])?$/,
        use: {
          loader:'url-loader',
          options: {
            limit:10000,
            mimetype:'application/font-woff',
            publicPath:'dist/'
          }
        }
      },
      {
        test: /\.(ttf|eot)(\?v=[0-9]\.[0-9]\.[0-9])?$/,
        use: {
          loader:'file-loader',
          options: {
            publicPath:'dist/'
          }
        }
      }*/
    ]
  },
  stats: "verbose"
}<|endoftext|>


<commit_before>import Config from '../config'
import Emitter from '../helpers/emitter'
import * as Element from '../helpers/element'
import * as Parser from '../helpers/parser'

export default class Base extends Emitter
{
	static getDirective(owner = null)
	{
		const Module = this
		const d = Module.directive

		if(owner)
		{
			const OwnerModule = owner.constructor
			const od = OwnerModule.getDirective(owner.$owner)

			return `${od}-${d}`
		}

		return d
	}

	static getSelector(owner = null)
	{
		const Module = this

		let p = Config.directivePrefix
		let d = Module.getDirective(owner)
		let selector = [`[${p}${d}]`]

		if(owner)
		{
			let ref = owner.$element.getAttribute(`${p}ref`)
			selector.push(`[${p}${d}\\:${ref}]`)
		}

		return selector.join(',')
	}

	static getSettings(element, owner = null)
	{
		const Module = this

		let p = Config.directivePrefix
		let d = Module.getDirective(owner)
		let ref = Module.getReference(element, owner)
		let settings = ''

		if(ref)
		{
			settings = element.getAttribute(`${p}${d}:${ref}`)
		}
		else
		{
			settings = element.getAttribute(`${p}${d}`) || element.getAttribute(d)
		}

		return Parser.settings(settings)
	}

	static getReference(element, owner = null)
	{
		const Module = this

		let p = Config.directivePrefix
		let d = Module.getDirective(owner)

		if(!element.hasAttribute(`${p}${d}`) && owner)
		{
			let ref = owner.$element.getAttribute(`${p}ref`)
			let hasRef = element.hasAttribute(`${p}${d}:${ref}`)

			if(hasRef)
			{
				return ref
			}
		}

		return null
	}

	static trigger(eventType, data = {})
	{
		const Module = this
		const ExtendedModule = Module.extending
		const eventFn = Module.events[eventType]

		if(eventFn)
		{
			eventFn.call(data.target, data)
		}

		if(ExtendedModule)
		{
			ExtendedModule.trigger(eventType, data)
		}
	}

	static extend(settings)
	{
		const SuperModule = this
		const Module = function()
		{
			SuperModule.apply(this, arguments)
		}

		Module.prototype = new SuperModule()

		if(settings.methods)
		{
			for(let name of Object.keys(settings.methods))
			{
				let method = settings.methods[name]
				Module.prototype[name] = method
			}
		}

		Module.prototype.constructor = Module

		Module.getDirective = SuperModule.getDirective
		Module.getSelector = SuperModule.getSelector
		Module.getSettings = SuperModule.getSettings
		Module.getReference = SuperModule.getReference
		Module.trigger = SuperModule.trigger
		Module.extend = SuperModule.extend
		Module.start = SuperModule.start

		Module.extending = SuperModule
		Module.directive = settings.directive
		Module.modules = Object.assign({}, SuperModule.modules, settings.modules)
		Module.defaults = Object.assign({}, SuperModule.defaults, settings.defaults)
		Module.methods = Module.prototype
		Module.events = settings.events || {}

		return Module
	}

	static start(container = document, owner = null)
	{
		const Module = this
		const elements = container.querySelectorAll(Module.getSelector(owner))
		const modules = []

		let p = Config.directivePrefix

		for(let element of Array.from(elements))
		{
			if(owner && !Module.getReference(element, owner))
			{
				let OwnerModule = owner.constructor
				let ownerElement = Element.closest(element, OwnerModule.getSelector(owner.$owner))
				if(ownerElement !== owner.$element)
				{
					continue;
				}
			}

			let settings = Module.getSettings(element, owner)
			let e = {
				element: element,
				settings: settings
			}

			Module.trigger('beforeInit', e)

			let module = new Module(e.element, e.settings, owner)

			for(let property of Object.keys(Module.modules))
			{
				let SubModule = Module.modules[property]
				let subModules = SubModule.start(container, module)

				module.$owns[property] = subModules
			}

			module.$element.removeAttribute(`${p}cloak`)

			Module.trigger('init', {target: module})

			modules.push(module)
		}

		return modules
	}

	constructor(element, settings = {}, owner = null)
	{
		super()
		Emitter.call(this)

		if(!element) return

		const Module = this.constructor

		this.$element = element
		this.$owner = owner
		this.$owns = {}
		this.$settings = Object.assign({}, Module.defaults, settings)

		Element.data(element, Module.getDirective(owner), this)
	}
}

Base.directive = null
Base.modules = {}
Base.defaults = {}
Base.methods = Base.prototype
Base.events = {}
<commit_msg>Rudimentary propagation control<commit_after>import Config from '../config'
import Emitter from '../helpers/emitter'
import * as Element from '../helpers/element'
import * as Parser from '../helpers/parser'

export default class Base extends Emitter
{
	static getDirective(owner = null)
	{
		const Module = this
		const d = Module.directive

		if(owner)
		{
			const OwnerModule = owner.constructor
			const od = OwnerModule.getDirective(owner.$owner)

			return `${od}-${d}`
		}

		return d
	}

	static getSelector(owner = null)
	{
		const Module = this

		let p = Config.directivePrefix
		let d = Module.getDirective(owner)
		let selector = [`[${p}${d}]`]

		if(owner)
		{
			let ref = owner.$element.getAttribute(`${p}ref`)
			selector.push(`[${p}${d}\\:${ref}]`)
		}

		return selector.join(',')
	}

	static getSettings(element, owner = null)
	{
		const Module = this

		let p = Config.directivePrefix
		let d = Module.getDirective(owner)
		let ref = Module.getReference(element, owner)
		let settings = ''

		if(ref)
		{
			settings = element.getAttribute(`${p}${d}:${ref}`)
		}
		else
		{
			settings = element.getAttribute(`${p}${d}`) || element.getAttribute(d)
		}

		return Parser.settings(settings)
	}

	static getReference(element, owner = null)
	{
		const Module = this

		let p = Config.directivePrefix
		let d = Module.getDirective(owner)

		if(!element.hasAttribute(`${p}${d}`) && owner)
		{
			let ref = owner.$element.getAttribute(`${p}ref`)
			let hasRef = element.hasAttribute(`${p}${d}:${ref}`)

			if(hasRef)
			{
				return ref
			}
		}

		return null
	}

	static trigger(eventType, data = {})
	{
		const Module = this
		const ExtendedModule = Module.extending
		const eventFn = Module.events[eventType]

		if(eventFn)
		{
			eventFn.call(data.target, data)
		}

		if(ExtendedModule && !data.stopPropagation)
		{
			ExtendedModule.trigger(eventType, data)
		}
	}

	static extend(settings)
	{
		const SuperModule = this
		const Module = function()
		{
			SuperModule.apply(this, arguments)
		}

		Module.prototype = new SuperModule()

		if(settings.methods)
		{
			for(let name of Object.keys(settings.methods))
			{
				let method = settings.methods[name]
				Module.prototype[name] = method
			}
		}

		Module.prototype.constructor = Module

		Module.getDirective = SuperModule.getDirective
		Module.getSelector = SuperModule.getSelector
		Module.getSettings = SuperModule.getSettings
		Module.getReference = SuperModule.getReference
		Module.trigger = SuperModule.trigger
		Module.extend = SuperModule.extend
		Module.start = SuperModule.start

		Module.extending = SuperModule
		Module.directive = settings.directive
		Module.modules = Object.assign({}, SuperModule.modules, settings.modules)
		Module.defaults = Object.assign({}, SuperModule.defaults, settings.defaults)
		Module.methods = Module.prototype
		Module.events = settings.events || {}

		return Module
	}

	static start(container = document, owner = null)
	{
		const Module = this
		const elements = container.querySelectorAll(Module.getSelector(owner))
		const modules = []

		let p = Config.directivePrefix

		for(let element of Array.from(elements))
		{
			if(owner && !Module.getReference(element, owner))
			{
				let OwnerModule = owner.constructor
				let ownerElement = Element.closest(element, OwnerModule.getSelector(owner.$owner))
				if(ownerElement !== owner.$element)
				{
					continue;
				}
			}

			let settings = Module.getSettings(element, owner)
			let e = {
				element: element,
				settings: settings
			}

			Module.trigger('beforeInit', e)

			let module = new Module(e.element, e.settings, owner)

			for(let property of Object.keys(Module.modules))
			{
				let SubModule = Module.modules[property]
				let subModules = SubModule.start(container, module)

				module.$owns[property] = subModules
			}

			module.$element.removeAttribute(`${p}cloak`)

			Module.trigger('init', {target: module})

			modules.push(module)
		}

		return modules
	}

	constructor(element, settings = {}, owner = null)
	{
		super()
		Emitter.call(this)

		if(!element) return

		const Module = this.constructor

		this.$element = element
		this.$owner = owner
		this.$owns = {}
		this.$settings = Object.assign({}, Module.defaults, settings)

		Element.data(element, Module.getDirective(owner), this)
	}
}

Base.directive = null
Base.modules = {}
Base.defaults = {}
Base.methods = Base.prototype
Base.events = {}
<|endoftext|>


<commit_before>'use strict';

var b = require('ast-types').builders;
var reduce = require('lodash/collection/reduce');
var buildObject = require('./object');
var buildStrict = require('./use-strict');
var buildVariable = require('./variable');
var getPrimaryKey = require('../../util/get-primary-key');

module.exports = function buildResolveMap(data, opts) {
    var map = getResolveMap(data.models, opts);

    var program = []
        .concat(buildStrict(opts))
        .concat(buildResolveMapExport(map, opts))
        .concat(buildExportedFunc(opts, getTypeRegisterAst(), 'registerType'))
        .concat(buildExportedFunc(opts, getTypeGetterAst(), 'getType'));

    return b.program(program);
};

function getResolveMap(models, opts) {
    var resolveMap = {};
    for (var type in models) {
        resolveMap[models[type].name] = getTypeResolver(models[type], opts);
    }

    return buildObject(resolveMap, opts);
}

function getTypeResolver(model) {
    return {
        name: model.name,
        table: model.table,
        primaryKey: getPrimaryKeyArg(model),
        aliases: model.aliasedFields,
        referenceMap: getRefFieldMapArg(model)
    };
}

function getRefFieldMapArg(model) {
    return reduce(model.references, buildReferenceMap, {});
}

function getPrimaryKeyArg(model) {
    var primaryKey = getPrimaryKey(model);
    return primaryKey ? primaryKey.originalName : null;
}

function buildReferenceMap(refMap, reference) {
    refMap[reference.field] = reference.refField;
    return refMap;
}

function buildResolveMapExport(map, opts) {
    if (opts.es6) {
        return [b.exportDeclaration(false, buildVariable('resolveMap', map, opts.es6))];
    }

    return [
        buildVariable('resolveMap', map, opts.es6),
        b.expressionStatement(
            b.assignmentExpression(
                '=',
                b.memberExpression(
                    b.identifier('exports'),
                    b.identifier('resolveMap'),
                    false
                ),
                b.identifier('resolveMap')
            )
        )
    ];
}

function buildExportedFunc(opts, ast, name) {
    var func = (opts.es6 ? b.functionDeclaration : b.functionExpression)(
        b.identifier(name),
        [b.identifier('type')],
        b.blockStatement(ast)
    );

    if (opts.es6) {
        return [b.exportDeclaration(false, func)];
    }

    return [
        b.expressionStatement(b.assignmentExpression(
            '=',
            b.memberExpression(
                b.identifier('exports'),
                b.identifier(name),
                false
            ),
            func
        ))
    ];
}

// Can't be bothered trying replicate this with the builder right now :x
// On a totally unrelated side-note, someone should make a thing that takes
// AST and generates code that can build it with the `ast-types` builder
// Then you would be able to easily make parts of it dynamic etc. Much like
// this project, I guess.
function getTypeRegisterAst() {
    return [
        {
            'type': 'IfStatement',
            'test': {
                'type': 'UnaryExpression',
                'operator': '!',
                'argument': {
                    'type': 'MemberExpression',
                    'computed': true,
                    'object': {
                        'type': 'Identifier',
                        'name': 'resolveMap'
                    },
                    'property': {
                        'type': 'MemberExpression',
                        'computed': false,
                        'object': {
                            'type': 'Identifier',
                            'name': 'type'
                        },
                        'property': {
                            'type': 'Identifier',
                            'name': 'name'
                        }
                    }
                },
                'prefix': true
            },
            'consequent': {
                'type': 'BlockStatement',
                'body': [
                    {
                        'type': 'ThrowStatement',
                        'argument': {
                            'type': 'NewExpression',
                            'callee': {
                                'type': 'Identifier',
                                'name': 'Error'
                            },
                            'arguments': [
                                {
                                    'type': 'BinaryExpression',
                                    'operator': '+',
                                    'left': {
                                        'type': 'BinaryExpression',
                                        'operator': '+',
                                        'left': {
                                            'type': 'Literal',
                                            'value': 'Cannot register type "'
                                        },
                                        'right': {
                                            'type': 'MemberExpression',
                                            'computed': false,
                                            'object': {
                                                'type': 'Identifier',
                                                'name': 'type'
                                            },
                                            'property': {
                                                'type': 'Identifier',
                                                'name': 'name'
                                            }
                                        }
                                    },
                                    'right': {
                                        'type': 'Literal',
                                        'value': '" - resolve map does not exist for that type'
                                    }
                                }
                            ]
                        }
                    }
                ]
            },
            'alternate': null
        },
        {
            'type': 'ExpressionStatement',
            'expression': {
                'type': 'AssignmentExpression',
                'operator': '=',
                'left': {
                    'type': 'MemberExpression',
                    'computed': false,
                    'object': {
                        'type': 'MemberExpression',
                        'computed': true,
                        'object': {
                            'type': 'Identifier',
                            'name': 'resolveMap'
                        },
                        'property': {
                            'type': 'MemberExpression',
                            'computed': false,
                            'object': {
                                'type': 'Identifier',
                                'name': 'type'
                            },
                            'property': {
                                'type': 'Identifier',
                                'name': 'name'
                            }
                        }
                    },
                    'property': {
                        'type': 'Identifier',
                        'name': 'type'
                    }
                },
                'right': {
                    'type': 'Identifier',
                    'name': 'type'
                }
            }
        }
    ];
}

function getTypeGetterAst() {
    return [{
        'type': 'IfStatement',
        'test': {
            'type': 'LogicalExpression',
            'operator': '||',
            'left': {
                'type': 'UnaryExpression',
                'operator': '!',
                'argument': {
                    'type': 'MemberExpression',
                    'computed': true,
                    'object': {
                        'type': 'Identifier',
                        'name': 'resolveMap'
                    },
                    'property': {
                        'type': 'Identifier',
                        'name': 'type'
                    }
                },
                'prefix': true
            },
            'right': {
                'type': 'UnaryExpression',
                'operator': '!',
                'argument': {
                    'type': 'MemberExpression',
                    'computed': false,
                    'object': {
                        'type': 'MemberExpression',
                        'computed': true,
                        'object': {
                            'type': 'Identifier',
                            'name': 'resolveMap'
                        },
                        'property': {
                            'type': 'Identifier',
                            'name': 'type'
                        }
                    },
                    'property': {
                        'type': 'Identifier',
                        'name': 'type'
                    }
                },
                'prefix': true
            }
        },
        'consequent': {
            'type': 'BlockStatement',
            'body': [
                {
                    'type': 'ThrowStatement',
                    'argument': {
                        'type': 'NewExpression',
                        'callee': {
                            'type': 'Identifier',
                            'name': 'Error'
                        },
                        'arguments': [
                            {
                                'type': 'BinaryExpression',
                                'operator': '+',
                                'left': {
                                    'type': 'BinaryExpression',
                                    'operator': '+',
                                    'left': {
                                        'type': 'Literal',
                                        'value': 'No type registered for type \''
                                    },
                                    'right': {
                                        'type': 'Identifier',
                                        'name': 'type'
                                    }
                                },
                                'right': {
                                    'type': 'Literal',
                                    'value': '\''
                                }
                            }
                        ]
                    }
                }
            ]
        },
        'alternate': null
    },
    {
        'type': 'ReturnStatement',
        'argument': {
            'type': 'MemberExpression',
            'computed': false,
            'object': {
                'type': 'MemberExpression',
                'computed': true,
                'object': {
                    'type': 'Identifier',
                    'name': 'resolveMap'
                },
                'property': {
                    'type': 'Identifier',
                    'name': 'type'
                }
            },
            'property': {
                'type': 'Identifier',
                'name': 'type'
            }
        }
    }];
}
<commit_msg>Add list references to resolve map<commit_after>'use strict';

var b = require('ast-types').builders;
var reduce = require('lodash/collection/reduce');
var buildObject = require('./object');
var buildStrict = require('./use-strict');
var buildVariable = require('./variable');
var getPrimaryKey = require('../../util/get-primary-key');

module.exports = function buildResolveMap(data, opts) {
    var map = getResolveMap(data.models, opts);

    var program = []
        .concat(buildStrict(opts))
        .concat(buildResolveMapExport(map, opts))
        .concat(buildExportedFunc(opts, getTypeRegisterAst(), 'registerType'))
        .concat(buildExportedFunc(opts, getTypeGetterAst(), 'getType'));

    return b.program(program);
};

function getResolveMap(models, opts) {
    var resolveMap = {};
    for (var type in models) {
        resolveMap[models[type].name] = getTypeResolver(models[type], opts);
    }

    return buildObject(resolveMap, opts);
}

function getTypeResolver(model) {
    return {
        name: model.name,
        table: model.table,
        primaryKey: getPrimaryKeyArg(model),
        aliases: model.aliasedFields,
        referenceMap: getRefFieldMapArg(model),
        listReferences: getListRefFieldMapArg(model)
    };
}

function getListRefFieldMapArg(model) {
    return reduce(model.listReferences, buildReferenceMap, {});
}

function getRefFieldMapArg(model) {
    return reduce(model.references, buildReferenceMap, {});
}

function getPrimaryKeyArg(model) {
    var primaryKey = getPrimaryKey(model);
    return primaryKey ? primaryKey.originalName : null;
}

function buildReferenceMap(refMap, reference) {
    refMap[reference.field] = reference.refField;
    return refMap;
}

function buildResolveMapExport(map, opts) {
    if (opts.es6) {
        return [b.exportDeclaration(false, buildVariable('resolveMap', map, opts.es6))];
    }

    return [
        buildVariable('resolveMap', map, opts.es6),
        b.expressionStatement(
            b.assignmentExpression(
                '=',
                b.memberExpression(
                    b.identifier('exports'),
                    b.identifier('resolveMap'),
                    false
                ),
                b.identifier('resolveMap')
            )
        )
    ];
}

function buildExportedFunc(opts, ast, name) {
    var func = (opts.es6 ? b.functionDeclaration : b.functionExpression)(
        b.identifier(name),
        [b.identifier('type')],
        b.blockStatement(ast)
    );

    if (opts.es6) {
        return [b.exportDeclaration(false, func)];
    }

    return [
        b.expressionStatement(b.assignmentExpression(
            '=',
            b.memberExpression(
                b.identifier('exports'),
                b.identifier(name),
                false
            ),
            func
        ))
    ];
}

// Can't be bothered trying replicate this with the builder right now :x
// On a totally unrelated side-note, someone should make a thing that takes
// AST and generates code that can build it with the `ast-types` builder
// Then you would be able to easily make parts of it dynamic etc. Much like
// this project, I guess.
function getTypeRegisterAst() {
    return [
        {
            'type': 'IfStatement',
            'test': {
                'type': 'UnaryExpression',
                'operator': '!',
                'argument': {
                    'type': 'MemberExpression',
                    'computed': true,
                    'object': {
                        'type': 'Identifier',
                        'name': 'resolveMap'
                    },
                    'property': {
                        'type': 'MemberExpression',
                        'computed': false,
                        'object': {
                            'type': 'Identifier',
                            'name': 'type'
                        },
                        'property': {
                            'type': 'Identifier',
                            'name': 'name'
                        }
                    }
                },
                'prefix': true
            },
            'consequent': {
                'type': 'BlockStatement',
                'body': [
                    {
                        'type': 'ThrowStatement',
                        'argument': {
                            'type': 'NewExpression',
                            'callee': {
                                'type': 'Identifier',
                                'name': 'Error'
                            },
                            'arguments': [
                                {
                                    'type': 'BinaryExpression',
                                    'operator': '+',
                                    'left': {
                                        'type': 'BinaryExpression',
                                        'operator': '+',
                                        'left': {
                                            'type': 'Literal',
                                            'value': 'Cannot register type "'
                                        },
                                        'right': {
                                            'type': 'MemberExpression',
                                            'computed': false,
                                            'object': {
                                                'type': 'Identifier',
                                                'name': 'type'
                                            },
                                            'property': {
                                                'type': 'Identifier',
                                                'name': 'name'
                                            }
                                        }
                                    },
                                    'right': {
                                        'type': 'Literal',
                                        'value': '" - resolve map does not exist for that type'
                                    }
                                }
                            ]
                        }
                    }
                ]
            },
            'alternate': null
        },
        {
            'type': 'ExpressionStatement',
            'expression': {
                'type': 'AssignmentExpression',
                'operator': '=',
                'left': {
                    'type': 'MemberExpression',
                    'computed': false,
                    'object': {
                        'type': 'MemberExpression',
                        'computed': true,
                        'object': {
                            'type': 'Identifier',
                            'name': 'resolveMap'
                        },
                        'property': {
                            'type': 'MemberExpression',
                            'computed': false,
                            'object': {
                                'type': 'Identifier',
                                'name': 'type'
                            },
                            'property': {
                                'type': 'Identifier',
                                'name': 'name'
                            }
                        }
                    },
                    'property': {
                        'type': 'Identifier',
                        'name': 'type'
                    }
                },
                'right': {
                    'type': 'Identifier',
                    'name': 'type'
                }
            }
        }
    ];
}

function getTypeGetterAst() {
    return [{
        'type': 'IfStatement',
        'test': {
            'type': 'LogicalExpression',
            'operator': '||',
            'left': {
                'type': 'UnaryExpression',
                'operator': '!',
                'argument': {
                    'type': 'MemberExpression',
                    'computed': true,
                    'object': {
                        'type': 'Identifier',
                        'name': 'resolveMap'
                    },
                    'property': {
                        'type': 'Identifier',
                        'name': 'type'
                    }
                },
                'prefix': true
            },
            'right': {
                'type': 'UnaryExpression',
                'operator': '!',
                'argument': {
                    'type': 'MemberExpression',
                    'computed': false,
                    'object': {
                        'type': 'MemberExpression',
                        'computed': true,
                        'object': {
                            'type': 'Identifier',
                            'name': 'resolveMap'
                        },
                        'property': {
                            'type': 'Identifier',
                            'name': 'type'
                        }
                    },
                    'property': {
                        'type': 'Identifier',
                        'name': 'type'
                    }
                },
                'prefix': true
            }
        },
        'consequent': {
            'type': 'BlockStatement',
            'body': [
                {
                    'type': 'ThrowStatement',
                    'argument': {
                        'type': 'NewExpression',
                        'callee': {
                            'type': 'Identifier',
                            'name': 'Error'
                        },
                        'arguments': [
                            {
                                'type': 'BinaryExpression',
                                'operator': '+',
                                'left': {
                                    'type': 'BinaryExpression',
                                    'operator': '+',
                                    'left': {
                                        'type': 'Literal',
                                        'value': 'No type registered for type \''
                                    },
                                    'right': {
                                        'type': 'Identifier',
                                        'name': 'type'
                                    }
                                },
                                'right': {
                                    'type': 'Literal',
                                    'value': '\''
                                }
                            }
                        ]
                    }
                }
            ]
        },
        'alternate': null
    },
    {
        'type': 'ReturnStatement',
        'argument': {
            'type': 'MemberExpression',
            'computed': false,
            'object': {
                'type': 'MemberExpression',
                'computed': true,
                'object': {
                    'type': 'Identifier',
                    'name': 'resolveMap'
                },
                'property': {
                    'type': 'Identifier',
                    'name': 'type'
                }
            },
            'property': {
                'type': 'Identifier',
                'name': 'type'
            }
        }
    }];
}
<|endoftext|>


<commit_before>"use strict";

let moment = require("moment");
let mongoose = require("mongoose");
let util = require("util");

let BetSchema = new mongoose.Schema({
  account: String,
  nick: String,
  season: Number,
  round: Number,
  points: Number,
  bets: [{
    _id: false,
    firstName: String,
    lastName: String,
    code: String
  }],
  created: {
    type: Date,
    default: Date.now,
  },
});

BetSchema.statics.isBetWindowOpen = function() {
  return this.db.model("event").findNext("qualifying").then(event => {
    if (event) {
      let min = moment(event.start).startOf("isoweek");
      if (min.isBefore()) {
        return Promise.resolve(event.start);
      } else {
        let date = min.format("MMMM D, HH:mm UTC");
        let message = util.format("Bets will be allowed after %s, until qualifying!", date);
        throw new Error(message);
      }
    } else {
      throw new Error("Bets are not allowed");
    }
  });
};

BetSchema.statics.activeBetRound = function() {
  let max_date = moment().utc().isoWeekday(7).hour(23).minute(59).toDate();
  console.log("D", max_date);
  let season = max_date.getFullYear();
  return this.db.model("event").findOne({season: season, start: {$lte: max_date}}).sort("-start")
    .then(event => {
      if (event) {
        return event;
      } else {
        throw new Error("No active round found");
      }
    });
};

BetSchema.statics.userBets = function(nick, round) {
  return this.findOne({nick: nick, round: round, season: (new Date).getFullYear()});
};

BetSchema.statics.setUserBets = function(account, round, names) {
  return this.db.model("season").driversForNames(names).then(drivers => {
    let query = {
      account: account.account,
      season: (new Date).getFullYear(),
      round: round,
    };
    let values = {
      nick: account.nick,
      account: account.account,
      season: query.season,
      round: query.round,
      bets: drivers,
      created: new Date,
    };
    return Bet
      .update(query, values, {upsert: true})
      .then(status => drivers);
  });
};

BetSchema.statics.latestRound = function(season) {
  let query = {points: {$exists: true}};

  if (season) {
    query.season = season;
  }

  return this.findOne(query)
    .sort({season: -1, round: -1})
    .then(bet => bet || Promise.reject(new Error("No points data")))
    .then(bet => ({season: bet.season, round: bet.round}));
};

BetSchema.statics.pointsForRound = function(round) {
  if (round == "last") {
    round = 0;
  }
  return Promise.resolve(round == 0 ? this.latestRound() : {round: round, season: (new Date).getFullYear()})
    .then(entry => this.find({round: entry.round, season: entry.season, points: {$exists: true}}).sort("-points"))
    .then(bets => {
      if (!bets.length) {
        throw new Error(util.format("No results for round %d", round));
      }

      return this.model("event").findOne({type: "race", season: bets[0].season, round: bets[0].round}).then(event => ({
        season: event.season,
        round: event.round,
        name: event.name,
        bets: bets
      }));
    });
};

BetSchema.statics.pointsForSeason = function(season) {
  return this.latestRound(season)
    .then(entry => this.model("event").findOne({type: "race", season: entry.season, round: entry.round}))
    .then(event => new Promise((resolve, reject) => {
      let query = [
        {$match: {season: event.season, points: {$exists: true}}},
        {$group: {_id: "$nick", points: {$sum: "$points"}, nick: {$first: "$nick"}}},
        {$sort: {points: -1}}
      ];
      this.aggregate(query, (error, result) => {
        if (error) {
          throw error;
        }
        resolve({
          season: event.season,
          round: event.round,
          name: event.name,
          bets: result
        });
      })
    }));
}

let Bet = mongoose.model("bet", BetSchema);

class PointsCalculator {
  constructor(results) {
    this.results = results;
    this.scoring = [10, 5, 3, 1];
    this.bonus = 5;
  }

  process(bet) {
    return new Promise((resolve, reject) => {
      bet.points = this.scores(...bet.bets);
      resolve(bet);
    });
  }

  scores(d1, d2, d3) {
    let points = [d1, d2, d3].reduce((value, driver, i) => {
      if (driver.code == this.results[i].code) {
        return value + this.scoring[i];
      } else if (this.driverOnPodium(driver)) {
        return value + this.scoring[3];
      }
      return value;
    }, 0);

    let max = this.scoring.slice(0, 3).reduce((sum, x) => sum + x, 0);

    if (points == max) {
      points += this.bonus;
    }

    return points;
  }

  driverOnPodium(driver) {
    return this.results.slice(0, 3).map(d => d.code).indexOf(driver.code) != -1;
  }
}

exports.configure = services => {
  let database = services.get("database");
  let whois = services.get("whois");
  let commands = services.get("command.manager");
  let events = services.get("event.manager");
  let Result = database.model("result");
  let Event = database.model("event");

  commands.add("bet", (nick, ...names) => {
    if (names.length == 0) {
      return Bet.userBets(nick, this.activeEvent.round).then(doc => {
        if (doc) {
          let names = doc.bets.map((d, i) => util.format("%d. %s %s", i+1, d.firstName, d.lastName));
          return util.format("%s: %s", nick, names.join(" "));
        } else {
          return Promise.resolve(util.format("%s: You have no bets for this round", nick));
        }
      });
    } else {
      return whois.auth(nick)
        // .catch(() => ({nick: nick, account: nick + "_auth"}))
        .then(account => Bet.setUserBets(account, this.activeEvent.round, names))
        .then(drivers => {
          let names = drivers.map((d, i) => util.format("%d. %s %s", i+1, d.firstName, d.lastName));
          return util.format("%s: %s [OK]", nick, names.join(" "));
        });
    }
  })
  .validate(() => Bet.isBetWindowOpen())
  .validate((nick, ...names) => {
    if (names.length != 0 && names.length != 3) {
      return Promise.reject("Need three names to bet");
    }
  })
  .validate(() => {
    return Bet.activeBetRound().then(event => {
      // console.log("GOT EVENT", event);
      this.activeEvent = event;
    });
  });

  commands.add("top", (nick, round) => {
    if (round) {
      return Bet.pointsForRound(round).then(result => {
        let points = result.bets.map(bet => util.format("%s %d", bet.nick, bet.points));
        let message = util.format("Score for %s: %s", result.name, points.join("; "));
        return {type: "notice", content: message};
      });
    } else {
      return Bet.pointsForSeason().then(result => {
        let points = result.bets.map(bet => util.format("%s %d", bet.nick, bet.points));
        let message = util.format("Points after %s: %s", result.name, points.join("; "));
        return {type: "notice", content: message};
      });
    }
  });

  events.on("raceresults.result", event => {
    let query = {season: event.season, round: event.round};
    Promise.all([Result.findOne(query), Bet.find(query)])
      .then(([result, bets]) => Promise.all(bets.map(bet => (new PointsCalculator(result.results)).process(bet))))
      .then(bets => Promise.all(bets.map(bet => bet.save())))
      .then(() => console.log("Updated scores for round", event.round));
  });

  // (new Bet({
  //   account: "foobar_demo",
  //   nick: "foobar",
  //   season: 2016,
  //   round: 3,
  //   bets: [
  //     {firstName: "Lewis", lastName: "Hamilton", code: "HAM"},
  //     {firstName: "Kimi", lastName: "Räikkönen", code: "RAI"},
  //     {firstName: "Sebastian", lastName: "Vettel", code: "VET"}
  //   ]
  // })).save();
  //
  // (new Bet({
  //   account: "foobar_demo",
  //   nick: "foobar",
  //   season: 2016,
  //   round: 2,
  //   bets: [
  //     {firstName: "Lewis", lastName: "Hamilton", code: "HAM"},
  //     {firstName: "Nico", lastName: "Rosberg", code: "ROS"},
  //     {firstName: "Jenson", lastName: "Button", code: "Button"}
  //   ]
  // })).save();
  //
  // (new Bet({
  //   account: "foobar_demo",
  //   nick: "foobar",
  //   season: 2016,
  //   round: 5,
  //   bets: [
  //     {firstName: "Nico", lastName: "Rosberg", code: "ROS"},
  //     {firstName: "Kimi", lastName: "Räikkönen", code: "RAI"},
  //     {firstName: "Fernando", lastName: "Alonso", code: "ALO"}
  //   ]
  // })).save();
  //
  // (new Bet({
  //   account: "miska_demo",
  //   nick: "miska",
  //   season: 2016,
  //   round: 3,
  //   bets: [
  //     {firstName: "Sebastian", lastName: "Vettel", code: "VET"},
  //     {firstName: "Lewis", lastName: "Hamilton", code: "HAM"},
  //     {firstName: "Kimi", lastName: "Räikkönen", code: "RAI"},
  //   ]
  // })).save();
  //
  // (new Bet({
  //   account: "miska_demo",
  //   nick: "miska",
  //   season: 2016,
  //   round: 5,
  //   bets: [
  //     {firstName: "Lewis", lastName: "Hamilton", code: "HAM"},
  //     {firstName: "Nico", lastName: "Rosberg", code: "ROS"},
  //       {firstName: "Sebastian", lastName: "Vettel", code: "VET"},
  //   ]
  // })).save();

  // let sqlite = require("sqlite3");
  // let source = new sqlite.Database("data/database.sqlite");

  // database.model("season").findOne({_id: 2016}).then(season => {
  //   let drivers = new Map(season.drivers.map(d => [d.firstName + " " + d.lastName, d]));
  //   drivers.set("Pastor Maldonado", {firstName: "Pastor", lastName: "Maldonado", code: "MAL"});
  //   drivers.set("Sergio Perez", {firstName: "Sergio", lastName: "Pérez", code: "PER"});
  //   drivers.set("Roberto Merhi", {firstName: "Roberto", lastName: "Merhi", code: "MER"});
  //   drivers.set("Nico Hulkenberg", {firstName: "Nico", lastName: "Hülkenberg", code: "HUL"});
  //
  //   source.all("SELECT * from betgame_bets", (error, rows) => {
  //     rows.map(row => {
  //       let bet = new Bet({
  //         account: row.user,
  //         nick: row.nick,
  //         season: row.season,
  //         round: row.round,
  //         created: new Date(row.time),
  //         bets: [row.d1, row.d2, row.d3].map(name => {
  //           if (!drivers.has(name)) {
  //             console.error("NO NAME", name);
  //           }
  //           return drivers.get(name)
  //         }),
  //       });
  //       bet.save();
  //     });
  //   });
  // });

  // source.all("SELECT * FROM betgame_points WHERE season = 2015", (error, rows) => {
  //   rows.map(row => {
  //     Bet.findOne({account: row.user, season: row.season, round: row.round}).then(bet => {
  //       bet.points = row.points;
  //       bet.save();
  //     });
  //   });
  // });
};
<commit_msg>cleanups<commit_after>"use strict";

let moment = require("moment");
let mongoose = require("mongoose");
let util = require("util");

let BetSchema = new mongoose.Schema({
  account: String,
  nick: String,
  season: Number,
  round: Number,
  points: Number,
  bets: [{
    _id: false,
    firstName: String,
    lastName: String,
    code: String
  }],
  created: {
    type: Date,
    default: Date.now,
  },
});

BetSchema.statics.isBetWindowOpen = function() {
  return this.db.model("event").findNext("qualifying").then(event => {
    if (event) {
      let min = moment(event.start).startOf("isoweek");
      if (min.isBefore()) {
        return Promise.resolve(event.start);
      } else {
        let date = min.format("MMMM D, HH:mm UTC");
        let message = util.format("Bets will be allowed after %s, until qualifying!", date);
        throw new Error(message);
      }
    } else {
      throw new Error("Bets are not allowed");
    }
  });
};

BetSchema.statics.activeBetRound = function() {
  let max_date = moment().utc().isoWeekday(7).hour(23).minute(59).toDate();
  console.log("D", max_date);
  let season = max_date.getFullYear();
  return this.db.model("event").findOne({season: season, start: {$lte: max_date}}).sort("-start")
    .then(event => {
      if (event) {
        return event;
      } else {
        throw new Error("No active round found");
      }
    });
};

BetSchema.statics.userBets = function(nick, round) {
  return this.findOne({nick: nick, round: round, season: (new Date).getFullYear()});
};

BetSchema.statics.setUserBets = function(account, round, names) {
  return this.db.model("season").driversForNames(names).then(drivers => {
    let query = {
      account: account.account,
      season: (new Date).getFullYear(),
      round: round,
    };
    let values = {
      nick: account.nick,
      account: account.account,
      season: query.season,
      round: query.round,
      bets: drivers,
      created: new Date,
    };
    return Bet
      .update(query, values, {upsert: true})
      .then(status => drivers);
  });
};

BetSchema.statics.latestRound = function(season) {
  let query = {points: {$exists: true}};

  if (season) {
    query.season = season;
  }

  return this.findOne(query)
    .sort({season: -1, round: -1})
    .then(bet => bet || Promise.reject(new Error("No points data")))
    .then(bet => ({season: bet.season, round: bet.round}));
};

BetSchema.statics.pointsForRound = function(round) {
  if (round == "last") {
    round = 0;
  }
  return Promise.resolve(round == 0 ? this.latestRound() : {round: round, season: (new Date).getFullYear()})
    .then(entry => this.find({round: entry.round, season: entry.season, points: {$exists: true}}).sort("-points"))
    .then(bets => {
      if (!bets.length) {
        throw new Error(util.format("No results for round %d", round));
      }

      return this.model("event").findOne({type: "race", season: bets[0].season, round: bets[0].round}).then(event => ({
        season: event.season,
        round: event.round,
        name: event.name,
        bets: bets
      }));
    });
};

BetSchema.statics.pointsForSeason = function(season) {
  return this.latestRound(season)
    .then(entry => this.model("event").findOne({type: "race", season: entry.season, round: entry.round}))
    .then(event => new Promise((resolve, reject) => {
      let query = [
        {$match: {season: event.season, points: {$exists: true}}},
        {$group: {_id: "$nick", points: {$sum: "$points"}, nick: {$first: "$nick"}}},
        {$sort: {points: -1}}
      ];
      this.aggregate(query, (error, result) => {
        if (error) {
          throw error;
        }
        resolve({
          season: event.season,
          round: event.round,
          name: event.name,
          bets: result
        });
      })
    }));
}

let Bet = mongoose.model("bet", BetSchema);

class PointsCalculator {
  constructor(results) {
    this.results = results;
    this.scoring = [10, 5, 3, 1];
    this.bonus = 5;
  }

  process(bet) {
    return new Promise((resolve, reject) => {
      bet.points = this.scores(...bet.bets);
      resolve(bet);
    });
  }

  scores(d1, d2, d3) {
    let points = [d1, d2, d3].reduce((value, driver, i) => {
      if (driver.code == this.results[i].code) {
        return value + this.scoring[i];
      } else if (this.driverOnPodium(driver)) {
        return value + this.scoring[3];
      }
      return value;
    }, 0);

    let max = this.scoring.slice(0, 3).reduce((sum, x) => sum + x, 0);

    if (points == max) {
      points += this.bonus;
    }

    return points;
  }

  driverOnPodium(driver) {
    return this.results.slice(0, 3).map(d => d.code).indexOf(driver.code) != -1;
  }
}
exports.configure = services => {
  let database = services.get("database");
  let whois = services.get("whois");
  let commands = services.get("command.manager");
  let events = services.get("event.manager");
  let Result = database.model("result");
  let Event = database.model("event");

  commands.add("bet", (nick, ...names) => {
    if (names.length == 0) {
      return Bet.userBets(nick, this.activeEvent.round).then(doc => {
        if (doc) {
          let names = doc.bets.map((d, i) => util.format("%d. %s %s", i+1, d.firstName, d.lastName));
          return util.format("%s: %s", nick, names.join(" "));
        } else {
          return Promise.resolve(util.format("%s: You have no bets for this round", nick));
        }
      });
    } else {
      return whois.auth(nick)
        // .catch(() => ({nick: nick, account: nick + "_auth"}))
        .then(account => Bet.setUserBets(account, this.activeEvent.round, names))
        .then(drivers => {
          let names = drivers.map((d, i) => util.format("%d. %s %s", i+1, d.firstName, d.lastName));
          return util.format("%s: %s [OK]", nick, names.join(" "));
        });
    }
  })
  .validate(() => Bet.isBetWindowOpen())
  .validate((nick, ...names) => {
    if (names.length != 0 && names.length != 3) {
      return Promise.reject("Need three names to bet");
    }
  })
  .validate(() => {
    return Bet.activeBetRound().then(event => {
      // console.log("GOT EVENT", event);
      this.activeEvent = event;
    });
  });

  commands.add("top", (nick, round) => {
    if (round) {
      return Bet.pointsForRound(round).then(result => {
        let points = result.bets.map(bet => util.format("%s %d", bet.nick, bet.points));
        let message = util.format("Score for %s: %s", result.name, points.join("; "));
        return {type: "notice", content: message};
      });
    } else {
      return Bet.pointsForSeason().then(result => {
        let points = result.bets.map(bet => util.format("%s %d", bet.nick, bet.points));
        let message = util.format("Points after %s: %s", result.name, points.join("; "));
        return {type: "notice", content: message};
      });
    }
  });

  events.on("raceresults.result", event => {
    let query = {season: event.season, round: event.round};
    Promise.all([Result.findOne(query), Bet.find(query)])
      .then(([result, bets]) => Promise.all(bets.map(bet => (new PointsCalculator(result.results)).process(bet))))
      .then(bets => Promise.all(bets.map(bet => bet.save())))
      .then(() => console.log("Updated scores for round", event.round));
  });

  // (new Bet({
  //   account: "foobar_demo",
  //   nick: "foobar",
  //   season: 2016,
  //   round: 3,
  //   bets: [
  //     {firstName: "Lewis", lastName: "Hamilton", code: "HAM"},
  //     {firstName: "Kimi", lastName: "Räikkönen", code: "RAI"},
  //     {firstName: "Sebastian", lastName: "Vettel", code: "VET"}
  //   ]
  // })).save();
  //
  // (new Bet({
  //   account: "foobar_demo",
  //   nick: "foobar",
  //   season: 2016,
  //   round: 2,
  //   bets: [
  //     {firstName: "Lewis", lastName: "Hamilton", code: "HAM"},
  //     {firstName: "Nico", lastName: "Rosberg", code: "ROS"},
  //     {firstName: "Jenson", lastName: "Button", code: "Button"}
  //   ]
  // })).save();
  //
  // (new Bet({
  //   account: "foobar_demo",
  //   nick: "foobar",
  //   season: 2016,
  //   round: 5,
  //   bets: [
  //     {firstName: "Nico", lastName: "Rosberg", code: "ROS"},
  //     {firstName: "Kimi", lastName: "Räikkönen", code: "RAI"},
  //     {firstName: "Fernando", lastName: "Alonso", code: "ALO"}
  //   ]
  // })).save();
  //
  // (new Bet({
  //   account: "miska_demo",
  //   nick: "miska",
  //   season: 2016,
  //   round: 3,
  //   bets: [
  //     {firstName: "Sebastian", lastName: "Vettel", code: "VET"},
  //     {firstName: "Lewis", lastName: "Hamilton", code: "HAM"},
  //     {firstName: "Kimi", lastName: "Räikkönen", code: "RAI"},
  //   ]
  // })).save();
  //
  // (new Bet({
  //   account: "miska_demo",
  //   nick: "miska",
  //   season: 2016,
  //   round: 5,
  //   bets: [
  //     {firstName: "Lewis", lastName: "Hamilton", code: "HAM"},
  //     {firstName: "Nico", lastName: "Rosberg", code: "ROS"},
  //       {firstName: "Sebastian", lastName: "Vettel", code: "VET"},
  //   ]
  // })).save();

  // let sqlite = require("sqlite3");
  // let source = new sqlite.Database("data/database.sqlite");

  // database.model("season").findOne({_id: 2016}).then(season => {
  //   let drivers = new Map(season.drivers.map(d => [d.firstName + " " + d.lastName, d]));
  //   drivers.set("Pastor Maldonado", {firstName: "Pastor", lastName: "Maldonado", code: "MAL"});
  //   drivers.set("Sergio Perez", {firstName: "Sergio", lastName: "Pérez", code: "PER"});
  //   drivers.set("Roberto Merhi", {firstName: "Roberto", lastName: "Merhi", code: "MER"});
  //   drivers.set("Nico Hulkenberg", {firstName: "Nico", lastName: "Hülkenberg", code: "HUL"});
  //
  //   source.all("SELECT * from betgame_bets", (error, rows) => {
  //     rows.map(row => {
  //       let bet = new Bet({
  //         account: row.user,
  //         nick: row.nick,
  //         season: row.season,
  //         round: row.round,
  //         created: new Date(row.time),
  //         bets: [row.d1, row.d2, row.d3].map(name => {
  //           if (!drivers.has(name)) {
  //             console.error("NO NAME", name);
  //           }
  //           return drivers.get(name)
  //         }),
  //       });
  //       bet.save();
  //     });
  //   });
  // });

  // source.all("SELECT * FROM betgame_points WHERE season = 2015", (error, rows) => {
  //   rows.map(row => {
  //     Bet.findOne({account: row.user, season: row.season, round: row.round}).then(bet => {
  //       bet.points = row.points;
  //       bet.save();
  //     });
  //   });
  // });
};
<|endoftext|>


<commit_before>// @cliDescription  Generate a new React Native project with Ignite CLI.
// @cliAlias n
// ----------------------------------------------------------------------------
const isIgniteDirectory = require('../lib/isIgniteDirectory')
const exitCodes = require('../lib/exitCodes')
const path = require('path')
const header = require('../brand/header')
const addEmptyBoilerplate = require('../lib/addEmptyBoilerplate')
const { forEach, keys, reduce, concat, trim, isEmpty, match, not } = require('ramda')

/**
 * Creates a new ignite project based on an optional boilerplate.
 *
 * @param {any} context - The gluegun context.
 */
async function command (context) {
  const { parameters, strings, print, system, filesystem, ignite } = context
  const { isBlank, upperFirst, camelCase } = strings
  const { log } = ignite

  // grab the project name
  const projectName = parameters.second

  // check for kebabs
  const isKebabCase = not(isEmpty(match(/.-/g, projectName || '')))

  // camelCase the project name for user example
  const projectNameCamel = upperFirst(camelCase(projectName))

  // ensure we're in a supported directory
  if (isIgniteDirectory(process.cwd())) {
    context.print.error('The `ignite new` command cannot be run within an already ignited project.')
    process.exit(exitCodes.NOT_IGNITE_PROJECT)
  }

  // prevent installing when node_modules/react-native exists
  if (filesystem.exists('node_modules/react-native')) {
    context.print.error('The `ignite new` command cannot be run within a directory with `node_modules/react-native` installed.')
    context.print.error('Try installing from a directory without a `node_modules` directory.')
    process.exit(exitCodes.EXISTING_REACT_NATIVE)
  }

  // verify the project name is a thing
  if (isBlank(projectName)) {
    print.info(`${context.runtime.brand} new <projectName>\n`)
    print.error('Project name is required')
    process.exit(exitCodes.PROJECT_NAME)
  }

  // verify the project name isn't kebab cased
  if (isKebabCase) {
    print.error(`Please use camel case for your project name. Ex: ${projectNameCamel}`)
    process.exit(exitCodes.PROJECT_NAME)
  }

  // verify the directory doesn't exist already
  if (filesystem.exists(projectName) === 'dir') {
    print.error(`Directory ${projectName} already exists.`)
    process.exit(exitCodes.DIRECTORY_EXISTS)
  }

  // print a header
  header()
  print.newline()
  print.info(`🔥 igniting app ${print.colors.yellow(projectName)}`)

  // skip the boilerplate?
  // NOTE(steve): this expression is intentionally evaluating against false because of
  // --no-boilerplate and how minimist works.
  if (parameters.options.boilerplate === false) {
    addEmptyBoilerplate(context)
    return
  }

  // make & jump into the project directory
  log(`making directory ${projectName}`)
  filesystem.dir(projectName)
  process.chdir(projectName)
  log(`switched directory to ${process.cwd()}`)

  // make a temporary package.json file so node stops walking up the diretories
  // NOTE(steve): a lot of pain went into this 1 function call
  filesystem.write('package.json', {
    name: 'ignite-shim',
    description: 'A temporary package.json created to prevent node from wandering too far.',
    repository: 'infinitered/ignite',
    license: 'MIT'
  })

  // grab the right boilerplate
  const cliBoilerplate = parameters.options.boilerplate || parameters.options.b
  const boilerplateName = strings.isBlank(cliBoilerplate) ? 'ir-boilerplate-2016' : cliBoilerplate

  // pick the inbound cli options
  const cliOpts = parameters.options

  // turn this back into a string
  const forwardingOptions = trim(reduce((src, k) => {
    const v = cliOpts[k]
    return concat(v === true ? `--${k} ` : `--${k} ${v} `, src)
  })('', keys(cliOpts)))

  // let's kick off the template
  let ok = false
  try {
    const command = trim(`ignite boilerplate-install ${boilerplateName} ${projectName} ${forwardingOptions}`)
    log(`running boilerplate: ${command}`)
    await system.exec(command, { stdio: 'inherit' })
    log('finished boilerplate')
    ok = true
  } catch (e) {
    log('error running boilerplate')
    log(e)
  }

  // always clean up the app-template stuff
  log('cleaning temporary files')
  filesystem.remove('node_modules')
  filesystem.remove('ignite')
  filesystem.remove('package.json')

  // did we install everything successfully?
  if (ok) {
    log(`moving contents of ${projectName} into place`)
    // move everything that's 1 deep back up to here
    forEach(
      filename => filesystem.move(path.join(projectName, filename), filename)
      , filesystem.list(projectName) || []
    )
    log(`removing unused sub directory ${projectName}`)
    filesystem.remove(projectName)
  }
  log('finished running new')
}

module.exports = command
<commit_msg>Guard against ignite new ignite<commit_after>// @cliDescription  Generate a new React Native project with Ignite CLI.
// @cliAlias n
// ----------------------------------------------------------------------------
const isIgniteDirectory = require('../lib/isIgniteDirectory')
const exitCodes = require('../lib/exitCodes')
const path = require('path')
const header = require('../brand/header')
const addEmptyBoilerplate = require('../lib/addEmptyBoilerplate')
const { forEach, keys, reduce, concat, trim, isEmpty, match, not } = require('ramda')

/**
 * Creates a new ignite project based on an optional boilerplate.
 *
 * @param {any} context - The gluegun context.
 */
async function command (context) {
  const { parameters, strings, print, system, filesystem, ignite } = context
  const { isBlank, upperFirst, camelCase } = strings
  const { log } = ignite

  // grab the project name
  const projectName = parameters.second

  // check for kebabs
  const isKebabCase = not(isEmpty(match(/.-/g, projectName || '')))

  // camelCase the project name for user example
  const projectNameCamel = upperFirst(camelCase(projectName))

  // Guard against `ignite new ignite`
  if (projectNameCamel === 'ignite') {
    context.print.error('Crossing the streams is bad! Please use another name for your project.')
    process.exit(exitCodes.PROJECT_NAME)
  }

  // ensure we're in a supported directory
  if (isIgniteDirectory(process.cwd())) {
    context.print.error('The `ignite new` command cannot be run within an already ignited project.')
    process.exit(exitCodes.NOT_IGNITE_PROJECT)
  }

  // prevent installing when node_modules/react-native exists
  if (filesystem.exists('node_modules/react-native')) {
    context.print.error('The `ignite new` command cannot be run within a directory with `node_modules/react-native` installed.')
    context.print.error('Try installing from a directory without a `node_modules` directory.')
    process.exit(exitCodes.EXISTING_REACT_NATIVE)
  }

  // verify the project name is a thing
  if (isBlank(projectName)) {
    print.info(`${context.runtime.brand} new <projectName>\n`)
    print.error('Project name is required')
    process.exit(exitCodes.PROJECT_NAME)
  }

  // verify the project name isn't kebab cased
  if (isKebabCase) {
    print.error(`Please use camel case for your project name. Ex: ${projectNameCamel}`)
    process.exit(exitCodes.PROJECT_NAME)
  }

  // verify the directory doesn't exist already
  if (filesystem.exists(projectName) === 'dir') {
    print.error(`Directory ${projectName} already exists.`)
    process.exit(exitCodes.DIRECTORY_EXISTS)
  }

  // print a header
  header()
  print.newline()
  print.info(`🔥 igniting app ${print.colors.yellow(projectName)}`)

  // skip the boilerplate?
  // NOTE(steve): this expression is intentionally evaluating against false because of
  // --no-boilerplate and how minimist works.
  if (parameters.options.boilerplate === false) {
    addEmptyBoilerplate(context)
    return
  }

  // make & jump into the project directory
  log(`making directory ${projectName}`)
  filesystem.dir(projectName)
  process.chdir(projectName)
  log(`switched directory to ${process.cwd()}`)

  // make a temporary package.json file so node stops walking up the diretories
  // NOTE(steve): a lot of pain went into this 1 function call
  filesystem.write('package.json', {
    name: 'ignite-shim',
    description: 'A temporary package.json created to prevent node from wandering too far.',
    repository: 'infinitered/ignite',
    license: 'MIT'
  })

  // grab the right boilerplate
  const cliBoilerplate = parameters.options.boilerplate || parameters.options.b
  const boilerplateName = strings.isBlank(cliBoilerplate) ? 'ir-boilerplate-2016' : cliBoilerplate

  // pick the inbound cli options
  const cliOpts = parameters.options

  // turn this back into a string
  const forwardingOptions = trim(reduce((src, k) => {
    const v = cliOpts[k]
    return concat(v === true ? `--${k} ` : `--${k} ${v} `, src)
  })('', keys(cliOpts)))

  // let's kick off the template
  let ok = false
  try {
    const command = trim(`ignite boilerplate-install ${boilerplateName} ${projectName} ${forwardingOptions}`)
    log(`running boilerplate: ${command}`)
    await system.exec(command, { stdio: 'inherit' })
    log('finished boilerplate')
    ok = true
  } catch (e) {
    log('error running boilerplate')
    log(e)
  }

  // always clean up the app-template stuff
  log('cleaning temporary files')
  filesystem.remove('node_modules')
  filesystem.remove('ignite')
  filesystem.remove('package.json')

  // did we install everything successfully?
  if (ok) {
    log(`moving contents of ${projectName} into place`)
    // move everything that's 1 deep back up to here
    forEach(
      filename => filesystem.move(path.join(projectName, filename), filename)
      , filesystem.list(projectName) || []
    )
    log(`removing unused sub directory ${projectName}`)
    filesystem.remove(projectName)
  }
  log('finished running new')
}

module.exports = command
<|endoftext|>


<commit_before>"use strict";

let moment = require("moment");
let mongoose = require("mongoose");
let util = require("util");

let BetSchema = new mongoose.Schema({
  season: Number,
  round: Number,
  account: String,
  nick: String,
  bets: [{
    _id: false,
    firstName: String,
    lastName: String,
    code: String,
    points: Number,
  }],
  created: {
    type: Date,
    default: Date.now,
  },
  points: Number,
  maximum: Boolean,
  fallback: Boolean,
});

/**
 * Find latest bets for each user that has placed bets during current season.
 */
BetSchema.statics.activeUserBets = function() {
  let season = (new Date).getFullYear();
  let round = null;
  return this.latestRound(season)
    .then(entry => {
      round = entry.round;

      return [
        {$match: {season: season}},
        {$sort: {round: -1}},
        {$group: {
          _id: "$account",
          account: {$first: "$account"},
          nick: {$first: "$nick"},
          bets: {$first: "$bets"},
          round: {$first: "$round"},
          season: {$first: "$season"}
        }}
      ];
    })
    .then(query => new Promise((resolve, reject) => this.aggregate(query, (error, result) => {
      error ? reject(error) : resolve(result);
    })))
    .then(result => ({
      season: season,
      round: round,
      bets: result,
    }));
};

BetSchema.statics.ensureActiveUsersHaveBets = function() {
  return this.activeUserBets().then(result => {
    let max_round = result.round;
    let bets = result.bets.filter(bet => (bet.round < max_round));
    let Bet = this.model("bet");

    return Promise.all(bets.map(bet => {
      let data = {
        fallback: true,
        round: max_round,
        season: bet.season,
        account: bet.account,
        nick: bet.nick,
        bets: bet.bets,
      }
      return (new Bet(data)).save();
    }));
  });
};

/**
 * Current rules are such that bet window is open on the following Monday after the race,
 * until next qualifying session begins.
 */
BetSchema.statics.isBetWindowOpen = function() {
  let storage = this.db.model("event");
  let is_season_first = true;
  return storage.findPrevious("race")
    .then(event => {
      // Assert that last GP weekend has finished.
      if (event) {
        is_season_first = (event.start.getFullYear() != (new Date).getFullYear());
        let min = moment(event.start).add(7, "days").startOf("isoweek");
        if (min.isAfter()) {
          let date = min.format("MMMM D, HH:mm UTC");
          let message = util.format("Bets will be allowed after %s, until qualifying!", date);
          throw new Error(message);
        }
      }
    })
    .then(() => storage.findNext())
    .then(event => {
      // Assert that the quali for the next race has not finished.
      if (event) {
        if (event.type == "race") {
          let min = moment(event.start).add(7, "days").startOf("isoweek");
          let date = min.format("MMMM D, HH:mm UTC");
          let message = util.format("Bets will be allowed after %s, until qualifying!", date);
          throw new Error(message);
        } else if (is_season_first) {
          let min = moment(event.start).subtract(7, "days").startOf("isoweek");
          if (min.isAfter()) {
            let date = min.format("MMMM D, HH:mm UTC");
            let message = util.format("Bets will be allowed after %s, until qualifying!", date);
            throw new Error(message);
          }
        }
      } else {
        throw new Error("Bets are not allowed");
      }
    });
};

BetSchema.statics.activeBetRound = function() {
  return this.db.model("event").findNext().then(event => {
    // if (event && event.type != "race") {
    if (event) {
      return event;
    } else {
      throw new Error("No active round found");
    }
  });
};

BetSchema.statics.userBets = function(nick, round) {
  return this.findOne({nick: nick, round: round, season: (new Date).getFullYear()});
};

BetSchema.statics.setUserBets = function(account, round, names) {
  return this.db.model("season").driversForNames(names).then(drivers => {
    drivers.forEach((driver, i) => {
      if (drivers.indexOf(driver) < i) {
        throw new Error(util.format("Duplicate driver '%s'", driver.code));
      }
    });

    let query = {
      account: account.account,
      season: (new Date).getFullYear(),
      round: round,
    };
    let values = {
      nick: account.nick,
      account: account.account,
      season: query.season,
      round: query.round,
      bets: drivers,
      created: new Date,
    };
    return this
      .update(query, values, {upsert: true})
      .then(status => drivers);
  });
};

BetSchema.statics.latestRound = function(season) {
  let query = {
    results: {$exists: true},
    season: season || (new Date).getFullYear(),
  };

  return this.model("event").findOne(query)
    .sort({season: -1, round: -1})
    .then(event => event || Promise.reject(new Error("No event data")))
    .then(event => ({season: event.season, round: event.round}));
};

BetSchema.statics.pointsForRound = function(round) {
  if (round == "last") {
    round = 0;
  }
  return Promise.resolve(round == 0 ? this.latestRound() : {round: round, season: (new Date).getFullYear()})
    .then(entry => this.find({round: entry.round, season: entry.season, points: {$exists: true}}).sort("-points"))
    .then(bets => {
      if (!bets.length) {
        throw new Error(util.format("No results for round %d", round));
      }

      return this.model("event").findOne({type: "race", season: bets[0].season, round: bets[0].round}).then(event => ({
        season: event.season,
        round: event.round,
        name: event.name,
        bets: bets
      }));
    });
};

BetSchema.statics.pointsForSeason = function(season) {
  return this.latestRound(season)
    .then(entry => this.model("event").findOne({type: "race", season: entry.season, round: entry.round}))
    .then(event => new Promise((resolve, reject) => {
      let query = [
        {$match: {season: event.season, points: {$exists: true}}},
        {$group: {_id: "$account", points: {$sum: "$points"}, nick: {$first: "$nick"}}},
        {$sort: {points: -1}}
      ];
      this.aggregate(query, (error, result) => {
        if (error) {
          throw error;
        }
        resolve({
          season: event.season,
          round: event.round,
          name: event.name,
          bets: result
        });
      })
    }));
}

/**
 * @deprecated
 */
class PointsCalculator {
  constructor(results) {
    this.results = results;
    this.scoring = [10, 5, 3, 1];
    this.bonus = 5;
  }

  process(bet) {
    return new Promise((resolve, reject) => {
      bet.points = this.scores(bet);
      resolve(bet);
    });
  }

  scores(bet) {
    let points = [...bet.bets].reduce((value, driver, i) => {
      if (driver.code == this.results[i].code) {
        driver.points = this.scoring[i];
        return value + driver.points;
      } else if (this.driverOnPodium(driver)) {
        driver.points = this.scoring[3];
        return value + driver.points;
      } else {
        driver.points = 0;
      }
      return value;
    }, 0);

    let max = this.scoring.slice(0, 3).reduce((sum, x) => sum + x, 0);

    if (points == max) {
      bet.maximum = true;
      points += this.bonus;
    } else {
      bet.maximum = false;
    }

    return points;
  }

  driverOnPodium(driver) {
    return this.results.slice(0, 3).map(d => d.code).indexOf(driver.code) != -1;
  }
}

class PointsCalculatorV2 {
  constructor(results) {
    this.results = results;
    this.scoring = [10, 7, 5, 3, 1];
    this.bonus = 5;
  }

  process(bet) {
    return new Promise((resolve, reject) => {
      bet.points = this.scores(bet);
      resolve(bet);
    });
  }

  scores(bet) {
    let total_points = 0;
    let total_delta = 0;

    for (let i = 0; i < bet.bets.length; i++) {
      let driver = bet.bets[i];
      let delta = this.delta(bet.bets, i);

      if (delta == 0) {
        driver.points = this.scoring[i];
      } else if (delta <= 2) {
        driver.points = this.scoring[2 + delta];
      } else {
        driver.points = 0;
      }

      total_delta += delta;
      total_points += driver.points;
    }

    if (total_delta == 0) {
      total_points += this.bonus;
      bet.maximum = true;
    } else {
      bet.maximum = false;
    }

    return total_points;
  }

  delta(bets, i) {
    let pos = this.results.findIndex(driver => {
      return driver.code == bets[i].code;
    });

    return pos == -1 ? 99 : Math.abs(i - pos);
  }
}

exports.configure = services => {
  let database = services.get("database");
  let whois = services.get("whois");
  let commands = services.get("command.manager");
  let events = services.get("event.manager");
  let Bet = database.model("bet", BetSchema);
  let Event = database.model("event");
  let connection = services.get("connection");
  let notifier = services.get("racecalendar.notifications");

  notifier.on("qualifying", event => {
    Bet.find({season: event.season, round: event.round}).then(bets => {
      connection.amsg(util.format("Qualifying is starting so bet window is now closed. We got %d bets placed for this round!", bets.length));
    });
  });

  notifier.on("qualifying", -30, () => {
    connection.amsg("Qualifying starts in 30 minutes! Time for placing bets is NOW!");
  });

  notifier.on("qualifying", -120, () => {
    connection.amsg("Only two hours until the start of qualifying! Remember to check your bets for this round!");
  });

  commands.add("bet", command => {
    let names = command.params;
    if (names.length == 0) {
      return Bet.userBets(command.nick, this.activeEvent.round).then(doc => {
        if (doc) {
          let names = doc.bets.map((d, i) => util.format("%d. %s %s", i+1, d.firstName, d.lastName));
          return util.format("%s: %s", command.nick, names.join(" "));
        } else {
          return Promise.resolve(util.format("%s: You have no bets for this round", command.nick));
        }
      });
    } else {
      return whois.auth(command.nick)
        .then(account => Bet.setUserBets(account, this.activeEvent.round, names))
        .then(drivers => {
          let names = drivers.map((d, i) => util.format("%d. %s %s", i+1, d.firstName, d.lastName));
          return util.format("%s: %s [OK]", command.nick, names.join(" "));
        });
    }
  })
  .validate((nick, ...names) => {
    if (names.length) {
      return Bet.isBetWindowOpen()
    }
  })
  .validate((nick, ...names) => {
    if (names.length != 0 && names.length != 3) {
      return Promise.reject("Need three names to bet");
    }
  })
  .validate(() => {
    return Bet.activeBetRound().then(event => {
      console.log("GOT EVENT", event);
      this.activeEvent = event;
    });
  });

  commands.add("top", command => {
    let round = command.params[0];
    if (round) {
      return Bet.pointsForRound(round).then(result => {
        let points = result.bets.map(bet => util.format("%s %d", bet.nick, bet.points));
        let message = util.format("Score for %s: %s", result.name, points.join("; "));
        return {type: "notice", content: message};
      });
    } else {
      return Bet.pointsForSeason().then(result => {
        let points = result.bets.map(bet => util.format("%s %d", bet.nick, bet.points));
        let message = util.format("Points after %s: %s", result.name, points.join("; "));
        return {type: "notice", content: message};
      });
    }
  });

  events.on("racecalendar.results", event => {
    let query = {season: event.event.season, round: event.event.round};
    let results = event.event.results;

    Bet.ensureActiveUsersHaveBets()
      .then(() => Bet.find(query))
      .then(bets => Promise.all(bets.map(bet => (new PointsCalculatorV2(results)).process(bet))))
      .then(bets => Promise.all(bets.map(bet => bet.save())))
      .then(bets => {
        if (bets.length) {
          let winners = bets.sort((a, b) => b.points - a.points)
            .filter((bet, i, temp) => (i == 0 || bet.points == temp[0].points));
          let points = winners[0].points;
          let nicks = winners.map(bet => bet.nick).join(", ");
          connection.amsg(util.format("Dudes! Bet scores are updated for the %s", event.event.name));
          connection.amsg(util.format("The highest score in this round was %d by %s", winners[0].points, nicks));
        }
      })
      .then(() => console.log("Updated scores for round", event.event.round));
  });
};
<commit_msg>Remove debug<commit_after>"use strict";

let moment = require("moment");
let mongoose = require("mongoose");
let util = require("util");

let BetSchema = new mongoose.Schema({
  season: Number,
  round: Number,
  account: String,
  nick: String,
  bets: [{
    _id: false,
    firstName: String,
    lastName: String,
    code: String,
    points: Number,
  }],
  created: {
    type: Date,
    default: Date.now,
  },
  points: Number,
  maximum: Boolean,
  fallback: Boolean,
});

/**
 * Find latest bets for each user that has placed bets during current season.
 */
BetSchema.statics.activeUserBets = function() {
  let season = (new Date).getFullYear();
  let round = null;
  return this.latestRound(season)
    .then(entry => {
      round = entry.round;

      return [
        {$match: {season: season}},
        {$sort: {round: -1}},
        {$group: {
          _id: "$account",
          account: {$first: "$account"},
          nick: {$first: "$nick"},
          bets: {$first: "$bets"},
          round: {$first: "$round"},
          season: {$first: "$season"}
        }}
      ];
    })
    .then(query => new Promise((resolve, reject) => this.aggregate(query, (error, result) => {
      error ? reject(error) : resolve(result);
    })))
    .then(result => ({
      season: season,
      round: round,
      bets: result,
    }));
};

BetSchema.statics.ensureActiveUsersHaveBets = function() {
  return this.activeUserBets().then(result => {
    let max_round = result.round;
    let bets = result.bets.filter(bet => (bet.round < max_round));
    let Bet = this.model("bet");

    return Promise.all(bets.map(bet => {
      let data = {
        fallback: true,
        round: max_round,
        season: bet.season,
        account: bet.account,
        nick: bet.nick,
        bets: bet.bets,
      }
      return (new Bet(data)).save();
    }));
  });
};

/**
 * Current rules are such that bet window is open on the following Monday after the race,
 * until next qualifying session begins.
 */
BetSchema.statics.isBetWindowOpen = function() {
  let storage = this.db.model("event");
  let is_season_first = true;
  return storage.findPrevious("race")
    .then(event => {
      // Assert that last GP weekend has finished.
      if (event) {
        is_season_first = (event.start.getFullYear() != (new Date).getFullYear());
        let min = moment(event.start).add(7, "days").startOf("isoweek");
        if (min.isAfter()) {
          let date = min.format("MMMM D, HH:mm UTC");
          let message = util.format("Bets will be allowed after %s, until qualifying!", date);
          throw new Error(message);
        }
      }
    })
    .then(() => storage.findNext())
    .then(event => {
      // Assert that the quali for the next race has not finished.
      if (event) {
        if (event.type == "race") {
          let min = moment(event.start).add(7, "days").startOf("isoweek");
          let date = min.format("MMMM D, HH:mm UTC");
          let message = util.format("Bets will be allowed after %s, until qualifying!", date);
          throw new Error(message);
        } else if (is_season_first) {
          let min = moment(event.start).subtract(7, "days").startOf("isoweek");
          if (min.isAfter()) {
            let date = min.format("MMMM D, HH:mm UTC");
            let message = util.format("Bets will be allowed after %s, until qualifying!", date);
            throw new Error(message);
          }
        }
      } else {
        throw new Error("Bets are not allowed");
      }
    });
};

BetSchema.statics.activeBetRound = function() {
  return this.db.model("event").findNext().then(event => {
    // if (event && event.type != "race") {
    if (event) {
      return event;
    } else {
      throw new Error("No active round found");
    }
  });
};

BetSchema.statics.userBets = function(nick, round) {
  return this.findOne({nick: nick, round: round, season: (new Date).getFullYear()});
};

BetSchema.statics.setUserBets = function(account, round, names) {
  return this.db.model("season").driversForNames(names).then(drivers => {
    drivers.forEach((driver, i) => {
      if (drivers.indexOf(driver) < i) {
        throw new Error(util.format("Duplicate driver '%s'", driver.code));
      }
    });

    let query = {
      account: account.account,
      season: (new Date).getFullYear(),
      round: round,
    };
    let values = {
      nick: account.nick,
      account: account.account,
      season: query.season,
      round: query.round,
      bets: drivers,
      created: new Date,
    };
    return this
      .update(query, values, {upsert: true})
      .then(status => drivers);
  });
};

BetSchema.statics.latestRound = function(season) {
  let query = {
    results: {$exists: true},
    season: season || (new Date).getFullYear(),
  };

  return this.model("event").findOne(query)
    .sort({season: -1, round: -1})
    .then(event => event || Promise.reject(new Error("No event data")))
    .then(event => ({season: event.season, round: event.round}));
};

BetSchema.statics.pointsForRound = function(round) {
  if (round == "last") {
    round = 0;
  }
  return Promise.resolve(round == 0 ? this.latestRound() : {round: round, season: (new Date).getFullYear()})
    .then(entry => this.find({round: entry.round, season: entry.season, points: {$exists: true}}).sort("-points"))
    .then(bets => {
      if (!bets.length) {
        throw new Error(util.format("No results for round %d", round));
      }

      return this.model("event").findOne({type: "race", season: bets[0].season, round: bets[0].round}).then(event => ({
        season: event.season,
        round: event.round,
        name: event.name,
        bets: bets
      }));
    });
};

BetSchema.statics.pointsForSeason = function(season) {
  return this.latestRound(season)
    .then(entry => this.model("event").findOne({type: "race", season: entry.season, round: entry.round}))
    .then(event => new Promise((resolve, reject) => {
      let query = [
        {$match: {season: event.season, points: {$exists: true}}},
        {$group: {_id: "$account", points: {$sum: "$points"}, nick: {$first: "$nick"}}},
        {$sort: {points: -1}}
      ];
      this.aggregate(query, (error, result) => {
        if (error) {
          throw error;
        }
        resolve({
          season: event.season,
          round: event.round,
          name: event.name,
          bets: result
        });
      })
    }));
}

/**
 * @deprecated
 */
class PointsCalculator {
  constructor(results) {
    this.results = results;
    this.scoring = [10, 5, 3, 1];
    this.bonus = 5;
  }

  process(bet) {
    return new Promise((resolve, reject) => {
      bet.points = this.scores(bet);
      resolve(bet);
    });
  }

  scores(bet) {
    let points = [...bet.bets].reduce((value, driver, i) => {
      if (driver.code == this.results[i].code) {
        driver.points = this.scoring[i];
        return value + driver.points;
      } else if (this.driverOnPodium(driver)) {
        driver.points = this.scoring[3];
        return value + driver.points;
      } else {
        driver.points = 0;
      }
      return value;
    }, 0);

    let max = this.scoring.slice(0, 3).reduce((sum, x) => sum + x, 0);

    if (points == max) {
      bet.maximum = true;
      points += this.bonus;
    } else {
      bet.maximum = false;
    }

    return points;
  }

  driverOnPodium(driver) {
    return this.results.slice(0, 3).map(d => d.code).indexOf(driver.code) != -1;
  }
}

class PointsCalculatorV2 {
  constructor(results) {
    this.results = results;
    this.scoring = [10, 7, 5, 3, 1];
    this.bonus = 5;
  }

  process(bet) {
    return new Promise((resolve, reject) => {
      bet.points = this.scores(bet);
      resolve(bet);
    });
  }

  scores(bet) {
    let total_points = 0;
    let total_delta = 0;

    for (let i = 0; i < bet.bets.length; i++) {
      let driver = bet.bets[i];
      let delta = this.delta(bet.bets, i);

      if (delta == 0) {
        driver.points = this.scoring[i];
      } else if (delta <= 2) {
        driver.points = this.scoring[2 + delta];
      } else {
        driver.points = 0;
      }

      total_delta += delta;
      total_points += driver.points;
    }

    if (total_delta == 0) {
      total_points += this.bonus;
      bet.maximum = true;
    } else {
      bet.maximum = false;
    }

    return total_points;
  }

  delta(bets, i) {
    let pos = this.results.findIndex(driver => {
      return driver.code == bets[i].code;
    });

    return pos == -1 ? 99 : Math.abs(i - pos);
  }
}

exports.configure = services => {
  let database = services.get("database");
  let whois = services.get("whois");
  let commands = services.get("command.manager");
  let events = services.get("event.manager");
  let Bet = database.model("bet", BetSchema);
  let Event = database.model("event");
  let connection = services.get("connection");
  let notifier = services.get("racecalendar.notifications");

  notifier.on("qualifying", event => {
    Bet.find({season: event.season, round: event.round}).then(bets => {
      connection.amsg(util.format("Qualifying is starting so bet window is now closed. We got %d bets placed for this round!", bets.length));
    });
  });

  notifier.on("qualifying", -30, () => {
    connection.amsg("Qualifying starts in 30 minutes! Time for placing bets is NOW!");
  });

  notifier.on("qualifying", -120, () => {
    connection.amsg("Only two hours until the start of qualifying! Remember to check your bets for this round!");
  });

  commands.add("bet", command => {
    let names = command.params;
    if (names.length == 0) {
      return Bet.userBets(command.nick, this.activeEvent.round).then(doc => {
        if (doc) {
          let names = doc.bets.map((d, i) => util.format("%d. %s %s", i+1, d.firstName, d.lastName));
          return util.format("%s: %s", command.nick, names.join(" "));
        } else {
          return Promise.resolve(util.format("%s: You have no bets for this round", command.nick));
        }
      });
    } else {
      return whois.auth(command.nick)
        .then(account => Bet.setUserBets(account, this.activeEvent.round, names))
        .then(drivers => {
          let names = drivers.map((d, i) => util.format("%d. %s %s", i+1, d.firstName, d.lastName));
          return util.format("%s: %s [OK]", command.nick, names.join(" "));
        });
    }
  })
  .validate((nick, ...names) => {
    if (names.length) {
      return Bet.isBetWindowOpen()
    }
  })
  .validate((nick, ...names) => {
    if (names.length != 0 && names.length != 3) {
      return Promise.reject("Need three names to bet");
    }
  })
  .validate(() => {
    return Bet.activeBetRound().then(event => {
      // console.log("GOT EVENT", event);
      this.activeEvent = event;
    });
  });

  commands.add("top", command => {
    let round = command.params[0];
    if (round) {
      return Bet.pointsForRound(round).then(result => {
        let points = result.bets.map(bet => util.format("%s %d", bet.nick, bet.points));
        let message = util.format("Score for %s: %s", result.name, points.join("; "));
        return {type: "notice", content: message};
      });
    } else {
      return Bet.pointsForSeason().then(result => {
        let points = result.bets.map(bet => util.format("%s %d", bet.nick, bet.points));
        let message = util.format("Points after %s: %s", result.name, points.join("; "));
        return {type: "notice", content: message};
      });
    }
  });

  events.on("racecalendar.results", event => {
    let query = {season: event.event.season, round: event.event.round};
    let results = event.event.results;

    Bet.ensureActiveUsersHaveBets()
      .then(() => Bet.find(query))
      .then(bets => Promise.all(bets.map(bet => (new PointsCalculatorV2(results)).process(bet))))
      .then(bets => Promise.all(bets.map(bet => bet.save())))
      .then(bets => {
        if (bets.length) {
          let winners = bets.sort((a, b) => b.points - a.points)
            .filter((bet, i, temp) => (i == 0 || bet.points == temp[0].points));
          let points = winners[0].points;
          let nicks = winners.map(bet => bet.nick).join(", ");
          connection.amsg(util.format("Dudes! Bet scores are updated for the %s", event.event.name));
          connection.amsg(util.format("The highest score in this round was %d by %s", winners[0].points, nicks));
        }
      })
      .then(() => console.log("Updated scores for round", event.event.round));
  });
};
<|endoftext|>