<commit_before>// redisdatabank.js
//
// implementation of Databank interface using redis
//
// Copyright 2011,2012 StatusNet Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

var databank = require('databank'),
    redis = require('redis'),
    Databank = databank.Databank,
    DatabankError = databank.DatabankError,
    AlreadyExistsError = databank.AlreadyExistsError,
    NoSuchThingError = databank.NoSuchThingError,
    NotConnectedError = databank.NotConnectedError,
    AlreadyConnectedError = databank.AlreadyConnectedError;

function RedisDatabank(params) {
    // Ignore params
    this.client = null;
    this.schema = params.schema || {};
}

RedisDatabank.prototype = new Databank();

RedisDatabank.prototype.toKey = function(type, id) {
    return type + ':' + id;
};

RedisDatabank.prototype.indexKey = function(type, prop, val) {
    return 'databank:index:' + type + ':' + prop + ':' + val;
};

RedisDatabank.prototype.connect = function(params, callback) {

    var onConnectionError = function(err) {
        if (callback) {
            callback(new DatabankError(err));
        }
    }, that = this;

    this.client = redis.createClient();

    this.client.on('error', onConnectionError);

    this.client.once('connect', function() {
        // Only want this once
        that.client.removeListener('error', onConnectionError);
        if (callback) {
            callback(null);
        }
    });
};

RedisDatabank.prototype.disconnect = function(callback) {
    this.client.quit(function(err) {
        if (err) {
            callback(new DatabankError());
        } else {
            callback(null);
        }
    });
};

RedisDatabank.prototype.create = function(type, id, value, callback) {
    var bank = this;

    this.client.setnx(this.toKey(type, id), JSON.stringify(value), function(err, result) {
        if (err) {
            callback(new DatabankError(err));
        } else if (result === 0) {
            callback(new AlreadyExistsError(type, id));
        } else {
            bank.index(type, id, value, function(err) {
                if (err) {
                    callback(err, null);
                } else {
                    callback(null, value);
                }
            });
        }
    });
};

RedisDatabank.prototype.read = function(type, id, callback) {
    this.client.get(this.toKey(type, id), function(err, value) {
        if (err) {
            callback(new DatabankError(err), null);
        } else if (value === null) {
            callback(new NoSuchThingError(type, id), null);
        } else {
            callback(null, JSON.parse(value));
        }
    });
};

RedisDatabank.prototype.update = function(type, id, value, callback) {
    var bank = this;

    bank.deindex(type, id, function(err) {
        if (err) {
            callback(err, null);
        } else {
            bank.client.set(bank.toKey(type, id), JSON.stringify(value), function(err) {
                if (err) {
                    callback(new DatabankError(err), null);
                } else {
                    bank.index(type, id, value, function(err) {
                        if (err) {
                            callback(err, null);
                        } else {
                            callback(null, value);
                        }
                    });
                }
            });
        }
    });
};

RedisDatabank.prototype.del = function(type, id, callback) {
    var bank = this;

    bank.deindex(type, id, function(err) {
        if (err) {
            callback(err, null);
        } else {
            bank.client.del(bank.toKey(type, id), function(err, count) {
                if (err) {
                    callback(err);
                } else if (count === 0) {
                    callback(new NoSuchThingError(type, id));
                } else {
                    callback(null);
                }
            });
        }
    });
};

RedisDatabank.prototype.readAll = function(type, ids, callback) {

    var keys = [],
        bank = this;

    keys = ids.map(function(id) { return bank.toKey(type, id); } );

    this.client.mget(keys, function(err, values) {
        var results = {}, i = 0, key, id, value;

        if (err) {
            callback(new DatabankError(err), null);
        } else {
            for (i = 0; i < values.length; i++) {
                key = keys[i];
                id = ids[i];
                value = JSON.parse(values[i]);
                results[id] = value;
            }
            callback(null, results);
        }
    });
};

RedisDatabank.prototype.search = function(type, criteria, onResult, callback) {
    var bank = this,
        indices = [],
        property,
        indexed = {},
        unindexed = {},
        haveIndexed = false,
        indexKeys = [],
        scanKeys = function(keys) {
            var i, cnt = 0, hadErr;
            if (keys.length === 0) {
                // not an error, just no results
                callback(null);
            } else {
                for (i in keys) {
                    bank.client.get(keys[i], function(err, value) {
                        if (err) {
                            hadErr = true;
                            callback(err);
                        } else if (!hadErr) {
                            value = JSON.parse(value);
                            if (bank.matchesCriteria(value, unindexed)) {
                                onResult(value);
                            }
                            cnt++;
                            if (cnt == keys.length) {
                                // last one out turn off the lights
                                callback(null);
                            }
                        }
                    });
                }
            }
        };

    // Determine which criteria, if any, are on an indexed property

    if (bank.schema && bank.schema[type] && bank.schema[type].indices) {
        indices = bank.schema[type].indices;
        for (property in criteria) {
            if (indices.indexOf(property) == -1) {
                unindexed[property] = criteria[property];
            } else {
                haveIndexed = true;
                indexed[property] = criteria[property];
            }
        }
    } else {
        unindexed = criteria;
    }

    // If there are any indexed properties, use set intersection to get candidate keys
    if (haveIndexed) {
        for (property in indexed) {
            indexKeys.push(bank.indexKey(type, property, indexed[property]));
        }
        // intersection of all keys. note: with just one arg, sinter returns all
        // values under that key
        bank.client.sinter(indexKeys, function(err, keys) {
            if (err) {
                callback(err);
            } else {
                scanKeys(keys);
            }
        });
    } else {
        // Get every record of a given type
        bank.client.keys(type + ':*', function(err, keys) {
            if (err) {
                callback(err);
            } else {
                scanKeys(keys);
            }
        });
    }
};

RedisDatabank.prototype.incr = function(type, id, callback) {
    this.client.incr(this.toKey(type, id), callback);
};

RedisDatabank.prototype.decr = function(type, id, callback) {
    this.client.decr(this.toKey(type, id), callback);
};

RedisDatabank.prototype.index = function(type, id, obj, callback) {
    if (!this.schema ||
        !this.schema[type] ||
        !this.schema[type].indices ||
        this.schema[type].indices.length === 0) {
        callback(null);
    } else {
        var indices = this.schema[type].indices,
            key = this.toKey(type, id),
            updated = 0,
            i = 0,
            hadErr = false,
            bank = this,
            addToIndex = function(prop, callback) {
                var val = Databank.deepProperty(obj, prop),
                    indexKey = bank.indexKey(type, prop, val);

                bank.client.sadd(indexKey, key, function(err, result) {
                    if (err) {
                        callback(err);
                    } else {
                        // Shouldn't have been there before, but we kind of don't care
                        callback(null);
                    }
                });
            };

        for (i = 0; i < indices.length; i++) {
            addToIndex(indices[i], function(err) {
                if (err) {
                    hadErr = true;
                    callback(err);
                } else if (!hadErr) {
                    updated++;
                    if (updated === indices.length) {
                        callback(null);
                    }
                }
            });
        }
    }
};

RedisDatabank.prototype.deindex = function(type, id, callback) {
    var bank = this;

    if (!this.schema ||
        !this.schema[type] ||
        !this.schema[type].indices ||
        this.schema[type].indices.length === 0) {
        callback(null);
    } else {
        // We have to do an extra read here. :(
        // FIXME: have a path to pass down the "old object" if we've already read it
        this.read(type, id, function(err, obj) {
            var indices = bank.schema[type].indices,
                key = bank.toKey(type, id),
                updated = 0,
                i = 0,
                hadErr = false,
                delFromIndex = function(prop, callback) {
                    var val = Databank.deepProperty(obj, prop),
                        indexKey = bank.indexKey(type, prop, val);

                    bank.client.srem(indexKey, key, function(err, result) {
                        if (err) {
                            callback(err);
                        } else {
                            // Shouldn't have been there before, but we kind of don't care
                            callback(null);
                        }
                    });
                };

            if (err) {
                callback(err);
            } else {
                for (i = 0; i < indices.length; i++) {
                    delFromIndex(indices[i], function(err) {
                        if (err) {
                            hadErr = true;
                            callback(err);
                        } else if (!hadErr) {
                            updated++;
                            if (updated === indices.length) {
                                callback(null);
                            }
                        }
                    });
                }
            }
        });
    }
};

module.exports = RedisDatabank;
<commit_msg>Check for connection state<commit_after>// redisdatabank.js
//
// implementation of Databank interface using redis
//
// Copyright 2011,2012 StatusNet Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

var databank = require('databank'),
    redis = require('redis'),
    Databank = databank.Databank,
    DatabankError = databank.DatabankError,
    AlreadyExistsError = databank.AlreadyExistsError,
    NoSuchThingError = databank.NoSuchThingError,
    NotConnectedError = databank.NotConnectedError,
    AlreadyConnectedError = databank.AlreadyConnectedError;

function RedisDatabank(params) {
    // Ignore params
    this.client = null;
    this.schema = params.schema || {};
}

RedisDatabank.prototype = new Databank();

RedisDatabank.prototype.toKey = function(type, id) {
    return type + ':' + id;
};

RedisDatabank.prototype.indexKey = function(type, prop, val) {
    return 'databank:index:' + type + ':' + prop + ':' + val;
};

RedisDatabank.prototype.connect = function(params, callback) {

    var onConnectionError = function(err) {
        if (callback) {
            callback(new DatabankError(err));
        }
    }, that = this;

    if (this.client) {
        callback(new AlreadyConnectedError());
        return;
    }

    this.client = redis.createClient();

    this.client.on('error', onConnectionError);

    this.client.once('connect', function() {
        // Only want this once
        that.client.removeListener('error', onConnectionError);
        if (callback) {
            callback(null);
        }
    });
};

RedisDatabank.prototype.disconnect = function(callback) {

    if (!this.client) {
        callback(new NotConnectedError());
        return;
    }

    this.client.quit(function(err) {
        if (err) {
            callback(new DatabankError());
        } else {
            callback(null);
        }
    });
};

RedisDatabank.prototype.create = function(type, id, value, callback) {
    var bank = this;

    if (!this.client) {
        callback(new NotConnectedError(), null);
        return;
    }

    this.client.setnx(this.toKey(type, id), JSON.stringify(value), function(err, result) {
        if (err) {
            callback(new DatabankError(err));
        } else if (result === 0) {
            callback(new AlreadyExistsError(type, id));
        } else {
            bank.index(type, id, value, function(err) {
                if (err) {
                    callback(err, null);
                } else {
                    callback(null, value);
                }
            });
        }
    });
};

