<commit_before>const path = require('path');
const chalk = require('chalk');
const didYouMean = require('didyoumean2');

class CommandManager {
    constructor(bot) {
        this.bot = bot;
        this._commands = [];
        this._categories = [];
    }

    _validateCommand(object) {
        if (typeof object !== 'object')
            return 'command setup is invalid';
        if (typeof object.run !== 'function')
            return 'run function is missing';
        if (typeof object.info !== 'object')
            return 'info object is missing';
        if (typeof object.info.name !== 'string')
            return 'info object is missing a valid name field';
        return null;
    }

    loadCommands() {
        this._commands = [];
        this._categories = [];

        const bot = this.bot;

        const commandImports = bot.managers.dynamicImports.getImport('commands');
        Object.keys(commandImports).forEach(file => {
            let command = commandImports[file];
            let name = path.basename(file);

            if (command instanceof Array) {
                command.forEach((e, i) => this._validateAndLoad(e, file, `${name}.${i}`));
            } else {
                this._validateAndLoad(command, file, name);
            }
        });
    }

    _validateAndLoad(command, file, name) {
        let error = this._validateCommand(command);

        if (error) {
            return this.bot.logger.severe(`Failed to load '${name}': ${chalk.red(error)}`);
        }

        if (!command.category) {
            // TODO: Any better way to do this?
            let base = path.join(this.bot.managers.dynamicImports.base, 'commands');

            let category = 'Uncategorized';
            if (file.indexOf(path.sep) > -1) {
                category = path.dirname(path.relative(base, file))
                    .replace(new RegExp(path.sep.replace('\\', '\\\\'), 'g'), '/');
            }

            command.info.category = category;

            if (this._categories.indexOf(category) === -1)
                this._categories.push(category);
        }

        if (typeof command.init === 'function') {
            try {
                command.init(this.bot);
            } catch (err) {
                return this.bot.logger.severe(`Failed to init '${name}':`, err);
            }
        }

        this._commands.push(command);
    }

    all(category) {
        return !category ? this._commands : this._commands.filter(c => c.info.category.toLowerCase() === category.toLowerCase());
    }

    categories() {
        return this._categories;
    }

    get(name) {
        return this.findBy('name', name)
            || this._commands.find(command => command.info.aliases instanceof Array && command.info.aliases.indexOf(name) > -1);
    }

    findBy(key, value) {
        return this._commands.find(c => c.info[key] === value);
    }

    handleCommand(msg, input) {
        const prefix = this.bot.config.prefix;
        if (!input.startsWith(prefix)) return;

        let split = input.substr(prefix.length).trim().split(' ');
        let base = split[0].toLowerCase();
        let args = split.slice(1);

        // Try to find a built in command first
        let command = this.get(base);

        if (command) {
            return this.execute(msg, command, args);
        } else {
            return this._handleShortcuts(msg, base, args);
        }

        // Temporarily disabled
    }

    _handleShortcuts(msg, name, shortcutArgs) {
        // If that fails, look for a shortcut
        const shortcut = this.bot.storage('shortcuts').get(name);

        if (!shortcut) {
            // If no shortcuts could be found either, try finding the closest command
            const maybe = didYouMean(name, this.all().map(c => c.info.name), {
                threshold: 5,
                thresholdType: 'edit-distance'
            });

            if (maybe) {
                return msg.edit(`:question: Did you mean \`${this.bot.config.prefix}${maybe}\`?`).then(m => m.delete(5000));
            } else {
                return msg.edit(`:no_entry_sign: No commands were found that were similar to \`${this.bot.config.prefix}${name}\``)
                    .then(m => m.delete(5000));
            }
        }

        const commands = shortcut.command.split(';;');

        return Promise.all(
            commands.map(c => c.trim()).filter(c => c.length > 0).map(commandString => {
                const base = commandString.split(' ')[0].toLowerCase();
                const args = commandString.split(' ').splice(1).concat(shortcutArgs);

                const command = this.get(base);

                if (command) {
                    return this.execute(msg, command, args);
                } else {
                    return msg.edit(`:no_entry_sign: The shortcut \`${shortcut.name}\` is improperly set up!`)
                        .then(m => m.delete(2000));
                }
            })
        );
    }

    execute(msg, command, args) {
        msg.editEmbed = ((embed) => msg.edit('', { embed })).bind(msg);

        msg.error = ((message, delay) => {
            if (message.message === 'Not Found') {
                // Kinda sick of these :\
                return;
            }

            let displayMessage = message.message || message;

            this.bot.logger.severe(message);
            msg.edit(`:x: ${displayMessage || 'Something failed!'}`)
                .then(m => m.delete(delay || 2000));
        }).bind(msg);

        return Promise.resolve(command.run(this.bot, msg, args)).catch(msg.error);
    }
}

module.exports = CommandManager;
<commit_msg>Fixed command handling<commit_after>const path = require('path');
const chalk = require('chalk');
const didYouMean = require('didyoumean2');

class CommandManager {
    constructor(bot) {
        this.bot = bot;
        this._commands = [];
        this._categories = [];
    }

    _validateCommand(object) {
        if (typeof object !== 'object')
            return 'command setup is invalid';
        if (typeof object.run !== 'function')
            return 'run function is missing';
        if (typeof object.info !== 'object')
            return 'info object is missing';
        if (typeof object.info.name !== 'string')
            return 'info object is missing a valid name field';
        return null;
    }

    loadCommands() {
        this._commands = [];
        this._categories = [];

        const bot = this.bot;

        const commandImports = bot.managers.dynamicImports.getImport('commands');
        Object.keys(commandImports).forEach(file => {
            let command = commandImports[file];
            let name = path.basename(file);

            if (command instanceof Array) {
                command.forEach((e, i) => this._validateAndLoad(e, file, `${name}.${i}`));
            } else {
                this._validateAndLoad(command, file, name);
            }
        });
    }

    _validateAndLoad(command, file, name) {
        let error = this._validateCommand(command);

        if (error) {
            return this.bot.logger.severe(`Failed to load '${name}': ${chalk.red(error)}`);
        }

        if (!command.category) {
            // TODO: Any better way to do this?
            let base = path.join(this.bot.managers.dynamicImports.base, 'commands');

            let category = 'Uncategorized';
            if (file.indexOf(path.sep) > -1) {
                category = path.dirname(path.relative(base, file))
                    .replace(new RegExp(path.sep.replace('\\', '\\\\'), 'g'), '/');
            }

            command.info.category = category;

            if (this._categories.indexOf(category) === -1)
                this._categories.push(category);
        }

        if (typeof command.init === 'function') {
            try {
                command.init(this.bot);
            } catch (err) {
                return this.bot.logger.severe(`Failed to init '${name}':`, err);
            }
        }

        this._commands.push(command);
    }

    all(category) {
        return !category ? this._commands : this._commands.filter(c => c.info.category.toLowerCase() === category.toLowerCase());
    }

    categories() {
        return this._categories;
    }

    get(name) {
        return this.findBy('name', name)
            || this._commands.find(command => command.info.aliases instanceof Array && command.info.aliases.indexOf(name) > -1);
    }

    findBy(key, value) {
        return this._commands.find(c => c.info[key] === value);
    }

    handleCommand(msg, input) {
        const prefix = this.bot.config.prefix;
        if (!input.startsWith(prefix)) return;

        let split = input.substr(prefix.length).trim().split(' ');
        let base = split[0].toLowerCase();
        let args = split.slice(1);

        // Try to find a built in command first
        let command = this.get(base);

        if (command) {
            return this.execute(msg, command, args);
        } else {
            return this._handleShortcuts(msg, base, args);
        }

        // Temporarily disabled
    }

    _handleShortcuts(msg, name, shortcutArgs) {
        // If that fails, look for a shortcut
        const shortcut = this.bot.storage('shortcuts').get(name);

        if (!shortcut) {
            // If no shortcuts could be found either, try finding the closest command
            const maybe = didYouMean(name, this.all().map(c => c.info.name), {
                threshold: 5,
                thresholdType: 'edit-distance'
            });

            if (maybe) {
                return msg.edit(`:question: Did you mean \`${this.bot.config.prefix}${maybe}\`?`).then(m => m.delete(5000));
            } else {
                return msg.edit(`:no_entry_sign: No commands were found that were similar to \`${this.bot.config.prefix}${name}\``)
                    .then(m => m.delete(5000));
            }
        }

        const commands = shortcut.command.split(';;');

        return Promise.all(
            commands.map(c => c.trim()).filter(c => c.length > 0).map(commandString => {
                const base = commandString.split(' ')[0].toLowerCase();
                const args = commandString.split(' ').splice(1).concat(shortcutArgs);

                const command = this.get(base);

                if (command) {
                    return this.execute(msg, command, args);
                } else {
                    return msg.edit(`:no_entry_sign: The shortcut \`${shortcut.name}\` is improperly set up!`)
                        .then(m => m.delete(2000));
                }
            })
        );
    }

    execute(msg, command, args) {
        msg.editEmbed = ((embed) => msg.edit('', { embed })).bind(msg);

        msg.error = ((message, delay) => {
            console.log('I GOTS STUFF = ' + message);
            if (message.message === 'Not Found') {
                // Kinda sick of these :\
                return;
            }

            let displayMessage = message.message || message;

            this.bot.logger.severe(message);
            msg.edit(`:x: ${displayMessage || 'Something failed!'}`)
                .then(m => m.delete(delay || 2000));
        }).bind(msg);

        // return Promise.resolve(command.run(this.bot, msg, args));
        try {
            const returned = command.run(this.bot, msg, args);
            return Promise.resolve(returned);
        } catch (err) {
            msg.error(err);
            return Promise.resolve();
        }
    }
}

