mirror of
https://github.com/fergalmoran/roboto-promoto.git
synced 2025-12-22 01:30:36 +00:00
Initial Commit
This commit is contained in:
13
lib/.jshintrc
Normal file
13
lib/.jshintrc
Normal file
@@ -0,0 +1,13 @@
|
||||
{
|
||||
"node": true,
|
||||
"esnext": true,
|
||||
"bitwise": true,
|
||||
"eqeqeq": true,
|
||||
"immed": true,
|
||||
"latedef": true,
|
||||
"newcap": true,
|
||||
"noarg": true,
|
||||
"regexp": true,
|
||||
"undef": true,
|
||||
"smarttabs": true
|
||||
}
|
||||
10
lib/config/config.js
Normal file
10
lib/config/config.js
Normal file
@@ -0,0 +1,10 @@
|
||||
'use strict';
|
||||
|
||||
var _ = require('lodash');
|
||||
|
||||
/**
|
||||
* Load environment configuration
|
||||
*/
|
||||
module.exports = _.merge(
|
||||
require('./env/all.js'),
|
||||
require('./env/' + process.env.NODE_ENV + '.js') || {});
|
||||
37
lib/config/dummydata.js
Normal file
37
lib/config/dummydata.js
Normal file
@@ -0,0 +1,37 @@
|
||||
'use strict';
|
||||
|
||||
var mongoose = require('mongoose'),
|
||||
User = mongoose.model('User'),
|
||||
Promotion = mongoose.model('Promotion'),
|
||||
Thing = mongoose.model('Thing');
|
||||
var user;
|
||||
|
||||
console.log("Creating users");
|
||||
// Clear old users, then add a default user
|
||||
User.find({}).remove(function() {
|
||||
user = new User({
|
||||
provider: 'local',
|
||||
name: 'Test User',
|
||||
email: 'test@test.com',
|
||||
password: 'test' }, function() {
|
||||
console.log('finished populating users');
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
console.log("Creating promotions");
|
||||
Promotion.find({}).remove(function(){
|
||||
Promotion.create({
|
||||
title: 'Dev prom 1',
|
||||
user: user
|
||||
}, {
|
||||
title: 'Dev prom 2',
|
||||
user: user
|
||||
}, {
|
||||
title: 'Dev prom 3',
|
||||
user: user
|
||||
}, function(){
|
||||
console.log('finished populating promotions');
|
||||
});
|
||||
});
|
||||
|
||||
17
lib/config/env/all.js
vendored
Normal file
17
lib/config/env/all.js
vendored
Normal file
@@ -0,0 +1,17 @@
|
||||
'use strict';
|
||||
|
||||
var path = require('path');
|
||||
|
||||
var rootPath = path.normalize(__dirname + '/../../..');
|
||||
|
||||
module.exports = {
|
||||
root: rootPath,
|
||||
port: process.env.PORT || 3000,
|
||||
mongo: {
|
||||
options: {
|
||||
db: {
|
||||
safe: true
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
8
lib/config/env/development.js
vendored
Normal file
8
lib/config/env/development.js
vendored
Normal file
@@ -0,0 +1,8 @@
|
||||
'use strict';
|
||||
|
||||
module.exports = {
|
||||
env: 'development',
|
||||
mongo: {
|
||||
uri: 'mongodb://localhost/roboto-dev'
|
||||
}
|
||||
};
|
||||
10
lib/config/env/production.js
vendored
Normal file
10
lib/config/env/production.js
vendored
Normal file
@@ -0,0 +1,10 @@
|
||||
'use strict';
|
||||
|
||||
module.exports = {
|
||||
env: 'production',
|
||||
mongo: {
|
||||
uri: process.env.MONGOLAB_URI ||
|
||||
process.env.MONGOHQ_URL ||
|
||||
'mongodb://localhost/roboto'
|
||||
}
|
||||
};
|
||||
8
lib/config/env/test.js
vendored
Normal file
8
lib/config/env/test.js
vendored
Normal file
@@ -0,0 +1,8 @@
|
||||
'use strict';
|
||||
|
||||
module.exports = {
|
||||
env: 'test',
|
||||
mongo: {
|
||||
uri: 'mongodb://localhost/roboto-test'
|
||||
}
|
||||
};
|
||||
69
lib/config/express.js
Normal file
69
lib/config/express.js
Normal file
@@ -0,0 +1,69 @@
|
||||
'use strict';
|
||||
|
||||
var express = require('express'),
|
||||
path = require('path'),
|
||||
config = require('./config'),
|
||||
passport = require('passport'),
|
||||
mongoStore = require('connect-mongo')(express);
|
||||
|
||||
/**
|
||||
* Express configuration
|
||||
*/
|
||||
module.exports = function(app) {
|
||||
app.configure('development', function(){
|
||||
app.use(require('connect-livereload')());
|
||||
|
||||
// Disable caching of scripts for easier testing
|
||||
app.use(function noCache(req, res, next) {
|
||||
if (req.url.indexOf('/scripts/') === 0) {
|
||||
res.header('Cache-Control', 'no-cache, no-store, must-revalidate');
|
||||
res.header('Pragma', 'no-cache');
|
||||
res.header('Expires', 0);
|
||||
}
|
||||
next();
|
||||
});
|
||||
|
||||
app.use(express.static(path.join(config.root, '.tmp')));
|
||||
app.use(express.static(path.join(config.root, 'app')));
|
||||
app.set('views', config.root + '/app/views');
|
||||
});
|
||||
|
||||
app.configure('production', function(){
|
||||
app.use(express.favicon(path.join(config.root, 'public', 'favicon.ico')));
|
||||
app.use(express.static(path.join(config.root, 'public')));
|
||||
app.set('views', config.root + '/views');
|
||||
});
|
||||
|
||||
app.configure(function(){
|
||||
app.engine('html', require('ejs').renderFile);
|
||||
app.set('view engine', 'html');
|
||||
app.use(express.logger('dev'));
|
||||
app.use(express.json());
|
||||
app.use(express.urlencoded());
|
||||
app.use(express.methodOverride());
|
||||
app.use(express.cookieParser());
|
||||
|
||||
// Persist sessions with mongoStore
|
||||
app.use(express.session({
|
||||
secret: 'angular-fullstack secret',
|
||||
store: new mongoStore({
|
||||
url: config.mongo.uri,
|
||||
collection: 'sessions'
|
||||
}, function () {
|
||||
console.log("db connection open");
|
||||
})
|
||||
}));
|
||||
|
||||
//use passport session
|
||||
app.use(passport.initialize());
|
||||
app.use(passport.session());
|
||||
|
||||
// Router (only error handlers should come after this)
|
||||
app.use(app.router);
|
||||
});
|
||||
|
||||
// Error handler
|
||||
app.configure('development', function(){
|
||||
app.use(express.errorHandler());
|
||||
});
|
||||
};
|
||||
48
lib/config/passport.js
Normal file
48
lib/config/passport.js
Normal file
@@ -0,0 +1,48 @@
|
||||
'use strict';
|
||||
|
||||
var mongoose = require('mongoose'),
|
||||
User = mongoose.model('User'),
|
||||
passport = require('passport'),
|
||||
LocalStrategy = require('passport-local').Strategy;
|
||||
|
||||
/**
|
||||
* Passport configuration
|
||||
*/
|
||||
passport.serializeUser(function(user, done) {
|
||||
done(null, user.id);
|
||||
});
|
||||
passport.deserializeUser(function(id, done) {
|
||||
User.findOne({
|
||||
_id: id
|
||||
}, '-salt -hashedPassword', function(err, user) { // don't ever give out the password or salt
|
||||
done(err, user);
|
||||
});
|
||||
});
|
||||
|
||||
// add other strategies for more authentication flexibility
|
||||
passport.use(new LocalStrategy({
|
||||
usernameField: 'email',
|
||||
passwordField: 'password' // this is the virtual field on the model
|
||||
},
|
||||
function(email, password, done) {
|
||||
User.findOne({
|
||||
email: email
|
||||
}, function(err, user) {
|
||||
if (err) return done(err);
|
||||
|
||||
if (!user) {
|
||||
return done(null, false, {
|
||||
message: 'This email is not registered.'
|
||||
});
|
||||
}
|
||||
if (!user.authenticate(password)) {
|
||||
return done(null, false, {
|
||||
message: 'This password is not correct.'
|
||||
});
|
||||
}
|
||||
return done(null, user);
|
||||
});
|
||||
}
|
||||
));
|
||||
|
||||
module.exports = passport;
|
||||
18
lib/controllers/api.js
Normal file
18
lib/controllers/api.js
Normal file
@@ -0,0 +1,18 @@
|
||||
'use strict';
|
||||
|
||||
var mongoose = require('mongoose'),
|
||||
Promotion = mongoose.model('Promotion'),
|
||||
Thing = mongoose.model('Thing');
|
||||
|
||||
/**
|
||||
* Get awesome things
|
||||
*/
|
||||
exports.awesomeThings = function(req, res) {
|
||||
return Thing.find(function (err, things) {
|
||||
if (!err) {
|
||||
return res.json(things);
|
||||
} else {
|
||||
return res.send(err);
|
||||
}
|
||||
});
|
||||
};
|
||||
27
lib/controllers/index.js
Normal file
27
lib/controllers/index.js
Normal file
@@ -0,0 +1,27 @@
|
||||
'use strict';
|
||||
|
||||
var path = require('path');
|
||||
|
||||
/**
|
||||
* Send partial, or 404 if it doesn't exist
|
||||
*/
|
||||
exports.partials = function(req, res) {
|
||||
var stripped = req.url.split('.')[0];
|
||||
var requestedView = path.join('./', stripped);
|
||||
res.render(requestedView, function(err, html) {
|
||||
if(err) {
|
||||
console.log("Error rendering partial '" + requestedView + "'\n", err);
|
||||
res.status(404);
|
||||
res.send(404);
|
||||
} else {
|
||||
res.send(html);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Send our single page app
|
||||
*/
|
||||
exports.index = function(req, res) {
|
||||
res.render('index');
|
||||
};
|
||||
59
lib/controllers/promotions.js
Normal file
59
lib/controllers/promotions.js
Normal file
@@ -0,0 +1,59 @@
|
||||
'use strict';
|
||||
|
||||
var mongoose = require('mongoose'),
|
||||
Promotion = mongoose.model('Promotion'),
|
||||
passport = require('passport');
|
||||
|
||||
|
||||
exports.list = function(req, res){
|
||||
return Promotion.find(function(err, promotions){
|
||||
if (!err){
|
||||
return res.json(promotions);
|
||||
}else{
|
||||
return res.send(err);
|
||||
}
|
||||
});
|
||||
};
|
||||
exports.create = function (req, res, next) {
|
||||
var newPromotion = new Promotion(req.body);
|
||||
newPromotion.provider = 'local';
|
||||
newPromotion.save(function(err) {
|
||||
if (err) return res.json(400, err);
|
||||
|
||||
req.logIn(newPromotion, function(err) {
|
||||
if (err) return next(err);
|
||||
|
||||
return res.json(req.promotion.promotionInfo);
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
exports.show = function (req, res, next) {
|
||||
var promotionId = req.params.id;
|
||||
|
||||
Promotion.findById(promotionId, function (err, promotion) {
|
||||
if (err) return next(err);
|
||||
if (!promotion) return res.send(404);
|
||||
|
||||
res.send({ profile: promotion.profile });
|
||||
});
|
||||
};
|
||||
|
||||
exports.changePassword = function(req, res, next) {
|
||||
var promotionId = req.promotion._id;
|
||||
var oldPass = String(req.body.oldPassword);
|
||||
var newPass = String(req.body.newPassword);
|
||||
|
||||
Promotion.findById(promotionId, function (err, promotion) {
|
||||
if(promotion.authenticate(oldPass)) {
|
||||
promotion.password = newPass;
|
||||
promotion.save(function(err) {
|
||||
if (err) return res.send(400);
|
||||
|
||||
res.send(200);
|
||||
});
|
||||
} else {
|
||||
res.send(403);
|
||||
}
|
||||
});
|
||||
};
|
||||
28
lib/controllers/session.js
Normal file
28
lib/controllers/session.js
Normal file
@@ -0,0 +1,28 @@
|
||||
'use strict';
|
||||
|
||||
var mongoose = require('mongoose'),
|
||||
passport = require('passport');
|
||||
|
||||
/**
|
||||
* Logout
|
||||
*/
|
||||
exports.logout = function (req, res) {
|
||||
req.logout();
|
||||
res.send(200);
|
||||
};
|
||||
|
||||
/**
|
||||
* Login
|
||||
*/
|
||||
exports.login = function (req, res, next) {
|
||||
passport.authenticate('local', function(err, user, info) {
|
||||
var error = err || info;
|
||||
if (error) return res.json(401, error);
|
||||
|
||||
req.logIn(user, function(err) {
|
||||
|
||||
if (err) return res.send(err);
|
||||
res.json(req.user.userInfo);
|
||||
});
|
||||
})(req, res, next);
|
||||
};
|
||||
65
lib/controllers/users.js
Normal file
65
lib/controllers/users.js
Normal file
@@ -0,0 +1,65 @@
|
||||
'use strict';
|
||||
|
||||
var mongoose = require('mongoose'),
|
||||
User = mongoose.model('User'),
|
||||
passport = require('passport');
|
||||
|
||||
/**
|
||||
* Create user
|
||||
*/
|
||||
exports.create = function (req, res, next) {
|
||||
var newUser = new User(req.body);
|
||||
newUser.provider = 'local';
|
||||
newUser.save(function(err) {
|
||||
if (err) return res.json(400, err);
|
||||
|
||||
req.logIn(newUser, function(err) {
|
||||
if (err) return next(err);
|
||||
|
||||
return res.json(req.user.userInfo);
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Get profile of specified user
|
||||
*/
|
||||
exports.show = function (req, res, next) {
|
||||
var userId = req.params.id;
|
||||
|
||||
User.findById(userId, function (err, user) {
|
||||
if (err) return next(err);
|
||||
if (!user) return res.send(404);
|
||||
|
||||
res.send({ profile: user.profile });
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Change password
|
||||
*/
|
||||
exports.changePassword = function(req, res, next) {
|
||||
var userId = req.user._id;
|
||||
var oldPass = String(req.body.oldPassword);
|
||||
var newPass = String(req.body.newPassword);
|
||||
|
||||
User.findById(userId, function (err, user) {
|
||||
if(user.authenticate(oldPass)) {
|
||||
user.password = newPass;
|
||||
user.save(function(err) {
|
||||
if (err) return res.send(400);
|
||||
|
||||
res.send(200);
|
||||
});
|
||||
} else {
|
||||
res.send(403);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Get current user
|
||||
*/
|
||||
exports.me = function(req, res) {
|
||||
res.json(req.user || null);
|
||||
};
|
||||
25
lib/middleware.js
Normal file
25
lib/middleware.js
Normal file
@@ -0,0 +1,25 @@
|
||||
'use strict';
|
||||
|
||||
/**
|
||||
* Custom middleware used by the application
|
||||
*/
|
||||
module.exports = {
|
||||
|
||||
/**
|
||||
* Protect routes on your api from unauthenticated access
|
||||
*/
|
||||
auth: function auth(req, res, next) {
|
||||
if (req.isAuthenticated()) return next();
|
||||
res.send(401);
|
||||
},
|
||||
|
||||
/**
|
||||
* Set a cookie for angular so it knows we have an http session
|
||||
*/
|
||||
setUserCookie: function(req, res, next) {
|
||||
if(req.user) {
|
||||
res.cookie('user', JSON.stringify(req.user.userInfo));
|
||||
}
|
||||
next();
|
||||
}
|
||||
};
|
||||
22
lib/models/promotion.js
Normal file
22
lib/models/promotion.js
Normal file
@@ -0,0 +1,22 @@
|
||||
'use strict';
|
||||
|
||||
var mongoose = require('mongoose'),
|
||||
Schema = mongoose.Schema;
|
||||
|
||||
/** Promotion Schema */
|
||||
var PromotionSchema = new Schema({
|
||||
created: { type: Date, default: Date.now },
|
||||
title: { type: String, default: '', trim: true, required: true },
|
||||
user: { type: Schema.ObjectId, ref: 'User', required: true }
|
||||
});
|
||||
|
||||
/* Indexes */
|
||||
PromotionSchema.index({title: 1, user: 1}, {unique: true});
|
||||
var _model = mongoose.model('Promotion', PromotionSchema);
|
||||
|
||||
/* Operations */
|
||||
PromotionSchema.statics.findByTitle = function(title, cb){
|
||||
console.log("findByTitle: " + title);
|
||||
_model.findOne({title: title}, cb);
|
||||
};
|
||||
|
||||
22
lib/models/thing.js
Normal file
22
lib/models/thing.js
Normal file
@@ -0,0 +1,22 @@
|
||||
'use strict';
|
||||
|
||||
var mongoose = require('mongoose'),
|
||||
Schema = mongoose.Schema;
|
||||
|
||||
/**
|
||||
* Thing Schema
|
||||
*/
|
||||
var ThingSchema = new Schema({
|
||||
name: String,
|
||||
info: String,
|
||||
awesomeness: Number
|
||||
});
|
||||
|
||||
/**
|
||||
* Validations
|
||||
*/
|
||||
ThingSchema.path('awesomeness').validate(function (num) {
|
||||
return num >= 1 && num <= 10;
|
||||
}, 'Awesomeness must be between 1 and 10');
|
||||
|
||||
mongoose.model('Thing', ThingSchema);
|
||||
145
lib/models/user.js
Normal file
145
lib/models/user.js
Normal file
@@ -0,0 +1,145 @@
|
||||
'use strict';
|
||||
|
||||
var mongoose = require('mongoose'),
|
||||
Schema = mongoose.Schema,
|
||||
crypto = require('crypto');
|
||||
|
||||
var authTypes = ['github', 'twitter', 'facebook', 'google'];
|
||||
|
||||
/**
|
||||
* User Schema
|
||||
*/
|
||||
var UserSchema = new Schema({
|
||||
name: String,
|
||||
email: String,
|
||||
role: {
|
||||
type: String,
|
||||
default: 'user'
|
||||
},
|
||||
hashedPassword: String,
|
||||
provider: String,
|
||||
salt: String,
|
||||
facebook: {},
|
||||
twitter: {},
|
||||
github: {},
|
||||
google: {}
|
||||
});
|
||||
|
||||
/**
|
||||
* Virtuals
|
||||
*/
|
||||
UserSchema
|
||||
.virtual('password')
|
||||
.set(function(password) {
|
||||
this._password = password;
|
||||
this.salt = this.makeSalt();
|
||||
this.hashedPassword = this.encryptPassword(password);
|
||||
})
|
||||
.get(function() {
|
||||
return this._password;
|
||||
});
|
||||
|
||||
// Basic info to identify the current authenticated user in the app
|
||||
UserSchema.virtual('userInfo').get(function() {
|
||||
return {
|
||||
'name': this.name,
|
||||
'role': this.role,
|
||||
'provider': this.provider
|
||||
};
|
||||
});
|
||||
|
||||
// Public profile information
|
||||
UserSchema.virtual('profile').get(function() {
|
||||
return {
|
||||
'name': this.name,
|
||||
'role': this.role
|
||||
};
|
||||
});
|
||||
|
||||
/**
|
||||
* Validations
|
||||
*/
|
||||
|
||||
// Validate empty email
|
||||
UserSchema.path('email').validate(function(email) {
|
||||
// if you are authenticating by any of the oauth strategies, don't validate
|
||||
if (authTypes.indexOf(this.provider) !== -1) return true;
|
||||
return email.length;
|
||||
}, 'Email cannot be blank');
|
||||
|
||||
// Validate empty password
|
||||
UserSchema.path('hashedPassword').validate(function(hashedPassword) {
|
||||
// if you are authenticating by any of the oauth strategies, don't validate
|
||||
if (authTypes.indexOf(this.provider) !== -1) return true;
|
||||
return hashedPassword.length;
|
||||
}, 'Password cannot be blank');
|
||||
|
||||
// Validate email is not taken
|
||||
UserSchema.path('email').validate(function(value, respond) {
|
||||
var self = this;
|
||||
this.constructor.findOne({email: value}, function(err, user) {
|
||||
if(err) throw err;
|
||||
if(user) {
|
||||
if(self.id === user.id) return respond(true);
|
||||
return respond(false);
|
||||
}
|
||||
respond(true);
|
||||
});
|
||||
}, 'The specified email address is already in use.');
|
||||
|
||||
var validatePresenceOf = function(value) {
|
||||
return value && value.length;
|
||||
};
|
||||
|
||||
/**
|
||||
* Pre-save hook
|
||||
*/
|
||||
UserSchema.pre('save', function(next) {
|
||||
if (!this.isNew) return next();
|
||||
|
||||
if (!validatePresenceOf(this.hashedPassword) && authTypes.indexOf(this.provider) === -1)
|
||||
next(new Error('Invalid password'));
|
||||
else
|
||||
next();
|
||||
});
|
||||
|
||||
/**
|
||||
* Methods
|
||||
*/
|
||||
UserSchema.methods = {
|
||||
/**
|
||||
* Authenticate - check if the passwords are the same
|
||||
*
|
||||
* @param {String} plainText
|
||||
* @return {Boolean}
|
||||
* @api public
|
||||
*/
|
||||
authenticate: function(plainText) {
|
||||
return this.encryptPassword(plainText) === this.hashedPassword;
|
||||
},
|
||||
|
||||
/**
|
||||
* Make salt
|
||||
*
|
||||
* @return {String}
|
||||
* @api public
|
||||
*/
|
||||
makeSalt: function() {
|
||||
return crypto.randomBytes(16).toString('base64');
|
||||
},
|
||||
|
||||
/**
|
||||
* Encrypt password
|
||||
*
|
||||
* @param {String} password
|
||||
* @return {String}
|
||||
* @api public
|
||||
*/
|
||||
encryptPassword: function(password) {
|
||||
if (!password || !this.salt) return '';
|
||||
var salt = new Buffer(this.salt, 'base64');
|
||||
return crypto.pbkdf2Sync(password, salt, 10000, 64).toString('base64');
|
||||
}
|
||||
};
|
||||
|
||||
module.exports = mongoose.model('User', UserSchema);
|
||||
36
lib/routes.js
Normal file
36
lib/routes.js
Normal file
@@ -0,0 +1,36 @@
|
||||
'use strict';
|
||||
|
||||
var api = require('./controllers/api'),
|
||||
index = require('./controllers'),
|
||||
users = require('./controllers/users'),
|
||||
promotions = require('./controllers/promotions'),
|
||||
session = require('./controllers/session');
|
||||
|
||||
var middleware = require('./middleware');
|
||||
|
||||
/**
|
||||
* Application routes
|
||||
*/
|
||||
module.exports = function(app) {
|
||||
|
||||
// Server API Routes
|
||||
app.get('/api/awesomeThings', api.awesomeThings);
|
||||
app.get('/api/promotions', promotions.list);
|
||||
|
||||
app.post('/api/users', users.create);
|
||||
app.put('/api/users', users.changePassword);
|
||||
app.get('/api/users/me', users.me);
|
||||
app.get('/api/users/:id', users.show);
|
||||
|
||||
app.post('/api/session', session.login);
|
||||
app.del('/api/session', session.logout);
|
||||
|
||||
// All undefined api routes should return a 404
|
||||
app.get('/api/*', function(req, res) {
|
||||
res.send(404);
|
||||
});
|
||||
|
||||
// All other routes to use Angular routing in app/scripts/app.js
|
||||
app.get('/partials/*', index.partials);
|
||||
app.get('/*', middleware.setUserCookie, index.index);
|
||||
};
|
||||
Reference in New Issue
Block a user