RedisDatabank.prototype.read = function(type, id, callback) {

    if (!this.client) {
        callback(new NotConnectedError(), null);
        return;
    }

    this.client.get(this.toKey(type, id), function(err, value) {
        if (err) {
            callback(new DatabankError(err), null);
        } else if (value === null) {
            callback(new NoSuchThingError(type, id), null);
        } else {
            callback(null, JSON.parse(value));
        }
    });
};

RedisDatabank.prototype.update = function(type, id, value, callback) {
    var bank = this;

    bank.deindex(type, id, function(err) {
        if (err) {
            callback(err, null);
        } else {
            bank.client.set(bank.toKey(type, id), JSON.stringify(value), function(err) {
                if (err) {
                    callback(new DatabankError(err), null);
                } else {
                    bank.index(type, id, value, function(err) {
                        if (err) {
                            callback(err, null);
                        } else {
                            callback(null, value);
                        }
                    });
                }
            });
        }
    });
};

RedisDatabank.prototype.del = function(type, id, callback) {
    var bank = this;

    if (!this.client) {
        callback(new NotConnectedError());
        return;
    }

    bank.deindex(type, id, function(err) {
        if (err) {
            callback(err, null);
        } else {
            bank.client.del(bank.toKey(type, id), function(err, count) {
                if (err) {
                    callback(err);
                } else if (count === 0) {
                    callback(new NoSuchThingError(type, id));
                } else {
                    callback(null);
                }
            });
        }
    });
};

RedisDatabank.prototype.readAll = function(type, ids, callback) {

    var keys = [],
        bank = this;

    if (!this.client) {
        callback(new NotConnectedError(), null);
        return;
    }

    keys = ids.map(function(id) { return bank.toKey(type, id); } );

    this.client.mget(keys, function(err, values) {
        var results = {}, i = 0, key, id, value;

        if (err) {
            callback(new DatabankError(err), null);
        } else {
            for (i = 0; i < values.length; i++) {
                key = keys[i];
                id = ids[i];
                value = JSON.parse(values[i]);
                results[id] = value;
            }
            callback(null, results);
        }
    });
};

RedisDatabank.prototype.search = function(type, criteria, onResult, callback) {
    var bank = this,
        indices = [],
        property,
        indexed = {},
        unindexed = {},
        haveIndexed = false,
        indexKeys = [],
        scanKeys = function(keys) {
            var i, cnt = 0, hadErr;
            if (keys.length === 0) {
                // not an error, just no results
                callback(null);
            } else {
                for (i in keys) {
                    bank.client.get(keys[i], function(err, value) {
                        if (err) {
                            hadErr = true;
                            callback(err);
                        } else if (!hadErr) {
                            value = JSON.parse(value);
                            if (bank.matchesCriteria(value, unindexed)) {
                                onResult(value);
                            }
                            cnt++;
                            if (cnt == keys.length) {
                                // last one out turn off the lights
                                callback(null);
                            }
                        }
                    });
                }
            }
        };

    if (!bank.client) {
        callback(new NotConnectedError(), null);
        return;
    }

    // Determine which criteria, if any, are on an indexed property

    if (bank.schema && bank.schema[type] && bank.schema[type].indices) {
        indices = bank.schema[type].indices;
        for (property in criteria) {
            if (indices.indexOf(property) == -1) {
                unindexed[property] = criteria[property];
            } else {
                haveIndexed = true;
                indexed[property] = criteria[property];
            }
        }
    } else {
        unindexed = criteria;
    }

    // If there are any indexed properties, use set intersection to get candidate keys
    if (haveIndexed) {
        for (property in indexed) {
            indexKeys.push(bank.indexKey(type, property, indexed[property]));
        }
        // intersection of all keys. note: with just one arg, sinter returns all
        // values under that key
        bank.client.sinter(indexKeys, function(err, keys) {
            if (err) {
                callback(err);
            } else {
                scanKeys(keys);
            }
        });
    } else {
        // Get every record of a given type
        bank.client.keys(type + ':*', function(err, keys) {
            if (err) {
                callback(err);
            } else {
                scanKeys(keys);
            }
        });
    }
};

RedisDatabank.prototype.incr = function(type, id, callback) {
    if (!this.client) {
        callback(new NotConnectedError(), null);
        return;
    }
    this.client.incr(this.toKey(type, id), callback);
};

RedisDatabank.prototype.decr = function(type, id, callback) {
    if (!this.client) {
        callback(new NotConnectedError(), null);
        return;
    }
    this.client.decr(this.toKey(type, id), callback);
};

RedisDatabank.prototype.index = function(type, id, obj, callback) {
    if (!this.schema ||
        !this.schema[type] ||
        !this.schema[type].indices ||
        this.schema[type].indices.length === 0) {
        callback(null);
    } else {
        var indices = this.schema[type].indices,
            key = this.toKey(type, id),
            updated = 0,
            i = 0,
            hadErr = false,
            bank = this,
            addToIndex = function(prop, callback) {
                var val = Databank.deepProperty(obj, prop),
                    indexKey = bank.indexKey(type, prop, val);

                bank.client.sadd(indexKey, key, function(err, result) {
                    if (err) {
                        callback(err);
                    } else {
                        // Shouldn't have been there before, but we kind of don't care
                        callback(null);
                    }
                });
            };

        for (i = 0; i < indices.length; i++) {
            addToIndex(indices[i], function(err) {
                if (err) {
                    hadErr = true;
                    callback(err);
                } else if (!hadErr) {
                    updated++;
                    if (updated === indices.length) {
                        callback(null);
                    }
                }
            });
        }
    }
};

RedisDatabank.prototype.deindex = function(type, id, callback) {
    var bank = this;

    if (!this.schema ||
        !this.schema[type] ||
        !this.schema[type].indices ||
        this.schema[type].indices.length === 0) {
        callback(null);
    } else {
        // We have to do an extra read here. :(
        // FIXME: have a path to pass down the "old object" if we've already read it
        this.read(type, id, function(err, obj) {
            var indices = bank.schema[type].indices,
                key = bank.toKey(type, id),
                updated = 0,
                i = 0,
                hadErr = false,
                delFromIndex = function(prop, callback) {
                    var val = Databank.deepProperty(obj, prop),
                        indexKey = bank.indexKey(type, prop, val);

                    bank.client.srem(indexKey, key, function(err, result) {
                        if (err) {
                            callback(err);
                        } else {
                            // Shouldn't have been there before, but we kind of don't care
                            callback(null);
                        }
                    });
                };

            if (err) {
                callback(err);
            } else {
                for (i = 0; i < indices.length; i++) {
                    delFromIndex(indices[i], function(err) {
                        if (err) {
                            hadErr = true;
                            callback(err);
                        } else if (!hadErr) {
                            updated++;
                            if (updated === indices.length) {
                                callback(null);
                            }
                        }
                    });
                }
            }
        });
    }
};

module.exports = RedisDatabank;
<|endoftext|>


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

var binary = {
    "==": function (left, right) { return left == right; },
    "!=": function (left, right) { return left != right; },
    "<": function (left, right) { return left < right; },
    ">": function (left, right) { return left > right; },
    "<=": function (left, right) { return left <= right; },
    ">=": function (left, right) { return left >= right; },
    "<<": function (left, right) { return left >> right; },
    ">>": function (left, right) { return left << right; },
    "+": function (left, right) { return left != null ? (right != null ? left + right : left) : right; }, // preventing string operation with null to convert to "null"
    "-": function (left, right) { return left != null ? (right != null ? left - right : left) : -right; }, // preventing string operation with null to conert to NaN
    "*": function (left, right) { return left * right; },
    "/": function (left, right) { return left / right; },
    "%": function (left, right) { return left % right; }
};

var unary = {
    "!": function (argument) { return !argument; },
    "+": function (argument) { return +argument; },
    "-": function (argument) { return -argument; }
};

var expressions = {
    "?:": function (expression, model) {
        if (evaluate(expression.expression, model)) {
            return evaluate(expression.positive, model);
        }
        else {
            return evaluate(expression.negative, model);
        }
    },
    "?=": function (expression, model) {
        var value = evaluate(expression.expression, model);
        for (var i = 0; i < expression.options.length; i++) {
            var option = expression.options[i];
            if (evaluate(option.value, model) == value) {
                return evaluate(option.result, model);
            }
        }
        return evaluate(expression.otherwise, model);
    },
    "f": function (expression, model) {
        var object = evaluate(expression.object, model);
        if (object) {
            return object[expression.field];
        }
        return null;
    },
    "i": function (expression, model) {
        return model.get(expression.identifier);
    },
    "b": function (expression, model) {
        return binary[expression.operator](evaluate(expression.left, model), evaluate(expression.right, model));
    },
    "u": function (expression, model) {
        return unary[expression.operator](evaluate(expression.argument, model));
    },
    "??": function (expression, model) {
        var left = evaluate(expression.left, model);
        if (left == null) {
            return evaluate(expression.right, model);
        }
        return left;
    },
    "&&": function (expression, model) {
        return evaluate(expression.left, model) && evaluate(expression.right, model);
    },
    "||": function (expression, model) {
        return evaluate(expression.left, model) || evaluate(expression.right, model);
    },
    "@": function (expression, model) {
        var sequence = evaluate(expression.sequence, model);
        if (sequence) {
            return sequence[evaluate(expression.key, model)];
        }
        return null;
    },
    "@:": function (expression, model) {
        var sequence = evaluate(expression.sequence, model);
        if (sequence) {
            return sequence.slice(evaluate(expression.start, model), evaluate(expression.end, model));
        }
        return null;
    },
    "s": function (expression, model) {
        function sequencer(item) {
            return {
                get: function (k) {
                    return k == expression.key ? item : model[k];
                }
            };
        }

        var result = [];
        var sequence = evaluate(expression.sequence, model);
        if (sequence && sequence.constructor === Array) {
            if (expression.order) {
                var comparisons = sequence.map(function (x) { return { field: evaluate(expression.order, sequencer(x)), item: x }; });
                comparisons.sort(function (a, b) {
                    if (a.field != null) {
                        if (b.field != null) {
                            if (typeof a.field === "string") {
                                if (typeof b.field === "string") {
                                    return a.field.localeCompare(b.field);
                                }
                                return 1;
                            }
                            else if (typeof b.field === "string") {
                                return -1;
                            }
                            else if (a.field < b.field) {
                                return -1;
                            }
                            else if (b.field < a.field) {
                                return 1;
                            }
                            else {
                                return 0;
                            }
                        }
                        else {
                            return -1;
                        }
                    }
                    else if (b.field) {
                        return 1;
                    }
                    else {
                        return 0;
                    }
                });
                sequence = comparisons.map(function (x) { return x.item; });
            }
            for (var i = 0; i < sequence.length; i++) {
                var item = sequence[i];
                var s = sequencer(item);
                if (!expression.condition || evaluate(expression.condition, s)) {
                    result.push(expression.selection ? evaluate(expression.selection, s) : item);
                }
            }
        }
        return result;
    },
    "=": function (expression, model) {
        return null;
    }
};

