diff --git a/GhoulAlert/app.js b/GhoulAlert/app.js index ab7aed4..0a39b01 100644 --- a/GhoulAlert/app.js +++ b/GhoulAlert/app.js @@ -7,6 +7,10 @@ var logger = require('morgan'); var indexRouter = require('./routes/index'); var usersRouter = require('./routes/users'); +require('./config/passport'); +var apiV1IndexRouter = require('./routes/apiV1/index'); +var apiV1UsersRouter = require('./routes/apiV1/users'); + var app = express(); // view engine setup @@ -21,6 +25,8 @@ app.use(express.static(path.join(__dirname, 'public'))); app.use('/', indexRouter); app.use('/users', usersRouter); +app.use('/apiV1', apiV1IndexRouter); +app.use('/apiV1/users', apiV1UsersRouter); // catch 404 and forward to error handler app.use(function(req, res, next) { diff --git a/GhoulAlert/bin/www b/GhoulAlert/bin/www index 8f90936..c8a614d 100755 --- a/GhoulAlert/bin/www +++ b/GhoulAlert/bin/www @@ -7,6 +7,7 @@ var app = require('../app'); var debug = require('debug')('ghoulalert:server'); var http = require('http'); +var models = require('../models'); /** * Get port from environment and store in Express. @@ -21,13 +22,15 @@ app.set('port', port); var server = http.createServer(app); -/** - * Listen on provided port, on all network interfaces. - */ +models.sequelize.sync().then(function(){ + /** + * Listen on provided port, on all network interfaces. + */ -server.listen(port); -server.on('error', onError); -server.on('listening', onListening); + server.listen(port); + server.on('error', onError); + server.on('listening', onListening); +}) /** * Normalize a port into a number, string, or false. diff --git a/GhoulAlert/config/config.json b/GhoulAlert/config/config.json new file mode 100644 index 0000000..d10a249 --- /dev/null +++ b/GhoulAlert/config/config.json @@ -0,0 +1,23 @@ +{ + "development": { + "username": "ghoul", + "password": "ghoul_user!61", + "database": "ghoul_development", + "host": "127.0.0.1", + "dialect": "mysql" + }, + "test": { + "username": "ghoul", + "password": "ghoul_user!61", + "database": "ghoul_test", + "host": "127.0.0.1", + "dialect": "mysql" + }, + "production": { + "username": "root", + "password": null, + "database": "ghoul_production", + "host": "127.0.0.1", + "dialect": "mysql" + } +} diff --git a/GhoulAlert/config/passport.js b/GhoulAlert/config/passport.js new file mode 100644 index 0000000..e100fdb --- /dev/null +++ b/GhoulAlert/config/passport.js @@ -0,0 +1,19 @@ +'use strict'; + +var models = require('../models'); +var passport = require('passport'); +var localStrat = require('passport-local'); + +var User = models.User; + +passport.use(new localStrat({ + usernameField: 'username', + passwordField: 'password', +}, (username, password, done) => { + User.findOne({ where: { username: username } }).then((user) => { + if(!user || !user.validatePassword(password)) { + return done(null, false, { errors: {'username or password': 'is invalid'}}); + } + return done(null, user); + }).catch(done); +})); diff --git a/GhoulAlert/models/index.js b/GhoulAlert/models/index.js new file mode 100644 index 0000000..0c5236d --- /dev/null +++ b/GhoulAlert/models/index.js @@ -0,0 +1,41 @@ +'use strict'; + +const fs = require('fs'); +const path = require('path'); +const Sequelize = require('sequelize'); +const basename = path.basename(__filename); +const env = process.env.NODE_ENV || 'development'; +const config = require(__dirname + '/../config/config.json')[env]; +const db = {}; + +let sequelize; +if (config.use_env_variable) { + sequelize = new Sequelize(process.env[config.use_env_variable], config); +} else { + sequelize = new Sequelize(config.database, config.username, config.password, config); +} + +fs + .readdirSync(__dirname) + .filter(file => { + return (file.indexOf('.') !== 0) && (file !== basename) && (file.slice(-3) === '.js'); + }) + .forEach(file => { + const model = sequelize['import'](path.join(__dirname, file)); + db[model.name] = model; + }); + +Object.keys(db).forEach(modelName => { + if (db[modelName].associate) { + db[modelName].associate(db); + } +}); + +// Relationships go here +db.User.hasMany(db.Marker); +db.Marker.belongsTo(db.User); + +db.sequelize = sequelize; +db.Sequelize = Sequelize; + +module.exports = db; diff --git a/GhoulAlert/models/marker.js b/GhoulAlert/models/marker.js new file mode 100644 index 0000000..487839e --- /dev/null +++ b/GhoulAlert/models/marker.js @@ -0,0 +1,46 @@ +'use strict'; + +module.exports = (sequelize, DataTypes) => { + var Marker = sequelize.define('Marker', { + id: { + type: DataTypes.INTEGER, + primaryKey: true, + autoIncrement: true + }, + + latitude: { + type: DataTypes.FLOAT, + allowNull: false, + validate: { + notEmpty: true + } + }, + + longitude: { + type: DataTypes.FLOAT, + allowNull: false, + validate: { + notEmpty: true + } + }, + + name: { + type: DataTypes.STRING, + allowNull: false, + unique: { + args: true, + msg: "Name of the marker must be unique" + }, + validate: { + notEmpty: true + } + }, + + description: { + type: DataTypes.STRING, + allowNull: true, + } + }); + + return Marker; +} diff --git a/GhoulAlert/models/user.js b/GhoulAlert/models/user.js new file mode 100644 index 0000000..29b5ff3 --- /dev/null +++ b/GhoulAlert/models/user.js @@ -0,0 +1,67 @@ +'use strict'; +const crypto = require('crypto'); +const jwt = require('jsonwebtoken'); + +module.exports = (sequelize, DataTypes) => { + // Define the user model + var User = sequelize.define('User', { + // The user has an id, which identifies them and acts as a primary key + id: { + type: DataTypes.INTEGER, + primaryKey: true, + autoIncrement: true + }, + // The user has a "username", of the string datatype, that is unique + username: { + type: DataTypes.STRING, + allowNull: false, + unique: { + args: true, + msg: "Username already exists; it must be unique." + } + }, + + // The user has a "password", composed of a hash and salt + hash: DataTypes.TEXT('long'), + salt: DataTypes.TEXT('long') + }) + + User.prototype.setPassword = function(password) { + this.salt = crypto.randomBytes(16).toString('hex'); + this.hash = crypto.pbkdf2Sync(password, this.salt, 10000, 512, 'sha512').toString('hex'); + } + + User.prototype.validatePassword = function(password) { + const hash = crypto.pbkdf2Sync(password, this.salt, 10000, 512, 'sha512').toString('hex'); + return this.hash === hash; + } + + User.prototype.generateJWT = function() { + const today = new Date(); + const expires = new Date(today); + expires.setDate(today.getDate() + 30); + + return jwt.sign({ + username: this.username, + id: this.id, + exp: parseInt(expires.getTime() / 1000, 10), + }, 'secret'); + } + + User.prototype.toAuthJSON = function() { + return { + id: this.id, + username: this.username, + token: this.generateJWT() + }; + } + + User.prototype.toJSON = function() { + return { + id: this.id, + username: this.username + } + } + + return User; +} diff --git a/GhoulAlert/package-lock.json b/GhoulAlert/package-lock.json index 96ac84f..5de3660 100644 --- a/GhoulAlert/package-lock.json +++ b/GhoulAlert/package-lock.json @@ -17,11 +17,25 @@ "@types/babel-types": "*" } }, + "@types/geojson": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/@types/geojson/-/geojson-1.0.6.tgz", + "integrity": "sha512-Xqg/lIZMrUd0VRmSRbCAewtwGZiAk3mEUDvV4op1tGl+LvyPcb/MIOSxTl9z+9+J+R4/vpjiCAT4xeKzH9ji1w==" + }, + "@types/node": { + "version": "11.9.5", + "resolved": "https://registry.npmjs.org/@types/node/-/node-11.9.5.tgz", + "integrity": "sha512-vVjM0SVzgaOUpflq4GYBvCpozes8OgIIS5gVXVka+OfK3hvnkC1i93U8WiY2OtNE4XUWyyy/86Kf6e0IHTQw1Q==" + }, + "@types/semver": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/@types/semver/-/semver-5.5.0.tgz", + "integrity": "sha512-41qEJgBH/TWgo5NFSvBCJ1qkoi3Q6ONSF2avrHq1LVEZfYpdHmj0y9SuTK+u9ZhG1sYQKBL1AWXKyLWP4RaUoQ==" + }, "abbrev": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz", - "integrity": "sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==", - "dev": true + "integrity": "sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==" }, "accepts": { "version": "1.3.5", @@ -74,8 +88,7 @@ "ansi-regex": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz", - "integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=", - "dev": true + "integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=" }, "ansi-styles": { "version": "3.2.1", @@ -147,6 +160,11 @@ "integrity": "sha1-WWZ/QfrdTyDMvCu5a41Pf3jsA2c=", "dev": true }, + "async": { + "version": "1.5.2", + "resolved": "https://registry.npmjs.org/async/-/async-1.5.2.tgz", + "integrity": "sha1-7GphrlZIDAw8skHJVhjiCJL5Zyo=" + }, "async-each": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/async-each/-/async-each-1.0.1.tgz", @@ -187,8 +205,7 @@ "balanced-match": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz", - "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=", - "dev": true + "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=" }, "base": { "version": "0.11.2", @@ -265,6 +282,11 @@ "integrity": "sha512-EgmjVLMn22z7eGGv3kcnHwSnJXmFHjISTY9E/S5lIcTD3Oxw05QTcBLNkJFzcb3cNueUdF/IN4U+d78V0zO8Hw==", "dev": true }, + "bluebird": { + "version": "3.5.3", + "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.5.3.tgz", + "integrity": "sha512-/qKPUQlaW1OyR51WeCPBvRnAlnZFUJkCSG5HzGnuIqhgyJtF+T94lFnn33eiazjRm2LAHVy2guNnaq48X9SJuw==" + }, "body-parser": { "version": "1.18.3", "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.18.3.tgz", @@ -309,7 +331,6 @@ "version": "1.1.11", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", - "dev": true, "requires": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" @@ -344,6 +365,11 @@ } } }, + "buffer-equal-constant-time": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz", + "integrity": "sha1-+OcRMvf/5uAaXJaXpMbz5I1cyBk=" + }, "bytes": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.0.0.tgz", @@ -468,6 +494,26 @@ "integrity": "sha1-T6kXw+WclKAEzWH47lCdplFocUM=", "dev": true }, + "cli-color": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/cli-color/-/cli-color-1.4.0.tgz", + "integrity": "sha512-xu6RvQqqrWEo6MPR1eixqGPywhYBHRs653F9jfXB2Hx4jdM/3WxiNE1vppRmxtMIfl16SFYTpYlrnqH/HsK/2w==", + "requires": { + "ansi-regex": "^2.1.1", + "d": "1", + "es5-ext": "^0.10.46", + "es6-iterator": "^2.0.3", + "memoizee": "^0.4.14", + "timers-ext": "^0.1.5" + }, + "dependencies": { + "ansi-regex": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", + "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=" + } + } + }, "cliui": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/cliui/-/cliui-2.1.0.tgz", @@ -478,6 +524,20 @@ "wordwrap": "0.0.2" } }, + "cls-bluebird": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/cls-bluebird/-/cls-bluebird-2.1.0.tgz", + "integrity": "sha1-N+8eCAqP+1XC9BZPU28ZGeeWiu4=", + "requires": { + "is-bluebird": "^1.0.2", + "shimmer": "^1.1.0" + } + }, + "code-point-at": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/code-point-at/-/code-point-at-1.1.0.tgz", + "integrity": "sha1-DQcLTQQ6W+ozovGkDi7bPZpMz3c=" + }, "collection-visit": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/collection-visit/-/collection-visit-1.0.0.tgz", @@ -503,6 +563,11 @@ "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=", "dev": true }, + "commander": { + "version": "2.19.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.19.0.tgz", + "integrity": "sha512-6tvAOO+D6OENvRAh524Dh9jcfKTYDQAqvqezbCW82xj5X0pSrcpxtvRKHLG0yBY6SD7PSDrJaj+0AiOcKVd1Xg==" + }, "component-emitter": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/component-emitter/-/component-emitter-1.2.1.tgz", @@ -512,8 +577,16 @@ "concat-map": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", - "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=", - "dev": true + "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=" + }, + "config-chain": { + "version": "1.1.12", + "resolved": "https://registry.npmjs.org/config-chain/-/config-chain-1.1.12.tgz", + "integrity": "sha512-a1eOIcu8+7lUInge4Rpf/n4Krkf3Dd9lqhljRzII1/Zno/kRtUWnznPO3jOKBmTEktkt3fkxisUcivoj0ebzoA==", + "requires": { + "ini": "^1.3.4", + "proto-list": "~1.2.1" + } }, "configstore": { "version": "3.1.2", @@ -612,6 +685,14 @@ "integrity": "sha1-ojD2T1aDEOFJgAmUB5DsmVRbyn4=", "dev": true }, + "d": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/d/-/d-1.0.0.tgz", + "integrity": "sha1-dUu1v+VUUdpppYuU1F9MWwRi1Y8=", + "requires": { + "es5-ext": "^0.10.9" + } + }, "debug": { "version": "2.6.9", "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", @@ -684,6 +765,11 @@ } } }, + "denque": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/denque/-/denque-1.4.0.tgz", + "integrity": "sha512-gh513ac7aiKrAgjiIBWZG0EASyDF9p4JMWwKA8YU5s9figrL5SRNEMT6FDynsegakuhWd1wVqTvqvqAoDxw7wQ==" + }, "depd": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz", @@ -708,12 +794,45 @@ "is-obj": "^1.0.0" } }, + "dottie": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/dottie/-/dottie-2.0.1.tgz", + "integrity": "sha512-ch5OQgvGDK2u8pSZeSYAQaV/lczImd7pMJ7BcEPXmnFVjy4yJIzP6CsODJUTH8mg1tyH1Z2abOiuJO3DjZ/GBw==" + }, "duplexer3": { "version": "0.1.4", "resolved": "https://registry.npmjs.org/duplexer3/-/duplexer3-0.1.4.tgz", "integrity": "sha1-7gHdHKwO08vH/b6jfcCo8c4ALOI=", "dev": true }, + "ecdsa-sig-formatter": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.11.tgz", + "integrity": "sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ==", + "requires": { + "safe-buffer": "^5.0.1" + } + }, + "editorconfig": { + "version": "0.15.2", + "resolved": "https://registry.npmjs.org/editorconfig/-/editorconfig-0.15.2.tgz", + "integrity": "sha512-GWjSI19PVJAM9IZRGOS+YKI8LN+/sjkSjNyvxL5ucqP9/IqtYNXBaQ/6c/hkPNYQHyOHra2KoXZI/JVpuqwmcQ==", + "requires": { + "@types/node": "^10.11.7", + "@types/semver": "^5.5.0", + "commander": "^2.19.0", + "lru-cache": "^4.1.3", + "semver": "^5.6.0", + "sigmund": "^1.0.1" + }, + "dependencies": { + "@types/node": { + "version": "10.12.27", + "resolved": "https://registry.npmjs.org/@types/node/-/node-10.12.27.tgz", + "integrity": "sha512-e9wgeY6gaY21on3ve0xAjgBVjGDWq/xUteK0ujsE53bUoxycMkqfnkUgMt6ffZtykZ5X12Mg3T7Pw4TRCObDKg==" + } + } + }, "ee-first": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", @@ -724,6 +843,54 @@ "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", "integrity": "sha1-rT/0yG7C0CkyL1oCw6mmBslbP1k=" }, + "end-of-stream": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.1.tgz", + "integrity": "sha512-1MkrZNvWTKCaigbn+W15elq2BB/L22nqrSY5DKlo3X6+vclJm8Bb5djXJBmEX6fS3+zCh/F4VBK5Z2KxJt4s2Q==", + "requires": { + "once": "^1.4.0" + } + }, + "es5-ext": { + "version": "0.10.48", + "resolved": "https://registry.npmjs.org/es5-ext/-/es5-ext-0.10.48.tgz", + "integrity": "sha512-CdRvPlX/24Mj5L4NVxTs4804sxiS2CjVprgCmrgoDkdmjdY4D+ySHa7K3jJf8R40dFg0tIm3z/dk326LrnuSGw==", + "requires": { + "es6-iterator": "~2.0.3", + "es6-symbol": "~3.1.1", + "next-tick": "1" + } + }, + "es6-iterator": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/es6-iterator/-/es6-iterator-2.0.3.tgz", + "integrity": "sha1-p96IkUGgWpSwhUQDstCg+/qY87c=", + "requires": { + "d": "1", + "es5-ext": "^0.10.35", + "es6-symbol": "^3.1.1" + } + }, + "es6-symbol": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/es6-symbol/-/es6-symbol-3.1.1.tgz", + "integrity": "sha1-vwDvT9q2uhtG7Le2KbTH7VcVzHc=", + "requires": { + "d": "1", + "es5-ext": "~0.10.14" + } + }, + "es6-weak-map": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/es6-weak-map/-/es6-weak-map-2.0.2.tgz", + "integrity": "sha1-XjqzIlH/0VOKH45f+hNXdy+S2W8=", + "requires": { + "d": "1", + "es5-ext": "^0.10.14", + "es6-iterator": "^2.0.1", + "es6-symbol": "^3.1.1" + } + }, "escape-html": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", @@ -745,6 +912,15 @@ "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", "integrity": "sha1-Qa4u62XvpiJorr/qg6x9eSmbCIc=" }, + "event-emitter": { + "version": "0.3.5", + "resolved": "https://registry.npmjs.org/event-emitter/-/event-emitter-0.3.5.tgz", + "integrity": "sha1-34xp7vFkeSPHFXuc6DhAYQsCzDk=", + "requires": { + "d": "1", + "es5-ext": "~0.10.14" + } + }, "execa": { "version": "0.7.0", "resolved": "https://registry.npmjs.org/execa/-/execa-0.7.0.tgz", @@ -832,6 +1008,22 @@ "vary": "~1.1.2" } }, + "express-jwt": { + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/express-jwt/-/express-jwt-5.3.1.tgz", + "integrity": "sha512-1C9RNq0wMp/JvsH/qZMlg3SIPvKu14YkZ4YYv7gJQ1Vq+Dv8LH9tLKenS5vMNth45gTlEUGx+ycp9IHIlaHP/g==", + "requires": { + "async": "^1.5.0", + "express-unless": "^0.3.0", + "jsonwebtoken": "^8.1.0", + "lodash.set": "^4.0.0" + } + }, + "express-unless": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/express-unless/-/express-unless-0.3.1.tgz", + "integrity": "sha1-JVfBRudb65A+LSR/m1ugFFJpbiA=" + }, "extend-shallow": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-3.0.2.tgz", @@ -961,6 +1153,14 @@ "unpipe": "~1.0.0" } }, + "find-up": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-3.0.0.tgz", + "integrity": "sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==", + "requires": { + "locate-path": "^3.0.0" + } + }, "for-in": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/for-in/-/for-in-1.0.2.tgz", @@ -986,6 +1186,21 @@ "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", "integrity": "sha1-PYyt2Q2XZWn6g1qx+OSyOhBWBac=" }, + "fs-extra": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-7.0.1.tgz", + "integrity": "sha512-YJDaCJZEnBmcbw13fvdAM9AwNOJwOzrE4pqMqBq5nFiEqXUqHwlK4B+3pUw6JNvfSPtX05xFHtYy/1ni01eGCw==", + "requires": { + "graceful-fs": "^4.1.2", + "jsonfile": "^4.0.0", + "universalify": "^0.1.0" + } + }, + "fs.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=" + }, "fsevents": { "version": "1.2.7", "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-1.2.7.tgz", @@ -1006,7 +1221,8 @@ "ansi-regex": { "version": "2.1.1", "bundled": true, - "dev": true + "dev": true, + "optional": true }, "aproba": { "version": "1.2.0", @@ -1027,12 +1243,14 @@ "balanced-match": { "version": "1.0.0", "bundled": true, - "dev": true + "dev": true, + "optional": true }, "brace-expansion": { "version": "1.1.11", "bundled": true, "dev": true, + "optional": true, "requires": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" @@ -1047,17 +1265,20 @@ "code-point-at": { "version": "1.1.0", "bundled": true, - "dev": true + "dev": true, + "optional": true }, "concat-map": { "version": "0.0.1", "bundled": true, - "dev": true + "dev": true, + "optional": true }, "console-control-strings": { "version": "1.1.0", "bundled": true, - "dev": true + "dev": true, + "optional": true }, "core-util-is": { "version": "1.0.2", @@ -1174,7 +1395,8 @@ "inherits": { "version": "2.0.3", "bundled": true, - "dev": true + "dev": true, + "optional": true }, "ini": { "version": "1.3.5", @@ -1186,6 +1408,7 @@ "version": "1.0.0", "bundled": true, "dev": true, + "optional": true, "requires": { "number-is-nan": "^1.0.0" } @@ -1200,6 +1423,7 @@ "version": "3.0.4", "bundled": true, "dev": true, + "optional": true, "requires": { "brace-expansion": "^1.1.7" } @@ -1207,12 +1431,14 @@ "minimist": { "version": "0.0.8", "bundled": true, - "dev": true + "dev": true, + "optional": true }, "minipass": { "version": "2.3.5", "bundled": true, "dev": true, + "optional": true, "requires": { "safe-buffer": "^5.1.2", "yallist": "^3.0.0" @@ -1231,6 +1457,7 @@ "version": "0.5.1", "bundled": true, "dev": true, + "optional": true, "requires": { "minimist": "0.0.8" } @@ -1311,7 +1538,8 @@ "number-is-nan": { "version": "1.0.1", "bundled": true, - "dev": true + "dev": true, + "optional": true }, "object-assign": { "version": "4.1.1", @@ -1323,6 +1551,7 @@ "version": "1.4.0", "bundled": true, "dev": true, + "optional": true, "requires": { "wrappy": "1" } @@ -1408,7 +1637,8 @@ "safe-buffer": { "version": "5.1.2", "bundled": true, - "dev": true + "dev": true, + "optional": true }, "safer-buffer": { "version": "2.1.2", @@ -1444,6 +1674,7 @@ "version": "1.0.2", "bundled": true, "dev": true, + "optional": true, "requires": { "code-point-at": "^1.0.0", "is-fullwidth-code-point": "^1.0.0", @@ -1463,6 +1694,7 @@ "version": "3.0.1", "bundled": true, "dev": true, + "optional": true, "requires": { "ansi-regex": "^2.0.0" } @@ -1506,12 +1738,14 @@ "wrappy": { "version": "1.0.2", "bundled": true, - "dev": true + "dev": true, + "optional": true }, "yallist": { "version": "3.0.3", "bundled": true, - "dev": true + "dev": true, + "optional": true } } }, @@ -1520,6 +1754,24 @@ "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==" }, + "generate-function": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/generate-function/-/generate-function-2.3.1.tgz", + "integrity": "sha512-eeB5GfMNeevm/GRYq20ShmsaGcmI81kIX2K9XQx5miC8KdHaC6Jm0qQ8ZNeGOi7wYB8OsdxKs+Y2oVuTFuVwKQ==", + "requires": { + "is-property": "^1.0.2" + } + }, + "generic-pool": { + "version": "3.5.0", + "resolved": "https://registry.npmjs.org/generic-pool/-/generic-pool-3.5.0.tgz", + "integrity": "sha512-dEkxmX+egB2o4NR80c/q+xzLLzLX+k68/K8xv81XprD+Sk7ZtP14VugeCz+fUwv5FzpWq40pPtAkzPRqT8ka9w==" + }, + "get-caller-file": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-1.0.3.tgz", + "integrity": "sha512-3t6rVToeoZfYSGd8YoLFR2DJkiQrIiUrGcjvFX2mDw3bn6k2OtwHN0TNCLbBO+w8qTvimhDkv+LSscbJY1vE6w==" + }, "get-stream": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-3.0.0.tgz", @@ -1532,6 +1784,19 @@ "integrity": "sha1-3BXKHGcjh8p2vTesCjlbogQqLCg=", "dev": true }, + "glob": { + "version": "7.1.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.3.tgz", + "integrity": "sha512-vcfuiIxogLV4DlGBHIUOwI0IbrJ8HWPc4MU7HzviGeNho/UJDfi6B5p3sHeWIQ0KGIU0Jpxi5ZHxemQfLkkAwQ==", + "requires": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.0.4", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + } + }, "glob-parent": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-3.1.0.tgz", @@ -1584,8 +1849,7 @@ "graceful-fs": { "version": "4.1.15", "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.1.15.tgz", - "integrity": "sha512-6uHUhOPEBgQ24HM+r6b/QwWfZq+yiFcipKFrOFiBEnWdy5sdzYoi+pJeQaPI5qOLRFqWmAXUPQNsielzdLoecA==", - "dev": true + "integrity": "sha512-6uHUhOPEBgQ24HM+r6b/QwWfZq+yiFcipKFrOFiBEnWdy5sdzYoi+pJeQaPI5qOLRFqWmAXUPQNsielzdLoecA==" }, "has": { "version": "1.0.3", @@ -1670,6 +1934,20 @@ "integrity": "sha1-khi5srkoojixPcT7a21XbyMUU+o=", "dev": true }, + "inflection": { + "version": "1.12.0", + "resolved": "https://registry.npmjs.org/inflection/-/inflection-1.12.0.tgz", + "integrity": "sha1-ogCTVlbW9fa8TcdQLhrstwMihBY=" + }, + "inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", + "requires": { + "once": "^1.3.0", + "wrappy": "1" + } + }, "inherits": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", @@ -1678,8 +1956,12 @@ "ini": { "version": "1.3.5", "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.5.tgz", - "integrity": "sha512-RZY5huIKCMRWDUqZlEi72f/lmXKMvuszcMBduliQ3nnWbx9X/ZBQO7DijMEYS9EhHBb2qacRUMtC7svLwe0lcw==", - "dev": true + "integrity": "sha512-RZY5huIKCMRWDUqZlEi72f/lmXKMvuszcMBduliQ3nnWbx9X/ZBQO7DijMEYS9EhHBb2qacRUMtC7svLwe0lcw==" + }, + "invert-kv": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/invert-kv/-/invert-kv-2.0.0.tgz", + "integrity": "sha512-wPVv/y/QQ/Uiirj/vh3oP+1Ww+AWehmi1g5fFWGPF6IpCBCDVrhgHRMvrLfdYcwDh3QJbGXDW4JAuzxElLSqKA==" }, "ipaddr.js": { "version": "1.8.0", @@ -1704,6 +1986,11 @@ "binary-extensions": "^1.0.0" } }, + "is-bluebird": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-bluebird/-/is-bluebird-1.0.2.tgz", + "integrity": "sha1-CWQ5Bg9KpBGr7hkUOoTWpVNG1uI=" + }, "is-buffer": { "version": "1.1.6", "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.6.tgz", @@ -1777,8 +2064,7 @@ "is-fullwidth-code-point": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", - "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=", - "dev": true + "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=" }, "is-glob": { "version": "4.0.0", @@ -1843,6 +2129,11 @@ "resolved": "https://registry.npmjs.org/is-promise/-/is-promise-2.1.0.tgz", "integrity": "sha1-eaKp7OfwlugPNtKy87wWwf9L8/o=" }, + "is-property": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-property/-/is-property-1.0.2.tgz", + "integrity": "sha1-V/4cTkhHTt1lsJkR8msc1Ald2oQ=" + }, "is-redirect": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/is-redirect/-/is-redirect-1.0.0.tgz", @@ -1866,8 +2157,7 @@ "is-stream": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-1.1.0.tgz", - "integrity": "sha1-EtSj3U5o4Lec6428hBc66A2RykQ=", - "dev": true + "integrity": "sha1-EtSj3U5o4Lec6428hBc66A2RykQ=" }, "is-windows": { "version": "1.0.2", @@ -1884,8 +2174,7 @@ "isexe": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", - "integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=", - "dev": true + "integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=" }, "isobject": { "version": "3.0.1", @@ -1893,11 +2182,66 @@ "integrity": "sha1-TkMekrEalzFjaqH5yNHMvP2reN8=", "dev": true }, + "js-beautify": { + "version": "1.8.9", + "resolved": "https://registry.npmjs.org/js-beautify/-/js-beautify-1.8.9.tgz", + "integrity": "sha512-MwPmLywK9RSX0SPsUJjN7i+RQY9w/yC17Lbrq9ViEefpLRgqAR2BgrMN2AbifkUuhDV8tRauLhLda/9+bE0YQA==", + "requires": { + "config-chain": "^1.1.12", + "editorconfig": "^0.15.2", + "glob": "^7.1.3", + "mkdirp": "~0.5.0", + "nopt": "~4.0.1" + }, + "dependencies": { + "nopt": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/nopt/-/nopt-4.0.1.tgz", + "integrity": "sha1-0NRoWv1UFRk8jHUFYC0NF81kR00=", + "requires": { + "abbrev": "1", + "osenv": "^0.1.4" + } + } + } + }, "js-stringify": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/js-stringify/-/js-stringify-1.0.2.tgz", "integrity": "sha1-Fzb939lyTyijaCrcYjCufk6Weds=" }, + "jsonfile": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-4.0.0.tgz", + "integrity": "sha1-h3Gq4HmbZAdrdmQPygWPnBDjPss=", + "requires": { + "graceful-fs": "^4.1.6" + } + }, + "jsonwebtoken": { + "version": "8.5.1", + "resolved": "https://registry.npmjs.org/jsonwebtoken/-/jsonwebtoken-8.5.1.tgz", + "integrity": "sha512-XjwVfRS6jTMsqYs0EsuJ4LGxXV14zQybNd4L2r0UvbVnSF9Af8x7p5MzbJ90Ioz/9TI41/hTCvznF/loiSzn8w==", + "requires": { + "jws": "^3.2.2", + "lodash.includes": "^4.3.0", + "lodash.isboolean": "^3.0.3", + "lodash.isinteger": "^4.0.4", + "lodash.isnumber": "^3.0.3", + "lodash.isplainobject": "^4.0.6", + "lodash.isstring": "^4.0.1", + "lodash.once": "^4.0.0", + "ms": "^2.1.1", + "semver": "^5.6.0" + }, + "dependencies": { + "ms": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.1.tgz", + "integrity": "sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg==" + } + } + }, "jstransformer": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/jstransformer/-/jstransformer-1.0.0.tgz", @@ -1907,6 +2251,25 @@ "promise": "^7.0.1" } }, + "jwa": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/jwa/-/jwa-1.4.1.tgz", + "integrity": "sha512-qiLX/xhEEFKUAJ6FiBMbes3w9ATzyk5W7Hvzpa/SLYdxNtng+gcurvrI7TbACjIXlsJyr05/S1oUhZrc63evQA==", + "requires": { + "buffer-equal-constant-time": "1.0.1", + "ecdsa-sig-formatter": "1.0.11", + "safe-buffer": "^5.0.1" + } + }, + "jws": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/jws/-/jws-3.2.2.tgz", + "integrity": "sha512-YHlZCB6lMTllWDtSPHz/ZXTsi8S00usEV6v1tjq8tOUZzw7DpSDWVXjXDre6ed1w/pd495ODpHZYSdkRTsa0HA==", + "requires": { + "jwa": "^1.4.1", + "safe-buffer": "^5.0.1" + } + }, "kind-of": { "version": "3.2.2", "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", @@ -1929,11 +2292,73 @@ "resolved": "https://registry.npmjs.org/lazy-cache/-/lazy-cache-1.0.4.tgz", "integrity": "sha1-odePw6UEdMuAhF07O24dpJpEbo4=" }, + "lcid": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/lcid/-/lcid-2.0.0.tgz", + "integrity": "sha512-avPEb8P8EGnwXKClwsNUgryVjllcRqtMYa49NTsbQagYuT1DcXnl1915oxWjoyGrXR6zH/Y0Zc96xWsPcoDKeA==", + "requires": { + "invert-kv": "^2.0.0" + } + }, + "locate-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-3.0.0.tgz", + "integrity": "sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A==", + "requires": { + "p-locate": "^3.0.0", + "path-exists": "^3.0.0" + } + }, "lodash": { "version": "4.17.11", "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.11.tgz", "integrity": "sha512-cQKh8igo5QUhZ7lg38DYWAxMvjSAKG0A8wGSVimP07SIUEK2UO+arSRKbRZWtelMtN5V0Hkwh5ryOto/SshYIg==" }, + "lodash.includes": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/lodash.includes/-/lodash.includes-4.3.0.tgz", + "integrity": "sha1-YLuYqHy5I8aMoeUTJUgzFISfVT8=" + }, + "lodash.isboolean": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/lodash.isboolean/-/lodash.isboolean-3.0.3.tgz", + "integrity": "sha1-bC4XHbKiV82WgC/UOwGyDV9YcPY=" + }, + "lodash.isinteger": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/lodash.isinteger/-/lodash.isinteger-4.0.4.tgz", + "integrity": "sha1-YZwK89A/iwTDH1iChAt3sRzWg0M=" + }, + "lodash.isnumber": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/lodash.isnumber/-/lodash.isnumber-3.0.3.tgz", + "integrity": "sha1-POdoEMWSjQM1IwGsKHMX8RwLH/w=" + }, + "lodash.isplainobject": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz", + "integrity": "sha1-fFJqUtibRcRcxpC4gWO+BJf1UMs=" + }, + "lodash.isstring": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/lodash.isstring/-/lodash.isstring-4.0.1.tgz", + "integrity": "sha1-1SfftUVuynzJu5XV2ur4i6VKVFE=" + }, + "lodash.once": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/lodash.once/-/lodash.once-4.1.1.tgz", + "integrity": "sha1-DdOXEhPHxW34gJd9UEyI+0cal6w=" + }, + "lodash.set": { + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/lodash.set/-/lodash.set-4.3.2.tgz", + "integrity": "sha1-2HV7HagH3eJIFrDWqEvqGnYjCyM=" + }, + "long": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/long/-/long-4.0.0.tgz", + "integrity": "sha512-XsP+KhQif4bjX1kbuSiySJFNAehNxgLb6hPRGJ9QsUr8ajHkuXGdrHmFUTUUXhDwVX2R5bY4JNZEwbUiMhV+MA==" + }, "longest": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/longest/-/longest-1.0.1.tgz", @@ -1949,12 +2374,19 @@ "version": "4.1.5", "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-4.1.5.tgz", "integrity": "sha512-sWZlbEP2OsHNkXrMl5GYk/jKk70MBng6UU4YI/qGDYbgf6YbP4EvmqISbXCoJiRKs+1bSpFHVgQxvJ17F2li5g==", - "dev": true, "requires": { "pseudomap": "^1.0.2", "yallist": "^2.1.2" } }, + "lru-queue": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/lru-queue/-/lru-queue-0.1.0.tgz", + "integrity": "sha1-Jzi9nw089PhEkMVzbEhpmsYyzaM=", + "requires": { + "es5-ext": "~0.10.2" + } + }, "make-dir": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-1.3.0.tgz", @@ -1964,6 +2396,14 @@ "pify": "^3.0.0" } }, + "map-age-cleaner": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/map-age-cleaner/-/map-age-cleaner-0.1.3.tgz", + "integrity": "sha512-bJzx6nMoP6PDLPBFmg7+xRKeFZvFboMrGlxmNj9ClvX53KrmvM5bXFXEWjbz4cz1AFn+jWJ9z/DJSz7hrs0w3w==", + "requires": { + "p-defer": "^1.0.0" + } + }, "map-cache": { "version": "0.2.2", "resolved": "https://registry.npmjs.org/map-cache/-/map-cache-0.2.2.tgz", @@ -1984,6 +2424,31 @@ "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", "integrity": "sha1-hxDXrwqmJvj/+hzgAWhUUmMlV0g=" }, + "mem": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/mem/-/mem-4.1.0.tgz", + "integrity": "sha512-I5u6Q1x7wxO0kdOpYBB28xueHADYps5uty/zg936CiG8NTe5sJL8EjrCuLneuDW3PlMdZBGDIn8BirEVdovZvg==", + "requires": { + "map-age-cleaner": "^0.1.1", + "mimic-fn": "^1.0.0", + "p-is-promise": "^2.0.0" + } + }, + "memoizee": { + "version": "0.4.14", + "resolved": "https://registry.npmjs.org/memoizee/-/memoizee-0.4.14.tgz", + "integrity": "sha512-/SWFvWegAIYAO4NQMpcX+gcra0yEZu4OntmUdrBaWrJncxOqAziGFlHxc7yjKVK2uu3lpPW27P27wkR82wA8mg==", + "requires": { + "d": "1", + "es5-ext": "^0.10.45", + "es6-weak-map": "^2.0.2", + "event-emitter": "^0.3.5", + "is-promise": "^2.1", + "lru-queue": "0.1", + "next-tick": "1", + "timers-ext": "^0.1.5" + } + }, "merge-descriptors": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.1.tgz", @@ -2041,11 +2506,15 @@ "mime-db": "~1.38.0" } }, + "mimic-fn": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-1.2.0.tgz", + "integrity": "sha512-jf84uxzwiuiIVKiOLpfYk7N46TSy8ubTonmneY9vrpHNAnp0QBt2BxWV9dO3/j+BoVAb+a5G6YDPW3M5HOdMWQ==" + }, "minimatch": { "version": "3.0.4", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", - "dev": true, "requires": { "brace-expansion": "^1.1.7" } @@ -2077,6 +2546,34 @@ } } }, + "mkdirp": { + "version": "0.5.1", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz", + "integrity": "sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM=", + "requires": { + "minimist": "0.0.8" + }, + "dependencies": { + "minimist": { + "version": "0.0.8", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz", + "integrity": "sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0=" + } + } + }, + "moment": { + "version": "2.24.0", + "resolved": "https://registry.npmjs.org/moment/-/moment-2.24.0.tgz", + "integrity": "sha512-bV7f+6l2QigeBBZSM/6yTNq4P2fNpSWj/0e7jQcy87A8e7o2nAfP/34/2ky5Vw4B9S446EtIhodAzkFCcR4dQg==" + }, + "moment-timezone": { + "version": "0.5.23", + "resolved": "https://registry.npmjs.org/moment-timezone/-/moment-timezone-0.5.23.tgz", + "integrity": "sha512-WHFH85DkCfiNMDX5D3X7hpNH3/PUhjTGcD0U1SgfBGZxJ3qUmJh5FdvaFjcClxOvB3rzdfj4oRffbI38jEnC1w==", + "requires": { + "moment": ">= 2.9.0" + } + }, "morgan": { "version": "1.9.1", "resolved": "https://registry.npmjs.org/morgan/-/morgan-1.9.1.tgz", @@ -2094,6 +2591,39 @@ "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" }, + "mysql2": { + "version": "1.6.5", + "resolved": "https://registry.npmjs.org/mysql2/-/mysql2-1.6.5.tgz", + "integrity": "sha512-zedaOOyb3msuuZcJJnxIX/EGOpmljDG7B+UevRH5lqcv+yhy9eCwkArBz8/AO+/rlY3/oCsOdG8R5oD6k0hNfg==", + "requires": { + "denque": "^1.4.0", + "generate-function": "^2.3.1", + "iconv-lite": "^0.4.24", + "long": "^4.0.0", + "lru-cache": "^4.1.3", + "named-placeholders": "^1.1.2", + "seq-queue": "^0.0.5", + "sqlstring": "^2.3.1" + }, + "dependencies": { + "iconv-lite": { + "version": "0.4.24", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", + "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", + "requires": { + "safer-buffer": ">= 2.1.2 < 3" + } + } + } + }, + "named-placeholders": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/named-placeholders/-/named-placeholders-1.1.2.tgz", + "integrity": "sha512-wiFWqxoLL3PGVReSZpjLVxyJ1bRqe+KKJVbr4hGs1KWfTZTQyezHFBbuKj9hsizHyGV2ne7EMjHdxEGAybD5SA==", + "requires": { + "lru-cache": "^4.1.3" + } + }, "nan": { "version": "2.12.1", "resolved": "https://registry.npmjs.org/nan/-/nan-2.12.1.tgz", @@ -2133,6 +2663,16 @@ "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.1.tgz", "integrity": "sha1-KzJxhOiZIQEXeyhWP7XnECrNDKk=" }, + "next-tick": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/next-tick/-/next-tick-1.0.0.tgz", + "integrity": "sha1-yobR/ogoFpsBICCOPchCS524NCw=" + }, + "nice-try": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/nice-try/-/nice-try-1.0.5.tgz", + "integrity": "sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ==" + }, "nodemon": { "version": "1.18.10", "resolved": "https://registry.npmjs.org/nodemon/-/nodemon-1.18.10.tgz", @@ -2187,11 +2727,15 @@ "version": "2.0.2", "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-2.0.2.tgz", "integrity": "sha1-NakjLfo11wZ7TLLd8jV7GHFTbF8=", - "dev": true, "requires": { "path-key": "^2.0.0" } }, + "number-is-nan": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/number-is-nan/-/number-is-nan-1.0.1.tgz", + "integrity": "sha1-CXtgK1NCKlIsGvuHkDGDNpQaAR0=" + }, "object-assign": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", @@ -2250,11 +2794,114 @@ "resolved": "https://registry.npmjs.org/on-headers/-/on-headers-1.0.2.tgz", "integrity": "sha512-pZAE+FJLoyITytdqK0U5s+FIpjN0JP3OzFi/u8Rx+EV5/W+JTWGXG8xFzevE7AjBfDqHv/8vL8qQsIhHnqRkrA==" }, + "once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", + "requires": { + "wrappy": "1" + } + }, + "os-homedir": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/os-homedir/-/os-homedir-1.0.2.tgz", + "integrity": "sha1-/7xJiDNuDoM94MFox+8VISGqf7M=" + }, + "os-locale": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/os-locale/-/os-locale-3.1.0.tgz", + "integrity": "sha512-Z8l3R4wYWM40/52Z+S265okfFj8Kt2cC2MKY+xNi3kFs+XGI7WXu/I309QQQYbRW4ijiZ+yxs9pqEhJh0DqW3Q==", + "requires": { + "execa": "^1.0.0", + "lcid": "^2.0.0", + "mem": "^4.0.0" + }, + "dependencies": { + "cross-spawn": { + "version": "6.0.5", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-6.0.5.tgz", + "integrity": "sha512-eTVLrBSt7fjbDygz805pMnstIs2VTBNkRm0qxZd+M7A5XDdxVRWO5MxGBXZhjY4cqLYLdtrGqRf8mBPmzwSpWQ==", + "requires": { + "nice-try": "^1.0.4", + "path-key": "^2.0.1", + "semver": "^5.5.0", + "shebang-command": "^1.2.0", + "which": "^1.2.9" + } + }, + "execa": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/execa/-/execa-1.0.0.tgz", + "integrity": "sha512-adbxcyWV46qiHyvSp50TKt05tB4tK3HcmF7/nxfAdhnox83seTDbwnaqKO4sXRy7roHAIFqJP/Rw/AuEbX61LA==", + "requires": { + "cross-spawn": "^6.0.0", + "get-stream": "^4.0.0", + "is-stream": "^1.1.0", + "npm-run-path": "^2.0.0", + "p-finally": "^1.0.0", + "signal-exit": "^3.0.0", + "strip-eof": "^1.0.0" + } + }, + "get-stream": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-4.1.0.tgz", + "integrity": "sha512-GMat4EJ5161kIy2HevLlr4luNjBgvmj413KaQA7jt4V8B4RDsfpHk7WQ9GVqfYyyx8OS/L66Kox+rJRNklLK7w==", + "requires": { + "pump": "^3.0.0" + } + } + } + }, + "os-tmpdir": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/os-tmpdir/-/os-tmpdir-1.0.2.tgz", + "integrity": "sha1-u+Z0BseaqFxc/sdm/lc0VV36EnQ=" + }, + "osenv": { + "version": "0.1.5", + "resolved": "https://registry.npmjs.org/osenv/-/osenv-0.1.5.tgz", + "integrity": "sha512-0CWcCECdMVc2Rw3U5w9ZjqX6ga6ubk1xDVKxtBQPK7wis/0F2r9T6k4ydGYhecl7YUBxBVxhL5oisPsNxAPe2g==", + "requires": { + "os-homedir": "^1.0.0", + "os-tmpdir": "^1.0.0" + } + }, + "p-defer": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/p-defer/-/p-defer-1.0.0.tgz", + "integrity": "sha1-n26xgvbJqozXQwBKfU+WsZaw+ww=" + }, "p-finally": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/p-finally/-/p-finally-1.0.0.tgz", - "integrity": "sha1-P7z7FbiZpEEjs0ttzBi3JDNqLK4=", - "dev": true + "integrity": "sha1-P7z7FbiZpEEjs0ttzBi3JDNqLK4=" + }, + "p-is-promise": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/p-is-promise/-/p-is-promise-2.0.0.tgz", + "integrity": "sha512-pzQPhYMCAgLAKPWD2jC3Se9fEfrD9npNos0y150EeqZll7akhEgGhTW/slB6lHku8AvYGiJ+YJ5hfHKePPgFWg==" + }, + "p-limit": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.1.0.tgz", + "integrity": "sha512-NhURkNcrVB+8hNfLuysU8enY5xn2KXphsHBaC2YmRNTZRc7RWusw6apSpdEj3jo4CMb6W9nrF6tTnsJsJeyu6g==", + "requires": { + "p-try": "^2.0.0" + } + }, + "p-locate": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-3.0.0.tgz", + "integrity": "sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ==", + "requires": { + "p-limit": "^2.0.0" + } + }, + "p-try": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.0.0.tgz", + "integrity": "sha512-hMp0onDKIajHfIkdRk3P4CdCmErkYAxxDtP3Wx/4nZ3aGlau2VKh3mZpcuFkH27WQkL/3WBCPOktzA9ZOAnMQQ==" }, "package-json": { "version": "4.0.1", @@ -2279,17 +2926,43 @@ "integrity": "sha1-s2PlXoAGym/iF4TS2yK9FdeRfxQ=", "dev": true }, + "passport": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/passport/-/passport-0.4.0.tgz", + "integrity": "sha1-xQlWkTR71a07XhgCOMORTRbwWBE=", + "requires": { + "passport-strategy": "1.x.x", + "pause": "0.0.1" + } + }, + "passport-local": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/passport-local/-/passport-local-1.0.0.tgz", + "integrity": "sha1-H+YyaMkudWBmJkN+O5BmYsFbpu4=", + "requires": { + "passport-strategy": "1.x.x" + } + }, + "passport-strategy": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/passport-strategy/-/passport-strategy-1.0.0.tgz", + "integrity": "sha1-tVOaqPwiWj0a0XlHbd8ja0QPUuQ=" + }, "path-dirname": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/path-dirname/-/path-dirname-1.0.2.tgz", "integrity": "sha1-zDPSTVJeCZpTiMAzbG4yuRYGCeA=", "dev": true }, + "path-exists": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-3.0.0.tgz", + "integrity": "sha1-zg6+ql94yxiSXqfYENe1mwEP1RU=" + }, "path-is-absolute": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", - "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=", - "dev": true + "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=" }, "path-is-inside": { "version": "1.0.2", @@ -2300,8 +2973,7 @@ "path-key": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/path-key/-/path-key-2.0.1.tgz", - "integrity": "sha1-QRyttXTFoUDTpLGRDUDYDMn0C0A=", - "dev": true + "integrity": "sha1-QRyttXTFoUDTpLGRDUDYDMn0C0A=" }, "path-parse": { "version": "1.0.6", @@ -2313,6 +2985,11 @@ "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz", "integrity": "sha1-32BBeABfUi8V60SQ5yR6G/qmf4w=" }, + "pause": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/pause/-/pause-0.0.1.tgz", + "integrity": "sha1-HUCLP9t2kjuVQ9lvtMnf1TXZy10=" + }, "pify": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/pify/-/pify-3.0.0.tgz", @@ -2345,6 +3022,11 @@ "asap": "~2.0.3" } }, + "proto-list": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/proto-list/-/proto-list-1.2.4.tgz", + "integrity": "sha1-IS1b/hMYMGpCD2QCuOJv85ZHqEk=" + }, "proxy-addr": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.4.tgz", @@ -2357,8 +3039,7 @@ "pseudomap": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/pseudomap/-/pseudomap-1.0.2.tgz", - "integrity": "sha1-8FKijacOYYkX7wqKw0wa5aaChrM=", - "dev": true + "integrity": "sha1-8FKijacOYYkX7wqKw0wa5aaChrM=" }, "pstree.remy": { "version": "1.1.6", @@ -2480,6 +3161,15 @@ "resolved": "https://registry.npmjs.org/pug-walk/-/pug-walk-1.1.7.tgz", "integrity": "sha1-wA1cUSi6xYBr7BXSt+fNq+QlMfM=" }, + "pump": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz", + "integrity": "sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==", + "requires": { + "end-of-stream": "^1.1.0", + "once": "^1.3.1" + } + }, "qs": { "version": "6.5.2", "resolved": "https://registry.npmjs.org/qs/-/qs-6.5.2.tgz", @@ -2590,6 +3280,16 @@ "resolved": "https://registry.npmjs.org/repeat-string/-/repeat-string-1.6.1.tgz", "integrity": "sha1-jcrkcOHIirwtYA//Sndihtp15jc=" }, + "require-directory": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", + "integrity": "sha1-jGStX9MNqxyXbiNE/+f3kqam30I=" + }, + "require-main-filename": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/require-main-filename/-/require-main-filename-1.0.1.tgz", + "integrity": "sha1-l/cXtp1IeE9fUmpsWqj/3aBVpNE=" + }, "resolve": { "version": "1.10.0", "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.10.0.tgz", @@ -2610,6 +3310,15 @@ "integrity": "sha512-TTlYpa+OL+vMMNG24xSlQGEJ3B/RzEfUlLct7b5G/ytav+wPrplCpVMFuwzXbkecJrb6IYo1iFb0S9v37754mg==", "dev": true }, + "retry-as-promised": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/retry-as-promised/-/retry-as-promised-2.3.2.tgz", + "integrity": "sha1-zZdO5P2bX+A8vzGHHuSCIcB3N7c=", + "requires": { + "bluebird": "^3.4.6", + "debug": "^2.6.9" + } + }, "right-align": { "version": "0.1.3", "resolved": "https://registry.npmjs.org/right-align/-/right-align-0.1.3.tgz", @@ -2640,8 +3349,7 @@ "semver": { "version": "5.6.0", "resolved": "https://registry.npmjs.org/semver/-/semver-5.6.0.tgz", - "integrity": "sha512-RS9R6R35NYgQn++fkDWaOmqGoj4Ek9gGs+DPxNUZKuwE183xjJroKvyo1IzVFeXvUrvmALy6FWD5xrdJT25gMg==", - "dev": true + "integrity": "sha512-RS9R6R35NYgQn++fkDWaOmqGoj4Ek9gGs+DPxNUZKuwE183xjJroKvyo1IzVFeXvUrvmALy6FWD5xrdJT25gMg==" }, "semver-diff": { "version": "2.1.0", @@ -2672,6 +3380,96 @@ "statuses": "~1.4.0" } }, + "seq-queue": { + "version": "0.0.5", + "resolved": "https://registry.npmjs.org/seq-queue/-/seq-queue-0.0.5.tgz", + "integrity": "sha1-1WgS4cAXpuTnw+Ojeh2m143TyT4=" + }, + "sequelize": { + "version": "4.42.1", + "resolved": "https://registry.npmjs.org/sequelize/-/sequelize-4.42.1.tgz", + "integrity": "sha512-W9i/CkBjCHLzEkJQkaxXaK82MA16b7F74PjtE7EUR+d7WU/X3U+YU5givWR+/VRXay1VXDyBOfXgw9/zdhDSDg==", + "requires": { + "bluebird": "^3.5.0", + "cls-bluebird": "^2.1.0", + "debug": "^3.1.0", + "depd": "^1.1.0", + "dottie": "^2.0.0", + "generic-pool": "3.5.0", + "inflection": "1.12.0", + "lodash": "^4.17.1", + "moment": "^2.20.0", + "moment-timezone": "^0.5.14", + "retry-as-promised": "^2.3.2", + "semver": "^5.5.0", + "terraformer-wkt-parser": "^1.1.2", + "toposort-class": "^1.0.1", + "uuid": "^3.2.1", + "validator": "^10.4.0", + "wkx": "^0.4.1" + }, + "dependencies": { + "debug": { + "version": "3.2.6", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.6.tgz", + "integrity": "sha512-mel+jf7nrtEl5Pn1Qx46zARXKDpBbvzezse7p7LqINmdoIk8PYP5SySaxEmYv6TZ0JyEKA1hsCId6DIhgITtWQ==", + "requires": { + "ms": "^2.1.1" + } + }, + "ms": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.1.tgz", + "integrity": "sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg==" + } + } + }, + "sequelize-cli": { + "version": "5.4.0", + "resolved": "https://registry.npmjs.org/sequelize-cli/-/sequelize-cli-5.4.0.tgz", + "integrity": "sha512-4Gvl0yH0T3hhSdiiOci3+IKIfVG9x2os0hGWsbfa8QuyGgk9mZOqgTBnSCRtuxsdAyzUix9kfcTnfNolVNtprg==", + "requires": { + "bluebird": "^3.5.3", + "cli-color": "^1.4.0", + "fs-extra": "^7.0.1", + "js-beautify": "^1.8.8", + "lodash": "^4.17.5", + "resolve": "^1.5.0", + "umzug": "^2.1.0", + "yargs": "^12.0.5" + }, + "dependencies": { + "cliui": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-4.1.0.tgz", + "integrity": "sha512-4FG+RSG9DL7uEwRUZXZn3SS34DiDPfzP0VOiEwtUWlE+AR2EIg+hSyvrIgUUfhdgR/UkAeW2QHgeP+hWrXs7jQ==", + "requires": { + "string-width": "^2.1.1", + "strip-ansi": "^4.0.0", + "wrap-ansi": "^2.0.0" + } + }, + "yargs": { + "version": "12.0.5", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-12.0.5.tgz", + "integrity": "sha512-Lhz8TLaYnxq/2ObqHDql8dX8CJi97oHxrjUcYtzKbbykPtVW9WB+poxI+NM2UIzsMgNCZTIf0AQwsjK5yMAqZw==", + "requires": { + "cliui": "^4.0.0", + "decamelize": "^1.2.0", + "find-up": "^3.0.0", + "get-caller-file": "^1.0.1", + "os-locale": "^3.0.0", + "require-directory": "^2.1.1", + "require-main-filename": "^1.0.1", + "set-blocking": "^2.0.0", + "string-width": "^2.0.0", + "which-module": "^2.0.0", + "y18n": "^3.2.1 || ^4.0.0", + "yargs-parser": "^11.1.1" + } + } + } + }, "serve-static": { "version": "1.13.2", "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.13.2.tgz", @@ -2683,6 +3481,11 @@ "send": "0.16.2" } }, + "set-blocking": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz", + "integrity": "sha1-BF+XgtARrppoA93TgrJDkrPYkPc=" + }, "set-value": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/set-value/-/set-value-2.0.0.tgz", @@ -2715,7 +3518,6 @@ "version": "1.2.0", "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-1.2.0.tgz", "integrity": "sha1-RKrGW2lbAzmJaMOfNj/uXer98eo=", - "dev": true, "requires": { "shebang-regex": "^1.0.0" } @@ -2723,14 +3525,22 @@ "shebang-regex": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-1.0.0.tgz", - "integrity": "sha1-2kL0l0DAtC2yypcoVxyxkMmO/qM=", - "dev": true + "integrity": "sha1-2kL0l0DAtC2yypcoVxyxkMmO/qM=" + }, + "shimmer": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/shimmer/-/shimmer-1.2.1.tgz", + "integrity": "sha512-sQTKC1Re/rM6XyFM6fIAGHRPVGvyXfgzIDvzoq608vM+jeyVD0Tu1E6Np0Kc2zAIFWIj963V2800iF/9LPieQw==" + }, + "sigmund": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/sigmund/-/sigmund-1.0.1.tgz", + "integrity": "sha1-P/IfGYytIXX587eBhT/ZTQ0ZtZA=" }, "signal-exit": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.2.tgz", - "integrity": "sha1-tf3AjxKH6hF4Yo5BXiUTK3NkbG0=", - "dev": true + "integrity": "sha1-tf3AjxKH6hF4Yo5BXiUTK3NkbG0=" }, "snapdragon": { "version": "0.8.2", @@ -2873,6 +3683,11 @@ "extend-shallow": "^3.0.0" } }, + "sqlstring": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/sqlstring/-/sqlstring-2.3.1.tgz", + "integrity": "sha1-R1OT/56RR5rqYtyvDKPRSYOn+0A=" + }, "static-extend": { "version": "0.1.2", "resolved": "https://registry.npmjs.org/static-extend/-/static-extend-0.1.2.tgz", @@ -2903,7 +3718,6 @@ "version": "2.1.1", "resolved": "https://registry.npmjs.org/string-width/-/string-width-2.1.1.tgz", "integrity": "sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw==", - "dev": true, "requires": { "is-fullwidth-code-point": "^2.0.0", "strip-ansi": "^4.0.0" @@ -2922,7 +3736,6 @@ "version": "4.0.0", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz", "integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=", - "dev": true, "requires": { "ansi-regex": "^3.0.0" } @@ -2930,8 +3743,7 @@ "strip-eof": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/strip-eof/-/strip-eof-1.0.0.tgz", - "integrity": "sha1-u0P/VZim6wXYm1n80SnJgzE2Br8=", - "dev": true + "integrity": "sha1-u0P/VZim6wXYm1n80SnJgzE2Br8=" }, "strip-json-comments": { "version": "2.0.1", @@ -2957,12 +3769,38 @@ "execa": "^0.7.0" } }, + "terraformer": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/terraformer/-/terraformer-1.0.9.tgz", + "integrity": "sha512-YlmQ1fsMWTkKGDGibCRWgmLzrpDRUr63Q025LJ/taYQ6j1Yb8q9McKF7NBi6ACAyUXO6F/bl9w6v4MY307y5Ag==", + "requires": { + "@types/geojson": "^1.0.0" + } + }, + "terraformer-wkt-parser": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/terraformer-wkt-parser/-/terraformer-wkt-parser-1.2.0.tgz", + "integrity": "sha512-QU3iA54St5lF8Za1jg1oj4NYc8sn5tCZ08aNSWDeGzrsaV48eZk1iAVWasxhNspYBoCqdHuoot1pUTUrE1AJ4w==", + "requires": { + "@types/geojson": "^1.0.0", + "terraformer": "~1.0.5" + } + }, "timed-out": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/timed-out/-/timed-out-4.0.1.tgz", "integrity": "sha1-8y6srFoXW+ol1/q1Zas+2HQe9W8=", "dev": true }, + "timers-ext": { + "version": "0.1.7", + "resolved": "https://registry.npmjs.org/timers-ext/-/timers-ext-0.1.7.tgz", + "integrity": "sha512-b85NUNzTSdodShTIbky6ZF02e8STtVVfD+fu4aXXShEELpozH+bCpJLYMPZbsABN2wDH7fJpqIoXxJpzbf0NqQ==", + "requires": { + "es5-ext": "~0.10.46", + "next-tick": "1" + } + }, "to-fast-properties": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-1.0.3.tgz", @@ -3004,6 +3842,11 @@ "resolved": "https://registry.npmjs.org/token-stream/-/token-stream-0.0.1.tgz", "integrity": "sha1-zu78cXp2xDFvEm0LnbqlXX598Bo=" }, + "toposort-class": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/toposort-class/-/toposort-class-1.0.1.tgz", + "integrity": "sha1-f/0feMi+KMO6Rc1OGj9e4ZO9mYg=" + }, "touch": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/touch/-/touch-3.1.0.tgz", @@ -3045,6 +3888,15 @@ "integrity": "sha1-bgkk1r2mta/jSeOabWMoUKD4grc=", "optional": true }, + "umzug": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/umzug/-/umzug-2.2.0.tgz", + "integrity": "sha512-xZLW76ax70pND9bx3wqwb8zqkFGzZIK8dIHD9WdNy/CrNfjWcwQgQkGCuUqcuwEBvUm+g07z+qWvY+pxDmMEEw==", + "requires": { + "babel-runtime": "^6.23.0", + "bluebird": "^3.5.3" + } + }, "undefsafe": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/undefsafe/-/undefsafe-2.0.2.tgz", @@ -3098,6 +3950,11 @@ "crypto-random-string": "^1.0.0" } }, + "universalify": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.1.2.tgz", + "integrity": "sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==" + }, "unpipe": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", @@ -3205,6 +4062,16 @@ "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz", "integrity": "sha1-n5VxD1CiZ5R7LMwSR0HBAoQn5xM=" }, + "uuid": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.3.2.tgz", + "integrity": "sha512-yXJmeNaw3DnnKAOKJE51sL/ZaYfWJRl1pK9dr19YFCu0ObS231AB1/LbqTKRAQ5kw8A90rA6fr4riOUpTZvQZA==" + }, + "validator": { + "version": "10.11.0", + "resolved": "https://registry.npmjs.org/validator/-/validator-10.11.0.tgz", + "integrity": "sha512-X/p3UZerAIsbBfN/IwahhYaBbY68EN/UQBWHtsbXGT5bfrH/p4NQzUCG1kF/rtKaNpnJ7jAu6NGTdSNtyNIXMw==" + }, "vary": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", @@ -3219,11 +4086,15 @@ "version": "1.3.1", "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz", "integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==", - "dev": true, "requires": { "isexe": "^2.0.0" } }, + "which-module": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/which-module/-/which-module-2.0.0.tgz", + "integrity": "sha1-2e8H3Od7mQK4o6j6SzHD4/fm6Ho=" + }, "widest-line": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/widest-line/-/widest-line-2.0.1.tgz", @@ -3247,11 +4118,66 @@ "acorn-globals": "^3.0.0" } }, + "wkx": { + "version": "0.4.6", + "resolved": "https://registry.npmjs.org/wkx/-/wkx-0.4.6.tgz", + "integrity": "sha512-LHxXlzRCYQXA9ZHgs8r7Gafh0gVOE8o3QmudM1PIkOdkXXjW7Thcl+gb2P2dRuKgW8cqkitCRZkkjtmWzpHi7A==", + "requires": { + "@types/node": "*" + } + }, "wordwrap": { "version": "0.0.2", "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-0.0.2.tgz", "integrity": "sha1-t5Zpu0LstAn4PVg8rVLKF+qhZD8=" }, + "wrap-ansi": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-2.1.0.tgz", + "integrity": "sha1-2Pw9KE3QV5T+hJc8rs3Rz4JP3YU=", + "requires": { + "string-width": "^1.0.1", + "strip-ansi": "^3.0.1" + }, + "dependencies": { + "ansi-regex": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", + "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=" + }, + "is-fullwidth-code-point": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz", + "integrity": "sha1-754xOG8DGn8NZDr4L95QxFfvAMs=", + "requires": { + "number-is-nan": "^1.0.0" + } + }, + "string-width": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz", + "integrity": "sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M=", + "requires": { + "code-point-at": "^1.0.0", + "is-fullwidth-code-point": "^1.0.0", + "strip-ansi": "^3.0.0" + } + }, + "strip-ansi": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", + "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", + "requires": { + "ansi-regex": "^2.0.0" + } + } + } + }, + "wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=" + }, "write-file-atomic": { "version": "2.4.2", "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-2.4.2.tgz", @@ -3269,11 +4195,15 @@ "integrity": "sha1-SWsswQnsqNus/i3HK2A8F8WHCtQ=", "dev": true }, + "y18n": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-4.0.0.tgz", + "integrity": "sha512-r9S/ZyXu/Xu9q1tYlpsLIsa3EeLXXk0VwlxqTcFRfg9EhMW+17kbt9G0NrgCmhGb5vT2hyhJZLfDGx+7+5Uj/w==" + }, "yallist": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/yallist/-/yallist-2.1.2.tgz", - "integrity": "sha1-HBH5IY8HYImkfdUS+TxmmaaoHVI=", - "dev": true + "integrity": "sha1-HBH5IY8HYImkfdUS+TxmmaaoHVI=" }, "yargs": { "version": "3.10.0", @@ -3285,6 +4215,22 @@ "decamelize": "^1.0.0", "window-size": "0.1.0" } + }, + "yargs-parser": { + "version": "11.1.1", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-11.1.1.tgz", + "integrity": "sha512-C6kB/WJDiaxONLJQnF8ccx9SEeoTTLek8RVbaOIsrAUS8VrBEXfmeSnCZxygc+XC2sNMBIwOOnfcxiynjHsVSQ==", + "requires": { + "camelcase": "^5.0.0", + "decamelize": "^1.2.0" + }, + "dependencies": { + "camelcase": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.0.0.tgz", + "integrity": "sha512-faqwZqnWxbxn+F1d399ygeamQNy3lPp/H9H6rNrqYh4FSVCtcY+3cub1MxA8o9mDd55mM8Aghuu/kuyYA6VTsA==" + } + } } } } diff --git a/GhoulAlert/package.json b/GhoulAlert/package.json index b356bd6..050e928 100644 --- a/GhoulAlert/package.json +++ b/GhoulAlert/package.json @@ -10,9 +10,16 @@ "cookie-parser": "~1.4.3", "debug": "~2.6.9", "express": "~4.16.0", + "express-jwt": "^5.3.1", "http-errors": "~1.6.2", + "jsonwebtoken": "^8.5.1", "morgan": "~1.9.0", - "pug": "^2.0.3" + "mysql2": "^1.6.5", + "passport": "^0.4.0", + "passport-local": "^1.0.0", + "pug": "^2.0.3", + "sequelize": "^4.42.1", + "sequelize-cli": "^5.4.0" }, "devDependencies": { "nodemon": "^1.18.10" diff --git a/GhoulAlert/public/javascripts/hello.js b/GhoulAlert/public/javascripts/hello.js new file mode 100644 index 0000000..bf6b817 --- /dev/null +++ b/GhoulAlert/public/javascripts/hello.js @@ -0,0 +1 @@ +console.log("Hello"); diff --git a/GhoulAlert/public/javascripts/js.cookie.js b/GhoulAlert/public/javascripts/js.cookie.js new file mode 100644 index 0000000..9a0945e --- /dev/null +++ b/GhoulAlert/public/javascripts/js.cookie.js @@ -0,0 +1,165 @@ +/*! + * JavaScript Cookie v2.2.0 + * https://github.com/js-cookie/js-cookie + * + * Copyright 2006, 2015 Klaus Hartl & Fagner Brack + * Released under the MIT license + */ +;(function (factory) { + var registeredInModuleLoader = false; + if (typeof define === 'function' && define.amd) { + define(factory); + registeredInModuleLoader = true; + } + if (typeof exports === 'object') { + module.exports = factory(); + registeredInModuleLoader = true; + } + if (!registeredInModuleLoader) { + var OldCookies = window.Cookies; + var api = window.Cookies = factory(); + api.noConflict = function () { + window.Cookies = OldCookies; + return api; + }; + } +}(function () { + function extend () { + var i = 0; + var result = {}; + for (; i < arguments.length; i++) { + var attributes = arguments[ i ]; + for (var key in attributes) { + result[key] = attributes[key]; + } + } + return result; + } + + function init (converter) { + function api (key, value, attributes) { + var result; + if (typeof document === 'undefined') { + return; + } + + // Write + + if (arguments.length > 1) { + attributes = extend({ + path: '/' + }, api.defaults, attributes); + + if (typeof attributes.expires === 'number') { + var expires = new Date(); + expires.setMilliseconds(expires.getMilliseconds() + attributes.expires * 864e+5); + attributes.expires = expires; + } + + // We're using "expires" because "max-age" is not supported by IE + attributes.expires = attributes.expires ? attributes.expires.toUTCString() : ''; + + try { + result = JSON.stringify(value); + if (/^[\{\[]/.test(result)) { + value = result; + } + } catch (e) {} + + if (!converter.write) { + value = encodeURIComponent(String(value)) + .replace(/%(23|24|26|2B|3A|3C|3E|3D|2F|3F|40|5B|5D|5E|60|7B|7D|7C)/g, decodeURIComponent); + } else { + value = converter.write(value, key); + } + + key = encodeURIComponent(String(key)); + key = key.replace(/%(23|24|26|2B|5E|60|7C)/g, decodeURIComponent); + key = key.replace(/[\(\)]/g, escape); + + var stringifiedAttributes = ''; + + for (var attributeName in attributes) { + if (!attributes[attributeName]) { + continue; + } + stringifiedAttributes += '; ' + attributeName; + if (attributes[attributeName] === true) { + continue; + } + stringifiedAttributes += '=' + attributes[attributeName]; + } + return (document.cookie = key + '=' + value + stringifiedAttributes); + } + + // Read + + if (!key) { + result = {}; + } + + // To prevent the for loop in the first place assign an empty array + // in case there are no cookies at all. Also prevents odd result when + // calling "get()" + var cookies = document.cookie ? document.cookie.split('; ') : []; + var rdecode = /(%[0-9A-Z]{2})+/g; + var i = 0; + + for (; i < cookies.length; i++) { + var parts = cookies[i].split('='); + var cookie = parts.slice(1).join('='); + + if (!this.json && cookie.charAt(0) === '"') { + cookie = cookie.slice(1, -1); + } + + try { + var name = parts[0].replace(rdecode, decodeURIComponent); + cookie = converter.read ? + converter.read(cookie, name) : converter(cookie, name) || + cookie.replace(rdecode, decodeURIComponent); + + if (this.json) { + try { + cookie = JSON.parse(cookie); + } catch (e) {} + } + + if (key === name) { + result = cookie; + break; + } + + if (!key) { + result[name] = cookie; + } + } catch (e) {} + } + + return result; + } + + api.set = api; + api.get = function (key) { + return api.call(api, key); + }; + api.getJSON = function () { + return api.apply({ + json: true + }, [].slice.call(arguments)); + }; + api.defaults = {}; + + api.remove = function (key, attributes) { + api(key, '', extend(attributes, { + expires: -1 + })); + }; + + api.withConverter = init; + + return api; + } + + return init(function () {}); +})); diff --git a/GhoulAlert/public/javascripts/main.js b/GhoulAlert/public/javascripts/main.js new file mode 100644 index 0000000..c1f5aaf --- /dev/null +++ b/GhoulAlert/public/javascripts/main.js @@ -0,0 +1,317 @@ +// Global variables +var map; +var modalMap; +var modalMapMarker; + +// Map initialization +function initMap() { + $(document).ready(() => { + map = new google.maps.Map(document.getElementById('map'), { + center: {lat: 42.3143286, lng: -71.0403235}, + zoom: 8 + }); + + modalMap = new google.maps.Map(document.getElementById('add-marker-map'), { + center: {lat: 42.3143286, lng: -71.0403235}, + zoom: 14 + }); + + modalMapMarker = new google.maps.Marker({ + position: modalMap.getCenter(), + map: modalMap, + title: 'Sighting Occurred Here' + }); + + google.maps.event.addListener(modalMap, 'click', function(event) { + modalMapMarker.setPosition(event.latLng); + modalMap.panTo(event.latLng); + }); + }); +} + +$(document).ready(() => { + // Message functions + var msg = { + showAlert: function(message) { + $(".alert-message").text(message); + $(".alert").slideDown(); + setTimeout(function() { + $('.alert').slideUp('fast'); + }, 3000); + }, + + showAffirmation: function(message) { + $(".affirmation-message").text(message); + $(".affirmation").slideDown(); + setTimeout(function() { + $('.affirmation').slideUp('fast'); + }, 3000); + }, + + closeAlert: function() { + $(".alert-message").text(""); + $(".alert").slideUp(); + }, + + closeAffirmation: function() { + $(".affirmation-message").text(""); + $(".affirmation").slideUp(); + } + } + + // User data & functions + var user = { + username: null, + token: null, + isLogged: false, + + setTokenHeader: function() { + $.ajaxSetup({ + headers: { + Authorization: "Token " + this.token + } + }); + }, + + loadFromCookie: function() { + if (Cookies.get('username') && Cookies.get('token')) { + this.username = Cookies.get('username'); + this.token = Cookies.get('token'); + this.isLogged = true; + $(".login-name").text(this.username); + $(".no-login").hide(); + $(".has-login").show(); + this.setTokenHeader(); + } + }, + + loadData: function(data) { + this.username = data.username; + this.token = data.token; + this.isLogged = true; + + Cookies.set("username", this.username, { expires: 30 }); + Cookies.set("token", this.token, { expires: 30 }); + this.setTokenHeader(); + }, + + handleServerErrors: function(response) { + var msg_string = ""; + if (response.errors) { + Object.keys(response.errors).forEach( (key, idx) => { + msg_string += key + " " + response.errors[key] + ". "; + }); + } else if (response.error) { + msg_string = response.error.message; + } else { + msg_string = "Failed user operations. Try again."; + } + + msg.showAlert(msg_string); + }, + + login: function(username, password) { + $.post('apiV1/users/login', { username: username, password: password }, (data) => { + if (data.error || data.errors) { + this.handleServerErrors(data); + return false; + } else { + this.loadData(data); + + msg.showAffirmation("Welcome back, " + this.username + "!"); + + $(".login-name").text(this.username); + $(".use-login").hide(); + $(".has-login").show(); + + return true; + } + }).fail((data) => { + var response = data.responseJSON; + + this.handleServerErrors(response); + + return false; + }); + }, + + logout: function() { + this.username = null; + this.token = null; + this.isLogged = false; + $(".login-name").text(""); + Cookies.remove('username'); + Cookies.remove('token'); + }, + + create: function(username, password) { + $.post('apiV1/users', { username: username, password: password }, (data) => { + if (data.error || data.errors) { + this.handleServerErrors(data); + return false; + } else { + this.loadData(data); + + msg.showAffirmation("Welcome, " + this.username + "!"); + + $(".login-name").text(this.username); + $(".create-login").hide(); + $(".has-login").show(); + + return true; + } + }).fail((data) => { + var response = data.responseJSON; + + this.handleServerErrors(response); + + return false; + }); + } + + }; + + user.loadFromCookie(); + + // Marker data and functions + var markers = { + collection: [], + + toMap: function() { + for (var i = 0; i < this.collection.length; i++) { + var dbMarker = this.collection[i]; + new google.maps.Marker({ + position: { lat: parseFloat(dbMarker.latitude), lng: parseFloat(dbMarker.longitude) }, + map: map, + title: dbMarker.name + }); + } + }, + + newToMap: function(newMarker) { + this.collection.push(newMarker); + new google.maps.Marker({ + position: {lat: parseFloat(newMarker.latitude), lng: parseFloat(newMarker.longitude)}, + map: map, + title: newMarker.name + }); + }, + + retrieve: function() { + $.get("apiV1/markers/withusers", (data) => { + if (!(data.error || data.errors)) { + this.collection = data; + this.toMap() + } else { + console.log("WARNING: Marker retrieval failure"); + } + }).fail((data) => { + console.log("WARNING: Marker retrieval failure"); + }); + }, + + handleErrorsInModal: function(response) { + var msg_string = ""; + if (response.errors) { + Object.keys(response.errors).forEach( (key, idx) => { + msg_string += key + " " + response.errors[key] + ". "; + }); + } else if (response.error) { + msg_string = response.error.message; + } else { + msg_string = "Failed marker operations. Try again."; + } + + $(".marker-errors").text(msg_string); + }, + + create: function(lat, long, name, desc) { + $.post("apiV1/markers", {latitude: lat, longitude: long, name: name, description: desc}, (data) => { + if (data.error || data.errors) { + this.handleErrorsInModal(data); + return false; + } else { + $("#newmarker").hide(); + msg.showAffirmation("Successfully created a marker for '" + data.marker.name + "'."); + this.newToMap(data.marker); + map.panTo({ lat: parseFloat(data.marker.latitude), lng: parseFloat(data.marker.longitude) }); + return true; + } + }).fail((data) => { + var response = data.responseJSON; + this.handleErrorsInModal(response); + return false; + }); + } + }; + + markers.retrieve(); + + + // Input Handling + $("#create-login-button").click((e) => { + $(".no-login").hide(); + $(".create-login").show(); + }); + + $("#use-login-button").click((e) => { + $(".no-login").hide(); + $(".use-login").show(); + }); + + $("#destroy-login-button").click((e) => { + user.logout(); + $(".has-login").hide(); + $(".no-login").show(); + }); + + $("#new-user-submit").click((e) => { + e.preventDefault(); + var username = $("#new-user-username").val(); + var password = $("#new-user-password").val(); + user.create(username, password); + }); + + $("#new-user-cancel").click((e) => { + $(".create-login").hide(); + $(".no-login").show(); + }); + + $("#existing-user-submit").click((e) => { + e.preventDefault(); + var username = $("#existing-user-username").val(); + var password = $("#existing-user-password").val(); + user.login(username, password); + }); + + $("#existing-user-cancel").click((e) => { + $(".use-login").hide(); + $(".no-login").show(); + }); + + $("#add-marker-button").click((e) => { + if (user.isLogged){ + modalMap.setCenter(map.getCenter()); + modalMapMarker.setPosition(map.getCenter()); + $("#newmarker").show(); + } else { + msg.showAlert("You must be logged in to add a marker."); + } + }); + + $(".cancelbtn").click((e) => { + $(".marker-errors").text(""); + $("#newmarker").hide(); + }); + + $("#add").click((e) => { + e.preventDefault(); + $(".marker-errors").text(""); + var mLat = modalMapMarker.getPosition().lat; + var mLng = modalMapMarker.getPosition().lng; + var mName = $("#markerName").val(); + var mDesc = $("#description").val(); + markers.create(mLat, mLng, mName, mDesc); + }); + +}); diff --git a/GhoulAlert/public/stylesheets/style.css b/GhoulAlert/public/stylesheets/style.css index 9453385..00d398c 100644 --- a/GhoulAlert/public/stylesheets/style.css +++ b/GhoulAlert/public/stylesheets/style.css @@ -1,8 +1,262 @@ -body { - padding: 50px; - font: 14px "Lucida Grande", Helvetica, Arial, sans-serif; +body {text-align:center; font-family: Arial, Helvetica, sans-serif; background-color:black;} + +/* #808080 h4 { color: #EADD84; font-family: 'Rouge Script', cursive; font-size: 70px; font-weight: normal; line-height: 48px; margin: 0 0 50px; text-align: center; text-shadow: 1px 1px 2px #082b34; }*/ + +/*h4 { color: #EADD84; font-family: "Great Vibes", cursive; font-size: 65px; line-height: 160px; font-weight: normal; margin-bottom: 0px; margin-top: 40px; text-align: center; text-shadow: 0 1px 1px #fff; }*/ + +h3, h1 { color: #EADD84; font-family: 'Rouge Script', sans-serif; font-size: 45px; font-weight: bold; letter-spacing: -1px; line-height: 1; text-align: center; } + +section { + float: center; +} +aside { + float: center; +} + +/* Full-width input fields */ +input[type=text], input[type=password], textarea { + width: 100%; + padding: 12px 20px; + margin: 8px; + display: inline-block; + border: 1px solid #EADD84; + box-sizing: border-box; +} + +.small_input { + max-width: 128px; + max-height: 24px; + margin: 0; + margin-right: 8px; +} + +/* Set a style for all buttons */ +button { + background-color: #EADD84; + color: white; + padding: 14px 20px; + margin: 8px 0; + border: none; + width: 100%; + position: centered; +} + +button:hover { + opacity: 0.8; +} + +button.small_button, button.tiny_button { + background-color: blue; + border-radius: 1em; +} + +button.small_button { + padding: 8px 12px; + margin-right: 8px; + max-width: 128px; + max-height: 32px; +} + +button.tiny_button { + max-width: 18px; + max-height: 18px; +} + +.inline { + display: inline-block; +} + +/* Navbar styles */ +navbar { + position: fixed; + top: 0; + left: 0; + width: 100%; + background: #4c4c4c; + background: -moz-linear-gradient(top, #4c4c4c 0%, #595959 12%, #666666 25%, #474747 39%, #2c2c2c 50%, #000000 51%, #111111 60%, #2b2b2b 76%, #1c1c1c 91%, #131313 100%); + background: -webkit-linear-gradient(top, #4c4c4c 0%,#595959 12%,#666666 25%,#474747 39%,#2c2c2c 50%,#000000 51%,#111111 60%,#2b2b2b 76%,#1c1c1c 91%,#131313 100%); + background: linear-gradient(to bottom, #4c4c4c 0%,#595959 12%,#666666 25%,#474747 39%,#2c2c2c 50%,#000000 51%,#111111 60%,#2b2b2b 76%,#1c1c1c 91%,#131313 100%); + filter: progid:DXImageTransform.Microsoft.gradient( startColorstr='#4c4c4c', endColorstr='#131313',GradientType=0 ); + height: 6em; +} + +.title, .login, .login-name { + display:inline-block; +} + +.title { + float: left; + font-size: 2em; + font-weight: bold; + color: white; + padding-top: 0.25em; + padding-left: 0.25em; +} + +.login { + float: right; + width: 50%; + text-align: right; +} + +.has-login, .create-login, .use-login, .alert, .affirmation { + display: none; +} + +.login-name { + color: white; + font-weight: bold; + margin-right: 8px; +} + +/* Message styles */ +#messages { + position: fixed; + top: 6em; + left: 0; + width: 100%; +} + +.alert, .affirmation { + width: 100%; + color: white; + min-height: 4em; +} + +.alert-message, .affirmation-message { + font-size: 1.25em; + vertical-align: middle; + padding: 2em; +} + +.alert { + background-color: red; +} + +.affirmation { + background-color: green; +} + +.close-alert, .close-affirmation { + position: fixed; + top: 8em; + right: 0; +} + +/* Map Styles */ +#map { + height: 40em; + width: 100%; + margin-top: 6.5em; +} + +.marker-errors { + color: red; + font-weight: bold; + margin: 1em; +} + +/* Extra styles for the cancel button */ +.cancelbtn { + width: auto; + padding: 10px 18px; + background-color: #A9A9A9; +} + +.container { + padding: 16px; +} + +span.psw { + float: right; + padding-top: 16px; +} + +/* The Modal (background) */ +.modal { + display: none; /* Hidden by default */ + position: fixed; /* Stay in place */ + z-index: 1; /* Sit on top */ + left: 0; + top: 0; + width: 100%; /* Full width */ + height: 100%; /* Full height */ + overflow: auto; /* Enable scroll if needed */ + background-color: rgb(0,0,0); /* Fallback color */ + background-color: rgba(0,0,0,0.4); /* Black w/ opacity */ + padding-top: 60px; +} + +/* Modal Content/Box */ +.modal-content { + background-color: #fefefe; + margin: 5% auto 15% auto; /* 5% from the top, 15% from the bottom and centered */ + border: 1px solid #EADD84; + width: 80%; /* Could be more or less, depending on screen size */ +} + +/* The Close Button (x) */ +.close { + position: absolute; + right: 25px; + top: 0; + color: #A9A9A9; + font-size: 35px; + font-weight: bold; +} + +#add-marker-map, #add-marker-form { + display: inline-block; + width: 45%; + height: 25em; + margin-bottom: 1em; + vertical-align: top; +} + +/* Add Zoom Animation */ +.animate { + -webkit-animation: animatezoom 0.6s; + animation: animatezoom 0.6s +} + +@-webkit-keyframes animatezoom { + from {-webkit-transform: scale(0)} + to {-webkit-transform: scale(1)} +} + +@keyframes animatezoom { + from {transform: scale(0)} + to {transform: scale(1)} +} + +/* Change styles for span and cancel button on extra small screens */ +@media screen and (max-width: 420px) { + span.psw { + display: block; + float: none; + } + .cancelbtn { + width: 100%; + } + + .title { + display: none; + } + + .login { + width: 100%; + } +} + +/* Change map size at low height */ +@media screen and (max-height: 750px) { + #map { + height: 30em; + } } -a { - color: #00B7FF; +@media screen and (max-height: 640px) { + #map { + height: 25em; + } } diff --git a/GhoulAlert/routes/apiV1/README.md b/GhoulAlert/routes/apiV1/README.md new file mode 100644 index 0000000..40d83c3 --- /dev/null +++ b/GhoulAlert/routes/apiV1/README.md @@ -0,0 +1,539 @@ +# API Documentation + +The API (Application Programming Interface) is the backbone of this application. +It manages the current models (as of now, the Users and Markers), and it handles +authorization for various features. + +When specific requests in the proper format are made to the API routes, the +application responds with JSON-formatted data. In addition to allowing the application +to "speak" with itself, this also allows other applications to receive some of this +application's data for their own use. + +# Authorization + +Some routes will be marked with **Authorization: Required**. This means that a +special token assigned to identify users is necessary in requests, or the application will +respond only with a 401 HTTP code. + +This token is received when a new user is created, when a user refreshes their +token through the user login route, or when a user checks their own status with the +current user route. Tokens will be valid for 30 days from when they are created/refreshed. + +This token should be attached to a header named **Authorization**, +in the format **Token [token here]**. + +# Paths + +## Users + +These are the registered users of our application. Users only need to register to +create, edit, and delete markers; simply viewing markers is accessible to anyone. + +### Create User + +`POST apiV1/users` + +**Authorization: Optional** + +Makes a new user with the provided username and password, and responds with a token +to be used for authorization. + +#### Expected Request Format + +``` +{ + "username": "[username{string}]", + "password": "[password{string}]" +} +``` + +* **username:** The user's username +* **password:** The password that this user will use to confirm their identity + +#### Expected Response + +``` +{ + "id": [id{integer}], + "username": [username{string}], + "token": [token{string}] +} +``` + +* **id:** The ID number of the user as they are identified in the database +* **username:** The user's username +* **token:** The token that identifies the user in requests requiring authorization + +#### Constraints and Error Handling + +* Responds with an error when username or password is not provided +* Responds with a validation error if username is not unique (user with that username already exists) + +### User Login + +`POST apiV1/users/login` + +**Authorization: Optional** + +Refreshes and responds with a new identifying token for the user with the provided +username and password. + +#### Expected Request Format + +``` +{ + "username": "[username{string}]", + "password": "[password{string}]" +} +``` + +* **username:** The user's username +* **password:** The password that this user will use to confirm their identity + +#### Expected Response + +``` +{ + "id": [id{integer}], + "username": [username{string}], + "token": [token{string}] +} +``` + +* **id:** The ID number of the user as they are identified in the database +* **username:** The user's username +* **token:** The token that identifies the user in requests requiring authorization + +#### Constraints and Error Handling + +* Responds with an error when username or password is not provided +* Responds with an error if no user exists with given username +* Responds with an error if the given password for the given user is wrong + +### Current User + +`GET apiV1/users/current` + +**Authorization: Required** + +Responds with data for the current user based on the provided authentication token. + +#### Expected Request Format + +No request body + +#### Expected Response + +``` +{ + "id": [id{integer}], + "username": [username{string}], + "token": [token{string}] +} +``` + +* **id:** The ID number of the user as they are identified in the database +* **username:** The user's username +* **token:** The token that identifies the user in requests requiring authorization + +#### Constraints and Error Handling + +* Responds with an error if there is no authentication token +* Responds with an error if the authentication token does not map to a user + +## Markers + +These represent points on the Google Map that users can set. The database stores relevant +information in these objects and sends it to the map service to be processed onto the map. + +### All Markers + +`GET apiV1/markers` + +**Authorization: Optional** + +Responds with an array containing all stored markers. + +#### Expected Request Format + +No request body + +#### Expected Response + +``` +[ + { + "id": [id{integer}], + "latitude": [latitude{float}], + "longitude": [longitude{float}], + "name": [name{string}], + "description": [description{string}], + "UserId": [ID of owner{integer}] + }, + ... +] +``` + +* **id:** The ID number of this marker as it is identified in the database +* **latitude:** The latitude value of the marker +* **longitude:** The longitude value of the marker +* **name:** The name for the marker +* **description:** A description for the marker +* **UserId:** The ID number of the user that created this marker + +#### Constraints and Error Handling + +N/A + +### All Markers (And User Data) + +`GET apiV1/markers/withusers` + +**Authorization: Optional** + +Similar to the above, but includes user data (primarily the username of the owner) + +#### Expected Request Format + +No request body + +#### Expected Response + +``` +[ + { + "id": [id{integer}], + "latitude": [latitude{float}], + "longitude": [longitude{float}], + "name": [name{string}], + "description": [description{string}], + "User": { + "id": [ID of owner{integer}], + "username": [user username{string}] + } + }, + ... +] +``` + +* **id:** The ID number of this marker as it is identified in the database +* **latitude:** The latitude value of the marker +* **longitude:** The longitude value of the marker +* **name:** The name for the marker +* **description:** A description for the marker +* **User:** The user that owns this marker +* **User.id** The ID number of the user that created this marker +* **User.username** The username of the user that created this marker + +#### Constraints and Error Handling + +N/A + +### Get One Marker + +`GET apiV1/markers/:id` + +**Where :id = ID of the marker** + +**Authorization: Optional** + +Obtains the data for one marker by its ID + +#### Expected Request Format + +No request body + +#### Expected Response + +``` +{ + "id": [id{integer}], + "latitude": [latitude{float}], + "longitude": [longitude{float}], + "name": [name{string}], + "description": [description{string}], + "UserId": [ID of owner{integer}] +} +``` + +* **id:** The ID number of this marker as it is identified in the database +* **latitude:** The latitude value of the marker +* **longitude:** The longitude value of the marker +* **name:** The name for the marker +* **description:** A description for the marker +* **UserId** The ID number of the user that created this marker + +#### Constraints and Error Handling + +* Responds with a 404 error if there is no marker with the given id + +### Get One Marker (And User Data) + +`GET apiV1/markers/:id/withuser` + +**Where :id = ID of the marker** + +**Authorization: Optional** + +Same as above, but includes user data such as their username + +#### Expected Request Format + +No request body + +#### Expected Response + +``` +{ + "id": [id{integer}], + "latitude": [latitude{float}], + "longitude": [longitude{float}], + "name": [name{string}], + "description": [description{string}], + "User": { + "id": [ID of owner{integer}], + "username": [user username{string}] + } +} +``` + +* **id:** The ID number of this marker as it is identified in the database +* **latitude:** The latitude value of the marker +* **longitude:** The longitude value of the marker +* **name:** The name for the marker +* **description:** A description for the marker +* **User:** The user that owns this marker +* **User.id** The ID number of the user that created this marker +* **User.username** The username of the user that created this marker + +#### Constraints and Error Handling + +* Responds with a 404 error if there is no marker with the given id + +### Create a Marker + +`POST apiV1/markers` + +**Authorization: Required** + +Creates a new marker with the given parameters, owned by the user that owns the +provided authorization token. + +#### Expected Request Format + +``` +{ + "latitude": [latitude{float}], + "longitude": [longitude{float}], + "name": [name{string}], + "description": [description{string}] , +} +``` + +* **latitude:** The latitude value of the marker +* **longitude:** The longitude value of the marker +* **name:** The name for the marker +* **description:** A description for the marker (optional) + +#### Expected Response + +##### Success Case + +``` +{ + "success": true, + "marker": { + "id": [id{integer}], + "latitude": [latitude{float}], + "longitude": [longitude{float}], + "name": [name{string}], + "description": [description{string/null}], + "UserId": [Owner's id{integer}], + "updatedAt": [Update date{date}], + "createdAt": [Creation date{date}] + } +} +``` +* **success:** Whether or not creation succeeded +* **marker.id:** The ID number of this marker as it is identified in the database +* **marker.latitude:** The latitude value of the marker +* **marker.longitude:** The longitude value of the marker +* **marker.name:** The name for the marker +* **marker.description:** A description for the marker +* **marker.UserId** The ID number of the user that created this marker +* **marker.updatedAt** The date this object was last updated +* **marker.createdAt** The date this object was first created + +##### Failure Case + +This occurs if the error is validation based + +``` +{ + "success": false, + "error": { + "message": [Error message{string}] + } +} +``` +* **success:** Whether or not creation succeeded +* **error.message:** A description of the error that has occurred + +#### Constraints and Error Handling + +* Responds with an error if marker latitude, longitude, or name are not provided +* Responds with an error if there is no authorization token +* Responds with an error if the authorization token does not match a user +* Responds with an error if the name of the marker is not unique + +### Edit a Marker + +`PUT apiV1/markers/:id` + +**Where :id = ID of marker to edit** + +**Authorization: Required** + +Edits the marker with the provided ID, but only if the authorized user owns it. + +#### Expected Request Format + +The format is flexible; you only need to provide the properties that will be changed. + +``` +{ + "latitude": [latitude{float}] , + "longitude": [longitude{float}] , + "name": [name{string}] , + "description": [description{string}] , +} +``` + +* **latitude:** The latitude value of the marker +* **longitude:** The longitude value of the marker +* **name:** The name for the marker +* **description:** A description for the marker + +#### Expected Response + +##### Success Case + +``` +{ + "success": true, + "editedFrom": { + "id": [id{integer}], + "latitude": [latitude{float}], + "longitude": [longitude{float}], + "name": [name{string}], + "description": [description{string/null}], + "UserId": [Owner's id{integer}], + "updatedAt": [Update date{date}], + "createdAt": [Creation date{date}] + }, + "editedTo": { + "id": [id{integer}], + "latitude": [latitude{float}], + "longitude": [longitude{float}], + "name": [name{string}], + "description": [description{string/null}], + "UserId": [Owner's id{integer}], + "updatedAt": [Update date{date}], + "createdAt": [Creation date{date}] + } +} +``` + +* **success:** Whether or not editing succeeded +* **editedFrom:** The state of the marker before it was edited +* **editedTo:** The state of the marker after being edited + +##### Failure Case + +This occurs if the error is validation based + +``` +{ + "success": false, + "error": { + "message": [Error message{string}] + } +} +``` +* **success:** Whether or not editing succeeded +* **error.message:** A description of the error that has occurred + +#### Constraints and Error Handling + +* Responds with an error if there is no authentication token +* Responds with an error if the authentication token does not match to a user +* Responds with an error if there is no marker with the given id +* Responds with an error if the user does not own the marker +* Responds with an error if the name is edited to one that already exists in the database + +### Delete a Marker + +`DELETE apiV1/markers/:id` + +**Where :id = The ID of the marker to be deleted** + +**Authorization: Required** + +If the authorized user owns the specified marker, it will be deleted + +#### Expected Request Format + +No request body + +#### Expected Response + +##### Success Case + +``` +{ + "success": true, + "deleted": { + "id": [id{integer}], + "latitude": [latitude{float}], + "longitude": [longitude{float}], + "name": [name{string}], + "description": [description{string/null}], + "UserId": [Owner's id{integer}], + "updatedAt": [Update date{date}], + "createdAt": [Creation date{date}] + } +} +``` + +* **success:** Whether or not deletion succeeded +* **deleted:** The object that was deleted +* **deleted.id:** The ID number of this marker as it is identified in the database +* **deleted.latitude:** The latitude value of the marker +* **deleted.longitude:** The longitude value of the marker +* **deleted.name:** The name for the marker +* **deleted.description:** A description for the marker +* **deleted.UserId** The ID number of the user that created this marker +* **deleted.updatedAt** The date this object was last updated +* **deleted.createdAt** The date this object was first created + +##### Failure Case + +This occurs if the error is validation based + +``` +{ + "success": false, + "error": { + "message": [Error message{string}] + } +} +``` +* **success:** Whether or not deletion succeeded +* **error.message:** A description of the error that has occurred + +#### Constraints and Error Handling + +* Responds with an error if there is no authentication token +* Responds with an error if the authentication token does not match to a user +* Responds with an error if there is no marker with the given id +* Responds with an error if the user does not own the marker diff --git a/GhoulAlert/routes/apiV1/index.js b/GhoulAlert/routes/apiV1/index.js new file mode 100644 index 0000000..5d2c469 --- /dev/null +++ b/GhoulAlert/routes/apiV1/index.js @@ -0,0 +1,7 @@ +const express = require('express'); +const router = express.Router(); + +router.use('/users', require('./users')); +router.use('/markers', require('./markers')); + +module.exports = router; diff --git a/GhoulAlert/routes/apiV1/markers.js b/GhoulAlert/routes/apiV1/markers.js new file mode 100644 index 0000000..15629b9 --- /dev/null +++ b/GhoulAlert/routes/apiV1/markers.js @@ -0,0 +1,250 @@ +var passport = require('passport'); +var router = require('express').Router(); +var auth = require('../authorize'); +var models = require('../../models'); +const Sequelize = require('sequelize'); + +var User = models.User; +var Marker = models.Marker; + +//Get all markers +router.get('/', auth.optional, (req, res, next) => { + return Marker.findAll({ + attributes: ['id', 'latitude', 'longitude', 'name', 'description', 'UserId'] + }).then((markers) => { + return res.json(markers); + }); +}); + +//Get all markers with basic user information included +router.get('/withusers', auth.optional, (req, res, next) => { + return Marker.findAll({ + attributes: ['id', 'latitude', 'longitude', 'name', 'description'], + include: [{ model: User, attributes: ['id', 'username'] }] + }).then((markers) => { + return res.json(markers); + }); +}); + +//Get a specific marker +router.get('/:mid', auth.optional, (req, res, next) => { + var marker_id = req.params.mid; + + return Marker.findOne({ where: { id: marker_id } }).then((marker) => { + if (!marker) { + return res.status(404).json({ + errors: { + marker: "not found" + } + }); + } + + return res.json(marker.toJSON()); + }); +}); + +//Get a specific marker with basic user information +router.get('/:mid/withuser', auth.optional, (req, res, next) => { + var marker_id = req.params.mid; + + return Marker.findOne({ + where: { id: marker_id }, + attributes: ['id', 'latitude', 'longitude', 'name', 'description'], + include: [{ model: User, attributes: ['id', 'username'] }] + }).then((marker) => { + if (!marker) { + return res.status(404).json({ + errors: { + marker: "not found" + } + }); + } + + return res.json(marker.toJSON()); + }); +}); + +//Post a new marker. User authentication required. +router.post('/', auth.required, (req, res, next) => { + var username = req.payload.username; + var marker = req.body; + + if(!marker.latitude) { + return res.status(422).json({ + errors: { + latitude: 'is required', + }, + }); + } + + if(!marker.longitude) { + return res.status(422).json({ + errors: { + longitude: 'is required', + }, + }); + } + + if(!marker.name) { + return res.status(422).json({ + errors: { + name: 'is required', + }, + }); + } + + return User.findOne({ where: { username: username } }).then((user) => { + if (!user) { + return res.sendStatus(400).json({ + errors: { + authentication: 'is invalid' + } + }); + } + + var finalMarker = Marker.build(marker); + finalMarker.setUser(user); + + return finalMarker.save() + .then(() => { + res.json({ + success: true, + marker: finalMarker.toJSON() + }); + }).catch(Sequelize.ValidationError, function (error) { + res.json({ + success: false, + error: { message: error.message } + }); + }); + + }); + +}); + +//Edit a marker. Authenticated user must match marker owner. +router.put('/:mid', auth.required, (req, res, next) => { + var marker_id = req.params.mid; + var username = req.payload.username; + var marker_update = req.body; + + return User.findOne({ where: { username: username } }).then((user) => { + if (!user) { + return res.sendStatus(400).json({ + errors: { + authentication: 'is invalid' + } + }); + } + + return Marker.findOne({ where: { id: marker_id } }).then((marker) => { + if (!marker) { + return res.sendStatus(404).json({ + errors: { + marker: 'not found' + } + }); + } + + marker.getUser().then((owner) => { + if (owner.id != user_id) { + return res.sendStatus(403).json({ + errors: { + user: 'not authorized to modify this marker' + } + }); + } + }); + + var preUpdateInfo = marker.toJSON(); + + marker.update(marker_update).then(() => { + return res.json({ + success: true, + editedFrom: preUpdateInfo, + editedTo: marker.toJSON() + }); + }).catch((error) => { + return res.json({ + success: false, + marker: preUpdateInfo, + error: { + message: error.message + } + }); + }); + + }); + + }); +}); + +//Delete a marker. Authenticated user must match marker owner. +router.delete('/:mid', auth.required, (req, res, next) => { + var marker_id = req.params.mid; + var username = req.payload.username; + + return User.findOne({ where: { username: username } }).then((user) => { + if (!user) { + return res.sendStatus(400).json({ + errors: { + authentication: 'is invalid' + } + }); + } + + return Marker.findOne({ where: { id: marker_id } }).then((marker) => { + if (!marker) { + return res.sendStatus(404).json({ + errors: { + marker: 'not found' + } + }); + } + + marker.getUser().then((owner) => { + if (owner.id != user.id) { + return res.sendStatus(403); + } + }); + + var destroyedInfo = marker.toJSON(); + + marker.destroy().then(() => { + return res.json({ + success: true, + deleted: destroyedInfo + }); + }).catch((error) => { + return res.json({ + success: false, + error: { + message: error.message + } + }); + }); + + }); + + }); +}); + +//GET all of a user's markers +router.get('/from/:uid', auth.optional, (req, res, next) => { + var uid = req.params.uid; + + return User.findOne({ where: { id: uid } }).then((user) =>{ + if(!user) { + return res.sendStatus(404); + } + + user.getMarkers().then((markers) => { + var markersJSON = markers.map(marker => marker.toJSON()); + return res.json(markersJSON); + }); + + }); +}); + + +module.exports = router; diff --git a/GhoulAlert/routes/apiV1/users.js b/GhoulAlert/routes/apiV1/users.js new file mode 100644 index 0000000..a1865e4 --- /dev/null +++ b/GhoulAlert/routes/apiV1/users.js @@ -0,0 +1,93 @@ +var passport = require('passport'); +var router = require('express').Router(); +var auth = require('../authorize'); +var models = require('../../models'); +const Sequelize = require('sequelize'); + +var User = models.User; + +//Creates a new user with a given username and password +router.post('/', auth.optional, (req, res, next) => { + var user = req.body; + + if(!user.username) { + return res.status(422).json({ + errors: { + username: 'is required', + }, + }); + } + + if(!user.password) { + return res.status(422).json({ + errors: { + password: 'is required', + }, + }); + } + + var finalUser = User.build(user); + + finalUser.setPassword(user.password); + + return finalUser.save() + .then(() => res.json(finalUser.toAuthJSON())) + .catch(Sequelize.ValidationError, function (error) { + res.json({ error: { message: error.message } }); + }); +}); + +//Creates a new token for an existing user if they provide the correct username +//and password +router.post('/login', auth.optional, (req, res, next) => { + var user = req.body; + + if(!user.username) { + return res.status(422).json({ + errors: { + username: 'is required', + }, + }); + } + + if(!user.password) { + return res.status(422).json({ + errors: { + password: 'is required', + }, + }); + } + + return passport.authenticate('local', { session: false }, (err, passportUser, info) => { + if(err) { + return next(err); + } + + if(passportUser) { + const user = passportUser; + user.token = passportUser.generateJWT(); + + return res.json(user.toAuthJSON()); + } else { + return res.json(info); + } + + return res.status(400).info; + })(req, res, next); +}); + +//Gets information on the current user if they have authentication +router.get('/current', auth.required, (req, res, next) => { + var id = req.payload.id; + + return User.findOne({ where: { id: id } }) + .then((user) => { + if(!user) { + return res.sendStatus(400); + } + + return res.json(user.toAuthJSON()); + }); +}); + +module.exports = router; diff --git a/GhoulAlert/routes/authorize.js b/GhoulAlert/routes/authorize.js new file mode 100644 index 0000000..85061f5 --- /dev/null +++ b/GhoulAlert/routes/authorize.js @@ -0,0 +1,26 @@ +const jwt = require('express-jwt'); + +const getTokenFromHeaders = (req) => { + const { headers: { authorization } } = req; + + if(authorization && authorization.split(' ')[0] === 'Token') { + return authorization.split(' ')[1]; + } + return null; +}; + +const auth = { + required: jwt({ + secret: 'secret', + userProperty: 'payload', + getToken: getTokenFromHeaders, + }), + optional: jwt({ + secret: 'secret', + userProperty: 'payload', + getToken: getTokenFromHeaders, + credentialsRequired: false, + }), +}; + +module.exports = auth; diff --git a/GhoulAlert/routes/index.js b/GhoulAlert/routes/index.js index ecca96a..31eae25 100644 --- a/GhoulAlert/routes/index.js +++ b/GhoulAlert/routes/index.js @@ -1,9 +1,18 @@ var express = require('express'); var router = express.Router(); +var models = require('../models'); + +/* API routes */ +router.use('/apiV1', require('./apiV1')); /* GET home page. */ router.get('/', function(req, res, next) { - res.render('index', { title: 'Express' }); + models.User.findAll().then(function(users){ + res.render('index', { + title: 'Express', + users: users + }); + }) }); module.exports = router; diff --git a/GhoulAlert/views/index.pug b/GhoulAlert/views/index.pug index 3d63b9a..10a59bd 100644 --- a/GhoulAlert/views/index.pug +++ b/GhoulAlert/views/index.pug @@ -1,5 +1,60 @@ extends layout - -block content - h1= title - p Welcome to #{title} +block content + head + meta(name='viewport', content='width=device-width, initial-scale=1') + script(src='https://ajax.googleapis.com/ajax/libs/jquery/3.2.1/jquery.min.js') + script(src='javascripts/js.cookie.js') + script(src='javascripts/main.js') + script(src='https://maps.googleapis.com/maps/api/js?key=AIzaSyBK2Mfs4UEpqJXALJwBje0UdMKWjt88Zh8&callback=initMap') + body + navbar.title-login + .title Ghoul Alert + .login + .no-login + button#create-login-button.small_button.inline(type='button') New Account + button#use-login-button.small_button.inline(type='button') Log In + .has-login + .login-name + button#destroy-login-button.small_button(type='button') Log Out + .create-login + form.user-login-create-form + input#new-user-username.small_input.inline(type='text', name='new-username', placeholder='Username', required='') + input#new-user-password.small_input.inline(type='password', name='new-password', placeholder='Password', required='') + button#new-user-submit.small_button.inline(type='submit') Create User + button#new-user-cancel.small_button.inline(type='button') Cancel + .use-login + form.user-login-use-form + input#existing-user-username.small_input.inline(type='text', name='existing-username', placeholder='Username', required='') + input#existing-user-password.small_input.inline(type='password', name='existing-password', placeholder='Password', required='') + button#existing-user-submit.small_button.inline(type='submit') Log In + button#existing-user-cancel.small_button.inline(type='button') Cancel + + + section#map + + #messages + .alert + .alert-message + .affirmation + .affirmation-message + + section + button#add-marker-button(style='width:auto;') ADD SIGHTING + #newmarker.modal + form.modal-content.animate(action='/') + h3 Marking a New Sighting + .imgcontainer + span.close(onclick="document.getElementById('id01').style.display='none'", title='Close Modal') × + #add-marker-map.container + #add-marker-form.container + label(for='name') + b Sighting Name + input#markerName(type='text', placeholder='Enter name for Marker', name='name', required='') + label(for='description') + b Description + textarea#description(placeholder='Enter description', name='description', rows='8', required='') + button#add(type='submit') CONFIRM SIGHTING + .marker-errors + .container(style='background-color:#f1f1f1;') + button.cancelbtn(type='button') Cancel + section diff --git a/GhoulAlert/views/index2.pug b/GhoulAlert/views/index2.pug new file mode 100644 index 0000000..b23a299 --- /dev/null +++ b/GhoulAlert/views/index2.pug @@ -0,0 +1,118 @@ +extends layout +block content + head + meta(name='viewport', content='width=device-width, initial-scale=1') + script(src='https://ajax.googleapis.com/ajax/libs/jquery/3.2.1/jquery.min.js') + body + h3 Welcome to Ghoul Alert App + iframe(src='https://www.google.com/maps/d/embed?mid=1_UI6WRqjCzomDvp1b1sLZYz7qY8&hl=en_US', width='640', height='480') + section + button(onclick="document.getElementById('id01').style.display='block'", style='width:auto;') LOG IN + #id01.modal + form.modal-content.animate(action='/UserProfile.pug') + h3 Member Log In + .imgcontainer + span.close(onclick="document.getElementById('id01').style.display='none'", title='Close Modal') × + .container + label(for='uname') + b Username + input#username(type='text', placeholder='Enter Username', name='uname', required='') + label(for='psw') + b Password + input#password(type='password', placeholder='Enter Password', name='psw', required='') + button#login(type='submit') Login + label + input(type='checkbox', checked='checked', name='remember') + | Remember me + .container(style='background-color:#f1f1f1;') + button.cancelbtn(type='button', onclick="document.getElementById('id01').style.display='none'") Cancel + span.psw + | Forgot + a(href='#') password? + script. + // Get the modal + var modal = document.getElementById('id01'); + // When the user clicks anywhere outside of the modal, close it + window.onclick = function(event) { + if (event.target == modal) { + modal.style.display = "none"; + } + } + script. + $(document).ready(function () { + $("#login").click(function (e) { + $.ajax({ + type: "POST", + url: "apiV1/users/login", + contentType: "application/json; charset=utf-8", + data: '{"username":"' + $("#username").val() + '","password":"' + $("#password").val() + '"}', + dataType: "json", + success: function (result, status, xhr) { + if (result.d == "Success") { + $("#messageSpan").text("Login Successful, Redireting to your profile page."); + setTimeout(function () { window.location = "UserProfile.html"; }, 2000); + } + else + $("#messageSpan").text("Login failed, Please try again."); + }, + error: function (xhr, status, error) { + $("#dbData").html("Result: " + status + " " + error + " " + xhr.status + " " + xhr.statusText) + } + }); + }); + }); + section + button(onclick="document.getElementById('id02').style.display='block'", style='width:auto;') SIGN UP + #id02.modal + form.modal-content.animate(action='/UserProfile.pug') + h3 Member Sign Up + .imgcontainer + span.close(onclick="document.getElementById('id02').style.display='none'", title='Close Modal') × + .container + label(for='uname') + b Username + input#username(type='text', placeholder='Enter Username', name='uname', required='') + label(for='psw') + b Password + input#password(type='password', placeholder='Enter Password', name='psw', required='') + button(type='submit') Sign Up + label + input(type='checkbox', checked='checked', name='remember') + | Remember me + .container(style='background-color:#f1f1f1;') + button.cancelbtn(type='button', onclick="document.getElementById('id02').style.display='none'") Cancel + span.psw + | Forgot + a(href='#') password? + script. + // Get the modal + var modal = document.getElementById('id02'); + // When the user clicks anywhere outside of the modal, close it + window.onclick = function(event) { + if (event.target == modal) { + modal.style.display = "none"; + } + } + script. + $(document).ready(function () { + $("#submit").click(function (e) { + $.ajax({ + type: "POST", + url: "apiV1/users", + contentType: "application/json; charset=utf-8", + data: '{"username":"' + $("#username").val() + '","password":"' + $("#password").val() + '"}', + dataType: "json", + success: function (result, status, xhr) { + if (result.d == "Success") { + $("#messageSpan").text("Login Successful, Redireting to your profile page."); + setTimeout(function () { window.location = "UserProfile.html"; }, 2000); + } + else + $("#messageSpan").text("Login failed, Please try again."); + }, + error: function (xhr, status, error) { + $("#dbData").html("Result: " + status + " " + error + " " + xhr.status + " " + xhr.statusText) + } + }); + }); + }); diff --git a/GhoulAlert/views/index_temp.pug b/GhoulAlert/views/index_temp.pug new file mode 100644 index 0000000..fef48a8 --- /dev/null +++ b/GhoulAlert/views/index_temp.pug @@ -0,0 +1,9 @@ +extends layout + +block content + h1= title + p Welcome to #{title} + h1 Users + ol + each user in users + li= user.username diff --git a/GhoulAlert/views/layout.pug b/GhoulAlert/views/layout.pug index 15af079..3eda6ba 100644 --- a/GhoulAlert/views/layout.pug +++ b/GhoulAlert/views/layout.pug @@ -1,7 +1,7 @@ doctype html html head - title= title + title Ghoul Alert link(rel='stylesheet', href='/stylesheets/style.css') body block content diff --git a/README.md b/README.md index 3202c3a..fba35c4 100644 --- a/README.md +++ b/README.md @@ -142,3 +142,55 @@ Has debug console. Uses the nodemon package to refresh the server on changes. This is the most useful option for development. `DEBUG=ghoulalert:* npm run devstart` + +## 2/28/2019 +### MySQL with Sequelize + +This program can now connect to a MySQL database using the Sequelize module as +an ORM (Object Relational Mapping Tool). + +In order for the connection to be established, the database it is expecting needs to +actually exist. + +1. First, follow the instructions to install the MySQL Community Server: +https://dev.mysql.com/downloads/mysql/ + +2. Next, you will want to install the MySQL Workbench to help you manage the server more easily: +https://dev.mysql.com/downloads/workbench/ + +3. When you open the workbench, under MySQL connections there will be a local instance. +This is what we will be working with, so open it. + +4. In the left panel in the Administration tab, there will be an icon for "Users and Privileges". +Click this. + +5. Here, we need to set up a database user according to the configuration of the program. +Click "Add Account". Give it the login name "ghoul", and the password "ghoul_user!61". +Select the "Administrative Roles" tab, and click the "DBA" checkmark, which will select everything. +Select the "Schema Privileges" tab, and click the "Select "ALL"" button to give this user all privileges. +Finalize this by clicking the "Apply" button. + +6. With the user created, we now need to create the database itself. In the workbench, the words "Schema" and "Database" are used interchangeably, so press the button in the topbar represented by a cylinder to add the schemas that we need. + +7. All you need to enter in the Schema Editor is the schema name. Give it the name "ghoul_development" +and press the small "Apply" button to the bottom right. Repeat this process to create another schema called "ghoul_test". We will use that later when testing is implemented. + +8. This is all we need to do to set the database up - Sequelize will take care of creating tables. Navigate to the GhoulAlert directory in the command line/terminal and run the application, and it should successfully connect to and shape the database based on the existing models. + +9. Now that the database is set up, let's test the program MVC flow. As of now, we have a view (index.pug) that has a section that iterates on each user that exists, as sent to the view by the route, which in turn +attains that data from Sequelize reading the user.js model. We can use this view to make sure each step of that process works. + +10. Open the MySQL Workbench window again. We need to make a quick modification for the manual insertions we will make to work. On the left panel, open the Schemas tab. Drop down the "ghoul_development" schema, and "Tables". Hover over "Users", and click the wrench icon. Click on the row for "createdAt", and in the "Default" field, type "Now()". Do the same for the "updatedAt" row, then press the "Apply" button at the bottom right and apply these changes. + +11. In the middle section, there should be a tab starting with the word "Query". Select this tab and paste the following code (making sure it's the only text present): + +``` +INSERT INTO ghoul_development.Users(username, password) +VALUES ("Manmoth", "bright_light"); +``` +Press the lightning bolt in the bar above the code to run this query. + +12. Run the GhoulAlert application. Once it's active, navigate to localhost:3000. +Under Users, Manmoth's name should be listed. + +Catching up to this point is vital to contributing to the project. Complete these instructions and you will be ready when it's time to delegate coding work.