module.exports = CommandManager;
<|endoftext|>


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

    angular
        .module('lazyGoogleMaps')
        .directive('lazyGoogleMaps', lazyGoogleMaps);

    function lazyGoogleMaps ($window, $q, $rootScope, lazyGoogleMapsUtils) {
        var _ = $window._;
        var directive = {
            restrict: 'E',
            scope: {
                lat: '=?',
                lng: '=?',
                country: '=?',
                city: '=?',
                place: '=?',
                customSearchElementId: '=?',
                freezeButton: '=?',
                freezed: '=?',
                isLoading: '@',
                markers: '=?'
            },
            replace: true,
            templateUrl: 'map/map.html',
            link: link
        };
        return directive;

        function link(scope) {
            var maps;
            var renderedMap;
            var marker;
            var infowindow;
            var searchInputElement;
            var markerClusterer;

            scope.isLoading = true;

            scope.toggleFreezed = function () {
                scope.freezed = !scope.freezed;
                if (scope.freezed) { return freeze(); }
                unFreeze();
            };

            function freeze() {
                removeClickListener();
                renderedMap.setOptions({
                    disableDoubleClickZoom: false,
                    draggable: false,
                    scrollwheel: false
                });
            }

            function unFreeze() {
                addClickListener();
                renderedMap.setOptions({
                    disableDoubleClickZoom: true,
                    draggable: true,
                    scrollwheel: true
                });
            }

            if (maps) {
                return renderMap();
            }

            injectMap().then(function () {
                $rootScope.$broadcast('lazyGoogleMaps:loaded');
                renderMap();
            });

            function injectMap() {
                var deferred = $q.defer();
                var options = ['sensor=false', 'callback=mapLoaded', 'libraries=places'];
                var source = 'https://maps.googleapis.com/maps/api/js?' + options.join('&');
                var scriptTag = document.createElement('script');
                scriptTag.type = 'text/javascript';
                scriptTag.src = source;

                if (lazyGoogleMapsUtils.isLoading) {
                    scope.$on('lazyGoogleMaps:loaded', renderMap);
                    deferred.reject();

                    return deferred.promise;
                }

                document.body.appendChild(scriptTag);
                lazyGoogleMapsUtils.isLoading = true;

                $window.mapLoaded = function () {
                    deferred.resolve();
                };

                return deferred.promise;
            }

            function renderMap() {
                var mapOptions;
                var watchLatLng;
                maps = google.maps;
                mapOptions = {
                    zoom: 8,
                    center: new google.maps.LatLng(-33.868001, 151.195248),
                    panControlOptions: { position: maps.ControlPosition.LEFT_CENTER },
                    zoomControlOptions: { position: maps.ControlPosition.LEFT_CENTER },
                    streetViewControlOptions: { position: maps.ControlPosition.LEFT_CENTER }
                };
                renderedMap = new maps.Map(document.getElementById('lazy-google-maps-canvas-' + scope.$id), mapOptions);
                marker = new maps.Marker({ map: renderedMap });
                infowindow = new google.maps.InfoWindow();

                renderPlaceSearch();
                addClickListener();

                watchLatLng = scope.$watchGroup(['lat', 'lng'], function (coordinates) {
                    var watchAddress;
                    if(coordinates[0] && coordinates[0]) {
                        placeMarkerByLatLng();
                        return watchLatLng();
                    }

                    watchAddress = scope.$watchGroup(['city', 'country', 'place'], function (address) {
                        if (address[0] || address[1] || address[2]) {
                            watchLatLng();
                            placeMarkerByAddress();
                            return watchAddress();
                        }
                    });
                });

                scope.isLoading = false;

                if (scope.freezed) {
                    freeze();
                }
            }

            function placeMarkerByLatLng() {
                var latLng = scope.lat + ',' + scope.lng;

                lazyGoogleMapsUtils.reverseGeocode(latLng).then(function (place) {
                    if(place) {
                        processPlace(place);
                    }
                });
            }

            function placeMarkerByAddress() {
                var address = (scope.city || '') + ' ' + (scope.country || '') + ' ' + (scope.place || '');

                lazyGoogleMapsUtils.geocode(address).then(function (place) {
                    if(place) {
                        processPlace(place);
                    }
                });
            }

            function renderPlaceSearch() {
                var searchBox;
                searchInputElement = getSearchInput();
                searchBox = new google.maps.places.SearchBox((searchInputElement));

                if (!scope.customSearchElementId) {
                    renderedMap.controls[maps.ControlPosition.TOP_LEFT].push(searchInputElement);
                }

                maps.event.addListener(searchBox, 'places_changed', function() {
                    var place = searchBox.getPlaces()[0];

                    if (!place) {
                        return;
                    }

                    processPlace(place);
                });

                function getSearchInput() {
                    if (scope.customSearchElementId) {
                        return document.getElementById(scope.customSearchElementId);
                    }
                    return document.getElementById('lazy-google-maps-search-' + scope.$id);
                }
            }

            function addClickListener() {
                maps.event.addListener(renderedMap, 'click', function(target) {
                    var latLng = target.latLng.A + ',' + target.latLng.F;

                    lazyGoogleMapsUtils.reverseGeocode(latLng).then(function (place) {
                        if(place) {
                            processPlace(place);
                        }
                    });
                });
            }

            function processPlace(place) {
                var infoWindowContent;

                infowindow.close();

                if (_.has(place, 'geometry.viewport.Da')) {
                    renderedMap.fitBounds(place.geometry.viewport);
                }

                renderedMap.panTo(place.geometry.location);

                if (scope.markers) {
                    return setMarkerCluster();
                }

                setMarker(place);

                scope.place = place.formatted_address;
                scope.lat = place.geometry.location.A || place.geometry.location.lat;
                scope.lng = place.geometry.location.F || place.geometry.location.lng;

                searchInputElement.value = scope.place;

                processAddressComponents(place);

                infoWindowContent = '
' + scope.city + '
' + scope.place + '
';
                infowindow.setContent(infoWindowContent);
                infowindow.open(renderedMap, marker);

            }

            function setMarker(place) {
                var markerImage = {
                    url: place.icon,
                    size: new maps.Size(71, 71),
                    origin: new maps.Point(0, 0),
                    anchor: new maps.Point(17, 34),
                    scaledSize: new maps.Size(35, 35)
                };
                var position = place.geometry.location;
                if (place.icon) {
                    marker.setIcon((markerImage));
                }
                marker.setPosition(position);
                marker.setVisible(true);
            }

            function processAddressComponents(place) {
                var country = _.filter(place.address_components, { types: ['country'] });
                var city = _.filter(place.address_components, { types: ['locality'] });

                scope.country = country[0] ? country[0].long_name : scope.place;
                scope.city = city[0] ? city[0].long_name : scope.country;
            }

            function removeClickListener() {
                maps.event.clearListeners(renderedMap, 'click');
            }

            function setMarkerCluster() {

                var markerBounds = new google.maps.LatLngBounds();
                var imageUrl = 'http://chart.apis.google.com/chart?cht=mm&chs=24x32&chco=FFFFFF,008CFF,000000&ext=.png';
                var markerImage = new google.maps.MarkerImage(imageUrl, new google.maps.Size(24, 32));

                var markers = _.map(scope.markers, function (position) {
                    var latLng = new google.maps.LatLng(position.lat, position.lng);

                    var googleMarker = new google.maps.Marker({
                        position: latLng,
                        icon: markerImage,
                        title: 'Click to zoom'
                    });

                    maps.event.addListener(googleMarker, 'click', function() {
                        renderedMap.setZoom(17);
                        renderedMap.setCenter(googleMarker.getPosition());
                        console.log(googleMarker);
                    });

                    markerBounds.extend(latLng);
                    return googleMarker;
                });

                var mcOptions = {
                    gridSize: 90,
                    maxZoom: 15,
                    styles: [{
                        url: imageUrl,
                        height: 35,
                        width: 35,
                        anchor: [16, 0],
                        textColor: '#ff00ff',
                        textSize: 10
                    }]
                };
                markerClusterer = new MarkerClusterer(renderedMap, markers, mcOptions);
                renderedMap.fitBounds(markerBounds);
            }
        }
    }
})();
<commit_msg>do not override the default icon for marker<commit_after>(function() {
    'use strict';

    angular
        .module('lazyGoogleMaps')
        .directive('lazyGoogleMaps', lazyGoogleMaps);

    function lazyGoogleMaps ($window, $q, $rootScope, lazyGoogleMapsUtils) {
        var _ = $window._;
        var directive = {
            restrict: 'E',
            scope: {
                lat: '=?',
                lng: '=?',
                country: '=?',
                city: '=?',
                place: '=?',
                customSearchElementId: '=?',
                freezeButton: '=?',
                freezed: '=?',
                isLoading: '@',
                markers: '=?'
            },
            replace: true,
            templateUrl: 'map/map.html',
            link: link
        };
        return directive;

        function link(scope) {
            var maps;
            var renderedMap;
            var marker;
            var infowindow;
            var searchInputElement;
            var markerClusterer;

            scope.isLoading = true;

            scope.toggleFreezed = function () {
                scope.freezed = !scope.freezed;
                if (scope.freezed) { return freeze(); }
                unFreeze();
            };

            function freeze() {
                removeClickListener();
                renderedMap.setOptions({
                    disableDoubleClickZoom: false,
                    draggable: false,
                    scrollwheel: false
                });
            }

            function unFreeze() {
                addClickListener();
                renderedMap.setOptions({
                    disableDoubleClickZoom: true,
                    draggable: true,
                    scrollwheel: true
                });
            }

            if (maps) {
                return renderMap();
            }

            injectMap().then(function () {
                $rootScope.$broadcast('lazyGoogleMaps:loaded');
                renderMap();
            });

            function injectMap() {
                var deferred = $q.defer();
                var options = ['sensor=false', 'callback=mapLoaded', 'libraries=places'];
                var source = 'https://maps.googleapis.com/maps/api/js?' + options.join('&');
                var scriptTag = document.createElement('script');
                scriptTag.type = 'text/javascript';
                scriptTag.src = source;

                if (lazyGoogleMapsUtils.isLoading) {
                    scope.$on('lazyGoogleMaps:loaded', renderMap);
                    deferred.reject();

                    return deferred.promise;
                }

                document.body.appendChild(scriptTag);
                lazyGoogleMapsUtils.isLoading = true;

                $window.mapLoaded = function () {
                    deferred.resolve();
                };

                return deferred.promise;
            }

            function renderMap() {
                var mapOptions;
                var watchLatLng;
                maps = google.maps;
                mapOptions = {
                    zoom: 8,
                    center: new google.maps.LatLng(-33.868001, 151.195248),
                    panControlOptions: { position: maps.ControlPosition.LEFT_CENTER },
                    zoomControlOptions: { position: maps.ControlPosition.LEFT_CENTER },
                    streetViewControlOptions: { position: maps.ControlPosition.LEFT_CENTER }
                };
                renderedMap = new maps.Map(document.getElementById('lazy-google-maps-canvas-' + scope.$id), mapOptions);
                marker = new maps.Marker({ map: renderedMap });
                infowindow = new google.maps.InfoWindow();

                renderPlaceSearch();
                addClickListener();

                watchLatLng = scope.$watchGroup(['lat', 'lng'], function (coordinates) {
                    var watchAddress;
                    if(coordinates[0] && coordinates[0]) {
                        placeMarkerByLatLng();
                        return watchLatLng();
                    }

                    watchAddress = scope.$watchGroup(['city', 'country', 'place'], function (address) {
                        if (address[0] || address[1] || address[2]) {
                            watchLatLng();
                            placeMarkerByAddress();
                            return watchAddress();
                        }
                    });
                });

                scope.isLoading = false;

                if (scope.freezed) {
                    freeze();
                }
            }

            function placeMarkerByLatLng() {
                var latLng = scope.lat + ',' + scope.lng;

                lazyGoogleMapsUtils.reverseGeocode(latLng).then(function (place) {
                    if(place) {
                        processPlace(place);
                    }
                });
            }

            function placeMarkerByAddress() {
                var address = (scope.city || '') + ' ' + (scope.country || '') + ' ' + (scope.place || '');

                lazyGoogleMapsUtils.geocode(address).then(function (place) {
                    if(place) {
                        processPlace(place);
                    }
                });
            }

            function renderPlaceSearch() {
                var searchBox;
                searchInputElement = getSearchInput();
                searchBox = new google.maps.places.SearchBox((searchInputElement));

                if (!scope.customSearchElementId) {
                    renderedMap.controls[maps.ControlPosition.TOP_LEFT].push(searchInputElement);
                }

                maps.event.addListener(searchBox, 'places_changed', function() {
                    var place = searchBox.getPlaces()[0];

                    if (!place) {
                        return;
                    }

                    processPlace(place);
                });

                function getSearchInput() {
                    if (scope.customSearchElementId) {
                        return document.getElementById(scope.customSearchElementId);
                    }
                    return document.getElementById('lazy-google-maps-search-' + scope.$id);
                }
            }

            function addClickListener() {
                maps.event.addListener(renderedMap, 'click', function(target) {
                    var latLng = target.latLng.A + ',' + target.latLng.F;

                    lazyGoogleMapsUtils.reverseGeocode(latLng).then(function (place) {
                        if(place) {
                            processPlace(place);
                        }
                    });
                });
            }

            function processPlace(place) {
                var infoWindowContent;

                infowindow.close();

                if (_.has(place, 'geometry.viewport.Da')) {
                    renderedMap.fitBounds(place.geometry.viewport);
                }

                renderedMap.panTo(place.geometry.location);

                if (scope.markers) {
                    return setMarkerCluster();
                }

                setMarker(place);

                scope.place = place.formatted_address;
                scope.lat = place.geometry.location.A || place.geometry.location.lat;
                scope.lng = place.geometry.location.F || place.geometry.location.lng;

                searchInputElement.value = scope.place;

                processAddressComponents(place);

                infoWindowContent = '
' + scope.city + '
' + scope.place + '
';
                infowindow.setContent(infoWindowContent);
                infowindow.open(renderedMap, marker);

            }

            function setMarker(place) {
                var position = place.geometry.location;
                marker.setPosition(position);
                marker.setVisible(true);
            }

            function processAddressComponents(place) {
                var country = _.filter(place.address_components, { types: ['country'] });
                var city = _.filter(place.address_components, { types: ['locality'] });

                scope.country = country[0] ? country[0].long_name : scope.place;
                scope.city = city[0] ? city[0].long_name : scope.country;
            }

            function removeClickListener() {
                maps.event.clearListeners(renderedMap, 'click');
            }

            function setMarkerCluster() {

                var markerBounds = new google.maps.LatLngBounds();
                var imageUrl = 'http://chart.apis.google.com/chart?cht=mm&chs=24x32&chco=FFFFFF,008CFF,000000&ext=.png';
                var markerImage = new google.maps.MarkerImage(imageUrl, new google.maps.Size(24, 32));

                var markers = _.map(scope.markers, function (position) {
                    var latLng = new google.maps.LatLng(position.lat, position.lng);

                    var googleMarker = new google.maps.Marker({
                        position: latLng,
                        icon: markerImage,
                        title: 'Click to zoom'
                    });

                    maps.event.addListener(googleMarker, 'click', function() {
                        renderedMap.setZoom(17);
                        renderedMap.setCenter(googleMarker.getPosition());
                        console.log(googleMarker);
                    });

                    markerBounds.extend(latLng);
                    return googleMarker;
                });

                var mcOptions = {
                    gridSize: 90,
                    maxZoom: 15,
                    styles: [{
                        url: imageUrl,
                        height: 35,
                        width: 35,
                        anchor: [16, 0],
                        textColor: '#ff00ff',
                        textSize: 10
                    }]
                };
                markerClusterer = new MarkerClusterer(renderedMap, markers, mcOptions);
                renderedMap.fitBounds(markerBounds);
            }
        }
    }
})();
<|endoftext|>