var modifiers = {
    round: function (value) {
        return Math.round(value)
    },
    floor: function (value) {
        return Math.floor(value)
    },
    ciel: function (value) {
        return Math.ciel(value)
    },
    ciel: function (value) {
        return Math.ciel(value)
    },
    slice: function (value, size) {
		if (value && value.constructor === Array) {
			var slices = [];
			var sliceCount = Math.ceil(value.length / size);
			for (var s = 0; s < sliceCount; s++) {
				slices.push(value.slice(s * size, (s + 1) * size));
			}
			return slices;
		}
        return value;
    },
    properties: function (value) {
        var arr = [];
        if (value && typeof value === "object") {
            for (var key in value) {
                if (value.hasOwnProperty(key)) {
                    arr.push({key: key, value: value[key]});
                }
            }
        }
        return arr;
    },
    number: function (value) {
        var f = parseFloat(value);
        if (!isNaN(f)) {
            var s = f.toString();
            var p = s.indexOf('.');
            var i = p > 0 ? s.slice(0, p) : s;
            var n = i.length - 3;
            while (n > 0) {
                i = i.slice(0, n) + ',' + i.slice(n);
                n -= 3;
            }
            return p > 0 ? i + s.slice(p) : i;
        }
        return "";
    },
    date: function (value, format) {
        if (value != null) {
            var d = new Date(value);
            if (format === "f") {
                return d.getDate() + "/" +
                    (d.getMonth() + 1) + "/" +
                    d.getFullYear() + " " +
                    ("0" + d.getHours()).slice(-2) + ":" +
                    ("0" + d.getMinutes()).slice(-2);
            }
			if (format === "t") {
                return ("0" + d.getHours()).slice(-2) + ":" +
                    ("0" + d.getMinutes()).slice(-2);
            }

            return d.getDate() + "/" +
                (d.getMonth() + 1) + "/" +
                d.getFullYear();
        }
        return "";
    },
    sqrt: function (value) {
        return Math.sqrt(value);
    },
    pow: function (value) {
        return Math.pow(value);
    },
    sin: function (value) {
        return Math.sin(value);
    },
    cos: function (value) {
        return Math.cos(value);
    },
    tan: function (value) {
        return Math.tan(value);
    },
    asin: function (value) {
        return Math.asic(value);
    },
    acos: function (value) {
        return Math.acos(value);
    },
    atan: function (value) {
        return Math.atan(value);
    },
    PI: function () {
        return Math.PI;
    }
};

function extend(source, obj) {
    if (source) {
        for (var key in source) {
            if (source.hasOwnProperty(key)) {
                obj[key] = source[key];
            }
        }
    }
    return obj;
}

function evaluate(expression, model) {
    while (expression && expression["!exp"]) {
        if (expression.$x) {
            // Expression loop
            expression = null;
        }
        else {
            expression.$x = true;
            var r = expressions[expression["!exp"]](expression, model);
            expression.$x = false;
            expression = r;
        }
    }
    if (expression && expression["!type"]) {
        var modifier = modifiers[expression["!type"]];
        if (modifier) {
            var init = expression["!init"];
            var args = [];
            if (init && init.length > 0) {
                args = init.map(function (x) { return evaluate(x, model); });
            }
            return modifier.apply(null, args);
        }
        return null;
    }
    return expression;
}

function isNotWhitespace(ch) {
    return !(ch === ' ' || ch === '\r' || ch === '\t' ||
    ch === '\n' || ch === '\v' || ch === '\u00A0');
}

function isNotIdentifier(ch) {
    return !('a' <= ch && ch <= 'z' ||
    'A' <= ch && ch <= 'Z' ||
    '_' === ch || ch === '$' || ch === '#');
}

function search(str, s, cb) {
    var i = 0;
    if (typeof s === "function") {
        cb = s;
    }
    else {
        i = s;
    }
    while (i < str.length && !cb(str[i])) {
        i++;
    }
    return i;
}

function Model(baseModel, model, parameters, env) {
    this.baseModel = baseModel;
    this.model = model;
    this.parameters = parameters;
    this.env = env;
}

Model.prototype.get = function (key) {
    if (key) {
        if (key[0] == '$') {
            var name = key.slice(1);
            var n = key.length === 1 ? 0 : parseInt(name);
            if (isNaN(n)) {
                if (this.env && this.env[name] != undefined) {
                    return this.env[name];
                }
                else if (this.baseModel) {
                    return this.baseModel.get(key);
                }
            }
            else if (this.parameters && n < this.parameters.length) {
                return evaluate(this.parameters[n].parameter, this.parameters[n].model);
            }
        }
        else {
            var v = null;
            if (typeof this.model === "object") {
                v = this.model[key];
            }
            if (v == null && this.baseModel != null) {
                return this.baseModel.get(key);
            }
            return v;
        }
    }

    return null;
};

function Rendo(template) {
    this.components = {};

    var components = oil.parse(template);

    for (var i = 0; i < components.length; i++) {
        var component = components[i];

        var name = component["!type"];
        if (name) {
            var render = component.render
                ? component.render
                : (component["!init"] && component["!init"].length > 0
                ? component["!init"][0]
                : (component["!ref"] || null));

            var scope = {};

            for (var key in component) {
                if (key[0] !== '!' && key !== "render") {
                    scope[key] = component[key];
                }
            }

            this.components[name] = {
                render: render,
                scope: scope
            };
        }
    }
}

Rendo.prototype.renderValue = function (value) {
    if (value != null && typeof value !== "object") {
        return "" + value;
    }
    return "";
};

Rendo.prototype.renderContents = function (contents, model) {
    var result = "";

    var s = 0;
    var o = contents.indexOf("{{");
    while (o >= s) {
        result += contents.slice(s, o);
        o += 2;

        var component = null;
        var fromModel = false;
        var i = search(contents, o, isNotWhitespace);

        if (contents[i] === ':' || contents[i] === '+') {
            fromModel = contents[i] == '+';
            o = i + 1;
            i = search(contents, o, isNotIdentifier);
            component = contents.slice(o, i);
        }

        // Reading to closing '}'
        var c = i;
        var count = 0;
        while (c < contents.length) {
            if (contents[c] === '{') {
                count++;
            }
            else if (contents[c] === '}') {
                if (count === 0) {
                    break;
                }
                count--;
            }
            c++;
        }

        var expressions = oil.parse(contents.slice(i, c));
        if (expressions.length > 0) {
            var models = evaluate(expressions[0], model);
            var parameters = expressions.slice(1).map(function (x) { return { model: model, parameter: x }; });

            var isArray = false;
            if (!models || models.constructor !== Array) {
                models = [models];
            }
            else {
                isArray = true;
            }

            for (var m = 0; m < models.length; m++) {
                var env = isArray ? {
                    index: m,
                    first: m === 0,
                    last: m === models.length - 1,
					previous: m > 0 ? models[m - 1] : null,
					next: m < models.length - 1 ? models[m + 1] : null
                } : null;
                if (component) {
                    if (fromModel) {
                        var renderContents = model.get(component);
                        if (renderContents) {
                            result += this.renderContents(renderContents, new Model(model, models[m], [{model: model, parameter: models[m]}].concat(parameters), env));
                        }
                    }
                    else {
                        result += this.render(component, new Model(model, models[m], null, env), [{model: model, parameter: models[m]}].concat(parameters));
                    }
                }
                else {
                    result += this.renderValue(models[m]);
                }
            }
        }
        else if (component) {
            // Render same model
            if (fromModel) {
                var renderContents = model.get(component);
                if (renderContents) {
                    result += this.renderContents(renderContents, model);
                }
            }
            else {
                result += this.render(component, model);
            }
        }

        if (c === contents.length || contents[c + 1] != "}") {
            return result;
        }

        s = c + 2;
        o = contents.indexOf("{{", s);
    }

    if (s < contents.length) {
        result += contents.slice(s);
    }

    return result;
};

Rendo.prototype.render = function (componentName, model, parameters) {
    if (componentName == null) {
        return this.renderValue(model);
    }

    var component = this.components[componentName];
    if (!component) {
        return "";
    }

    var contents = component.render;
    if (!contents) {
        return "";
    }

    var isArray = false;
    var models;
    if (model) {
        if (model.constructor === Array) {
            models = model;
            isArray = true;
        }
        else {
            models = [model];
        }
    }
    else {
        models = [null];
    }

    var result = "";
    for (var m = 0; m < models.length; m++) {
        var model = models[m];
        var env = isArray ? {
            index: m,
            first: m === 0,
            last: m === models.length - 1
        } : null;
        if (!model || model.constructor !== Model) {
            model = new Model(null, model, null, env);
        }

        result += this.renderContents(contents, new Model(model, component.scope, parameters, env));
    }
    return result;
};

Rendo.render = function (template, component, model) {
    return new Rendo(template).render(component, model);
};

module.exports = Rendo;<commit_msg>Sequence condition fix and any function<commit_after>var oil = require('oil');

var binary = {
    "==": function (left, right) { return left == right; },
    "!=": function (left, right) { return left != right; },
    "<": function (left, right) { return left < right; },
    ">": function (left, right) { return left > right; },
    "<=": function (left, right) { return left <= right; },
    ">=": function (left, right) { return left >= right; },
    "<<": function (left, right) { return left >> right; },
    ">>": function (left, right) { return left << right; },
    "+": function (left, right) { return left != null ? (right != null ? left + right : left) : right; }, // preventing string operation with null to convert to "null"
    "-": function (left, right) { return left != null ? (right != null ? left - right : left) : -right; }, // preventing string operation with null to conert to NaN
    "*": function (left, right) { return left * right; },
    "/": function (left, right) { return left / right; },
    "%": function (left, right) { return left % right; }
};

