<commit_before>/**************************************************************************************
 nexl-server
 Copyright (c) 2016 Yevgeny Sergeyev
 License : Apache 2.0

 JavaScript based read only configuration data storage where every single data element is a javascript variable
 **************************************************************************************/

(function () {
    var CMD_LINE_OPTS_DEF = [
        {name: 'nexl-source', alias: "s", desc: "Points to nexl sources directory"},
        {name: 'debug', alias: "d", type: Boolean, defaultValue: false, desc: "Print debug message to console"},
        {name: 'port', alias: "p", type: Number, defaultValue: 8080, desc: "nexl server port"},
        {name: 'binding', alias: "b", type: String, defaultValue: 'localhost', desc: "nexl server binging"},
        {name: 'help', alias: "h", type: Boolean, defaultValue: false, desc: "display this help"}
    ];

    // includes
    var nexlEngine = require('nexl-engine');
    var path = require('path');
    var bodyParser = require('body-parser');
    var util = require('util');
    var express = require('express');
    var app = express();
    var osHomeDir = require('os-homedir');
    var favicon = require('serve-favicon');
    var figlet = require('figlet');
    var commandLineArgs = require('command-line-args');
    var chalk = require('chalk');
    var fs = require('fs');

    var NEXL_SOURCES_ROOT;
    var FILE_SEPARATOR = path.sep;

    var NEXL_REST_URL = '/nexl-rest';
    var REST_LIST_SOURCES = NEXL_REST_URL + '/list-nexl-sources';
    var REST_LIST_JS_VARIABLES = NEXL_REST_URL + '/list-js-variables';

    var HR = Array(55).join('-');

    var server;
    var cmdLineOpts;

    // C:\Program Files\nodejs\node.exe => C:\Program Files\nodejs
    function removeLastPathElement(path) {
        return path.replace(/[\\\/][^\\\/]*$/g, "");
    }

    function fixSlashes(path) {
        return path.replace(/[\\\/]/g, FILE_SEPARATOR);
    }

    function isString(obj) {
        return Object.prototype.toString.call(obj) === "[object String]";
    }

    function isArray(obj) {
        return Object.prototype.toString.call(obj) === "[object Array]";
    }

    function isObject(obj) {
        return Object.prototype.toString.call(obj) === "[object Object]";
    }

    function wrapWithArrayIfNeeded(obj) {
        return isArray(obj) ? obj : [obj];
    }

    function assemblePath() {
        // converting arguments to array
        var args = Array.prototype.slice.call(arguments);

        // joining array's elements
        var path = args.join(FILE_SEPARATOR);

        // removing double/triple/... slash characters
        return path.replace(/\/{2,}/, "/").replace(/\\{2,}/, "\\");
    }

    function printDebug(msg) {
        if (cmdLineOpts.debug) {
            console.log(util.format("DEBUG [%s] %s", new Date(), msg));
        }
    }

    function makeJsonPResult(fieldName, input, preResult) {
        // wrapping with object
        var result = {};
        result[fieldName] = preResult;

        // stringifying
        result = JSON.stringify(result);

        // wrapping with jsonP callback
        return util.format('%s ( %s )', input.callback, result);
    }

    function sendResult(preResult, input, res) {
        var data;

        // is jsonp ?
        if (isJsonP(input)) {
            data = makeJsonPResult('data', input, preResult);
        } else {
            data = preResult.join("\n");
        }

        res.write(data);
    }

    function isJsonP(input) {
        return input.callback !== undefined;
    }

    function sendException(err, input, res) {
        err = err.toString();

        if (isJsonP(input)) {
            err = makeJsonPResult('error', input, err);
        } else {
            res.status(500);
        }

        res.write(err);
    }

    function evalNexlExpressionWrapper(input, res) {
        var nexlSource = {
            asFile: {
                fileName: input.script2Exec
            }
        };

        var expression = input.expression;
        var args = input.args;

        // evaluating
        try {
            var result = nexlEngine.evalNexlExpression(nexlSource, expression, args);
            sendResult(result, input, res);
        } catch (e) {
            sendException(e, input, res);
        }

        // ending http session
        res.end();
    }

    function throwError(e, res) {
        res.status(500);
        res.write(e.toString());
        res.end();
        throw e;
    }

    function evalNexlExpression(input, res) {
        // calculating absolute path for script
        var scriptPath = fixSlashes(input.url);
        scriptPath = assemblePath(NEXL_SOURCES_ROOT, scriptPath);

        input.script2Exec = scriptPath;
        evalNexlExpressionWrapper(input, res);
    }

    function retrieveHttpSource(req, res) {
        if (req.method.toUpperCase() == "GET") {
            return req.query;
        }

        if (req.method.toUpperCase() == "POST") {
            return req.body;
        }

        throwError("Unsupported HTTP-method = [" + req.method + "]", res);
    }

    function prepareRequestAndValidate(req, res) {
        var httpSource = retrieveHttpSource(req, res);

        // removing leading ? character and leading slash character
        var url = req.url.replace(/\?.*/g, '').replace(/^[\/\\]/g, '');
        url = decodeURI(url);

        var input = {
            url: url,
            expression: httpSource.expression,
            args: httpSource,
            callback: httpSource.callback
        };

        validatePath(input.url);

        delete httpSource["expression"];
        delete httpSource["callback"];

        // validating expression
        if (!input.expression) {
            throw util.format("You trying to evaluate the following : [%s], method : [%s]. But you didn't provide a nexl expression", input.url, req.method);
        }

        return input;
    }

    function handlePostRequests(req, res) {
        var input = prepareRequestAndValidate(req, res);

        printDebug(util.format("POST request is accepted. url=[%s], clientHost = [%s]", req.url, req.connection.remoteAddress));

        evalNexlExpression(input, res);
    }

    // produces json object with deep structure. x.y.z will be treated as 3 nested objects
    function produceJson(key, val, result) {
        // KEY can contain dots and going to be splitted by dots
        var values = key.split('.');

        var obj = result;

        // iterating over spliited elements in key and putting every element into json object
        for (var i = 0; i < values.length - 1; i++) {
            var subValue = values[i];

            // is sub element doesn't exist in json object, create it
            if (!obj[subValue]) {
                obj[subValue] = {};
            }

            // diving deeper
            obj = obj[subValue];
        }

        obj[values[values.length - 1]] = val;
    }

    function handleRootPage(req, res) {
        figlet.defaults({fontPath: "assets/fonts"});

        figlet("Welcome to nexl-server", function (err, data) {
            if (err) {
                throw 'Something went wrong...' + err
            }

            res.status(200);
            res.write(data);
            res.end();
        });

    }

    function handleGetRequests(req, res) {
        // is root url ?
        if (req.url == "/") {
            handleRootPage(req, res);
            return;
        }

        var input = prepareRequestAndValidate(req, res);

        printDebug(util.format("GET request is accepted. url=[%s], clientHost = [%s]", req.url, req.connection.remoteAddress));

        // not a root url, handling script
        evalNexlExpression(input, res);
    }

    function use(req, res) {
        res.status(404);
        res.write("Bad request");
        res.end();
    }

    function errorHandler(req, res, next) {
        use(req, res);
    }

    function printHelp() {
        var maxLen = 0;
        // discovering length of longest option
        for (var item in CMD_LINE_OPTS_DEF) {
            var item = CMD_LINE_OPTS_DEF[item];
            maxLen = Math.max(maxLen, item.name.length);
        }

        // printing
        for (var item in CMD_LINE_OPTS_DEF) {
            var item = CMD_LINE_OPTS_DEF[item];
            var spaces = maxLen - item.name.length + 1;
            spaces = Array(spaces).join(' ');
            var text = util.format("-%s, --%s %s %s", item.alias, item.name, spaces, item.desc);
            console.log(text);
        }
    }

    function handleArgs() {
        try {
            cmdLineOpts = commandLineArgs(CMD_LINE_OPTS_DEF);
        } catch (e) {
            console.log('You have specified wrong command line option(s)');
            printHelp();
            throw e;
        }

        // is help ?
        if (cmdLineOpts.help) {
            printHelp();
            process.exit();
        }

        console.log('Type the following to view all command line switches :\n\tnpm start -- --help');

        // handling nexl-sources directory
        NEXL_SOURCES_ROOT = cmdLineOpts['nexl-source'];
        if (!NEXL_SOURCES_ROOT) {
            NEXL_SOURCES_ROOT = path.join(osHomeDir(), 'nexl-sources');
            var msg = util.format("\nWarning ! nexl sources root directory is not provided, using default directory for nexl sources : [%s]\n", NEXL_SOURCES_ROOT);
            console.log(chalk.yellow.bold(msg));
        }

        printDebug(util.format("nexl sources directory is [%s]", NEXL_SOURCES_ROOT));
        fs.exists(NEXL_SOURCES_ROOT, function (result) {
            if (!result) {
                console.log(chalk.red.bold(util.format("nexl sources root directory [%s] doesn't exist ! But you can still create it without server restart", NEXL_SOURCES_ROOT)));
            }
        });
    }

    function enumerateFiles(dir, collection) {
        var fileItems = [];
        var dirItems = [];

        var items = fs.readdirSync(dir);
        for (var key in  items) {
            var item = items[key];
            var subPath = path.join(dir, item);
            var stat = fs.statSync(subPath);

            if (stat.isDirectory()) {
                var dirItem = {};
                dirItem.text = item;
                dirItem.type = 'dir';
                dirItem.children = enumerateFiles(subPath);
                dirItems.push(dirItem);
            }

            if (stat.isFile()) {
                var fileItem = {};
                fileItem.text = item;
                fileItem.type = 'file';
                fileItem.icon = "jstree-file";
                fileItems.push(fileItem);
            }
        }

        return dirItems.concat(fileItems);
    }

    // handles special rest requests of nexl-server
    function listNexlSources(req, res) {
        var input = retrieveHttpSource(req, res);

        try {
            var result = enumerateFiles(NEXL_SOURCES_ROOT);
        } catch (e) {
            sendException(e, input, res);
            res.end();
            return;
        }

        // is jsonp ?
        if (isJsonP(input)) {
            result = makeJsonPResult('data', input, result);
            res.write(result);
        } else {
            res.json(result);
        }

        res.end();
    }

    function validatePath(scriptPath) {
        if (path.isAbsolute(scriptPath)) {
            throw util.format('The [%s] path is unacceptable', scriptPath);
        }

        if (!scriptPath.match(/^[a-zA-Z_0-9]/)) {
            throw util.format('The [%s] path is unacceptable', scriptPath);
        }
    }

    function listJsVariables(req, res) {
        var input = retrieveHttpSource(req, res);
        var scriptPath = input.nexlSource;

        if (!scriptPath || scriptPath.length < 1) {
            throw "nexl source is not provided";
        }

        scriptPath = scriptPath.replace(/^[\/\\]/g, '');

        // validating nexl source path
        validatePath(scriptPath);

        scriptPath = fixSlashes(scriptPath);
        scriptPath = assemblePath(NEXL_SOURCES_ROOT, scriptPath);

        var nexlSource = {
            asFile: {
                fileName: scriptPath
            }
        };

        try {
            var result = nexlEngine.resolveJsVariables(nexlSource);
            sendResult(result, input, res);
        } catch (e) {
            sendException(e, input, res);
        }

        // ending http session
        res.end();
    }

    function handleManagementRestRequests(req, res) {
        var url = req.url;

        switch (url.replace(/\?.*/, '')) {
            case REST_LIST_SOURCES:
                listNexlSources(req, res);
                return;

            case REST_LIST_JS_VARIABLES:
                listJsVariables(req, res);
                return;
        }

        res.status(500);
        res.write('Unknown rest service = ' + url);
        res.end();
    }

    function applyBinders() {
        // favicon
        app.use(favicon(path.join(__dirname, 'favicon.ico')));

        // apply body parser ( for POST requests )
        app.use(bodyParser.json());
        app.use(bodyParser.urlencoded({
            extended: true
        }));

        // server's rest
        app.post(NEXL_REST_URL + '/*', handleManagementRestRequests);
        app.get(NEXL_REST_URL + '/*', handleManagementRestRequests);

        // GET-request handler
        app.get("/*", handleGetRequests);

        // POST-request handler
        app.post("/*", handlePostRequests);

        // error handler
        app.use(errorHandler);
    }

    function printCurrentVersion() {
        try {
            console.log(util.format('nexl-server version is [%s]\n', require('./package.json').version));
        } catch (e) {
            console.log("It's not fatal but failed to print a nexl-server version. Please open me a bug. Exception : " + e);
        }
    }

    function printStartupMessage() {
        printCurrentVersion();

        figlet.defaults({fontPath: "assets/fonts"});

        console.log(HR);
        figlet("nexl-server", function (err, data) {
            if (err) {
                throw 'Something went wrong...' + err
            }

            console.log(data);
            console.log(HR);
            var port = server.address().port;
            var address = server.address().address;
            console.log(chalk.green(util.format("\nNEXL server is up and listening at [%s:%s]", address, port)));
        });
    }

    function createHttpServer() {
        // creating http-server
        server = app.listen(cmdLineOpts.port, cmdLineOpts.binding, function () {
            printStartupMessage();
        });
    }

    function start() {
        applyBinders();
        handleArgs();
        createHttpServer();
    }

    module.exports = start;
}());<commit_msg>- grammar fix;<commit_after>/**************************************************************************************
 nexl-server
 Copyright (c) 2016 Yevgeny Sergeyev
 License : Apache 2.0

 JavaScript based read only configuration data storage where every single data element is a javascript variable
 **************************************************************************************/

