<commit_before>var pull    = require('pull-stream')
var pt      = require('pull-traverse')
var semver  = require('semver')
var cat     = require('pull-cat')
var clean    = require('./clean')
var paramap  = require('pull-paramap')

module.exports = createResolve

function check(pkg, name, range) {
  if(!pkg) return false
  if(pkg.tree[name] && semver.satisfies(pkg.tree[name].version, range, true))
    return true
  return check(pkg.parent, name, range)
}

function fixModule (module) {
  if('string' === typeof module) {
  var parts = module.split('@')
    return {name: parts.shift(), version: parts.shift() || '*'}
  }
  return module
}

function niceError(err, parent, name, range) {
  if(!err) return
  err.message = 'package: '
  + parent.name + '@' + parent.version
  + ' could not resolve '
  + name + '@' + range
  + '\n' + err.message

  return err
}

function merge (a, b) {
  var c = {}
  a = a || {}
  b = b || {}
  for(var k in a)
    c[k] = a[k]
  for(var k in b)
    c[k] = b[k]
  return c
}

function isPackage(pkg) {
  return pkg.name && pkg.version && (pkg.dependencies || pkg.devDependencies)
}

function has(deps, module, vrange) {
  if(!deps || !deps[module]) return false
  return semver.satisfies(deps[module].version, vrange, true)
}

function hasDep(pkg, module, vrange) {
  if(pkg.tree && has(pkg.tree, module, vrange)) return true
  else if(pkg.parent)
    return hasDep(pkg.parent, module, vrange)
  return false
}

var unresolved = {}

function createResolve (resolvePackage) {

  function resolveTree (module, opts, cb) {

    module = fixModule(module)

    var filter = opts.filter || function (pkg, root) {
      if(!pkg) return
      pkg.parent.tree[pkg.name] = pkg
    }

    if(!module) return cb(null, {})

    //this is the root package - if we are resolving from a package.json
    //we should just use that, instead of calling resolve pkg.
    var root

    if(isPackage(module)) tree(module)
    else
      resolvePackage(module.name, module.version, opts, function (err, pkg) {
        if(err) return cb(err)
        tree(pkg)
      })

    function tree (root) {
      if(opts.available) {
        root.parent = {tree: opts.available}
      }
      pull(cat([
        pull.values([root]),
        pt.depthFirst(root, function (pkg) {
          var deps = merge(
            pkg.dependencies || {},
            opts.optional === false ? {} : opts.pkg.optionalDependencies
          )

          //merge deps and dev deps if this is the root module and we are in --dev mode
          if(opts.dev && pkg === root)
            deps = pkg.dependencies = merge(pkg.devDependencies, deps)

          pkg.tree = {}
          return pull(
            pull.values(Object.keys(deps)),
            //this could be parallel,
            //but it's not the bottle neck.
            paramap(function (name, cb) {
              //check if there is already a module that resolves this...
              if(hasDep(pkg, name, deps[name])) return cb()
              //filter out versions that we already have.
              //if(opts.check !== false && check(pkg, name, deps[name]))
              //  return cb()
              unresolved[name + '@' + deps[name]] = true
              resolvePackage(name, deps[name], opts, function (err, _pkg) {
                delete unresolved[name + '@' + deps[name]]

                cb(niceError(err, pkg, name, deps[name]), _pkg)
              })
            }),
            pull.filter(),
            pull.through(function (_pkg) {
              _pkg.parent = pkg
              filter(_pkg, root)
            })
          )
        })
      ]),
      pull.drain(null, function (err) {
        cb(err, clean(root))
      }))
    }
  }

  //install non-conflicting modules as low in the tree as possible.
  function resolveTreeGreedy (module, opts, cb) {
    if(!cb) cb = opts, opts = null

    opts = opts || {}
    opts.filter = function (pkg, root) {
      if(!pkg) return
      if(!root.tree[pkg.name]) {
        root.tree[pkg.name] = pkg
        pkg.parent = root
      }
      else {
        pkg.parent.tree[pkg.name] = pkg
      }
      return pkg
    }

    resolveTree(module, opts, cb)
  }

  return function (module, opts, cb) {
    if(!cb)
      cb = opts, opts = {}

    function resolve(module, cb) {
      if(opts && opts.greedy)
        resolveTreeGreedy(module, opts, cb)
      else
        resolveTree(module, opts, cb)
    }

    if(Array.isArray(module) && module.length > 1) {

      var n = module.length, a = {}

      module.forEach(function (m) {
        resolve(m, next)
      })

      function next (err, tree) {
        if(err) return n = 0, cb(err)
        a[tree.name] = tree
        if(--n) return
        cb(null, a)
      }

    } else {
      if(Array.isArray(module))
        module = module.shift()

      resolve(module, cb)
    }
  }
}

//This Should Never Happen.
process.on('exit', function () {
  var keys = Object.keys(unresolved)
  if(keys.length)
  throw new Error('resolve did not callback: ' + keys.join(', '))
})

<commit_msg>pkg.optionalDependencies, not opts.pkg....<commit_after>var pull    = require('pull-stream')
var pt      = require('pull-traverse')
var semver  = require('semver')
var cat     = require('pull-cat')
var clean    = require('./clean')
var paramap  = require('pull-paramap')

module.exports = createResolve

function check(pkg, name, range) {
  if(!pkg) return false
  if(pkg.tree[name] && semver.satisfies(pkg.tree[name].version, range, true))
    return true
  return check(pkg.parent, name, range)
}

function fixModule (module) {
  if('string' === typeof module) {
  var parts = module.split('@')
    return {name: parts.shift(), version: parts.shift() || '*'}
  }
  return module
}

function niceError(err, parent, name, range) {
  if(!err) return
  err.message = 'package: '
  + parent.name + '@' + parent.version
  + ' could not resolve '
  + name + '@' + range
  + '\n' + err.message

  return err
}

function merge (a, b) {
  var c = {}
  a = a || {}
  b = b || {}
  for(var k in a)
    c[k] = a[k]
  for(var k in b)
    c[k] = b[k]
  return c
}

function isPackage(pkg) {
  return pkg.name && pkg.version && (pkg.dependencies || pkg.devDependencies)
}

function has(deps, module, vrange) {
  if(!deps || !deps[module]) return false
  return semver.satisfies(deps[module].version, vrange, true)
}

function hasDep(pkg, module, vrange) {
  if(pkg.tree && has(pkg.tree, module, vrange)) return true
  else if(pkg.parent)
    return hasDep(pkg.parent, module, vrange)
  return false
}

var unresolved = {}

function createResolve (resolvePackage) {

  function resolveTree (module, opts, cb) {

    module = fixModule(module)

    var filter = opts.filter || function (pkg, root) {
      if(!pkg) return
      pkg.parent.tree[pkg.name] = pkg
    }

    if(!module) return cb(null, {})

    //this is the root package - if we are resolving from a package.json
    //we should just use that, instead of calling resolve pkg.
    var root

    if(isPackage(module)) tree(module)
    else
      resolvePackage(module.name, module.version, opts, function (err, pkg) {
        if(err) return cb(err)
        tree(pkg)
      })

    function tree (root) {
      if(opts.available) {
        root.parent = {tree: opts.available}
      }
      pull(cat([
        pull.values([root]),
        pt.depthFirst(root, function (pkg) {
          var deps = merge(
            pkg.dependencies || {},
            opts.optional === false ? {} : pkg.optionalDependencies
          )

          //merge deps and dev deps if this is the root module and we are in --dev mode
          if(opts.dev && pkg === root)
            deps = pkg.dependencies = merge(pkg.devDependencies, deps)

          pkg.tree = {}
          return pull(
            pull.values(Object.keys(deps)),
            //this could be parallel,
            //but it's not the bottle neck.
            paramap(function (name, cb) {
              //check if there is already a module that resolves this...
              if(hasDep(pkg, name, deps[name])) return cb()
              //filter out versions that we already have.
              //if(opts.check !== false && check(pkg, name, deps[name]))
              //  return cb()
              unresolved[name + '@' + deps[name]] = true
              resolvePackage(name, deps[name], opts, function (err, _pkg) {
                delete unresolved[name + '@' + deps[name]]

                cb(niceError(err, pkg, name, deps[name]), _pkg)
              })
            }),
            pull.filter(),
            pull.through(function (_pkg) {
              _pkg.parent = pkg
              filter(_pkg, root)
            })
          )
        })
      ]),
      pull.drain(null, function (err) {
        cb(err, clean(root))
      }))
    }
  }

  //install non-conflicting modules as low in the tree as possible.
  function resolveTreeGreedy (module, opts, cb) {
    if(!cb) cb = opts, opts = null

    opts = opts || {}
    opts.filter = function (pkg, root) {
      if(!pkg) return
      if(!root.tree[pkg.name]) {
        root.tree[pkg.name] = pkg
        pkg.parent = root
      }
      else {
        pkg.parent.tree[pkg.name] = pkg
      }
      return pkg
    }

    resolveTree(module, opts, cb)
  }

  return function (module, opts, cb) {
    if(!cb)
      cb = opts, opts = {}

    function resolve(module, cb) {
      if(opts && opts.greedy)
        resolveTreeGreedy(module, opts, cb)
      else
        resolveTree(module, opts, cb)
    }

    if(Array.isArray(module) && module.length > 1) {

      var n = module.length, a = {}

      module.forEach(function (m) {
        resolve(m, next)
      })

      function next (err, tree) {
        if(err) return n = 0, cb(err)
        a[tree.name] = tree
        if(--n) return
        cb(null, a)
      }

    } else {
      if(Array.isArray(module))
        module = module.shift()

      resolve(module, cb)
    }
  }
}

//This Should Never Happen.
process.on('exit', function () {
  var keys = Object.keys(unresolved)
  if(keys.length)
  throw new Error('resolve did not callback: ' + keys.join(', '))
})

<|endoftext|>