var unary = {
    "!": function (argument) { return !argument; },
    "+": function (argument) { return +argument; },
    "-": function (argument) { return -argument; }
};

var expressions = {
    "?:": function (expression, model) {
        if (evaluate(expression.expression, model)) {
            return evaluate(expression.positive, model);
        }
        else {
            return evaluate(expression.negative, model);
        }
    },
    "?=": function (expression, model) {
        var value = evaluate(expression.expression, model);
        for (var i = 0; i < expression.options.length; i++) {
            var option = expression.options[i];
            if (evaluate(option.value, model) == value) {
                return evaluate(option.result, model);
            }
        }
        return evaluate(expression.otherwise, model);
    },
    "f": function (expression, model) {
        var object = evaluate(expression.object, model);
        if (object) {
            return object[expression.field];
        }
        return null;
    },
    "i": function (expression, model) {
        return model.get(expression.identifier);
    },
    "b": function (expression, model) {
        return binary[expression.operator](evaluate(expression.left, model), evaluate(expression.right, model));
    },
    "u": function (expression, model) {
        return unary[expression.operator](evaluate(expression.argument, model));
    },
    "??": function (expression, model) {
        var left = evaluate(expression.left, model);
        if (left == null) {
            return evaluate(expression.right, model);
        }
        return left;
    },
    "&&": function (expression, model) {
        return evaluate(expression.left, model) && evaluate(expression.right, model);
    },
    "||": function (expression, model) {
        return evaluate(expression.left, model) || evaluate(expression.right, model);
    },
    "@": function (expression, model) {
        var sequence = evaluate(expression.sequence, model);
        if (sequence) {
            return sequence[evaluate(expression.key, model)];
        }
        return null;
    },
    "@:": function (expression, model) {
        var sequence = evaluate(expression.sequence, model);
        if (sequence) {
            return sequence.slice(evaluate(expression.start, model), evaluate(expression.end, model));
        }
        return null;
    },
    "s": function (expression, model) {
        function sequencer(item) {
            return {
                get: function (k) {
                    return k == expression.key ? item : model.get(k);
                }
            };
        }

        var result = [];
        var sequence = evaluate(expression.sequence, model);
        if (sequence && sequence.constructor === Array) {
            if (expression.order) {
                var comparisons = sequence.map(function (x) { return { field: evaluate(expression.order, sequencer(x)), item: x }; });
                comparisons.sort(function (a, b) {
                    if (a.field != null) {
                        if (b.field != null) {
                            if (typeof a.field === "string") {
                                if (typeof b.field === "string") {
                                    return a.field.localeCompare(b.field);
                                }
                                return 1;
                            }
                            else if (typeof b.field === "string") {
                                return -1;
                            }
                            else if (a.field < b.field) {
                                return -1;
                            }
                            else if (b.field < a.field) {
                                return 1;
                            }
                            else {
                                return 0;
                            }
                        }
                        else {
                            return -1;
                        }
                    }
                    else if (b.field) {
                        return 1;
                    }
                    else {
                        return 0;
                    }
                });
                sequence = comparisons.map(function (x) { return x.item; });
            }
            for (var i = 0; i < sequence.length; i++) {
                var item = sequence[i];
                var s = sequencer(item);
                if (!expression.condition || evaluate(expression.condition, s)) {
                    result.push(expression.selection ? evaluate(expression.selection, s) : item);
                }
            }
        }
        return result;
    },
    "=": function (expression, model) {
        return null;
    }
};

var modifiers = {
    round: function (value) {
        return Math.round(value)
    },
    floor: function (value) {
        return Math.floor(value)
    },
    ciel: function (value) {
        return Math.ciel(value)
    },
    ciel: function (value) {
        return Math.ciel(value)
    },
    any: function (value) {
        if (value == null) {
            return false;
        }
        else if (value.constructor === Array) {
            return value.length > 0;
        }
        return true;
    },
    slice: function (value, size) {
		if (value && value.constructor === Array) {
			var slices = [];
			var sliceCount = Math.ceil(value.length / size);
			for (var s = 0; s < sliceCount; s++) {
				slices.push(value.slice(s * size, (s + 1) * size));
			}
			return slices;
		}
        return value;
    },
    properties: function (value) {
        var arr = [];
        if (value && typeof value === "object") {
            for (var key in value) {
                if (value.hasOwnProperty(key)) {
                    arr.push({key: key, value: value[key]});
                }
            }
        }
        return arr;
    },
    number: function (value) {
        var f = parseFloat(value);
        if (!isNaN(f)) {
            var s = f.toString();
            var p = s.indexOf('.');
            var i = p > 0 ? s.slice(0, p) : s;
            var n = i.length - 3;
            while (n > 0) {
                i = i.slice(0, n) + ',' + i.slice(n);
                n -= 3;
            }
            return p > 0 ? i + s.slice(p) : i;
        }
        return "";
    },
    date: function (value, format) {
        if (value != null) {
            var d = new Date(value);
            if (format === "f") {
                return d.getDate() + "/" +
                    (d.getMonth() + 1) + "/" +
                    d.getFullYear() + " " +
                    ("0" + d.getHours()).slice(-2) + ":" +
                    ("0" + d.getMinutes()).slice(-2);
            }
			if (format === "t") {
                return ("0" + d.getHours()).slice(-2) + ":" +
                    ("0" + d.getMinutes()).slice(-2);
            }

            return d.getDate() + "/" +
                (d.getMonth() + 1) + "/" +
                d.getFullYear();
        }
        return "";
    },
    sqrt: function (value) {
        return Math.sqrt(value);
    },
    pow: function (value) {
        return Math.pow(value);
    },
    sin: function (value) {
        return Math.sin(value);
    },
    cos: function (value) {
        return Math.cos(value);
    },
    tan: function (value) {
        return Math.tan(value);
    },
    asin: function (value) {
        return Math.asic(value);
    },
    acos: function (value) {
        return Math.acos(value);
    },
    atan: function (value) {
        return Math.atan(value);
    },
    PI: function () {
        return Math.PI;
    }
};

function extend(source, obj) {
    if (source) {
        for (var key in source) {
            if (source.hasOwnProperty(key)) {
                obj[key] = source[key];
            }
        }
    }
    return obj;
}

function evaluate(expression, model) {
    while (expression && expression["!exp"]) {
        if (expression.$x) {
            // Expression loop
            expression = null;
        }
        else {
            expression.$x = true;
            var r = expressions[expression["!exp"]](expression, model);
            expression.$x = false;
            expression = r;
        }
    }
    if (expression && expression["!type"]) {
        var modifier = modifiers[expression["!type"]];
        if (modifier) {
            var init = expression["!init"];
            var args = [];
            if (init && init.length > 0) {
                args = init.map(function (x) { return evaluate(x, model); });
            }
            return modifier.apply(null, args);
        }
        return null;
    }
    return expression;
}

function isNotWhitespace(ch) {
    return !(ch === ' ' || ch === '\r' || ch === '\t' ||
    ch === '\n' || ch === '\v' || ch === '\u00A0');
}

function isNotIdentifier(ch) {
    return !('a' <= ch && ch <= 'z' ||
    'A' <= ch && ch <= 'Z' ||
    '_' === ch || ch === '$' || ch === '#');
}

function search(str, s, cb) {
    var i = 0;
    if (typeof s === "function") {
        cb = s;
    }
    else {
        i = s;
    }
    while (i < str.length && !cb(str[i])) {
        i++;
    }
    return i;
}

function Model(baseModel, model, parameters, env) {
    this.baseModel = baseModel;
    this.model = model;
    this.parameters = parameters;
    this.env = env;
}

Model.prototype.get = function (key) {
    if (key) {
        if (key[0] == '$') {
            var name = key.slice(1);
            var n = key.length === 1 ? 0 : parseInt(name);
            if (isNaN(n)) {
                if (this.env && this.env[name] != undefined) {
                    return this.env[name];
                }
                else if (this.baseModel) {
                    return this.baseModel.get(key);
                }
            }
            else if (this.parameters && n < this.parameters.length) {
                return evaluate(this.parameters[n].parameter, this.parameters[n].model);
            }
        }
        else {
            var v = null;
            if (typeof this.model === "object") {
                v = this.model[key];
            }
            if (v == null && this.baseModel != null) {
                return this.baseModel.get(key);
            }
            return v;
        }
    }

    return null;
};

function Rendo(template) {
    this.components = {};

    var components = oil.parse(template);

    for (var i = 0; i < components.length; i++) {
        var component = components[i];

        var name = component["!type"];
        if (name) {
            var render = component.render
                ? component.render
                : (component["!init"] && component["!init"].length > 0
                ? component["!init"][0]
                : (component["!ref"] || null));

            var scope = {};

            for (var key in component) {
                if (key[0] !== '!' && key !== "render") {
                    scope[key] = component[key];
                }
            }

            this.components[name] = {
                render: render,
                scope: scope
            };
        }
    }
}

Rendo.prototype.renderValue = function (value) {
    if (value != null && typeof value !== "object") {
        return "" + value;
    }
    return "";
};