(function () {
    var CMD_LINE_OPTS_DEF = [
        {name: 'nexl-source', alias: "s", desc: "Points to nexl sources directory"},
        {name: 'debug', alias: "d", type: Boolean, defaultValue: false, desc: "Print debug message to console"},
        {name: 'port', alias: "p", type: Number, defaultValue: 8080, desc: "nexl server port"},
        {name: 'binding', alias: "b", type: String, defaultValue: 'localhost', desc: "nexl server binging"},
        {name: 'help', alias: "h", type: Boolean, defaultValue: false, desc: "display this help"}
    ];

    // includes
    var nexlEngine = require('nexl-engine');
    var path = require('path');
    var bodyParser = require('body-parser');
    var util = require('util');
    var express = require('express');
    var app = express();
    var osHomeDir = require('os-homedir');
    var favicon = require('serve-favicon');
    var figlet = require('figlet');
    var commandLineArgs = require('command-line-args');
    var chalk = require('chalk');
    var fs = require('fs');

    var NEXL_SOURCES_ROOT;
    var FILE_SEPARATOR = path.sep;

    var NEXL_REST_URL = '/nexl-rest';
    var REST_LIST_SOURCES = NEXL_REST_URL + '/list-nexl-sources';
    var REST_LIST_JS_VARIABLES = NEXL_REST_URL + '/list-js-variables';

    var HR = Array(55).join('-');

    var server;
    var cmdLineOpts;

    // C:\Program Files\nodejs\node.exe => C:\Program Files\nodejs
    function removeLastPathElement(path) {
        return path.replace(/[\\\/][^\\\/]*$/g, "");
    }

    function fixSlashes(path) {
        return path.replace(/[\\\/]/g, FILE_SEPARATOR);
    }

    function isString(obj) {
        return Object.prototype.toString.call(obj) === "[object String]";
    }

    function isArray(obj) {
        return Object.prototype.toString.call(obj) === "[object Array]";
    }

    function isObject(obj) {
        return Object.prototype.toString.call(obj) === "[object Object]";
    }

    function wrapWithArrayIfNeeded(obj) {
        return isArray(obj) ? obj : [obj];
    }

    function assemblePath() {
        // converting arguments to array
        var args = Array.prototype.slice.call(arguments);

        // joining array's elements
        var path = args.join(FILE_SEPARATOR);

        // removing double/triple/... slash characters
        return path.replace(/\/{2,}/, "/").replace(/\\{2,}/, "\\");
    }

    function printDebug(msg) {
        if (cmdLineOpts.debug) {
            console.log(util.format("DEBUG [%s] %s", new Date(), msg));
        }
    }

    function makeJsonPResult(fieldName, input, preResult) {
        // wrapping with object
        var result = {};
        result[fieldName] = preResult;

        // stringifying
        result = JSON.stringify(result);

        // wrapping with jsonP callback
        return util.format('%s ( %s )', input.callback, result);
    }

    function sendResult(preResult, input, res) {
        var data;

        // is jsonp ?
        if (isJsonP(input)) {
            data = makeJsonPResult('data', input, preResult);
        } else {
            data = preResult.join("\n");
        }

        res.write(data);
    }

    function isJsonP(input) {
        return input.callback !== undefined;
    }

    function sendException(err, input, res) {
        err = err.toString();

        if (isJsonP(input)) {
            err = makeJsonPResult('error', input, err);
        } else {
            res.status(500);
        }

        res.write(err);
    }

    function evalNexlExpressionWrapper(input, res) {
        var nexlSource = {
            asFile: {
                fileName: input.script2Exec
            }
        };

        var expression = input.expression;
        var args = input.args;

        // evaluating
        try {
            var result = nexlEngine.evalNexlExpression(nexlSource, expression, args);
            sendResult(result, input, res);
        } catch (e) {
            sendException(e, input, res);
        }

        // ending http session
        res.end();
    }

    function throwError(e, res) {
        res.status(500);
        res.write(e.toString());
        res.end();
        throw e;
    }

    function evalNexlExpression(input, res) {
        // calculating absolute path for script
        var scriptPath = fixSlashes(input.url);
        scriptPath = assemblePath(NEXL_SOURCES_ROOT, scriptPath);

        input.script2Exec = scriptPath;
        evalNexlExpressionWrapper(input, res);
    }

    function retrieveHttpSource(req, res) {
        if (req.method.toUpperCase() == "GET") {
            return req.query;
        }

        if (req.method.toUpperCase() == "POST") {
            return req.body;
        }

        throwError("Unsupported HTTP-method = [" + req.method + "]", res);
    }

    function prepareRequestAndValidate(req, res) {
        var httpSource = retrieveHttpSource(req, res);

        // removing leading ? character and leading slash character
        var url = req.url.replace(/\?.*/g, '').replace(/^[\/\\]/g, '');
        url = decodeURI(url);

        var input = {
            url: url,
            expression: httpSource.expression,
            args: httpSource,
            callback: httpSource.callback
        };

        validatePath(input.url);

        delete httpSource["expression"];
        delete httpSource["callback"];

        // validating expression
        if (!input.expression) {
            throw util.format("You are trying to evaluate the following : [%s], method : [%s]. But you didn't provide a nexl expression", input.url, req.method);
        }

        return input;
    }

    function handlePostRequests(req, res) {
        var input = prepareRequestAndValidate(req, res);

        printDebug(util.format("POST request is accepted. url=[%s], clientHost = [%s]", req.url, req.connection.remoteAddress));

        evalNexlExpression(input, res);
    }

    // produces json object with deep structure. x.y.z will be treated as 3 nested objects
    function produceJson(key, val, result) {
        // KEY can contain dots and going to be splitted by dots
        var values = key.split('.');

        var obj = result;

        // iterating over spliited elements in key and putting every element into json object
        for (var i = 0; i < values.length - 1; i++) {
            var subValue = values[i];

            // is sub element doesn't exist in json object, create it
            if (!obj[subValue]) {
                obj[subValue] = {};
            }

            // diving deeper
            obj = obj[subValue];
        }

        obj[values[values.length - 1]] = val;
    }

    function handleRootPage(req, res) {
        figlet.defaults({fontPath: "assets/fonts"});

        figlet("Welcome to nexl-server", function (err, data) {
            if (err) {
                throw 'Something went wrong...' + err
            }

            res.status(200);
            res.write(data);
            res.end();
        });

    }

    function handleGetRequests(req, res) {
        // is root url ?
        if (req.url == "/") {
            handleRootPage(req, res);
            return;
        }

        var input = prepareRequestAndValidate(req, res);

        printDebug(util.format("GET request is accepted. url=[%s], clientHost = [%s]", req.url, req.connection.remoteAddress));

        // not a root url, handling script
        evalNexlExpression(input, res);
    }

    function use(req, res) {
        res.status(404);
        res.write("Bad request");
        res.end();
    }

    function errorHandler(req, res, next) {
        use(req, res);
    }

    function printHelp() {
        var maxLen = 0;
        // discovering length of longest option
        for (var item in CMD_LINE_OPTS_DEF) {
            var item = CMD_LINE_OPTS_DEF[item];
            maxLen = Math.max(maxLen, item.name.length);
        }

        // printing
        for (var item in CMD_LINE_OPTS_DEF) {
            var item = CMD_LINE_OPTS_DEF[item];
            var spaces = maxLen - item.name.length + 1;
            spaces = Array(spaces).join(' ');
            var text = util.format("-%s, --%s %s %s", item.alias, item.name, spaces, item.desc);
            console.log(text);
        }
    }

    function handleArgs() {
        try {
            cmdLineOpts = commandLineArgs(CMD_LINE_OPTS_DEF);
        } catch (e) {
            console.log('You have specified wrong command line option(s)');
            printHelp();
            throw e;
        }

        // is help ?
        if (cmdLineOpts.help) {
            printHelp();
            process.exit();
        }

        console.log('Type the following to view all command line switches :\n\tnpm start -- --help');

        // handling nexl-sources directory
        NEXL_SOURCES_ROOT = cmdLineOpts['nexl-source'];
        if (!NEXL_SOURCES_ROOT) {
            NEXL_SOURCES_ROOT = path.join(osHomeDir(), 'nexl-sources');
            var msg = util.format("\nWarning ! nexl sources root directory is not provided, using default directory for nexl sources : [%s]\n", NEXL_SOURCES_ROOT);
            console.log(chalk.yellow.bold(msg));
        }

        printDebug(util.format("nexl sources directory is [%s]", NEXL_SOURCES_ROOT));
        fs.exists(NEXL_SOURCES_ROOT, function (result) {
            if (!result) {
                console.log(chalk.red.bold(util.format("nexl sources root directory [%s] doesn't exist ! But you can still create it without server restart", NEXL_SOURCES_ROOT)));
            }
        });
    }

    function enumerateFiles(dir, collection) {
        var fileItems = [];
        var dirItems = [];

        var items = fs.readdirSync(dir);
        for (var key in  items) {
            var item = items[key];
            var subPath = path.join(dir, item);
            var stat = fs.statSync(subPath);

            if (stat.isDirectory()) {
                var dirItem = {};
                dirItem.text = item;
                dirItem.type = 'dir';
                dirItem.children = enumerateFiles(subPath);
                dirItems.push(dirItem);
            }

            if (stat.isFile()) {
                var fileItem = {};
                fileItem.text = item;
                fileItem.type = 'file';
                fileItem.icon = "jstree-file";
                fileItems.push(fileItem);
            }
        }

        return dirItems.concat(fileItems);
    }

    // handles special rest requests of nexl-server
    function listNexlSources(req, res) {
        var input = retrieveHttpSource(req, res);

        try {
            var result = enumerateFiles(NEXL_SOURCES_ROOT);
        } catch (e) {
            sendException(e, input, res);
            res.end();
            return;
        }

        // is jsonp ?
        if (isJsonP(input)) {
            result = makeJsonPResult('data', input, result);
            res.write(result);
        } else {
            res.json(result);
        }

        res.end();
    }

    function validatePath(scriptPath) {
        if (path.isAbsolute(scriptPath)) {
            throw util.format('The [%s] path is unacceptable', scriptPath);
        }

        if (!scriptPath.match(/^[a-zA-Z_0-9]/)) {
            throw util.format('The [%s] path is unacceptable', scriptPath);
        }
    }

    function listJsVariables(req, res) {
        var input = retrieveHttpSource(req, res);
        var scriptPath = input.nexlSource;

        if (!scriptPath || scriptPath.length < 1) {
            throw "nexl source is not provided";
        }

        scriptPath = scriptPath.replace(/^[\/\\]/g, '');

        // validating nexl source path
        validatePath(scriptPath);

        scriptPath = fixSlashes(scriptPath);
        scriptPath = assemblePath(NEXL_SOURCES_ROOT, scriptPath);

        var nexlSource = {
            asFile: {
                fileName: scriptPath
            }
        };

        try {
            var result = nexlEngine.resolveJsVariables(nexlSource);
            sendResult(result, input, res);
        } catch (e) {
            sendException(e, input, res);
        }

        // ending http session
        res.end();
    }

    function handleManagementRestRequests(req, res) {
        var url = req.url;

        switch (url.replace(/\?.*/, '')) {
            case REST_LIST_SOURCES:
                listNexlSources(req, res);
                return;

            case REST_LIST_JS_VARIABLES:
                listJsVariables(req, res);
                return;
        }

        res.status(500);
        res.write('Unknown rest service = ' + url);
        res.end();
    }

    function applyBinders() {
        // favicon
        app.use(favicon(path.join(__dirname, 'favicon.ico')));

        // apply body parser ( for POST requests )
        app.use(bodyParser.json());
        app.use(bodyParser.urlencoded({
            extended: true
        }));

        // server's rest
        app.post(NEXL_REST_URL + '/*', handleManagementRestRequests);
        app.get(NEXL_REST_URL + '/*', handleManagementRestRequests);

        // GET-request handler
        app.get("/*", handleGetRequests);

        // POST-request handler
        app.post("/*", handlePostRequests);

        // error handler
        app.use(errorHandler);
    }

    function printCurrentVersion() {
        try {
            console.log(util.format('nexl-server version is [%s]\n', require('./package.json').version));
        } catch (e) {
            console.log("It's not fatal but failed to print a nexl-server version. Please open me a bug. Exception : " + e);
        }
    }

    function printStartupMessage() {
        printCurrentVersion();

        figlet.defaults({fontPath: "assets/fonts"});

        console.log(HR);
        figlet("nexl-server", function (err, data) {
            if (err) {
                throw 'Something went wrong...' + err
            }

            console.log(data);
            console.log(HR);
            var port = server.address().port;
            var address = server.address().address;
            console.log(chalk.green(util.format("\nNEXL server is up and listening at [%s:%s]", address, port)));
        });
    }

    function createHttpServer() {
        // creating http-server
        server = app.listen(cmdLineOpts.port, cmdLineOpts.binding, function () {
            printStartupMessage();
        });
    }

    function start() {
        applyBinders();
        handleArgs();
        createHttpServer();
    }

    module.exports = start;
}());<|endoftext|>