<commit_before>/*    Copyright 2016 Firewalla LLC
 *
 *    This program is free software: you can redistribute it and/or  modify
 *    it under the terms of the GNU Affero General Public License, version 3,
 *    as published by the Free Software Foundation.
 *
 *    This program is distributed in the hope that it will be useful,
 *    but WITHOUT ANY WARRANTY; without even the implied warranty of
 *    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *    GNU Affero General Public License for more details.
 *
 *    You should have received a copy of the GNU Affero General Public License
 *    along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

'use strict';

const log = require('../../net2/logger.js')(__filename);
const cp = require('child_process');
const ipTool = require('ip');
const util = require('util');
const routing = require('../routing/routing.js');

const iptables = require('../../net2/Iptables.js');
const wrapIptables = iptables.wrapIptables;
const ipset = require('../../net2/Ipset.js');
const platformLoader = require('../../platform/PlatformLoader.js');
const platform = platformLoader.getPlatform();

const execAsync = util.promisify(cp.exec);

const VPN_CLIENT_RULE_TABLE_PREFIX = "vpn_client";

class VPNClientEnforcer {
  constructor() {
    return this;
  }

  _getRoutingTableName(vpnIntf) {
    return `${VPN_CLIENT_RULE_TABLE_PREFIX}_${vpnIntf}`;
  }

  async getRtId(vpnIntf) {
    const tableName = this._getRoutingTableName(vpnIntf);
    return await routing.createCustomizedRoutingTable(tableName).catch((err) => null);
  }

  async enforceStrictVPN(vpnIntf) {
    if (!vpnIntf) {
      throw "Interface is not specified";
    }
    const rtId = await this.getRtId(vpnIntf);
    if (!rtId)
      return;
    const rtIdHex = Number(rtId).toString(16);
    const cmd = wrapIptables(`sudo iptables -w -A FW_VPN_CLIENT -m mark --mark 0x${rtIdHex}/0xffff -m set ! --match-set ${ipset.CONSTANTS.IPSET_MONITORED_NET} dst,dst ! -o ${vpnIntf} -j FW_DROP`);
    await execAsync(cmd).catch((err) => {
      log.error(`Failed to enforce strict vpn on ${vpnIntf}`, err);
    });
  }

  async unenforceStrictVPN(vpnIntf) {
    if (!vpnIntf) {
      throw "Interface is not specified";
    }
    const rtId = await this.getRtId(vpnIntf);
    if (!rtId)
      return;
    const rtIdHex = Number(rtId).toString(16);
    const cmd = wrapIptables(`sudo iptables -w -D FW_VPN_CLIENT -m mark --mark 0x${rtIdHex}/0xffff -m set ! --match-set ${ipset.CONSTANTS.IPSET_MONITORED_NET} dst,dst ! -o ${vpnIntf} -j FW_DROP`);
    await execAsync(cmd).catch((err) => {
      log.error(`Failed to unenforce strict vpn on ${vpnIntf}`, err);
      throw err;
    });
  }

  async enforceVPNClientRoutes(remoteIP, vpnIntf, routedSubnets = [], overrideDefaultRoute = true) {
    if (!vpnIntf)
      throw "Interface is not specified";
    const tableName = this._getRoutingTableName(vpnIntf);
    // ensure customized routing table is created
    const rtId = await routing.createCustomizedRoutingTable(tableName);
    await routing.flushRoutingTable(tableName);
    // add policy based rule, the priority 6000 is a bit higher than the firerouter's application defined fwmark
    await routing.createPolicyRoutingRule("all", null, tableName, 6000, `${rtId}/0xffff`);
    if (platform.isFireRouterManaged()) {
      // on firerouter-managed platform, no need to copy main routing table to the vpn client routing table
      // but need to grant access to wan_routable table for packets from vpn interface
      await routing.createPolicyRoutingRule("all", vpnIntf, "wan_routable", 5000, null, 4);
    } else {
      // copy all routes from main routing table on non-firerouter-managed platform
      let cmd = "ip route list";
      if (overrideDefaultRoute)
        // do not copy default route from main routing table
        cmd = "ip route list | grep -v default";
      const routes = await execAsync(cmd);
      await Promise.all(routes.stdout.split('\n').map(async route => {
        if (route.length > 0) {
          cmd = util.format("sudo ip route add %s table %s", route, tableName);
          await execAsync(cmd).catch((err) => {}); // this usually happens when multiple function calls are executed simultaneously. It should have no side effect and will be consistent eventually
        }
      }));
    }
    for (let routedSubnet of routedSubnets) {
      const cidr = ipTool.cidrSubnet(routedSubnet);
      // change subnet to ip route acceptable format
      const formattedSubnet = `${cidr.networkAddress}/${cidr.subnetMaskLength}`;
      await routing.addRouteToTable(formattedSubnet, remoteIP, vpnIntf, tableName).catch((err) => {});
    }
    if (overrideDefaultRoute) {
      // then add remote IP as gateway of default route to vpn client table
      await routing.addRouteToTable("default", remoteIP, vpnIntf, tableName).catch((err) => {}); // this usually happens when multiple function calls are executed simultaneously. It should have no side effect and will be consistent eventually
    }
    // add inbound connmark rule for vpn client interface
    await execAsync(wrapIptables(`sudo iptables -w -t nat -A FW_PREROUTING_VC_INBOUND -i ${vpnIntf} -j CONNMARK --set-xmark ${rtId}/0xffff`)).catch((err) => {
      log.error(`Failed to add VPN client ipv4 inbound connmark rule for ${vpnIntf}`, err.message);
    });
    await execAsync(wrapIptables(`sudo ip6tables -w -t nat -A FW_PREROUTING_VC_INBOUND -i ${vpnIntf} -j CONNMARK --set-xmark ${rtId}/0xffff`)).catch((err) => {
      log.error(`Failed to add VPN client ipv6 inbound connmark rule for ${vpnIntf}`, err.message);
    });
  }

  async flushVPNClientRoutes(vpnIntf) {
    if (!vpnIntf)
      throw "Interface is not specified";
    const tableName = this._getRoutingTableName(vpnIntf);
    const rtId = await routing.createCustomizedRoutingTable(tableName);
    await routing.flushRoutingTable(tableName);
    // remove policy based rule
    await routing.removePolicyRoutingRule("all", null, tableName, 6000, `${rtId}/0xffff`);
    await routing.removePolicyRoutingRule("all", vpnIntf, "wan_routable", 5000, null, 4);
    // remove inbound connmark rule for vpn client interface
    await execAsync(wrapIptables(`sudo iptables -w -t nat -D FW_PREROUTING_VC_INBOUND -i ${vpnIntf} -j CONNMARK --set-xmark ${rtId}/0xffff`)).catch((err) => {
      log.error(`Failed to remove VPN client ipv4 inbound connmark rule for ${vpnIntf}`, err.message);
    });
    await execAsync(wrapIptables(`sudo ip6tables -w -t nat -D FW_PREROUTING_VC_INBOUND -i ${vpnIntf} -j CONNMARK --set-xmark ${rtId}/0xffff`)).catch((err) => {
      log.error(`Failed to remove VPN client ipv6 inbound connmark rule for ${vpnIntf}`, err.message);
    });
  }

  _getVPNClientIPSetName(vpnIntf) {
    return `vpn_client_${vpnIntf}_set`;
  }

  async enforceDNSRedirect(vpnIntf, dnsServers, remoteIP) {
    if (!vpnIntf || !dnsServers || dnsServers.length == 0)
      return;
    const rtId = await this.getRtId(vpnIntf);
    if (!rtId)
      return;
    const rtIdHex = Number(rtId).toString(16);
    const tableName = this._getRoutingTableName(vpnIntf);
    for (let i in dnsServers) {
      const dnsServer = dnsServers[i];
      // add to vpn client routing table
      if (remoteIP)
        await routing.addRouteToTable(dnsServer, remoteIP, vpnIntf, tableName).catch((err) => {});
      // round robin rule for multiple dns servers
      if (i == 0) {
        // no need to use statistic module for the first rule
        let cmd = wrapIptables(`sudo iptables -w -t nat -I FW_PREROUTING_DNS_VPN_CLIENT -m mark --mark 0x${rtIdHex}/0xffff -p tcp --dport 53 -j DNAT --to-destination ${dnsServer}`);
        await execAsync(cmd).catch((err) => {
          log.error(`Failed to enforce DNS redirect rule: ${cmd}, intf: ${vpnIntf}, dnsServer: ${dnsServer}`, err);
        });
        cmd = wrapIptables(`sudo iptables -w -t nat -I FW_PREROUTING_DNS_VPN_CLIENT -m mark --mark 0x${rtIdHex}/0xffff -p udp --dport 53 -j DNAT --to-destination ${dnsServer}`);
        await execAsync(cmd).catch((err) => {
          log.error(`Failed to enforce DNS redirect rule: ${cmd}, intf: ${vpnIntf}, dnsServer: ${dnsServer}`, err);
        });
      } else {
        let cmd = wrapIptables(`sudo iptables -w -t nat -I FW_PREROUTING_DNS_VPN_CLIENT -m mark --mark 0x${rtIdHex}/0xffff  -p tcp --dport 53 -m statistic --mode nth --every ${Number(i) + 1} --packet 0 -j DNAT --to-destination ${dnsServer}`);
        await execAsync(cmd).catch((err) => {
          log.error(`Failed to enforce DNS redirect rule: ${cmd}, intf: ${vpnIntf}, dnsServer: ${dnsServer}`, err);
        });
        cmd = wrapIptables(`sudo iptables -w -t nat -I FW_PREROUTING_DNS_VPN_CLIENT -m mark --mark 0x${rtIdHex}/0xffff  -p udp --dport 53 -m statistic --mode nth --every ${Number(i) + 1} --packet 0 -j DNAT --to-destination ${dnsServer}`);
        await execAsync(cmd).catch((err) => {
          log.error(`Failed to enforce DNS redirect rule: ${cmd}, intf: ${vpnIntf}, dnsServer: ${dnsServer}`, err);
        });
      }
    }
  }

  async unenforceDNSRedirect(vpnIntf, dnsServers, remoteIP) {
    if (!vpnIntf || !dnsServers || dnsServers.length == 0)
      return;
    const rtId = await this.getRtId(vpnIntf);
    if (!rtId)
      return;
    const rtIdHex = Number(rtId).toString(16);
    const tableName = this._getRoutingTableName(vpnIntf);
    for (let i in dnsServers) {
      const dnsServer = dnsServers[i];
      // remove from vpn client routing table
      if (remoteIP)
        await routing.removeRouteFromTable(dnsServer, remoteIP, vpnIntf, tableName).catch((err) => {});
      // round robin rule for multiple dns servers
      if (i == 0) {
        // no need to use statistic module for the first rule
        let cmd = wrapIptables(`sudo iptables -w -t nat -D FW_PREROUTING_DNS_VPN_CLIENT -m mark --mark 0x${rtIdHex}/0xffff -p tcp --dport 53 -j DNAT --to-destination ${dnsServer}`);
        await execAsync(cmd).catch((err) => {
          log.error(`Failed to enforce DNS redirect rule: ${cmd}, intf: ${vpnIntf}, dnsServer: ${dnsServer}`, err);
        });
        cmd = wrapIptables(`sudo iptables -w -t nat -D FW_PREROUTING_DNS_VPN_CLIENT -m mark --mark 0x${rtIdHex}/0xffff -p udp --dport 53 -j DNAT --to-destination ${dnsServer}`);
        await execAsync(cmd).catch((err) => {
          log.error(`Failed to enforce DNS redirect rule: ${cmd}, intf: ${vpnIntf}, dnsServer: ${dnsServer}`, err);
        });
      } else {
        let cmd = wrapIptables(`sudo iptables -w -t nat -D FW_PREROUTING_DNS_VPN_CLIENT -m mark --mark 0x${rtIdHex}/0xffff -p tcp --dport 53 -m statistic --mode nth --every ${Number(i) + 1} --packet 0 -j DNAT --to-destination ${dnsServer}`);
        await execAsync(cmd).catch((err) => {
          log.error(`Failed to enforce DNS redirect rule: ${cmd}, intf: ${vpnIntf}, dnsServer: ${dnsServer}`, err);
        });
        cmd = wrapIptables(`sudo iptables -w -t nat -D FW_PREROUTING_DNS_VPN_CLIENT -m mark --mark 0x${rtIdHex}/0xffff -p udp --dport 53 -m statistic --mode nth --every ${Number(i) + 1} --packet 0 -j DNAT --to-destination ${dnsServer}`);
        await execAsync(cmd).catch((err) => {
          log.error(`Failed to enforce DNS redirect rule: ${cmd}, intf: ${vpnIntf}, dnsServer: ${dnsServer}`, err);
        });
      }
    }
  }
}

const instance = new VPNClientEnforcer();
module.exports = instance;<commit_msg>catch error from routing.removePolicyRoutingRule in VPNClientEnforcer.js<commit_after>/*    Copyright 2016 Firewalla LLC
 *
 *    This program is free software: you can redistribute it and/or  modify
 *    it under the terms of the GNU Affero General Public License, version 3,
 *    as published by the Free Software Foundation.
 *
 *    This program is distributed in the hope that it will be useful,
 *    but WITHOUT ANY WARRANTY; without even the implied warranty of
 *    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *    GNU Affero General Public License for more details.
 *
 *    You should have received a copy of the GNU Affero General Public License
 *    along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

'use strict';

const log = require('../../net2/logger.js')(__filename);
const cp = require('child_process');
const ipTool = require('ip');
const util = require('util');
const routing = require('../routing/routing.js');

const iptables = require('../../net2/Iptables.js');
const wrapIptables = iptables.wrapIptables;
const ipset = require('../../net2/Ipset.js');
const platformLoader = require('../../platform/PlatformLoader.js');
const platform = platformLoader.getPlatform();

const execAsync = util.promisify(cp.exec);

const VPN_CLIENT_RULE_TABLE_PREFIX = "vpn_client";

class VPNClientEnforcer {
  constructor() {
    return this;
  }

  _getRoutingTableName(vpnIntf) {
    return `${VPN_CLIENT_RULE_TABLE_PREFIX}_${vpnIntf}`;
  }

  async getRtId(vpnIntf) {
    const tableName = this._getRoutingTableName(vpnIntf);
    return await routing.createCustomizedRoutingTable(tableName).catch((err) => null);
  }

  async enforceStrictVPN(vpnIntf) {
    if (!vpnIntf) {
      throw "Interface is not specified";
    }
    const rtId = await this.getRtId(vpnIntf);
    if (!rtId)
      return;
    const rtIdHex = Number(rtId).toString(16);
    const cmd = wrapIptables(`sudo iptables -w -A FW_VPN_CLIENT -m mark --mark 0x${rtIdHex}/0xffff -m set ! --match-set ${ipset.CONSTANTS.IPSET_MONITORED_NET} dst,dst ! -o ${vpnIntf} -j FW_DROP`);
    await execAsync(cmd).catch((err) => {
      log.error(`Failed to enforce strict vpn on ${vpnIntf}`, err);
    });
  }

  async unenforceStrictVPN(vpnIntf) {
    if (!vpnIntf) {
      throw "Interface is not specified";
    }
    const rtId = await this.getRtId(vpnIntf);
    if (!rtId)
      return;
    const rtIdHex = Number(rtId).toString(16);
    const cmd = wrapIptables(`sudo iptables -w -D FW_VPN_CLIENT -m mark --mark 0x${rtIdHex}/0xffff -m set ! --match-set ${ipset.CONSTANTS.IPSET_MONITORED_NET} dst,dst ! -o ${vpnIntf} -j FW_DROP`);
    await execAsync(cmd).catch((err) => {
      log.error(`Failed to unenforce strict vpn on ${vpnIntf}`, err);
      throw err;
    });
  }

  async enforceVPNClientRoutes(remoteIP, vpnIntf, routedSubnets = [], overrideDefaultRoute = true) {
    if (!vpnIntf)
      throw "Interface is not specified";
    const tableName = this._getRoutingTableName(vpnIntf);
    // ensure customized routing table is created
    const rtId = await routing.createCustomizedRoutingTable(tableName);
    await routing.flushRoutingTable(tableName);
    // add policy based rule, the priority 6000 is a bit higher than the firerouter's application defined fwmark
    await routing.createPolicyRoutingRule("all", null, tableName, 6000, `${rtId}/0xffff`);
    if (platform.isFireRouterManaged()) {
      // on firerouter-managed platform, no need to copy main routing table to the vpn client routing table
      // but need to grant access to wan_routable table for packets from vpn interface
      await routing.createPolicyRoutingRule("all", vpnIntf, "wan_routable", 5000, null, 4);
    } else {
      // copy all routes from main routing table on non-firerouter-managed platform
      let cmd = "ip route list";
      if (overrideDefaultRoute)
        // do not copy default route from main routing table
        cmd = "ip route list | grep -v default";
      const routes = await execAsync(cmd);
      await Promise.all(routes.stdout.split('\n').map(async route => {
        if (route.length > 0) {
          cmd = util.format("sudo ip route add %s table %s", route, tableName);
          await execAsync(cmd).catch((err) => {}); // this usually happens when multiple function calls are executed simultaneously. It should have no side effect and will be consistent eventually
        }
      }));
    }
    for (let routedSubnet of routedSubnets) {
      const cidr = ipTool.cidrSubnet(routedSubnet);
      // change subnet to ip route acceptable format
      const formattedSubnet = `${cidr.networkAddress}/${cidr.subnetMaskLength}`;
      await routing.addRouteToTable(formattedSubnet, remoteIP, vpnIntf, tableName).catch((err) => {});
    }
    if (overrideDefaultRoute) {
      // then add remote IP as gateway of default route to vpn client table
      await routing.addRouteToTable("default", remoteIP, vpnIntf, tableName).catch((err) => {}); // this usually happens when multiple function calls are executed simultaneously. It should have no side effect and will be consistent eventually
    }
    // add inbound connmark rule for vpn client interface
    await execAsync(wrapIptables(`sudo iptables -w -t nat -A FW_PREROUTING_VC_INBOUND -i ${vpnIntf} -j CONNMARK --set-xmark ${rtId}/0xffff`)).catch((err) => {
      log.error(`Failed to add VPN client ipv4 inbound connmark rule for ${vpnIntf}`, err.message);
    });
    await execAsync(wrapIptables(`sudo ip6tables -w -t nat -A FW_PREROUTING_VC_INBOUND -i ${vpnIntf} -j CONNMARK --set-xmark ${rtId}/0xffff`)).catch((err) => {
      log.error(`Failed to add VPN client ipv6 inbound connmark rule for ${vpnIntf}`, err.message);
    });
  }

  async flushVPNClientRoutes(vpnIntf) {
    if (!vpnIntf)
      throw "Interface is not specified";
    const tableName = this._getRoutingTableName(vpnIntf);
    const rtId = await routing.createCustomizedRoutingTable(tableName);
    await routing.flushRoutingTable(tableName);
    // remove policy based rule
    await routing.removePolicyRoutingRule("all", null, tableName, 6000, `${rtId}/0xffff`).catch((err) => {
      log.error(`Failed to remove policy routing rule`, err.message);
    });
    await routing.removePolicyRoutingRule("all", vpnIntf, "wan_routable", 5000, null, 4).catch((err) => {
      log.error(`Failed to remove policy routing rule`, err.message);
    });
    // remove inbound connmark rule for vpn client interface
    await execAsync(wrapIptables(`sudo iptables -w -t nat -D FW_PREROUTING_VC_INBOUND -i ${vpnIntf} -j CONNMARK --set-xmark ${rtId}/0xffff`)).catch((err) => {
      log.error(`Failed to remove VPN client ipv4 inbound connmark rule for ${vpnIntf}`, err.message);
    });
    await execAsync(wrapIptables(`sudo ip6tables -w -t nat -D FW_PREROUTING_VC_INBOUND -i ${vpnIntf} -j CONNMARK --set-xmark ${rtId}/0xffff`)).catch((err) => {
      log.error(`Failed to remove VPN client ipv6 inbound connmark rule for ${vpnIntf}`, err.message);
    });
  }

  _getVPNClientIPSetName(vpnIntf) {
    return `vpn_client_${vpnIntf}_set`;
  }

  async enforceDNSRedirect(vpnIntf, dnsServers, remoteIP) {
    if (!vpnIntf || !dnsServers || dnsServers.length == 0)
      return;
    const rtId = await this.getRtId(vpnIntf);
    if (!rtId)
      return;
    const rtIdHex = Number(rtId).toString(16);
    const tableName = this._getRoutingTableName(vpnIntf);
    for (let i in dnsServers) {
      const dnsServer = dnsServers[i];
      // add to vpn client routing table
      if (remoteIP)
        await routing.addRouteToTable(dnsServer, remoteIP, vpnIntf, tableName).catch((err) => {});
      // round robin rule for multiple dns servers
      if (i == 0) {
        // no need to use statistic module for the first rule
        let cmd = wrapIptables(`sudo iptables -w -t nat -I FW_PREROUTING_DNS_VPN_CLIENT -m mark --mark 0x${rtIdHex}/0xffff -p tcp --dport 53 -j DNAT --to-destination ${dnsServer}`);
        await execAsync(cmd).catch((err) => {
          log.error(`Failed to enforce DNS redirect rule: ${cmd}, intf: ${vpnIntf}, dnsServer: ${dnsServer}`, err);
        });
        cmd = wrapIptables(`sudo iptables -w -t nat -I FW_PREROUTING_DNS_VPN_CLIENT -m mark --mark 0x${rtIdHex}/0xffff -p udp --dport 53 -j DNAT --to-destination ${dnsServer}`);
        await execAsync(cmd).catch((err) => {
          log.error(`Failed to enforce DNS redirect rule: ${cmd}, intf: ${vpnIntf}, dnsServer: ${dnsServer}`, err);
        });
      } else {
        let cmd = wrapIptables(`sudo iptables -w -t nat -I FW_PREROUTING_DNS_VPN_CLIENT -m mark --mark 0x${rtIdHex}/0xffff  -p tcp --dport 53 -m statistic --mode nth --every ${Number(i) + 1} --packet 0 -j DNAT --to-destination ${dnsServer}`);
        await execAsync(cmd).catch((err) => {
          log.error(`Failed to enforce DNS redirect rule: ${cmd}, intf: ${vpnIntf}, dnsServer: ${dnsServer}`, err);
        });
        cmd = wrapIptables(`sudo iptables -w -t nat -I FW_PREROUTING_DNS_VPN_CLIENT -m mark --mark 0x${rtIdHex}/0xffff  -p udp --dport 53 -m statistic --mode nth --every ${Number(i) + 1} --packet 0 -j DNAT --to-destination ${dnsServer}`);
        await execAsync(cmd).catch((err) => {
          log.error(`Failed to enforce DNS redirect rule: ${cmd}, intf: ${vpnIntf}, dnsServer: ${dnsServer}`, err);
        });
      }
    }
  }

  async unenforceDNSRedirect(vpnIntf, dnsServers, remoteIP) {
    if (!vpnIntf || !dnsServers || dnsServers.length == 0)
      return;
    const rtId = await this.getRtId(vpnIntf);
    if (!rtId)
      return;
    const rtIdHex = Number(rtId).toString(16);
    const tableName = this._getRoutingTableName(vpnIntf);
    for (let i in dnsServers) {
      const dnsServer = dnsServers[i];
      // remove from vpn client routing table
      if (remoteIP)
        await routing.removeRouteFromTable(dnsServer, remoteIP, vpnIntf, tableName).catch((err) => {});
      // round robin rule for multiple dns servers
      if (i == 0) {
        // no need to use statistic module for the first rule
        let cmd = wrapIptables(`sudo iptables -w -t nat -D FW_PREROUTING_DNS_VPN_CLIENT -m mark --mark 0x${rtIdHex}/0xffff -p tcp --dport 53 -j DNAT --to-destination ${dnsServer}`);
        await execAsync(cmd).catch((err) => {
          log.error(`Failed to enforce DNS redirect rule: ${cmd}, intf: ${vpnIntf}, dnsServer: ${dnsServer}`, err);
        });
        cmd = wrapIptables(`sudo iptables -w -t nat -D FW_PREROUTING_DNS_VPN_CLIENT -m mark --mark 0x${rtIdHex}/0xffff -p udp --dport 53 -j DNAT --to-destination ${dnsServer}`);
        await execAsync(cmd).catch((err) => {
          log.error(`Failed to enforce DNS redirect rule: ${cmd}, intf: ${vpnIntf}, dnsServer: ${dnsServer}`, err);
        });
      } else {
        let cmd = wrapIptables(`sudo iptables -w -t nat -D FW_PREROUTING_DNS_VPN_CLIENT -m mark --mark 0x${rtIdHex}/0xffff -p tcp --dport 53 -m statistic --mode nth --every ${Number(i) + 1} --packet 0 -j DNAT --to-destination ${dnsServer}`);
        await execAsync(cmd).catch((err) => {
          log.error(`Failed to enforce DNS redirect rule: ${cmd}, intf: ${vpnIntf}, dnsServer: ${dnsServer}`, err);
        });
        cmd = wrapIptables(`sudo iptables -w -t nat -D FW_PREROUTING_DNS_VPN_CLIENT -m mark --mark 0x${rtIdHex}/0xffff -p udp --dport 53 -m statistic --mode nth --every ${Number(i) + 1} --packet 0 -j DNAT --to-destination ${dnsServer}`);
        await execAsync(cmd).catch((err) => {
          log.error(`Failed to enforce DNS redirect rule: ${cmd}, intf: ${vpnIntf}, dnsServer: ${dnsServer}`, err);
        });
      }
    }
  }
}

const instance = new VPNClientEnforcer();
module.exports = instance;<|endoftext|>


<commit_before>define(function () {
  return {
    name: "Color fonts",
    description: "Four color font formats are currently in existence: COLR/CPAL, SBIX, SVG-OpenType, and CBDT/CBLC. Support is fragmented. When unsupported, the browser will fall back to regular glyphs in the font. Read about [the differences of the formats](http://pixelambacht.nl/2014/multicolor-fonts/).",
    keywords: ["color", "multicolor"],
    browsers: [
      { name: "ie",      range: "-10", support: "no" },
      { name: "ie",      range: "11-",  support: "yes", note: "Only COLR/CPAL on Windows 8.1+." },
      { name: "edge",    range: "-", support: "no" },
      { name: "chrome",  range: "-",  support: "no" },
      { name: "firefox", range: "-35", support: "no" },
      { name: "firefox", range: "36-",  support: "yes", note: "Only COLR/CPAL and OpenType-SVG." },
      { name: "opera",   range: "-",  support: "no" },
      { name: "safari",  range: "-8", support: "no" },
      { name: "safari",  range: "9-", support: "yes", note: "Only SBIX." },
      { name: "ios",     range: "-8.1", support: "no" },
      { name: "ios",     range: "9.1-", support: "yes", note: "Only Apple's proprietary SBIX." },
      { name: "android", range: "-",  support: "no" }
    ],
    features: [
      {
        name: "SBIX",
        description: "Apple's implementation of color fonts. Uses embedded PNG images to represent the color glyphs.",
        specification: "https://www.microsoft.com/typography/otspec/sbix.htm",
        keywords: ['color', 'multicolor', 'sbix', 'apple'],
        browsers: [
          { name: "ie", range: "-", support: "no" },
          { name: "edge", range: "-", support: "no" },
          { name: "chrome", range: "-", support: "no" },
          { name: "firefox", range: "-", support: "no" },
          { name: "safari", range: "-8", support: "no" },
          { name: "safari", range: "9-", support: "yes" },
          { name: "ios", range: "-8.1", support: "no" },
          { name: "ios", range: "9.1-", support: "yes" },
          { name: "opera", range: "-", support: "no" },
          { name: "android", range: "-", support: "no" }
        ]
      },
      {
        name: "COLR/CPAL",
        description: "Microsoft's color format. Uses layered glyphs to create multicolor ones.",
        specification: "https://www.microsoft.com/typography/otspec/colr.htm",
        keywords: ['color', 'multicolor', 'colr', 'cpal', 'microsoft'],
        browsers: [
          { name: "ie", range: "-10", support: "no" },
          { name: "ie", range: "11-", support: "yes", note: "Only on Windows 8.1 and above." },
          { name: "edge", range: "-", support: "yes" },
          { name: "chrome", range: "-", support: "no" },
          { name: "firefox", range: "-31", support: "no" },
          { name: "firefox", range: "32-", support: "yes" },
          { name: "safari", range: "-", support: "no" },
          { name: "ios", range: "-", support: "no" },
          { name: "opera", range: "-", support: "no" },
          { name: "android", range: "-", support: "no" }
        ]
      },
      {
        name: "OpenType-SVG",
        description: "Mozilla and Adobe's color format. Uses embedded SVG images to represent the color glyphs.",
        specification: "https://www.microsoft.com/typography/otspec/svg.htm",
        keywords: ['color', 'multicolor', 'svg', 'adobe', 'mozilla', 'svg-in-opentype', 'opentype-svg'],
        browsers: [
          { name: "ie", range: "-", support: "no" },
          { name: "edge", range: "-", support: "no" },
          { name: "chrome", range: "-", support: "no" },
          { name: "firefox", range: "-25", support: "no" },
          { name: "firefox", range: "26-", support: "yes" },
          { name: "safari", range: "-", support: "no" },
          { name: "ios", range: "-", support: "no" },
          { name: "opera", range: "-", support: "no" },
          { name: "android", range: "-", support: "no" }
        ]
      },
      {
        name: "CBDT/CBLC",
        description: "Google's CBDT/CBLC font format. Currently only supported on Android when installed as a system font; not available through @font-face. Uses embedded PNG images to represent the color glyphs.",
        specification: "https://www.microsoft.com/typography/otspec/cbdt.htm",
        keywords: ['color', 'multicolor', 'google', 'cbdt', 'cblc'],
        browsers: [
          { name: "ie", range: "-", support: "no" },
          { name: "edge", range: "-", support: "no" },
          { name: "chrome", range: "-", support: "no" },
          { name: "firefox", range: "-", support: "no" },
          { name: "safari", range: "-", support: "no" },
          { name: "ios", range: "-", support: "no" },
          { name: "opera", range: "-", support: "no" },
          { name: "android", range: "-", support: "no" }
        ]
      }
    ]
  };
});
<commit_msg>OpenType-SVG instead of SVG-OpenType.<commit_after>define(function () {
  return {
    name: "Color fonts",
    description: "Four color font formats are currently in existence: COLR/CPAL, SBIX, OpenType-SVG, and CBDT/CBLC. Support is fragmented. When unsupported, the browser will fall back to regular glyphs in the font. Read about [the differences of the formats](http://pixelambacht.nl/2014/multicolor-fonts/).",
    keywords: ["color", "multicolor"],
    browsers: [
      { name: "ie",      range: "-10", support: "no" },
      { name: "ie",      range: "11-",  support: "yes", note: "Only COLR/CPAL on Windows 8.1+." },
      { name: "edge",    range: "-", support: "no" },
      { name: "chrome",  range: "-",  support: "no" },
      { name: "firefox", range: "-35", support: "no" },
      { name: "firefox", range: "36-",  support: "yes", note: "Only COLR/CPAL and OpenType-SVG." },
      { name: "opera",   range: "-",  support: "no" },
      { name: "safari",  range: "-8", support: "no" },
      { name: "safari",  range: "9-", support: "yes", note: "Only SBIX." },
      { name: "ios",     range: "-8.1", support: "no" },
      { name: "ios",     range: "9.1-", support: "yes", note: "Only Apple's proprietary SBIX." },
      { name: "android", range: "-",  support: "no" }
    ],
    features: [
      {
        name: "SBIX",
        description: "Apple's implementation of color fonts. Uses embedded PNG images to represent the color glyphs.",
        specification: "https://www.microsoft.com/typography/otspec/sbix.htm",
        keywords: ['color', 'multicolor', 'sbix', 'apple'],
        browsers: [
          { name: "ie", range: "-", support: "no" },
          { name: "edge", range: "-", support: "no" },
          { name: "chrome", range: "-", support: "no" },
          { name: "firefox", range: "-", support: "no" },
          { name: "safari", range: "-8", support: "no" },
          { name: "safari", range: "9-", support: "yes" },
          { name: "ios", range: "-8.1", support: "no" },
          { name: "ios", range: "9.1-", support: "yes" },
          { name: "opera", range: "-", support: "no" },
          { name: "android", range: "-", support: "no" }
        ]
      },
      {
        name: "COLR/CPAL",
        description: "Microsoft's color format. Uses layered glyphs to create multicolor ones.",
        specification: "https://www.microsoft.com/typography/otspec/colr.htm",
        keywords: ['color', 'multicolor', 'colr', 'cpal', 'microsoft'],
        browsers: [
          { name: "ie", range: "-10", support: "no" },
          { name: "ie", range: "11-", support: "yes", note: "Only on Windows 8.1 and above." },
          { name: "edge", range: "-", support: "yes" },
          { name: "chrome", range: "-", support: "no" },
          { name: "firefox", range: "-31", support: "no" },
          { name: "firefox", range: "32-", support: "yes" },
          { name: "safari", range: "-", support: "no" },
          { name: "ios", range: "-", support: "no" },
          { name: "opera", range: "-", support: "no" },
          { name: "android", range: "-", support: "no" }
        ]
      },
      {
        name: "OpenType-SVG",
        description: "Mozilla and Adobe's color format. Uses embedded SVG images to represent the color glyphs.",
        specification: "https://www.microsoft.com/typography/otspec/svg.htm",
        keywords: ['color', 'multicolor', 'svg', 'adobe', 'mozilla', 'svg-in-opentype', 'opentype-svg'],
        browsers: [
          { name: "ie", range: "-", support: "no" },
          { name: "edge", range: "-", support: "no" },
          { name: "chrome", range: "-", support: "no" },
          { name: "firefox", range: "-25", support: "no" },
          { name: "firefox", range: "26-", support: "yes" },
          { name: "safari", range: "-", support: "no" },
          { name: "ios", range: "-", support: "no" },
          { name: "opera", range: "-", support: "no" },
          { name: "android", range: "-", support: "no" }
        ]
      },
      {
        name: "CBDT/CBLC",
        description: "Google's CBDT/CBLC font format. Currently only supported on Android when installed as a system font; not available through @font-face. Uses embedded PNG images to represent the color glyphs.",
        specification: "https://www.microsoft.com/typography/otspec/cbdt.htm",
        keywords: ['color', 'multicolor', 'google', 'cbdt', 'cblc'],
        browsers: [
          { name: "ie", range: "-", support: "no" },
          { name: "edge", range: "-", support: "no" },
          { name: "chrome", range: "-", support: "no" },
          { name: "firefox", range: "-", support: "no" },
          { name: "safari", range: "-", support: "no" },
          { name: "ios", range: "-", support: "no" },
          { name: "opera", range: "-", support: "no" },
          { name: "android", range: "-", support: "no" }
        ]
      }
    ]
  };
});
<|endoftext|>


<commit_before>/*
  The goal of these helpers is to generate SQL queries
  that are valid for both SQLite and Postgres
*/

