<commit_before>/**
 * Copyright (c) 2014 "Fronteer LTD"
 * Grasshopper Event Engine
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Affero General Public License as
 * published by the Free Software Foundation, either version 3 of the
 * License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Affero General Public License for more details.
 *
 * You should have received a copy of the GNU Affero General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

var _ = require('lodash');
var Sequelize = require('sequelize');

var AppsConstants = require('gh-apps/lib/constants');
var log = require('./logger').logger('gh-core/db');

var init = module.exports.init = function(config, callback) {

    /*!
     * Pipe sequelize log statements to our own logger
     *
     * @param  {String}     msg     The sequelize log statement
     */
    config.db.logging = function(msg) {
        log().debug(msg);
    };

    // Set up a connection to the database
    var sequelize = new Sequelize(config.db.database, config.db.username, config.db.password, config.db);
    sequelize
    .authenticate()
    .done(function(err) {
        if (err) {
            log().error({'err': err}, 'Unable to set up a connection to the database');
            return callback({'code': 500, 'msg': 'Unable to set up a connection to the database'});
        }

        log().debug('Connected to the database');

        // Set up the model
        _setUpModel(sequelize);

        // By setting `force` to `true` we will drop each table and recreate it. This is useful
        // during development/testing when models tend to change. We NEVER do this in production though
        var force = false;
        if (process.env['NODE_ENV'] !== 'production' && config.db.dropOnStartup === true) {
            force = true;
        }

        sequelize
        .sync({'force': force})
        .done(function(err) {
            if (err) {
                log().error({'err': err}, 'Unable to sync the model to the database');
                return callback({'code': 500, 'msg': 'Unable to sync the model to the database'});
            }

            log().debug('Synced model to database');

            return callback();
        });
    });
};

/**
 * Set up the DB model
 *
 * @param  {Sequelize}      sequelize       A sequelize instance that is connected to the database
 * @api private
 */
var _setUpModel = function(sequelize) {

    /**
     * The Tenant model
     *
     * @param  {Number}       id                      The id of the tenant
     * @param  {String}       displayName             The name of the tenant
     */
    var Tenant = module.exports.Tenant = sequelize.define('Tenant', {
        'displayName': {
            'type': Sequelize.STRING,
            'allowNull': false
        }
    });

    /**
     * The App model
     *
     * @param  {Number}       id                      The id of the app
     * @param  {String}       displayName             The name of the app (e.g. `Timetable 2013-14`)
     * @param  {Boolean}      enabled                 Whether the app is currently enabled
     * @param  {String}       host                    The host on which the app can be found
     * @param  {String}       type                    The type of the app
     * @param  {Tenant}       tenant                  The tenant associated to the app
     */
    var App = module.exports.App = sequelize.define('App', {
        'displayName': {
            'type': Sequelize.STRING,
            'allowNull': false
        },
        'host': {
            'type': Sequelize.STRING,
            'allowNull': false,
            'unique': true
        },
        'type': {
            'type': Sequelize.ENUM(_.values(AppsConstants.types)),
            'allowNull': false
        },
        'enabled': {
            'type': Sequelize.BOOLEAN,
            'defaultValue': true
        },
        'TenantId': {
            'type': Sequelize.INTEGER,
            'allowNull': false,
            'references': Tenant,
            'referencesKey': 'id'
        }
    });

    Tenant.hasMany(App, {'foreignKey': {
        allowNull: false
    }});
    App.belongsTo(Tenant);

    /**
     * The global administrator model
     *
     * @param  {String}       displayName             The name of the global administrator
     * @param  {String}       password                The password for the global administrator
     * @param  {String}       username                The username for the global administrator
     */
    var GlobalAdmin = module.exports.GlobalAdmin = sequelize.define('GlobalAdmin', {
        'displayName': {
            'type': Sequelize.STRING,
            'allowNull': false
        },
        'password': {
            'type': Sequelize.STRING,
            'allowNull': false
        },
        'username': {
            'type': Sequelize.STRING,
            'allowNull': false,
            'unique': true
        }
    }, {
        'instanceMethods': {
            'canAdmin': function(appId) {
                // Global administrators can administer any application
                return true;
            },
            'isGlobalAdmin': function() {
                return true;
            },
            'toJSON': function() {
                // Never return the password hash
                delete this.dataValues.password;
                return this.dataValues;
            }
        }
    });

    /**
     * The user model
     *
     * @param  {String}       authenticationStrategy  The authentication strategy for the user
     * @param  {String}       displayName             The name of the user
     * @param  {String}       email                   The email address for the user
     * @param  {String}       emailPreference         The email preference for the user
     * @param  {String}       isAdmin                 Whether the user is an app administrator
     * @param  {String}       password                The password with which the user will authenticate
     * @param  {String}       shibbolethId            The unique shibboleth id for the user
     * @param  {Date}         termsAndConditions      The timestamp (ISO 8601) at which the user accepted the Terms and Conditions
     */
    var User = module.exports.User = sequelize.define('User', {
        'authenticationStrategy': {
            'type': Sequelize.ENUM('local', 'shibboleth'),
            'allowNull': false
        },
        'displayName': {
            'type': Sequelize.STRING,
            'allowNull': false
        },
        'email': {
            'type': Sequelize.STRING,
            'allowNull': true,
            'unique': true
        },
        'emailPreference': {
            'type': Sequelize.ENUM('immediate', 'no'),
            'allowNull': false,
            'defaultValue': 'immediate'
        },
        'isAdmin': {
            'type': Sequelize.BOOLEAN,
            'defaultValue': false
        },
        'password': {
            'type': Sequelize.STRING,
            'allowNull': true
        },
        'shibbolethId': {
            'type': Sequelize.STRING,
            'allowNull': true
        },
        'termsAndConditions': Sequelize.DATE,
        'AppId': {
            'type': Sequelize.INTEGER,
            'allowNull': false,
            'references': App,
            'referencesKey': 'id'
        }
    }, {
        'instanceMethods': {
            'canAdmin': function(appId) {
                return (this.AppId === appId && this.isAdmin);
            },
            'isAppAdmin': function() {
                return this.isAdmin;
            },
            'isGlobalAdmin': function() {
                return false;
            },
            'toJSON': function() {
                // Never return the password hash
                delete this.dataValues.password;

                // Don't return unnecessary data
                delete this.dataValues.Event_Organisers;
                delete this.dataValues.Group_Members;

                return this.dataValues;
            }
        }
    });

    App.hasMany(User, {'foreignKey': {
        allowNull: false
    }});
    User.belongsTo(App);

    /**
     * The group model
     *
     * @property {Date}         lockedOn            When the group was locked
     */
    var Group = module.exports.Group = sequelize.define('Group', {
        'lockedOn': Sequelize.DATE,
        'AppId': {
            'type': Sequelize.INTEGER,
            'allowNull': false,
            'references': App,
            'referencesKey': 'id'
        },
    }, {
        'instanceMethods': {
            'toJSON': function() {
                // Don't return unnecessary data
                delete this.dataValues.Group_Members;

                return this.dataValues;
            }
        }
    });
    Group.belongsTo(App);
    App.hasMany(Group);

    Group.belongsTo(User, {'as': 'LockedBy'});

    Group.hasMany(User, {'as': 'Members', 'through': 'Group_Members'});
    User.hasMany(Group, {'as': 'Memberships', 'through': 'Group_Members'});

    /**
     * The organization unit (orgunit) model
     *
     * @property {Number}       id                  The unique identifier of the organizational unit
     * @property {String}       displayName         The display name of the organizational unit
     * @property {String}       type                A label that identifies what this organization unit represents within the tree of organizational units
     * @property {String}       [sourceId]          The identifier of the serie in an external system
     * @property {OrgUnit}      [parent]            The parent organization unit for this unit. If left `null` then this node will be a root node
     * @property {Serie[]}      [series]            The series that are associated with this organizational unit
     * @property {Event[]}      [events]            The events that are directly associated with this organizational unit
     * @property {App}          app                 The application with which the organizational unit is associated
     * @property {Group}        group               The group who can edit this organizational unit and the units below it
     */
    var OrgUnit = module.exports.OrgUnit = sequelize.define('OrgUnit', {
        'displayName': {
            'type': Sequelize.STRING,
            'allowNull': false
        },
        'type': {
            'type': Sequelize.STRING(16),
            'allowNull': false
        },
        'description': Sequelize.STRING,
        'sourceId': Sequelize.STRING,
        'AppId': {
            'type': Sequelize.INTEGER,
            'allowNull': false,
            'references': App,
            'referencesKey': 'id'
        },
        'GroupId': {
            'type': Sequelize.INTEGER,
            'allowNull': false,
            'references': Group,
            'referencesKey': 'id'
        }
    });
    OrgUnit.belongsTo(App);
    App.hasMany(OrgUnit);

    OrgUnit.belongsTo(Group);
    Group.hasMany(OrgUnit);

    OrgUnit.hasOne(OrgUnit, { 'as': 'Parent', 'foreignKey': 'parentId' });

    /**
     * The serie model
     *
     * @property {Number}       id                  The unique identifier of the serie
     * @property {String}       displayName         The display name of the serie
     * @property {String}       description         The description of the serie
     * @property {String}       image               The path to an image for the serie
     */
    var Serie = module.exports.Serie = sequelize.define('Serie', {
        'displayName': Sequelize.STRING,
        'description': Sequelize.TEXT,
        'image': Sequelize.STRING,
        'GroupId': {
            'type': Sequelize.INTEGER,
            'allowNull': false,
            'references': Group,
            'referencesKey': 'id'
        }
    });
    Serie.belongsTo(App);
    App.hasMany(Serie);

    Serie.belongsTo(Group);
    Group.hasMany(Serie);

    OrgUnit.hasMany(Serie);
    Serie.hasMany(OrgUnit);

    /**
     * The event model
     *
     * @property {String}       description         The description of the event
     * @property {String}       displayName         The name of the event
     * @property {Date}         end                 The timestamp at which the event ends
     * @property {String}       image               The path to an image for the event
     * @property {String}       location            The location of the event
     * @property {String}       notes               The notes for the event
     * @property {Date}         start               The timestamp at which the event starts
     * @property {String}       [sourceId]          The identifier of the event in an external system
     * @property {String}       [organiserOther]    The name(s) of the unrecognised user(s) that organise the event. If no organisers are added, the current user will be added as the organiser
     */
    var Event = module.exports.Event = sequelize.define('Event', {
        'description': Sequelize.TEXT,
        'displayName': {
            'type': Sequelize.STRING,
            'allowNull': false
        },
        'end': Sequelize.DATE,
        'GroupId': {
            'type': Sequelize.INTEGER,
            'allowNull': false,
            'references': Group,
            'referencesKey': 'id'
        },
        'image': Sequelize.STRING,
        'location': Sequelize.STRING,
        'notes': Sequelize.TEXT,
        'start': Sequelize.DATE,
        'organiserOther': {
            'type': Sequelize.TEXT,
            'set': function(val) {
                if (val && _.isArray(val)) {
                    this.setDataValue('organiserOther', val.join(','));
                } else if (val) {
                    log().warn({'val': val}, 'Trying to set the organiserOther field with a value that is not an array');
                    this.setDataValue('organiserOther', '');
                }
            },
            'get': function() {
                if (this.getDataValue('organiserOther')) {
                    return this.getDataValue('organiserOther').split(',');
                } else {
                    return [];
                }
            }
        }
    }, {
        'instanceMethods': {
            'toJSON': function() {
                // Produce a pretty list of organisers
                var others = this.organiserOther || [];
                var users = this.Organisers || [];
                this.dataValues.organisers = others.concat(users);

                // Don't return the separate organiser lists
                delete this.dataValues.organiserOther;
                delete this.dataValues.Organisers;

                // Don't return unnecesary data
                delete this.dataValues.EventsSeries;

                // Return all the other values
                return this.dataValues;
            }
        }
    });
    Event.belongsTo(App);
    App.hasMany(Event);

    Event.belongsTo(Group);
    Group.hasMany(Event);

    Event.hasMany(User, {'as': 'Organisers', 'through': 'Event_Organisers'});
    User.hasMany(Event, {'as': 'OrganisedEvents', 'through': 'Event_Organisers'});

    Serie.hasMany(Event);
    Event.hasMany(Serie);

    /**
     * The calendar model
     *
     * @property {String}       id                  The unique identifier of the calendar
     * @property {User}         user                The user who owns the calendar
     * @property {Serie[]}      series              The series in this calendar
     * @property {Event[]}      events              The events in this calendar
     * @property {App}          app                 The application with which the calendar is associated
     *
    var Calendar = module.exports.Calendar = sequelize.define('Calendar', {});

    Calendar.belongsTo(User);
    User.hasOne(Calendar);

    Calendar.belongsTo(App);
    App.hasMany(Calendar);

    Calendar.hasMany(Serie);
    Serie.hasMany(Calendar);

    Calendar.hasMany(Event);
    Event.hasMany(Calendar); */
};
<commit_msg>Added foreign key columns to each DB model and cleaned up jsdoc a bit<commit_after>/**
 * Copyright (c) 2014 "Fronteer LTD"
 * Grasshopper Event Engine
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Affero General Public License as
 * published by the Free Software Foundation, either version 3 of the
 * License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Affero General Public License for more details.
 *
 * You should have received a copy of the GNU Affero General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

var _ = require('lodash');
var Sequelize = require('sequelize');

var AppsConstants = require('gh-apps/lib/constants');
var log = require('./logger').logger('gh-core/db');

var init = module.exports.init = function(config, callback) {

    /*!
     * Pipe sequelize log statements to our own logger
     *
     * @param  {String}     msg     The sequelize log statement
     */
    config.db.logging = function(msg) {
        log().debug(msg);
    };

    // Set up a connection to the database
    var sequelize = new Sequelize(config.db.database, config.db.username, config.db.password, config.db);
    sequelize
    .authenticate()
    .done(function(err) {
        if (err) {
            log().error({'err': err}, 'Unable to set up a connection to the database');
            return callback({'code': 500, 'msg': 'Unable to set up a connection to the database'});
        }

        log().debug('Connected to the database');

        // Set up the model
        _setUpModel(sequelize);

        // By setting `force` to `true` we will drop each table and recreate it. This is useful
        // during development/testing when models tend to change. We NEVER do this in production though
        var force = false;
        if (process.env['NODE_ENV'] !== 'production' && config.db.dropOnStartup === true) {
            force = true;
        }

        sequelize
        .sync({'force': force})
        .done(function(err) {
            if (err) {
                log().error({'err': err}, 'Unable to sync the model to the database');
                return callback({'code': 500, 'msg': 'Unable to sync the model to the database'});
            }

            log().debug('Synced model to database');

            return callback();
        });
    });
};

