<commit_before>/*eslint no-invalid-this: 0*/ // eslint doesn't understand Promise.coroutine wrapping
"use strict";
var Promise = require("bluebird");
var IrcRoom = require("../models/IrcRoom");
var IrcAction = require("../models/IrcAction");
var MatrixRoom = require("matrix-appservice-bridge").MatrixRoom;
var ConfigValidator = require("matrix-appservice-bridge").ConfigValidator;

var log = require("../logging").get("Provisioner");
var promiseutil = require("../promiseutil.js");

var matrixRoomIdValidation = {
    "type": "string",
    "pattern": "^!.*:.*$"
};

var validationProperties = {
    "matrix_room_id" : matrixRoomIdValidation,
    "remote_room_channel" : {
        "type": "string",
        "pattern": "^([#+&]|(![A-Z0-9]{5}))[^\\s:,]+$"
    },
    "remote_room_server" : {
        "type": "string",
        "pattern": "^[a-z\\.0-9:-]+$"
    },
    "op_nick" : {
        "type": "string"
    },
    "key" : {
        "type": "string"
    },
    "user_id" : {
        "type": "string"
    }
};

function Provisioner(ircBridge, enabled) {
    this._ircBridge = ircBridge;
    this._enabled = enabled;
    this._pendingRequests = {};
    // {
    //   $domain: {
    //     $nick: {
    //        userId : string
    //        defer: Deferred
    //     }
    //   }

    this._linkValidator = new ConfigValidator({
        "type": "object",
        "properties": validationProperties,
        "required": [
            "matrix_room_id",
            "remote_room_channel",
            "remote_room_server",
            "op_nick",
            "user_id"
        ]
    });
    this._unlinkValidator = new ConfigValidator({
        "type": "object",
        "properties": validationProperties,
        "required": [
            "matrix_room_id",
            "remote_room_channel",
            "remote_room_server"
        ]
    });
    this._roomIdValidator = new ConfigValidator({
        "type": "object",
        "properties": {
            "matrix_room_id" : matrixRoomIdValidation
        }
    });

    if (enabled) {
        log.info("Starting provisioning...");
    }
    else {
        log.info("Provisioning disabled.");
    }

    let as = this._ircBridge.getAppServiceBridge().appService;
    let self = this;

    if (enabled && !(as.app.use && as.app.get && as.app.post)) {
        throw new Error('Could not start provisioning API');
    }

    // Disable all provision endpoints by not calling 'next' and returning an error instead
    if (!enabled) {
        as.app.use(function(req, res, next) {
            if (self.isProvisionRequest(req)) {
                res.header("Access-Control-Allow-Origin", "*");
                res.header("Access-Control-Allow-Headers",
                    "Origin, X-Requested-With, Content-Type, Accept");
                res.status(200);
                res.json({error : 'Provisioning is not enabled.'});
            }
            else {
                next();
            }
        });
    }

    // Deal with CORS (temporarily for s-web)
    as.app.use(function(req, res, next) {
        if (self.isProvisionRequest(req)) {
                res.header("Access-Control-Allow-Origin", "*");
                res.header("Access-Control-Allow-Headers",
                    "Origin, X-Requested-With, Content-Type, Accept");
        }
        next();
    });

    as.app.post("/_matrix/provision/link", Promise.coroutine(function*(req, res) {
        try {
            yield self.requestLink(req.body);
            res.json({});
        }
        catch (err) {
            res.status(500).json({error: err.message});
            throw err;
        }
    }));

    as.app.post("/_matrix/provision/unlink", Promise.coroutine(function*(req, res) {
        try {
            yield self.unlink(req.body);
            res.json({});
        }
        catch (err) {
            res.status(500).json({error: err.message});
            throw err;
        }
    }));

    as.app.get("/_matrix/provision/listlinks/:roomId", Promise.coroutine(function*(req, res) {
        try {
            let list = yield self.listings(req.params.roomId);
            res.json(list);
        }
        catch (err) {
            res.status(500).json({error: err.message});
            throw err;
        }
    }));

    if (enabled) {
        log.info("Provisioning started");
    }
}

Provisioner.prototype.isProvisionRequest = function(req) {
    return req.url === '/_matrix/provision/unlink' ||
            req.url === '/_matrix/provision/link'||
            req.url.match(/^\/_matrix\/provision\/listlinks/)
};

Provisioner.prototype._updateBridgingState = Promise.coroutine(
    function*(roomId, userId, status, skey) {
        let intent = this._ircBridge.getAppServiceBridge().getIntent();
        try {
            yield intent.client.sendStateEvent(roomId, 'm.room.bridging', {
                user_id: userId,
                status: status // pending, success, failure
            }, skey);
        }
        catch (err) {
            console.error(err);
            throw new Error(`Could not update m.room.bridging state in this room`);
        }
    }
);

Provisioner.prototype._authoriseProvisioning = Promise.coroutine(
    function*(server, userId, ircChannel, roomId, opNick, key) {
        let ircDomain = server.domain;

        let existing = this._getRequest(server, opNick);
        if (existing) {
            let from = existing.userId;
            throw new Error(`Bridging request already sent to `+
                            `${opNick} on ${server.domain} from ${from}`);
        }

        // (Matrix) Check power level of user
        log.info(`Check power level of user`);
        let matrixClient = this._ircBridge.getAppServiceBridge().getClientFactory().getClientAs();

        let powerState = yield matrixClient.getStateEvent(roomId, 'm.room.power_levels');

        if (!powerState) {
            throw new Error('Power levels not available');
        }

        let actualPower = 0;
        if (powerState.users[userId] !== undefined) {
            actualPower = powerState.users[userId];
        }
        else if (powerState.users_default !== undefined) {
            actualPower = powerState.users_default;
        }

        let requiredPower = 50;
        if (powerState.events["m.room.power_levels"] !== undefined) {
            requiredPower = powerState.events["m.room.power_levels"]
        }
        else if (powerState.state_default !== undefined) {
            requiredPower = powerState.state_default;
        }

        if (actualPower < requiredPower) {
            throw new Error('User does not possess high enough power level');
        }

        // (IRC) Check that op's nick is actually op
        log.info(`Check that op's nick is actually op`);
        let botClient = yield this._ircBridge.getBotClient(server);
        yield botClient.joinChannel(ircChannel, key);
        log.info(`Bot joined channel ${ircChannel}`);
        let info = yield botClient.getOperators(ircChannel);

        if (info.operatorNicks.indexOf(opNick) == -1) {
            throw new Error(`Provided user is not an op of ${ircChannel}`);
        }

        // (Matrix) update room state
        log.info(`Sending m.room.bridging`);
        // State key for m.room.bridging
        let skey = `irc://${ircDomain}/${ircChannel}`;

        // Send pending m.room.bridging
        yield this._updateBridgingState(roomId, userId, 'pending', skey);

        // (IRC) Ask operator for authorisation
        // Time that operator has to respond before giving up
        let timeoutSeconds = 1;

        // Deliberately not yielding on this so that 200 OK is returned
        log.info(`Contacting operator`);
        this._contactOperator(
            botClient, server, opNick, ircChannel,
            roomId, userId, skey, timeoutSeconds);
    }
);

Provisioner.prototype._contactOperator = Promise.coroutine(
    function*(botClient, server, opNick, ircChannel, roomId, userId, skey, timeoutSeconds) {
        let d = promiseutil.defer();

        // Send PM to operator
        let authRequestAction = new IrcAction("message",
            `${userId} has requested to bridge ${roomId} with ${ircChannel} on this IRC ` +
            `network. Respond with 'yes' or 'y' to allow, or simply ignore this message to ` +
            `dissallow. You have ${timeoutSeconds} seconds from when this message was sent.`);
        let pmRoom = new IrcRoom(server, opNick);

        this._setRequest(server, opNick, {userId: userId, defer: d});

        this._ircBridge.sendIrcAction(pmRoom, botClient, authRequestAction);
        try {
            yield d.promise.timeout(timeoutSeconds * 1000);
            this._removeRequest(server, opNick);
        }
        catch (err) {
            log.info(`Operator ${opNick} did not respond (${err.message})`);
            yield this._updateBridgingState(roomId, userId, 'failure', skey);
            this._removeRequest(server, opNick);
            return;
        }
        try {
            yield this._dolink(server, ircChannel, roomId);
        }
        catch (err) {
            log.info(`Failed to create link following authorisation (${err.message})`);
            yield this._updateBridgingState(roomId, userId, 'failure', skey);
            this._removeRequest(server, opNick);
            return;
        }
        yield this._updateBridgingState(roomId, userId, 'success', skey);
    }
);

Provisioner.prototype._removeRequest = function (server, opNick) {
    delete this._pendingRequests[server.domain][opNick];
}

Provisioner.prototype._getRequest = function (server, opNick) {
    if (this._pendingRequests[server.domain]) {
        return this._pendingRequests[server.domain][opNick];
    }
}

Provisioner.prototype._setRequest = function (server, opNick, request) {
    if (!this._pendingRequests[server.domain]) {
        this._pendingRequests[server.domain] = {};
    }
    this._pendingRequests[server.domain][opNick] = request;
}

Provisioner.prototype.handlePm = function(server, fromUser, text) {
    if (['y', 'yes'].indexOf(text.trim().toLowerCase()) == -1) {
        log.warn(`Provisioner only handles text 'yes'/'y' ` +
                 `(from ${fromUser.nick} on ${server.domain})`);
        return;
    }
    let request = this._getRequest(server, fromUser.nick);
    if (request) {
        log.info(`${fromUser.nick} has authorised a new provisioning`);
        request.defer.resolve();
        return;
    }
    log.warn(`Provisioner was not expecting PM from ${fromUser.nick} on ${server.domain}`);
}

// Link an IRC channel to a matrix room ID
Provisioner.prototype.requestLink = Promise.coroutine(function*(options) {
    try {
        this._linkValidator.validate(options);
    }
    catch (err) {
        log.error(err);
        throw new Error("Malformed parameters");
    }

    let ircDomain = options.remote_room_server;
    let ircChannel = options.remote_room_channel;
    let roomId = options.matrix_room_id;
    let opNick = options.op_nick;
    let key = options.key || undefined; // Optional key
    let userId = options.user_id;
    let mappingLogId = `${roomId} <---> ${ircDomain}/${ircChannel}`;

    // Try to find the domain requested for linking
    //TODO: ircDomain might include protocol, i.e. irc://irc.freenode.net
    let server = this._ircBridge.getServer(ircDomain);

    if (!server) {
        throw new Error(`Server requested for linking not found ('${ircDomain}')`);
    }

    if (server.isExcludedChannel(ircChannel)) {
        throw new Error(`Server is configured to exclude given channel ('${ircChannel}')`);
    }

    let entry = yield this._ircBridge.getStore().getRoom(roomId, ircDomain, ircChannel);
    if (!entry) {
        // Ask OP for provisioning authentication
        try {
            yield this._authoriseProvisioning(server, userId, ircChannel, roomId, opNick, key);
        }
        catch (err) {
            console.error(err.stack);
            throw new Error(err.message);
        }
    }
    else {
        throw new Error(`Room mapping already exists (${mappingLogId},` +
                        `origin = ${entry.data.origin})`);
    }
});