import moment from 'moment'

const isLite = knex => {
  return knex.client.config.client === 'sqlite3'
}

module.exports = knex => {
  const dateParse = exp => {
    return isLite(knex) ? knex.raw(`strftime('%Y-%m-%dT%H:%M:%fZ', ${exp})`) : knex.raw(exp)
  }

  const dateFormat = date => {
    const iso = moment(date)
      .toDate()
      .toISOString()
    return dateParse(`'${iso}'`)
  }

  const columnOrDateFormat = colOrDate => {
    const lite = isLite(knex)

    if (colOrDate.sql) {
      return colOrDate.sql
    }

    if (typeof colOrDate === 'string') {
      return lite ? dateParse(colOrDate) : `"${colOrDate}"`
    }

    return dateFormat(colOrDate)
  }

  return {
    isLite: () => isLite(knex),

    // knex's createTableIfNotExists doesn't work with postgres
    // https://github.com/tgriesser/knex/issues/1303
    createTableIfNotExists: (tableName, cb) => {
      return knex.schema.hasTable(tableName).then(exists => {
        if (exists) {
          return
        }
        return knex.schema.createTableIfNotExists(tableName, cb)
      })
    },

    date: {
      format: dateFormat,

      now: () => (isLite(knex) ? knex.raw("strftime('%Y-%m-%dT%H:%M:%fZ', 'now')") : knex.raw('now()')),

      isBefore: (d1, d2) => {
        d1 = columnOrDateFormat(d1)
        d2 = columnOrDateFormat(d2)

        return knex.raw(d1 + ' < ' + d2)
      },

      isAfter: (d1, d2) => {
        d1 = columnOrDateFormat(d1)
        d2 = columnOrDateFormat(d2)

        return knex.raw(d1 + ' > ' + d2)
      },

      isBetween: (d1, d2, d3) => {
        d1 = columnOrDateFormat(d1)
        d2 = columnOrDateFormat(d2)
        d3 = columnOrDateFormat(d3)

        return knex.raw(`${d1} BETWEEN ${d2} AND ${d3}`)
      },

      isSameDay: (d1, d2) => {
        d1 = columnOrDateFormat(d1)
        d2 = columnOrDateFormat(d2)

        return knex.raw(`date(${d1}) = date(${d2})`)
      },

      hourOfDay: date => {
        date = columnOrDateFormat(date)
        return isLite(knex) ? knex.raw(`strftime('%H', ${date})`) : knex.raw(`to_char(${date}, 'HH24')`)
      }
    },

    bool: {
      true: () => (isLite(knex) ? 1 : true),
      false: () => (isLite(knex) ? 0 : false),
      parse: value => (isLite(knex) ? !!value : value)
    },

    json: {
      set: obj => {
        return isLite(knex) ? obj && JSON.stringify(obj) : obj
      },
      get: obj => {
        return isLite(knex) ? obj && JSON.parse(obj) : obj
      }
    }
  }
}
<commit_msg>docs(dbhelpers): documented database helpers<commit_after>/**
 * Helpers for the [Botpress Database]{@link Database} running on [Knex]{@link http://knexjs.org/}
 * @public
 * @module DatabaseHelpers
 * @example
 * import { DatabaseHelpers } from 'botpress'
 * // or
 * const { DatabaseHelpers } from 'botpress'
 *
 * const helpers = DatabaseHelpers(await bp.db.get())
 */