<commit_before>angular.module('player',[])
.config(function($interpolateProvider){
    $interpolateProvider.startSymbol('[[');
    $interpolateProvider.endSymbol(']]');
})
.controller('songlist',function($scope, $http){
    $scope.state = 'gettingdata'
    $http.get('/book/songs/').success(function(response){
        $scope.list = {};
        $scope.snippets = {}
        $scope.state = ["Getting", "Getting"]
        angular.forEach(response,function(song){
            $scope.state = [" Edit", "Cancel"];
            $scope.list[song.id]=song;
            angular.forEach($scope.list[song.id].snippets, function(snip){
                snip.editing = false;
            });
        });
        $scope.snippets = $scope.list[1].snippets;
        $scope.editing = false;
        $scope.player = videojs('playerbox',{"preload":"auto", "controls":true, "autoplay":true});
    }).error(function(error){
        console.log('Errored!!!!\n'+error);
    });
    $scope.loadSong = function(song_id){
        $scope.snippets = $scope.list[song_id].snippets;
        $scope.editing = false;
        $scope.state = [" Edit", "Delete"];
        song=$scope.list[song_id];
        console.log($scope.player.src({src:"/static/"+song.path, type:"video/"+song.type}));
        $scope.player.play();
    };
    $scope.editSnip = function(snip_id){
        angular.forEach($scope.snippets, function(snip){
            snip.editing = snip.id == snip_id ? ! snip.editing : snip.editing;
        });
        $scope.editing = ! $scope.editing;
        $scope.state = $scope.editing ? ["Undo","Save"] : [" Edit", "Delete"];
    };
    $scope.saveSnip = function(snip_id){
        $scope.snippets[snip_id].start = $scope.currentStart;
        $scope.snippets[snip_id].end = $scope.currentEnd;
        $scope.editSnip(snip_id);
    };
    $scope.getCurrent = function(){
        return 99;
    }
})
<commit_msg>Get current time from player and save it.<commit_after>angular.module('player',[])
.config(function($interpolateProvider){
    $interpolateProvider.startSymbol('[[');
    $interpolateProvider.endSymbol(']]');
})
.controller('songlist',function($scope, $http){
    $scope.state = 'gettingdata'
    $http.get('/book/songs/').success(function(response){
        $scope.list = {};
        $scope.snippets = {}
        $scope.state = ["Getting", "Getting"]
        angular.forEach(response,function(song){
            $scope.state = [" Edit", "Cancel"];
            $scope.list[song.id]=song;
            angular.forEach($scope.list[song.id].snippets, function(snip){
                snip.editing = false;
            });
        });
        $scope.snippets = $scope.list[1].snippets;
        $scope.editing = false;
        $scope.player = videojs('playerbox',{"preload":"auto", "controls":true, "autoplay":true});
    }).error(function(error){
        console.log('Errored!!!!\n'+error);
    });
    $scope.loadSong = function(song_id){
        $scope.snippets = $scope.list[song_id].snippets;
        $scope.editing = false;
        $scope.state = [" Edit", "Delete"];
        song=$scope.list[song_id];
        console.log($scope.player.src({src:"/static/"+song.path, type:"video/"+song.type}));
        $scope.player.play();
    };
    $scope.editSnip = function(snip_id){
        angular.forEach($scope.snippets, function(snip){
            snip.editing = snip.id == snip_id ? ! snip.editing : snip.editing;
        });
        $scope.editing = ! $scope.editing;
        $scope.state = $scope.editing ? ["Undo","Save"] : [" Edit", "Delete"];
    };
    $scope.saveSnip = function(snip_id){
        $scope.snippets[snip_id].start = $scope.currentStart;
        $scope.snippets[snip_id].end = $scope.currentEnd;
        $scope.editSnip(snip_id);
    };
    $scope.getCurrent = function(){
        return $scope.player.currentTime();
    }
})
<|endoftext|>