/**
 * Set up the DB model
 *
 * @param  {Sequelize}      sequelize       A sequelize instance that is connected to the database
 * @api private
 */
var _setUpModel = function(sequelize) {

    /**
     * The Tenant model
     *
     * @property  {Number}      id                      The id of the tenant
     * @property  {String}      displayName             The name of the tenant
     */
    var Tenant = module.exports.Tenant = sequelize.define('Tenant', {
        'displayName': {
            'type': Sequelize.STRING,
            'allowNull': false
        }
    });

    /**
     * The Application model
     *
     * @property  {Number}      id                      The id of the application
     * @property  {String}      displayName             The name of the application (e.g. `Timetable 2013-14`)
     * @property  {Boolean}     [enabled]               Whether the application is currently enabled, defaults to `true`
     * @property  {String}      host                    The host on which the application can be found
     * @property  {String}      type                    The type of the application
     */
    var App = module.exports.App = sequelize.define('App', {
        'displayName': {
            'type': Sequelize.STRING,
            'allowNull': false
        },
        'host': {
            'type': Sequelize.STRING,
            'allowNull': false,
            'unique': true
        },
        'type': {
            'type': Sequelize.ENUM(_.values(AppsConstants.types)),
            'allowNull': false
        },
        'enabled': {
            'type': Sequelize.BOOLEAN,
            'defaultValue': true
        },
        'TenantId': {
            'type': Sequelize.INTEGER,
            'allowNull': false,
            'references': Tenant,
            'referencesKey': 'id'
        }
    });

    Tenant.hasMany(App);
    App.belongsTo(Tenant);

    /**
     * The global administrator model
     *
     * @property  {String}      displayName             The name of the global administrator
     * @property  {String}      password                The password for the global administrator
     * @property  {String}      username                The username for the global administrator
     */
    var GlobalAdmin = module.exports.GlobalAdmin = sequelize.define('GlobalAdmin', {
        'displayName': {
            'type': Sequelize.STRING,
            'allowNull': false
        },
        'password': {
            'type': Sequelize.STRING,
            'allowNull': false
        },
        'username': {
            'type': Sequelize.STRING,
            'allowNull': false,
            'unique': true
        }
    }, {
        'instanceMethods': {
            'canAdmin': function(appId) {
                // Global administrators can administer any application
                return true;
            },
            'isGlobalAdmin': function() {
                return true;
            },
            'toJSON': function() {
                // Never return the password hash
                delete this.dataValues.password;
                return this.dataValues;
            }
        }
    });

    /**
     * The user model
     *
     * @property  {String}      authenticationStrategy  The authentication strategy for the user
     * @property  {String}      displayName             The name of the user
     * @property  {String}      email                   The email address for the user
     * @property  {String}      emailPreference         The email preference for the user
     * @property  {String}      isAdmin                 Whether the user is an app administrator
     * @property  {String}      password                The password with which the user will authenticate
     * @property  {String}      shibbolethId            The unique shibboleth id for the user
     * @property  {Date}        [termsAndConditions]    The timestamp at which the user accepted the Terms and Conditions
     */
    var User = module.exports.User = sequelize.define('User', {
        'authenticationStrategy': {
            'type': Sequelize.ENUM('local', 'shibboleth'),
            'allowNull': false
        },
        'displayName': {
            'type': Sequelize.STRING,
            'allowNull': false
        },
        'email': {
            'type': Sequelize.STRING,
            'allowNull': true,
            'unique': true
        },
        'emailPreference': {
            'type': Sequelize.ENUM('immediate', 'no'),
            'allowNull': false,
            'defaultValue': 'immediate'
        },
        'isAdmin': {
            'type': Sequelize.BOOLEAN,
            'defaultValue': false
        },
        'password': {
            'type': Sequelize.STRING,
            'allowNull': true
        },
        'shibbolethId': {
            'type': Sequelize.STRING,
            'allowNull': true
        },
        'termsAndConditions': Sequelize.DATE,
        'AppId': {
            'type': Sequelize.INTEGER,
            'allowNull': false,
            'references': App,
            'referencesKey': 'id'
        }
    }, {
        'instanceMethods': {
            'canAdmin': function(appId) {
                return (this.AppId === appId && this.isAdmin);
            },
            'isAppAdmin': function() {
                return this.isAdmin;
            },
            'isGlobalAdmin': function() {
                return false;
            },
            'toJSON': function() {
                // Never return the password hash
                delete this.dataValues.password;

                // Don't return unnecessary data
                delete this.dataValues.Event_Organisers;
                delete this.dataValues.Group_Members;

                return this.dataValues;
            }
        }
    });

    App.hasMany(User);
    User.belongsTo(App);

    /**
     * The group model
     *
     * @property  {Date}        lockedOn            When the group was locked
     */
    var Group = module.exports.Group = sequelize.define('Group', {
        'lockedOn': Sequelize.DATE,
        'AppId': {
            'type': Sequelize.INTEGER,
            'allowNull': false,
            'references': App,
            'referencesKey': 'id'
        },
        'LockedById': {
            'type': Sequelize.INTEGER,
            'allowNull': true,
            'references': User,
            'referencesKey': 'id'
        }
    }, {
        'instanceMethods': {
            'toJSON': function() {
                // Don't return unnecessary data
                delete this.dataValues.Group_Members;

                return this.dataValues;
            }
        }
    });
    Group.belongsTo(App);
    App.hasMany(Group);

    Group.belongsTo(User, {'as': 'LockedBy'});

    Group.hasMany(User, {'as': 'Members', 'through': 'Group_Members'});
    User.hasMany(Group, {'as': 'Memberships', 'through': 'Group_Members'});

    /**
     * The organization unit (orgunit) model
     *
     * @property  {Number}      id                  The unique identifier of the organizational unit
     * @property  {String}      displayName         The display name of the organizational unit
     * @property  {Group}       group               The group who can edit this organizational unit and the units below it
     * @property  {String}      [sourceId]          The identifier of the serie in an external system
     * @property  {String}      type                A label that identifies what this organization unit represents within the tree of organizational units
     */
    var OrgUnit = module.exports.OrgUnit = sequelize.define('OrgUnit', {
        'displayName': {
            'type': Sequelize.STRING,
            'allowNull': false
        },
        'type': {
            'type': Sequelize.STRING(16),
            'allowNull': false
        },
        'description': Sequelize.STRING,
        'sourceId': Sequelize.STRING,
        'AppId': {
            'type': Sequelize.INTEGER,
            'allowNull': false,
            'references': App,
            'referencesKey': 'id'
        },
        'GroupId': {
            'type': Sequelize.INTEGER,
            'allowNull': false,
            'references': Group,
            'referencesKey': 'id'
        },
        'ParentId': {
            'type': Sequelize.INTEGER,
            'allowNull': true,
            'references': OrgUnit,
            'referencesKey': 'id'
        }
    });
    OrgUnit.belongsTo(App);
    App.hasMany(OrgUnit);

    OrgUnit.belongsTo(Group);
    Group.hasMany(OrgUnit);

    OrgUnit.hasOne(OrgUnit, { 'as': 'Parent', 'foreignKey': 'parentId' });

    /**
     * The serie model
     *
     * @property  {Number}      id                  The unique identifier of the serie
     * @property  {String}      displayName         The display name of the serie
     * @property  {String}      description         The description of the serie
     * @property  {String}      image               The path to an image for the serie
     */
    var Serie = module.exports.Serie = sequelize.define('Serie', {
        'displayName': Sequelize.STRING,
        'description': Sequelize.TEXT,
        'image': Sequelize.STRING,
        'AppId': {
            'type': Sequelize.INTEGER,
            'allowNull': false,
            'references': App,
            'referencesKey': 'id'
        },
        'GroupId': {
            'type': Sequelize.INTEGER,
            'allowNull': false,
            'references': Group,
            'referencesKey': 'id'
        }
    });
    Serie.belongsTo(App);
    App.hasMany(Serie);

    Serie.belongsTo(Group);
    Group.hasMany(Serie);

    OrgUnit.hasMany(Serie);
    Serie.hasMany(OrgUnit);

    /**
     * The event model
     *
     * @property  {String}      description         The description of the event
     * @property  {String}      displayName         The name of the event
     * @property  {Date}        end                 The timestamp at which the event ends
     * @property  {String}      image               The path to an image for the event
     * @property  {String}      location            The location of the event
     * @property  {String}      notes               The notes for the event
     * @property  {Date}        start               The timestamp at which the event starts
     * @property  {String}      [sourceId]          The identifier of the event in an external system
     * @property  {String}      [organiserOther]    The name(s) of the unrecognised user(s) that organise the event. If no organisers are added, the current user will be added as the organiser
     */
    var Event = module.exports.Event = sequelize.define('Event', {
        'description': Sequelize.TEXT,
        'displayName': {
            'type': Sequelize.STRING,
            'allowNull': false
        },
        'end': Sequelize.DATE,
        'image': Sequelize.STRING,
        'location': Sequelize.STRING,
        'notes': Sequelize.TEXT,
        'start': Sequelize.DATE,
        'organiserOther': {
            'type': Sequelize.TEXT,
            'set': function(val) {
                if (val && _.isArray(val)) {
                    this.setDataValue('organiserOther', val.join(','));
                } else if (val) {
                    log().warn({'val': val}, 'Trying to set the organiserOther field with a value that is not an array');
                    this.setDataValue('organiserOther', '');
                }
            },
            'get': function() {
                if (this.getDataValue('organiserOther')) {
                    return this.getDataValue('organiserOther').split(',');
                } else {
                    return [];
                }
            }
        },
        'AppId': {
            'type': Sequelize.INTEGER,
            'allowNull': false,
            'references': App,
            'referencesKey': 'id'
        },
        'GroupId': {
            'type': Sequelize.INTEGER,
            'allowNull': false,
            'references': Group,
            'referencesKey': 'id'
        }
    }, {
        'instanceMethods': {
            'toJSON': function() {
                // Produce a pretty list of organisers
                var others = this.organiserOther || [];
                var users = this.Organisers || [];
                this.dataValues.organisers = others.concat(users);

                // Don't return the separate organiser lists
                delete this.dataValues.organiserOther;
                delete this.dataValues.Organisers;

                // Don't return unnecesary data
                delete this.dataValues.EventsSeries;

                // Return all the other values
                return this.dataValues;
            }
        }
    });
    Event.belongsTo(App);
    App.hasMany(Event);

    Event.belongsTo(Group);
    Group.hasMany(Event);

    Event.hasMany(User, {'as': 'Organisers', 'through': 'Event_Organisers'});
    User.hasMany(Event, {'as': 'OrganisedEvents', 'through': 'Event_Organisers'});

    Serie.hasMany(Event);
    Event.hasMany(Serie);

    /**
     * The calendar model
     *
     * @property {String}       id                  The unique identifier of the calendar
     * @property {User}         user                The user who owns the calendar
     * @property {Serie[]}      series              The series in this calendar
     * @property {Event[]}      events              The events in this calendar
     * @property {App}          app                 The application with which the calendar is associated
     *
    var Calendar = module.exports.Calendar = sequelize.define('Calendar', {});

    Calendar.belongsTo(User);
    User.hasOne(Calendar);

    Calendar.belongsTo(App);
    App.hasMany(Calendar);

    Calendar.hasMany(Serie);
    Serie.hasMany(Calendar);

    Calendar.hasMany(Event);
    Event.hasMany(Calendar); */
};
<|endoftext|>