/*
  The goal of these helpers is to generate SQL queries
  that are valid for both SQLite and Postgres
*/

import moment from 'moment'

const isLite = knex => {
  return knex.client.config.client === 'sqlite3'
}

module.exports = knex => {
  const dateParse = exp => {
    return isLite(knex) ? knex.raw(`strftime('%Y-%m-%dT%H:%M:%fZ', ${exp})`) : knex.raw(exp)
  }

  const dateFormat = date => {
    const iso = moment(date)
      .toDate()
      .toISOString()
    return dateParse(`'${iso}'`)
  }

  const columnOrDateFormat = colOrDate => {
    const lite = isLite(knex)

    if (colOrDate.sql) {
      return colOrDate.sql
    }

    if (typeof colOrDate === 'string') {
      return lite ? dateParse(colOrDate) : `"${colOrDate}"`
    }

    return dateFormat(colOrDate)
  }

  return {
    /**
     * Returns whether or not the current database is SQLite
     * @return {Boolean} Returns true if the database is SQLite, false if Postgres
     */
    isLite: () => isLite(knex),

    /**
     * **This is a workaround utility function**
     * knex's createTableIfNotExists doesn't work with postgres
     * https://github.com/tgriesser/knex/issues/1303
     * @param  {String}   tableName Name of the table to create
     * @param  {Function<table>} cb        Callback function. Identical to Knex's callback.
     */
    createTableIfNotExists: (tableName, cb) => {
      return knex.schema.hasTable(tableName).then(exists => {
        if (exists) {
          return
        }
        return knex.schema.createTableIfNotExists(tableName, cb)
      })
    },

    date: {
      format: dateFormat,
      now: () => (isLite(knex) ? knex.raw("strftime('%Y-%m-%dT%H:%M:%fZ', 'now')") : knex.raw('now()')),

      isBefore: (d1, d2) => {
        d1 = columnOrDateFormat(d1)
        d2 = columnOrDateFormat(d2)

        return knex.raw(d1 + ' < ' + d2)
      },

      isAfter: (d1, d2) => {
        d1 = columnOrDateFormat(d1)
        d2 = columnOrDateFormat(d2)

        return knex.raw(d1 + ' > ' + d2)
      },

      isBetween: (d1, d2, d3) => {
        d1 = columnOrDateFormat(d1)
        d2 = columnOrDateFormat(d2)
        d3 = columnOrDateFormat(d3)

        return knex.raw(`${d1} BETWEEN ${d2} AND ${d3}`)
      },

      isSameDay: (d1, d2) => {
        d1 = columnOrDateFormat(d1)
        d2 = columnOrDateFormat(d2)

        return knex.raw(`date(${d1}) = date(${d2})`)
      },

      hourOfDay: date => {
        date = columnOrDateFormat(date)
        return isLite(knex) ? knex.raw(`strftime('%H', ${date})`) : knex.raw(`to_char(${date}, 'HH24')`)
      }
    },

    bool: {
      true: () => (isLite(knex) ? 1 : true),
      false: () => (isLite(knex) ? 0 : false),
      parse: value => (isLite(knex) ? !!value : value)
    },

    json: {
      set: obj => {
        return isLite(knex) ? obj && JSON.stringify(obj) : obj
      },
      get: obj => {
        return isLite(knex) ? obj && JSON.parse(obj) : obj
      }
    }
  }
}
<|endoftext|>