Rendo.prototype.renderContents = function (contents, model) {
    var result = "";

    var s = 0;
    var o = contents.indexOf("{{");
    while (o >= s) {
        result += contents.slice(s, o);
        o += 2;

        var component = null;
        var fromModel = false;
        var i = search(contents, o, isNotWhitespace);

        if (contents[i] === ':' || contents[i] === '+') {
            fromModel = contents[i] == '+';
            o = i + 1;
            i = search(contents, o, isNotIdentifier);
            component = contents.slice(o, i);
        }

        // Reading to closing '}'
        var c = i;
        var count = 0;
        while (c < contents.length) {
            if (contents[c] === '{') {
                count++;
            }
            else if (contents[c] === '}') {
                if (count === 0) {
                    break;
                }
                count--;
            }
            c++;
        }

        var expressions = oil.parse(contents.slice(i, c));
        if (expressions.length > 0) {
            var models = evaluate(expressions[0], model);
            var parameters = expressions.slice(1).map(function (x) { return { model: model, parameter: x }; });

            var isArray = false;
            if (!models || models.constructor !== Array) {
                models = [models];
            }
            else {
                isArray = true;
            }

            for (var m = 0; m < models.length; m++) {
                var env = isArray ? {
                    index: m,
                    first: m === 0,
                    last: m === models.length - 1,
					previous: m > 0 ? models[m - 1] : null,
					next: m < models.length - 1 ? models[m + 1] : null
                } : null;
                if (component) {
                    if (fromModel) {
                        var renderContents = model.get(component);
                        if (renderContents) {
                            result += this.renderContents(renderContents, new Model(model, models[m], [{model: model, parameter: models[m]}].concat(parameters), env));
                        }
                    }
                    else {
                        result += this.render(component, new Model(model, models[m], null, env), [{model: model, parameter: models[m]}].concat(parameters));
                    }
                }
                else {
                    result += this.renderValue(models[m]);
                }
            }
        }
        else if (component) {
            // Render same model
            if (fromModel) {
                var renderContents = model.get(component);
                if (renderContents) {
                    result += this.renderContents(renderContents, model);
                }
            }
            else {
                result += this.render(component, model);
            }
        }

        if (c === contents.length || contents[c + 1] != "}") {
            return result;
        }

        s = c + 2;
        o = contents.indexOf("{{", s);
    }

    if (s < contents.length) {
        result += contents.slice(s);
    }

    return result;
};

Rendo.prototype.render = function (componentName, model, parameters) {
    if (componentName == null) {
        return this.renderValue(model);
    }

    var component = this.components[componentName];
    if (!component) {
        return "";
    }

    var contents = component.render;
    if (!contents) {
        return "";
    }

    var isArray = false;
    var models;
    if (model) {
        if (model.constructor === Array) {
            models = model;
            isArray = true;
        }
        else {
            models = [model];
        }
    }
    else {
        models = [null];
    }

    var result = "";
    for (var m = 0; m < models.length; m++) {
        var model = models[m];
        var env = isArray ? {
            index: m,
            first: m === 0,
            last: m === models.length - 1
        } : null;
        if (!model || model.constructor !== Model) {
            model = new Model(null, model, null, env);
        }

        result += this.renderContents(contents, new Model(model, component.scope, parameters, env));
    }
    return result;
};

Rendo.render = function (template, component, model) {
    return new Rendo(template).render(component, model);
};

module.exports = Rendo;<|endoftext|>


<commit_before>var http        = require('http')
var https       = require('https')
var HttpProxy   = require('http-proxy')
var midware     = require('midware')
var parseUrl    = require('url').parse
var replay      = require('./replay')
var middlewares = require('./middlewares')
var assign      = require('lodash').assign

module.exports = Route

function Route(path) {
  this.opts     = {}
  this.replays  = []
  this.path     = path
  this.mw       = midware()
  this.proxy    = new HttpProxy()
}

Route.prototype.target  =
Route.prototype.forward = function (url) {
  this.opts.target = url
  return this
}

Route.prototype.balance = function (urls) {
  if (Array.isArray(urls)) {
    this.opts.balance = urls
  }
  return this
}

Route.prototype.options = function (opts) {
  assign(this.opts, opts)
  return this
}

Route.prototype.replay = function (url, opts) {
  if (typeof url === 'string') {
    url = { target: url }
  }
  this.replays.push(assign({}, url, opts))
  return this
}

Route.prototype.use = function () {
  this.mw.apply(null, arguments)
  return this
}

Route.prototype.on = function () {
  this.proxy.on.apply(this.proxy, arguments)
  return this
}

Route.prototype.once = function () {
  this.proxy.once.apply(this.proxy, arguments)
  return this
}

Route.prototype.off = function () {
  this.proxy.off.apply(this.proxy, arguments)
  return this
}

Route.prototype.host = function (host) {
  this.mw(middlewares.host(host))
  return this
}

Route.prototype.transformResponseBody = function (transformer) {
  this.mw(middlewares.transformResponseBody(transformer))
  return this
}

Route.prototype.transformRequestBody = function (transformer) {
  this.mw(middlewares.transformRequestBody(transformer))
  return this
}

Route.prototype.toPath = function (path, params) {
  this.mw(middlewares.toPath(path, params))
  return this
}

Route.prototype.headers = function (headers) {
  this.mw(middlewares.headers(headers))
  return this
}

Route.prototype.handle = function (options, req, res) {
  var route = this
  var opts = assign({}, options, this.opts)

  // Dispatch the route middleware
  this.mw.run(req, res, forwarder)

  function forwarder(err) {
    if (err) {
      return route.proxy.emit('route:error', err, req, res)
    }

    // Ensure that we can forward/replay the request
    if (!opts.target && !route.replays.length) {
      return cannotForwardError(route, req, res)
    }

    // Forward the request
    proxyRequest(req, res, route, opts)

    // And replay it, if necessary
    replayRequest(req, route, opts)
  }
}

function proxyRequest(req, res, route, opts) {
  // Balance the request, if required
  var balance = opts.balance
  if (balance) {
    opts.target = balance.shift()
    balance.push(opts.target)
  }

  if (!opts.target) { return }

  if (opts.forwardHost) {
    req.headers.host = parseUrl(opts.target).host
  }

  // Forward the request to the main target
  route.proxy.web(req, res, opts)
}

function replayRequest(req, route, opts) {
  var replays = route.replays.length
    ? route.replays
    : opts.replays

  replays
    .map(function (replay) {
      return assign({}, opts, replay)
    })
    .forEach(replay(req, route))
}

function cannotForwardError(route, req, res) {
  var error = { 'message': 'Target URL was not defined for this route' }
  route.proxy.emit('route:error', null, req, res)
  res.writeHead(404, { 'Content-Type': 'application/json' })
  return res.end(JSON.stringify(error))
}
<commit_msg>fix: no route handler<commit_after>var http        = require('http')
var https       = require('https')
var HttpProxy   = require('http-proxy')
var midware     = require('midware')
var parseUrl    = require('url').parse
var replay      = require('./replay')
var middlewares = require('./middlewares')
var assign      = require('lodash').assign

module.exports = Route

function Route(path) {
  this.opts     = {}
  this.replays  = []
  this.path     = path
  this.mw       = midware()
  this.proxy    = new HttpProxy()
}

Route.prototype.target  =
Route.prototype.forward = function (url) {
  this.opts.target = url
  return this
}

Route.prototype.balance = function (urls) {
  if (Array.isArray(urls)) {
    this.opts.balance = urls
  }
  return this
}

Route.prototype.options = function (opts) {
  assign(this.opts, opts)
  return this
}

Route.prototype.replay = function (url, opts) {
  if (typeof url === 'string') {
    url = { target: url }
  }
  this.replays.push(assign({}, url, opts))
  return this
}

Route.prototype.use = function () {
  this.mw.apply(null, arguments)
  return this
}

Route.prototype.on = function () {
  this.proxy.on.apply(this.proxy, arguments)
  return this
}

Route.prototype.once = function () {
  this.proxy.once.apply(this.proxy, arguments)
  return this
}

Route.prototype.off = function () {
  this.proxy.off.apply(this.proxy, arguments)
  return this
}

Route.prototype.host = function (host) {
  this.mw(middlewares.host(host))
  return this
}

Route.prototype.transformResponseBody = function (transformer) {
  this.mw(middlewares.transformResponseBody(transformer))
  return this
}

Route.prototype.transformRequestBody = function (transformer) {
  this.mw(middlewares.transformRequestBody(transformer))
  return this
}

Route.prototype.toPath = function (path, params) {
  this.mw(middlewares.toPath(path, params))
  return this
}

Route.prototype.headers = function (headers) {
  this.mw(middlewares.headers(headers))
  return this
}

Route.prototype.handle = function (options, req, res) {
  var route = this
  var opts = assign({}, options, this.opts)

  // Dispatch the route middleware
  this.mw.run(req, res, forwarder)

  function forwarder(err) {
    if (err) {
      return route.proxy.emit('route:error', err, req, res)
    }

    // Ensure that we can forward/replay the request
    if (!opts.target && !opts.balance && !route.replays.length) {
      return cannotForwardError(route, req, res)
    }

    // Forward the request
    proxyRequest(req, res, route, opts)

    // And replay it, if necessary
    replayRequest(req, route, opts)
  }
}

function proxyRequest(req, res, route, opts) {
  // Balance the request, if required
  var balance = opts.balance
  if (balance) {
    opts.target = balance.shift()
    balance.push(opts.target)
  }

  if (!opts.target) { return }

  if (opts.forwardHost) {
    req.headers.host = parseUrl(opts.target).host
  }

  // Forward the request to the main target
  route.proxy.web(req, res, opts)
}

function replayRequest(req, route, opts) {
  var replays = route.replays.length
    ? route.replays
    : opts.replays

  replays
    .map(function (replay) {
      return assign({}, opts, replay)
    })
    .forEach(replay(req, route))
}

function cannotForwardError(route, req, res) {
  var error = { 'message': 'Target URL was not defined for this route' }
  route.proxy.emit('route:error', null, req, res)
  res.writeHead(404, { 'Content-Type': 'application/json' })
  return res.end(JSON.stringify(error))
}
<|endoftext|>


