<commit_before>'use strict';

const Result = require('./result');

const toType = function (obj) {
    // toType credit goes to Angus Croll @ https://javascriptweblog.wordpress.com/2011/08/08/fixing-the-javascript-typeof-operator/
    return ({}).toString.call(obj).match(/\s([a-zA-Z]+)/)[1].toLowerCase();
};

class Schema {
  constructor(props) {
    //Check prop syntax for errors
    for (let prop in props) {
      if (toType(props[prop]) !== "object") {
        throw new Error("Invalid porperty description at: " + prop);
      }
      if (!props[prop].type) throw new Error("Type is required @: " + prop);
    }
    this.props = props;
    this.messages = {
      type: "Invalid type",
      arrayError: "Array is invalid"
    };
  }

  getRequiredProps() {
    let props = [];
    for (let prop in this.props) {
      let optional = this.props[prop].optional;
      if (!optional) {
        props.push(prop);
      }
    }
    return props;
  }

  _validateArray(array, propDef, propName) {
    let errors = [];
    let type = propDef.type[0];
    let success = true;
    array.forEach((val) => {
      if (toType(val) !== type && type !== "any") {
        success = false;
      } else if (propDef.subRules) {
        propDef.subRules.forEach((rule, index) => {
          let result = rule(val, type, propName);
          if (result instanceof Error) {
            errors.push(result.message);
          } else if (result === false) {
            errors.push("fails against rule at index: " + index);
          }
        });
      }
    });

    if (!success) {
      errors.push("Error with " + propName + ": " + this.messages.arrayType);
    }
  }

  _validateObjProp(val, propDef, propName) {
    let errors = [];
    if (propDef.type instanceof Schema) {
      let result = propDef.type.validate(val);
      return result.errors ? result.errors : [];
    }

    // Check type, immediatley return if there is an error here
    if (toType(val) !== propDef.type && propDef.type !== "any") {
      return "Error with " + propName + ": " + this.messages.type;
    }

    // Special validation for arrays
    if (toType(val) === "array") {
       errors = errors.concat(this._validateArray(val, propDef, propName));
    }

    // Run rule functions
    if (propDef.rules) {
      propDef.rules.forEach((rule) => {
        let result = rule(val, propDef.type, propName);
        if (result instanceof Error) {
          errors.push(result.message);
        }
      });
    }

    return errors;
  }

  validate(obj) {
    let requiredProps = this.getRequiredProps();
    let errors = [];
    let satisfiedProps = 0;

    // Make sure all requiredProps are set
    for (let prop of requiredProps) {
      if (obj[prop] === undefined) {
        errors.push(prop + " is required");
      }
    }

    // Make sure all props are valid
    for (let objProp in obj) {
      if (!(objProp in this.props)) {
        errors.push(objProp + " not in schema rules");
      } else {
        let objPropErrors = this._validateObjProp(obj[objProp], this.props[objProp], objProp);
        errors = errors.concat(objPropErrors);
      }
    }

    return new Result(errors);
  }
}

const rules = {
  messages: {
    max: "Exceeds maximum allowable",
    min: "Does not meet minimum required",
    regex: "Fails regex statement test"
  },
  max: function(maxNum) {
    return function(val, message) {
      let success;
      if (toType(val) == "number") {
        success = val < maxNum;
      } else {
        if (val.length === undefined) {
          throw new Error("Value cannot be compared");
        }
        success = val.length < maxNum;
      }
      if (!success) return new Error(message || this.messages.max);
    };
  },
  min: function(minNum) {
    return function(val, message) {
      let success;
      if (toType(val) == "number") {
        success = val > minNum;
      } else {
        if (val.length === undefined) {
          throw new Error("Value cannot be compared");
        }
        success = val.length > minNum;
      }
      if (!success) return new Error(message || this.messages.min);
    };
  },
  regex: function(regStmnt) {
    return function(str, message) {
      let success = regStmnt.test(str);
      if (!success) return new Error(message || this.messages.regex);
    };
  }
};

exports.Schema = Schema;
exports.rules = rules;
<commit_msg>Arrays can be of type schema now<commit_after>'use strict';

const Result = require('./result');

const toType = function (obj) {
    // toType credit goes to Angus Croll @ https://javascriptweblog.wordpress.com/2011/08/08/fixing-the-javascript-typeof-operator/
    return ({}).toString.call(obj).match(/\s([a-zA-Z]+)/)[1].toLowerCase();
};

class Schema {
  constructor(props) {
    //Check prop syntax for errors
    for (let prop in props) {
      if (toType(props[prop]) !== "object") {
        throw new Error("Invalid porperty description at: " + prop);
      }
      if (!props[prop].type) throw new Error("Type is required @: " + prop);
    }
    this.props = props;
    this.messages = {
      type: "Invalid type",
      arrayError: "Array is invalid"
    };
  }

  getRequiredProps() {
    let props = [];
    for (let prop in this.props) {
      let optional = this.props[prop].optional;
      if (!optional) {
        props.push(prop);
      }
    }
    return props;
  }

  _validateArray(array, propDef, propName) {
    let errors = [];
    let type = propDef.type[0];
    let success = true;
    array.forEach((val) => {
      if (type instanceof Schema) {
        let result = type.validate(val);
        return result.errors ? result.errors : [];
      } else if (toType(val) !== type && type !== "any") {
        success = false;
      } else if (propDef.subRules) {
        propDef.subRules.forEach((rule, index) => {
          let result = rule(val, type, propName);
          if (result instanceof Error) {
            errors.push(result.message);
          } else if (result === false) {
            errors.push("fails against rule at index: " + index);
          }
        });
      }
    });

    if (!success) {
      errors.push("Error with " + propName + ": " + this.messages.arrayType);
    }
  }

  _validateObjProp(val, propDef, propName) {
    let errors = [];
    if (propDef.type instanceof Schema) {
      let result = propDef.type.validate(val);
      return result.errors ? result.errors : [];
    }

    // Check type, immediatley return if there is an error here
    if (toType(val) !== propDef.type && propDef.type !== "any") {
      return "Error with " + propName + ": " + this.messages.type;
    }

    // Special validation for arrays
    if (toType(val) === "array") {
       errors = errors.concat(this._validateArray(val, propDef, propName));
    }

    // Run rule functions
    if (propDef.rules) {
      propDef.rules.forEach((rule) => {
        let result = rule(val, propDef.type, propName);
        if (result instanceof Error) {
          errors.push(result.message);
        }
      });
    }

    return errors;
  }

  validate(obj) {
    let requiredProps = this.getRequiredProps();
    let errors = [];
    let satisfiedProps = 0;

    // Make sure all requiredProps are set
    for (let prop of requiredProps) {
      if (obj[prop] === undefined) {
        errors.push(prop + " is required");
      }
    }

    // Make sure all props are valid
    for (let objProp in obj) {
      if (!(objProp in this.props)) {
        errors.push(objProp + " not in schema rules");
      } else {
        let objPropErrors = this._validateObjProp(obj[objProp], this.props[objProp], objProp);
        errors = errors.concat(objPropErrors);
      }
    }

    return new Result(errors);
  }
}

const rules = {
  messages: {
    max: "Exceeds maximum allowable",
    min: "Does not meet minimum required",
    regex: "Fails regex statement test"
  },
  max: function(maxNum) {
    return function(val, message) {
      let success;
      if (toType(val) == "number") {
        success = val < maxNum;
      } else {
        if (val.length === undefined) {
          throw new Error("Value cannot be compared");
        }
        success = val.length < maxNum;
      }
      if (!success) return new Error(message || this.messages.max);
    };
  },
  min: function(minNum) {
    return function(val, message) {
      let success;
      if (toType(val) == "number") {
        success = val > minNum;
      } else {
        if (val.length === undefined) {
          throw new Error("Value cannot be compared");
        }
        success = val.length > minNum;
      }
      if (!success) return new Error(message || this.messages.min);
    };
  },
  regex: function(regStmnt) {
    return function(str, message) {
      let success = regStmnt.test(str);
      if (!success) return new Error(message || this.messages.regex);
    };
  }
};

exports.Schema = Schema;
exports.rules = rules;
<|endoftext|>