<commit_before>'use strict';

var utils = require('./utils.js');
var _ = require('lodash');

var slugger = require('uslug');

/**
 * Defines a set of functions usable in all swig templates, are merged into context on render
 * @param  {Object}   swig        Swig engine
 */
module.exports.swigFunctions = function(swig) {

  var self = this;

  this.context = {};
  this.data = {};
  this.settings = {};
  this.typeInfo = {};

  this.paginate = false;
  this.curPage = 1;
  this.maxPage = -1;
  this.pageUrl = 'page-';
  this.paginationBaseUrl = null;
  this.cachedData = {};
  this.CURRENT_URL = '/';

  /**
   * Returns a standard url for a given object, only works for standard scaffolding url structure
   * @param  {Object}   object     Object to generate url for
   * @returns {String}   Url for the object passed in
   */
  var url = function(object) {
    if(typeof object === 'string') {
      var types = getTypes(true);

      object = _.find(types, function(type){ return type.name.toLowerCase() == object.toLowerCase() || type.id.toLowerCase() == object.toLowerCase() });
    }

    if(!object) {
      return '';
    }

    if(object.slug) {
      return '/' + object.slug + '/';
    }

    var slug = object.name ? slugger(object.name).toLowerCase() : "";
    var prefix = object._type ? object._type : '';

    var url = '';
    if(prefix) {
      url = '/' + prefix + '/' + slug + '/';
    } else {
      url = '/' + slug + '/';
    }

    return url;
  };

  /**
   * Sets the data set used by all the functions in this class
   * @param  {Object}   data   The data to be used by all functions in this class
   */
  this.setData = function(data) {
    self.cachedData = {};
    self.data = data;
  };

  /**
   * Sets the type info used by all the functions in this class
   * @param  {Object}   typeInfo   The type info to be used by all functions in this class
   */
  this.setTypeInfo = function(typeInfo) {
    self.typeInfo = typeInfo;
  };

  /**
   * Sets the settings used by all the functions in this class
   * @param  {Object}   settings   The settings to be used by all functions in this class
   */
  this.setSettings = function(settings) {
    self.settings = settings;
  };

  /**
   * Returns all content types for a given site
   * @returns  {Array}  An array of type object (slug and name of type)
   */
  var getTypes = function(returnOneOffs) {
    var types = [];

    for(var key in self.typeInfo) {
      if(returnOneOffs || !self.typeInfo[key].oneOff) {

        var slug = key;

        if(self.typeInfo[key] && self.typeInfo[key].customUrls && self.typeInfo[key].customUrls.listUrl) {
          if(!(self.typeInfo[key].customUrls.listUrl === '#')) {
            slug = self.typeInfo[key].customUrls.listUrl;
          }
        }

        types.push({ slug: slug, name: self.typeInfo[key].name, id: key });
      }
    }

    return types;
  };

  /**
   * Returns a published item based off its type and id or a relation string from the CMS
   * @param    {String} Can either be a relation string (from the CMS) or a type name
   * @param    {String} (OPTIONAL) If the first parameter was the type, this must be the ID of the item
   * @returns  {Object} The published item specified by the type/id or relation string passed in
   */
  var getItem = function(type, key, ignorePub) {
    if(!type) {
      return {};
    }

    if(!key) {
      if(Array.isArray(type)) {
        if(type.length > 0) {
          type = type[0];
        } else {
          return {};
        }
      }
      var parts = type.split(" ", 2);
      if(parts.length !== 2) {
        return {};
      }

      type = parts[0];
      key = parts[1];
    }

    if(!self.typeInfo[type]) {
      return {};
    }

    var item = self.data[type][key];

    if(!item) {
      return {};
    }

    if(!ignorePub && !self.typeInfo[type].oneOff) {
      if(!item.publish_date) {
        return {};
      }

      var now = Date.now();
      var pdate = Date.parse(item.publish_date);

      if(pdate > now + (1 * 60 * 1000)) {
        return {};
      }
    }

    var relationshipFields = [];

    if(self.typeInfo[type] && self.typeInfo[type].controls) {
      self.typeInfo[type].controls.forEach(function(control) {
        if(control.controlType === "relation") {
          relationshipFields.push({ name: control.name, isSingle: control.meta.isSingle });
        }
      });
    }

    item = adjustRelationshipFields(relationshipFields, item);

    item._type = type;

    return item;
  };

  /**
   * Returns an array of items from a relation
   * @param    {Array}  An array of relation strings from the CMS
   * @returns  {Array}  All published items specified by relation strings
   */
  var getItems = function(arr) {
    if(!arr) {
      return [];
    }
    var items = [];

    arr.forEach(function(itm) {
      var obj = getItem(itm);
      if(!_.isEmpty(obj)) {
        items.push(getItem(itm));
      }
    });

    return items;
  }

  var generatedSlugs = {};
  var generateSlug = function(value) {
    if(!generatedSlugs[value._type]) {
      generatedSlugs[value._type] = {};
    }

    if(value.slug) {
      generatedSlugs[value._type][value.slug] = true;
      return value.slug;
    }

    var tmpSlug = slugger(value.name).toLowerCase();

    var no = 2;
    while(generatedSlugs[value._type][tmpSlug]) {
      tmpSlug = slugger(value.name).toLowerCase() + '_' + no;
      no++;
    }

    generatedSlugs[value._type][tmpSlug] = true;

    return tmpSlug;
  }

  var adjustRelationshipFields = function(fields, object) {
    fields.forEach(function(field) {
      var desc = Object.getOwnPropertyDescriptor(object, field.name);
      if(desc && desc.get) { // Don't double dip
        return;
      }

      var val = object[field.name];

      if(field.isSingle) {
        Object.defineProperty(object, field.name, {
          enumerable: true,
          configurable: true,
          get: function() {
            if(!val) return val;

            return getItem(val);
          }
        });
      } else {
        Object.defineProperty(object, field.name, {
          enumerable: true,
          configurable: true,
          get: function() {
            if(!val) return val;

            return getItems(val);
          }
        });
      }
    });

    return object;
  }

  /**
   * Returns all the data specified by the arguments
   * @param    {String} Name of type to retrieve data for
   * @param    {String} (optional) Other type to return with this data, can specifiy as many types as needed
   * @returns  {Array}  All items from type (or types)
   */
  var getCombined = function() {
    var names = [].slice.call(arguments, 0);

    if(self.cachedData[names.join(',')])
    {
      return self.cachedData[names.join(',')];
    }

    generatedSlugs = {};
    var data = [];
    names.forEach(function(name) {
      var tempData = self.data[name] || {};

      var relationshipFields = [];

      if(self.typeInfo[name] && self.typeInfo[name].controls) {
        self.typeInfo[name].controls.forEach(function(control) {
          if(control.controlType === "relation") {
            relationshipFields.push({ name: control.name, isSingle: control.meta.isSingle });
          }
        });
      }

      if(self.typeInfo[name] && self.typeInfo[name].oneOff) {
        tempData = adjustRelationshipFields(relationshipFields, tempData);
        data = tempData;
        return;
      }

      tempData = _.omit(tempData, function(value, key) { return key.indexOf('_') === 0; });

      var no = 1;
      // convert it into an array
      tempData = _.map(tempData, function(value, key) {
        var tmpSlug = "";

        value._id = key;
        value._type = name;

        if(value.name)  {
          if(!value.slug) {
            var tmpSlug = generateSlug(value);
            var prefix = '';

            if(self.typeInfo[name] && self.typeInfo[name].customUrls && self.typeInfo[name].customUrls.listUrl) {
              if(!(self.typeInfo[name].customUrls.listUrl === '#')) {
                prefix = self.typeInfo[name].customUrls.listUrl + '/';
              }
            } else {
              prefix = name + '/';
            }

            if(self.typeInfo[name] && self.typeInfo[name].customUrls && self.typeInfo[name].customUrls.individualUrl) {
              prefix += utils.parseCustomUrl(self.typeInfo[name].customUrls.individualUrl, value) + '/';
            }

            prefix += tmpSlug;

            value.slug = prefix;
          }
        }

        value = adjustRelationshipFields(relationshipFields, value);

        return value;
      });
      tempData = _.filter(tempData, function(item) {
        if(!item.publish_date) {
          return false;
        }

        var now = Date.now();
        var pdate = Date.parse(item.publish_date);

        if(pdate > now + (1 * 60 * 1000)) {
          return false;
        }

        return true;
      });

      data = data.concat(tempData);
    });

    self.cachedData[names.join(',')] = data;

    return data;
  };

  var paginate = function(data, perPage, pageName) {
    if(self.curPage === 1 && self.paginate === true)
    {
      throw new Error('Can only paginate one set of data in a template.');
    }

    var items = utils.slice(data, perPage, perPage * (self.curPage-1));
    self.paginate = true;

    if(self.paginationBaseUrl === null) {
      self.paginationBaseUrl = self.CURRENT_URL;
    }

    self.pageUrl = pageName || self.pageUrl;
    self.maxPage = Math.ceil(_(data).size() / perPage);

    return items;
  };

  var getCurPage = function() {
    return self.curPage;
  };

  var getMaxPage = function() {
    return self.maxPage;
  };

  var getPageUrl = function(pageNum) {
    if(pageNum == 1) {
      return self.paginationBaseUrl;
    }

    return self.paginationBaseUrl + self.pageUrl + pageNum + '/';
  };

  var getCurrentUrl = function() {
    return self.CURRENT_URL;
  };

  var getSetting = function(key) {
    if(!self.settings.general) {
      return null;
    }

    return self.settings.general[key];
  };

  var randomElement = function(array) {
    if(!array || !_.isArray(array)) {
      return null;
    }

    var index = [Math.floor(Math.random() * array.length)];
    return array[index];
  };

  var sortItems = function(input, property, reverse) {

    if(_.size(input) === 0) {
      return input;
    }

    var first = input[0];
    var sortProperty = '_sort_' + property;

    if(first[sortProperty]) {
      property = sortProperty;
    }

    if(reverse) {
      return _.sortBy(input, property).reverse();
    }

    return _.sortBy(input, property)
  };

  var nextItem = function(item, sort_name, reverse_sort) {
    var type = item._type;
    var items = getCombined(type);

    if(sort_name) {
      items = sortItems(items, sort_name, reverse_sort);
    }

    var nextItem = null;
    var previousItem = null;

    items.some(function(itm) {
      if(previousItem && previousItem._id == item._id) {
        nextItem = itm;
        return true;
      }

      previousItem = itm;
    });

    return nextItem;
  };

  var prevItem = function(item, sort_name, reverse_sort) {
    var type = item._type;
    var items = getCombined(type);

    if(sort_name) {
      items = sortItems(items, sort_name, reverse_sort);
    }

    var returnItem = null;
    var previousItem = null;

    items.some(function(itm) {
      if(itm._id == item._id) {
        returnItem = previousItem;
        return true;
      }

      previousItem = itm;
    });

    return returnItem;
  };

  var merge = function() {
    var arrs = [].slice.call(arguments, 0);

    var newArr = [];

    arrs.forEach(function(arr) {
      newArr = newArr.concat(arr);
    })

    return newArr;
  }

  // FUNCTIONS USED FOR PAGINATION HELPING, IGNORE FOR MOST CASES
  this.shouldPaginate = function() {
    return self.curPage <= self.maxPage;
  };

  // Reset initial data
  this.init = function() {
    self.paginate = false;
    self.curPage = 1;
    self.pageUrl = 'page-'
    self.maxPage = -1;
    self.paginationBaseUrl = null;
  };

  this.increasePage = function() {
    self.curPage = self.curPage + 1;
  };

  this.setParams = function(params) {
    for(var key in params) {
      self[key] = params[key];
    }
  };

  this.getFunctions = function() {
    var functions = {
      get: getCombined,
      getItem: function(holder) {
        return holder;
      },
      _realGetItem: function(type, key, ignorePub) {
        return getItem(type, key, ignorePub);
      },
      getItems: function(holder) {
        return holder;
      },
      getTypes: getTypes,
      paginate: paginate,
      getCurPage: getCurPage,
      getMaxPage: getMaxPage,
      getPageUrl: getPageUrl,
      url: url,
      getCurrentUrl: getCurrentUrl,
      getSetting: getSetting,
      random: randomElement,
      cmsVersion: 'v2',
      merge: merge,
      nextItem: nextItem,
      prevItem: prevItem
    };

    var types = [];
    for(var type in self.typeInfo) {
      types.push(type);
    }

    var cms = {};

    types.forEach(function(type) {

      Object.defineProperty(cms, type, {
        get: function() { return getCombined(type); },
        enumerable: true,
        configurable: true
      })

    });

    functions['cms'] = cms;

    Object.defineProperty(functions, 'cms_types', {
      get: function() { return getTypes() },
      enumerable: true,
      configurable: true
    })

    return functions;
  };

  return this;
};<commit_msg>options to real get item<commit_after>'use strict';