<commit_before>/* Blob.js
 * A Blob implementation.
 * 2013-12-27
 *
 * By Eli Grey, http://eligrey.com
 * By Devin Samarin, https://github.com/eboyjr
 * License: X11/MIT
 *   See LICENSE.md
 */

/*global self, unescape */
/*jslint bitwise: true, regexp: true, confusion: true, es5: true, vars: true, white: true,
  plusplus: true */

/*! @source http://purl.eligrey.com/github/Blob.js/blob/master/Blob.js */

if (typeof Blob != "function" || typeof URL === "undefined")
if (typeof Blob === "function" && typeof webkitURL !== "undefined") self.URL = webkitURL;
else var Blob = (function (view) {
	"use strict";

	var BlobBuilder = view.BlobBuilder || view.WebKitBlobBuilder || view.MozBlobBuilder || view.MSBlobBuilder || (function(view) {
		var
			  get_class = function(object) {
				return Object.prototype.toString.call(object).match(/^\[object\s(.*)\]$/)[1];
			}
			, FakeBlobBuilder = function BlobBuilder() {
				this.data = [];
			}
			, FakeBlob = function Blob(data, type, encoding) {
				this.data = data;
				this.size = data.length;
				this.type = type;
				this.encoding = encoding;
			}
			, FBB_proto = FakeBlobBuilder.prototype
			, FB_proto = FakeBlob.prototype
			, FileReaderSync = view.FileReaderSync
			, FileException = function(type) {
				this.code = this[this.name = type];
			}
			, file_ex_codes = (
				  "NOT_FOUND_ERR SECURITY_ERR ABORT_ERR NOT_READABLE_ERR ENCODING_ERR "
				+ "NO_MODIFICATION_ALLOWED_ERR INVALID_STATE_ERR SYNTAX_ERR"
			).split(" ")
			, file_ex_code = file_ex_codes.length
			, real_URL = view.URL || view.webkitURL || view
			, real_create_object_URL = real_URL.createObjectURL
			, real_revoke_object_URL = real_URL.revokeObjectURL
			, URL = real_URL
			, btoa = view.btoa
			, atob = view.atob

			, ArrayBuffer = view.ArrayBuffer
			, Uint8Array = view.Uint8Array
		;
		FakeBlob.fake = FB_proto.fake = true;
		while (file_ex_code--) {
			FileException.prototype[file_ex_codes[file_ex_code]] = file_ex_code + 1;
		}
		if (!real_URL.createObjectURL) {
			URL = view.URL = {};
		}
		URL.createObjectURL = function(blob) {
			var
				  type = blob.type
				, data_URI_header
			;
			if (type === null) {
				type = "application/octet-stream";
			}
			if (blob instanceof FakeBlob) {
				data_URI_header = "data:" + type;
				if (blob.encoding === "base64") {
					return data_URI_header + ";base64," + blob.data;
				} else if (blob.encoding === "URI") {
					return data_URI_header + "," + decodeURIComponent(blob.data);
				} if (btoa) {
					return data_URI_header + ";base64," + btoa(blob.data);
				} else {
					return data_URI_header + "," + encodeURIComponent(blob.data);
				}
			} else if (real_create_object_URL) {
				return real_create_object_URL.call(real_URL, blob);
			}
		};
		URL.revokeObjectURL = function(object_URL) {
			if (object_URL.substring(0, 5) !== "data:" && real_revoke_object_URL) {
				real_revoke_object_URL.call(real_URL, object_URL);
			}
		};
		FBB_proto.append = function(data/*, endings*/) {
			var bb = this.data;
			// decode data to a binary string
			if (Uint8Array && (data instanceof ArrayBuffer || data instanceof Uint8Array)) {
				var
					  str = ""
					, buf = new Uint8Array(data)
					, i = 0
					, buf_len = buf.length
				;
				for (; i < buf_len; i++) {
					str += String.fromCharCode(buf[i]);
				}
				bb.push(str);
			} else if (get_class(data) === "Blob" || get_class(data) === "File") {
				if (FileReaderSync) {
					var fr = new FileReaderSync;
					bb.push(fr.readAsBinaryString(data));
				} else {
					// async FileReader won't work as BlobBuilder is sync
					throw new FileException("NOT_READABLE_ERR");
				}
			} else if (data instanceof FakeBlob) {
				if (data.encoding === "base64" && atob) {
					bb.push(atob(data.data));
				} else if (data.encoding === "URI") {
					bb.push(decodeURIComponent(data.data));
				} else if (data.encoding === "raw") {
					bb.push(data.data);
				}
			} else {
				if (typeof data !== "string") {
					data += ""; // convert unsupported types to strings
				}
				// decode UTF-16 to binary string
				bb.push(unescape(encodeURIComponent(data)));
			}
		};
		FBB_proto.getBlob = function(type) {
			if (!arguments.length) {
				type = null;
			}
			return new FakeBlob(this.data.join(""), type, "raw");
		};
		FBB_proto.toString = function() {
			return "[object BlobBuilder]";
		};
		FB_proto.slice = function(start, end, type) {
			var args = arguments.length;
			if (args < 3) {
				type = null;
			}
			return new FakeBlob(
				  this.data.slice(start, args > 1 ? end : this.data.length)
				, type
				, this.encoding
			);
		};
		FB_proto.toString = function() {
			return "[object Blob]";
		};
		return FakeBlobBuilder;
	}(view));

	return function Blob(blobParts, options) {
		var type = options ? (options.type || "") : "";
		var builder = new BlobBuilder();
		if (blobParts) {
			for (var i = 0, len = blobParts.length; i < len; i++) {
				builder.append(blobParts[i]);
			}
		}
		return builder.getBlob(type);
	};
}(typeof self !== "undefined" && self || typeof window !== "undefined" && window || this.content || this));
<commit_msg>Remove Blob polyfill<commit_after><|endoftext|>