<commit_before>var React = require('react');
var Link = require('react-router').Link;
var $ = require('jquery');

var SearchForm = React.createClass({

    componentDidMount: function() {
        var estateType;
        for (var i = this.props.searchCriteria.estateType.length - 1; i >= 0; i--) {
          estateType = this.props.searchCriteria.estateType[i];
          this.refs[estateType].checked = true;
        };
        this.refs.localisation.value = this.props.searchCriteria.localisation;
        this.refs.priceMin.value = this.props.searchCriteria.price.min;
        this.refs.priceMax.value = this.props.searchCriteria.price.max;
        this.refs.roomNbrMin.value = this.props.searchCriteria.roomNbr.min;
        this.refs.roomNbrMax.value = this.props.searchCriteria.roomNbr.max;
        this.refs.squareMin.value = this.props.searchCriteria.square.min;
        this.refs.squareMax.value = this.props.searchCriteria.square.max;
        var estateState;
        for (var i = this.props.searchCriteria.estateState.length - 1; i >= 0; i--) {
          estateState = this.props.searchCriteria.estateState[i];
          this.refs[estateState].checked = true;
        };
        if (this.props.searchCriteria.garage){
          this.refs.garage.checked = this.props.searchCriteria.garage;
        }

        if (this.props.searchCriteria.terrace){
          this.refs.terrace.checked = this.props.searchCriteria.terrace;
        }
    },
    validateAmount: function(event) {
        if (isNaN(event.target.value)) {
            $("#" + event.target.name).addClass("has-error");
        } else {
            $("#" + event.target.name).removeClass("has-error");
        }
    },
    changeButton: function(event){
      event.preventDefault();
      //remonte de parent en parent en commencant par l'element lui-meme pour trouver le premier "a"
      var elem = $(event.target).closest( "a" );
        if ($("#more").hasClass("collapse")) {
            elem.html('Moins de critères ');
            $("#more").removeClass("collapse");
        } else {
          elem.html('Plus de critères ');
            $("#more").addClass("collapse");
        };
    },
    handleSaveSearch: function() {
        function cleanArray(actual) {
            var newArray = new Array();
            for (var i = 0; i < actual.length; i++) {
                if (actual[i]) {
                    newArray.push(actual[i]);
                }
            }
            return newArray;
        }
        var estateType = [this.refs.house.checked ? "house" : undefined, this.refs.appart.checked ? "appart" : undefined, this.refs.ground.checked ? "ground" : undefined, this.refs.investment.checked ? "investment" : undefined, this.refs.commercial.checked ? "commercial" : undefined];
        estateType = cleanArray(estateType);
        var localisation = this.refs.localisation.value;
        var priceMin = this.refs.priceMin.value;
        var priceMax = this.refs.priceMax.value;
        var roomNbrMin = this.refs.roomNbrMin.value;
        var roomNbrMax = this.refs.roomNbrMax.value;
        var squareMin = this.refs.squareMin.value;
        var squareMax = this.refs.squareMax.value;
        var estateState = [this.refs.prefect.checked ? "prefect" : undefined, this.refs.reNew.checked ? "reNew" : undefined, this.refs.good.checked ? "good" : undefined, this.refs.toBeRefresh.checked ? "toBeRefresh" : undefined];
        estateState = cleanArray(estateState);
        var garage = this.refs.garage.checked;
        var terrace = this.refs.terrace.checked;

        var searchCriteria = {
            estateType: estateType,
            localisation: localisation,
            price: {
                min: priceMin,
                max: priceMax
            },
            roomNbr: {
                min: roomNbrMin,
                max: roomNbrMax
            },
            square: {
                min: '',
                max: ''
            },
            estateState: estateState,
            garage: garage,
            terrace: terrace
        };

        this.props.onClickSearch(searchCriteria);
    },

    render: function() {
        return (

                        <form className="form-horizontal" role="form">

                                Type de bien:

                                        <input type="checkbox" name="house" ref="house" value="house" />Maison

                                        <input type="checkbox" name="appart" ref="appart" value="appart" />Appartemment

                                        <input type="checkbox" name="ground" ref="ground" value="ground" />Terrain à batir

                                        <input type="checkbox" name="investment" ref="investment" value="investment" />Immeuble de rapport

                                        <input type="checkbox" name="commercial" ref="commercial" value="commercial" />Surface commerciale

                                Localisation:

                                <input type="text" name="localisation" className="form-control" ref="localisation" />

                                Prix:

                                    Entre

                                        <input type="text" name="priceMin" className="form-control" ref="priceMin" onBlur={this.validateAmount} />

                                    et

                                        <input type="text" name="priceMax" className="form-control" ref="priceMax" onBlur={this.validateAmount}/>

                                Nombre de chambres:

                                    De

                                        <input type="text" name="roomNbrMin" className="form-control" ref="roomNbrMin" onBlur={this.validateAmount}/>

                                    à

                                        <input type="text" name="roomNbrMax" className="form-control" ref="roomNbrMax" onBlur={this.validateAmount}/>

                              Plus de critères

                            {/* ************************** collapse ***************************** */}

                                    Surface:

                                        Entre

                                            <input type="text" name="squareMin" className="form-control" ref="squareMin" onBlur={this.validateAmount} />

                                        et

                                            <input type="text" name="squareMax" className="form-control" ref="squareMax" onBlur={this.validateAmount}/>

                                        m²

                                    Etat du bâtiment:

                                                <input type="checkbox" name="prefect" ref="prefect" value="prefect" />Excellent état

                                                <input type="checkbox" name="reNew" ref="reNew" value="reNew" />Remis à neuf

                                                <input type="checkbox" name="good" ref="good" value="good" />Bon état

                                                <input type="checkbox" name="toBeRefresh" ref="toBeRefresh" value="toBeRefresh" />A rafraîchir

                                    Plus:

                                                <input type="checkbox" name="garage" ref="garage" value="garage" />Garage

                                                <input type="checkbox" name="terrace" ref="terrace" value="terrace" />Terrasse

                            {/* ************************** end collapse ***************************** */}

                              <Link to="/SearchViaMapResults" className="btn btn-brand-flat"> Rechercher via map</Link>
                              <Link to="/searchresults" onClick={this.handleSaveSearch} className="btn btn-brand-flat pull-right"> Rechercher</Link>

                        </form>

      );
    }
});

