From 9f7aeb872e8b31cccdbf35d7fce53a03deea3d05 Mon Sep 17 00:00:00 2001 From: Joe Alves Date: Tue, 31 May 2016 15:18:25 -0400 Subject: [PATCH] The big SQL overhaul. Expect bugs! --- generated/package.json | 4 + generated/seed.js | 25 ++-- .../app/configure/authentication/facebook.js | 22 ++-- .../app/configure/authentication/google.js | 22 ++-- .../app/configure/authentication/index.js | 26 +++-- .../app/configure/authentication/local.js | 14 ++- .../app/configure/authentication/twitter.js | 33 ++---- generated/server/app/configure/index.js | 6 +- generated/server/app/index.js | 80 +++++++------ generated/server/app/routes/members/index.js | 2 +- generated/server/db/_db.js | 7 ++ generated/server/db/index.js | 32 +---- generated/server/db/models/index.js | 4 - generated/server/db/models/user.js | 110 +++++++++--------- generated/server/env/development.js | 4 +- generated/server/env/production.js | 4 +- generated/server/main.js | 9 +- generated/tests/server/models/user-test.js | 26 ++--- .../tests/server/routes/members-only-test.js | 33 +++--- 19 files changed, 215 insertions(+), 248 deletions(-) create mode 100644 generated/server/db/_db.js delete mode 100644 generated/server/db/models/index.js diff --git a/generated/package.json b/generated/package.json index c699459..8d38fa3 100644 --- a/generated/package.json +++ b/generated/package.json @@ -24,6 +24,7 @@ "chai": "^2.1.0", "chalk": "^1.0.0", "connect-mongo": "^0.7.0", + "connect-session-sequelize": "^3.0.0", "cookie-parser": "^1.3.4", "express": "^4.12.0", "express-session": "^1.10.3", @@ -58,7 +59,10 @@ "passport-google-oauth": "^0.1.5", "passport-local": "^1.0.0", "passport-twitter": "^1.0.2", + "pg": "^4.5.5", + "pg-hstore": "^2.3.2", "run-sequence": "^1.0.2", + "sequelize": "^3.23.3", "serve-favicon": "^2.2.0", "sinon": "^1.13.0", "socket.io": "^1.3.4", diff --git a/generated/seed.js b/generated/seed.js index b7a9dd9..bc82e48 100644 --- a/generated/seed.js +++ b/generated/seed.js @@ -17,18 +17,10 @@ name in the environment files. */ -var mongoose = require('mongoose'); -var Promise = require('bluebird'); var chalk = require('chalk'); -var connectToDb = require('./server/db'); -var User = mongoose.model('User'); - -var wipeCollections = function () { - var removeUsers = User.remove({}); - return Promise.all([ - removeUsers - ]); -}; +var db = require('./server/db'); +var User = db.model('user'); +var Promise = require('sequelize').Promise; var seedUsers = function () { @@ -43,14 +35,15 @@ var seedUsers = function () { } ]; - return User.create(users); + var creatingUsers = users.map(function (userObj) { + return User.create(userObj); + }); + + return Promise.all(creatingUsers); }; -connectToDb - .then(function () { - return wipeCollections(); - }) +db.sync({ force: true }) .then(function () { return seedUsers(); }) diff --git a/generated/server/app/configure/authentication/facebook.js b/generated/server/app/configure/authentication/facebook.js index b7ca6b1..8510bef 100644 --- a/generated/server/app/configure/authentication/facebook.js +++ b/generated/server/app/configure/authentication/facebook.js @@ -1,10 +1,10 @@ 'use strict'; var passport = require('passport'); var FacebookStrategy = require('passport-facebook').Strategy; -var mongoose = require('mongoose'); -var UserModel = mongoose.model('User'); -module.exports = function (app) { +module.exports = function (app, db) { + + var User = db.define('user'); var facebookConfig = app.getValue('env').FACEBOOK; @@ -16,19 +16,19 @@ module.exports = function (app) { var verifyCallback = function (accessToken, refreshToken, profile, done) { - UserModel.findOne({ 'facebook.id': profile.id }).exec() + User.findOne({ + where: { + facebook_id: profile.id + } + }) .then(function (user) { - if (user) { return user; } else { - return UserModel.create({ - facebook: { - id: profile.id - } + return User.create({ + facebook_id: profile.id }); } - }) .then(function (userToLogin) { done(null, userToLogin); @@ -45,7 +45,7 @@ module.exports = function (app) { app.get('/auth/facebook', passport.authenticate('facebook')); app.get('/auth/facebook/callback', - passport.authenticate('facebook', { failureRedirect: '/login' }), + passport.authenticate('facebook', {failureRedirect: '/login'}), function (req, res) { res.redirect('/'); }); diff --git a/generated/server/app/configure/authentication/google.js b/generated/server/app/configure/authentication/google.js index 10eb8d1..e643da2 100644 --- a/generated/server/app/configure/authentication/google.js +++ b/generated/server/app/configure/authentication/google.js @@ -2,10 +2,10 @@ var passport = require('passport'); var GoogleStrategy = require('passport-google-oauth').OAuth2Strategy; -var mongoose = require('mongoose'); -var UserModel = mongoose.model('User'); -module.exports = function (app) { +module.exports = function (app, db) { + + var User = db.model('user'); var googleConfig = app.getValue('env').GOOGLE; @@ -17,19 +17,19 @@ module.exports = function (app) { var verifyCallback = function (accessToken, refreshToken, profile, done) { - UserModel.findOne({ 'google.id': profile.id }).exec() + User.findOne({ + where: { + google_id: profile.id + } + }) .then(function (user) { - if (user) { return user; } else { - return UserModel.create({ - google: { - id: profile.id - } + return User.create({ + google_id: profile.id }); } - }) .then(function (userToLogin) { done(null, userToLogin); @@ -51,7 +51,7 @@ module.exports = function (app) { })); app.get('/auth/google/callback', - passport.authenticate('google', { failureRedirect: '/login' }), + passport.authenticate('google', {failureRedirect: '/login'}), function (req, res) { res.redirect('/'); }); diff --git a/generated/server/app/configure/authentication/index.js b/generated/server/app/configure/authentication/index.js index 75c9428..66381b3 100644 --- a/generated/server/app/configure/authentication/index.js +++ b/generated/server/app/configure/authentication/index.js @@ -1,10 +1,8 @@ 'use strict'; +var path = require('path'); var session = require('express-session'); -var MongoStore = require('connect-mongo')(session); var passport = require('passport'); -var path = require('path'); -var mongoose = require('mongoose'); -var UserModel = mongoose.model('User'); +var SequelizeStore = require('connect-session-sequelize')(session.Store); var ENABLED_AUTH_STRATEGIES = [ 'local', @@ -13,14 +11,22 @@ var ENABLED_AUTH_STRATEGIES = [ //'google' ]; -module.exports = function (app) { +module.exports = function (app, db) { + + var dbStore = new SequelizeStore({ + db: db + }); + + var User = db.model('user'); + + dbStore.sync(); // First, our session middleware will set/read sessions from the request. // Our sessions will get stored in Mongo using the same connection from // mongoose. Check out the sessions collection in your MongoCLI. app.use(session({ secret: app.getValue('env').SESSION_SECRET, - store: new MongoStore({mongooseConnection: mongoose.connection}), + store: dbStore, resave: false, saveUninitialized: false })); @@ -38,7 +44,11 @@ module.exports = function (app) { // When we receive a cookie from the browser, we use that id to set our req.user // to a user found in the database. passport.deserializeUser(function (id, done) { - UserModel.findById(id, done); + User.findById(id) + .then(function (user) { + done(null, user); + }) + .catch(done); }); // We provide a simple GET /session in order to get session information directly. @@ -60,7 +70,7 @@ module.exports = function (app) { // Each strategy enabled gets registered. ENABLED_AUTH_STRATEGIES.forEach(function (strategyName) { - require(path.join(__dirname, strategyName))(app); + require(path.join(__dirname, strategyName))(app, db); }); }; diff --git a/generated/server/app/configure/authentication/local.js b/generated/server/app/configure/authentication/local.js index 6fd14f4..1418d62 100644 --- a/generated/server/app/configure/authentication/local.js +++ b/generated/server/app/configure/authentication/local.js @@ -1,15 +1,19 @@ 'use strict'; var passport = require('passport'); var LocalStrategy = require('passport-local').Strategy; -var mongoose = require('mongoose'); -var User = mongoose.model('User'); -module.exports = function (app) { +module.exports = function (app, db) { + + var User = db.model('user'); // When passport.authenticate('local') is used, this function will receive // the email and password to run the actual authentication logic. var strategyFn = function (email, password, done) { - User.findOne({ email: email }) + User.findOne({ + where: { + email: email + } + }) .then(function (user) { // user.correctPassword is a method from the User schema. if (!user || !user.correctPassword(password)) { @@ -22,7 +26,7 @@ module.exports = function (app) { .catch(done); }; - passport.use(new LocalStrategy({ usernameField: 'email', passwordField: 'password' }, strategyFn)); + passport.use(new LocalStrategy({usernameField: 'email', passwordField: 'password'}, strategyFn)); // A POST /login route is created to handle login. app.post('/login', function (req, res, next) { diff --git a/generated/server/app/configure/authentication/twitter.js b/generated/server/app/configure/authentication/twitter.js index 3401bb9..3b68ba2 100644 --- a/generated/server/app/configure/authentication/twitter.js +++ b/generated/server/app/configure/authentication/twitter.js @@ -2,10 +2,10 @@ var passport = require('passport'); var TwitterStrategy = require('passport-twitter').Strategy; -var mongoose = require('mongoose'); -var UserModel = mongoose.model('User'); -module.exports = function (app) { +module.exports = function (app, db) { + + var User = db.model('user'); var twitterConfig = app.getValue('env').TWITTER; @@ -16,32 +16,21 @@ module.exports = function (app) { }; var createNewUser = function (token, tokenSecret, profile) { - return UserModel.create({ - twitter: { - id: profile.id, - username: profile.username, - token: token, - tokenSecret: tokenSecret - } + return User.create({ + twitter_id: profile.id }); }; - var updateUserCredentials = function (user, token, tokenSecret, profile) { - - user.twitter.token = token; - user.twitter.tokenSecret = tokenSecret; - user.twitter.username = profile.username; - - return user.save(); - - }; - var verifyCallback = function (token, tokenSecret, profile, done) { - UserModel.findOne({'twitter.id': profile.id}).exec() + UserModel.findOne({ + where: { + twitter_id: profile.id + } + }).exec() .then(function (user) { if (user) { // If a user with this twitter id already exists. - return updateUserCredentials(user, token, tokenSecret, profile); + return user; } else { // If this twitter id has never been seen before and no user is attached. return createNewUser(token, tokenSecret, profile); } diff --git a/generated/server/app/configure/index.js b/generated/server/app/configure/index.js index cadc12d..480e942 100644 --- a/generated/server/app/configure/index.js +++ b/generated/server/app/configure/index.js @@ -1,5 +1,5 @@ 'use strict'; -module.exports = function (app) { +module.exports = function (app, db) { // setValue and getValue are merely alias // for app.set and app.get used in the less @@ -18,6 +18,6 @@ module.exports = function (app) { // variable inside of server/app/configure/app-variables.js app.use(app.getValue('log')); - require('./authentication')(app); + require('./authentication')(app, db); -}; \ No newline at end of file +}; diff --git a/generated/server/app/index.js b/generated/server/app/index.js index ab1dba3..ce76090 100644 --- a/generated/server/app/index.js +++ b/generated/server/app/index.js @@ -2,40 +2,46 @@ var path = require('path'); var express = require('express'); var app = express(); -module.exports = app; - -// Pass our express application pipeline into the configuration -// function located at server/app/configure/index.js -require('./configure')(app); - -// Routes that will be accessed via AJAX should be prepended with -// /api so they are isolated from our GET /* wildcard. -app.use('/api', require('./routes')); - - -/* - This middleware will catch any URLs resembling a file extension - for example: .js, .html, .css - This allows for proper 404s instead of the wildcard '/*' catching - URLs that bypass express.static because the given file does not exist. - */ -app.use(function (req, res, next) { - - if (path.extname(req.path).length > 0) { - res.status(404).end(); - } else { - next(null); - } - -}); - -app.get('/*', function (req, res) { - res.sendFile(app.get('indexHTMLPath')); -}); - -// Error catching endware. -app.use(function (err, req, res, next) { - console.error(err) - console.error(err.stack); - res.status(err.status || 500).send(err.message || 'Internal server error.'); -}); + +module.exports = function (db) { + + // Pass our express application pipeline into the configuration + // function located at server/app/configure/index.js + require('./configure')(app, db); + + // Routes that will be accessed via AJAX should be prepended with + // /api so they are isolated from our GET /* wildcard. + app.use('/api', require('./routes')); + + + /* + This middleware will catch any URLs resembling a file extension + for example: .js, .html, .css + This allows for proper 404s instead of the wildcard '/*' catching + URLs that bypass express.static because the given file does not exist. + */ + app.use(function (req, res, next) { + + if (path.extname(req.path).length > 0) { + res.status(404).end(); + } else { + next(null); + } + + }); + + app.get('/*', function (req, res) { + res.sendFile(app.get('indexHTMLPath')); + }); + + // Error catching endware. + app.use(function (err, req, res, next) { + console.error(err); + console.error(err.stack); + res.status(err.status || 500).send(err.message || 'Internal server error.'); + }); + + return app; + +}; + diff --git a/generated/server/app/routes/members/index.js b/generated/server/app/routes/members/index.js index ad0dd55..242403b 100644 --- a/generated/server/app/routes/members/index.js +++ b/generated/server/app/routes/members/index.js @@ -29,4 +29,4 @@ router.get('/secret-stash', ensureAuthenticated, function (req, res) { res.send(_.shuffle(theStash)); -}); \ No newline at end of file +}); diff --git a/generated/server/db/_db.js b/generated/server/db/_db.js new file mode 100644 index 0000000..cc8080b --- /dev/null +++ b/generated/server/db/_db.js @@ -0,0 +1,7 @@ +var path = require('path'); +var Sequelize = require('sequelize'); + +var env = require(path.join(__dirname, '../env')); +var db = new Sequelize(env.DATABASE_URI); + +module.exports = db; diff --git a/generated/server/db/index.js b/generated/server/db/index.js index f16bd78..cc61b38 100644 --- a/generated/server/db/index.js +++ b/generated/server/db/index.js @@ -1,32 +1,6 @@ 'use strict'; -var Promise = require('bluebird'); -var path = require('path'); -var chalk = require('chalk'); +var db = require('./_db'); +module.exports = db; -var DATABASE_URI = require(path.join(__dirname, '../env')).DATABASE_URI; +require('./models/user')(db); -var mongoose = require('mongoose'); -var db = mongoose.connect(DATABASE_URI).connection; - -// Promises returned from mongoose queries/operations are BLUEBIRD promises -mongoose.Promise = Promise; - -// Require our models -- these should register the model into mongoose -// so the rest of the application can simply call mongoose.model('User') -// anywhere the User model needs to be used. -require('./models'); - -// Modifying startDbPromise to return the db object to have an access to it when .then on startDbPromise -var startDbPromise = new Promise(function (resolve, reject) { - db.on('open', function () { - resolve(db); - }); - db.on('error', reject); -}); - -console.log(chalk.yellow('Opening connection to MongoDB . . .')); -startDbPromise.then(function () { - console.log(chalk.green('MongoDB connection opened!')); -}); - -module.exports = startDbPromise; diff --git a/generated/server/db/models/index.js b/generated/server/db/models/index.js deleted file mode 100644 index 4a341f7..0000000 --- a/generated/server/db/models/index.js +++ /dev/null @@ -1,4 +0,0 @@ -// Require our models -- these should register the model into mongoose -// so the rest of the application can simply call mongoose.model('User') -// anywhere the User model needs to be used. -require('./user'); \ No newline at end of file diff --git a/generated/server/db/models/user.js b/generated/server/db/models/user.js index 8db523e..0dd70d7 100644 --- a/generated/server/db/models/user.js +++ b/generated/server/db/models/user.js @@ -1,66 +1,60 @@ 'use strict'; var crypto = require('crypto'); -var mongoose = require('mongoose'); var _ = require('lodash'); +var Sequelize = require('sequelize'); + +module.exports = function (db) { + + db.define('user', { + email: { + type: Sequelize.STRING + }, + password: { + type: Sequelize.STRING + }, + salt: { + type: Sequelize.STRING + }, + twitter_id: { + type: Sequelize.STRING + }, + facebook_id: { + type: Sequelize.STRING + }, + google_id: { + type: Sequelize.STRING + } + }, { + instanceMethods: { + sanitize: function () { + return _.omit(this.toJSON(), ['password', 'salt']); + }, + correctPassword: function (candidatePassword) { + return this.Model.encryptPassword(candidatePassword, this.salt) === this.password; + } + }, + classMethods: { + generateSalt: function () { + return crypto.randomBytes(16).toString('base64'); + }, + encryptPassword: function (plainText, salt) { + var hash = crypto.createHash('sha1'); + hash.update(plainText); + hash.update(salt); + return hash.digest('hex'); + } + }, + hooks: { + beforeValidate: function (user) { + if (user.changed('password')) { + user.salt = user.Model.generateSalt(); + user.password = user.Model.encryptPassword(user.password, user.salt); + } + } + } + }); -var schema = new mongoose.Schema({ - email: { - type: String - }, - password: { - type: String - }, - salt: { - type: String - }, - twitter: { - id: String, - username: String, - token: String, - tokenSecret: String - }, - facebook: { - id: String - }, - google: { - id: String - } -}); -// method to remove sensitive information from user objects before sending them out -schema.methods.sanitize = function () { - return _.omit(this.toJSON(), ['password', 'salt']); -}; - -// generateSalt, encryptPassword and the pre 'save' and 'correctPassword' operations -// are all used for local authentication security. -var generateSalt = function () { - return crypto.randomBytes(16).toString('base64'); -}; -var encryptPassword = function (plainText, salt) { - var hash = crypto.createHash('sha1'); - hash.update(plainText); - hash.update(salt); - return hash.digest('hex'); }; -schema.pre('save', function (next) { - - if (this.isModified('password')) { - this.salt = this.constructor.generateSalt(); - this.password = this.constructor.encryptPassword(this.password, this.salt); - } - - next(); - -}); - -schema.statics.generateSalt = generateSalt; -schema.statics.encryptPassword = encryptPassword; - -schema.method('correctPassword', function (candidatePassword) { - return encryptPassword(candidatePassword, this.salt) === this.password; -}); - -mongoose.model('User', schema); diff --git a/generated/server/env/development.js b/generated/server/env/development.js index a2fc7cc..f8413ce 100644 --- a/generated/server/env/development.js +++ b/generated/server/env/development.js @@ -1,5 +1,5 @@ module.exports = { - "DATABASE_URI": "mongodb://localhost:27017/fsg-app", + "DATABASE_URI": "postgres://localhost:5432/fsg", "SESSION_SECRET": "Optimus Prime is my real dad", "TWITTER": { "consumerKey": "INSERT_TWITTER_CONSUMER_KEY_HERE", @@ -16,4 +16,4 @@ module.exports = { "clientSecret": "INSERT_GOOGLE_CLIENT_SECRET_HERE", "callbackURL": "INSERT_GOOGLE_CALLBACK_HERE" } -}; \ No newline at end of file +}; diff --git a/generated/server/env/production.js b/generated/server/env/production.js index 0720eb7..b97518c 100644 --- a/generated/server/env/production.js +++ b/generated/server/env/production.js @@ -7,7 +7,7 @@ */ module.exports = { - "DATABASE_URI": process.env.MONGOLAB_URI, + "DATABASE_URI": process.env.DATABASE_URI, "SESSION_SECRET": process.env.SESSION_SECRET, "TWITTER": { "consumerKey": process.env.TWITTER_CONSUMER_KEY, @@ -24,4 +24,4 @@ module.exports = { "clientSecret": process.env.GOOGLE_CLIENT_SECRET, "callbackURL": process.env.GOOGLE_CALLBACK_URL } -}; \ No newline at end of file +}; diff --git a/generated/server/main.js b/generated/server/main.js index 80410f5..7a091ee 100644 --- a/generated/server/main.js +++ b/generated/server/main.js @@ -1,15 +1,12 @@ 'use strict'; var chalk = require('chalk'); - -// Requires in ./db/index.js -- which returns a promise that represents -// mongoose establishing a connection to a MongoDB database. -var startDb = require('./db'); +var db = require('./db'); // Create a node server instance! cOoL! var server = require('http').createServer(); var createApplication = function () { - var app = require('./app'); + var app = require('./app')(db); server.on('request', app); // Attach the Express application. require('./io')(server); // Attach socket.io. }; @@ -24,7 +21,7 @@ var startServer = function () { }; -startDb.then(createApplication).then(startServer).catch(function (err) { +db.sync().then(createApplication).then(startServer).catch(function (err) { console.error(chalk.red(err.stack)); process.kill(1); }); diff --git a/generated/tests/server/models/user-test.js b/generated/tests/server/models/user-test.js index e937e99..c415da2 100644 --- a/generated/tests/server/models/user-test.js +++ b/generated/tests/server/models/user-test.js @@ -1,28 +1,18 @@ -var dbURI = 'mongodb://localhost:27017/testingDB'; -var clearDB = require('mocha-mongoose')(dbURI); - var sinon = require('sinon'); var expect = require('chai').expect; -var mongoose = require('mongoose'); - -// Require in all models. -require('../../../server/db/models'); -var User = mongoose.model('User'); +var Sequelize = require('sequelize'); +var dbURI = 'postgres://localhost:5432/testing-fsg'; +var db = new Sequelize(dbURI); -describe('User model', function () { +require('../../../server/db/models/user')(db); - beforeEach('Establish DB connection', function (done) { - if (mongoose.connection.db) return done(); - mongoose.connect(dbURI, done); - }); +var User = db.model('user'); - afterEach('Clear test database', function (done) { - clearDB(done); - }); +describe('User model', function () { - it('should exist', function () { - expect(User).to.be.a('function'); + beforeEach('Sync DB', function () { + return db.sync({ force: true }); }); describe('password encryption', function () { diff --git a/generated/tests/server/routes/members-only-test.js b/generated/tests/server/routes/members-only-test.js index 1d4d6ea..7bcf148 100644 --- a/generated/tests/server/routes/members-only-test.js +++ b/generated/tests/server/routes/members-only-test.js @@ -1,26 +1,27 @@ // Instantiate all models -var mongoose = require('mongoose'); -require('../../../server/db/models'); -var User = mongoose.model('User'); - var expect = require('chai').expect; -var dbURI = 'mongodb://localhost:27017/testingDB'; -var clearDB = require('mocha-mongoose')(dbURI); +var Sequelize = require('sequelize'); +var dbURI = 'postgres://localhost:5432/testing-fsg'; +var db = new Sequelize(dbURI, { + logging: false +}); +require('../../../server/db/models/user')(db); var supertest = require('supertest'); -var app = require('../../../server/app'); describe('Members Route', function () { - beforeEach('Establish DB connection', function (done) { - if (mongoose.connection.db) return done(); - mongoose.connect(dbURI, done); - }); + var app, User; - afterEach('Clear test database', function (done) { - clearDB(done); - }); + beforeEach('Sync DB', function () { + return db.sync({ force: true }); + }); + + beforeEach('Create app', function () { + app = require('../../../server/app')(db); + User = db.model('user'); + }); describe('Unauthenticated request', function () { @@ -48,7 +49,9 @@ describe('Members Route', function () { }; beforeEach('Create a user', function (done) { - User.create(userInfo, done); + return User.create(userInfo).then(function (user) { + done(); + }).catch(done); }); beforeEach('Create loggedIn user agent and authenticate', function (done) {