Provisioner.prototype._dolink = Promise.coroutine(
    function*(server, ircChannel, roomId) {
        let ircDomain = server.domain;
        let mappingLogId = `${roomId} <---> ${ircDomain}/${ircChannel}`;
        log.info(`Provisioning link for room ${mappingLogId}`);

        // Create rooms for the link
        let ircRoom = new IrcRoom(server, ircChannel);
        let mxRoom = new MatrixRoom(roomId);

        let entry = yield this._ircBridge.getStore().getRoom(roomId, ircDomain, ircChannel);
        if (!entry) {
            yield this._ircBridge.getStore().storeRoom(ircRoom, mxRoom, 'provision');
        }
        else {
            throw new Error(`Room mapping already exists (${mappingLogId},` +
                            `origin = ${entry.data.origin})`);
        }
    }
);

// Unlink an IRC channel from a matrix room ID
Provisioner.prototype.unlink = Promise.coroutine(function*(options) {
    try {
        this._unlinkValidator.validate(options);
    }
    catch (err) {
        log.error(err);
        throw new Error("Malformed parameters");
    }

    let ircDomain = options.remote_room_server;
    let ircChannel = options.remote_room_channel;
    let roomId = options.matrix_room_id;
    let mappingLogId = `${roomId} <-/-> ${ircDomain}/${ircChannel}`;

    log.info(`Provisioning unlink for room ${mappingLogId}`);

    // Try to find the domain requested for unlinking
    let server = this._ircBridge.getServer(ircDomain);

    if (!server) {
        throw new Error("Server requested for linking not found");
    }

    // Delete the room link
    let entry = yield this._ircBridge.getStore()
        .getRoom(roomId, ircDomain, ircChannel, 'provision');

    if (entry) {
        yield this._ircBridge.getStore().removeRoom(roomId, ircDomain, ircChannel, 'provision');
    }
    else {
        throw new Error(`Provisioned room mapping does not exist (${mappingLogId})`);
    }
});

// List all mappings currently provisioned with the given matrix_room_id
Provisioner.prototype.listings = function(roomId) {
    try {
        this._roomIdValidator.validate({"matrix_room_id": roomId});
    }
    catch (err) {
        log.error(err);
        throw new Error("Malformed parameters");
    }

    return this._ircBridge.getStore()
        .getProvisionedMappings(roomId)
        .map((entry) => {
            return {
                matrix_room_id : entry.matrix.roomId,
                remote_room_channel : entry.remote.data.channel,
                remote_room_server : entry.remote.data.domain,
            }
        });
};

module.exports = Provisioner;
<commit_msg>_dolink->_doLink<commit_after>/*eslint no-invalid-this: 0*/ // eslint doesn't understand Promise.coroutine wrapping
"use strict";
var Promise = require("bluebird");
var IrcRoom = require("../models/IrcRoom");
var IrcAction = require("../models/IrcAction");
var MatrixRoom = require("matrix-appservice-bridge").MatrixRoom;
var ConfigValidator = require("matrix-appservice-bridge").ConfigValidator;

var log = require("../logging").get("Provisioner");
var promiseutil = require("../promiseutil.js");

var matrixRoomIdValidation = {
    "type": "string",
    "pattern": "^!.*:.*$"
};

var validationProperties = {
    "matrix_room_id" : matrixRoomIdValidation,
    "remote_room_channel" : {
        "type": "string",
        "pattern": "^([#+&]|(![A-Z0-9]{5}))[^\\s:,]+$"
    },
    "remote_room_server" : {
        "type": "string",
        "pattern": "^[a-z\\.0-9:-]+$"
    },
    "op_nick" : {
        "type": "string"
    },
    "key" : {
        "type": "string"
    },
    "user_id" : {
        "type": "string"
    }
};

function Provisioner(ircBridge, enabled) {
    this._ircBridge = ircBridge;
    this._enabled = enabled;
    this._pendingRequests = {};
    // {
    //   $domain: {
    //     $nick: {
    //        userId : string
    //        defer: Deferred
    //     }
    //   }

    this._linkValidator = new ConfigValidator({
        "type": "object",
        "properties": validationProperties,
        "required": [
            "matrix_room_id",
            "remote_room_channel",
            "remote_room_server",
            "op_nick",
            "user_id"
        ]
    });
    this._unlinkValidator = new ConfigValidator({
        "type": "object",
        "properties": validationProperties,
        "required": [
            "matrix_room_id",
            "remote_room_channel",
            "remote_room_server"
        ]
    });
    this._roomIdValidator = new ConfigValidator({
        "type": "object",
        "properties": {
            "matrix_room_id" : matrixRoomIdValidation
        }
    });

    if (enabled) {
        log.info("Starting provisioning...");
    }
    else {
        log.info("Provisioning disabled.");
    }

    let as = this._ircBridge.getAppServiceBridge().appService;
    let self = this;

    if (enabled && !(as.app.use && as.app.get && as.app.post)) {
        throw new Error('Could not start provisioning API');
    }

    // Disable all provision endpoints by not calling 'next' and returning an error instead
    if (!enabled) {
        as.app.use(function(req, res, next) {
            if (self.isProvisionRequest(req)) {
                res.header("Access-Control-Allow-Origin", "*");
                res.header("Access-Control-Allow-Headers",
                    "Origin, X-Requested-With, Content-Type, Accept");
                res.status(200);
                res.json({error : 'Provisioning is not enabled.'});
            }
            else {
                next();
            }
        });
    }

    // Deal with CORS (temporarily for s-web)
    as.app.use(function(req, res, next) {
        if (self.isProvisionRequest(req)) {
                res.header("Access-Control-Allow-Origin", "*");
                res.header("Access-Control-Allow-Headers",
                    "Origin, X-Requested-With, Content-Type, Accept");
        }
        next();
    });

    as.app.post("/_matrix/provision/link", Promise.coroutine(function*(req, res) {
        try {
            yield self.requestLink(req.body);
            res.json({});
        }
        catch (err) {
            res.status(500).json({error: err.message});
            throw err;
        }
    }));

    as.app.post("/_matrix/provision/unlink", Promise.coroutine(function*(req, res) {
        try {
            yield self.unlink(req.body);
            res.json({});
        }
        catch (err) {
            res.status(500).json({error: err.message});
            throw err;
        }
    }));

    as.app.get("/_matrix/provision/listlinks/:roomId", Promise.coroutine(function*(req, res) {
        try {
            let list = yield self.listings(req.params.roomId);
            res.json(list);
        }
        catch (err) {
            res.status(500).json({error: err.message});
            throw err;
        }
    }));

    if (enabled) {
        log.info("Provisioning started");
    }
}

Provisioner.prototype.isProvisionRequest = function(req) {
    return req.url === '/_matrix/provision/unlink' ||
            req.url === '/_matrix/provision/link'||
            req.url.match(/^\/_matrix\/provision\/listlinks/)
};

Provisioner.prototype._updateBridgingState = Promise.coroutine(
    function*(roomId, userId, status, skey) {
        let intent = this._ircBridge.getAppServiceBridge().getIntent();
        try {
            yield intent.client.sendStateEvent(roomId, 'm.room.bridging', {
                user_id: userId,
                status: status // pending, success, failure
            }, skey);
        }
        catch (err) {
            console.error(err);
            throw new Error(`Could not update m.room.bridging state in this room`);
        }
    }
);

Provisioner.prototype._authoriseProvisioning = Promise.coroutine(
    function*(server, userId, ircChannel, roomId, opNick, key) {
        let ircDomain = server.domain;

        let existing = this._getRequest(server, opNick);
        if (existing) {
            let from = existing.userId;
            throw new Error(`Bridging request already sent to `+
                            `${opNick} on ${server.domain} from ${from}`);
        }

        // (Matrix) Check power level of user
        log.info(`Check power level of user`);
        let matrixClient = this._ircBridge.getAppServiceBridge().getClientFactory().getClientAs();

        let powerState = yield matrixClient.getStateEvent(roomId, 'm.room.power_levels');

        if (!powerState) {
            throw new Error('Power levels not available');
        }

        let actualPower = 0;
        if (powerState.users[userId] !== undefined) {
            actualPower = powerState.users[userId];
        }
        else if (powerState.users_default !== undefined) {
            actualPower = powerState.users_default;
        }

        let requiredPower = 50;
        if (powerState.events["m.room.power_levels"] !== undefined) {
            requiredPower = powerState.events["m.room.power_levels"]
        }
        else if (powerState.state_default !== undefined) {
            requiredPower = powerState.state_default;
        }

        if (actualPower < requiredPower) {
            throw new Error('User does not possess high enough power level');
        }

        // (IRC) Check that op's nick is actually op
        log.info(`Check that op's nick is actually op`);
        let botClient = yield this._ircBridge.getBotClient(server);
        yield botClient.joinChannel(ircChannel, key);
        log.info(`Bot joined channel ${ircChannel}`);
        let info = yield botClient.getOperators(ircChannel);

        if (info.operatorNicks.indexOf(opNick) == -1) {
            throw new Error(`Provided user is not an op of ${ircChannel}`);
        }

        // (Matrix) update room state
        log.info(`Sending m.room.bridging`);
        // State key for m.room.bridging
        let skey = `irc://${ircDomain}/${ircChannel}`;

        // Send pending m.room.bridging
        yield this._updateBridgingState(roomId, userId, 'pending', skey);

        // (IRC) Ask operator for authorisation
        // Time that operator has to respond before giving up
        let timeoutSeconds = 1;

        // Deliberately not yielding on this so that 200 OK is returned
        log.info(`Contacting operator`);
        this._contactOperator(
            botClient, server, opNick, ircChannel,
            roomId, userId, skey, timeoutSeconds);
    }
);

Provisioner.prototype._contactOperator = Promise.coroutine(
    function*(botClient, server, opNick, ircChannel, roomId, userId, skey, timeoutSeconds) {
        let d = promiseutil.defer();

        // Send PM to operator
        let authRequestAction = new IrcAction("message",
            `${userId} has requested to bridge ${roomId} with ${ircChannel} on this IRC ` +
            `network. Respond with 'yes' or 'y' to allow, or simply ignore this message to ` +
            `dissallow. You have ${timeoutSeconds} seconds from when this message was sent.`);
        let pmRoom = new IrcRoom(server, opNick);

        this._setRequest(server, opNick, {userId: userId, defer: d});

        this._ircBridge.sendIrcAction(pmRoom, botClient, authRequestAction);
        try {
            yield d.promise.timeout(timeoutSeconds * 1000);
            this._removeRequest(server, opNick);
        }
        catch (err) {
            log.info(`Operator ${opNick} did not respond (${err.message})`);
            yield this._updateBridgingState(roomId, userId, 'failure', skey);
            this._removeRequest(server, opNick);
            return;
        }
        try {
            yield this._doLink(server, ircChannel, roomId);
        }
        catch (err) {
            log.info(`Failed to create link following authorisation (${err.message})`);
            yield this._updateBridgingState(roomId, userId, 'failure', skey);
            this._removeRequest(server, opNick);
            return;
        }
        yield this._updateBridgingState(roomId, userId, 'success', skey);
    }
);