<commit_before>/* globals window */
'use strict';

var _       = require('underscore');
var Fluxxor = require('fluxxor');

/**
 * Usage:
 *
 * In stores.js:
 *
 * var params = {
 *     queries : {
 *         alias1 : 'some-media-query-string'
 *     },
 *     default : 'alias1'
 * };
 *
 * modules.exports = {
 *     MatchMediaStore : new MatchMediaStore(params)
 * };
 *
 * In a component's getStateFromFlux method:
 *
 * return {
 *     mq : matchMediaStore.getMatches()
 * };
 *
 * getMatches() returns an object of boolean values corresponding to the configured media queries
 * {
 *     alias1 : true
 * }
 *
 * When rendering on the server the default query is set to true
 */
module.exports = Fluxxor.createStore({

    initialize : function(params)
    {
        var defaultMatch, queries, store;

        store = this;

        if (_.has(params, 'queries') && _.has(params, 'default')) {
            queries      = params.queries;
            defaultMatch = params.default;
        } else {
            queries      = params;
            defaultMatch = null;
        }

        this.mqls = _.mapObject(queries, function (query, alias) {
            var mql = {
                matches : alias === defaultMatch
            };

            if (
                typeof window !== 'undefined' &&
                typeof window.matchMedia === 'function'
            ) {
                mql = window.matchMedia(query);

                mql.addListener(store.emit.bind(store, 'change'));
            }

            return mql;
        });
    },

    /**
     * Returns an object of boolean values corresponding to the currently matched media queries.
     */
    getMatches : function()
    {
        return _.mapObject(this.mqls, function (query) {
            return query.matches;
        });
    },

    fromObject : function(state)
    {
        this.mqls = state.mqls;
    },

    toObject : function()
    {
        return {
            mqls : this.mqls
        };
    }
});
<commit_msg>#44 | Only setting mqls if matchMedia is absent<commit_after>/* globals window */
'use strict';

var _       = require('underscore');
var Fluxxor = require('fluxxor');

/**
 * Usage:
 *
 * In stores.js:
 *
 * var params = {
 *     queries : {
 *         alias1 : 'some-media-query-string'
 *     },
 *     default : 'alias1'
 * };
 *
 * modules.exports = {
 *     MatchMediaStore : new MatchMediaStore(params)
 * };
 *
 * In a component's getStateFromFlux method:
 *
 * return {
 *     mq : matchMediaStore.getMatches()
 * };
 *
 * getMatches() returns an object of boolean values corresponding to the configured media queries
 * {
 *     alias1 : true
 * }
 *
 * When rendering on the server the default query is set to true
 */
module.exports = Fluxxor.createStore({

    initialize : function(params)
    {
        var defaultMatch, queries, store;

        store = this;

        if (_.has(params, 'queries') && _.has(params, 'default')) {
            queries      = params.queries;
            defaultMatch = params.default;
        } else {
            queries      = params;
            defaultMatch = null;
        }

        this.mqls = _.mapObject(queries, function (query, alias) {
            var mql = {
                matches : alias === defaultMatch
            };

            if (
                typeof window !== 'undefined' &&
                typeof window.matchMedia === 'function'
            ) {
                mql = window.matchMedia(query);

                mql.addListener(store.emit.bind(store, 'change'));
            }

            return mql;
        });
    },

    /**
     * Returns an object of boolean values corresponding to the currently matched media queries.
     */
    getMatches : function()
    {
        return _.mapObject(this.mqls, function (query) {
            return query.matches;
        });
    },

    fromObject : function(state)
    {
        if (
            typeof window === 'undefined' ||
            typeof window.matchMedia !== 'function'
        ) {
            this.mqls = state.mqls;
        }
    },

    toObject : function()
    {
        return {
            mqls : this.mqls
        };
    }
});
<|endoftext|>


<commit_before>'use strict';

let path = require('path');

const config = require('../../config');

let knex = require('knex')(config.knex);

const lwip = require('lwip');

const offset = 12;
const img_size = 88 + offset;

const img_dir = path.join(__dirname, '..', '..', 'data');

const table_name = 'drst_bot';

const ssr_rate = 0.015;
const sr_rate = 0.10;

class Card {
	initialize() {
		var self = this;

		self.cards = {
			ssr: [],
			sr: [],
			r: []
		};

		return new Promise((resolve, reject) => {
			return knex(table_name).then((rows) => {
				rows.forEach((row) => {
					Array.from(Array(row.multiplier)).forEach(() => {
						self.cards[row.rarity].push(row);
					});
				});
				knex.destroy();
				resolve();
			}).catch((e) => {
				console.log(e);
			});
		});
	}

	_pickCard(isSR) {
		let self = this;

		return new Promise((resolve, reject) => {
			let rarity = Math.random();

			if(isSR) {
				if(rarity < ssr_rate) {
					resolve(self.cards.ssr[parseInt(Math.random() * self.cards.ssr.length)]);
				}
				else {
					resolve(self.cards.sr[parseInt(Math.random() * self.cards.sr.length)]);
				}
			}
			else {
				let rarity = Math.random();
				if(rarity < ssr_rate) {
					resolve(self.cards.ssr[parseInt(Math.random() * self.cards.ssr.length)]);
				}
				else if(rarity < ssr_rate + sr_rate) {
					resolve(self.cards.sr[parseInt(Math.random() * self.cards.sr.length)]);
				}
				else {
					resolve(self.cards.r[parseInt(Math.random() * self.cards.r.length)]);
				}
			}
		});
	}

	pickSingleCard() {
		var self = this;

		return new Promise((resolve, reject) => {
			self._pickCard().then((card) => {
				resolve([card]);
			});
		});
	}

	pickMultipleCards() {
		var self = this;

		return new Promise((resolve, reject) => {
			let promises = Array.from(Array(10)).map((e, i) => {
				if(i === 9) {
					return self._pickCard(true);
				}
				else {
					return self._pickCard();
				}
			});
			Promise.all(promises).then((res) => {
				resolve(res);
			}).catch((e) => {
				console.log(e);
			});
		});
	}

	createImage(cards) {
		let self = this;

		return new Promise((resolve, reject) => {
			let width = img_size * (cards.length === 1 ? 1 : 5);
			let height = img_size * (cards.length === 1 ? 1 : 2);

			lwip.create(width, height, {
				a: 1,
				r: 1,
				g: 1,
				b: 1
			}, (err, image) => {
				cards.reduce((promise, card, i) => {
					return promise.then((image) => {
						return new Promise((resolve, reject) => {
							let filename = card.filename;
							let offset_width = (i % 5) * img_size;
							let offset_height = (i < 5 ? 0 : img_size);
							lwip.open(path.join(img_dir, filename), (err, src) => {
								image.paste(offset_width + offset / 2, offset_height + offset / 2, src, (err, dst) => {
									if(err) {
										throw new Error(err);
									}
									else {
										resolve(dst);
									}
								});
							});
						});
					});
				}, Promise.resolve(image)).then((dst) => {
					dst.toBuffer('png', function(err, buffer) {
						if(err) {
							throw new Error(err);
						}
						resolve(buffer);
					});
				}).catch((e) => {
					console.log(e);
				});
			});
		});
	}
}

module.exports = Card;

<commit_msg>fix multiplier<commit_after>'use strict';

let path = require('path');

const config = require('../../config');

let knex = require('knex')(config.knex);

const lwip = require('lwip');

const offset = 12;
const img_size = 88 + offset;

const img_dir = path.join(__dirname, '..', '..', 'data');

const table_name = 'drst_bot';

const ssr_rate = 0.015;
const sr_rate = 0.10;

class Card {
	initialize() {
		var self = this;

		self.cards = {
			ssr: [],
			sr: [],
			r: []
		};

		return new Promise((resolve, reject) => {
			return knex(table_name).where('multiplier', '!=', 0).then((rows) => {
				rows.forEach((row) => {
					Array.from(Array(row.multiplier)).forEach(() => {
						self.cards[row.rarity].push(row);
					});
				});
				knex.destroy();
				resolve();
			}).catch((e) => {
				console.log(e);
			});
		});
	}

	_pickCard(isSR) {
		let self = this;

		return new Promise((resolve, reject) => {
			let rarity = Math.random();

			if(isSR) {
				if(rarity < ssr_rate) {
					resolve(self.cards.ssr[parseInt(Math.random() * self.cards.ssr.length)]);
				}
				else {
					resolve(self.cards.sr[parseInt(Math.random() * self.cards.sr.length)]);
				}
			}
			else {
				let rarity = Math.random();
				if(rarity < ssr_rate) {
					resolve(self.cards.ssr[parseInt(Math.random() * self.cards.ssr.length)]);
				}
				else if(rarity < ssr_rate + sr_rate) {
					resolve(self.cards.sr[parseInt(Math.random() * self.cards.sr.length)]);
				}
				else {
					resolve(self.cards.r[parseInt(Math.random() * self.cards.r.length)]);
				}
			}
		});
	}

	pickSingleCard() {
		var self = this;

		return new Promise((resolve, reject) => {
			self._pickCard().then((card) => {
				resolve([card]);
			});
		});
	}

	pickMultipleCards() {
		var self = this;

		return new Promise((resolve, reject) => {
			let promises = Array.from(Array(10)).map((e, i) => {
				if(i === 9) {
					return self._pickCard(true);
				}
				else {
					return self._pickCard();
				}
			});
			Promise.all(promises).then((res) => {
				resolve(res);
			}).catch((e) => {
				console.log(e);
			});
		});
	}