<commit_before>/*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 Entity = require('/entity.js').Entity;
var Utils = require('/utils.js').Utils;
var PerspectiveCamera = require('/camera.js').PerspectiveCamera;
var SpriteComponent = require('/sprite.js').SpriteComponent;
var DirectionalLight = require('/light.js').DirectionalLight;
var Material = require('/material.js').Material;

function Scene(window) {
    Entity.call(this);
    this.lights = [];
    this.entities = [];
    this.camera = new PerspectiveCamera({
        window: window,
        near: 1,
        far: 1000,
        fieldOfView: 45
    });
    this.window = window;
    this.graphics = window.graphics;
    this.layers = {};
    this.directionalLight = new DirectionalLight(new Vector3(0,0,1));
    this.addLight(this.directionalLight);
}

Utils.extend(Scene, Entity);

Scene.prototype.addEntity = function (entity, layer) {
    layer = layer || 0;
    if (!this.layers[layer]) {
        this.layers[layer] = [];
    }
    this.layers[layer].push(entity);
    this.entities.push(entity);
};

Scene.prototype.addLight = function (light) {
    this.lights.push(light);
};

Scene.prototype.update = function(elapsed) {
    for (var layer in this.layers) {
        for (var i=0; i<this.layers[layer].length; i++) {
            this.layers[layer][i].update(elapsed);
        }
    }
    for (var i=0; i<this.lights.length; i++) {
        this.lights[i].update(elapsed);
    }
    this.camera.update(elapsed);
};

Scene.prototype.draw = function() {
    // Setup all used materials with camera and lights for the current frame.
    Material.setup(this.camera, this.lights);
    for (var layer in this.layers) {
        for (var i=0; i<this.layers[layer].length; i++) {
            this.layers[layer][i].draw();
        }
        // When sprite components are drawn, the sprites are really just being
        // added to a list. The function below will actually draw the sprites.
        SpriteComponent.draw();
        // When sprites are drawn the depth state is automatically changed,
        // let's reset it here to prepare for the next frame.
        this.graphics.setDepthState('default');
    }
};

module.exports.Scene = Scene;
<commit_msg>Added Layer.<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 Entity = require('/entity.js').Entity;
var Utils = require('/utils.js').Utils;
var PerspectiveCamera = require('/camera.js').PerspectiveCamera;
var SpriteComponent = require('/sprite.js').SpriteComponent;
var DirectionalLight = require('/light.js').DirectionalLight;
var Material = require('/material.js').Material;

function Scene(window) {
    Entity.call(this);
    this.lights = [];
    this.camera = new PerspectiveCamera({
        window: window,
        near: 1,
        far: 1000,
        fieldOfView: 45
    });
    this.window = window;
    this.graphics = window.graphics;
    this.layers = {};
    this.directionalLight = new DirectionalLight(new Vector3(0,0,1));
    this.addLight(this.directionalLight);
}

Utils.extend(Scene, Entity);

Scene.prototype.addEntity = function (entity, layer) {
    layer = layer || 'default';
    if (!this.layers[layer]) {
        this.layers[layer] = new Layer(this.graphics);
    }
    this.layers[layer].entities.push(entity);
};

Scene.prototype.addLight = function (light) {
    this.lights.push(light);
};

Scene.prototype.update = function(elapsed) {
    for (var layer in this.layers) {
        this.layers[layer].update(elapsed);
    }
    for (var i=0; i<this.lights.length; i++) {
        this.lights[i].update(elapsed);
    }
    this.camera.update(elapsed);
};

Scene.prototype.draw = function() {
    // Setup all used materials with camera and lights for the current frame.
    Material.setup(this.camera, this.lights);
    for (var layer in this.layers) {
        this.layers[layer].draw();
    }
};

function Layer(graphics) {
    this.graphics = graphics;
    this.entities = [];
}

Layer.prototype.update = function(elapsed) {
    var destroyed = [];
    for (var i=0; i<this.entities.length; i++) {
        this.entities[i].update(elapsed);
        if (this.entities[i].destroyed) {
            destroyed.push(this.entities[i]);
        }
    }
    for (var i=0; i<destroyed.length; i++) {
        var index = this.entities.indexOf(destroyed[i]);
        this.entities.splice(index, 1);
    }
};

Layer.prototype.draw = function() {
    for (var i=0; i<this.entities.length; i++) {
        this.entities[i].draw();
    }
    // When sprite components are drawn, the sprites are really just being
    // added to a list. The function below will actually draw the sprites.
    SpriteComponent.draw();
    // When sprites are drawn the depth state is automatically changed,
    // let's reset it here to prepare for the next frame.
    this.graphics.setDepthState('default');
};

module.exports.Scene = Scene;
module.exports.Layer = Layer;
<|endoftext|>


<commit_before>/* jshint esnext: true, node: true, loopfunc: true, undef: true, unused: true */

var Route = require('zfinder-route');
var arrayUtils = require('zero-lang-array');
var bodyParser = require('body-parser');
var colors = require('colors/safe');
var connect = require('connect');
var connectRedirection = require('connect-redirection');
var getInstalledPath = require('module-path');
var getPort = require('get-port');
var objectUtils = require('zero-lang-object');
var json = require('zero-encoding-json');
var path = require('path');
var serveStatic = require('serve-static');
var sprintf = require('zero-fmt-sprintf');
var urlrouter = require('urlrouter');
var utils = require('zfinder-utils');
var url = require('url');

function startServer(server, config) {
    server.listen(config.port);
    var url = sprintf('http://127.0.0.1:%d/', config.port);
    utils.log(colors.grey('\n[INFO: zfinder server started!]'));
    utils.log(colors.grey('url: ') + colors.green(url));
    if (config.open) {
        require('open')(url);
    }
}

module.exports = function (config) {
    // put necessary options to process.envs
    process.env.ZFINDER_DEBUG = config.debug;

    var server = connect();

    // add middleware functionalities {
        server
            // FIXME body is not available until you set the correct headers
            .use(bodyParser.json()) // parse json body
            .use(bodyParser.urlencoded({extended: true})) // parse urlencoded body
            .use(bodyParser.raw()) // parse raw body
            .use(bodyParser.text()) // parse text body
            .use(connectRedirection()) // res.redirect()
            .use(function(req, res, next) {
                var urlInfo = url.parse(req.url, true);
                var query = urlInfo.query || {};
                var body = req.body || {};

                // add req._urlInfo
                req._urlInfo = urlInfo;

                // add req._params (combination of query and body)
                req._params = objectUtils.extend({}, query, body);

                // add req._mw (check which middleware to run)
                req._mw = req._params._mw || '';

                // res._JSONRes(data) (generate JSON response)
                res._JSONRes = function(data) {
                    var body = json.stringify(data);
                    var buf = new Buffer(body, 'utf8');
                    res.setHeader('Content-Type', 'application/json; charset=utf-8');
                    res.setHeader('Content-Length', buf.length);
                    res.end(buf);
                };

                // res._HTMLRes(data) (generate HTML response)
                res._HTMLRes = function(data) {
                    var buf = new Buffer(data);
                    res.setHeader('Content-Type', 'text/html; charset=utf-8');
                    res.setHeader('Content-Length', buf.length);
                    res.end(buf);
                };

                next();
            });
    // }

    // process middlewares {
        var middlewares = objectUtils.extend({}, config.middleware, config.localmiddleware);
        var routes = [];
        objectUtils.each(middlewares, function(options, mwName) {
            options = options || {};
            var mw;
            try {
                utils.log(colors.grey(
                    sprintf('\n[INFO: middleware %s processing]', mwName)
                ));
                mw = require(mwName);
            } catch(e) {
                console.error(
                    colors.red('[ERROR: middleware no found]'),
                    colors.grey('try to install it:'),
                    sprintf('npm install %s -g', mwName)
                );
            }
            if (mw && mw.getRoutes) {
                arrayUtils.each(mw.getRoutes(), function(route) {
                    route.server = route.serve(options, config);
                    routes.push(route);
                });

                var partsOfMwname = mwName.split(path.sep); // for local ones
                var serverName = partsOfMwname[partsOfMwname.length - 1];
                server.use(
                    sprintf('%s/%s', config.path.middlewareRoot, serverName),
                    serveStatic(options.local ? mwName : getInstalledPath(mwName, __dirname))
                );
                utils.log(colors.grey(
                    sprintf('[INFO: middleware %s processed]', mwName)
                ));
            }
        });
        // sort by priority
        routes.sort(function(a, b) {
            return b.priority - a.priority;
        });
        // apply middlewares
        arrayUtils.each(routes, function(route) {
            server.use(urlrouter(function (app) {
                utils.log(colors.grey(
                    sprintf('\n[INFO: route %s applying]', route.name)
                ));
                utils.log(function(){ route.print(); });

                app[route.method](route.url, route.server);

                utils.log(colors.grey(
                    sprintf('[INFO: route %s applied]', route.name)
                ));
            }));
        });
    // }

    // basic server {
        // serve zfinder files
        utils.log(colors.grey('\n[INFO: route zfinder:get applying]'));
        utils.log(function() {
            Route.standardOutput({
                name: 'zfinder:get',
                method: 'get',
                url: '*',
            });
        });
        server.use(
            config.path.zfinderRoot,
            serveStatic(path.resolve(__dirname, '../'))
        );
        utils.log(colors.grey('[INFO: route zfinder:get applied]'));

        // if no middleware matched, fallback to a static file server
        utils.log(colors.grey('\n[INFO: route static-files:get applying]'));
        utils.log(function() {
            Route.standardOutput({
                name: 'static-files:get',
                method: 'get',
                url: '*',
            });
        });
        server.use(
            serveStatic(config.root)
        );
        utils.log(colors.grey('[INFO: route static-files:get applied]'));
    // }

    // start server {
        if (config.port) {
            startServer(server, config);
        } else {
            getPort(function(err, port) {
                if (err) throw err;
                config.port = port;
                startServer(server, config);
            });
        }
    // }
};

<commit_msg>add themes for middlewares<commit_after>/* jshint esnext: true, node: true, loopfunc: true, undef: true, unused: true */

var Route = require('zfinder-route');
var arrayUtils = require('zero-lang-array');
var bodyParser = require('body-parser');
var colors = require('colors/safe');
var connect = require('connect');
var connectRedirection = require('connect-redirection');
var getInstalledPath = require('module-path');
var getPort = require('get-port');
var objectUtils = require('zero-lang-object');
var json = require('zero-encoding-json');
var path = require('path');
var serveStatic = require('serve-static');
var sprintf = require('zero-fmt-sprintf');
var urlrouter = require('urlrouter');
var utils = require('zfinder-utils');
var url = require('url');

function startServer(server, config) {
    server.listen(config.port);
    var url = sprintf('http://127.0.0.1:%d/', config.port);
    utils.log(colors.grey('\n[INFO: zfinder server started!]'));
    utils.log(colors.grey('url: ') + colors.green(url));
    if (config.open) {
        require('open')(url);
    }
}

