From 2ac21e5def7505462158e2427fba5050335fd642 Mon Sep 17 00:00:00 2001 From: jacksoncharles Date: Tue, 17 Oct 2017 22:27:27 +0100 Subject: [PATCH 01/78] first commit of possible forum service --- loadData.sh | 5 ++ sample_data/Forum.json | 23 +++++++ sample_data/Reply.json | 76 +++++++++++++++++++++++ sample_data/Thread.json | 130 ++++++++++++++++++++++++++++++++++++++++ serverless.yml | 70 ++++++++++++++++++---- 5 files changed, 292 insertions(+), 12 deletions(-) create mode 100755 loadData.sh create mode 100644 sample_data/Forum.json create mode 100644 sample_data/Reply.json create mode 100644 sample_data/Thread.json diff --git a/loadData.sh b/loadData.sh new file mode 100755 index 0000000..49eb75a --- /dev/null +++ b/loadData.sh @@ -0,0 +1,5 @@ +#!/usr/bin/env bash + +aws dynamodb batch-write-item --request-items file://sample_data/Forum.json +aws dynamodb batch-write-item --request-items file://sample_data/Thread.json +aws dynamodb batch-write-item --request-items file://sample_data/Reply.json \ No newline at end of file diff --git a/sample_data/Forum.json b/sample_data/Forum.json new file mode 100644 index 0000000..273c665 --- /dev/null +++ b/sample_data/Forum.json @@ -0,0 +1,23 @@ +{ + "Forum": [ + { + "PutRequest": { + "Item": { + "Name": {"S":"Amazon DynamoDB"}, + "Category": {"S":"Amazon Web Services"}, + "Threads": {"N":"2"}, + "Messages": {"N":"4"}, + "Views": {"N":"1000"} + } + } + }, + { + "PutRequest": { + "Item": { + "Name": {"S":"Amazon S3"}, + "Category": {"S":"Amazon Web Services"} + } + } + } + ] +} diff --git a/sample_data/Reply.json b/sample_data/Reply.json new file mode 100644 index 0000000..7c70b0d --- /dev/null +++ b/sample_data/Reply.json @@ -0,0 +1,76 @@ +{ + "Reply": [ + { + "PutRequest": { + "Item": { + "Id": { + "S": "Amazon DynamoDB#DynamoDB Thread 1" + }, + "ReplyDateTime": { + "S": "2015-09-15T19:58:22.947Z" + }, + "Message": { + "S": "DynamoDB Thread 1 Reply 1 text" + }, + "PostedBy": { + "S": "User A" + } + } + } + }, + { + "PutRequest": { + "Item": { + "Id": { + "S": "Amazon DynamoDB#DynamoDB Thread 1" + }, + "ReplyDateTime": { + "S": "2015-09-22T19:58:22.947Z" + }, + "Message": { + "S": "DynamoDB Thread 1 Reply 2 text" + }, + "PostedBy": { + "S": "User B" + } + } + } + }, + { + "PutRequest": { + "Item": { + "Id": { + "S": "Amazon DynamoDB#DynamoDB Thread 2" + }, + "ReplyDateTime": { + "S": "2015-09-29T19:58:22.947Z" + }, + "Message": { + "S": "DynamoDB Thread 2 Reply 1 text" + }, + "PostedBy": { + "S": "User A" + } + } + } + }, + { + "PutRequest": { + "Item": { + "Id": { + "S": "Amazon DynamoDB#DynamoDB Thread 2" + }, + "ReplyDateTime": { + "S": "2015-10-05T19:58:22.947Z" + }, + "Message": { + "S": "DynamoDB Thread 2 Reply 2 text" + }, + "PostedBy": { + "S": "User A" + } + } + } + } + ] +} \ No newline at end of file diff --git a/sample_data/Thread.json b/sample_data/Thread.json new file mode 100644 index 0000000..64d2edd --- /dev/null +++ b/sample_data/Thread.json @@ -0,0 +1,130 @@ +{ + "Thread": [ + { + "PutRequest": { + "Item": { + "ForumName": { + "S": "Amazon DynamoDB" + }, + "Subject": { + "S": "DynamoDB Thread 1" + }, + "Message": { + "S": "DynamoDB thread 1 message" + }, + "LastPostedBy": { + "S": "User A" + }, + "LastPostedDateTime": { + "S": "2015-09-22T19:58:22.514Z" + }, + "Views": { + "N": "0" + }, + "Replies": { + "N": "0" + }, + "Answered": { + "N": "0" + }, + "Tags": { + "L": [ + { + "S": "index" + }, + { + "S": "primarykey" + }, + { + "S": "table" + } + ] + } + } + } + }, + { + "PutRequest": { + "Item": { + "ForumName": { + "S": "Amazon DynamoDB" + }, + "Subject": { + "S": "DynamoDB Thread 2" + }, + "Message": { + "S": "DynamoDB thread 2 message" + }, + "LastPostedBy": { + "S": "User A" + }, + "LastPostedDateTime": { + "S": "2015-09-15T19:58:22.514Z" + }, + "Views": { + "N": "0" + }, + "Replies": { + "N": "0" + }, + "Answered": { + "N": "0" + }, + "Tags": { + "L": [ + { + "S": "items" + }, + { + "S": "attributes" + }, + { + "S": "throughput" + } + ] + } + } + } + }, + { + "PutRequest": { + "Item": { + "ForumName": { + "S": "Amazon S3" + }, + "Subject": { + "S": "S3 Thread 1" + }, + "Message": { + "S": "S3 thread 1 message" + }, + "LastPostedBy": { + "S": "User A" + }, + "LastPostedDateTime": { + "S": "2015-09-29T19:58:22.514Z" + }, + "Views": { + "N": "0" + }, + "Replies": { + "N": "0" + }, + "Answered": { + "N": "0" + }, + "Tags": { + "L": [ + { + "S": "largeobjects" + }, + { + "S": "multipart upload" + } + ] + } + } + } + } + ] +} \ No newline at end of file diff --git a/serverless.yml b/serverless.yml index ba76452..25d22d7 100644 --- a/serverless.yml +++ b/serverless.yml @@ -11,7 +11,7 @@ # # Happy Coding! -service: post-api +service: forum-service # You can pin your service to only deploy with a specific Serverless version # Check out our docs for more details @@ -40,7 +40,9 @@ provider: # - "Ref" : "ServerlessDeploymentBucket" # - "/*" environment: - DYNAMODB_TABLE: posts + DYNAMODB_FORUM_TABLE: Forum + DYNAMODB_REPLY_TABLE: Reply + DYNAMODB_THREAD_TABLE: Thread iamRoleStatements: - Effect: Allow Action: @@ -162,33 +164,77 @@ functions: resources: Resources: - PostsDynamoDbTable: + ForumDynamoDbTable: Type: 'AWS::DynamoDB::Table' DeletionPolicy: Retain Properties: AttributeDefinitions: - - AttributeName: "id" + AttributeName: "Name" AttributeType: "S" + KeySchema: + - + AttributeName: "Name" + KeyType: "HASH" + ProvisionedThroughput: + ReadCapacityUnits: 1 + WriteCapacityUnits: 1 + StreamSpecification: + StreamViewType: "NEW_AND_OLD_IMAGES" + TableName: ${DYNAMODB_FORUM_TABLE} + ThreadDynamoDbTable: + Type: 'AWS::DynamoDB::Table' + DeletionPolicy: Retain + DependsOn: ${DYNAMODB_FORUM_TABLE} + Properties: + AttributeDefinitions: - - AttributeName: "updatedAt" - AttributeType: "N" + AttributeName: "ForumName" + AttributeType: "S" - - AttributeName: "dummyHashKey" + AttributeName: "Subject" AttributeType: "S" KeySchema: - - AttributeName: "id" + AttributeName: "ForumName" KeyType: "HASH" + - + AttributeName: "Subject" + KeyType: "RANGE" + ProvisionedThroughput: + ReadCapacityUnits: 1 + WriteCapacityUnits: 1 + StreamSpecification: + StreamViewType: "NEW_AND_OLD_IMAGES" + TableName: ${self:provider.environment.DYNAMODB_THREAD_TABLE} + ReplyDynamoDbTable: + Type: 'AWS::DynamoDB::Table' + DeletionPolicy: Retain + DependsOn: ${DYNAMODB_THREAD_TABLE} + Properties: + AttributeDefinitions: + - + AttributeName: "Id" + AttributeType: "S" + - + AttributeName: "ReplyDateTime" + AttributeType: "S" + KeySchema: + - + AttributeName: "Id" + KeyType: "HASH" + - + AttributeName: "ReplyDateTime" + KeyType: "RANGE" GlobalSecondaryIndexes: - - IndexName: UpdatedAtIndex + IndexName: PostedBy-Message-Index KeySchema: - - AttributeName: "dummyHashKey" + AttributeName: "PostedBy" KeyType: HASH - - AttributeName: "updatedAt" + AttributeName: "Message" KeyType: RANGE Projection: ProjectionType: KEYS_ONLY @@ -200,4 +246,4 @@ resources: WriteCapacityUnits: 1 StreamSpecification: StreamViewType: "NEW_AND_OLD_IMAGES" - TableName: ${self:provider.environment.DYNAMODB_TABLE} \ No newline at end of file + TableName: ${self:provider.environment.DYNAMODB_REPLY_TABLE} \ No newline at end of file From cfac163b12842af597a7d6b2c720772c1f778441 Mon Sep 17 00:00:00 2001 From: jacksoncharles Date: Thu, 19 Oct 2017 22:04:42 +0100 Subject: [PATCH 02/78] fixed clourformation --- serverless.yml | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/serverless.yml b/serverless.yml index 25d22d7..02ebe37 100644 --- a/serverless.yml +++ b/serverless.yml @@ -181,11 +181,10 @@ resources: WriteCapacityUnits: 1 StreamSpecification: StreamViewType: "NEW_AND_OLD_IMAGES" - TableName: ${DYNAMODB_FORUM_TABLE} + TableName: Forum ThreadDynamoDbTable: Type: 'AWS::DynamoDB::Table' DeletionPolicy: Retain - DependsOn: ${DYNAMODB_FORUM_TABLE} Properties: AttributeDefinitions: - @@ -206,11 +205,10 @@ resources: WriteCapacityUnits: 1 StreamSpecification: StreamViewType: "NEW_AND_OLD_IMAGES" - TableName: ${self:provider.environment.DYNAMODB_THREAD_TABLE} + TableName: Thread ReplyDynamoDbTable: Type: 'AWS::DynamoDB::Table' DeletionPolicy: Retain - DependsOn: ${DYNAMODB_THREAD_TABLE} Properties: AttributeDefinitions: - @@ -219,6 +217,12 @@ resources: - AttributeName: "ReplyDateTime" AttributeType: "S" + - + AttributeName: "PostedBy" + AttributeType: "S" + - + AttributeName: "Message" + AttributeType: "S" KeySchema: - AttributeName: "Id" @@ -246,4 +250,4 @@ resources: WriteCapacityUnits: 1 StreamSpecification: StreamViewType: "NEW_AND_OLD_IMAGES" - TableName: ${self:provider.environment.DYNAMODB_REPLY_TABLE} \ No newline at end of file + TableName: Reply \ No newline at end of file From 866b399c9442dbbef5b2c2f0f050023e6969b4a0 Mon Sep 17 00:00:00 2001 From: jacksoncharles Date: Mon, 20 Nov 2017 23:01:21 +0000 Subject: [PATCH 03/78] findbyid --- api/{list.js => findbyid.js} | 52 +++++++++--------------------------- api/get.js | 4 +-- loadData.sh | 1 - sample_data/Reply.json | 8 +++--- serverless.yml | 8 +++--- 5 files changed, 22 insertions(+), 51 deletions(-) rename api/{list.js => findbyid.js} (56%) diff --git a/api/list.js b/api/findbyid.js similarity index 56% rename from api/list.js rename to api/findbyid.js index a7d153e..cfb9c20 100644 --- a/api/list.js +++ b/api/findbyid.js @@ -17,19 +17,15 @@ var params = { }; */ -const params = { - TableName: process.env.DYNAMODB_TABLE, - Limit: 5, - IndexName: 'UpdatedAtIndex', - KeyConditionExpression: 'dummyHashKey = :x', - ExpressionAttributeValues: { - ':x': 'OK' - }, - ProjectionExpression: "id, userId, parentId, correctAnswer, title, body, createdAt, updatedAt" -} - +module.exports.findbyid = (event, context, callback) => { -module.exports.list = (event, context, callback) => { + const params = { + TableName: process.env.DYNAMODB_REPLY_TABLE, + KeyConditionExpression: "Id = :searchstring", + ExpressionAttributeValues: { + ":searchstring" : event.pathParameters.id + } + } // Do we have any parameters? /* @@ -41,39 +37,16 @@ module.exports.list = (event, context, callback) => { // Do we have a page number? } */ + + dynamoDb.query(params, function(error, data) { // Handle potential errors if (error) { + console.log('=== the error is ==='); console.error(error); - callback(null, { - statusCode: error.statusCode || 501, - headers: { 'Content-Type': 'text/plain' }, - body: 'Couldn\'t fetch the posts.', - }); - - return; - } - // Create a response - const response = { - statusCode: 200, - body: JSON.stringify(data), - }; - - callback(null, response); - - }); - - // Fetch all posts from the database - /* - dynamoDb.scan(params, (error, result) => { - - // Handle potential errors - if (error) { - - console.error(error); callback(null, { statusCode: error.statusCode || 501, headers: { 'Content-Type': 'text/plain' }, @@ -86,10 +59,9 @@ module.exports.list = (event, context, callback) => { // Create a response const response = { statusCode: 200, - body: JSON.stringify(result.Items), + body: JSON.stringify(data), }; callback(null, response); }); - */ }; \ No newline at end of file diff --git a/api/get.js b/api/get.js index f5806d8..71b9717 100644 --- a/api/get.js +++ b/api/get.js @@ -9,8 +9,8 @@ module.exports.get = (event, context, callback) => { const params = { TableName: process.env.DYNAMODB_TABLE, Key: { - id: event.pathParameters.id, - }, + id: event.pathParameters.id + } }; // fetch post from the database diff --git a/loadData.sh b/loadData.sh index 49eb75a..a16e8e0 100755 --- a/loadData.sh +++ b/loadData.sh @@ -1,5 +1,4 @@ #!/usr/bin/env bash - aws dynamodb batch-write-item --request-items file://sample_data/Forum.json aws dynamodb batch-write-item --request-items file://sample_data/Thread.json aws dynamodb batch-write-item --request-items file://sample_data/Reply.json \ No newline at end of file diff --git a/sample_data/Reply.json b/sample_data/Reply.json index 7c70b0d..97511e7 100644 --- a/sample_data/Reply.json +++ b/sample_data/Reply.json @@ -4,7 +4,7 @@ "PutRequest": { "Item": { "Id": { - "S": "Amazon DynamoDB#DynamoDB Thread 1" + "S": "1" }, "ReplyDateTime": { "S": "2015-09-15T19:58:22.947Z" @@ -22,7 +22,7 @@ "PutRequest": { "Item": { "Id": { - "S": "Amazon DynamoDB#DynamoDB Thread 1" + "S": "1" }, "ReplyDateTime": { "S": "2015-09-22T19:58:22.947Z" @@ -40,7 +40,7 @@ "PutRequest": { "Item": { "Id": { - "S": "Amazon DynamoDB#DynamoDB Thread 2" + "S": "2" }, "ReplyDateTime": { "S": "2015-09-29T19:58:22.947Z" @@ -58,7 +58,7 @@ "PutRequest": { "Item": { "Id": { - "S": "Amazon DynamoDB#DynamoDB Thread 2" + "S": "2" }, "ReplyDateTime": { "S": "2015-10-05T19:58:22.947Z" diff --git a/serverless.yml b/serverless.yml index 02ebe37..6f3fd9e 100644 --- a/serverless.yml +++ b/serverless.yml @@ -67,13 +67,13 @@ functions: method: post cors: true - list: - handler: api/list.list + findbyid: + handler: api/findbyid.findbyid memorySize: 128 - description: List posts + description: Retrieve a paginated list of replies by the value of Id events: - http: - path: posts + path: replies/{id} method: get cors: true From 8964aad52088b6a36a5edf745b9f05f9739658d9 Mon Sep 17 00:00:00 2001 From: jacksoncharles Date: Tue, 21 Nov 2017 21:12:55 +0000 Subject: [PATCH 04/78] pagination --- api/findbyid.js | 23 ++++++++++++++++++++++- 1 file changed, 22 insertions(+), 1 deletion(-) diff --git a/api/findbyid.js b/api/findbyid.js index cfb9c20..a0d101e 100644 --- a/api/findbyid.js +++ b/api/findbyid.js @@ -19,7 +19,7 @@ var params = { module.exports.findbyid = (event, context, callback) => { - const params = { + let params = { TableName: process.env.DYNAMODB_REPLY_TABLE, KeyConditionExpression: "Id = :searchstring", ExpressionAttributeValues: { @@ -27,6 +27,25 @@ module.exports.findbyid = (event, context, callback) => { } } + + + if ( event.queryStringParameters ) { + + if ( event.queryStringParameters.hasOwnProperty('limit') ) { + + params['Limit'] = event.queryStringParameters.limit; + } + + // Pagination + if ( event.queryStringParameters.hasOwnProperty('id') && event.queryStringParameters.hasOwnProperty('replydatetime') ) { + + params['ExclusiveStartKey'] = { + Id: event.queryStringParameters.id, + ReplyDateTime: event.queryStringParameters.replydatetime + } + } + } + // Do we have any parameters? /* if( event.queryStringParameters !== null && typeof event.queryStringParameters === 'object' ) { @@ -41,6 +60,8 @@ module.exports.findbyid = (event, context, callback) => { dynamoDb.query(params, function(error, data) { + console.log( '=== data ===', data ); + // Handle potential errors if (error) { From 59a3e79fbd575b68a4139e1e4824247f2f3b5be0 Mon Sep 17 00:00:00 2001 From: jacksoncharles Date: Tue, 21 Nov 2017 21:14:19 +0000 Subject: [PATCH 05/78] pagination --- api/findbyid.js | 2 -- 1 file changed, 2 deletions(-) diff --git a/api/findbyid.js b/api/findbyid.js index a0d101e..25763b8 100644 --- a/api/findbyid.js +++ b/api/findbyid.js @@ -27,8 +27,6 @@ module.exports.findbyid = (event, context, callback) => { } } - - if ( event.queryStringParameters ) { if ( event.queryStringParameters.hasOwnProperty('limit') ) { From 1ccb6e77be68f8a292bf78bc3d64f6fddd3c80a3 Mon Sep 17 00:00:00 2001 From: jacksoncharles Date: Fri, 24 Nov 2017 16:17:50 +0000 Subject: [PATCH 06/78] refactor --- api/create.js | 24 +++++++- api/get.js | 25 +++++--- api/{findbyid.js => replies/list.js} | 90 ++++++++++++++++------------ serverless.yml | 6 +- 4 files changed, 95 insertions(+), 50 deletions(-) rename api/{findbyid.js => replies/list.js} (52%) diff --git a/api/create.js b/api/create.js index 913c7af..eaa16f7 100644 --- a/api/create.js +++ b/api/create.js @@ -2,12 +2,24 @@ const uuid = require('uuid'); const AWS = require('aws-sdk'); - AWS.config.setPromisesDependency(require('bluebird')); - const dynamoDb = new AWS.DynamoDB.DocumentClient(); -module.exports.create = (event, context, callback) => { +var Reply = function() { + + /** + * Mandatory parameters for the query. The value of "Item" will be populated with + * use passed parameters + * + * @type Object + */ + var parameters = { + TableName: process.env.DYNAMODB_REPLY_TABLE, + Item: {} + } +} + +Reply.prototype.hydrate = function( event ) { const requestBody = JSON.parse(event.body); // User submitted data @@ -18,6 +30,12 @@ module.exports.create = (event, context, callback) => { const title = requestBody.title; const body = requestBody.body; + return this; +} + +module.exports.create = (event, context, callback) => { + + /** * Save the post to permanent storage * diff --git a/api/get.js b/api/get.js index 71b9717..8d31d31 100644 --- a/api/get.js +++ b/api/get.js @@ -1,20 +1,31 @@ 'use strict'; const AWS = require('aws-sdk'); // eslint-disable-line import/no-extraneous-dependencies - const dynamoDb = new AWS.DynamoDB.DocumentClient(); -module.exports.get = (event, context, callback) => { +var Reply = function() { + + /** + * Mandatory parameters for the query. + * + * @type Object + */ + var parameters = { - const params = { - TableName: process.env.DYNAMODB_TABLE, + TableName: process.env.DYNAMODB_REPLY_TABLE, Key: { - id: event.pathParameters.id + Id: event.pathParameters.id, + ReplyDateTime: event.pathParameters.replydatetime, } - }; + } +} + +module.exports.get = (event, context, callback) => { + + var Query = new Reply(); // fetch post from the database - dynamoDb.get(params, (error, result) => { + dynamoDb.get(Query.parameters, (error, result) => { // handle potential errors if (error) { diff --git a/api/findbyid.js b/api/replies/list.js similarity index 52% rename from api/findbyid.js rename to api/replies/list.js index 25763b8..90ea327 100644 --- a/api/findbyid.js +++ b/api/replies/list.js @@ -3,69 +3,85 @@ const AWS = require('aws-sdk'); // eslint-disable-line import/no-extraneous-dependencies const dynamoDb = new AWS.DynamoDB.DocumentClient(); -/* -var params = { - TableName: process.env.DYNAMODB_TABLE, - Limit: 10 -}; - -var params = { - - TableName: process.env.DYNAMODB_TABLE, - IndexName: "UpdatedAtIndex", - ScanIndexForward: true -}; -*/ - -module.exports.findbyid = (event, context, callback) => { - - let params = { +var Reply = function() { + + /** + * Mandatory parameters for the query. + * + * @type Object + */ + var parameters = { + Key: TableName: process.env.DYNAMODB_REPLY_TABLE, KeyConditionExpression: "Id = :searchstring", ExpressionAttributeValues: { ":searchstring" : event.pathParameters.id } } +} - if ( event.queryStringParameters ) { - if ( event.queryStringParameters.hasOwnProperty('limit') ) { +/** + * If there are any pagination parameters set them here by updating the value of + * parameters object property + * + * @return this + */ +Reply.prototype.setPagination = function( event ) { - params['Limit'] = event.queryStringParameters.limit; - } + if ( event.queryStringParameters ) { // Pagination if ( event.queryStringParameters.hasOwnProperty('id') && event.queryStringParameters.hasOwnProperty('replydatetime') ) { - params['ExclusiveStartKey'] = { + this.parameters['ExclusiveStartKey'] = { Id: event.queryStringParameters.id, ReplyDateTime: event.queryStringParameters.replydatetime } } } - - // Do we have any parameters? - /* - if( event.queryStringParameters !== null && typeof event.queryStringParameters === 'object' ) { - // Do we have a limit clause? - if( event.queryStringParameters.hasOwnProperty('limit') ) params.Limit = event.queryStringParameters.limit; + return this; +} + +/** + * Override the default value of "Limit" with any value passed by the query string. + * + * @return void + */ +Reply.prototype.setLimit = function( event ) { - // Do we have a page number? + if ( event.queryStringParameters ) { + + if ( event.queryStringParameters.hasOwnProperty('limit') ) { + + params['Limit'] = event.queryStringParameters.limit; + } } - */ - - dynamoDb.query(params, function(error, data) { + return this; +} + +/** + * Code starts to execute here using previously defined functions chained together. + * + * @param Object event AWS Lambda uses this parameter to pass in event data to the handler. + * @param Object context AWS Lambda uses this parameter to provide your handler the runtime information of the Lambda function that is executing. + * @param Function callback Optional parameter used to pass a callback + * + * @return JSON JSON encoded response. + */ +module.exports.list = (event, context, callback) => { - console.log( '=== data ===', data ); + var Query = new Reply() + .setPagination() + .setLimit(); + + dynamoDb.query(Query.parameters, function(error, data) { // Handle potential errors if (error) { - console.log('=== the error is ==='); - console.error(error); - callback(null, { statusCode: error.statusCode || 501, headers: { 'Content-Type': 'text/plain' }, @@ -82,5 +98,5 @@ module.exports.findbyid = (event, context, callback) => { }; callback(null, response); - }); + }); }; \ No newline at end of file diff --git a/serverless.yml b/serverless.yml index 6f3fd9e..c878eb6 100644 --- a/serverless.yml +++ b/serverless.yml @@ -21,7 +21,7 @@ provider: name: aws runtime: nodejs6.10 # you can overwrite defaults here - stage: dev + stage: prod region: eu-west-1 # you can add statements to the Lambda function's IAM Role here @@ -67,8 +67,8 @@ functions: method: post cors: true - findbyid: - handler: api/findbyid.findbyid + list: + handler: api/list.list memorySize: 128 description: Retrieve a paginated list of replies by the value of Id events: From 9526da045695c7ce67a0fa1c9fa2865eb2f9768c Mon Sep 17 00:00:00 2001 From: jacksoncharles Date: Wed, 29 Nov 2017 00:06:40 +0000 Subject: [PATCH 07/78] working replies list with two tables --- api/{ => replies}/create.js | 0 api/{ => replies}/delete.js | 0 api/{ => replies}/get.js | 0 api/replies/list.js | 209 +++++++++++++++++++++--------------- api/{ => replies}/update.js | 0 loadData.sh | 2 - sample_data/Forum.json | 23 ---- sample_data/Reply.json | 70 ++++++------ sample_data/Thread.json | 145 ++++++++++--------------- serverless.yml | 80 +++++++------- 10 files changed, 256 insertions(+), 273 deletions(-) rename api/{ => replies}/create.js (100%) rename api/{ => replies}/delete.js (100%) rename api/{ => replies}/get.js (100%) rename api/{ => replies}/update.js (100%) delete mode 100644 sample_data/Forum.json diff --git a/api/create.js b/api/replies/create.js similarity index 100% rename from api/create.js rename to api/replies/create.js diff --git a/api/delete.js b/api/replies/delete.js similarity index 100% rename from api/delete.js rename to api/replies/delete.js diff --git a/api/get.js b/api/replies/get.js similarity index 100% rename from api/get.js rename to api/replies/get.js diff --git a/api/replies/list.js b/api/replies/list.js index 90ea327..46ae133 100644 --- a/api/replies/list.js +++ b/api/replies/list.js @@ -3,100 +3,133 @@ const AWS = require('aws-sdk'); // eslint-disable-line import/no-extraneous-dependencies const dynamoDb = new AWS.DynamoDB.DocumentClient(); -var Reply = function() { - - /** - * Mandatory parameters for the query. - * - * @type Object - */ - var parameters = { - Key: - TableName: process.env.DYNAMODB_REPLY_TABLE, - KeyConditionExpression: "Id = :searchstring", - ExpressionAttributeValues: { - ":searchstring" : event.pathParameters.id - } - } -} - - /** - * If there are any pagination parameters set them here by updating the value of - * parameters object property + * Handler for the lambda function. * - * @return this - */ -Reply.prototype.setPagination = function( event ) { - - if ( event.queryStringParameters ) { - - // Pagination - if ( event.queryStringParameters.hasOwnProperty('id') && event.queryStringParameters.hasOwnProperty('replydatetime') ) { - - this.parameters['ExclusiveStartKey'] = { - Id: event.queryStringParameters.id, - ReplyDateTime: event.queryStringParameters.replydatetime - } - } - } - - return this; -} - -/** - * Override the default value of "Limit" with any value passed by the query string. - * - * @return void - */ -Reply.prototype.setLimit = function( event ) { - - if ( event.queryStringParameters ) { - - if ( event.queryStringParameters.hasOwnProperty('limit') ) { - - params['Limit'] = event.queryStringParameters.limit; - } - } - - return this; -} - -/** - * Code starts to execute here using previously defined functions chained together. - * - * @param Object event AWS Lambda uses this parameter to pass in event data to the handler. - * @param Object context AWS Lambda uses this parameter to provide your handler the runtime information of the Lambda function that is executing. - * @param Function callback Optional parameter used to pass a callback + * @param {Object} event - AWS Lambda uses this parameter to pass in event data to the handler. + * @param {Object} context - AWS Lambda uses this parameter to provide your handler the runtime information of the Lambda function that is executing. + * @param {Function} callback - Optional parameter used to pass a callback * * @return JSON JSON encoded response. */ module.exports.list = (event, context, callback) => { - var Query = new Reply() - .setPagination() - .setLimit(); - - dynamoDb.query(Query.parameters, function(error, data) { + /** + * Reply object used to build a query captured in the property "parameters". + * + * @param {Object} event - AWS Lambda uses this parameter to pass in event data to the handler. + * + * @constructor + */ + var Reply = function( event ) { + + /** + * Capture the event object passed as a parameter; + * + * @type event + */ + this.event = event; + + /** + * Used to hold the dynamodb query parameters built using values + * within property this.event + * + * @todo : Change TableName to value of process.env.DYNAMODB_REPLY_TABLE + * + * @type Object + */ + this.parameters = { + TableName: 'Reply' + } + } - // Handle potential errors - if (error) { - - callback(null, { - statusCode: error.statusCode || 501, - headers: { 'Content-Type': 'text/plain' }, - body: 'Couldn\'t fetch the posts.', - }); - - return; - } - - // Create a response - const response = { - statusCode: 200, - body: JSON.stringify(data), - }; - - callback(null, response); - }); + /** + * The primary key must be set as a query parameter. + * + * @return this + */ + Reply.prototype.setPrimaryKey = function() { + + this.parameters['KeyConditionExpression'] = "ThreadId = :searchstring"; + this.parameters['ExpressionAttributeValues'] = { + ":searchstring" : this.event.queryStringParameters.threadid + }; + + return this; + } + + /** + * If there are any pagination parameters set them here by updating the value of + * parameters object property + * + * @return this + */ + Reply.prototype.setPagination = function() { + + if ( this.event.queryStringParameters ) { + + // Pagination + if ( this.event.queryStringParameters.hasOwnProperty('threadid') && this.event.queryStringParameters.hasOwnProperty('datetime') ) { + + this.parameters['ExclusiveStartKey'] = { + ThreadId: this.event.queryStringParameters.threadid, + DateTime: this.event.queryStringParameters.datetime + } + } + } + + return this; + } + + /** + * Override the default value of "Limit" with any value passed by the query string. + * + * @return void + */ + Reply.prototype.setLimit = function() { + + if ( this.event.queryStringParameters ) { + + if ( this.event.queryStringParameters.hasOwnProperty('limit') ) { + + this.parameters['Limit'] = this.event.queryStringParameters.limit; + } + } + + return this; + } + + /** + * Instantiate an instance of Reply and build the parameters for the query. + * + * @type {Reply} + */ + var Query = new Reply( event ) + .setPrimaryKey() + .setPagination() + .setLimit(); + + dynamoDb.query( Query.parameters, function( error, data ) { + + // Handle potential errors + if (error) { + + console.log('=== error ===', error ); + callback(null, { + statusCode: error.statusCode || 501, + headers: { 'Content-Type': 'text/plain' }, + body: 'Couldn\'t fetch the posts.', + }); + + return; + } + + // Create a response + const response = { + statusCode: 200, + body: JSON.stringify(data), + }; + + callback(null, response); + }); }; \ No newline at end of file diff --git a/api/update.js b/api/replies/update.js similarity index 100% rename from api/update.js rename to api/replies/update.js diff --git a/loadData.sh b/loadData.sh index a16e8e0..53a77d2 100755 --- a/loadData.sh +++ b/loadData.sh @@ -1,4 +1,2 @@ #!/usr/bin/env bash -aws dynamodb batch-write-item --request-items file://sample_data/Forum.json -aws dynamodb batch-write-item --request-items file://sample_data/Thread.json aws dynamodb batch-write-item --request-items file://sample_data/Reply.json \ No newline at end of file diff --git a/sample_data/Forum.json b/sample_data/Forum.json deleted file mode 100644 index 273c665..0000000 --- a/sample_data/Forum.json +++ /dev/null @@ -1,23 +0,0 @@ -{ - "Forum": [ - { - "PutRequest": { - "Item": { - "Name": {"S":"Amazon DynamoDB"}, - "Category": {"S":"Amazon Web Services"}, - "Threads": {"N":"2"}, - "Messages": {"N":"4"}, - "Views": {"N":"1000"} - } - } - }, - { - "PutRequest": { - "Item": { - "Name": {"S":"Amazon S3"}, - "Category": {"S":"Amazon Web Services"} - } - } - } - ] -} diff --git a/sample_data/Reply.json b/sample_data/Reply.json index 97511e7..0cae30b 100644 --- a/sample_data/Reply.json +++ b/sample_data/Reply.json @@ -3,17 +3,23 @@ { "PutRequest": { "Item": { - "Id": { + "ThreadId": { "S": "1" }, - "ReplyDateTime": { - "S": "2015-09-15T19:58:22.947Z" + "UserId": { + "S": "1" }, "Message": { - "S": "DynamoDB Thread 1 Reply 1 text" + "S": "Mauris sed vestibulum nibh, non viverra lacus. Vestibulum non lacus massa. Vivamus sodales nibh vel cursus dignissim. Maecenas tempus, ligula id pulvinar semper, neque nunc suscipit ex, a scelerisque justo sapien sit amet magna. Praesent ut dictum purus, vitae sollicitudin sapien. Nunc pretium eros eu nulla hendrerit molestie. Duis nisl nibh, auctor at finibus ac, aliquam nec tellus. Suspendisse sed fringilla ex, id ultrices diam. Vestibulum nulla orci, rhoncus ac efficitur semper, pretium sed nisl. Ut in lectus in augue malesuada laoreet eu sed ante. Etiam ac mattis dolor, sed varius tellus." + }, + "DateTime": { + "S": "2015-09-15T19:58:22.947Z" + }, + "UserName": { + "S": "primordial" }, - "PostedBy": { - "S": "User A" + "UserAvatarLink": { + "S": "http://via.placeholder.com/350x150" } } } @@ -21,35 +27,23 @@ { "PutRequest": { "Item": { - "Id": { + "ThreadId": { "S": "1" }, - "ReplyDateTime": { - "S": "2015-09-22T19:58:22.947Z" + "UserId": { + "S": "2" }, "Message": { - "S": "DynamoDB Thread 1 Reply 2 text" + "S": "Donec volutpat, arcu vel egestas luctus, urna turpis bibendum lorem, quis vulputate leo justo vitae leo. Donec hendrerit dapibus turpis, et posuere mi rhoncus eu. Donec sit amet enim bibendum, convallis metus in, interdum sapien. Duis malesuada eu leo quis dictum. Duis auctor neque eros, sit amet venenatis lacus rhoncus vitae. Nam hendrerit nibh eget neque gravida, sit amet lobortis sem aliquet. Maecenas vitae nisi aliquam, venenatis nunc a, dictum lorem." }, - "PostedBy": { - "S": "User B" - } - } - } - }, - { - "PutRequest": { - "Item": { - "Id": { - "S": "2" + "DateTime": { + "S": "2015-08-15T19:58:22.947Z" }, - "ReplyDateTime": { - "S": "2015-09-29T19:58:22.947Z" - }, - "Message": { - "S": "DynamoDB Thread 2 Reply 1 text" + "UserName": { + "S": "primordial" }, - "PostedBy": { - "S": "User A" + "UserAvatarLink": { + "S": "http://via.placeholder.com/350x150" } } } @@ -57,17 +51,23 @@ { "PutRequest": { "Item": { - "Id": { - "S": "2" + "ThreadId": { + "S": "1" }, - "ReplyDateTime": { - "S": "2015-10-05T19:58:22.947Z" + "UserId": { + "S": "1" }, "Message": { - "S": "DynamoDB Thread 2 Reply 2 text" + "S": "In viverra justo diam, eget imperdiet libero vestibulum quis. In malesuada leo quis semper dignissim. Nulla auctor ullamcorper turpis, vitae blandit felis aliquet non. Praesent vel volutpat felis. Curabitur euismod nibh quam, pulvinar facilisis est pulvinar vitae. In est metus, commodo non commodo quis, facilisis a leo. Integer et semper lorem. Phasellus at cursus turpis. Quisque lacinia tincidunt enim id porta. Ut tempor augue at placerat imperdiet. Nullam non nisi dapibus, volutpat sem vel, dictum felis. Proin pharetra nisi id diam vulputate, non facilisis lacus commodo." + }, + "DateTime": { + "S": "2015-07-15T19:58:22.947Z" + }, + "UserName": { + "S": "primordial" }, - "PostedBy": { - "S": "User A" + "UserAvatarLink": { + "S": "http://via.placeholder.com/350x150" } } } diff --git a/sample_data/Thread.json b/sample_data/Thread.json index 64d2edd..3503662 100644 --- a/sample_data/Thread.json +++ b/sample_data/Thread.json @@ -3,42 +3,26 @@ { "PutRequest": { "Item": { - "ForumName": { - "S": "Amazon DynamoDB" + "Id": { + "S": "1" }, - "Subject": { - "S": "DynamoDB Thread 1" + "UserId": { + "S": "1" + }, + "Title": { + "S": "Neque porro quisquam est qui dolorem ipsum quia dolor sit amet, consectetur, adipisci velit..." }, "Message": { - "S": "DynamoDB thread 1 message" + "S": "Mauris sed vestibulum nibh, non viverra lacus. Vestibulum non lacus massa. Vivamus sodales nibh vel cursus dignissim. Maecenas tempus, ligula id pulvinar semper, neque nunc suscipit ex, a scelerisque justo sapien sit amet magna. Praesent ut dictum purus, vitae sollicitudin sapien. Nunc pretium eros eu nulla hendrerit molestie. Duis nisl nibh, auctor at finibus ac, aliquam nec tellus. Suspendisse sed fringilla ex, id ultrices diam. Vestibulum nulla orci, rhoncus ac efficitur semper, pretium sed nisl. Ut in lectus in augue malesuada laoreet eu sed ante. Etiam ac mattis dolor, sed varius tellus." }, - "LastPostedBy": { - "S": "User A" + "DateTime": { + "S": "2015-09-15T19:58:22.947Z" + }, + "UserName": { + "S": "primordial" }, - "LastPostedDateTime": { - "S": "2015-09-22T19:58:22.514Z" - }, - "Views": { - "N": "0" - }, - "Replies": { - "N": "0" - }, - "Answered": { - "N": "0" - }, - "Tags": { - "L": [ - { - "S": "index" - }, - { - "S": "primarykey" - }, - { - "S": "table" - } - ] + "UserAvatarLink": { + "S": "http://via.placeholder.com/75x100" } } } @@ -46,42 +30,26 @@ { "PutRequest": { "Item": { - "ForumName": { - "S": "Amazon DynamoDB" + "Id": { + "S": "2" }, - "Subject": { - "S": "DynamoDB Thread 2" + "UserId": { + "S": "2" + }, + "Title": { + "S": "Quisque eget arcu gravida, accumsan lectus sed, iaculis justo. " }, "Message": { - "S": "DynamoDB thread 2 message" + "S": "Donec volutpat, arcu vel egestas luctus, urna turpis bibendum lorem, quis vulputate leo justo vitae leo. Donec hendrerit dapibus turpis, et posuere mi rhoncus eu. Donec sit amet enim bibendum, convallis metus in, interdum sapien. Duis malesuada eu leo quis dictum. Duis auctor neque eros, sit amet venenatis lacus rhoncus vitae. Nam hendrerit nibh eget neque gravida, sit amet lobortis sem aliquet. Maecenas vitae nisi aliquam, venenatis nunc a, dictum lorem." }, - "LastPostedBy": { - "S": "User A" + "DateTime": { + "S": "2015-09-15T19:58:22.947Z" }, - "LastPostedDateTime": { - "S": "2015-09-15T19:58:22.514Z" - }, - "Views": { - "N": "0" - }, - "Replies": { - "N": "0" - }, - "Answered": { - "N": "0" - }, - "Tags": { - "L": [ - { - "S": "items" - }, - { - "S": "attributes" - }, - { - "S": "throughput" - } - ] + "UserName": { + "S": "primordial" + }, + "UserAvatarLink": { + "S": "http://via.placeholder.com/75x100" } } } @@ -89,39 +57,44 @@ { "PutRequest": { "Item": { - "ForumName": { - "S": "Amazon S3" + "Id": { + "S": "3" + }, + "UserId": { + "S": "3" }, - "Subject": { - "S": "S3 Thread 1" + "Title": { + "S": "Sed consectetur lacus at arcu iaculis, nec semper augue ultricies." }, "Message": { - "S": "S3 thread 1 message" + "S": "In viverra justo diam, eget imperdiet libero vestibulum quis. In malesuada leo quis semper dignissim. Nulla auctor ullamcorper turpis, vitae blandit felis aliquet non. Praesent vel volutpat felis. Curabitur euismod nibh quam, pulvinar facilisis est pulvinar vitae. In est metus, commodo non commodo quis, facilisis a leo. Integer et semper lorem. Phasellus at cursus turpis. Quisque lacinia tincidunt enim id porta. Ut tempor augue at placerat imperdiet. Nullam non nisi dapibus, volutpat sem vel, dictum felis. Proin pharetra nisi id diam vulputate, non facilisis lacus commodo." }, - "LastPostedBy": { - "S": "User A" + "DateTime": { + "S": "2015-09-15T19:58:22.947Z" }, - "LastPostedDateTime": { - "S": "2015-09-29T19:58:22.514Z" + "UserName": { + "S": "primordial" }, - "Views": { - "N": "0" + "UserAvatarLink": { + "S": "http://via.placeholder.com/75x100" + } + } + } + }, + { + "PutRequest": { + "Item": { + "Id": { + "S": "2" }, - "Replies": { - "N": "0" + "ReplyDateTime": { + "S": "2015-10-05T19:58:22.947Z" }, - "Answered": { - "N": "0" + "Message": { + "S": "DynamoDB Thread 2 Reply 2 text" }, - "Tags": { - "L": [ - { - "S": "largeobjects" - }, - { - "S": "multipart upload" - } - ] + "PostedBy": { + "S": "User A" } } } diff --git a/serverless.yml b/serverless.yml index c878eb6..983387d 100644 --- a/serverless.yml +++ b/serverless.yml @@ -40,7 +40,6 @@ provider: # - "Ref" : "ServerlessDeploymentBucket" # - "/*" environment: - DYNAMODB_FORUM_TABLE: Forum DYNAMODB_REPLY_TABLE: Reply DYNAMODB_THREAD_TABLE: Thread iamRoleStatements: @@ -58,46 +57,46 @@ provider: functions: create: - handler: api/create.create + handler: api/replies/create.create memorySize: 128 description: Submit post information. events: - http: - path: posts + path: replies method: post cors: true list: - handler: api/list.list + handler: api/replies/list.list memorySize: 128 description: Retrieve a paginated list of replies by the value of Id events: - http: - path: replies/{id} + path: replies method: get cors: true update: - handler: api/update.update + handler: api/replies/update.update events: - http: - path: posts/{id} + path: replies/{id} method: put cors: true get: - handler: api/get.get + handler: api/replies/get.get events: - http: - path: posts/{id} + path: replies/{id} method: get cors: true delete: - handler: api/delete.delete + handler: api/replies/delete.delete events: - http: - path: posts/{id} + path: replies/{id} method: delete cors: true @@ -164,42 +163,45 @@ functions: resources: Resources: - ForumDynamoDbTable: + ThreadDynamoDbTable: Type: 'AWS::DynamoDB::Table' DeletionPolicy: Retain Properties: AttributeDefinitions: - - AttributeName: "Name" + AttributeName: "Id" AttributeType: "S" - KeySchema: - - AttributeName: "Name" - KeyType: "HASH" - ProvisionedThroughput: - ReadCapacityUnits: 1 - WriteCapacityUnits: 1 - StreamSpecification: - StreamViewType: "NEW_AND_OLD_IMAGES" - TableName: Forum - ThreadDynamoDbTable: - Type: 'AWS::DynamoDB::Table' - DeletionPolicy: Retain - Properties: - AttributeDefinitions: + AttributeName: "UserId" + AttributeType: "S" - - AttributeName: "ForumName" + AttributeName: "Message" AttributeType: "S" - - AttributeName: "Subject" + AttributeName: "DateTime" AttributeType: "S" KeySchema: - - AttributeName: "ForumName" + AttributeName: "Id" KeyType: "HASH" - - AttributeName: "Subject" + AttributeName: "DateTime" KeyType: "RANGE" + GlobalSecondaryIndexes: + - + IndexName: Thread-UserId-Message-Index + KeySchema: + - + AttributeName: "UserId" + KeyType: HASH + - + AttributeName: "Message" + KeyType: RANGE + Projection: + ProjectionType: KEYS_ONLY + ProvisionedThroughput: + ReadCapacityUnits: 1 + WriteCapacityUnits: 1 ProvisionedThroughput: ReadCapacityUnits: 1 WriteCapacityUnits: 1 @@ -212,30 +214,30 @@ resources: Properties: AttributeDefinitions: - - AttributeName: "Id" + AttributeName: "ThreadId" AttributeType: "S" - - AttributeName: "ReplyDateTime" + AttributeName: "UserId" AttributeType: "S" - - AttributeName: "PostedBy" + AttributeName: "Message" AttributeType: "S" - - AttributeName: "Message" + AttributeName: "DateTime" AttributeType: "S" KeySchema: - - AttributeName: "Id" + AttributeName: "ThreadId" KeyType: "HASH" - - AttributeName: "ReplyDateTime" + AttributeName: "DateTime" KeyType: "RANGE" GlobalSecondaryIndexes: - - IndexName: PostedBy-Message-Index + IndexName: Reply-UserId-Message-Index KeySchema: - - AttributeName: "PostedBy" + AttributeName: "UserId" KeyType: HASH - AttributeName: "Message" From 1ae1c70200d90f1018bcd313889d814eb29e804c Mon Sep 17 00:00:00 2001 From: jacksoncharles Date: Wed, 29 Nov 2017 00:14:58 +0000 Subject: [PATCH 08/78] readme --- README.md | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/README.md b/README.md index 834bfb5..1144e76 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -# Restful Lambda API - post-service +# Implementation of the AWS DynamoDb example forum using the Serverless framework - aws-dynamodb-serverless-example-forum An [AWS Lambda](https://aws.amazon.com/lambda/) solution written using the [Serverless Toolkit](http://serverless.com) with a [DynamoDB](https://aws.amazon.com/dynamodb) backend. @@ -20,12 +20,12 @@ Then, from the project root folder simply enter the following command to provisi NAME | URL | VERB | DESCRIPTION ---- | --- | ---- | ----------- -CREATE | /posts | POST | Create a new item in permanent storage -LIST | /posts | GET | Retrieve a paginated listing from permanent storage -GET | /posts/:id | GET | Retrieve a individual item using the id passed as a route parameter -UPDATE | /posts/:id | PUT | Update details of a post by providing a full array of model data -EDIT | /posts/:id | PATCH | Update details of a post by providing only those elements you wish to update -DELETE | /posts/:id | DELETE | Remove an item from permanent storage +CREATE | /replies | POST | Create a new item in permanent storage +LIST | /replies | GET | Retrieve a paginated listing from permanent storage +GET | /replies/:id | GET | Retrieve a individual item using the id passed as a route parameter +UPDATE | /replies/:id | PUT | Update details of a post by providing a full array of model data +EDIT | /replies/:id | PATCH | Update details of a post by providing only those elements you wish to update +DELETE | /replies/:id | DELETE | Remove an item from permanent storage ## Issues -Please report any bugs on the [Issue Tracker](https://github.com/jacksoncharles/post-service/issues). \ No newline at end of file +Please report any bugs on the [Issue Tracker](https://github.com/jacksoncharles/aws-dynamodb-serverless-example-forum/issues). \ No newline at end of file From 77cb427bcc9739cd7c8797d08c08f58d72c56b49 Mon Sep 17 00:00:00 2001 From: jacksoncharles Date: Wed, 29 Nov 2017 00:16:24 +0000 Subject: [PATCH 09/78] readme --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 1144e76..b495329 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -# Implementation of the AWS DynamoDb example forum using the Serverless framework - aws-dynamodb-serverless-example-forum +# AWS DynamoDb Example Forum An [AWS Lambda](https://aws.amazon.com/lambda/) solution written using the [Serverless Toolkit](http://serverless.com) with a [DynamoDB](https://aws.amazon.com/dynamodb) backend. @@ -28,4 +28,4 @@ EDIT | /replies/:id | PATCH | Update details of a post by providing only those e DELETE | /replies/:id | DELETE | Remove an item from permanent storage ## Issues -Please report any bugs on the [Issue Tracker](https://github.com/jacksoncharles/aws-dynamodb-serverless-example-forum/issues). \ No newline at end of file +Please report any bugs on the [Issue Tracker](https://github.com/jacksoncharles/dynamodb-example-forum/issues). \ No newline at end of file From 7e7ce78f09bc27f63e5a4f3ec23789df1c2efa69 Mon Sep 17 00:00:00 2001 From: jacksoncharles Date: Thu, 30 Nov 2017 17:03:36 +0000 Subject: [PATCH 10/78] practice --- api/replies/create.js | 180 ++++++++++++++++++++++-------------------- api/replies/list.js | 54 +++++++++---- serverless.yml | 33 ++++++-- 3 files changed, 158 insertions(+), 109 deletions(-) diff --git a/api/replies/create.js b/api/replies/create.js index eaa16f7..5e1c153 100644 --- a/api/replies/create.js +++ b/api/replies/create.js @@ -5,108 +5,116 @@ const AWS = require('aws-sdk'); AWS.config.setPromisesDependency(require('bluebird')); const dynamoDb = new AWS.DynamoDB.DocumentClient(); -var Reply = function() { - - /** - * Mandatory parameters for the query. The value of "Item" will be populated with - * use passed parameters - * - * @type Object - */ - var parameters = { - TableName: process.env.DYNAMODB_REPLY_TABLE, - Item: {} - } -} - -Reply.prototype.hydrate = function( event ) { - - const requestBody = JSON.parse(event.body); // User submitted data - - // Grab the individual elements of the post. - const userId = requestBody.userId; - const parentId = requestBody.parentId; - const correctAnswer = requestBody.correctAnswer; - const title = requestBody.title; - const body = requestBody.body; - - return this; -} - +/** + * Handler for the lambda function. + * + * @param {Object} event - AWS Lambda uses this parameter to pass in event data to the handler. + * @param {Object} context - AWS Lambda uses this parameter to provide your handler the runtime information of the Lambda function that is executing. + * @param {Function} callback - Optional parameter used to pass a callback + * + * @return JSON JSON encoded response. + */ module.exports.create = (event, context, callback) => { + var Reply = function( event ) { + + /** + * Capture the event object passed as a parameter; + * + * @type event + */ + this.event = event; + + /** + * Used to build the object being saved to permanent storage. The value of "Item" will + * be populated with user passed parameters + * + * @todo : Change TableName to value of process.env.DYNAMODB_REPLY_TABLE + * + * @type Object + */ + this.parameters = { + TableName: 'Reply' + Item: {} + } + } /** - * Save the post to permanent storage + * Populates the "item" object prior to saving * - * @param {object} post [New post] - * @return {array} [The newly created post] + * @return {this} */ - const submitPost = post => { - - console.log('Submitting post'); + Reply.prototype.hydrate = function() { + + const requestBody = JSON.parse( this.event.body ); // User submitted data + const timestamp = new Date().getTime(); - const postDetail = { - TableName: process.env.DYNAMODB_TABLE, - Item: post, + this.parameters.Item = { + Id: uuid.v1(), + ThreadId: data.threadid, + UserId: data.userid, + Message: data.message, + UserName: data.username, + DateTime: timestamp }; - return dynamoDb.put(postDetail).promise() - .then(res => post); - }; + return this; + } /** - * Format the post ready for saving to permanent storage + * Validates the data passed in the event object * - * @param {Array} data [User submitted data] - * @return {Object} [Individual post formatted as an object] + * @return {this} */ - const formatPost = (data) => { - - const timestamp = new Date().getTime(); - return { - id: uuid.v1(), - userId: data.userId, - parentId: data.parentId, - correctAnswer: data.correctAnswer, - title: data.title, - body: data.body, - createdAt: timestamp, - updatedAt: timestamp, - dummyHashKey: 'OK' - }; - }; - - // Validate the submitted data - if ( - typeof title !== 'string' || - typeof body !== 'string' || - typeof userId !== 'number' - ) { - console.error('Validation Failed'); - callback(new Error('Couldn\'t submit post because of validation errors.')); - return; + Reply.prototype.validates = function() { + + if ( + typeof title !== 'string' || + typeof body !== 'string' || + typeof userId !== 'number' + ) { + console.error('Validation Failed'); + callback(new Error('Couldn\'t submit post because of validation errors.')); + return; + } } - - // Submit the post and respond accordingly - submitPost(formatPost(requestBody)) - .then(res => { - callback(null, { - statusCode: 200, - body: JSON.stringify({ - message: `Sucessfully submitted post`, - postId: res.id - }) - }); - }) - .catch(err => { - console.log(err); + + // Instantiate an instance of Reply and build the Item. + var Item = new Reply( event ); + + if ( Item.validates() == false ) { + callback(null, { - statusCode: 500, + statusCode: 422, body: JSON.stringify({ - message: `Unable to submit post` + message: Item.errors }) }) - }); - + } + else { + + // Save to permanent storage + return dynamoDb.put( Item.parameters ).promise() + .then( res => reply ) + .then( res => { + + callback(null, { + statusCode: 200, + body: JSON.stringify({ + message: `Sucessfully submitted post`, + Id: res.id + }) + }); + }) + .catch(err => { + + console.log('=== we have an error saving to permanent storage', err ); + callback(null, { + statusCode: 500, + body: JSON.stringify({ + message: `Unable to submit post` + }) + }) + }); + } }; \ No newline at end of file diff --git a/api/replies/list.js b/api/replies/list.js index 46ae133..9c0db7e 100644 --- a/api/replies/list.js +++ b/api/replies/list.js @@ -15,13 +15,13 @@ const dynamoDb = new AWS.DynamoDB.DocumentClient(); module.exports.list = (event, context, callback) => { /** - * Reply object used to build a query captured in the property "parameters". + * Query object used to build this.parameters. * * @param {Object} event - AWS Lambda uses this parameter to pass in event data to the handler. * * @constructor */ - var Reply = function( event ) { + var Query = function( event ) { /** * Capture the event object passed as a parameter; @@ -44,16 +44,39 @@ module.exports.list = (event, context, callback) => { } /** - * The primary key must be set as a query parameter. + * If "threadid" has been passed as an index build the query parameters * * @return this */ - Reply.prototype.setPrimaryKey = function() { + Query.prototype.setThreadIndex = function() { - this.parameters['KeyConditionExpression'] = "ThreadId = :searchstring"; - this.parameters['ExpressionAttributeValues'] = { - ":searchstring" : this.event.queryStringParameters.threadid - }; + if ( this.event.queryStringParameters && this.event.queryStringParameters.threadid ) { + + this.parameters['IndexName'] = "ThreadIndex"; + this.parameters['KeyConditionExpression'] = "ThreadId = :searchstring"; + this.parameters['ExpressionAttributeValues'] = { + ":searchstring" : this.event.queryStringParameters.threadid + }; + } + + return this; + } + + /** + * If "userid" has been passed as an index build the query parameters + * + * @return this + */ + Query.prototype.setUserIndex = function() { + + if ( this.event.queryStringParameters && this.event.queryStringParameters.userid ) { + + this.parameters['IndexName'] = "UserIndex"; + this.parameters['KeyConditionExpression'] = "UserId = :searchstring"; + this.parameters['ExpressionAttributeValues'] = { + ":searchstring" : this.event.queryStringParameters.userid + }; + } return this; } @@ -64,7 +87,7 @@ module.exports.list = (event, context, callback) => { * * @return this */ - Reply.prototype.setPagination = function() { + Query.prototype.setPagination = function() { if ( this.event.queryStringParameters ) { @@ -86,7 +109,7 @@ module.exports.list = (event, context, callback) => { * * @return void */ - Reply.prototype.setLimit = function() { + Query.prototype.setLimit = function() { if ( this.event.queryStringParameters ) { @@ -100,16 +123,17 @@ module.exports.list = (event, context, callback) => { } /** - * Instantiate an instance of Reply and build the parameters for the query. + * Instantiate an instance of Query and build the parameters. * - * @type {Reply} + * @type {Query} */ - var Query = new Reply( event ) - .setPrimaryKey() + var Reply = new Query( event ) + .setThreadIndex() + .setUserIndex() .setPagination() .setLimit(); - dynamoDb.query( Query.parameters, function( error, data ) { + dynamoDb.query( Reply.parameters, function( error, data ) { // Handle potential errors if (error) { diff --git a/serverless.yml b/serverless.yml index 983387d..3d1ef07 100644 --- a/serverless.yml +++ b/serverless.yml @@ -56,7 +56,7 @@ provider: functions: - create: + replyCreate: handler: api/replies/create.create memorySize: 128 description: Submit post information. @@ -66,7 +66,7 @@ functions: method: post cors: true - list: + replyList: handler: api/replies/list.list memorySize: 128 description: Retrieve a paginated list of replies by the value of Id @@ -76,7 +76,7 @@ functions: method: get cors: true - update: + replyUpdate: handler: api/replies/update.update events: - http: @@ -84,7 +84,7 @@ functions: method: put cors: true - get: + replyGet: handler: api/replies/get.get events: - http: @@ -92,7 +92,7 @@ functions: method: get cors: true - delete: + replyDelete: handler: api/replies/delete.delete events: - http: @@ -213,6 +213,9 @@ resources: DeletionPolicy: Retain Properties: AttributeDefinitions: + - + AttributeName: "Id" + AttributeType: "S" - AttributeName: "ThreadId" AttributeType: "S" @@ -227,20 +230,34 @@ resources: AttributeType: "S" KeySchema: - - AttributeName: "ThreadId" + AttributeName: "Id" KeyType: "HASH" - AttributeName: "DateTime" KeyType: "RANGE" GlobalSecondaryIndexes: - - IndexName: Reply-UserId-Message-Index + IndexName: Thread-Index + KeySchema: + - + AttributeName: "ThreadId" + KeyType: HASH + - + AttributeName: "DateTime" + KeyType: RANGE + Projection: + ProjectionType: KEYS_ONLY + ProvisionedThroughput: + ReadCapacityUnits: 1 + WriteCapacityUnits: 1 + - + IndexName: User-Index KeySchema: - AttributeName: "UserId" KeyType: HASH - - AttributeName: "Message" + AttributeName: "DateTime" KeyType: RANGE Projection: ProjectionType: KEYS_ONLY From 32cc3f00c8a9a7edd100c63f1823642b06fc4531 Mon Sep 17 00:00:00 2001 From: jacksoncharles Date: Thu, 30 Nov 2017 23:29:52 +0000 Subject: [PATCH 11/78] replyList functionality and renaming of methods plus dynamodb global secondary indexes --- api/replies/create.js | 35 ++++++---- api/replies/delete.js | 2 +- api/replies/get.js | 2 +- api/replies/{list.js => replyList.js} | 4 +- api/replies/update.js | 2 +- sample_data/Reply.json | 98 +++++++++++++++++++++++---- serverless.yml | 27 ++++---- 7 files changed, 124 insertions(+), 46 deletions(-) rename api/replies/{list.js => replyList.js} (96%) diff --git a/api/replies/create.js b/api/replies/create.js index 5e1c153..ba3dc7e 100644 --- a/api/replies/create.js +++ b/api/replies/create.js @@ -14,7 +14,7 @@ const dynamoDb = new AWS.DynamoDB.DocumentClient(); * * @return JSON JSON encoded response. */ -module.exports.create = (event, context, callback) => { +module.exports.replyCreate = (event, context, callback) => { var Reply = function( event ) { @@ -37,6 +37,13 @@ module.exports.create = (event, context, callback) => { TableName: 'Reply' Item: {} } + + /** + * Used to hold any validation messages. + * + * @type {Array} + */ + this.errors = []; } /** @@ -46,15 +53,14 @@ module.exports.create = (event, context, callback) => { */ Reply.prototype.hydrate = function() { - const requestBody = JSON.parse( this.event.body ); // User submitted data const timestamp = new Date().getTime(); this.parameters.Item = { Id: uuid.v1(), - ThreadId: data.threadid, - UserId: data.userid, - Message: data.message, - UserName: data.username, + ThreadId: this.event.queryStringParameters.threadid, + UserId: this.event.queryStringParameters.userid, + Message: this.event.queryStringParameters.message, + UserName: this.event.queryStringParameters.username, DateTime: timestamp }; @@ -68,15 +74,14 @@ module.exports.create = (event, context, callback) => { */ Reply.prototype.validates = function() { - if ( - typeof title !== 'string' || - typeof body !== 'string' || - typeof userId !== 'number' - ) { - console.error('Validation Failed'); - callback(new Error('Couldn\'t submit post because of validation errors.')); - return; - } + this.errors = []; + + if ( this.event.queryStringParameters.hasOwnProperty('threadid') == false && typeof this.event.queryStringParameters.threadid !== 'string' ) this.errors.push('threadid missing or invalid'); + if ( this.event.queryStringParameters.hasOwnProperty('userid') == false && typeof this.event.queryStringParameters.userid !== 'string' ) this.errors.push('userid missing or invalid'); + if ( this.event.queryStringParameters.hasOwnProperty('message') == false && typeof this.event.queryStringParameters.message !== 'string' ) this.errors.push('message missing or invalid'); + if ( this.event.queryStringParameters.hasOwnProperty('username') == false && typeof this.event.queryStringParameters.username !== 'string' ) this.errors.push('username missing or invalid'); + + return this.errors.length ? 0 : 1; } // Instantiate an instance of Reply and build the Item. diff --git a/api/replies/delete.js b/api/replies/delete.js index d3eb6a8..3b9d503 100644 --- a/api/replies/delete.js +++ b/api/replies/delete.js @@ -4,7 +4,7 @@ const AWS = require('aws-sdk'); // eslint-disable-line import/no-extraneous-depe const dynamoDb = new AWS.DynamoDB.DocumentClient(); -module.exports.delete = (event, context, callback) => { +module.exports.replyDelete = (event, context, callback) => { const params = { TableName: process.env.DYNAMODB_TABLE, diff --git a/api/replies/get.js b/api/replies/get.js index 8d31d31..9528700 100644 --- a/api/replies/get.js +++ b/api/replies/get.js @@ -20,7 +20,7 @@ var Reply = function() { } } -module.exports.get = (event, context, callback) => { +module.exports.replyGet = (event, context, callback) => { var Query = new Reply(); diff --git a/api/replies/list.js b/api/replies/replyList.js similarity index 96% rename from api/replies/list.js rename to api/replies/replyList.js index 9c0db7e..e3f86f4 100644 --- a/api/replies/list.js +++ b/api/replies/replyList.js @@ -12,7 +12,7 @@ const dynamoDb = new AWS.DynamoDB.DocumentClient(); * * @return JSON JSON encoded response. */ -module.exports.list = (event, context, callback) => { +module.exports.replyList = (event, context, callback) => { /** * Query object used to build this.parameters. @@ -50,6 +50,7 @@ module.exports.list = (event, context, callback) => { */ Query.prototype.setThreadIndex = function() { + console.info('inside setThreadIndex updated'); if ( this.event.queryStringParameters && this.event.queryStringParameters.threadid ) { this.parameters['IndexName'] = "ThreadIndex"; @@ -59,6 +60,7 @@ module.exports.list = (event, context, callback) => { }; } + console.log('=== this.parameters ===', this.parameters ); return this; } diff --git a/api/replies/update.js b/api/replies/update.js index 2cae50c..32e6054 100644 --- a/api/replies/update.js +++ b/api/replies/update.js @@ -4,7 +4,7 @@ const AWS = require('aws-sdk'); // eslint-disable-line import/no-extraneous-depe const dynamoDb = new AWS.DynamoDB.DocumentClient(); -module.exports.update = (event, context, callback) => { +module.exports.replyUpdate = (event, context, callback) => { const timestamp = new Date().getTime(); const data = JSON.parse(event.body); diff --git a/sample_data/Reply.json b/sample_data/Reply.json index 0cae30b..6a938eb 100644 --- a/sample_data/Reply.json +++ b/sample_data/Reply.json @@ -3,6 +3,9 @@ { "PutRequest": { "Item": { + "Id": { + "S": "1" + }, "ThreadId": { "S": "1" }, @@ -12,14 +15,11 @@ "Message": { "S": "Mauris sed vestibulum nibh, non viverra lacus. Vestibulum non lacus massa. Vivamus sodales nibh vel cursus dignissim. Maecenas tempus, ligula id pulvinar semper, neque nunc suscipit ex, a scelerisque justo sapien sit amet magna. Praesent ut dictum purus, vitae sollicitudin sapien. Nunc pretium eros eu nulla hendrerit molestie. Duis nisl nibh, auctor at finibus ac, aliquam nec tellus. Suspendisse sed fringilla ex, id ultrices diam. Vestibulum nulla orci, rhoncus ac efficitur semper, pretium sed nisl. Ut in lectus in augue malesuada laoreet eu sed ante. Etiam ac mattis dolor, sed varius tellus." }, - "DateTime": { + "ReplyDateTime": { "S": "2015-09-15T19:58:22.947Z" }, "UserName": { "S": "primordial" - }, - "UserAvatarLink": { - "S": "http://via.placeholder.com/350x150" } } } @@ -27,8 +27,11 @@ { "PutRequest": { "Item": { + "Id": { + "S": "2" + }, "ThreadId": { - "S": "1" + "S": "2" }, "UserId": { "S": "2" @@ -36,14 +39,35 @@ "Message": { "S": "Donec volutpat, arcu vel egestas luctus, urna turpis bibendum lorem, quis vulputate leo justo vitae leo. Donec hendrerit dapibus turpis, et posuere mi rhoncus eu. Donec sit amet enim bibendum, convallis metus in, interdum sapien. Duis malesuada eu leo quis dictum. Duis auctor neque eros, sit amet venenatis lacus rhoncus vitae. Nam hendrerit nibh eget neque gravida, sit amet lobortis sem aliquet. Maecenas vitae nisi aliquam, venenatis nunc a, dictum lorem." }, - "DateTime": { + "ReplyDateTime": { "S": "2015-08-15T19:58:22.947Z" }, "UserName": { "S": "primordial" + } + } + } + }, + { + "PutRequest": { + "Item": { + "Id": { + "S": "3" + }, + "ThreadId": { + "S": "3" + }, + "UserId": { + "S": "1" }, - "UserAvatarLink": { - "S": "http://via.placeholder.com/350x150" + "Message": { + "S": "In viverra justo diam, eget imperdiet libero vestibulum quis. In malesuada leo quis semper dignissim. Nulla auctor ullamcorper turpis, vitae blandit felis aliquet non. Praesent vel volutpat felis. Curabitur euismod nibh quam, pulvinar facilisis est pulvinar vitae. In est metus, commodo non commodo quis, facilisis a leo. Integer et semper lorem. Phasellus at cursus turpis. Quisque lacinia tincidunt enim id porta. Ut tempor augue at placerat imperdiet. Nullam non nisi dapibus, volutpat sem vel, dictum felis. Proin pharetra nisi id diam vulputate, non facilisis lacus commodo." + }, + "ReplyDateTime": { + "S": "2015-07-15T19:58:22.947Z" + }, + "UserName": { + "S": "primordial" } } } @@ -51,26 +75,74 @@ { "PutRequest": { "Item": { + "Id": { + "S": "4" + }, "ThreadId": { "S": "1" }, "UserId": { "S": "1" }, + "Message": { + "S": "Mauris sed vestibulum nibh, non viverra lacus. Vestibulum non lacus massa. Vivamus sodales nibh vel cursus dignissim. Maecenas tempus, ligula id pulvinar semper, neque nunc suscipit ex, a scelerisque justo sapien sit amet magna. Praesent ut dictum purus, vitae sollicitudin sapien. Nunc pretium eros eu nulla hendrerit molestie. Duis nisl nibh, auctor at finibus ac, aliquam nec tellus. Suspendisse sed fringilla ex, id ultrices diam. Vestibulum nulla orci, rhoncus ac efficitur semper, pretium sed nisl. Ut in lectus in augue malesuada laoreet eu sed ante. Etiam ac mattis dolor, sed varius tellus." + }, + "ReplyDateTime": { + "S": "2015-09-15T19:58:22.947Z" + }, + "UserName": { + "S": "primordial" + } + } + } + }, + { + "PutRequest": { + "Item": { + "Id": { + "S": "5" + }, + "ThreadId": { + "S": "2" + }, + "UserId": { + "S": "2" + }, + "Message": { + "S": "Donec volutpat, arcu vel egestas luctus, urna turpis bibendum lorem, quis vulputate leo justo vitae leo. Donec hendrerit dapibus turpis, et posuere mi rhoncus eu. Donec sit amet enim bibendum, convallis metus in, interdum sapien. Duis malesuada eu leo quis dictum. Duis auctor neque eros, sit amet venenatis lacus rhoncus vitae. Nam hendrerit nibh eget neque gravida, sit amet lobortis sem aliquet. Maecenas vitae nisi aliquam, venenatis nunc a, dictum lorem." + }, + "ReplyDateTime": { + "S": "2015-08-15T19:58:22.947Z" + }, + "UserName": { + "S": "primordial" + } + } + } + }, + { + "PutRequest": { + "Item": { + "Id": { + "S": "6" + }, + "ThreadId": { + "S": "3" + }, + "UserId": { + "S": "1" + }, "Message": { "S": "In viverra justo diam, eget imperdiet libero vestibulum quis. In malesuada leo quis semper dignissim. Nulla auctor ullamcorper turpis, vitae blandit felis aliquet non. Praesent vel volutpat felis. Curabitur euismod nibh quam, pulvinar facilisis est pulvinar vitae. In est metus, commodo non commodo quis, facilisis a leo. Integer et semper lorem. Phasellus at cursus turpis. Quisque lacinia tincidunt enim id porta. Ut tempor augue at placerat imperdiet. Nullam non nisi dapibus, volutpat sem vel, dictum felis. Proin pharetra nisi id diam vulputate, non facilisis lacus commodo." }, - "DateTime": { + "ReplyDateTime": { "S": "2015-07-15T19:58:22.947Z" }, "UserName": { "S": "primordial" - }, - "UserAvatarLink": { - "S": "http://via.placeholder.com/350x150" } } } - } + } ] } \ No newline at end of file diff --git a/serverless.yml b/serverless.yml index 3d1ef07..e4b797e 100644 --- a/serverless.yml +++ b/serverless.yml @@ -67,7 +67,7 @@ functions: cors: true replyList: - handler: api/replies/list.list + handler: api/replies/replyList.replyList memorySize: 128 description: Retrieve a paginated list of replies by the value of Id events: @@ -178,14 +178,14 @@ resources: AttributeName: "Message" AttributeType: "S" - - AttributeName: "DateTime" + AttributeName: "ThreadDateTime" AttributeType: "S" KeySchema: - AttributeName: "Id" KeyType: "HASH" - - AttributeName: "DateTime" + AttributeName: "ThreadDateTime" KeyType: "RANGE" GlobalSecondaryIndexes: - @@ -223,44 +223,43 @@ resources: AttributeName: "UserId" AttributeType: "S" - - AttributeName: "Message" - AttributeType: "S" - - - AttributeName: "DateTime" + AttributeName: "ReplyDateTime" AttributeType: "S" KeySchema: - AttributeName: "Id" KeyType: "HASH" - - AttributeName: "DateTime" + AttributeName: "ReplyDateTime" KeyType: "RANGE" GlobalSecondaryIndexes: - - IndexName: Thread-Index + IndexName: ThreadIndex KeySchema: - AttributeName: "ThreadId" KeyType: HASH - - AttributeName: "DateTime" + AttributeName: "ReplyDateTime" KeyType: RANGE Projection: - ProjectionType: KEYS_ONLY + ProjectionType: INCLUDE + NonKeyAttributes: "Message, UserName" ProvisionedThroughput: ReadCapacityUnits: 1 WriteCapacityUnits: 1 - - IndexName: User-Index + IndexName: UserIndex KeySchema: - AttributeName: "UserId" KeyType: HASH - - AttributeName: "DateTime" + AttributeName: "ReplyDateTime" KeyType: RANGE Projection: - ProjectionType: KEYS_ONLY + ProjectionType: INCLUDE + NonKeyAttributes: "Message, UserName" ProvisionedThroughput: ReadCapacityUnits: 1 WriteCapacityUnits: 1 From 5a15b31f26c781cd29f93af5b8a09b33a4b38dc3 Mon Sep 17 00:00:00 2001 From: jacksoncharles Date: Thu, 30 Nov 2017 23:46:12 +0000 Subject: [PATCH 12/78] inline docs --- api/replies/replyList.js | 12 +++++------- serverless.yml | 4 +--- 2 files changed, 6 insertions(+), 10 deletions(-) diff --git a/api/replies/replyList.js b/api/replies/replyList.js index e3f86f4..4959677 100644 --- a/api/replies/replyList.js +++ b/api/replies/replyList.js @@ -44,13 +44,12 @@ module.exports.replyList = (event, context, callback) => { } /** - * If "threadid" has been passed as an index build the query parameters + * If "threadid" has been passed as parameter this method will build the query. * * @return this */ Query.prototype.setThreadIndex = function() { - console.info('inside setThreadIndex updated'); if ( this.event.queryStringParameters && this.event.queryStringParameters.threadid ) { this.parameters['IndexName'] = "ThreadIndex"; @@ -60,13 +59,12 @@ module.exports.replyList = (event, context, callback) => { }; } - console.log('=== this.parameters ===', this.parameters ); return this; } /** - * If "userid" has been passed as an index build the query parameters - * + * If "userid" has been passed as parameter this method will build the query. + * * @return this */ Query.prototype.setUserIndex = function() { @@ -75,6 +73,7 @@ module.exports.replyList = (event, context, callback) => { this.parameters['IndexName'] = "UserIndex"; this.parameters['KeyConditionExpression'] = "UserId = :searchstring"; + this.parameters['ProjectionExpression'] = "Id, UserId, Message, ReplyDateTime"; this.parameters['ExpressionAttributeValues'] = { ":searchstring" : this.event.queryStringParameters.userid }; @@ -143,8 +142,7 @@ module.exports.replyList = (event, context, callback) => { console.log('=== error ===', error ); callback(null, { statusCode: error.statusCode || 501, - headers: { 'Content-Type': 'text/plain' }, - body: 'Couldn\'t fetch the posts.', + body: error.ValidationException || 'Couldn\'t fetch the posts.' }); return; diff --git a/serverless.yml b/serverless.yml index e4b797e..801619c 100644 --- a/serverless.yml +++ b/serverless.yml @@ -243,8 +243,7 @@ resources: AttributeName: "ReplyDateTime" KeyType: RANGE Projection: - ProjectionType: INCLUDE - NonKeyAttributes: "Message, UserName" + ProjectionType: ALL ProvisionedThroughput: ReadCapacityUnits: 1 WriteCapacityUnits: 1 @@ -259,7 +258,6 @@ resources: KeyType: RANGE Projection: ProjectionType: INCLUDE - NonKeyAttributes: "Message, UserName" ProvisionedThroughput: ReadCapacityUnits: 1 WriteCapacityUnits: 1 From 5447f416eb634093cc84b63b6478821185f75737 Mon Sep 17 00:00:00 2001 From: jacksoncharles Date: Fri, 1 Dec 2017 13:47:14 +0000 Subject: [PATCH 13/78] refactor --- api/replies/delete.js | 41 ---- api/replies/get.js | 52 ----- api/replies/{create.js => replyCreate.js} | 78 ++++++-- api/replies/replyDelete.js | 55 ++++++ api/replies/replyGet.js | 57 ++++++ api/replies/replyList.js | 116 ++++++++--- api/replies/replyUpdate.js | 225 ++++++++++++++++++++++ api/replies/update.js | 63 ------ package.json | 3 +- serverless.yml | 2 +- 10 files changed, 495 insertions(+), 197 deletions(-) delete mode 100644 api/replies/delete.js delete mode 100644 api/replies/get.js rename api/replies/{create.js => replyCreate.js} (63%) create mode 100644 api/replies/replyDelete.js create mode 100644 api/replies/replyGet.js create mode 100644 api/replies/replyUpdate.js delete mode 100644 api/replies/update.js diff --git a/api/replies/delete.js b/api/replies/delete.js deleted file mode 100644 index 3b9d503..0000000 --- a/api/replies/delete.js +++ /dev/null @@ -1,41 +0,0 @@ -'use strict'; - -const AWS = require('aws-sdk'); // eslint-disable-line import/no-extraneous-dependencies - -const dynamoDb = new AWS.DynamoDB.DocumentClient(); - -module.exports.replyDelete = (event, context, callback) => { - - const params = { - TableName: process.env.DYNAMODB_TABLE, - Key: { - id: event.pathParameters.id, - }, - }; - - // delete the post from the database - dynamoDb.delete(params, (error) => { - - // handle potential errors - if (error) { - - console.error('=== error ===', error); - - callback(null, { - statusCode: error.statusCode || 501, - headers: { 'Content-Type': 'text/plain' }, - body: 'Couldn\'t remove the post item.', - }); - return; - } - - // create a response - const response = { - statusCode: 200, - body: JSON.stringify({}), - }; - - callback(null, response); - - }); -}; \ No newline at end of file diff --git a/api/replies/get.js b/api/replies/get.js deleted file mode 100644 index 9528700..0000000 --- a/api/replies/get.js +++ /dev/null @@ -1,52 +0,0 @@ -'use strict'; - -const AWS = require('aws-sdk'); // eslint-disable-line import/no-extraneous-dependencies -const dynamoDb = new AWS.DynamoDB.DocumentClient(); - -var Reply = function() { - - /** - * Mandatory parameters for the query. - * - * @type Object - */ - var parameters = { - - TableName: process.env.DYNAMODB_REPLY_TABLE, - Key: { - Id: event.pathParameters.id, - ReplyDateTime: event.pathParameters.replydatetime, - } - } -} - -module.exports.replyGet = (event, context, callback) => { - - var Query = new Reply(); - - // fetch post from the database - dynamoDb.get(Query.parameters, (error, result) => { - - // handle potential errors - if (error) { - - console.error('=== error ===', error); - - callback(null, { - statusCode: error.statusCode || 501, - headers: { 'Content-Type': 'text/plain' }, - body: 'Couldn\'t fetch the post item.', - }); - return; - } - - // create a response - const response = { - - statusCode: 200, - body: JSON.stringify(result.Item), - }; - - callback(null, response); - }); -}; \ No newline at end of file diff --git a/api/replies/create.js b/api/replies/replyCreate.js similarity index 63% rename from api/replies/create.js rename to api/replies/replyCreate.js index ba3dc7e..e65bdfc 100644 --- a/api/replies/create.js +++ b/api/replies/replyCreate.js @@ -2,6 +2,8 @@ const uuid = require('uuid'); const AWS = require('aws-sdk'); +var schema = require('validate'); + AWS.config.setPromisesDependency(require('bluebird')); const dynamoDb = new AWS.DynamoDB.DocumentClient(); @@ -16,7 +18,7 @@ const dynamoDb = new AWS.DynamoDB.DocumentClient(); */ module.exports.replyCreate = (event, context, callback) => { - var Reply = function( event ) { + var Query = function( event ) { /** * Capture the event object passed as a parameter; @@ -38,6 +40,36 @@ module.exports.replyCreate = (event, context, callback) => { Item: {} } + /** + * Holds the schema for validating the parameters passed with the request. Anything failing + * validation will be stored inside this.errors + * + * @type {Object} + */ + this.validator = schema({ + + ThreadId: { + type: 'string', + required: true, + message: 'threadid is required.' + }, + UserId: { + type: 'number', + required: true, + message: 'userid is required.' + }, + Message: { + type: 'string', + required: true, + message: 'message is required.' + }, + UserName: { + type: 'string', + required: true, + message: 'username is required.' + } + }); + /** * Used to hold any validation messages. * @@ -51,7 +83,7 @@ module.exports.replyCreate = (event, context, callback) => { * * @return {this} */ - Reply.prototype.hydrate = function() { + Query.prototype.hydrate = function() { const timestamp = new Date().getTime(); @@ -72,52 +104,66 @@ module.exports.replyCreate = (event, context, callback) => { * * @return {this} */ - Reply.prototype.validates = function() { + Query.prototype.validates = function() { this.errors = []; + /* if ( this.event.queryStringParameters.hasOwnProperty('threadid') == false && typeof this.event.queryStringParameters.threadid !== 'string' ) this.errors.push('threadid missing or invalid'); if ( this.event.queryStringParameters.hasOwnProperty('userid') == false && typeof this.event.queryStringParameters.userid !== 'string' ) this.errors.push('userid missing or invalid'); if ( this.event.queryStringParameters.hasOwnProperty('message') == false && typeof this.event.queryStringParameters.message !== 'string' ) this.errors.push('message missing or invalid'); if ( this.event.queryStringParameters.hasOwnProperty('username') == false && typeof this.event.queryStringParameters.username !== 'string' ) this.errors.push('username missing or invalid'); + */ + // Validate the query parameters + this.errors = this.validator.validate( this.event.queryStringParameters ); return this.errors.length ? 0 : 1; } - // Instantiate an instance of Reply and build the Item. - var Item = new Reply( event ); + /** + * Instantiate an instance of Query + * + * @type {Query} + */ + var Reply = new Query( event ); - if ( Item.validates() == false ) { + // Check to see if the parameters passed in the request validate. + if ( Reply.validates() == false ) { + // Handle validation errors callback(null, { statusCode: 422, body: JSON.stringify({ - message: Item.errors + message: Reply.errors }) }) } else { // Save to permanent storage - return dynamoDb.put( Item.parameters ).promise() + return dynamoDb.put( Reply.parameters ).promise() .then( res => reply ) .then( res => { - callback(null, { + // create a response + const response = { + statusCode: 200, - body: JSON.stringify({ - message: `Sucessfully submitted post`, - Id: res.id - }) - }); + body: JSON.stringify(res), + }; + + + callback(null, response); }) .catch(err => { - console.log('=== we have an error saving to permanent storage', err ); + console.log('=== error ===', error ); + + // Handle DynamoDb errors callback(null, { statusCode: 500, body: JSON.stringify({ - message: `Unable to submit post` + message: `Unable to submit reply` }) }) }); diff --git a/api/replies/replyDelete.js b/api/replies/replyDelete.js new file mode 100644 index 0000000..e5e47b1 --- /dev/null +++ b/api/replies/replyDelete.js @@ -0,0 +1,55 @@ +'use strict'; + +const AWS = require('aws-sdk'); // eslint-disable-line import/no-extraneous-dependencies +const dynamoDb = new AWS.DynamoDB.DocumentClient(); + +/** + * Handler for the lambda function. + * + * @param {Object} event - AWS Lambda uses this parameter to pass in event data to the handler. + * @param {Object} context - AWS Lambda uses this parameter to provide your handler the runtime information of the Lambda function that is executing. + * @param {Function} callback - Optional parameter used to pass a callback + * + * @return JSON JSON encoded response. + */ +module.exports.replyDelete = (event, context, callback) => { + + /** + * The parameters used by DynamoDb + * + * @type {Object} + */ + const parameters = { + TableName: 'Reply', + Key: { + id: event.pathParameters.id, + } + }; + + // Run the query to remove the item from permenent storage + dynamoDb.delete(parameters, (error) => { + + // Handle any potential DynamoDb errors + if (error) { + + console.error('=== error ===', error); + + callback(null, { + statusCode: error.statusCode || 501, + headers: { 'Content-Type': 'text/plain' }, + body: 'Couldn\'t remove the post item.', + }); + + } + else { + + // Create a successful response + const response = { + statusCode: 204, + body: JSON.stringify({}), + }; + + callback(null, response); + } + }); +}; \ No newline at end of file diff --git a/api/replies/replyGet.js b/api/replies/replyGet.js new file mode 100644 index 0000000..b4d6c65 --- /dev/null +++ b/api/replies/replyGet.js @@ -0,0 +1,57 @@ +'use strict'; + +const AWS = require('aws-sdk'); // eslint-disable-line import/no-extraneous-dependencies +const dynamoDb = new AWS.DynamoDB.DocumentClient(); + +/** + * Handler for the lambda function. + * + * @param {Object} event - AWS Lambda uses this parameter to pass in event data to the handler. + * @param {Object} context - AWS Lambda uses this parameter to provide your handler the runtime information of the Lambda function that is executing. + * @param {Function} callback - Optional parameter used to pass a callback + * + * @return JSON JSON encoded response. + */ +module.exports.replyGet = (event, context, callback) => { + + /** + * The parameters needed by DynamoDb + * + * @type {Object} + */ + const parameters = { + + TableName: 'Reply', + Key: { + Id: event.pathParameters.id + } + } + + // Run the query to retrieve the Item from permanent storage + dynamoDb.get(Query.parameters, (error, result) => { + + // Handle any potential DynamoDb errors + if (error) { + + console.error('=== error ===', error); + + callback(null, { + statusCode: error.statusCode || 501, + headers: { 'Content-Type': 'text/plain' }, + body: 'Couldn\'t fetch the post item.', + }); + + } + else { + + // create a response + const response = { + + statusCode: 200, + body: JSON.stringify(result.Item), + }; + + callback(null, response); + } + }); +}; \ No newline at end of file diff --git a/api/replies/replyList.js b/api/replies/replyList.js index 4959677..87d755a 100644 --- a/api/replies/replyList.js +++ b/api/replies/replyList.js @@ -41,13 +41,65 @@ module.exports.replyList = (event, context, callback) => { this.parameters = { TableName: 'Reply' } + + /** + * Holds the schema for validating the parameters passed with the request. Anything failing + * validation will be stored inside this.errors + * + * @type {Object} + */ + this.validator = schema({ + + ThreadId: { + type: 'string', + required: false, + message: 'threadid is not a string.' + }, + UserId: { + type: 'number', + required: false, + message: 'userid is not a number.' + } + }); + + /** + * Used to hold any validation errors. + * + * @type {Array} + */ + this.errors = []; + } - /** - * If "threadid" has been passed as parameter this method will build the query. - * - * @return this - */ + /** + * Validates the parameters passed + * + * @return {boolean} + */ + Query.prototype.validates = function() { + + if ( this.event.hasOwnProperty('queryStringParameters') == false ) { + + this.errors[] = { + code: 422, + message: 'No query string parameters passed, threadid or userid required' + }; + + } else { + + this.errors = this.validator.validate( this.event.queryStringParameters ); + } + + + + return this.errors.length ? 0 : 1; + } + + /** + * If "threadid" has been passed as parameter this method will build the query. + * + * @return this + */ Query.prototype.setThreadIndex = function() { if ( this.event.queryStringParameters && this.event.queryStringParameters.threadid ) { @@ -124,36 +176,54 @@ module.exports.replyList = (event, context, callback) => { } /** - * Instantiate an instance of Query and build the parameters. + * Instantiate an instance of Query * * @type {Query} */ - var Reply = new Query( event ) + var Reply = new Query( event ); + + // Check to see if the parameters passed in the request validate. + if ( Reply.validates() == false ) { + + // Handle validation errors + callback(null, { + statusCode: 422, + body: JSON.stringify({ + message: Reply.errors + }) + }) + } + else { + + Reply .setThreadIndex() .setUserIndex() .setPagination() .setLimit(); - dynamoDb.query( Reply.parameters, function( error, data ) { + dynamoDb.query( Reply.parameters, function( error, data ) { - // Handle potential errors - if (error) { + // Handle potential errors + if (error) { - console.log('=== error ===', error ); - callback(null, { - statusCode: error.statusCode || 501, - body: error.ValidationException || 'Couldn\'t fetch the posts.' - }); + console.log('=== error ===', error ); + callback(null, { + statusCode: error.statusCode || 501, + body: error.ValidationException || 'Couldn\'t fetch the posts.' + }); - return; - } + return; + } + else { - // Create a response - const response = { - statusCode: 200, - body: JSON.stringify(data), - }; + // Create a response + const response = { + statusCode: 200, + body: JSON.stringify(data), + }; - callback(null, response); + callback(null, response); + } + } }); }; \ No newline at end of file diff --git a/api/replies/replyUpdate.js b/api/replies/replyUpdate.js new file mode 100644 index 0000000..a8ae158 --- /dev/null +++ b/api/replies/replyUpdate.js @@ -0,0 +1,225 @@ +'use strict'; + +const AWS = require('aws-sdk'); // eslint-disable-line import/no-extraneous-dependencies +const dynamoDb = new AWS.DynamoDB.DocumentClient(); + +/** + * Handler for the lambda function. + * + * @param {Object} event - AWS Lambda uses this parameter to pass in event data to the handler. + * @param {Object} context - AWS Lambda uses this parameter to provide your handler the runtime information of the Lambda function that is executing. + * @param {Function} callback - Optional parameter used to pass a callback + * + * @return JSON JSON encoded response. + */ +module.exports.replyUpdate = (event, context, callback) => { + + var Reply = function( event ) { + + /** + * Capture the event object passed as a parameter; + * + * @type event + */ + this.event = event; + + /** + * Used to build the object being saved to permanent storage. The value of "Item" will + * be populated with user passed parameters + * + * @todo : Change TableName to value of process.env.DYNAMODB_REPLY_TABLE + * + * @type Object + */ + this.parameters = { + TableName: 'Reply' + Item: {} + } + + /** + * Holds the schema for validating the parameters passed with the request. Anything failing + * validation will be stored inside this.errors + * + * @type {Object} + */ + this.validator = schema({ + + ThreadId: { + type: 'string', + required: true, + message: 'threadid is required.' + }, + UserId: { + type: 'number', + required: true, + message: 'userid is required.' + }, + Message: { + type: 'string', + required: true, + message: 'message is required.' + }, + UserName: { + type: 'string', + required: true, + message: 'username is required.' + } + }); + + /** + * Used to hold any validation messages. + * + * @type {Array} + */ + this.errors = []; + + } + + /** + * Populates the "item" object prior to saving + * + * @return {this} + */ + Query.prototype.hydrate = function() { + + const timestamp = new Date().getTime(); + + this.parameters.Item = { + Id: uuid.v1(), + ThreadId: this.event.queryStringParameters.threadid, + UserId: this.event.queryStringParameters.userid, + Message: this.event.queryStringParameters.message, + UserName: this.event.queryStringParameters.username, + DateTime: timestamp + }; + + return this; + } + + /** + * Validates the data passed in the event object + * + * @return {this} + */ + Query.prototype.validates = function() { + + this.errors = []; + + /* + if ( this.event.queryStringParameters.hasOwnProperty('threadid') == false && typeof this.event.queryStringParameters.threadid !== 'string' ) this.errors.push('threadid missing or invalid'); + if ( this.event.queryStringParameters.hasOwnProperty('userid') == false && typeof this.event.queryStringParameters.userid !== 'string' ) this.errors.push('userid missing or invalid'); + if ( this.event.queryStringParameters.hasOwnProperty('message') == false && typeof this.event.queryStringParameters.message !== 'string' ) this.errors.push('message missing or invalid'); + if ( this.event.queryStringParameters.hasOwnProperty('username') == false && typeof this.event.queryStringParameters.username !== 'string' ) this.errors.push('username missing or invalid'); + */ + // Validate the query parameters + this.errors = this.validator.validate( this.event.queryStringParameters ); + + return this.errors.length ? 0 : 1; + } + + /** + * Instantiate an instance of Query + * + * @type {Query} + */ + var Reply = new Query( event ); + + // Check to see if the parameters passed in the request validate. + if ( Reply.validates() == false ) { + + // Handle validation errors + callback(null, { + statusCode: 422, + body: JSON.stringify({ + message: Reply.errors + }) + }) + } + else { + + // Save to permanent storage + return dynamoDb.put( Reply.parameters ).promise() + .then( res => reply ) + .then( res => { + + // create a response + const response = { + + statusCode: 200, + body: JSON.stringify(res), + }; + + + callback(null, response); + }) + .catch(err => { + + console.log('=== error ===', error ); + + // Handle DynamoDb errors + callback(null, { + statusCode: 500, + body: JSON.stringify({ + message: `Unable to submit reply` + }) + }) + }); + } + + + + const timestamp = new Date().getTime(); + const data = JSON.parse(event.body); + + console.log( '=== data ===', data ); + console.log( '=== event ===', event ); + + // validation + if (typeof data.title !== 'string' || typeof data.body !== 'string') { + + console.error('Validation Failed'); + callback(null, { + statusCode: 400, + headers: { 'Content-Type': 'text/plain' }, + body: 'Couldn\'t update the post item.', + }); + + return; + } + + const params = { + TableName: process.env.DYNAMODB_TABLE, + Key: { + id: event.pathParameters.id, + }, + ExpressionAttributeValues: { + ':title': data.title, + ':body': data.body, + ':updated_at': timestamp, + }, + UpdateExpression: 'SET title = :title, body = :body, updated_at = :updated_at', + ReturnValues: 'ALL_NEW', + }; + + // update the post in the database + dynamoDb.update(params, (error, result) => { + + // handle potential errors + if (error) { + console.error('=== error ===', error); + callback(null, { + statusCode: error.statusCode || 501, + headers: { 'Content-Type': 'text/plain' }, + body: 'Couldn\'t fetch the post item.', + }); + return; + } + + // create a response + const response = { + statusCode: 200, + body: JSON.stringify(result.Attributes), + }; + callback(null, response); + }); +}; \ No newline at end of file diff --git a/api/replies/update.js b/api/replies/update.js deleted file mode 100644 index 32e6054..0000000 --- a/api/replies/update.js +++ /dev/null @@ -1,63 +0,0 @@ -'use strict'; - -const AWS = require('aws-sdk'); // eslint-disable-line import/no-extraneous-dependencies - -const dynamoDb = new AWS.DynamoDB.DocumentClient(); - -module.exports.replyUpdate = (event, context, callback) => { - - const timestamp = new Date().getTime(); - const data = JSON.parse(event.body); - - console.log( '=== data ===', data ); - console.log( '=== event ===', event ); - - // validation - if (typeof data.title !== 'string' || typeof data.body !== 'string') { - - console.error('Validation Failed'); - callback(null, { - statusCode: 400, - headers: { 'Content-Type': 'text/plain' }, - body: 'Couldn\'t update the post item.', - }); - - return; - } - - const params = { - TableName: process.env.DYNAMODB_TABLE, - Key: { - id: event.pathParameters.id, - }, - ExpressionAttributeValues: { - ':title': data.title, - ':body': data.body, - ':updated_at': timestamp, - }, - UpdateExpression: 'SET title = :title, body = :body, updated_at = :updated_at', - ReturnValues: 'ALL_NEW', - }; - - // update the post in the database - dynamoDb.update(params, (error, result) => { - - // handle potential errors - if (error) { - console.error('=== error ===', error); - callback(null, { - statusCode: error.statusCode || 501, - headers: { 'Content-Type': 'text/plain' }, - body: 'Couldn\'t fetch the post item.', - }); - return; - } - - // create a response - const response = { - statusCode: 200, - body: JSON.stringify(result.Attributes), - }; - callback(null, response); - }); -}; \ No newline at end of file diff --git a/package.json b/package.json index be7a72f..48dfb1e 100644 --- a/package.json +++ b/package.json @@ -10,6 +10,7 @@ "license": "ISC", "dependencies": { "bluebird": "^3.5.1", - "uuid": "^3.1.0" + "uuid": "^3.1.0", + "validate": "^3.0.1" } } diff --git a/serverless.yml b/serverless.yml index 801619c..122f16f 100644 --- a/serverless.yml +++ b/serverless.yml @@ -57,7 +57,7 @@ provider: functions: replyCreate: - handler: api/replies/create.create + handler: api/replies/replyCreate.replyCreate memorySize: 128 description: Submit post information. events: From 24b6f11eb84b8dc42268706d61cd903893436d0f Mon Sep 17 00:00:00 2001 From: jacksoncharles Date: Fri, 1 Dec 2017 15:46:01 +0000 Subject: [PATCH 14/78] refactoring --- README.md | 67 ++++++++++--- api/replies/replyCreate.js | 3 +- api/replies/replyList.js | 8 +- api/replies/replyUpdate.js | 95 ++++-------------- api/threads/threadList.js | 196 +++++++++++++++++++++++++++++++++++++ sample_data/Reply.json | 133 +++++++++++++++++++++---- sample_data/Thread.json | 135 ++++++++++++++++++++----- serverless.yml | 86 +++++++++++++--- 8 files changed, 567 insertions(+), 156 deletions(-) create mode 100644 api/threads/threadList.js diff --git a/README.md b/README.md index b495329..e899f3c 100644 --- a/README.md +++ b/README.md @@ -1,31 +1,68 @@ -# AWS DynamoDb Example Forum +# Forum Microservice -An [AWS Lambda](https://aws.amazon.com/lambda/) solution written using the [Serverless Toolkit](http://serverless.com) with a [DynamoDB](https://aws.amazon.com/dynamodb) backend. +A possible starter-for-10 forum microservice. -The service includes pagination, key/value searches plus a collection of common needs including but not limited too, order/by and limit clauses. +Includes basic functionality such as pagination and global secondary indexes for retrieiving by user or thread. And is loosely inspired by the [AWS Example Forum](http://docs.aws.amazon.com/amazondynamodb/latest/developerguide/SampleData.CreateTables.html). + +Can be built and implemented as part of a distributed solution. + +## Technology Stack +1. [AWS Lambda](https://aws.amazon.com/lambda/) +2. [Serverless Framework](http://serverless.com) +3. [DynamoDB](https://aws.amazon.com/dynamodb) +4. [NodeJs](https://nodejs.org/) ## Installation & Deployment +Deploying the forum microservice will provision and create the following resources. -NOTE: To deploy from your desktop you must have an existing AWS account and command line access. +1. API Gateway entitled "forum-microservice" with 10 endpoints. +2. 10 * Lambda functions with associated Cloud Watch logs. +3. 2 * DynamoDB tables. -Firstly, ensure you have installed the [Serverless Toolkit](http://serverless.com). +To deploy from your desktop you must have an existing AWS account with command line access. Firstly, ensure you have installed the [Serverless Framework](http://serverless.com). +``` npm install serverless -g +``` Then, from the project root folder simply enter the following command to provision and deploy your sevice to AWS. +``` sls deploy +``` + +If you wish to load test data into your application you can run the loadData script. + +``` + ./loadData.sh +``` + +## Removal +To remove the solution from AWS at the command line + +``` + sls remove +``` + +NOTE: Will automatically remove any Lambda functions, Cloud Watch logs and API Gateway configurations. It will +not remove DynamoDb tables; They must be deleted manually. + +## Lambda Functions and EndPoints +Will create X Lambda functions accessible via [API Gateway](https://aws.amazon.com/api-gateway/) configured endpoints. -## EndPoints +NAME | LAMBDA | URL | VERB | DESCRIPTION +---- | ------ | --- | ---- | ----------- +CREATE | threadCreate | /threads | POST | Create a new item in permanent storage. +LIST | | threadList | /threads | GET | Retrieve a paginated listing from permanent storage. +GET | | threadGet | /threads/:id | GET | Retrieve a individual item using the ```threadid``` or ```userid``` passed in the query string. +UPDATE | threadUpdate| /threads/:id | PUT | Update details of a post by providing a full array of model data. +DELETE | threadDelete | /threads/:id | DELETE | Remove an item from permanent storage. +CREATE | replyCreate | /replies | POST | Create a new item in permanent storage. +LIST | replyList | /replies | GET | Retrieve a paginated listing from permanent storage. +GET | replyGet | /replies/:id | GET | Retrieve a individual item using the ```threadid``` or ```userid``` passed in the query string. +UPDATE | replyUpdate | /replies/:id | PUT | Update details of a post by providing a full array of model data. +DELETE | replyDelete | /replies/:id | DELETE | Remove an item from permanent storage. -NAME | URL | VERB | DESCRIPTION ----- | --- | ---- | ----------- -CREATE | /replies | POST | Create a new item in permanent storage -LIST | /replies | GET | Retrieve a paginated listing from permanent storage -GET | /replies/:id | GET | Retrieve a individual item using the id passed as a route parameter -UPDATE | /replies/:id | PUT | Update details of a post by providing a full array of model data -EDIT | /replies/:id | PATCH | Update details of a post by providing only those elements you wish to update -DELETE | /replies/:id | DELETE | Remove an item from permanent storage ## Issues -Please report any bugs on the [Issue Tracker](https://github.com/jacksoncharles/dynamodb-example-forum/issues). \ No newline at end of file +Please report any feedback on the [Issue Tracker](https://github.com/jacksoncharles/forum-microservice/issues). \ No newline at end of file diff --git a/api/replies/replyCreate.js b/api/replies/replyCreate.js index e65bdfc..5efcd2d 100644 --- a/api/replies/replyCreate.js +++ b/api/replies/replyCreate.js @@ -93,7 +93,8 @@ module.exports.replyCreate = (event, context, callback) => { UserId: this.event.queryStringParameters.userid, Message: this.event.queryStringParameters.message, UserName: this.event.queryStringParameters.username, - DateTime: timestamp + CreatedDateTime: timestamp, + UpdatedDateTime: timestamp }; return this; diff --git a/api/replies/replyList.js b/api/replies/replyList.js index 87d755a..13b39af 100644 --- a/api/replies/replyList.js +++ b/api/replies/replyList.js @@ -125,7 +125,7 @@ module.exports.replyList = (event, context, callback) => { this.parameters['IndexName'] = "UserIndex"; this.parameters['KeyConditionExpression'] = "UserId = :searchstring"; - this.parameters['ProjectionExpression'] = "Id, UserId, Message, ReplyDateTime"; + this.parameters['ProjectionExpression'] = "Id, UserId, Message, CreatedDateTime"; this.parameters['ExpressionAttributeValues'] = { ":searchstring" : this.event.queryStringParameters.userid }; @@ -145,11 +145,11 @@ module.exports.replyList = (event, context, callback) => { if ( this.event.queryStringParameters ) { // Pagination - if ( this.event.queryStringParameters.hasOwnProperty('threadid') && this.event.queryStringParameters.hasOwnProperty('datetime') ) { + if ( this.event.queryStringParameters.hasOwnProperty('threadid') && this.event.queryStringParameters.hasOwnProperty('CreatedDateTime') ) { this.parameters['ExclusiveStartKey'] = { ThreadId: this.event.queryStringParameters.threadid, - DateTime: this.event.queryStringParameters.datetime + DateTime: this.event.queryStringParameters.CreatedDateTime } } } @@ -209,7 +209,7 @@ module.exports.replyList = (event, context, callback) => { console.log('=== error ===', error ); callback(null, { statusCode: error.statusCode || 501, - body: error.ValidationException || 'Couldn\'t fetch the posts.' + body: error.ValidationException || 'Couldn\'t fetch the replies.' }); return; diff --git a/api/replies/replyUpdate.js b/api/replies/replyUpdate.js index a8ae158..89b1078 100644 --- a/api/replies/replyUpdate.js +++ b/api/replies/replyUpdate.js @@ -90,7 +90,7 @@ module.exports.replyUpdate = (event, context, callback) => { UserId: this.event.queryStringParameters.userid, Message: this.event.queryStringParameters.message, UserName: this.event.queryStringParameters.username, - DateTime: timestamp + UpdatedDateTime: timestamp }; return this; @@ -137,89 +137,28 @@ module.exports.replyUpdate = (event, context, callback) => { } else { - // Save to permanent storage - return dynamoDb.put( Reply.parameters ).promise() - .then( res => reply ) - .then( res => { + // Update the post in the database + dynamoDb.update( Reply.parameters, (error, result) => { + + // Handle any potential DynamoDb errors + if (error) { + + console.error('=== error ===', error); + + callback(null, { + statusCode: error.statusCode || 501, + headers: { 'Content-Type': 'text/plain' }, + body: 'Couldn\'t fetch the post item.', + }); + return; + } // create a response const response = { - statusCode: 200, - body: JSON.stringify(res), + body: JSON.stringify(result.Attributes), }; - - callback(null, response); - }) - .catch(err => { - - console.log('=== error ===', error ); - - // Handle DynamoDb errors - callback(null, { - statusCode: 500, - body: JSON.stringify({ - message: `Unable to submit reply` - }) - }) }); } - - - - const timestamp = new Date().getTime(); - const data = JSON.parse(event.body); - - console.log( '=== data ===', data ); - console.log( '=== event ===', event ); - - // validation - if (typeof data.title !== 'string' || typeof data.body !== 'string') { - - console.error('Validation Failed'); - callback(null, { - statusCode: 400, - headers: { 'Content-Type': 'text/plain' }, - body: 'Couldn\'t update the post item.', - }); - - return; - } - - const params = { - TableName: process.env.DYNAMODB_TABLE, - Key: { - id: event.pathParameters.id, - }, - ExpressionAttributeValues: { - ':title': data.title, - ':body': data.body, - ':updated_at': timestamp, - }, - UpdateExpression: 'SET title = :title, body = :body, updated_at = :updated_at', - ReturnValues: 'ALL_NEW', - }; - - // update the post in the database - dynamoDb.update(params, (error, result) => { - - // handle potential errors - if (error) { - console.error('=== error ===', error); - callback(null, { - statusCode: error.statusCode || 501, - headers: { 'Content-Type': 'text/plain' }, - body: 'Couldn\'t fetch the post item.', - }); - return; - } - - // create a response - const response = { - statusCode: 200, - body: JSON.stringify(result.Attributes), - }; - callback(null, response); - }); }; \ No newline at end of file diff --git a/api/threads/threadList.js b/api/threads/threadList.js new file mode 100644 index 0000000..8e84d5d --- /dev/null +++ b/api/threads/threadList.js @@ -0,0 +1,196 @@ +'use strict'; + +const AWS = require('aws-sdk'); // eslint-disable-line import/no-extraneous-dependencies +const dynamoDb = new AWS.DynamoDB.DocumentClient(); + +/** + * Handler for the lambda function. + * + * @param {Object} event - AWS Lambda uses this parameter to pass in event data to the handler. + * @param {Object} context - AWS Lambda uses this parameter to provide your handler the runtime information of the Lambda function that is executing. + * @param {Function} callback - Optional parameter used to pass a callback + * + * @return JSON JSON encoded response. + */ +module.exports.threadList = (event, context, callback) => { + + /** + * Query object used to build this.parameters. + * + * @param {Object} event - AWS Lambda uses this parameter to pass in event data to the handler. + * + * @constructor + */ + var Query = function( event ) { + + /** + * Capture the event object passed as a parameter; + * + * @type event + */ + this.event = event; + + /** + * Used to hold the dynamodb query parameters built using values + * within property this.event + * + * @todo : Change TableName to value of process.env.DYNAMODB_REPLY_TABLE + * + * @type Object + */ + this.parameters = { + TableName: 'Thread' + } + + /** + * Holds the schema for validating the parameters passed with the request. Anything failing + * validation will be stored inside this.errors + * + * @type {Object} + */ + this.validator = schema({ + + UserId: { + type: 'number', + required: false, + message: 'userid is not a number.' + } + }); + + /** + * Used to hold any validation errors. + * + * @type {Array} + */ + this.errors = []; + + } + + /** + * Validates the parameters passed + * + * @return {boolean} + */ + Query.prototype.validates = function() { + + this.errors = []; + + this.errors = this.validator.validate( this.event.queryStringParameters ); + + return this.errors.length ? 0 : 1; + } + + /** + * If "userid" has been passed as parameter this method will build the query. + * + * @return this + */ + Query.prototype.setUserIndex = function() { + + if ( this.event.queryStringParameters && this.event.queryStringParameters.userid ) { + + this.parameters['IndexName'] = "UserIndex"; + this.parameters['KeyConditionExpression'] = "UserId = :searchstring"; + this.parameters['ProjectionExpression'] = "Id, UserId, Message, CreatedDateTime"; + this.parameters['ExpressionAttributeValues'] = { + ":searchstring" : this.event.queryStringParameters.userid + }; + } + + return this; + } + + /** + * If there are any pagination parameters set them here by updating the value of + * parameters object property + * + * @return this + */ + Query.prototype.setPagination = function() { + + if ( this.event.queryStringParameters ) { + + // Pagination + if ( this.event.queryStringParameters.hasOwnProperty('id') && this.event.queryStringParameters.hasOwnProperty('threaddatetime') ) { + + this.parameters['ExclusiveStartKey'] = { + Id: this.event.queryStringParameters.id, + ThreadDateTime: this.event.queryStringParameters.threaddatetime + } + } + } + + return this; + } + + /** + * Override the default value of "Limit" with any value passed by the query string. + * + * @return void + */ + Query.prototype.setLimit = function() { + + if ( this.event.queryStringParameters ) { + + if ( this.event.queryStringParameters.hasOwnProperty('limit') ) { + + this.parameters['Limit'] = this.event.queryStringParameters.limit; + } + } + + return this; + } + + /** + * Instantiate an instance of Query + * + * @type {Query} + */ + var Reply = new Query( event ); + + // Check to see if the parameters passed in the request validate. + if ( Reply.validates() == false ) { + + // Handle validation errors + callback(null, { + statusCode: 422, + body: JSON.stringify({ + message: Reply.errors + }) + }) + } + else { + + Reply + .setThreadIndex() + .setUserIndex() + .setPagination() + .setLimit(); + + // Run the DynamoDb query. + dynamoDb.query( Reply.parameters, function( error, data ) { + + // Handle any potential DynamoDb errors + if (error) { + + console.log('=== error ===', error ); + callback(null, { + statusCode: error.statusCode || 501, + body: error.ValidationException || 'Couldn\'t fetch the threads.' + }); + + return; + } + else { + + // Create a response + const response = { + statusCode: 200, + body: JSON.stringify(data), + }; + + callback(null, response); + } + } + }); +}; \ No newline at end of file diff --git a/sample_data/Reply.json b/sample_data/Reply.json index 6a938eb..4dc3ac1 100644 --- a/sample_data/Reply.json +++ b/sample_data/Reply.json @@ -15,7 +15,10 @@ "Message": { "S": "Mauris sed vestibulum nibh, non viverra lacus. Vestibulum non lacus massa. Vivamus sodales nibh vel cursus dignissim. Maecenas tempus, ligula id pulvinar semper, neque nunc suscipit ex, a scelerisque justo sapien sit amet magna. Praesent ut dictum purus, vitae sollicitudin sapien. Nunc pretium eros eu nulla hendrerit molestie. Duis nisl nibh, auctor at finibus ac, aliquam nec tellus. Suspendisse sed fringilla ex, id ultrices diam. Vestibulum nulla orci, rhoncus ac efficitur semper, pretium sed nisl. Ut in lectus in augue malesuada laoreet eu sed ante. Etiam ac mattis dolor, sed varius tellus." }, - "ReplyDateTime": { + "CreatedDateTime": { + "S": "2015-09-15T19:58:22.947Z" + }, + "UpdatedDateTime": { "S": "2015-09-15T19:58:22.947Z" }, "UserName": { @@ -39,11 +42,14 @@ "Message": { "S": "Donec volutpat, arcu vel egestas luctus, urna turpis bibendum lorem, quis vulputate leo justo vitae leo. Donec hendrerit dapibus turpis, et posuere mi rhoncus eu. Donec sit amet enim bibendum, convallis metus in, interdum sapien. Duis malesuada eu leo quis dictum. Duis auctor neque eros, sit amet venenatis lacus rhoncus vitae. Nam hendrerit nibh eget neque gravida, sit amet lobortis sem aliquet. Maecenas vitae nisi aliquam, venenatis nunc a, dictum lorem." }, - "ReplyDateTime": { + "CreatedDateTime": { + "S": "2015-08-15T19:58:22.947Z" + }, + "UpdatedDateTime": { "S": "2015-08-15T19:58:22.947Z" }, "UserName": { - "S": "primordial" + "S": "bemed" } } } @@ -63,11 +69,14 @@ "Message": { "S": "In viverra justo diam, eget imperdiet libero vestibulum quis. In malesuada leo quis semper dignissim. Nulla auctor ullamcorper turpis, vitae blandit felis aliquet non. Praesent vel volutpat felis. Curabitur euismod nibh quam, pulvinar facilisis est pulvinar vitae. In est metus, commodo non commodo quis, facilisis a leo. Integer et semper lorem. Phasellus at cursus turpis. Quisque lacinia tincidunt enim id porta. Ut tempor augue at placerat imperdiet. Nullam non nisi dapibus, volutpat sem vel, dictum felis. Proin pharetra nisi id diam vulputate, non facilisis lacus commodo." }, - "ReplyDateTime": { + "CreatedDateTime": { + "S": "2015-07-15T19:58:22.947Z" + }, + "UpdatedDateTime": { "S": "2015-07-15T19:58:22.947Z" }, "UserName": { - "S": "primordial" + "S": "fatlouis" } } } @@ -82,16 +91,19 @@ "S": "1" }, "UserId": { - "S": "1" + "S": "4" }, "Message": { "S": "Mauris sed vestibulum nibh, non viverra lacus. Vestibulum non lacus massa. Vivamus sodales nibh vel cursus dignissim. Maecenas tempus, ligula id pulvinar semper, neque nunc suscipit ex, a scelerisque justo sapien sit amet magna. Praesent ut dictum purus, vitae sollicitudin sapien. Nunc pretium eros eu nulla hendrerit molestie. Duis nisl nibh, auctor at finibus ac, aliquam nec tellus. Suspendisse sed fringilla ex, id ultrices diam. Vestibulum nulla orci, rhoncus ac efficitur semper, pretium sed nisl. Ut in lectus in augue malesuada laoreet eu sed ante. Etiam ac mattis dolor, sed varius tellus." }, - "ReplyDateTime": { - "S": "2015-09-15T19:58:22.947Z" + "CreatedDateTime": { + "S": "2015-09-15T19:51:22.947Z" + }, + "UpdatedDateTime": { + "S": "2015-09-15T19:51:22.947Z" }, "UserName": { - "S": "primordial" + "S": "mickeymouse" } } } @@ -106,13 +118,16 @@ "S": "2" }, "UserId": { - "S": "2" + "S": "1" }, "Message": { "S": "Donec volutpat, arcu vel egestas luctus, urna turpis bibendum lorem, quis vulputate leo justo vitae leo. Donec hendrerit dapibus turpis, et posuere mi rhoncus eu. Donec sit amet enim bibendum, convallis metus in, interdum sapien. Duis malesuada eu leo quis dictum. Duis auctor neque eros, sit amet venenatis lacus rhoncus vitae. Nam hendrerit nibh eget neque gravida, sit amet lobortis sem aliquet. Maecenas vitae nisi aliquam, venenatis nunc a, dictum lorem." }, - "ReplyDateTime": { - "S": "2015-08-15T19:58:22.947Z" + "CreatedDateTime": { + "S": "2015-08-15T19:53:22.947Z" + }, + "UpdatedDateTime": { + "S": "2015-08-15T19:53:22.947Z" }, "UserName": { "S": "primordial" @@ -130,19 +145,103 @@ "S": "3" }, "UserId": { - "S": "1" + "S": "2" }, "Message": { "S": "In viverra justo diam, eget imperdiet libero vestibulum quis. In malesuada leo quis semper dignissim. Nulla auctor ullamcorper turpis, vitae blandit felis aliquet non. Praesent vel volutpat felis. Curabitur euismod nibh quam, pulvinar facilisis est pulvinar vitae. In est metus, commodo non commodo quis, facilisis a leo. Integer et semper lorem. Phasellus at cursus turpis. Quisque lacinia tincidunt enim id porta. Ut tempor augue at placerat imperdiet. Nullam non nisi dapibus, volutpat sem vel, dictum felis. Proin pharetra nisi id diam vulputate, non facilisis lacus commodo." }, - "ReplyDateTime": { - "S": "2015-07-15T19:58:22.947Z" + "CreatedDateTime": { + "S": "2015-07-15T19:52:22.947Z" + }, + "UpdatedDateTime": { + "S": "2015-07-15T19:52:22.947Z" }, "UserName": { - "S": "primordial" + "S": "bemed" + } + } + } + }, + { + "PutRequest": { + "Item": { + "Id": { + "S": "7" + }, + "ThreadId": { + "S": "1" + }, + "UserId": { + "S": "5" + }, + "Message": { + "S": "Morbi lobortis fringilla tortor, non pellentesque purus porta et. Suspendisse potenti. Fusce varius nunc nec metus egestas, tincidunt finibus mi rutrum. Maecenas porta magna eu felis bibendum placerat. Maecenas pharetra ut est quis eleifend. Suspendisse dapibus tincidunt vehicula. Morbi consectetur scelerisque lectus id dignissim. Donec scelerisque lectus libero, vitae porttitor massa rutrum vitae." + }, + "CreatedDateTime": { + "S": "2015-09-15T19:54:22.947Z" + }, + "UpdatedDateTime": { + "S": "2015-09-15T19:54:22.947Z" + }, + "UserName": { + "S": "queenie" + } + } + } + }, + { + "PutRequest": { + "Item": { + "Id": { + "S": "8" + }, + "ThreadId": { + "S": "2" + }, + "UserId": { + "S": "5" + }, + "Message": { + "S": "Donec pretium sem tellus. In maximus nisl in feugiat pharetra. Nunc gravida malesuada massa at feugiat. Integer nec semper est, ut rhoncus leo. Fusce finibus ante ac ultricies dignissim. Mauris vulputate et risus finibus laoreet. Suspendisse potenti. Cras tristique lorem vitae lacinia egestas. Mauris nec facilisis erat. Curabitur non porta dui." + }, + "CreatedDateTime": { + "S": "2015-08-15T19:55:22.947Z" + }, + "UpdatedDateTime": { + "S": "2015-08-15T19:55:22.947Z" + }, + "UserName": { + "S": "queenie" + } + } + } + }, + { + "PutRequest": { + "Item": { + "Id": { + "S": "9" + }, + "ThreadId": { + "S": "3" + }, + "UserId": { + "S": "6" + }, + "Message": { + "S": "Duis mollis urna metus, eu posuere neque venenatis id. Aenean et consectetur leo. Sed vestibulum, arcu a mattis tristique, purus ipsum efficitur dui, in bibendum tortor est non eros. Donec pretium metus at felis finibus pretium. Mauris lobortis luctus ex ut feugiat. Aliquam pretium lacinia euismod. Nulla purus turpis, eleifend malesuada sagittis in, tincidunt posuere quam. Praesent in massa vel leo porta maximus id eget arcu." + }, + "CreatedDateTime": { + "S": "2015-07-15T19:56:22.947Z" + }, + "UpdatedDateTime": { + "S": "2015-07-15T19:56:22.947Z" + }, + "UserName": { + "S": "kingkong" } } } - } + } ] } \ No newline at end of file diff --git a/sample_data/Thread.json b/sample_data/Thread.json index 3503662..f31bbcb 100644 --- a/sample_data/Thread.json +++ b/sample_data/Thread.json @@ -9,20 +9,23 @@ "UserId": { "S": "1" }, + "ForumId": { + "S": "WebConfection" + }, "Title": { "S": "Neque porro quisquam est qui dolorem ipsum quia dolor sit amet, consectetur, adipisci velit..." }, "Message": { "S": "Mauris sed vestibulum nibh, non viverra lacus. Vestibulum non lacus massa. Vivamus sodales nibh vel cursus dignissim. Maecenas tempus, ligula id pulvinar semper, neque nunc suscipit ex, a scelerisque justo sapien sit amet magna. Praesent ut dictum purus, vitae sollicitudin sapien. Nunc pretium eros eu nulla hendrerit molestie. Duis nisl nibh, auctor at finibus ac, aliquam nec tellus. Suspendisse sed fringilla ex, id ultrices diam. Vestibulum nulla orci, rhoncus ac efficitur semper, pretium sed nisl. Ut in lectus in augue malesuada laoreet eu sed ante. Etiam ac mattis dolor, sed varius tellus." }, - "DateTime": { - "S": "2015-09-15T19:58:22.947Z" + "CreatedDateTime": { + "S": "2015-01-15T19:58:22.947Z" + }, + "UpdatedDateTime": { + "S": "2015-01-15T19:58:22.947Z" }, "UserName": { "S": "primordial" - }, - "UserAvatarLink": { - "S": "http://via.placeholder.com/75x100" } } } @@ -36,20 +39,23 @@ "UserId": { "S": "2" }, + "ForumId": { + "S": "WebConfection" + }, "Title": { - "S": "Quisque eget arcu gravida, accumsan lectus sed, iaculis justo. " + "S": "Neque porro quisquam est qui dolorem ipsum quia dolor sit amet, consectetur, adipisci velit..." }, "Message": { - "S": "Donec volutpat, arcu vel egestas luctus, urna turpis bibendum lorem, quis vulputate leo justo vitae leo. Donec hendrerit dapibus turpis, et posuere mi rhoncus eu. Donec sit amet enim bibendum, convallis metus in, interdum sapien. Duis malesuada eu leo quis dictum. Duis auctor neque eros, sit amet venenatis lacus rhoncus vitae. Nam hendrerit nibh eget neque gravida, sit amet lobortis sem aliquet. Maecenas vitae nisi aliquam, venenatis nunc a, dictum lorem." + "S": "Mauris sed vestibulum nibh, non viverra lacus. Vestibulum non lacus massa. Vivamus sodales nibh vel cursus dignissim. Maecenas tempus, ligula id pulvinar semper, neque nunc suscipit ex, a scelerisque justo sapien sit amet magna. Praesent ut dictum purus, vitae sollicitudin sapien. Nunc pretium eros eu nulla hendrerit molestie. Duis nisl nibh, auctor at finibus ac, aliquam nec tellus. Suspendisse sed fringilla ex, id ultrices diam. Vestibulum nulla orci, rhoncus ac efficitur semper, pretium sed nisl. Ut in lectus in augue malesuada laoreet eu sed ante. Etiam ac mattis dolor, sed varius tellus." }, - "DateTime": { - "S": "2015-09-15T19:58:22.947Z" + "CreatedDateTime": { + "S": "2015-02-15T19:58:22.947Z" }, - "UserName": { - "S": "primordial" + "UpdatedDateTime": { + "S": "2015-02-15T19:58:22.947Z" }, - "UserAvatarLink": { - "S": "http://via.placeholder.com/75x100" + "UserName": { + "S": "bemed" } } } @@ -63,20 +69,53 @@ "UserId": { "S": "3" }, + "ForumId": { + "S": "WebConfection" + }, "Title": { - "S": "Sed consectetur lacus at arcu iaculis, nec semper augue ultricies." + "S": "Neque porro quisquam est qui dolorem ipsum quia dolor sit amet, consectetur, adipisci velit..." }, "Message": { - "S": "In viverra justo diam, eget imperdiet libero vestibulum quis. In malesuada leo quis semper dignissim. Nulla auctor ullamcorper turpis, vitae blandit felis aliquet non. Praesent vel volutpat felis. Curabitur euismod nibh quam, pulvinar facilisis est pulvinar vitae. In est metus, commodo non commodo quis, facilisis a leo. Integer et semper lorem. Phasellus at cursus turpis. Quisque lacinia tincidunt enim id porta. Ut tempor augue at placerat imperdiet. Nullam non nisi dapibus, volutpat sem vel, dictum felis. Proin pharetra nisi id diam vulputate, non facilisis lacus commodo." + "S": "Mauris sed vestibulum nibh, non viverra lacus. Vestibulum non lacus massa. Vivamus sodales nibh vel cursus dignissim. Maecenas tempus, ligula id pulvinar semper, neque nunc suscipit ex, a scelerisque justo sapien sit amet magna. Praesent ut dictum purus, vitae sollicitudin sapien. Nunc pretium eros eu nulla hendrerit molestie. Duis nisl nibh, auctor at finibus ac, aliquam nec tellus. Suspendisse sed fringilla ex, id ultrices diam. Vestibulum nulla orci, rhoncus ac efficitur semper, pretium sed nisl. Ut in lectus in augue malesuada laoreet eu sed ante. Etiam ac mattis dolor, sed varius tellus." + }, + "CreatedDateTime": { + "S": "2015-03-15T19:58:22.947Z" }, - "DateTime": { - "S": "2015-09-15T19:58:22.947Z" + "UpdatedDateTime": { + "S": "2015-03-15T19:58:22.947Z" }, "UserName": { - "S": "primordial" + "S": "fatlouis" + } + } + } + }, + { + "PutRequest": { + "Item": { + "Id": { + "S": "4" + }, + "UserId": { + "S": "4" + }, + "ForumId": { + "S": "WebConfection" + }, + "Title": { + "S": "Neque porro quisquam est qui dolorem ipsum quia dolor sit amet, consectetur, adipisci velit..." + }, + "Message": { + "S": "Mauris sed vestibulum nibh, non viverra lacus. Vestibulum non lacus massa. Vivamus sodales nibh vel cursus dignissim. Maecenas tempus, ligula id pulvinar semper, neque nunc suscipit ex, a scelerisque justo sapien sit amet magna. Praesent ut dictum purus, vitae sollicitudin sapien. Nunc pretium eros eu nulla hendrerit molestie. Duis nisl nibh, auctor at finibus ac, aliquam nec tellus. Suspendisse sed fringilla ex, id ultrices diam. Vestibulum nulla orci, rhoncus ac efficitur semper, pretium sed nisl. Ut in lectus in augue malesuada laoreet eu sed ante. Etiam ac mattis dolor, sed varius tellus." + }, + "CreatedDateTime": { + "S": "2015-04-15T19:58:22.947Z" + }, + "UpdatedDateTime": { + "S": "2015-04-15T19:58:22.947Z" }, - "UserAvatarLink": { - "S": "http://via.placeholder.com/75x100" + "UserName": { + "S": "mickeymouse" } } } @@ -85,19 +124,61 @@ "PutRequest": { "Item": { "Id": { - "S": "2" + "S": "5" + }, + "UserId": { + "S": "5" + }, + "ForumId": { + "S": "WebConfection" + }, + "Title": { + "S": "Neque porro quisquam est qui dolorem ipsum quia dolor sit amet, consectetur, adipisci velit..." + }, + "Message": { + "S": "Mauris sed vestibulum nibh, non viverra lacus. Vestibulum non lacus massa. Vivamus sodales nibh vel cursus dignissim. Maecenas tempus, ligula id pulvinar semper, neque nunc suscipit ex, a scelerisque justo sapien sit amet magna. Praesent ut dictum purus, vitae sollicitudin sapien. Nunc pretium eros eu nulla hendrerit molestie. Duis nisl nibh, auctor at finibus ac, aliquam nec tellus. Suspendisse sed fringilla ex, id ultrices diam. Vestibulum nulla orci, rhoncus ac efficitur semper, pretium sed nisl. Ut in lectus in augue malesuada laoreet eu sed ante. Etiam ac mattis dolor, sed varius tellus." + }, + "CreatedDateTime": { + "S": "2015-04-15T19:58:22.947Z" + }, + "UpdatedDateTime": { + "S": "2015-04-15T19:58:22.947Z" + }, + "UserName": { + "S": "queenie" + } + } + } + }, + { + "PutRequest": { + "Item": { + "Id": { + "S": "5" + }, + "UserId": { + "S": "6" + }, + "ForumId": { + "S": "WebConfection" }, - "ReplyDateTime": { - "S": "2015-10-05T19:58:22.947Z" + "Title": { + "S": "Neque porro quisquam est qui dolorem ipsum quia dolor sit amet, consectetur, adipisci velit..." }, "Message": { - "S": "DynamoDB Thread 2 Reply 2 text" + "S": "Mauris sed vestibulum nibh, non viverra lacus. Vestibulum non lacus massa. Vivamus sodales nibh vel cursus dignissim. Maecenas tempus, ligula id pulvinar semper, neque nunc suscipit ex, a scelerisque justo sapien sit amet magna. Praesent ut dictum purus, vitae sollicitudin sapien. Nunc pretium eros eu nulla hendrerit molestie. Duis nisl nibh, auctor at finibus ac, aliquam nec tellus. Suspendisse sed fringilla ex, id ultrices diam. Vestibulum nulla orci, rhoncus ac efficitur semper, pretium sed nisl. Ut in lectus in augue malesuada laoreet eu sed ante. Etiam ac mattis dolor, sed varius tellus." + }, + "CreatedDateTime": { + "S": "2015-04-15T19:58:22.947Z" }, - "PostedBy": { - "S": "User A" + "UpdatedDateTime": { + "S": "2015-04-15T19:58:22.947Z" + }, + "UserName": { + "S": "kingkong" } } } - } + } ] } \ No newline at end of file diff --git a/serverless.yml b/serverless.yml index 122f16f..1bef4c3 100644 --- a/serverless.yml +++ b/serverless.yml @@ -40,8 +40,8 @@ provider: # - "Ref" : "ServerlessDeploymentBucket" # - "/*" environment: - DYNAMODB_REPLY_TABLE: Reply DYNAMODB_THREAD_TABLE: Thread + DYNAMODB_REPLY_TABLE: Reply iamRoleStatements: - Effect: Allow Action: @@ -56,6 +56,50 @@ provider: functions: + threadCreate: + handler: api/threads/threadCreate.threadCreate + memorySize: 128 + description: Submit thread. + events: + - http: + path: replies + method: post + cors: true + + replyList: + handler: api/threads/threadList.threadList + memorySize: 128 + description: Retrieve a paginated list of threads. + events: + - http: + path: replies + method: get + cors: true + + replyUpdate: + handler: api/threads/threadUpdate.threadUpdate + events: + - http: + path: threads/{id} + method: put + cors: true + + replyGet: + handler: api/threads/threadGet.threadGet + events: + - http: + path: threads/{id} + method: get + cors: true + + replyDelete: + handler: api/threads/threadDelete.threadDelete + events: + - http: + path: threads/{id} + method: delete + cors: true + replyCreate: handler: api/replies/replyCreate.replyCreate memorySize: 128 @@ -77,7 +121,7 @@ functions: cors: true replyUpdate: - handler: api/replies/update.update + handler: api/replies/replyUpdate.replyUpdate events: - http: path: replies/{id} @@ -85,7 +129,7 @@ functions: cors: true replyGet: - handler: api/replies/get.get + handler: api/replies/replyGet.replyGet events: - http: path: replies/{id} @@ -93,7 +137,7 @@ functions: cors: true replyDelete: - handler: api/replies/delete.delete + handler: api/replies/replyDelete.replyDelete events: - http: path: replies/{id} @@ -172,10 +216,10 @@ resources: AttributeName: "Id" AttributeType: "S" - - AttributeName: "UserId" - AttributeType: "S" + AttributeName: "ForumId" + AttributeType: "S" - - AttributeName: "Message" + AttributeName: "UserId" AttributeType: "S" - AttributeName: "ThreadDateTime" @@ -189,16 +233,30 @@ resources: KeyType: "RANGE" GlobalSecondaryIndexes: - - IndexName: Thread-UserId-Message-Index + IndexName: ForumIndex + KeySchema: + - + AttributeName: "ForumId" + KeyType: HASH + - + AttributeName: "ThreadDateTime" + KeyType: RANGE + Projection: + ProjectionType: ALL + ProvisionedThroughput: + ReadCapacityUnits: 1 + WriteCapacityUnits: 1 + - + IndexName: UserIndex KeySchema: - AttributeName: "UserId" KeyType: HASH - - AttributeName: "Message" + AttributeName: "ThreadDateTime" KeyType: RANGE Projection: - ProjectionType: KEYS_ONLY + ProjectionType: INCLUDE ProvisionedThroughput: ReadCapacityUnits: 1 WriteCapacityUnits: 1 @@ -223,14 +281,14 @@ resources: AttributeName: "UserId" AttributeType: "S" - - AttributeName: "ReplyDateTime" + AttributeName: "CreatedDateTime" AttributeType: "S" KeySchema: - AttributeName: "Id" KeyType: "HASH" - - AttributeName: "ReplyDateTime" + AttributeName: "CreatedDateTime" KeyType: "RANGE" GlobalSecondaryIndexes: - @@ -240,7 +298,7 @@ resources: AttributeName: "ThreadId" KeyType: HASH - - AttributeName: "ReplyDateTime" + AttributeName: "CreatedDateTime" KeyType: RANGE Projection: ProjectionType: ALL @@ -254,7 +312,7 @@ resources: AttributeName: "UserId" KeyType: HASH - - AttributeName: "ReplyDateTime" + AttributeName: "CreatedDateTime" KeyType: RANGE Projection: ProjectionType: INCLUDE From c75e94ae19e7026dadeb716185c1179f296b44d9 Mon Sep 17 00:00:00 2001 From: jacksoncharles Date: Fri, 1 Dec 2017 16:34:29 +0000 Subject: [PATCH 15/78] dynamobb practice --- api/replies/replyCreate.js | 16 ++-- api/replies/replyList.js | 30 +++--- api/replies/replyUpdate.js | 16 ++-- api/threads/threadCreate.js | 178 ++++++++++++++++++++++++++++++++++++ api/threads/threadDelete.js | 55 +++++++++++ api/threads/threadGet.js | 57 ++++++++++++ api/threads/threadList.js | 14 +-- api/threads/threadUpdate.js | 170 ++++++++++++++++++++++++++++++++++ npm-debug.log | 109 ++++++++++++++++++++++ package.json | 1 + 10 files changed, 608 insertions(+), 38 deletions(-) create mode 100644 api/threads/threadCreate.js create mode 100644 api/threads/threadDelete.js create mode 100644 api/threads/threadGet.js create mode 100644 api/threads/threadUpdate.js create mode 100644 npm-debug.log diff --git a/api/replies/replyCreate.js b/api/replies/replyCreate.js index 5efcd2d..20d0264 100644 --- a/api/replies/replyCreate.js +++ b/api/replies/replyCreate.js @@ -18,7 +18,7 @@ const dynamoDb = new AWS.DynamoDB.DocumentClient(); */ module.exports.replyCreate = (event, context, callback) => { - var Query = function( event ) { + var QueryBuilder = function( event ) { /** * Capture the event object passed as a parameter; @@ -83,7 +83,7 @@ module.exports.replyCreate = (event, context, callback) => { * * @return {this} */ - Query.prototype.hydrate = function() { + QueryBuilder.prototype.hydrate = function() { const timestamp = new Date().getTime(); @@ -105,7 +105,7 @@ module.exports.replyCreate = (event, context, callback) => { * * @return {this} */ - Query.prototype.validates = function() { + QueryBuilder.prototype.validates = function() { this.errors = []; @@ -124,25 +124,25 @@ module.exports.replyCreate = (event, context, callback) => { /** * Instantiate an instance of Query * - * @type {Query} + * @type {QueryBuilder} */ - var Reply = new Query( event ); + var Query = new QueryBuilder( event ); // Check to see if the parameters passed in the request validate. - if ( Reply.validates() == false ) { + if ( Query.validates() == false ) { // Handle validation errors callback(null, { statusCode: 422, body: JSON.stringify({ - message: Reply.errors + message: Query.errors }) }) } else { // Save to permanent storage - return dynamoDb.put( Reply.parameters ).promise() + return dynamoDb.put( Query.hydrate().parameters ).promise() .then( res => reply ) .then( res => { diff --git a/api/replies/replyList.js b/api/replies/replyList.js index 13b39af..6e5e33e 100644 --- a/api/replies/replyList.js +++ b/api/replies/replyList.js @@ -15,13 +15,13 @@ const dynamoDb = new AWS.DynamoDB.DocumentClient(); module.exports.replyList = (event, context, callback) => { /** - * Query object used to build this.parameters. + * QueryBuilder object used to build this.parameters. * * @param {Object} event - AWS Lambda uses this parameter to pass in event data to the handler. * * @constructor */ - var Query = function( event ) { + var QueryBuilder = function( event ) { /** * Capture the event object passed as a parameter; @@ -76,7 +76,7 @@ module.exports.replyList = (event, context, callback) => { * * @return {boolean} */ - Query.prototype.validates = function() { + QueryBuilder.prototype.validates = function() { if ( this.event.hasOwnProperty('queryStringParameters') == false ) { @@ -100,7 +100,7 @@ module.exports.replyList = (event, context, callback) => { * * @return this */ - Query.prototype.setThreadIndex = function() { + QueryBuilder.prototype.setThreadIndex = function() { if ( this.event.queryStringParameters && this.event.queryStringParameters.threadid ) { @@ -119,7 +119,7 @@ module.exports.replyList = (event, context, callback) => { * * @return this */ - Query.prototype.setUserIndex = function() { + QueryBuilder.prototype.setUserIndex = function() { if ( this.event.queryStringParameters && this.event.queryStringParameters.userid ) { @@ -140,7 +140,7 @@ module.exports.replyList = (event, context, callback) => { * * @return this */ - Query.prototype.setPagination = function() { + QueryBuilder.prototype.setPagination = function() { if ( this.event.queryStringParameters ) { @@ -158,11 +158,11 @@ module.exports.replyList = (event, context, callback) => { } /** - * Override the default value of "Limit" with any value passed by the query string. + * Set a value for "Limit" with any value passed by the query string. * * @return void */ - Query.prototype.setLimit = function() { + QueryBuilder.prototype.setLimit = function() { if ( this.event.queryStringParameters ) { @@ -176,32 +176,32 @@ module.exports.replyList = (event, context, callback) => { } /** - * Instantiate an instance of Query + * Instantiate an instance of QueryBuilder * - * @type {Query} + * @type {QueryBuilder} */ - var Reply = new Query( event ); + var Query = new QueryBuilder( event ); // Check to see if the parameters passed in the request validate. - if ( Reply.validates() == false ) { + if ( Query.validates() == false ) { // Handle validation errors callback(null, { statusCode: 422, body: JSON.stringify({ - message: Reply.errors + message: Query.errors }) }) } else { - Reply + Query .setThreadIndex() .setUserIndex() .setPagination() .setLimit(); - dynamoDb.query( Reply.parameters, function( error, data ) { + dynamoDb.query( Query.parameters, function( error, data ) { // Handle potential errors if (error) { diff --git a/api/replies/replyUpdate.js b/api/replies/replyUpdate.js index 89b1078..cda3d50 100644 --- a/api/replies/replyUpdate.js +++ b/api/replies/replyUpdate.js @@ -14,7 +14,7 @@ const dynamoDb = new AWS.DynamoDB.DocumentClient(); */ module.exports.replyUpdate = (event, context, callback) => { - var Reply = function( event ) { + var QueryBuilder = function( event ) { /** * Capture the event object passed as a parameter; @@ -80,7 +80,7 @@ module.exports.replyUpdate = (event, context, callback) => { * * @return {this} */ - Query.prototype.hydrate = function() { + QueryBuilder.prototype.hydrate = function() { const timestamp = new Date().getTime(); @@ -101,7 +101,7 @@ module.exports.replyUpdate = (event, context, callback) => { * * @return {this} */ - Query.prototype.validates = function() { + QueryBuilder.prototype.validates = function() { this.errors = []; @@ -120,25 +120,25 @@ module.exports.replyUpdate = (event, context, callback) => { /** * Instantiate an instance of Query * - * @type {Query} + * @type {QueryBuilder} */ - var Reply = new Query( event ); + var Query = new QueryBuilder( event ); // Check to see if the parameters passed in the request validate. - if ( Reply.validates() == false ) { + if ( Query.validates() == false ) { // Handle validation errors callback(null, { statusCode: 422, body: JSON.stringify({ - message: Reply.errors + message: Query.errors }) }) } else { // Update the post in the database - dynamoDb.update( Reply.parameters, (error, result) => { + dynamoDb.update( Query.hydrate().parameters, (error, result) => { // Handle any potential DynamoDb errors if (error) { diff --git a/api/threads/threadCreate.js b/api/threads/threadCreate.js new file mode 100644 index 0000000..054eb3e --- /dev/null +++ b/api/threads/threadCreate.js @@ -0,0 +1,178 @@ +'use strict'; + +const uuid = require('uuid'); +const AWS = require('aws-sdk'); +var schema = require('validate'); + +AWS.config.setPromisesDependency(require('bluebird')); +const dynamoDb = new AWS.DynamoDB.DocumentClient(); + +/** + * Handler for the lambda function. + * + * @param {Object} event - AWS Lambda uses this parameter to pass in event data to the handler. + * @param {Object} context - AWS Lambda uses this parameter to provide your handler the runtime information of the Lambda function that is executing. + * @param {Function} callback - Optional parameter used to pass a callback + * + * @return JSON JSON encoded response. + */ +module.exports.threadCreate = (event, context, callback) => { + + var QueryBuilder = function( event ) { + + /** + * Capture the event object passed as a parameter; + * + * @type event + */ + this.event = event; + + /** + * Used to build the object being saved to permanent storage. The value of "Item" will + * be populated with user passed parameters + * + * @todo : Change TableName to value of process.env.DYNAMODB_REPLY_TABLE + * + * @type Object + */ + this.parameters = { + TableName: 'Thread' + Item: {} + } + + /** + * Holds the schema for validating the parameters passed with the request. Anything failing + * validation will be stored inside this.errors + * + * @type {Object} + */ + this.validator = schema({ + + ThreadId: { + type: 'string', + required: true, + message: 'threadid is required.' + }, + UserId: { + type: 'number', + required: true, + message: 'userid is required.' + }, + Title: { + type: 'string', + required: true, + message: 'title is required.' + }, + Message: { + type: 'string', + required: true, + message: 'message is required.' + }, + UserName: { + type: 'string', + required: true, + message: 'username is required.' + } + }); + + /** + * Used to hold any validation messages. + * + * @type {Array} + */ + this.errors = []; + } + + /** + * Populates the "item" object prior to saving + * + * @return {this} + */ + QueryBuilder.prototype.hydrate = function() { + + const timestamp = new Date().getTime(); + + this.parameters.Item = { + Id: uuid.v1(), + ThreadId: this.event.queryStringParameters.threadid, + UserId: this.event.queryStringParameters.userid, + Title: this.event.queryStringParameters.title, + Message: this.event.queryStringParameters.message, + UserName: this.event.queryStringParameters.username, + CreatedDateTime: timestamp, + UpdatedDateTime: timestamp + }; + + return this; + } + + /** + * Validates the data passed in the event object + * + * @return {this} + */ + QueryBuilder.prototype.validates = function() { + + this.errors = []; + + /* + if ( this.event.queryStringParameters.hasOwnProperty('threadid') == false && typeof this.event.queryStringParameters.threadid !== 'string' ) this.errors.push('threadid missing or invalid'); + if ( this.event.queryStringParameters.hasOwnProperty('userid') == false && typeof this.event.queryStringParameters.userid !== 'string' ) this.errors.push('userid missing or invalid'); + if ( this.event.queryStringParameters.hasOwnProperty('message') == false && typeof this.event.queryStringParameters.message !== 'string' ) this.errors.push('message missing or invalid'); + if ( this.event.queryStringParameters.hasOwnProperty('username') == false && typeof this.event.queryStringParameters.username !== 'string' ) this.errors.push('username missing or invalid'); + */ + // Validate the query parameters + this.errors = this.validator.validate( this.event.queryStringParameters ); + + return this.errors.length ? 0 : 1; + } + + /** + * Instantiate an instance of Query + * + * @type {QueryBuilder} + */ + var Query = new QueryBuilder( event ); + + // Check to see if the parameters passed in the request validate. + if ( Query.validates() == false ) { + + // Handle validation errors + callback(null, { + statusCode: 422, + body: JSON.stringify({ + message: Query.errors + }) + }) + } + else { + + // Save to permanent storage + return dynamoDb.put( Query.hydrate().parameters ).promise() + .then( res => reply ) + .then( res => { + + // create a response + const response = { + + statusCode: 200, + body: JSON.stringify(res), + }; + + + callback(null, response); + }) + .catch(err => { + + console.log('=== error ===', error ); + + // Handle DynamoDb errors + callback(null, { + statusCode: 500, + body: JSON.stringify({ + message: `Unable to submit reply` + }) + }) + }); + } +}; \ No newline at end of file diff --git a/api/threads/threadDelete.js b/api/threads/threadDelete.js new file mode 100644 index 0000000..bc80f87 --- /dev/null +++ b/api/threads/threadDelete.js @@ -0,0 +1,55 @@ +'use strict'; + +const AWS = require('aws-sdk'); // eslint-disable-line import/no-extraneous-dependencies +const dynamoDb = new AWS.DynamoDB.DocumentClient(); + +/** + * Handler for the lambda function. + * + * @param {Object} event - AWS Lambda uses this parameter to pass in event data to the handler. + * @param {Object} context - AWS Lambda uses this parameter to provide your handler the runtime information of the Lambda function that is executing. + * @param {Function} callback - Optional parameter used to pass a callback + * + * @return JSON JSON encoded response. + */ +module.exports.threadDelete = (event, context, callback) => { + + /** + * The parameters used by DynamoDb + * + * @type {Object} + */ + const parameters = { + TableName: 'Thread', + Key: { + id: event.pathParameters.id, + } + }; + + // Run the query to remove the item from permenent storage + dynamoDb.delete(parameters, (error) => { + + // Handle any potential DynamoDb errors + if (error) { + + console.error('=== error ===', error); + + callback(null, { + statusCode: error.statusCode || 501, + headers: { 'Content-Type': 'text/plain' }, + body: 'Couldn\'t remove the post item.', + }); + + } + else { + + // Create a successful response + const response = { + statusCode: 204, + body: JSON.stringify({}), + }; + + callback(null, response); + } + }); +}; \ No newline at end of file diff --git a/api/threads/threadGet.js b/api/threads/threadGet.js new file mode 100644 index 0000000..bd57f55 --- /dev/null +++ b/api/threads/threadGet.js @@ -0,0 +1,57 @@ +'use strict'; + +const AWS = require('aws-sdk'); // eslint-disable-line import/no-extraneous-dependencies +const dynamoDb = new AWS.DynamoDB.DocumentClient(); + +/** + * Handler for the lambda function. + * + * @param {Object} event - AWS Lambda uses this parameter to pass in event data to the handler. + * @param {Object} context - AWS Lambda uses this parameter to provide your handler the runtime information of the Lambda function that is executing. + * @param {Function} callback - Optional parameter used to pass a callback + * + * @return JSON JSON encoded response. + */ +module.exports.threadGet = (event, context, callback) => { + + /** + * The parameters needed by DynamoDb + * + * @type {Object} + */ + const parameters = { + + TableName: 'Thread', + Key: { + Id: event.pathParameters.id + } + } + + // Run the query to retrieve the Item from permanent storage + dynamoDb.get(Query.parameters, (error, result) => { + + // Handle any potential DynamoDb errors + if (error) { + + console.error('=== error ===', error); + + callback(null, { + statusCode: error.statusCode || 501, + headers: { 'Content-Type': 'text/plain' }, + body: 'Couldn\'t fetch the post item.', + }); + + } + else { + + // create a response + const response = { + + statusCode: 200, + body: JSON.stringify(result.Item), + }; + + callback(null, response); + } + }); +}; \ No newline at end of file diff --git a/api/threads/threadList.js b/api/threads/threadList.js index 8e84d5d..e541b2c 100644 --- a/api/threads/threadList.js +++ b/api/threads/threadList.js @@ -21,7 +21,7 @@ module.exports.threadList = (event, context, callback) => { * * @constructor */ - var Query = function( event ) { + var QueryBuilder = function( event ) { /** * Capture the event object passed as a parameter; @@ -144,31 +144,31 @@ module.exports.threadList = (event, context, callback) => { /** * Instantiate an instance of Query * - * @type {Query} + * @type {QueryBuilder} */ - var Reply = new Query( event ); + var Query = new QueryBuilder( event ); // Check to see if the parameters passed in the request validate. - if ( Reply.validates() == false ) { + if ( Query.validates() == false ) { // Handle validation errors callback(null, { statusCode: 422, body: JSON.stringify({ - message: Reply.errors + message: Query.errors }) }) } else { - Reply + Query .setThreadIndex() .setUserIndex() .setPagination() .setLimit(); // Run the DynamoDb query. - dynamoDb.query( Reply.parameters, function( error, data ) { + dynamoDb.query( Query.parameters, function( error, data ) { // Handle any potential DynamoDb errors if (error) { diff --git a/api/threads/threadUpdate.js b/api/threads/threadUpdate.js new file mode 100644 index 0000000..27b27d8 --- /dev/null +++ b/api/threads/threadUpdate.js @@ -0,0 +1,170 @@ +'use strict'; + +const AWS = require('aws-sdk'); // eslint-disable-line import/no-extraneous-dependencies +const dynamoDb = new AWS.DynamoDB.DocumentClient(); + +/** + * Handler for the lambda function. + * + * @param {Object} event - AWS Lambda uses this parameter to pass in event data to the handler. + * @param {Object} context - AWS Lambda uses this parameter to provide your handler the runtime information of the Lambda function that is executing. + * @param {Function} callback - Optional parameter used to pass a callback + * + * @return JSON JSON encoded response. + */ +module.exports.threadUpdate = (event, context, callback) => { + + var QueryBuilder = function( event ) { + + /** + * Capture the event object passed as a parameter; + * + * @type event + */ + this.event = event; + + /** + * Used to build the object being saved to permanent storage. The value of "Item" will + * be populated with user passed parameters + * + * @todo : Change TableName to value of process.env.DYNAMODB_REPLY_TABLE + * + * @type Object + */ + this.parameters = { + TableName: 'Thread' + Item: {} + } + + /** + * Holds the schema for validating the parameters passed with the request. Anything failing + * validation will be stored inside this.errors + * + * @type {Object} + */ + this.validator = schema({ + + ThreadId: { + type: 'string', + required: true, + message: 'threadid is required.' + }, + UserId: { + type: 'number', + required: true, + message: 'userid is required.' + }, + Title: { + type: 'string', + required: true, + message: 'title is required.' + }, + Message: { + type: 'string', + required: true, + message: 'message is required.' + }, + UserName: { + type: 'string', + required: true, + message: 'username is required.' + } + }); + + /** + * Used to hold any validation messages. + * + * @type {Array} + */ + this.errors = []; + + } + + /** + * Populates the "item" object prior to saving + * + * @return {this} + */ + QueryBuilder.prototype.hydrate = function() { + + const timestamp = new Date().getTime(); + + this.parameters.Item = { + Id: uuid.v1(), + ThreadId: this.event.queryStringParameters.threadid, + UserId: this.event.queryStringParameters.userid, + Title: this.event.queryStringParameters.title, + Message: this.event.queryStringParameters.message, + UserName: this.event.queryStringParameters.username, + UpdatedDateTime: timestamp + }; + + return this; + } + + /** + * Validates the data passed in the event object + * + * @return {this} + */ + QueryBuilder.prototype.validates = function() { + + this.errors = []; + + /* + if ( this.event.queryStringParameters.hasOwnProperty('threadid') == false && typeof this.event.queryStringParameters.threadid !== 'string' ) this.errors.push('threadid missing or invalid'); + if ( this.event.queryStringParameters.hasOwnProperty('userid') == false && typeof this.event.queryStringParameters.userid !== 'string' ) this.errors.push('userid missing or invalid'); + if ( this.event.queryStringParameters.hasOwnProperty('message') == false && typeof this.event.queryStringParameters.message !== 'string' ) this.errors.push('message missing or invalid'); + if ( this.event.queryStringParameters.hasOwnProperty('username') == false && typeof this.event.queryStringParameters.username !== 'string' ) this.errors.push('username missing or invalid'); + */ + // Validate the query parameters + this.errors = this.validator.validate( this.event.queryStringParameters ); + + return this.errors.length ? 0 : 1; + } + + /** + * Instantiate an instance of Query + * + * @type {QueryBuilder} + */ + var Query = new QueryBuilder( event ); + + // Check to see if the parameters passed in the request validate. + if ( Query.validates() == false ) { + + // Handle validation errors + callback(null, { + statusCode: 422, + body: JSON.stringify({ + message: Query.errors + }) + }) + } + else { + + // Update the post in the database + dynamoDb.update( Query.hydrate().parameters, (error, result) => { + + // Handle any potential DynamoDb errors + if (error) { + + console.error('=== error ===', error); + + callback(null, { + statusCode: error.statusCode || 501, + headers: { 'Content-Type': 'text/plain' }, + body: 'Couldn\'t fetch the post item.', + }); + return; + } + + // create a response + const response = { + statusCode: 200, + body: JSON.stringify(result.Attributes), + }; + callback(null, response); + }); + } +}; \ No newline at end of file diff --git a/npm-debug.log b/npm-debug.log new file mode 100644 index 0000000..451569c --- /dev/null +++ b/npm-debug.log @@ -0,0 +1,109 @@ +0 info it worked if it ends with ok +1 verbose cli [ '/home/charles/.nvm/versions/node/v7.6.0/bin/node', +1 verbose cli '/home/charles/.nvm/versions/node/v7.6.0/bin/npm', +1 verbose cli 'install', +1 verbose cli 'node-lint', +1 verbose cli '--save' ] +2 info using npm@4.1.2 +3 info using node@v7.6.0 +4 silly loadCurrentTree Starting +5 silly install loadCurrentTree +6 silly install readLocalPackageData +7 silly fetchPackageMetaData node-lint +8 silly fetchNamedPackageData node-lint +9 silly mapToRegistry name node-lint +10 silly mapToRegistry using default registry +11 silly mapToRegistry registry https://registry.npmjs.org/ +12 silly mapToRegistry data Result { +12 silly mapToRegistry raw: 'node-lint', +12 silly mapToRegistry scope: null, +12 silly mapToRegistry escapedName: 'node-lint', +12 silly mapToRegistry name: 'node-lint', +12 silly mapToRegistry rawSpec: '', +12 silly mapToRegistry spec: 'latest', +12 silly mapToRegistry type: 'tag' } +13 silly mapToRegistry uri https://registry.npmjs.org/node-lint +14 verbose request uri https://registry.npmjs.org/node-lint +15 verbose request no auth needed +16 info attempt registry request try #1 at 4:30:32 PM +17 verbose request using bearer token for auth +18 verbose request id e9da042a418257b8 +19 http request GET https://registry.npmjs.org/node-lint +20 http 404 https://registry.npmjs.org/node-lint +21 verbose headers { 'content-type': 'application/json', +21 verbose headers 'cache-control': 'max-age=0', +21 verbose headers 'content-length': '2', +21 verbose headers 'accept-ranges': 'bytes', +21 verbose headers date: 'Fri, 01 Dec 2017 16:30:33 GMT', +21 verbose headers via: '1.1 varnish', +21 verbose headers connection: 'keep-alive', +21 verbose headers 'x-served-by': 'cache-lcy19224-LCY', +21 verbose headers 'x-cache': 'MISS', +21 verbose headers 'x-cache-hits': '0', +21 verbose headers 'x-timer': 'S1512145832.086324,VS0,VE1158', +21 verbose headers vary: 'Accept-Encoding' } +22 silly get cb [ 404, +22 silly get { 'content-type': 'application/json', +22 silly get 'cache-control': 'max-age=0', +22 silly get 'content-length': '2', +22 silly get 'accept-ranges': 'bytes', +22 silly get date: 'Fri, 01 Dec 2017 16:30:33 GMT', +22 silly get via: '1.1 varnish', +22 silly get connection: 'keep-alive', +22 silly get 'x-served-by': 'cache-lcy19224-LCY', +22 silly get 'x-cache': 'MISS', +22 silly get 'x-cache-hits': '0', +22 silly get 'x-timer': 'S1512145832.086324,VS0,VE1158', +22 silly get vary: 'Accept-Encoding' } ] +23 silly fetchPackageMetaData Error: Registry returned 404 for GET on https://registry.npmjs.org/node-lint +23 silly fetchPackageMetaData at makeError (/home/charles/.nvm/versions/node/v7.6.0/lib/node_modules/npm/node_modules/npm-registry-client/lib/request.js:304:12) +23 silly fetchPackageMetaData at CachingRegistryClient. (/home/charles/.nvm/versions/node/v7.6.0/lib/node_modules/npm/node_modules/npm-registry-client/lib/request.js:282:14) +23 silly fetchPackageMetaData at Request._callback (/home/charles/.nvm/versions/node/v7.6.0/lib/node_modules/npm/node_modules/npm-registry-client/lib/request.js:212:14) +23 silly fetchPackageMetaData at Request.self.callback (/home/charles/.nvm/versions/node/v7.6.0/lib/node_modules/npm/node_modules/request/request.js:186:22) +23 silly fetchPackageMetaData at emitTwo (events.js:106:13) +23 silly fetchPackageMetaData at Request.emit (events.js:192:7) +23 silly fetchPackageMetaData at Request. (/home/charles/.nvm/versions/node/v7.6.0/lib/node_modules/npm/node_modules/request/request.js:1081:10) +23 silly fetchPackageMetaData at emitOne (events.js:96:13) +23 silly fetchPackageMetaData at Request.emit (events.js:189:7) +23 silly fetchPackageMetaData at IncomingMessage. (/home/charles/.nvm/versions/node/v7.6.0/lib/node_modules/npm/node_modules/request/request.js:1001:12) +23 silly fetchPackageMetaData error for node-lint { Error: Registry returned 404 for GET on https://registry.npmjs.org/node-lint +23 silly fetchPackageMetaData at makeError (/home/charles/.nvm/versions/node/v7.6.0/lib/node_modules/npm/node_modules/npm-registry-client/lib/request.js:304:12) +23 silly fetchPackageMetaData at CachingRegistryClient. (/home/charles/.nvm/versions/node/v7.6.0/lib/node_modules/npm/node_modules/npm-registry-client/lib/request.js:282:14) +23 silly fetchPackageMetaData at Request._callback (/home/charles/.nvm/versions/node/v7.6.0/lib/node_modules/npm/node_modules/npm-registry-client/lib/request.js:212:14) +23 silly fetchPackageMetaData at Request.self.callback (/home/charles/.nvm/versions/node/v7.6.0/lib/node_modules/npm/node_modules/request/request.js:186:22) +23 silly fetchPackageMetaData at emitTwo (events.js:106:13) +23 silly fetchPackageMetaData at Request.emit (events.js:192:7) +23 silly fetchPackageMetaData at Request. (/home/charles/.nvm/versions/node/v7.6.0/lib/node_modules/npm/node_modules/request/request.js:1081:10) +23 silly fetchPackageMetaData at emitOne (events.js:96:13) +23 silly fetchPackageMetaData at Request.emit (events.js:189:7) +23 silly fetchPackageMetaData at IncomingMessage. (/home/charles/.nvm/versions/node/v7.6.0/lib/node_modules/npm/node_modules/request/request.js:1001:12) pkgid: 'node-lint', statusCode: 404, code: 'E404' } +24 silly rollbackFailedOptional Starting +25 silly rollbackFailedOptional Finishing +26 silly runTopLevelLifecycles Finishing +27 silly install printInstalled +28 verbose stack Error: Registry returned 404 for GET on https://registry.npmjs.org/node-lint +28 verbose stack at makeError (/home/charles/.nvm/versions/node/v7.6.0/lib/node_modules/npm/node_modules/npm-registry-client/lib/request.js:304:12) +28 verbose stack at CachingRegistryClient. (/home/charles/.nvm/versions/node/v7.6.0/lib/node_modules/npm/node_modules/npm-registry-client/lib/request.js:282:14) +28 verbose stack at Request._callback (/home/charles/.nvm/versions/node/v7.6.0/lib/node_modules/npm/node_modules/npm-registry-client/lib/request.js:212:14) +28 verbose stack at Request.self.callback (/home/charles/.nvm/versions/node/v7.6.0/lib/node_modules/npm/node_modules/request/request.js:186:22) +28 verbose stack at emitTwo (events.js:106:13) +28 verbose stack at Request.emit (events.js:192:7) +28 verbose stack at Request. (/home/charles/.nvm/versions/node/v7.6.0/lib/node_modules/npm/node_modules/request/request.js:1081:10) +28 verbose stack at emitOne (events.js:96:13) +28 verbose stack at Request.emit (events.js:189:7) +28 verbose stack at IncomingMessage. (/home/charles/.nvm/versions/node/v7.6.0/lib/node_modules/npm/node_modules/request/request.js:1001:12) +29 verbose statusCode 404 +30 verbose pkgid node-lint +31 verbose cwd /home/charles/Code/forum-microservice +32 error Linux 4.4.0-91-generic +33 error argv "/home/charles/.nvm/versions/node/v7.6.0/bin/node" "/home/charles/.nvm/versions/node/v7.6.0/bin/npm" "install" "node-lint" "--save" +34 error node v7.6.0 +35 error npm v4.1.2 +36 error code E404 +37 error 404 Registry returned 404 for GET on https://registry.npmjs.org/node-lint +38 error 404 +39 error 404 'node-lint' is not in the npm registry. +40 error 404 You should bug the author to publish it (or use the name yourself!) +41 error 404 Note that you can also install from a +42 error 404 tarball, folder, http url, or git url. +43 verbose exit [ 1, true ] diff --git a/package.json b/package.json index 48dfb1e..95ecbbd 100644 --- a/package.json +++ b/package.json @@ -10,6 +10,7 @@ "license": "ISC", "dependencies": { "bluebird": "^3.5.1", + "lint": "^1.1.2", "uuid": "^3.1.0", "validate": "^3.0.1" } From 8f12fbb8289b674942aae418cd907c0c19409ab4 Mon Sep 17 00:00:00 2001 From: jacksoncharles Date: Sun, 3 Dec 2017 22:54:24 +0000 Subject: [PATCH 16/78] refactoring --- api/package.json | 17 ++++++ api/replies/replyList.js | 117 ++++++++++++++------------------------- 2 files changed, 58 insertions(+), 76 deletions(-) create mode 100644 api/package.json diff --git a/api/package.json b/api/package.json new file mode 100644 index 0000000..d98f588 --- /dev/null +++ b/api/package.json @@ -0,0 +1,17 @@ +{ + "name": "forumapi", + "version": "0.0.1", + "description": "Serverless module dependencies", + "author": "me", + "license": "MIT", + "private": true, + "repository": { + "type": "git", + "url": "git://github.com/" + }, + "keywords": [], + "devDependencies": {}, + "dependencies": { + "validator": "^9.1.2" + } +} \ No newline at end of file diff --git a/api/replies/replyList.js b/api/replies/replyList.js index 6e5e33e..7c6f7d9 100644 --- a/api/replies/replyList.js +++ b/api/replies/replyList.js @@ -2,6 +2,7 @@ const AWS = require('aws-sdk'); // eslint-disable-line import/no-extraneous-dependencies const dynamoDb = new AWS.DynamoDB.DocumentClient(); +const validator = require('validator'); /** * Handler for the lambda function. @@ -14,6 +15,8 @@ const dynamoDb = new AWS.DynamoDB.DocumentClient(); */ module.exports.replyList = (event, context, callback) => { + let e = event; + /** * QueryBuilder object used to build this.parameters. * @@ -21,14 +24,7 @@ module.exports.replyList = (event, context, callback) => { * * @constructor */ - var QueryBuilder = function( event ) { - - /** - * Capture the event object passed as a parameter; - * - * @type event - */ - this.event = event; + function QueryBuilder() { /** * Used to hold the dynamodb query parameters built using values @@ -42,34 +38,13 @@ module.exports.replyList = (event, context, callback) => { TableName: 'Reply' } - /** - * Holds the schema for validating the parameters passed with the request. Anything failing - * validation will be stored inside this.errors - * - * @type {Object} - */ - this.validator = schema({ - - ThreadId: { - type: 'string', - required: false, - message: 'threadid is not a string.' - }, - UserId: { - type: 'number', - required: false, - message: 'userid is not a number.' - } - }); - /** * Used to hold any validation errors. * * @type {Array} */ this.errors = []; - - } + }; /** * Validates the parameters passed @@ -78,20 +53,23 @@ module.exports.replyList = (event, context, callback) => { */ QueryBuilder.prototype.validates = function() { - if ( this.event.hasOwnProperty('queryStringParameters') == false ) { + this.errors = []; + + if ( e.hasOwnProperty('queryStringParameters') ) { - this.errors[] = { - code: 422, - message: 'No query string parameters passed, threadid or userid required' - }; + if ( validator.isAlphanumeric(e.queryStringParameters.threadid) == false ) { + this.errors.push( new Error('threadid must be an alphanumeric string') ); + } + + if ( validator.isNumeric(e.queryStringParameters.userid) == false ) { + this.errors.push( new Error('userid must be numeric') ); + } } else { - this.errors = this.validator.validate( this.event.queryStringParameters ); + this.errors.push( new Error('No query string parameters passed, threadid or userid required') ); } - - return this.errors.length ? 0 : 1; } @@ -102,12 +80,12 @@ module.exports.replyList = (event, context, callback) => { */ QueryBuilder.prototype.setThreadIndex = function() { - if ( this.event.queryStringParameters && this.event.queryStringParameters.threadid ) { + if( e.queryStringParameters.hasOwnProperty('threadid') ) { this.parameters['IndexName'] = "ThreadIndex"; this.parameters['KeyConditionExpression'] = "ThreadId = :searchstring"; this.parameters['ExpressionAttributeValues'] = { - ":searchstring" : this.event.queryStringParameters.threadid + ":searchstring" : e.queryStringParameters.threadid }; } @@ -121,13 +99,12 @@ module.exports.replyList = (event, context, callback) => { */ QueryBuilder.prototype.setUserIndex = function() { - if ( this.event.queryStringParameters && this.event.queryStringParameters.userid ) { + if ( e.queryStringParameters.hasOwnProperty('userid') ) { this.parameters['IndexName'] = "UserIndex"; this.parameters['KeyConditionExpression'] = "UserId = :searchstring"; - this.parameters['ProjectionExpression'] = "Id, UserId, Message, CreatedDateTime"; this.parameters['ExpressionAttributeValues'] = { - ":searchstring" : this.event.queryStringParameters.userid + ":searchstring" : e.queryStringParameters.userid }; } @@ -142,15 +119,12 @@ module.exports.replyList = (event, context, callback) => { */ QueryBuilder.prototype.setPagination = function() { - if ( this.event.queryStringParameters ) { + // Pagination + if ( e.queryStringParameters.hasOwnProperty('threadid') && e.queryStringParameters.hasOwnProperty('createddatetime') ) { - // Pagination - if ( this.event.queryStringParameters.hasOwnProperty('threadid') && this.event.queryStringParameters.hasOwnProperty('CreatedDateTime') ) { - - this.parameters['ExclusiveStartKey'] = { - ThreadId: this.event.queryStringParameters.threadid, - DateTime: this.event.queryStringParameters.CreatedDateTime - } + this.parameters['ExclusiveStartKey'] = { + ThreadId: e.queryStringParameters.threadid, + DateTime: e.queryStringParameters.createddatetime } } @@ -164,12 +138,9 @@ module.exports.replyList = (event, context, callback) => { */ QueryBuilder.prototype.setLimit = function() { - if ( this.event.queryStringParameters ) { - - if ( this.event.queryStringParameters.hasOwnProperty('limit') ) { + if ( e.queryStringParameters.hasOwnProperty('limit') ) { - this.parameters['Limit'] = this.event.queryStringParameters.limit; - } + this.parameters['Limit'] = e.queryStringParameters.limit; } return this; @@ -180,20 +151,9 @@ module.exports.replyList = (event, context, callback) => { * * @type {QueryBuilder} */ - var Query = new QueryBuilder( event ); + let Query = new QueryBuilder(); - // Check to see if the parameters passed in the request validate. - if ( Query.validates() == false ) { - - // Handle validation errors - callback(null, { - statusCode: 422, - body: JSON.stringify({ - message: Query.errors - }) - }) - } - else { + if ( Query.validates() ) { Query .setThreadIndex() @@ -206,11 +166,8 @@ module.exports.replyList = (event, context, callback) => { // Handle potential errors if (error) { - console.log('=== error ===', error ); - callback(null, { - statusCode: error.statusCode || 501, - body: error.ValidationException || 'Couldn\'t fetch the replies.' - }); + console.log('=== dynamodb validation error ===', JSON.stringify( error )); + callback(null, JSON.stringify( error ) ); return; } @@ -224,6 +181,14 @@ module.exports.replyList = (event, context, callback) => { callback(null, response); } - } - }); + }) + } + else { + + // Handle validation errors + callback(null, { + statusCode: 422, + body: JSON.stringify(Query.errors) + }); + } }; \ No newline at end of file From 75803b27084f354cb0c1e830401a0f4c334ec64e Mon Sep 17 00:00:00 2001 From: jacksoncharles Date: Sun, 3 Dec 2017 23:05:31 +0000 Subject: [PATCH 17/78] refactoring --- loadData.sh | 3 +- npm-debug.log | 109 ------------------------------------------------- package.json | 3 +- serverless.yml | 12 +++--- 4 files changed, 9 insertions(+), 118 deletions(-) delete mode 100644 npm-debug.log diff --git a/loadData.sh b/loadData.sh index 53a77d2..f6c3dda 100755 --- a/loadData.sh +++ b/loadData.sh @@ -1,2 +1,3 @@ #!/usr/bin/env bash -aws dynamodb batch-write-item --request-items file://sample_data/Reply.json \ No newline at end of file +aws dynamodb batch-write-item --request-items file://sample_data/Reply.json +aws dynamodb batch-write-item --request-items file://sample_data/Thread.json \ No newline at end of file diff --git a/npm-debug.log b/npm-debug.log deleted file mode 100644 index 451569c..0000000 --- a/npm-debug.log +++ /dev/null @@ -1,109 +0,0 @@ -0 info it worked if it ends with ok -1 verbose cli [ '/home/charles/.nvm/versions/node/v7.6.0/bin/node', -1 verbose cli '/home/charles/.nvm/versions/node/v7.6.0/bin/npm', -1 verbose cli 'install', -1 verbose cli 'node-lint', -1 verbose cli '--save' ] -2 info using npm@4.1.2 -3 info using node@v7.6.0 -4 silly loadCurrentTree Starting -5 silly install loadCurrentTree -6 silly install readLocalPackageData -7 silly fetchPackageMetaData node-lint -8 silly fetchNamedPackageData node-lint -9 silly mapToRegistry name node-lint -10 silly mapToRegistry using default registry -11 silly mapToRegistry registry https://registry.npmjs.org/ -12 silly mapToRegistry data Result { -12 silly mapToRegistry raw: 'node-lint', -12 silly mapToRegistry scope: null, -12 silly mapToRegistry escapedName: 'node-lint', -12 silly mapToRegistry name: 'node-lint', -12 silly mapToRegistry rawSpec: '', -12 silly mapToRegistry spec: 'latest', -12 silly mapToRegistry type: 'tag' } -13 silly mapToRegistry uri https://registry.npmjs.org/node-lint -14 verbose request uri https://registry.npmjs.org/node-lint -15 verbose request no auth needed -16 info attempt registry request try #1 at 4:30:32 PM -17 verbose request using bearer token for auth -18 verbose request id e9da042a418257b8 -19 http request GET https://registry.npmjs.org/node-lint -20 http 404 https://registry.npmjs.org/node-lint -21 verbose headers { 'content-type': 'application/json', -21 verbose headers 'cache-control': 'max-age=0', -21 verbose headers 'content-length': '2', -21 verbose headers 'accept-ranges': 'bytes', -21 verbose headers date: 'Fri, 01 Dec 2017 16:30:33 GMT', -21 verbose headers via: '1.1 varnish', -21 verbose headers connection: 'keep-alive', -21 verbose headers 'x-served-by': 'cache-lcy19224-LCY', -21 verbose headers 'x-cache': 'MISS', -21 verbose headers 'x-cache-hits': '0', -21 verbose headers 'x-timer': 'S1512145832.086324,VS0,VE1158', -21 verbose headers vary: 'Accept-Encoding' } -22 silly get cb [ 404, -22 silly get { 'content-type': 'application/json', -22 silly get 'cache-control': 'max-age=0', -22 silly get 'content-length': '2', -22 silly get 'accept-ranges': 'bytes', -22 silly get date: 'Fri, 01 Dec 2017 16:30:33 GMT', -22 silly get via: '1.1 varnish', -22 silly get connection: 'keep-alive', -22 silly get 'x-served-by': 'cache-lcy19224-LCY', -22 silly get 'x-cache': 'MISS', -22 silly get 'x-cache-hits': '0', -22 silly get 'x-timer': 'S1512145832.086324,VS0,VE1158', -22 silly get vary: 'Accept-Encoding' } ] -23 silly fetchPackageMetaData Error: Registry returned 404 for GET on https://registry.npmjs.org/node-lint -23 silly fetchPackageMetaData at makeError (/home/charles/.nvm/versions/node/v7.6.0/lib/node_modules/npm/node_modules/npm-registry-client/lib/request.js:304:12) -23 silly fetchPackageMetaData at CachingRegistryClient. (/home/charles/.nvm/versions/node/v7.6.0/lib/node_modules/npm/node_modules/npm-registry-client/lib/request.js:282:14) -23 silly fetchPackageMetaData at Request._callback (/home/charles/.nvm/versions/node/v7.6.0/lib/node_modules/npm/node_modules/npm-registry-client/lib/request.js:212:14) -23 silly fetchPackageMetaData at Request.self.callback (/home/charles/.nvm/versions/node/v7.6.0/lib/node_modules/npm/node_modules/request/request.js:186:22) -23 silly fetchPackageMetaData at emitTwo (events.js:106:13) -23 silly fetchPackageMetaData at Request.emit (events.js:192:7) -23 silly fetchPackageMetaData at Request. (/home/charles/.nvm/versions/node/v7.6.0/lib/node_modules/npm/node_modules/request/request.js:1081:10) -23 silly fetchPackageMetaData at emitOne (events.js:96:13) -23 silly fetchPackageMetaData at Request.emit (events.js:189:7) -23 silly fetchPackageMetaData at IncomingMessage. (/home/charles/.nvm/versions/node/v7.6.0/lib/node_modules/npm/node_modules/request/request.js:1001:12) -23 silly fetchPackageMetaData error for node-lint { Error: Registry returned 404 for GET on https://registry.npmjs.org/node-lint -23 silly fetchPackageMetaData at makeError (/home/charles/.nvm/versions/node/v7.6.0/lib/node_modules/npm/node_modules/npm-registry-client/lib/request.js:304:12) -23 silly fetchPackageMetaData at CachingRegistryClient. (/home/charles/.nvm/versions/node/v7.6.0/lib/node_modules/npm/node_modules/npm-registry-client/lib/request.js:282:14) -23 silly fetchPackageMetaData at Request._callback (/home/charles/.nvm/versions/node/v7.6.0/lib/node_modules/npm/node_modules/npm-registry-client/lib/request.js:212:14) -23 silly fetchPackageMetaData at Request.self.callback (/home/charles/.nvm/versions/node/v7.6.0/lib/node_modules/npm/node_modules/request/request.js:186:22) -23 silly fetchPackageMetaData at emitTwo (events.js:106:13) -23 silly fetchPackageMetaData at Request.emit (events.js:192:7) -23 silly fetchPackageMetaData at Request. (/home/charles/.nvm/versions/node/v7.6.0/lib/node_modules/npm/node_modules/request/request.js:1081:10) -23 silly fetchPackageMetaData at emitOne (events.js:96:13) -23 silly fetchPackageMetaData at Request.emit (events.js:189:7) -23 silly fetchPackageMetaData at IncomingMessage. (/home/charles/.nvm/versions/node/v7.6.0/lib/node_modules/npm/node_modules/request/request.js:1001:12) pkgid: 'node-lint', statusCode: 404, code: 'E404' } -24 silly rollbackFailedOptional Starting -25 silly rollbackFailedOptional Finishing -26 silly runTopLevelLifecycles Finishing -27 silly install printInstalled -28 verbose stack Error: Registry returned 404 for GET on https://registry.npmjs.org/node-lint -28 verbose stack at makeError (/home/charles/.nvm/versions/node/v7.6.0/lib/node_modules/npm/node_modules/npm-registry-client/lib/request.js:304:12) -28 verbose stack at CachingRegistryClient. (/home/charles/.nvm/versions/node/v7.6.0/lib/node_modules/npm/node_modules/npm-registry-client/lib/request.js:282:14) -28 verbose stack at Request._callback (/home/charles/.nvm/versions/node/v7.6.0/lib/node_modules/npm/node_modules/npm-registry-client/lib/request.js:212:14) -28 verbose stack at Request.self.callback (/home/charles/.nvm/versions/node/v7.6.0/lib/node_modules/npm/node_modules/request/request.js:186:22) -28 verbose stack at emitTwo (events.js:106:13) -28 verbose stack at Request.emit (events.js:192:7) -28 verbose stack at Request. (/home/charles/.nvm/versions/node/v7.6.0/lib/node_modules/npm/node_modules/request/request.js:1081:10) -28 verbose stack at emitOne (events.js:96:13) -28 verbose stack at Request.emit (events.js:189:7) -28 verbose stack at IncomingMessage. (/home/charles/.nvm/versions/node/v7.6.0/lib/node_modules/npm/node_modules/request/request.js:1001:12) -29 verbose statusCode 404 -30 verbose pkgid node-lint -31 verbose cwd /home/charles/Code/forum-microservice -32 error Linux 4.4.0-91-generic -33 error argv "/home/charles/.nvm/versions/node/v7.6.0/bin/node" "/home/charles/.nvm/versions/node/v7.6.0/bin/npm" "install" "node-lint" "--save" -34 error node v7.6.0 -35 error npm v4.1.2 -36 error code E404 -37 error 404 Registry returned 404 for GET on https://registry.npmjs.org/node-lint -38 error 404 -39 error 404 'node-lint' is not in the npm registry. -40 error 404 You should bug the author to publish it (or use the name yourself!) -41 error 404 Note that you can also install from a -42 error 404 tarball, folder, http url, or git url. -43 verbose exit [ 1, true ] diff --git a/package.json b/package.json index 95ecbbd..202fbda 100644 --- a/package.json +++ b/package.json @@ -11,7 +11,6 @@ "dependencies": { "bluebird": "^3.5.1", "lint": "^1.1.2", - "uuid": "^3.1.0", - "validate": "^3.0.1" + "uuid": "^3.1.0" } } diff --git a/serverless.yml b/serverless.yml index 1bef4c3..9b2b2a1 100644 --- a/serverless.yml +++ b/serverless.yml @@ -66,7 +66,7 @@ functions: method: post cors: true - replyList: + threadList: handler: api/threads/threadList.threadList memorySize: 128 description: Retrieve a paginated list of threads. @@ -76,7 +76,7 @@ functions: method: get cors: true - replyUpdate: + threadUpdate: handler: api/threads/threadUpdate.threadUpdate events: - http: @@ -84,7 +84,7 @@ functions: method: put cors: true - replyGet: + threadGet: handler: api/threads/threadGet.threadGet events: - http: @@ -92,7 +92,7 @@ functions: method: get cors: true - replyDelete: + threadDelete: handler: api/threads/threadDelete.threadDelete events: - http: @@ -256,7 +256,7 @@ resources: AttributeName: "ThreadDateTime" KeyType: RANGE Projection: - ProjectionType: INCLUDE + ProjectionType: ALL ProvisionedThroughput: ReadCapacityUnits: 1 WriteCapacityUnits: 1 @@ -315,7 +315,7 @@ resources: AttributeName: "CreatedDateTime" KeyType: RANGE Projection: - ProjectionType: INCLUDE + ProjectionType: ALL ProvisionedThroughput: ReadCapacityUnits: 1 WriteCapacityUnits: 1 From fdff8519e898be5c68c6f483d7e914e5419649bc Mon Sep 17 00:00:00 2001 From: jacksoncharles Date: Sat, 9 Dec 2017 19:25:08 +0000 Subject: [PATCH 18/78] replyList changes --- api/_classes/ReplyQueryBuilder.js | 191 ++++++++++++++++++++++++++++++ api/replies/replyList.js | 160 +++---------------------- 2 files changed, 207 insertions(+), 144 deletions(-) create mode 100644 api/_classes/ReplyQueryBuilder.js diff --git a/api/_classes/ReplyQueryBuilder.js b/api/_classes/ReplyQueryBuilder.js new file mode 100644 index 0000000..e35fb68 --- /dev/null +++ b/api/_classes/ReplyQueryBuilder.js @@ -0,0 +1,191 @@ +'use strict'; + +const validator = require('validator'); + +/** + * Responsible for turning parameters passeed are turned in DynamoDb parameters by building + * this.parameters object using data passed inside this.events.queryStringParameters + * + * @type {class} + */ +module.exports = class ReplyQueryBuilder { + + constructor( event ) { + + this._event = event; + + /** + * Used to hold the dynamodb query parameters built using values + * within property this.event + * + * @todo : Change TableName to value of process.env.DYNAMODB_REPLY_TABLE + * + * @type {object} + */ + this._parameters = { + TableName: 'Reply' + } + + /** + * Used to hold any validation errors. + * + * @type {array} + */ + this._errors = []; + } + + /** + * Getter + * + * @return {object} parameters + */ + get parameters() { + + return this._parameters; + } + + /** + * Setter + * + * @return {object} parameters + */ + set parameters( parameters ) { + + this._parameters = parameters; + } + + /** + * Getter + * + * @return {array} errors + */ + get errors() { + + return this._errors; + } + + /** + * Setter + * + * @return {array} errors + */ + set errors( errors ) { + + this._errors = errors; + } + + /** + * Validates the parameters passed inside this.events object + * + * @return {boolean} + */ + validates() { + + this._errors = []; // Reset the errors array before running thr logic + + if ( this._event.hasOwnProperty( 'queryStringParameters' ) ) { + + if( this._event.queryStringParameters.hasOwnProperty( 'threadid' ) == false && + this._event.queryStringParameters.hasOwnProperty( 'userid' ) == false + ) { + + this._errors.push( new Error( 'You must provide a threadid or userid parameter' ) ); + } + + if( this._event.queryStringParameters.hasOwnProperty( 'threadid' ) ) { + + if ( validator.isAlphanumeric( this._event.queryStringParameters.threadid ) == false ) { + + this._errors.push( new Error( 'Your threadid parameter must be an alphanumeric string' ) ); + } + } + + if( this._event.queryStringParameters.hasOwnProperty('userid') ) { + + if ( validator.isNumeric( this._event.queryStringParameters.userid ) == false ) { + + this._errors.push( new Error( 'Your userid parameter must be numeric' ) ); + } + } + + } else { + + this._errors.push( new Error('You must provide a threadid or userid parameter') ); + } + + return this._errors.length > 0 ? 0 : 1; + } + + /** + * If "threadid" has been passed inside this.event this method will build upon this.parameters object + * + * @return this + */ + buildThreadIndex() { + + if( this._event.queryStringParameters.hasOwnProperty('threadid') ) { + + this._parameters['IndexName'] = "ThreadIndex"; + this._parameters['KeyConditionExpression'] = "ThreadId = :searchstring"; + this._parameters['ExpressionAttributeValues'] = { + ":searchstring" : this._event.queryStringParameters.threadid + }; + } + + return this; + } + + /** + * If "userid" has been passed inside this.event this method will build upon this.parameters object + * + * @return this + */ + buildUserIndex() { + + if ( this._event.queryStringParameters.hasOwnProperty('userid') ) { + + this._parameters['IndexName'] = "UserIndex"; + this._parameters['KeyConditionExpression'] = "UserId = :searchstring"; + this._parameters['ExpressionAttributeValues'] = { + ":searchstring" : this._event.queryStringParameters.userid + }; + } + + return this; + } + + /** + * If pagination parameters have been passed inside this.event this method will build upon this.parameters object + * + * @return this + */ + buildPagination() { + + if ( this._event.queryStringParameters.hasOwnProperty('threadid') && + this._event.queryStringParameters.hasOwnProperty('createddatetime') ) + { + + this._parameters['ExclusiveStartKey'] = { + ThreadId: this._event.queryStringParameters.threadid, + DateTime: this._event.queryStringParameters.createddatetime + } + } + + return this; + } + + /** + * Set a value for "Limit" with any value passed by the query string. + * + * @return void + */ + buildLimit() { + + if ( this._event.queryStringParameters.hasOwnProperty('limit') ) { + + this._parameters['Limit'] = this._event.queryStringParameters.limit; + } + + return this; + } +} \ No newline at end of file diff --git a/api/replies/replyList.js b/api/replies/replyList.js index 7c6f7d9..5818a14 100644 --- a/api/replies/replyList.js +++ b/api/replies/replyList.js @@ -1,8 +1,9 @@ 'use strict'; -const AWS = require('aws-sdk'); // eslint-disable-line import/no-extraneous-dependencies +const AWS = require('aws-sdk'); const dynamoDb = new AWS.DynamoDB.DocumentClient(); -const validator = require('validator'); + +var ReplyQueryBuilder = require("./../_classes/ReplyQueryBuilder"); /** * Handler for the lambda function. @@ -15,155 +16,25 @@ const validator = require('validator'); */ module.exports.replyList = (event, context, callback) => { - let e = event; - - /** - * QueryBuilder object used to build this.parameters. - * - * @param {Object} event - AWS Lambda uses this parameter to pass in event data to the handler. - * - * @constructor - */ - function QueryBuilder() { - - /** - * Used to hold the dynamodb query parameters built using values - * within property this.event - * - * @todo : Change TableName to value of process.env.DYNAMODB_REPLY_TABLE - * - * @type Object - */ - this.parameters = { - TableName: 'Reply' - } - - /** - * Used to hold any validation errors. - * - * @type {Array} - */ - this.errors = []; - }; - - /** - * Validates the parameters passed - * - * @return {boolean} - */ - QueryBuilder.prototype.validates = function() { - - this.errors = []; - - if ( e.hasOwnProperty('queryStringParameters') ) { - - if ( validator.isAlphanumeric(e.queryStringParameters.threadid) == false ) { - this.errors.push( new Error('threadid must be an alphanumeric string') ); - } - - if ( validator.isNumeric(e.queryStringParameters.userid) == false ) { - this.errors.push( new Error('userid must be numeric') ); - } - - } else { - - this.errors.push( new Error('No query string parameters passed, threadid or userid required') ); - } - - return this.errors.length ? 0 : 1; - } - - /** - * If "threadid" has been passed as parameter this method will build the query. - * - * @return this - */ - QueryBuilder.prototype.setThreadIndex = function() { - - if( e.queryStringParameters.hasOwnProperty('threadid') ) { - - this.parameters['IndexName'] = "ThreadIndex"; - this.parameters['KeyConditionExpression'] = "ThreadId = :searchstring"; - this.parameters['ExpressionAttributeValues'] = { - ":searchstring" : e.queryStringParameters.threadid - }; - } - - return this; - } - - /** - * If "userid" has been passed as parameter this method will build the query. - * - * @return this - */ - QueryBuilder.prototype.setUserIndex = function() { - - if ( e.queryStringParameters.hasOwnProperty('userid') ) { - - this.parameters['IndexName'] = "UserIndex"; - this.parameters['KeyConditionExpression'] = "UserId = :searchstring"; - this.parameters['ExpressionAttributeValues'] = { - ":searchstring" : e.queryStringParameters.userid - }; - } - - return this; - } - - /** - * If there are any pagination parameters set them here by updating the value of - * parameters object property - * - * @return this - */ - QueryBuilder.prototype.setPagination = function() { - - // Pagination - if ( e.queryStringParameters.hasOwnProperty('threadid') && e.queryStringParameters.hasOwnProperty('createddatetime') ) { - - this.parameters['ExclusiveStartKey'] = { - ThreadId: e.queryStringParameters.threadid, - DateTime: e.queryStringParameters.createddatetime - } - } - - return this; - } - - /** - * Set a value for "Limit" with any value passed by the query string. - * - * @return void - */ - QueryBuilder.prototype.setLimit = function() { - - if ( e.queryStringParameters.hasOwnProperty('limit') ) { - - this.parameters['Limit'] = e.queryStringParameters.limit; - } - - return this; - } - /** * Instantiate an instance of QueryBuilder * * @type {QueryBuilder} */ - let Query = new QueryBuilder(); + let Query = new ReplyQueryBuilder( event ); if ( Query.validates() ) { Query - .setThreadIndex() - .setUserIndex() - .setPagination() - .setLimit(); + .buildThreadIndex() + .buildUserIndex() + .buildPagination() + .buildLimit(); + /** Run a dynamoDb query passing-in Query.parameters */ dynamoDb.query( Query.parameters, function( error, data ) { - // Handle potential errors + /** Handle potential dynamoDb errors */ if (error) { console.log('=== dynamodb validation error ===', JSON.stringify( error )); @@ -173,22 +44,23 @@ module.exports.replyList = (event, context, callback) => { } else { - // Create a response + /** All successful. Create a valid response */ const response = { statusCode: 200, - body: JSON.stringify(data), + body: JSON.stringify( data ), }; - callback(null, response); + callback( null, response ); } }) } else { - // Handle validation errors + /** Handle validation errors generated by ReplyQueryBuiler using arameters passed + inside the event object */ callback(null, { statusCode: 422, - body: JSON.stringify(Query.errors) + body: JSON.stringify( Query.errors ) }); } }; \ No newline at end of file From c37376ad89fabdfe87aa8cf2765a28fa0825e358 Mon Sep 17 00:00:00 2001 From: jacksoncharles Date: Sat, 9 Dec 2017 19:41:07 +0000 Subject: [PATCH 19/78] replyList / class changes --- api/_classes/ReplyQueryBuilder.js | 64 +++++++++++++++---------------- api/replies/replyList.js | 2 +- 2 files changed, 32 insertions(+), 34 deletions(-) diff --git a/api/_classes/ReplyQueryBuilder.js b/api/_classes/ReplyQueryBuilder.js index e35fb68..3b32372 100644 --- a/api/_classes/ReplyQueryBuilder.js +++ b/api/_classes/ReplyQueryBuilder.js @@ -10,9 +10,14 @@ const validator = require('validator'); */ module.exports = class ReplyQueryBuilder { - constructor( event ) { + constructor( criterion ) { - this._event = event; + /** + * Key/value pairs used to build our DynamoDb parameters. + * + * @type {object} + */ + this._criterion = criterion; /** * Used to hold the dynamodb query parameters built using values @@ -83,36 +88,29 @@ module.exports = class ReplyQueryBuilder { this._errors = []; // Reset the errors array before running thr logic - if ( this._event.hasOwnProperty( 'queryStringParameters' ) ) { - - if( this._event.queryStringParameters.hasOwnProperty( 'threadid' ) == false && - this._event.queryStringParameters.hasOwnProperty( 'userid' ) == false - ) { + if( this._criterion.hasOwnProperty( 'threadid' ) == false && + this._criterion.hasOwnProperty( 'userid' ) == false + ) { - this._errors.push( new Error( 'You must provide a threadid or userid parameter' ) ); - } + this._errors.push( new Error( 'You must provide a threadid or userid parameter' ) ); + } - if( this._event.queryStringParameters.hasOwnProperty( 'threadid' ) ) { + if( this_criterion.hasOwnProperty( 'threadid' ) ) { - if ( validator.isAlphanumeric( this._event.queryStringParameters.threadid ) == false ) { + if ( validator.isAlphanumeric( this._criterion.threadid ) == false ) { - this._errors.push( new Error( 'Your threadid parameter must be an alphanumeric string' ) ); - } + this._errors.push( new Error( 'Your threadid parameter must be an alphanumeric string' ) ); } - - if( this._event.queryStringParameters.hasOwnProperty('userid') ) { + } + + if( this._criterion.hasOwnProperty('userid') ) { - if ( validator.isNumeric( this._event.queryStringParameters.userid ) == false ) { + if ( validator.isNumeric( this._criterion.userid ) == false ) { - this._errors.push( new Error( 'Your userid parameter must be numeric' ) ); - } + this._errors.push( new Error( 'Your userid parameter must be numeric' ) ); } - - } else { - - this._errors.push( new Error('You must provide a threadid or userid parameter') ); } - + return this._errors.length > 0 ? 0 : 1; } @@ -123,12 +121,12 @@ module.exports = class ReplyQueryBuilder { */ buildThreadIndex() { - if( this._event.queryStringParameters.hasOwnProperty('threadid') ) { + if( this._criterion.hasOwnProperty('threadid') ) { this._parameters['IndexName'] = "ThreadIndex"; this._parameters['KeyConditionExpression'] = "ThreadId = :searchstring"; this._parameters['ExpressionAttributeValues'] = { - ":searchstring" : this._event.queryStringParameters.threadid + ":searchstring" : this._criterion.threadid }; } @@ -142,12 +140,12 @@ module.exports = class ReplyQueryBuilder { */ buildUserIndex() { - if ( this._event.queryStringParameters.hasOwnProperty('userid') ) { + if ( this._criterion.hasOwnProperty('userid') ) { this._parameters['IndexName'] = "UserIndex"; this._parameters['KeyConditionExpression'] = "UserId = :searchstring"; this._parameters['ExpressionAttributeValues'] = { - ":searchstring" : this._event.queryStringParameters.userid + ":searchstring" : this._criterion.userid }; } @@ -161,13 +159,13 @@ module.exports = class ReplyQueryBuilder { */ buildPagination() { - if ( this._event.queryStringParameters.hasOwnProperty('threadid') && - this._event.queryStringParameters.hasOwnProperty('createddatetime') ) + if ( this._criterion.hasOwnProperty('threadid') && + this._criterion.hasOwnProperty('createddatetime') ) { this._parameters['ExclusiveStartKey'] = { - ThreadId: this._event.queryStringParameters.threadid, - DateTime: this._event.queryStringParameters.createddatetime + ThreadId: this._criterion.threadid, + DateTime: this._criterion.createddatetime } } @@ -181,9 +179,9 @@ module.exports = class ReplyQueryBuilder { */ buildLimit() { - if ( this._event.queryStringParameters.hasOwnProperty('limit') ) { + if ( this._criterion.hasOwnProperty('limit') ) { - this._parameters['Limit'] = this._event.queryStringParameters.limit; + this._parameters['Limit'] = this._criterion.limit; } return this; diff --git a/api/replies/replyList.js b/api/replies/replyList.js index 5818a14..2a768aa 100644 --- a/api/replies/replyList.js +++ b/api/replies/replyList.js @@ -21,7 +21,7 @@ module.exports.replyList = (event, context, callback) => { * * @type {QueryBuilder} */ - let Query = new ReplyQueryBuilder( event ); + let Query = new ReplyQueryBuilder( event.queryStringParameters ); if ( Query.validates() ) { From 340e9ea5033177f7f0cb3a1d51fa3c63eacbd64f Mon Sep 17 00:00:00 2001 From: jacksoncharles Date: Sat, 9 Dec 2017 23:15:11 +0000 Subject: [PATCH 20/78] polish replyList --- api/_classes/DynamodbError.js | 8 +++ api/_classes/ValidationError.js | 8 +++ .../_classes/ReplyQueryBuilder.js | 58 ++++++++++++------- api/replies/replyList.js | 54 ++++++++++++----- 4 files changed, 92 insertions(+), 36 deletions(-) create mode 100644 api/_classes/DynamodbError.js create mode 100644 api/_classes/ValidationError.js rename api/{ => replies}/_classes/ReplyQueryBuilder.js (73%) diff --git a/api/_classes/DynamodbError.js b/api/_classes/DynamodbError.js new file mode 100644 index 0000000..52733b7 --- /dev/null +++ b/api/_classes/DynamodbError.js @@ -0,0 +1,8 @@ +'use strict'; + +/** + * + * + * @type {class} + */ +module.exports = class DynamodbError extends Error {} \ No newline at end of file diff --git a/api/_classes/ValidationError.js b/api/_classes/ValidationError.js new file mode 100644 index 0000000..2916c14 --- /dev/null +++ b/api/_classes/ValidationError.js @@ -0,0 +1,8 @@ +'use strict'; + +/** + * + * + * @type {class} + */ +module.exports = class ValidationError extends Error {} \ No newline at end of file diff --git a/api/_classes/ReplyQueryBuilder.js b/api/replies/_classes/ReplyQueryBuilder.js similarity index 73% rename from api/_classes/ReplyQueryBuilder.js rename to api/replies/_classes/ReplyQueryBuilder.js index 3b32372..6ebffd3 100644 --- a/api/_classes/ReplyQueryBuilder.js +++ b/api/replies/_classes/ReplyQueryBuilder.js @@ -1,6 +1,7 @@ 'use strict'; const validator = require('validator'); +var ValidationError = require("./../../_classes/ValidationError"); /** * Responsible for turning parameters passeed are turned in DynamoDb parameters by building @@ -79,39 +80,53 @@ module.exports = class ReplyQueryBuilder { this._errors = errors; } - /** - * Validates the parameters passed inside this.events object - * - * @return {boolean} - */ + /** + * Validates the parameters passed inside this.events object + * + * @return {boolean} + */ validates() { this._errors = []; // Reset the errors array before running thr logic - if( this._criterion.hasOwnProperty( 'threadid' ) == false && - this._criterion.hasOwnProperty( 'userid' ) == false - ) { + if( this._criterion !== null && typeof this._criterion === 'object' ) { - this._errors.push( new Error( 'You must provide a threadid or userid parameter' ) ); - } + if( this._criterion.hasOwnProperty( 'threadid' ) == false && + this._criterion.hasOwnProperty( 'userid' ) == false + ) { - if( this_criterion.hasOwnProperty( 'threadid' ) ) { + this._errors.push( { "message": "You must provide a threadid or userid parameter" } ); + } - if ( validator.isAlphanumeric( this._criterion.threadid ) == false ) { + if( this._criterion.hasOwnProperty( 'threadid' ) ) { - this._errors.push( new Error( 'Your threadid parameter must be an alphanumeric string' ) ); - } - } - - if( this._criterion.hasOwnProperty('userid') ) { + if ( validator.isAlphanumeric( this._criterion.threadid ) == false ) { - if ( validator.isNumeric( this._criterion.userid ) == false ) { + this._errors.push( { "message": "Your threadid parameter must be an alphanumeric string" } ); + } + } - this._errors.push( new Error( 'Your userid parameter must be numeric' ) ); - } + if( this._criterion.hasOwnProperty('userid') ) { + + if ( validator.isNumeric( this._criterion.userid ) == false ) { + + this._errors.push( { "message": "Your userid parameter must be numeric" } ); + } + } } + else { + + this._errors.push( { "message" : "You must supply a threadid or userid" } ); + } + + if( this._errors.length ) { + + throw new ValidationError( JSON.stringify( this._errors ) ); - return this._errors.length > 0 ? 0 : 1; + return; + } + + return this; } /** @@ -162,7 +177,6 @@ module.exports = class ReplyQueryBuilder { if ( this._criterion.hasOwnProperty('threadid') && this._criterion.hasOwnProperty('createddatetime') ) { - this._parameters['ExclusiveStartKey'] = { ThreadId: this._criterion.threadid, DateTime: this._criterion.createddatetime diff --git a/api/replies/replyList.js b/api/replies/replyList.js index 2a768aa..1cc2080 100644 --- a/api/replies/replyList.js +++ b/api/replies/replyList.js @@ -3,7 +3,9 @@ const AWS = require('aws-sdk'); const dynamoDb = new AWS.DynamoDB.DocumentClient(); -var ReplyQueryBuilder = require("./../_classes/ReplyQueryBuilder"); +var ReplyQueryBuilder = require("./_classes/ReplyQueryBuilder"); +var ValidationError = require("./../_classes/ValidationError"); +var DynamodbError = require("./../_classes/DynamodbError"); /** * Handler for the lambda function. @@ -23,9 +25,10 @@ module.exports.replyList = (event, context, callback) => { */ let Query = new ReplyQueryBuilder( event.queryStringParameters ); - if ( Query.validates() ) { + try { Query + .validates() .buildThreadIndex() .buildUserIndex() .buildPagination() @@ -37,30 +40,53 @@ module.exports.replyList = (event, context, callback) => { /** Handle potential dynamoDb errors */ if (error) { - console.log('=== dynamodb validation error ===', JSON.stringify( error )); - callback(null, JSON.stringify( error ) ); + console.log('***error***', error ); - return; + throw new DynamodbError(error); } else { /** All successful. Create a valid response */ + + /** @type {number} return the correct http status code */ + let statusCode = data.length > 0 ? 204 : 200 + const response = { - statusCode: 200, + statusCode: statusCode, body: JSON.stringify( data ), }; callback( null, response ); } - }) + }); } - else { + catch( e ) { - /** Handle validation errors generated by ReplyQueryBuiler using arameters passed - inside the event object */ - callback(null, { - statusCode: 422, - body: JSON.stringify( Query.errors ) - }); + if( e instanceof ValidationError ) { + + callback(null, { + statusCode: 422, + body: JSON.stringify( Query.errors ) + }); + + } else if ( e instanceof DynamodbError ) { + + console.log('<<>>', e ); + + callback(null, { + statusCode: 500, + body: JSON.stringify( e ) + }); + + } else { + + console.log('<<>>', e ); + + callback(null, { + statusCode: 500, + body: JSON.stringify( e ) + }); + + } } }; \ No newline at end of file From 6996ed7fd1a36db55ef71b4fff4ba863e7666bb3 Mon Sep 17 00:00:00 2001 From: jacksoncharles Date: Sat, 9 Dec 2017 23:18:34 +0000 Subject: [PATCH 21/78] polish replyList --- api/replies/replyList.js | 27 ++++++++++----------------- 1 file changed, 10 insertions(+), 17 deletions(-) diff --git a/api/replies/replyList.js b/api/replies/replyList.js index 1cc2080..2e50842 100644 --- a/api/replies/replyList.js +++ b/api/replies/replyList.js @@ -38,26 +38,19 @@ module.exports.replyList = (event, context, callback) => { dynamoDb.query( Query.parameters, function( error, data ) { /** Handle potential dynamoDb errors */ - if (error) { + if (error) throw new DynamodbError(error); - console.log('***error***', error ); + /** All successful. Create a valid response */ - throw new DynamodbError(error); - } - else { + /** @type {number} return the correct http status code */ + let statusCode = data.length > 0 ? 204 : 200 + + const response = { + statusCode: statusCode, + body: JSON.stringify( data ), + }; - /** All successful. Create a valid response */ - - /** @type {number} return the correct http status code */ - let statusCode = data.length > 0 ? 204 : 200 - - const response = { - statusCode: statusCode, - body: JSON.stringify( data ), - }; - - callback( null, response ); - } + callback( null, response ); }); } catch( e ) { From fa2ef523d758edc13601a629469c3f646645466e Mon Sep 17 00:00:00 2001 From: jacksoncharles Date: Sat, 9 Dec 2017 23:20:24 +0000 Subject: [PATCH 22/78] polish replyList --- api/replies/replyList.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/api/replies/replyList.js b/api/replies/replyList.js index 2e50842..8184d15 100644 --- a/api/replies/replyList.js +++ b/api/replies/replyList.js @@ -43,7 +43,7 @@ module.exports.replyList = (event, context, callback) => { /** All successful. Create a valid response */ /** @type {number} return the correct http status code */ - let statusCode = data.length > 0 ? 204 : 200 + let statusCode = data.length > 0 ? 200 : 204 const response = { statusCode: statusCode, From b21c0a38bb4f414446c01fcfe16d08258e2d55f3 Mon Sep 17 00:00:00 2001 From: jacksoncharles Date: Mon, 11 Dec 2017 12:34:11 +0000 Subject: [PATCH 23/78] refactored to use error class --- api/replies/_classes/ReplyQueryBuilder.js | 18 ++------ api/replies/_models/Reply.js | 35 +++++++++++++++ api/replies/replyList.js | 54 ++++++++++------------- 3 files changed, 62 insertions(+), 45 deletions(-) create mode 100644 api/replies/_models/Reply.js diff --git a/api/replies/_classes/ReplyQueryBuilder.js b/api/replies/_classes/ReplyQueryBuilder.js index 6ebffd3..d0e8625 100644 --- a/api/replies/_classes/ReplyQueryBuilder.js +++ b/api/replies/_classes/ReplyQueryBuilder.js @@ -81,13 +81,13 @@ module.exports = class ReplyQueryBuilder { } /** - * Validates the parameters passed inside this.events object + * Validates the parameters passed inside this._criterion object * * @return {boolean} */ validates() { - this._errors = []; // Reset the errors array before running thr logic + this._errors = []; // Reset the errors array before running the validation logic if( this._criterion !== null && typeof this._criterion === 'object' ) { @@ -122,8 +122,6 @@ module.exports = class ReplyQueryBuilder { if( this._errors.length ) { throw new ValidationError( JSON.stringify( this._errors ) ); - - return; } return this; @@ -183,21 +181,11 @@ module.exports = class ReplyQueryBuilder { } } - return this; - } - - /** - * Set a value for "Limit" with any value passed by the query string. - * - * @return void - */ - buildLimit() { - if ( this._criterion.hasOwnProperty('limit') ) { this._parameters['Limit'] = this._criterion.limit; } return this; - } + } } \ No newline at end of file diff --git a/api/replies/_models/Reply.js b/api/replies/_models/Reply.js new file mode 100644 index 0000000..f58bcfe --- /dev/null +++ b/api/replies/_models/Reply.js @@ -0,0 +1,35 @@ +'use strict'; + +const AWS = require('aws-sdk'); +const dynamoDb = new AWS.DynamoDB.DocumentClient(); + +var DynamodbError = require("./../../_classes/DynamodbError"); + +/** + * + * + * @type {class} + */ +module.exports = class Reply { + + /** + * Retrieve an array of replies according to the parameters passed + * + * @return {array} Array of replies + */ + static list( parameters ) { + + return new Promise( function( resolve, reject ) { + + /** Run a dynamoDb query passing-in Query.parameters */ + return dynamoDb.query( parameters, function( error, data ) { + + /** Handle potential dynamoDb errors */ + if ( error ) return reject( error ); + + /** All successful. Create a valid response */ + return resolve( JSON.stringify( data ) ); + }); + }); + } +} \ No newline at end of file diff --git a/api/replies/replyList.js b/api/replies/replyList.js index 8184d15..5186f68 100644 --- a/api/replies/replyList.js +++ b/api/replies/replyList.js @@ -1,12 +1,11 @@ 'use strict'; -const AWS = require('aws-sdk'); -const dynamoDb = new AWS.DynamoDB.DocumentClient(); - var ReplyQueryBuilder = require("./_classes/ReplyQueryBuilder"); var ValidationError = require("./../_classes/ValidationError"); var DynamodbError = require("./../_classes/DynamodbError"); +var Reply = require("./_models/Reply"); + /** * Handler for the lambda function. * @@ -31,53 +30,48 @@ module.exports.replyList = (event, context, callback) => { .validates() .buildThreadIndex() .buildUserIndex() - .buildPagination() - .buildLimit(); - - /** Run a dynamoDb query passing-in Query.parameters */ - dynamoDb.query( Query.parameters, function( error, data ) { + .buildPagination(); - /** Handle potential dynamoDb errors */ - if (error) throw new DynamodbError(error); + /** @type {model} Contains a list of items and optional pagination data */ + Reply.list( Query.parameters ) + .then( ( replies ) => { - /** All successful. Create a valid response */ - - /** @type {number} return the correct http status code */ - let statusCode = data.length > 0 ? 200 : 204 - const response = { - statusCode: statusCode, - body: JSON.stringify( data ), + statusCode: ( replies.length > 0 ? 200 : 204 ), + body: replies }; - callback( null, response ); - }); - } - catch( e ) { + return callback( null, response ); + }) + .catch( ( error ) => { - if( e instanceof ValidationError ) { + console.log('<<>>', error ); callback(null, { - statusCode: 422, - body: JSON.stringify( Query.errors ) + statusCode: 500, + body: JSON.stringify( error ) }); - } else if ( e instanceof DynamodbError ) { + }); + } + catch( error ) { + + if( error instanceof ValidationError ) { - console.log('<<>>', e ); + console.log('=== the error is ===', error ); callback(null, { - statusCode: 500, - body: JSON.stringify( e ) + statusCode: 422, + body: error.message }); } else { - console.log('<<>>', e ); + console.log('<<>>', error ); callback(null, { statusCode: 500, - body: JSON.stringify( e ) + body: JSON.stringify( error ) }); } From c326f57721f5cfbb18a0195c4b874469824a24d0 Mon Sep 17 00:00:00 2001 From: jacksoncharles Date: Mon, 11 Dec 2017 15:05:16 +0000 Subject: [PATCH 24/78] refactoring --- api/replies/_models/Reply.js | 11 +++++++++-- api/replies/replyList.js | 16 ++++++---------- 2 files changed, 15 insertions(+), 12 deletions(-) diff --git a/api/replies/_models/Reply.js b/api/replies/_models/Reply.js index f58bcfe..86a65df 100644 --- a/api/replies/_models/Reply.js +++ b/api/replies/_models/Reply.js @@ -29,7 +29,14 @@ module.exports = class Reply { /** All successful. Create a valid response */ return resolve( JSON.stringify( data ) ); - }); - }); + }); + + }) + .catch( function( error ) { + + console.log('<<>>', error ); + + throw new DynamodbError( error ); + }); } } \ No newline at end of file diff --git a/api/replies/replyList.js b/api/replies/replyList.js index 5186f68..8d5b705 100644 --- a/api/replies/replyList.js +++ b/api/replies/replyList.js @@ -41,25 +41,22 @@ module.exports.replyList = (event, context, callback) => { body: replies }; - return callback( null, response ); + return callback( null, response ); }) - .catch( ( error ) => { - - console.log('<<>>', error ); + .catch( function( error ) { callback(null, { statusCode: 500, - body: JSON.stringify( error ) + body: JSON.stringify( { message: error.message } ) }); - }); + }); } - catch( error ) { + catch( error ) { + // All error handling performed here if( error instanceof ValidationError ) { - console.log('=== the error is ===', error ); - callback(null, { statusCode: 422, body: error.message @@ -73,7 +70,6 @@ module.exports.replyList = (event, context, callback) => { statusCode: 500, body: JSON.stringify( error ) }); - } } }; \ No newline at end of file From b6784316561df625556a83acb5545040f5f1a041 Mon Sep 17 00:00:00 2001 From: jacksoncharles Date: Mon, 11 Dec 2017 16:34:52 +0000 Subject: [PATCH 25/78] replyGet --- api/replies/_models/Reply.js | 78 +++++++++++++++++++++++++++++++++++- api/replies/replyDelete.js | 51 ++++++++--------------- api/replies/replyGet.js | 51 ++++++++--------------- api/replies/replyList.js | 1 + serverless.yml | 3 -- 5 files changed, 111 insertions(+), 73 deletions(-) diff --git a/api/replies/_models/Reply.js b/api/replies/_models/Reply.js index 86a65df..e056372 100644 --- a/api/replies/_models/Reply.js +++ b/api/replies/_models/Reply.js @@ -5,6 +5,8 @@ const dynamoDb = new AWS.DynamoDB.DocumentClient(); var DynamodbError = require("./../../_classes/DynamodbError"); +const Table = 'Reply'; + /** * * @@ -12,6 +14,80 @@ var DynamodbError = require("./../../_classes/DynamodbError"); */ module.exports = class Reply { + /** + * Retrieve an array of replies according to the parameters passed + * + * @return {array} Array of replies + */ + static destory( id ) { + + return new Promise( function( resolve, reject ) { + + /** @type {Object} Holds the parameters for the get request */ + const parameters = { + + TableName : Table, + Key : { + Id : id + } + } + + /** Run a dynamoDb get request passing-in our parameters */ + return dynamoDb.delete( parameters, function( error, data ) { + + /** Handle potential dynamoDb errors */ + if ( error ) return reject( error ); + + /** All successful. Create a valid response */ + return resolve( JSON.stringify( data ) ); + }); + + }) + .catch( function( error ) { + + console.log('<<>>', error ); + + throw new DynamodbError( error ); + }); + } + + /** + * Retrieve an array of replies according to the parameters passed + * + * @return {array} Array of replies + */ + static find( id ) { + + return new Promise( function( resolve, reject ) { + + /** @type {Object} Holds the parameters for the get request */ + const parameters = { + + TableName : Table, + Key : { + Id : id + } + } + + /** Run a dynamoDb get request passing-in our parameters */ + return dynamoDb.get( parameters, function( error, data ) { + + /** Handle potential dynamoDb errors */ + if ( error ) return reject( error ); + + /** All successful. Create a valid response */ + return resolve( JSON.stringify( data ) ); + }); + + }) + .catch( function( error ) { + + console.log('<<>>', error ); + + throw new DynamodbError( error ); + }); + } + /** * Retrieve an array of replies according to the parameters passed * @@ -35,7 +111,7 @@ module.exports = class Reply { .catch( function( error ) { console.log('<<>>', error ); - + throw new DynamodbError( error ); }); } diff --git a/api/replies/replyDelete.js b/api/replies/replyDelete.js index e5e47b1..039b2e2 100644 --- a/api/replies/replyDelete.js +++ b/api/replies/replyDelete.js @@ -1,7 +1,8 @@ 'use strict'; -const AWS = require('aws-sdk'); // eslint-disable-line import/no-extraneous-dependencies -const dynamoDb = new AWS.DynamoDB.DocumentClient(); +var DynamodbError = require("./../_classes/DynamodbError"); + +var Reply = require("./_models/Reply"); /** * Handler for the lambda function. @@ -13,43 +14,25 @@ const dynamoDb = new AWS.DynamoDB.DocumentClient(); * @return JSON JSON encoded response. */ module.exports.replyDelete = (event, context, callback) => { - - /** - * The parameters used by DynamoDb - * - * @type {Object} - */ - const parameters = { - TableName: 'Reply', - Key: { - id: event.pathParameters.id, - } - }; - - // Run the query to remove the item from permenent storage - dynamoDb.delete(parameters, (error) => { - // Handle any potential DynamoDb errors - if (error) { + Reply.delete( event.pathParameters.id ) + .then( ( reply ) => { - console.error('=== error ===', error); + const response = { + statusCode: 204, + body: reply + } - callback(null, { - statusCode: error.statusCode || 501, - headers: { 'Content-Type': 'text/plain' }, - body: 'Couldn\'t remove the post item.', - }); + return callback( null, response ); + }) + .catch( function( error ) { - } - else { + console.log('<<>>', error ); - // Create a successful response - const response = { - statusCode: 204, - body: JSON.stringify({}), - }; + callback(null, { + statusCode: 500, + body: JSON.stringify( { message: error.message } ) + }); - callback(null, response); - } }); }; \ No newline at end of file diff --git a/api/replies/replyGet.js b/api/replies/replyGet.js index b4d6c65..84088b9 100644 --- a/api/replies/replyGet.js +++ b/api/replies/replyGet.js @@ -1,7 +1,8 @@ 'use strict'; -const AWS = require('aws-sdk'); // eslint-disable-line import/no-extraneous-dependencies -const dynamoDb = new AWS.DynamoDB.DocumentClient(); +var DynamodbError = require("./../_classes/DynamodbError"); + +var Reply = require("./_models/Reply"); /** * Handler for the lambda function. @@ -14,44 +15,24 @@ const dynamoDb = new AWS.DynamoDB.DocumentClient(); */ module.exports.replyGet = (event, context, callback) => { - /** - * The parameters needed by DynamoDb - * - * @type {Object} - */ - const parameters = { + Reply.find( event.pathParameters.id ) + .then( ( reply ) => { - TableName: 'Reply', - Key: { - Id: event.pathParameters.id + const response = { + statusCode: 200, + body: reply } - } - - // Run the query to retrieve the Item from permanent storage - dynamoDb.get(Query.parameters, (error, result) => { - - // Handle any potential DynamoDb errors - if (error) { - - console.error('=== error ===', error); - callback(null, { - statusCode: error.statusCode || 501, - headers: { 'Content-Type': 'text/plain' }, - body: 'Couldn\'t fetch the post item.', - }); + return callback( null, response ); + }) + .catch( function( error ) { - } - else { - - // create a response - const response = { + console.log('<<>>', error ); - statusCode: 200, - body: JSON.stringify(result.Item), - }; + callback(null, { + statusCode: 500, + body: JSON.stringify( { message: error.message } ) + }); - callback(null, response); - } }); }; \ No newline at end of file diff --git a/api/replies/replyList.js b/api/replies/replyList.js index 8d5b705..0ec9461 100644 --- a/api/replies/replyList.js +++ b/api/replies/replyList.js @@ -1,6 +1,7 @@ 'use strict'; var ReplyQueryBuilder = require("./_classes/ReplyQueryBuilder"); + var ValidationError = require("./../_classes/ValidationError"); var DynamodbError = require("./../_classes/DynamodbError"); diff --git a/serverless.yml b/serverless.yml index 9b2b2a1..21dadbb 100644 --- a/serverless.yml +++ b/serverless.yml @@ -287,9 +287,6 @@ resources: - AttributeName: "Id" KeyType: "HASH" - - - AttributeName: "CreatedDateTime" - KeyType: "RANGE" GlobalSecondaryIndexes: - IndexName: ThreadIndex From eaab76e9eee8d8fc0da390ba9fe9fd4a934ac40d Mon Sep 17 00:00:00 2001 From: jacksoncharles Date: Mon, 11 Dec 2017 22:08:36 +0000 Subject: [PATCH 26/78] refactor to service --- api/{_classes => _errors}/DynamodbError.js | 0 api/{_classes => _errors}/ValidationError.js | 0 api/_services/DynamodbService.js | 98 ++++++++++++++ api/replies/_classes/ReplyQueryBuilder.js | 10 +- api/replies/_models/Reply.js | 132 ++++--------------- api/replies/replyDelete.js | 15 ++- api/replies/replyGet.js | 14 +- api/replies/replyList.js | 15 ++- 8 files changed, 159 insertions(+), 125 deletions(-) rename api/{_classes => _errors}/DynamodbError.js (100%) rename api/{_classes => _errors}/ValidationError.js (100%) create mode 100644 api/_services/DynamodbService.js diff --git a/api/_classes/DynamodbError.js b/api/_errors/DynamodbError.js similarity index 100% rename from api/_classes/DynamodbError.js rename to api/_errors/DynamodbError.js diff --git a/api/_classes/ValidationError.js b/api/_errors/ValidationError.js similarity index 100% rename from api/_classes/ValidationError.js rename to api/_errors/ValidationError.js diff --git a/api/_services/DynamodbService.js b/api/_services/DynamodbService.js new file mode 100644 index 0000000..10517f5 --- /dev/null +++ b/api/_services/DynamodbService.js @@ -0,0 +1,98 @@ +'use strict'; + +const AWS = require('aws-sdk'); +const dynamoDb = new AWS.DynamoDB.DocumentClient(); + +var DynamodbError = require('./../_errors/DynamodbError'); + +/** + * + * + * @type {class} + */ +module.exports = class DynamodbService { + + /** + * Retrieve an array of replies according to the parameters passed + * + * @return {Array} Array of replies. + */ + static destroy( parameters ) { + + return new Promise( function( resolve, reject ) { + + /** Run a dynamoDb get request passing-in our parameters */ + return dynamoDb.delete( parameters, function( error, data ) { + + /** Handle potential dynamoDb errors */ + if ( error ) return reject( error ); + + /** All successful. Create a valid response */ + return resolve( JSON.stringify( data ) ); + }); + + }) + .catch( function( error ) { + + console.log('<<>>', error ); + + throw new DynamodbError( error ); + }); + } + + /** + * Retrieve an array of replies according to the parameters passed + * + * @return {array} Array of replies + */ + static find( parameters ) { + + return new Promise( function( resolve, reject ) { + + /** Run a dynamoDb get request passing-in our parameters */ + return dynamoDb.get( parameters, function( error, data ) { + + /** Handle potential dynamoDb errors */ + if ( error ) return reject( error ); + + /** All successful. Create a valid response */ + return resolve( JSON.stringify( data ) ); + }); + + }) + .catch( function( error ) { + + console.log('<<>>', error ); + + throw new DynamodbError( error ); + }); + } + + /** + * Retrieve an array of replies according to the parameters passed + * + * @return {array} Array of replies + */ + static list( parameters ) { + + return new Promise( function( resolve, reject ) { + + /** Run a dynamoDb query passing-in Query.parameters */ + return dynamoDb.query( parameters, function( error, data ) { + + /** Handle potential dynamoDb errors */ + if ( error ) return reject( error ); + + /** All successful. Create a valid response */ + return resolve( JSON.stringify( data ) ); + }); + + }) + .catch( function( error ) { + + console.log('<<>>', error ); + + throw new DynamodbError( error ); + }); + } +} \ No newline at end of file diff --git a/api/replies/_classes/ReplyQueryBuilder.js b/api/replies/_classes/ReplyQueryBuilder.js index d0e8625..20a36cb 100644 --- a/api/replies/_classes/ReplyQueryBuilder.js +++ b/api/replies/_classes/ReplyQueryBuilder.js @@ -1,7 +1,7 @@ 'use strict'; const validator = require('validator'); -var ValidationError = require("./../../_classes/ValidationError"); +var ValidationError = require("./../../_errors/ValidationError"); /** * Responsible for turning parameters passeed are turned in DynamoDb parameters by building @@ -12,12 +12,8 @@ var ValidationError = require("./../../_classes/ValidationError"); module.exports = class ReplyQueryBuilder { constructor( criterion ) { - - /** - * Key/value pairs used to build our DynamoDb parameters. - * - * @type {object} - */ + + /** @type {Object} Key/value pairs used to build our DynamoDb parameters. */ this._criterion = criterion; /** diff --git a/api/replies/_models/Reply.js b/api/replies/_models/Reply.js index e056372..7bf8c33 100644 --- a/api/replies/_models/Reply.js +++ b/api/replies/_models/Reply.js @@ -1,12 +1,5 @@ 'use strict'; -const AWS = require('aws-sdk'); -const dynamoDb = new AWS.DynamoDB.DocumentClient(); - -var DynamodbError = require("./../../_classes/DynamodbError"); - -const Table = 'Reply'; - /** * * @@ -14,105 +7,30 @@ const Table = 'Reply'; */ module.exports = class Reply { - /** - * Retrieve an array of replies according to the parameters passed - * - * @return {array} Array of replies - */ - static destory( id ) { - - return new Promise( function( resolve, reject ) { - - /** @type {Object} Holds the parameters for the get request */ - const parameters = { - - TableName : Table, - Key : { - Id : id - } - } - - /** Run a dynamoDb get request passing-in our parameters */ - return dynamoDb.delete( parameters, function( error, data ) { - - /** Handle potential dynamoDb errors */ - if ( error ) return reject( error ); - - /** All successful. Create a valid response */ - return resolve( JSON.stringify( data ) ); - }); - - }) - .catch( function( error ) { - - console.log('<<>>', error ); - - throw new DynamodbError( error ); - }); - } - - /** - * Retrieve an array of replies according to the parameters passed - * - * @return {array} Array of replies - */ - static find( id ) { - - return new Promise( function( resolve, reject ) { - - /** @type {Object} Holds the parameters for the get request */ - const parameters = { - - TableName : Table, - Key : { - Id : id - } - } - - /** Run a dynamoDb get request passing-in our parameters */ - return dynamoDb.get( parameters, function( error, data ) { - - /** Handle potential dynamoDb errors */ - if ( error ) return reject( error ); - - /** All successful. Create a valid response */ - return resolve( JSON.stringify( data ) ); - }); - - }) - .catch( function( error ) { - - console.log('<<>>', error ); - - throw new DynamodbError( error ); - }); - } - - /** - * Retrieve an array of replies according to the parameters passed - * - * @return {array} Array of replies - */ - static list( parameters ) { - - return new Promise( function( resolve, reject ) { - - /** Run a dynamoDb query passing-in Query.parameters */ - return dynamoDb.query( parameters, function( error, data ) { - - /** Handle potential dynamoDb errors */ - if ( error ) return reject( error ); - - /** All successful. Create a valid response */ - return resolve( JSON.stringify( data ) ); - }); - - }) - .catch( function( error ) { - - console.log('<<>>', error ); - - throw new DynamodbError( error ); - }); + constructor() { + + this.validation = { + + ThreadId: { + type: 'string', + required: true, + message: 'threadid is required.' + }, + UserId: { + type: 'number', + required: true, + message: 'userid is required.' + }, + Message: { + type: 'string', + required: true, + message: 'message is required.' + }, + UserName: { + type: 'string', + required: true, + message: 'username is required.' + } + } } } \ No newline at end of file diff --git a/api/replies/replyDelete.js b/api/replies/replyDelete.js index 039b2e2..ceb59d0 100644 --- a/api/replies/replyDelete.js +++ b/api/replies/replyDelete.js @@ -1,9 +1,11 @@ 'use strict'; -var DynamodbError = require("./../_classes/DynamodbError"); +var DynamodbError = require("./../_errors/DynamodbError"); var Reply = require("./_models/Reply"); +var Dynamodb = require("./../_services/DynamodbService"); + /** * Handler for the lambda function. * @@ -15,7 +17,16 @@ var Reply = require("./_models/Reply"); */ module.exports.replyDelete = (event, context, callback) => { - Reply.delete( event.pathParameters.id ) + /** @type {Object} Holds the parameters for the get request */ + const parameters = { + + TableName : process.env.DYNAMODB_REPLY_TABLE, + Key : { + Id : event.pathParameters.id + } + } + + Dynamodb.destroy( parameters ) .then( ( reply ) => { const response = { diff --git a/api/replies/replyGet.js b/api/replies/replyGet.js index 84088b9..5f85595 100644 --- a/api/replies/replyGet.js +++ b/api/replies/replyGet.js @@ -1,8 +1,9 @@ 'use strict'; -var DynamodbError = require("./../_classes/DynamodbError"); +var DynamodbError = require("./../_errors/DynamodbError"); var Reply = require("./_models/Reply"); +var Dynamodb = require("./../_services/DynamodbService"); /** * Handler for the lambda function. @@ -15,7 +16,16 @@ var Reply = require("./_models/Reply"); */ module.exports.replyGet = (event, context, callback) => { - Reply.find( event.pathParameters.id ) + /** @type {Object} Holds the parameters for the get request */ + const parameters = { + + TableName : process.env.DYNAMODB_REPLY_TABLE, + Key : { + Id : event.pathParameters.id + } + } + + Dynamodb.find( parameters ) .then( ( reply ) => { const response = { diff --git a/api/replies/replyList.js b/api/replies/replyList.js index 0ec9461..9f9b981 100644 --- a/api/replies/replyList.js +++ b/api/replies/replyList.js @@ -2,11 +2,13 @@ var ReplyQueryBuilder = require("./_classes/ReplyQueryBuilder"); -var ValidationError = require("./../_classes/ValidationError"); -var DynamodbError = require("./../_classes/DynamodbError"); - +var ValidationError = require("./../_errors/ValidationError"); +var DynamodbError = require("./../_errors/DynamodbError"); var Reply = require("./_models/Reply"); + +var DynamodbService = require("./../_services/DynamodbService"); + /** * Handler for the lambda function. * @@ -34,7 +36,7 @@ module.exports.replyList = (event, context, callback) => { .buildPagination(); /** @type {model} Contains a list of items and optional pagination data */ - Reply.list( Query.parameters ) + DynamodbService.list( Query.parameters ) .then( ( replies ) => { const response = { @@ -53,9 +55,8 @@ module.exports.replyList = (event, context, callback) => { }); } - catch( error ) { - - // All error handling performed here + catch( error ) { // Catch any errors thrown by the ReplyQueryBuilder class + if( error instanceof ValidationError ) { callback(null, { From be028c42b764ae8666f7ec8d04ab1b7fc0024509 Mon Sep 17 00:00:00 2001 From: jacksoncharles Date: Thu, 14 Dec 2017 18:40:44 +0000 Subject: [PATCH 27/78] replty modelling --- api/_services/Dynamic.js | 162 ++++++++++++++++++++++++++++ api/_services/DynamodbService.js | 2 +- api/replies/_models/Reply.js | 63 ++++++----- api/replies/replyCreate.js | 176 ++++++------------------------- api/replies/replyDelete.js | 11 +- api/replies/replyGet.js | 14 +-- api/replies/replyUpdate.js | 172 ++++++------------------------ 7 files changed, 270 insertions(+), 330 deletions(-) create mode 100644 api/_services/Dynamic.js diff --git a/api/_services/Dynamic.js b/api/_services/Dynamic.js new file mode 100644 index 0000000..40f315f --- /dev/null +++ b/api/_services/Dynamic.js @@ -0,0 +1,162 @@ +'use strict'; + +const AWS = require('aws-sdk'); +const dynamoDb = new AWS.DynamoDB.DocumentClient(); + +var DynamodbError = require('./../_errors/DynamodbError'); +var ValidationError = require('./../_errors/ValidationError'); + +/** + * Wrapper for DynamoDb with basic CRUD functionality and a validation method + * + * @type {class} + */ +module.exports = class Dynamic { + + /** + * Save the current instance to permanent storage creating a new record or updating an existing record + * + * @return {Promise} + */ + save() { + + return new Promise( function( resolve, reject ) { + + // Save to permanent storage + return dynamoDb.delete( this.properties(), function( error, data ) { + + // create a response + const response = { + + statusCode: 200, + body: JSON.stringify( data ) + }; + + callback (null, response ); + }); + }) + .catch( function( error ) { + + console.log('<<>>', error ); + + throw new DynamodbError( error ); + }); + } + + /** + * Validates the rules defined in this.validation_rules and throws an error + * else returns {this} + * + * @return {this} + */ + validate() { + + let errors = this.validator.validate( Object.keys( this ) ); + + throw new ValidationError( errors ); + + return this; + } + + /** + * Retrieve an array of replies according to the parameters passed + * + * @return {Array} Array of replies. + */ + static destroy( id : number ) { + + /** @type {Object} Holds the parameters for the get request */ + const parameters = { + + TableName : process.env.DYNAMODB_REPLY_TABLE, + Key : { + Id : id + } + } + + return new Promise( function( resolve, reject ) { + + /** Run a dynamoDb get request passing-in our parameters */ + return dynamoDb.delete( parameters, function( error, data ) { + + /** Handle potential dynamoDb errors */ + if ( error ) return reject( error ); + + /** All successful. Create a valid response */ + return resolve( JSON.stringify( data ) ); + }); + + }) + .catch( function( error ) { + + console.log('<<>>', error ); + + throw new DynamodbError( error ); + }); + } + + /** + * Retrieve an array of replies according to the parameters passed + * + * @return {array} Array of replies + */ + static find( id ) { + + /** @type {Object} Holds the parameters for the get request */ + const parameters = { + + TableName : process.env.DYNAMODB_REPLY_TABLE, + Key : { + Id : id + } + } + + return new Promise( function( resolve, reject ) { + + /** Run a dynamoDb get request passing-in our parameters */ + return dynamoDb.get( parameters, function( error, data ) { + + /** Handle potential dynamoDb errors */ + if ( error ) return reject( error ); + + /** All successful. Create a valid response */ + return resolve( new this( data.Item ) ); + }); + + }) + .catch( function( error ) { + + console.log('<<>>', error ); + + throw new DynamodbError( error ); + }); + } + + /** + * Retrieve an array of replies according to the parameters passed + * + * @return {array} Array of replies + */ + static list( parameters ) { + + return new Promise( function( resolve, reject ) { + + /** Run a dynamoDb query passing-in Query.parameters */ + return dynamoDb.query( parameters, function( error, data ) { + + /** Handle potential dynamoDb errors */ + if ( error ) return reject( error ); + + /** All successful. Create a valid response */ + return resolve( JSON.stringify( data ) ); + }); + + }) + .catch( function( error ) { + + console.log('<<>>', error ); + + throw new DynamodbError( error ); + }); + } +} \ No newline at end of file diff --git a/api/_services/DynamodbService.js b/api/_services/DynamodbService.js index 10517f5..6342503 100644 --- a/api/_services/DynamodbService.js +++ b/api/_services/DynamodbService.js @@ -6,7 +6,7 @@ const dynamoDb = new AWS.DynamoDB.DocumentClient(); var DynamodbError = require('./../_errors/DynamodbError'); /** - * + * CRUD service for DynamoDb. * * @type {class} */ diff --git a/api/replies/_models/Reply.js b/api/replies/_models/Reply.js index 7bf8c33..dd3f0dc 100644 --- a/api/replies/_models/Reply.js +++ b/api/replies/_models/Reply.js @@ -1,36 +1,51 @@ 'use strict'; +const uuid = require('uuid'); + +var schema = require('validate'); +var Dynamic = require('./../../_services/Dynamic'); + /** - * + * Reply class. Each instance maps to one document in permanent storage and extends the + * Dynamic wrapper class. * * @type {class} */ -module.exports = class Reply { +module.exports = class Reply extends Dynamic { + - constructor() { + constructor( parameters = {} ) { - this.validation = { + /** Grab all the parameters and assign as class properties */ + Object.assign(this, parameters ); - ThreadId: { - type: 'string', - required: true, - message: 'threadid is required.' - }, - UserId: { - type: 'number', - required: true, - message: 'userid is required.' - }, - Message: { - type: 'string', - required: true, - message: 'message is required.' - }, - UserName: { - type: 'string', - required: true, - message: 'username is required.' - } + /** @type {Object} Create the validation rules */ + this.validation_rules = { + Id: { + type: 'string', + required: false, + message: 'id must be a string' + }, + ThreadId: { + type: 'string', + required: true, + message: 'threadid is required' + }, + UserId: { + type: 'number', + required: true, + message: 'userid is required' + }, + Message: { + type: 'string', + required: true, + message: 'message is required' + }, + UserName: { + type: 'string', + required: true, + message: 'username is required' + } } } } \ No newline at end of file diff --git a/api/replies/replyCreate.js b/api/replies/replyCreate.js index 20d0264..cd4aaa6 100644 --- a/api/replies/replyCreate.js +++ b/api/replies/replyCreate.js @@ -1,11 +1,7 @@ 'use strict'; -const uuid = require('uuid'); -const AWS = require('aws-sdk'); -var schema = require('validate'); - -AWS.config.setPromisesDependency(require('bluebird')); -const dynamoDb = new AWS.DynamoDB.DocumentClient(); +var Reply = require("./_models/Reply"); +var ValidationError = require("./../_errors/ValidationError"); /** * Handler for the lambda function. @@ -18,155 +14,47 @@ const dynamoDb = new AWS.DynamoDB.DocumentClient(); */ module.exports.replyCreate = (event, context, callback) => { - var QueryBuilder = function( event ) { + let reply = new Reply( this.event.queryStringParameters ); - /** - * Capture the event object passed as a parameter; - * - * @type event - */ - this.event = event; + reply + .validate() + .save() + .then( ( data ) => { - /** - * Used to build the object being saved to permanent storage. The value of "Item" will - * be populated with user passed parameters - * - * @todo : Change TableName to value of process.env.DYNAMODB_REPLY_TABLE - * - * @type Object - */ - this.parameters = { - TableName: 'Reply' - Item: {} + const response = { + statusCode: 200, + body: data } - /** - * Holds the schema for validating the parameters passed with the request. Anything failing - * validation will be stored inside this.errors - * - * @type {Object} - */ - this.validator = schema({ - - ThreadId: { - type: 'string', - required: true, - message: 'threadid is required.' - }, - UserId: { - type: 'number', - required: true, - message: 'userid is required.' - }, - Message: { - type: 'string', - required: true, - message: 'message is required.' - }, - UserName: { - type: 'string', - required: true, - message: 'username is required.' - } - }); - - /** - * Used to hold any validation messages. - * - * @type {Array} - */ - this.errors = []; - } - - /** - * Populates the "item" object prior to saving - * - * @return {this} - */ - QueryBuilder.prototype.hydrate = function() { - - const timestamp = new Date().getTime(); - - this.parameters.Item = { - Id: uuid.v1(), - ThreadId: this.event.queryStringParameters.threadid, - UserId: this.event.queryStringParameters.userid, - Message: this.event.queryStringParameters.message, - UserName: this.event.queryStringParameters.username, - CreatedDateTime: timestamp, - UpdatedDateTime: timestamp - }; - - return this; - } - - /** - * Validates the data passed in the event object - * - * @return {this} - */ - QueryBuilder.prototype.validates = function() { - - this.errors = []; + return callback( null, response ); + }) + .catch( function( error ) { - /* - if ( this.event.queryStringParameters.hasOwnProperty('threadid') == false && typeof this.event.queryStringParameters.threadid !== 'string' ) this.errors.push('threadid missing or invalid'); - if ( this.event.queryStringParameters.hasOwnProperty('userid') == false && typeof this.event.queryStringParameters.userid !== 'string' ) this.errors.push('userid missing or invalid'); - if ( this.event.queryStringParameters.hasOwnProperty('message') == false && typeof this.event.queryStringParameters.message !== 'string' ) this.errors.push('message missing or invalid'); - if ( this.event.queryStringParameters.hasOwnProperty('username') == false && typeof this.event.queryStringParameters.username !== 'string' ) this.errors.push('username missing or invalid'); - */ - // Validate the query parameters - this.errors = this.validator.validate( this.event.queryStringParameters ); + if( error instanceof ValidationError ) { - return this.errors.length ? 0 : 1; - } - - /** - * Instantiate an instance of Query - * - * @type {QueryBuilder} - */ - var Query = new QueryBuilder( event ); - - // Check to see if the parameters passed in the request validate. - if ( Query.validates() == false ) { - - // Handle validation errors - callback(null, { - statusCode: 422, - body: JSON.stringify({ - message: Query.errors - }) - }) - } - else { - - // Save to permanent storage - return dynamoDb.put( Query.hydrate().parameters ).promise() - .then( res => reply ) - .then( res => { + callback(null, { + statusCode: 422, + body: error.message + }); + } + else if( error instanceof DynamodbError ) { - // create a response - const response = { + console.log('<<>>', error ); - statusCode: 200, - body: JSON.stringify(res), - }; + callback(null, { + statusCode: 500, + body: JSON.stringify( error ) + }); + } + else { - callback(null, response); - }) - .catch(err => { + console.log('<<>>', error ); - console.log('=== error ===', error ); - - // Handle DynamoDb errors callback(null, { statusCode: 500, - body: JSON.stringify({ - message: `Unable to submit reply` - }) - }) - }); - } + body: JSON.stringify( error ) + }); + } + }); }; \ No newline at end of file diff --git a/api/replies/replyDelete.js b/api/replies/replyDelete.js index ceb59d0..954b8d6 100644 --- a/api/replies/replyDelete.js +++ b/api/replies/replyDelete.js @@ -17,16 +17,7 @@ var Dynamodb = require("./../_services/DynamodbService"); */ module.exports.replyDelete = (event, context, callback) => { - /** @type {Object} Holds the parameters for the get request */ - const parameters = { - - TableName : process.env.DYNAMODB_REPLY_TABLE, - Key : { - Id : event.pathParameters.id - } - } - - Dynamodb.destroy( parameters ) + Reply.destroy( event.pathParameters.id ) .then( ( reply ) => { const response = { diff --git a/api/replies/replyGet.js b/api/replies/replyGet.js index 5f85595..cd95f9d 100644 --- a/api/replies/replyGet.js +++ b/api/replies/replyGet.js @@ -1,9 +1,6 @@ 'use strict'; -var DynamodbError = require("./../_errors/DynamodbError"); - var Reply = require("./_models/Reply"); -var Dynamodb = require("./../_services/DynamodbService"); /** * Handler for the lambda function. @@ -16,16 +13,7 @@ var Dynamodb = require("./../_services/DynamodbService"); */ module.exports.replyGet = (event, context, callback) => { - /** @type {Object} Holds the parameters for the get request */ - const parameters = { - - TableName : process.env.DYNAMODB_REPLY_TABLE, - Key : { - Id : event.pathParameters.id - } - } - - Dynamodb.find( parameters ) + Reply.find( event.pathParameters.id ) .then( ( reply ) => { const response = { diff --git a/api/replies/replyUpdate.js b/api/replies/replyUpdate.js index cda3d50..944ce54 100644 --- a/api/replies/replyUpdate.js +++ b/api/replies/replyUpdate.js @@ -1,7 +1,7 @@ 'use strict'; -const AWS = require('aws-sdk'); // eslint-disable-line import/no-extraneous-dependencies -const dynamoDb = new AWS.DynamoDB.DocumentClient(); +var Reply = require("./_models/Reply"); +var ValidationError = require("./../_errors/ValidationError"); /** * Handler for the lambda function. @@ -14,151 +14,47 @@ const dynamoDb = new AWS.DynamoDB.DocumentClient(); */ module.exports.replyUpdate = (event, context, callback) => { - var QueryBuilder = function( event ) { + let reply = new Reply( this.event.queryStringParameters ); - /** - * Capture the event object passed as a parameter; - * - * @type event - */ - this.event = event; + reply + .validate() + .save() + .then( ( reply ) => { - /** - * Used to build the object being saved to permanent storage. The value of "Item" will - * be populated with user passed parameters - * - * @todo : Change TableName to value of process.env.DYNAMODB_REPLY_TABLE - * - * @type Object - */ - this.parameters = { - TableName: 'Reply' - Item: {} + const response = { + statusCode: 200, + body: reply } - /** - * Holds the schema for validating the parameters passed with the request. Anything failing - * validation will be stored inside this.errors - * - * @type {Object} - */ - this.validator = schema({ + return callback( null, response ); + }) + .catch( function( error ) { - ThreadId: { - type: 'string', - required: true, - message: 'threadid is required.' - }, - UserId: { - type: 'number', - required: true, - message: 'userid is required.' - }, - Message: { - type: 'string', - required: true, - message: 'message is required.' - }, - UserName: { - type: 'string', - required: true, - message: 'username is required.' - } - }); + if( error instanceof ValidationError ) { - /** - * Used to hold any validation messages. - * - * @type {Array} - */ - this.errors = []; - - } - - /** - * Populates the "item" object prior to saving - * - * @return {this} - */ - QueryBuilder.prototype.hydrate = function() { - - const timestamp = new Date().getTime(); - - this.parameters.Item = { - Id: uuid.v1(), - ThreadId: this.event.queryStringParameters.threadid, - UserId: this.event.queryStringParameters.userid, - Message: this.event.queryStringParameters.message, - UserName: this.event.queryStringParameters.username, - UpdatedDateTime: timestamp - }; - - return this; - } - - /** - * Validates the data passed in the event object - * - * @return {this} - */ - QueryBuilder.prototype.validates = function() { - - this.errors = []; - - /* - if ( this.event.queryStringParameters.hasOwnProperty('threadid') == false && typeof this.event.queryStringParameters.threadid !== 'string' ) this.errors.push('threadid missing or invalid'); - if ( this.event.queryStringParameters.hasOwnProperty('userid') == false && typeof this.event.queryStringParameters.userid !== 'string' ) this.errors.push('userid missing or invalid'); - if ( this.event.queryStringParameters.hasOwnProperty('message') == false && typeof this.event.queryStringParameters.message !== 'string' ) this.errors.push('message missing or invalid'); - if ( this.event.queryStringParameters.hasOwnProperty('username') == false && typeof this.event.queryStringParameters.username !== 'string' ) this.errors.push('username missing or invalid'); - */ - // Validate the query parameters - this.errors = this.validator.validate( this.event.queryStringParameters ); - - return this.errors.length ? 0 : 1; - } - - /** - * Instantiate an instance of Query - * - * @type {QueryBuilder} - */ - var Query = new QueryBuilder( event ); + callback(null, { + statusCode: 422, + body: error.message + }); + } + else if( error instanceof DynamodbError ) { - // Check to see if the parameters passed in the request validate. - if ( Query.validates() == false ) { + console.log('<<>>', error ); - // Handle validation errors - callback(null, { - statusCode: 422, - body: JSON.stringify({ - message: Query.errors - }) - }) - } - else { + callback(null, { + statusCode: 500, + body: JSON.stringify( error ) + }); - // Update the post in the database - dynamoDb.update( Query.hydrate().parameters, (error, result) => { - - // Handle any potential DynamoDb errors - if (error) { + } + else { - console.error('=== error ===', error); - - callback(null, { - statusCode: error.statusCode || 501, - headers: { 'Content-Type': 'text/plain' }, - body: 'Couldn\'t fetch the post item.', - }); - return; - } + console.log('<<>>', error ); - // create a response - const response = { - statusCode: 200, - body: JSON.stringify(result.Attributes), - }; - callback(null, response); - }); - } + callback(null, { + statusCode: 500, + body: JSON.stringify( error ) + }); + } + }); }; \ No newline at end of file From 90414e2691e2bfa33cfb934388ac534cb10005ce Mon Sep 17 00:00:00 2001 From: jacksoncharles Date: Thu, 14 Dec 2017 20:08:47 +0000 Subject: [PATCH 28/78] tweaks --- api/_services/Dynamic.js | 4 ++-- api/replies/_models/Reply.js | 4 +++- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/api/_services/Dynamic.js b/api/_services/Dynamic.js index 40f315f..e55a3c2 100644 --- a/api/_services/Dynamic.js +++ b/api/_services/Dynamic.js @@ -63,7 +63,7 @@ module.exports = class Dynamic { * * @return {Array} Array of replies. */ - static destroy( id : number ) { + static destroy( id ) { /** @type {Object} Holds the parameters for the get request */ const parameters = { @@ -120,7 +120,7 @@ module.exports = class Dynamic { if ( error ) return reject( error ); /** All successful. Create a valid response */ - return resolve( new this( data.Item ) ); + return resolve( new this.name( data.Item ) ); }); }) diff --git a/api/replies/_models/Reply.js b/api/replies/_models/Reply.js index dd3f0dc..d854107 100644 --- a/api/replies/_models/Reply.js +++ b/api/replies/_models/Reply.js @@ -2,7 +2,7 @@ const uuid = require('uuid'); -var schema = require('validate'); +var schema = require('validator'); var Dynamic = require('./../../_services/Dynamic'); /** @@ -16,6 +16,8 @@ module.exports = class Reply extends Dynamic { constructor( parameters = {} ) { + this.name = 'Reply'; + /** Grab all the parameters and assign as class properties */ Object.assign(this, parameters ); From 4f523daa4803b4e1a4682e45c6a1ab274af619ff Mon Sep 17 00:00:00 2001 From: jacksoncharles Date: Sun, 17 Dec 2017 22:52:21 +0000 Subject: [PATCH 29/78] extended model pattern --- api/_classes/CustomErrors.js | 28 ++++++++++ api/{_services => _classes}/Dynamic.js | 15 ++++-- .../DynamodbService.js | 15 +++++- api/_errors/DynamodbError.js | 8 --- api/_errors/ValidationError.js | 8 --- api/replies/_classes/ReplyQueryBuilder.js | 4 +- api/replies/_models/Reply.js | 53 ------------------- api/replies/replyCreate.js | 6 ++- api/replies/replyGet.js | 17 ++++-- api/replies/replyList.js | 12 ++--- 10 files changed, 77 insertions(+), 89 deletions(-) create mode 100644 api/_classes/CustomErrors.js rename api/{_services => _classes}/Dynamic.js (89%) rename api/{_services => _classes}/DynamodbService.js (88%) delete mode 100644 api/_errors/DynamodbError.js delete mode 100644 api/_errors/ValidationError.js delete mode 100644 api/replies/_models/Reply.js diff --git a/api/_classes/CustomErrors.js b/api/_classes/CustomErrors.js new file mode 100644 index 0000000..7fa18cd --- /dev/null +++ b/api/_classes/CustomErrors.js @@ -0,0 +1,28 @@ +'use strict'; + +/** + * + * + * @type {class} + */ +class DynamodbError extends Error {} + +/** + * + * + * @type {class} + */ +class NotFoundError extends Error {} + +/** + * + * + * @type {class} + */ +class ValidationError extends Error {} + +module.exports = { + DynamodbError : DynamodbError, + NotFoundError : NotFoundError, + ValidationError : ValidationError +} \ No newline at end of file diff --git a/api/_services/Dynamic.js b/api/_classes/Dynamic.js similarity index 89% rename from api/_services/Dynamic.js rename to api/_classes/Dynamic.js index e55a3c2..45620e9 100644 --- a/api/_services/Dynamic.js +++ b/api/_classes/Dynamic.js @@ -3,8 +3,10 @@ const AWS = require('aws-sdk'); const dynamoDb = new AWS.DynamoDB.DocumentClient(); -var DynamodbError = require('./../_errors/DynamodbError'); -var ValidationError = require('./../_errors/ValidationError'); +var CustomErrors = require("./../_errors/CustomErrors"); +var DynamodbError = CustomErrors.DynamodbError; +var ValidationError = CustomErrors.ValidationError; +var NotFoundError = CustomErrors.NotFoundError; /** * Wrapper for DynamoDb with basic CRUD functionality and a validation method @@ -102,6 +104,8 @@ module.exports = class Dynamic { */ static find( id ) { + var self = this; + /** @type {Object} Holds the parameters for the get request */ const parameters = { @@ -119,12 +123,15 @@ module.exports = class Dynamic { /** Handle potential dynamoDb errors */ if ( error ) return reject( error ); + let newInstance = self.model( data ); + //console.log( '=== M ===', m ); + /** All successful. Create a valid response */ - return resolve( new this.name( data.Item ) ); + return resolve( JSON.stringify( data ) ); }); }) - .catch( function( error ) { + .catch( function( error ) { // Capture a dynamoDb rejection console.log('<<>>', error ); diff --git a/api/_services/DynamodbService.js b/api/_classes/DynamodbService.js similarity index 88% rename from api/_services/DynamodbService.js rename to api/_classes/DynamodbService.js index 6342503..7ccdfcb 100644 --- a/api/_services/DynamodbService.js +++ b/api/_classes/DynamodbService.js @@ -45,7 +45,18 @@ module.exports = class DynamodbService { * * @return {array} Array of replies */ - static find( parameters ) { + static find( id ) { + + var self = this; + + /** @type {Object} Holds the parameters for the get request */ + const parameters = { + + TableName : process.env.DYNAMODB_REPLY_TABLE, + Key : { + Id : id + } + } return new Promise( function( resolve, reject ) { @@ -60,7 +71,7 @@ module.exports = class DynamodbService { }); }) - .catch( function( error ) { + .catch( function( error ) { // Capture a dynamoDb rejection console.log('<<>>', error ); diff --git a/api/_errors/DynamodbError.js b/api/_errors/DynamodbError.js deleted file mode 100644 index 52733b7..0000000 --- a/api/_errors/DynamodbError.js +++ /dev/null @@ -1,8 +0,0 @@ -'use strict'; - -/** - * - * - * @type {class} - */ -module.exports = class DynamodbError extends Error {} \ No newline at end of file diff --git a/api/_errors/ValidationError.js b/api/_errors/ValidationError.js deleted file mode 100644 index 2916c14..0000000 --- a/api/_errors/ValidationError.js +++ /dev/null @@ -1,8 +0,0 @@ -'use strict'; - -/** - * - * - * @type {class} - */ -module.exports = class ValidationError extends Error {} \ No newline at end of file diff --git a/api/replies/_classes/ReplyQueryBuilder.js b/api/replies/_classes/ReplyQueryBuilder.js index 20a36cb..7c8949d 100644 --- a/api/replies/_classes/ReplyQueryBuilder.js +++ b/api/replies/_classes/ReplyQueryBuilder.js @@ -1,7 +1,9 @@ 'use strict'; const validator = require('validator'); -var ValidationError = require("./../../_errors/ValidationError"); + +var CustomErrors = require("./../../_errors/CustomErrors"); +var ValidationError = CustomErrors.ValidationError; /** * Responsible for turning parameters passeed are turned in DynamoDb parameters by building diff --git a/api/replies/_models/Reply.js b/api/replies/_models/Reply.js deleted file mode 100644 index d854107..0000000 --- a/api/replies/_models/Reply.js +++ /dev/null @@ -1,53 +0,0 @@ -'use strict'; - -const uuid = require('uuid'); - -var schema = require('validator'); -var Dynamic = require('./../../_services/Dynamic'); - -/** - * Reply class. Each instance maps to one document in permanent storage and extends the - * Dynamic wrapper class. - * - * @type {class} - */ -module.exports = class Reply extends Dynamic { - - - constructor( parameters = {} ) { - - this.name = 'Reply'; - - /** Grab all the parameters and assign as class properties */ - Object.assign(this, parameters ); - - /** @type {Object} Create the validation rules */ - this.validation_rules = { - Id: { - type: 'string', - required: false, - message: 'id must be a string' - }, - ThreadId: { - type: 'string', - required: true, - message: 'threadid is required' - }, - UserId: { - type: 'number', - required: true, - message: 'userid is required' - }, - Message: { - type: 'string', - required: true, - message: 'message is required' - }, - UserName: { - type: 'string', - required: true, - message: 'username is required' - } - } - } -} \ No newline at end of file diff --git a/api/replies/replyCreate.js b/api/replies/replyCreate.js index cd4aaa6..75b9168 100644 --- a/api/replies/replyCreate.js +++ b/api/replies/replyCreate.js @@ -1,7 +1,9 @@ 'use strict'; -var Reply = require("./_models/Reply"); -var ValidationError = require("./../_errors/ValidationError"); +var Reply = require("./_classes/Reply"); + +var CustomErrors = require("./../_errors/CustomErrors"); +var ValidationError = CustomErrors.ValidationError; /** * Handler for the lambda function. diff --git a/api/replies/replyGet.js b/api/replies/replyGet.js index cd95f9d..95d1afb 100644 --- a/api/replies/replyGet.js +++ b/api/replies/replyGet.js @@ -2,6 +2,8 @@ var Reply = require("./_models/Reply"); +var DynamodbService = require("./../_services/DynamodbService"); + /** * Handler for the lambda function. * @@ -11,17 +13,22 @@ var Reply = require("./_models/Reply"); * * @return JSON JSON encoded response. */ -module.exports.replyGet = (event, context, callback) => { +module.exports.replyGet = ( event, context, callback ) => { Reply.find( event.pathParameters.id ) .then( ( reply ) => { - const response = { - statusCode: 200, + let statusCode = 200; + if( Object.keys( reply ).length === 0 ) statusCode = 404; // Catch a 404 + + let response = { + statusCode: statusCode, // Will be used by the API gateway in the response body: reply } - return callback( null, response ); + console.log( 'response', response ); + + callback( null, response ); }) .catch( function( error ) { @@ -29,7 +36,7 @@ module.exports.replyGet = (event, context, callback) => { callback(null, { statusCode: 500, - body: JSON.stringify( { message: error.message } ) + body: error }); }); diff --git a/api/replies/replyList.js b/api/replies/replyList.js index 9f9b981..809a296 100644 --- a/api/replies/replyList.js +++ b/api/replies/replyList.js @@ -1,11 +1,11 @@ 'use strict'; -var ReplyQueryBuilder = require("./_classes/ReplyQueryBuilder"); - -var ValidationError = require("./../_errors/ValidationError"); -var DynamodbError = require("./../_errors/DynamodbError"); var Reply = require("./_models/Reply"); +var ReplyQueryBuilder = require("./_classes/ReplyQueryBuilder"); + +var CustomErrors = require("./../_errors/CustomErrors"); +var DynamodbError = CustomErrors.DynamodbError; var DynamodbService = require("./../_services/DynamodbService"); @@ -36,7 +36,7 @@ module.exports.replyList = (event, context, callback) => { .buildPagination(); /** @type {model} Contains a list of items and optional pagination data */ - DynamodbService.list( Query.parameters ) + Reply.list( Query.parameters ) .then( ( replies ) => { const response = { @@ -44,7 +44,7 @@ module.exports.replyList = (event, context, callback) => { body: replies }; - return callback( null, response ); + callback( null, response ); }) .catch( function( error ) { From 04224eab95919ccf6b5ef7fa79e65137bad42e35 Mon Sep 17 00:00:00 2001 From: jacksoncharles Date: Sun, 17 Dec 2017 23:20:29 +0000 Subject: [PATCH 30/78] tweaks --- api/replies/_classes/Reply.js | 54 +++++++++++++++++++++++ api/replies/_classes/ReplyQueryBuilder.js | 2 +- api/replies/replyCreate.js | 2 +- api/replies/replyDelete.js | 6 ++- api/replies/replyGet.js | 4 +- api/replies/replyList.js | 2 +- api/replies/replyUpdate.js | 4 +- 7 files changed, 65 insertions(+), 9 deletions(-) create mode 100644 api/replies/_classes/Reply.js diff --git a/api/replies/_classes/Reply.js b/api/replies/_classes/Reply.js new file mode 100644 index 0000000..1911684 --- /dev/null +++ b/api/replies/_classes/Reply.js @@ -0,0 +1,54 @@ +'use strict'; + +var Dynamic = require('./../../_classes/Dynamic'); + +/** + * Reply class. Each instance maps to one document in permanent storage and extends the + * Dynamic wrapper class. + * + * @type {class} + */ +module.exports = class Reply extends Dynamic { + + constructor( parameters = {} ) { + + /** Grab all the parameters and assign as class properties */ + Object.assign(this, parameters ); + + /** @type {Object} Create the validation rules */ + /* + this.validation_rules = { + Id: { + type: 'string', + required: false, + message: 'id must be a string' + }, + ThreadId: { + type: 'string', + required: true, + message: 'threadid is required' + }, + UserId: { + type: 'number', + required: true, + message: 'userid is required' + }, + Message: { + type: 'string', + required: true, + message: 'message is required' + }, + UserName: { + type: 'string', + required: true, + message: 'username is required' + } + } + */ + } + + static model( parameters ) { + + return new this; + } +} \ No newline at end of file diff --git a/api/replies/_classes/ReplyQueryBuilder.js b/api/replies/_classes/ReplyQueryBuilder.js index 7c8949d..b433c0a 100644 --- a/api/replies/_classes/ReplyQueryBuilder.js +++ b/api/replies/_classes/ReplyQueryBuilder.js @@ -2,7 +2,7 @@ const validator = require('validator'); -var CustomErrors = require("./../../_errors/CustomErrors"); +var CustomErrors = require("./../../_classes/CustomErrors"); var ValidationError = CustomErrors.ValidationError; /** diff --git a/api/replies/replyCreate.js b/api/replies/replyCreate.js index 75b9168..c1dbc7e 100644 --- a/api/replies/replyCreate.js +++ b/api/replies/replyCreate.js @@ -2,7 +2,7 @@ var Reply = require("./_classes/Reply"); -var CustomErrors = require("./../_errors/CustomErrors"); +var CustomErrors = require("./../_classes/CustomErrors"); var ValidationError = CustomErrors.ValidationError; /** diff --git a/api/replies/replyDelete.js b/api/replies/replyDelete.js index 954b8d6..2193c0f 100644 --- a/api/replies/replyDelete.js +++ b/api/replies/replyDelete.js @@ -1,10 +1,12 @@ 'use strict'; -var DynamodbError = require("./../_errors/DynamodbError"); +var CustomErrors = require("./../../_classes/CustomErrors"); +var ValidationError = CustomErrors.ValidationError; + var Reply = require("./_models/Reply"); -var Dynamodb = require("./../_services/DynamodbService"); +var Dynamodb = require("./../_classes/DynamodbService"); /** * Handler for the lambda function. diff --git a/api/replies/replyGet.js b/api/replies/replyGet.js index 95d1afb..5dc37b3 100644 --- a/api/replies/replyGet.js +++ b/api/replies/replyGet.js @@ -1,8 +1,6 @@ 'use strict'; -var Reply = require("./_models/Reply"); - -var DynamodbService = require("./../_services/DynamodbService"); +var Reply = require("./_classes/Reply"); /** * Handler for the lambda function. diff --git a/api/replies/replyList.js b/api/replies/replyList.js index 809a296..a7ef684 100644 --- a/api/replies/replyList.js +++ b/api/replies/replyList.js @@ -1,6 +1,6 @@ 'use strict'; -var Reply = require("./_models/Reply"); +var Reply = require("./_classes/Reply"); var ReplyQueryBuilder = require("./_classes/ReplyQueryBuilder"); diff --git a/api/replies/replyUpdate.js b/api/replies/replyUpdate.js index 944ce54..b3bd9fc 100644 --- a/api/replies/replyUpdate.js +++ b/api/replies/replyUpdate.js @@ -1,7 +1,9 @@ 'use strict'; var Reply = require("./_models/Reply"); -var ValidationError = require("./../_errors/ValidationError"); + +var CustomErrors = require("./../../_classes/CustomErrors"); +var ValidationError = CustomErrors.ValidationError; /** * Handler for the lambda function. From c9d6cd202e23142f622037e9ab7c2ce4981fc719 Mon Sep 17 00:00:00 2001 From: jacksoncharles Date: Mon, 18 Dec 2017 23:27:56 +0000 Subject: [PATCH 31/78] fixes --- api/_classes/Dynamic.js | 27 ++++++++++++++++---- api/replies/_classes/Reply.js | 47 ++++++++--------------------------- api/replies/replyGet.js | 9 ++----- api/replies/replyList.js | 9 +++---- 4 files changed, 39 insertions(+), 53 deletions(-) diff --git a/api/_classes/Dynamic.js b/api/_classes/Dynamic.js index 45620e9..ff9552e 100644 --- a/api/_classes/Dynamic.js +++ b/api/_classes/Dynamic.js @@ -3,7 +3,7 @@ const AWS = require('aws-sdk'); const dynamoDb = new AWS.DynamoDB.DocumentClient(); -var CustomErrors = require("./../_errors/CustomErrors"); +var CustomErrors = require("./CustomErrors"); var DynamodbError = CustomErrors.DynamodbError; var ValidationError = CustomErrors.ValidationError; var NotFoundError = CustomErrors.NotFoundError; @@ -15,6 +15,12 @@ var NotFoundError = CustomErrors.NotFoundError; */ module.exports = class Dynamic { + constructor( parameters ) { + + /** Grab all the parameters and assign as class properties */ + Object.assign(this, parameters ); + } + /** * Save the current instance to permanent storage creating a new record or updating an existing record * @@ -123,11 +129,11 @@ module.exports = class Dynamic { /** Handle potential dynamoDb errors */ if ( error ) return reject( error ); - let newInstance = self.model( data ); - //console.log( '=== M ===', m ); + /** @type {Object} Create a new instance of self and populate with the data */ + let modelInstance = self.model( data.Item ); /** All successful. Create a valid response */ - return resolve( JSON.stringify( data ) ); + return resolve( modelInstance ); }); }) @@ -146,6 +152,8 @@ module.exports = class Dynamic { */ static list( parameters ) { + var self = this; + return new Promise( function( resolve, reject ) { /** Run a dynamoDb query passing-in Query.parameters */ @@ -154,8 +162,17 @@ module.exports = class Dynamic { /** Handle potential dynamoDb errors */ if ( error ) return reject( error ); + let items = []; + + for ( let item of data.Items ) { + + items.push( self.model( item ) ); + } + + data['Items'] = items; + /** All successful. Create a valid response */ - return resolve( JSON.stringify( data ) ); + return resolve( data ); }); }) diff --git a/api/replies/_classes/Reply.js b/api/replies/_classes/Reply.js index 1911684..d2f9da4 100644 --- a/api/replies/_classes/Reply.js +++ b/api/replies/_classes/Reply.js @@ -10,45 +10,20 @@ var Dynamic = require('./../../_classes/Dynamic'); */ module.exports = class Reply extends Dynamic { - constructor( parameters = {} ) { - - /** Grab all the parameters and assign as class properties */ - Object.assign(this, parameters ); + constructor( parameters ) { - /** @type {Object} Create the validation rules */ - /* - this.validation_rules = { - Id: { - type: 'string', - required: false, - message: 'id must be a string' - }, - ThreadId: { - type: 'string', - required: true, - message: 'threadid is required' - }, - UserId: { - type: 'number', - required: true, - message: 'userid is required' - }, - Message: { - type: 'string', - required: true, - message: 'message is required' - }, - UserName: { - type: 'string', - required: true, - message: 'username is required' - } - } - */ - } + super( parameters ); + } + /** + * Return a new instance of this + * + * @param {Object} parameters - Properties to be assigned to the newly created object + * + * @return {Object} New instance of the Reply object + */ static model( parameters ) { - return new this; + return new Reply( parameters ); } } \ No newline at end of file diff --git a/api/replies/replyGet.js b/api/replies/replyGet.js index 5dc37b3..a8e0e13 100644 --- a/api/replies/replyGet.js +++ b/api/replies/replyGet.js @@ -16,16 +16,11 @@ module.exports.replyGet = ( event, context, callback ) => { Reply.find( event.pathParameters.id ) .then( ( reply ) => { - let statusCode = 200; - if( Object.keys( reply ).length === 0 ) statusCode = 404; // Catch a 404 - let response = { - statusCode: statusCode, // Will be used by the API gateway in the response - body: reply + statusCode: Object.keys( reply ).length === 0 ? 404 : 200, + body: JSON.stringify( reply ) } - console.log( 'response', response ); - callback( null, response ); }) .catch( function( error ) { diff --git a/api/replies/replyList.js b/api/replies/replyList.js index a7ef684..c1eaa4e 100644 --- a/api/replies/replyList.js +++ b/api/replies/replyList.js @@ -4,10 +4,9 @@ var Reply = require("./_classes/Reply"); var ReplyQueryBuilder = require("./_classes/ReplyQueryBuilder"); -var CustomErrors = require("./../_errors/CustomErrors"); +var CustomErrors = require("./../_classes/CustomErrors"); var DynamodbError = CustomErrors.DynamodbError; - -var DynamodbService = require("./../_services/DynamodbService"); +var ValidationError = CustomErrors.ValidationError; /** * Handler for the lambda function. @@ -40,8 +39,8 @@ module.exports.replyList = (event, context, callback) => { .then( ( replies ) => { const response = { - statusCode: ( replies.length > 0 ? 200 : 204 ), - body: replies + statusCode: replies.Items.length > 0 ? 200 : 204, + body: JSON.stringify( replies ) }; callback( null, response ); From 2adefa8b656f9b9cd6d5bc776ccfd715f0ea8596 Mon Sep 17 00:00:00 2001 From: jacksoncharles Date: Mon, 18 Dec 2017 23:33:59 +0000 Subject: [PATCH 32/78] readme --- README.md | 21 +++++++++++++++------ 1 file changed, 15 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index e899f3c..bd47930 100644 --- a/README.md +++ b/README.md @@ -1,10 +1,10 @@ # Forum Microservice -A possible starter-for-10 forum microservice. +A template for a forum microservice. -Includes basic functionality such as pagination and global secondary indexes for retrieiving by user or thread. And is loosely inspired by the [AWS Example Forum](http://docs.aws.amazon.com/amazondynamodb/latest/developerguide/SampleData.CreateTables.html). +A robust solution with common functionality including pagination and global secondary indexes for retrieiving by user, thread or primayr key. And is loosely inspired by the [AWS Example Forum](http://docs.aws.amazon.com/amazondynamodb/latest/developerguide/SampleData.CreateTables.html). -Can be built and implemented as part of a distributed solution. +Designed to be implemented as part of a distributed solution. ## Technology Stack 1. [AWS Lambda](https://aws.amazon.com/lambda/) @@ -15,9 +15,9 @@ Can be built and implemented as part of a distributed solution. ## Installation & Deployment Deploying the forum microservice will provision and create the following resources. -1. API Gateway entitled "forum-microservice" with 10 endpoints. +1. API Gateway entitled forum-microservice with 10 endpoints. 2. 10 * Lambda functions with associated Cloud Watch logs. -3. 2 * DynamoDB tables. +3. 2 * DynamoDB tables called Thread and Reply. To deploy from your desktop you must have an existing AWS account with command line access. Firstly, ensure you have installed the [Serverless Framework](http://serverless.com). @@ -25,7 +25,16 @@ To deploy from your desktop you must have an existing AWS account with command l npm install serverless -g ``` -Then, from the project root folder simply enter the following command to provision and deploy your sevice to AWS. +Then, from the project root + +``` + npm install +``` + + +``` + ./api/npm install +``` ``` sls deploy From 018041616ffbe1048c2e4ea1807c483dc1a38d3c Mon Sep 17 00:00:00 2001 From: jacksoncharles Date: Mon, 18 Dec 2017 23:36:24 +0000 Subject: [PATCH 33/78] readme --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index bd47930..5a33b97 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,7 @@ A template for a forum microservice. -A robust solution with common functionality including pagination and global secondary indexes for retrieiving by user, thread or primayr key. And is loosely inspired by the [AWS Example Forum](http://docs.aws.amazon.com/amazondynamodb/latest/developerguide/SampleData.CreateTables.html). +A robust solution with common functionality including pagination and global secondary indexes for retrieiving by user, thread or unique key. And is loosely inspired by the [AWS Example Forum](http://docs.aws.amazon.com/amazondynamodb/latest/developerguide/SampleData.CreateTables.html). Designed to be implemented as part of a distributed solution. From c18b5fda995fddb21b87c7a099e4a802c48d75bf Mon Sep 17 00:00:00 2001 From: jacksoncharles Date: Mon, 18 Dec 2017 23:38:41 +0000 Subject: [PATCH 34/78] readme --- README.md | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/README.md b/README.md index 5a33b97..899c372 100644 --- a/README.md +++ b/README.md @@ -2,9 +2,7 @@ A template for a forum microservice. -A robust solution with common functionality including pagination and global secondary indexes for retrieiving by user, thread or unique key. And is loosely inspired by the [AWS Example Forum](http://docs.aws.amazon.com/amazondynamodb/latest/developerguide/SampleData.CreateTables.html). - -Designed to be implemented as part of a distributed solution. +A robust solution with common functionality including pagination and global secondary indexes for retrieiving by user, thread or unique key. It is loosely inspired by the [AWS Example Forum](http://docs.aws.amazon.com/amazondynamodb/latest/developerguide/SampleData.CreateTables.html) and designed as part of a distributed solution. ## Technology Stack 1. [AWS Lambda](https://aws.amazon.com/lambda/) From 3ae38ff9ee65d3495a537d98bc2c42c52502cfde Mon Sep 17 00:00:00 2001 From: jacksoncharles Date: Mon, 18 Dec 2017 23:40:40 +0000 Subject: [PATCH 35/78] readme --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 899c372..786882d 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # Forum Microservice -A template for a forum microservice. +A microservice template for a Q&A application such as comments functionality or discussion forum. A robust solution with common functionality including pagination and global secondary indexes for retrieiving by user, thread or unique key. It is loosely inspired by the [AWS Example Forum](http://docs.aws.amazon.com/amazondynamodb/latest/developerguide/SampleData.CreateTables.html) and designed as part of a distributed solution. From a7e818481b768af55627b0f91c43c3bed95cd2b0 Mon Sep 17 00:00:00 2001 From: jacksoncharles Date: Mon, 18 Dec 2017 23:46:57 +0000 Subject: [PATCH 36/78] readme --- README.md | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 786882d..c0498bc 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,7 @@ # Forum Microservice -A microservice template for a Q&A application such as comments functionality or discussion forum. +A microservice template for a Q&A application such as comments functionality or discussion forum deployed using +the [Serverless Framework](http://serverless.com). A robust solution with common functionality including pagination and global secondary indexes for retrieiving by user, thread or unique key. It is loosely inspired by the [AWS Example Forum](http://docs.aws.amazon.com/amazondynamodb/latest/developerguide/SampleData.CreateTables.html) and designed as part of a distributed solution. @@ -17,23 +18,28 @@ Deploying the forum microservice will provision and create the following resourc 2. 10 * Lambda functions with associated Cloud Watch logs. 3. 2 * DynamoDB tables called Thread and Reply. -To deploy from your desktop you must have an existing AWS account with command line access. Firstly, ensure you have installed the [Serverless Framework](http://serverless.com). +To deploy from your desktop you must have an existing AWS account with command line access. + +Firstly, install the [Serverless Framework](http://serverless.com). ``` npm install serverless -g ``` -Then, from the project root +Secondly, install the [Serverless Framework](http://serverless.com) dependencies. ``` npm install ``` +Next, install your microservice API dependencies. ``` ./api/npm install ``` +Lasty, deploy your microservice API. + ``` sls deploy ``` From 13ef622e152672307e9645f3a157437f80cafbba Mon Sep 17 00:00:00 2001 From: jacksoncharles Date: Wed, 27 Dec 2017 19:43:58 +0000 Subject: [PATCH 37/78] replies --- api/_classes/Dynamic.js | 138 ++++++++++---------- api/_classes/DynamodbService.js | 3 +- api/_classes/{CustomErrors.js => Errors.js} | 0 api/package.json | 3 +- api/replies/_classes/Reply.js | 34 +++++ api/replies/_classes/ReplyQueryBuilder.js | 4 +- api/replies/replyCreate.js | 46 ++++--- api/replies/replyDelete.js | 4 +- api/replies/replyList.js | 6 +- api/replies/replyUpdate.js | 46 ++++--- package.json | 3 +- 11 files changed, 171 insertions(+), 116 deletions(-) rename api/_classes/{CustomErrors.js => Errors.js} (100%) diff --git a/api/_classes/Dynamic.js b/api/_classes/Dynamic.js index ff9552e..2974566 100644 --- a/api/_classes/Dynamic.js +++ b/api/_classes/Dynamic.js @@ -1,12 +1,12 @@ 'use strict'; const AWS = require('aws-sdk'); -const dynamoDb = new AWS.DynamoDB.DocumentClient(); +const dynamodb = new AWS.DynamoDB.DocumentClient(); -var CustomErrors = require("./CustomErrors"); -var DynamodbError = CustomErrors.DynamodbError; -var ValidationError = CustomErrors.ValidationError; -var NotFoundError = CustomErrors.NotFoundError; +var Errors = require("./Errors"); +var DynamodbError = Errors.DynamodbError; +var ValidationError = Errors.ValidationError; +var NotFoundError = Errors.NotFoundError; /** * Wrapper for DynamoDb with basic CRUD functionality and a validation method @@ -20,7 +20,7 @@ module.exports = class Dynamic { /** Grab all the parameters and assign as class properties */ Object.assign(this, parameters ); } - + /** * Save the current instance to permanent storage creating a new record or updating an existing record * @@ -28,19 +28,28 @@ module.exports = class Dynamic { */ save() { + var self = this; + return new Promise( function( resolve, reject ) { + /** @type {Object} Holds the parameters for the get request */ + const parameters = { + + TableName : process.env.DYNAMODB_REPLY_TABLE, + Item : self.properties() + } + // Save to permanent storage - return dynamoDb.delete( this.properties(), function( error, data ) { + return dynamodb.put( parameters, function( error, data ) { - // create a response - const response = { + // Handle DynamoDb errors + if( error ) return reject( error ); - statusCode: 200, - body: JSON.stringify( data ) - }; + /** @type {Object} Create a new instance of self and populate with the data */ + let modelInstance = self.constructor.model( parameters.Item ); - callback (null, response ); + /** All successful. Create a valid response */ + return resolve( modelInstance ); }); }) .catch( function( error ) { @@ -51,58 +60,6 @@ module.exports = class Dynamic { }); } - /** - * Validates the rules defined in this.validation_rules and throws an error - * else returns {this} - * - * @return {this} - */ - validate() { - - let errors = this.validator.validate( Object.keys( this ) ); - - throw new ValidationError( errors ); - - return this; - } - - /** - * Retrieve an array of replies according to the parameters passed - * - * @return {Array} Array of replies. - */ - static destroy( id ) { - - /** @type {Object} Holds the parameters for the get request */ - const parameters = { - - TableName : process.env.DYNAMODB_REPLY_TABLE, - Key : { - Id : id - } - } - - return new Promise( function( resolve, reject ) { - - /** Run a dynamoDb get request passing-in our parameters */ - return dynamoDb.delete( parameters, function( error, data ) { - - /** Handle potential dynamoDb errors */ - if ( error ) return reject( error ); - - /** All successful. Create a valid response */ - return resolve( JSON.stringify( data ) ); - }); - - }) - .catch( function( error ) { - - console.log('<<>>', error ); - - throw new DynamodbError( error ); - }); - } - /** * Retrieve an array of replies according to the parameters passed * @@ -123,10 +80,10 @@ module.exports = class Dynamic { return new Promise( function( resolve, reject ) { - /** Run a dynamoDb get request passing-in our parameters */ - return dynamoDb.get( parameters, function( error, data ) { + /** Run a dynamodb get request passing-in our parameters */ + return dynamodb.get( parameters, function( error, data ) { - /** Handle potential dynamoDb errors */ + /** Handle potential dynamodb errors */ if ( error ) return reject( error ); /** @type {Object} Create a new instance of self and populate with the data */ @@ -137,7 +94,7 @@ module.exports = class Dynamic { }); }) - .catch( function( error ) { // Capture a dynamoDb rejection + .catch( function( error ) { // Capture a dynamodb rejection console.log('<<>>', error ); @@ -156,10 +113,10 @@ module.exports = class Dynamic { return new Promise( function( resolve, reject ) { - /** Run a dynamoDb query passing-in Query.parameters */ - return dynamoDb.query( parameters, function( error, data ) { + /** Run a dynamodb query passing-in Query.parameters */ + return dynamodb.query( parameters, function( error, data ) { - /** Handle potential dynamoDb errors */ + /** Handle potential dynamodb errors */ if ( error ) return reject( error ); let items = []; @@ -183,4 +140,41 @@ module.exports = class Dynamic { throw new DynamodbError( error ); }); } + + /** + * Retrieve an array of replies according to the parameters passed + * + * @return {Array} Array of replies. + */ + static destroy( id ) { + + /** @type {Object} Holds the parameters for the get request */ + const parameters = { + + TableName : process.env.DYNAMODB_REPLY_TABLE, + Key : { + Id : id + } + } + + return new Promise( function( resolve, reject ) { + + /** Run a dynamodb get request passing-in our parameters */ + return dynamodb.delete( parameters, function( error, data ) { + + /** Handle potential dynamodb errors */ + if ( error ) return reject( error ); + + /** All successful. Create a valid response */ + return resolve( JSON.stringify( data ) ); + }); + + }) + .catch( function( error ) { + + console.log('<<>>', error ); + + throw new DynamodbError( error ); + }); + } } \ No newline at end of file diff --git a/api/_classes/DynamodbService.js b/api/_classes/DynamodbService.js index 7ccdfcb..f11fdff 100644 --- a/api/_classes/DynamodbService.js +++ b/api/_classes/DynamodbService.js @@ -3,7 +3,8 @@ const AWS = require('aws-sdk'); const dynamoDb = new AWS.DynamoDB.DocumentClient(); -var DynamodbError = require('./../_errors/DynamodbError'); +var Errors = require("./Errors"); +var DynamodbError = Errors.DynamodbError; /** * CRUD service for DynamoDb. diff --git a/api/_classes/CustomErrors.js b/api/_classes/Errors.js similarity index 100% rename from api/_classes/CustomErrors.js rename to api/_classes/Errors.js diff --git a/api/package.json b/api/package.json index d98f588..f3244a8 100644 --- a/api/package.json +++ b/api/package.json @@ -12,6 +12,7 @@ "keywords": [], "devDependencies": {}, "dependencies": { - "validator": "^9.1.2" + "validator": "^9.1.2", + "uuid": "^3.1.0" } } \ No newline at end of file diff --git a/api/replies/_classes/Reply.js b/api/replies/_classes/Reply.js index d2f9da4..1eeeb7b 100644 --- a/api/replies/_classes/Reply.js +++ b/api/replies/_classes/Reply.js @@ -1,5 +1,9 @@ 'use strict'; +const validator = require('validator'); + +var Errors = require("./../../_classes/Errors"); +var ValidationError = Errors.ValidationError; var Dynamic = require('./../../_classes/Dynamic'); /** @@ -26,4 +30,34 @@ module.exports = class Reply extends Dynamic { return new Reply( parameters ); } + + properties() { + + let now = new Date(); + + return { + 'Id': this.Id, + 'Message': this.Message, + 'ThreadId': this.ThreadId, + 'CreatedDateTime': now.toJSON(), + 'UpdatedDateTime': now.toJSON(), + 'UserId': this.UserId, + 'UserName': this.UserName + } + } + + validate() { + + let errors = []; + + if( typeof this.Id == 'undefined' || validator.isEmpty( this.Id ) ) errors.push({'Id': 'must provide a unique string for Id'}); + if( typeof this.Message == 'undefined' || validator.isEmpty( this.Message ) ) errors.push({'Message': 'must provide a value for Message'}); + if( typeof this.ThreadId == 'undefined' || validator.isEmpty( this.ThreadId ) ) errors.push({'ThreadId': 'must provide a value for ThreadId'}); + if( typeof this.UserId == 'undefined' || validator.isEmpty( this.UserId ) ) errors.push({'UserId': 'must provide a value for UserId'}); + if( typeof this.UserName == 'undefined' || validator.isEmpty( this.UserName ) ) errors.push({'UserName': 'must provide a value for UserName'}); + + if( errors.length ) throw new ValidationError( JSON.stringify( errors ) ); + + return this; + } } \ No newline at end of file diff --git a/api/replies/_classes/ReplyQueryBuilder.js b/api/replies/_classes/ReplyQueryBuilder.js index b433c0a..c4edc54 100644 --- a/api/replies/_classes/ReplyQueryBuilder.js +++ b/api/replies/_classes/ReplyQueryBuilder.js @@ -2,8 +2,8 @@ const validator = require('validator'); -var CustomErrors = require("./../../_classes/CustomErrors"); -var ValidationError = CustomErrors.ValidationError; +var Errors = require("./../../_classes/Errors"); +var ValidationError = Errors.ValidationError; /** * Responsible for turning parameters passeed are turned in DynamoDb parameters by building diff --git a/api/replies/replyCreate.js b/api/replies/replyCreate.js index c1dbc7e..c257e04 100644 --- a/api/replies/replyCreate.js +++ b/api/replies/replyCreate.js @@ -1,9 +1,9 @@ 'use strict'; +const uuidv1 = require('uuid/v1'); var Reply = require("./_classes/Reply"); - -var CustomErrors = require("./../_classes/CustomErrors"); -var ValidationError = CustomErrors.ValidationError; +var Errors = require("./../_classes/Errors"); +var ValidationError = Errors.ValidationError; /** * Handler for the lambda function. @@ -16,21 +16,35 @@ var ValidationError = CustomErrors.ValidationError; */ module.exports.replyCreate = (event, context, callback) => { - let reply = new Reply( this.event.queryStringParameters ); + try { - reply - .validate() - .save() - .then( ( data ) => { + let parameters = JSON.parse( event.body ); + parameters['Id']= uuidv1(); - const response = { - statusCode: 200, - body: data - } + let reply = new Reply( parameters ); + + reply + .validate() + .save() + .then( ( data ) => { + + const response = { + statusCode: 200, + body: JSON.stringify( data ) + } + + return callback( null, response ); + }) + .catch( function( error ) { + + callback(null, { + statusCode: 500, + body: JSON.stringify( { message: error.message } ) + }); + + }); - return callback( null, response ); - }) - .catch( function( error ) { + } catch( error ) { if( error instanceof ValidationError ) { @@ -58,5 +72,5 @@ module.exports.replyCreate = (event, context, callback) => { body: JSON.stringify( error ) }); } - }); + } }; \ No newline at end of file diff --git a/api/replies/replyDelete.js b/api/replies/replyDelete.js index 2193c0f..836e9ac 100644 --- a/api/replies/replyDelete.js +++ b/api/replies/replyDelete.js @@ -1,7 +1,7 @@ 'use strict'; -var CustomErrors = require("./../../_classes/CustomErrors"); -var ValidationError = CustomErrors.ValidationError; +var Errors = require("./../../_classes/Errors"); +var ValidationError = Errors.ValidationError; var Reply = require("./_models/Reply"); diff --git a/api/replies/replyList.js b/api/replies/replyList.js index c1eaa4e..ec56031 100644 --- a/api/replies/replyList.js +++ b/api/replies/replyList.js @@ -4,9 +4,9 @@ var Reply = require("./_classes/Reply"); var ReplyQueryBuilder = require("./_classes/ReplyQueryBuilder"); -var CustomErrors = require("./../_classes/CustomErrors"); -var DynamodbError = CustomErrors.DynamodbError; -var ValidationError = CustomErrors.ValidationError; +var Errors = require("./../_classes/Errors"); +var DynamodbError = Errors.DynamodbError; +var ValidationError = Errors.ValidationError; /** * Handler for the lambda function. diff --git a/api/replies/replyUpdate.js b/api/replies/replyUpdate.js index b3bd9fc..78390ec 100644 --- a/api/replies/replyUpdate.js +++ b/api/replies/replyUpdate.js @@ -1,9 +1,8 @@ 'use strict'; -var Reply = require("./_models/Reply"); - -var CustomErrors = require("./../../_classes/CustomErrors"); -var ValidationError = CustomErrors.ValidationError; +var Reply = require("./_classes/Reply"); +var Errors = require("./../_classes/Errors"); +var ValidationError = Errors.ValidationError; /** * Handler for the lambda function. @@ -16,21 +15,34 @@ var ValidationError = CustomErrors.ValidationError; */ module.exports.replyUpdate = (event, context, callback) => { - let reply = new Reply( this.event.queryStringParameters ); + try { - reply - .validate() - .save() - .then( ( reply ) => { + let parameters = JSON.parse( event.body ); - const response = { - statusCode: 200, - body: reply - } + let reply = new Reply( parameters ); + + reply + .validate() + .save() + .then( ( data ) => { + + const response = { + statusCode: 200, + body: JSON.stringify( data ) + } + + return callback( null, response ); + }) + .catch( function( error ) { + + callback(null, { + statusCode: 500, + body: JSON.stringify( { message: error.message } ) + }); + + }); - return callback( null, response ); - }) - .catch( function( error ) { + } catch( error ) { if( error instanceof ValidationError ) { @@ -58,5 +70,5 @@ module.exports.replyUpdate = (event, context, callback) => { body: JSON.stringify( error ) }); } - }); + } }; \ No newline at end of file diff --git a/package.json b/package.json index 202fbda..3bd006b 100644 --- a/package.json +++ b/package.json @@ -10,7 +10,6 @@ "license": "ISC", "dependencies": { "bluebird": "^3.5.1", - "lint": "^1.1.2", - "uuid": "^3.1.0" + "lint": "^1.1.2" } } From 95d0d809750e1270d0a503403de1808c107d2200 Mon Sep 17 00:00:00 2001 From: jacksoncharles Date: Wed, 27 Dec 2017 19:53:00 +0000 Subject: [PATCH 38/78] readme --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index c0498bc..f1d0940 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ -# Forum Microservice +# Q&A Microservice Template -A microservice template for a Q&A application such as comments functionality or discussion forum deployed using +A microservice template for a Q&A solution such as comments functionality or discussion forum deployed using the [Serverless Framework](http://serverless.com). A robust solution with common functionality including pagination and global secondary indexes for retrieiving by user, thread or unique key. It is loosely inspired by the [AWS Example Forum](http://docs.aws.amazon.com/amazondynamodb/latest/developerguide/SampleData.CreateTables.html) and designed as part of a distributed solution. From be0f6ebd804135c6b60f42c6c04bb66bf1e5d13e Mon Sep 17 00:00:00 2001 From: jacksoncharles Date: Wed, 27 Dec 2017 19:58:53 +0000 Subject: [PATCH 39/78] readme --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index f1d0940..593c6f5 100644 --- a/README.md +++ b/README.md @@ -3,7 +3,7 @@ A microservice template for a Q&A solution such as comments functionality or discussion forum deployed using the [Serverless Framework](http://serverless.com). -A robust solution with common functionality including pagination and global secondary indexes for retrieiving by user, thread or unique key. It is loosely inspired by the [AWS Example Forum](http://docs.aws.amazon.com/amazondynamodb/latest/developerguide/SampleData.CreateTables.html) and designed as part of a distributed solution. +A simple but robust solution that can be built upon. Includes common functionality including pagination and global secondary indexes for retrieiving by user, thread or unique key. It is loosely inspired by the [AWS Example Forum](http://docs.aws.amazon.com/amazondynamodb/latest/developerguide/SampleData.CreateTables.html) and designed as part of a distributed solution. ## Technology Stack 1. [AWS Lambda](https://aws.amazon.com/lambda/) From 91a3fad60a9f31432ae6bbc801afd7204ceac009 Mon Sep 17 00:00:00 2001 From: jacksoncharles Date: Wed, 27 Dec 2017 20:00:18 +0000 Subject: [PATCH 40/78] readme --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 593c6f5..5854520 100644 --- a/README.md +++ b/README.md @@ -3,7 +3,7 @@ A microservice template for a Q&A solution such as comments functionality or discussion forum deployed using the [Serverless Framework](http://serverless.com). -A simple but robust solution that can be built upon. Includes common functionality including pagination and global secondary indexes for retrieiving by user, thread or unique key. It is loosely inspired by the [AWS Example Forum](http://docs.aws.amazon.com/amazondynamodb/latest/developerguide/SampleData.CreateTables.html) and designed as part of a distributed solution. +A simple but robust solution that can be built upon. Includes common functionality including pagination and global secondary indexes for retrieiving by user, thread or unique key. It is loosely inspired by the [AWS Example Forum](http://docs.aws.amazon.com/amazondynamodb/latest/developerguide/SampleData.CreateTables.html) and designed to be implemented as part of a distributed system. ## Technology Stack 1. [AWS Lambda](https://aws.amazon.com/lambda/) From 30c91369491828e01554f26065508054cc7c4f27 Mon Sep 17 00:00:00 2001 From: jacksoncharles Date: Wed, 27 Dec 2017 20:57:10 +0000 Subject: [PATCH 41/78] readme --- README.md | 4 +- api/_classes/Dynamic.js | 4 +- api/replies/_classes/Reply.js | 20 +- api/threads/_classes/Thread.js | 73 +++++++ api/threads/_classes/ThreadQueryBuilder.js | 189 +++++++++++++++++++ api/threads/threadCreate.js | 194 +++++-------------- api/threads/threadDelete.js | 55 +++--- api/threads/threadGet.js | 53 ++---- api/threads/threadList.js | 210 +++++---------------- api/threads/threadUpdate.js | 194 +++++-------------- 10 files changed, 457 insertions(+), 539 deletions(-) create mode 100644 api/threads/_classes/Thread.js create mode 100644 api/threads/_classes/ThreadQueryBuilder.js diff --git a/README.md b/README.md index 5854520..87c2349 100644 --- a/README.md +++ b/README.md @@ -66,8 +66,8 @@ Will create X Lambda functions accessible via [API Gateway](https://aws.amazon.c NAME | LAMBDA | URL | VERB | DESCRIPTION ---- | ------ | --- | ---- | ----------- CREATE | threadCreate | /threads | POST | Create a new item in permanent storage. -LIST | | threadList | /threads | GET | Retrieve a paginated listing from permanent storage. -GET | | threadGet | /threads/:id | GET | Retrieve a individual item using the ```threadid``` or ```userid``` passed in the query string. +LIST | threadList | /threads | GET | Retrieve a paginated listing from permanent storage. +GET | threadGet | /threads/:id | GET | Retrieve a individual item using the ```threadid``` or ```userid``` passed in the query string. UPDATE | threadUpdate| /threads/:id | PUT | Update details of a post by providing a full array of model data. DELETE | threadDelete | /threads/:id | DELETE | Remove an item from permanent storage. CREATE | replyCreate | /replies | POST | Create a new item in permanent storage. diff --git a/api/_classes/Dynamic.js b/api/_classes/Dynamic.js index 2974566..660b878 100644 --- a/api/_classes/Dynamic.js +++ b/api/_classes/Dynamic.js @@ -87,7 +87,7 @@ module.exports = class Dynamic { if ( error ) return reject( error ); /** @type {Object} Create a new instance of self and populate with the data */ - let modelInstance = self.model( data.Item ); + let modelInstance = self.constructor.model( data.Item ); /** All successful. Create a valid response */ return resolve( modelInstance ); @@ -123,7 +123,7 @@ module.exports = class Dynamic { for ( let item of data.Items ) { - items.push( self.model( item ) ); + items.push( self.constructor.model( item ) ); } data['Items'] = items; diff --git a/api/replies/_classes/Reply.js b/api/replies/_classes/Reply.js index 1eeeb7b..964c33d 100644 --- a/api/replies/_classes/Reply.js +++ b/api/replies/_classes/Reply.js @@ -31,21 +31,29 @@ module.exports = class Reply extends Dynamic { return new Reply( parameters ); } + /** + * Return an object that represents the properties of this class + * + * @return {Object} - Class properties that can be saved to dynamodb + */ properties() { - let now = new Date(); - return { 'Id': this.Id, - 'Message': this.Message, 'ThreadId': this.ThreadId, - 'CreatedDateTime': now.toJSON(), - 'UpdatedDateTime': now.toJSON(), 'UserId': this.UserId, - 'UserName': this.UserName + 'UserName': this.UserName, + 'Message': this.Message, + 'CreatedDateTime': this.CreatedDateTime, + 'UpdatedDateTime': this.UpdatedDateTime } } + /** + * Validate the class properties and throw an exception if necessary + * + * @return {this} - Instance of this + */ validate() { let errors = []; diff --git a/api/threads/_classes/Thread.js b/api/threads/_classes/Thread.js new file mode 100644 index 0000000..931caf5 --- /dev/null +++ b/api/threads/_classes/Thread.js @@ -0,0 +1,73 @@ +'use strict'; + +const validator = require('validator'); + +var Errors = require("./../../_classes/Errors"); +var ValidationError = Errors.ValidationError; +var Dynamic = require('./../../_classes/Dynamic'); + +/** + * Thread class. Each instance maps to one document in permanent storage and extends the + * Dynamic wrapper class. + * + * @type {class} + */ +module.exports = class Thread extends Dynamic { + + constructor( parameters ) { + + super( parameters ); + } + + /** + * Return a new instance of this + * + * @param {Object} parameters - Properties to be assigned to the newly created object + * + * @return {Object} New instance of the Thread object + */ + static model( parameters ) { + + return new Thread( parameters ); + } + + /** + * Return an object that represents the properties of this class + * + * @return {Object} - Class properties that can be saved to dynamodb + */ + properties() { + + return { + 'Id': this.Id, + 'ForumId': this.ForumId, + 'UserId': this.UserId, + 'UserName': this.UserName, + 'Title': this.Title, + 'Message': this.Message, + 'CreatedDateTime': this.CreatedDateTime, + 'UpdatedDateTime': this.UpdatedDateTime + } + } + + /** + * Validate the class properties and throw an exception if necessary + * + * @return {this} - Instance of this + */ + validate() { + + let errors = []; + + if( typeof this.Id == 'undefined' || validator.isEmpty( this.Id ) ) errors.push({'Id': 'must provide a unique string for Id'}); + if( typeof this.ForumId == 'undefined' || validator.isEmpty( this.ForumId ) ) errors.push({'ForumId': 'must provide a value for ForumId'}); + if( typeof this.UserId == 'undefined' || validator.isEmpty( this.UserId ) ) errors.push({'UserId': 'must provide a value for UserId'}); + if( typeof this.UserName == 'undefined' || validator.isEmpty( this.UserName ) ) errors.push({'UserName': 'must provide a value for UserName'}); + if( typeof this.Title == 'undefined' || validator.isEmpty( this.Title ) ) errors.push({'Title': 'must provide a value for Title'}); + if( typeof this.Message == 'undefined' || validator.isEmpty( this.Message ) ) errors.push({'Message': 'must provide a value for Message'}); + + if( errors.length ) throw new ValidationError( JSON.stringify( errors ) ); + + return this; + } +} \ No newline at end of file diff --git a/api/threads/_classes/ThreadQueryBuilder.js b/api/threads/_classes/ThreadQueryBuilder.js new file mode 100644 index 0000000..c808eaa --- /dev/null +++ b/api/threads/_classes/ThreadQueryBuilder.js @@ -0,0 +1,189 @@ +'use strict'; + +const validator = require('validator'); + +var Errors = require("./../../_classes/Errors"); +var ValidationError = Errors.ValidationError; + +/** + * Responsible for turning parameters passeed are turned in DynamoDb parameters by building + * this.parameters object using data passed inside this.events.queryStringParameters + * + * @type {class} + */ +module.exports = class ThreadQueryBuilder { + + constructor( criterion ) { + + /** @type {Object} Key/value pairs used to build our DynamoDb parameters. */ + this._criterion = criterion; + + /** + * Used to hold the dynamodb query parameters built using values + * within property this.event + * + * @todo : Change TableName to value of process.env.DYNAMODB_THREAD_TABLE + * + * @type {object} + */ + this._parameters = { + TableName: 'Thread' + } + + /** + * Used to hold any validation errors. + * + * @type {array} + */ + this._errors = []; + } + + /** + * Getter + * + * @return {object} parameters + */ + get parameters() { + + return this._parameters; + } + + /** + * Setter + * + * @return {object} parameters + */ + set parameters( parameters ) { + + this._parameters = parameters; + } + + /** + * Getter + * + * @return {array} errors + */ + get errors() { + + return this._errors; + } + + /** + * Setter + * + * @return {array} errors + */ + set errors( errors ) { + + this._errors = errors; + } + + /** + * Validates the parameters passed inside this._criterion object + * + * @return {boolean} + */ + validates() { + + this._errors = []; // Reset the errors array before running the validation logic + + if( this._criterion !== null && typeof this._criterion === 'object' ) { + + if( this._criterion.hasOwnProperty( 'threadid' ) == false && + this._criterion.hasOwnProperty( 'userid' ) == false + ) { + + this._errors.push( { "message": "You must provide a threadid or userid parameter" } ); + } + + if( this._criterion.hasOwnProperty( 'threadid' ) ) { + + if ( validator.isAlphanumeric( this._criterion.threadid ) == false ) { + + this._errors.push( { "message": "Your threadid parameter must be an alphanumeric string" } ); + } + } + + if( this._criterion.hasOwnProperty('userid') ) { + + if ( validator.isNumeric( this._criterion.userid ) == false ) { + + this._errors.push( { "message": "Your userid parameter must be numeric" } ); + } + } + } + else { + + this._errors.push( { "message" : "You must supply a threadid or userid" } ); + } + + if( this._errors.length ) { + + throw new ValidationError( JSON.stringify( this._errors ) ); + } + + return this; + } + + /** + * If "threadid" has been passed inside this.event this method will build upon this.parameters object + * + * @return this + */ + buildThreadIndex() { + + if( this._criterion.hasOwnProperty('threadid') ) { + + this._parameters['IndexName'] = "ThreadIndex"; + this._parameters['KeyConditionExpression'] = "ThreadId = :searchstring"; + this._parameters['ExpressionAttributeValues'] = { + ":searchstring" : this._criterion.threadid + }; + } + + return this; + } + + /** + * If "userid" has been passed inside this.event this method will build upon this.parameters object + * + * @return this + */ + buildUserIndex() { + + if ( this._criterion.hasOwnProperty('userid') ) { + + this._parameters['IndexName'] = "UserIndex"; + this._parameters['KeyConditionExpression'] = "UserId = :searchstring"; + this._parameters['ExpressionAttributeValues'] = { + ":searchstring" : this._criterion.userid + }; + } + + return this; + } + + /** + * If pagination parameters have been passed inside this.event this method will build upon this.parameters object + * + * @return this + */ + buildPagination() { + + if ( this._criterion.hasOwnProperty('threadid') && + this._criterion.hasOwnProperty('createddatetime') ) + { + this._parameters['ExclusiveStartKey'] = { + ThreadId: this._criterion.threadid, + DateTime: this._criterion.createddatetime + } + } + + if ( this._criterion.hasOwnProperty('limit') ) { + + this._parameters['Limit'] = this._criterion.limit; + } + + return this; + } +} \ No newline at end of file diff --git a/api/threads/threadCreate.js b/api/threads/threadCreate.js index 054eb3e..1d09de2 100644 --- a/api/threads/threadCreate.js +++ b/api/threads/threadCreate.js @@ -1,11 +1,9 @@ 'use strict'; +const uuidv1 = require('uuid/v1'); -const uuid = require('uuid'); -const AWS = require('aws-sdk'); -var schema = require('validate'); - -AWS.config.setPromisesDependency(require('bluebird')); -const dynamoDb = new AWS.DynamoDB.DocumentClient(); +var Thread = require("./_classes/Thread"); +var Errors = require("./../_classes/Errors"); +var ValidationError = Errors.ValidationError; /** * Handler for the lambda function. @@ -18,161 +16,61 @@ const dynamoDb = new AWS.DynamoDB.DocumentClient(); */ module.exports.threadCreate = (event, context, callback) => { - var QueryBuilder = function( event ) { - - /** - * Capture the event object passed as a parameter; - * - * @type event - */ - this.event = event; - - /** - * Used to build the object being saved to permanent storage. The value of "Item" will - * be populated with user passed parameters - * - * @todo : Change TableName to value of process.env.DYNAMODB_REPLY_TABLE - * - * @type Object - */ - this.parameters = { - TableName: 'Thread' - Item: {} - } + try { - /** - * Holds the schema for validating the parameters passed with the request. Anything failing - * validation will be stored inside this.errors - * - * @type {Object} - */ - this.validator = schema({ - - ThreadId: { - type: 'string', - required: true, - message: 'threadid is required.' - }, - UserId: { - type: 'number', - required: true, - message: 'userid is required.' - }, - Title: { - type: 'string', - required: true, - message: 'title is required.' - }, - Message: { - type: 'string', - required: true, - message: 'message is required.' - }, - UserName: { - type: 'string', - required: true, - message: 'username is required.' - } - }); + let parameters = JSON.parse( event.body ); + parameters['Id']= uuidv1(); - /** - * Used to hold any validation messages. - * - * @type {Array} - */ - this.errors = []; - } + let thread = new Thread( parameters ); - /** - * Populates the "item" object prior to saving - * - * @return {this} - */ - QueryBuilder.prototype.hydrate = function() { - - const timestamp = new Date().getTime(); - - this.parameters.Item = { - Id: uuid.v1(), - ThreadId: this.event.queryStringParameters.threadid, - UserId: this.event.queryStringParameters.userid, - Title: this.event.queryStringParameters.title, - Message: this.event.queryStringParameters.message, - UserName: this.event.queryStringParameters.username, - CreatedDateTime: timestamp, - UpdatedDateTime: timestamp - }; - - return this; - } + thread + .validate() + .save() + .then( ( data ) => { - /** - * Validates the data passed in the event object - * - * @return {this} - */ - QueryBuilder.prototype.validates = function() { - - this.errors = []; - - /* - if ( this.event.queryStringParameters.hasOwnProperty('threadid') == false && typeof this.event.queryStringParameters.threadid !== 'string' ) this.errors.push('threadid missing or invalid'); - if ( this.event.queryStringParameters.hasOwnProperty('userid') == false && typeof this.event.queryStringParameters.userid !== 'string' ) this.errors.push('userid missing or invalid'); - if ( this.event.queryStringParameters.hasOwnProperty('message') == false && typeof this.event.queryStringParameters.message !== 'string' ) this.errors.push('message missing or invalid'); - if ( this.event.queryStringParameters.hasOwnProperty('username') == false && typeof this.event.queryStringParameters.username !== 'string' ) this.errors.push('username missing or invalid'); - */ - // Validate the query parameters - this.errors = this.validator.validate( this.event.queryStringParameters ); - - return this.errors.length ? 0 : 1; - } + const response = { + statusCode: 200, + body: JSON.stringify( data ) + } - /** - * Instantiate an instance of Query - * - * @type {QueryBuilder} - */ - var Query = new QueryBuilder( event ); - - // Check to see if the parameters passed in the request validate. - if ( Query.validates() == false ) { - - // Handle validation errors - callback(null, { - statusCode: 422, - body: JSON.stringify({ - message: Query.errors - }) + return callback( null, response ); }) - } - else { + .catch( function( error ) { - // Save to permanent storage - return dynamoDb.put( Query.hydrate().parameters ).promise() - .then( res => reply ) - .then( res => { + callback(null, { + statusCode: 500, + body: JSON.stringify( { message: error.message } ) + }); - // create a response - const response = { + }); - statusCode: 200, - body: JSON.stringify(res), - }; + } catch( error ) { + if( error instanceof ValidationError ) { - callback(null, response); - }) - .catch(err => { + callback(null, { + statusCode: 422, + body: error.message + }); + } + else if( error instanceof DynamodbError ) { + + console.log('<<>>', error ); - console.log('=== error ===', error ); - - // Handle DynamoDb errors callback(null, { statusCode: 500, - body: JSON.stringify({ - message: `Unable to submit reply` - }) - }) - }); + body: JSON.stringify( error ) + }); + + } + else { + + console.log('<<>>', error ); + + callback(null, { + statusCode: 500, + body: JSON.stringify( error ) + }); + } } }; \ No newline at end of file diff --git a/api/threads/threadDelete.js b/api/threads/threadDelete.js index bc80f87..4daa70d 100644 --- a/api/threads/threadDelete.js +++ b/api/threads/threadDelete.js @@ -1,7 +1,12 @@ 'use strict'; -const AWS = require('aws-sdk'); // eslint-disable-line import/no-extraneous-dependencies -const dynamoDb = new AWS.DynamoDB.DocumentClient(); +var Errors = require("./../../_classes/Errors"); +var ValidationError = Errors.ValidationError; + + +var Thread = require("./_models/Thread"); + +var Dynamodb = require("./../_classes/DynamodbService"); /** * Handler for the lambda function. @@ -13,43 +18,25 @@ const dynamoDb = new AWS.DynamoDB.DocumentClient(); * @return JSON JSON encoded response. */ module.exports.threadDelete = (event, context, callback) => { - - /** - * The parameters used by DynamoDb - * - * @type {Object} - */ - const parameters = { - TableName: 'Thread', - Key: { - id: event.pathParameters.id, - } - }; - // Run the query to remove the item from permenent storage - dynamoDb.delete(parameters, (error) => { + Thread.destroy( event.pathParameters.id ) + .then( ( thread ) => { - // Handle any potential DynamoDb errors - if (error) { - - console.error('=== error ===', error); + const response = { + statusCode: 204, + body: thread + } - callback(null, { - statusCode: error.statusCode || 501, - headers: { 'Content-Type': 'text/plain' }, - body: 'Couldn\'t remove the post item.', - }); + return callback( null, response ); + }) + .catch( function( error ) { - } - else { + console.log('<<>>', error ); - // Create a successful response - const response = { - statusCode: 204, - body: JSON.stringify({}), - }; + callback(null, { + statusCode: 500, + body: JSON.stringify( { message: error.message } ) + }); - callback(null, response); - } }); }; \ No newline at end of file diff --git a/api/threads/threadGet.js b/api/threads/threadGet.js index bd57f55..eba8336 100644 --- a/api/threads/threadGet.js +++ b/api/threads/threadGet.js @@ -1,7 +1,6 @@ 'use strict'; -const AWS = require('aws-sdk'); // eslint-disable-line import/no-extraneous-dependencies -const dynamoDb = new AWS.DynamoDB.DocumentClient(); +var Thread = require("./_classes/Thread"); /** * Handler for the lambda function. @@ -12,46 +11,26 @@ const dynamoDb = new AWS.DynamoDB.DocumentClient(); * * @return JSON JSON encoded response. */ -module.exports.threadGet = (event, context, callback) => { - - /** - * The parameters needed by DynamoDb - * - * @type {Object} - */ - const parameters = { - - TableName: 'Thread', - Key: { - Id: event.pathParameters.id - } - } - - // Run the query to retrieve the Item from permanent storage - dynamoDb.get(Query.parameters, (error, result) => { - - // Handle any potential DynamoDb errors - if (error) { - - console.error('=== error ===', error); +module.exports.threadGet = ( event, context, callback ) => { - callback(null, { - statusCode: error.statusCode || 501, - headers: { 'Content-Type': 'text/plain' }, - body: 'Couldn\'t fetch the post item.', - }); + Thread.find( event.pathParameters.id ) + .then( ( thread ) => { + let response = { + statusCode: Object.keys( thread ).length === 0 ? 404 : 200, + body: JSON.stringify( thread ) } - else { - // create a response - const response = { + callback( null, response ); + }) + .catch( function( error ) { - statusCode: 200, - body: JSON.stringify(result.Item), - }; + console.log('<<>>', error ); + + callback(null, { + statusCode: 500, + body: error + }); - callback(null, response); - } }); }; \ No newline at end of file diff --git a/api/threads/threadList.js b/api/threads/threadList.js index e541b2c..a624bab 100644 --- a/api/threads/threadList.js +++ b/api/threads/threadList.js @@ -1,7 +1,12 @@ 'use strict'; -const AWS = require('aws-sdk'); // eslint-disable-line import/no-extraneous-dependencies -const dynamoDb = new AWS.DynamoDB.DocumentClient(); +var Thread = require("./_classes/Thread"); + +var ThreadQueryBuilder = require("./_classes/ThreadQueryBuilder"); + +var Errors = require("./../_classes/Errors"); +var DynamodbError = Errors.DynamodbError; +var ValidationError = Errors.ValidationError; /** * Handler for the lambda function. @@ -15,182 +20,57 @@ const dynamoDb = new AWS.DynamoDB.DocumentClient(); module.exports.threadList = (event, context, callback) => { /** - * Query object used to build this.parameters. - * - * @param {Object} event - AWS Lambda uses this parameter to pass in event data to the handler. + * Instantiate an instance of QueryBuilder * - * @constructor - */ - var QueryBuilder = function( event ) { - - /** - * Capture the event object passed as a parameter; - * - * @type event - */ - this.event = event; - - /** - * Used to hold the dynamodb query parameters built using values - * within property this.event - * - * @todo : Change TableName to value of process.env.DYNAMODB_REPLY_TABLE - * - * @type Object - */ - this.parameters = { - TableName: 'Thread' - } - - /** - * Holds the schema for validating the parameters passed with the request. Anything failing - * validation will be stored inside this.errors - * - * @type {Object} - */ - this.validator = schema({ - - UserId: { - type: 'number', - required: false, - message: 'userid is not a number.' - } - }); - - /** - * Used to hold any validation errors. - * - * @type {Array} - */ - this.errors = []; - - } - - /** - * Validates the parameters passed - * - * @return {boolean} + * @type {QueryBuilder} */ - Query.prototype.validates = function() { - - this.errors = []; - - this.errors = this.validator.validate( this.event.queryStringParameters ); - - return this.errors.length ? 0 : 1; - } + let Query = new ThreadQueryBuilder( event.queryStringParameters ); - /** - * If "userid" has been passed as parameter this method will build the query. - * - * @return this - */ - Query.prototype.setUserIndex = function() { + try { - if ( this.event.queryStringParameters && this.event.queryStringParameters.userid ) { - - this.parameters['IndexName'] = "UserIndex"; - this.parameters['KeyConditionExpression'] = "UserId = :searchstring"; - this.parameters['ProjectionExpression'] = "Id, UserId, Message, CreatedDateTime"; - this.parameters['ExpressionAttributeValues'] = { - ":searchstring" : this.event.queryStringParameters.userid + Query + .validates() + .buildForumIndex() + .buildUserIndex() + .buildPagination(); + + /** @type {model} Contains a list of items and optional pagination data */ + Thread.list( Query.parameters ) + .then( ( threads ) => { + + const response = { + statusCode: threads.Items.length > 0 ? 200 : 204, + body: JSON.stringify( threads ) }; - } - - return this; - } - /** - * If there are any pagination parameters set them here by updating the value of - * parameters object property - * - * @return this - */ - Query.prototype.setPagination = function() { - - if ( this.event.queryStringParameters ) { - - // Pagination - if ( this.event.queryStringParameters.hasOwnProperty('id') && this.event.queryStringParameters.hasOwnProperty('threaddatetime') ) { + callback( null, response ); + }) + .catch( function( error ) { - this.parameters['ExclusiveStartKey'] = { - Id: this.event.queryStringParameters.id, - ThreadDateTime: this.event.queryStringParameters.threaddatetime - } - } - } + callback(null, { + statusCode: 500, + body: JSON.stringify( { message: error.message } ) + }); - return this; + }); } + catch( error ) { // Catch any errors thrown by the ThreadQueryBuilder class + + if( error instanceof ValidationError ) { - /** - * Override the default value of "Limit" with any value passed by the query string. - * - * @return void - */ - Query.prototype.setLimit = function() { + callback(null, { + statusCode: 422, + body: error.message + }); - if ( this.event.queryStringParameters ) { + } else { - if ( this.event.queryStringParameters.hasOwnProperty('limit') ) { + console.log('<<>>', error ); - this.parameters['Limit'] = this.event.queryStringParameters.limit; - } + callback(null, { + statusCode: 500, + body: JSON.stringify( error ) + }); } - - return this; } - - /** - * Instantiate an instance of Query - * - * @type {QueryBuilder} - */ - var Query = new QueryBuilder( event ); - - // Check to see if the parameters passed in the request validate. - if ( Query.validates() == false ) { - - // Handle validation errors - callback(null, { - statusCode: 422, - body: JSON.stringify({ - message: Query.errors - }) - }) - } - else { - - Query - .setThreadIndex() - .setUserIndex() - .setPagination() - .setLimit(); - - // Run the DynamoDb query. - dynamoDb.query( Query.parameters, function( error, data ) { - - // Handle any potential DynamoDb errors - if (error) { - - console.log('=== error ===', error ); - callback(null, { - statusCode: error.statusCode || 501, - body: error.ValidationException || 'Couldn\'t fetch the threads.' - }); - - return; - } - else { - - // Create a response - const response = { - statusCode: 200, - body: JSON.stringify(data), - }; - - callback(null, response); - } - } - }); }; \ No newline at end of file diff --git a/api/threads/threadUpdate.js b/api/threads/threadUpdate.js index 27b27d8..431d68a 100644 --- a/api/threads/threadUpdate.js +++ b/api/threads/threadUpdate.js @@ -1,7 +1,8 @@ 'use strict'; -const AWS = require('aws-sdk'); // eslint-disable-line import/no-extraneous-dependencies -const dynamoDb = new AWS.DynamoDB.DocumentClient(); +var Thread = require("./_classes/Thread"); +var Errors = require("./../_classes/Errors"); +var ValidationError = Errors.ValidationError; /** * Handler for the lambda function. @@ -14,157 +15,60 @@ const dynamoDb = new AWS.DynamoDB.DocumentClient(); */ module.exports.threadUpdate = (event, context, callback) => { - var QueryBuilder = function( event ) { - - /** - * Capture the event object passed as a parameter; - * - * @type event - */ - this.event = event; - - /** - * Used to build the object being saved to permanent storage. The value of "Item" will - * be populated with user passed parameters - * - * @todo : Change TableName to value of process.env.DYNAMODB_REPLY_TABLE - * - * @type Object - */ - this.parameters = { - TableName: 'Thread' - Item: {} - } + try { + + let parameters = JSON.parse( event.body ); + + let thread = new Thread( parameters ); - /** - * Holds the schema for validating the parameters passed with the request. Anything failing - * validation will be stored inside this.errors - * - * @type {Object} - */ - this.validator = schema({ - - ThreadId: { - type: 'string', - required: true, - message: 'threadid is required.' - }, - UserId: { - type: 'number', - required: true, - message: 'userid is required.' - }, - Title: { - type: 'string', - required: true, - message: 'title is required.' - }, - Message: { - type: 'string', - required: true, - message: 'message is required.' - }, - UserName: { - type: 'string', - required: true, - message: 'username is required.' + thread + .validate() + .save() + .then( ( data ) => { + + const response = { + statusCode: 200, + body: JSON.stringify( data ) } + + return callback( null, response ); + }) + .catch( function( error ) { + + callback(null, { + statusCode: 500, + body: JSON.stringify( { message: error.message } ) + }); + }); - /** - * Used to hold any validation messages. - * - * @type {Array} - */ - this.errors = []; + } catch( error ) { - } + if( error instanceof ValidationError ) { - /** - * Populates the "item" object prior to saving - * - * @return {this} - */ - QueryBuilder.prototype.hydrate = function() { - - const timestamp = new Date().getTime(); - - this.parameters.Item = { - Id: uuid.v1(), - ThreadId: this.event.queryStringParameters.threadid, - UserId: this.event.queryStringParameters.userid, - Title: this.event.queryStringParameters.title, - Message: this.event.queryStringParameters.message, - UserName: this.event.queryStringParameters.username, - UpdatedDateTime: timestamp - }; - - return this; - } + callback(null, { + statusCode: 422, + body: error.message + }); + } + else if( error instanceof DynamodbError ) { - /** - * Validates the data passed in the event object - * - * @return {this} - */ - QueryBuilder.prototype.validates = function() { - - this.errors = []; - - /* - if ( this.event.queryStringParameters.hasOwnProperty('threadid') == false && typeof this.event.queryStringParameters.threadid !== 'string' ) this.errors.push('threadid missing or invalid'); - if ( this.event.queryStringParameters.hasOwnProperty('userid') == false && typeof this.event.queryStringParameters.userid !== 'string' ) this.errors.push('userid missing or invalid'); - if ( this.event.queryStringParameters.hasOwnProperty('message') == false && typeof this.event.queryStringParameters.message !== 'string' ) this.errors.push('message missing or invalid'); - if ( this.event.queryStringParameters.hasOwnProperty('username') == false && typeof this.event.queryStringParameters.username !== 'string' ) this.errors.push('username missing or invalid'); - */ - // Validate the query parameters - this.errors = this.validator.validate( this.event.queryStringParameters ); - - return this.errors.length ? 0 : 1; - } + console.log('<<>>', error ); - /** - * Instantiate an instance of Query - * - * @type {QueryBuilder} - */ - var Query = new QueryBuilder( event ); - - // Check to see if the parameters passed in the request validate. - if ( Query.validates() == false ) { - - // Handle validation errors - callback(null, { - statusCode: 422, - body: JSON.stringify({ - message: Query.errors - }) - }) - } - else { - - // Update the post in the database - dynamoDb.update( Query.hydrate().parameters, (error, result) => { - - // Handle any potential DynamoDb errors - if (error) { - - console.error('=== error ===', error); - - callback(null, { - statusCode: error.statusCode || 501, - headers: { 'Content-Type': 'text/plain' }, - body: 'Couldn\'t fetch the post item.', - }); - return; - } + callback(null, { + statusCode: 500, + body: JSON.stringify( error ) + }); - // create a response - const response = { - statusCode: 200, - body: JSON.stringify(result.Attributes), - }; - callback(null, response); - }); + } + else { + + console.log('<<>>', error ); + + callback(null, { + statusCode: 500, + body: JSON.stringify( error ) + }); + } } }; \ No newline at end of file From d98003039306e8e78cbad5f9bdcea5a85522e8e3 Mon Sep 17 00:00:00 2001 From: jacksoncharles Date: Wed, 27 Dec 2017 20:58:36 +0000 Subject: [PATCH 42/78] readme --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 87c2349..eb5938c 100644 --- a/README.md +++ b/README.md @@ -61,7 +61,7 @@ NOTE: Will automatically remove any Lambda functions, Cloud Watch logs and API G not remove DynamoDb tables; They must be deleted manually. ## Lambda Functions and EndPoints -Will create X Lambda functions accessible via [API Gateway](https://aws.amazon.com/api-gateway/) configured endpoints. +Will create 10 Lambda functions accessible via [API Gateway](https://aws.amazon.com/api-gateway/) configured endpoints. NAME | LAMBDA | URL | VERB | DESCRIPTION ---- | ------ | --- | ---- | ----------- From 38e0897bbd3445fc4c32a9a8b98ffd2afbbedfcd Mon Sep 17 00:00:00 2001 From: jacksoncharles Date: Wed, 27 Dec 2017 21:00:45 +0000 Subject: [PATCH 43/78] layout and inline docs --- api/threads/_classes/ThreadQueryBuilder.js | 2 +- api/threads/threadCreate.js | 1 + api/threads/threadDelete.js | 3 --- api/threads/threadList.js | 2 -- 4 files changed, 2 insertions(+), 6 deletions(-) diff --git a/api/threads/_classes/ThreadQueryBuilder.js b/api/threads/_classes/ThreadQueryBuilder.js index c808eaa..79d5bd3 100644 --- a/api/threads/_classes/ThreadQueryBuilder.js +++ b/api/threads/_classes/ThreadQueryBuilder.js @@ -85,7 +85,7 @@ module.exports = class ThreadQueryBuilder { */ validates() { - this._errors = []; // Reset the errors array before running the validation logic + this._errors = []; // Empty the errors array before running the validation logic if( this._criterion !== null && typeof this._criterion === 'object' ) { diff --git a/api/threads/threadCreate.js b/api/threads/threadCreate.js index 1d09de2..f15e5f0 100644 --- a/api/threads/threadCreate.js +++ b/api/threads/threadCreate.js @@ -1,4 +1,5 @@ 'use strict'; + const uuidv1 = require('uuid/v1'); var Thread = require("./_classes/Thread"); diff --git a/api/threads/threadDelete.js b/api/threads/threadDelete.js index 4daa70d..4d83fdd 100644 --- a/api/threads/threadDelete.js +++ b/api/threads/threadDelete.js @@ -2,10 +2,7 @@ var Errors = require("./../../_classes/Errors"); var ValidationError = Errors.ValidationError; - - var Thread = require("./_models/Thread"); - var Dynamodb = require("./../_classes/DynamodbService"); /** diff --git a/api/threads/threadList.js b/api/threads/threadList.js index a624bab..184ea18 100644 --- a/api/threads/threadList.js +++ b/api/threads/threadList.js @@ -1,9 +1,7 @@ 'use strict'; var Thread = require("./_classes/Thread"); - var ThreadQueryBuilder = require("./_classes/ThreadQueryBuilder"); - var Errors = require("./../_classes/Errors"); var DynamodbError = Errors.DynamodbError; var ValidationError = Errors.ValidationError; From 67bd5e0bc18b80e4746c3d7f448e6f333c1559ff Mon Sep 17 00:00:00 2001 From: jacksoncharles Date: Wed, 27 Dec 2017 21:02:08 +0000 Subject: [PATCH 44/78] layout and inline docs --- api/replies/_classes/Reply.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/api/replies/_classes/Reply.js b/api/replies/_classes/Reply.js index 964c33d..f45ffe6 100644 --- a/api/replies/_classes/Reply.js +++ b/api/replies/_classes/Reply.js @@ -56,7 +56,7 @@ module.exports = class Reply extends Dynamic { */ validate() { - let errors = []; + let errors = []; // Create an empty array to hold any validation errors if( typeof this.Id == 'undefined' || validator.isEmpty( this.Id ) ) errors.push({'Id': 'must provide a unique string for Id'}); if( typeof this.Message == 'undefined' || validator.isEmpty( this.Message ) ) errors.push({'Message': 'must provide a value for Message'}); From 66a9de1318bfda93034d589e77f43bff680e30a0 Mon Sep 17 00:00:00 2001 From: jacksoncharles Date: Wed, 27 Dec 2017 21:03:16 +0000 Subject: [PATCH 45/78] layout and inline docs --- api/_classes/Dynamic.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/api/_classes/Dynamic.js b/api/_classes/Dynamic.js index 660b878..d58e9fa 100644 --- a/api/_classes/Dynamic.js +++ b/api/_classes/Dynamic.js @@ -11,7 +11,7 @@ var NotFoundError = Errors.NotFoundError; /** * Wrapper for DynamoDb with basic CRUD functionality and a validation method * - * @type {class} + * @type {Class} */ module.exports = class Dynamic { From 836d4fd7364763980c9dd15a214aaeda008bc751 Mon Sep 17 00:00:00 2001 From: jacksoncharles Date: Wed, 27 Dec 2017 21:19:10 +0000 Subject: [PATCH 46/78] syntax update --- api/_classes/Dynamic.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/api/_classes/Dynamic.js b/api/_classes/Dynamic.js index d58e9fa..51fa434 100644 --- a/api/_classes/Dynamic.js +++ b/api/_classes/Dynamic.js @@ -87,7 +87,7 @@ module.exports = class Dynamic { if ( error ) return reject( error ); /** @type {Object} Create a new instance of self and populate with the data */ - let modelInstance = self.constructor.model( data.Item ); + let modelInstance = self.model( data.Item ); /** All successful. Create a valid response */ return resolve( modelInstance ); @@ -123,7 +123,7 @@ module.exports = class Dynamic { for ( let item of data.Items ) { - items.push( self.constructor.model( item ) ); + items.push( self.model( item ) ); } data['Items'] = items; From 8124bf8b0a50e6a49b99bd21ef6f78482a08f175 Mon Sep 17 00:00:00 2001 From: jacksoncharles Date: Wed, 27 Dec 2017 21:31:58 +0000 Subject: [PATCH 47/78] removal of template docs --- api/_classes/DynamodbService.js | 110 -------------------------------- serverless.yml | 95 +-------------------------- 2 files changed, 1 insertion(+), 204 deletions(-) delete mode 100644 api/_classes/DynamodbService.js diff --git a/api/_classes/DynamodbService.js b/api/_classes/DynamodbService.js deleted file mode 100644 index f11fdff..0000000 --- a/api/_classes/DynamodbService.js +++ /dev/null @@ -1,110 +0,0 @@ -'use strict'; - -const AWS = require('aws-sdk'); -const dynamoDb = new AWS.DynamoDB.DocumentClient(); - -var Errors = require("./Errors"); -var DynamodbError = Errors.DynamodbError; - -/** - * CRUD service for DynamoDb. - * - * @type {class} - */ -module.exports = class DynamodbService { - - /** - * Retrieve an array of replies according to the parameters passed - * - * @return {Array} Array of replies. - */ - static destroy( parameters ) { - - return new Promise( function( resolve, reject ) { - - /** Run a dynamoDb get request passing-in our parameters */ - return dynamoDb.delete( parameters, function( error, data ) { - - /** Handle potential dynamoDb errors */ - if ( error ) return reject( error ); - - /** All successful. Create a valid response */ - return resolve( JSON.stringify( data ) ); - }); - - }) - .catch( function( error ) { - - console.log('<<>>', error ); - - throw new DynamodbError( error ); - }); - } - - /** - * Retrieve an array of replies according to the parameters passed - * - * @return {array} Array of replies - */ - static find( id ) { - - var self = this; - - /** @type {Object} Holds the parameters for the get request */ - const parameters = { - - TableName : process.env.DYNAMODB_REPLY_TABLE, - Key : { - Id : id - } - } - - return new Promise( function( resolve, reject ) { - - /** Run a dynamoDb get request passing-in our parameters */ - return dynamoDb.get( parameters, function( error, data ) { - - /** Handle potential dynamoDb errors */ - if ( error ) return reject( error ); - - /** All successful. Create a valid response */ - return resolve( JSON.stringify( data ) ); - }); - - }) - .catch( function( error ) { // Capture a dynamoDb rejection - - console.log('<<>>', error ); - - throw new DynamodbError( error ); - }); - } - - /** - * Retrieve an array of replies according to the parameters passed - * - * @return {array} Array of replies - */ - static list( parameters ) { - - return new Promise( function( resolve, reject ) { - - /** Run a dynamoDb query passing-in Query.parameters */ - return dynamoDb.query( parameters, function( error, data ) { - - /** Handle potential dynamoDb errors */ - if ( error ) return reject( error ); - - /** All successful. Create a valid response */ - return resolve( JSON.stringify( data ) ); - }); - - }) - .catch( function( error ) { - - console.log('<<>>', error ); - - throw new DynamodbError( error ); - }); - } -} \ No newline at end of file diff --git a/serverless.yml b/serverless.yml index 21dadbb..38a1626 100644 --- a/serverless.yml +++ b/serverless.yml @@ -1,47 +1,15 @@ -# Welcome to Serverless! -# -# This file is the main config file for your service. -# It's very minimal at this point and uses default values. -# You can always add more config options for more control. -# We've included some commented out config examples here. -# Just uncomment any of them to get that config option. -# -# For full config options, check the docs: -# docs.serverless.com -# -# Happy Coding! - service: forum-service -# You can pin your service to only deploy with a specific Serverless version -# Check out our docs for more details -# frameworkVersion: "=X.X.X" - provider: name: aws runtime: nodejs6.10 -# you can overwrite defaults here stage: prod region: eu-west-1 -# you can add statements to the Lambda function's IAM Role here -# iamRoleStatements: -# - Effect: "Allow" -# Action: -# - "s3:ListBucket" -# Resource: { "Fn::Join" : ["", ["arn:aws:s3:::", { "Ref" : "ServerlessDeploymentBucket" } ] ] } -# - Effect: "Allow" -# Action: -# - "s3:PutObject" -# Resource: -# Fn::Join: -# - "" -# - - "arn:aws:s3:::" -# - "Ref" : "ServerlessDeploymentBucket" -# - "/*" environment: DYNAMODB_THREAD_TABLE: Thread DYNAMODB_REPLY_TABLE: Reply + iamRoleStatements: - Effect: Allow Action: @@ -144,67 +112,6 @@ functions: method: delete cors: true -# you can define service wide environment variables here -# environment: -# variable1: value1 - -# you can add packaging information here -#package: -# include: -# - include-me.js -# - include-me-dir/** -# exclude: -# - exclude-me.js -# - exclude-me-dir/** - -#functions: -# hello: -# handler: handler.hello - -# The following are a few example events you can configure -# NOTE: Please make sure to change your handler code to work with those events -# Check the event documentation for details -# events: -# - http: -# path: users/create -# method: get -# - s3: ${env:BUCKET} -# - schedule: rate(10 minutes) -# - sns: greeter-topic -# - stream: arn:aws:dynamodb:region:XXXXXX:table/foo/stream/1970-01-01T00:00:00.000 -# - alexaSkill -# - iot: -# sql: "SELECT * FROM 'some_topic'" -# - cloudwatchEvent: -# event: -# source: -# - "aws.ec2" -# detail-type: -# - "EC2 Instance State-change Notification" -# detail: -# state: -# - pending -# - cloudwatchLog: '/aws/lambda/hello' -# - cognitoUserPool: -# pool: MyUserPool -# trigger: PreSignUp - -# Define function environment variables here -# environment: -# variable2: value2 - -# you can add CloudFormation resource templates here -#resources: -# Resources: -# NewResource: -# Type: AWS::S3::Bucket -# Properties: -# BucketName: my-new-bucket -# Outputs: -# NewOutput: -# Description: "Description for the output" -# Value: "Some output value" - resources: Resources: ThreadDynamoDbTable: From 07f852661820506dd12179c7f2952d912a9b47a4 Mon Sep 17 00:00:00 2001 From: jacksoncharles Date: Wed, 27 Dec 2017 21:35:54 +0000 Subject: [PATCH 48/78] bug fixing --- api/threads/_classes/ThreadQueryBuilder.js | 26 +++++++++++----------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/api/threads/_classes/ThreadQueryBuilder.js b/api/threads/_classes/ThreadQueryBuilder.js index 79d5bd3..20ed086 100644 --- a/api/threads/_classes/ThreadQueryBuilder.js +++ b/api/threads/_classes/ThreadQueryBuilder.js @@ -89,18 +89,18 @@ module.exports = class ThreadQueryBuilder { if( this._criterion !== null && typeof this._criterion === 'object' ) { - if( this._criterion.hasOwnProperty( 'threadid' ) == false && + if( this._criterion.hasOwnProperty( 'forumid' ) == false && this._criterion.hasOwnProperty( 'userid' ) == false ) { - this._errors.push( { "message": "You must provide a threadid or userid parameter" } ); + this._errors.push( { "message": "You must provide a forumid or userid parameter" } ); } - if( this._criterion.hasOwnProperty( 'threadid' ) ) { + if( this._criterion.hasOwnProperty( 'forumid' ) ) { if ( validator.isAlphanumeric( this._criterion.threadid ) == false ) { - this._errors.push( { "message": "Your threadid parameter must be an alphanumeric string" } ); + this._errors.push( { "message": "Your forumid parameter must be an alphanumeric string" } ); } } @@ -114,7 +114,7 @@ module.exports = class ThreadQueryBuilder { } else { - this._errors.push( { "message" : "You must supply a threadid or userid" } ); + this._errors.push( { "message" : "You must supply a forumid or userid" } ); } if( this._errors.length ) { @@ -126,18 +126,18 @@ module.exports = class ThreadQueryBuilder { } /** - * If "threadid" has been passed inside this.event this method will build upon this.parameters object + * If "forumid" has been passed inside this.event this method will build upon this.parameters object * * @return this */ - buildThreadIndex() { + buildForumIndex() { - if( this._criterion.hasOwnProperty('threadid') ) { + if( this._criterion.hasOwnProperty('forumid') ) { - this._parameters['IndexName'] = "ThreadIndex"; - this._parameters['KeyConditionExpression'] = "ThreadId = :searchstring"; + this._parameters['IndexName'] = "ForumIndex"; + this._parameters['KeyConditionExpression'] = "ForumId = :searchstring"; this._parameters['ExpressionAttributeValues'] = { - ":searchstring" : this._criterion.threadid + ":searchstring" : this._criterion.forumid }; } @@ -170,11 +170,11 @@ module.exports = class ThreadQueryBuilder { */ buildPagination() { - if ( this._criterion.hasOwnProperty('threadid') && + if ( this._criterion.hasOwnProperty('forumid') && this._criterion.hasOwnProperty('createddatetime') ) { this._parameters['ExclusiveStartKey'] = { - ThreadId: this._criterion.threadid, + ForumId: this._criterion.threadid, DateTime: this._criterion.createddatetime } } From 883af029ab27317245e5fe31ce59d38792bf3909 Mon Sep 17 00:00:00 2001 From: jacksoncharles Date: Wed, 27 Dec 2017 22:09:51 +0000 Subject: [PATCH 49/78] fix to test data and thread table configuration --- sample_data/Thread.json | 2 +- serverless.yml | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/sample_data/Thread.json b/sample_data/Thread.json index f31bbcb..e53f2ae 100644 --- a/sample_data/Thread.json +++ b/sample_data/Thread.json @@ -154,7 +154,7 @@ "PutRequest": { "Item": { "Id": { - "S": "5" + "S": "6" }, "UserId": { "S": "6" diff --git a/serverless.yml b/serverless.yml index 38a1626..330dd6a 100644 --- a/serverless.yml +++ b/serverless.yml @@ -129,14 +129,14 @@ resources: AttributeName: "UserId" AttributeType: "S" - - AttributeName: "ThreadDateTime" + AttributeName: "CreatedDateTime" AttributeType: "S" KeySchema: - AttributeName: "Id" KeyType: "HASH" - - AttributeName: "ThreadDateTime" + AttributeName: "CreatedDateTime" KeyType: "RANGE" GlobalSecondaryIndexes: - @@ -146,7 +146,7 @@ resources: AttributeName: "ForumId" KeyType: HASH - - AttributeName: "ThreadDateTime" + AttributeName: "CreatedDateTime" KeyType: RANGE Projection: ProjectionType: ALL @@ -160,7 +160,7 @@ resources: AttributeName: "UserId" KeyType: HASH - - AttributeName: "ThreadDateTime" + AttributeName: "CreatedDateTime" KeyType: RANGE Projection: ProjectionType: ALL From 02185ad0a943f1ad709e13c2e03e065a9c7b4550 Mon Sep 17 00:00:00 2001 From: jacksoncharles Date: Thu, 28 Dec 2017 07:52:02 +0000 Subject: [PATCH 50/78] catchup --- api/replies/_classes/ReplyQueryBuilder.js | 2 +- api/replies/replyList.js | 2 +- api/threads/_classes/ThreadQueryBuilder.js | 2 +- api/threads/threadList.js | 4 +++- sample_data/Thread.json | 12 ++++++------ serverless.yml | 4 ++-- 6 files changed, 14 insertions(+), 12 deletions(-) diff --git a/api/replies/_classes/ReplyQueryBuilder.js b/api/replies/_classes/ReplyQueryBuilder.js index c4edc54..eb8ba4b 100644 --- a/api/replies/_classes/ReplyQueryBuilder.js +++ b/api/replies/_classes/ReplyQueryBuilder.js @@ -83,7 +83,7 @@ module.exports = class ReplyQueryBuilder { * * @return {boolean} */ - validates() { + validate() { this._errors = []; // Reset the errors array before running the validation logic diff --git a/api/replies/replyList.js b/api/replies/replyList.js index ec56031..6f8c2cc 100644 --- a/api/replies/replyList.js +++ b/api/replies/replyList.js @@ -29,7 +29,7 @@ module.exports.replyList = (event, context, callback) => { try { Query - .validates() + .validate() .buildThreadIndex() .buildUserIndex() .buildPagination(); diff --git a/api/threads/_classes/ThreadQueryBuilder.js b/api/threads/_classes/ThreadQueryBuilder.js index 20ed086..6439d63 100644 --- a/api/threads/_classes/ThreadQueryBuilder.js +++ b/api/threads/_classes/ThreadQueryBuilder.js @@ -83,7 +83,7 @@ module.exports = class ThreadQueryBuilder { * * @return {boolean} */ - validates() { + validate() { this._errors = []; // Empty the errors array before running the validation logic diff --git a/api/threads/threadList.js b/api/threads/threadList.js index 184ea18..6490e66 100644 --- a/api/threads/threadList.js +++ b/api/threads/threadList.js @@ -26,12 +26,14 @@ module.exports.threadList = (event, context, callback) => { try { + console.log('=== gotcha ==='); Query - .validates() + .validate() .buildForumIndex() .buildUserIndex() .buildPagination(); + console.log('=== Query.parameters ===', Query.parameters ); /** @type {model} Contains a list of items and optional pagination data */ Thread.list( Query.parameters ) .then( ( threads ) => { diff --git a/sample_data/Thread.json b/sample_data/Thread.json index e53f2ae..f792a2a 100644 --- a/sample_data/Thread.json +++ b/sample_data/Thread.json @@ -10,7 +10,7 @@ "S": "1" }, "ForumId": { - "S": "WebConfection" + "S": "webconfection" }, "Title": { "S": "Neque porro quisquam est qui dolorem ipsum quia dolor sit amet, consectetur, adipisci velit..." @@ -40,7 +40,7 @@ "S": "2" }, "ForumId": { - "S": "WebConfection" + "S": "webconfection" }, "Title": { "S": "Neque porro quisquam est qui dolorem ipsum quia dolor sit amet, consectetur, adipisci velit..." @@ -70,7 +70,7 @@ "S": "3" }, "ForumId": { - "S": "WebConfection" + "S": "webconfection" }, "Title": { "S": "Neque porro quisquam est qui dolorem ipsum quia dolor sit amet, consectetur, adipisci velit..." @@ -100,7 +100,7 @@ "S": "4" }, "ForumId": { - "S": "WebConfection" + "S": "webconfection" }, "Title": { "S": "Neque porro quisquam est qui dolorem ipsum quia dolor sit amet, consectetur, adipisci velit..." @@ -130,7 +130,7 @@ "S": "5" }, "ForumId": { - "S": "WebConfection" + "S": "webconfection" }, "Title": { "S": "Neque porro quisquam est qui dolorem ipsum quia dolor sit amet, consectetur, adipisci velit..." @@ -160,7 +160,7 @@ "S": "6" }, "ForumId": { - "S": "WebConfection" + "S": "webconfection" }, "Title": { "S": "Neque porro quisquam est qui dolorem ipsum quia dolor sit amet, consectetur, adipisci velit..." diff --git a/serverless.yml b/serverless.yml index 330dd6a..ac7bd57 100644 --- a/serverless.yml +++ b/serverless.yml @@ -30,7 +30,7 @@ functions: description: Submit thread. events: - http: - path: replies + path: threads method: post cors: true @@ -40,7 +40,7 @@ functions: description: Retrieve a paginated list of threads. events: - http: - path: replies + path: threads method: get cors: true From c2be59833b20381125c6a90680218c6c76a77ca8 Mon Sep 17 00:00:00 2001 From: jacksoncharles Date: Thu, 28 Dec 2017 11:28:43 +0000 Subject: [PATCH 51/78] catchup --- api/_classes/Dynamic.js | 11 ++++++++--- api/replies/_classes/Reply.js | 11 +++++++++++ api/threads/_classes/Thread.js | 11 +++++++++++ api/threads/_classes/ThreadQueryBuilder.js | 4 ++-- api/threads/threadCreate.js | 8 ++------ api/threads/threadList.js | 2 -- api/threads/threadUpdate.js | 2 -- serverless.yml | 1 + 8 files changed, 35 insertions(+), 15 deletions(-) diff --git a/api/_classes/Dynamic.js b/api/_classes/Dynamic.js index 51fa434..cd06e26 100644 --- a/api/_classes/Dynamic.js +++ b/api/_classes/Dynamic.js @@ -35,7 +35,7 @@ module.exports = class Dynamic { /** @type {Object} Holds the parameters for the get request */ const parameters = { - TableName : process.env.DYNAMODB_REPLY_TABLE, + TableName : self.constructor.table(), Item : self.properties() } @@ -72,20 +72,25 @@ module.exports = class Dynamic { /** @type {Object} Holds the parameters for the get request */ const parameters = { - TableName : process.env.DYNAMODB_REPLY_TABLE, + TableName : self.table(), Key : { Id : id } } + console.log('=== parameters ===', parameters ); + return new Promise( function( resolve, reject ) { /** Run a dynamodb get request passing-in our parameters */ return dynamodb.get( parameters, function( error, data ) { + console.log('=== error is ===', error ); + /** Handle potential dynamodb errors */ if ( error ) return reject( error ); + console.log('=== got this far ===', data ); /** @type {Object} Create a new instance of self and populate with the data */ let modelInstance = self.model( data.Item ); @@ -151,7 +156,7 @@ module.exports = class Dynamic { /** @type {Object} Holds the parameters for the get request */ const parameters = { - TableName : process.env.DYNAMODB_REPLY_TABLE, + TableName : self.constructor.table(), Key : { Id : id } diff --git a/api/replies/_classes/Reply.js b/api/replies/_classes/Reply.js index f45ffe6..b3ec313 100644 --- a/api/replies/_classes/Reply.js +++ b/api/replies/_classes/Reply.js @@ -19,6 +19,17 @@ module.exports = class Reply extends Dynamic { super( parameters ); } + /** + * Return a string containing the name of the table in perment storage + * for the model. + * + * @return {String} - Name of the string + */ + static table() { + + return 'Reply'; + } + /** * Return a new instance of this * diff --git a/api/threads/_classes/Thread.js b/api/threads/_classes/Thread.js index 931caf5..380696b 100644 --- a/api/threads/_classes/Thread.js +++ b/api/threads/_classes/Thread.js @@ -19,6 +19,17 @@ module.exports = class Thread extends Dynamic { super( parameters ); } + /** + * Return a string containing the name of the table in perment storage + * for the model. + * + * @return {String} - Name of the string + */ + static table() { + + return 'Thread'; + } + /** * Return a new instance of this * diff --git a/api/threads/_classes/ThreadQueryBuilder.js b/api/threads/_classes/ThreadQueryBuilder.js index 6439d63..ccd19cb 100644 --- a/api/threads/_classes/ThreadQueryBuilder.js +++ b/api/threads/_classes/ThreadQueryBuilder.js @@ -98,7 +98,7 @@ module.exports = class ThreadQueryBuilder { if( this._criterion.hasOwnProperty( 'forumid' ) ) { - if ( validator.isAlphanumeric( this._criterion.threadid ) == false ) { + if ( validator.isAlphanumeric( this._criterion.forumid ) == false ) { this._errors.push( { "message": "Your forumid parameter must be an alphanumeric string" } ); } @@ -174,7 +174,7 @@ module.exports = class ThreadQueryBuilder { this._criterion.hasOwnProperty('createddatetime') ) { this._parameters['ExclusiveStartKey'] = { - ForumId: this._criterion.threadid, + ForumId: this._criterion.forumid, DateTime: this._criterion.createddatetime } } diff --git a/api/threads/threadCreate.js b/api/threads/threadCreate.js index f15e5f0..03a1859 100644 --- a/api/threads/threadCreate.js +++ b/api/threads/threadCreate.js @@ -29,12 +29,10 @@ module.exports.threadCreate = (event, context, callback) => { .save() .then( ( data ) => { - const response = { + return callback( null, { statusCode: 200, body: JSON.stringify( data ) - } - - return callback( null, response ); + }); }) .catch( function( error ) { @@ -42,7 +40,6 @@ module.exports.threadCreate = (event, context, callback) => { statusCode: 500, body: JSON.stringify( { message: error.message } ) }); - }); } catch( error ) { @@ -62,7 +59,6 @@ module.exports.threadCreate = (event, context, callback) => { statusCode: 500, body: JSON.stringify( error ) }); - } else { diff --git a/api/threads/threadList.js b/api/threads/threadList.js index 6490e66..976f260 100644 --- a/api/threads/threadList.js +++ b/api/threads/threadList.js @@ -26,14 +26,12 @@ module.exports.threadList = (event, context, callback) => { try { - console.log('=== gotcha ==='); Query .validate() .buildForumIndex() .buildUserIndex() .buildPagination(); - console.log('=== Query.parameters ===', Query.parameters ); /** @type {model} Contains a list of items and optional pagination data */ Thread.list( Query.parameters ) .then( ( threads ) => { diff --git a/api/threads/threadUpdate.js b/api/threads/threadUpdate.js index 431d68a..32b0a5f 100644 --- a/api/threads/threadUpdate.js +++ b/api/threads/threadUpdate.js @@ -18,7 +18,6 @@ module.exports.threadUpdate = (event, context, callback) => { try { let parameters = JSON.parse( event.body ); - let thread = new Thread( parameters ); thread @@ -39,7 +38,6 @@ module.exports.threadUpdate = (event, context, callback) => { statusCode: 500, body: JSON.stringify( { message: error.message } ) }); - }); } catch( error ) { diff --git a/serverless.yml b/serverless.yml index ac7bd57..10f00a0 100644 --- a/serverless.yml +++ b/serverless.yml @@ -3,6 +3,7 @@ service: forum-service provider: name: aws runtime: nodejs6.10 + profile: jacksoncharles stage: prod region: eu-west-1 From 7d61e4e3fdc09dc4a1b46f784c32d23f47d5e21d Mon Sep 17 00:00:00 2001 From: jacksoncharles Date: Thu, 28 Dec 2017 15:55:09 +0000 Subject: [PATCH 52/78] tweaks --- README.md | 4 ++-- api/_classes/Dynamic.js | 9 +++------ package.json | 3 ++- 3 files changed, 7 insertions(+), 9 deletions(-) diff --git a/README.md b/README.md index eb5938c..7bf3c13 100644 --- a/README.md +++ b/README.md @@ -3,7 +3,7 @@ A microservice template for a Q&A solution such as comments functionality or discussion forum deployed using the [Serverless Framework](http://serverless.com). -A simple but robust solution that can be built upon. Includes common functionality including pagination and global secondary indexes for retrieiving by user, thread or unique key. It is loosely inspired by the [AWS Example Forum](http://docs.aws.amazon.com/amazondynamodb/latest/developerguide/SampleData.CreateTables.html) and designed to be implemented as part of a distributed system. +A simple and robust solution that can be built upon. Includes pagination and global secondary indexes for retrieiving by user, thread or unique key and is multi-tenancy compatible. It is loosely inspired by the [AWS Example Forum](http://docs.aws.amazon.com/amazondynamodb/latest/developerguide/SampleData.CreateTables.html) and designed to be implemented as part of a distributed system. ## Technology Stack 1. [AWS Lambda](https://aws.amazon.com/lambda/) @@ -35,7 +35,7 @@ Secondly, install the [Serverless Framework](http://serverless.com) dependencies Next, install your microservice API dependencies. ``` - ./api/npm install + npm buildapi ``` Lasty, deploy your microservice API. diff --git a/api/_classes/Dynamic.js b/api/_classes/Dynamic.js index cd06e26..6481ce4 100644 --- a/api/_classes/Dynamic.js +++ b/api/_classes/Dynamic.js @@ -78,19 +78,14 @@ module.exports = class Dynamic { } } - console.log('=== parameters ===', parameters ); - return new Promise( function( resolve, reject ) { /** Run a dynamodb get request passing-in our parameters */ return dynamodb.get( parameters, function( error, data ) { - console.log('=== error is ===', error ); - /** Handle potential dynamodb errors */ if ( error ) return reject( error ); - console.log('=== got this far ===', data ); /** @type {Object} Create a new instance of self and populate with the data */ let modelInstance = self.model( data.Item ); @@ -153,10 +148,12 @@ module.exports = class Dynamic { */ static destroy( id ) { + var self = this; + /** @type {Object} Holds the parameters for the get request */ const parameters = { - TableName : self.constructor.table(), + TableName : self.table(), Key : { Id : id } diff --git a/package.json b/package.json index 3bd006b..3e5aa49 100644 --- a/package.json +++ b/package.json @@ -4,7 +4,8 @@ "description": "", "main": "index.js", "scripts": { - "test": "echo \"Error: no test specified\" && exit 1" + "test": "echo \"Error: no test specified\" && exit 1", + "buildapi": "cd api; npm install" }, "author": "", "license": "ISC", From 0a073de726c18c33c743da70717f3b1528503d70 Mon Sep 17 00:00:00 2001 From: jacksoncharles Date: Fri, 29 Dec 2017 19:19:49 +0000 Subject: [PATCH 53/78] inline docs --- api/_classes/Dynamic.js | 3 ++- api/replies/replyCreate.js | 2 ++ api/replies/replyDelete.js | 10 +++------- api/replies/replyGet.js | 2 +- api/replies/replyUpdate.js | 5 +++++ api/threads/threadDelete.js | 7 +++---- api/threads/threadGet.js | 2 +- api/threads/threadUpdate.js | 6 ++++++ {sample_data => data}/Reply.json | 0 {sample_data => data}/Thread.json | 0 loadData.sh | 4 ++-- serverless.yml | 4 ---- 12 files changed, 25 insertions(+), 20 deletions(-) rename {sample_data => data}/Reply.json (100%) rename {sample_data => data}/Thread.json (100%) diff --git a/api/_classes/Dynamic.js b/api/_classes/Dynamic.js index 6481ce4..7f004c3 100644 --- a/api/_classes/Dynamic.js +++ b/api/_classes/Dynamic.js @@ -77,7 +77,8 @@ module.exports = class Dynamic { Id : id } } - + console.log( '=== parameters ===', parameters ); + return new Promise( function( resolve, reject ) { /** Run a dynamodb get request passing-in our parameters */ diff --git a/api/replies/replyCreate.js b/api/replies/replyCreate.js index c257e04..b7bc6cd 100644 --- a/api/replies/replyCreate.js +++ b/api/replies/replyCreate.js @@ -37,6 +37,8 @@ module.exports.replyCreate = (event, context, callback) => { }) .catch( function( error ) { + console.log('<<>>', error ); + callback(null, { statusCode: 500, body: JSON.stringify( { message: error.message } ) diff --git a/api/replies/replyDelete.js b/api/replies/replyDelete.js index 836e9ac..9c4d94a 100644 --- a/api/replies/replyDelete.js +++ b/api/replies/replyDelete.js @@ -1,12 +1,8 @@ 'use strict'; -var Errors = require("./../../_classes/Errors"); +var Errors = require("./../_classes/Errors"); var ValidationError = Errors.ValidationError; - - -var Reply = require("./_models/Reply"); - -var Dynamodb = require("./../_classes/DynamodbService"); +var Reply = require("./_classes/Reply"); /** * Handler for the lambda function. @@ -31,7 +27,7 @@ module.exports.replyDelete = (event, context, callback) => { }) .catch( function( error ) { - console.log('<<>>', error ); + console.log('<<>>', error ); callback(null, { statusCode: 500, diff --git a/api/replies/replyGet.js b/api/replies/replyGet.js index a8e0e13..4d60a48 100644 --- a/api/replies/replyGet.js +++ b/api/replies/replyGet.js @@ -25,7 +25,7 @@ module.exports.replyGet = ( event, context, callback ) => { }) .catch( function( error ) { - console.log('<<>>', error ); + console.log('<<>>', error ); callback(null, { statusCode: 500, diff --git a/api/replies/replyUpdate.js b/api/replies/replyUpdate.js index 78390ec..8bf4f17 100644 --- a/api/replies/replyUpdate.js +++ b/api/replies/replyUpdate.js @@ -17,8 +17,13 @@ module.exports.replyUpdate = (event, context, callback) => { try { + // Get the parameters passed in the body of the request let parameters = JSON.parse( event.body ); + // Grab the value of hash key "id" passed in the route + parameters['Id'] = event.pathParameters.id; + + // Create a new instance of the reply object passing in our parameters let reply = new Reply( parameters ); reply diff --git a/api/threads/threadDelete.js b/api/threads/threadDelete.js index 4d83fdd..17cf341 100644 --- a/api/threads/threadDelete.js +++ b/api/threads/threadDelete.js @@ -1,9 +1,8 @@ 'use strict'; -var Errors = require("./../../_classes/Errors"); +var Errors = require("./../_classes/Errors"); var ValidationError = Errors.ValidationError; -var Thread = require("./_models/Thread"); -var Dynamodb = require("./../_classes/DynamodbService"); +var Thread = require("./_classes/Thread"); /** * Handler for the lambda function. @@ -28,7 +27,7 @@ module.exports.threadDelete = (event, context, callback) => { }) .catch( function( error ) { - console.log('<<>>', error ); + console.log('<<>>', error ); callback(null, { statusCode: 500, diff --git a/api/threads/threadGet.js b/api/threads/threadGet.js index eba8336..a54f0d8 100644 --- a/api/threads/threadGet.js +++ b/api/threads/threadGet.js @@ -25,7 +25,7 @@ module.exports.threadGet = ( event, context, callback ) => { }) .catch( function( error ) { - console.log('<<>>', error ); + console.log('<<>>', error ); callback(null, { statusCode: 500, diff --git a/api/threads/threadUpdate.js b/api/threads/threadUpdate.js index 32b0a5f..1f55d39 100644 --- a/api/threads/threadUpdate.js +++ b/api/threads/threadUpdate.js @@ -17,7 +17,13 @@ module.exports.threadUpdate = (event, context, callback) => { try { + // Get the parameters passed in the body of the request let parameters = JSON.parse( event.body ); + + // Grab the value of hash key "id" passed in the route + parameters['Id'] = event.pathParameters.id; + + // Create a new instance of the thread object passing in our parameters let thread = new Thread( parameters ); thread diff --git a/sample_data/Reply.json b/data/Reply.json similarity index 100% rename from sample_data/Reply.json rename to data/Reply.json diff --git a/sample_data/Thread.json b/data/Thread.json similarity index 100% rename from sample_data/Thread.json rename to data/Thread.json diff --git a/loadData.sh b/loadData.sh index f6c3dda..eb58fbb 100755 --- a/loadData.sh +++ b/loadData.sh @@ -1,3 +1,3 @@ #!/usr/bin/env bash -aws dynamodb batch-write-item --request-items file://sample_data/Reply.json -aws dynamodb batch-write-item --request-items file://sample_data/Thread.json \ No newline at end of file +aws dynamodb batch-write-item --request-items file://data/Reply.json +aws dynamodb batch-write-item --request-items file://data/Thread.json \ No newline at end of file diff --git a/serverless.yml b/serverless.yml index 10f00a0..41cc731 100644 --- a/serverless.yml +++ b/serverless.yml @@ -3,7 +3,6 @@ service: forum-service provider: name: aws runtime: nodejs6.10 - profile: jacksoncharles stage: prod region: eu-west-1 @@ -136,9 +135,6 @@ resources: - AttributeName: "Id" KeyType: "HASH" - - - AttributeName: "CreatedDateTime" - KeyType: "RANGE" GlobalSecondaryIndexes: - IndexName: ForumIndex From d78598392d0e33af8e853b0cc77694993ef643b1 Mon Sep 17 00:00:00 2001 From: jacksoncharles Date: Fri, 29 Dec 2017 19:40:14 +0000 Subject: [PATCH 54/78] readme --- README.md | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 7bf3c13..9dfd8aa 100644 --- a/README.md +++ b/README.md @@ -1,9 +1,8 @@ -# Q&A Microservice Template +# AWS Serverless Q&A Template Solution -A microservice template for a Q&A solution such as comments functionality or discussion forum deployed using -the [Serverless Framework](http://serverless.com). +A template Q&A solution that can be used for common functionality such as comments, discussion forums or surveys. Deployed to AWS using the [Serverless Framework](http://serverless.com). -A simple and robust solution that can be built upon. Includes pagination and global secondary indexes for retrieiving by user, thread or unique key and is multi-tenancy compatible. It is loosely inspired by the [AWS Example Forum](http://docs.aws.amazon.com/amazondynamodb/latest/developerguide/SampleData.CreateTables.html) and designed to be implemented as part of a distributed system. +Includes pagination and global secondary indexes for retrieiving by user, thread or unique key and is multi-tenancy compatible. It is loosely inspired by the [AWS Example Forum](http://docs.aws.amazon.com/amazondynamodb/latest/developerguide/SampleData.CreateTables.html) and designed to be implemented as part of a distributed system. ## Technology Stack 1. [AWS Lambda](https://aws.amazon.com/lambda/) From 8c9063e8dec02309355124f3713415e14b93b47e Mon Sep 17 00:00:00 2001 From: jacksoncharles Date: Fri, 29 Dec 2017 19:59:10 +0000 Subject: [PATCH 55/78] readme --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 9dfd8aa..14a30e1 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # AWS Serverless Q&A Template Solution -A template Q&A solution that can be used for common functionality such as comments, discussion forums or surveys. Deployed to AWS using the [Serverless Framework](http://serverless.com). +A template Q&A solution that can be used for everyday services such as discussion forums, comments and surveys. Deployed to AWS using the [Serverless Framework](http://serverless.com). Includes pagination and global secondary indexes for retrieiving by user, thread or unique key and is multi-tenancy compatible. It is loosely inspired by the [AWS Example Forum](http://docs.aws.amazon.com/amazondynamodb/latest/developerguide/SampleData.CreateTables.html) and designed to be implemented as part of a distributed system. @@ -77,4 +77,4 @@ DELETE | replyDelete | /replies/:id | DELETE | Remove an item from permanent sto ## Issues -Please report any feedback on the [Issue Tracker](https://github.com/jacksoncharles/forum-microservice/issues). \ No newline at end of file +Please report any feedback on the [Issue Tracker](https://github.com/jacksoncharles/aws-serverless-qa-template-solution/issues). \ No newline at end of file From d373781b253cedfc649fbca2bf9bf3bbea75b78c Mon Sep 17 00:00:00 2001 From: jacksoncharles Date: Fri, 29 Dec 2017 20:00:26 +0000 Subject: [PATCH 56/78] readme --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 14a30e1..0883a07 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # AWS Serverless Q&A Template Solution -A template Q&A solution that can be used for everyday services such as discussion forums, comments and surveys. Deployed to AWS using the [Serverless Framework](http://serverless.com). +A template Q&A engine that can be built upon and used for everyday services such as discussion forums, comments and surveys. Deployed to AWS using the [Serverless Framework](http://serverless.com). Includes pagination and global secondary indexes for retrieiving by user, thread or unique key and is multi-tenancy compatible. It is loosely inspired by the [AWS Example Forum](http://docs.aws.amazon.com/amazondynamodb/latest/developerguide/SampleData.CreateTables.html) and designed to be implemented as part of a distributed system. From 71ccf85b853eff8189bc0281af4d09df62e6941a Mon Sep 17 00:00:00 2001 From: jacksoncharles Date: Fri, 29 Dec 2017 20:14:41 +0000 Subject: [PATCH 57/78] readme --- README.md | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 0883a07..9b551b0 100644 --- a/README.md +++ b/README.md @@ -1,14 +1,16 @@ # AWS Serverless Q&A Template Solution -A template Q&A engine that can be built upon and used for everyday services such as discussion forums, comments and surveys. Deployed to AWS using the [Serverless Framework](http://serverless.com). +A big data template Q&A engine that can be built upon and used for everyday services such as discussion forums, comments and surveys. Deployed to AWS using the [Serverless Framework](http://serverless.com). Includes pagination and global secondary indexes for retrieiving by user, thread or unique key and is multi-tenancy compatible. It is loosely inspired by the [AWS Example Forum](http://docs.aws.amazon.com/amazondynamodb/latest/developerguide/SampleData.CreateTables.html) and designed to be implemented as part of a distributed system. ## Technology Stack 1. [AWS Lambda](https://aws.amazon.com/lambda/) -2. [Serverless Framework](http://serverless.com) -3. [DynamoDB](https://aws.amazon.com/dynamodb) -4. [NodeJs](https://nodejs.org/) +2. [AWS DynamoDB](https://aws.amazon.com/dynamodb) +3. [AWS API Gateway](https://aws.amazon.com/api-gateway) +3. [AWS Cloudwatch](https://aws.amazon.com/cloudwatch) +4. [Serverless Framework](http://serverless.com) +5. [NodeJs](https://nodejs.org/) ## Installation & Deployment Deploying the forum microservice will provision and create the following resources. From 6ffbac9c6596292b86b92cd0a62474a4d3fd39bb Mon Sep 17 00:00:00 2001 From: jacksoncharles Date: Fri, 29 Dec 2017 20:27:02 +0000 Subject: [PATCH 58/78] readme --- README.md | 2 +- serverless.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 9b551b0..e6d2cb7 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,7 @@ A big data template Q&A engine that can be built upon and used for everyday services such as discussion forums, comments and surveys. Deployed to AWS using the [Serverless Framework](http://serverless.com). -Includes pagination and global secondary indexes for retrieiving by user, thread or unique key and is multi-tenancy compatible. It is loosely inspired by the [AWS Example Forum](http://docs.aws.amazon.com/amazondynamodb/latest/developerguide/SampleData.CreateTables.html) and designed to be implemented as part of a distributed system. +Includes pagination and global secondary indexes for retrieiving by user, thread or unique key and is multi-tenancy ready. It is loosely inspired by the [AWS Example Forum](http://docs.aws.amazon.com/amazondynamodb/latest/developerguide/SampleData.CreateTables.html) and designed to be implemented as part of a distributed system. ## Technology Stack 1. [AWS Lambda](https://aws.amazon.com/lambda/) diff --git a/serverless.yml b/serverless.yml index 41cc731..c7d0167 100644 --- a/serverless.yml +++ b/serverless.yml @@ -143,7 +143,7 @@ resources: AttributeName: "ForumId" KeyType: HASH - - AttributeName: "CreatedDateTime" + AttributeName: "UpdatedDateTime" KeyType: RANGE Projection: ProjectionType: ALL From dac4254b7a416aeae3d494cf05193a22cf3b4891 Mon Sep 17 00:00:00 2001 From: jacksoncharles Date: Fri, 29 Dec 2017 20:51:03 +0000 Subject: [PATCH 59/78] readme --- README.md | 2 +- data/Thread.json | 30 +++++++++++++++--------------- serverless.yml | 5 ++++- 3 files changed, 20 insertions(+), 17 deletions(-) diff --git a/README.md b/README.md index e6d2cb7..fb7fbbd 100644 --- a/README.md +++ b/README.md @@ -15,7 +15,7 @@ Includes pagination and global secondary indexes for retrieiving by user, thread ## Installation & Deployment Deploying the forum microservice will provision and create the following resources. -1. API Gateway entitled forum-microservice with 10 endpoints. +1. API Gateway entitled qa-service with 10 endpoints. 2. 10 * Lambda functions with associated Cloud Watch logs. 3. 2 * DynamoDB tables called Thread and Reply. diff --git a/data/Thread.json b/data/Thread.json index f792a2a..48d7671 100644 --- a/data/Thread.json +++ b/data/Thread.json @@ -10,7 +10,7 @@ "S": "1" }, "ForumId": { - "S": "webconfection" + "S": "myforumid" }, "Title": { "S": "Neque porro quisquam est qui dolorem ipsum quia dolor sit amet, consectetur, adipisci velit..." @@ -22,7 +22,7 @@ "S": "2015-01-15T19:58:22.947Z" }, "UpdatedDateTime": { - "S": "2015-01-15T19:58:22.947Z" + "S": "2015-01-15T19:58:23.947Z" }, "UserName": { "S": "primordial" @@ -40,7 +40,7 @@ "S": "2" }, "ForumId": { - "S": "webconfection" + "S": "myforumid" }, "Title": { "S": "Neque porro quisquam est qui dolorem ipsum quia dolor sit amet, consectetur, adipisci velit..." @@ -52,7 +52,7 @@ "S": "2015-02-15T19:58:22.947Z" }, "UpdatedDateTime": { - "S": "2015-02-15T19:58:22.947Z" + "S": "2015-02-15T19:58:24.947Z" }, "UserName": { "S": "bemed" @@ -70,7 +70,7 @@ "S": "3" }, "ForumId": { - "S": "webconfection" + "S": "myforumid" }, "Title": { "S": "Neque porro quisquam est qui dolorem ipsum quia dolor sit amet, consectetur, adipisci velit..." @@ -82,7 +82,7 @@ "S": "2015-03-15T19:58:22.947Z" }, "UpdatedDateTime": { - "S": "2015-03-15T19:58:22.947Z" + "S": "2015-03-15T19:58:32.947Z" }, "UserName": { "S": "fatlouis" @@ -100,7 +100,7 @@ "S": "4" }, "ForumId": { - "S": "webconfection" + "S": "myforumid" }, "Title": { "S": "Neque porro quisquam est qui dolorem ipsum quia dolor sit amet, consectetur, adipisci velit..." @@ -109,10 +109,10 @@ "S": "Mauris sed vestibulum nibh, non viverra lacus. Vestibulum non lacus massa. Vivamus sodales nibh vel cursus dignissim. Maecenas tempus, ligula id pulvinar semper, neque nunc suscipit ex, a scelerisque justo sapien sit amet magna. Praesent ut dictum purus, vitae sollicitudin sapien. Nunc pretium eros eu nulla hendrerit molestie. Duis nisl nibh, auctor at finibus ac, aliquam nec tellus. Suspendisse sed fringilla ex, id ultrices diam. Vestibulum nulla orci, rhoncus ac efficitur semper, pretium sed nisl. Ut in lectus in augue malesuada laoreet eu sed ante. Etiam ac mattis dolor, sed varius tellus." }, "CreatedDateTime": { - "S": "2015-04-15T19:58:22.947Z" + "S": "2015-04-15T19:58:32.947Z" }, "UpdatedDateTime": { - "S": "2015-04-15T19:58:22.947Z" + "S": "2015-04-15T19:58:42.947Z" }, "UserName": { "S": "mickeymouse" @@ -130,7 +130,7 @@ "S": "5" }, "ForumId": { - "S": "webconfection" + "S": "myforumid" }, "Title": { "S": "Neque porro quisquam est qui dolorem ipsum quia dolor sit amet, consectetur, adipisci velit..." @@ -139,10 +139,10 @@ "S": "Mauris sed vestibulum nibh, non viverra lacus. Vestibulum non lacus massa. Vivamus sodales nibh vel cursus dignissim. Maecenas tempus, ligula id pulvinar semper, neque nunc suscipit ex, a scelerisque justo sapien sit amet magna. Praesent ut dictum purus, vitae sollicitudin sapien. Nunc pretium eros eu nulla hendrerit molestie. Duis nisl nibh, auctor at finibus ac, aliquam nec tellus. Suspendisse sed fringilla ex, id ultrices diam. Vestibulum nulla orci, rhoncus ac efficitur semper, pretium sed nisl. Ut in lectus in augue malesuada laoreet eu sed ante. Etiam ac mattis dolor, sed varius tellus." }, "CreatedDateTime": { - "S": "2015-04-15T19:58:22.947Z" + "S": "2015-04-15T19:58:52.947Z" }, "UpdatedDateTime": { - "S": "2015-04-15T19:58:22.947Z" + "S": "2015-04-15T19:58:52.947Z" }, "UserName": { "S": "queenie" @@ -160,7 +160,7 @@ "S": "6" }, "ForumId": { - "S": "webconfection" + "S": "myforumid" }, "Title": { "S": "Neque porro quisquam est qui dolorem ipsum quia dolor sit amet, consectetur, adipisci velit..." @@ -169,10 +169,10 @@ "S": "Mauris sed vestibulum nibh, non viverra lacus. Vestibulum non lacus massa. Vivamus sodales nibh vel cursus dignissim. Maecenas tempus, ligula id pulvinar semper, neque nunc suscipit ex, a scelerisque justo sapien sit amet magna. Praesent ut dictum purus, vitae sollicitudin sapien. Nunc pretium eros eu nulla hendrerit molestie. Duis nisl nibh, auctor at finibus ac, aliquam nec tellus. Suspendisse sed fringilla ex, id ultrices diam. Vestibulum nulla orci, rhoncus ac efficitur semper, pretium sed nisl. Ut in lectus in augue malesuada laoreet eu sed ante. Etiam ac mattis dolor, sed varius tellus." }, "CreatedDateTime": { - "S": "2015-04-15T19:58:22.947Z" + "S": "2015-04-15T19:58:53.947Z" }, "UpdatedDateTime": { - "S": "2015-04-15T19:58:22.947Z" + "S": "2015-04-15T19:58:53.947Z" }, "UserName": { "S": "kingkong" diff --git a/serverless.yml b/serverless.yml index c7d0167..5608ac3 100644 --- a/serverless.yml +++ b/serverless.yml @@ -1,4 +1,4 @@ -service: forum-service +service: qa-service provider: name: aws @@ -131,6 +131,9 @@ resources: - AttributeName: "CreatedDateTime" AttributeType: "S" + - + AttributeName: "UpdatedDateTime" + AttributeType: "S" KeySchema: - AttributeName: "Id" From 1ca29f47d667e9de437e90d2fbee1e23ced7cba9 Mon Sep 17 00:00:00 2001 From: jacksoncharles Date: Fri, 29 Dec 2017 20:52:46 +0000 Subject: [PATCH 60/78] readme --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index fb7fbbd..871ef1d 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ -# AWS Serverless Q&A Template Solution +# AWS Serverless Q&A Template -A big data template Q&A engine that can be built upon and used for everyday services such as discussion forums, comments and surveys. Deployed to AWS using the [Serverless Framework](http://serverless.com). +A big data Q&A template service that can be easily built upon and used for everyday services such as discussion forums, comments and surveys. Deployed to AWS using the [Serverless Framework](http://serverless.com). Includes pagination and global secondary indexes for retrieiving by user, thread or unique key and is multi-tenancy ready. It is loosely inspired by the [AWS Example Forum](http://docs.aws.amazon.com/amazondynamodb/latest/developerguide/SampleData.CreateTables.html) and designed to be implemented as part of a distributed system. From 468b5271474c3a4447b7e170cbe7cf4536b436a3 Mon Sep 17 00:00:00 2001 From: jacksoncharles Date: Fri, 29 Dec 2017 20:53:56 +0000 Subject: [PATCH 61/78] readme --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 871ef1d..f0e3501 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -# AWS Serverless Q&A Template +# Big data, serverless Q&A template service deployed AWS A big data Q&A template service that can be easily built upon and used for everyday services such as discussion forums, comments and surveys. Deployed to AWS using the [Serverless Framework](http://serverless.com). From 948705be17b8956d11c2b862bcffe904825acddf Mon Sep 17 00:00:00 2001 From: jacksoncharles Date: Fri, 29 Dec 2017 20:54:32 +0000 Subject: [PATCH 62/78] readme --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index f0e3501..bc89afc 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -# Big data, serverless Q&A template service deployed AWS +# Big data, Q&A template A big data Q&A template service that can be easily built upon and used for everyday services such as discussion forums, comments and surveys. Deployed to AWS using the [Serverless Framework](http://serverless.com). From 3f779b6b7ffe36a67e1810ed3524d172fdd8a8eb Mon Sep 17 00:00:00 2001 From: jacksoncharles Date: Fri, 29 Dec 2017 20:55:22 +0000 Subject: [PATCH 63/78] readme --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index bc89afc..8002596 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -# Big data, Q&A template +# Serverless Q&A template A big data Q&A template service that can be easily built upon and used for everyday services such as discussion forums, comments and surveys. Deployed to AWS using the [Serverless Framework](http://serverless.com). From bb02237867fb5ea6a5e3b51f84b32f7ce3564ca8 Mon Sep 17 00:00:00 2001 From: jacksoncharles Date: Fri, 29 Dec 2017 20:58:33 +0000 Subject: [PATCH 64/78] readme --- README.md | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index 8002596..f06dba2 100644 --- a/README.md +++ b/README.md @@ -1,8 +1,8 @@ # Serverless Q&A template -A big data Q&A template service that can be easily built upon and used for everyday services such as discussion forums, comments and surveys. Deployed to AWS using the [Serverless Framework](http://serverless.com). +A big data Q&A serverless template that can be easily built upon and used for everyday services such as discussion forums, comments and surveys. Deployed to AWS using the [Serverless Framework](http://serverless.com). -Includes pagination and global secondary indexes for retrieiving by user, thread or unique key and is multi-tenancy ready. It is loosely inspired by the [AWS Example Forum](http://docs.aws.amazon.com/amazondynamodb/latest/developerguide/SampleData.CreateTables.html) and designed to be implemented as part of a distributed system. +Includes pagination and global secondary indexes for retrieiving by user, thread or unique key and is multi-tenancy ready. Loosely inspired and modelled upon the [AWS Example Forum](http://docs.aws.amazon.com/amazondynamodb/latest/developerguide/SampleData.CreateTables.html) and designed to be implemented as part of a distributed system. ## Technology Stack 1. [AWS Lambda](https://aws.amazon.com/lambda/) @@ -13,7 +13,7 @@ Includes pagination and global secondary indexes for retrieiving by user, thread 5. [NodeJs](https://nodejs.org/) ## Installation & Deployment -Deploying the forum microservice will provision and create the following resources. +Deploying the Q&A service will provision and create the following resources. 1. API Gateway entitled qa-service with 10 endpoints. 2. 10 * Lambda functions with associated Cloud Watch logs. @@ -33,10 +33,10 @@ Secondly, install the [Serverless Framework](http://serverless.com) dependencies npm install ``` -Next, install your microservice API dependencies. +Next, install your Q&A service dependencies. ``` - npm buildapi + npm run-script buildapi ``` Lasty, deploy your microservice API. @@ -79,4 +79,4 @@ DELETE | replyDelete | /replies/:id | DELETE | Remove an item from permanent sto ## Issues -Please report any feedback on the [Issue Tracker](https://github.com/jacksoncharles/aws-serverless-qa-template-solution/issues). \ No newline at end of file +Please report any feedback on the [Issue Tracker](https://github.com/jacksoncharles/serverless-qa-template/issues). \ No newline at end of file From a3d9220e172816c6d954c9a61d3b532cf37101a4 Mon Sep 17 00:00:00 2001 From: jacksoncharles Date: Fri, 29 Dec 2017 21:11:03 +0000 Subject: [PATCH 65/78] readme --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index f06dba2..e52d31e 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,7 @@ A big data Q&A serverless template that can be easily built upon and used for everyday services such as discussion forums, comments and surveys. Deployed to AWS using the [Serverless Framework](http://serverless.com). -Includes pagination and global secondary indexes for retrieiving by user, thread or unique key and is multi-tenancy ready. Loosely inspired and modelled upon the [AWS Example Forum](http://docs.aws.amazon.com/amazondynamodb/latest/developerguide/SampleData.CreateTables.html) and designed to be implemented as part of a distributed system. +Includes pagination and global secondary indexes for retrieiving by user, thread or unique key and is multi-tenancy ready. Loosely inspired and modeled upon the [AWS Example Forum](http://docs.aws.amazon.com/amazondynamodb/latest/developerguide/SampleData.CreateTables.html) and designed to be implemented as part of a distributed system. ## Technology Stack 1. [AWS Lambda](https://aws.amazon.com/lambda/) From 5260149a2cabedd4e83a11798a155b41175f099b Mon Sep 17 00:00:00 2001 From: jacksoncharles Date: Fri, 29 Dec 2017 21:14:06 +0000 Subject: [PATCH 66/78] readme --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index e52d31e..4b654cd 100644 --- a/README.md +++ b/README.md @@ -39,7 +39,7 @@ Next, install your Q&A service dependencies. npm run-script buildapi ``` -Lasty, deploy your microservice API. +Lasty, deploy your Q&A service. ``` sls deploy From e444d791238956482e33cc9ea2c8ea0f9f596fd2 Mon Sep 17 00:00:00 2001 From: jacksoncharles Date: Fri, 29 Dec 2017 21:33:50 +0000 Subject: [PATCH 67/78] readme --- README.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 4b654cd..c566b71 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ -# Serverless Q&A template +# Serverless Q&A Template API -A big data Q&A serverless template that can be easily built upon and used for everyday services such as discussion forums, comments and surveys. Deployed to AWS using the [Serverless Framework](http://serverless.com). +A big data Q&A serverless template API that can be easily built upon and used for everyday services such as discussion forums, comments and surveys. Deployed to AWS using the [Serverless Framework](http://serverless.com). Includes pagination and global secondary indexes for retrieiving by user, thread or unique key and is multi-tenancy ready. Loosely inspired and modeled upon the [AWS Example Forum](http://docs.aws.amazon.com/amazondynamodb/latest/developerguide/SampleData.CreateTables.html) and designed to be implemented as part of a distributed system. @@ -64,7 +64,7 @@ not remove DynamoDb tables; They must be deleted manually. ## Lambda Functions and EndPoints Will create 10 Lambda functions accessible via [API Gateway](https://aws.amazon.com/api-gateway/) configured endpoints. -NAME | LAMBDA | URL | VERB | DESCRIPTION +NAME | LAMBDA | GATEWAY URL | VERB | DESCRIPTION ---- | ------ | --- | ---- | ----------- CREATE | threadCreate | /threads | POST | Create a new item in permanent storage. LIST | threadList | /threads | GET | Retrieve a paginated listing from permanent storage. From 288cfa5e511a20813228af1ed72c7e1055e4d8dd Mon Sep 17 00:00:00 2001 From: jacksoncharles Date: Fri, 29 Dec 2017 21:34:34 +0000 Subject: [PATCH 68/78] readme --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index c566b71..4c59cc7 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # Serverless Q&A Template API -A big data Q&A serverless template API that can be easily built upon and used for everyday services such as discussion forums, comments and surveys. Deployed to AWS using the [Serverless Framework](http://serverless.com). +A big data, serverless Q&A template API that can be easily built upon and used for everyday services such as discussion forums, comments and surveys. Deployed to AWS using the [Serverless Framework](http://serverless.com). Includes pagination and global secondary indexes for retrieiving by user, thread or unique key and is multi-tenancy ready. Loosely inspired and modeled upon the [AWS Example Forum](http://docs.aws.amazon.com/amazondynamodb/latest/developerguide/SampleData.CreateTables.html) and designed to be implemented as part of a distributed system. From b8fc4824f5855f5f45a70403ab9ae4b822c0cf6d Mon Sep 17 00:00:00 2001 From: jacksoncharles Date: Fri, 29 Dec 2017 21:39:08 +0000 Subject: [PATCH 69/78] readme --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 4c59cc7..1475bfb 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # Serverless Q&A Template API -A big data, serverless Q&A template API that can be easily built upon and used for everyday services such as discussion forums, comments and surveys. Deployed to AWS using the [Serverless Framework](http://serverless.com). +A big data, serverless Q&A template API with robust CRUD functionality that can be easily built upon and used for everyday services such as discussion forums, comments and surveys. Deployed to AWS using the [Serverless Framework](http://serverless.com). Includes pagination and global secondary indexes for retrieiving by user, thread or unique key and is multi-tenancy ready. Loosely inspired and modeled upon the [AWS Example Forum](http://docs.aws.amazon.com/amazondynamodb/latest/developerguide/SampleData.CreateTables.html) and designed to be implemented as part of a distributed system. From f64efe1161168d85854209d2f60c38ab95d51a8d Mon Sep 17 00:00:00 2001 From: jacksoncharles Date: Fri, 29 Dec 2017 22:09:19 +0000 Subject: [PATCH 70/78] title and description --- package.json | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/package.json b/package.json index 3e5aa49..73cc864 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { - "name": "post-api", + "name": "serverless-qa-template-api", "version": "1.0.0", - "description": "", + "description": "Big data, serverless Q&A template API. Written in NodeJS and deployed to AWS. Designed to be implemented as part of a distributed service. Exploits Lambda, API Gateway, Cloudwatch & Dynamodb.", "main": "index.js", "scripts": { "test": "echo \"Error: no test specified\" && exit 1", @@ -12,5 +12,8 @@ "dependencies": { "bluebird": "^3.5.1", "lint": "^1.1.2" + }, + "devDependencies": { + "eslint": "^4.14.0" } } From 58f7f32579cfa85239f29ff006e76bb3816dd8a5 Mon Sep 17 00:00:00 2001 From: jacksoncharles Date: Sat, 30 Dec 2017 16:12:14 +0000 Subject: [PATCH 71/78] linter changes --- .eslintrc.js | 10 +++++++ api/_classes/Dynamic.js | 3 +- api/replies/_classes/ReplyQueryBuilder.js | 35 +++++++++++----------- api/replies/replyCreate.js | 2 ++ api/replies/replyDelete.js | 26 +++++++++++----- api/replies/replyList.js | 9 ++++++ api/replies/replyUpdate.js | 2 ++ api/threads/_classes/ThreadQueryBuilder.js | 34 ++++++++++----------- api/threads/threadCreate.js | 2 ++ api/threads/threadDelete.js | 26 +++++++++++----- api/threads/threadList.js | 11 +++++++ api/threads/threadUpdate.js | 2 ++ package.json | 7 +++-- 13 files changed, 116 insertions(+), 53 deletions(-) create mode 100644 .eslintrc.js diff --git a/.eslintrc.js b/.eslintrc.js new file mode 100644 index 0000000..3860c59 --- /dev/null +++ b/.eslintrc.js @@ -0,0 +1,10 @@ +module.exports = { + "env": { + "es6": true, + "node": true + }, + "rules": { + "no-console":0 + }, + "extends": "eslint:recommended" +}; \ No newline at end of file diff --git a/api/_classes/Dynamic.js b/api/_classes/Dynamic.js index 7f004c3..ebebd2c 100644 --- a/api/_classes/Dynamic.js +++ b/api/_classes/Dynamic.js @@ -9,7 +9,7 @@ var ValidationError = Errors.ValidationError; var NotFoundError = Errors.NotFoundError; /** - * Wrapper for DynamoDb with basic CRUD functionality and a validation method + * Wrapper for DynamoDb with basic CRUD functionality. * * @type {Class} */ @@ -77,7 +77,6 @@ module.exports = class Dynamic { Id : id } } - console.log( '=== parameters ===', parameters ); return new Promise( function( resolve, reject ) { diff --git a/api/replies/_classes/ReplyQueryBuilder.js b/api/replies/_classes/ReplyQueryBuilder.js index eb8ba4b..9f8328b 100644 --- a/api/replies/_classes/ReplyQueryBuilder.js +++ b/api/replies/_classes/ReplyQueryBuilder.js @@ -89,34 +89,35 @@ module.exports = class ReplyQueryBuilder { if( this._criterion !== null && typeof this._criterion === 'object' ) { - if( this._criterion.hasOwnProperty( 'threadid' ) == false && - this._criterion.hasOwnProperty( 'userid' ) == false - ) { + if( this._criterion.hasOwnProperty( 'threadid' ) == false && + this._criterion.hasOwnProperty( 'userid' ) == false - this._errors.push( { "message": "You must provide a threadid or userid parameter" } ); - } + ) { - if( this._criterion.hasOwnProperty( 'threadid' ) ) { + this._errors.push( { "message": "You must provide a threadid or userid parameter" } ); + } + + if( this._criterion.hasOwnProperty( 'threadid' ) ) { - if ( validator.isAlphanumeric( this._criterion.threadid ) == false ) { + if ( validator.isAlphanumeric( this._criterion.threadid ) == false ) { - this._errors.push( { "message": "Your threadid parameter must be an alphanumeric string" } ); - } - } + this._errors.push( { "message": "Your threadid parameter must be an alphanumeric string" } ); + } + } - if( this._criterion.hasOwnProperty('userid') ) { + if( this._criterion.hasOwnProperty('userid') ) { - if ( validator.isNumeric( this._criterion.userid ) == false ) { + if ( validator.isNumeric( this._criterion.userid ) == false ) { - this._errors.push( { "message": "Your userid parameter must be numeric" } ); - } - } + this._errors.push( { "message": "Your userid parameter must be numeric" } ); + } + } } else { this._errors.push( { "message" : "You must supply a threadid or userid" } ); } - + if( this._errors.length ) { throw new ValidationError( JSON.stringify( this._errors ) ); @@ -171,7 +172,7 @@ module.exports = class ReplyQueryBuilder { buildPagination() { if ( this._criterion.hasOwnProperty('threadid') && - this._criterion.hasOwnProperty('createddatetime') ) + this._criterion.hasOwnProperty('createddatetime') ) { this._parameters['ExclusiveStartKey'] = { ThreadId: this._criterion.threadid, diff --git a/api/replies/replyCreate.js b/api/replies/replyCreate.js index b7bc6cd..6139b61 100644 --- a/api/replies/replyCreate.js +++ b/api/replies/replyCreate.js @@ -2,8 +2,10 @@ const uuidv1 = require('uuid/v1'); var Reply = require("./_classes/Reply"); + var Errors = require("./../_classes/Errors"); var ValidationError = Errors.ValidationError; +var DynamodbError = Errors.DynamodbError; /** * Handler for the lambda function. diff --git a/api/replies/replyDelete.js b/api/replies/replyDelete.js index 9c4d94a..ace29a0 100644 --- a/api/replies/replyDelete.js +++ b/api/replies/replyDelete.js @@ -1,9 +1,10 @@ 'use strict'; -var Errors = require("./../_classes/Errors"); -var ValidationError = Errors.ValidationError; var Reply = require("./_classes/Reply"); +var Errors = require("./../_classes/Errors"); +var DynamodbError = Errors.DynamodbError; + /** * Handler for the lambda function. * @@ -27,12 +28,23 @@ module.exports.replyDelete = (event, context, callback) => { }) .catch( function( error ) { - console.log('<<>>', error ); + if( error instanceof DynamodbError ) { + + console.log('<<>>', error ); - callback(null, { - statusCode: 500, - body: JSON.stringify( { message: error.message } ) - }); + callback(null, { + statusCode: 500, + body: JSON.stringify( error ) + }); + } else { + + console.log('<<>>', error ); + + callback(null, { + statusCode: 500, + body: JSON.stringify( error ) + }); + } }); }; \ No newline at end of file diff --git a/api/replies/replyList.js b/api/replies/replyList.js index 6f8c2cc..00ea3cb 100644 --- a/api/replies/replyList.js +++ b/api/replies/replyList.js @@ -63,6 +63,15 @@ module.exports.replyList = (event, context, callback) => { body: error.message }); + } else if( error instanceof DynamodbError ) { + + console.log('<<>>', error ); + + callback(null, { + statusCode: 500, + body: JSON.stringify( error ) + }); + } else { console.log('<<>>', error ); diff --git a/api/replies/replyUpdate.js b/api/replies/replyUpdate.js index 8bf4f17..ae824cd 100644 --- a/api/replies/replyUpdate.js +++ b/api/replies/replyUpdate.js @@ -1,8 +1,10 @@ 'use strict'; var Reply = require("./_classes/Reply"); + var Errors = require("./../_classes/Errors"); var ValidationError = Errors.ValidationError; +var DynamodbError = Errors.DynamodbError; /** * Handler for the lambda function. diff --git a/api/threads/_classes/ThreadQueryBuilder.js b/api/threads/_classes/ThreadQueryBuilder.js index ccd19cb..2525068 100644 --- a/api/threads/_classes/ThreadQueryBuilder.js +++ b/api/threads/_classes/ThreadQueryBuilder.js @@ -89,34 +89,34 @@ module.exports = class ThreadQueryBuilder { if( this._criterion !== null && typeof this._criterion === 'object' ) { - if( this._criterion.hasOwnProperty( 'forumid' ) == false && - this._criterion.hasOwnProperty( 'userid' ) == false - ) { + if( this._criterion.hasOwnProperty( 'forumid' ) == false && + this._criterion.hasOwnProperty( 'userid' ) == false + ) { - this._errors.push( { "message": "You must provide a forumid or userid parameter" } ); - } + this._errors.push( { "message": "You must provide a forumid or userid parameter" } ); + } - if( this._criterion.hasOwnProperty( 'forumid' ) ) { + if( this._criterion.hasOwnProperty( 'forumid' ) ) { - if ( validator.isAlphanumeric( this._criterion.forumid ) == false ) { + if ( validator.isAlphanumeric( this._criterion.forumid ) == false ) { - this._errors.push( { "message": "Your forumid parameter must be an alphanumeric string" } ); - } - } + this._errors.push( { "message": "Your forumid parameter must be an alphanumeric string" } ); + } + } - if( this._criterion.hasOwnProperty('userid') ) { + if( this._criterion.hasOwnProperty('userid') ) { - if ( validator.isNumeric( this._criterion.userid ) == false ) { + if ( validator.isNumeric( this._criterion.userid ) == false ) { - this._errors.push( { "message": "Your userid parameter must be numeric" } ); - } - } + this._errors.push( { "message": "Your userid parameter must be numeric" } ); + } + } } else { this._errors.push( { "message" : "You must supply a forumid or userid" } ); } - + if( this._errors.length ) { throw new ValidationError( JSON.stringify( this._errors ) ); @@ -171,7 +171,7 @@ module.exports = class ThreadQueryBuilder { buildPagination() { if ( this._criterion.hasOwnProperty('forumid') && - this._criterion.hasOwnProperty('createddatetime') ) + this._criterion.hasOwnProperty('createddatetime') ) { this._parameters['ExclusiveStartKey'] = { ForumId: this._criterion.forumid, diff --git a/api/threads/threadCreate.js b/api/threads/threadCreate.js index 03a1859..f734031 100644 --- a/api/threads/threadCreate.js +++ b/api/threads/threadCreate.js @@ -3,8 +3,10 @@ const uuidv1 = require('uuid/v1'); var Thread = require("./_classes/Thread"); + var Errors = require("./../_classes/Errors"); var ValidationError = Errors.ValidationError; +var DynamodbError = Errors.DynamodbError; /** * Handler for the lambda function. diff --git a/api/threads/threadDelete.js b/api/threads/threadDelete.js index 17cf341..7ce1141 100644 --- a/api/threads/threadDelete.js +++ b/api/threads/threadDelete.js @@ -1,9 +1,10 @@ 'use strict'; -var Errors = require("./../_classes/Errors"); -var ValidationError = Errors.ValidationError; var Thread = require("./_classes/Thread"); +var Errors = require("./../_classes/Errors"); +var DynamodbError = Errors.DynamodbError; + /** * Handler for the lambda function. * @@ -27,12 +28,23 @@ module.exports.threadDelete = (event, context, callback) => { }) .catch( function( error ) { - console.log('<<>>', error ); + if( error instanceof DynamodbError ) { + + console.log('<<>>', error ); - callback(null, { - statusCode: 500, - body: JSON.stringify( { message: error.message } ) - }); + callback(null, { + statusCode: 500, + body: JSON.stringify( error ) + }); + } else { + + console.log('<<>>', error ); + + callback(null, { + statusCode: 500, + body: JSON.stringify( error ) + }); + } }); }; \ No newline at end of file diff --git a/api/threads/threadList.js b/api/threads/threadList.js index 976f260..5852182 100644 --- a/api/threads/threadList.js +++ b/api/threads/threadList.js @@ -1,7 +1,9 @@ 'use strict'; var Thread = require("./_classes/Thread"); + var ThreadQueryBuilder = require("./_classes/ThreadQueryBuilder"); + var Errors = require("./../_classes/Errors"); var DynamodbError = Errors.DynamodbError; var ValidationError = Errors.ValidationError; @@ -61,6 +63,15 @@ module.exports.threadList = (event, context, callback) => { body: error.message }); + } else if( error instanceof DynamodbError ) { + + console.log('<<>>', error ); + + callback(null, { + statusCode: 500, + body: JSON.stringify( error ) + }); + } else { console.log('<<>>', error ); diff --git a/api/threads/threadUpdate.js b/api/threads/threadUpdate.js index 1f55d39..df6b13a 100644 --- a/api/threads/threadUpdate.js +++ b/api/threads/threadUpdate.js @@ -1,8 +1,10 @@ 'use strict'; var Thread = require("./_classes/Thread"); + var Errors = require("./../_classes/Errors"); var ValidationError = Errors.ValidationError; +var DynamodbError = Errors.DynamodbError; /** * Handler for the lambda function. diff --git a/package.json b/package.json index 73cc864..fd969d7 100644 --- a/package.json +++ b/package.json @@ -10,10 +10,11 @@ "author": "", "license": "ISC", "dependencies": { - "bluebird": "^3.5.1", - "lint": "^1.1.2" + "bluebird": "^3.5.1" }, "devDependencies": { - "eslint": "^4.14.0" + "eslint": "^4.14.0", + "eslint-config-airbnb-base": "^12.1.0", + "eslint-plugin-import": "^2.8.0" } } From a7af32275129bfbe7f5e609e58972e6a1bfbfdfc Mon Sep 17 00:00:00 2001 From: jacksoncharles Date: Sat, 30 Dec 2017 16:59:36 +0000 Subject: [PATCH 72/78] linter --- .eslintrc.js | 19 +- api/replies/_classes/Reply.js | 93 ++++---- api/replies/_classes/ReplyQueryBuilder.js | 230 +++++++++----------- api/replies/replyCreate.js | 128 ++++++----- api/replies/replyDelete.js | 76 ++++--- api/replies/replyGet.js | 51 +++-- api/replies/replyList.js | 132 ++++++------ api/replies/replyUpdate.js | 131 ++++++------ api/threads/_classes/Thread.js | 96 ++++----- api/threads/_classes/ThreadQueryBuilder.js | 234 ++++++++++----------- api/threads/threadCreate.js | 115 +++++----- api/threads/threadDelete.js | 76 ++++--- api/threads/threadGet.js | 51 +++-- api/threads/threadList.js | 132 ++++++------ api/threads/threadUpdate.js | 130 ++++++------ 15 files changed, 790 insertions(+), 904 deletions(-) diff --git a/.eslintrc.js b/.eslintrc.js index 3860c59..c6e12ce 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -1,10 +1,11 @@ module.exports = { - "env": { - "es6": true, - "node": true - }, - "rules": { - "no-console":0 - }, - "extends": "eslint:recommended" -}; \ No newline at end of file + "extends": "airbnb-base", + "env": { + "node": true + }, + "rules": { + "strict": "off", + "no-console": "off", + "import/no-unresolved": "off" + } +} \ No newline at end of file diff --git a/api/replies/_classes/Reply.js b/api/replies/_classes/Reply.js index b3ec313..3a313a4 100644 --- a/api/replies/_classes/Reply.js +++ b/api/replies/_classes/Reply.js @@ -2,81 +2,74 @@ const validator = require('validator'); -var Errors = require("./../../_classes/Errors"); -var ValidationError = Errors.ValidationError; -var Dynamic = require('./../../_classes/Dynamic'); +const Dynamic = require('./../../_classes/Dynamic'); + +const Errors = require('./../../_classes/Errors'); + +const { ValidationError } = Errors.ValidationError; + /** * Reply class. Each instance maps to one document in permanent storage and extends the * Dynamic wrapper class. - * + * * @type {class} */ module.exports = class Reply extends Dynamic { - - constructor( parameters ) { - - super( parameters ); - } - - /** + /** * Return a string containing the name of the table in perment storage * for the model. * * @return {String} - Name of the string */ - static table() { - - return 'Reply'; - } + static table() { + return 'Reply'; + } - /** + /** * Return a new instance of this - * + * * @param {Object} parameters - Properties to be assigned to the newly created object - * + * * @return {Object} New instance of the Reply object */ - static model( parameters ) { - - return new Reply( parameters ); - } + static model(parameters) { + return new Reply(parameters); + } - /** + /** * Return an object that represents the properties of this class - * + * * @return {Object} - Class properties that can be saved to dynamodb */ - properties() { - - return { - 'Id': this.Id, - 'ThreadId': this.ThreadId, - 'UserId': this.UserId, - 'UserName': this.UserName, - 'Message': this.Message, - 'CreatedDateTime': this.CreatedDateTime, - 'UpdatedDateTime': this.UpdatedDateTime - } - } + properties() { + return { + Id: this.Id, + ThreadId: this.ThreadId, + UserId: this.UserId, + UserName: this.UserName, + Message: this.Message, + CreatedDateTime: this.CreatedDateTime, + UpdatedDateTime: this.UpdatedDateTime, + }; + } - /** + /** * Validate the class properties and throw an exception if necessary - * + * * @return {this} - Instance of this */ - validate() { + validate() { + const errors = []; // Create an empty array to hold any validation errors - let errors = []; // Create an empty array to hold any validation errors + if (typeof this.Id === 'undefined' || validator.isEmpty(this.Id)) errors.push({ Id: 'must provide a unique string for Id' }); + if (typeof this.Message === 'undefined' || validator.isEmpty(this.Message)) errors.push({ Message: 'must provide a value for Message' }); + if (typeof this.ThreadId === 'undefined' || validator.isEmpty(this.ThreadId)) errors.push({ ThreadId: 'must provide a value for ThreadId' }); + if (typeof this.UserId === 'undefined' || validator.isEmpty(this.UserId)) errors.push({ UserId: 'must provide a value for UserId' }); + if (typeof this.UserName === 'undefined' || validator.isEmpty(this.UserName)) errors.push({ UserName: 'must provide a value for UserName' }); - if( typeof this.Id == 'undefined' || validator.isEmpty( this.Id ) ) errors.push({'Id': 'must provide a unique string for Id'}); - if( typeof this.Message == 'undefined' || validator.isEmpty( this.Message ) ) errors.push({'Message': 'must provide a value for Message'}); - if( typeof this.ThreadId == 'undefined' || validator.isEmpty( this.ThreadId ) ) errors.push({'ThreadId': 'must provide a value for ThreadId'}); - if( typeof this.UserId == 'undefined' || validator.isEmpty( this.UserId ) ) errors.push({'UserId': 'must provide a value for UserId'}); - if( typeof this.UserName == 'undefined' || validator.isEmpty( this.UserName ) ) errors.push({'UserName': 'must provide a value for UserName'}); - - if( errors.length ) throw new ValidationError( JSON.stringify( errors ) ); + if (errors.length) throw new ValidationError(JSON.stringify(errors)); - return this; - } -} \ No newline at end of file + return this; + } +}; diff --git a/api/replies/_classes/ReplyQueryBuilder.js b/api/replies/_classes/ReplyQueryBuilder.js index 9f8328b..7a3b0a5 100644 --- a/api/replies/_classes/ReplyQueryBuilder.js +++ b/api/replies/_classes/ReplyQueryBuilder.js @@ -2,189 +2,167 @@ const validator = require('validator'); -var Errors = require("./../../_classes/Errors"); -var ValidationError = Errors.ValidationError; +const Errors = require('./../../_classes/Errors'); + +const ValidationError = Errors.ValidationError; /** - * Responsible for turning parameters passeed are turned in DynamoDb parameters by building + * Responsible for turning parameters passeed are turned in DynamoDb parameters by building * this.parameters object using data passed inside this.events.queryStringParameters - * + * * @type {class} */ module.exports = class ReplyQueryBuilder { + constructor(criterion) { + /** @type {Object} Key/value pairs used to build our DynamoDb parameters. */ + this._criterion = criterion; - constructor( criterion ) { - - /** @type {Object} Key/value pairs used to build our DynamoDb parameters. */ - this._criterion = criterion; - - /** + /** * Used to hold the dynamodb query parameters built using values * within property this.event - * + * * @todo : Change TableName to value of process.env.DYNAMODB_REPLY_TABLE - * + * * @type {object} */ - this._parameters = { - TableName: 'Reply' - } + this._parameters = { + TableName: 'Reply', + }; - /** + /** * Used to hold any validation errors. - * + * * @type {array} */ - this._errors = []; - } + this._errors = []; + } - /** + /** * Getter - * + * * @return {object} parameters */ - get parameters() { + get parameters() { + return this._parameters; + } - return this._parameters; - } - - /** + /** * Setter - * + * * @return {object} parameters */ - set parameters( parameters ) { - - this._parameters = parameters; - } + set parameters(parameters) { + this._parameters = parameters; + } - /** + /** * Getter - * + * * @return {array} errors */ - get errors() { - - return this._errors; - } + get errors() { + return this._errors; + } - /** + /** * Setter - * + * * @return {array} errors */ - set errors( errors ) { + set errors(errors) { + this._errors = errors; + } - this._errors = errors; - } - - /** + /** * Validates the parameters passed inside this._criterion object - * - * @return {boolean} + * + * @return {boolean} */ - validate() { - - this._errors = []; // Reset the errors array before running the validation logic + validate() { + this._errors = []; // Reset the errors array before running the validation logic - if( this._criterion !== null && typeof this._criterion === 'object' ) { + if (this._criterion !== null && typeof this._criterion === 'object') { + if (this._criterion.hasOwnProperty('threadid') == false && + this._criterion.hasOwnProperty('userid') == false - if( this._criterion.hasOwnProperty( 'threadid' ) == false && - this._criterion.hasOwnProperty( 'userid' ) == false + ) { + this._errors.push({ message: 'You must provide a threadid or userid parameter' }); + } - ) { - - this._errors.push( { "message": "You must provide a threadid or userid parameter" } ); - } - - if( this._criterion.hasOwnProperty( 'threadid' ) ) { - - if ( validator.isAlphanumeric( this._criterion.threadid ) == false ) { - - this._errors.push( { "message": "Your threadid parameter must be an alphanumeric string" } ); - } - } - - if( this._criterion.hasOwnProperty('userid') ) { - - if ( validator.isNumeric( this._criterion.userid ) == false ) { - - this._errors.push( { "message": "Your userid parameter must be numeric" } ); - } - } + if (this._criterion.hasOwnProperty('threadid')) { + if (validator.isAlphanumeric(this._criterion.threadid) == false) { + this._errors.push({ message: 'Your threadid parameter must be an alphanumeric string' }); } - else { + } - this._errors.push( { "message" : "You must supply a threadid or userid" } ); + if (this._criterion.hasOwnProperty('userid')) { + if (validator.isNumeric(this._criterion.userid) == false) { + this._errors.push({ message: 'Your userid parameter must be numeric' }); } + } + } else { + this._errors.push({ message: 'You must supply a threadid or userid' }); + } - if( this._errors.length ) { - - throw new ValidationError( JSON.stringify( this._errors ) ); - } + if (this._errors.length) { + throw new ValidationError(JSON.stringify(this._errors)); + } - return this; - } + return this; + } - /** + /** * If "threadid" has been passed inside this.event this method will build upon this.parameters object * * @return this */ - buildThreadIndex() { - - if( this._criterion.hasOwnProperty('threadid') ) { - - this._parameters['IndexName'] = "ThreadIndex"; - this._parameters['KeyConditionExpression'] = "ThreadId = :searchstring"; - this._parameters['ExpressionAttributeValues'] = { - ":searchstring" : this._criterion.threadid - }; - } - - return this; + buildThreadIndex() { + if (this._criterion.hasOwnProperty('threadid')) { + this._parameters.IndexName = 'ThreadIndex'; + this._parameters.KeyConditionExpression = 'ThreadId = :searchstring'; + this._parameters.ExpressionAttributeValues = { + ':searchstring': this._criterion.threadid, + }; } - /** + return this; + } + + /** * If "userid" has been passed inside this.event this method will build upon this.parameters object - * + * * @return this */ - buildUserIndex() { - - if ( this._criterion.hasOwnProperty('userid') ) { - - this._parameters['IndexName'] = "UserIndex"; - this._parameters['KeyConditionExpression'] = "UserId = :searchstring"; - this._parameters['ExpressionAttributeValues'] = { - ":searchstring" : this._criterion.userid - }; - } - - return this; + buildUserIndex() { + if (this._criterion.hasOwnProperty('userid')) { + this._parameters.IndexName = 'UserIndex'; + this._parameters.KeyConditionExpression = 'UserId = :searchstring'; + this._parameters.ExpressionAttributeValues = { + ':searchstring': this._criterion.userid, + }; } - /** + return this; + } + + /** * If pagination parameters have been passed inside this.event this method will build upon this.parameters object - * + * * @return this */ - buildPagination() { - - if ( this._criterion.hasOwnProperty('threadid') && - this._criterion.hasOwnProperty('createddatetime') ) - { - this._parameters['ExclusiveStartKey'] = { - ThreadId: this._criterion.threadid, - DateTime: this._criterion.createddatetime - } - } - - if ( this._criterion.hasOwnProperty('limit') ) { - - this._parameters['Limit'] = this._criterion.limit; - } + buildPagination() { + if (this._criterion.hasOwnProperty('threadid') && + this._criterion.hasOwnProperty('createddatetime')) { + this._parameters.ExclusiveStartKey = { + ThreadId: this._criterion.threadid, + DateTime: this._criterion.createddatetime, + }; + } - return this; + if (this._criterion.hasOwnProperty('limit')) { + this._parameters.Limit = this._criterion.limit; } -} \ No newline at end of file + + return this; + } +}; diff --git a/api/replies/replyCreate.js b/api/replies/replyCreate.js index 6139b61..d147fb6 100644 --- a/api/replies/replyCreate.js +++ b/api/replies/replyCreate.js @@ -1,80 +1,72 @@ 'use strict'; + const uuidv1 = require('uuid/v1'); -var Reply = require("./_classes/Reply"); +const Reply = require('./_classes/Reply'); + +const Errors = require('./../_classes/Errors'); -var Errors = require("./../_classes/Errors"); -var ValidationError = Errors.ValidationError; -var DynamodbError = Errors.DynamodbError; +const { ValidationError } = Errors.ValidationError; +const { DynamodbError } = Errors.DynamodbError; /** * Handler for the lambda function. - * - * @param {Object} event - AWS Lambda uses this parameter to pass in event data to the handler. - * @param {Object} context - AWS Lambda uses this parameter to provide your handler the runtime information of the Lambda function that is executing. - * @param {Function} callback - Optional parameter used to pass a callback - * + * + * @param {Object} event - AWS Lambda uses this parameter to pass in event + * data to the handler. + * @param {Object} context - AWS Lambda uses this parameter to provide your + * handler the runtime information of the Lambda + * function that is executing. + * @param {Function} callback - Optional parameter used to pass a callback. + * * @return JSON JSON encoded response. */ module.exports.replyCreate = (event, context, callback) => { - - try { - - let parameters = JSON.parse( event.body ); - parameters['Id']= uuidv1(); - - let reply = new Reply( parameters ); - - reply - .validate() - .save() - .then( ( data ) => { - - const response = { - statusCode: 200, - body: JSON.stringify( data ) - } - - return callback( null, response ); - }) - .catch( function( error ) { - - console.log('<<>>', error ); - - callback(null, { - statusCode: 500, - body: JSON.stringify( { message: error.message } ) - }); - + try { + const parameters = JSON.parse(event.body); + parameters.Id = uuidv1(); + + const reply = new Reply(parameters); + + reply + .validate() + .save() + .then((data) => { + const response = { + statusCode: 200, + body: JSON.stringify(data), + }; + + return callback(null, response); + }) + .catch((error) => { + console.log('<<>>', error); + + callback(null, { + statusCode: 500, + body: JSON.stringify({ message: error.message }), }); - - } catch( error ) { - - if( error instanceof ValidationError ) { - - callback(null, { - statusCode: 422, - body: error.message - }); - } - else if( error instanceof DynamodbError ) { - - console.log('<<>>', error ); - - callback(null, { - statusCode: 500, - body: JSON.stringify( error ) - }); - - } - else { - - console.log('<<>>', error ); - - callback(null, { - statusCode: 500, - body: JSON.stringify( error ) - }); - } + }); + } catch (error) { + if (error instanceof ValidationError) { + callback(null, { + statusCode: 422, + body: error.message, + }); + } else if (error instanceof DynamodbError) { + console.log('<<>>', error); + + callback(null, { + statusCode: 500, + body: JSON.stringify(error), + }); + } else { + console.log('<<>>', error); + + callback(null, { + statusCode: 500, + body: JSON.stringify(error), + }); } -}; \ No newline at end of file + } +}; diff --git a/api/replies/replyDelete.js b/api/replies/replyDelete.js index ace29a0..67ade63 100644 --- a/api/replies/replyDelete.js +++ b/api/replies/replyDelete.js @@ -1,50 +1,48 @@ 'use strict'; -var Reply = require("./_classes/Reply"); +const Reply = require('./_classes/Reply'); -var Errors = require("./../_classes/Errors"); -var DynamodbError = Errors.DynamodbError; +const Errors = require('./../_classes/Errors'); + +const { DynamodbError } = Errors.DynamodbError; /** * Handler for the lambda function. - * - * @param {Object} event - AWS Lambda uses this parameter to pass in event data to the handler. - * @param {Object} context - AWS Lambda uses this parameter to provide your handler the runtime information of the Lambda function that is executing. - * @param {Function} callback - Optional parameter used to pass a callback - * + * + * @param {Object} event - AWS Lambda uses this parameter to pass in event + * data to the handler. + * @param {Object} context - AWS Lambda uses this parameter to provide your + * handler the runtime information of the Lambda + * function that is executing. + * @param {Function} callback - Optional parameter used to pass a callback + * * @return JSON JSON encoded response. */ module.exports.replyDelete = (event, context, callback) => { - - Reply.destroy( event.pathParameters.id ) - .then( ( reply ) => { - - const response = { - statusCode: 204, - body: reply - } - - return callback( null, response ); + Reply.destroy(event.pathParameters.id) + .then((reply) => { + const response = { + statusCode: 204, + body: reply, + }; + + return callback(null, response); }) - .catch( function( error ) { - - if( error instanceof DynamodbError ) { - - console.log('<<>>', error ); - - callback(null, { - statusCode: 500, - body: JSON.stringify( error ) - }); - - } else { - - console.log('<<>>', error ); - - callback(null, { - statusCode: 500, - body: JSON.stringify( error ) - }); - } + .catch((error) => { + if (error instanceof DynamodbError) { + console.log('<<>>', error); + + callback(null, { + statusCode: 500, + body: JSON.stringify(error), + }); + } else { + console.log('<<>>', error); + + callback(null, { + statusCode: 500, + body: JSON.stringify(error), + }); + } }); -}; \ No newline at end of file +}; diff --git a/api/replies/replyGet.js b/api/replies/replyGet.js index 4d60a48..1338896 100644 --- a/api/replies/replyGet.js +++ b/api/replies/replyGet.js @@ -1,36 +1,35 @@ 'use strict'; -var Reply = require("./_classes/Reply"); +const Reply = require('./_classes/Reply'); /** * Handler for the lambda function. - * - * @param {Object} event - AWS Lambda uses this parameter to pass in event data to the handler. - * @param {Object} context - AWS Lambda uses this parameter to provide your handler the runtime information of the Lambda function that is executing. - * @param {Function} callback - Optional parameter used to pass a callback - * + * + * @param {Object} event - AWS Lambda uses this parameter to pass in event + * data to the handler. + * @param {Object} context - AWS Lambda uses this parameter to provide your + * handler the runtime information of the Lambda + * function that is executing. + * @param {Function} callback - Optional parameter used to pass a callback + * * @return JSON JSON encoded response. */ -module.exports.replyGet = ( event, context, callback ) => { - - Reply.find( event.pathParameters.id ) - .then( ( reply ) => { - - let response = { - statusCode: Object.keys( reply ).length === 0 ? 404 : 200, - body: JSON.stringify( reply ) - } - - callback( null, response ); +module.exports.replyGet = (event, context, callback) => { + Reply.find(event.pathParameters.id) + .then((reply) => { + const response = { + statusCode: Object.keys(reply).length === 0 ? 404 : 200, + body: JSON.stringify(reply), + }; + + callback(null, response); }) - .catch( function( error ) { - - console.log('<<>>', error ); - - callback(null, { - statusCode: 500, - body: error - }); + .catch((error) => { + console.log('<<>>', error); + callback(null, { + statusCode: 500, + body: error, + }); }); -}; \ No newline at end of file +}; diff --git a/api/replies/replyList.js b/api/replies/replyList.js index 00ea3cb..4eb4408 100644 --- a/api/replies/replyList.js +++ b/api/replies/replyList.js @@ -1,85 +1,77 @@ 'use strict'; -var Reply = require("./_classes/Reply"); +const Reply = require('./_classes/Reply'); -var ReplyQueryBuilder = require("./_classes/ReplyQueryBuilder"); +const ReplyQueryBuilder = require('./_classes/ReplyQueryBuilder'); -var Errors = require("./../_classes/Errors"); -var DynamodbError = Errors.DynamodbError; -var ValidationError = Errors.ValidationError; +const Errors = require('./../_classes/Errors'); + +const { DynamodbError } = Errors.DynamodbError; +const { ValidationError } = Errors.ValidationError; /** * Handler for the lambda function. - * - * @param {Object} event - AWS Lambda uses this parameter to pass in event data to the handler. - * @param {Object} context - AWS Lambda uses this parameter to provide your handler the runtime information of the Lambda function that is executing. - * @param {Function} callback - Optional parameter used to pass a callback - * + * + * @param {Object} event - AWS Lambda uses this parameter to pass in event + * data to the handler. + * @param {Object} context - AWS Lambda uses this parameter to provide your + * handler the runtime information of the Lambda + * function that is executing. + * @param {Function} callback - Optional parameter used to pass a callback + * * @return JSON JSON encoded response. */ module.exports.replyList = (event, context, callback) => { - - /** + /** * Instantiate an instance of QueryBuilder - * + * * @type {QueryBuilder} */ - let Query = new ReplyQueryBuilder( event.queryStringParameters ); - - try { - - Query - .validate() - .buildThreadIndex() - .buildUserIndex() - .buildPagination(); - - /** @type {model} Contains a list of items and optional pagination data */ - Reply.list( Query.parameters ) - .then( ( replies ) => { - - const response = { - statusCode: replies.Items.length > 0 ? 200 : 204, - body: JSON.stringify( replies ) - }; - - callback( null, response ); - }) - .catch( function( error ) { - - callback(null, { - statusCode: 500, - body: JSON.stringify( { message: error.message } ) - }); - + const Query = new ReplyQueryBuilder(event.queryStringParameters); + + try { + Query + .validate() + .buildThreadIndex() + .buildUserIndex() + .buildPagination(); + + /** @type {model} Contains a list of items and optional pagination data */ + Reply.list(Query.parameters) + .then((replies) => { + const response = { + statusCode: replies.Items.length > 0 ? 200 : 204, + body: JSON.stringify(replies), + }; + + callback(null, response); + }) + .catch((error) => { + callback(null, { + statusCode: 500, + body: JSON.stringify({ message: error.message }), }); + }); + } catch (error) { // Catch any errors thrown by the ReplyQueryBuilder class + if (error instanceof ValidationError) { + callback(null, { + statusCode: 422, + body: error.message, + }); + } else if (error instanceof DynamodbError) { + console.log('<<>>', error); + + callback(null, { + statusCode: 500, + body: JSON.stringify(error), + }); + } else { + console.log('<<>>', error); + + callback(null, { + statusCode: 500, + body: JSON.stringify(error), + }); } - catch( error ) { // Catch any errors thrown by the ReplyQueryBuilder class - - if( error instanceof ValidationError ) { - - callback(null, { - statusCode: 422, - body: error.message - }); - - } else if( error instanceof DynamodbError ) { - - console.log('<<>>', error ); - - callback(null, { - statusCode: 500, - body: JSON.stringify( error ) - }); - - } else { - - console.log('<<>>', error ); - - callback(null, { - statusCode: 500, - body: JSON.stringify( error ) - }); - } - } -}; \ No newline at end of file + } +}; diff --git a/api/replies/replyUpdate.js b/api/replies/replyUpdate.js index ae824cd..6100e89 100644 --- a/api/replies/replyUpdate.js +++ b/api/replies/replyUpdate.js @@ -1,81 +1,72 @@ 'use strict'; -var Reply = require("./_classes/Reply"); +const Reply = require('./_classes/Reply'); -var Errors = require("./../_classes/Errors"); -var ValidationError = Errors.ValidationError; -var DynamodbError = Errors.DynamodbError; +const Errors = require('./../_classes/Errors'); + +const { ValidationError } = Errors.ValidationError; +const { DynamodbError } = Errors.DynamodbError; /** * Handler for the lambda function. - * - * @param {Object} event - AWS Lambda uses this parameter to pass in event data to the handler. - * @param {Object} context - AWS Lambda uses this parameter to provide your handler the runtime information of the Lambda function that is executing. - * @param {Function} callback - Optional parameter used to pass a callback - * + * + * @param {Object} event - AWS Lambda uses this parameter to pass in event + * data to the handler. + * @param {Object} context - AWS Lambda uses this parameter to provide your handler + * the runtime information of the Lambda function that is + * executing. + * @param {Function} callback - Optional parameter used to pass a callback + * * @return JSON JSON encoded response. */ module.exports.replyUpdate = (event, context, callback) => { - - try { - - // Get the parameters passed in the body of the request - let parameters = JSON.parse( event.body ); - - // Grab the value of hash key "id" passed in the route - parameters['Id'] = event.pathParameters.id; - - // Create a new instance of the reply object passing in our parameters - let reply = new Reply( parameters ); - - reply - .validate() - .save() - .then( ( data ) => { - - const response = { - statusCode: 200, - body: JSON.stringify( data ) - } - - return callback( null, response ); - }) - .catch( function( error ) { - - callback(null, { - statusCode: 500, - body: JSON.stringify( { message: error.message } ) - }); - + try { + // Get the parameters passed in the body of the request + const parameters = JSON.parse(event.body); + + // Grab the value of hash key "id" passed in the route + parameters.Id = event.pathParameters.id; + + // Create a new instance of the reply object passing in our parameters + const reply = new Reply(parameters); + + reply + .validate() + .save() + .then((data) => { + const response = { + statusCode: 200, + body: JSON.stringify(data), + }; + + return callback(null, response); + }) + .catch((error) => { + callback(null, { + statusCode: 500, + body: JSON.stringify({ message: error.message }), }); - - } catch( error ) { - - if( error instanceof ValidationError ) { - - callback(null, { - statusCode: 422, - body: error.message - }); - } - else if( error instanceof DynamodbError ) { - - console.log('<<>>', error ); - - callback(null, { - statusCode: 500, - body: JSON.stringify( error ) - }); - - } - else { - - console.log('<<>>', error ); - - callback(null, { - statusCode: 500, - body: JSON.stringify( error ) - }); - } + }); + } catch (error) { + if (error instanceof ValidationError) { + callback(null, { + statusCode: 422, + body: error.message, + }); + } else if (error instanceof DynamodbError) { + console.log('<<>>', error); + + callback(null, { + statusCode: 500, + body: JSON.stringify(error), + }); + } else { + console.log('<<>>', error); + + callback(null, { + statusCode: 500, + body: JSON.stringify(error), + }); } -}; \ No newline at end of file + } +}; diff --git a/api/threads/_classes/Thread.js b/api/threads/_classes/Thread.js index 380696b..5dfcdff 100644 --- a/api/threads/_classes/Thread.js +++ b/api/threads/_classes/Thread.js @@ -2,83 +2,75 @@ const validator = require('validator'); -var Errors = require("./../../_classes/Errors"); -var ValidationError = Errors.ValidationError; -var Dynamic = require('./../../_classes/Dynamic'); +const Dynamic = require('./../../_classes/Dynamic'); + +const Errors = require('./../../_classes/Errors'); + +const { ValidationError } = Errors.ValidationError; /** * Thread class. Each instance maps to one document in permanent storage and extends the * Dynamic wrapper class. - * + * * @type {class} */ module.exports = class Thread extends Dynamic { - - constructor( parameters ) { - - super( parameters ); - } - - /** + /** * Return a string containing the name of the table in perment storage * for the model. * * @return {String} - Name of the string */ - static table() { - - return 'Thread'; - } + static table() { + return 'Thread'; + } - /** + /** * Return a new instance of this - * + * * @param {Object} parameters - Properties to be assigned to the newly created object - * + * * @return {Object} New instance of the Thread object */ - static model( parameters ) { - - return new Thread( parameters ); - } + static model(parameters) { + return new Thread(parameters); + } - /** + /** * Return an object that represents the properties of this class - * + * * @return {Object} - Class properties that can be saved to dynamodb */ - properties() { - - return { - 'Id': this.Id, - 'ForumId': this.ForumId, - 'UserId': this.UserId, - 'UserName': this.UserName, - 'Title': this.Title, - 'Message': this.Message, - 'CreatedDateTime': this.CreatedDateTime, - 'UpdatedDateTime': this.UpdatedDateTime - } - } + properties() { + return { + Id: this.Id, + ForumId: this.ForumId, + UserId: this.UserId, + UserName: this.UserName, + Title: this.Title, + Message: this.Message, + CreatedDateTime: this.CreatedDateTime, + UpdatedDateTime: this.UpdatedDateTime, + }; + } - /** + /** * Validate the class properties and throw an exception if necessary - * + * * @return {this} - Instance of this */ - validate() { + validate() { + const errors = []; - let errors = []; + if (typeof this.Id === 'undefined' || validator.isEmpty(this.Id)) errors.push({ Id: 'must provide a unique string for Id' }); + if (typeof this.ForumId === 'undefined' || validator.isEmpty(this.ForumId)) errors.push({ ForumId: 'must provide a value for ForumId' }); + if (typeof this.UserId === 'undefined' || validator.isEmpty(this.UserId)) errors.push({ UserId: 'must provide a value for UserId' }); + if (typeof this.UserName === 'undefined' || validator.isEmpty(this.UserName)) errors.push({ UserName: 'must provide a value for UserName' }); + if (typeof this.Title === 'undefined' || validator.isEmpty(this.Title)) errors.push({ Title: 'must provide a value for Title' }); + if (typeof this.Message === 'undefined' || validator.isEmpty(this.Message)) errors.push({ Message: 'must provide a value for Message' }); - if( typeof this.Id == 'undefined' || validator.isEmpty( this.Id ) ) errors.push({'Id': 'must provide a unique string for Id'}); - if( typeof this.ForumId == 'undefined' || validator.isEmpty( this.ForumId ) ) errors.push({'ForumId': 'must provide a value for ForumId'}); - if( typeof this.UserId == 'undefined' || validator.isEmpty( this.UserId ) ) errors.push({'UserId': 'must provide a value for UserId'}); - if( typeof this.UserName == 'undefined' || validator.isEmpty( this.UserName ) ) errors.push({'UserName': 'must provide a value for UserName'}); - if( typeof this.Title == 'undefined' || validator.isEmpty( this.Title ) ) errors.push({'Title': 'must provide a value for Title'}); - if( typeof this.Message == 'undefined' || validator.isEmpty( this.Message ) ) errors.push({'Message': 'must provide a value for Message'}); - - if( errors.length ) throw new ValidationError( JSON.stringify( errors ) ); + if (errors.length) throw new ValidationError(JSON.stringify(errors)); - return this; - } -} \ No newline at end of file + return this; + } +}; diff --git a/api/threads/_classes/ThreadQueryBuilder.js b/api/threads/_classes/ThreadQueryBuilder.js index 2525068..ceaad16 100644 --- a/api/threads/_classes/ThreadQueryBuilder.js +++ b/api/threads/_classes/ThreadQueryBuilder.js @@ -2,188 +2,166 @@ const validator = require('validator'); -var Errors = require("./../../_classes/Errors"); -var ValidationError = Errors.ValidationError; +const Errors = require('./../../_classes/Errors'); + +const ValidationError = Errors.ValidationError; /** - * Responsible for turning parameters passeed are turned in DynamoDb parameters by building + * Responsible for turning parameters passeed are turned in DynamoDb parameters by building * this.parameters object using data passed inside this.events.queryStringParameters - * + * * @type {class} */ module.exports = class ThreadQueryBuilder { + constructor(criterion) { + /** @type {Object} Key/value pairs used to build our DynamoDb parameters. */ + this._criterion = criterion; - constructor( criterion ) { - - /** @type {Object} Key/value pairs used to build our DynamoDb parameters. */ - this._criterion = criterion; - - /** + /** * Used to hold the dynamodb query parameters built using values * within property this.event - * + * * @todo : Change TableName to value of process.env.DYNAMODB_THREAD_TABLE - * + * * @type {object} */ - this._parameters = { - TableName: 'Thread' - } + this._parameters = { + TableName: 'Thread', + }; - /** + /** * Used to hold any validation errors. - * + * * @type {array} */ - this._errors = []; - } + this._errors = []; + } - /** + /** * Getter - * + * * @return {object} parameters */ - get parameters() { - - return this._parameters; - } + get parameters() { + return this._parameters; + } - /** + /** * Setter - * + * * @return {object} parameters */ - set parameters( parameters ) { + set parameters(parameters) { + this._parameters = parameters; + } - this._parameters = parameters; - } - - /** + /** * Getter - * + * * @return {array} errors */ - get errors() { - - return this._errors; - } + get errors() { + return this._errors; + } - /** + /** * Setter - * + * * @return {array} errors */ - set errors( errors ) { - - this._errors = errors; - } + set errors(errors) { + this._errors = errors; + } - /** + /** * Validates the parameters passed inside this._criterion object - * - * @return {boolean} + * + * @return {boolean} */ - validate() { - - this._errors = []; // Empty the errors array before running the validation logic - - if( this._criterion !== null && typeof this._criterion === 'object' ) { - - if( this._criterion.hasOwnProperty( 'forumid' ) == false && - this._criterion.hasOwnProperty( 'userid' ) == false - ) { - - this._errors.push( { "message": "You must provide a forumid or userid parameter" } ); - } - - if( this._criterion.hasOwnProperty( 'forumid' ) ) { - - if ( validator.isAlphanumeric( this._criterion.forumid ) == false ) { - - this._errors.push( { "message": "Your forumid parameter must be an alphanumeric string" } ); - } - } - - if( this._criterion.hasOwnProperty('userid') ) { - - if ( validator.isNumeric( this._criterion.userid ) == false ) { - - this._errors.push( { "message": "Your userid parameter must be numeric" } ); - } - } + validate() { + this._errors = []; // Empty the errors array before running the validation logic + + if (this._criterion !== null && typeof this._criterion === 'object') { + if (this._criterion.hasOwnProperty('forumid') == false && + this._criterion.hasOwnProperty('userid') == false + ) { + this._errors.push({ message: 'You must provide a forumid or userid parameter' }); + } + + if (this._criterion.hasOwnProperty('forumid')) { + if (validator.isAlphanumeric(this._criterion.forumid) == false) { + this._errors.push({ message: 'Your forumid parameter must be an alphanumeric string' }); } - else { + } - this._errors.push( { "message" : "You must supply a forumid or userid" } ); + if (this._criterion.hasOwnProperty('userid')) { + if (validator.isNumeric(this._criterion.userid) == false) { + this._errors.push({ message: 'Your userid parameter must be numeric' }); } + } + } else { + this._errors.push({ message: 'You must supply a forumid or userid' }); + } - if( this._errors.length ) { - - throw new ValidationError( JSON.stringify( this._errors ) ); - } + if (this._errors.length) { + throw new ValidationError(JSON.stringify(this._errors)); + } - return this; - } + return this; + } - /** + /** * If "forumid" has been passed inside this.event this method will build upon this.parameters object * * @return this */ - buildForumIndex() { - - if( this._criterion.hasOwnProperty('forumid') ) { - - this._parameters['IndexName'] = "ForumIndex"; - this._parameters['KeyConditionExpression'] = "ForumId = :searchstring"; - this._parameters['ExpressionAttributeValues'] = { - ":searchstring" : this._criterion.forumid - }; - } - - return this; + buildForumIndex() { + if (this._criterion.hasOwnProperty('forumid')) { + this._parameters.IndexName = 'ForumIndex'; + this._parameters.KeyConditionExpression = 'ForumId = :searchstring'; + this._parameters.ExpressionAttributeValues = { + ':searchstring': this._criterion.forumid, + }; } - /** + return this; + } + + /** * If "userid" has been passed inside this.event this method will build upon this.parameters object - * + * * @return this */ - buildUserIndex() { - - if ( this._criterion.hasOwnProperty('userid') ) { - - this._parameters['IndexName'] = "UserIndex"; - this._parameters['KeyConditionExpression'] = "UserId = :searchstring"; - this._parameters['ExpressionAttributeValues'] = { - ":searchstring" : this._criterion.userid - }; - } - - return this; + buildUserIndex() { + if (this._criterion.hasOwnProperty('userid')) { + this._parameters.IndexName = 'UserIndex'; + this._parameters.KeyConditionExpression = 'UserId = :searchstring'; + this._parameters.ExpressionAttributeValues = { + ':searchstring': this._criterion.userid, + }; } - /** + return this; + } + + /** * If pagination parameters have been passed inside this.event this method will build upon this.parameters object - * + * * @return this */ - buildPagination() { - - if ( this._criterion.hasOwnProperty('forumid') && - this._criterion.hasOwnProperty('createddatetime') ) - { - this._parameters['ExclusiveStartKey'] = { - ForumId: this._criterion.forumid, - DateTime: this._criterion.createddatetime - } - } - - if ( this._criterion.hasOwnProperty('limit') ) { - - this._parameters['Limit'] = this._criterion.limit; - } + buildPagination() { + if (this._criterion.hasOwnProperty('forumid') && + this._criterion.hasOwnProperty('createddatetime')) { + this._parameters.ExclusiveStartKey = { + ForumId: this._criterion.forumid, + DateTime: this._criterion.createddatetime, + }; + } - return this; + if (this._criterion.hasOwnProperty('limit')) { + this._parameters.Limit = this._criterion.limit; } -} \ No newline at end of file + + return this; + } +}; diff --git a/api/threads/threadCreate.js b/api/threads/threadCreate.js index f734031..c88526d 100644 --- a/api/threads/threadCreate.js +++ b/api/threads/threadCreate.js @@ -2,74 +2,65 @@ const uuidv1 = require('uuid/v1'); -var Thread = require("./_classes/Thread"); +const Thread = require('./_classes/Thread'); -var Errors = require("./../_classes/Errors"); -var ValidationError = Errors.ValidationError; -var DynamodbError = Errors.DynamodbError; +const Errors = require('./../_classes/Errors'); + +const { ValidationError } = Errors.ValidationError; +const { DynamodbError } = Errors.DynamodbError; /** * Handler for the lambda function. - * - * @param {Object} event - AWS Lambda uses this parameter to pass in event data to the handler. - * @param {Object} context - AWS Lambda uses this parameter to provide your handler the runtime information of the Lambda function that is executing. - * @param {Function} callback - Optional parameter used to pass a callback - * + * + * @param {Object} event - AWS Lambda uses this parameter to pass in event + * data to the handler. + * @param {Object} context - AWS Lambda uses this parameter to provide your + * handler the runtime information of the Lambda + * function that is executing. + * @param {Function} callback - Optional parameter used to pass a callback + * * @return JSON JSON encoded response. */ module.exports.threadCreate = (event, context, callback) => { - - try { - - let parameters = JSON.parse( event.body ); - parameters['Id']= uuidv1(); - - let thread = new Thread( parameters ); - - thread - .validate() - .save() - .then( ( data ) => { - - return callback( null, { - statusCode: 200, - body: JSON.stringify( data ) - }); - }) - .catch( function( error ) { - - callback(null, { - statusCode: 500, - body: JSON.stringify( { message: error.message } ) - }); + try { + const parameters = JSON.parse(event.body); + parameters.Id = uuidv1(); + + const thread = new Thread(parameters); + + thread + .validate() + .save() + .then(data => callback(null, { + statusCode: 200, + body: JSON.stringify(data), + })) + .catch((error) => { + callback(null, { + statusCode: 500, + body: JSON.stringify({ message: error.message }), }); - - } catch( error ) { - - if( error instanceof ValidationError ) { - - callback(null, { - statusCode: 422, - body: error.message - }); - } - else if( error instanceof DynamodbError ) { - - console.log('<<>>', error ); - - callback(null, { - statusCode: 500, - body: JSON.stringify( error ) - }); - } - else { - - console.log('<<>>', error ); - - callback(null, { - statusCode: 500, - body: JSON.stringify( error ) - }); - } + }); + } catch (error) { + if (error instanceof ValidationError) { + callback(null, { + statusCode: 422, + body: error.message, + }); + } else if (error instanceof DynamodbError) { + console.log('<<>>', error); + + callback(null, { + statusCode: 500, + body: JSON.stringify(error), + }); + } else { + console.log('<<>>', error); + + callback(null, { + statusCode: 500, + body: JSON.stringify(error), + }); } -}; \ No newline at end of file + } +}; diff --git a/api/threads/threadDelete.js b/api/threads/threadDelete.js index 7ce1141..acce4f9 100644 --- a/api/threads/threadDelete.js +++ b/api/threads/threadDelete.js @@ -1,50 +1,48 @@ 'use strict'; -var Thread = require("./_classes/Thread"); +const Thread = require('./_classes/Thread'); -var Errors = require("./../_classes/Errors"); -var DynamodbError = Errors.DynamodbError; +const Errors = require('./../_classes/Errors'); + +const { DynamodbError } = Errors.DynamodbError; /** * Handler for the lambda function. - * - * @param {Object} event - AWS Lambda uses this parameter to pass in event data to the handler. - * @param {Object} context - AWS Lambda uses this parameter to provide your handler the runtime information of the Lambda function that is executing. - * @param {Function} callback - Optional parameter used to pass a callback - * + * + * @param {Object} event - AWS Lambda uses this parameter to pass in event + * data to the handler. + * @param {Object} context - AWS Lambda uses this parameter to provide your + * handler the runtime information of the Lambda + * function that is executing. + * @param {Function} callback - Optional parameter used to pass a callback + * * @return JSON JSON encoded response. */ module.exports.threadDelete = (event, context, callback) => { - - Thread.destroy( event.pathParameters.id ) - .then( ( thread ) => { - - const response = { - statusCode: 204, - body: thread - } - - return callback( null, response ); + Thread.destroy(event.pathParameters.id) + .then((thread) => { + const response = { + statusCode: 204, + body: thread, + }; + + return callback(null, response); }) - .catch( function( error ) { - - if( error instanceof DynamodbError ) { - - console.log('<<>>', error ); - - callback(null, { - statusCode: 500, - body: JSON.stringify( error ) - }); - - } else { - - console.log('<<>>', error ); - - callback(null, { - statusCode: 500, - body: JSON.stringify( error ) - }); - } + .catch((error) => { + if (error instanceof DynamodbError) { + console.log('<<>>', error); + + callback(null, { + statusCode: 500, + body: JSON.stringify(error), + }); + } else { + console.log('<<>>', error); + + callback(null, { + statusCode: 500, + body: JSON.stringify(error), + }); + } }); -}; \ No newline at end of file +}; diff --git a/api/threads/threadGet.js b/api/threads/threadGet.js index a54f0d8..64ce223 100644 --- a/api/threads/threadGet.js +++ b/api/threads/threadGet.js @@ -1,36 +1,35 @@ 'use strict'; -var Thread = require("./_classes/Thread"); +const Thread = require('./_classes/Thread'); /** * Handler for the lambda function. - * - * @param {Object} event - AWS Lambda uses this parameter to pass in event data to the handler. - * @param {Object} context - AWS Lambda uses this parameter to provide your handler the runtime information of the Lambda function that is executing. - * @param {Function} callback - Optional parameter used to pass a callback - * + * + * @param {Object} event - AWS Lambda uses this parameter to pass in event data + * to the handler. + * @param {Object} context - AWS Lambda uses this parameter to provide your + * handler the runtime information of the Lambda + * function that is executing. + * @param {Function} callback - Optional parameter used to pass a callback + * * @return JSON JSON encoded response. */ -module.exports.threadGet = ( event, context, callback ) => { - - Thread.find( event.pathParameters.id ) - .then( ( thread ) => { - - let response = { - statusCode: Object.keys( thread ).length === 0 ? 404 : 200, - body: JSON.stringify( thread ) - } - - callback( null, response ); +module.exports.threadGet = (event, context, callback) => { + Thread.find(event.pathParameters.id) + .then((thread) => { + const response = { + statusCode: Object.keys(thread).length === 0 ? 404 : 200, + body: JSON.stringify(thread), + }; + + callback(null, response); }) - .catch( function( error ) { - - console.log('<<>>', error ); - - callback(null, { - statusCode: 500, - body: error - }); + .catch((error) => { + console.log('<<>>', error); + callback(null, { + statusCode: 500, + body: error, + }); }); -}; \ No newline at end of file +}; diff --git a/api/threads/threadList.js b/api/threads/threadList.js index 5852182..9eeca28 100644 --- a/api/threads/threadList.js +++ b/api/threads/threadList.js @@ -1,85 +1,77 @@ 'use strict'; -var Thread = require("./_classes/Thread"); +const Thread = require('./_classes/Thread'); -var ThreadQueryBuilder = require("./_classes/ThreadQueryBuilder"); +const ThreadQueryBuilder = require('./_classes/ThreadQueryBuilder'); -var Errors = require("./../_classes/Errors"); -var DynamodbError = Errors.DynamodbError; -var ValidationError = Errors.ValidationError; +const Errors = require('./../_classes/Errors'); + +const { DynamodbError } = Errors.DynamodbError; +const { ValidationError } = Errors.ValidationError; /** * Handler for the lambda function. - * - * @param {Object} event - AWS Lambda uses this parameter to pass in event data to the handler. - * @param {Object} context - AWS Lambda uses this parameter to provide your handler the runtime information of the Lambda function that is executing. - * @param {Function} callback - Optional parameter used to pass a callback - * + * + * @param {Object} event - AWS Lambda uses this parameter to pass in event + * data to the handler. + * @param {Object} context - AWS Lambda uses this parameter to provide your + * handler the runtime information of the Lambda + * function that is executing. + * @param {Function} callback - Optional parameter used to pass a callback + * * @return JSON JSON encoded response. */ module.exports.threadList = (event, context, callback) => { - - /** + /** * Instantiate an instance of QueryBuilder - * + * * @type {QueryBuilder} */ - let Query = new ThreadQueryBuilder( event.queryStringParameters ); - - try { - - Query - .validate() - .buildForumIndex() - .buildUserIndex() - .buildPagination(); - - /** @type {model} Contains a list of items and optional pagination data */ - Thread.list( Query.parameters ) - .then( ( threads ) => { - - const response = { - statusCode: threads.Items.length > 0 ? 200 : 204, - body: JSON.stringify( threads ) - }; - - callback( null, response ); - }) - .catch( function( error ) { - - callback(null, { - statusCode: 500, - body: JSON.stringify( { message: error.message } ) - }); - + const Query = new ThreadQueryBuilder(event.queryStringParameters); + + try { + Query + .validate() + .buildForumIndex() + .buildUserIndex() + .buildPagination(); + + /** @type {model} Contains a list of items and optional pagination data */ + Thread.list(Query.parameters) + .then((threads) => { + const response = { + statusCode: threads.Items.length > 0 ? 200 : 204, + body: JSON.stringify(threads), + }; + + callback(null, response); + }) + .catch((error) => { + callback(null, { + statusCode: 500, + body: JSON.stringify({ message: error.message }), }); + }); + } catch (error) { // Catch any errors thrown by the ThreadQueryBuilder class + if (error instanceof ValidationError) { + callback(null, { + statusCode: 422, + body: error.message, + }); + } else if (error instanceof DynamodbError) { + console.log('<<>>', error); + + callback(null, { + statusCode: 500, + body: JSON.stringify(error), + }); + } else { + console.log('<<>>', error); + + callback(null, { + statusCode: 500, + body: JSON.stringify(error), + }); } - catch( error ) { // Catch any errors thrown by the ThreadQueryBuilder class - - if( error instanceof ValidationError ) { - - callback(null, { - statusCode: 422, - body: error.message - }); - - } else if( error instanceof DynamodbError ) { - - console.log('<<>>', error ); - - callback(null, { - statusCode: 500, - body: JSON.stringify( error ) - }); - - } else { - - console.log('<<>>', error ); - - callback(null, { - statusCode: 500, - body: JSON.stringify( error ) - }); - } - } -}; \ No newline at end of file + } +}; diff --git a/api/threads/threadUpdate.js b/api/threads/threadUpdate.js index df6b13a..27b2afb 100644 --- a/api/threads/threadUpdate.js +++ b/api/threads/threadUpdate.js @@ -1,80 +1,72 @@ 'use strict'; -var Thread = require("./_classes/Thread"); +const Thread = require('./_classes/Thread'); -var Errors = require("./../_classes/Errors"); -var ValidationError = Errors.ValidationError; -var DynamodbError = Errors.DynamodbError; +const Errors = require('./../_classes/Errors'); + +const { ValidationError } = Errors.ValidationError; +const { DynamodbError } = Errors.DynamodbError; /** * Handler for the lambda function. - * - * @param {Object} event - AWS Lambda uses this parameter to pass in event data to the handler. - * @param {Object} context - AWS Lambda uses this parameter to provide your handler the runtime information of the Lambda function that is executing. - * @param {Function} callback - Optional parameter used to pass a callback - * + * + * @param {Object} event - AWS Lambda uses this parameter to pass in event data + * to the handler. + * @param {Object} context - AWS Lambda uses this parameter to provide your + * handler the runtime information of the Lambda + * function that is executing. + * @param {Function} callback - Optional parameter used to pass a callback + * * @return JSON JSON encoded response. */ module.exports.threadUpdate = (event, context, callback) => { - - try { - - // Get the parameters passed in the body of the request - let parameters = JSON.parse( event.body ); - - // Grab the value of hash key "id" passed in the route - parameters['Id'] = event.pathParameters.id; - - // Create a new instance of the thread object passing in our parameters - let thread = new Thread( parameters ); - - thread - .validate() - .save() - .then( ( data ) => { - - const response = { - statusCode: 200, - body: JSON.stringify( data ) - } - - return callback( null, response ); - }) - .catch( function( error ) { - - callback(null, { - statusCode: 500, - body: JSON.stringify( { message: error.message } ) - }); + try { + // Get the parameters passed in the body of the request + const parameters = JSON.parse(event.body); + + // Grab the value of hash key "id" passed in the route + parameters.Id = event.pathParameters.id; + + // Create a new instance of the thread object passing in our parameters + const thread = new Thread(parameters); + + thread + .validate() + .save() + .then((data) => { + const response = { + statusCode: 200, + body: JSON.stringify(data), + }; + + return callback(null, response); + }) + .catch((error) => { + callback(null, { + statusCode: 500, + body: JSON.stringify({ message: error.message }), }); - - } catch( error ) { - - if( error instanceof ValidationError ) { - - callback(null, { - statusCode: 422, - body: error.message - }); - } - else if( error instanceof DynamodbError ) { - - console.log('<<>>', error ); - - callback(null, { - statusCode: 500, - body: JSON.stringify( error ) - }); - - } - else { - - console.log('<<>>', error ); - - callback(null, { - statusCode: 500, - body: JSON.stringify( error ) - }); - } + }); + } catch (error) { + if (error instanceof ValidationError) { + callback(null, { + statusCode: 422, + body: error.message, + }); + } else if (error instanceof DynamodbError) { + console.log('<<>>', error); + + callback(null, { + statusCode: 500, + body: JSON.stringify(error), + }); + } else { + console.log('<<>>', error); + + callback(null, { + statusCode: 500, + body: JSON.stringify(error), + }); } -}; \ No newline at end of file + } +}; From 0cec07d38a3dd2318f4bc58ee5fc0245e4430cdf Mon Sep 17 00:00:00 2001 From: jacksoncharles Date: Sun, 31 Dec 2017 15:00:15 +0000 Subject: [PATCH 73/78] linter changes --- api/replies/_classes/ReplyQueryBuilder.js | 163 +++++++++++---------- api/threads/_classes/ThreadQueryBuilder.js | 140 +++++++++--------- 2 files changed, 155 insertions(+), 148 deletions(-) diff --git a/api/replies/_classes/ReplyQueryBuilder.js b/api/replies/_classes/ReplyQueryBuilder.js index 7a3b0a5..d07e291 100644 --- a/api/replies/_classes/ReplyQueryBuilder.js +++ b/api/replies/_classes/ReplyQueryBuilder.js @@ -4,7 +4,7 @@ const validator = require('validator'); const Errors = require('./../../_classes/Errors'); -const ValidationError = Errors.ValidationError; +const { ValidationError } = Errors.ValidationError; /** * Responsible for turning parameters passeed are turned in DynamoDb parameters by building @@ -15,113 +15,113 @@ const ValidationError = Errors.ValidationError; module.exports = class ReplyQueryBuilder { constructor(criterion) { /** @type {Object} Key/value pairs used to build our DynamoDb parameters. */ - this._criterion = criterion; + this.criterion = criterion; /** - * Used to hold the dynamodb query parameters built using values - * within property this.event - * - * @todo : Change TableName to value of process.env.DYNAMODB_REPLY_TABLE - * - * @type {object} - */ - this._parameters = { + * Used to hold the dynamodb query parameters built using values + * within property this.event + * + * @todo : Change TableName to value of process.env.DYNAMODB_REPLY_TABLE + * + * @type {object} + */ + this.parameters = { TableName: 'Reply', }; /** - * Used to hold any validation errors. - * - * @type {array} - */ - this._errors = []; + * Used to hold any validation errors. + * + * @type {array} + */ + this.errors = []; } /** - * Getter - * - * @return {object} parameters - */ + * Getter + * + * @return {object} parameters + */ get parameters() { - return this._parameters; + return this.parameters; } /** - * Setter - * - * @return {object} parameters - */ + * Setter + * + * @return {object} parameters + */ set parameters(parameters) { - this._parameters = parameters; + this.parameters = parameters; } /** - * Getter - * - * @return {array} errors - */ + * Getter + * + * @return {array} errors + */ get errors() { - return this._errors; + return this.errors; } /** - * Setter - * - * @return {array} errors - */ + * Setter + * + * @return {array} errors + */ set errors(errors) { - this._errors = errors; + this.errors = errors; } /** - * Validates the parameters passed inside this._criterion object - * - * @return {boolean} - */ + * Validates the parameters passed inside this.criterion object + * + * @return {boolean} + */ validate() { - this._errors = []; // Reset the errors array before running the validation logic - - if (this._criterion !== null && typeof this._criterion === 'object') { - if (this._criterion.hasOwnProperty('threadid') == false && - this._criterion.hasOwnProperty('userid') == false + this.errors = []; // Reset the errors array before running the validation logic + if (this.criterion !== null && typeof this.criterion === 'object') { + if (Object.prototype.hasOwnProperty.call(this.criterion, 'threadid') === false && + Object.prototype.hasOwnProperty.call(this.criterion, 'userid') === false ) { - this._errors.push({ message: 'You must provide a threadid or userid parameter' }); + this.errors.push({ message: 'You must provide a threadid or userid parameter' }); } - if (this._criterion.hasOwnProperty('threadid')) { - if (validator.isAlphanumeric(this._criterion.threadid) == false) { - this._errors.push({ message: 'Your threadid parameter must be an alphanumeric string' }); + if (Object.prototype.hasOwnProperty.call(this.criterion, 'threadid')) { + if (validator.isAlphanumeric(this.criterion.threadid) === false) { + this.errors.push({ message: 'Your threadid parameter must be an alphanumeric string' }); } } - if (this._criterion.hasOwnProperty('userid')) { - if (validator.isNumeric(this._criterion.userid) == false) { - this._errors.push({ message: 'Your userid parameter must be numeric' }); + if (Object.prototype.hasOwnProperty.call(this.criterion, 'userid')) { + if (validator.isNumeric(this.criterion.userid) === false) { + this.errors.push({ message: 'Your userid parameter must be numeric' }); } } } else { - this._errors.push({ message: 'You must supply a threadid or userid' }); + this.errors.push({ message: 'You must supply a threadid or userid' }); } - if (this._errors.length) { - throw new ValidationError(JSON.stringify(this._errors)); + if (this.errors.length) { + throw new ValidationError(JSON.stringify(this.errors)); } return this; } /** - * If "threadid" has been passed inside this.event this method will build upon this.parameters object + * If "threadid" has been passed inside this.event this method will build + * upon this.parameters object * * @return this */ buildThreadIndex() { - if (this._criterion.hasOwnProperty('threadid')) { - this._parameters.IndexName = 'ThreadIndex'; - this._parameters.KeyConditionExpression = 'ThreadId = :searchstring'; - this._parameters.ExpressionAttributeValues = { - ':searchstring': this._criterion.threadid, + if (Object.prototype.hasOwnProperty.call(this.criterion, 'threadid')) { + this.parameters.IndexName = 'ThreadIndex'; + this.parameters.KeyConditionExpression = 'ThreadId = :searchstring'; + this.parameters.ExpressionAttributeValues = { + ':searchstring': this.criterion.threadid, }; } @@ -129,16 +129,17 @@ module.exports = class ReplyQueryBuilder { } /** - * If "userid" has been passed inside this.event this method will build upon this.parameters object - * - * @return this - */ + * If "userid" has been passed inside this.event this method will build upon + * this.parameters object. + * + * @return this + */ buildUserIndex() { - if (this._criterion.hasOwnProperty('userid')) { - this._parameters.IndexName = 'UserIndex'; - this._parameters.KeyConditionExpression = 'UserId = :searchstring'; - this._parameters.ExpressionAttributeValues = { - ':searchstring': this._criterion.userid, + if (Object.prototype.hasOwnProperty.call(this.criterion, 'userid')) { + this.parameters.IndexName = 'UserIndex'; + this.parameters.KeyConditionExpression = 'UserId = :searchstring'; + this.parameters.ExpressionAttributeValues = { + ':searchstring': this.criterion.userid, }; } @@ -146,21 +147,23 @@ module.exports = class ReplyQueryBuilder { } /** - * If pagination parameters have been passed inside this.event this method will build upon this.parameters object - * - * @return this - */ + * If pagination parameters have been passed inside this.event this + * method will build upon this.parameters object. + * + * @return this + */ buildPagination() { - if (this._criterion.hasOwnProperty('threadid') && - this._criterion.hasOwnProperty('createddatetime')) { - this._parameters.ExclusiveStartKey = { - ThreadId: this._criterion.threadid, - DateTime: this._criterion.createddatetime, + if (Object.prototype.hasOwnProperty.call(this.criterion, 'threadid') && + Object.prototype.hasOwnProperty.call(this.criterion, 'createddatetime') + ) { + this.parameters.ExclusiveStartKey = { + ThreadId: this.criterion.threadid, + DateTime: this.criterion.createddatetime, }; } - if (this._criterion.hasOwnProperty('limit')) { - this._parameters.Limit = this._criterion.limit; + if (Object.prototype.hasOwnProperty.call(this.criterion, 'limit')) { + this.parameters.Limit = this.criterion.limit; } return this; diff --git a/api/threads/_classes/ThreadQueryBuilder.js b/api/threads/_classes/ThreadQueryBuilder.js index ceaad16..ca3050c 100644 --- a/api/threads/_classes/ThreadQueryBuilder.js +++ b/api/threads/_classes/ThreadQueryBuilder.js @@ -4,7 +4,7 @@ const validator = require('validator'); const Errors = require('./../../_classes/Errors'); -const ValidationError = Errors.ValidationError; +const { ValidationError } = Errors.ValidationError; /** * Responsible for turning parameters passeed are turned in DynamoDb parameters by building @@ -15,7 +15,7 @@ const ValidationError = Errors.ValidationError; module.exports = class ThreadQueryBuilder { constructor(criterion) { /** @type {Object} Key/value pairs used to build our DynamoDb parameters. */ - this._criterion = criterion; + this.criterion = criterion; /** * Used to hold the dynamodb query parameters built using values @@ -25,7 +25,7 @@ module.exports = class ThreadQueryBuilder { * * @type {object} */ - this._parameters = { + this.parameters = { TableName: 'Thread', }; @@ -34,93 +34,94 @@ module.exports = class ThreadQueryBuilder { * * @type {array} */ - this._errors = []; + this.errors = []; } /** - * Getter - * - * @return {object} parameters - */ + * Getter + * + * @return {object} parameters + */ get parameters() { - return this._parameters; + return this.parameters; } /** - * Setter - * - * @return {object} parameters - */ + * Setter + * + * @return {object} parameters + */ set parameters(parameters) { - this._parameters = parameters; + this.parameters = parameters; } /** - * Getter - * - * @return {array} errors - */ + * Getter + * + * @return {array} errors + */ get errors() { - return this._errors; + return this.errors; } /** - * Setter - * - * @return {array} errors - */ + * Setter + * + * @return {array} errors + */ set errors(errors) { - this._errors = errors; + this.errors = errors; } /** - * Validates the parameters passed inside this._criterion object - * - * @return {boolean} - */ + * Validates the parameters passed inside this.criterion object + * + * @return {boolean} + */ validate() { - this._errors = []; // Empty the errors array before running the validation logic + this.errors = []; // Empty the errors array before running the validation logic - if (this._criterion !== null && typeof this._criterion === 'object') { - if (this._criterion.hasOwnProperty('forumid') == false && - this._criterion.hasOwnProperty('userid') == false + if (this.criterion !== null && typeof this.criterion === 'object') { + if (Object.prototype.hasOwnProperty.call(this.criterion, 'forumid') === false && + Object.prototype.hasOwnProperty.call(this.criterion, 'userid') === false ) { - this._errors.push({ message: 'You must provide a forumid or userid parameter' }); + this.errors.push({ message: 'You must provide a forumid or userid parameter' }); } - if (this._criterion.hasOwnProperty('forumid')) { - if (validator.isAlphanumeric(this._criterion.forumid) == false) { - this._errors.push({ message: 'Your forumid parameter must be an alphanumeric string' }); + if (Object.prototype.hasOwnProperty.call(this.criterion, 'forumid')) { + if (validator.isAlphanumeric(this.criterion.forumid) === false) { + this.errors.push({ message: 'Your forumid parameter must be an alphanumeric string' }); } } - if (this._criterion.hasOwnProperty('userid')) { - if (validator.isNumeric(this._criterion.userid) == false) { - this._errors.push({ message: 'Your userid parameter must be numeric' }); + if (Object.prototype.hasOwnProperty.call(this.criterion, 'userid')) { + if (validator.isNumeric(this.criterion.userid) === false) { + this.errors.push({ message: 'Your userid parameter must be numeric' }); } } } else { - this._errors.push({ message: 'You must supply a forumid or userid' }); + this.errors.push({ message: 'You must supply a forumid or userid' }); } - if (this._errors.length) { - throw new ValidationError(JSON.stringify(this._errors)); + if (this.errors.length) { + throw new ValidationError(JSON.stringify(this.errors)); } return this; } /** - * If "forumid" has been passed inside this.event this method will build upon this.parameters object + * If "forumid" has been passed inside this.event this method will build upon + * this.parameters object. * * @return this */ buildForumIndex() { - if (this._criterion.hasOwnProperty('forumid')) { - this._parameters.IndexName = 'ForumIndex'; - this._parameters.KeyConditionExpression = 'ForumId = :searchstring'; - this._parameters.ExpressionAttributeValues = { - ':searchstring': this._criterion.forumid, + if (Object.prototype.hasOwnProperty.call(this.criterion, 'forumid')) { + this.parameters.IndexName = 'ForumIndex'; + this.parameters.KeyConditionExpression = 'ForumId = :searchstring'; + this.parameters.ExpressionAttributeValues = { + ':searchstring': this.criterion.forumid, }; } @@ -128,16 +129,17 @@ module.exports = class ThreadQueryBuilder { } /** - * If "userid" has been passed inside this.event this method will build upon this.parameters object - * - * @return this - */ + * If "userid" has been passed inside this.event this method will build upon + * this.parameters object. + * + * @return this + */ buildUserIndex() { - if (this._criterion.hasOwnProperty('userid')) { - this._parameters.IndexName = 'UserIndex'; - this._parameters.KeyConditionExpression = 'UserId = :searchstring'; - this._parameters.ExpressionAttributeValues = { - ':searchstring': this._criterion.userid, + if (Object.prototype.hasOwnProperty.call(this.criterion, 'userid')) { + this.parameters.IndexName = 'UserIndex'; + this.parameters.KeyConditionExpression = 'UserId = :searchstring'; + this.parameters.ExpressionAttributeValues = { + ':searchstring': this.criterion.userid, }; } @@ -145,21 +147,23 @@ module.exports = class ThreadQueryBuilder { } /** - * If pagination parameters have been passed inside this.event this method will build upon this.parameters object - * - * @return this - */ + * If pagination parameters have been passed inside this.event this method + * will build upon this.parameters object. + * + * @return this + */ buildPagination() { - if (this._criterion.hasOwnProperty('forumid') && - this._criterion.hasOwnProperty('createddatetime')) { - this._parameters.ExclusiveStartKey = { - ForumId: this._criterion.forumid, - DateTime: this._criterion.createddatetime, + if (Object.prototype.hasOwnProperty.call(this.criterion, 'forumid') && + Object.prototype.hasOwnProperty.call(this.criterion, 'createddatetime') + ) { + this.parameters.ExclusiveStartKey = { + ForumId: this.criterion.forumid, + DateTime: this.criterion.createddatetime, }; } - if (this._criterion.hasOwnProperty('limit')) { - this._parameters.Limit = this._criterion.limit; + if (Object.prototype.hasOwnProperty.call(this.criterion, 'limit')) { + this.parameters.Limit = this.criterion.limit; } return this; From 9c2990a36b49b7108ee3c04973ab3c25fec72ce0 Mon Sep 17 00:00:00 2001 From: jacksoncharles Date: Mon, 1 Jan 2018 20:20:39 +0000 Subject: [PATCH 74/78] remove destructuring syntax --- api/replies/_classes/Reply.js | 2 +- api/replies/_classes/ReplyQueryBuilder.js | 20 ++++++++++---------- api/replies/replyCreate.js | 4 ++-- api/replies/replyDelete.js | 2 +- api/replies/replyList.js | 6 +++--- api/replies/replyUpdate.js | 4 ++-- api/threads/_classes/Thread.js | 2 +- api/threads/_classes/ThreadQueryBuilder.js | 22 +++++++++++----------- api/threads/threadCreate.js | 4 ++-- api/threads/threadDelete.js | 2 +- api/threads/threadList.js | 4 ++-- api/threads/threadUpdate.js | 5 +++-- 12 files changed, 39 insertions(+), 38 deletions(-) diff --git a/api/replies/_classes/Reply.js b/api/replies/_classes/Reply.js index 3a313a4..5c06dcb 100644 --- a/api/replies/_classes/Reply.js +++ b/api/replies/_classes/Reply.js @@ -6,7 +6,7 @@ const Dynamic = require('./../../_classes/Dynamic'); const Errors = require('./../../_classes/Errors'); -const { ValidationError } = Errors.ValidationError; +const ValidationError = Errors.ValidationError; /** diff --git a/api/replies/_classes/ReplyQueryBuilder.js b/api/replies/_classes/ReplyQueryBuilder.js index d07e291..f0e0708 100644 --- a/api/replies/_classes/ReplyQueryBuilder.js +++ b/api/replies/_classes/ReplyQueryBuilder.js @@ -4,7 +4,7 @@ const validator = require('validator'); const Errors = require('./../../_classes/Errors'); -const { ValidationError } = Errors.ValidationError; +const ValidationError = Errors.ValidationError; /** * Responsible for turning parameters passeed are turned in DynamoDb parameters by building @@ -25,7 +25,7 @@ module.exports = class ReplyQueryBuilder { * * @type {object} */ - this.parameters = { + this.params = { TableName: 'Reply', }; @@ -34,7 +34,7 @@ module.exports = class ReplyQueryBuilder { * * @type {array} */ - this.errors = []; + this.failures = []; } /** @@ -43,7 +43,7 @@ module.exports = class ReplyQueryBuilder { * @return {object} parameters */ get parameters() { - return this.parameters; + return this.params; } /** @@ -51,8 +51,8 @@ module.exports = class ReplyQueryBuilder { * * @return {object} parameters */ - set parameters(parameters) { - this.parameters = parameters; + set parameters(params) { + this.params = params; } /** @@ -61,16 +61,16 @@ module.exports = class ReplyQueryBuilder { * @return {array} errors */ get errors() { - return this.errors; + return this.failures; } /** * Setter * - * @return {array} errors + * @return {array} failures */ - set errors(errors) { - this.errors = errors; + set errors(failures) { + this.failures = failures; } /** diff --git a/api/replies/replyCreate.js b/api/replies/replyCreate.js index d147fb6..5d3f24c 100644 --- a/api/replies/replyCreate.js +++ b/api/replies/replyCreate.js @@ -6,8 +6,8 @@ const Reply = require('./_classes/Reply'); const Errors = require('./../_classes/Errors'); -const { ValidationError } = Errors.ValidationError; -const { DynamodbError } = Errors.DynamodbError; +const ValidationError = Errors.ValidationError; +const DynamodbError = Errors.DynamodbError; /** * Handler for the lambda function. diff --git a/api/replies/replyDelete.js b/api/replies/replyDelete.js index 67ade63..f29ae63 100644 --- a/api/replies/replyDelete.js +++ b/api/replies/replyDelete.js @@ -4,7 +4,7 @@ const Reply = require('./_classes/Reply'); const Errors = require('./../_classes/Errors'); -const { DynamodbError } = Errors.DynamodbError; +const DynamodbError = Errors.DynamodbError; /** * Handler for the lambda function. diff --git a/api/replies/replyList.js b/api/replies/replyList.js index 4eb4408..1d87a5f 100644 --- a/api/replies/replyList.js +++ b/api/replies/replyList.js @@ -5,9 +5,8 @@ const Reply = require('./_classes/Reply'); const ReplyQueryBuilder = require('./_classes/ReplyQueryBuilder'); const Errors = require('./../_classes/Errors'); - -const { DynamodbError } = Errors.DynamodbError; -const { ValidationError } = Errors.ValidationError; +const DynamodbError = Errors.DynamodbError; +const ValidationError = Errors.ValidationError; /** * Handler for the lambda function. @@ -53,6 +52,7 @@ module.exports.replyList = (event, context, callback) => { }); }); } catch (error) { // Catch any errors thrown by the ReplyQueryBuilder class + console.log('<<>>', ValidationError); if (error instanceof ValidationError) { callback(null, { statusCode: 422, diff --git a/api/replies/replyUpdate.js b/api/replies/replyUpdate.js index 6100e89..41aff55 100644 --- a/api/replies/replyUpdate.js +++ b/api/replies/replyUpdate.js @@ -4,8 +4,8 @@ const Reply = require('./_classes/Reply'); const Errors = require('./../_classes/Errors'); -const { ValidationError } = Errors.ValidationError; -const { DynamodbError } = Errors.DynamodbError; +const ValidationError = Errors.ValidationError; +const DynamodbError = Errors.DynamodbError; /** * Handler for the lambda function. diff --git a/api/threads/_classes/Thread.js b/api/threads/_classes/Thread.js index 5dfcdff..8135f72 100644 --- a/api/threads/_classes/Thread.js +++ b/api/threads/_classes/Thread.js @@ -6,7 +6,7 @@ const Dynamic = require('./../../_classes/Dynamic'); const Errors = require('./../../_classes/Errors'); -const { ValidationError } = Errors.ValidationError; +const ValidationError = Errors.ValidationError; /** * Thread class. Each instance maps to one document in permanent storage and extends the diff --git a/api/threads/_classes/ThreadQueryBuilder.js b/api/threads/_classes/ThreadQueryBuilder.js index ca3050c..d97626f 100644 --- a/api/threads/_classes/ThreadQueryBuilder.js +++ b/api/threads/_classes/ThreadQueryBuilder.js @@ -4,7 +4,7 @@ const validator = require('validator'); const Errors = require('./../../_classes/Errors'); -const { ValidationError } = Errors.ValidationError; +const ValidationError = Errors.ValidationError; /** * Responsible for turning parameters passeed are turned in DynamoDb parameters by building @@ -25,7 +25,7 @@ module.exports = class ThreadQueryBuilder { * * @type {object} */ - this.parameters = { + this.params = { TableName: 'Thread', }; @@ -34,7 +34,7 @@ module.exports = class ThreadQueryBuilder { * * @type {array} */ - this.errors = []; + this.failures = []; } /** @@ -43,7 +43,7 @@ module.exports = class ThreadQueryBuilder { * @return {object} parameters */ get parameters() { - return this.parameters; + return this.params; } /** @@ -51,26 +51,26 @@ module.exports = class ThreadQueryBuilder { * * @return {object} parameters */ - set parameters(parameters) { - this.parameters = parameters; + set parameters(params) { + this.params = params; } /** * Getter * - * @return {array} errors + * @return {array} failures */ get errors() { - return this.errors; + return this.failures; } /** * Setter * - * @return {array} errors + * @return {array} failures */ - set errors(errors) { - this.errors = errors; + set errors(failures) { + this.failures = failures; } /** diff --git a/api/threads/threadCreate.js b/api/threads/threadCreate.js index c88526d..02c39dc 100644 --- a/api/threads/threadCreate.js +++ b/api/threads/threadCreate.js @@ -6,8 +6,8 @@ const Thread = require('./_classes/Thread'); const Errors = require('./../_classes/Errors'); -const { ValidationError } = Errors.ValidationError; -const { DynamodbError } = Errors.DynamodbError; +const ValidationError = Errors.ValidationError; +const DynamodbError = Errors.DynamodbError; /** * Handler for the lambda function. diff --git a/api/threads/threadDelete.js b/api/threads/threadDelete.js index acce4f9..aab9e64 100644 --- a/api/threads/threadDelete.js +++ b/api/threads/threadDelete.js @@ -4,7 +4,7 @@ const Thread = require('./_classes/Thread'); const Errors = require('./../_classes/Errors'); -const { DynamodbError } = Errors.DynamodbError; +const DynamodbError = Errors.DynamodbError; /** * Handler for the lambda function. diff --git a/api/threads/threadList.js b/api/threads/threadList.js index 9eeca28..1007706 100644 --- a/api/threads/threadList.js +++ b/api/threads/threadList.js @@ -6,8 +6,8 @@ const ThreadQueryBuilder = require('./_classes/ThreadQueryBuilder'); const Errors = require('./../_classes/Errors'); -const { DynamodbError } = Errors.DynamodbError; -const { ValidationError } = Errors.ValidationError; +const DynamodbError = Errors.DynamodbError; +const ValidationError = Errors.ValidationError; /** * Handler for the lambda function. diff --git a/api/threads/threadUpdate.js b/api/threads/threadUpdate.js index 27b2afb..6313756 100644 --- a/api/threads/threadUpdate.js +++ b/api/threads/threadUpdate.js @@ -4,8 +4,8 @@ const Thread = require('./_classes/Thread'); const Errors = require('./../_classes/Errors'); -const { ValidationError } = Errors.ValidationError; -const { DynamodbError } = Errors.DynamodbError; +const ValidationError = Errors.ValidationError; +const DynamodbError = Errors.DynamodbError; /** * Handler for the lambda function. @@ -48,6 +48,7 @@ module.exports.threadUpdate = (event, context, callback) => { }); }); } catch (error) { + if (error instanceof ValidationError) { callback(null, { statusCode: 422, From 76355dcfe0ec7bda3eba26eb5ddd161bce64fb50 Mon Sep 17 00:00:00 2001 From: jacksoncharles Date: Mon, 1 Jan 2018 20:41:19 +0000 Subject: [PATCH 75/78] remove consoole.log and linter changes --- .eslintrc.js | 3 ++- api/replies/replyList.js | 2 +- api/threads/threadUpdate.js | 1 - data/Reply.json | 18 +++++++++--------- data/Thread.json | 12 ++++++------ 5 files changed, 18 insertions(+), 18 deletions(-) diff --git a/.eslintrc.js b/.eslintrc.js index c6e12ce..4249f36 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -6,6 +6,7 @@ module.exports = { "rules": { "strict": "off", "no-console": "off", - "import/no-unresolved": "off" + "import/no-unresolved": "off", + "prefer-destructuring": "off" } } \ No newline at end of file diff --git a/api/replies/replyList.js b/api/replies/replyList.js index 1d87a5f..68fc67e 100644 --- a/api/replies/replyList.js +++ b/api/replies/replyList.js @@ -5,6 +5,7 @@ const Reply = require('./_classes/Reply'); const ReplyQueryBuilder = require('./_classes/ReplyQueryBuilder'); const Errors = require('./../_classes/Errors'); + const DynamodbError = Errors.DynamodbError; const ValidationError = Errors.ValidationError; @@ -52,7 +53,6 @@ module.exports.replyList = (event, context, callback) => { }); }); } catch (error) { // Catch any errors thrown by the ReplyQueryBuilder class - console.log('<<>>', ValidationError); if (error instanceof ValidationError) { callback(null, { statusCode: 422, diff --git a/api/threads/threadUpdate.js b/api/threads/threadUpdate.js index 6313756..3719832 100644 --- a/api/threads/threadUpdate.js +++ b/api/threads/threadUpdate.js @@ -48,7 +48,6 @@ module.exports.threadUpdate = (event, context, callback) => { }); }); } catch (error) { - if (error instanceof ValidationError) { callback(null, { statusCode: 422, diff --git a/data/Reply.json b/data/Reply.json index 4dc3ac1..346a5d4 100644 --- a/data/Reply.json +++ b/data/Reply.json @@ -22,7 +22,7 @@ "S": "2015-09-15T19:58:22.947Z" }, "UserName": { - "S": "primordial" + "S": "UserNameOne" } } } @@ -49,7 +49,7 @@ "S": "2015-08-15T19:58:22.947Z" }, "UserName": { - "S": "bemed" + "S": "UserNameTwo" } } } @@ -76,7 +76,7 @@ "S": "2015-07-15T19:58:22.947Z" }, "UserName": { - "S": "fatlouis" + "S": "UserNameThree" } } } @@ -103,7 +103,7 @@ "S": "2015-09-15T19:51:22.947Z" }, "UserName": { - "S": "mickeymouse" + "S": "UserNameFour" } } } @@ -130,7 +130,7 @@ "S": "2015-08-15T19:53:22.947Z" }, "UserName": { - "S": "primordial" + "S": "UserNameOne" } } } @@ -157,7 +157,7 @@ "S": "2015-07-15T19:52:22.947Z" }, "UserName": { - "S": "bemed" + "S": "UserNameTwo" } } } @@ -184,7 +184,7 @@ "S": "2015-09-15T19:54:22.947Z" }, "UserName": { - "S": "queenie" + "S": "UserNameFive" } } } @@ -211,7 +211,7 @@ "S": "2015-08-15T19:55:22.947Z" }, "UserName": { - "S": "queenie" + "S": "UserNameFive" } } } @@ -238,7 +238,7 @@ "S": "2015-07-15T19:56:22.947Z" }, "UserName": { - "S": "kingkong" + "S": "UserNameSix" } } } diff --git a/data/Thread.json b/data/Thread.json index 48d7671..9561dff 100644 --- a/data/Thread.json +++ b/data/Thread.json @@ -25,7 +25,7 @@ "S": "2015-01-15T19:58:23.947Z" }, "UserName": { - "S": "primordial" + "S": "UserNameOne" } } } @@ -55,7 +55,7 @@ "S": "2015-02-15T19:58:24.947Z" }, "UserName": { - "S": "bemed" + "S": "UserNameTwo" } } } @@ -85,7 +85,7 @@ "S": "2015-03-15T19:58:32.947Z" }, "UserName": { - "S": "fatlouis" + "S": "UserNameThree" } } } @@ -115,7 +115,7 @@ "S": "2015-04-15T19:58:42.947Z" }, "UserName": { - "S": "mickeymouse" + "S": "UserNameFour" } } } @@ -145,7 +145,7 @@ "S": "2015-04-15T19:58:52.947Z" }, "UserName": { - "S": "queenie" + "S": "UserNameFive" } } } @@ -175,7 +175,7 @@ "S": "2015-04-15T19:58:53.947Z" }, "UserName": { - "S": "kingkong" + "S": "UserNameSix" } } } From 06a61691ac9b91fa719f8c3c586592e654c3d3d3 Mon Sep 17 00:00:00 2001 From: jacksoncharles Date: Tue, 2 Jan 2018 22:07:47 +0000 Subject: [PATCH 76/78] use default promise library --- api/_classes/Dynamic.js | 124 +++++++++++++++--------------------- api/replies/replyCreate.js | 7 -- api/replies/replyDelete.js | 19 ++---- api/replies/replyList.js | 7 -- api/replies/replyUpdate.js | 7 -- api/threads/threadCreate.js | 9 +-- api/threads/threadDelete.js | 9 --- api/threads/threadList.js | 7 -- api/threads/threadUpdate.js | 7 -- 9 files changed, 58 insertions(+), 138 deletions(-) diff --git a/api/_classes/Dynamic.js b/api/_classes/Dynamic.js index ebebd2c..abd0850 100644 --- a/api/_classes/Dynamic.js +++ b/api/_classes/Dynamic.js @@ -1,6 +1,10 @@ 'use strict'; const AWS = require('aws-sdk'); + +/** Set the promise library to the default global */ +AWS.config.setPromisesDependency(null); + const dynamodb = new AWS.DynamoDB.DocumentClient(); var Errors = require("./Errors"); @@ -30,34 +34,26 @@ module.exports = class Dynamic { var self = this; - return new Promise( function( resolve, reject ) { - - /** @type {Object} Holds the parameters for the get request */ - const parameters = { + /** @type {Object} Holds the parameters for the get request */ + const parameters = { - TableName : self.constructor.table(), - Item : self.properties() - } + TableName : self.constructor.table(), + Item : self.properties() + } - // Save to permanent storage - return dynamodb.put( parameters, function( error, data ) { + // Save to permanent storage + return dynamodb.put( parameters ).promise().then( - // Handle DynamoDb errors - if( error ) return reject( error ); + function(data) { /** @type {Object} Create a new instance of self and populate with the data */ - let modelInstance = self.constructor.model( parameters.Item ); + return self.constructor.model( parameters.Item ); + }, + function(error) { - /** All successful. Create a valid response */ - return resolve( modelInstance ); - }); - }) - .catch( function( error ) { - - console.log('<<>>', error ); - - throw new DynamodbError( error ); - }); + console.log('<<>>', error); + } + ); } /** @@ -78,29 +74,22 @@ module.exports = class Dynamic { } } - return new Promise( function( resolve, reject ) { + /** Run a dynamodb get request passing-in our parameters */ + return dynamodb.get( parameters ).promise().then( - /** Run a dynamodb get request passing-in our parameters */ - return dynamodb.get( parameters, function( error, data ) { - - /** Handle potential dynamodb errors */ - if ( error ) return reject( error ); + // Successful response will be automatically resolve + // the promise using the 'complete' event + function(data) { /** @type {Object} Create a new instance of self and populate with the data */ - let modelInstance = self.model( data.Item ); - - /** All successful. Create a valid response */ - return resolve( modelInstance ); - }); - - }) - .catch( function( error ) { // Capture a dynamodb rejection + return self.model( data.Item ); + }, + function(error) { - console.log('<<>>', error ); - - throw new DynamodbError( error ); - }); - } + console.log('<<>>', error); + } + ); + } /** * Retrieve an array of replies according to the parameters passed @@ -111,13 +100,12 @@ module.exports = class Dynamic { var self = this; - return new Promise( function( resolve, reject ) { + /** Run a dynamodb query passing-in Query.parameters */ + return dynamodb.query( parameters ).promise().then( - /** Run a dynamodb query passing-in Query.parameters */ - return dynamodb.query( parameters, function( error, data ) { - - /** Handle potential dynamodb errors */ - if ( error ) return reject( error ); + // Successful response will be automatically resolve + // the promise using the 'complete' event + function(data) { let items = []; @@ -129,16 +117,13 @@ module.exports = class Dynamic { data['Items'] = items; /** All successful. Create a valid response */ - return resolve( data ); - }); - - }) - .catch( function( error ) { + return data; + }, + function(error) { - console.log('<<>>', error ); - - throw new DynamodbError( error ); - }); + console.log('<<>>', error); + } + ); } /** @@ -159,24 +144,19 @@ module.exports = class Dynamic { } } - return new Promise( function( resolve, reject ) { - - /** Run a dynamodb get request passing-in our parameters */ - return dynamodb.delete( parameters, function( error, data ) { + /** Run a dynamodb get request passing-in our parameters */ + return dynamodb.delete( parameters ).promise().then( - /** Handle potential dynamodb errors */ - if ( error ) return reject( error ); - - /** All successful. Create a valid response */ - return resolve( JSON.stringify( data ) ); - }); + // Successful response will be automatically resolve + // the promise using the 'complete' event + function(data) { - }) - .catch( function( error ) { + return JSON.stringify( data ); + }, + function(error) { - console.log('<<>>', error ); - - throw new DynamodbError( error ); - }); + console.log('<<>>', error); + } + ); } } \ No newline at end of file diff --git a/api/replies/replyCreate.js b/api/replies/replyCreate.js index 5d3f24c..5a1d97d 100644 --- a/api/replies/replyCreate.js +++ b/api/replies/replyCreate.js @@ -53,13 +53,6 @@ module.exports.replyCreate = (event, context, callback) => { statusCode: 422, body: error.message, }); - } else if (error instanceof DynamodbError) { - console.log('<<>>', error); - - callback(null, { - statusCode: 500, - body: JSON.stringify(error), - }); } else { console.log('<<>>', error); diff --git a/api/replies/replyDelete.js b/api/replies/replyDelete.js index f29ae63..88b36c9 100644 --- a/api/replies/replyDelete.js +++ b/api/replies/replyDelete.js @@ -29,20 +29,11 @@ module.exports.replyDelete = (event, context, callback) => { return callback(null, response); }) .catch((error) => { - if (error instanceof DynamodbError) { - console.log('<<>>', error); + console.log('<<>>', error); - callback(null, { - statusCode: 500, - body: JSON.stringify(error), - }); - } else { - console.log('<<>>', error); - - callback(null, { - statusCode: 500, - body: JSON.stringify(error), - }); - } + callback(null, { + statusCode: 500, + body: JSON.stringify(error), + }); }); }; diff --git a/api/replies/replyList.js b/api/replies/replyList.js index 68fc67e..5d1f319 100644 --- a/api/replies/replyList.js +++ b/api/replies/replyList.js @@ -58,13 +58,6 @@ module.exports.replyList = (event, context, callback) => { statusCode: 422, body: error.message, }); - } else if (error instanceof DynamodbError) { - console.log('<<>>', error); - - callback(null, { - statusCode: 500, - body: JSON.stringify(error), - }); } else { console.log('<<>>', error); diff --git a/api/replies/replyUpdate.js b/api/replies/replyUpdate.js index 41aff55..72588c9 100644 --- a/api/replies/replyUpdate.js +++ b/api/replies/replyUpdate.js @@ -53,13 +53,6 @@ module.exports.replyUpdate = (event, context, callback) => { statusCode: 422, body: error.message, }); - } else if (error instanceof DynamodbError) { - console.log('<<>>', error); - - callback(null, { - statusCode: 500, - body: JSON.stringify(error), - }); } else { console.log('<<>>', error); diff --git a/api/threads/threadCreate.js b/api/threads/threadCreate.js index 02c39dc..8cc3816 100644 --- a/api/threads/threadCreate.js +++ b/api/threads/threadCreate.js @@ -42,21 +42,14 @@ module.exports.threadCreate = (event, context, callback) => { }); }); } catch (error) { + if (error instanceof ValidationError) { callback(null, { statusCode: 422, body: error.message, }); - } else if (error instanceof DynamodbError) { - console.log('<<>>', error); - - callback(null, { - statusCode: 500, - body: JSON.stringify(error), - }); } else { console.log('<<>>', error); - callback(null, { statusCode: 500, body: JSON.stringify(error), diff --git a/api/threads/threadDelete.js b/api/threads/threadDelete.js index aab9e64..963ff2c 100644 --- a/api/threads/threadDelete.js +++ b/api/threads/threadDelete.js @@ -29,20 +29,11 @@ module.exports.threadDelete = (event, context, callback) => { return callback(null, response); }) .catch((error) => { - if (error instanceof DynamodbError) { - console.log('<<>>', error); - - callback(null, { - statusCode: 500, - body: JSON.stringify(error), - }); - } else { console.log('<<>>', error); callback(null, { statusCode: 500, body: JSON.stringify(error), }); - } }); }; diff --git a/api/threads/threadList.js b/api/threads/threadList.js index 1007706..d2a257c 100644 --- a/api/threads/threadList.js +++ b/api/threads/threadList.js @@ -58,13 +58,6 @@ module.exports.threadList = (event, context, callback) => { statusCode: 422, body: error.message, }); - } else if (error instanceof DynamodbError) { - console.log('<<>>', error); - - callback(null, { - statusCode: 500, - body: JSON.stringify(error), - }); } else { console.log('<<>>', error); diff --git a/api/threads/threadUpdate.js b/api/threads/threadUpdate.js index 3719832..1ba1679 100644 --- a/api/threads/threadUpdate.js +++ b/api/threads/threadUpdate.js @@ -53,13 +53,6 @@ module.exports.threadUpdate = (event, context, callback) => { statusCode: 422, body: error.message, }); - } else if (error instanceof DynamodbError) { - console.log('<<>>', error); - - callback(null, { - statusCode: 500, - body: JSON.stringify(error), - }); } else { console.log('<<>>', error); From 1989dad97c8e81dfeaf8750e32d5165b474f5a3e Mon Sep 17 00:00:00 2001 From: jacksoncharles Date: Tue, 2 Jan 2018 22:19:34 +0000 Subject: [PATCH 77/78] remove dynamodberror --- api/_classes/Dynamic.js | 1 - api/_classes/Errors.js | 8 -------- api/replies/replyCreate.js | 1 - api/replies/replyDelete.js | 2 -- api/replies/replyList.js | 1 - api/replies/replyUpdate.js | 1 - api/threads/threadCreate.js | 1 - api/threads/threadDelete.js | 4 ---- api/threads/threadList.js | 1 - api/threads/threadUpdate.js | 1 - 10 files changed, 21 deletions(-) diff --git a/api/_classes/Dynamic.js b/api/_classes/Dynamic.js index abd0850..fedf85f 100644 --- a/api/_classes/Dynamic.js +++ b/api/_classes/Dynamic.js @@ -8,7 +8,6 @@ AWS.config.setPromisesDependency(null); const dynamodb = new AWS.DynamoDB.DocumentClient(); var Errors = require("./Errors"); -var DynamodbError = Errors.DynamodbError; var ValidationError = Errors.ValidationError; var NotFoundError = Errors.NotFoundError; diff --git a/api/_classes/Errors.js b/api/_classes/Errors.js index 7fa18cd..17e59fd 100644 --- a/api/_classes/Errors.js +++ b/api/_classes/Errors.js @@ -1,12 +1,5 @@ 'use strict'; -/** - * - * - * @type {class} - */ -class DynamodbError extends Error {} - /** * * @@ -22,7 +15,6 @@ class NotFoundError extends Error {} class ValidationError extends Error {} module.exports = { - DynamodbError : DynamodbError, NotFoundError : NotFoundError, ValidationError : ValidationError } \ No newline at end of file diff --git a/api/replies/replyCreate.js b/api/replies/replyCreate.js index 5a1d97d..f5d248d 100644 --- a/api/replies/replyCreate.js +++ b/api/replies/replyCreate.js @@ -7,7 +7,6 @@ const Reply = require('./_classes/Reply'); const Errors = require('./../_classes/Errors'); const ValidationError = Errors.ValidationError; -const DynamodbError = Errors.DynamodbError; /** * Handler for the lambda function. diff --git a/api/replies/replyDelete.js b/api/replies/replyDelete.js index 88b36c9..8c193ec 100644 --- a/api/replies/replyDelete.js +++ b/api/replies/replyDelete.js @@ -4,8 +4,6 @@ const Reply = require('./_classes/Reply'); const Errors = require('./../_classes/Errors'); -const DynamodbError = Errors.DynamodbError; - /** * Handler for the lambda function. * diff --git a/api/replies/replyList.js b/api/replies/replyList.js index 5d1f319..3352e46 100644 --- a/api/replies/replyList.js +++ b/api/replies/replyList.js @@ -6,7 +6,6 @@ const ReplyQueryBuilder = require('./_classes/ReplyQueryBuilder'); const Errors = require('./../_classes/Errors'); -const DynamodbError = Errors.DynamodbError; const ValidationError = Errors.ValidationError; /** diff --git a/api/replies/replyUpdate.js b/api/replies/replyUpdate.js index 72588c9..8a47e65 100644 --- a/api/replies/replyUpdate.js +++ b/api/replies/replyUpdate.js @@ -5,7 +5,6 @@ const Reply = require('./_classes/Reply'); const Errors = require('./../_classes/Errors'); const ValidationError = Errors.ValidationError; -const DynamodbError = Errors.DynamodbError; /** * Handler for the lambda function. diff --git a/api/threads/threadCreate.js b/api/threads/threadCreate.js index 8cc3816..a1c4d8f 100644 --- a/api/threads/threadCreate.js +++ b/api/threads/threadCreate.js @@ -7,7 +7,6 @@ const Thread = require('./_classes/Thread'); const Errors = require('./../_classes/Errors'); const ValidationError = Errors.ValidationError; -const DynamodbError = Errors.DynamodbError; /** * Handler for the lambda function. diff --git a/api/threads/threadDelete.js b/api/threads/threadDelete.js index 963ff2c..8572039 100644 --- a/api/threads/threadDelete.js +++ b/api/threads/threadDelete.js @@ -2,10 +2,6 @@ const Thread = require('./_classes/Thread'); -const Errors = require('./../_classes/Errors'); - -const DynamodbError = Errors.DynamodbError; - /** * Handler for the lambda function. * diff --git a/api/threads/threadList.js b/api/threads/threadList.js index d2a257c..3340e39 100644 --- a/api/threads/threadList.js +++ b/api/threads/threadList.js @@ -6,7 +6,6 @@ const ThreadQueryBuilder = require('./_classes/ThreadQueryBuilder'); const Errors = require('./../_classes/Errors'); -const DynamodbError = Errors.DynamodbError; const ValidationError = Errors.ValidationError; /** diff --git a/api/threads/threadUpdate.js b/api/threads/threadUpdate.js index 1ba1679..7c3ec01 100644 --- a/api/threads/threadUpdate.js +++ b/api/threads/threadUpdate.js @@ -5,7 +5,6 @@ const Thread = require('./_classes/Thread'); const Errors = require('./../_classes/Errors'); const ValidationError = Errors.ValidationError; -const DynamodbError = Errors.DynamodbError; /** * Handler for the lambda function. From d74116223629145a229aa5d3b604662f10817652 Mon Sep 17 00:00:00 2001 From: jacksoncharles Date: Tue, 2 Jan 2018 22:21:37 +0000 Subject: [PATCH 78/78] linter changes --- api/replies/replyDelete.js | 2 -- api/threads/threadCreate.js | 1 - api/threads/threadDelete.js | 10 +++++----- 3 files changed, 5 insertions(+), 8 deletions(-) diff --git a/api/replies/replyDelete.js b/api/replies/replyDelete.js index 8c193ec..e0bbaf3 100644 --- a/api/replies/replyDelete.js +++ b/api/replies/replyDelete.js @@ -2,8 +2,6 @@ const Reply = require('./_classes/Reply'); -const Errors = require('./../_classes/Errors'); - /** * Handler for the lambda function. * diff --git a/api/threads/threadCreate.js b/api/threads/threadCreate.js index a1c4d8f..463116b 100644 --- a/api/threads/threadCreate.js +++ b/api/threads/threadCreate.js @@ -41,7 +41,6 @@ module.exports.threadCreate = (event, context, callback) => { }); }); } catch (error) { - if (error instanceof ValidationError) { callback(null, { statusCode: 422, diff --git a/api/threads/threadDelete.js b/api/threads/threadDelete.js index 8572039..ae87c73 100644 --- a/api/threads/threadDelete.js +++ b/api/threads/threadDelete.js @@ -25,11 +25,11 @@ module.exports.threadDelete = (event, context, callback) => { return callback(null, response); }) .catch((error) => { - console.log('<<>>', error); + console.log('<<>>', error); - callback(null, { - statusCode: 500, - body: JSON.stringify(error), - }); + callback(null, { + statusCode: 500, + body: JSON.stringify(error), + }); }); };