	createImage(cards) {
		let self = this;

		return new Promise((resolve, reject) => {
			let width = img_size * (cards.length === 1 ? 1 : 5);
			let height = img_size * (cards.length === 1 ? 1 : 2);

			lwip.create(width, height, {
				a: 1,
				r: 1,
				g: 1,
				b: 1
			}, (err, image) => {
				cards.reduce((promise, card, i) => {
					return promise.then((image) => {
						return new Promise((resolve, reject) => {
							let filename = card.filename;
							let offset_width = (i % 5) * img_size;
							let offset_height = (i < 5 ? 0 : img_size);
							lwip.open(path.join(img_dir, filename), (err, src) => {
								image.paste(offset_width + offset / 2, offset_height + offset / 2, src, (err, dst) => {
									if(err) {
										throw new Error(err);
									}
									else {
										resolve(dst);
									}
								});
							});
						});
					});
				}, Promise.resolve(image)).then((dst) => {
					dst.toBuffer('png', function(err, buffer) {
						if(err) {
							throw new Error(err);
						}
						resolve(buffer);
					});
				}).catch((e) => {
					console.log(e);
				});
			});
		});
	}
}

module.exports = Card;

<|endoftext|>


<commit_before>dojo.provide("dojox.mobile.app._event");
dojo.experimental("dojox.mobile.app._event.js");

dojo.mixin(dojox.mobile.app, {
	eventMap: {},

	connectFlick: function(target, context, method){
		// summary:
		//		Listens for a flick event on a DOM node.  If the mouse/touch
		//		moves more than 15 pixels in any given direction it is a flick.
		//		The synthetic event fired specifies the direction as
		//

		//
'ltr' Left To Right

		//
'rtl' Right To Left

		//
'ttb' Top To Bottom

		//
'btt' Bottom To Top

		//

		// target: Node
		//		The DOM node to connect to

		var startX;
		var startY;
		var isFlick = false;

		var currentX;
		var currentY;

		var connMove;
		var connUp;

		var direction;

		var time;

		// Listen to to the mousedown/touchstart event
		var connDown = dojo.connect("onmousedown", target, function(event){
			isFlick = false;
			startX = event.targetTouches ? event.targetTouches[0].clientX : event.clientX;
			startY = event.targetTouches ? event.targetTouches[0].clientY : event.clientY;

			time = (new Date()).getTime();

			connMove = dojo.connect(target, "onmousemove", onMove);
			connUp = dojo.connect(target, "onmouseup", onUp);
		});

		// The function that handles the mousemove/touchmove event
		var onMove = function(event){
			dojo.stopEvent(event);

			currentX = event.targetTouches ? event.targetTouches[0].clientX : event.clientX;
			currentY = event.targetTouches ? event.targetTouches[0].clientY : event.clientY;
			if(Math.abs(Math.abs(currentX) - Math.abs(startX)) > 15){
				isFlick = true;

				direction = (currentX > startX) ? "ltr" : "rtl";
			}else if(Math.abs(Math.abs(currentY) - Math.abs(startY)) > 15){
				isFlick = true;

				direction = (currentY > startY) ? "ttb" : "btt";
			}
		};

		var onUp = function(event){
			dojo.stopEvent(event);

			connMove && dojo.disconnect(connMove);
			connUp && dojo.disconnect(connUp);

			if(isFlick){
				var flickEvt = {
					target: target,
					direction: direction,
					duration: (new Date()).getTime() - time
				};
				if(context && method){
					context[method](flickEvt);
				}else{
					method(flickEvt);
				}
			}
		};

	}
});

dojox.mobile.app.isIPhone = (dojo.isSafari
	&& (navigator.userAgent.indexOf("iPhone") > -1 ||
		navigator.userAgent.indexOf("iPod") > -1
	));
dojox.mobile.app.isWebOS = (navigator.userAgent.indexOf("webOS") > -1);

if(dojox.mobile.app.isIPhone){
	// We are touchable.
	// Override the dojo._connect function to replace mouse events with touch events

	dojox.mobile.app.eventMap = {
		onmousedown: "ontouchstart",
		mousedown: "ontouchstart",
		onmouseup: "ontouchend",
		mouseup: "ontouchend",
		onmousemove: "ontouchmove",
		mousemove: "ontouchmove"
	};

}
dojo._oldConnect = dojo._connect;
dojo._connect = function(obj, event, context, method, dontFix){
	event = dojox.mobile.app.eventMap[event] || event;
	if(event == "flick" || event == "onflick"){
		if(window["Mojo"]){
			event = Mojo.Event.flick;
		}else{
			return dojox.mobile.app.connectFlick(obj, context, method);
		}
	}

	return dojo._oldConnect(obj, event, context, method, dontFix);
}<commit_msg>Refs #12107 Adds support for touch events on Android !strict<commit_after>dojo.provide("dojox.mobile.app._event");
dojo.experimental("dojox.mobile.app._event.js");

dojo.mixin(dojox.mobile.app, {
	eventMap: {},

	connectFlick: function(target, context, method){
		// summary:
		//		Listens for a flick event on a DOM node.  If the mouse/touch
		//		moves more than 15 pixels in any given direction it is a flick.
		//		The synthetic event fired specifies the direction as
		//

		//
'ltr' Left To Right

		//
'rtl' Right To Left

		//
'ttb' Top To Bottom

		//
'btt' Bottom To Top

		//

		// target: Node
		//		The DOM node to connect to

		var startX;
		var startY;
		var isFlick = false;

		var currentX;
		var currentY;

		var connMove;
		var connUp;

		var direction;

		var time;

		// Listen to to the mousedown/touchstart event
		var connDown = dojo.connect("onmousedown", target, function(event){
			isFlick = false;
			startX = event.targetTouches ? event.targetTouches[0].clientX : event.clientX;
			startY = event.targetTouches ? event.targetTouches[0].clientY : event.clientY;

			time = (new Date()).getTime();

			connMove = dojo.connect(target, "onmousemove", onMove);
			connUp = dojo.connect(target, "onmouseup", onUp);
		});

		// The function that handles the mousemove/touchmove event
		var onMove = function(event){
			dojo.stopEvent(event);

			currentX = event.targetTouches ? event.targetTouches[0].clientX : event.clientX;
			currentY = event.targetTouches ? event.targetTouches[0].clientY : event.clientY;
			if(Math.abs(Math.abs(currentX) - Math.abs(startX)) > 15){
				isFlick = true;

				direction = (currentX > startX) ? "ltr" : "rtl";
			}else if(Math.abs(Math.abs(currentY) - Math.abs(startY)) > 15){
				isFlick = true;

				direction = (currentY > startY) ? "ttb" : "btt";
			}
		};

		var onUp = function(event){
			dojo.stopEvent(event);

			connMove && dojo.disconnect(connMove);
			connUp && dojo.disconnect(connUp);

			if(isFlick){
				var flickEvt = {
					target: target,
					direction: direction,
					duration: (new Date()).getTime() - time
				};
				if(context && method){
					context[method](flickEvt);
				}else{
					method(flickEvt);
				}
			}
		};

	}
});

dojox.mobile.app.isIPhone = (dojo.isSafari
	&& (navigator.userAgent.indexOf("iPhone") > -1 ||
		navigator.userAgent.indexOf("iPod") > -1
	));
dojox.mobile.app.isWebOS = (navigator.userAgent.indexOf("webOS") > -1);
dojox.mobile.app.isAndroid = (navigator.userAgent.toLowerCase().indexOf("android") > -1);

if(dojox.mobile.app.isIPhone || dojox.mobile.app.isAndroid){
	// We are touchable.
	// Override the dojo._connect function to replace mouse events with touch events

	dojox.mobile.app.eventMap = {
		onmousedown: "ontouchstart",
		mousedown: "ontouchstart",
		onmouseup: "ontouchend",
		mouseup: "ontouchend",
		onmousemove: "ontouchmove",
		mousemove: "ontouchmove"
	};

}
dojo._oldConnect = dojo._connect;
dojo._connect = function(obj, event, context, method, dontFix){
	event = dojox.mobile.app.eventMap[event] || event;
	if(event == "flick" || event == "onflick"){
		if(dojo.global["Mojo"]){
			event = Mojo.Event.flick;
		}else{
			return dojox.mobile.app.connectFlick(obj, context, method);
		}
	}

	return dojo._oldConnect(obj, event, context, method, dontFix);
}<|endoftext|>


<commit_before>var expect = require('chai').expect;

function toNumber(word){
  if ( word === "zero" ){
    return 0;
  }
  if ( word === "one" ){
    return 1;
  }
  if ( word === "two" ){
    return 2;
  }
  if ( word === "three" ){
    return 3;
  }
  if ( word === "four" ){
    return 4;
  }
  if ( word === "five" ){
    return 5;
  }
  if ( word === "six" ){
    return 6;
  }
  if ( word === "seven" ){
    return 7;
  }
  if ( word === "eight" ){
    return 8;
  }
  if ( word === "nine" ){
    return 9;
  }

}

function add(a, b){
  return (toNumber(a) + toNumber(b))

}

expect(toNumber("zero")).to.equal(0);
expect(toNumber("one")).to.equal(1);
<commit_msg>more functions with @jessyriordan<commit_after>var expect = require('chai').expect;

function toNumber(word){
  if ( word === "zero" ){
    return 0;
  }
  if ( word === "one" ){
    return 1;
  }
  if ( word === "two" ){
    return 2;
  }
  if ( word === "three" ){
    return 3;
  }
  if ( word === "four" ){
    return 4;
  }
  if ( word === "five" ){
    return 5;
  }
  if ( word === "six" ){
    return 6;
  }
  if ( word === "seven" ){
    return 7;
  }
  if ( word === "eight" ){
    return 8;
  }
  if ( word === "nine" ){
    return 9;
  }

}

function add(a, b){
  return (toNumber(a) + toNumber(b))

}

function minus(a, b){
  return (toNumber(a) - toNumber(b))
}

function multiply(a, b){
  return (toNumber(a) * toNumber(b))
}

function divide(a, b){
  return (toNumber(a) / toNumber(b))
}

expect(toNumber("zero")).to.equal(0);
expect(toNumber("one")).to.equal(1);
<|endoftext|>