<commit_before>(function () { "use strict";

function parentPath(x) {
	var match = x.match(/((\/[\w_-]+)+)\/[\w_-]+$/);
	return match ? match[1] : '/';
}

var _ = require('underscore');
var util = require('util');
var assert = require('assert');
var buscomponent = require('./buscomponent.js');

function SchoolsDB () {
}

util.inherits(SchoolsDB, buscomponent.BusComponent);

function _reqschooladm (f, soft, scdb, status) {
	soft = soft || false;

	return function(query, ctx, cb) {
		var forward = _.bind(function() { return _.bind(f, this)(query, ctx, cb); }, this);

		if (soft && !query.schoolid)
			return forward();

		var lsa = null;
		if (this && this.bus) lsa = this;
		if (scdb && scdb.bus) lsa = scdb;

		assert.ok(lsa);

		lsa.request({name: 'isSchoolAdmin', ctx: ctx, status: status, schoolid: query.schoolid}, function(ok, schoolid) {
			if (!ok)
				return cb('permission-denied');

			query.schoolid = schoolid;
			forward();
		});
	};
}

SchoolsDB.prototype.isSchoolAdmin = buscomponent.provide('isSchoolAdmin', ['ctx', 'status', 'schoolid', 'reply'],
	function(ctx, status, schoolid, cb)
{
	var self = this;

	(parseInt(schoolid) == schoolid ? function(cont) { cont(); } : function(cont) {
		ctx.query('SELECT id FROM schools WHERE ? IN (id, name, path)', [schoolid], function(res) {
			if (res.length == 0)
				return cb(false, null);

			assert.equal(res.length, 1);

			schoolid = res[0].id;
			cont();
		});
	})(function() {
		if (ctx.access.has('schooldb'))
			return cb(true, schoolid);

		status = status || ['admin', 'xadmin'];

		self.loadSchoolAdmins(schoolid, ctx, function(admins) {
			cb(_.chain(admins).filter(function(a) { return status.indexOf(a.status) != -1 && a.adminid == ctx.user.id; }).value().length != 0, schoolid);
		});
	});
});

SchoolsDB.prototype.loadSchoolAdmins = function(schoolid, ctx, cb) {
	ctx.query('SELECT sa.schoolid AS schoolid, sa.uid AS adminid, sa.status AS status, users.name AS adminname ' +
		'FROM schools AS c ' +
		'JOIN schools AS p ON c.path LIKE CONCAT(p.path, "%") OR p.id = c.id ' +
		'JOIN schooladmins AS sa ON sa.schoolid = p.id ' +
		'JOIN users ON users.id = sa.uid ' +
		'WHERE c.id = ?', [schoolid], cb);
};

SchoolsDB.prototype.loadSchoolInfo = function(lookfor, ctx, cfg, cb) {
	var self = this;

	ctx.query('SELECT schools.id, schools.name, schools.path, descpage, config, eventid, type, targetid, time, srcuser, url AS banner '+
		'FROM schools ' +
		'LEFT JOIN events ON events.targetid = schools.id AND events.type = "school-create" ' +
		'LEFT JOIN httpresources ON httpresources.groupassoc = schools.id AND httpresources.role = "schools.banner" ' +
		'WHERE ? IN (schools.id, schools.path, schools.name) ' +
		'LIMIT 1', [lookfor], function(res) {
		if (res.length == 0)
			return cb('get-school-info-notfound');

		var s = res[0];
		s.parentPath = null;

		assert.ok(s.eventid);

		if (s.config == '')
			s.config = {};
		else
			s.config = JSON.parse(s.config);

		assert.ok(s.config);

		self.loadSchoolAdmins(s.id, ctx, function(admins) {
			s.admins = admins;

			ctx.query('SELECT * FROM schools AS c WHERE c.path LIKE ?', [s.path + '/%'], function(subschools) {
			ctx.query('SELECT COUNT(uid) AS usercount ' +
				'FROM schoolmembers AS sm '+
				'LEFT JOIN schools AS c ON sm.schoolid = c.id ' +
				'LEFT JOIN schools AS p ON c.path LIKE CONCAT(p.path, "/%") OR p.id = c.id ' +
				'WHERE p.id = ?', [s.id], function(usercount) {
			ctx.query('SELECT c.*,u.name AS username,u.id AS uid, url AS profilepic, trustedhtml ' +
				'FROM ecomments AS c '+
				'LEFT JOIN users AS u ON c.commenter = u.id ' +
				'LEFT JOIN httpresources ON httpresources.user = c.commenter AND httpresources.role = "profile.image" '+
				'WHERE c.eventid = ?',
				[s.eventid],
				function(comments) {
				s.comments = comments;
				s.subschools = subschools;
				s.usercount = usercount[0].usercount;

				ctx.query('SELECT oh.stocktextid AS stockid, oh.stockname, ' +
					'SUM(ABS(money)) AS moneysum, ' +
					'SUM(ABS(money) / (UNIX_TIMESTAMP() - buytime + 300)) AS wsum ' +
					'FROM orderhistory AS oh ' +
					'JOIN schoolmembers AS sm ON sm.uid = oh.userid AND sm.jointime < oh.buytime AND sm.schoolid = ? ' +
					'GROUP BY stocktextid ORDER BY wsum DESC LIMIT 10', [s.id], function(popular) {
					if (s.path.replace(/[^\/]/g, '').length != 1) { // need higher-level
						s.parentPath = parentPath(s.path);
						self.loadSchoolInfo(s.parentPath, ctx.user, ctx.access, cfg, function(code, result) {
							assert.equal(code, 'get-school-info-success');

							s.parentSchool = result;

							s.config = _.defaults(s.config, s.parentSchool.config, cfg.schoolConfigDefaults);

							cb('get-school-info-success', s);
						});
					} else {
						s.config = _.defaults(s.config, cfg.schoolConfigDefaults);

						cb('get-school-info-success', s);
					}
				});
			});
			});
			});
		});
	});
};

SchoolsDB.prototype.getSchoolInfo = buscomponent.provideQT('client-get-school-info', function(query, ctx, cb) {
	var self = this;

	self.getServerConfig(function(cfg) {
		self.loadSchoolInfo(query.lookfor, ctx, cfg, function(code, result) {
			cb(code, {'result': result});
		});
	});
});

SchoolsDB.prototype.schoolExists = buscomponent.provideQT('client-school-exists', function(query, ctx, cb) {
	ctx.query('SELECT path FROM schools WHERE ? IN (id, path, name)', [query.lookfor], function(res) {
		cb('school-exists-success', {exists: res.length > 0, path: res.length > 0 ? res[0].path : null});
	});
});

SchoolsDB.prototype.changeDescription = buscomponent.provideQT('client-school-change-description', _reqschooladm(function(query, ctx, cb) {
	ctx.query('UPDATE schools SET descpage = ? WHERE id = ?', [query.descpage, query.schoolid], function() {
		cb('school-change-description-success');
	});
}));

SchoolsDB.prototype.changeMemberStatus = buscomponent.provideQT('client-school-change-member-status', _reqschooladm(function(query, ctx, cb) {
	ctx.query('UPDATE schoolmembers SET pending = 0 WHERE schoolid = ? AND uid = ?', [query.schoolid, query.uid], function() {
		if (query.status == 'member') {
			ctx.query('DELETE FROM schooladmins WHERE uid = ? AND schoolid = ?', [query.uid, query.schoolid], function() {
				cb('school-change-member-status-success');
			});
		} else {
			ctx.query('REPLACE INTO schooladmins (schoolid, uid, status) VALUES(?, ?, ?)', [query.schoolid, query.uid, query.status], function() {
				cb('school-change-member-status-success');
			});
		}
	});
}));

SchoolsDB.prototype.deleteComment = buscomponent.provideQT('client-school-delete-comment', _reqschooladm(function(query, ctx, cb) {
	var self = this;

	ctx.query('SELECT c.commentid AS cid FROM ecomments AS c ' +
		'JOIN events AS e ON e.eventid = c.eventid ' +
		'WHERE c.commentid = ? AND e.targetid = ? AND e.type = "school-create"',
		[query.commentid, query.schoolid], function(res) {
		if (res.length == 0)
			return cb('permission-denied');

		assert.ok(res.length == 1 && res[0].cid == query.commentid);

		ctx.query('UPDATE ecomments SET comment = ?, trustedhtml = 1 WHERE commentid = ?',
			[self.readTemplate('comment-deleted-by-group-admin.html'), query.commentid], function() {
			cb('school-delete-comment-success');
		});
	});
}));

SchoolsDB.prototype.kickUser = buscomponent.provideQT('client-school-kick-user', _reqschooladm(function(query, ctx, cb) {
	ctx.query('DELETE FROM schoolmembers WHERE uid = ? AND schoolid = ?',
		[query.uid, query.schoolid], function() {
		ctx.query('DELETE FROM schooladmins WHERE uid = ? AND schoolid = ?',
			[query.uid, query.schoolid], function() {
			cb('school-kick-user-success');
		});
	});
}));

SchoolsDB.prototype.createSchool = buscomponent.provideQT('client-create-school', function(query, ctx, cb) {
	if (!query.schoolpath)
		query.schoolpath = '/' + query.schoolname.replace(/[^\w_-]/g, '');

	ctx.getConnection(function(conn) {
		conn.query('START TRANSACTION', [], function() {
		conn.query('SELECT COUNT(*) AS c FROM schools WHERE path = ?', [query.schoolpath], function(r) {
			assert.equal(r.length, 1);
			if (r[0].c == 1 || !query.schoolname.trim() ||
				!/^(\/[\w_-]+)+$/.test(query.schoolpath)) {
				conn.query('ROLLBACK', function() {
					conn.release();
				});

				return cb('create-school-already-exists');
			}

			var createCB = function() {
				conn.query('INSERT INTO schools (name,path) VALUES(?,?)', [query.schoolname,query.schoolpath], function(res) {
					ctx.feed({'type': 'school-create', 'targetid': res.insertId, 'srcuser': ctx.user.id});

					conn.query('COMMIT', function() {
						conn.release();
					});

					cb('create-school-success');
				});
			};

			if (query.schoolpath.replace(/[^\/]/g, '').length == 1)
				createCB();
			else conn.query('SELECT COUNT(*) AS c FROM schools WHERE path = ?', [parentPath(query.schoolpath)], function(r) {
				assert.equal(r.length, 1);
				if (r[0].c != 1) {
					conn.query('ROLLBACK', function() {
						conn.release();
					});

					return cb('create-school-missing-parent');
				}

				createCB();
			});
		});
		});
	});
});

SchoolsDB.prototype.listSchools = buscomponent.provideQT('client-list-schools', function(query, ctx, cb) {
	var where = 'WHERE 1 ';
	var params = [];
	if (query.parentPath) {
		where = 'AND path LIKE ? OR path = ? ';
		params = params.concat([query.parentPath + '/%', query.parentPath]);
	}

	if (query.search) {
		var likestring = '%' + (query.search.toString()).replace(/%/g, '\\%') + '%';

		where += 'AND (name LIKE ? OR path LIKE ?) ';
		params = params.concat([likestring, likestring]);
	}

	ctx.query('SELECT schools.id, schools.name, COUNT(sm.uid) AS usercount, schools.path FROM schools ' +
		'LEFT JOIN schoolmembers AS sm ON sm.schoolid=schools.id AND NOT pending ' +
		where +
		'GROUP BY schools.id', params, function(results) {
			cb('list-schools-success', {'result': results});
		}
	);
});

SchoolsDB.prototype.publishBanner = buscomponent.provideQT('client-school-publish-banner', function(query, ctx, cb) {
	query.__groupassoc__ = query.schoolid;
	query.role = 'schools.banner';

	_reqschooladm(_.bind(function(query, ctx, cb) {
		this.request({name: 'client-publish', query: query, user: user, access: access}, cb);
	}, this), false, this)(query, ctx, cb);
});

SchoolsDB.prototype.createInviteLink = buscomponent.provideQT('client-create-invite-link', function(query, ctx, cb) {
	_reqschooladm(_.bind(function(query, ctx, cb) {
		this.request({name: 'createInviteLink', query: query, user: user, access: access}, cb);
	}, this), true, this)(query, ctx, cb);
});

exports.SchoolsDB = SchoolsDB;

})();
<commit_msg><commit_after>(function () { "use strict";

function parentPath(x) {
	var match = x.match(/((\/[\w_-]+)+)\/[\w_-]+$/);
	return match ? match[1] : '/';
}

var _ = require('underscore');
var util = require('util');
var assert = require('assert');
var buscomponent = require('./buscomponent.js');

function SchoolsDB () {
}

util.inherits(SchoolsDB, buscomponent.BusComponent);

function _reqschooladm (f, soft, scdb, status) {
	soft = soft || false;

	return function(query, ctx, cb) {
		var forward = _.bind(function() { return _.bind(f, this)(query, ctx, cb); }, this);

		if (soft && !query.schoolid)
			return forward();

		var lsa = null;
		if (this && this.bus) lsa = this;
		if (scdb && scdb.bus) lsa = scdb;

		assert.ok(lsa);

		lsa.request({name: 'isSchoolAdmin', ctx: ctx, status: status, schoolid: query.schoolid}, function(ok, schoolid) {
			if (!ok)
				return cb('permission-denied');

			query.schoolid = schoolid;
			forward();
		});
	};
}

SchoolsDB.prototype.isSchoolAdmin = buscomponent.provide('isSchoolAdmin', ['ctx', 'status', 'schoolid', 'reply'],
	function(ctx, status, schoolid, cb)
{
	var self = this;

	(parseInt(schoolid) == schoolid ? function(cont) { cont(); } : function(cont) {
		ctx.query('SELECT id FROM schools WHERE ? IN (id, name, path)', [schoolid], function(res) {
			if (res.length == 0)
				return cb(false, null);

			assert.equal(res.length, 1);

			schoolid = res[0].id;
			cont();
		});
	})(function() {
		if (ctx.access.has('schooldb'))
			return cb(true, schoolid);

		status = status || ['admin', 'xadmin'];

		self.loadSchoolAdmins(schoolid, ctx, function(admins) {
			cb(_.chain(admins).filter(function(a) { return status.indexOf(a.status) != -1 && a.adminid == ctx.user.id; }).value().length != 0, schoolid);
		});
	});
});

SchoolsDB.prototype.loadSchoolAdmins = function(schoolid, ctx, cb) {
	ctx.query('SELECT sa.schoolid AS schoolid, sa.uid AS adminid, sa.status AS status, users.name AS adminname ' +
		'FROM schools AS c ' +
		'JOIN schools AS p ON c.path LIKE CONCAT(p.path, "%") OR p.id = c.id ' +
		'JOIN schooladmins AS sa ON sa.schoolid = p.id ' +
		'JOIN users ON users.id = sa.uid ' +
		'WHERE c.id = ?', [schoolid], cb);
};

SchoolsDB.prototype.loadSchoolInfo = function(lookfor, ctx, cfg, cb) {
	var self = this;

	ctx.query('SELECT schools.id, schools.name, schools.path, descpage, config, eventid, type, targetid, time, srcuser, url AS banner '+
		'FROM schools ' +
		'LEFT JOIN events ON events.targetid = schools.id AND events.type = "school-create" ' +
		'LEFT JOIN httpresources ON httpresources.groupassoc = schools.id AND httpresources.role = "schools.banner" ' +
		'WHERE ? IN (schools.id, schools.path, schools.name) ' +
		'LIMIT 1', [lookfor], function(res) {
		if (res.length == 0)
			return cb('get-school-info-notfound');

		var s = res[0];
		s.parentPath = null;

		assert.ok(s.eventid);

		if (s.config == '')
			s.config = {};
		else
			s.config = JSON.parse(s.config);

		assert.ok(s.config);

		self.loadSchoolAdmins(s.id, ctx, function(admins) {
			s.admins = admins;

			ctx.query('SELECT * FROM schools AS c WHERE c.path LIKE ?', [s.path + '/%'], function(subschools) {
			ctx.query('SELECT COUNT(uid) AS usercount ' +
				'FROM schoolmembers AS sm '+
				'LEFT JOIN schools AS c ON sm.schoolid = c.id ' +
				'LEFT JOIN schools AS p ON c.path LIKE CONCAT(p.path, "/%") OR p.id = c.id ' +
				'WHERE p.id = ?', [s.id], function(usercount) {
			ctx.query('SELECT c.*,u.name AS username,u.id AS uid, url AS profilepic, trustedhtml ' +
				'FROM ecomments AS c '+
				'LEFT JOIN users AS u ON c.commenter = u.id ' +
				'LEFT JOIN httpresources ON httpresources.user = c.commenter AND httpresources.role = "profile.image" '+
				'WHERE c.eventid = ?',
				[s.eventid],
				function(comments) {
				s.comments = comments;
				s.subschools = subschools;
				s.usercount = usercount[0].usercount;

				ctx.query('SELECT oh.stocktextid AS stockid, oh.stockname, ' +
					'SUM(ABS(money)) AS moneysum, ' +
					'SUM(ABS(money) / (UNIX_TIMESTAMP() - buytime + 300)) AS wsum ' +
					'FROM orderhistory AS oh ' +
					'JOIN schoolmembers AS sm ON sm.uid = oh.userid AND sm.jointime < oh.buytime AND sm.schoolid = ? ' +
					'GROUP BY stocktextid ORDER BY wsum DESC LIMIT 10', [s.id], function(popular) {
					if (s.path.replace(/[^\/]/g, '').length != 1) { // need higher-level
						s.parentPath = parentPath(s.path);
						self.loadSchoolInfo(s.parentPath, ctx.user, ctx.access, cfg, function(code, result) {
							assert.equal(code, 'get-school-info-success');

							s.parentSchool = result;

							s.config = _.defaults(s.config, s.parentSchool.config, cfg.schoolConfigDefaults);

							cb('get-school-info-success', s);
						});
					} else {
						s.config = _.defaults(s.config, cfg.schoolConfigDefaults);

						cb('get-school-info-success', s);
					}
				});
			});
			});
			});
		});
	});
};

SchoolsDB.prototype.getSchoolInfo = buscomponent.provideQT('client-get-school-info', function(query, ctx, cb) {
	var self = this;

	self.getServerConfig(function(cfg) {
		self.loadSchoolInfo(query.lookfor, ctx, cfg, function(code, result) {
			cb(code, {'result': result});
		});
	});
});

SchoolsDB.prototype.schoolExists = buscomponent.provideQT('client-school-exists', function(query, ctx, cb) {
	ctx.query('SELECT path FROM schools WHERE ? IN (id, path, name)', [query.lookfor], function(res) {
		cb('school-exists-success', {exists: res.length > 0, path: res.length > 0 ? res[0].path : null});
	});
});

SchoolsDB.prototype.changeDescription = buscomponent.provideQT('client-school-change-description', _reqschooladm(function(query, ctx, cb) {
	ctx.query('UPDATE schools SET descpage = ? WHERE id = ?', [query.descpage, query.schoolid], function() {
		cb('school-change-description-success');
	});
}));

SchoolsDB.prototype.changeMemberStatus = buscomponent.provideQT('client-school-change-member-status', _reqschooladm(function(query, ctx, cb) {
	ctx.query('UPDATE schoolmembers SET pending = 0 WHERE schoolid = ? AND uid = ?', [query.schoolid, query.uid], function() {
		if (query.status == 'member') {
			ctx.query('DELETE FROM schooladmins WHERE uid = ? AND schoolid = ?', [query.uid, query.schoolid], function() {
				cb('school-change-member-status-success');
			});
		} else {
			ctx.query('REPLACE INTO schooladmins (schoolid, uid, status) VALUES(?, ?, ?)', [query.schoolid, query.uid, query.status], function() {
				cb('school-change-member-status-success');
			});
		}
	});
}));

SchoolsDB.prototype.deleteComment = buscomponent.provideQT('client-school-delete-comment', _reqschooladm(function(query, ctx, cb) {
	var self = this;

	ctx.query('SELECT c.commentid AS cid FROM ecomments AS c ' +
		'JOIN events AS e ON e.eventid = c.eventid ' +
		'WHERE c.commentid = ? AND e.targetid = ? AND e.type = "school-create"',
		[query.commentid, query.schoolid], function(res) {
		if (res.length == 0)
			return cb('permission-denied');

		assert.ok(res.length == 1 && res[0].cid == query.commentid);

		ctx.query('UPDATE ecomments SET comment = ?, trustedhtml = 1 WHERE commentid = ?',
			[self.readTemplate('comment-deleted-by-group-admin.html'), query.commentid], function() {
			cb('school-delete-comment-success');
		});
	});
}));

SchoolsDB.prototype.kickUser = buscomponent.provideQT('client-school-kick-user', _reqschooladm(function(query, ctx, cb) {
	ctx.query('DELETE FROM schoolmembers WHERE uid = ? AND schoolid = ?',
		[query.uid, query.schoolid], function() {
		ctx.query('DELETE FROM schooladmins WHERE uid = ? AND schoolid = ?',
			[query.uid, query.schoolid], function() {
			cb('school-kick-user-success');
		});
	});
}));

SchoolsDB.prototype.createSchool = buscomponent.provideQT('client-create-school', function(query, ctx, cb) {
	if (!query.schoolpath)
		query.schoolpath = '/' + query.schoolname.replace(/[^\w_-]/g, '');

	ctx.getConnection(function(conn) {
		conn.query('START TRANSACTION', [], function() {
		conn.query('SELECT COUNT(*) AS c FROM schools WHERE path = ?', [query.schoolpath], function(r) {
			assert.equal(r.length, 1);
			if (r[0].c == 1 || !query.schoolname.trim() ||
				!/^(\/[\w_-]+)+$/.test(query.schoolpath)) {
				conn.query('ROLLBACK', function() {
					conn.release();
				});

				return cb('create-school-already-exists');
			}

			var createCB = function() {
				conn.query('INSERT INTO schools (name,path) VALUES(?,?)', [query.schoolname,query.schoolpath], function(res) {
					ctx.feed({'type': 'school-create', 'targetid': res.insertId, 'srcuser': ctx.user.id});

					conn.query('COMMIT', function() {
						conn.release();
					});

					cb('create-school-success');
				});
			};

			if (query.schoolpath.replace(/[^\/]/g, '').length == 1)
				createCB();
			else conn.query('SELECT COUNT(*) AS c FROM schools WHERE path = ?', [parentPath(query.schoolpath)], function(r) {
				assert.equal(r.length, 1);
				if (r[0].c != 1) {
					conn.query('ROLLBACK', function() {
						conn.release();
					});

					return cb('create-school-missing-parent');
				}

				createCB();
			});
		});
		});
	});
});

SchoolsDB.prototype.listSchools = buscomponent.provideQT('client-list-schools', function(query, ctx, cb) {
	var where = 'WHERE 1 ';
	var params = [];
	if (query.parentPath) {
		where = 'AND path LIKE ? OR path = ? ';
		params = params.concat([query.parentPath + '/%', query.parentPath]);
	}

	if (query.search) {
		var likestring = '%' + (query.search.toString()).replace(/%/g, '\\%') + '%';

		where += 'AND (name LIKE ? OR path LIKE ?) ';
		params = params.concat([likestring, likestring]);
	}

	ctx.query('SELECT schools.id, schools.name, COUNT(sm.uid) AS usercount, schools.path FROM schools ' +
		'LEFT JOIN schoolmembers AS sm ON sm.schoolid=schools.id AND NOT pending ' +
		where +
		'GROUP BY schools.id', params, function(results) {
			cb('list-schools-success', {'result': results});
		}
	);
});

SchoolsDB.prototype.publishBanner = buscomponent.provideQT('client-school-publish-banner', function(query, ctx, cb) {
	query.__groupassoc__ = query.schoolid;
	query.role = 'schools.banner';

	_reqschooladm(_.bind(function(query, ctx, cb) {
		this.request({name: 'client-publish', query: query, ctx: ctx}, cb);
	}, this), false, this)(query, ctx, cb);
});

SchoolsDB.prototype.createInviteLink = buscomponent.provideQT('client-create-invite-link', function(query, ctx, cb) {
	_reqschooladm(_.bind(function(query, ctx, cb) {
		this.request({name: 'createInviteLink', query: query, ctx: ctx}, cb);
	}, this), true, this)(query, ctx, cb);
});

exports.SchoolsDB = SchoolsDB;

})();
<|endoftext|>