var utils = require('./utils.js');
var _ = require('lodash');

var slugger = require('uslug');

/**
 * Defines a set of functions usable in all swig templates, are merged into context on render
 * @param  {Object}   swig        Swig engine
 */
module.exports.swigFunctions = function(swig) {

  var self = this;

  this.context = {};
  this.data = {};
  this.settings = {};
  this.typeInfo = {};

  this.paginate = false;
  this.curPage = 1;
  this.maxPage = -1;
  this.pageUrl = 'page-';
  this.paginationBaseUrl = null;
  this.cachedData = {};
  this.CURRENT_URL = '/';

  /**
   * Returns a standard url for a given object, only works for standard scaffolding url structure
   * @param  {Object}   object     Object to generate url for
   * @returns {String}   Url for the object passed in
   */
  var url = function(object) {
    if(typeof object === 'string') {
      var types = getTypes(true);

      object = _.find(types, function(type){ return type.name.toLowerCase() == object.toLowerCase() || type.id.toLowerCase() == object.toLowerCase() });
    }

    if(!object) {
      return '';
    }

    if(object.slug) {
      return '/' + object.slug + '/';
    }

    var slug = object.name ? slugger(object.name).toLowerCase() : "";
    var prefix = object._type ? object._type : '';

    var url = '';
    if(prefix) {
      url = '/' + prefix + '/' + slug + '/';
    } else {
      url = '/' + slug + '/';
    }

    return url;
  };

  /**
   * Sets the data set used by all the functions in this class
   * @param  {Object}   data   The data to be used by all functions in this class
   */
  this.setData = function(data) {
    self.cachedData = {};
    self.data = data;
  };

  /**
   * Sets the type info used by all the functions in this class
   * @param  {Object}   typeInfo   The type info to be used by all functions in this class
   */
  this.setTypeInfo = function(typeInfo) {
    self.typeInfo = typeInfo;
  };

  /**
   * Sets the settings used by all the functions in this class
   * @param  {Object}   settings   The settings to be used by all functions in this class
   */
  this.setSettings = function(settings) {
    self.settings = settings;
  };

  /**
   * Returns all content types for a given site
   * @returns  {Array}  An array of type object (slug and name of type)
   */
  var getTypes = function(returnOneOffs) {
    var types = [];

    for(var key in self.typeInfo) {
      if(returnOneOffs || !self.typeInfo[key].oneOff) {

        var slug = key;

        if(self.typeInfo[key] && self.typeInfo[key].customUrls && self.typeInfo[key].customUrls.listUrl) {
          if(!(self.typeInfo[key].customUrls.listUrl === '#')) {
            slug = self.typeInfo[key].customUrls.listUrl;
          }
        }

        types.push({ slug: slug, name: self.typeInfo[key].name, id: key });
      }
    }

    return types;
  };

  /**
   * Returns a published item based off its type and id or a relation string from the CMS
   * @param    {String} Can either be a relation string (from the CMS) or a type name
   * @param    {String} (OPTIONAL) If the first parameter was the type, this must be the ID of the item
   * @returns  {Object} The published item specified by the type/id or relation string passed in
   */
  var getItem = function(type, key, ignorePub) {
    if(!type) {
      return {};
    }

    if(!key) {
      if(Array.isArray(type)) {
        if(type.length > 0) {
          type = type[0];
        } else {
          return {};
        }
      }
      var parts = type.split(" ", 2);
      if(parts.length !== 2) {
        return {};
      }

      type = parts[0];
      key = parts[1];
    }

    if(!self.typeInfo[type]) {
      return {};
    }

    var item = self.data[type][key];

    if(!item) {
      return {};
    }

    if(!ignorePub && !self.typeInfo[type].oneOff) {
      if(!item.publish_date) {
        return {};
      }

      var now = Date.now();
      var pdate = Date.parse(item.publish_date);

      if(pdate > now + (1 * 60 * 1000)) {
        return {};
      }
    }

    var relationshipFields = [];

    if(self.typeInfo[type] && self.typeInfo[type].controls) {
      self.typeInfo[type].controls.forEach(function(control) {
        if(control.controlType === "relation") {
          relationshipFields.push({ name: control.name, isSingle: control.meta.isSingle });
        }
      });
    }

    item = adjustRelationshipFields(relationshipFields, item);

    item._type = type;

    return item;
  };

  /**
   * Returns an array of items from a relation
   * @param    {Array}  An array of relation strings from the CMS
   * @returns  {Array}  All published items specified by relation strings
   */
  var getItems = function(arr) {
    if(!arr) {
      return [];
    }
    var items = [];

    arr.forEach(function(itm) {
      var obj = getItem(itm);
      if(!_.isEmpty(obj)) {
        items.push(getItem(itm));
      }
    });

    return items;
  }

  var generatedSlugs = {};
  var generateSlug = function(value) {
    if(!generatedSlugs[value._type]) {
      generatedSlugs[value._type] = {};
    }

    if(value.slug) {
      generatedSlugs[value._type][value.slug] = true;
      return value.slug;
    }

    var tmpSlug = slugger(value.name).toLowerCase();

    var no = 2;
    while(generatedSlugs[value._type][tmpSlug]) {
      tmpSlug = slugger(value.name).toLowerCase() + '_' + no;
      no++;
    }

    generatedSlugs[value._type][tmpSlug] = true;

    return tmpSlug;
  }

  var adjustRelationshipFields = function(fields, object) {
    fields.forEach(function(field) {
      var desc = Object.getOwnPropertyDescriptor(object, field.name);
      if(desc && desc.get) { // Don't double dip
        return;
      }

      var val = object[field.name];

      if(field.isSingle) {
        Object.defineProperty(object, field.name, {
          enumerable: true,
          configurable: true,
          get: function() {
            if(!val) return val;

            return getItem(val);
          }
        });
      } else {
        Object.defineProperty(object, field.name, {
          enumerable: true,
          configurable: true,
          get: function() {
            if(!val) return val;

            return getItems(val);
          }
        });
      }
    });

    return object;
  }

  /**
   * Returns all the data specified by the arguments
   * @param    {String} Name of type to retrieve data for
   * @param    {String} (optional) Other type to return with this data, can specifiy as many types as needed
   * @returns  {Array}  All items from type (or types)
   */
  var getCombined = function() {
    var names = [].slice.call(arguments, 0);

    if(self.cachedData[names.join(',')])
    {
      return self.cachedData[names.join(',')];
    }

    generatedSlugs = {};
    var data = [];
    names.forEach(function(name) {
      var tempData = self.data[name] || {};

      var relationshipFields = [];

      if(self.typeInfo[name] && self.typeInfo[name].controls) {
        self.typeInfo[name].controls.forEach(function(control) {
          if(control.controlType === "relation") {
            relationshipFields.push({ name: control.name, isSingle: control.meta.isSingle });
          }
        });
      }

      if(self.typeInfo[name] && self.typeInfo[name].oneOff) {
        tempData = adjustRelationshipFields(relationshipFields, tempData);
        data = tempData;
        return;
      }

      tempData = _.omit(tempData, function(value, key) { return key.indexOf('_') === 0; });

      var no = 1;
      // convert it into an array
      tempData = _.map(tempData, function(value, key) {
        var tmpSlug = "";

        value._id = key;
        value._type = name;

        if(value.name)  {
          if(!value.slug) {
            var tmpSlug = generateSlug(value);
            var prefix = '';

            if(self.typeInfo[name] && self.typeInfo[name].customUrls && self.typeInfo[name].customUrls.listUrl) {
              if(!(self.typeInfo[name].customUrls.listUrl === '#')) {
                prefix = self.typeInfo[name].customUrls.listUrl + '/';
              }
            } else {
              prefix = name + '/';
            }

            if(self.typeInfo[name] && self.typeInfo[name].customUrls && self.typeInfo[name].customUrls.individualUrl) {
              prefix += utils.parseCustomUrl(self.typeInfo[name].customUrls.individualUrl, value) + '/';
            }

            prefix += tmpSlug;

            value.slug = prefix;
          }
        }

        value = adjustRelationshipFields(relationshipFields, value);

        return value;
      });
      tempData = _.filter(tempData, function(item) {
        if(!item.publish_date) {
          return false;
        }

        var now = Date.now();
        var pdate = Date.parse(item.publish_date);

        if(pdate > now + (1 * 60 * 1000)) {
          return false;
        }

        return true;
      });

      data = data.concat(tempData);
    });

    self.cachedData[names.join(',')] = data;

    return data;
  };

  var paginate = function(data, perPage, pageName) {
    if(self.curPage === 1 && self.paginate === true)
    {
      throw new Error('Can only paginate one set of data in a template.');
    }

    var items = utils.slice(data, perPage, perPage * (self.curPage-1));
    self.paginate = true;

    if(self.paginationBaseUrl === null) {
      self.paginationBaseUrl = self.CURRENT_URL;
    }

    self.pageUrl = pageName || self.pageUrl;
    self.maxPage = Math.ceil(_(data).size() / perPage);

    return items;
  };

  var getCurPage = function() {
    return self.curPage;
  };

  var getMaxPage = function() {
    return self.maxPage;
  };

  var getPageUrl = function(pageNum) {
    if(pageNum == 1) {
      return self.paginationBaseUrl;
    }

    return self.paginationBaseUrl + self.pageUrl + pageNum + '/';
  };

  var getCurrentUrl = function() {
    return self.CURRENT_URL;
  };

  var getSetting = function(key) {
    if(!self.settings.general) {
      return null;
    }

    return self.settings.general[key];
  };

  var randomElement = function(array) {
    if(!array || !_.isArray(array)) {
      return null;
    }

    var index = [Math.floor(Math.random() * array.length)];
    return array[index];
  };

  var sortItems = function(input, property, reverse) {

    if(_.size(input) === 0) {
      return input;
    }

    var first = input[0];
    var sortProperty = '_sort_' + property;

    if(first[sortProperty]) {
      property = sortProperty;
    }

    if(reverse) {
      return _.sortBy(input, property).reverse();
    }

    return _.sortBy(input, property)
  };

  var nextItem = function(item, sort_name, reverse_sort) {
    var type = item._type;
    var items = getCombined(type);

    if(sort_name) {
      items = sortItems(items, sort_name, reverse_sort);
    }

    var nextItem = null;
    var previousItem = null;

    items.some(function(itm) {
      if(previousItem && previousItem._id == item._id) {
        nextItem = itm;
        return true;
      }

      previousItem = itm;
    });

    return nextItem;
  };

  var prevItem = function(item, sort_name, reverse_sort) {
    var type = item._type;
    var items = getCombined(type);

    if(sort_name) {
      items = sortItems(items, sort_name, reverse_sort);
    }

    var returnItem = null;
    var previousItem = null;

    items.some(function(itm) {
      if(itm._id == item._id) {
        returnItem = previousItem;
        return true;
      }

      previousItem = itm;
    });

    return returnItem;
  };

  var merge = function() {
    var arrs = [].slice.call(arguments, 0);

    var newArr = [];

    arrs.forEach(function(arr) {
      newArr = newArr.concat(arr);
    })

    return newArr;
  }

  // FUNCTIONS USED FOR PAGINATION HELPING, IGNORE FOR MOST CASES
  this.shouldPaginate = function() {
    return self.curPage <= self.maxPage;
  };

  // Reset initial data
  this.init = function() {
    self.paginate = false;
    self.curPage = 1;
    self.pageUrl = 'page-'
    self.maxPage = -1;
    self.paginationBaseUrl = null;
  };

  this.increasePage = function() {
    self.curPage = self.curPage + 1;
  };

  this.setParams = function(params) {
    for(var key in params) {
      self[key] = params[key];
    }
  };

  this.getFunctions = function() {
    var functions = {
      get: getCombined,
      getItem: function(type, key, ignorePub) {
        if(typeof type === 'string' && key) {
          return getItem(type, key, ignorePub);
        }

        return type;
      },
      _realGetItem: function(type, key, ignorePub) {
        return getItem(type, key, ignorePub);
      },
      getItems: function(holder) {
        return holder;
      },
      getTypes: getTypes,
      paginate: paginate,
      getCurPage: getCurPage,
      getMaxPage: getMaxPage,
      getPageUrl: getPageUrl,
      url: url,
      getCurrentUrl: getCurrentUrl,
      getSetting: getSetting,
      random: randomElement,
      cmsVersion: 'v2',
      merge: merge,
      nextItem: nextItem,
      prevItem: prevItem
    };

    var types = [];
    for(var type in self.typeInfo) {
      types.push(type);
    }

    var cms = {};

    types.forEach(function(type) {

      Object.defineProperty(cms, type, {
        get: function() { return getCombined(type); },
        enumerable: true,
        configurable: true
      })

    });

    functions['cms'] = cms;

    Object.defineProperty(functions, 'cms_types', {
      get: function() { return getTypes() },
      enumerable: true,
      configurable: true
    })

    return functions;
  };

  return this;
};<|endoftext|>