<commit_before>define(['./wireClient', './store', './util'], function(wireClient, store, util) {

  "use strict";

  // Namespace: sync
  //
  // Sync is where all the magic happens. It connects the <store> and <wireClient>
  //

  var sync; // set below.

  var prefix = '_remoteStorage_', busy=false, stateCbs=[], syncOkNow=true;

  var logger = util.getLogger('sync');

  function getState(path) {//should also distinguish between synced and locally modified for the path probably
    if(busy) {
      return 'busy';
    } else {
      return 'connected';
    }
  }
  function setBusy(val) {
    busy=val;
    for(var i=0;i<stateCbs.length;i++) {
      stateCbs[i](val?'busy':'connected');
    }

    if(! val) {
    }
  }
  function on(eventType, cb) {
    if(eventType=='state') {
      stateCbs.push(cb);
    }
  }
  function dirMerge(dirPath, remote, cached, diff, force, access, startOne, finishOne, clearCb) {
    for(var i in remote) {
      if((!cached[i] && !diff[i]) || cached[i] < remote[i]) {//should probably include force and keep in this decision
        pullNode(dirPath+i, force, access, startOne, finishOne);
      }
    }
    for(var i in cached) {
      if(!remote[i] || cached[i] > remote[i]) {
        if(util.isDir(i)) {
          pullNode(dirPath+i, force, access, startOne, finishOne);
        } else {//recurse
          var childNode = store.getNode(dirPath+i);
          var childData = store.getNodeData(dirPath + i);
          startOne();
          if(typeof(childData) === 'object') {
            childData = JSON.stringify(childData);
          }
          wireClient.set(dirPath+i, childData, childNode.mimeType, function(err) {
            if(err) {
              logger.error('wireclient said error', err);
            }
            finishOne(err);
          });
        }
      }
    }
    for(var i in diff) {
      if(!cached[i]) {//outgoing delete
        if(remote[i]) {
          startOne();
          wireClient.set(dirPath+i, undefined, undefined, function(err) {
            finishOne();
          });
        } else {
          clearCb(i);
        }
      } else {
        clearCb(i);
      }
    }
  }

  function getFileName(path) {
    var parts = path.split('/');
    if(util.isDir(path)) {
      return parts[parts.length-2]+'/';
    } else {
      return parts[parts.length-1];
    }
  }

  function findForce(path, node) {
    console.log("findForce", path, node);
    if(! node) {
      return null;
    } else if(! node.startForce) {
      var parentPath = util.containingDir(path);
      if((!path) || (parentPath == path)) {
        return false;
      } else if(parentPath) {
        return findForce(parentPath, store.getNode(parentPath));
      }
    } else {
      return node.startForce;
    }
  }

  function hasDiff(parentPath, fname) {
    var parent = store.getNode(parentPath);
    return !! parent.diff[fname];
  }

  function pushNode(path, finishOne) {
    if(util.isDir(path)) {
      return;
    }
    logger.debug('pushNode', path);
    var parentPath = util.containingDir(path);
    var fname = getFileName(path)
    if(hasDiff(parentPath, fname)) {
      logger.debug('pushNode!', path);
      var data = store.getNodeData(path, true);
      var node = store.getNode(path);
      if(! data) {
        console.error("ATTEMPTED TO PUSH EMPTY DATA", node, data);
      }
      wireClient.set(path, data, node.mimeType, function(err) {
        logger.debug("wire client set result", arguments);
        if(! err) {
          store.clearDiff(parentPath, fname);
        } else {
          logger.error('pushNode', err);
        }
        finishOne(err);
      });
    }
  }

  function pullNode(path, force, access, startOne, finishOne) {
    var thisNode = store.getNode(path);
    var thisData = store.getNodeData(path);
    var isDir = util.isDir(path);
    if((! thisData) && isDir) {
      thisData = {};
    }
    logger.debug('pullNode "'+path+'"', thisNode);

    if(thisNode.startAccess == 'rw' || !access) {
      force = thisNode.startAccess;
    }

    if(! force) {
      force = findForce(path, thisNode);
    }

    startOne();

    if(force || access) {
      wireClient.get(path, function(err, data) {
        console.log("WIRE CLIENT SAID ERR", err);
        if(!err && data) {
          if(isDir) {
            dirMerge(path, data, thisData, thisNode.diff, force, access, startOne, finishOne, function(i) {
              store.clearDiff(path, i);
            });
          } else {
            store.setNodeData(path, data, false);
          }
        } else {
          if(isDir) {
            for(var key in thisData) {
              startOne();
              pushNode(path + key, finishOne);
            }
          } else {
            pushNode(path, finishOne);
          }
        }

        finishOne(err);

      });

      return;

    } else if(thisData && isDir) {
      for(var i in thisData) {
        if(util.isDir(i)) {
          pullNode(path+i, force, access, startOne, finishOne);
        }
      }
    }

    // this is an edge case, reached when all of the following are true:
    // * this is NOT a directory node
    // * neither this node nor any of it's parent have startForce set
    // * this node doesn't have it's startAccess flag set
    // * neither 'force' nor 'access' are forced by this pullNode call
    finishOne();

  }

  // TODO: DRY those two:

  function fetchNow(path, callback) {
    var outstanding = 0, errors=[];
    function startOne() {
      outstanding++;
    }
    function finishOne(err) {
      if(err) {
        errors.push(err);
      }
      outstanding--;
      if(outstanding == 0) {
        setBusy(false);
        callback(errors || null, store.getNode(path));
      }
    }
    setBusy(true);
    pullNode(path, false, true, startOne, finishOne)
  }

  function syncNow(path, callback) {

    if(! path) {
      throw "path is required";
    }

    if(! syncOkNow) {
      return callback(null);
    }

    if(wireClient.getState() == 'anonymous') {
      if(callback) {
        callback(['not connected']);
      }
      return;
    }

    var outstanding=0, errors=[];
    function startOne() {
      outstanding++;
    }
    function finishOne(err) {
      if(err) {
        errors.push(path);
      }
      outstanding--;
      if(outstanding==0) {
        setBusy(false);
        setTimeout(function() {
          syncOkNow = true;
        }, sync.minPollInterval);
        if(callback) {
          callback(errors.length > 0 ? errors : null);
        } else {
          console.log('syncNow done');
        }
      }
    }
    logger.info('syncNow '+path);
    setBusy(true);
    syncOkNow = false;
    pullNode(path, false, false, startOne, finishOne);
  }

  sync = {
    // Property: minPollInterval
    // Minimal interval between syncNow calls.
    // All calls that happen in between, immediately succeed
    // (call their callbacks) without doing anything.
    minPollInterval: 3000,
    syncNow: syncNow,
    fetchNow: fetchNow,
    getState : getState,
    on: on
  };

  return sync;

});
<commit_msg>fixed nested directory sync<commit_after>define(['./wireClient', './store', './util'], function(wireClient, store, util) {

  "use strict";

  // Namespace: sync
  //
  // Sync is where all the magic happens. It connects the <store> and <wireClient>
  //

  var sync; // set below.

  var prefix = '_remoteStorage_', busy=false, stateCbs=[], syncOkNow=true;

  var logger = util.getLogger('sync');

  function getState(path) {//should also distinguish between synced and locally modified for the path probably
    if(busy) {
      return 'busy';
    } else {
      return 'connected';
    }
  }
  function setBusy(val) {
    busy=val;
    for(var i=0;i<stateCbs.length;i++) {
      stateCbs[i](val?'busy':'connected');
    }

    if(! val) {
    }
  }
  function on(eventType, cb) {
    if(eventType=='state') {
      stateCbs.push(cb);
    }
  }
  function dirMerge(dirPath, remote, cached, diff, force, access, startOne, finishOne, clearCb) {
    for(var i in remote) {
      if((!cached[i] && !diff[i]) || cached[i] < remote[i]) {//should probably include force and keep in this decision
        pullNode(dirPath+i, force, access, startOne, finishOne);
      }
    }
    for(var i in cached) {
      if(!remote[i] || cached[i] > remote[i]) {
        if(util.isDir(i)) {
          pullNode(dirPath+i, force, access, startOne, finishOne);
        } else {//recurse
          var childNode = store.getNode(dirPath+i);
          var childData = store.getNodeData(dirPath + i);
          startOne();
          if(typeof(childData) === 'object') {
            childData = JSON.stringify(childData);
          }
          wireClient.set(dirPath+i, childData, childNode.mimeType, function(err) {
            if(err) {
              logger.error('wireclient said error', err);
            }
            finishOne(err);
          });
        }
      }
    }
    for(var i in diff) {
      if(!cached[i]) {//outgoing delete
        if(remote[i]) {
          startOne();
          wireClient.set(dirPath+i, undefined, undefined, function(err) {
            finishOne();
          });
        } else {
          clearCb(i);
        }
      } else {
        clearCb(i);
      }
    }
  }

  function getFileName(path) {
    var parts = path.split('/');
    if(util.isDir(path)) {
      return parts[parts.length-2]+'/';
    } else {
      return parts[parts.length-1];
    }
  }

  function findForce(path, node) {
    console.log("findForce", path, node);
    if(! node) {
      return null;
    } else if(! node.startForce) {
      var parentPath = util.containingDir(path);
      if((!path) || (parentPath == path)) {
        return false;
      } else if(parentPath) {
        return findForce(parentPath, store.getNode(parentPath));
      }
    } else {
      return node.startForce;
    }
  }

  function hasDiff(parentPath, fname) {
    var parent = store.getNode(parentPath);
    return !! parent.diff[fname];
  }

  function pushNode(path, startOne, finishOne) {
    if(util.isDir(path)) {
      var dirData = store.getNodeData(path);
      for(var key in dirData) {
        startOne();
        pushNode(path + key, startOne, finishOne);
      }
      return;
    }
    logger.debug('pushNode', path);
    var parentPath = util.containingDir(path);
    var fname = getFileName(path)
    if(hasDiff(parentPath, fname)) {
      logger.debug('pushNode!', path);
      var data = store.getNodeData(path, true);
      var node = store.getNode(path);
      if(! data) {
        console.error("ATTEMPTED TO PUSH EMPTY DATA", node, data);
      }
      wireClient.set(path, data, node.mimeType, function(err) {
        logger.debug("wire client set result", arguments);
        if(! err) {
          store.clearDiff(parentPath, fname);
        } else {
          logger.error('pushNode', err);
        }
        finishOne(err);
      });
    }
  }

  function pullNode(path, force, access, startOne, finishOne) {
    var thisNode = store.getNode(path);
    var thisData = store.getNodeData(path);
    var isDir = util.isDir(path);
    if((! thisData) && isDir) {
      thisData = {};
    }
    logger.debug('pullNode "'+path+'"', thisNode);

    if(thisNode.startAccess == 'rw' || !access) {
      force = thisNode.startAccess;
    }

    if(! force) {
      force = findForce(path, thisNode);
    }

    startOne();

    if(force || access) {
      wireClient.get(path, function(err, data) {
        console.log("WIRE CLIENT SAID ERR", err);
        if(!err && data) {
          if(isDir) {
            dirMerge(path, data, thisData, thisNode.diff, force, access, startOne, finishOne, function(i) {
              store.clearDiff(path, i);
            });
          } else {
            store.setNodeData(path, data, false);
          }
        } else {
          pushNode(path, startOne, finishOne);
        }

        finishOne(err);

      });

      return;

    } else if(thisData && isDir) {
      for(var i in thisData) {
        if(util.isDir(i)) {
          pullNode(path+i, force, access, startOne, finishOne);
        }
      }
    }

    // this is an edge case, reached when all of the following are true:
    // * this is NOT a directory node
    // * neither this node nor any of it's parent have startForce set
    // * this node doesn't have it's startAccess flag set
    // * neither 'force' nor 'access' are forced by this pullNode call
    finishOne();

  }

  // TODO: DRY those two:

  function fetchNow(path, callback) {
    var outstanding = 0, errors=[];
    function startOne() {
      outstanding++;
    }
    function finishOne(err) {
      if(err) {
        errors.push(err);
      }
      outstanding--;
      if(outstanding == 0) {
        setBusy(false);
        callback(errors || null, store.getNode(path));
      }
    }
    setBusy(true);
    pullNode(path, false, true, startOne, finishOne)
  }

  function syncNow(path, callback) {

    if(! path) {
      throw "path is required";
    }

    if(! syncOkNow) {
      return callback(null);
    }

    if(wireClient.getState() == 'anonymous') {
      if(callback) {
        callback(['not connected']);
      }
      return;
    }

    var outstanding=0, errors=[];
    function startOne() {
      outstanding++;
    }
    function finishOne(err) {
      if(err) {
        errors.push(path);
      }
      outstanding--;
      if(outstanding==0) {
        setBusy(false);
        setTimeout(function() {
          syncOkNow = true;
        }, sync.minPollInterval);
        if(callback) {
          callback(errors.length > 0 ? errors : null);
        } else {
          console.log('syncNow done');
        }
      }
    }
    logger.info('syncNow '+path);
    setBusy(true);
    syncOkNow = false;
    pullNode(path, false, false, startOne, finishOne);
  }

  sync = {
    // Property: minPollInterval
    // Minimal interval between syncNow calls.
    // All calls that happen in between, immediately succeed
    // (call their callbacks) without doing anything.
    minPollInterval: 3000,
    syncNow: syncNow,
    fetchNow: fetchNow,
    getState : getState,
    on: on
  };

  return sync;

});
<|endoftext|>