<commit_before>/**
 * Created by user on 12/12/14.
 */
(function () {
    var Wave = function(config){
        // var config = {"level": 2, "waves": 5, "enemyPerWave" : 2, "lanes" : 2, "life" : 1, "types" : ["boss"]}
        this.config = config;
        this.currentIndex = 0;
        this.activeIndex = 0;
        this.complete = false;
        this.timer = 0;
        if(this.config.gameState.currentLevel == this.config.gameState.survivalLevel){
            loadEnemiesOnSurvival(this);
        }else{
            generateEnemyProperties(this);
        }

        this.push();
        //console.log("start of wave");
        // setTimer(this);
    }

    var loadEnemiesOnSurvival = function(me){
        //var enemyList = [EnemyData.boss,EnemyData.enemy_regular, EnemyData.weak];
        me.counter = 1;
        me.surivalTime = 6000;
        var property = [EnemyData.enemy_regular.extras.id, 2, me.surivalTime];
        me.config.data.stageDatas.push(property);
        me.timer = setInterval(function(){if(me.config.data.stageDatas.length < 50){ createData(me)}},1000);
    }

    var createData = function(me){
        ++me.counter;

        var mainType = getTypeForSurvival(me);
        var type = getTypeForSurvival(me,mainType);
        var lane = Math.floor((Math.random()*3))+1;
        var time = getTimeForSurvival(mainType,type, me);
        var property = [type.extras.id, lane, time,"", mainType];
        me.config.data.stageDatas.push(property);

    }
    var getTimeForSurvival = function(mainType, type,me){
        if(mainType == "powerup"){
            return 1000;
        }else{
            if(me.counter % 8 == 0){
                me.surivalTime = (me.surivalTime - Math.ceil(me.surivalTime*.2)) < 500 ? 500 : (me.surivalTime - Math.ceil(me.surivalTime*.2));
                console.log("updated survival time---->"+me.surivalTime);
            }

            return me.surivalTime + type.extras.life *.4*1000;
        }

    }
    var getTypeForSurvival = function(me, type){
        var enemyList = [EnemyData.boss,EnemyData.enemy_regular, EnemyData.weak];
        var powerupList = [PowerupsData.cleats,PowerupsData.cleats,PowerupsData.bullhorn];
        if(type == undefined){
            if(me.counter % 5 == 0){
                return "powerup";
            }else{
                return "enemy";
            }
        }else{
            if(type == "enemy"){
                return enemyList[Math.floor(Math.random() * enemyList.length)];
            }else{
                return powerupList[Math.floor(Math.random() * powerupList.length)];
            }
        }

    }

    var generateEnemyProperties = function(me){
        if(me.config.data.stageDatas.length == 0){
            var types = me.config.data.types;
            var size = me.config.data.size;
            for(var i = 0; i< size; i++){
                var properties = {};
                properties[0] = types[Math.floor(Math.random()*types.length)];
                properties[1] = Math.floor(Math.random()*me.config.lanes)+1;
                properties[2] = me.config.data.time;
                properties[3] = "";

                me.config.data.stageDatas.push(properties);
            }
        }

    }

    var setNext = function(time, me){
       // console.log(time);
        me.timer = setTimeout(function(){
            me.push();
        }, time);
    }

    Wave.prototype.forcePush = function(){
        clearTimeout(this.timer);
        this.push();
    }

    Wave.prototype.push = function(){
        if(!this.complete){
            var properties = this.config.data.stageDatas.shift();
            if(properties[4] == undefined || properties[4] == "enemy"){
                this.pushEnemy(properties,this.config.lanesObj);
                this.activeIndex++;
            }else{
                this.pushPowerUp(properties);
            }
            this.currentIndex++;

            if(this.currentIndex >= this.config.data.size && this.config.gameState.currentLevel != this.config.gameState.survivalLevel) {
                console.log("complete");
                this.complete = true;
            }

        }
    }

    Wave.prototype.pushPowerUp = function(properties){
        console.log("power up");
        //set config and call new Gem.
        //dispatch to pushGem.
        //currently set some constant postion in lane, later can modify.
        var type = properties[0];
        var lane = properties[1];
        var time = properties[2];
        var msg = properties[3];
        //console.log("time===============  :::::::::::::::>>>>"+ time);
        lane = this.config.lanes == 1? 2 : lane;
        var onKill = (time == undefined || time == -1) ? true: false;
        var config = {"id": type, "laneId": lane, "waveId": this.config.id, "onKill": onKill, "loader" : this.config.loader};
        var powerup = new sprites.Powerup(config);
        EventBus.dispatch("pushPowerup",powerup);
        if(!(msg==""||msg==undefined)){
            EventBus.dispatch("showCommentary",msg);
        }
        if(!onKill){
            setNext(time, this);
        }
    }

    Wave.prototype.pushEnemy = function(enemyProperties,lanesObj){
        console.log(this.currentIndex +"  //"+ this.complete)
        var type = enemyProperties[0];
        var lane = enemyProperties[1];
        var time = enemyProperties[2];
        var msg =  enemyProperties[3];
        lane = this.config.lanes == 1? 2 : lane;
        console.log("time===============  :::::::::::::::>>>>"+ time);
        var onKill = (time == undefined || time == -1) ? true: false;
        var config = {"id": type, "lanesObj" : lanesObj, "laneId": lane, "waveId": this.config.id, "onKill": onKill, "loader" : this.config.loader, "gameState" : this.config.gameState};
        var enemy = new sprites.Enemy(config);
        if(!(msg==""||msg==undefined)){
            EventBus.dispatch("showCommentary",msg);
        }
        EventBus.dispatch("pushEnemy",enemy);
        if(!onKill){
            setNext(time, this);
        }

    }

    Wave.prototype.killEnemy = function(){
        this.activeIndex--;

    }
    Wave.prototype.isComplete = function(){
        if(this.complete && this.activeIndex == 0){
            return true;
        }else{
            //resetTimer(this);
            return false;
        }
    }
    Wave.prototype.getId = function(){
        return this.config.id;
    }
    Wave.prototype.clearAll = function(){
        clearTimeout(this.timer);
        clearInterval(this.timer);;
        this.timer = 0;
    }

    window.Wave = Wave;

}());<commit_msg>More type of enemies in Survival level<commit_after>/**
 * Created by user on 12/12/14.
 */