module.exports.SearchForm = SearchForm;
<commit_msg>Daily<commit_after>var React = require('react');
var Link = require('react-router').Link;
var $ = require('jquery');

var SearchForm = React.createClass({

    componentDidMount: function() {
        var estateType;
        for (var i = this.props.searchCriteria.estateType.length - 1; i >= 0; i--) {
          estateType = this.props.searchCriteria.estateType[i];
          this.refs[estateType].checked = true;
        };
        this.refs.localisation.value = this.props.searchCriteria.localisation;
        this.refs.priceMin.value = this.props.searchCriteria.price.min;
        this.refs.priceMax.value = this.props.searchCriteria.price.max;
        this.refs.roomNbrMin.value = this.props.searchCriteria.roomNbr.min;
        this.refs.roomNbrMax.value = this.props.searchCriteria.roomNbr.max;
        this.refs.squareMin.value = this.props.searchCriteria.square.min;
        this.refs.squareMax.value = this.props.searchCriteria.square.max;
        var estateState;
        for (var i = this.props.searchCriteria.estateState.length - 1; i >= 0; i--) {
          estateState = this.props.searchCriteria.estateState[i];
          this.refs[estateState].checked = true;
        };
        if (this.props.searchCriteria.garage){
          this.refs.garage.checked = this.props.searchCriteria.garage;
        }

        if (this.props.searchCriteria.terrace){
          this.refs.terrace.checked = this.props.searchCriteria.terrace;
        }
    },
    validateAmount: function(event) {
        if (isNaN(event.target.value)) {
            $("#" + event.target.name).addClass("has-error");
        } else {
            $("#" + event.target.name).removeClass("has-error");
        }
    },
    changeButton: function(event){
      event.preventDefault();
      //remonte de parent en parent en commencant par l'element lui-meme pour trouver le premier "a"
      var elem = $(event.target).closest( "a" );
        if ($("#more").hasClass("collapse")) {
            elem.html('Moins de critères ');
            $("#more").removeClass("collapse");
        } else {
          elem.html('Plus de critères ');
            $("#more").addClass("collapse");
        };
    },
    handleSaveSearch: function() {
        function cleanArray(actual) {
            var newArray = new Array();
            for (var i = 0; i < actual.length; i++) {
                if (actual[i]) {
                    newArray.push(actual[i]);
                }
            }
            return newArray;
        }
        var estateType = [this.refs.house.checked ? "house" : undefined, this.refs.appart.checked ? "appart" : undefined, this.refs.ground.checked ? "ground" : undefined, this.refs.investment.checked ? "investment" : undefined, this.refs.commercial.checked ? "commercial" : undefined];
        estateType = cleanArray(estateType);
        var localisation = this.refs.localisation.value;
        var priceMin = this.refs.priceMin.value;
        var priceMax = this.refs.priceMax.value;
        var roomNbrMin = this.refs.roomNbrMin.value;
        var roomNbrMax = this.refs.roomNbrMax.value;
        var squareMin = this.refs.squareMin.value;
        var squareMax = this.refs.squareMax.value;
        var estateState = [this.refs.prefect.checked ? "prefect" : undefined, this.refs.reNew.checked ? "reNew" : undefined, this.refs.good.checked ? "good" : undefined, this.refs.toBeRefresh.checked ? "toBeRefresh" : undefined];
        estateState = cleanArray(estateState);
        var garage = this.refs.garage.checked;
        var terrace = this.refs.terrace.checked;

        var searchCriteria = {
            estateType: estateType,
            localisation: localisation,
            price: {
                min: priceMin,
                max: priceMax
            },
            roomNbr: {
                min: roomNbrMin,
                max: roomNbrMax
            },
            square: {
                min: '',
                max: ''
            },
            estateState: estateState,
            garage: garage,
            terrace: terrace
        };

        this.props.onClickSearch(searchCriteria);
    },

    render: function() {
        return (

                        <form className="form-horizontal" role="form">

                                Type de bien:

                                        <input type="checkbox" name="house" ref="house" value="house" />Maison

                                        <input type="checkbox" name="appart" ref="appart" value="appart" />Appartemment

                                        <input type="checkbox" name="ground" ref="ground" value="ground" />Terrain à batir

                                        <input type="checkbox" name="investment" ref="investment" value="investment" />Immeuble de rapport

                                        <input type="checkbox" name="commercial" ref="commercial" value="commercial" />Surface commerciale

                                Localisation:

                                <input type="text" name="localisation" className="form-control" ref="localisation" />

                                Prix:

                                    Entre

                                        <input type="text" name="priceMin" className="form-control" ref="priceMin" onBlur={this.validateAmount} />

                                    et

                                        <input type="text" name="priceMax" className="form-control" ref="priceMax" onBlur={this.validateAmount}/>

                                Nombre de chambres:

                                    De

                                        <input type="text" name="roomNbrMin" className="form-control" ref="roomNbrMin" onBlur={this.validateAmount}/>

                                    à

                                        <input type="text" name="roomNbrMax" className="form-control" ref="roomNbrMax" onBlur={this.validateAmount}/>

                              Plus de critères

                            {/* ************************** collapse ***************************** */}

                                    Surface:

                                        Entre

                                            <input type="text" name="squareMin" className="form-control" ref="squareMin" onBlur={this.validateAmount} />

                                        et

                                            <input type="text" name="squareMax" className="form-control" ref="squareMax" onBlur={this.validateAmount}/>

                                        m²

                                    Etat du bâtiment:

                                                <input type="checkbox" name="prefect" ref="prefect" value="prefect" />Excellent état

                                                <input type="checkbox" name="reNew" ref="reNew" value="reNew" />Remis à neuf

                                                <input type="checkbox" name="good" ref="good" value="good" />Bon état

                                                <input type="checkbox" name="toBeRefresh" ref="toBeRefresh" value="toBeRefresh" />A rafraîchir

                                    Plus:

                                                <input type="checkbox" name="garage" ref="garage" value="garage" />Garage

                                                <input type="checkbox" name="terrace" ref="terrace" value="terrace" />Terrasse

                            {/* ************************** end collapse ***************************** */}

                              <Link to="/SearchViaMapResults" className="btn btn-brand-flat"> Autour de moi</Link>
                              <Link to="/searchresults" onClick={this.handleSaveSearch} className="btn btn-brand-flat pull-right"> Rechercher</Link>

                        </form>

      );
    }
});

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