<commit_before>'use strict';

var arg = require('../util').arg;

var methods = {
    /** Higher order functions */
    each: function(func, context) {
        context = arg(context, this);

        this.data.forEach(func, context);
        return this;
    },
    map: function(func, context){
        return this.copy({
            data: this.data.map(func, context)
        });
    },
    mapEach: function(func, context) {
        context = arg(context, this);

        this.data = this.data.map(func, context);
        return this;
    },
    merge: function(heightmap, func, maxLength) {
        maxLength = arg(maxLength, Math.max(this.data.length, heightmap.data.length));

        var target = this,
            source = heightmap,
            i;
        for (i = 0; i < maxLength; i++) {
            var targetVal = target.data[i];
            var srcVal    = source.data[i];

            target.data[i] = func(targetVal, srcVal, i, target.data, source.data);
        }
        return this;
    },
};
module.exports = methods;
<commit_msg>added adjustEvery()<commit_after>'use strict';

var arg = require('../util').arg;

var methods = {
    each: function(func, context) {
        context = arg(context, this);

        this.data.forEach(func, context);
        return this;
    },
    map: function(func, context) {
        return this.copy({
            data: this.data.map(func, context)
        });
    },
    mapEach: function(func, context) {
        context = arg(context, this);

        this.data = this.data.map(func, context);
        return this;
    },
    merge: function(heightmap, func, maxLength) {
        maxLength = arg(maxLength, Math.max(this.data.length, heightmap.data.length));

        var target = this,
            source = heightmap,
            i;
        for (i = 0; i < maxLength; i++) {
            var targetVal = target.data[i];
            var srcVal    = source.data[i];

            target.data[i] = func(targetVal, srcVal, i, target.data, source.data);
        }
        return this;
    },
    adjustEvery: function(interval, func) {
        return this.mapEach(function(val, i, arr) {
            if (i % interval === 0) {
                return func(val, i, arr);
            } else {
                return val;
            }
        });
    }
};
module.exports = methods;
<|endoftext|>


<commit_before>'use strict';

/**
 * @file Manages Json Web Token authentication for our API
 * @module jwt
 */
const config = require(__dirname + '/config.js');

const _ = require('underscore');
const expressJWT = require('express-jwt');
const jwt = require('jsonwebtoken');
const users = require(__dirname + '/users.js');

/**
 * Express middleware to verify the JWT token (if any) and assigns it to req.user
 * @function authorize
 * @param {Object} db - Databse connection
 * @returns {function}
 */

function authorize(db) {
	return expressJWT({
		credentialsRequired: false, // We have our own way of handling if user is authorized or not
		secret   : config.jwt.secret,
		isRevoked: isRevoked(db),
		audience : config.hostedOn,
		issuer   : config.hostedOn
	});
}

/**
 * Function for determining if a JWT is valid or not
 * @function isRevoked
 *
 * @param {Object} db - Databse connection
 * @returns {isRevokedCallback}
 */

/**
 * Function that returns whether token is revoked or not.
 * @callback isRevokedCallback
 *
 * @param {Object} req - Express request object
 * @param {Object} payload - Object with the JWT claims
 * @param {isRevokedCallbackCallback} done - Callback
 */

/**
 * Returns whether token is revoked or not.
 * @callback isRevokedCallbackCallback
 * @param {Object} err - Null if success, error object if failure.
 *
 * @param {Boolean} revoked - True if the JWT is revoked, false otherwise.
 */

function isRevoked(db) {
	return (req, payload, done) => {
		if(typeof payload !== 'object') {
			done(null, true);
			return;
		}

		// Expiration date
		const expiration = payload.exp * 1000;
		// Make sure token hasn't expired yet
		const timeLeft = expiration - Date.now();
		// Make sure expiration is accurate within 30 seconds to account for time differences between computers.
		const clockTolerance = 30;

		if(timeLeft < (clockTolerance * -1000)) {
			done(null, true);
			return;
		}

		users.get(db, payload.user, (err, isUser, userDoc) => {
			if(err) {
				done(err, true);
				return;
			}
			if(!isUser) {
				done(null, true);
				return;
			}

			// Make sure token wasn't issued before last password change
			/*
			 * @TODO Automatically log user out in front-end on password change
			 * or prevent session that changed password from expiring.
			 */
			/* if(typeof userDoc['lastPasswordChange'] === 'object' && (payload.iat * 1000) < userDoc['lastPasswordChange'].getTime()) {
				done(null, true);
				return;
			}*/

			// Make sure token isn't blacklisted (usually if logged out)
			const jwt = req.get('Authorization').slice(7);

			isBlacklisted(db, jwt, (err, blacklisted) => {
				if(err) {
					done(err, true);
					return;
				}

				// Update 'lastVisited' field in user document
				const userdata = db.collection('users');
				userdata.update(userDoc, { $currentDate: { lastVisited: true }});

				done(null, blacklisted);
			});
		});
	};
}

/**
 * If no req.user is set, will default to false. Attach this middleware after you attach the authorize middleware!
 * @function fallback
 *
 * @param {Object} req - Express request object
 * @param {Object} res - Express response object
 * @param {function} next - Callback with no parameters
 */

function fallback(req, res, next) {
	// If user doesn't exist or is invalid, default req.user to false
	if(typeof req.user === 'undefined') {
		req.user = false;
	}
	next();
}

/**
 * Express middleware to catch any errors if the JWT token is invalid.
 * @function catchUnauthorized
 */

function catchUnauthorized(err, req, res, next) {
	if(err.name === 'UnauthorizedError') {
		res.status(401).json({ error: err.message });
		return;
	}
	next();
}

/**
 * Generates a JSON Web Token that should be stored on the client
 * and sent in the header with every API call required authentication.
 * @function generate
 *
 * @param {Object} db - Database connection
 * @param {string} user - Username
 * @param {generateCallback} callback - Callback
 */

/**
 * Returns a valid JWT token to give to user
 * @callback generateCallback
 *
 * @param {Object} err - Null if success, error object if failure.
 * @param {string} token - JWT token. Error if null.
 */

function generate(db, user, rememberMe, callback) {
	if(typeof callback !== 'function') return;

	if(typeof db !== 'object') {
		callback(new Error('Invalid database connection!'), null);
		return;
	}

	const expiration = rememberMe ? '30 days' : '12 hours';

	users.get(db, user, (err, isUser, userDoc) => {
		if(err) {
			callback(err, null);
			return;
		}
		if(!isUser) {
			callback(new Error('User doesn\'t exist!'), null);
			return;
		}

		// Default scope
		const scopes = {
			'pleb': true
		};

		if(_.isArray(userDoc['scopes'])) {
			for(const scope of userDoc['scopes']) {
				scopes[scope] = true;
			}
		}

		jwt.sign({
			user, scopes
		}, config.jwt.secret, {
			subject  : 'MyMICDS API',
			algorithm: 'HS256',
			expiresIn: expiration,
			audience : config.hostedOn,
			issuer   : config.hostedOn

		}, (err, token) => {
			if(err) {
				callback(new Error('There was a problem generating a JWT!'), null);
				return;
			}

			callback(null, token);

		});
	});
}

/**
 * Determines whether or not a JWT is in the JWTBlacklist collection, usually because they logged out.
 * @function isBlacklisted
 *
 * @param {Object} db - Database connection
 * @param {string} jwt - JSON Web Token
 * @param {isBlacklistedCallback} callback - Callback
 */