<commit_before>const os = require('os');
const fs = require('fs');
const path = require('path');
const child_process = require('child_process');
const utils = require('../utils.js');
const KiteError = require('../kite-error');

const {STATES} = require('../constants');

const KEY_BAT = path.join(__dirname, 'read-key.bat');
const ARCH_BAT = path.join(__dirname, 'read-arch.bat');
const FALLBACK_INSTALL_PATH = path.join(process.env.ProgramW6432, 'Kite');

function findInstallPath() {
  try {
    const registryPath = String(child_process.execSync(KEY_BAT)).trim();

    return registryPath !== 'not found'
      ? registryPath
      : FALLBACK_INSTALL_PATH;
  } catch (err) {
    return FALLBACK_INSTALL_PATH;
  }
}

const WindowsSupport = {
  RELEASE_URL: 'https://alpha.kite.com/release/dls/windows/current',
  KITE_INSTALLER_PATH: path.join(process.env.TMP, 'Kite.exe'),
  KITE_EXE_PATH: path.join(findInstallPath(), 'kited.exe'),
  SESSION_FILE_PATH: path.join(process.env.LOCALAPPDATA, '.kite', 'session.json'),
  LOCAL_TOKEN_PATH: path.join(process.env.LOCALAPPDATA, '.kite', 'localtoken'),

  get releaseURL() {
    return this.RELEASE_URL;
  },

  get downloadPath() {
    return this.KITE_INSTALLER_PATH;
  },

  get installPath() {
    return this.KITE_EXE_PATH;
  },

  get sessionFilePath() {
    return this.SESSION_FILE_PATH;
  },

  get localTokenPath() {
    return this.LOCAL_TOKEN_PATH;
  },

  isAdmin() {
    try {
      child_process.execSync('net session');
      return true;
    } catch (e) {
      return false;
    }
  },

  arch() {
    return String(child_process.execSync(KEY_BAT)).trim();
  },

  isKiteSupported() {
    return parseFloat(os.release()) >= 6.1 &&
           this.arch() === '64bit';
  },

  isKiteInstalled() {
    return fs.existsSync(this.KITE_EXE_PATH)
      ? Promise.resolve()
      : Promise.reject(new KiteError('bad_state', STATES.UNINSTALLED));
  },

  installKite(opts) {
    opts = opts || {};

    utils.guardCall(opts.onInstallStart);
    return utils.spawnPromise(this.KITE_INSTALLER_PATH)
    .then(() => utils.guardCall(opts.onCopy))
    .then(() => fs.unlinkSync(this.KITE_INSTALLER_PATH))
    .then(() => utils.guardCall(opts.onRemove));
  },

  isKiteRunning() {
    return utils.spawnPromise('tasklist', 'tasklist_error')
    .then(stdout => {
      const procs = stdout.split('\n');
      if (procs.indexOf('kited.exe') === -1) {
        throw new KiteError('bad_state', STATES.INSTALLED);
      }
    });
  },

  runKite() {
    return utils.spawnPromise(this.KITE_EXE_PATH);
  },
};

module.exports = WindowsSupport;
<commit_msg>Fix typo in script name<commit_after>const os = require('os');
const fs = require('fs');
const path = require('path');
const child_process = require('child_process');
const utils = require('../utils.js');
const KiteError = require('../kite-error');

const {STATES} = require('../constants');

const KEY_BAT = path.join(__dirname, 'read-key.bat');
const ARCH_BAT = path.join(__dirname, 'read-arch.bat');
const FALLBACK_INSTALL_PATH = path.join(process.env.ProgramW6432, 'Kite');

function findInstallPath() {
  try {
    const registryPath = String(child_process.execSync(KEY_BAT)).trim();

    return registryPath !== 'not found'
      ? registryPath
      : FALLBACK_INSTALL_PATH;
  } catch (err) {
    return FALLBACK_INSTALL_PATH;
  }
}

const WindowsSupport = {
  RELEASE_URL: 'https://alpha.kite.com/release/dls/windows/current',
  KITE_INSTALLER_PATH: path.join(process.env.TMP, 'Kite.exe'),
  KITE_EXE_PATH: path.join(findInstallPath(), 'kited.exe'),
  SESSION_FILE_PATH: path.join(process.env.LOCALAPPDATA, '.kite', 'session.json'),
  LOCAL_TOKEN_PATH: path.join(process.env.LOCALAPPDATA, '.kite', 'localtoken'),

  get releaseURL() {
    return this.RELEASE_URL;
  },

  get downloadPath() {
    return this.KITE_INSTALLER_PATH;
  },

  get installPath() {
    return this.KITE_EXE_PATH;
  },

  get sessionFilePath() {
    return this.SESSION_FILE_PATH;
  },

  get localTokenPath() {
    return this.LOCAL_TOKEN_PATH;
  },

  isAdmin() {
    try {
      child_process.execSync('net session');
      return true;
    } catch (e) {
      return false;
    }
  },

  arch() {
    return String(child_process.execSync(ARCH_BAT)).trim();
  },

  isKiteSupported() {
    return parseFloat(os.release()) >= 6.1 &&
           this.arch() === '64bit';
  },

  isKiteInstalled() {
    return fs.existsSync(this.KITE_EXE_PATH)
      ? Promise.resolve()
      : Promise.reject(new KiteError('bad_state', STATES.UNINSTALLED));
  },

  installKite(opts) {
    opts = opts || {};

    utils.guardCall(opts.onInstallStart);
    return utils.spawnPromise(this.KITE_INSTALLER_PATH)
    .then(() => utils.guardCall(opts.onCopy))
    .then(() => fs.unlinkSync(this.KITE_INSTALLER_PATH))
    .then(() => utils.guardCall(opts.onRemove));
  },

  isKiteRunning() {
    return utils.spawnPromise('tasklist', 'tasklist_error')
    .then(stdout => {
      const procs = stdout.split('\n');
      if (procs.indexOf('kited.exe') === -1) {
        throw new KiteError('bad_state', STATES.INSTALLED);
      }
    });
  },

  runKite() {
    return utils.spawnPromise(this.KITE_EXE_PATH);
  },
};

module.exports = WindowsSupport;
<|endoftext|>


<commit_before>/**
 * Layout made of proportionally sized columns and rows.
 *
 * @class
 * @extends OO.ui.Layout
 *
 * @constructor
 * @param {OO.ui.PanelLayout[]} panels Panels in the grid
 * @param {Object} [config] Configuration options
 * @cfg {number[]} [widths] Widths of columns as ratios
 * @cfg {number[]} [heights] Heights of rows as ratios
 */
OO.ui.GridLayout = function OoUiGridLayout( panels, config ) {
	var i, len, widths;

	// Config initialization
	config = config || {};

	// Parent constructor
	OO.ui.GridLayout.super.call( this, config );

	// Properties
	this.panels = [];
	this.widths = [];
	this.heights = [];

	// Initialization
	this.$element.addClass( 'oo-ui-gridLayout' );
	for ( i = 0, len = panels.length; i < len; i++ ) {
		this.panels.push( panels[i] );
		this.$element.append( panels[i].$element );
	}
	if ( config.widths || config.heights ) {
		this.layout( config.widths || [ 1 ], config.heights || [ 1 ] );
	} else {
		// Arrange in columns by default
		widths = this.panels.map( function () { return 1; } );
		this.layout( widths, [ 1 ] );
	}
};

/* Setup */

OO.inheritClass( OO.ui.GridLayout, OO.ui.Layout );

/* Events */

/**
 * @event layout
 */

/**
 * @event update
 */

/* Methods */

/**
 * Set grid dimensions.
 *
 * @param {number[]} widths Widths of columns as ratios
 * @param {number[]} heights Heights of rows as ratios
 * @fires layout
 * @throws {Error} If grid is not large enough to fit all panels
 */
OO.ui.GridLayout.prototype.layout = function ( widths, heights ) {
	var x, y,
		xd = 0,
		yd = 0,
		cols = widths.length,
		rows = heights.length;

	// Verify grid is big enough to fit panels
	if ( cols * rows < this.panels.length ) {
		throw new Error( 'Grid is not large enough to fit ' + this.panels.length + 'panels' );
	}

	// Sum up denominators
	for ( x = 0; x < cols; x++ ) {
		xd += widths[x];
	}
	for ( y = 0; y < rows; y++ ) {
		yd += heights[y];
	}
	// Store factors
	this.widths = [];
	this.heights = [];
	for ( x = 0; x < cols; x++ ) {
		this.widths[x] = widths[x] / xd;
	}
	for ( y = 0; y < rows; y++ ) {
		this.heights[y] = heights[y] / yd;
	}
	// Synchronize view
	this.update();
	this.emit( 'layout' );
};

/**
 * Update panel positions and sizes.
 *
 * @fires update
 */
OO.ui.GridLayout.prototype.update = function () {
	var x, y, panel, width, height, dimensions,
		i = 0,
		top = 0,
		left = 0,
		cols = this.widths.length,
		rows = this.heights.length;

	for ( y = 0; y < rows; y++ ) {
		height = this.heights[y];
		for ( x = 0; x < cols; x++ ) {
			width = this.widths[x];
			panel = this.panels[i];
			dimensions = {
				width: Math.round( width * 100 ) + '%',
				height: Math.round( height * 100 ) + '%',
				top: Math.round( top * 100 ) + '%'
			};
			// If RTL, reverse:
			if ( OO.ui.Element.getDir( this.$.context ) === 'rtl' ) {
				dimensions.right = Math.round( left * 100 ) + '%';
			} else {
				dimensions.left = Math.round( left * 100 ) + '%';
			}
			// HACK: Work around IE bug by setting visibility: hidden; if width or height is zero
			if ( width === 0 || height === 0 ) {
				dimensions.visibility = 'hidden';
			}
			panel.$element.css( dimensions );
			i++;
			left += width;
		}
		top += height;
		left = 0;
	}

	this.emit( 'update' );
};

/**
 * Get a panel at a given position.
 *
 * The x and y position is affected by the current grid layout.
 *
 * @param {number} x Horizontal position
 * @param {number} y Vertical position
 * @return {OO.ui.PanelLayout} The panel at the given postion
 */