module.exports = function (config) {
    // put necessary options to process.envs
    process.env.ZFINDER_DEBUG = config.debug;

    var server = connect();

    // add middleware functionalities {
        server
            // FIXME body is not available until you set the correct headers
            .use(bodyParser.json()) // parse json body
            .use(bodyParser.urlencoded({extended: true})) // parse urlencoded body
            .use(bodyParser.raw()) // parse raw body
            .use(bodyParser.text()) // parse text body
            .use(connectRedirection()) // res.redirect()
            .use(function(req, res, next) {
                var urlInfo = url.parse(req.url, true);
                var query = urlInfo.query || {};
                var body = req.body || {};

                // add req._urlInfo
                req._urlInfo = urlInfo;

                // add req._params (combination of query and body)
                req._params = objectUtils.extend({}, query, body);

                // add req._mw (check which middleware to run)
                req._mw = req._params._mw || '';

                // res._JSONRes(data) (generate JSON response)
                res._JSONRes = function(data) {
                    var body = json.stringify(data);
                    var buf = new Buffer(body, 'utf8');
                    res.setHeader('Content-Type', 'application/json; charset=utf-8');
                    res.setHeader('Content-Length', buf.length);
                    res.end(buf);
                };

                // res._HTMLRes(data) (generate HTML response)
                res._HTMLRes = function(data) {
                    var buf = new Buffer(data);
                    res.setHeader('Content-Type', 'text/html; charset=utf-8');
                    res.setHeader('Content-Length', buf.length);
                    res.end(buf);
                };

                next();
            });
    // }

    // process middlewares {
        var middlewares = objectUtils.extend({}, config.middleware, config.localmiddleware);
        var routes = [];
        objectUtils.each(middlewares, function(options, mwName) {
            options = options || {};
            var mw;
            try {
                utils.log(colors.grey(
                    sprintf('\n[INFO: middleware %s processing]', mwName)
                ));
                mw = require(mwName);
            } catch(e) {
                console.error(
                    colors.red('[ERROR: middleware no found]'),
                    colors.grey('try to install it:'),
                    sprintf('npm install %s -g', mwName)
                );
            }
            if (mw && mw.getRoutes) {
                arrayUtils.each(mw.getRoutes(), function(route) {
                    route.server = route.serve(options, config);
                    routes.push(route);
                });

                var partsOfMwname = mwName.split(path.sep); // for local ones
                var serverName = partsOfMwname[partsOfMwname.length - 1];
                server.use(
                    sprintf('%s/%s', config.path.middlewareRoot, serverName),
                    serveStatic(options.local ? mwName : getInstalledPath(mwName, __dirname))
                );
                // theme for middleware
                if (mw.theme) {
                    server.use(
                        sprintf('%s/%s/theme', config.path.middlewareRoot, serverName),
                        serveStatic(mw.theme) // FIXME should be absolute path
                    );
                }
                utils.log(colors.grey(
                    sprintf('[INFO: middleware %s processed]', mwName)
                ));
            }
        });
        // sort by priority
        routes.sort(function(a, b) {
            return b.priority - a.priority;
        });
        // apply middlewares
        arrayUtils.each(routes, function(route) {
            server.use(urlrouter(function (app) {
                utils.log(colors.grey(
                    sprintf('\n[INFO: route %s applying]', route.name)
                ));
                utils.log(function(){ route.print(); });

                app[route.method](route.url, route.server);

                utils.log(colors.grey(
                    sprintf('[INFO: route %s applied]', route.name)
                ));
            }));
        });
    // }

    // basic server {
        // serve zfinder files
        utils.log(colors.grey('\n[INFO: route zfinder:get applying]'));
        utils.log(function() {
            Route.standardOutput({
                name: 'zfinder:get',
                method: 'get',
                url: '*',
            });
        });
        server.use(
            config.path.zfinderRoot,
            serveStatic(path.resolve(__dirname, '../'))
        );
        utils.log(colors.grey('[INFO: route zfinder:get applied]'));

        // if no middleware matched, fallback to a static file server
        utils.log(colors.grey('\n[INFO: route static-files:get applying]'));
        utils.log(function() {
            Route.standardOutput({
                name: 'static-files:get',
                method: 'get',
                url: '*',
            });
        });
        server.use(
            serveStatic(config.root)
        );
        utils.log(colors.grey('[INFO: route static-files:get applied]'));
    // }

    // start server {
        if (config.port) {
            startServer(server, config);
        } else {
            getPort(function(err, port) {
                if (err) throw err;
                config.port = port;
                startServer(server, config);
            });
        }
    // }
};

<|endoftext|>


<commit_before>//
// Sets up intial project settings, middleware, mounted apps, and
// global configuration such as overriding Backbone.sync and
// populating sharify data
//
import _ from 'underscore'
import artsyError from 'artsy-error-handler'
import artsyPassport from 'artsy-passport'
import artsyXapp from 'artsy-xapp'
import Backbone from 'backbone'
import bodyParser from 'body-parser'
import bucketAssets from 'bucket-assets'
import cookieParser from 'cookie-parser'
import express from 'express'
import favicon from 'serve-favicon'
import flash from 'connect-flash'
import glob from 'glob'
import helmet from 'helmet'
import logger from 'morgan'
import path from 'path'
import session from 'cookie-session'
import sharify from 'sharify'
import siteAssociation from 'artsy-eigen-web-association'
import superSync from 'backbone-super-sync'
import { IpFilter as ipfilter } from 'express-ipfilter'
import timeout from 'connect-timeout'
import './setup_sharify'
import cache from './cache'
import downcase from './middleware/downcase'
import ensureSSL from './middleware/ensure_ssl'
import escapedFragmentMiddleware from './middleware/escaped_fragment'
import flashMiddleware from './middleware/flash'
import hardcodedRedirects from './middleware/hardcoded_redirects'
import hstsMiddleware from './middleware/hsts'
import localsMiddleware from './middleware/locals'
import proxyGravity from './middleware/proxy_to_gravity'
import proxyReflection from './middleware/proxy_to_reflection'
import sameOriginMiddleware from './middleware/same_origin'
import unsupportedBrowserCheck from './middleware/unsupported_browser'
import logFormat from './logger_format'
import CurrentUser from './current_user'
import splitTestMiddleware from '../desktop/components/split_test/middleware'
import config from '../config'
import debug from 'debug'

const {
  API_REQUEST_TIMEOUT,
  API_URL,
  COOKIE_DOMAIN,
  DEFAULT_CACHE_TIME,
  IP_BLACKLIST,
  NODE_ENV,
  OPENREDIS_URL,
  REQUEST_EXPIRE_MS,
  REQUEST_LIMIT,
  SESSION_COOKIE_KEY,
  SESSION_COOKIE_MAX_AGE,
  SESSION_SECRET,
  APP_TIMEOUT
} = config
const timeoutDebug = debug('timeout')

export default function (app) {
  // Timeout middleware
  if (NODE_ENV === 'production') app.use(timeout(APP_TIMEOUT || '29s'))

  // Blacklist IPs
  app.use(ipfilter(IP_BLACKLIST.split(','), { log: false, mode: 'deny' }))

  // Rate limiting
  if (OPENREDIS_URL && cache.client) {
    const limiter = require('express-limiter')(app, cache.client)
    limiter({
      path: '*',
      method: 'all',
      lookup: ['headers.x-forwarded-for'],
      total: REQUEST_LIMIT,
      expire: REQUEST_EXPIRE_MS,
      onRateLimited (req, res, next) {
        console.log('Rate limit exceeded for', req.headers['x-forwarded-for'])
        return next()
      }
    })
  }

  // Blank page used by Eigen for caching web views.
  // See: https://github.com/artsy/microgravity-private/pull/1138
  app.use(require('../desktop/apps/blank'))

  // Make sure we're using SSL and prevent clickjacking
  app.use(ensureSSL)
  app.use(hstsMiddleware)
  if (!NODE_ENV === 'test') app.use(helmet.frameguard())

  // Override Backbone to use server-side sync, inject the XAPP token,
  // add redis caching, and timeout for slow responses.
  superSync.timeout = API_REQUEST_TIMEOUT
  superSync.cacheClient = cache.client
  superSync.defaultCacheTime = DEFAULT_CACHE_TIME
  Backbone.sync = function (method, model, options) {
    if (options.headers == null) { options.headers = {} }
    options.headers['X-XAPP-TOKEN'] = artsyXapp.token || ''
    return superSync(method, model, options)
      .catch((err) => {
        timeoutDebug('BACKBONE SYNC ERROR')
        timeoutDebug(_.result(model.url) || options.url)
        timeoutDebug(JSON.stringify(options))
        timeoutDebug(err)
        timeoutDebug(err.stack)
        throw err
      })
  }

  // Inject sharify data before any app code
  app.use(sharify)

  // Cookie and session middleware
  app.use(cookieParser())
  app.set('trust proxy', true)
  app.use(session({
    secret: SESSION_SECRET,
    domain: COOKIE_DOMAIN,
    name: SESSION_COOKIE_KEY,
    maxAge: SESSION_COOKIE_MAX_AGE,
    secure: (NODE_ENV === 'production') || (NODE_ENV === 'staging')
  }))

  // Body parser has to be after proxy middleware for
  // node-http-proxy to work with POST/PUT/DELETE
  app.use('/api', proxyGravity.api)
  app.use(proxyReflection)
  app.use(bodyParser.json())
  app.use(bodyParser.urlencoded({ extended: true }))

  // Passport middleware for authentication.
  app.use(artsyPassport(_.extend({}, config, {
    CurrentUser: CurrentUser,
    ARTSY_URL: API_URL,
    userKeys: [
      'collector_level',
      'default_profile_id',
      'email',
      'has_partner_access',
      'id',
      'lab_features',
      'name',
      'paddle_number',
      'phone',
      'type'
    ]
  })))

  // Static assets
  if (NODE_ENV === 'development' || NODE_ENV === 'test') {
    app.use(require('stylus').middleware({
      src: path.resolve(__dirname, '../desktop'),
      dest: path.resolve(__dirname, '../desktop/public')
    }))
    app.use(require('browserify-dev-middleware')({
      src: path.resolve(__dirname, '../desktop'),
      transforms: [
        require('babelify'),
        require('caching-coffeeify'),
        require('jadeify')
      ],
      insertGlobals: true,
      debug: true
    }))
    app.use(require('stylus').middleware({
      src: path.resolve(__dirname, '../mobile'),
      dest: path.resolve(__dirname, '../mobile/public')
    }))
    app.use(require('browserify-dev-middleware')({
      src: path.resolve(__dirname, '../mobile'),
      transforms: [
        require('babelify'),
        require('caching-coffeeify'),
        require('jadeify')
      ],
      insertGlobals: true,
      debug: true
    }))
  }
  glob.sync(path.resolve(__dirname, '../') + '/desktop/**/public/')
    .concat(glob.sync(path.resolve(__dirname, '../') + '/mobile/**/public/'))
    .forEach((fld) => app.use(express.static(fld)))
  app.use(favicon(
    path.resolve(__dirname, '../mobile/public/images/favicon.ico')
  ))
  app.use('/(.well-known/)?apple-app-site-association', siteAssociation)

  // Redirect requests before they even have to deal with Force routing
  app.use(downcase)
  app.use(hardcodedRedirects)

  // General helpers and express middleware
  app.use(bucketAssets())
  app.use(flash())
  app.use(flashMiddleware)
  app.use(localsMiddleware)
  app.use(artsyError.helpers)
  app.use(sameOriginMiddleware)
  app.use(escapedFragmentMiddleware)
  app.use(NODE_ENV === 'development' ? logger('dev') : logger(logFormat))
  app.use(unsupportedBrowserCheck)
  app.use(splitTestMiddleware)

  // Direct mobile devices to the mobile app, otherwise fall through to
  // the desktop app
  app.use((req, res, next) => {
    if (res.locals.sd.IS_MOBILE) require('../mobile')(req, res, next)
    else next()
  })
  app.use(require('../desktop'))

  // Routes for pinging system time and up
  app.get('/system/time', (req, res) => res.send(200, { time: Date.now() }))
  app.get('/system/up', (req, res) => res.send(200, { nodejs: true }))

  // Ensure CurrentUser is set for Artsy Passport
  // TODO: Investigate race condition b/t reaction-force's use of AP
  artsyPassport.options.CurrentUser = CurrentUser

  // Error handler at the end
  const dir = path.resolve(__dirname, '../desktop/components/error_handler')
  app.set('view engine', 'jade')
  app.set('views', dir)
  app.use((err, req, res, next) => {
    if (req.timedout) {
      timeoutDebug(`Error from: ${req.url}`)
      timeoutDebug(err)
    }
    res.status(err.status || 500).render('index')
  })
}
<commit_msg>Added 404 handler in-app<commit_after>//
// Sets up intial project settings, middleware, mounted apps, and
// global configuration such as overriding Backbone.sync and
// populating sharify data
//
import _ from 'underscore'
import artsyError from 'artsy-error-handler'
import artsyPassport from 'artsy-passport'
import artsyXapp from 'artsy-xapp'
import Backbone from 'backbone'
import bodyParser from 'body-parser'
import bucketAssets from 'bucket-assets'
import cookieParser from 'cookie-parser'
import express from 'express'
import favicon from 'serve-favicon'
import flash from 'connect-flash'
import glob from 'glob'
import helmet from 'helmet'
import logger from 'morgan'
import path from 'path'
import session from 'cookie-session'
import sharify from 'sharify'
import siteAssociation from 'artsy-eigen-web-association'
import superSync from 'backbone-super-sync'
import { IpFilter as ipfilter } from 'express-ipfilter'
import timeout from 'connect-timeout'
import './setup_sharify'
import cache from './cache'
import downcase from './middleware/downcase'
import ensureSSL from './middleware/ensure_ssl'
import escapedFragmentMiddleware from './middleware/escaped_fragment'
import flashMiddleware from './middleware/flash'
import hardcodedRedirects from './middleware/hardcoded_redirects'
import hstsMiddleware from './middleware/hsts'
import localsMiddleware from './middleware/locals'
import proxyGravity from './middleware/proxy_to_gravity'
import proxyReflection from './middleware/proxy_to_reflection'
import sameOriginMiddleware from './middleware/same_origin'
import unsupportedBrowserCheck from './middleware/unsupported_browser'
import logFormat from './logger_format'
import CurrentUser from './current_user'
import splitTestMiddleware from '../desktop/components/split_test/middleware'
import config from '../config'
import debug from 'debug'