(function () {
    var Wave = function(config){
        // var config = {"level": 2, "waves": 5, "enemyPerWave" : 2, "lanes" : 2, "life" : 1, "types" : ["boss"]}
        this.config = config;
        this.currentIndex = 0;
        this.activeIndex = 0;
        this.complete = false;
        this.timer = 0;
        if(this.config.gameState.currentLevel == this.config.gameState.survivalLevel){
            loadEnemiesOnSurvival(this);
        }else{
            generateEnemyProperties(this);
        }

        this.push();
        //console.log("start of wave");
        // setTimer(this);
    }

    var loadEnemiesOnSurvival = function(me){
        //var enemyList = [EnemyData.boss,EnemyData.enemy_regular, EnemyData.weak];
        me.counter = 1;
        me.surivalTime = 6000;
        var property = [EnemyData.enemy_regular.extras.id, 2, me.surivalTime];
        me.config.data.stageDatas.push(property);
        me.timer = setInterval(function(){if(me.config.data.stageDatas.length < 50){ createData(me)}},1000);
    }

    var createData = function(me){
        ++me.counter;

        var mainType = getTypeForSurvival(me);
        var type = getTypeForSurvival(me,mainType);
        var lane = Math.floor((Math.random()*3))+1;
        var time = getTimeForSurvival(mainType,type, me);
        var property = [type.extras.id, lane, time,"", mainType];
        me.config.data.stageDatas.push(property);

    }
    var getTimeForSurvival = function(mainType, type,me){
        if(mainType == "powerup"){
            return 1000;
        }else{
            if(me.counter % 8 == 0){
                me.surivalTime = (me.surivalTime - Math.ceil(me.surivalTime*.2)) < 500 ? 500 : (me.surivalTime - Math.ceil(me.surivalTime*.2));
                console.log("updated survival time---->"+me.surivalTime);
            }

            return me.surivalTime + type.extras.life *.4*1000;
        }

    }
    var getTypeForSurvival = function(me, type){
        var enemyList = [EnemyData.boss,EnemyData.enemy_regular, EnemyData.weak,EnemyData.badGuy,EnemyData.fast,EnemyData.regular];
        var powerupList = [PowerupsData.cleats,PowerupsData.cleats,PowerupsData.bullhorn];
        if(type == undefined){
            if(me.counter % 5 == 0){
                return "powerup";
            }else{
                return "enemy";
            }
        }else{
            if(type == "enemy"){
                return enemyList[Math.floor(Math.random() * enemyList.length)];
            }else{
                return powerupList[Math.floor(Math.random() * powerupList.length)];
            }
        }

    }

    var generateEnemyProperties = function(me){
        if(me.config.data.stageDatas.length == 0){
            var types = me.config.data.types;
            var size = me.config.data.size;
            for(var i = 0; i< size; i++){
                var properties = {};
                properties[0] = types[Math.floor(Math.random()*types.length)];
                properties[1] = Math.floor(Math.random()*me.config.lanes)+1;
                properties[2] = me.config.data.time;
                properties[3] = "";

                me.config.data.stageDatas.push(properties);
            }
        }

    }

    var setNext = function(time, me){
       // console.log(time);
        me.timer = setTimeout(function(){
            me.push();
        }, time);
    }

    Wave.prototype.forcePush = function(){
        clearTimeout(this.timer);
        this.push();
    }

    Wave.prototype.push = function(){
        if(!this.complete){
            var properties = this.config.data.stageDatas.shift();
            if(properties[4] == undefined || properties[4] == "enemy"){
                this.pushEnemy(properties,this.config.lanesObj);
                this.activeIndex++;
            }else{
                this.pushPowerUp(properties);
            }
            this.currentIndex++;

            if(this.currentIndex >= this.config.data.size && this.config.gameState.currentLevel != this.config.gameState.survivalLevel) {
                console.log("complete");
                this.complete = true;
            }

        }
    }

    Wave.prototype.pushPowerUp = function(properties){
        console.log("power up");
        //set config and call new Gem.
        //dispatch to pushGem.
        //currently set some constant postion in lane, later can modify.
        var type = properties[0];
        var lane = properties[1];
        var time = properties[2];
        var msg = properties[3];
        //console.log("time===============  :::::::::::::::>>>>"+ time);
        lane = this.config.lanes == 1? 2 : lane;
        var onKill = (time == undefined || time == -1) ? true: false;
        var config = {"id": type, "laneId": lane, "waveId": this.config.id, "onKill": onKill, "loader" : this.config.loader};
        var powerup = new sprites.Powerup(config);
        EventBus.dispatch("pushPowerup",powerup);
        if(!(msg==""||msg==undefined)){
            EventBus.dispatch("showCommentary",msg);
        }
        if(!onKill){
            setNext(time, this);
        }
    }

    Wave.prototype.pushEnemy = function(enemyProperties,lanesObj){
        console.log(this.currentIndex +"  //"+ this.complete)
        var type = enemyProperties[0];
        var lane = enemyProperties[1];
        var time = enemyProperties[2];
        var msg =  enemyProperties[3];
        lane = this.config.lanes == 1? 2 : lane;
        console.log("time===============  :::::::::::::::>>>>"+ time);
        var onKill = (time == undefined || time == -1) ? true: false;
        var config = {"id": type, "lanesObj" : lanesObj, "laneId": lane, "waveId": this.config.id, "onKill": onKill, "loader" : this.config.loader, "gameState" : this.config.gameState};
        var enemy = new sprites.Enemy(config);
        if(!(msg==""||msg==undefined)){
            EventBus.dispatch("showCommentary",msg);
        }
        EventBus.dispatch("pushEnemy",enemy);
        if(!onKill){
            setNext(time, this);
        }

    }

    Wave.prototype.killEnemy = function(){
        this.activeIndex--;

    }
    Wave.prototype.isComplete = function(){
        if(this.complete && this.activeIndex == 0){
            return true;
        }else{
            //resetTimer(this);
            return false;
        }
    }
    Wave.prototype.getId = function(){
        return this.config.id;
    }
    Wave.prototype.clearAll = function(){
        clearTimeout(this.timer);
        clearInterval(this.timer);;
        this.timer = 0;
    }

    window.Wave = Wave;

}());<|endoftext|>