/**
 * Returns whether or not JWT is blacklisted.
 * @callback isBlacklistedCallback
 *
 * @param {Object} err - Null if success, error object if failure.
 * @param {Boolean} blacklisted - True if blacklisted, false if not. Null if error.
 */

function isBlacklisted(db, jwt, callback) {
	if(typeof callback !== 'function') return;

	if(typeof db !== 'object') {
		callback(new Error('Invalid database connection!'), null);
		return;
	}
	if(typeof jwt !== 'string') {
		callback(new Error('Invalid JWT!'), null);
		return;
	}

	const JWTdata = db.collection('JWTBlacklist');

	JWTdata.find({ jwt }).toArray((err, docs) => {
		if(err) {
			callback(new Error('There was a problem querying the database!'), null);
			return;
		}

		callback(null, docs.length > 0);

	});
}

/**
 * Revokes a JSON Web Token so it can't be used with the API anymore. Used typically for logging the user out.
 * @function revoke
 *
 * @param {Object} db - Database connection
 * @param {Object} payload - Object with all the claims of the JWT
 * @param {string} jwt - JSON Web Token to disable
 * @param {revokeCallback} callback - Callback
 */

/**
 * Returns whether revocation was successful or not.
 * @callback revokeCallback
 *
 * @param {Object} err - Null if success, error object if failure.
 */

function revoke(db, payload, jwt, callback) {
	if(typeof callback !== 'function') {
		callback = () => {};
	}

	if(typeof db !== 'object') {
		callback(new Error('Invalid database connection!'));
		return;
	}
	if(typeof payload !== 'object') {
		callback(new Error('Invalid payload!'));
		return;
	}
	if(typeof jwt !== 'string') {
		callback(new Error('Invalid JWT!'));
		return;
	}

	const current = new Date();
	const JWTdata = db.collection('JWTBlacklist');

	JWTdata.insert({
		user: payload.user,
		jwt,
		expires: new Date(payload.exp * 1000),
		revoked: current
	}, err => {
		if(err) {
			callback(new Error('There was a problem revoking the JWT in the database!'));
			return;
		}

		callback(null);

	});
}

module.exports.authorize         = authorize;
module.exports.fallback          = fallback;
module.exports.catchUnauthorized = catchUnauthorized;
module.exports.generate          = generate;
module.exports.revoke            = revoke;
<commit_msg>start conversion from blacklist to whitelist<commit_after>'use strict';

/**
 * @file Manages Json Web Token authentication for our API
 * @module jwt
 */
const config = require(__dirname + '/config.js');

const _ = require('underscore');
const expressJWT = require('express-jwt');
const jwt = require('jsonwebtoken');
const users = require(__dirname + '/users.js');

/**
 * Express middleware to verify the JWT token (if any) and assigns it to req.user
 * @function authorize
 * @param {Object} db - Databse connection
 * @returns {function}
 */

function authorize(db) {
	return expressJWT({
		credentialsRequired: false, // We have our own way of handling if user is authorized or not
		secret   : config.jwt.secret,
		isRevoked: isRevoked(db),
		audience : config.hostedOn,
		issuer   : config.hostedOn
	});
}

/**
 * Function for determining if a JWT is valid or not
 * @function isRevoked
 *
 * @param {Object} db - Databse connection
 * @returns {isRevokedCallback}
 */

/**
 * Function that returns whether token is revoked or not.
 * @callback isRevokedCallback
 *
 * @param {Object} req - Express request object
 * @param {Object} payload - Object with the JWT claims
 * @param {isRevokedCallbackCallback} done - Callback
 */

/**
 * Returns whether token is revoked or not.
 * @callback isRevokedCallbackCallback
 * @param {Object} err - Null if success, error object if failure.
 *
 * @param {Boolean} revoked - True if the JWT is revoked, false otherwise.
 */

function isRevoked(db) {
	return (req, payload, done) => {
		if(typeof payload !== 'object') {
			done(null, true);
			return;
		}

		// Expiration date
		const expiration = payload.exp * 1000;
		// Make sure token hasn't expired yet
		const timeLeft = expiration - Date.now();
		// Make sure expiration is accurate within 30 seconds to account for time differences between computers.
		const clockTolerance = 30;

		if(timeLeft < (clockTolerance * -1000)) {
			done(null, true);
			return;
		}

		users.get(db, payload.user, (err, isUser, userDoc) => {
			if(err) {
				done(err, true);
				return;
			}
			if(!isUser) {
				done(null, true);
				return;
			}

			// Make sure token wasn't issued before last password change
			/*
			 * @TODO Automatically log user out in front-end on password change
			 * or prevent session that changed password from expiring.
			 */
			/* if(typeof userDoc['lastPasswordChange'] === 'object' && (payload.iat * 1000) < userDoc['lastPasswordChange'].getTime()) {
				done(null, true);
				return;
			}*/

			// Make sure token isn't blacklisted (usually if logged out)
			const jwt = req.get('Authorization').slice(7);

			isBlacklisted(db, jwt, (err, blacklisted) => {
				if(err) {
					done(err, true);
					return;
				}

				// Update 'lastVisited' field in user document
				const userdata = db.collection('users');
				userdata.update(userDoc, { $currentDate: { lastVisited: true }});

				done(null, blacklisted);
			});
		});
	};
}

/**
 * If no req.user is set, will default to false. Attach this middleware after you attach the authorize middleware!
 * @function fallback
 *
 * @param {Object} req - Express request object
 * @param {Object} res - Express response object
 * @param {function} next - Callback with no parameters
 */

function fallback(req, res, next) {
	// If user doesn't exist or is invalid, default req.user to false
	if(typeof req.user === 'undefined') {
		req.user = false;
	}
	next();
}

/**
 * Express middleware to catch any errors if the JWT token is invalid.
 * @function catchUnauthorized
 */

function catchUnauthorized(err, req, res, next) {
	if(err.name === 'UnauthorizedError') {
		res.status(401).json({ error: err.message });
		return;
	}
	next();
}

/**
 * Generates a JSON Web Token that should be stored on the client
 * and sent in the header with every API call required authentication.
 * @function generate
 *
 * @param {Object} db - Database connection
 * @param {string} user - Username
 * @param {generateCallback} callback - Callback
 */

/**
 * Returns a valid JWT token to give to user
 * @callback generateCallback
 *
 * @param {Object} err - Null if success, error object if failure.
 * @param {string} token - JWT token. Error if null.
 */

function generate(db, user, rememberMe, callback) {
	if(typeof callback !== 'function') return;

	if(typeof db !== 'object') {
		callback(new Error('Invalid database connection!'), null);
		return;
	}

	const expiration = rememberMe ? '30 days' : '12 hours';

	users.get(db, user, (err, isUser, userDoc) => {
		if(err) {
			callback(err, null);
			return;
		}
		if(!isUser) {
			callback(new Error('User doesn\'t exist!'), null);
			return;
		}

		// Default scope
		const scopes = {
			'pleb': true
		};

		if(_.isArray(userDoc['scopes'])) {
			for(const scope of userDoc['scopes']) {
				scopes[scope] = true;
			}
		}

		jwt.sign({
			user, scopes
		}, config.jwt.secret, {
			subject  : 'MyMICDS API',
			algorithm: 'HS256',
			expiresIn: expiration,
			audience : config.hostedOn,
			issuer   : config.hostedOn

		}, (err, token) => {
			if(err) {
				callback(new Error('There was a problem generating a JWT!'), null);
				return;
			}

			const JWTdata = db.collection('JWTWhitelist');
			JWTdata.insertOne({ user, jwt: token }, err => {
				if(err) {
					callback(new Error('There was a problem registering the JWT!'), null);
					return;
				}

				callback(null, token);
			});
		});
	});
}

/**
 * Determines whether or not a JWT is in the JWTBlacklist collection, usually because they logged out.
 * @function isBlacklisted
 *
 * @param {Object} db - Database connection
 * @param {string} jwt - JSON Web Token
 * @param {isBlacklistedCallback} callback - Callback
 */

/**
 * Returns whether or not JWT is blacklisted.
 * @callback isBlacklistedCallback
 *
 * @param {Object} err - Null if success, error object if failure.
 * @param {Boolean} blacklisted - True if blacklisted, false if not. Null if error.
 */

function isBlacklisted(db, jwt, callback) {
	if(typeof callback !== 'function') return;

	if(typeof db !== 'object') {
		callback(new Error('Invalid database connection!'), null);
		return;
	}
	if(typeof jwt !== 'string') {
		callback(new Error('Invalid JWT!'), null);
		return;
	}

	const JWTdata = db.collection('JWTWhitelist');

	JWTdata.find({ jwt }).toArray((err, docs) => {
		if(err) {
			callback(new Error('There was a problem querying the database!'), null);
			return;
		}

		callback(null, docs.length < 1);
	});
}

/**
 * Revokes a JSON Web Token so it can't be used with the API anymore. Used typically for logging the user out.
 * @function revoke
 *
 * @param {Object} db - Database connection
 * @param {Object} payload - Object with all the claims of the JWT
 * @param {string} jwt - JSON Web Token to disable
 * @param {revokeCallback} callback - Callback
 */

/**
 * Returns whether revocation was successful or not.
 * @callback revokeCallback
 *
 * @param {Object} err - Null if success, error object if failure.
 */

function revoke(db, payload, jwt, callback) {
	if(typeof callback !== 'function') {
		callback = () => {};
	}

	if(typeof db !== 'object') {
		callback(new Error('Invalid database connection!'));
		return;
	}
	if(typeof payload !== 'object') {
		callback(new Error('Invalid payload!'));
		return;
	}
	if(typeof jwt !== 'string') {
		callback(new Error('Invalid JWT!'));
		return;
	}

	const JWTdata = db.collection('JWTWhitelist');

	JWTdata.deleteOne({ user: payload.user, jwt }, err => {
		if(err) {
			callback(new Error('There was a problem revoking the JWT in the database!'));
			return;
		}

		callback(null);
	});
}

module.exports.authorize         = authorize;
module.exports.fallback          = fallback;
module.exports.catchUnauthorized = catchUnauthorized;
module.exports.generate          = generate;
module.exports.revoke            = revoke;
<|endoftext|>