const {
  API_REQUEST_TIMEOUT,
  API_URL,
  COOKIE_DOMAIN,
  DEFAULT_CACHE_TIME,
  IP_BLACKLIST,
  NODE_ENV,
  OPENREDIS_URL,
  REQUEST_EXPIRE_MS,
  REQUEST_LIMIT,
  SESSION_COOKIE_KEY,
  SESSION_COOKIE_MAX_AGE,
  SESSION_SECRET,
  APP_TIMEOUT
} = config
const timeoutDebug = debug('timeout')

export default function (app) {
  // Timeout middleware
  if (NODE_ENV === 'production') app.use(timeout(APP_TIMEOUT || '29s'))

  // Blacklist IPs
  app.use(ipfilter(IP_BLACKLIST.split(','), { log: false, mode: 'deny' }))

  // Rate limiting
  if (OPENREDIS_URL && cache.client) {
    const limiter = require('express-limiter')(app, cache.client)
    limiter({
      path: '*',
      method: 'all',
      lookup: ['headers.x-forwarded-for'],
      total: REQUEST_LIMIT,
      expire: REQUEST_EXPIRE_MS,
      onRateLimited (req, res, next) {
        console.log('Rate limit exceeded for', req.headers['x-forwarded-for'])
        return next()
      }
    })
  }

  // Blank page used by Eigen for caching web views.
  // See: https://github.com/artsy/microgravity-private/pull/1138
  app.use(require('../desktop/apps/blank'))

  // Make sure we're using SSL and prevent clickjacking
  app.use(ensureSSL)
  app.use(hstsMiddleware)
  if (!NODE_ENV === 'test') app.use(helmet.frameguard())

  // Override Backbone to use server-side sync, inject the XAPP token,
  // add redis caching, and timeout for slow responses.
  superSync.timeout = API_REQUEST_TIMEOUT
  superSync.cacheClient = cache.client
  superSync.defaultCacheTime = DEFAULT_CACHE_TIME
  Backbone.sync = function (method, model, options) {
    if (options.headers == null) { options.headers = {} }
    options.headers['X-XAPP-TOKEN'] = artsyXapp.token || ''
    return superSync(method, model, options)
      .catch((err) => {
        timeoutDebug('BACKBONE SYNC ERROR')
        timeoutDebug(_.result(model.url) || options.url)
        timeoutDebug(JSON.stringify(options))
        timeoutDebug(err)
        timeoutDebug(err.stack)
        throw err
      })
  }

  // Inject sharify data before any app code
  app.use(sharify)

  // Cookie and session middleware
  app.use(cookieParser())
  app.set('trust proxy', true)
  app.use(session({
    secret: SESSION_SECRET,
    domain: COOKIE_DOMAIN,
    name: SESSION_COOKIE_KEY,
    maxAge: SESSION_COOKIE_MAX_AGE,
    secure: (NODE_ENV === 'production') || (NODE_ENV === 'staging')
  }))

  // Body parser has to be after proxy middleware for
  // node-http-proxy to work with POST/PUT/DELETE
  app.use('/api', proxyGravity.api)
  app.use(proxyReflection)
  app.use(bodyParser.json())
  app.use(bodyParser.urlencoded({ extended: true }))

  // Passport middleware for authentication.
  app.use(artsyPassport(_.extend({}, config, {
    CurrentUser: CurrentUser,
    ARTSY_URL: API_URL,
    userKeys: [
      'collector_level',
      'default_profile_id',
      'email',
      'has_partner_access',
      'id',
      'lab_features',
      'name',
      'paddle_number',
      'phone',
      'type'
    ]
  })))

  // Static assets
  if (NODE_ENV === 'development' || NODE_ENV === 'test') {
    app.use(require('stylus').middleware({
      src: path.resolve(__dirname, '../desktop'),
      dest: path.resolve(__dirname, '../desktop/public')
    }))
    app.use(require('browserify-dev-middleware')({
      src: path.resolve(__dirname, '../desktop'),
      transforms: [
        require('babelify'),
        require('caching-coffeeify'),
        require('jadeify')
      ],
      insertGlobals: true,
      debug: true
    }))
    app.use(require('stylus').middleware({
      src: path.resolve(__dirname, '../mobile'),
      dest: path.resolve(__dirname, '../mobile/public')
    }))
    app.use(require('browserify-dev-middleware')({
      src: path.resolve(__dirname, '../mobile'),
      transforms: [
        require('babelify'),
        require('caching-coffeeify'),
        require('jadeify')
      ],
      insertGlobals: true,
      debug: true
    }))
  }
  glob.sync(path.resolve(__dirname, '../') + '/desktop/**/public/')
    .concat(glob.sync(path.resolve(__dirname, '../') + '/mobile/**/public/'))
    .forEach((fld) => app.use(express.static(fld)))
  app.use(favicon(
    path.resolve(__dirname, '../mobile/public/images/favicon.ico')
  ))
  app.use('/(.well-known/)?apple-app-site-association', siteAssociation)

  // Redirect requests before they even have to deal with Force routing
  app.use(downcase)
  app.use(hardcodedRedirects)

  // General helpers and express middleware
  app.use(bucketAssets())
  app.use(flash())
  app.use(flashMiddleware)
  app.use(localsMiddleware)
  app.use(artsyError.helpers)
  app.use(sameOriginMiddleware)
  app.use(escapedFragmentMiddleware)
  app.use(NODE_ENV === 'development' ? logger('dev') : logger(logFormat))
  app.use(unsupportedBrowserCheck)
  app.use(splitTestMiddleware)

  // Direct mobile devices to the mobile app, otherwise fall through to
  // the desktop app
  app.use((req, res, next) => {
    if (res.locals.sd.IS_MOBILE) require('../mobile')(req, res, next)
    else next()
  })
  app.use(require('../desktop'))

  // Routes for pinging system time and up
  app.get('/system/time', (req, res) => res.send(200, { time: Date.now() }))
  app.get('/system/up', (req, res) => res.send(200, { nodejs: true }))

  // Ensure CurrentUser is set for Artsy Passport
  // TODO: Investigate race condition b/t reaction-force's use of AP
  artsyPassport.options.CurrentUser = CurrentUser

  // 404 handler
  app.get('*', (req, res, next) => {
    const err = new Error()
    err.status = 404
    err.message = 'Not Found'
    next(err)
  })

  // Error handler at the end
  const dir = path.resolve(__dirname, '../desktop/components/error_handler')
  app.set('view engine', 'jade')
  app.set('views', dir)
  app.use((err, req, res, next) => {
    if (req.timedout) {
      timeoutDebug(`Error from: ${req.url}`)
      timeoutDebug(err)
    }
    const code = err.status || 500
    const message = err.message || err.text || err.toString()
    res.status(code).render('index', { message, stack: err.stack, code })
  })
}
<|endoftext|>