<commit_before>const ERR = require('async-stacktrace');
const async = require('async');
const pgArray = require('pg').types.arrayParser;
const colors = require('colors');
const _ = require('lodash');

const sqldb = require('@prairielearn/prairielib/sql-db');
const sqlLoader = require('@prairielearn/prairielib/sql-loader');
const sql = sqlLoader.loadSqlEquiv(__filename);

module.exports = {};

/**
 * will produce a description of a given database's schema. This will include
 * information about tables, enums, contraints, indices, etc.
 *
 * This functions accepts an 'options' object with various options that determine
 * how the function will run. The following properties are available on the
 * 'options' object:
 *
 * databaseName [REQUIRED]: the name of the database to describe
 * outputFormat [default: 'string']: determines how the description is formatted
 * coloredOutput [default: false]: if the output should be colored for better readability
 *
 * @param  {Object}   options  Options for this function
 * @param  {Function} callback Will receive results of an error when complete
 */
module.exports.describe = function(options, callback) {
    if (!options) return callback(new Error('options must not be null'));
    if (!options.databaseName) return callback(new Error('you must specify a database name with databaseName'));
    if (options.outputFormat && !(options.outputFormat == 'string' || options.outputFormat == 'object')) {
        return callback(new Error(`'${options.outputFormat}' is not a valid output format`));
    }

    var tables;
    var ignoreColumns = {};

    var output = {
        tables: {},
        enums: {},
    };

    var formatText = function(text, formatter) {
        if (options.coloredOutput) {
            return formatter(text);
        }
        return text;
    };

    async.series([
        (callback) => {
            // Connect to the database
            var pgConfig = {
                user: options.postgresqlUser || 'postgres',
                database: options.databaseName,
                host: options.postgresqlHost || 'localhost',
                max: 10,
                idleTimeoutMillis: 30000,
            };
            var idleErrorHandler = function(err) {
                throw Error('idle client error', err);
            };
            sqldb.init(pgConfig, idleErrorHandler, function(err) {
                if (ERR(err, callback)) return;
                callback(null);
            });
        },
        (callback) => {
            // Get the names of the tables
            sqldb.query(sql.get_tables, [], (err, results) => {
                if (ERR(err, callback)) return;
                tables = results.rows;

                // Filter out ignored tables
                if (options.ignoreTables && _.isArray(options.ignoreTables)) {
                    tables = _.filter(tables, table => options.ignoreTables.indexOf(table.name) == -1);
                }

                // Initialize output with names of tables
                if (options.outputFormat === 'string') {
                    tables.forEach((table) => output.tables[table.name] = '');
                } else {
                    tables.forEach((table) => output.tables[table.name] = {});
                }

                callback(null);
            });
        },
        (callback) => {
            // Transform ignored columns into a map from table names to arrays
            // of column names
            if (options.ignoreColumns && _.isArray(options.ignoreColumns)) {
                ignoreColumns = _.filter(options.ignoreColumns, ignore => {
                    return /^[^\s.]*\.[^\s.]*$/.test(ignore);
                });
                ignoreColumns = _.reduce(ignoreColumns, (result, value) => {
                    var res = /^(([^\s.]*)\.([^\s.]*))$/.exec(value);
                    var table = res[2];
                    var column = res[3];
                    (result[table] || (result[table] = [])).push(column);
                    return result;
                }, {});
            }
            callback(null);
        },
        (callback) => {
            // Get column info for each table
            async.each(tables, (table, callback) => {
                async.series([
                    (callback) => {
                        const params = {
                            oid: table.oid,
                        };
                        sqldb.query(sql.get_columns_for_table, params, (err, results) => {
                            if (ERR(err, callback)) return;

                            const rows = _.filter(results.rows, row => {
                                return (ignoreColumns[table.name] || []).indexOf(row.name) == -1;
                            });

                            if (rows.length == 0) {
                                return callback(null);
                            }

                            // Transform table info into a string, if needed
                            if (options.outputFormat === 'string') {
                                output.tables[table.name] += formatText('columns\n', colors.underline);
                                output.tables[table.name] += rows.map((row) => {
                                    var rowText = formatText(`    ${row.name}`, colors.bold);
                                    rowText += ':' + formatText(` ${row.type}`, colors.green);
                                    if (row.notnull) {
                                        rowText += formatText(' not null', colors.gray);
                                    }
                                    if (row.default) {
                                        rowText += formatText(` default ${row.default}`, colors.gray);
                                    }
                                    return rowText;
                                }).join('\n');
                            } else {
                                output.tables[table.name].columns = rows;
                            }
                            callback(null);
                        });
                    },
                    (callback) => {
                        const params = {
                            oid: table.oid,
                        };
                        sqldb.query(sql.get_indexes_for_table, params, (err, results) => {
                            if (ERR(err, callback)) return;

                            if (results.rows.length == 0) {
                                return callback(null);
                            }

                            if (options.outputFormat === 'string') {
                                if (output.tables[table.name].length != 0) {
                                    output.tables[table.name] += '\n\n';
                                }
                                output.tables[table.name] += formatText('indexes\n', colors.underline);
                                output.tables[table.name] += results.rows.map((row) => {
                                    const using = row.indexdef.substring(row.indexdef.indexOf('USING '))
                                    var rowText = formatText(`    ${row.name}`, colors.bold) + ':';
                                    rowText += row.constraintdef ? formatText(` ${row.constraintdef}`, colors.green) : '';
                                    rowText += using ? formatText(` ${using}`, colors.green) : '';
                                    return rowText;
                                }).join('\n');
                            } else {
                                output.tables[table.name].indexes = results.rows;
                            }
                            callback(null);
                        });
                    },
                    (callback) => {
                        const params = {
                            oid: table.oid,
                        };
                        sqldb.query(sql.get_foreign_key_constraints_for_table, params, (err, results) => {
                            if (ERR(err, callback)) return;

                            if (results.rows.length == 0) {
                                return callback(null);
                            }

                            if (options.outputFormat === 'string') {
                                if (output.tables[table.name].length != 0) {
                                    output.tables[table.name] += '\n\n';
                                }
                                output.tables[table.name] += formatText('foreign-key constraints\n', colors.underline);
                                output.tables[table.name] += results.rows.map((row) => {
                                    var rowText = formatText(`    ${row.name}:`, colors.bold);
                                    rowText += formatText(` ${row.def}`, colors.green);
                                    return rowText;
                                }).join('\n');
                            } else {
                                output.tables[table.name].foreignKeyConstraings = results.rows;
                            }
                            callback(null);
                        });
                    },
                    (callback) => {
                        const params = {
                            oid: table.oid,
                        };
                        sqldb.query(sql.get_references_for_table, params, (err, results) => {
                            if (ERR(err, callback)) return;

                            // Filter out references from ignored tables
                            let rows = results.rows;
                            if (options.ignoreTables && _.isArray(options.ignoreTables)) {
                                rows = _.filter(results.rows, row => {
                                    return options.ignoreTables.indexOf(row.table) == -1;
                                });
                            }

                            if (rows.length == 0) {
                                return callback(null);
                            }

                            if (options.outputFormat === 'string') {
                                if (output.tables[table.name].length != 0) {
                                    output.tables[table.name] += '\n\n';
                                }
                                output.tables[table.name] += formatText('referenced by\n', colors.underline);
                                output.tables[table.name] += rows.map((row) => {
                                    var rowText = formatText(`    ${row.table}:`, colors.bold);
                                    rowText += formatText(` ${row.condef}`, colors.green);
                                    return rowText;
                                }).join('\n');
                            } else {
                                output.tables[table.name].references = rows;
                            }
                            callback(null);
                        });
                    },
                ], (err) => {
                    if (ERR(err, callback)) return;
                    callback(null);
                });
            }, (err) => {
                if (ERR(err, callback)) return;
                callback(null);
            });
        },
        (callback) => {
            // Get all enums
            sqldb.query(sql.get_enums, [], (err, results) => {
                if (ERR(err, callback)) return;

                // Filter ignored enums
                let rows = results.rows;
                if (options.ignoreEnums && _.isArray(options.ignoreEnums)) {
                    rows = _.filter(results.rows, row => {
                        return options.ignoreEnums.indexOf(row.name) == -1;
                    });
                }

                if (rows.length == 0) {
                    return callback(null);
                }

                rows.forEach((row) => {
                    if (options.outputFormat == 'string') {
                        const values = pgArray.create(row.values, String).parse();
                        output.enums[row.name] = formatText(values.join(', '), colors.gray);
                    } else {
                        output.enums[row.name] = pgArray.create(row.values, String).parse();
                    }
                });

                callback(null);
            });
        },
        (callback) => {
            // We need to tack on a newline to everything if we're in string mode
            if (options.outputFormat == 'string') {
                output.tables = _.mapValues(output.tables, item => item + '\n');
                output.enums = _.mapValues(output.enums, item => item + '\n');
            }
            callback(null);
        },
        (callback) => {
            sqldb.close((err) => {
                if (ERR(err, callback)) return;
                callback(null);
            });
        },
    ], (err) => {
        if (ERR(err, callback)) return;
        callback(null, output);
    });
};
<commit_msg>missing semicolon fix<commit_after>const ERR = require('async-stacktrace');
const async = require('async');
const pgArray = require('pg').types.arrayParser;
const colors = require('colors');
const _ = require('lodash');