<commit_before>import React from 'react';

import { contactUs } from '../common';

import styles from '../Legal.scss';

export const title = 'Privacy Policy';

export const order = [
  'introduction', 'information_collection', 'cookies', 'use_of_information',
  'information_sharing', 'public_forums', 'child_safety', 'external_links',
  'third_party_data', 'security', 'storage_of_information', 'rights', 'contact',
];

export const sections = {
  introduction: {
    title: 'Introduction',
    content: [

        This is the privacy and cookies policy for RoundUp Social, Inc., and our related service providers, including but not limited to Google, Localytics, Twilio, OpenTable, Groupon, Uber and Mogl, Imgur, Giphy (we, us and our) in connection with our mobile software application and related website (collectively, the “App”). For the purposes of the Data Protection Act 1998 and similar acts, we are the data controller.

,

        We are committed to protecting your privacy on-line. We appreciate that you do not want the personal information you provide to us distributed indiscriminately and here we explain how we collect information, what we do with it and what controls you have.

,

        By using the App on certain terminals such as an iPhone, iPad, iPod, Windows, Mac or Android, you consent to the collection and use of information in accordance with this privacy policy.

,

        We reserve the right to change this privacy policy from time to time by changing it on the App and website. This privacy policy was last updated on Saturday April 30th, 2016.

,
    ],
  },
  information_collection: {
    title: 'Information we may collect from you',
    content: [

We may collect and process the following information about you:
,

          information (such as your name, email address, postal address, billing address, telephone number, payment information, occupation, how you heard of us) that you provide by using products on the App or completing forms, including if you register as a user of the App, upload or submit any material via the App, request any information, or enter into any competition or promotion we may sponsor;

          in connection with an account sign-in facility, your log-in and password details;

          details of any transactions made by you through the App;

          communications you send to us, for example to report a problem or to submit queries, concerns or comments regarding the App or its content; and

          information from surveys that we may, from time to time, run on the App for research purposes, if you choose to respond to, or participate in, them.

          Also, the purpose of our App is to make it easy for people to get together, meet, in places they love. As such, we request and have access to your calendar, your current or past locations, address books (contacts) and other personal information.

,

        You are under no obligation to provide any such information. However, if you should choose to withhold requested information, we may not be able to provide you with all or some of the services.

,

        We may, from time to time, run referral programs or similar initiatives, such as a "Tell a Friend" program, that invite you to provide us with the contact details of someone who is known to you who may find our products or services to be of interest. We will only use those contact details for the purpose of the relevant initiative (and never for general marketing purposes). By providing us with their details, you confirm that you have their permission to do so and must not provide us with the details of anyone from whom you do not have such permission. We reserve the right to identify you as the person who has made the referral in the message that is sent to them.

,

        When you visit the App, we may automatically collect additional information about you, such as the type of internet browser you use, any website from which you have come to the App and your IP address (the unique address which identifies your computer on the internet) which is automatically recognized by our web server. You cannot be identified from this information and it is only used to assist us in providing an effective service on the App and to collect broad demographic information for aggregate use.

,
    ],
  },
  cookies: {
    title: 'Cookies',
    content: [

        When you visit the App, our web server sends a cookie to your computer. Cookies are small pieces of information which are issued to your computer when you visit a website and which store and sometimes track information about your use of the App. A number of cookies we use last only for the duration of your web session and expire when you close your browser. Other cookies are used to remember you when you return to the App and will last for longer.

,

        We use cookies to:

,

          remember that you have visited us before; this means we can identify the number of unique visitors we receive. This allows us to make sure we have enough capacity for the number of users that we get;

          customize elements of the promotional layout and/or content of the pages of the App;

          collect anonymous statistical information about how you use the App (including how long you spend on the App) and where you have come to the App from, so that we can improve the App and learn which parts of the App are most popular with visitors;

          gather information about the pages on the App that you visit; and

          remember your login session so you can move from one page to another within the App.

,

        Some of the cookies used by the App are set by us, and some are set by third parties who are delivering services on our behalf, including but not limited to Google, Localytics, Twilio, OpenTable, Groupon, Uber and Mogl.

,

        Most web browsers automatically accept cookies but, if you prefer, you can change your browser to prevent that or to notify you each time a cookie is set. You can also learn more about cookies by visiting www.allaboutcookies.org which includes additional useful information on cookies and how to block cookies using different types of browser. Please note, however, that by blocking or deleting cookies used on the App, you may not be able to take full advantage of the App.

,

        Our cookies

,

Essential session management

              Creating a specific log-in session for a visitor to our App in order that the site remembers that a visitor is logged in and that their page requests are delivered in an effective, secure and consistent manner.

              Recognizing when a visitor to the App has visited before; this means we can identify the number of unique visitors we receive to the App and allows us to make sure we have enough capacity for the number of users that we get.

              Recognizing when a visitor to the App is a registered member.

              We may also log information from your computer including the existence of cookies, your IP address and information about your browser program in order to allow us to diagnose problems, administer and track your usage of our services.

Functionality

              Customizing elements of the promotional layout and/or content of the pages of the App for example by storing a country code and providing users with content relevant to their country.

Performance and measurement

              Collecting statistical information about how our visitors use the App so that we can improve the App and learn which parts are most popular to visitors.

,

        In addition to cookies, tracking Gifs may be set by us or third parties in respect of your use of the App. Tracking Gifs are small image files within the content of the App or the body of our newsletters so we or third parties can understand which parts of the App are visited and whether particular content is of interest.

,
    ],
  },
  use_of_information: {
    title: 'Uses made of your information',
    content: [

We will use the information you provide to:
,

          enable us to process your orders and to provide you with the services and information offered through the App and which you request;

          administer your account with us;

          verify and carry out financial transactions in relation to payments you make online;

          audit the downloading of data from the App;

          improve the layout and/or content of the pages of the App and customise them for users;

          identify visitors to the App;

          carry out research on our users' demographics and tracking of sales data;

          send you information we think you may find useful or which you have requested from us, including information about our products and services, provided you have indicated that you do not object to being contacted for these purposes.

          We will send analytics to our retail partners but such analytics will not contain any personally identifiable information about you without your consent.

,

        You can tell us not to contact you with information regarding our products and services, either at the point such information is collected on the App (by checking or un-checking (as directed) the relevant box) or, where you do not wish us to continue to use your information in this way, by following the unsubscribe instructions on any communications sent to you. You can also exercise the right at any time by contacting us using the Contacting us details at the end of this privacy policy.

,
    ],
  },
  information_sharing: {
    title: 'Information sharing',
    content: [

        We may disclose aggregate statistics about visitors to the App, customers and sales in order to describe our services to prospective partners, advertisers, sponsors and other reputable third parties and for other lawful purposes, but these statistics will include no personally identifiable information.

,

        We may disclose your personal information to any of our affiliates, or to our agents or contractors who assist us in providing the services we offer through the App, processing transactions, fulfilling requests for information, receiving and sending communications, updating marketing lists, analyzing data, providing support services or in other tasks, from time to time. Our agents and contractors will only use your information to the extent necessary to perform their functions.

,

        In the event that we undergo re-organization or are sold to a third party, you agree that any personal information we hold about you may be transferred to that re-organized entity or third party.

,

        We may disclose your personal information if required to do so by law or if we believe that such action is necessary to prevent fraud or cyber crime or to protect the App or the rights, property or personal safety of any person.

,
    ],
  },
  public_forums: {
    title: 'Public forums',
    content: [

        The App may, from time to time, make chat rooms, message boards, news groups and/or other public forums available to its users. Any information that is disclosed in these areas becomes public information and you should exercise caution when using these and never disclose your personal information.

,
    ],
  },
  child_safety: {
    title: 'Child safety',
    content: [

        Protecting the safety of children when they use the Internet is very important to us. We recommend that children receive permission from their parent or guardian before gaining access to the App or sending personal information to us or anyone else online.

,
    ],
  },
  external_links: {
    title: 'External links',
    content: [

        The App may, from time to time, contain links to external sites. We are not responsible for the privacy policies or the content of such sites.

,
    ],
  },
  third_party_data: {
    title: 'Third Party Data',
    content: [

        RoundUp Social Inc. use or may in the future use{' '}
        (i) the Google Maps API(s) and and you are hereby, in addition, bound by the Google Privacy Policy;{' '}
        (ii) Localytics and you are additionally bound by their Privacy Policy;{' '}
        (iii) Twillio and you are additionally bound by their Privacy Policy;{' '}
        (iv) OpenTable and you are additionally bound by their Privacy Policy;{' '}
        (v) Groupon and you are additionally bound by their Privacy Policy;{' '}
        (vi) Uber and you are additionally bound by their Privacy Policy;{' '}
        (vii) Mogl and you are additionally bound by their Privacy Policy.
        (viii) Imgur and you are additionally bound by their Privacy Policy.
        (viii) Giphy and you are additionally bound by their Privacy Policy.

,
    ],
  },
  security: {
    title: 'Security',
    content: [

        We place great importance on the security of all personally identifiable information associated with our users. We have security measures in place to attempt to protect against the loss, misuse and alteration of personal information under our control. For example, our security and privacy policies are periodically reviewed and enhanced as necessary and only authorized personnel have access to personal information. Whilst we cannot ensure or guarantee that loss, misuse or alteration of information will never occur, we use all reasonable efforts to prevent it.

,

        You should bear in mind that submission of information over the internet is never entirely secure. We cannot guarantee the security of information you submit via the App whilst it is in transit over the internet and any such submission is at your own risk.

,

        It is advisable to close your browser when you have finished your user session to help ensure others do not access your personal information if you use a shared computer or a computer in a public place.

,
    ],
  },
  storage_of_information: {
    title: 'Storage of your information',
    content: [

        Information that you submit via the App is sent to and stored on secure servers operated by Google Cloud Services. This is necessary in order to process the information. Information submitted by you may be transferred by us to our other offices and/or to the third parties mentioned in the circumstances described above (see Information sharing), which may be situated outside the United States.

,
    ],
  },
  rights: {
    title: 'Your rights',
    content: [

        You have a legal right under the Data Protection Act 1998 to a copy of all the personal information about you held by us. On request, we will provide you with a copy of this information subject to a fee not exceeding $20.00. You also have a right to correct any errors in that information. As mentioned above, you have a right to prevent the use of your personal information for direct marketing purposes.

,
    ],
  },
  contact: contactUs,
};
<commit_msg>[fixed] linked to allaboutcookies<commit_after>import React from 'react';