<commit_before>'use strict';

const unirest = require('unirest');
const RoomModel = require('../../models/rooms');

const SlackClient = require('slack-client');
const RtmClient = require('slack-client').RtmClient;

module.exports = (function() {

    const Slack = {};

    const postToSlack = function(options) {

    }

    var getAvailableRooms = function() {

        RoomModel.find({'status.name': 'Available'}).
            limit(5).
            select('mame location').
            exec(function (error, data) {
                if (error) {
                    console.log(error);
                } else {
                    return data;
                }
            });
    }

    Slack.process = function(options) {

        var rooms = getAvailableRooms();

        return rooms;
    }

    return Slack;

})();
<commit_msg>working on slack integration<commit_after>'use strict';

const unirest = require('unirest');
const RoomModel = require('../../models/rooms');

const SlackClient = require('slack-client');
const RtmClient = require('slack-client').RtmClient;

module.exports = (function() {

    const Slack = {};

    const postToSlack = function(options) {

    }

    var getAvailableRooms = function() {

        RoomModel.find({'status.name': 'Available'}).
            limit(5).
            select('mame location').
            exec(function (error, data) {
                if (error) {
                    console.log(error);
                } else {
                    return data;
                }
            });
    }

    Slack.process = function(options) {

        var rooms = getAvailableRooms();

        console.log(rooms);
        return rooms;
    }

    return Slack;

})();
<|endoftext|>


<commit_before>const crypto = require('crypto');

module.exports = {
    encrypt(password, salt) {
        return crypto.createHmac('sha1', salt).
        update(password).
        digest('hex');
    },
    check(password, salt, hash) {
        return this.encrypt(password, salt) === hash;
    },
    generateSalt() {
        return Math.floor(1000 + Math.random() * 9000);
    }
};<commit_msg>Changed salt generation<commit_after>const crypto = require('crypto');

module.exports = {
    encrypt(password, salt) {
        return crypto.createHmac('sha1', salt).
        update(password).
        digest('hex');
    },
    check(password, salt, hash) {
        return this.encrypt(password, salt) === hash;
    },
    generateSalt() {
        return crypto.randomBytes(8).toString('hex');
    }
};<|endoftext|>


<commit_before>import DS from 'ember-data';

export default DS.Model.extend({
  vendor: DS.belongsTo('vendor'),
  location: DS.attr('string'),
  manager: DS.belongsTo('account'),
  time: DS.attr('string'),
  portions: DS.hasMany('portion'),
  active: DS.attr('boolean')
});
<commit_msg>Ensure the order is active by default on client<commit_after>import DS from 'ember-data';

export default DS.Model.extend({
  vendor: DS.belongsTo('vendor'),
  location: DS.attr('string'),
  manager: DS.belongsTo('account'),
  time: DS.attr('string'),
  portions: DS.hasMany('portion'),
  active: DS.attr('boolean', { defaultValue: true })
});
<|endoftext|>


<commit_before>/* eslint-disable no-unused-vars */
/* global angular, mediaTypeParser, linkHeaderParser */
/* eslint-enable no-unused-vars */

require('angular')
require('angular-hypermedia')
// BLOODY HACK: we need to get some variables into the global space for angular-hypermedia to work in the browsers (not require/browserify-ready)
// // Refer to https://github.com/jcassee/angular-hypermedia/issues/24 for explanation
require('../vendor/mediatype-parser-browser')
require('../vendor/linkheader-parser-browser')

var compPoolStats = require('./comp-pool-stats').init()

angular
  .module('CompPoolClient', ['hypermedia'])
  .factory('compPoolClient', ['compPoolRoot', 'ResourceContext', 'HalResource', '$log', '$q', compPoolClient])

function compPoolClient (compPoolRoot, ResourceContext, HalResource, $log, $q) {
  $log.debug('Creating compPoolClient for comp-pool at %s', compPoolRoot)
  var resource = new ResourceContext(HalResource).get(compPoolRoot)
  return $q.when(Object.assign(new ApiRoot($q.when(resource)), {
    getStats: compPoolStats.getCurrentStats
  }))
}

function ApiRoot (resourcePromise) {
  this.getJobsRoot = function () {
    var rootPromise = resourcePromise.then(function (resource) {
      return resource.$get()
    })
    return Object.assign(rootPromise, new JobsRoot(rootPromise))
  }
}