const sqldb = require('@prairielearn/prairielib/sql-db');
const sqlLoader = require('@prairielearn/prairielib/sql-loader');
const sql = sqlLoader.loadSqlEquiv(__filename);

module.exports = {};

/**
 * will produce a description of a given database's schema. This will include
 * information about tables, enums, contraints, indices, etc.
 *
 * This functions accepts an 'options' object with various options that determine
 * how the function will run. The following properties are available on the
 * 'options' object:
 *
 * databaseName [REQUIRED]: the name of the database to describe
 * outputFormat [default: 'string']: determines how the description is formatted
 * coloredOutput [default: false]: if the output should be colored for better readability
 *
 * @param  {Object}   options  Options for this function
 * @param  {Function} callback Will receive results of an error when complete
 */
module.exports.describe = function(options, callback) {
    if (!options) return callback(new Error('options must not be null'));
    if (!options.databaseName) return callback(new Error('you must specify a database name with databaseName'));
    if (options.outputFormat && !(options.outputFormat == 'string' || options.outputFormat == 'object')) {
        return callback(new Error(`'${options.outputFormat}' is not a valid output format`));
    }

    var tables;
    var ignoreColumns = {};

    var output = {
        tables: {},
        enums: {},
    };

    var formatText = function(text, formatter) {
        if (options.coloredOutput) {
            return formatter(text);
        }
        return text;
    };

    async.series([
        (callback) => {
            // Connect to the database
            var pgConfig = {
                user: options.postgresqlUser || 'postgres',
                database: options.databaseName,
                host: options.postgresqlHost || 'localhost',
                max: 10,
                idleTimeoutMillis: 30000,
            };
            var idleErrorHandler = function(err) {
                throw Error('idle client error', err);
            };
            sqldb.init(pgConfig, idleErrorHandler, function(err) {
                if (ERR(err, callback)) return;
                callback(null);
            });
        },
        (callback) => {
            // Get the names of the tables
            sqldb.query(sql.get_tables, [], (err, results) => {
                if (ERR(err, callback)) return;
                tables = results.rows;

                // Filter out ignored tables
                if (options.ignoreTables && _.isArray(options.ignoreTables)) {
                    tables = _.filter(tables, table => options.ignoreTables.indexOf(table.name) == -1);
                }

                // Initialize output with names of tables
                if (options.outputFormat === 'string') {
                    tables.forEach((table) => output.tables[table.name] = '');
                } else {
                    tables.forEach((table) => output.tables[table.name] = {});
                }

                callback(null);
            });
        },
        (callback) => {
            // Transform ignored columns into a map from table names to arrays
            // of column names
            if (options.ignoreColumns && _.isArray(options.ignoreColumns)) {
                ignoreColumns = _.filter(options.ignoreColumns, ignore => {
                    return /^[^\s.]*\.[^\s.]*$/.test(ignore);
                });
                ignoreColumns = _.reduce(ignoreColumns, (result, value) => {
                    var res = /^(([^\s.]*)\.([^\s.]*))$/.exec(value);
                    var table = res[2];
                    var column = res[3];
                    (result[table] || (result[table] = [])).push(column);
                    return result;
                }, {});
            }
            callback(null);
        },
        (callback) => {
            // Get column info for each table
            async.each(tables, (table, callback) => {
                async.series([
                    (callback) => {
                        const params = {
                            oid: table.oid,
                        };
                        sqldb.query(sql.get_columns_for_table, params, (err, results) => {
                            if (ERR(err, callback)) return;

                            const rows = _.filter(results.rows, row => {
                                return (ignoreColumns[table.name] || []).indexOf(row.name) == -1;
                            });

                            if (rows.length == 0) {
                                return callback(null);
                            }

                            // Transform table info into a string, if needed
                            if (options.outputFormat === 'string') {
                                output.tables[table.name] += formatText('columns\n', colors.underline);
                                output.tables[table.name] += rows.map((row) => {
                                    var rowText = formatText(`    ${row.name}`, colors.bold);
                                    rowText += ':' + formatText(` ${row.type}`, colors.green);
                                    if (row.notnull) {
                                        rowText += formatText(' not null', colors.gray);
                                    }
                                    if (row.default) {
                                        rowText += formatText(` default ${row.default}`, colors.gray);
                                    }
                                    return rowText;
                                }).join('\n');
                            } else {
                                output.tables[table.name].columns = rows;
                            }
                            callback(null);
                        });
                    },
                    (callback) => {
                        const params = {
                            oid: table.oid,
                        };
                        sqldb.query(sql.get_indexes_for_table, params, (err, results) => {
                            if (ERR(err, callback)) return;

                            if (results.rows.length == 0) {
                                return callback(null);
                            }

                            if (options.outputFormat === 'string') {
                                if (output.tables[table.name].length != 0) {
                                    output.tables[table.name] += '\n\n';
                                }
                                output.tables[table.name] += formatText('indexes\n', colors.underline);
                                output.tables[table.name] += results.rows.map((row) => {
                                    const using = row.indexdef.substring(row.indexdef.indexOf('USING '));
                                    var rowText = formatText(`    ${row.name}`, colors.bold) + ':';
                                    rowText += row.constraintdef ? formatText(` ${row.constraintdef}`, colors.green) : '';
                                    rowText += using ? formatText(` ${using}`, colors.green) : '';
                                    return rowText;
                                }).join('\n');
                            } else {
                                output.tables[table.name].indexes = results.rows;
                            }
                            callback(null);
                        });
                    },
                    (callback) => {
                        const params = {
                            oid: table.oid,
                        };
                        sqldb.query(sql.get_foreign_key_constraints_for_table, params, (err, results) => {
                            if (ERR(err, callback)) return;

                            if (results.rows.length == 0) {
                                return callback(null);
                            }

                            if (options.outputFormat === 'string') {
                                if (output.tables[table.name].length != 0) {
                                    output.tables[table.name] += '\n\n';
                                }
                                output.tables[table.name] += formatText('foreign-key constraints\n', colors.underline);
                                output.tables[table.name] += results.rows.map((row) => {
                                    var rowText = formatText(`    ${row.name}:`, colors.bold);
                                    rowText += formatText(` ${row.def}`, colors.green);
                                    return rowText;
                                }).join('\n');
                            } else {
                                output.tables[table.name].foreignKeyConstraings = results.rows;
                            }
                            callback(null);
                        });
                    },
                    (callback) => {
                        const params = {
                            oid: table.oid,
                        };
                        sqldb.query(sql.get_references_for_table, params, (err, results) => {
                            if (ERR(err, callback)) return;

                            // Filter out references from ignored tables
                            let rows = results.rows;
                            if (options.ignoreTables && _.isArray(options.ignoreTables)) {
                                rows = _.filter(results.rows, row => {
                                    return options.ignoreTables.indexOf(row.table) == -1;
                                });
                            }

                            if (rows.length == 0) {
                                return callback(null);
                            }

                            if (options.outputFormat === 'string') {
                                if (output.tables[table.name].length != 0) {
                                    output.tables[table.name] += '\n\n';
                                }
                                output.tables[table.name] += formatText('referenced by\n', colors.underline);
                                output.tables[table.name] += rows.map((row) => {
                                    var rowText = formatText(`    ${row.table}:`, colors.bold);
                                    rowText += formatText(` ${row.condef}`, colors.green);
                                    return rowText;
                                }).join('\n');
                            } else {
                                output.tables[table.name].references = rows;
                            }
                            callback(null);
                        });
                    },
                ], (err) => {
                    if (ERR(err, callback)) return;
                    callback(null);
                });
            }, (err) => {
                if (ERR(err, callback)) return;
                callback(null);
            });
        },
        (callback) => {
            // Get all enums
            sqldb.query(sql.get_enums, [], (err, results) => {
                if (ERR(err, callback)) return;

                // Filter ignored enums
                let rows = results.rows;
                if (options.ignoreEnums && _.isArray(options.ignoreEnums)) {
                    rows = _.filter(results.rows, row => {
                        return options.ignoreEnums.indexOf(row.name) == -1;
                    });
                }

                if (rows.length == 0) {
                    return callback(null);
                }

                rows.forEach((row) => {
                    if (options.outputFormat == 'string') {
                        const values = pgArray.create(row.values, String).parse();
                        output.enums[row.name] = formatText(values.join(', '), colors.gray);
                    } else {
                        output.enums[row.name] = pgArray.create(row.values, String).parse();
                    }
                });

                callback(null);
            });
        },
        (callback) => {
            // We need to tack on a newline to everything if we're in string mode
            if (options.outputFormat == 'string') {
                output.tables = _.mapValues(output.tables, item => item + '\n');
                output.enums = _.mapValues(output.enums, item => item + '\n');
            }
            callback(null);
        },
        (callback) => {
            sqldb.close((err) => {
                if (ERR(err, callback)) return;
                callback(null);
            });
        },
    ], (err) => {
        if (ERR(err, callback)) return;
        callback(null, output);
    });
};
<|endoftext|>


<commit_before>module.exports = (function(){
    "use strict";

    var _ = require("underscore"),
        mongoose = require('mongoose'),
        db = {};

    db.collectionName = 'users';
    db.schema = require('./schemas/user'),
    db.model = mongoose.model(db.collectionName, db.schema);

    db.privateFields = ["salt", "pass_hash"];

    function handleUser(err, doc, callback){
        if(err) {
            callback(err);
        }
        else if (doc != null) {
            callback(null, doc);
        }
        else {
            callback(new Error("User does not exist"));
        }
    }

    db.getByLogin = function(login, callback) {
        db.model.findOne({login: login}, this.buildIncludes()).lean().exec(function(err, doc){
            handleUser(err, doc, callback);
        });
    };

    db.getByEmail = function(email, callback) {
        db.model.findOne({email: email}, this.buildIncludes()).lean().exec(function(err, doc){
            handleUser(err, doc, callback);
         });
    };

    db.authenticate = function(login, password, callback){
        db.model.findOne({login: login}, function(err, doc){
            var valid = (doc != null) ? doc.authenticate(password) : false;

            if(!valid){
                handleUser(null, null, callback);
            }
            else {
                handleUser(err, doc, callback);
            }
        });
    };

    db.savePermissions = function(userId, nodeid, role, callback){
        db.model.findOne({_id: userId}, function(err, doc){
            if(err){
                callback(err);
            }
            else {
                var permissions = (!doc.permissions) ? [] : _.filter(doc.permissions, function(permission) {
                    return permission.nodeid.toString() != nodeid;
                });

                permissions[permissions.length] = {nodeid: nodeid, role: role};

                doc.permissions = permissions;
                doc.save(function(err, results){
                    callback(err, (!err) ? "Success" : null);
                });
         }
        });
    };

    db.deletePermission = function(userId, nodeId, callback){
        db.model.findOne({_id: userId}, function(err, doc){
            if(err){
                callback(err);
            }
            else {
                var permissions = (!doc.permissions) ? [] : _.filter(doc.permissions, function(permission) {
                    return permission.nodeid.toString() != nodeid;
                });

                doc.permissions = permissions;
                doc.save(function(err, results){
                    callback(err, (!err) ? "Success" : null);
                });
            }
        });
    };

    return _.extend(require("./mixins/crud"), db);
})();<commit_msg>Updated module to be able to extend the crud module correctly.<commit_after>"use strict";

