diff --git a/README.md b/README.md index 7225eef47..18a5fa223 100755 --- a/README.md +++ b/README.md @@ -34,7 +34,6 @@ Set-Up Resources ================================= -* [The Complete Guide To Building Scalable Apps On AWS](https://www.airpair.com/aws/posts/building-a-scalable-web-app-on-amazon-web-services-p1) * [List Of AWS Tips](https://wblinks.com/notes/aws-tips-i-wish-id-known-before-i-started/) * [Amazon Monthly Cost Estimate Calculator](http://calculator.s3.amazonaws.com/index.html) * [Set-Up AWS Billing Alerts](http://docs.aws.amazon.com/awsaccountbilling/latest/aboutv2/monitor-charges.html) diff --git a/lib/index.js b/lib/index.js index 83845acc9..6ad2a2c65 100644 --- a/lib/index.js +++ b/lib/index.js @@ -10,6 +10,7 @@ module.exports.models = { - AWS: require('./models/model_aws') + AWS: require('./models/model_aws'), + User: require('./models/model_user') }; \ No newline at end of file diff --git a/lib/models/model_user.js b/lib/models/model_user.js new file mode 100644 index 000000000..1e29c0ac2 --- /dev/null +++ b/lib/models/model_user.js @@ -0,0 +1,270 @@ +/** + * Model: Users + * - users of your application + */ + + +// Dependencies +var Config = require('../config/config'); +var Utilities = require('../utilities/utilities'); +var AppDynamoDB = require('./model_aws').DynamoDB; + +var moment = require('moment'); +var bcryptjs = require('bcryptjs'); +var _ = require('lodash'); +var validator = require('validator'); + + + +module.exports = new User(); + + + +function User() {} + + + +/** + * SignUp + */ + +User.prototype.signUp = function(data, callback) { + + // Defaults + var _this = this; + + + /** + * Validate + */ + + if (!data.email) return callback({ + status: 400, + message: 'Bad Request: Email is required' + }, null); + + if (!data.password) return callback({ + status: 400, + message: 'Bad Request: Password is required' + }, null); + + if (data.password && data.password.length < 8) return callback({ + status: 400, + message: 'Bad Request: Password must be at least 8 characters long' + }, null); + + + + // Check if email is already in use + _this.showByEmail(data.email, function(error, user) { + + if (error) return callback(error, null); + + if (user) return callback({ + status: 409, + message: 'Email is already in use' + }, null); + + + + /** + * Instantiate + */ + + var user = { + _id: Utilities.generateID('user'), + email: data.email ? data.email : null, + password: data.password ? data.password : null, + created: moment().unix(), + updated: moment().unix(), + plan: 'free', + sign_in_count: 0 + }; + + + // Hash Password + user = _this.hashPassword(user); + + + + /** + * Save + */ + + _this.save(user, function(error, user) { + + if (error) return (error, null); + + return callback(null, user); + + }); + }); +} + + + + + +/** + * ShowByEmail + */ + +User.prototype.showByEmail = function(email, callback) { + + + /** + * Validate + */ + + if (!email) return callback({ + status: 400, + message: 'Bad Request: Email is required' + }, null); + + + + /** + * Find User + */ + + var params = {}; + params.TableName = "app_users"; + params.IndexName = "email-index"; + params.KeyConditions = [ + AppDynamoDB.Condition("email", "EQ", email) + ]; + + AppDynamoDB.query(params, function(error, response) { + + if (error || !response) return callback({ + status: 500, + message: 'Sorry, something went wrong.' + raw: error + }, null); + + return callback(null, response.Items && response.Items[0] ? response.Items[0] : null); + + }); +} + + + + +/** + * Save + * - Updates existing record or creates a record if one does not already exist + */ + +User.prototype.save = function(data, callback) { + + + /** + * Validate + */ + + if (!data.email) return callback({ + status: 400, + message: 'Bad Request: Email is required' + }, null); + + if (!data.password) return callback({ + status: 400, + message: 'Bad Request: Password is required' + }, null); + + + /** + * Perform Save + */ + + var params = { + TableName: 'app_users', + ReturnValues: 'ALL_NEW', + Key: { + '_id': user._id + }, + UpdateExpression: 'SET ', + ExpressionAttributeNames: {}, + ExpressionAttributeValues: {} + }; + + + /** + * Basic Information + */ + + + // email + params.UpdateExpression = params.UpdateExpression + '#a0 = :email_val, '; + params.ExpressionAttributeNames['#a0'] = 'email'; + params.ExpressionAttributeValues[':email_val'] = user.email; + + // password + params.UpdateExpression = params.UpdateExpression + '#a1 = :password_val, '; + params.ExpressionAttributeNames['#a1'] = 'password'; + params.ExpressionAttributeValues[':password_val'] = user.password; + + // salt + params.UpdateExpression = params.UpdateExpression + '#a2 = :salt_val, '; + params.ExpressionAttributeNames['#a2'] = 'salt'; + params.ExpressionAttributeValues[':salt_val'] = user.salt; + + // plan + params.UpdateExpression = params.UpdateExpression + '#a3 = :plan_val, '; + params.ExpressionAttributeNames['#a3'] = 'plan'; + params.ExpressionAttributeValues[':plan_val'] = user.plan; + + // sign_in_count + params.UpdateExpression = params.UpdateExpression + '#a4 = :sign_in_count_val, '; + params.ExpressionAttributeNames['#a4'] = 'sign_in_count'; + params.ExpressionAttributeValues[':sign_in_count_val'] = user.sign_in_count; + + // updated + params.UpdateExpression = params.UpdateExpression + '#b0 = :updated_val, '; + params.ExpressionAttributeNames['#b0'] = 'updated'; + params.ExpressionAttributeValues[':updated_val'] = moment().unix(); + + // created + if (isNaN(user.created)) user.created = moment(user.created).unix(); + params.UpdateExpression = params.UpdateExpression + '#b1 = :created_val, '; + params.ExpressionAttributeNames['#b1'] = 'created'; + params.ExpressionAttributeValues[':created_val'] = user.created; + + + + /** + * Save + */ + + // Remove Any Trailing Commas & Space + params.UpdateExpression = params.UpdateExpression.trim(); + if (params.UpdateExpression[params.UpdateExpression.length - 1] === ',') params.UpdateExpression = params.UpdateExpression.substring(0, params.UpdateExpression.length - 1); + + + AppDynamoDB.updateItem(params, function(error, response) { + + if (error || !response) return callback({ + status: 500, + raw: error + }, null); + + return callback(null, response.Attributes); + + }); +} + + + + +/** + * Hash Password + */ + +User.prototype.hashPassword = function(user) { + + user.salt = bcryptjs.genSaltSync(10); + user.password = bcryptjs.hashSync(user.password, user.salt); + + return user; + +}; \ No newline at end of file diff --git a/lib/utilities/utilities.js b/lib/utilities/utilities.js new file mode 100644 index 000000000..5e078f3e4 --- /dev/null +++ b/lib/utilities/utilities.js @@ -0,0 +1,28 @@ +/** + * Utilities + */ + + +module.exports = new Utilities(); + + +function Utilities() {} + + +/** + * Generate ID + * - Generates a unique ID for a data record + * - Prefixs each id with a type + */ + +Utilities.prototype.generateID = function(type) { + + switch (type) { + + case 'user': + + return 'u_' + uuid.v1(); + + } + +}; \ No newline at end of file