Provisioner.prototype._removeRequest = function (server, opNick) {
    delete this._pendingRequests[server.domain][opNick];
}

Provisioner.prototype._getRequest = function (server, opNick) {
    if (this._pendingRequests[server.domain]) {
        return this._pendingRequests[server.domain][opNick];
    }
}

Provisioner.prototype._setRequest = function (server, opNick, request) {
    if (!this._pendingRequests[server.domain]) {
        this._pendingRequests[server.domain] = {};
    }
    this._pendingRequests[server.domain][opNick] = request;
}

Provisioner.prototype.handlePm = function(server, fromUser, text) {
    if (['y', 'yes'].indexOf(text.trim().toLowerCase()) == -1) {
        log.warn(`Provisioner only handles text 'yes'/'y' ` +
                 `(from ${fromUser.nick} on ${server.domain})`);
        return;
    }
    let request = this._getRequest(server, fromUser.nick);
    if (request) {
        log.info(`${fromUser.nick} has authorised a new provisioning`);
        request.defer.resolve();
        return;
    }
    log.warn(`Provisioner was not expecting PM from ${fromUser.nick} on ${server.domain}`);
}

// Link an IRC channel to a matrix room ID
Provisioner.prototype.requestLink = Promise.coroutine(function*(options) {
    try {
        this._linkValidator.validate(options);
    }
    catch (err) {
        log.error(err);
        throw new Error("Malformed parameters");
    }

    let ircDomain = options.remote_room_server;
    let ircChannel = options.remote_room_channel;
    let roomId = options.matrix_room_id;
    let opNick = options.op_nick;
    let key = options.key || undefined; // Optional key
    let userId = options.user_id;
    let mappingLogId = `${roomId} <---> ${ircDomain}/${ircChannel}`;

    // Try to find the domain requested for linking
    //TODO: ircDomain might include protocol, i.e. irc://irc.freenode.net
    let server = this._ircBridge.getServer(ircDomain);

    if (!server) {
        throw new Error(`Server requested for linking not found ('${ircDomain}')`);
    }

    if (server.isExcludedChannel(ircChannel)) {
        throw new Error(`Server is configured to exclude given channel ('${ircChannel}')`);
    }

    let entry = yield this._ircBridge.getStore().getRoom(roomId, ircDomain, ircChannel);
    if (!entry) {
        // Ask OP for provisioning authentication
        try {
            yield this._authoriseProvisioning(server, userId, ircChannel, roomId, opNick, key);
        }
        catch (err) {
            console.error(err.stack);
            throw new Error(err.message);
        }
    }
    else {
        throw new Error(`Room mapping already exists (${mappingLogId},` +
                        `origin = ${entry.data.origin})`);
    }
});

Provisioner.prototype._doLink = Promise.coroutine(
    function*(server, ircChannel, roomId) {
        let ircDomain = server.domain;
        let mappingLogId = `${roomId} <---> ${ircDomain}/${ircChannel}`;
        log.info(`Provisioning link for room ${mappingLogId}`);

        // Create rooms for the link
        let ircRoom = new IrcRoom(server, ircChannel);
        let mxRoom = new MatrixRoom(roomId);

        let entry = yield this._ircBridge.getStore().getRoom(roomId, ircDomain, ircChannel);
        if (!entry) {
            yield this._ircBridge.getStore().storeRoom(ircRoom, mxRoom, 'provision');
        }
        else {
            throw new Error(`Room mapping already exists (${mappingLogId},` +
                            `origin = ${entry.data.origin})`);
        }
    }
);

// Unlink an IRC channel from a matrix room ID
Provisioner.prototype.unlink = Promise.coroutine(function*(options) {
    try {
        this._unlinkValidator.validate(options);
    }
    catch (err) {
        log.error(err);
        throw new Error("Malformed parameters");
    }

    let ircDomain = options.remote_room_server;
    let ircChannel = options.remote_room_channel;
    let roomId = options.matrix_room_id;
    let mappingLogId = `${roomId} <-/-> ${ircDomain}/${ircChannel}`;

    log.info(`Provisioning unlink for room ${mappingLogId}`);

    // Try to find the domain requested for unlinking
    let server = this._ircBridge.getServer(ircDomain);

    if (!server) {
        throw new Error("Server requested for linking not found");
    }

    // Delete the room link
    let entry = yield this._ircBridge.getStore()
        .getRoom(roomId, ircDomain, ircChannel, 'provision');

    if (entry) {
        yield this._ircBridge.getStore().removeRoom(roomId, ircDomain, ircChannel, 'provision');
    }
    else {
        throw new Error(`Provisioned room mapping does not exist (${mappingLogId})`);
    }
});

// List all mappings currently provisioned with the given matrix_room_id
Provisioner.prototype.listings = function(roomId) {
    try {
        this._roomIdValidator.validate({"matrix_room_id": roomId});
    }
    catch (err) {
        log.error(err);
        throw new Error("Malformed parameters");
    }

    return this._ircBridge.getStore()
        .getProvisionedMappings(roomId)
        .map((entry) => {
            return {
                matrix_room_id : entry.matrix.roomId,
                remote_room_channel : entry.remote.data.channel,
                remote_room_server : entry.remote.data.domain,
            }
        });
};

module.exports = Provisioner;
<|endoftext|>


<commit_before>// TODO
<commit_msg>Export main function to setup simulated events.<commit_after>import DragDataStore from './DragDataStore'
import DataTransfer from './DataTransfer'
import simulateEvent from './simulateEvent'

export default function setupTouchDNDCustomEvents() {
  window.touchDnd = {
    store: null,
    dataTransfer: null,
    lastDraggedOver: null,
    draggedItem: null
  }

  var touchDnd = window.touchDnd

  document.addEventListener('touchstart', handleTouchStart, true)
  document.addEventListener('touchend', handleTouchEnd, true)
  document.addEventListener('touchmove', handleTouchMove, true)
}

function handleTouchStart (event) {
  if (event.target.hasAttribute("draggable")) {
    event.preventDefault()

    var target = document.elementFromPoint(
      event.changedTouches[0].clientX,
      event.changedTouches[0].clientY
    )

    var store = new DragDataStore()
    var dataTransfer = new DataTransfer(store)

    touchDnd.store = store
    touchDnd.dataTransfer = dataTransfer
    touchDnd.draggedItem = target

    store.mode = 'readwrite'

    simulateEvent('touchdragstart', event, dataTransfer, target)
  }
}

function handleTouchMove (event) {
  if (touchDnd.draggedItem) {
    event.preventDefault()

    touchDnd.store.mode = 'readwrite'
    var dataTransfer = touchDnd.dataTransfer

    simulateEvent('touchdrag', event, dataTransfer, touchDnd.draggedItem)

    var lastDraggedOver = touchDnd.lastDraggedOver

    var draggedOverTarget = document.elementFromPoint(
      event.changedTouches[0].clientX,
      event.changedTouches[0].clientY
    )

    if (lastDraggedOver !== draggedOverTarget) {
      if (lastDraggedOver) {
        simulateEvent('touchdragleave', event, dataTransfer, lastDraggedOver)
      }

      simulateEvent('touchdragenter', event, dataTransfer, draggedOverTarget)

      touchDnd.lastDraggedOver = draggedOverTarget
    }
  }
}

function handleTouchEnd (event) {
  if (touchDnd.draggedItem) {
    event.preventDefault()

    var target = document.elementFromPoint(
      event.changedTouches[0].clientX,
      event.changedTouches[0].clientY
    )

    var store = touchDnd.store
    var dataTransfer = touchDnd.dataTransfer

    store.mode = 'readonly'
    simulateEvent('touchdrop', event, dataTransfer, target)

    store.mode = 'protected'
    simulateEvent('touchdragend', event, dataTransfer, target)

    touchDnd.store = null
    touchDnd.dataTransfer = null
    touchDnd.draggedItem = null
  }
}

<|endoftext|>


<commit_before>import URL from 'url-parse';

import { DEFAULT_KEY } from './constants';
import { camelCase, actionTypeCase, initialEndpointState } from './utils';

const defaultResolver = () => DEFAULT_KEY;