var mongoose = require('mongoose'),
    crud = require("./mixins/crud"),
    _ = require("underscore"),
    collectionName = "users",
    schema = require('./schemas/user');

function handleUser(err, doc, callback){
    if(err) {
        callback(err);
    }
    else if (doc != null) {
        callback(null, doc);
    }
    else {
        callback(new Error("User does not exist"));
    }
}

var user = Object.create(crud, {
    model: {value: mongoose.model(collectionName, schema)},
    privateFields: {value: ["salt", "pass_hash"]}
});

user.getByLogin = function(login, callback) {
    this.model.findOne({login: login}, this.buildIncludes()).lean().exec(function(err, doc){
        handleUser(err, doc, callback);
    });
};

user.getByEmail = function(email, callback) {
    this.model.findOne({email: email}, this.buildIncludes()).lean().exec(function(err, doc){
        handleUser(err, doc, callback);
    });
};

user.authenticate = function(login, password, callback){
    this.model.findOne({login: login}, function(err, doc){
        var valid = (doc != null) ? doc.authenticate(password) : false;

        if(!valid){
            handleUser(null, null, callback);
        }
        else {
            handleUser(err, doc, callback);
        }
    });
};

user.savePermissions = function(userId, nodeid, role, callback){
    this.model.findOne({_id: userId}, function(err, doc){
        if(err){
            callback(err);
        }
        else {
            var permissions = (!doc.permissions) ? [] : _.filter(doc.permissions, function(permission) {
                return permission.nodeid.toString() != nodeid;
            });

            permissions[permissions.length] = {nodeid: nodeid, role: role};

            doc.permissions = permissions;
            doc.save(function(err, results){
                callback(err, (!err) ? "Success" : null);
            });
        }
    });
};

user.deletePermission = function(userId, nodeId, callback){
    this.model.findOne({_id: userId}, function(err, doc){
        if(err){
            callback(err);
        }
        else {
            var permissions = (!doc.permissions) ? [] : _.filter(doc.permissions, function(permission) {
                return permission.nodeid.toString() != nodeId;
            });

            doc.permissions = permissions;
            doc.save(function(err, results){
                callback(err, (!err) ? "Success" : null);
            });
        }
    });
};

module.exports = user;<|endoftext|>


<commit_before>
/*
 * browser.js: Browser specific functionality for director.
 *
 * (C) 2011, Nodejitsu Inc.
 * MIT LICENSE
 *
 */

if (!Array.prototype.filter) {
  Array.prototype.filter = function(filter, that) {
    var other = [], v;
    for (var i = 0, n = this.length; i < n; i++) {
      if (i in this && filter.call(that, v = this[i], i, this)) {
        other.push(v);
      }
    }
    return other;
  };
}

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

var dloc = document.location;

var listener = {
  mode: 'modern',
  hash: dloc.hash,
  history: false,

  check: function () {
    var h = dloc.hash;
    if (h != this.hash) {
      this.hash = h;
      this.onHashChanged();
    }
  },

  fire: function () {
    if (this.mode === 'modern') {
      this.history === true ? window.onpopstate() : window.onhashchange();
    }
    else {
      this.onHashChanged();
    }
  },

  init: function (fn, history) {
    var self = this;
    this.history = history;

    if (!window.Router.listeners) {
      window.Router.listeners = [];
    }

    function onchange(onChangeEvent) {
      for (var i = 0, l = window.Router.listeners.length; i < l; i++) {
        window.Router.listeners[i](onChangeEvent);
      }
    }

    //note IE8 is being counted as 'modern' because it has the hashchange event
    if ('onhashchange' in window && (document.documentMode === undefined
      || document.documentMode > 7)) {
      // At least for now HTML5 history is available for 'modern' browsers only
      if (this.history === true) {
        // There is an old bug in Chrome that causes onpopstate to fire even
        // upon initial page load. Since the handler is run manually in init(),
        // this would cause Chrome to run it twise. Currently the only
        // workaround seems to be to set the handler after the initial page load
        // http://code.google.com/p/chromium/issues/detail?id=63040
        setTimeout(function() {
          window.onpopstate = onchange;
        }, 500);
      }
      else {
        window.onhashchange = onchange;
      }
      this.mode = 'modern';
    }
    else {
      //
      // IE support, based on a concept by Erik Arvidson ...
      //
      var frame = document.createElement('iframe');
      frame.id = 'state-frame';
      frame.style.display = 'none';
      document.body.appendChild(frame);
      this.writeFrame('');

      if ('onpropertychange' in document && 'attachEvent' in document) {
        document.attachEvent('onpropertychange', function () {
          if (event.propertyName === 'location') {
            self.check();
          }
        });
      }

      window.setInterval(function () { self.check(); }, 50);

      this.onHashChanged = onchange;
      this.mode = 'legacy';
    }

    window.Router.listeners.push(fn);

    return this.mode;
  },

  destroy: function (fn) {
    if (!window.Router || !window.Router.listeners) {
      return;
    }

    var listeners = window.Router.listeners;

    for (var i = listeners.length - 1; i >= 0; i--) {
      if (listeners[i] === fn) {
        listeners.splice(i, 1);
      }
    }
  },

  setHash: function (s) {
    // Mozilla always adds an entry to the history
    if (this.mode === 'legacy') {
      this.writeFrame(s);
    }

    if (this.history === true) {
      window.history.pushState({}, document.title, s);
      // Fire an onpopstate event manually since pushing does not obviously
      // trigger the pop event.
      this.fire();
    } else {
      dloc.hash = (s[0] === '/') ? s : '/' + s;
    }
    return this;
  },

  writeFrame: function (s) {
    // IE support...
    var f = document.getElementById('state-frame');
    var d = f.contentDocument || f.contentWindow.document;
    d.open();
    d.write("<script>_hash = '" + s + "'; onload = parent.listener.syncHash;<script>");
    d.close();
  },

  syncHash: function () {
    // IE support...
    var s = this._hash;
    if (s != dloc.hash) {
      dloc.hash = s;
    }
    return this;
  },

  onHashChanged: function () {}
};

var Router = exports.Router = function (routes) {
  if (!(this instanceof Router)) return new Router(routes);

  this.params   = {};
  this.routes   = {};
  this.methods  = ['on', 'once', 'after', 'before'];
  this._methods = {};

  this._insert = this.insert;
  this.insert = this.insertEx;

  this.historySupport = (window.history != null ? window.history.pushState : null) != null

  this.configure();
  this.mount(routes || {});
};