import { contactUs } from '../common';

import styles from '../Legal.scss';

export const title = 'Privacy Policy';

export const order = [
  'introduction', 'information_collection', 'cookies', 'use_of_information',
  'information_sharing', 'public_forums', 'child_safety', 'external_links',
  'third_party_data', 'security', 'storage_of_information', 'rights', 'contact',
];

export const sections = {
  introduction: {
    title: 'Introduction',
    content: [

        This is the privacy and cookies policy for RoundUp Social, Inc., and our related service providers, including but not limited to Google, Localytics, Twilio, OpenTable, Groupon, Uber and Mogl, Imgur, Giphy (we, us and our) in connection with our mobile software application and related website (collectively, the “App”). For the purposes of the Data Protection Act 1998 and similar acts, we are the data controller.

,

        We are committed to protecting your privacy on-line. We appreciate that you do not want the personal information you provide to us distributed indiscriminately and here we explain how we collect information, what we do with it and what controls you have.

,

        By using the App on certain terminals such as an iPhone, iPad, iPod, Windows, Mac or Android, you consent to the collection and use of information in accordance with this privacy policy.

,

        We reserve the right to change this privacy policy from time to time by changing it on the App and website. This privacy policy was last updated on Saturday April 30th, 2016.

,
    ],
  },
  information_collection: {
    title: 'Information we may collect from you',
    content: [

We may collect and process the following information about you:
,

          information (such as your name, email address, postal address, billing address, telephone number, payment information, occupation, how you heard of us) that you provide by using products on the App or completing forms, including if you register as a user of the App, upload or submit any material via the App, request any information, or enter into any competition or promotion we may sponsor;

          in connection with an account sign-in facility, your log-in and password details;

          details of any transactions made by you through the App;

          communications you send to us, for example to report a problem or to submit queries, concerns or comments regarding the App or its content; and

          information from surveys that we may, from time to time, run on the App for research purposes, if you choose to respond to, or participate in, them.

          Also, the purpose of our App is to make it easy for people to get together, meet, in places they love. As such, we request and have access to your calendar, your current or past locations, address books (contacts) and other personal information.

,

        You are under no obligation to provide any such information. However, if you should choose to withhold requested information, we may not be able to provide you with all or some of the services.

,

        We may, from time to time, run referral programs or similar initiatives, such as a "Tell a Friend" program, that invite you to provide us with the contact details of someone who is known to you who may find our products or services to be of interest. We will only use those contact details for the purpose of the relevant initiative (and never for general marketing purposes). By providing us with their details, you confirm that you have their permission to do so and must not provide us with the details of anyone from whom you do not have such permission. We reserve the right to identify you as the person who has made the referral in the message that is sent to them.

,

        When you visit the App, we may automatically collect additional information about you, such as the type of internet browser you use, any website from which you have come to the App and your IP address (the unique address which identifies your computer on the internet) which is automatically recognized by our web server. You cannot be identified from this information and it is only used to assist us in providing an effective service on the App and to collect broad demographic information for aggregate use.

,
    ],
  },
  cookies: {
    title: 'Cookies',
    content: [

        When you visit the App, our web server sends a cookie to your computer. Cookies are small pieces of information which are issued to your computer when you visit a website and which store and sometimes track information about your use of the App. A number of cookies we use last only for the duration of your web session and expire when you close your browser. Other cookies are used to remember you when you return to the App and will last for longer.

,

        We use cookies to:

,

          remember that you have visited us before; this means we can identify the number of unique visitors we receive. This allows us to make sure we have enough capacity for the number of users that we get;

          customize elements of the promotional layout and/or content of the pages of the App;

          collect anonymous statistical information about how you use the App (including how long you spend on the App) and where you have come to the App from, so that we can improve the App and learn which parts of the App are most popular with visitors;

          gather information about the pages on the App that you visit; and

          remember your login session so you can move from one page to another within the App.

,

        Some of the cookies used by the App are set by us, and some are set by third parties who are delivering services on our behalf, including but not limited to Google, Localytics, Twilio, OpenTable, Groupon, Uber and Mogl.

,

        Most web browsers automatically accept cookies but, if you prefer, you can change your browser to prevent that or to notify you each time a cookie is set. You can also learn more about cookies by visiting www.allaboutcookies.org which includes additional useful information on cookies and how to block cookies using different types of browser. Please note, however, that by blocking or deleting cookies used on the App, you may not be able to take full advantage of the App.

,

        Our cookies

,

Essential session management

              Creating a specific log-in session for a visitor to our App in order that the site remembers that a visitor is logged in and that their page requests are delivered in an effective, secure and consistent manner.

              Recognizing when a visitor to the App has visited before; this means we can identify the number of unique visitors we receive to the App and allows us to make sure we have enough capacity for the number of users that we get.

              Recognizing when a visitor to the App is a registered member.

              We may also log information from your computer including the existence of cookies, your IP address and information about your browser program in order to allow us to diagnose problems, administer and track your usage of our services.

Functionality

              Customizing elements of the promotional layout and/or content of the pages of the App for example by storing a country code and providing users with content relevant to their country.

Performance and measurement

              Collecting statistical information about how our visitors use the App so that we can improve the App and learn which parts are most popular to visitors.

,

        In addition to cookies, tracking Gifs may be set by us or third parties in respect of your use of the App. Tracking Gifs are small image files within the content of the App or the body of our newsletters so we or third parties can understand which parts of the App are visited and whether particular content is of interest.

,
    ],
  },
  use_of_information: {
    title: 'Uses made of your information',
    content: [

We will use the information you provide to:
,

          enable us to process your orders and to provide you with the services and information offered through the App and which you request;

          administer your account with us;

          verify and carry out financial transactions in relation to payments you make online;

          audit the downloading of data from the App;

          improve the layout and/or content of the pages of the App and customise them for users;

          identify visitors to the App;

          carry out research on our users' demographics and tracking of sales data;

          send you information we think you may find useful or which you have requested from us, including information about our products and services, provided you have indicated that you do not object to being contacted for these purposes.

          We will send analytics to our retail partners but such analytics will not contain any personally identifiable information about you without your consent.

,

        You can tell us not to contact you with information regarding our products and services, either at the point such information is collected on the App (by checking or un-checking (as directed) the relevant box) or, where you do not wish us to continue to use your information in this way, by following the unsubscribe instructions on any communications sent to you. You can also exercise the right at any time by contacting us using the Contacting us details at the end of this privacy policy.

,
    ],
  },
  information_sharing: {
    title: 'Information sharing',
    content: [

        We may disclose aggregate statistics about visitors to the App, customers and sales in order to describe our services to prospective partners, advertisers, sponsors and other reputable third parties and for other lawful purposes, but these statistics will include no personally identifiable information.

,

        We may disclose your personal information to any of our affiliates, or to our agents or contractors who assist us in providing the services we offer through the App, processing transactions, fulfilling requests for information, receiving and sending communications, updating marketing lists, analyzing data, providing support services or in other tasks, from time to time. Our agents and contractors will only use your information to the extent necessary to perform their functions.

,

        In the event that we undergo re-organization or are sold to a third party, you agree that any personal information we hold about you may be transferred to that re-organized entity or third party.

,

        We may disclose your personal information if required to do so by law or if we believe that such action is necessary to prevent fraud or cyber crime or to protect the App or the rights, property or personal safety of any person.

,
    ],
  },
  public_forums: {
    title: 'Public forums',
    content: [

        The App may, from time to time, make chat rooms, message boards, news groups and/or other public forums available to its users. Any information that is disclosed in these areas becomes public information and you should exercise caution when using these and never disclose your personal information.

,
    ],
  },
  child_safety: {
    title: 'Child safety',
    content: [

        Protecting the safety of children when they use the Internet is very important to us. We recommend that children receive permission from their parent or guardian before gaining access to the App or sending personal information to us or anyone else online.

,
    ],
  },
  external_links: {
    title: 'External links',
    content: [

        The App may, from time to time, contain links to external sites. We are not responsible for the privacy policies or the content of such sites.

,
    ],
  },
  third_party_data: {
    title: 'Third Party Data',
    content: [

        RoundUp Social Inc. use or may in the future use{' '}
        (i) the Google Maps API(s) and and you are hereby, in addition, bound by the Google Privacy Policy;{' '}
        (ii) Localytics and you are additionally bound by their Privacy Policy;{' '}
        (iii) Twillio and you are additionally bound by their Privacy Policy;{' '}
        (iv) OpenTable and you are additionally bound by their Privacy Policy;{' '}
        (v) Groupon and you are additionally bound by their Privacy Policy;{' '}
        (vi) Uber and you are additionally bound by their Privacy Policy;{' '}
        (vii) Mogl and you are additionally bound by their Privacy Policy.
        (viii) Imgur and you are additionally bound by their Privacy Policy.
        (viii) Giphy and you are additionally bound by their Privacy Policy.

,
    ],
  },
  security: {
    title: 'Security',
    content: [

        We place great importance on the security of all personally identifiable information associated with our users. We have security measures in place to attempt to protect against the loss, misuse and alteration of personal information under our control. For example, our security and privacy policies are periodically reviewed and enhanced as necessary and only authorized personnel have access to personal information. Whilst we cannot ensure or guarantee that loss, misuse or alteration of information will never occur, we use all reasonable efforts to prevent it.

,

        You should bear in mind that submission of information over the internet is never entirely secure. We cannot guarantee the security of information you submit via the App whilst it is in transit over the internet and any such submission is at your own risk.

,

        It is advisable to close your browser when you have finished your user session to help ensure others do not access your personal information if you use a shared computer or a computer in a public place.

,
    ],
  },
  storage_of_information: {
    title: 'Storage of your information',
    content: [

        Information that you submit via the App is sent to and stored on secure servers operated by Google Cloud Services. This is necessary in order to process the information. Information submitted by you may be transferred by us to our other offices and/or to the third parties mentioned in the circumstances described above (see Information sharing), which may be situated outside the United States.

,
    ],
  },
  rights: {
    title: 'Your rights',
    content: [

        You have a legal right under the Data Protection Act 1998 to a copy of all the personal information about you held by us. On request, we will provide you with a copy of this information subject to a fee not exceeding $20.00. You also have a right to correct any errors in that information. As mentioned above, you have a right to prevent the use of your personal information for direct marketing purposes.

,
    ],
  },
  contact: contactUs,
};
<|endoftext|>


<commit_before>angular.module('FeedbackSignal', [])
.service('FeedbackSignal', function($rootScope) {

    var shared_service = {};

    shared_service.data;

    shared_service.current_state = function(data) {
        this.data = data;
        this.broadcast_now();
    }

    shared_service.get_data = function() {
        return this.data;
    }

    shared_service.broadcast_now = function() {
        $rootScope.$broadcast('checkFeedbackStatus');
    }

    return shared_service;
})
<commit_msg>what the fuck<commit_after>angular.module('FeedbackSignal', [])
.service('FeedbackSignal', function($rootScope) {

    var shared_service = {};

    shared_service.data;

    shared_service.current_state = function(data) {
        this.data = data;
        this.broadcast_now();
    }

    shared_service.get_data = function() {
        return this.data;
    }

    shared_service.broadcast_now = function() {
        $rootScope.$broadcast('checkFeedbackStatus');
    }

    return shared_service;
})
<|endoftext|>