function JobsRoot (apiRootPromise) {
  this.getJob = function (jobId) {
    return Object.assign(apiRootPromise, new Job(apiRootPromise.$linkRel(jobId).$get()))
  }

  this.getRandomJob = function () {
    var getJob = this.getJob

    apiRootPromise.then(function (apiRoot) {
      var relsArray = Object.keys(apiRoot.$links)
      var randomLink = relsArray[Math.floor(Math.random() * relsArray.length)]
      return getJob(randomLink)
    })
  }
}

var Job = function (apiJobPromise) {}

module.exports.client = compPoolClient
<commit_msg>Removed client stats tracking for refactoring<commit_after>/* eslint-disable no-unused-vars */
/* global angular, mediaTypeParser, linkHeaderParser */
/* eslint-enable no-unused-vars */

require('angular')
require('angular-hypermedia')
// BLOODY HACK: we need to get some variables into the global space for angular-hypermedia to work in the browsers (not require/browserify-ready)
// // Refer to https://github.com/jcassee/angular-hypermedia/issues/24 for explanation
require('../vendor/mediatype-parser-browser')
require('../vendor/linkheader-parser-browser')

angular
  .module('CompPoolClient', ['hypermedia'])
  .factory('compPoolClient', ['compPoolRoot', 'ResourceContext', 'HalResource', '$log', '$q', compPoolClient])

function compPoolClient (compPoolRoot, ResourceContext, HalResource, $log, $q) {
  $log.debug('Creating compPoolClient for comp-pool at %s', compPoolRoot)
  var resource = new ResourceContext(HalResource).get(compPoolRoot)
  return $q.when(Object.assign(new ApiRoot($q.when(resource))))
}

function ApiRoot (resourcePromise) {
  this.getJobsRoot = function () {
    var rootPromise = resourcePromise.then(function (resource) {
      return resource.$get()
    })
    return Object.assign(rootPromise, new JobsRoot(rootPromise))
  }
}

function JobsRoot (apiRootPromise) {
  this.getJob = function (jobId) {
    return Object.assign(apiRootPromise, new Job(apiRootPromise.$linkRel(jobId).$get()))
  }

  this.getRandomJob = function () {
    var getJob = this.getJob

    apiRootPromise.then(function (apiRoot) {
      var relsArray = Object.keys(apiRoot.$links)
      var randomLink = relsArray[Math.floor(Math.random() * relsArray.length)]
      return getJob(randomLink)
    })
  }
}

var Job = function (apiJobPromise) {}

module.exports.client = compPoolClient
<|endoftext|>


<commit_before>import moment from 'moment';
import { replies } from '../replies';

const CLICKBUS_URL = process.env.CLICKBUS_URL;

const tripListSizeThreshold = 10;

const buildFacebookElements = (origin, destination, session, searchUrl, trips) => {
    console.log('buildFacebookElements');
    const result = trips.map(trip => {
        const {
            price,
            arrivalTime,
            arrivalPlace,
            departurePlace,
            departureTime,
            busCompanyName,
            // busCompanyLogo,
            availableSeats,
            duration,
            scheduleId
        } = trip;
        const departure = {
            time: moment(departureTime).format('DD/MM HH:mm'),
            name: departurePlace
        };
        const arrival = {
            time: moment(arrivalTime).format('DD/MM HH:mm'),
            name: arrivalPlace
        };
        const company = busCompanyName;
        const seats = availableSeats;
        const formattedPrice = `R$${price.slice(0, -2)},${price.slice(-2)}`;
        const title = replies.trip.listTitle(
            company, departureTime, availableSeats, duration, formattedPrice
        );
        const subtitle = replies.trip.listItemFb(company, departure, arrival, seats, duration, true);
        const baseURL = `${process.env.BOT_URL}${process.env.POST_TO_CLICKBUS_HACK_PATH}`;
        const decodedScheduleTrip = new Buffer(scheduleId, 'base64').toString('utf8');
        const trips0 = decodedScheduleTrip.split('--').map((item, index) => {
            if ([2, 4, 8, 9].includes(index)) {
                return `"${item}"`;
            }
            if (index === 7) {
                return item === '1';
            }
            return parseInt(item, 10);
        }).toString();
        const postBody = {
            originSlug: origin,
            destinationSlug: destination,
            trips0,
            store: process.env.CLICKBUS_STORE,
            platform: process.env.CLICKBUS_PLATFORM
        };
        const postURL = process.env.CLICKBUS_WEB_POST_URL;
        const failUrl = searchUrl;
        const url = `${baseURL}?body=${JSON.stringify(postBody)}&url=${postURL}&session=${session}&failUrl=${failUrl}`;
        return {
            title,
            // image_url: busCompanyLogo,
            buttons: [{
                type: 'web_url',
                url,
                title: replies.trip.detail()
            }],
            subtitle
        };
    });
    return result.slice(0, 10);
};

const tripDialogReply = context => {
    const {
        origin,
        originMeta,
        destination,
        destinationMeta,
        timeFilter,
        apiError,
        trips,
        shortUrl,
        sessionCookie
    } = context;
    const hasOrigin = origin !== undefined;
    const hasDestination = destination !== undefined;
    const hasApiError = apiError !== undefined;
    const hasTrips = trips !== undefined;
    const hasNoTrips = hasTrips && !trips.length;
    if (hasApiError) {
        return replies.trip.apiError(apiError);
    }
    if (!hasOrigin && !hasDestination) {
        return replies.trip.noPlaces();
    }
    if (hasOrigin && !originMeta) {
        return replies.trip.noSlug(origin);
    }
    if (hasDestination && !destinationMeta) {
        return replies.trip.noSlug(destination);
    }
    if (hasOrigin && !hasDestination) {
        return replies.trip.noDestination();
    }
    if (hasDestination && !hasOrigin) {
        return replies.trip.noOrigin();
    }
    const day = timeFilter && timeFilter.from
        ? moment(timeFilter.from.value) : moment();
    const departureDay = day.format('YYYY-MM-DD');
    if (hasDestination && hasOrigin && !hasTrips) {
        const from = originMeta.slugs[0];
        const to = destinationMeta.slugs[0];
        const url = `${CLICKBUS_URL}/trips?from=${from}&to=${to}&departure=${departureDay}`;
        return {
            url,
            departureDay
        };
    }
    if (!originMeta || !destinationMeta) {
        return replies.trip.noTrips(origin, destination);
    }

    if (hasDestination && hasOrigin && hasTrips && hasNoTrips) {
        return replies.trip.noTripsWithUrl(origin, destination, shortUrl);
    }
    if (hasDestination && hasOrigin && hasTrips && timeFilter &&
                timeFilter.from && timeFilter.from.grain !== 'day') {
        const filteredTripsAfter = trips.filter(trip =>
            trip.departureTime.isAfter(timeFilter.from.value)
        );

        if (timeFilter.to === null) {
            const tripListAfter = filteredTripsAfter.map(trip => {
                const departure = {
                    time: moment(trip.departureTime).format('DD/MM HH:mm'),
                    name: trip.departurePlace
                };
                const arrival = {
                    time: moment(trip.arrivalTime).format('DD/MM HH:mm'),
                    name: trip.arrivalPlace
                };
                const company = trip.busCompanyName;
                const seats = trip.availableSeats;

                return replies.trip.listItemTg(company, departure, arrival, seats);
            });
            const structuredRely = buildFacebookElements(
                originMeta.slugs[0],
                destinationMeta.slugs[0],
                sessionCookie,
                shortUrl,
                filteredTripsAfter
            );
            return {
                textReply: replies.trip.filteredDepartureListAfter(
                    origin,
                    destination,
                    moment(timeFilter.from.value),
                    filteredTripsAfter.length,
                    shortUrl,
                    (filteredTripsAfter.length < tripListSizeThreshold)
                        ? tripListAfter.join('\n')
                        : null
                ),
                structuredRely
            };
        }
        const filteredTripsBetween = filteredTripsAfter.filter(trip =>
            trip.departureTime.isBefore(timeFilter.to.value)
        );
        const tripListBetween = filteredTripsBetween.map(trip => {
            const departure = {
                time: moment(trip.departureTime).format('DD/MM HH:mm'),
                name: trip.departurePlace
            };
            const arrival = {
                time: moment(trip.arrivalTime).format('DD/MM HH:mm'),
                name: trip.arrivalPlace
            };
            const company = trip.busCompanyName;
            const seats = trip.availableSeats;
            return replies.trip.listItemTg(company, departure, arrival, seats);
        }).join('\n');
        const structuredRely = buildFacebookElements(
            originMeta.slugs[0],
            destinationMeta.slugs[0],
            sessionCookie,
            shortUrl,
            filteredTripsBetween
        );
        return {
            textReply: replies.trip.filteredDepartureListBetween(
                origin,
                destination,
                moment(timeFilter.from.value),
                moment(timeFilter.to.value),
                filteredTripsBetween.length,
                shortUrl,
                (filteredTripsBetween.length < tripListSizeThreshold)
                    ? tripListBetween
                    : null
            ),
            structuredRely
        };
    }
    const tripList = trips.map(trip => {
        const departure = {
            time: moment(trip.departureTime).format('DD/MM HH:mm'),
            name: trip.departurePlace
        };
        const arrival = {
            time: moment(trip.arrivalTime).format('DD/MM HH:mm'),
            name: trip.arrivalPlace
        };
        const company = trip.busCompanyName;
        const seats = trip.availableSeats;
        return replies.trip.listItemTg(company, departure, arrival, seats);
    }).join('\n');
    const structuredRely = buildFacebookElements(
        originMeta.slugs[0],
        destinationMeta.slugs[0],
        sessionCookie,
        shortUrl,
        trips
    );
    // console.log('structuredRely', JSON.stringify(structuredRely));
    return {
        textReply: replies.trip.departureList(
            origin,
            destination,
            departureDay,
            trips.length,
            shortUrl,
            trips.length < tripListSizeThreshold ? tripList : null
        ),
        structuredRely
    };
};

export { tripDialogReply };
<commit_msg>use guessed svg logo urls instead of the busCompanyLogo from the API, fix #49<commit_after>import moment from 'moment';
import { replies } from '../replies';
import latinize from 'latinize';