Router.prototype.init = function (r) {
  var self = this;
  this.handler = function(onChangeEvent) {
    var newURL = onChangeEvent && onChangeEvent.newURL || window.location.hash;
    var url = self.history === true ? self.getPath() : newURL.replace(/.*#/, '');
    self.dispatch('on', url);
  };

  listener.init(this.handler, this.history);

  if (this.history === false) {
    if (dloc.hash === '' && r) {
      dloc.hash = r;
    } else if (dloc.hash.length > 0) {
      self.dispatch('on', dloc.hash.replace(/^#/, ''));
    }
  }
  else {
    routeTo = dloc.hash === '' && r ? r : dloc.hash.length > 0 ? dloc.hash.replace(/^#/, '') : null;
    if (routeTo) {
      window.history.replaceState({}, document.title, routeTo);
    }

    // Router has been initialized, but due to the chrome bug it will not
    // yet actually route HTML5 history state changes. Thus, decide if should route.
    if (routeTo || this.run_in_init === true) {
      this.handler();
    }
  }

  return this;
};

Router.prototype.explode = function () {
  var v = this.history === true ? this.getPath() : dloc.hash;
  if (v[1] === '/') { v=v.slice(1) }
  return v.slice(1, v.length).split("/");
};

Router.prototype.setRoute = function (i, v, val) {
  var url = this.explode();

  if (typeof i === 'number' && typeof v === 'string') {
    url[i] = v;
  }
  else if (typeof val === 'string') {
    url.splice(i, v, s);
  }
  else {
    url = [i];
  }

  listener.setHash(url.join('/'));
  return url;
};

//
// ### function insertEx(method, path, route, parent)
// #### @method {string} Method to insert the specific `route`.
// #### @path {Array} Parsed path to insert the `route` at.
// #### @route {Array|function} Route handlers to insert.
// #### @parent {Object} **Optional** Parent "routes" to insert into.
// insert a callback that will only occur once per the matched route.
//
Router.prototype.insertEx = function(method, path, route, parent) {
  if (method === "once") {
    method = "on";
    route = function(route) {
      var once = false;
      return function() {
        if (once) return;
        once = true;
        return route.apply(this, arguments);
      };
    }(route);
  }
  return this._insert(method, path, route, parent);
};

Router.prototype.getRoute = function (v) {
  var ret = v;

  if (typeof v === "number") {
    ret = this.explode()[v];
  }
  else if (typeof v === "string"){
    var h = this.explode();
    ret = h.indexOf(v);
  }
  else {
    ret = this.explode();
  }

  return ret;
};

Router.prototype.destroy = function () {
  listener.destroy(this.handler);
  return this;
};

Router.prototype.getPath = function () {
  var path = window.location.pathname;
  if (path.substr(0, 1) !== '/') {
    path = '/' + path;
  }
  return path;
};
<commit_msg>Use Router instead of window.Router.<commit_after>
/*
 * browser.js: Browser specific functionality for director.
 *
 * (C) 2011, Nodejitsu Inc.
 * MIT LICENSE
 *
 */

if (!Array.prototype.filter) {
  Array.prototype.filter = function(filter, that) {
    var other = [], v;
    for (var i = 0, n = this.length; i < n; i++) {
      if (i in this && filter.call(that, v = this[i], i, this)) {
        other.push(v);
      }
    }
    return other;
  };
}

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

var dloc = document.location;

var listener = {
  mode: 'modern',
  hash: dloc.hash,
  history: false,

  check: function () {
    var h = dloc.hash;
    if (h != this.hash) {
      this.hash = h;
      this.onHashChanged();
    }
  },

  fire: function () {
    if (this.mode === 'modern') {
      this.history === true ? window.onpopstate() : window.onhashchange();
    }
    else {
      this.onHashChanged();
    }
  },

  init: function (fn, history) {
    var self = this;
    this.history = history;

    if (!Router.listeners) {
      Router.listeners = [];
    }

    function onchange(onChangeEvent) {
      for (var i = 0, l = Router.listeners.length; i < l; i++) {
        Router.listeners[i](onChangeEvent);
      }
    }

    //note IE8 is being counted as 'modern' because it has the hashchange event
    if ('onhashchange' in window && (document.documentMode === undefined
      || document.documentMode > 7)) {
      // At least for now HTML5 history is available for 'modern' browsers only
      if (this.history === true) {
        // There is an old bug in Chrome that causes onpopstate to fire even
        // upon initial page load. Since the handler is run manually in init(),
        // this would cause Chrome to run it twise. Currently the only
        // workaround seems to be to set the handler after the initial page load
        // http://code.google.com/p/chromium/issues/detail?id=63040
        setTimeout(function() {
          window.onpopstate = onchange;
        }, 500);
      }
      else {
        window.onhashchange = onchange;
      }
      this.mode = 'modern';
    }
    else {
      //
      // IE support, based on a concept by Erik Arvidson ...
      //
      var frame = document.createElement('iframe');
      frame.id = 'state-frame';
      frame.style.display = 'none';
      document.body.appendChild(frame);
      this.writeFrame('');

      if ('onpropertychange' in document && 'attachEvent' in document) {
        document.attachEvent('onpropertychange', function () {
          if (event.propertyName === 'location') {
            self.check();
          }
        });
      }

      window.setInterval(function () { self.check(); }, 50);

      this.onHashChanged = onchange;
      this.mode = 'legacy';
    }

    Router.listeners.push(fn);

    return this.mode;
  },

  destroy: function (fn) {
    if (!Router || !Router.listeners) {
      return;
    }

    var listeners = Router.listeners;

    for (var i = listeners.length - 1; i >= 0; i--) {
      if (listeners[i] === fn) {
        listeners.splice(i, 1);
      }
    }
  },

  setHash: function (s) {
    // Mozilla always adds an entry to the history
    if (this.mode === 'legacy') {
      this.writeFrame(s);
    }

    if (this.history === true) {
      window.history.pushState({}, document.title, s);
      // Fire an onpopstate event manually since pushing does not obviously
      // trigger the pop event.
      this.fire();
    } else {
      dloc.hash = (s[0] === '/') ? s : '/' + s;
    }
    return this;
  },

  writeFrame: function (s) {
    // IE support...
    var f = document.getElementById('state-frame');
    var d = f.contentDocument || f.contentWindow.document;
    d.open();
    d.write("<script>_hash = '" + s + "'; onload = parent.listener.syncHash;<script>");
    d.close();
  },

  syncHash: function () {
    // IE support...
    var s = this._hash;
    if (s != dloc.hash) {
      dloc.hash = s;
    }
    return this;
  },

  onHashChanged: function () {}
};

var Router = exports.Router = function (routes) {
  if (!(this instanceof Router)) return new Router(routes);

  this.params   = {};
  this.routes   = {};
  this.methods  = ['on', 'once', 'after', 'before'];
  this._methods = {};

  this._insert = this.insert;
  this.insert = this.insertEx;

  this.historySupport = (window.history != null ? window.history.pushState : null) != null

  this.configure();
  this.mount(routes || {});
};

Router.prototype.init = function (r) {
  var self = this;
  this.handler = function(onChangeEvent) {
    var newURL = onChangeEvent && onChangeEvent.newURL || window.location.hash;
    var url = self.history === true ? self.getPath() : newURL.replace(/.*#/, '');
    self.dispatch('on', url);
  };

  listener.init(this.handler, this.history);

  if (this.history === false) {
    if (dloc.hash === '' && r) {
      dloc.hash = r;
    } else if (dloc.hash.length > 0) {
      self.dispatch('on', dloc.hash.replace(/^#/, ''));
    }
  }
  else {
    routeTo = dloc.hash === '' && r ? r : dloc.hash.length > 0 ? dloc.hash.replace(/^#/, '') : null;
    if (routeTo) {
      window.history.replaceState({}, document.title, routeTo);
    }

    // Router has been initialized, but due to the chrome bug it will not
    // yet actually route HTML5 history state changes. Thus, decide if should route.
    if (routeTo || this.run_in_init === true) {
      this.handler();
    }
  }

  return this;
};

Router.prototype.explode = function () {
  var v = this.history === true ? this.getPath() : dloc.hash;
  if (v[1] === '/') { v=v.slice(1) }
  return v.slice(1, v.length).split("/");
};

Router.prototype.setRoute = function (i, v, val) {
  var url = this.explode();

  if (typeof i === 'number' && typeof v === 'string') {
    url[i] = v;
  }
  else if (typeof val === 'string') {
    url.splice(i, v, s);
  }
  else {
    url = [i];
  }

  listener.setHash(url.join('/'));
  return url;
};

//
// ### function insertEx(method, path, route, parent)
// #### @method {string} Method to insert the specific `route`.
// #### @path {Array} Parsed path to insert the `route` at.
// #### @route {Array|function} Route handlers to insert.
// #### @parent {Object} **Optional** Parent "routes" to insert into.
// insert a callback that will only occur once per the matched route.
//
Router.prototype.insertEx = function(method, path, route, parent) {
  if (method === "once") {
    method = "on";
    route = function(route) {
      var once = false;
      return function() {
        if (once) return;
        once = true;
        return route.apply(this, arguments);
      };
    }(route);
  }
  return this._insert(method, path, route, parent);
};

Router.prototype.getRoute = function (v) {
  var ret = v;

  if (typeof v === "number") {
    ret = this.explode()[v];
  }
  else if (typeof v === "string"){
    var h = this.explode();
    ret = h.indexOf(v);
  }
  else {
    ret = this.explode();
  }

  return ret;
};

Router.prototype.destroy = function () {
  listener.destroy(this.handler);
  return this;
};

Router.prototype.getPath = function () {
  var path = window.location.pathname;
  if (path.substr(0, 1) !== '/') {
    path = '/' + path;
  }
  return path;
};
<|endoftext|>


<commit_before>import {
  Route,
  IndexRoute,
  Redirect,
  IndexRedirect,
  Link,
  IndexLink
} from 'react-router'

import Component from './component'
import Render, { Router } from './render'
import { State, Effect, Hook, Actions, StateDefaults } from 'jumpstate'
import Goto from './routing'
import { Middleware } from './reducer'

module.exports = {
  /* Core */
  Render,
  Component,
  State,
  Effect,
  Hook,
  Actions,

  /* React Router */
  Router,
  Route,
  IndexRoute,
  Redirect,
  IndexRedirect,
  Link,
  IndexLink,
  Goto,

  /* Redux */
  Middleware,

  /* Other */
  StateDefaults
}
<commit_msg>Expose getState and dispatch from jumpstate<commit_after>import {
  Route,
  IndexRoute,
  Redirect,
  IndexRedirect,
  Link,
  IndexLink
} from 'react-router'

import Component from './component'
import Render, { Router } from './render'
import { State, Effect, Hook, Actions, StateDefaults, getState, dispatch } from 'jumpstate'
import Goto from './routing'
import { Middleware } from './reducer'

module.exports = {
  /* Core */
  Render,
  Component,
  State,
  Effect,
  Hook,
  Actions,

  /* React Router */
  Router,
  Route,
  IndexRoute,
  Redirect,
  IndexRedirect,
  Link,
  IndexLink,
  Goto,

  /* Redux */
  Middleware,
  getState,
  dispatch,

  /* Other */
  StateDefaults
}
<|endoftext|>


<commit_before>'use strict'

const ms = require('ms')
const {STATIC_ORIGIN} = process.env

module.exports = async (_, next) => {
  // Enforce https
  if (_.app.env === 'production') {
    // Redirect http to https
    if (_.protocol !== 'https') {
      _.redirect(`https://${_.hostname}${_.originalUrl}`)
      return
    }

    // HSTS Headers
    _.set('strict-transport-security', `max-age=${ms('1y') / 1000}; includeSubDomains; preload`)
  }

  await next()

  // HTML responses only
  if (_.response.is('html')) {
    // Referrer Policy
    _.set('referrer-policy', 'no-referrer')

    // Frame Options
    _.set('x-frame-options', 'SAMEORIGIN')

    // XSS
    _.set('x-xss-protection', '1; mode=block')

    // Content type
    _.set('x-content-type-options', 'nosniff')

    // Content Security Policy
    _.csp('img-src', STATIC_ORIGIN || "'self'")
    _.csp('script-src', STATIC_ORIGIN || "'self'")
    _.csp('style-src', STATIC_ORIGIN || "'self'")
    _.csp('frame-src', "'self'")
    _.csp('connect-src', "'self'")
    _.csp('default-src', "'self'")
    _.csp('img-src', 'data: https://www.google-analytics.com')
    _.csp('script-src', 'https://www.google-analytics.com')
  }
}
<commit_msg>Use #secure<commit_after>'use strict'

const ms = require('ms')
const {STATIC_ORIGIN} = process.env

module.exports = async (_, next) => {
  // Enforce https
  if (_.app.env === 'production') {
    // Redirect http to https
    if (!_.secure) {
      _.redirect(`https://${_.hostname}${_.originalUrl}`)
      return
    }

    // HSTS Headers
    _.set('strict-transport-security', `max-age=${ms('1y') / 1000}; includeSubDomains; preload`)
  }

  await next()

  // HTML responses only
  if (_.response.is('html')) {
    // Referrer Policy
    _.set('referrer-policy', 'no-referrer')

    // Frame Options
    _.set('x-frame-options', 'SAMEORIGIN')

    // XSS
    _.set('x-xss-protection', '1; mode=block')

    // Content type
    _.set('x-content-type-options', 'nosniff')

    // Content Security Policy
    _.csp('img-src', STATIC_ORIGIN || "'self'")
    _.csp('script-src', STATIC_ORIGIN || "'self'")
    _.csp('style-src', STATIC_ORIGIN || "'self'")
    _.csp('frame-src', "'self'")
    _.csp('connect-src', "'self'")
    _.csp('default-src', "'self'")
    _.csp('img-src', 'data: https://www.google-analytics.com')
    _.csp('script-src', 'https://www.google-analytics.com')
  }
}
<|endoftext|>