OO.ui.GridLayout.prototype.getPanel = function ( x, y ) {
	return this.panels[ ( x * this.widths.length ) + y ];
};
<commit_msg>GridLayout: Set dimensions.visibility = '' again if we have a height and a width<commit_after>/**
 * Layout made of proportionally sized columns and rows.
 *
 * @class
 * @extends OO.ui.Layout
 *
 * @constructor
 * @param {OO.ui.PanelLayout[]} panels Panels in the grid
 * @param {Object} [config] Configuration options
 * @cfg {number[]} [widths] Widths of columns as ratios
 * @cfg {number[]} [heights] Heights of rows as ratios
 */
OO.ui.GridLayout = function OoUiGridLayout( panels, config ) {
	var i, len, widths;

	// Config initialization
	config = config || {};

	// Parent constructor
	OO.ui.GridLayout.super.call( this, config );

	// Properties
	this.panels = [];
	this.widths = [];
	this.heights = [];

	// Initialization
	this.$element.addClass( 'oo-ui-gridLayout' );
	for ( i = 0, len = panels.length; i < len; i++ ) {
		this.panels.push( panels[i] );
		this.$element.append( panels[i].$element );
	}
	if ( config.widths || config.heights ) {
		this.layout( config.widths || [ 1 ], config.heights || [ 1 ] );
	} else {
		// Arrange in columns by default
		widths = this.panels.map( function () { return 1; } );
		this.layout( widths, [ 1 ] );
	}
};

/* Setup */

OO.inheritClass( OO.ui.GridLayout, OO.ui.Layout );

/* Events */

/**
 * @event layout
 */

/**
 * @event update
 */

/* Methods */

/**
 * Set grid dimensions.
 *
 * @param {number[]} widths Widths of columns as ratios
 * @param {number[]} heights Heights of rows as ratios
 * @fires layout
 * @throws {Error} If grid is not large enough to fit all panels
 */
OO.ui.GridLayout.prototype.layout = function ( widths, heights ) {
	var x, y,
		xd = 0,
		yd = 0,
		cols = widths.length,
		rows = heights.length;

	// Verify grid is big enough to fit panels
	if ( cols * rows < this.panels.length ) {
		throw new Error( 'Grid is not large enough to fit ' + this.panels.length + 'panels' );
	}

	// Sum up denominators
	for ( x = 0; x < cols; x++ ) {
		xd += widths[x];
	}
	for ( y = 0; y < rows; y++ ) {
		yd += heights[y];
	}
	// Store factors
	this.widths = [];
	this.heights = [];
	for ( x = 0; x < cols; x++ ) {
		this.widths[x] = widths[x] / xd;
	}
	for ( y = 0; y < rows; y++ ) {
		this.heights[y] = heights[y] / yd;
	}
	// Synchronize view
	this.update();
	this.emit( 'layout' );
};

/**
 * Update panel positions and sizes.
 *
 * @fires update
 */
OO.ui.GridLayout.prototype.update = function () {
	var x, y, panel, width, height, dimensions,
		i = 0,
		top = 0,
		left = 0,
		cols = this.widths.length,
		rows = this.heights.length;

	for ( y = 0; y < rows; y++ ) {
		height = this.heights[y];
		for ( x = 0; x < cols; x++ ) {
			width = this.widths[x];
			panel = this.panels[i];
			dimensions = {
				width: Math.round( width * 100 ) + '%',
				height: Math.round( height * 100 ) + '%',
				top: Math.round( top * 100 ) + '%'
			};
			// If RTL, reverse:
			if ( OO.ui.Element.getDir( this.$.context ) === 'rtl' ) {
				dimensions.right = Math.round( left * 100 ) + '%';
			} else {
				dimensions.left = Math.round( left * 100 ) + '%';
			}
			// HACK: Work around IE bug by setting visibility: hidden; if width or height is zero
			if ( width === 0 || height === 0 ) {
				dimensions.visibility = 'hidden';
			} else {
				dimensions.visibility = '';
			}
			panel.$element.css( dimensions );
			i++;
			left += width;
		}
		top += height;
		left = 0;
	}

	this.emit( 'update' );
};

/**
 * Get a panel at a given position.
 *
 * The x and y position is affected by the current grid layout.
 *
 * @param {number} x Horizontal position
 * @param {number} y Vertical position
 * @return {OO.ui.PanelLayout} The panel at the given postion
 */
OO.ui.GridLayout.prototype.getPanel = function ( x, y ) {
	return this.panels[ ( x * this.widths.length ) + y ];
};
<|endoftext|>


<commit_before>"use strict";
var currentDate = new Date(),
    today =  new Date(),
    inPast = false,
    currentModel;

// initialize Hoodie
var hoodie  = new Hoodie();

// start appcache
// hoodie.appCache.start();

// bind main buttons
var buttonIds = [
  "#workout",
  "#abs",
  "#vegetable"
];
buttonIds.forEach(function(id){
  $(id).on('click', function(){
    $(this).toggleClass('clicked');
    if (currentModel[id.slice(1,id.length)]){
      currentModel[id.slice(1,id.length)] = false;
    }else{
      currentModel[id.slice(1,id.length)] = true;
    }
    hoodie.store.update('tracker',currentModel.id,currentModel);
  });
});

/*********************
 * Utility functions *
 *********************/

var updateDate = function updateDate(d){
  $('#date').text([monthHash[d.getMonth()],d.getDate()].join(" "));
  if (today.getDate() - d.getDate() === 0){
    inPast = false;
    $('#forwardButton').animate({'opacity':0},300);
  }else{
    inPast = true;
    $('#forwardButton').animate({'opacity':1},300);
  }

  getCurrentModel(currentDate);
}

var updateUIstate = function updateUIstate(){
  buttonIds.forEach(function(id){
    $(id).removeClass('clicked');
    if (currentModel[id.slice(1,id.length)]){
      $(id).addClass('clicked');
    }
  });
}

/***************
 * data model  *
 ***************/

var getCurrentModel = function getCurrentModel(date) {

  var modelId = [date.getMonth(),date.getDate(),date.getFullYear()].join(''),
      modelPrototype = {id: modelId, workout:false,abs:false,vegetable:false};

  hoodie.store.find('tracker',modelId)
    .done(function(model){
      currentModel = model;
      updateUIstate();
    })
    .fail(function(){
      console.log("failed to find today's model. Adding it now");
      hoodie.store.add('tracker', modelPrototype)
        .done(function(model){
          console.log('stored model');
          currentModel = model;
          updateUIstate();
        })
        .fail(function(e){
          console.log('failed to store model');
          console.log(e);
        });
    });

}

getCurrentModel(currentDate);

// bind date buttons

$('#forwardButton').css('opacity',0);
var monthHash = {
  0: 'January',
  1: 'February',
  2: 'March',
  3: 'April',
  4: 'May',
  5: 'June',
  6: 'July',
  7: 'August',
  8: 'September',
  9: 'October',
  10: 'November',
  11: 'December'
}

$('#backButton').on('click',function(){
  currentDate.setDate(currentDate.getDate()-1);
  updateDate(currentDate);
});

$('#forwardButton').on('click',function(){
  if (inPast){
    currentDate.setDate(currentDate.getDate()+1);
    updateDate(currentDate);
  }
});

$('#date').on('click',function(){
  currentDate.setDate(today.getDate());
  updateDate(today);
});

updateDate(currentDate);

/*********************
 * signIn and signUp *
 *********************/
$('#signInForm').hide();
$('#mainContainer').hide();
if (hoodie.account.username) {
  // user is signed in
  console.log(hoodie.account.username);
  $('#signInForm').hide();
  $('#mainContainer').show();
  $('#mainContainer').animate({opacity:1},600);
} else {
  // user is anonymous
  console.log('You are not logged in!');
  $('#signInForm').show();
  $('#signInForm').animate({opacity:1},600);
  $('#mainContainer').hide();
}

$('#signInForm').submit(function (event) {
  event.preventDefault();
  var username = $('#username').val().toLowerCase();
  var password = $('#password').val().toLowerCase();
  hoodie.account.signIn(username, password)
    .done(function(){
      console.log('login succeeded');
      $('#signInForm').hide();
      $('#mainContainer').show();
    })
    .fail(function(){
      console.log('failed login, attempting to create account');
      hoodie.account.signUp(username, password)
      .done(function(){
        $('#signInForm').hide();
        $('#mainContainer').show();
        console.log('created account');
      })
      .fail(function(){
        console.log('failed to create account');
      });
    });
});
<commit_msg>vegetables image<commit_after>"use strict";
var currentDate = new Date(),
    today =  new Date(),
    inPast = false,
    currentModel;

// initialize Hoodie
var hoodie  = new Hoodie();

// start appcache
hoodie.appCache.start();

// bind main buttons
var buttonIds = [
  "#workout",
  "#abs",
  "#vegetable"
];
buttonIds.forEach(function(id){
  $(id).on('click', function(){
    $(this).toggleClass('clicked');
    if (currentModel[id.slice(1,id.length)]){
      currentModel[id.slice(1,id.length)] = false;
    }else{
      currentModel[id.slice(1,id.length)] = true;
    }
    hoodie.store.update('tracker',currentModel.id,currentModel);
  });
});

/*********************
 * Utility functions *
 *********************/

var updateDate = function updateDate(d){
  $('#date').text([monthHash[d.getMonth()],d.getDate()].join(" "));
  if (today.getDate() - d.getDate() === 0){
    inPast = false;
    $('#forwardButton').animate({'opacity':0},300);
  }else{
    inPast = true;
    $('#forwardButton').animate({'opacity':1},300);
  }

  getCurrentModel(currentDate);
}

var updateUIstate = function updateUIstate(){
  buttonIds.forEach(function(id){
    $(id).removeClass('clicked');
    if (currentModel[id.slice(1,id.length)]){
      $(id).addClass('clicked');
    }
  });
}

/***************
 * data model  *
 ***************/

var getCurrentModel = function getCurrentModel(date) {

  var modelId = [date.getMonth(),date.getDate(),date.getFullYear()].join(''),
      modelPrototype = {id: modelId, workout:false,abs:false,vegetable:false};

  hoodie.store.find('tracker',modelId)
    .done(function(model){
      currentModel = model;
      updateUIstate();
    })
    .fail(function(){
      console.log("failed to find today's model. Adding it now");
      hoodie.store.add('tracker', modelPrototype)
        .done(function(model){
          console.log('stored model');
          currentModel = model;
          updateUIstate();
        })
        .fail(function(e){
          console.log('failed to store model');
          console.log(e);
        });
    });

}

getCurrentModel(currentDate);

// bind date buttons

$('#forwardButton').css('opacity',0);
var monthHash = {
  0: 'January',
  1: 'February',
  2: 'March',
  3: 'April',
  4: 'May',
  5: 'June',
  6: 'July',
  7: 'August',
  8: 'September',
  9: 'October',
  10: 'November',
  11: 'December'
}

$('#backButton').on('click',function(){
  currentDate.setDate(currentDate.getDate()-1);
  updateDate(currentDate);
});

$('#forwardButton').on('click',function(){
  if (inPast){
    currentDate.setDate(currentDate.getDate()+1);
    updateDate(currentDate);
  }
});

$('#date').on('click',function(){
  currentDate.setDate(today.getDate());
  updateDate(today);
});

updateDate(currentDate);

/*********************
 * signIn and signUp *
 *********************/
$('#signInForm').hide();
$('#mainContainer').hide();
if (hoodie.account.username) {
  // user is signed in
  console.log(hoodie.account.username);
  $('#signInForm').hide();
  $('#mainContainer').show();
  $('#mainContainer').animate({opacity:1},600);
} else {
  // user is anonymous
  console.log('You are not logged in!');
  $('#signInForm').show();
  $('#signInForm').animate({opacity:1},600);
  $('#mainContainer').hide();
}

$('#signInForm').submit(function (event) {
  event.preventDefault();
  var username = $('#username').val().toLowerCase();
  var password = $('#password').val().toLowerCase();
  hoodie.account.signIn(username, password)
    .done(function(){
      console.log('login succeeded');
      $('#signInForm').hide();
      $('#mainContainer').show();
    })
    .fail(function(){
      console.log('failed login, attempting to create account');
      hoodie.account.signUp(username, password)
      .done(function(){
        $('#signInForm').hide();
        $('#mainContainer').show();
        console.log('created account');
      })
      .fail(function(){
        console.log('failed to create account');
      });
    });
});
<|endoftext|>