const CLICKBUS_URL = process.env.CLICKBUS_URL;
const CLICKBUS_SVG_LOGO_URL = process.env.CLICKBUS_SVG_LOGO_URL || 'https://m.clickbus.com.br/app/public/img/buslines/br/';

const tripListSizeThreshold = 10;

const buildFacebookElements = (origin, destination, session, searchUrl, trips) => {
    console.log('buildFacebookElements');
    const result = trips.map(trip => {
        const {
            price,
            arrivalTime,
            arrivalPlace,
            departurePlace,
            departureTime,
            busCompanyName,
            // busCompanyLogo,
            availableSeats,
            duration,
            scheduleId
        } = trip;
        const busCompanyLogoName = latinize(busCompanyName).replace(/ |%20/ig, '-').toLowerCase();
        const busCompanyLogo = `${CLICKBUS_SVG_LOGO_URL}${busCompanyLogoName}.svg`;
        const departure = {
            time: moment(departureTime).format('DD/MM HH:mm'),
            name: departurePlace
        };
        const arrival = {
            time: moment(arrivalTime).format('DD/MM HH:mm'),
            name: arrivalPlace
        };
        const company = busCompanyName;
        const seats = availableSeats;
        const formattedPrice = `R$${price.slice(0, -2)},${price.slice(-2)}`;
        const title = replies.trip.listTitle(
            company, departureTime, availableSeats, duration, formattedPrice
        );
        const subtitle = replies.trip.listItemFb(company, departure, arrival, seats, duration, true);
        const baseURL = `${process.env.BOT_URL}${process.env.POST_TO_CLICKBUS_HACK_PATH}`;
        const decodedScheduleTrip = new Buffer(scheduleId, 'base64').toString('utf8');
        const trips0 = decodedScheduleTrip.split('--').map((item, index) => {
            if ([2, 4, 8, 9].includes(index)) {
                return `"${item}"`;
            }
            if (index === 7) {
                return item === '1';
            }
            return parseInt(item, 10);
        }).toString();
        const postBody = {
            originSlug: origin,
            destinationSlug: destination,
            trips0,
            store: process.env.CLICKBUS_STORE,
            platform: process.env.CLICKBUS_PLATFORM
        };
        const postURL = process.env.CLICKBUS_WEB_POST_URL;
        const failUrl = searchUrl;
        const url = `${baseURL}?body=${JSON.stringify(postBody)}&url=${postURL}&session=${session}&failUrl=${failUrl}`;
        return {
            title,
            image_url: busCompanyLogo,
            buttons: [{
                type: 'web_url',
                url,
                title: replies.trip.detail()
            }],
            subtitle
        };
    });
    return result.slice(0, 10);
};

const tripDialogReply = context => {
    const {
        origin,
        originMeta,
        destination,
        destinationMeta,
        timeFilter,
        apiError,
        trips,
        shortUrl,
        sessionCookie
    } = context;
    const hasOrigin = origin !== undefined;
    const hasDestination = destination !== undefined;
    const hasApiError = apiError !== undefined;
    const hasTrips = trips !== undefined;
    const hasNoTrips = hasTrips && !trips.length;
    if (hasApiError) {
        return replies.trip.apiError(apiError);
    }
    if (!hasOrigin && !hasDestination) {
        return replies.trip.noPlaces();
    }
    if (hasOrigin && !originMeta) {
        return replies.trip.noSlug(origin);
    }
    if (hasDestination && !destinationMeta) {
        return replies.trip.noSlug(destination);
    }
    if (hasOrigin && !hasDestination) {
        return replies.trip.noDestination();
    }
    if (hasDestination && !hasOrigin) {
        return replies.trip.noOrigin();
    }
    const day = timeFilter && timeFilter.from
        ? moment(timeFilter.from.value) : moment();
    const departureDay = day.format('YYYY-MM-DD');
    if (hasDestination && hasOrigin && !hasTrips) {
        const from = originMeta.slugs[0];
        const to = destinationMeta.slugs[0];
        const url = `${CLICKBUS_URL}/trips?from=${from}&to=${to}&departure=${departureDay}`;
        return {
            url,
            departureDay
        };
    }
    if (!originMeta || !destinationMeta) {
        return replies.trip.noTrips(origin, destination);
    }

    if (hasDestination && hasOrigin && hasTrips && hasNoTrips) {
        return replies.trip.noTripsWithUrl(origin, destination, shortUrl);
    }
    if (hasDestination && hasOrigin && hasTrips && timeFilter &&
                timeFilter.from && timeFilter.from.grain !== 'day') {
        const filteredTripsAfter = trips.filter(trip =>
            trip.departureTime.isAfter(timeFilter.from.value)
        );

        if (timeFilter.to === null) {
            const tripListAfter = filteredTripsAfter.map(trip => {
                const departure = {
                    time: moment(trip.departureTime).format('DD/MM HH:mm'),
                    name: trip.departurePlace
                };
                const arrival = {
                    time: moment(trip.arrivalTime).format('DD/MM HH:mm'),
                    name: trip.arrivalPlace
                };
                const company = trip.busCompanyName;
                const seats = trip.availableSeats;

                return replies.trip.listItemTg(company, departure, arrival, seats);
            });
            const structuredRely = buildFacebookElements(
                originMeta.slugs[0],
                destinationMeta.slugs[0],
                sessionCookie,
                shortUrl,
                filteredTripsAfter
            );
            return {
                textReply: replies.trip.filteredDepartureListAfter(
                    origin,
                    destination,
                    moment(timeFilter.from.value),
                    filteredTripsAfter.length,
                    shortUrl,
                    (filteredTripsAfter.length < tripListSizeThreshold)
                        ? tripListAfter.join('\n')
                        : null
                ),
                structuredRely
            };
        }
        const filteredTripsBetween = filteredTripsAfter.filter(trip =>
            trip.departureTime.isBefore(timeFilter.to.value)
        );
        const tripListBetween = filteredTripsBetween.map(trip => {
            const departure = {
                time: moment(trip.departureTime).format('DD/MM HH:mm'),
                name: trip.departurePlace
            };
            const arrival = {
                time: moment(trip.arrivalTime).format('DD/MM HH:mm'),
                name: trip.arrivalPlace
            };
            const company = trip.busCompanyName;
            const seats = trip.availableSeats;
            return replies.trip.listItemTg(company, departure, arrival, seats);
        }).join('\n');
        const structuredRely = buildFacebookElements(
            originMeta.slugs[0],
            destinationMeta.slugs[0],
            sessionCookie,
            shortUrl,
            filteredTripsBetween
        );
        return {
            textReply: replies.trip.filteredDepartureListBetween(
                origin,
                destination,
                moment(timeFilter.from.value),
                moment(timeFilter.to.value),
                filteredTripsBetween.length,
                shortUrl,
                (filteredTripsBetween.length < tripListSizeThreshold)
                    ? tripListBetween
                    : null
            ),
            structuredRely
        };
    }
    const tripList = trips.map(trip => {
        const departure = {
            time: moment(trip.departureTime).format('DD/MM HH:mm'),
            name: trip.departurePlace
        };
        const arrival = {
            time: moment(trip.arrivalTime).format('DD/MM HH:mm'),
            name: trip.arrivalPlace
        };
        const company = trip.busCompanyName;
        const seats = trip.availableSeats;
        return replies.trip.listItemTg(company, departure, arrival, seats);
    }).join('\n');
    const structuredRely = buildFacebookElements(
        originMeta.slugs[0],
        destinationMeta.slugs[0],
        sessionCookie,
        shortUrl,
        trips
    );
    // console.log('structuredRely', JSON.stringify(structuredRely));
    return {
        textReply: replies.trip.departureList(
            origin,
            destination,
            departureDay,
            trips.length,
            shortUrl,
            trips.length < tripListSizeThreshold ? tripList : null
        ),
        structuredRely
    };
};

export { tripDialogReply };
<|endoftext|>


<commit_before>function isBetween(n, start, end) {
  return typeof n === typeof start && start <= n && n <= end;
}

plural = function(n) {
  if ((isBetween((n % 10), 2, 4)) && !(isBetween((n % 100), 12, 14))) {
    return 'few';
  }
  if (n !== 1 && (isBetween((n % 10), 0, 1)) ||
      (isBetween((n % 10), 5, 9)) ||
        (isBetween((n % 100), 12, 14))) {
    return 'many';
  }
  if (n === 1) {
    return 'one';
  }
  return 'other';
};

pickRandom = function(arr) {
  return arr[Math.floor(Math.random() * arr.length)];
};

translations = {
  confirms: {
    one: 'potwierdzenie',
    few: 'potwierdzenia',
    many: 'potwierdzeń',
    other: 'potwierdzenia',
  },
  clears: {
    one: 'odwołanie',
    few: 'odwołania',
    many: 'odwołań',
    other: 'odwołania',
  },
};

taunts = [
  'Wszystko OK.',
  'Jest super.',
  'Jest w porządku, serio.',
  'Brak zgoszeń oznacza… brak zgłoszeń.',
  'Jeśli nie ma rozwiązania, to nie problemu.',
  'Dobrego dnia!',
];

toasts = {
  created: [
    'Dzięki!.',
    'Zgłoszenie rozesłane.',
    'Dobra robota.',
    'Powodzenia.',
  ],
  upvoted: [
    'Dzięki, potwierdzone.',
    'Zgłoszenie potwierdzone.',
    'Dobra robota.',
  ],
  downvoted: [
    'Dzięki, odwołane.',
    'Zgłoszenie odwołane.',
    'Uff!',
  ],
  removed: [
    'Puff!',
    'Było — i nie ma.',
    'Zgłoszenie usunięte',
    'Pomyłka?',
    'I po problemie.',
  ],
  thanked: [
    'Dobrego dnia!',
    'Komuś będzie miło.',
    'Sprzyjających wiatrów!',
  ],
};
<commit_msg>fix typo<commit_after>function isBetween(n, start, end) {
  return typeof n === typeof start && start <= n && n <= end;
}

plural = function(n) {
  if ((isBetween((n % 10), 2, 4)) && !(isBetween((n % 100), 12, 14))) {
    return 'few';
  }
  if (n !== 1 && (isBetween((n % 10), 0, 1)) ||
      (isBetween((n % 10), 5, 9)) ||
        (isBetween((n % 100), 12, 14))) {
    return 'many';
  }
  if (n === 1) {
    return 'one';
  }
  return 'other';
};

