<commit_before>/* Lottery Plug-in
 * A chat plug-in for a lottery system for PS
 * by: panpawn
 */
if (typeof Gold === 'undefined') global.Gold = {};

Gold.lottery = {
    ticketPrice: 1, //default ticket price of lottery ticket
    pot: 0,
    gameActive: false,
    maxTicketsPerUser: 10, //defualt max ticket cap for users to buy in the lottery
    maxTicketPrice: 20, //Max ticket price
    players: [],
    updatePot: function(amount) {
        this.pot = (this.pot + (amount*2));
    },
    resetGame: function() {
        this.pot = 0;
        this.players = [];
        this.gameActive = false;
    }
};

exports.commands = {
    loto: 'lottery',
    lotto: 'lottery',
    lottery: function(target, room, user) {
    	var parts = target.split(',');
		for (var u in parts) parts[u] = parts[u].trim();
		if (room.id !== 'gamechamber') return this.errorReply("You must be in Game Chamber to use this command.");
		if (!Rooms.get('gamechamber')) return this.errorReply("You must have the room \"Game Chamber\" in order to use this script.");
        switch (toId(parts[0])) {
            case 'buy':
            case 'join':
                if (!Gold.lottery.gameActive) return this.errorReply("The game of lottery is not currently running.");
                if (economy.readMoney(toId(user.name)) < Gold.lottery.ticketPrice) return this.errorReply("You do not have enough bucks to partake in this game of Lottery.  Sorry.");
                var filteredPlayerArray = Gold.lottery.players.filter(function(username) {
                    return username === toId(user.name);
                });
                if (filteredPlayerArray.length >= Gold.lottery.maxTicketsPerUser) {
                    return this.errorReply("You cannot get more than " + Gold.lottery.maxTicketsPerUser + " tickets for this game of lotto.");
                }
                economy.writeMoney('money', toId(user.name), -Gold.lottery.ticketPrice);
                Gold.lottery.updatePot(Gold.lottery.ticketPrice);
                Rooms.get('gamechamber').add("|raw|" + user.name + " has bought a lottery ticket.");
                Gold.lottery.players.push(toId(user.name));
                break;
            case 'new':
            case 'create':
                if (!this.can('ban', null, room)) return this.errorReply("Access denied.");
                if (Gold.lottery.gameActive) return this.errorReply("There is a game of Lottery already currently running.");
                if (!parts[1]) return this.errorReply("Usage: /lottery create, [ticket cost]");
                if (isNaN(Number(parts[1]))) return this.errorReply('The pot must be a number greater than 0');
                if (parts[1] > Gold.lottery.maxTicketPrice) return this.errorReply("Lottery tickets cannot cost more than " + Gold.lottery.maxTicketPrice + " bucks.");
                Gold.lottery.ticketPrice = parts[1];
                Gold.lottery.gameActive = true;
                var room_notification =
                    "
Lottery Game!
" +
                    "(Started by: " + Tools.escapeHTML(user.name) + ")
" +
                    "A game of lottery has been started!  Cost to join is " + Gold.lottery.ticketPrice + " Gold bucks.
" +
                    "To buy a ticket, do /lotto join. (Max tickets per user: " + Gold.lottery.maxTicketsPerUser + ")
";
                if (parts[2] === 'pmall') {
                    if (!this.can('hotpatch')) return this.errorReply("Access denied.");
                    var loto_notification =
                        "Lottery Game!
" +
                        "A game of Lottery has started in <button name=\"send\" value=\"/join gamechamber\">Game Chamber</button>!
" +
                        "The ticket cost to join is  " + Gold.lottery.ticketPrice + " Gold Bucks.  For every ticket bought, the server automatically matches that price towards the pot.
" +
                        "(For more information, hop in the room and do /lotto or ask for help!)";
                    var pmName = '~Gold Lottery';
		            for (var i in Users.users) {
		            	var message = '|pm|' + pmName + '|' + Users.users[i].getIdentity() + '|/html ' + loto_notification;
		            	Users.users[i].send(message);
		            }
                    Rooms.get('gamechamber').add('|raw|' + room_notification);
                } else {
                    Rooms.get('gamechamber').add('|raw|' + room_notification);
                }
                break;
            case 'end':
                if (!this.can('ban', null, room)) return this.errorReply("Access denied.");
                if (!Gold.lottery.gameActive) return this.errorReply("There is no active game of lottery currently running.");
                var winner = Gold.lottery.players[Math.floor(Math.random() * Gold.lottery.players.length)];
                var jackpot = Math.floor(100 * Math.random()) + 1;
                if (!Gold.lottery.pot == 0) {
                    if (jackpot == 100) {
                        Rooms.get("gamechamber").add('|raw|<blink>JACKPOT!</blink>');
                        Rooms.get("gamechamber").add('|raw|' + winner + ' has won the game of lottery for ' + (Gold.lottery.pot * 2) + ' bucks!');
                         economy.writeMoney('money', toId(winner), Gold.lottery.pot*2);
                    } else {
                        economy.writeMoney('money', toId(winner), Gold.lottery.pot);
                        Rooms.get("gamechamber").add('|raw|' + winner + ' has won the game of lottery for ' + Gold.lottery.pot + ' bucks!');
                        Gold.lottery.resetGame();
                    }
                } else if (Gold.lottery.pot === 0) {
                    this.add('|raw|This game has been cancelled due to a lack of players by ' + Tools.escapeHTML(toId(user.name)) + '.');
                    Gold.lottery.resetGame();
                }
                break;
            case 'setlimit':
                if (!this.can('hotpatch')) return this.errorReply("Access denied.");
                if (!parts[1]) return this.errorReply("Usage: /lotto setlimit, [limit of tickets per user].");
                if (isNaN(Number(parts[1]))) return this.errorReply('The pot must be a number greater than 0');
                Gold.lottery.maxTicketsPerUser = parts[1];
                this.add('|raw|' + Tools.escapeHTML(user.name) + ' has changed the lottery ticket cap to: ' + Gold.lottery.maxTicketsPerUser + '.');
                break;
            case 'limit':
                return this.sendReply("The current cap of lottery tickets per user is: " + Gold.lottery.maxTicketsPerUser);
                break;
            case 'tickets':
                if (!this.canBroadcast()) return;
                if (!Gold.lottery.gameActive) return this.errorReply("There is no active game of lottery currently running.");
                return this.sendReplyBox("Current tickets: (" + Gold.lottery.players.length + ")
 " + Gold.lottery.players);
                break;
            case 'pot':
                if (!this.canBroadcast()) return;
                if (!Gold.lottery.gameActive) return this.errorReply("There is no active game of lottery currently running.");
                return this.sendReplyBox("The current lottery pot is worth: " + Gold.lottery.pot + " bucks.");
                break;
            default:
                if (!this.canBroadcast()) return;
                this.sendReplyBox(
                    "Lottery Commands
" +
                    "(By: panpawn)
" +
                    "/lotto create, [ticket price] - Starts a game of lotto with the respected ticket price. (Requires @, #, &, ~)
" +
                    "/lotto create, [ticket price], pmall - Starts a game of lotto with the respected ticket price AND notifies everyone. (Requires ~)
" +
                    "/lotto join OR /loto buy - Buys 1 ticket for the current game of loto (no cap set as of now).
" +
                    "/lotto end - Picks a winner of the lotto.  (Requires @, #, &, ~)
" +
                    "/lotto setlimit, [ticketcap] - Sets the cap of tickets per user.  (Requires ~)
" +
                    "/lotto pot - Shows the current pot of the game of lotto.
" +
                    "/lotto tickets - Shows all of the current tickets in the current game of lotto."
                );
        }
    }
};<commit_msg>Remove trailing whitespace<commit_after>/* Lottery Plug-in
 * A chat plug-in for a lottery system for PS
 * by: panpawn
 */
if (typeof Gold === 'undefined') global.Gold = {};

Gold.lottery = {
    ticketPrice: 1, //default ticket price of lottery ticket
    pot: 0,
    gameActive: false,
    maxTicketsPerUser: 10, //defualt max ticket cap for users to buy in the lottery
    maxTicketPrice: 20, //Max ticket price
    players: [],
    updatePot: function(amount) {
        this.pot = (this.pot + (amount*2));
    },
    resetGame: function() {
        this.pot = 0;
        this.players = [];
        this.gameActive = false;
    }
};

exports.commands = {
    loto: 'lottery',
    lotto: 'lottery',
    lottery: function(target, room, user) {
    	var parts = target.split(',');
	for (var u in parts) parts[u] = parts[u].trim();
	if (room.id !== 'gamechamber') return this.errorReply("You must be in Game Chamber to use this command.");
	if (!Rooms.get('gamechamber')) return this.errorReply("You must have the room \"Game Chamber\" in order to use this script.");
        switch (toId(parts[0])) {
            case 'buy':
            case 'join':
                if (!Gold.lottery.gameActive) return this.errorReply("The game of lottery is not currently running.");
                if (economy.readMoney(toId(user.name)) < Gold.lottery.ticketPrice) return this.errorReply("You do not have enough bucks to partake in this game of Lottery.  Sorry.");
                var filteredPlayerArray = Gold.lottery.players.filter(function(username) {
                    return username === toId(user.name);
                });
                if (filteredPlayerArray.length >= Gold.lottery.maxTicketsPerUser) {
                    return this.errorReply("You cannot get more than " + Gold.lottery.maxTicketsPerUser + " tickets for this game of lotto.");
                }
                economy.writeMoney('money', toId(user.name), -Gold.lottery.ticketPrice);
                Gold.lottery.updatePot(Gold.lottery.ticketPrice);
                Rooms.get('gamechamber').add("|raw|" + user.name + " has bought a lottery ticket.");
                Gold.lottery.players.push(toId(user.name));
                break;
            case 'new':
            case 'create':
                if (!this.can('ban', null, room)) return this.errorReply("Access denied.");
                if (Gold.lottery.gameActive) return this.errorReply("There is a game of Lottery already currently running.");
                if (!parts[1]) return this.errorReply("Usage: /lottery create, [ticket cost]");
                if (isNaN(Number(parts[1]))) return this.errorReply('The pot must be a number greater than 0');
                if (parts[1] > Gold.lottery.maxTicketPrice) return this.errorReply("Lottery tickets cannot cost more than " + Gold.lottery.maxTicketPrice + " bucks.");
                Gold.lottery.ticketPrice = parts[1];
                Gold.lottery.gameActive = true;
                var room_notification =
                    "
Lottery Game!
" +
                    "(Started by: " + Tools.escapeHTML(user.name) + ")
" +
                    "A game of lottery has been started!  Cost to join is " + Gold.lottery.ticketPrice + " Gold bucks.
" +
                    "To buy a ticket, do /lotto join. (Max tickets per user: " + Gold.lottery.maxTicketsPerUser + ")
";
                if (parts[2] === 'pmall') {
                    if (!this.can('hotpatch')) return this.errorReply("Access denied.");
                    var loto_notification =
                        "Lottery Game!
" +
                        "A game of Lottery has started in <button name=\"send\" value=\"/join gamechamber\">Game Chamber</button>!
" +
                        "The ticket cost to join is  " + Gold.lottery.ticketPrice + " Gold Bucks.  For every ticket bought, the server automatically matches that price towards the pot.
" +
                        "(For more information, hop in the room and do /lotto or ask for help!)";
                    var pmName = '~Gold Lottery';
		            for (var i in Users.users) {
		            	var message = '|pm|' + pmName + '|' + Users.users[i].getIdentity() + '|/html ' + loto_notification;
		            	Users.users[i].send(message);
		            }
                    Rooms.get('gamechamber').add('|raw|' + room_notification);
                } else {
                    Rooms.get('gamechamber').add('|raw|' + room_notification);
                }
                break;
            case 'end':
                if (!this.can('ban', null, room)) return this.errorReply("Access denied.");
                if (!Gold.lottery.gameActive) return this.errorReply("There is no active game of lottery currently running.");
                var winner = Gold.lottery.players[Math.floor(Math.random() * Gold.lottery.players.length)];
                var jackpot = Math.floor(100 * Math.random()) + 1;
                if (!Gold.lottery.pot == 0) {
                    if (jackpot == 100) {
                        Rooms.get("gamechamber").add('|raw|<blink>JACKPOT!</blink>');
                        Rooms.get("gamechamber").add('|raw|' + winner + ' has won the game of lottery for ' + (Gold.lottery.pot * 2) + ' bucks!');
                         economy.writeMoney('money', toId(winner), Gold.lottery.pot*2);
                    } else {
                        economy.writeMoney('money', toId(winner), Gold.lottery.pot);
                        Rooms.get("gamechamber").add('|raw|' + winner + ' has won the game of lottery for ' + Gold.lottery.pot + ' bucks!');
                        Gold.lottery.resetGame();
                    }
                } else if (Gold.lottery.pot === 0) {
                    this.add('|raw|This game has been cancelled due to a lack of players by ' + Tools.escapeHTML(toId(user.name)) + '.');
                    Gold.lottery.resetGame();
                }
                break;
            case 'setlimit':
                if (!this.can('hotpatch')) return this.errorReply("Access denied.");
                if (!parts[1]) return this.errorReply("Usage: /lotto setlimit, [limit of tickets per user].");
                if (isNaN(Number(parts[1]))) return this.errorReply('The pot must be a number greater than 0');
                Gold.lottery.maxTicketsPerUser = parts[1];
                this.add('|raw|' + Tools.escapeHTML(user.name) + ' has changed the lottery ticket cap to: ' + Gold.lottery.maxTicketsPerUser + '.');
                break;
            case 'limit':
                return this.sendReply("The current cap of lottery tickets per user is: " + Gold.lottery.maxTicketsPerUser);
                break;
            case 'tickets':
                if (!this.canBroadcast()) return;
                if (!Gold.lottery.gameActive) return this.errorReply("There is no active game of lottery currently running.");
                return this.sendReplyBox("Current tickets: (" + Gold.lottery.players.length + ")
 " + Gold.lottery.players);
                break;
            case 'pot':
                if (!this.canBroadcast()) return;
                if (!Gold.lottery.gameActive) return this.errorReply("There is no active game of lottery currently running.");
                return this.sendReplyBox("The current lottery pot is worth: " + Gold.lottery.pot + " bucks.");
                break;
            default:
                if (!this.canBroadcast()) return;
                this.sendReplyBox(
                    "Lottery Commands
" +
                    "(By: panpawn)
" +
                    "/lotto create, [ticket price] - Starts a game of lotto with the respected ticket price. (Requires @, #, &, ~)
" +
                    "/lotto create, [ticket price], pmall - Starts a game of lotto with the respected ticket price AND notifies everyone. (Requires ~)
" +
                    "/lotto join OR /loto buy - Buys 1 ticket for the current game of loto (no cap set as of now).
" +
                    "/lotto end - Picks a winner of the lotto.  (Requires @, #, &, ~)
" +
                    "/lotto setlimit, [ticketcap] - Sets the cap of tickets per user.  (Requires ~)
" +
                    "/lotto pot - Shows the current pot of the game of lotto.
" +
                    "/lotto tickets - Shows all of the current tickets in the current game of lotto."
                );
        }
    }
};
<|endoftext|>


<commit_before>var cacheDB = require('./cachedb');
var Cache = require('./cache');

function CacheStorage() {
  this._origin = location.origin;
}

var CacheStorageProto = CacheStorage.prototype;

CacheStorageProto._vendCache = function(name) {
  var cache = new Cache();
  cache._name = name;
  cache._origin = this._origin;
  return cache;
};

CacheStorageProto.match = function(request, params) {
  return cacheDB.matchAcrossCaches(this._origin, request, params);
};

CacheStorageProto.has = function(name) {
  return cacheDB.hasCache(this._origin, name);
};

CacheStorageProto.open = function(name) {
  return cacheDB.openCache(this._origin, name).then(function() {
    return this._vendCache(name);
  }.bind(this));
};

CacheStorageProto.delete = function(name) {
  return cacheDB.deleteCache(this._origin, name);
};

CacheStorageProto.keys = function() {
  return cacheDB.cacheNames(this._origin);
};

self.cachesPolyfill = module.exports = new CacheStorage();
<commit_msg>Global crept in twice<commit_after>var cacheDB = require('./cachedb');
var Cache = require('./cache');

function CacheStorage() {
  this._origin = location.origin;
}

var CacheStorageProto = CacheStorage.prototype;

CacheStorageProto._vendCache = function(name) {
  var cache = new Cache();
  cache._name = name;
  cache._origin = this._origin;
  return cache;
};

CacheStorageProto.match = function(request, params) {
  return cacheDB.matchAcrossCaches(this._origin, request, params);
};

CacheStorageProto.has = function(name) {
  return cacheDB.hasCache(this._origin, name);
};

CacheStorageProto.open = function(name) {
  return cacheDB.openCache(this._origin, name).then(function() {
    return this._vendCache(name);
  }.bind(this));
};

CacheStorageProto.delete = function(name) {
  return cacheDB.deleteCache(this._origin, name);
};

CacheStorageProto.keys = function() {
  return cacheDB.cacheNames(this._origin);
};

module.exports = new CacheStorage();
<|endoftext|>


<commit_before>'use strict'

import Rx, {Observable as O} from 'rx'
import {demux} from 'muxer'
import R from 'ramda'

export const fromCB = R.compose(R.apply, O.fromNodeCallback)

export const FILE = R.curry((fs) => {
  return [{
    // New Methods
    open: signal$ => signal$.flatMap(fromCB(fs.open)),
    fstat: signal$ => signal$.flatMap(fromCB(fs.fstat)),
    read: signal$ => signal$.flatMap(fromCB(fs.read)),
    write: signal$ => signal$.flatMap(fromCB(fs.write)),
    close: signal$ => signal$.flatMap(fromCB(fs.close)),
    truncate: signal$ => signal$.flatMap(fromCB(fs.truncate)),
    rename: signal$ => signal$.flatMap(fromCB(fs.rename))
  }]
})

export const HTTP = R.curry((request) => {
  const requestBody = (params) => Rx.Observable.create((observer) => request(params)
    .on('data', (message) => observer.onNext({event: 'data', message}))
    .on('response', (message) => observer.onNext({event: 'response', message}))
    .on('complete', (message) => observer.onCompleted({event: 'completed', message}))
    .on('error', (error) => observer.onError(error))
  )

  const requestHead = (params) => requestBody(params)
    .first()
    .pluck('message')
    .tap((x) => x.destroy())

  const select = R.curry((event, request$) => request$.filter(x => x.event === event).pluck('message'))
  const executor = (signal$) => {
    const [{destroy$}] = demux(signal$, 'destroy$')
    destroy$.subscribe(request => request.destroy())
  }
  return [{
    // TODO: DEPRECATE
    requestBody,
    requestHead,
    select,
    // UPDATED METHODS
    request: signal$ => signal$.flatMap(requestBody)
  }, executor]
})
<commit_msg>refactor(transformers): use shareReplay(1)<commit_after>'use strict'

import Rx, {Observable as O} from 'rx'
import {demux} from 'muxer'
import R from 'ramda'

export const fromCB = R.compose(R.apply, O.fromNodeCallback)

export const FILE = R.curry((fs) => {
  return [{
    // New Methods
    open: signal$ => signal$.flatMap(fromCB(fs.open)).shareReplay(1),
    fstat: signal$ => signal$.flatMap(fromCB(fs.fstat)).shareReplay(1),
    read: signal$ => signal$.flatMap(fromCB(fs.read)).shareReplay(1),
    write: signal$ => signal$.flatMap(fromCB(fs.write)).shareReplay(1),
    close: signal$ => signal$.flatMap(fromCB(fs.close)).shareReplay(1),
    truncate: signal$ => signal$.flatMap(fromCB(fs.truncate)).shareReplay(1),
    rename: signal$ => signal$.flatMap(fromCB(fs.rename)).shareReplay(1)
  }]
})

export const HTTP = R.curry((request) => {
  const requestBody = (params) => Rx.Observable.create((observer) => request(params)
    .on('data', (message) => observer.onNext({event: 'data', message}))
    .on('response', (message) => observer.onNext({event: 'response', message}))
    .on('complete', (message) => observer.onCompleted({event: 'completed', message}))
    .on('error', (error) => observer.onError(error))
  )

  const requestHead = (params) => requestBody(params)
    .first()
    .pluck('message')
    .tap((x) => x.destroy())

  const select = R.curry((event, request$) => request$.filter(x => x.event === event).pluck('message'))
  const executor = (signal$) => {
    const [{destroy$}] = demux(signal$, 'destroy$')
    destroy$.subscribe(request => request.destroy())
  }
  return [{
    // TODO: DEPRECATE
    requestBody,
    requestHead,
    select,
    // UPDATED METHODS
    request: signal$ => signal$.flatMap(requestBody).shareReplay(1)
  }, executor]
})
<|endoftext|>


<commit_before>/**
 * The main module
 *
 * @context atl.general
 */
var $ = require('speakeasy/jquery').jQuery;
var images = $.map(
        [ 'alert.jpg', 'bad_ass.jpg', 'thumb_up.jpg' ],
        function(img) {
            return require('speakeasy/resources').getImageUrl(module, img);
        });

$(document).ready(function() {
    var imageIndex = Math.floor(4 * Math.random());
    $('.welcome-message').prepend('

');

    $('#chuck').bind('click', function() {
        $.ajax({
            url: 'http://api.icndb.com/jokes/random',
            success: function(data) {
                var joke = JSON.parse(data);

                if (joke.type === 'success') {
                    $('#chuck-norris-facts').html(joke.value.joke);
                }
                $('#chuckImage').attr('src', images[imageIndex]);
                imageIndex = (imageIndex + 1) % 3;
            }
        });
    }).trigger('click');
});
<commit_msg>Edit from the UI<commit_after>/**
 * The main module
 *
 * @context atl.general
 */
var $ = require('speakeasy/jquery').jQuery;
var images = $.map(
        [ 'alert.jpg', 'bad_ass.jpg', 'thumb_up.jpg' ],
        function(img) {
            return require('speakeasy/resources').getImageUrl(module, img);
        });

$(document).ready(function() {
    var imageIndex = Math.floor(4 * Math.random());
    $('.welcome-message').prepend('

');
    $('#chuckImage').attr('src', images[imageIndex]);

    $('#chuck').bind('click', function() {
        $.ajax({
            url: 'http://api.icndb.com/jokes/random',
            success: function(data) {
                var joke = JSON.parse(data);

                if (joke.type === 'success') {
                    $('#chuck-norris-facts').html(joke.value.joke);
                }
                $('#chuckImage').attr('src', images[imageIndex]);
                imageIndex = (imageIndex + 1) % 3;
            }
        });
    }).trigger('click');
});
<|endoftext|>


<commit_before>'use strict';

const Hoek = require('hoek');
var MongooseConnector = require('./MongooseConnector');

const internals = {};

internals.defaults = {
  uri: 'mongodb://126.0.0.1:27017',
  async: false
};

exports.register = (server, options, next) => {
  const settings = Hoek.applyToDefaults(internals.defaults, options);
  let connector = new MongooseConnector(settings);
  let connection = connector.connection;

  connector.on('ready', () => {
    let getConnection = () => connection;

    server.method('mongoose', getConnection, {});

    next();
  });

  connector.on('error', err => next(err));
};

exports.register.attributes = {
  pkg: require('../package.json')
};
<commit_msg>Minor typo. Fixes missing log error.<commit_after>'use strict';

const Hoek = require('hoek');
var MongooseConnector = require('./MongooseConnector');

const internals = {};

internals.defaults = {
  uri: 'mongodb://126.0.0.1:27017',
  async: false
};

exports.register = (server, options, next) => {
  const settings = Hoek.applyToDefaults(internals.defaults, options);
  let connector = new MongooseConnector(settings, server);
  let connection = connector.connection;

  connector.on('ready', () => {
    let getConnection = () => connection;

    server.method('mongoose', getConnection, {});

    next();
  });

  connector.on('error', err => next(err));
};

exports.register.attributes = {
  pkg: require('../package.json')
};
<|endoftext|>


<commit_before>'use strict';

const FS = require('./../lib/fs');

const DISHES_FILE = 'config/chat-plugins/thecafe-foodfight.json';
const FOODFIGHT_COOLDOWN = 5 * 60 * 1000;

const thecafe = /** @type {ChatRoom} */ (Rooms.get('thecafe'));

/** @type {{[k: string]: string[]}} */
let dishes = {};
try {
	dishes = require(`../${DISHES_FILE}`);
} catch (e) {
	if (e.code !== 'MODULE_NOT_FOUND' && e.code !== 'ENOENT') throw e;
}
if (!dishes || typeof dishes !== 'object') dishes = {};

function saveDishes() {
	FS(DISHES_FILE).write(JSON.stringify(dishes));
}

/**
 * Used instead of Dex.packTeam to generate more human-readable output.
 * @param {AnyObject[]} team
 * @param {string[]} ingredients
 */
function stringifyTeam(team, ingredients) {
	let output = '';
	for (let i = 0; i < team.length; i++) {
		const mon = team[i];
		output += `${ingredients[i]} (${mon.species}) @ ${mon.item}<br/>`;
		output += `Ability: ${mon.ability}<br/>`;
		if (mon.happiness !== 255) output += `Happiness: ${mon.happiness}<br/>`;
		let evs = [];
		for (const stat in mon.evs) {
			if (mon.evs[stat]) evs.push(`${mon.evs[stat]} ${stat}`);
		}
		if (evs.length) output += `EVs: ${evs.join(' / ')}<br/>`;
		output += `${mon.nature} Nature<br/>`;
		let ivs = [];
		for (const stat in mon.ivs) {
			if (mon.ivs[stat] !== 31) ivs.push(`${mon.ivs[stat]} ${stat}`);
		}
		if (ivs.length) output += `IVs: ${ivs.join(' / ')}<br/>`;
		output += /** @type {string[]} */ (mon.moves).map(move => `- ${move}<br/>`).join('');
		output += '
';
	}
	return output;
}

/**
 * @param {string} [generator]
 */
function generateTeam(generator = '') {
	let potentialPokemon = Object.keys(Dex.data.Pokedex).filter(mon => {
		const template = Dex.getTemplate(mon);
		return template.baseSpecies === template.species;
	});
	let speciesClause = true;
	switch (generator) {
	case 'ou':
		potentialPokemon = potentialPokemon.filter(mon => {
			const template = Dex.getTemplate(mon);
			return template.tier === 'OU';
		}).concat(potentialPokemon.filter(mon => {
			// There is probably a better way to get the ratios right, oh well.
			const template = Dex.getTemplate(mon);
			return template.tier === 'OU' || template.tier === 'UU';
		}));
		break;
	case 'ag':
		potentialPokemon = potentialPokemon.filter(mon => {
			const template = Dex.getTemplate(mon);
			const unviable = template.tier === 'NFE' || template.tier === 'PU' || template.tier === '(PU)' || template.tier.startsWith("LC");
			const illegal = template.tier === 'Unreleased' || template.tier === 'Illegal' || template.tier.startsWith("CAP");
			return !(unviable || illegal);
		});
		speciesClause = false;
		break;
	default:
		potentialPokemon = potentialPokemon.filter(mon => {
			const template = Dex.getTemplate(mon);
			const op = template.tier === 'AG' || template.tier === 'Uber';
			const unviable = template.tier === 'Illegal' || template.tier.includes("LC");
			return !(op || unviable);
		});
		potentialPokemon.push('miltank', 'miltank', 'miltank', 'miltank'); // 5x chance for miltank for flavor purposes.
	}

	/** @type {string[]} */
	const team = [];

	while (team.length < 6) {
		const randIndex = Math.floor(Math.random() * potentialPokemon.length);
		const potentialMon = potentialPokemon[randIndex];
		if (team.includes(potentialMon)) continue;
		team.push(potentialMon);
		if (speciesClause) potentialPokemon.splice(randIndex, 1);
	}

	return team.map(mon => Dex.getTemplate(mon).species);
}

/**
 * @return {[string, string[]]}
 */
function generateDish() {
	const keys = Object.keys(dishes);
	const entry = dishes[keys[Math.floor(Math.random() * keys.length)]].slice();
	const dish = entry.splice(0, 1)[0];
	const ingredients = [];
	while (ingredients.length < 6) {
		ingredients.push(entry.splice(Math.floor(Math.random() * entry.length), 1)[0]);
	}
	return [dish, ingredients];
}

/** @type {ChatCommands} */
const commands = {
	foodfight: function (target, room, user) {
		if (room !== thecafe) return this.errorReply("This command is only available in The Café.");

		if (!Object.keys(dishes).length) return this.errorReply("No dishes found. Add some dishes first.");

		// @ts-ignore
		if (user.foodfight && user.foodfight.timestamp + FOODFIGHT_COOLDOWN > Date.now()) return this.errorReply("Please wait a few minutes before using this command again.");

		target = toId(target);

		let team, importable;
		const [dish, ingredients] = generateDish();
		if (target === 'bf') {
			const bfTeam = Dex.generateTeam('gen7bssfactory');
			importable = stringifyTeam(bfTeam, ingredients);
			team = /** @type {Template[]} */ (bfTeam).map(val => val.species);
		} else {
			team = generateTeam(target);
		}
		// @ts-ignore
		user.foodfight = {team: team, dish: dish, ingredients: ingredients, timestamp: Date.now()};
		const importStr = importable ? `
Importable team:
${importable}</textarea>
` : '';
		return this.sendReplyBox(`<div class="ladder"><table style="text-align:center;"><tr><th colspan="7" style="font-size:10pt;">Your dish is: <u>${dish}</u></th></tr><tr><th>Team</th>${team.map(mon => `<psicon pokemon="${mon}"/> ${mon}	`).join('')}</tr><tr><th>Ingredients</th>${ingredients.map(ingredient => `${ingredient}	`).join('')}</tr>${importStr}</table></div>`);
	},
	checkfoodfight: function (target, room, user) {
		if (room !== thecafe) return this.errorReply("This command is only available in The Café.");

		const targetUser = this.targetUserOrSelf(target, false);
		if (!targetUser) return this.errorReply(`User ${this.targetUsername} not found.`);
		const self = targetUser === user;
		if (!self && !this.can('mute', targetUser, room)) return false;
		// @ts-ignore
		if (!targetUser.foodfight) return this.errorReply(`${self ? `You don't` : `This user doesn't`} have an active Foodfight team.`);
		// @ts-ignore
		return this.sendReplyBox(`<div class="ladder"><table style="text-align:center;"><tr><th colspan="7" style="font-size:10pt;">${self ? `Your` : `${this.targetUsername}'s`} dish is: <u>${targetUser.foodfight.dish}</u></th></tr><tr><th>Team</th>${targetUser.foodfight.team.map(mon => `<psicon pokemon="${mon}"/> ${mon}	`).join('')}</tr><tr><th>Ingredients</th>${targetUser.foodfight.ingredients.map(ingredient => `${ingredient}	`).join('')}</tr></table></div>`);
	},
	addingredients: 'adddish',
	adddish: function (target, room, user, connection, cmd) {
		if (room !== thecafe) return this.errorReply("This command is only available in The Café.");
		if (!this.can('mute', null, room)) return false;

		let [dish, ...ingredients] = target.split(',');
		dish = dish.trim();
		if (!dish || !ingredients.length) return this.parse('/help foodfight');
		const id = toId(dish);
		if (id === 'constructor') return this.errorReply("Invalid dish name.");
		ingredients = ingredients.map(ingredient => ingredient.trim());

		if ([...ingredients.entries()].some(([index, ingredient]) => ingredients.indexOf(ingredient) !== index)) {
			return this.errorReply("Please don't enter duplicate ingredients.");
		}

		if (ingredients.some(ingredient => ingredient.length > 19)) {
			return this.errorReply("Ingredients can only be 19 characters long.");
		}

		if (cmd === 'adddish') {
			if (dishes[id]) return this.errorReply("This dish already exists.");
			if (ingredients.length < 6) return this.errorReply("Dishes need at least 6 ingredients.");
			dishes[id] = [dish];
		} else {
			if (!dishes[id]) return this.errorReply(`Dish not found: ${dish}`);
			if (ingredients.some(ingredient => dishes[id].includes(ingredient))) return this.errorReply("Please don't enter duplicate ingredients.");
		}

		dishes[id] = dishes[id].concat(ingredients);
		saveDishes();
		this.sendReply(`${cmd.slice(3)} '${dish}: ${ingredients.join(', ')}' added successfully.`);
	},
	removedish: function (target, room, user) {
		if (room !== thecafe) return this.errorReply("This command is only available in The Café.");
		if (!this.can('mute', null, room)) return false;

		const id = toId(target);
		if (id === 'constructor') return this.errorReply("Invalid dish.");
		if (!dishes[id]) return this.errorReply(`Dish '${target}' not found.`);

		delete dishes[id];
		saveDishes();
		this.sendReply(`Dish '${target}' deleted successfully.`);
	},
	viewdishes: function (target, room, user, connection) {
		if (room !== thecafe) return this.errorReply("This command is only available in The Café.");

		return this.parse(`/join view-foodfight`);
	},
	foodfighthelp: [
		`/foodfight <generator> - Gives you a randomly generated Foodfight dish, ingredient list and team. Generator can be either 'ou' or 'ag', or left blank. If left blank, uses the normal Foodfight generator.`,
		`/checkfoodfight <username> - Gives you the last team and dish generated for the entered user, or your own if left blank. Anyone can check their own info, checking other people requires: % @ * # & ~`,
		`/adddish <dish>, <ingredient>, <ingredient>, ... - Adds a dish to the database. Requires: % @ * # & ~`,
		`/addingredients <dish>, <ingredient>, <ingredient>, ... - Adds extra ingredients to a dish in the database. Requires: % @ * # & ~`,
		`/removedish <dish> - Removes a dish from the database. Requires: % @ * # & ~`,
		`/viewdishes - Shows the entire database of dishes. Requires: % @ * # & ~`,
	],
};

exports.commands = commands;

/** @type {PageTable} */
const pages = {
	foodfight(query, user, connection) {
		if (!user.named) return Rooms.RETRY_AFTER_LOGIN;
		let buf = `|title|Foodfight\n|pagehtml|<div class="pad ladder"><h2>Foodfight Dish list</h2>`;
		if (!user.can('mute', null, thecafe)) {
			return buf + `<p>Access denied</p></div>`;
		}
		let content = Object.keys(dishes).map(entry => {
			const [dish, ...ingredients] = dishes[entry];
			return `<tr><td>${dish}</td><td>${ingredients.join(', ')}</td></tr>`;
		}).join('');

		if (!content) {
			buf += `<p>There are no dishes in the database.</p>`;
		} else {
			buf += `<table><tr><th><h3>Dishes</h3></th><th><h3>Ingredients</h3></th></tr>${content}</table>`;
		}
		buf += `</div>`;
		return buf;
	},
};
exports.pages = pages;
<commit_msg>Cafe: increase size of importable team label<commit_after>'use strict';

const FS = require('./../lib/fs');

const DISHES_FILE = 'config/chat-plugins/thecafe-foodfight.json';
const FOODFIGHT_COOLDOWN = 5 * 60 * 1000;

const thecafe = /** @type {ChatRoom} */ (Rooms.get('thecafe'));

/** @type {{[k: string]: string[]}} */
let dishes = {};
try {
	dishes = require(`../${DISHES_FILE}`);
} catch (e) {
	if (e.code !== 'MODULE_NOT_FOUND' && e.code !== 'ENOENT') throw e;
}
if (!dishes || typeof dishes !== 'object') dishes = {};

function saveDishes() {
	FS(DISHES_FILE).write(JSON.stringify(dishes));
}

/**
 * Used instead of Dex.packTeam to generate more human-readable output.
 * @param {AnyObject[]} team
 * @param {string[]} ingredients
 */
function stringifyTeam(team, ingredients) {
	let output = '';
	for (let i = 0; i < team.length; i++) {
		const mon = team[i];
		output += `${ingredients[i]} (${mon.species}) @ ${mon.item}<br/>`;
		output += `Ability: ${mon.ability}<br/>`;
		if (mon.happiness !== 255) output += `Happiness: ${mon.happiness}<br/>`;
		let evs = [];
		for (const stat in mon.evs) {
			if (mon.evs[stat]) evs.push(`${mon.evs[stat]} ${stat}`);
		}
		if (evs.length) output += `EVs: ${evs.join(' / ')}<br/>`;
		output += `${mon.nature} Nature<br/>`;
		let ivs = [];
		for (const stat in mon.ivs) {
			if (mon.ivs[stat] !== 31) ivs.push(`${mon.ivs[stat]} ${stat}`);
		}
		if (ivs.length) output += `IVs: ${ivs.join(' / ')}<br/>`;
		output += /** @type {string[]} */ (mon.moves).map(move => `- ${move}<br/>`).join('');
		output += '
';
	}
	return output;
}

/**
 * @param {string} [generator]
 */
function generateTeam(generator = '') {
	let potentialPokemon = Object.keys(Dex.data.Pokedex).filter(mon => {
		const template = Dex.getTemplate(mon);
		return template.baseSpecies === template.species;
	});
	let speciesClause = true;
	switch (generator) {
	case 'ou':
		potentialPokemon = potentialPokemon.filter(mon => {
			const template = Dex.getTemplate(mon);
			return template.tier === 'OU';
		}).concat(potentialPokemon.filter(mon => {
			// There is probably a better way to get the ratios right, oh well.
			const template = Dex.getTemplate(mon);
			return template.tier === 'OU' || template.tier === 'UU';
		}));
		break;
	case 'ag':
		potentialPokemon = potentialPokemon.filter(mon => {
			const template = Dex.getTemplate(mon);
			const unviable = template.tier === 'NFE' || template.tier === 'PU' || template.tier === '(PU)' || template.tier.startsWith("LC");
			const illegal = template.tier === 'Unreleased' || template.tier === 'Illegal' || template.tier.startsWith("CAP");
			return !(unviable || illegal);
		});
		speciesClause = false;
		break;
	default:
		potentialPokemon = potentialPokemon.filter(mon => {
			const template = Dex.getTemplate(mon);
			const op = template.tier === 'AG' || template.tier === 'Uber';
			const unviable = template.tier === 'Illegal' || template.tier.includes("LC");
			return !(op || unviable);
		});
		potentialPokemon.push('miltank', 'miltank', 'miltank', 'miltank'); // 5x chance for miltank for flavor purposes.
	}

	/** @type {string[]} */
	const team = [];

	while (team.length < 6) {
		const randIndex = Math.floor(Math.random() * potentialPokemon.length);
		const potentialMon = potentialPokemon[randIndex];
		if (team.includes(potentialMon)) continue;
		team.push(potentialMon);
		if (speciesClause) potentialPokemon.splice(randIndex, 1);
	}

	return team.map(mon => Dex.getTemplate(mon).species);
}

/**
 * @return {[string, string[]]}
 */
function generateDish() {
	const keys = Object.keys(dishes);
	const entry = dishes[keys[Math.floor(Math.random() * keys.length)]].slice();
	const dish = entry.splice(0, 1)[0];
	const ingredients = [];
	while (ingredients.length < 6) {
		ingredients.push(entry.splice(Math.floor(Math.random() * entry.length), 1)[0]);
	}
	return [dish, ingredients];
}

/** @type {ChatCommands} */
const commands = {
	foodfight: function (target, room, user) {
		if (room !== thecafe) return this.errorReply("This command is only available in The Café.");

		if (!Object.keys(dishes).length) return this.errorReply("No dishes found. Add some dishes first.");

		// @ts-ignore
		if (user.foodfight && user.foodfight.timestamp + FOODFIGHT_COOLDOWN > Date.now()) return this.errorReply("Please wait a few minutes before using this command again.");

		target = toId(target);

		let team, importable;
		const [dish, ingredients] = generateDish();
		if (target === 'bf') {
			const bfTeam = Dex.generateTeam('gen7bssfactory');
			importable = stringifyTeam(bfTeam, ingredients);
			team = /** @type {Template[]} */ (bfTeam).map(val => val.species);
		} else {
			team = generateTeam(target);
		}
		// @ts-ignore
		user.foodfight = {team: team, dish: dish, ingredients: ingredients, timestamp: Date.now()};
		const importStr = importable ? `
Importable team:
${importable}</textarea>
` : '';
		return this.sendReplyBox(`<div class="ladder"><table style="text-align:center;"><tr><th colspan="7" style="font-size:10pt;">Your dish is: <u>${dish}</u></th></tr><tr><th>Team</th>${team.map(mon => `<psicon pokemon="${mon}"/> ${mon}	`).join('')}</tr><tr><th>Ingredients</th>${ingredients.map(ingredient => `${ingredient}	`).join('')}</tr>${importStr}</table></div>`);
	},
	checkfoodfight: function (target, room, user) {
		if (room !== thecafe) return this.errorReply("This command is only available in The Café.");

		const targetUser = this.targetUserOrSelf(target, false);
		if (!targetUser) return this.errorReply(`User ${this.targetUsername} not found.`);
		const self = targetUser === user;
		if (!self && !this.can('mute', targetUser, room)) return false;
		// @ts-ignore
		if (!targetUser.foodfight) return this.errorReply(`${self ? `You don't` : `This user doesn't`} have an active Foodfight team.`);
		// @ts-ignore
		return this.sendReplyBox(`<div class="ladder"><table style="text-align:center;"><tr><th colspan="7" style="font-size:10pt;">${self ? `Your` : `${this.targetUsername}'s`} dish is: <u>${targetUser.foodfight.dish}</u></th></tr><tr><th>Team</th>${targetUser.foodfight.team.map(mon => `<psicon pokemon="${mon}"/> ${mon}	`).join('')}</tr><tr><th>Ingredients</th>${targetUser.foodfight.ingredients.map(ingredient => `${ingredient}	`).join('')}</tr></table></div>`);
	},
	addingredients: 'adddish',
	adddish: function (target, room, user, connection, cmd) {
		if (room !== thecafe) return this.errorReply("This command is only available in The Café.");
		if (!this.can('mute', null, room)) return false;

		let [dish, ...ingredients] = target.split(',');
		dish = dish.trim();
		if (!dish || !ingredients.length) return this.parse('/help foodfight');
		const id = toId(dish);
		if (id === 'constructor') return this.errorReply("Invalid dish name.");
		ingredients = ingredients.map(ingredient => ingredient.trim());

		if ([...ingredients.entries()].some(([index, ingredient]) => ingredients.indexOf(ingredient) !== index)) {
			return this.errorReply("Please don't enter duplicate ingredients.");
		}

		if (ingredients.some(ingredient => ingredient.length > 19)) {
			return this.errorReply("Ingredients can only be 19 characters long.");
		}

		if (cmd === 'adddish') {
			if (dishes[id]) return this.errorReply("This dish already exists.");
			if (ingredients.length < 6) return this.errorReply("Dishes need at least 6 ingredients.");
			dishes[id] = [dish];
		} else {
			if (!dishes[id]) return this.errorReply(`Dish not found: ${dish}`);
			if (ingredients.some(ingredient => dishes[id].includes(ingredient))) return this.errorReply("Please don't enter duplicate ingredients.");
		}

		dishes[id] = dishes[id].concat(ingredients);
		saveDishes();
		this.sendReply(`${cmd.slice(3)} '${dish}: ${ingredients.join(', ')}' added successfully.`);
	},
	removedish: function (target, room, user) {
		if (room !== thecafe) return this.errorReply("This command is only available in The Café.");
		if (!this.can('mute', null, room)) return false;

		const id = toId(target);
		if (id === 'constructor') return this.errorReply("Invalid dish.");
		if (!dishes[id]) return this.errorReply(`Dish '${target}' not found.`);

		delete dishes[id];
		saveDishes();
		this.sendReply(`Dish '${target}' deleted successfully.`);
	},
	viewdishes: function (target, room, user, connection) {
		if (room !== thecafe) return this.errorReply("This command is only available in The Café.");

		return this.parse(`/join view-foodfight`);
	},
	foodfighthelp: [
		`/foodfight <generator> - Gives you a randomly generated Foodfight dish, ingredient list and team. Generator can be either 'ou' or 'ag', or left blank. If left blank, uses the normal Foodfight generator.`,
		`/checkfoodfight <username> - Gives you the last team and dish generated for the entered user, or your own if left blank. Anyone can check their own info, checking other people requires: % @ * # & ~`,
		`/adddish <dish>, <ingredient>, <ingredient>, ... - Adds a dish to the database. Requires: % @ * # & ~`,
		`/addingredients <dish>, <ingredient>, <ingredient>, ... - Adds extra ingredients to a dish in the database. Requires: % @ * # & ~`,
		`/removedish <dish> - Removes a dish from the database. Requires: % @ * # & ~`,
		`/viewdishes - Shows the entire database of dishes. Requires: % @ * # & ~`,
	],
};

exports.commands = commands;

/** @type {PageTable} */
const pages = {
	foodfight(query, user, connection) {
		if (!user.named) return Rooms.RETRY_AFTER_LOGIN;
		let buf = `|title|Foodfight\n|pagehtml|<div class="pad ladder"><h2>Foodfight Dish list</h2>`;
		if (!user.can('mute', null, thecafe)) {
			return buf + `<p>Access denied</p></div>`;
		}
		let content = Object.keys(dishes).map(entry => {
			const [dish, ...ingredients] = dishes[entry];
			return `<tr><td>${dish}</td><td>${ingredients.join(', ')}</td></tr>`;
		}).join('');

		if (!content) {
			buf += `<p>There are no dishes in the database.</p>`;
		} else {
			buf += `<table><tr><th><h3>Dishes</h3></th><th><h3>Ingredients</h3></th></tr>${content}</table>`;
		}
		buf += `</div>`;
		return buf;
	},
};
exports.pages = pages;
<|endoftext|>


<commit_before>var utils = require('/utils.js');
var Entity = require('/entity.js').Entity;
var mat4 = require('/gl-matrix/mat4.js');
var vec3 = require('/gl-matrix/vec3.js');

function PerspectiveCamera(aspectRatio, fieldOfView, near, far) {
    Entity.call(this);

    // The default setup for the camera is to move it back a little and rotate
    // it, so that the camera is looking at negative z.
    this.translate(0,0,-5);
    this.rotate(0,(Math.PI / 180) * 180,0);

    this.aspectRatio = aspectRatio;
    this.near = near || 1;
    this.far = far || 1000;
    this.fieldOfView = fieldOfView || 45;

    this._view = mat4.create();
    this._projection = mat4.create();
    this._lookAt = vec3.create();
}

utils.extend(PerspectiveCamera, Entity);

PerspectiveCamera.prototype.projection = function() {
    return mat4.perspective(this._projection, (Math.PI / 180)*this.fieldOfView,
        this.aspectRatio, this.near, this.far);
};

PerspectiveCamera.prototype.view = function() {
    vec3.add(this._lookAt, this.position(), this.forward());
    mat4.lookAt(this._view, this.position(), this._lookAt, this.up());
    return this._view;
};

module.exports.PerspectiveCamera = PerspectiveCamera;<commit_msg>Added license.<commit_after>/*The MIT License (MIT)

Copyright (c) 2015 Jens Malmborg

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.*/

var utils = require('/utils.js');
var Entity = require('/entity.js').Entity;
var mat4 = require('/gl-matrix/mat4.js');
var vec3 = require('/gl-matrix/vec3.js');

function PerspectiveCamera(aspectRatio, fieldOfView, near, far) {
    Entity.call(this);

    // The default setup for the camera is to move it back a little and rotate
    // it, so that the camera is looking at negative z.
    this.translate(0,0,-5);
    this.rotate(0,(Math.PI / 180) * 180,0);

    this.aspectRatio = aspectRatio;
    this.near = near || 1;
    this.far = far || 1000;
    this.fieldOfView = fieldOfView || 45;

    this._view = mat4.create();
    this._projection = mat4.create();
    this._lookAt = vec3.create();
}

utils.extend(PerspectiveCamera, Entity);

PerspectiveCamera.prototype.projection = function() {
    return mat4.perspective(this._projection, (Math.PI / 180)*this.fieldOfView,
        this.aspectRatio, this.near, this.far);
};

PerspectiveCamera.prototype.view = function() {
    vec3.add(this._lookAt, this.position(), this.forward());
    mat4.lookAt(this._view, this.position(), this._lookAt, this.up());
    return this._view;
};

module.exports.PerspectiveCamera = PerspectiveCamera;<|endoftext|>


<commit_before>/*jshint -W121 */
'use strict';

var objectAssign = require('object-assign');
var jsonValidate = require('jsonschema').validate;
var debug = require('debug')('thalisalti:acl');

var aclSchema = require('./acl-schema.json');
var fast = require('./indexOf');

if (!String.prototype.startsWith) {
  String.prototype.startsWith = function (searchString, position) {
    return this.substr(position || 0, searchString.length) === searchString;
  };
}

function Salti(dbName, acl, thaliIdCallback, options) {
  // Both Salti(...) and new Salti(...) will work fine.
  if (!(this instanceof Salti)) {
    return new Salti(dbName, acl, thaliIdCallback, options);
  }

  if (!dbName || typeof dbName !== 'string') {
    throw new Error('invalid configuration - missing dbName');
  }
  this.dbName = dbName;

  if (!acl || !aclSchema || !jsonValidate(acl, aclSchema).valid) {
    throw new Error('invalid configuration - acl is not valid');
  }
  // ACL data should be cloned.
  this.acl = JSON.parse(JSON.stringify(acl));

  if (!thaliIdCallback || typeof thaliIdCallback !== 'function') {
    throw new Error('invalid configuration - callback is not a function');
  }
  this.thaliIdCallback = thaliIdCallback;

  this.options = objectAssign({}, Salti.defaults, options);

  this.init();
  return this.handler.bind(this);
}

Salti.defaults = {
  stripTrailingSlash: true,
  dbPrefix: undefined,
  dbPathTemplate: '{:db}',
  idTokenTemplate: '{:id}'
};

Salti.THALI_PREFIX = '/_local/thali_';

Salti.prototype.init = function () {
  var self = this;

  this.dbPath = this.dbName;
  if (this.options.dbPrefix) {
    // DB prefix in options can include leading slash.
    // We should strip it for DB path.
    this.dbPath = Salti.stripLeadingSlash(this.options.dbPrefix) + '/' +
      this.dbPath;
  }
  this.dbPrefix = '/' + this.dbPath;

  // Replacing dbPath template (for example {:db}) with dbPath
  var dbPathRE = new RegExp(this.options.dbPathTemplate, 'g');
  this.acl.forEach(function (roleData) {
    if (roleData.paths) {
      // If paths exists it will be an array (schema is verified).
      roleData.paths.forEach(function (pathData) {
        pathData.path = pathData.path.replace(dbPathRE, self.dbPath);
      });
    }
  });
};

Salti.prototype.handler = function (request, response, nextHandler) {
  this.request = request;
  this.response = response;
  this.nextHandler = nextHandler;
  this.path = request.path;
  debug(
    'method: %s, url: %s, path: %s',
    this.request.method, this.request.url, this.path
  );

  if (!this.request.connection.pskRole) {
    return this.unauthorized(0);
  }
  this.role = this.request.connection.pskRole;
  debug('pskRole: %s', this.role);

  if (this.options.stripTrailingSlash) {
    this.path = Salti.stripTrailingSlash(this.path);
  }

  // Role data lookup on ACL.
  var roleIndex = fast.indexOf(this.acl, this.role, 'role');
  if (roleIndex === -1) {
    return this.unauthorized(1);
  }
  var paths = this.acl[roleIndex].paths;

  // Raw paths lookup.
  var lookupResult = this.lookupPathVerb(paths, this.path);
  if (lookupResult === Salti.PATH_VERB_FOUND) {
    return this.authorized('found raw path');
  } else if (lookupResult === Salti.VERB_NOTFOUND) {
    // Raw path is found, but verb is not found.
    return this.unauthorized(2);
  }

  // Next section deals with
  // /{:db}/{:id}, /{:db}/_local/{:id} and /{:db}/{:id}/attachment stuff.
  var cuttedPath = this.getPathWithoutPrefix();
  if (!cuttedPath) {
    // Required DB Prefix is not valid.
    return this.unauthorized(3);
  }

  var pathParts = cuttedPath.split('/');
  if (pathParts[0] !== '') {
    // Sanity check, the first element should always be empty.
    return this.unauthorized(4);
  }

  var lookupPath;

  if (pathParts.length === 2) {
    // Is /{:id} authorized?
    lookupPath = this.dbPrefix + '/' + this.options.idTokenTemplate;
    if (this.lookupPathVerb(paths, lookupPath) === Salti.PATH_VERB_FOUND) {
      return this.authorized('found raw path and id');
    } else {
      return this.unauthorized(5);
    }
  }

  else if (pathParts.length === 3 && pathParts[1] === '_local') {
    // Is this a thali {:id}?
    var isThaliPrefix = cuttedPath.startsWith(Salti.THALI_PREFIX);
    if (isThaliPrefix) {
      // Is /_local/thali_{:id} authorized?
      lookupPath = this.dbPrefix + Salti.THALI_PREFIX +
        this.options.idTokenTemplate;
      if (this.lookupPathVerb(paths, lookupPath) !== Salti.PATH_VERB_FOUND) {
        debug('unauthorized thali prefix');
        return this.unauthorized(6);
      }
      var thaliId = cuttedPath.substring(Salti.THALI_PREFIX.length);
      if (!this.resolveThaliId(thaliId)) {
        debug('unauthorized thali id');
        return this.unauthorized(7);
      }
      debug('passed _thali check');
    }

    // Is /_local/{:id} authorized?
    lookupPath = this.dbPrefix + '/_local/' + this.options.idTokenTemplate;
    if (this.lookupPathVerb(paths, lookupPath) === Salti.PATH_VERB_FOUND) {
      return this.authorized('passed _local check');
    } else {
      return this.unauthorized(8);
    }
  }

  else if (pathParts.length === 3 && pathParts[2] === 'attachment') {
    // Is /{:id}/attachment authorized?
    lookupPath = this.dbPrefix + '/' + this.options.idTokenTemplate +
      '/attachment';
    if (this.lookupPathVerb(paths, lookupPath) === Salti.PATH_VERB_FOUND) {
      return this.authorized('passed attachment check');
    } else {
      return this.unauthorized(9);
    }
  }

  return this.unauthorized(10);
};

Salti.PATH_VERB_FOUND = 0;
Salti.PATH_NOTFOUND = 1;
Salti.VERB_NOTFOUND = 2;
Salti.prototype.lookupPathVerb = function (paths, lookupPath) {
  // Path data lookup on paths.
  var pathIndex = fast.indexOf(paths, lookupPath, 'path');
  if (pathIndex === -1) {
    debug('path is not found: lookupPath: %s', lookupPath);
    return Salti.PATH_NOTFOUND;
  }
  // Request method lookup on verbs.
  var verbs = paths[pathIndex].verbs;
  var verbIndex = fast.indexOf(verbs, this.request.method);
  if (verbIndex === -1) {
    debug(
      'verb and path are not found: lookupPath: %s, verb: %s',
      lookupPath, this.request.method
    );
    return Salti.VERB_NOTFOUND;
  }
  return Salti.PATH_VERB_FOUND;
};

Salti.prototype.resolveThaliId = function (thaliId) {
  if (thaliId.length > 0) {
    return this.thaliIdCallback(thaliId, this.request);
  }
  return false;
};

Salti.stripLeadingSlash = function (path) {
  if (path.length > 0 && path[0] === '/') {
    debug('ignoring leading slash for %s', path);
    return path.substring(1);
  }
  return path;
};
Salti.stripTrailingSlash = function (path) {
  // Single slash '/' is a valid path.
  // It's slash is leading, not trailing.
  // We shouldn't strip it.
  var lastIndex = path.length - 1;
  if (lastIndex > 0 && path[lastIndex] === '/') {
    debug('ignoring trailing slash for %s', path);
    return path.substring(0, lastIndex);
  }
  return path;
};

Salti.prototype.getPathWithoutPrefix = function () {
  var isDbPath = this.path.startsWith(this.dbPrefix);
  if (!isDbPath) {
    return null;
  }
  return this.path.substring(this.dbPrefix.length);
};

Salti.prototype.authorized = function () {
  debug.apply(debug, arguments);
  this.nextHandler();
  return;
};

Salti.MESSAGE_401 = {
  success: false,
  message: 'Unauthorized'
};

Salti.prototype.unauthorized = function (index) {
  debug(
    'unauthorized %s: pskRole: %s, path: %s, verb: %s',
    index, this.role, this.path, this.request.method
  );
  return this.response.status(401).send(Salti.MESSAGE_401);
};

module.exports = Salti;
<commit_msg>renamed cuttedPath to cutPath<commit_after>/*jshint -W121 */
'use strict';

var objectAssign = require('object-assign');
var jsonValidate = require('jsonschema').validate;
var debug = require('debug')('thalisalti:acl');

var aclSchema = require('./acl-schema.json');
var fast = require('./indexOf');

if (!String.prototype.startsWith) {
  String.prototype.startsWith = function (searchString, position) {
    return this.substr(position || 0, searchString.length) === searchString;
  };
}

function Salti(dbName, acl, thaliIdCallback, options) {
  // Both Salti(...) and new Salti(...) will work fine.
  if (!(this instanceof Salti)) {
    return new Salti(dbName, acl, thaliIdCallback, options);
  }

  if (!dbName || typeof dbName !== 'string') {
    throw new Error('invalid configuration - missing dbName');
  }
  this.dbName = dbName;

  if (!acl || !aclSchema || !jsonValidate(acl, aclSchema).valid) {
    throw new Error('invalid configuration - acl is not valid');
  }
  // ACL data should be cloned.
  this.acl = JSON.parse(JSON.stringify(acl));

  if (!thaliIdCallback || typeof thaliIdCallback !== 'function') {
    throw new Error('invalid configuration - callback is not a function');
  }
  this.thaliIdCallback = thaliIdCallback;

  this.options = objectAssign({}, Salti.defaults, options);

  this.init();
  return this.handler.bind(this);
}

Salti.defaults = {
  stripTrailingSlash: true,
  dbPrefix: undefined,
  dbPathTemplate: '{:db}',
  idTokenTemplate: '{:id}'
};

Salti.THALI_PREFIX = '/_local/thali_';

Salti.prototype.init = function () {
  var self = this;

  this.dbPath = this.dbName;
  if (this.options.dbPrefix) {
    // DB prefix in options can include leading slash.
    // We should strip it for DB path.
    this.dbPath = Salti.stripLeadingSlash(this.options.dbPrefix) + '/' +
      this.dbPath;
  }
  this.dbPrefix = '/' + this.dbPath;

  // Replacing dbPath template (for example {:db}) with dbPath
  var dbPathRE = new RegExp(this.options.dbPathTemplate, 'g');
  this.acl.forEach(function (roleData) {
    if (roleData.paths) {
      // If paths exists it will be an array (schema is verified).
      roleData.paths.forEach(function (pathData) {
        pathData.path = pathData.path.replace(dbPathRE, self.dbPath);
      });
    }
  });
};

Salti.prototype.handler = function (request, response, nextHandler) {
  this.request = request;
  this.response = response;
  this.nextHandler = nextHandler;
  this.path = request.path;
  debug(
    'method: %s, url: %s, path: %s',
    this.request.method, this.request.url, this.path
  );

  if (!this.request.connection.pskRole) {
    return this.unauthorized(0);
  }
  this.role = this.request.connection.pskRole;
  debug('pskRole: %s', this.role);

  if (this.options.stripTrailingSlash) {
    this.path = Salti.stripTrailingSlash(this.path);
  }

  // Role data lookup on ACL.
  var roleIndex = fast.indexOf(this.acl, this.role, 'role');
  if (roleIndex === -1) {
    return this.unauthorized(1);
  }
  var paths = this.acl[roleIndex].paths;

  // Raw paths lookup.
  var lookupResult = this.lookupPathVerb(paths, this.path);
  if (lookupResult === Salti.PATH_VERB_FOUND) {
    return this.authorized('found raw path');
  } else if (lookupResult === Salti.VERB_NOTFOUND) {
    // Raw path is found, but verb is not found.
    return this.unauthorized(2);
  }

  // Next section deals with
  // /{:db}/{:id}, /{:db}/_local/{:id} and /{:db}/{:id}/attachment stuff.
  var cutPath = this.getPathWithoutPrefix();
  if (!cutPath) {
    // Required DB Prefix is not valid.
    return this.unauthorized(3);
  }

  var pathParts = cutPath.split('/');
  if (pathParts[0] !== '') {
    // Sanity check, the first element should always be empty.
    return this.unauthorized(4);
  }

  var lookupPath;

  if (pathParts.length === 2) {
    // Is /{:id} authorized?
    lookupPath = this.dbPrefix + '/' + this.options.idTokenTemplate;
    if (this.lookupPathVerb(paths, lookupPath) === Salti.PATH_VERB_FOUND) {
      return this.authorized('found raw path and id');
    } else {
      return this.unauthorized(5);
    }
  }

  else if (pathParts.length === 3 && pathParts[1] === '_local') {
    // Is this a thali {:id}?
    var isThaliPrefix = cutPath.startsWith(Salti.THALI_PREFIX);
    if (isThaliPrefix) {
      // Is /_local/thali_{:id} authorized?
      lookupPath = this.dbPrefix + Salti.THALI_PREFIX +
        this.options.idTokenTemplate;
      if (this.lookupPathVerb(paths, lookupPath) !== Salti.PATH_VERB_FOUND) {
        debug('unauthorized thali prefix');
        return this.unauthorized(6);
      }
      var thaliId = cutPath.substring(Salti.THALI_PREFIX.length);
      if (!this.resolveThaliId(thaliId)) {
        debug('unauthorized thali id');
        return this.unauthorized(7);
      }
      debug('passed _thali check');
    }

    // Is /_local/{:id} authorized?
    lookupPath = this.dbPrefix + '/_local/' + this.options.idTokenTemplate;
    if (this.lookupPathVerb(paths, lookupPath) === Salti.PATH_VERB_FOUND) {
      return this.authorized('passed _local check');
    } else {
      return this.unauthorized(8);
    }
  }

  else if (pathParts.length === 3 && pathParts[2] === 'attachment') {
    // Is /{:id}/attachment authorized?
    lookupPath = this.dbPrefix + '/' + this.options.idTokenTemplate +
      '/attachment';
    if (this.lookupPathVerb(paths, lookupPath) === Salti.PATH_VERB_FOUND) {
      return this.authorized('passed attachment check');
    } else {
      return this.unauthorized(9);
    }
  }

  return this.unauthorized(10);
};

Salti.PATH_VERB_FOUND = 0;
Salti.PATH_NOTFOUND = 1;
Salti.VERB_NOTFOUND = 2;
Salti.prototype.lookupPathVerb = function (paths, lookupPath) {
  // Path data lookup on paths.
  var pathIndex = fast.indexOf(paths, lookupPath, 'path');
  if (pathIndex === -1) {
    debug('path is not found: lookupPath: %s', lookupPath);
    return Salti.PATH_NOTFOUND;
  }
  // Request method lookup on verbs.
  var verbs = paths[pathIndex].verbs;
  var verbIndex = fast.indexOf(verbs, this.request.method);
  if (verbIndex === -1) {
    debug(
      'verb and path are not found: lookupPath: %s, verb: %s',
      lookupPath, this.request.method
    );
    return Salti.VERB_NOTFOUND;
  }
  return Salti.PATH_VERB_FOUND;
};

Salti.prototype.resolveThaliId = function (thaliId) {
  if (thaliId.length > 0) {
    return this.thaliIdCallback(thaliId, this.request);
  }
  return false;
};

Salti.stripLeadingSlash = function (path) {
  if (path.length > 0 && path[0] === '/') {
    debug('ignoring leading slash for %s', path);
    return path.substring(1);
  }
  return path;
};
Salti.stripTrailingSlash = function (path) {
  // Single slash '/' is a valid path.
  // It's slash is leading, not trailing.
  // We shouldn't strip it.
  var lastIndex = path.length - 1;
  if (lastIndex > 0 && path[lastIndex] === '/') {
    debug('ignoring trailing slash for %s', path);
    return path.substring(0, lastIndex);
  }
  return path;
};

Salti.prototype.getPathWithoutPrefix = function () {
  var isDbPath = this.path.startsWith(this.dbPrefix);
  if (!isDbPath) {
    return null;
  }
  return this.path.substring(this.dbPrefix.length);
};

Salti.prototype.authorized = function () {
  debug.apply(debug, arguments);
  this.nextHandler();
  return;
};

Salti.MESSAGE_401 = {
  success: false,
  message: 'Unauthorized'
};

Salti.prototype.unauthorized = function (index) {
  debug(
    'unauthorized %s: pskRole: %s, path: %s, verb: %s',
    index, this.role, this.path, this.request.method
  );
  return this.response.status(401).send(Salti.MESSAGE_401);
};

module.exports = Salti;
<|endoftext|>


<commit_before>// Dependencies
const cayley = require('cayley')

// RDF constants
const RDF_SYNTAX = '<http://www.w3.org/1999/02/22-rdf-syntax-ns#';
const FLOW_VOCAB = '<http://schema.jillix.net/vocab/';
const SCHEMA = '<http://schema.org/';

// export the cayley client for custom use
exports.connect = (scope, state, args, data, next) => {
    state.client = cayley(scope.env.db);
    state.g = state.client.graph;
    return next ? next(null, data) : data;
};

/* ADAPTER CAYLEY QUERIES */

// get entrypoint for adapter
exports.adapter = (g, entrypoint_name) => {
    return [

        // get entrypoint vars
        g.V(entrypoint_name).In().In()
        .Has(RDF_SYNTAX + 'type>', FLOW_VOCAB + 'Entrypoint>')
        .Tag('subject')
        .Out(FLOW_VOCAB + 'environment>', 'predicate')
        .Out(FLOW_VOCAB + 'json>')
        .Out(RDF_SYNTAX + 'string>')
        .All(),

        // get sequence
        g.V(entrypoint_name).In().In().Tag('subject')
        .Has(RDF_SYNTAX + 'type>', FLOW_VOCAB + 'Entrypoint>')
        .Out(FLOW_VOCAB + 'sequence>', 'predicate')
        .All()
    ];
};

// reads sequence informations for flow
exports.flow = (g, sequence_id, role) => {
    return [

        // sequence details
        g.V(sequence_id)
        .Tag('subject')
        .Has(FLOW_VOCAB + 'role>', role)
        .Out([
            FLOW_VOCAB + 'role>',
            FLOW_VOCAB + 'onError>',
            FLOW_VOCAB + 'onEnd>',
            FLOW_VOCAB + 'next>',
        ], 'predicate')
        .All(),

        // sequence handler details
        g.V(sequence_id)
        .Has(FLOW_VOCAB + 'role>', role)
        .Out(FLOW_VOCAB + 'handler>')
        .Tag('subject')
        .Out([
            FLOW_VOCAB + 'state>',
            FLOW_VOCAB + 'data>',
            FLOW_VOCAB + 'once>',
            FLOW_VOCAB + 'stream>',
            FLOW_VOCAB + 'emit>',
            FLOW_VOCAB + 'next>'
        ], 'predicate')
        .All(),

        // sequence handler args
        g.V(sequence_id)
        .Has(FLOW_VOCAB + 'role>', role)
        .Out(FLOW_VOCAB + 'handler>')
        .Tag('subject')
        .Out(FLOW_VOCAB + 'args>', 'predicate')
        .Out(FLOW_VOCAB + 'json>')
        .Out(RDF_SYNTAX + 'string>')
        .All()
    ];
};

/* BUILDER CAYLEY QUERIES */

// get network nodes with name
exports.networks = (g, user_id) => {
    return g.V().Has(RDF_SYNTAX + 'type>', FLOW_VOCAB + 'Network>')
    .Tag('subject')
    .Out(SCHEMA + 'name>')
    .Save(RDF_SYNTAX + 'type>', 'predicate')
    .Out(RDF_SYNTAX + 'string>')
    .All();
};

// get all entrypoints of a network
exports.entrypoints = (g, network_id) => {
    return [

        // get entrypoint nodes
        g.V(network_id)
        .Out(FLOW_VOCAB + 'entrypoint>')
        .Tag('subject')
        .Save(RDF_SYNTAX + 'type>', 'predicate')
        .Out(SCHEMA + 'name>')
        .Out(RDF_SYNTAX + 'string>')
        .All(),

        // get entrypoint edges
        g.V(network_id)
        .Tag('subject')
        .Out(FLOW_VOCAB + 'entrypoint>', 'predicate')
        .All()
    ]
};

// get details of an entrypoint
exports.entrypoint = (g, entrypoint_id) => {
    return [

        // get environment nodes
        g.V(entrypoint_id)
        .Out(FLOW_VOCAB + 'environment>')
        .Tag('subject')
        .Save(RDF_SYNTAX + 'type>', 'predicate')
        .Out(SCHEMA + 'name>')
        .Out(RDF_SYNTAX + 'string>')
        .All(),

        // get sequence edge
        g.V(entrypoint_id)
        .Tag('subject')
        .Out(FLOW_VOCAB + 'environment>', 'predicate')
        .All(),

        // get sequence nodes
        g.V(entrypoint_id)
        .Out(FLOW_VOCAB + 'sequence>')
        .Tag('subject')
        .Save(RDF_SYNTAX + 'type>', 'predicate')
        .Out(SCHEMA + 'name>')
        .Out(RDF_SYNTAX + 'string>')
        .All(),

        // get sequence edge
        g.V(entrypoint_id)
        .Tag('subject')
        .Out(FLOW_VOCAB + 'sequence>', 'predicate')
        .All()
    ]
};

// get details of a sequence inclusive handlers
exports.sequence = (g, sequence_id) => {
    return [
        // get end/err sequences
        g.V(sequence_id)
        .Out([
            FLOW_VOCAB + 'onEnd>',
            FLOW_VOCAB + 'onError>'
        ])
        .Tag('subject')
        .Save(RDF_SYNTAX + 'type>', 'predicate')
        .Out(SCHEMA + 'name>')
        .Out(RDF_SYNTAX + 'string>')
        .All(),

        // get end/error edges
        g.V(sequence_id)
        .Tag('subject')
        .Out([
            FLOW_VOCAB + 'onEnd>',
            FLOW_VOCAB + 'onError>',
            FLOW_VOCAB + 'next>'
        ], 'predicate')
        .All(),

        g.V(sequence_id)
        .Out(FLOW_VOCAB + 'handler>')
        .Tag('subject')
        .Out([
            FLOW_VOCAB + 'emit>',
            FLOW_VOCAB + 'data>',
            FLOW_VOCAB + 'stream>',
            FLOW_VOCAB + 'next>'
        ], 'predicate')
        .All()
    ];
};

// get details of a handler
exports.handler = (g, handler_id) => {
    return [

        // state node
        g.V(handler_id)
        .Tag('subject')
        .Out(FLOW_VOCAB + 'state>', 'predicate')
        .Out(RDF_SYNTAX + 'string>')
        .All(),

        // arguments node
        g.V(handler_id)
        .Out(FLOW_VOCAB + 'args>')
        .Tag('subject')
        .Save(RDF_SYNTAX + 'type>', 'predicate')
        .Out(SCHEMA + 'name>')
        .Out(RDF_SYNTAX + 'string>')
        .All(),

         // handler edges
        g.V(handler_id)
        .Tag('subject')
        .Out(FLOW_VOCAB + 'args>', 'predicate')
        .All(),

        // argument's emits edges
        g.V(handler_id)
        .Tag('subject')
        .Out(FLOW_VOCAB + 'args>')
        .Out(FLOW_VOCAB + 'emit>', 'predicate')
        .All(),

        // sequences
        g.V(handler_id)
        .Out(FLOW_VOCAB + 'args>')
        .Out(FLOW_VOCAB + 'emit>')
        .Tag('subject')
        .Save(RDF_SYNTAX + 'type>', 'predicate')
        .Out(SCHEMA + 'name>')
        .Out(RDF_SYNTAX + 'string>')
        .All()
    ];
};
<commit_msg>update query to data model change<commit_after>// Dependencies
const cayley = require('cayley')

// RDF constants
const RDF_SYNTAX = '<http://www.w3.org/1999/02/22-rdf-syntax-ns#';
const FLOW_VOCAB = '<http://schema.jillix.net/vocab/';
const SCHEMA = '<http://schema.org/';

// export the cayley client for custom use
exports.connect = (scope, state, args, data, next) => {
    state.client = cayley(scope.env.db);
    state.g = state.client.graph;
    return next ? next(null, data) : data;
};

/* ADAPTER CAYLEY QUERIES */

// get entrypoint for adapter
exports.adapter = (g, entrypoint_name) => {
    return [

        // get entrypoint vars
        g.V(entrypoint_name).In().In()
        .Has(RDF_SYNTAX + 'type>', FLOW_VOCAB + 'Entrypoint>')
        .Tag('subject')
        .Out(FLOW_VOCAB + 'environment>', 'predicate')
        .Out(FLOW_VOCAB + 'json>')
        .Out(RDF_SYNTAX + 'string>')
        .All(),

        // get sequence
        g.V(entrypoint_name).In().In().Tag('subject')
        .Has(RDF_SYNTAX + 'type>', FLOW_VOCAB + 'Entrypoint>')
        .Out(FLOW_VOCAB + 'sequence>', 'predicate')
        .All()
    ];
};

// reads sequence informations for flow
exports.flow = (g, sequence_id, role) => {
    return [

        // sequence details
        g.V(sequence_id)
        .Tag('subject')
        .Has(FLOW_VOCAB + 'role>', role)
        .Out([
            FLOW_VOCAB + 'role>',
            FLOW_VOCAB + 'onError>',
            FLOW_VOCAB + 'onEnd>',
            FLOW_VOCAB + 'next>',
        ], 'predicate')
        .All(),

        // sequence handler details
        g.V(sequence_id)
        .Has(FLOW_VOCAB + 'role>', role)
        .Out(FLOW_VOCAB + 'handler>')
        .Tag('subject')
        .Out([
            RDF_SYNTAX + 'type>',
            FLOW_VOCAB + 'state>',
            FLOW_VOCAB + 'fn>',
            FLOW_VOCAB + 'sequence>',
            FLOW_VOCAB + 'next>'
        ], 'predicate')
        .All(),

        // sequence handler args
        g.V(sequence_id)
        .Has(FLOW_VOCAB + 'role>', role)
        .Out(FLOW_VOCAB + 'handler>')
        .Tag('subject')
        .Out(FLOW_VOCAB + 'args>', 'predicate')
        .Out(FLOW_VOCAB + 'json>')
        .Out(RDF_SYNTAX + 'string>')
        .All()
    ];
};

/* BUILDER CAYLEY QUERIES */

// get network nodes with name
exports.networks = (g, user_id) => {
    return g.V().Has(RDF_SYNTAX + 'type>', FLOW_VOCAB + 'Network>')
    .Tag('subject')
    .Out(SCHEMA + 'name>')
    .Save(RDF_SYNTAX + 'type>', 'predicate')
    .Out(RDF_SYNTAX + 'string>')
    .All();
};

// get all entrypoints of a network
exports.entrypoints = (g, network_id) => {
    return [

        // get entrypoint nodes
        g.V(network_id)
        .Out(FLOW_VOCAB + 'entrypoint>')
        .Tag('subject')
        .Save(RDF_SYNTAX + 'type>', 'predicate')
        .Out(SCHEMA + 'name>')
        .Out(RDF_SYNTAX + 'string>')
        .All(),

        // get entrypoint edges
        g.V(network_id)
        .Tag('subject')
        .Out(FLOW_VOCAB + 'entrypoint>', 'predicate')
        .All()
    ]
};

// get details of an entrypoint
exports.entrypoint = (g, entrypoint_id) => {
    return [

        // get environment nodes
        g.V(entrypoint_id)
        .Out(FLOW_VOCAB + 'environment>')
        .Tag('subject')
        .Save(RDF_SYNTAX + 'type>', 'predicate')
        .Out(SCHEMA + 'name>')
        .Out(RDF_SYNTAX + 'string>')
        .All(),

        // get environment edge
        g.V(entrypoint_id)
        .Tag('subject')
        .Out(FLOW_VOCAB + 'environment>', 'predicate')
        .All(),

        // get sequence nodes
        g.V(entrypoint_id)
        .Out(FLOW_VOCAB + 'sequence>')
        .Tag('subject')
        .Save(RDF_SYNTAX + 'type>', 'predicate')
        .Out(SCHEMA + 'name>')
        .Out(RDF_SYNTAX + 'string>')
        .All(),

        // get sequence edge
        g.V(entrypoint_id)
        .Tag('subject')
        .Out(FLOW_VOCAB + 'sequence>', 'predicate')
        .All()
    ]
};

// get details of a sequence inclusive handlers
exports.sequence = (g, sequence_id) => {
    return [
        // get end/err sequences
        g.V(sequence_id)
        .Out([
            FLOW_VOCAB + 'onEnd>',
            FLOW_VOCAB + 'onError>'
        ])
        .Tag('subject')
        .Save(RDF_SYNTAX + 'type>', 'predicate')
        .Out(SCHEMA + 'name>')
        .Out(RDF_SYNTAX + 'string>')
        .All(),

        // get end/error edges
        g.V(sequence_id)
        .Tag('subject')
        .Out([
            FLOW_VOCAB + 'onEnd>',
            FLOW_VOCAB + 'onError>',
            FLOW_VOCAB + 'next>'
        ], 'predicate')
        .All(),

        // handler
        g.V(sequence_id)
        .Out(FLOW_VOCAB + 'handler>')
        .Tag('subject')
        .Out([
            FLOW_VOCAB + 'emit>',
            FLOW_VOCAB + 'data>',
            FLOW_VOCAB + 'stream>',
            FLOW_VOCAB + 'next>'
        ], 'predicate')
        .All()
    ];
};

// get details of a handler
exports.handler = (g, handler_id) => {
    return [

        // state node
        g.V(handler_id)
        .Tag('subject')
        .Out(FLOW_VOCAB + 'state>', 'predicate')
        .Out(RDF_SYNTAX + 'string>')
        .All(),

        // arguments node
        g.V(handler_id)
        .Out(FLOW_VOCAB + 'args>')
        .Tag('subject')
        .Save(RDF_SYNTAX + 'type>', 'predicate')
        .Out(SCHEMA + 'name>')
        .Out(RDF_SYNTAX + 'string>')
        .All(),

         // handler edges
        g.V(handler_id)
        .Tag('subject')
        .Out(FLOW_VOCAB + 'args>', 'predicate')
        .All(),

        // argument's emits edges
        g.V(handler_id)
        .Tag('subject')
        .Out(FLOW_VOCAB + 'args>')
        .Out(FLOW_VOCAB + 'emit>', 'predicate')
        .All(),

        // sequences
        g.V(handler_id)
        .Out(FLOW_VOCAB + 'args>')
        .Out(FLOW_VOCAB + 'emit>')
        .Tag('subject')
        .Save(RDF_SYNTAX + 'type>', 'predicate')
        .Out(SCHEMA + 'name>')
        .Out(RDF_SYNTAX + 'string>')
        .All()
    ];
};
<|endoftext|>


<commit_before>var cp = require('child_process');
var path = require('path');
var Q = require('q');
var EventEmitter = require('events').EventEmitter;
var util = require('./util');
var HEARTBEAT_INTERVAL = util.HEARTBEAT_INTERVAL;
var HEARTBEAT_TIMEOUT = util.HEARTBEAT_TIMEOUT;
var MESSAGE = util.MESSAGE;
var DATA = util.DATA;
var MAIN = path.join(__dirname, 'main');
var cache = {};

function getKey(options) {
	var key = options && options.script;
	if (typeof key != 'string' || !(key = key.trim())) {
		return;
	}

	if (typeof options.value == 'string') {
		key = encodeURIComponent(key) + '?value=' + encodeURIComponent(options.value);
	}
	return key;
}

function fork(options, callback) {
	var key = getKey(options);
	if (!key) {
		return callback('options.script is required.');
	}
	var promise = cache[key];
	var defer;

	if (!promise) {
		defer = Q.defer();
		cache[key] = promise = defer.promise;
	}

	promise.done(function(result) {
		callback(null, result[0], result[1]);
	}, function(err) {
		callback(err);
	});

	if (!defer) {
		return;
	}

	var timeout, heartbeatTimeout, done;
	var emitter = new EventEmitter();
	var args = Array.isArray(options._args) ? options._args : [];
	delete options._args;
	args.push(MAIN, JSON.stringify(options));
	var child = cp.spawn('node', args, {
		detached: true,
		stdio: ['ipc']
	});

	child.on('error', errorHandler);
	child.on('close', errorHandler);
	child.on('exit', errorHandler);
	child.on('disconnect', errorHandler);
	child.on('message', function(msg) {
		clearTimeout(heartbeatTimeout);
		heartbeatTimeout = null;
		if (msg == MESSAGE) {
			return;
		}

		try {
			if (msg = JSON.parse(msg)) {
				if (msg.type == MESSAGE) {
					callbackHandler(null, msg.data);
				} else if (msg.type == DATA) {
					emitter.emit('data', msg.data);
				}
			}
		} catch(e) {}
	});
	if (child.stderr) {
		child.stderr.on('data', function(err) {
			if (done) {
				return;
			}
			killChild();
			callbackHandler(err + '');
		});
	}
	child.unref();
	keepAlive();
	promise.kill = killChild;

	function keepAlive() {
		timeout = setTimeout(function() {
			child.send(MESSAGE);
			keepAlive();
		}, HEARTBEAT_INTERVAL);
		if (!heartbeatTimeout) {
			heartbeatTimeout = setTimeout(errorHandler, HEARTBEAT_TIMEOUT);
		}
	}

	function errorHandler(err) {
		killChild();
		process.nextTick(function() {
			callbackHandler(err && err.stack || 'Error');
		});
	}

	function killChild(delay) {
		clearTimeout(heartbeatTimeout);
		clearTimeout(timeout);
		delete cache[key];
		var _kill = function() {
			try {
				process.kill(child.pid);
				child.removeAllListeners();
				child.on('error', util.noop);
				emitter.kill = util.noop;
				emitter.sendData = util.noop;
				emitter.emit('exit');
				if (child.stderr) {
					child.stderr.removeAllListeners();
				}
			} catch(e) {}
		};
		delay > 0 ? setTimeout(_kill, delay) : _kill();
	}

	function callbackHandler(err, data) {
		if (done) {
			return;
		}
		done = true;
		if (err) {
			defer.reject(err);
		} else {
			emitter.kill = killChild;
			emitter.sendData = function(data) {
				try {
					child.send(JSON.stringify({
						type: DATA,
						data: data
					}));
				} catch(e) {}
			};
			defer.resolve([data, emitter]);
		}
	}
}

exports.fork = function(options, callback) {
	try {
		return fork(options);
	} catch (err) {
		if (typeof callback === 'function') {
			callback(err);
		} else {
			throw err;
		}
	}
};

function kill(options, delay) {
	var key = getKey(options);
	if (!key) {
		return;
	}
	var child = cache[key];
	if (!child) {
		return;
	}
	child.kill(delay);
}

exports.kill = kill;

function exists(options) {
	var key = getKey(options);
	if (!key || !cache[key]) {
		return false;
	}

	return true;
}

exports.exists = exists;

<commit_msg>Release 0.1.5<commit_after>var cp = require('child_process');
var path = require('path');
var Q = require('q');
var EventEmitter = require('events').EventEmitter;
var util = require('./util');
var HEARTBEAT_INTERVAL = util.HEARTBEAT_INTERVAL;
var HEARTBEAT_TIMEOUT = util.HEARTBEAT_TIMEOUT;
var MESSAGE = util.MESSAGE;
var DATA = util.DATA;
var MAIN = path.join(__dirname, 'main');
var cache = {};

function getKey(options) {
	var key = options && options.script;
	if (typeof key != 'string' || !(key = key.trim())) {
		return;
	}

	if (typeof options.value == 'string') {
		key = encodeURIComponent(key) + '?value=' + encodeURIComponent(options.value);
	}
	return key;
}

function fork(options, callback) {
	var key = getKey(options);
	if (!key) {
		return callback('options.script is required.');
	}
	var promise = cache[key];
	var defer;

	if (!promise) {
		defer = Q.defer();
		cache[key] = promise = defer.promise;
	}

	promise.done(function(result) {
		callback(null, result[0], result[1]);
	}, function(err) {
		callback(err);
	});

	if (!defer) {
		return;
	}

	var timeout, heartbeatTimeout, done;
	var emitter = new EventEmitter();
	var args = Array.isArray(options._args) ? options._args : [];
	delete options._args;
	args.push(MAIN, JSON.stringify(options));
	var child = cp.spawn('node', args, {
		detached: true,
		stdio: ['ipc']
	});

	child.on('error', errorHandler);
	child.on('close', errorHandler);
	child.on('exit', errorHandler);
	child.on('disconnect', errorHandler);
	child.on('message', function(msg) {
		clearTimeout(heartbeatTimeout);
		heartbeatTimeout = null;
		if (msg == MESSAGE) {
			return;
		}

		try {
			if (msg = JSON.parse(msg)) {
				if (msg.type == MESSAGE) {
					callbackHandler(null, msg.data);
				} else if (msg.type == DATA) {
					emitter.emit('data', msg.data);
				}
			}
		} catch(e) {}
	});
	if (child.stderr) {
		child.stderr.on('data', function(err) {
			if (done) {
				return;
			}
			killChild();
			callbackHandler(err + '');
		});
	}
	child.unref();
	keepAlive();
	promise.kill = killChild;

	function keepAlive() {
		timeout = setTimeout(function() {
			child.send(MESSAGE);
			keepAlive();
		}, HEARTBEAT_INTERVAL);
		if (!heartbeatTimeout) {
			heartbeatTimeout = setTimeout(errorHandler, HEARTBEAT_TIMEOUT);
		}
	}

	function errorHandler(err) {
		killChild();
		process.nextTick(function() {
			callbackHandler(err && err.stack || 'Error');
		});
	}

	function killChild(delay) {
		clearTimeout(heartbeatTimeout);
		clearTimeout(timeout);
		delete cache[key];
		var _kill = function() {
			try {
				process.kill(child.pid);
				child.removeAllListeners();
				child.on('error', util.noop);
				emitter.kill = util.noop;
				emitter.sendData = util.noop;
				emitter.emit('exit');
				if (child.stderr) {
					child.stderr.removeAllListeners();
				}
			} catch(e) {}
		};
		delay > 0 ? setTimeout(_kill, delay) : _kill();
	}

	function callbackHandler(err, data) {
		if (done) {
			return;
		}
		done = true;
		if (err) {
			defer.reject(err);
		} else {
			emitter.kill = killChild;
			emitter.sendData = function(data) {
				try {
					child.send(JSON.stringify({
						type: DATA,
						data: data
					}));
				} catch(e) {}
			};
			defer.resolve([data, emitter]);
		}
	}
}

exports.fork = function(options, callback) {
	try {
		return fork(options, callback);
	} catch (err) {
		callback(err);
	}
};

function kill(options, delay) {
	var key = getKey(options);
	if (!key) {
		return;
	}
	var child = cache[key];
	if (!child) {
		return;
	}
	child.kill(delay);
}

exports.kill = kill;

function exists(options) {
	var key = getKey(options);
	if (!key || !cache[key]) {
		return false;
	}

	return true;
}

exports.exists = exists;

<|endoftext|>