export const createEndpoint = ({
  name,
  request,
  url,
  resolver = defaultResolver,
}) => {
  const actionTypeCaseName = actionTypeCase(name);
  const camelCaseName = camelCase(name);

  const parsedUrl = new URL(url);
  // Thanks, Jeremy
  const namedParam = /(\(\?)?:\w+/g;
  const urlParams = [];

  let match = true;

  while (match) {
    match = namedParam.exec(parsedUrl.pathname);
    if (match) urlParams.push(match[0]);
  }

  const ingestActionType =
    `${camelCaseName}/INGEST_${actionTypeCaseName}_RESPONSE`;

  const requestActionType =
    `${camelCaseName}/MAKE_${actionTypeCaseName}_REQUEST`;

  const ingestActionCreator = (payload, meta) => {
    return {
      error: (payload instanceof Error) ? true : false,
      meta,
      payload,
      type: ingestActionType,
    };
  };

  ingestActionCreator.toString = () => ingestActionType;

  const requestActionCreator = (...params) => {
    let options = {}, reqUrl = url;

    urlParams.forEach((p, i) => {
      reqUrl = reqUrl.replace(p, params[i]);
    });

    if (typeof params[params.length - 1] === 'object') {
      options = params[params.length - 1];
    }

    const metaParams = urlParams.reduce((memo, p, i) => {
      memo[p.replace(':', '')] = params[i];
      return memo;
    }, {});

    return {
      meta: {
        params: metaParams,
        path: resolver(...params),
      },
      payload: {
        options,
        url: reqUrl,
      },
      type: requestActionType,
    };
  };

  requestActionCreator.toString = () => requestActionType;

  const actionCreators = {
    ingest: ingestActionCreator,
    request: requestActionCreator,
  };

  const middleware = store => next => action => {
    if (action.type === requestActionType) {
      request(action.payload.url, action.payload.options)
        .then(data =>
          store.dispatch(actionCreators.ingest(data, action.meta))
        )
        .catch(error =>
          store.dispatch(actionCreators.ingest(error, action.meta))
        );
    }
    return next(action);
  }

  const reducer = (previous = {}, action) => {
    let nextState = null;

    if (action.type === requestActionType) {
      nextState = Object.assign({}, previous);
      const path = action.meta.path;
      if (!nextState[path]) {
        const init = initialEndpointState();
        init.pendingRequests = 1;
        nextState[path] = init
      } else {
        nextState[path] = Object.assign({}, nextState[path]);
        nextState[path].pendingRequests += 1;
      }
    }

    if (action.type === ingestActionType) {
      nextState = Object.assign({}, previous);
      const path = action.meta.path;
      const nextPathState = Object.assign({}, nextState[path]);
      nextState[path] = nextPathState;

      if (action.error) {
        const { message, name } = action.payload;
        nextPathState.error = { message, name }
      } else {
        nextPathState.data = action.payload;
        nextPathState.error = null;
      }
      nextPathState.pendingRequests--;
    }

    if (nextState !== null)
      return nextState;

    return previous;
  };

  const selectorMap = {};

  const selector = (path = DEFAULT_KEY) => {
    let s;

    if (!selectorMap[path]) {
      s = state => (state[path] && state[path].data) || null;
      selectorMap[path] = s;
    } else {
      s = selectorMap[path];
    }

    return s;
  };

  return {
    actionCreators,
    middleware,
    reducer,
    selector,
  };
};
<commit_msg>Handle Promise rejections as if they are errors<commit_after>import URL from 'url-parse';

import { DEFAULT_KEY } from './constants';
import { camelCase, actionTypeCase, initialEndpointState } from './utils';

const defaultResolver = () => DEFAULT_KEY;

export const createEndpoint = ({
  name,
  request,
  url,
  resolver = defaultResolver,
}) => {
  const actionTypeCaseName = actionTypeCase(name);
  const camelCaseName = camelCase(name);

  const parsedUrl = new URL(url);
  // Thanks, Jeremy
  const namedParam = /(\(\?)?:\w+/g;
  const urlParams = [];

  let match = true;

  while (match) {
    match = namedParam.exec(parsedUrl.pathname);
    if (match) urlParams.push(match[0]);
  }

  const ingestActionType =
    `${camelCaseName}/INGEST_${actionTypeCaseName}_RESPONSE`;

  const requestActionType =
    `${camelCaseName}/MAKE_${actionTypeCaseName}_REQUEST`;

  const ingestActionCreator = (payload, meta) => {
    return {
      error: (payload instanceof Error) ? true : false,
      meta,
      payload,
      type: ingestActionType,
    };
  };

  ingestActionCreator.toString = () => ingestActionType;

  const requestActionCreator = (...params) => {
    let options = {}, reqUrl = url;

    urlParams.forEach((p, i) => {
      reqUrl = reqUrl.replace(p, params[i]);
    });

    if (typeof params[params.length - 1] === 'object') {
      options = params[params.length - 1];
    }

    const metaParams = urlParams.reduce((memo, p, i) => {
      memo[p.replace(':', '')] = params[i];
      return memo;
    }, {});

    return {
      meta: {
        params: metaParams,
        path: resolver(...params),
      },
      payload: {
        options,
        url: reqUrl,
      },
      type: requestActionType,
    };
  };

  requestActionCreator.toString = () => requestActionType;

  const actionCreators = {
    ingest: ingestActionCreator,
    request: requestActionCreator,
  };

  const middleware = store => next => action => {
    if (action.type === requestActionType) {
      request(action.payload.url, action.payload.options)
        .then(data =>
          store.dispatch(actionCreators.ingest(data, action.meta))
        )
        .catch(error => {
          let payload;

          if (error instanceof Error) {
            payload = error;
          } else {
            payload = new Error(error);
          }

          store.dispatch(actionCreators.ingest(payload, action.meta))
        });
    }
    return next(action);
  }

  const reducer = (previous = {}, action) => {
    let nextState = null;

    if (action.type === requestActionType) {
      nextState = Object.assign({}, previous);
      const path = action.meta.path;
      if (!nextState[path]) {
        const init = initialEndpointState();
        init.pendingRequests = 1;
        nextState[path] = init
      } else {
        nextState[path] = Object.assign({}, nextState[path]);
        nextState[path].pendingRequests += 1;
      }
    }

    if (action.type === ingestActionType) {
      nextState = Object.assign({}, previous);
      const path = action.meta.path;
      const nextPathState = Object.assign({}, nextState[path]);
      nextState[path] = nextPathState;

      if (action.error) {
        const { message, name } = action.payload;
        nextPathState.error = { message, name }
      } else {
        nextPathState.data = action.payload;
        nextPathState.error = null;
      }
      nextPathState.pendingRequests--;
    }

    if (nextState !== null)
      return nextState;

    return previous;
  };

  const selectorMap = {};

  const selector = (path = DEFAULT_KEY) => {
    let s;

    if (!selectorMap[path]) {
      s = state => (state[path] && state[path].data) || null;
      selectorMap[path] = s;
    } else {
      s = selectorMap[path];
    }

    return s;
  };

  return {
    actionCreators,
    middleware,
    reducer,
    selector,
  };
};
<|endoftext|>


<commit_before>import React, {Component, PropTypes} from 'react';
import Matcher from 'route-parser';

// a helper to get the current location from the history object
function currentLocation(h){
  let loc;
  h.listen(location => loc = location)();
  return loc;
}

// pattern matching for urls
function matches(patterns, url){
  if (!Array.isArray(patterns)){
    patterns = [patterns];
  }

  for (let pattern of patterns){
    var matcher = new Matcher(pattern);
    let res = matcher.match(url);
    if (res){
      return res;
    }
  }
  return false;
}

// top level component. pass in a history object.
// <Router history={history}>
//   <App/>
// </Router>
// todo - use a default history singleton in browser
export class Router extends Component{
  state = {
    location: null
  };
  static propTypes = {
    history: PropTypes.object.isRequired
  };
  static childContextTypes = {
    routah: PropTypes.object
  };
  getChildContext(){
    return {
      routah: {
        history: this.props.history
      }
    };
  }
  render(){
    return this.props.children;
  }
}

export class Route extends Component{
  static propTypes = {
    match: PropTypes.oneOfType([PropTypes.string, PropTypes.arrayOf(PropTypes.string)]),
    component: PropTypes.func,
    notFound: PropTypes.func,
    props: PropTypes.object,
    onLeave: PropTypes.func,
    onEnter: PropTypes.func
  };
  static defaultProps = {
    notFound: () => <noscript/>,
    props: {},
    onMount: () => {},
    onEnter: (l, cb) => cb(),
    onLeave: (l, cb) => cb(),
    onUnload: () => {}
  };
  static contextTypes = {
    routah: PropTypes.object
  };
  refresh = location => {
    let doesMatch = true, match;
    if (this.props.path){
      match = matches(this.props.path, this.context.routah.history.createHref(location));
      doesMatch = !!match;
    }

    this.setState({
      location: {
        ...location,
        params: match || {}
      },
      matches: doesMatch
    });
  };
  componentWillMount(){
    let h = this.context.routah.history;
    this.dispose = this.context.routah.history.listen(this.refresh);

    if (!this.props.path || matches(this.props.path,  h.createHref(currentLocation(h)))){
      this.props.onMount(currentLocation(h));
    }

    this.disposeBefore = this.context.routah.history.listenBefore((location, callback) => {
      if (!this.props.path || matches(this.props.path,  h.createHref(location))){
        return this.props.onEnter(location, callback);
      }
      else {
        return this.props.onLeave(location, callback);
      }
    });

    this.disposeUnload = this.context.routah.history.listenBeforeUnload(() => {
      if (!this.props.path || matches(this.props.path,  h.createHref(currentLocation(h)))){
        return this.props.onUnload(currentLocation(h));
      }
    });

  }
  componentWillReceiveProps(next){
    if (next.path !== this.props.path){
      this.refresh(currentLocation(this.context.routah.history));
    }
  }
  render(){
    let {location} = this.state;
    if (this.state.matches){
      if (this.props.component){
        return React.createElement(this.props.component, {location, ...this.props.props});
      }
      return this.props.children(location);
    }
    return this.props.notFound(location);
  }
  componentWillUnmount(){
    this.dispose();
    this.disposeBefore();
    this.disposeUnload();
    delete this.dispose;
    delete this.disposeBefore;
    delete this.disposeUnload;
  }
}

export class Link extends Component{
  static contextTypes = {
    routah: PropTypes.object
  };
  static propTypes = {
    to: PropTypes.oneOfType([PropTypes.string, PropTypes.object]),
    onClick: PropTypes.func,
    className: PropTypes.string,
    activeClass: PropTypes.string,
    activeStyle: PropTypes.object
  };

  static defaultProps = {
    onClick: () => {},
    className: '',
    activeClass: '',
    style: {},
    activeStyle: {}
  };

  onClick = e => {
    e.preventDefault();
    this.props.onClick(e);
    this.context.routah.history.push(this.props.to);
  };
  render(){
    let h = this.context.routah.history;
    let active = h.createHref(this.props.to) === h.createHref(currentLocation(h));

    return <a
      href={this.context.routah.history.createHref(this.props.to)}
      {...this.props}
      className={`${this.props.className} ${active ? this.props.activeClass : ''}`}
      style={{...this.props.style, ...(active ? this.props.activeStyle : {})}}
      onClick={this.onClick}>
      {this.props.children}
    </a>;
  }
}

// todo - handle server side redirect
export class Redirect extends Component{

  static contextTypes = {
    routah: PropTypes.object
  };
  componentDidMount(){
    this.context.routah.history.push(this.props.to);
  }
  render(){
    return <noscript/>;
  }
}
<commit_msg>fix enter/leave hooks<commit_after>import React, {Component, PropTypes} from 'react';
import Matcher from 'route-parser';

// a helper to get the current location from the history object
function currentLocation(h){
  let loc;
  h.listen(location => loc = location)();
  return loc;
}

// pattern matching for urls
function matches(patterns, url){
  if (!Array.isArray(patterns)){
    patterns = [patterns];
  }

  for (let pattern of patterns){
    var matcher = new Matcher(pattern);
    let res = matcher.match(url);
    if (res){
      return res;
    }
  }
  return false;
}

// top level component. pass in a history object.
// <Router history={history}>
//   <App/>
// </Router>
// todo - use a default history singleton in browser
export class Router extends Component{
  state = {
    location: null
  };
  static propTypes = {
    history: PropTypes.object.isRequired
  };
  static childContextTypes = {
    routah: PropTypes.object
  };
  getChildContext(){
    return {
      routah: {
        history: this.props.history
      }
    };
  }
  render(){
    return this.props.children;
  }
}

export class Route extends Component{
  static propTypes = {
    match: PropTypes.oneOfType([PropTypes.string, PropTypes.arrayOf(PropTypes.string)]),
    component: PropTypes.func,
    notFound: PropTypes.func,
    props: PropTypes.object,
    onLeave: PropTypes.func,
    onEnter: PropTypes.func
  };
  static defaultProps = {
    notFound: () => <noscript/>,
    props: {},
    onMount: () => {},
    onEnter: (l, cb) => cb(),
    onLeave: (l, cb) => cb(),
    onUnload: () => {}
  };
  static contextTypes = {
    routah: PropTypes.object
  };
  refresh = location => {
    let doesMatch = true, match;
    if (this.props.path){
      match = matches(this.props.path, this.context.routah.history.createHref(location));
      doesMatch = !!match;
    }

    this.setState({
      location: {
        ...location,
        params: match || {}
      },
      matches: doesMatch
    });
  };
  componentWillMount(){
    let h = this.context.routah.history;
    this.dispose = this.context.routah.history.listen(this.refresh);

    if (!this.props.path || matches(this.props.path,  h.createHref(currentLocation(h)))){
      this.props.onMount(currentLocation(h));
    }

    this.disposeBefore = this.context.routah.history.listenBefore((location, callback) => {
      let matchesCurrent = !this.props.path || matches(this.props.path,  h.createHref(currentLocation(h)));
      let matchesNext = !this.props.path || matches(this.props.path,  h.createHref(location));
      if (!matchesCurrent && matchesNext){
        return this.props.onEnter(location, callback);
      }
      if (matchesCurrent && !matchesNext){
        return this.props.onLeave(location, callback);
      }

      return callback();
    });

    this.disposeUnload = this.context.routah.history.listenBeforeUnload(() => {
      if (!this.props.path || matches(this.props.path,  h.createHref(currentLocation(h)))){
        return this.props.onUnload(currentLocation(h));
      }
    });

  }
  componentWillReceiveProps(next){
    if (next.path !== this.props.path){
      this.refresh(currentLocation(this.context.routah.history));
    }
  }
  render(){
    let {location} = this.state;
    if (this.state.matches){
      if (this.props.component){
        return React.createElement(this.props.component, {location, ...this.props.props});
      }
      return this.props.children(location);
    }
    return this.props.notFound(location);
  }
  componentWillUnmount(){
    this.dispose();
    this.disposeBefore();
    this.disposeUnload();
    delete this.dispose;
    delete this.disposeBefore;
    delete this.disposeUnload;
  }
}

export class Link extends Component{
  static contextTypes = {
    routah: PropTypes.object
  };
  static propTypes = {
    to: PropTypes.oneOfType([PropTypes.string, PropTypes.object]),
    onClick: PropTypes.func,
    className: PropTypes.string,
    activeClass: PropTypes.string,
    activeStyle: PropTypes.object
  };

  static defaultProps = {
    onClick: () => {},
    className: '',
    activeClass: '',
    style: {},
    activeStyle: {}
  };

  onClick = e => {
    e.preventDefault();
    this.props.onClick(e);
    this.context.routah.history.push(this.props.to);
  };
  render(){
    let h = this.context.routah.history;
    let active = h.createHref(this.props.to) === h.createHref(currentLocation(h));

    return <a
      href={this.context.routah.history.createHref(this.props.to)}
      {...this.props}
      className={`${this.props.className} ${active ? this.props.activeClass : ''}`}
      style={{...this.props.style, ...(active ? this.props.activeStyle : {})}}
      onClick={this.onClick}>
      {this.props.children}
    </a>;
  }
}

// todo - handle server side redirect
export class Redirect extends Component{

  static contextTypes = {
    routah: PropTypes.object
  };
  componentDidMount(){
    this.context.routah.history.push(this.props.to);
  }
  render(){
    return <noscript/>;
  }
}
<|endoftext|>


<commit_before>
exports.render = function(model, outputFolder, options) {

// Port the code from this pull request
// https://github.com/rprieto/supersamples/issues/4

};
<commit_msg>Markdown renderer entry point: fix params<commit_after>
exports.render = function(model, options) {

// Port the code from this pull request
// https://github.com/rprieto/supersamples/issues/4

};
<|endoftext|>


<commit_before>'use strict';

require('basscss/css/basscss.css');
require('font-awesome/css/font-awesome.css');

require('./index.html');

var Elm = require('./Main.elm');
var mountNode = document.getElementById('main');

var app = Elm.embed(Elm.Main, mountNode);
<commit_msg>Remove font-awesome and basscss<commit_after>'use strict';

require('./index.html');
var Elm = require('./Main.elm');
var mountNode = document.getElementById('main');

var app = Elm.embed(Elm.Main, mountNode);
<|endoftext|>


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

  var DayScheduleSelector = function (el, options) {
    this.$el = $(el);
    this.options = $.extend({}, DayScheduleSelector.DEFAULTS, options);
    this.render();
    this.attachEvents(this.options);
    this.$selectingStart = null;
    this.selections = {};
  }

  DayScheduleSelector.DEFAULTS = {
    days        : [0, 1, 2, 3, 4, 5, 6],  // Sun - Sat
    startTime   : '08:00',                // HH:mm format
    endTime     : '20:00',                // HH:mm format
    interval    : 30,                     // minutes
    template    : '
'         +
                    '
'            +
                      '

' +
                      '

'   +
                    '
'                                  +
                  '
'
  };

  /**
   * Generate Date objects for each time slot in a day
   * @private
   * @param {String} start Start time in HH:mm format, e.g. "08:00"
   * @param {String} end End time in HH:mm format, e.g. "21:00"
   * @param {Number} interval Interval of each time slot in minutes, e.g. 30 (minutes)
   * @returns {Array} An array of Date objects representing the start time of the time slots
   */
  function generateDates(start, end, interval) {
    var numOfRows = Math.ceil(timeDiff(start, end) / interval);
    return $.map(new Array(numOfRows), function (_, i) {
      return new Date(Date.parse('2000-01-01 ' + start) + i * interval * 60000);
    });
  }

  /**
   * Return time difference in minutes
   * @private
   */
  function timeDiff(start, end) {   // time in HH:mm format
    var dummy = '2000-01-01 '; // need a dummy date to utilize the Date object
    return (Date.parse(dummy + end) -
            Date.parse(dummy + start)) / 60000;
  }

  /**
   * Convert a Date object to time in AM/PM format (ignore its date)
   * @private
   */
  function getAPTime(date) {
    var hours = date.getHours()
      , minutes = date.getMinutes()
      , ampm = hours >= 12 ? 'pm' : 'am'

    return hours + ':' + ('0' + minutes).slice(-2) + ampm;
  }

  /**
   * Render the calendar UI
   * @public
   */
  DayScheduleSelector.prototype.render = function () {
    this.$el.html(this.options.template);
    this.renderHeader();
    this.renderRows();
  };

  /**
   * Render the calendar header
   * @public
   */
  DayScheduleSelector.prototype.renderHeader = function () {
    var stringDays = ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat']
      , days = this.options.days
      , html = '';

    $.each(days, function (_, v) { html += '' + stringDays[v] + '	'; });
    this.$el.find('.schedule-header').html('
	' + html + '
');
  };

  /**
   * Render the calendar rows, including the time slots and the label
   * @public
   */
  DayScheduleSelector.prototype.renderRows = function () {
    var start = this.options.startTime
      , end = this.options.endTime
      , interval = this.options.interval
      , days = this.options.days
      , $el = this.$el.find('.schedule-rows');

    $.each(generateDates(start, end, interval), function (i, d) {
      var daysInARow = $.map(new Array(days.length), function (_, i) {
        return '	'
      }).join();

      $el.append('
' + getAPTime(d) + '	' + daysInARow + '
');
    });
  };

  /**
   * Is the day schedule selector in selecting mode?
   * @public
   */
  DayScheduleSelector.prototype.isSelecting = function () {
    return !!this.$selectingStart;
  }

  DayScheduleSelector.prototype.select = function ($slot) { $slot.attr('data-selected', 'selected'); }
  DayScheduleSelector.prototype.deselect = function ($slot) { $slot.removeAttr('data-selected'); }

  function isSlotSelected($slot) { return $slot.is('[data-selected]'); }
  function isSlotSelecting($slot) { return $slot.is('[data-selecting]'); }

  DayScheduleSelector.prototype.attachEvents = function () {
    var plugin = this
      , options = this.options
      , $slots;

    this.$el.on('click', 'td', function () {
      var day = $(this).data('day');
      if (!plugin.isSelecting()) {  // if we are not in selecting mode
        if (isSlotSelected($(this))) { plugin.deselect($(this)); }
        else {  // then start selecting
          plugin.$selectingStart = $(this);
          $(this).attr('data-selecting', 'selecting');
          plugin.$el.find('.time-slot').attr('data-disabled', 'disabled');
          plugin.$el.find('.time-slot[data-day="' + day + '"]').removeAttr('data-disabled');
        }
      } else {  // if we are in selecting mode
        if (day == plugin.$selectingStart.data('day')) {  // if clicking on the same day column
          // then end of selection
          plugin.$el.find('.time-slot[data-day="' + day + '"]').filter('[data-selecting]')
            .attr('data-selected', 'selected').removeAttr('data-selecting');
          plugin.$el.find('.time-slot').removeAttr('data-disabled');
          plugin.$selectingStart = null;
        }
      }
    });

    this.$el.on('mouseover', 'td', function () {
      var $slots, day, start, end, temp;
      if (plugin.isSelecting()) {  // if we are in selecting mode
        day = plugin.$selectingStart.data('day');
        $slots = plugin.$el.find('.time-slot[data-day="' + day + '"]');
        $slots.filter('[data-selecting]').removeAttr('data-selecting');
        start = $slots.index(plugin.$selectingStart);
        end = $slots.index(this);
        if (end < 0) return;  // not hovering on the same column
        if (start > end) { temp = start; start = end; end = temp; }
        $slots.slice(start, end + 1).attr('data-selecting', 'selecting');
      }
    });
  };

  /**
   * Serialize the selections
   * @public
   * @returns {Object} An object containing the selections of each day, e.g.
   *    {
   *      0: [],
   *      1: [["15:00:00 GMT-0500 (EST)", "16:30:00 GMT-0500 (EST)"]],
   *      2: [],
   *      3: [],
   *      5: [["09:00:00 GMT-0500 (EST)", "12:30:00 GMT-0500 (EST)"], ["15:00:00 GMT-0500 (EST)", "16:30:00 GMT-0500 (EST)"]],
   *      6: []
   *    }
   */
  DayScheduleSelector.prototype.serialize = function () {
    var plugin = this
      , selections = {};

    $.each(this.options.days, function (_, v) {
      var start, end;
      start = end = false; selections[v] = [];
      plugin.$el.find(".time-slot[data-day='" + v + "']").each(function () {
        if (isSlotSelected($(this)) && !start) { start = $(this).data('time'); }
        else if (!isSlotSelected($(this)) && !!start) {
          end = $(this).data('time');
          selections[v].push([start, end]);
          start = end = false;
        }
      });
    })
    return selections;
  }

  // DayScheduleSelector Plugin Definition
  // =====================================

  function Plugin(option) {
    return this.each(function (){
      var $this   = $(this)
        , data    = $this.data('artsy.dayScheduleSelector')
        , options = typeof option == 'object' && option;

      if (!data) {
        $this.data('artsy.dayScheduleSelector', (data = new DayScheduleSelector(this, options)));
      }
    })
  }

  $.fn.dayScheduleSelector = Plugin;

})(jQuery);
<commit_msg>Serialize/Deserialize.<commit_after>(function ($) {
  'use strict';

  var DayScheduleSelector = function (el, options) {
    this.$el = $(el);
    this.options = $.extend({}, DayScheduleSelector.DEFAULTS, options);
    this.render();
    this.attachEvents(this.options);
    this.$selectingStart = null;
  }

  DayScheduleSelector.DEFAULTS = {
    days        : [0, 1, 2, 3, 4, 5, 6],  // Sun - Sat
    startTime   : '08:00',                // HH:mm format
    endTime     : '20:00',                // HH:mm format
    interval    : 30,                     // minutes
    template    : '
'         +
                    '
'            +
                      '

' +
                      '

'   +
                    '
'                                  +
                  '
'
  };

  /**
   * Generate Date objects for each time slot in a day
   * @private
   * @param {String} start Start time in HH:mm format, e.g. "08:00"
   * @param {String} end End time in HH:mm format, e.g. "21:00"
   * @param {Number} interval Interval of each time slot in minutes, e.g. 30 (minutes)
   * @returns {Array} An array of Date objects representing the start time of the time slots
   */
  function generateDates(start, end, interval) {
    var numOfRows = Math.ceil(timeDiff(start, end) / interval);
    return $.map(new Array(numOfRows), function (_, i) {
      return new Date(Date.parse('2000-01-01 ' + start) + i * interval * 60000);
    });
  }

  /**
   * Return time difference in minutes
   * @private
   */
  function timeDiff(start, end) {   // time in HH:mm format
    var dummy = '2000-01-01 '; // need a dummy date to utilize the Date object
    return (Date.parse(dummy + end) -
            Date.parse(dummy + start)) / 60000;
  }

  /**
   * Convert a Date object to time in H:mm format with am/pm
   * @private
   * @returns {String} Time in H:mm format with am/pm, e.g. '9:30am'
   */
  function hmmAmPm(date) {
    var hours = date.getHours()
      , minutes = date.getMinutes()
      , ampm = hours >= 12 ? 'pm' : 'am';
    return hours + ':' + ('0' + minutes).slice(-2) + ampm;
  }

  /**
   * Convert a Date object to time in HH:mm format
   * @private
   * @returns {String} Time in HH:mm format, e.g. '09:30'
   */
  function hhmm(date) {
    var hours = date.getHours()
      , minutes = date.getMinutes();
    return ('0' + hours).slice(-2) + ':' + ('0' + minutes).slice(-2);
  }

  function hhmmToSecondsSinceMidnight(hhmm) {
    var h = hhmm.split(':')[0]
      , m = hhmm.split(':')[1];
    return parseInt(h) * 60 + parseInt(m);
  }

  function secondsSinceMidnightToHhmm(seconds) {
    return ('0' + Math.floor(seconds / 60)).slice(-2) + ':' +
           ('0' + (seconds % 60)).slice(-2);
  }

  /**
   * Render the calendar UI
   * @public
   */
  DayScheduleSelector.prototype.render = function () {
    this.$el.html(this.options.template);
    this.renderHeader();
    this.renderRows();
  };

  /**
   * Render the calendar header
   * @public
   */
  DayScheduleSelector.prototype.renderHeader = function () {
    var stringDays = ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat']
      , days = this.options.days
      , html = '';

    $.each(days, function (_, v) { html += '' + stringDays[v] + '	'; });
    this.$el.find('.schedule-header').html('
	' + html + '
');
  };

  /**
   * Render the calendar rows, including the time slots and the label
   * @public
   */
  DayScheduleSelector.prototype.renderRows = function () {
    var start = this.options.startTime
      , end = this.options.endTime
      , interval = this.options.interval
      , days = this.options.days
      , $el = this.$el.find('.schedule-rows');

    $.each(generateDates(start, end, interval), function (i, d) {
      var daysInARow = $.map(new Array(days.length), function (_, i) {
        return '	'
      }).join();

      $el.append('
' + hmmAmPm(d) + '	' + daysInARow + '
');
    });
  };

  /**
   * Is the day schedule selector in selecting mode?
   * @public
   */
  DayScheduleSelector.prototype.isSelecting = function () {
    return !!this.$selectingStart;
  }

  DayScheduleSelector.prototype.select = function ($slot) { $slot.attr('data-selected', 'selected'); }
  DayScheduleSelector.prototype.deselect = function ($slot) { $slot.removeAttr('data-selected'); }

  function isSlotSelected($slot) { return $slot.is('[data-selected]'); }
  function isSlotSelecting($slot) { return $slot.is('[data-selecting]'); }

  DayScheduleSelector.prototype.attachEvents = function () {
    var plugin = this
      , options = this.options
      , $slots;

    this.$el.on('click', 'td', function () {
      var day = $(this).data('day');
      if (!plugin.isSelecting()) {  // if we are not in selecting mode
        if (isSlotSelected($(this))) { plugin.deselect($(this)); }
        else {  // then start selecting
          plugin.$selectingStart = $(this);
          $(this).attr('data-selecting', 'selecting');
          plugin.$el.find('.time-slot').attr('data-disabled', 'disabled');
          plugin.$el.find('.time-slot[data-day="' + day + '"]').removeAttr('data-disabled');
        }
      } else {  // if we are in selecting mode
        if (day == plugin.$selectingStart.data('day')) {  // if clicking on the same day column
          // then end of selection
          plugin.$el.find('.time-slot[data-day="' + day + '"]').filter('[data-selecting]')
            .attr('data-selected', 'selected').removeAttr('data-selecting');
          plugin.$el.find('.time-slot').removeAttr('data-disabled');
          plugin.$selectingStart = null;
        }
      }
    });

    this.$el.on('mouseover', 'td', function () {
      var $slots, day, start, end, temp;
      if (plugin.isSelecting()) {  // if we are in selecting mode
        day = plugin.$selectingStart.data('day');
        $slots = plugin.$el.find('.time-slot[data-day="' + day + '"]');
        $slots.filter('[data-selecting]').removeAttr('data-selecting');
        start = $slots.index(plugin.$selectingStart);
        end = $slots.index(this);
        if (end < 0) return;  // not hovering on the same column
        if (start > end) { temp = start; start = end; end = temp; }
        $slots.slice(start, end + 1).attr('data-selecting', 'selecting');
      }
    });
  };

  /**
   * Serialize the selections
   * @public
   * @returns {Object} An object containing the selections of each day, e.g.
   *    {
   *      0: [],
   *      1: [["15:00", "16:30"]],
   *      2: [],
   *      3: [],
   *      5: [["09:00", "12:30"], ["15:00", "16:30"]],
   *      6: []
   *    }
   */
  DayScheduleSelector.prototype.serialize = function () {
    var plugin = this
      , selections = {};

    $.each(this.options.days, function (_, v) {
      var start, end;
      start = end = false; selections[v] = [];
      plugin.$el.find(".time-slot[data-day='" + v + "']").each(function () {
        if (isSlotSelected($(this)) && !start) { start = $(this).data('time'); }
        else if (!isSlotSelected($(this)) && !!start) {
          end = $(this).data('time');
          selections[v].push([start, end]);
          start = end = false;
        }
      });
    })
    return selections;
  };

  /**
   * Deserialize the schedule and render on the UI
   * @public
   * @param {Object} schedule An object containing the schedule of each day, e.g.
   *    {
   *      0: [],
   *      1: [["15:00", "16:30"]],
   *      2: [],
   *      3: [],
   *      5: [["09:00", "12:30"], ["15:00", "16:30"]],
   *      6: []
   *    }
   */
  DayScheduleSelector.prototype.deserialize = function (schedule) {
    var plugin = this, i;
    $.each(schedule, function(d, ds) {
      var $slots = plugin.$el.find('.time-slot[data-day="' + d + '"]');
      $.each(ds, function(_, s) {
        for (i = 0; i < $slots.length; i++) {
          if ($slots.eq(i).data('time') >= s[1]) { break; }
          if ($slots.eq(i).data('time') >= s[0]) { plugin.select($slots.eq(i)); }
        }
      })
    });
  };

  // DayScheduleSelector Plugin Definition
  // =====================================

  function Plugin(option) {
    return this.each(function (){
      var $this   = $(this)
        , data    = $this.data('artsy.dayScheduleSelector')
        , options = typeof option == 'object' && option;

      if (!data) {
        $this.data('artsy.dayScheduleSelector', (data = new DayScheduleSelector(this, options)));
      }
    })
  }

  $.fn.dayScheduleSelector = Plugin;

  // Expose some utility functions
  window.DayScheduleSelector = {
    ssmToHhmm: secondsSinceMidnightToHhmm,
    hhmmToSsm: hhmmToSecondsSinceMidnight
  };

})(jQuery);
<|endoftext|>


<commit_before>import React from 'react';
import Notebook from './components/notebook';

import * as commutable from 'commutable';
import Immutable from 'immutable';

const NotebookPreview = (props) => {
  const nb = props.notebook;
  const notebook = Immutable.Map.isMap(nb) ? nb : commutable.fromJS(nb);
  return <Notebook notebook={notebook} />;
};

NotebookPreview.propTypes = {
  notebook: React.PropTypes.any,
};

export default NotebookPreview;
<commit_msg>Check if notebook prop is null<commit_after>import React from 'react';
import Notebook from './components/notebook';

import * as commutable from 'commutable';
import Immutable from 'immutable';

const NotebookPreview = (props) => {
  if (props.notebook) {
    const nb = props.notebook;
    const notebook = Immutable.Map.isMap(nb) ? nb : commutable.fromJS(nb);
    return <Notebook notebook={notebook} />;
  } else {
    return <h1>Notebook not provided. </h1>;
  }
};

NotebookPreview.propTypes = {
  notebook: React.PropTypes.any,
};

export default NotebookPreview;
<|endoftext|>


<commit_before>export default function(babel) {
  const {types: t} = babel
  const identifiers = new Set()
  const declarators = new Set()
  return {
    name: 'babel-plugin-glamorous-displayName',
    visitor: {
      ImportDeclaration(path) {
        const defaultSpecifierPath = path.get('specifiers')[0]
        if (
          path.node.source.value !== 'glamorous' ||
          !t.isImportDefaultSpecifier(defaultSpecifierPath)
        ) {
          return
        }
        const {node: {local: {name}}} = defaultSpecifierPath
        const {referencePaths} = path.scope.getBinding(name)
        referencePaths.forEach(reference => {
          identifiers.add(reference)
        })
      },
      VariableDeclarator(path) {
        const {node} = path
        if (!isRequireCall(node.init) || !t.isIdentifier(node.id)) {
          return
        }
        const {id: {name}} = node
        if(path.scope.getBinding(name)){
          const {referencePaths} = path.scope.getBinding(name)
          referencePaths.forEach(reference => {
            identifiers.add(reference)
          })
        }
      },
      Program: {
        exit(path) {
          Array.from(identifiers).forEach(identifier => {
            const declarator = identifier.findParent(t.isVariableDeclarator)
            if (!declarator || declarators.has(declarator)) {
              return
            }
            declarators.add(declarator)
            const {node: {id: {name: displayName}}} = declarator
            declarator.parentPath.insertAfter(
              t.expressionStatement(
                t.assignmentExpression(
                  '=',
                  t.memberExpression(
                    t.identifier(displayName),
                    t.identifier('displayName')
                  ),
                  t.stringLiteral(displayName)
                )
              )
            )
          })
        }
      }
    }
  }

  function isRequireCall(callExpression) {
    return (
      callExpression &&
      callExpression.type === 'CallExpression' &&
      callExpression.callee.name === 'require' &&
      callExpression.arguments.length === 1 &&
      callExpression.arguments[0].value === 'glamorous'
    )
  }
}
<commit_msg>Revert "Fix error with imports"<commit_after>export default function(babel) {
  const {types: t} = babel
  const identifiers = new Set()
  const declarators = new Set()
  return {
    name: 'babel-plugin-glamorous-displayName',
    visitor: {
      ImportDeclaration(path) {
        const defaultSpecifierPath = path.get('specifiers')[0]
        if (
          path.node.source.value !== 'glamorous' ||
          !t.isImportDefaultSpecifier(defaultSpecifierPath)
        ) {
          return
        }
        const {node: {local: {name}}} = defaultSpecifierPath
        const {referencePaths} = path.scope.getBinding(name)
        referencePaths.forEach(reference => {
          identifiers.add(reference)
        })
      },
      VariableDeclarator(path) {
        const {node} = path
        if (!isRequireCall(node.init) || !t.isIdentifier(node.id)) {
          return
        }
        const {id: {name}} = node
        const {referencePaths} = path.scope.getBinding(name)
        referencePaths.forEach(reference => {
          identifiers.add(reference)
        })
      },
      Program: {
        exit(path) {
          Array.from(identifiers).forEach(identifier => {
            const declarator = identifier.findParent(t.isVariableDeclarator)
            if (!declarator || declarators.has(declarator)) {
              return
            }
            declarators.add(declarator)
            const {node: {id: {name: displayName}}} = declarator
            declarator.parentPath.insertAfter(
              t.expressionStatement(
                t.assignmentExpression(
                  '=',
                  t.memberExpression(
                    t.identifier(displayName),
                    t.identifier('displayName')
                  ),
                  t.stringLiteral(displayName)
                )
              )
            )
          })
        }
      }
    }
  }

  function isRequireCall(callExpression) {
    return (
      callExpression &&
      callExpression.type === 'CallExpression' &&
      callExpression.callee.name === 'require' &&
      callExpression.arguments.length === 1 &&
      callExpression.arguments[0].value === 'glamorous'
    )
  }
}
<|endoftext|>


<commit_before>const Promise = require('bluebird');
const mysqlModule = require('mysql');

/**
 * @namespace mysql
 */
export const mysql = {};

/**
 * @propery {Object} conn The MySQL connection
 */
mysql.conn = {};

/**
 * Pass-through to direct query (promisified)
 * @memberof mysql
 * @param {String} query The query to run
 */
mysql.query = (query) => {
  return new Promise((resolve, reject) => {
    mysql.conn.query(query, (err, result) => {
      if (err) {
        reject(err);
      } else {
        resolve(result);
      }
    });
  });
};

/**
 * Configs the MySQL Connection
 * @memberof mysql
 * @param {Object} cfg The config object
 *   @property {String} cfg.host
 *   @property {String} cfg.user
 *   @property {String} cfg.password
 *   @property {String} cfg.database
 */
mysql.config = (cfg) => {
  mysql.conn = mysqlModule.createConnection(cfg);
  mysql.conn.connect((err) => {
    if (err) {
      throw new Error('MySQL Connection Error', err);
    }
  });
};

/**
 * Creates a table
 * @memberof mysql
 * @param {String} name The name of the table to create
 * @param {Object} props The properties of the table
 * @returns {Object} promise
 */
mysql.createTable = (props) => {
  // Build query
  const len = Object.keys(props).length;
  let i = 1;
  let query = `CREATE TABLE IF NOT EXISTS ${mysql.tableName} (`;
  for (let prop in props) {
    let comma = (i !== len) ? ', ' : '';
    query += `${prop} ${props[prop].join(' ')}${comma}`;
    i++;
  }
  query += ');';
  // Run query
  return mysql.query(query);
};

/**
 * Creates a new record
 * @memberof mysql
 * @param {Object} body The record to insert
 * @param {Sting|Number} [version] The version of the model
 * @returns {Object} promise
 */
mysql.create = (body, version = false) => {
  return new Promise((resolve, reject) => {
    // Validate
    const validationErrors = mysql.validate(body, version);
    if (validationErrors) {
      reject(validationErrors);
    } else {
      // Build query
      let cols = [];
      let vals = [];
      for (let prop in body) {
        cols.push(prop);
        vals.push('"' + body[prop] + '"');
      }
      const query = `INSERT INTO ${mysql.tableName} (${cols.join(',')}) VALUES (${vals.join(',')});`;
      // Run query
      resolve(mysql.query(query));
    }
  });
};

/**
 * Queries for a record
 * @memberof mysql
 * @param {Object} query The query to execute
 * @returns {Object} promise
 */
mysql.read = (query) => {
  let where;
  if (query) {
    where = ` WHERE ${query}`;
  } else {
    where = '';
  }
  return mysql.query(`SELECT * FROM ${mysql.tableName}${where}`);
};

/**
 * Updates an existing record
 * @memberof mysql
 * @param {Object} query The query to identify update record(s)
 * @params {Object} body The record contents to update
 * @params {String|Number} version The version of the model
 * @returns {Object} promise
 */
mysql.update = (query, body, version = false) => {
  return new Promise((resolve, reject) => {
    const validationErrors = mysql.validate(body, version);
    if (validationErrors) {
      reject(validationErrors);
    } else {
      let i = 1;
      let changes = '';
      let len = Object.keys(body).length;
      for (let prop in body) {
        if ({}.hasOwnProperty.call(body, prop)) {
          let comma = (i !== len) ? ', ' : '';
          changes += `${prop}="${body[prop]}"${comma}`;
          i++;
        }
      }
      resolve(mysql.query(`UPDATE ${mysql.tableName} SET ${changes} WHERE ${query}`));
    }
  });
};

/**
 * Deletes a record
 * @memberof mysql
 * @param {Object} query
 * @returns {Object} promise
 */
mysql.delete = (query) => mysql.query(`DELETE FROM ${mysql.tableName} WHERE ${query}`);

/**
 * Extends the mysql object
 * @memberof mysql
 * @param {String} name The name of the method
 * @param {Function} fn The function to extend on the object
 */
mysql.extend = (name, fn) => {
  mysql[name] = fn.bind(mysql);
};
<commit_msg>Add quoting around table names<commit_after>const Promise = require('bluebird');
const mysqlModule = require('mysql');

/**
 * @namespace mysql
 */
export const mysql = {};

/**
 * @propery {Object} conn The MySQL connection
 */
mysql.conn = {};

/**
 * Pass-through to direct query (promisified)
 * @memberof mysql
 * @param {String} query The query to run
 */
mysql.query = (query) => {
  return new Promise((resolve, reject) => {
    mysql.conn.query(query, (err, result) => {
      if (err) {
        reject(err);
      } else {
        resolve(result);
      }
    });
  });
};

/**
 * Configs the MySQL Connection
 * @memberof mysql
 * @param {Object} cfg The config object
 *   @property {String} cfg.host
 *   @property {String} cfg.user
 *   @property {String} cfg.password
 *   @property {String} cfg.database
 */
mysql.config = (cfg) => {
  mysql.conn = mysqlModule.createConnection(cfg);
  mysql.conn.connect((err) => {
    if (err) {
      throw new Error('MySQL Connection Error', err);
    }
  });
};

/**
 * Creates a table
 * @memberof mysql
 * @param {String} name The name of the table to create
 * @param {Object} props The properties of the table
 * @returns {Object} promise
 */
mysql.createTable = (props) => {
  // Build query
  const len = Object.keys(props).length;
  let i = 1;
  let query = `CREATE TABLE IF NOT EXISTS \`${mysql.tableName}\` (`;
  for (let prop in props) {
    let comma = (i !== len) ? ', ' : '';
    query += `${prop} ${props[prop].join(' ')}${comma}`;
    i++;
  }
  query += ');';
  // Run query
  return mysql.query(query);
};

/**
 * Creates a new record
 * @memberof mysql
 * @param {Object} body The record to insert
 * @param {Sting|Number} [version] The version of the model
 * @returns {Object} promise
 */
mysql.create = (body, version = false) => {
  return new Promise((resolve, reject) => {
    // Validate
    const validationErrors = mysql.validate(body, version);
    if (validationErrors) {
      reject(validationErrors);
    } else {
      // Build query
      let cols = [];
      let vals = [];
      for (let prop in body) {
        cols.push(prop);
        vals.push('"' + body[prop] + '"');
      }
      const query = `INSERT INTO \`${mysql.tableName}\` (${cols.join(',')}) VALUES (${vals.join(',')});`;
      // Run query
      resolve(mysql.query(query));
    }
  });
};

/**
 * Queries for a record
 * @memberof mysql
 * @param {Object} query The query to execute
 * @returns {Object} promise
 */
mysql.read = (query) => {
  let where;
  if (query) {
    where = ` WHERE ${query}`;
  } else {
    where = '';
  }
  return mysql.query(`SELECT * FROM \`${mysql.tableName}\`${where}`);
};

/**
 * Updates an existing record
 * @memberof mysql
 * @param {Object} query The query to identify update record(s)
 * @params {Object} body The record contents to update
 * @params {String|Number} version The version of the model
 * @returns {Object} promise
 */
mysql.update = (query, body, version = false) => {
  return new Promise((resolve, reject) => {
    const validationErrors = mysql.validate(body, version);
    if (validationErrors) {
      reject(validationErrors);
    } else {
      let i = 1;
      let changes = '';
      let len = Object.keys(body).length;
      for (let prop in body) {
        if ({}.hasOwnProperty.call(body, prop)) {
          let comma = (i !== len) ? ', ' : '';
          changes += `${prop}="${body[prop]}"${comma}`;
          i++;
        }
      }
      resolve(mysql.query(`UPDATE \`${mysql.tableName}\` SET ${changes} WHERE ${query}`));
    }
  });
};

/**
 * Deletes a record
 * @memberof mysql
 * @param {Object} query
 * @returns {Object} promise
 */
mysql.delete = (query) => mysql.query(`DELETE FROM \`${mysql.tableName}\` WHERE ${query}`);

/**
 * Extends the mysql object
 * @memberof mysql
 * @param {String} name The name of the method
 * @param {Function} fn The function to extend on the object
 */
mysql.extend = (name, fn) => {
  mysql[name] = fn.bind(mysql);
};
<|endoftext|>


<commit_before>// Data ******************************************

var id = undefined;
var token = undefined;
var state = undefined;
var gref = {
  "client_id":"758681145932-76cra1qkeomh8nd1qens6u2q317cqpfa.apps.googleusercontent.com",
  "discoveryDocs": ["https://www.googleapis.com/discovery/v1/apis/drive/v3/rest"],
};

// Start/Entrypoint ******************************************

$(function(){
  // JQuery extention
  $.urlParam = function(name){
    var results = new RegExp('[\?&]' + name + '=([^&#]*)').exec(window.location.href);
    if (results==null){
       return null;
    }
    else{
       return decodeURI(results[1]) || 0;
    }
  }
  // Check params
  state = $.urlParam("state");
  if(state==undefined){
    gapi.load('client:auth2', initClientInstall);
  } else {
    gapi.load('client:auth2', initClient);
  }
})

// Init ******************************************

function initClientInstall(){
  gref.scope = "https://www.googleapis.com/auth/drive.install";
  gapi.client.init(gref).then(function(){
    gapi.auth2.getAuthInstance().signIn().then(function(){
      $('#wait').html("Installation done.");
    }).catch(function(err){
      console.log(err)
      alert("Error! See console for details.");
    });
  }).catch(function(err){
    console.log(err);
    alert("Error! See console for details.");
  });
};
function initClient(){
  gref.scope = "https://www.googleapis.com/auth/drive";
  gapi.client.init(gref).then(function(){
    if(gapi.auth2.getAuthInstance().currentUser.get().isSignedIn()){
      exe();
    } else {
      gapi.auth2.getAuthInstance().signIn().then(function(){
        exe();
      }).catch(function(err){
        console.log(err)
        alert("Error! See console for details.");
      });
    }
  }).catch(function(err){
    console.log(err);
    alert("Error! See console for details.");
  });
}
function exe(){
  token = gapi.auth2.getAuthInstance().currentUser.get().getAuthResponse(true).access_token;
  state = JSON.parse($.urlParam("state"));
  if (state==undefined) return;
  if(state.action=="open"){
    id = state.ids[0];
    _showContent(id);
  } else
  if(state.action=="create"){
    uiHideWait();
    uiShowDesktop();
  }
}

// Operations ******************************************

function _saveFile(){
  $('#sbtn').prop('disabled',true);
  $('#stl').html("Saving file, please wait...");
  var __updateFile = function(id, text, fname){
    const boundary = '-------314159265358979323846';
    const delimiter = "\r\n--" + boundary + "\r\n";
    const close_delim = "\r\n--" + boundary + "--";
    var metadata = {
      description : 'n/a',
      'mimeType': 'text/plain',
      'name' : fname
    };
    var multipartRequestBody =
      delimiter +  'Content-Type: application/json\r\n\r\n' +
      JSON.stringify(metadata) +
      delimiter + 'Content-Type: application/json\r\n\r\n' +
      text +
      close_delim;
      gapi.client.request({
        'path': '/upload/drive/v3/files/'+id,
        'method': 'PATCH',
        'params': {'fileId': id, 'uploadType': 'multipart'},
        'headers': { 'Content-Type': 'multipart/form-data; boundary="' + boundary + '"', 'Authorization': 'Bearer ' + token },
        'body': multipartRequestBody
      }).execute(function(file){
        uiShowDesktop();
        uiHideWait();
        $('#stl').html("File saved.");
        setTimeout(function(){
          $('#stl').html("");
          $('#sbtn').prop('disabled',false);
        },1000);
      });
  };
  var name = $('#fn').val();
  var content = $('#ct').val();
  var folderId = JSON.parse($.urlParam("state")).folderId;
  if(state.action=="create" && id==undefined){
    gapi.client.drive.files.create({
      "name" : name,
      "mimeType" : "text/plain",
      "fields" : "id",
      "parents" : [folderId]
    }).then(function(resp){
      id = resp.result.id;
      __updateFile(resp.result.id,content);
    }).catch(function(err){
      console.log(err);
      alert("Error! See console for details.");
      $('#sbtn').prop('disabled',true);
    });
  } else {
    __updateFile(id,content,name);
  }
}
function _showContent(id){
  // Get filename
  gapi.client.drive.files.get({
    fileId: id,
    fields: 'name'
  }).then(function(resp){
    var name = JSON.parse(resp.body).name;
    $('#fn').val(name);
  }).catch(function(err){
    console.log(err);
    alert("Error! See console for details.");
  });
  // Get content
  $.ajax({
    url: "https://www.googleapis.com/drive/v3/files/"+id+"?alt=media",
    headers : {"Authorization":"Bearer " + token}
  }).then(function(data){
    $('#ct').val(data);
    uiShowDesktop();
    uiHideWait();
  }).catch(function(err){
    console.log(err);
    alert("Error! See console for details.");
  });
}

// UI ******************************************

function uiShowDesktop(){
  $('#desktop').css("visibility","visible");
}
function uiHideWait(){
  $('#wait').css("visibility","hidden");
}
<commit_msg>Export of function.<commit_after>// Data ******************************************

var id = undefined;
var token = undefined;
var state = undefined;
var gref = {
  "client_id":"758681145932-76cra1qkeomh8nd1qens6u2q317cqpfa.apps.googleusercontent.com",
  "discoveryDocs": ["https://www.googleapis.com/discovery/v1/apis/drive/v3/rest"],
};

// Start/Entrypoint ******************************************

$(function(){
  // JQuery extention
  $.urlParam = function(name){
    var results = new RegExp('[\?&]' + name + '=([^&#]*)').exec(window.location.href);
    if (results==null){
       return null;
    }
    else{
       return decodeURI(results[1]) || 0;
    }
  }
  // Check params
  state = $.urlParam("state");
  if(state==undefined){
    gapi.load('client:auth2', initClientInstall);
  } else {
    gapi.load('client:auth2', initClient);
  }
})

// Init ******************************************

function initClientInstall(){
  gref.scope = "https://www.googleapis.com/auth/drive.install";
  gapi.client.init(gref).then(function(){
    gapi.auth2.getAuthInstance().signIn().then(function(){
      $('#wait').html("Installation done.");
    }).catch(function(err){
      console.log(err)
      alert("Error! See console for details.");
    });
  }).catch(function(err){
    console.log(err);
    alert("Error! See console for details.");
  });
};
function initClient(){
  gref.scope = "https://www.googleapis.com/auth/drive";
  gapi.client.init(gref).then(function(){
    if(gapi.auth2.getAuthInstance().currentUser.get().isSignedIn()){
      exe();
    } else {
      gapi.auth2.getAuthInstance().signIn().then(function(){
        exe();
      }).catch(function(err){
        console.log(err)
        alert("Error! See console for details.");
      });
    }
  }).catch(function(err){
    console.log(err);
    alert("Error! See console for details.");
  });
}
function exe(){
  token = gapi.auth2.getAuthInstance().currentUser.get().getAuthResponse(true).access_token;
  state = JSON.parse($.urlParam("state"));
  if (state==undefined) return;
  if(state.action=="open"){
    id = state.ids[0];
    _showContent(id);
  } else
  if(state.action=="create"){
    uiHideWait();
    uiShowDesktop();
  }
}

// Operations ******************************************

function _saveFile(){
  $('#sbtn').prop('disabled',true);
  $('#stl').html("Saving file, please wait...");
  var __updateFile = function(id, text, fname){
    const boundary = '-------314159265358979323846';
    const delimiter = "\r\n--" + boundary + "\r\n";
    const close_delim = "\r\n--" + boundary + "--";
    var metadata = {
      description : 'n/a',
      'mimeType': 'text/plain',
      'name' : fname
    };
    var multipartRequestBody =
      delimiter +  'Content-Type: application/json\r\n\r\n' +
      JSON.stringify(metadata) +
      delimiter + 'Content-Type: application/json\r\n\r\n' +
      text +
      close_delim;
      gapi.client.request({
        'path': '/upload/drive/v3/files/'+id,
        'method': 'PATCH',
        'params': {'fileId': id, 'uploadType': 'multipart'},
        'headers': { 'Content-Type': 'multipart/form-data; boundary="' + boundary + '"', 'Authorization': 'Bearer ' + token },
        'body': multipartRequestBody
      }).execute(function(file){
        uiShowDesktop();
        uiHideWait();
        $('#stl').html("File saved.");
        setTimeout(function(){
          $('#stl').html("");
          $('#sbtn').prop('disabled',false);
        },1000);
      });
  };
  var name = $('#fn').val();
  var content = $('#ct').val();
  var folderId = JSON.parse($.urlParam("state")).folderId;
  if(state.action=="create" && id==undefined){
    gapi.client.drive.files.create({
      "name" : name,
      "mimeType" : "text/plain",
      "fields" : "id",
      "parents" : [folderId]
    }).then(function(resp){
      id = resp.result.id;
      __updateFile(resp.result.id,content);
    }).catch(function(err){
      console.log(err);
      alert("Error! See console for details.");
      $('#sbtn').prop('disabled',true);
    });
  } else {
    __updateFile(id,content,name);
  }
}
function _showContent(id){
  // Get filename
  gapi.client.drive.files.get({
    fileId: id,
    fields: 'name'
  }).then(function(resp){
    var name = JSON.parse(resp.body).name;
    $('#fn').val(name);
  }).catch(function(err){
    console.log(err);
    alert("Error! See console for details.");
  });
  // Get content
  $.ajax({
    url: "https://www.googleapis.com/drive/v3/files/"+id+"?alt=media",
    headers : {"Authorization":"Bearer " + token}
  }).then(function(data){
    $('#ct').val(data);
    uiShowDesktop();
    uiHideWait();
  }).catch(function(err){
    console.log(err);
    alert("Error! See console for details.");
  });
}

// UI ******************************************

function uiShowDesktop(){
  $('#desktop').css("visibility","visible");
}
function uiHideWait(){
  $('#wait').css("visibility","hidden");
}

// Export ******************************************

module.exports = _saveFile;
<|endoftext|>