pickRandom = function(arr) {
  return arr[Math.floor(Math.random() * arr.length)];
};

translations = {
  confirms: {
    one: 'potwierdzenie',
    few: 'potwierdzenia',
    many: 'potwierdzeń',
    other: 'potwierdzenia',
  },
  clears: {
    one: 'odwołanie',
    few: 'odwołania',
    many: 'odwołań',
    other: 'odwołania',
  },
};

taunts = [
  'Wszystko OK.',
  'Jest super.',
  'Jest w porządku, serio.',
  'Brak zgłoszeń oznacza… brak zgłoszeń.',
  'Jeśli nie ma rozwiązania, to nie problemu.',
  'Dobrego dnia!',
];

toasts = {
  created: [
    'Dzięki!.',
    'Zgłoszenie rozesłane.',
    'Dobra robota.',
    'Powodzenia.',
  ],
  upvoted: [
    'Dzięki, potwierdzone.',
    'Zgłoszenie potwierdzone.',
    'Dobra robota.',
    'A więc jednak.',
  ],
  downvoted: [
    'Dzięki, odwołane.',
    'Zgłoszenie odwołane.',
    'Dobra robota.',
    'Uff!',
  ],
  removed: [
    'Puff!',
    'Było — i nie ma.',
    'Zgłoszenie usunięte',
    'Pomyłka?',
    'I po problemie.',
  ],
  thanked: [
    'Dobrego dnia!',
    'Komuś będzie miło.',
    'Sprzyjających wiatrów!',
  ],
};
<|endoftext|>


<commit_before>import { UPDATE_LOCATION } from 'react-router-redux';
import fetch from 'isomorphic-fetch';
// const BASE_URL = 'http://' + window.location.href.split('/')[2] + '/';
const BASE_URL = 'http://localhost:3000/';

function callApi(endpoint, id, req, body){
  let config = {credentials : 'include' };
  console.log("call api's ENDPOINT!!!!!!!! ", endpoint)
  // console.log('got an id:', id);
  //config.header = { Accept: 'application/json'};
  //console.log(arguments);
  if(req === 'POST' || req === 'PUT'){
   console.log('making POST or PUT req');
    config.headers= {
      "Content-Type":"application/json",
      "Accept":"application/json"
    }
    config.method = req;
    config.body = body;
  } else if(req === 'DELETE'){
    console.log('we are deleting', req)
    config.method = req
    //console.log('method',config.method,'method',config.body,'body')

  } else {
    config.id = id;
  }

  return fetch( BASE_URL + endpoint, config)
    .then(response =>
        response.text()
        .then(text => ({ text, response }))
      )
    .then(({ text, response }) => {
      console.log('text:', text, 'response:', response)
      // console.log('response.ok in middleware:', response.ok)
      if (!response.ok) {
        // throw new Error (text);
        return Promise.reject(text)
      }else{
        return text;
      }
    })
}

export const CALL_API = Symbol('Call API');

// hook into any action without to use the reducer
export default store => next => action => {
  // if (action.type === UPDATE_LOCATION) {
  //   // console.log('page changed')
  // }
  // return next(action)

  const callAPI = action[CALL_API]
  //console.log('here is our callAPI', callAPI)
  // So the middleware doesn't get applied to every single action
  if (typeof callAPI === 'undefined') {
    return next(action)
  }

  let { endpoint, id, req, body, types } = callAPI
  const [ requestType, successType, errorType ] = types

  return callApi(endpoint, id, req, body, types).then(
    response => next({
      response,
      type: successType,
      id: id

    }),
    error => next({
      type: errorType
    })
  )

}
<commit_msg>localhost hotfix<commit_after>import { UPDATE_LOCATION } from 'react-router-redux';
import fetch from 'isomorphic-fetch';
const BASE_URL = 'http://' + window.location.href.split('/')[2] + '/';
// const BASE_URL = 'http://localhost:3000/';

function callApi(endpoint, id, req, body){
  let config = {credentials : 'include' };
  console.log("call api's ENDPOINT!!!!!!!! ", endpoint)
  // console.log('got an id:', id);
  //config.header = { Accept: 'application/json'};
  //console.log(arguments);
  if(req === 'POST' || req === 'PUT'){
   console.log('making POST or PUT req');
    config.headers= {
      "Content-Type":"application/json",
      "Accept":"application/json"
    }
    config.method = req;
    config.body = body;
  } else if(req === 'DELETE'){
    console.log('we are deleting', req)
    config.method = req
    //console.log('method',config.method,'method',config.body,'body')

  } else {
    config.id = id;
  }

  return fetch( BASE_URL + endpoint, config)
    .then(response =>
        response.text()
        .then(text => ({ text, response }))
      )
    .then(({ text, response }) => {
      console.log('text:', text, 'response:', response)
      // console.log('response.ok in middleware:', response.ok)
      if (!response.ok) {
        // throw new Error (text);
        return Promise.reject(text)
      }else{
        return text;
      }
    })
}

export const CALL_API = Symbol('Call API');

// hook into any action without to use the reducer
export default store => next => action => {
  // if (action.type === UPDATE_LOCATION) {
  //   // console.log('page changed')
  // }
  // return next(action)

  const callAPI = action[CALL_API]
  //console.log('here is our callAPI', callAPI)
  // So the middleware doesn't get applied to every single action
  if (typeof callAPI === 'undefined') {
    return next(action)
  }

  let { endpoint, id, req, body, types } = callAPI
  const [ requestType, successType, errorType ] = types

  return callApi(endpoint, id, req, body, types).then(
    response => next({
      response,
      type: successType,
      id: id

    }),
    error => next({
      type: errorType
    })
  )

}
<|endoftext|>


<commit_before>//Button Logic

var filenameList = [];
$("#saveDocumentServer").click(function(){

    $.post(hostname, {filename: $("#filenameInput").val(), jsonData: editor.get()}, function(){
      console.log($("#filenameInput").val() + editor.get());
      updateDropDown();
    });
});

$('#serverSend').on('click', function () {
  sendToServer();
})

$('#serverLoad').on('click', function () {
  loadFromServer();
})

$('#serverAPI_1').on('click', function () {
  serverHost(1);
})
$('#serverAPI_2').on('click', function () {
  serverHost(2);
})
$('#serverAPI_3').on('click', function () {
  serverHost(3);
})

$('#serverDelete').on('click', function () {
  var file = $("#dropdownFiles :selected").text();
  if(confirm("Are you sure you want to delete "+file+" from the server?") == true){
    deleteFromServer();
    updateDropDown();
  }
});

$(document).ready(function(){
  updateDropDown();
});

function updateDropDown(){
  var dropdown = $("#dropdownFiles");
  $("#dropdownFiles").empty();
  console.log("Getting File Names");
  $.get(hostname, function(data, status){
    var filenameList = data;
    console.log(filenameList);
    $.each(filenameList, function(filenameList) {
        dropdown.append($("<option />").val(this).text(this));
        console.log("Attempting to write");
    });
  });
}

$('.nav a').on('click', function(){
    $('.btn-navbar').click(); //bootstrap 2.x
    $('.navbar-toggle').click() //bootstrap 3.x by Richard
});

$("#filenameInput").keydown(function(event){
    if(event.keyCode == 13){
        sendToServer();
    }
});
<commit_msg>Added autorefresh of dropdown<commit_after>//Button Logic

var filenameList = [];
$("#saveDocumentServer").click(function(){

    $.post(hostname, {filename: $("#filenameInput").val(), jsonData: editor.get()}, function(){
      console.log($("#filenameInput").val() + editor.get());
      updateDropDown();
    });
});

$('#serverSend').on('click', function () {
  sendToServer();
})

$('#serverLoad').on('click', function () {
  loadFromServer();
})

$('#serverAPI_1').on('click', function () {
  serverHost(1);
})
$('#serverAPI_2').on('click', function () {
  serverHost(2);
})
$('#serverAPI_3').on('click', function () {
  serverHost(3);
})

$('#serverDelete').on('click', function () {
  var file = $("#dropdownFiles :selected").text();
  if(confirm("Are you sure you want to delete "+file+" from the server?") == true){
    deleteFromServer();
    updateDropDown();
  }
});

$(document).ready(function(){
  updateDropDown();
});

function updateDropDown(){
  var dropdown = $("#dropdownFiles");
  $("#dropdownFiles").empty();
  console.log("Getting File Names");
  $.get(hostname, function(data, status){
    var filenameList = data;
    console.log(filenameList);
    $.each(filenameList, function(filenameList) {
        dropdown.append($("<option />").val(this).text(this));
        console.log("Attempting to write");
    });
  });
}

$('.nav a').on('click', function(){
    $('.btn-navbar').click(); //bootstrap 2.x
    $('.navbar-toggle').click() //bootstrap 3.x by Richard
});

$("#filenameInput").keydown(function(event){
    if(event.keyCode == 13){
        sendToServer();
    }
});

setInterval(function(){ //Update dropdown every 5 seconds
  updateDropDown();
},60000);
<|endoftext|>


<commit_before>import io from 'socket.io-client';
const socket = io('http://localhost:3000');

export const joinChat = username => {
  return dispatch => {
    socket.emit('connection');
    socket.emit('joinChat', username);
    socket.on('getUsers', users => {
      return dispatch(populateUsers(users));
    });
  };
};

export const populateUsers = users => ({
  type: 'POPULATE_USERS',
  users
});
<commit_msg>URL to connect to socketIO is dynamically generated<commit_after>import io from 'socket.io-client';
let ioURL = window.document.location.origin;
if (ioURL.indexOf('9000')) {
	ioURL = ioURL.replace('9000', '3000');
}
const socket = io(ioURL);

export const joinChat = username => {
	return dispatch => {
		socket.emit('connection');
		socket.emit('joinChat', username);
		socket.on('getUsers', users => {
			return dispatch(populateUsers(users));
		});
	};
};

export const populateUsers = users => ({
	type: 'POPULATE_USERS',
	users
});
<|endoftext|>


<commit_before>var express = require('express');
var router = express.Router();
var superAgent = require('superagent');
var config = require('../config/environment.js');

