Skip to content
Merged
22 changes: 20 additions & 2 deletions TODO.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,10 +18,28 @@
3. Write books REST API using TDD

- [ ] Create Books API CRUD functions
- [ ] - add
- [x] - add
- [ ] - delete
- [ ] - get
- [ ] - getAll
- [ ] - update
- [ ] Add Books API CRUD db adapters
- [x] - add
- [ ] - delete
- [ ] - get
- [ ] - getAll
- [ ] - update
- [ ] Add Books API CRUD route config
- [x] - add
- [ ] - delete
- [ ] - get
- [ ] - getAll
- [ ] - update
- [ ] Create authorize function
- [ ] Create data validation schemas
- [x] - add
- [ ] - delete
- [ ] - update
- [ ] Create authorize function
- [ ] - add
- [ ] - delete
- [ ] - update
30 changes: 30 additions & 0 deletions lib/adapters/db.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
const debug = require('debug')('booksapi:lib:adapters:db'); // eslint-disable-line no-unused-vars
const { v4: uuidv4 } = require('uuid');
const AWS = require('aws-sdk');

module.exports.add = function add(tableName, params) {
if (typeof tableName !== 'string') throw new Error('Bad argument: tableName must be a string');
if (typeof params !== 'object') throw new Error('Bad argument: params must be an object');

const dynamoDb = new AWS.DynamoDB.DocumentClient();

const putOptions = {
TableName: tableName,
Item: Object.assign(params, { uuid: uuidv4() })
};

debug(`putOptions: ${JSON.stringify(putOptions)}`);

return new Promise(function(resolve, reject) {
dynamoDb.put(putOptions, (err) => {
if (err) {
debug(`dynamo put error: ${err}`);
return reject(err);
}

debug('dynamo put success');

return resolve(putOptions.Item);
});
});
};
37 changes: 37 additions & 0 deletions lib/functions/books/add.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
'use strict';

const debug = require('debug')('booksapi:lib:functions:books:add'); // eslint-disable-line no-unused-vars

const db = require('../../adapters/db.js');

module.exports.add = async event => {
const data = JSON.parse(event.body);
const params = {
name: data.name,
releaseDate: data.releaseDate,
authorName: data.authorName
};

debug(`params: ${JSON.stringify(params)}`);

let bookRecord;
try {
bookRecord = await db.add(process.env.BOOKS_TABLE, params);
}
catch (err) {
return {
statusCode: 500,
body: JSON.stringify({
status: 500,
message: err.message
})
};
}

debug(`bookRecord: ${JSON.stringify(bookRecord)}`);

return {
statusCode: 201,
body: JSON.stringify(bookRecord)
};
};
30 changes: 30 additions & 0 deletions lib/schemas/add_book_request.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
{
"definitions": {},
"$schema": "http://json-schema.org/draft-04/schema#",
"type": "object",
"title": "Add Book Schema",
"required": [
"name",
"releaseDate",
"authorName"
],
"properties": {
"name": {
"title": "Name",
"type": "string",
"minLength": 1,
"maxLength": 150
},
"releaseDate": {
"type": "integer",
"title": "Release Date",
"minimum": 0
},
"authorName": {
"type": "string",
"title": "Author Name",
"minLength": 1,
"maxLength": 50
}
}
}
78 changes: 72 additions & 6 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

11 changes: 8 additions & 3 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,11 @@
"test": "test"
},
"dependencies": {
"aws-sdk": "^2.693.0",
"debug": "^4.1.1",
"serverless": "^1.72.0",
"serverless-pseudo-parameters": "^2.5.0"
"serverless-pseudo-parameters": "^2.5.0",
"uuid": "^8.1.0"
},
"devDependencies": {
"chai": "^4.2.0",
Expand All @@ -23,6 +26,7 @@
"ghooks": "^2.0.4",
"mocha": "^7.2.0",
"nyc": "^15.1.0",
"proxyquire": "^2.1.3",
"request": "^2.88.2",
"serverless-offline": "^6.4.0",
"sinon": "^9.0.2"
Expand All @@ -32,10 +36,11 @@
"lint": "eslint --ignore-path .gitignore .",
"start": "./node_modules/.bin/serverless offline -s dev",
"test": "npm run test:unit && npm run test:integration",
"test:unit": "./node_modules/.bin/mocha test/unit/**/*.test.js",
"test:unit": "./node_modules/.bin/mocha test/unit/**/**/*.test.js",
"test:integration": "echo \"Not implemented yet\"",
"debug:start": "export SLS_DEBUG=* && node --inspect-brk=9229 ./node_modules/.bin/serverless offline -s dev",
"debug:test": "export SLS_DEBUG=* && node ./node_modules/.bin/mocha --inspect-brk=9229 --recursive -R spec test/**/*.test.js"
"debug:test:unit": "export SLS_DEBUG=* && node ./node_modules/.bin/mocha --inspect-brk=9229 --recursive -R spec test/**/*.test.js",
"debug:test:integration": "echo \"Not implemented yet\""
},
"config": {
"ghooks": {
Expand Down
47 changes: 45 additions & 2 deletions serverless.yml
Original file line number Diff line number Diff line change
@@ -1,12 +1,29 @@
org: markjgsmith
app: serverless-books-api
service: books-service

custom:
db:
booksTable: ${self:service}-books-${self:provider.stage}

provider:
name: aws
runtime: nodejs12.x
stage: ${opt:stage, 'dev'}
region: us-east-1
environment:
BOOKS_TABLE: ${self:custom.db.booksTable}
DEBUG: booksapi:*
iamRoleStatements:
- Effect: Allow
Action:
- dynamodb:Query
- dynamodb:Scan
- dynamodb:GetItem
- dynamodb:PutItem
- dynamodb:UpdateItem
- dynamodb:DeleteItem
Resource:
- arn:aws:dynamodb:#{AWS::Region}:#{AWS::AccountId}:table/${self:custom.db.booksTable}
- arn:aws:dynamodb:#{AWS::Region}:#{AWS::AccountId}:table/${self:custom.db.booksTable}/index/uuid

functions:
hello:
Expand All @@ -16,6 +33,32 @@ functions:
method: get
path: /hello

addBook:
handler: lib/functions/books/add.add
events:
- http:
method: post
cors: true
path: /book/add
request:
schema:
application/json: ${file(lib/schemas/add_book_request.json)}

resources:
Resources:
booksTable:
Type: AWS::DynamoDB::Table
DeletionPolicy: Retain
Properties:
TableName: ${self:custom.db.booksTable}
AttributeDefinitions:
- AttributeName: uuid
AttributeType: S
KeySchema:
- AttributeName: uuid
KeyType: HASH
BillingMode: PAY_PER_REQUEST

plugins:
- serverless-pseudo-parameters
- serverless-offline
Loading