var firstLetterToLowerCase = (world) => {
	return world.charAt(0).toLowerCase() + world.slice(1);
};

var parseCookieString = (cookieString) => {
	var cookiePairs = cookieString.split('; ');

	var cookieName = null;
	var cookieValue = null;
	var cookieOptions = {
		path: '/',
		secure: false,
		expires: new Date('Fri, 31 Dec 9999 23:59:59 UTC'),
		httpOnly: false
	};

	cookiePairs.forEach((pair) => {
		var separatorIndexEqual =  pair.indexOf('=');
		var name = decodeURIComponent(firstLetterToLowerCase(pair.substr(0, separatorIndexEqual)));
		var value = decodeURIComponent(pair.substr(separatorIndexEqual + 1));

		if (cookieOptions[name]) {
			var date = new Date(value);

			if (isNaN(date.getTime())) {
				cookieOptions[name] = value;
			} else {
				cookieOptions[name] = date;
			}

		} else {
			cookieName = name;
			cookieValue = value;
		}
	});

	return {
		name: cookieName,
		value: cookieValue,
		options: cookieOptions
	};
};

var callRemoteServer = (req, res) => {
	var url = req.url;
	var httpRequest = null;

	if ((req.url.length > 1) && (req.url[req.url.length-1] === '/')) {
		url = url.substr(0, url.length - 1);
	}

	console.log(`API: ${req.method} ${config.$Api.server + url} query:`, req.query);

	switch(req.method) {
		case 'POST':
			httpRequest = superAgent
				.post(config.$Api.server + url)
				.send(req.body);
			break;
		case 'PUT':
			httpRequest = superAgent
				.put(config.$Api.server + url)
				.send(req.body);
			break;
		case 'PATCH':
			httpRequest = superAgent
				.patch(config.$Api.server + url)
				.send(req.body);
			break;
		case 'DELETE':
			httpRequest = superAgent
				.delete(config.$Api.server + url)
				.send(req.body);
			break;
		case 'GET':
			httpRequest = superAgent
				.get(config.$Api.server + url)
				.query(req.query);
			break;
	}

	if (req.get('Authorization')) {
		httpRequest = httpRequest.set('Authorization', req.get('Authorization'));
	}

	httpRequest
		.set('Cookie', req.get('Cookie'))
		.end((error, respond) => {
			if (error) {
				console.error('API ERROR', e);
				res.status(500).json({Error: 'API error', message: e.message});
			}

			if (respond) {
				var settedCookies = respond.header['set-cookie'];

				if (settedCookies) {
					settedCookies.forEach((cookieString) => {
						var cookie = parseCookieString(cookieString);
						res.cookie(cookie.name, cookie.value, cookie.options);
					});
				}
				res.status(respond.status).json(respond.body);
			}
		});
};

router.all('*', function(req, res) {
	callRemoteServer(req, res);
});

module.exports = router;<commit_msg>#! fix bug in api route proxy<commit_after>var express = require('express');
var router = express.Router();
var superAgent = require('superagent');
var config = require('../config/environment.js');

var firstLetterToLowerCase = (world) => {
	return world.charAt(0).toLowerCase() + world.slice(1);
};

var parseCookieString = (cookieString) => {
	var cookiePairs = cookieString.split('; ');

	var cookieName = null;
	var cookieValue = null;
	var cookieOptions = {
		path: '/',
		secure: false,
		expires: new Date('Fri, 31 Dec 9999 23:59:59 UTC'),
		httpOnly: false
	};

	cookiePairs.forEach((pair) => {
		var separatorIndexEqual =  pair.indexOf('=');
		var name = decodeURIComponent(firstLetterToLowerCase(pair.substr(0, separatorIndexEqual)));
		var value = decodeURIComponent(pair.substr(separatorIndexEqual + 1));

		if (cookieOptions[name]) {
			var date = new Date(value);

			if (isNaN(date.getTime())) {
				cookieOptions[name] = value;
			} else {
				cookieOptions[name] = date;
			}

		} else {
			cookieName = name;
			cookieValue = value;
		}
	});

	return {
		name: cookieName,
		value: cookieValue,
		options: cookieOptions
	};
};

var callRemoteServer = (req, res) => {
	var url = req.url;
	var httpRequest = null;

	if ((req.url.length > 1) && (req.url[req.url.length-1] === '/')) {
		url = url.substr(0, url.length - 1);
	}

	console.log(`API: ${req.method} ${config.$Api.server + url} query:`, req.query);

	switch(req.method) {
		case 'POST':
			httpRequest = superAgent
				.post(config.$Api.server + url)
				.send(req.body);
			break;
		case 'PUT':
			httpRequest = superAgent
				.put(config.$Api.server + url)
				.send(req.body);
			break;
		case 'PATCH':
			httpRequest = superAgent
				.patch(config.$Api.server + url)
				.send(req.body);
			break;
		case 'DELETE':
			httpRequest = superAgent
				.delete(config.$Api.server + url)
				.send(req.body);
			break;
		case 'GET':
			httpRequest = superAgent
				.get(config.$Api.server + url)
				.query(req.query);
			break;
	}

	if (req.get('Authorization')) {
		httpRequest = httpRequest.set('Authorization', req.get('Authorization'));
	}

	httpRequest
		.set('Cookie', req.get('Cookie'))
		.end((error, respond) => {
			if (error) {
				console.error('API ERROR', error);
				res.status(500).json({Error: 'API error', message: error.message});
			}

			if (respond) {
				var settedCookies = respond.header['set-cookie'];

				if (settedCookies) {
					settedCookies.forEach((cookieString) => {
						var cookie = parseCookieString(cookieString);
						res.cookie(cookie.name, cookie.value, cookie.options);
					});
				}
				res.status(respond.status).json(respond.body);
			}
		});
};

router.all('*', function(req, res) {
	callRemoteServer(req, res);
});

module.exports = router;<|endoftext|>


<commit_before>//<!--  Contains functions to create, draw and modify towers -->
//

function tower(posX,posY){
	this.posX=posX;
	this.posY=posY;
	this.direction= 0;
	this.size= 10;
	this.image = new Image();
	this.image.src = imgBullet;
	this.randomShort = 5;
	this.randomMedium = 5;
	this.randomLong = 5;
	this.targetCreep = 0;
	this.towerRange = 10; // dynamic or inherited later
	this.explodeCycle = undefined;

	this.drawTower=drawTower;
	function drawTower(ctx){
		ctx.save();
	  ctx.translate(this.posX, this.posY);
	  ctx.rotate(this.direction);
		ctx.drawImage(this.image,-50,-50);
	  ctx.restore();
	};

};

function drawAllTowers(ctx){
	for (var i = 0; i < towers.length; i++) {
		if (towers[i] != undefined) {
      towers[i].drawTower(ctx);
    };
  };
};

function updateTowers(){
	for (var i = 0; i < towers.length; i++) {
		if (towers[i] != undefined) {
			towers[i].targetCreep = findClosestTarget(i);
			towers[i].direction = aimTower(towers[i].posX,towers[i].posY,towers[i].targetCreep);
			chaseTargets(i);
			explode(i);
		};

		// if (inRange(i, towers[i].targetCreep)) {
		// 	towers[i].image.src = imgKaboom;
		// }
		// else {
		// 	towers[i].image.src = imgRoundArrow;
		// };

	};
};

function explode(i) {
	if (creeps[towers[i].targetCreep] == undefined) {
		console.log("explode returns early due to undefined targetCreep")
		return
	};

	var _distance = getDistance(i, towers[i].targetCreep);

	if ( (_distance < towers[i].towerRange) && (towers[i].explodeCycle == undefined) ) {
		towers[i].image.src = imgBang;
		towers[i].explodeCycle = gameLoopCounter;

		creeps[towers[i].targetCreep].image.src = imgDeer;
		creeps[towers[i].targetCreep].hitPoints = creeps[towers[i].targetCreep].hitPoints - 10;

		// console.log("Explode triggered! Tower: " + i)
		// towers[i].direction = towers[i].direction + 5
	};

	if ( towers[i].explodeCycle < gameLoopCounter - 5)  {
		towers[i] = undefined;
		// towers[i].image.src = imgBang;
		// towers[i].image.src = imgKaleidoscope;
		// console.log("Explode ended! Tower: " + i)
	};

};

function chaseTargets(i){

// 1 current polar speed
// 2 rate of accelleration
// 1 actual direction
// 2 intended direction
// 2 rate of direction change

// relocate X and relocate Y based on polar speed & direction via trig

		// if ( creeps[towers[i].targetCreep].posX > towers[i].posX ){
		// 	towers[i].posX++
		// }
		// else {
		// 	towers[i].posX--
		// };

		// if ( creeps[towers[i].targetCreep].posY > towers[i].posY ){
		// 	towers[i].posY++
		// }
		// else {
		// 	towers[i].posY--
		// };
}

function findClosestTarget(towerIndex) { // finds nearest creep
	var creepDistance = undefined;
	for (var i = 0; i < creeps.length; i++) {
		if (creeps[i] != undefined) {

			var _distance =	getDistance(towerIndex, i)

			if (creepDistance > _distance || creepDistance == undefined) {
				index = i;
				creepDistance = _distance;
			};
		};
	};
	return index
};

// FIND DIRECTION OF CREEP.

// AM I IN RANGE?

function getDistance(towerIndex, creepIndex){ // FIND DISTANCE FROM TOWER TO CREEP
	// if ((towers[towerIndex] == undefined) || (creeps[creepIndex] == undefined)) {

	if (towers[towerIndex] == undefined) {
		console.log("getDistance aborted early due to undefined tower Object")
		console.log("towerIndex = " + towerIndex)
		return
	};

	if (creeps[creepIndex] == undefined) {
		console.log("getDistance aborted early due to undefined creep Object")
		console.log("creepIndex = " + creepIndex)
		return
	};

	var distanceX = creeps[creepIndex].posX - towers[towerIndex].posX
	var distanceY = creeps[creepIndex].posY - towers[towerIndex].posY
	var distance = Math.sqrt((distanceX * distanceX) + (distanceY * distanceY))
	return distance
};

function inRange(towerIndex, creepIndex){
	if (towers[towerIndex].towerRange >= getDistance(towerIndex, creepIndex)) {
		return true
	}
	else {
		return false
	};
};

// DO THESE TWO THINGS INTERSECT?

function aimTower(x, y, _target) {

	var _distanceX = (creeps[_target].posX) - x;
	var _distanceY = ((creeps[_target].posY) - y ) * -1;
 	var _direction = Math.atan2(_distanceX, _distanceY)
 	return _direction - to_rad(90) // left turn to correct image
};

var towers = [];

// DRIVER CODE FOR DEV

var mah_tower3 = new tower(200,500);
towers.push(mah_tower3)

var mah_tower2 = new tower(300,200);
towers.push(mah_tower2)

var mah_tower = new tower(100,200);
towers.push(mah_tower)

var my_tower = new tower(100,300);
towers.push(my_tower)

var my_other_tower = new tower(500,300);
towers.push(my_other_tower)<commit_msg>commented inRange function<commit_after>//<!--  Contains functions to create, draw and modify towers -->
//

function tower(posX,posY){
	this.posX=posX;
	this.posY=posY;
	this.direction= 0;
	this.size= 10;
	this.image = new Image();
	this.image.src = imgBullet;
	this.randomShort = 5;
	this.randomMedium = 5;
	this.randomLong = 5;
	this.targetCreep = 0;
	this.towerRange = 10; // dynamic or inherited later
	this.explodeCycle = undefined;

	this.drawTower=drawTower;
	function drawTower(ctx){
		ctx.save();
	  ctx.translate(this.posX, this.posY);
	  ctx.rotate(this.direction);
		ctx.drawImage(this.image,-50,-50);
	  ctx.restore();
	};

};

function drawAllTowers(ctx){
	for (var i = 0; i < towers.length; i++) {
		if (towers[i] != undefined) {
      towers[i].drawTower(ctx);
    };
  };
};

function updateTowers(){
	for (var i = 0; i < towers.length; i++) {
		if (towers[i] != undefined) {
			towers[i].targetCreep = findClosestTarget(i);
			towers[i].direction = aimTower(towers[i].posX,towers[i].posY,towers[i].targetCreep);
			chaseTargets(i);
			explode(i);
		};

		// if (inRange(i, towers[i].targetCreep)) {
		// 	towers[i].image.src = imgKaboom;
		// }
		// else {
		// 	towers[i].image.src = imgRoundArrow;
		// };

	};
};

function explode(i) {
	if (creeps[towers[i].targetCreep] == undefined) {
		console.log("explode returns early due to undefined targetCreep")
		return
	};

	var _distance = getDistance(i, towers[i].targetCreep);

	if ( (_distance < towers[i].towerRange) && (towers[i].explodeCycle == undefined) ) {
		towers[i].image.src = imgBang;
		towers[i].explodeCycle = gameLoopCounter;

		creeps[towers[i].targetCreep].image.src = imgDeer;
		creeps[towers[i].targetCreep].hitPoints = creeps[towers[i].targetCreep].hitPoints - 10;

		// console.log("Explode triggered! Tower: " + i)
		// towers[i].direction = towers[i].direction + 5
	};

	if ( towers[i].explodeCycle < gameLoopCounter - 5)  {
		towers[i] = undefined;
		// towers[i].image.src = imgBang;
		// towers[i].image.src = imgKaleidoscope;
		// console.log("Explode ended! Tower: " + i)
	};

};

function chaseTargets(i){

// 1 current polar speed
// 2 rate of accelleration
// 1 actual direction
// 2 intended direction
// 2 rate of direction change

// relocate X and relocate Y based on polar speed & direction via trig

		// if ( creeps[towers[i].targetCreep].posX > towers[i].posX ){
		// 	towers[i].posX++
		// }
		// else {
		// 	towers[i].posX--
		// };

		// if ( creeps[towers[i].targetCreep].posY > towers[i].posY ){
		// 	towers[i].posY++
		// }
		// else {
		// 	towers[i].posY--
		// };
}

function findClosestTarget(towerIndex) { // finds nearest creep
	var creepDistance = undefined;
	for (var i = 0; i < creeps.length; i++) {
		if (creeps[i] != undefined) {

			var _distance =	getDistance(towerIndex, i)

			if (creepDistance > _distance || creepDistance == undefined) {
				index = i;
				creepDistance = _distance;
			};
		};
	};
	return index
};

// FIND DIRECTION OF CREEP.

// AM I IN RANGE?

function getDistance(towerIndex, creepIndex){ // FIND DISTANCE FROM TOWER TO CREEP
	// if ((towers[towerIndex] == undefined) || (creeps[creepIndex] == undefined)) {

	if (towers[towerIndex] == undefined) {
		console.log("getDistance aborted early due to undefined tower Object")
		console.log("towerIndex = " + towerIndex)
		return
	};

	if (creeps[creepIndex] == undefined) {
		console.log("getDistance aborted early due to undefined creep Object")
		console.log("creepIndex = " + creepIndex)
		return
	};

	var distanceX = creeps[creepIndex].posX - towers[towerIndex].posX
	var distanceY = creeps[creepIndex].posY - towers[towerIndex].posY
	var distance = Math.sqrt((distanceX * distanceX) + (distanceY * distanceY))
	return distance
};

// function inRange(towerIndex, creepIndex){
// 	if (towers[towerIndex].towerRange >= getDistance(towerIndex, creepIndex)) {
// 		return true
// 	}
// 	else {
// 		return false
// 	};
// };

// DO THESE TWO THINGS INTERSECT?

function aimTower(x, y, _target) {

	var _distanceX = (creeps[_target].posX) - x;
	var _distanceY = ((creeps[_target].posY) - y ) * -1;
 	var _direction = Math.atan2(_distanceX, _distanceY)
 	return _direction - to_rad(90) // left turn to correct image
};

var towers = [];

// DRIVER CODE FOR DEV

var mah_tower3 = new tower(200,500);
towers.push(mah_tower3)

var mah_tower2 = new tower(300,200);
towers.push(mah_tower2)

var mah_tower = new tower(100,200);
towers.push(mah_tower)

var my_tower = new tower(100,300);
towers.push(my_tower)

var my_other_tower = new tower(500,300);
towers.push(my_other_tower)<|endoftext|>


<commit_before>var traits = [

	// thanks to Stephen Dobson
	"frustrated poet",
	"tries to hide circular scar around neck",
	"one blue eye, one brown",
	"poor personal hygiene",
	"constantly scratching",
	"missing top two front teeth",
	'tattoo on inner forearm in an unusual foreign language. Think it says "warrior spirit" but actually means "import duty paid"',
	"thinks they can sing. They're wrong.",
	"silver necklace with four different faiths' holy symbols",
	"dagger with a (costume jewellery) jewelled pommel tucked into their belt",
	"yellow sash stretching from left shoulder to right waist",
	"missing right eye, wears a patch",
	"left little finger missing last joint, scarring evidently old",
	"greying at the temples. tries to hide with hat.",
	"dirty fingernails",
	"halitosis",
	"heavy drinker (red nose and cheeks)",
	"dried rations make them nauseous",

	// thanks to John Wilson
	"wide mouth",
	"beady eyes",
	"long face",
	"big hands",
	"long legs",
	"bristly hair",
	"limps (but no less agile for it)",
	"lisps",
	"always smiles",
	"greasy",
	"hirsute",

	// thanks to me
	"missing left eye, leaves scar uncovered",
	"has one gold tooth and no others",
	"missing both little fingers",
	'has a pet rat named "Mr Squeek" who lives in their pocket',
	"small hands",
	"dislikes loud noises",
	"scared of dogs",
	"afraid of heights",
	"claustrophobic",
	"tends to whisper",
	"arachnophobic",
	"distrusts men with beards",
	"village wrestling champion",
	"always wanted to be a magic user",
	"un-sure if their chosen deity exists",
	"considers themselves a master of strategy games",
	"inveterate gambler",
	"skilled at sleight of hand tricks",
	"scars run from corners of mouth to below ears",
	"tattoos on fingers of both hands spell words",
	"shameless womanizer",
	"has an invisible friend"
];

function get_trait(character_class) {
	var choice = randomRange(0, traits.length-1);
	return(traits[choice]);
}
<commit_msg>Adjust 2 trait text<commit_after>var traits = [

	// thanks to Stephen Dobson
	"frustrated poet",
	"tries to hide circular scar around neck",
	"one blue eye, one brown",
	"poor personal hygiene",
	"constantly scratching",
	"missing top two front teeth",
	'tattoo on inner forearm in an unusual foreign language. Thinks it says "warrior spirit" but actually means "import duty paid"',
	"thinks they can sing. They're wrong.",
	"silver necklace with four different faiths' holy symbols",
	"dagger with a (costume jewel) jewelled pommel tucked into their belt",
	"yellow sash stretching from left shoulder to right waist",
	"missing right eye, wears a patch",
	"left little finger missing last joint, scarring evidently old",
	"greying at the temples. tries to hide with hat.",
	"dirty fingernails",
	"halitosis",
	"heavy drinker (red nose and cheeks)",
	"dried rations make them nauseous",

	// thanks to John Wilson
	"wide mouth",
	"beady eyes",
	"long face",
	"big hands",
	"long legs",
	"bristly hair",
	"limps (but no less agile for it)",
	"lisps",
	"always smiles",
	"greasy",
	"hirsute",

	// thanks to me
	"missing left eye, leaves scar uncovered",
	"has one gold tooth and no others",
	"missing both little fingers",
	'has a pet rat named "Mr Squeek" who lives in their pocket',
	"small hands",
	"dislikes loud noises",
	"scared of dogs",
	"afraid of heights",
	"claustrophobic",
	"tends to whisper",
	"arachnophobic",
	"distrusts men with beards",
	"village wrestling champion",
	"always wanted to be a magic user",
	"un-sure if their chosen deity exists",
	"considers themselves a master of strategy games",
	"inveterate gambler",
	"skilled at sleight of hand tricks",
	"scars run from corners of mouth to below ears",
	"tattoos on fingers of both hands spell words",
	"shameless womanizer",
	"has an invisible friend"
];

function get_trait(character_class) {
	var choice = randomRange(0, traits.length-1);
	return(traits[choice]);
}
<|endoftext|>