diff --git a/aws/apigateway-aurora-lambda/README.md b/aws/apigateway-aurora-lambda/README.md index afc4cc1..7ec279c 100644 --- a/aws/apigateway-aurora-lambda/README.md +++ b/aws/apigateway-aurora-lambda/README.md @@ -3,14 +3,13 @@

-

UUID

+

Offices and Employees

- Write a backend that exposes an endpoint that whenever invoked generates a new UUID, writes that to the database and returns it to the calling client. The backend should also expose an API that accepts a UUID and updates the updated_at timestamp if its already present in the db, else inserts it. + Write a multi-tenant backend for a SAAS ERP solution which allows storage/retrieval/manipulation of offices and employee data. Employees can work in multiple offices and each office can have multiple employees.

- ___ - +---

@@ -27,19 +26,27 @@

- ___ +--- + +We’re always looking for people who value their work, so come and join us. We are hiring! - We’re always looking for people who value their work, so come and join us. We are hiring! ## Rest APIs + Use API Gateway to expose a backend that provides -- a GET request that returns a new UUID when invoked and writes the value to a mysql variant of Aurora DB -- a PUT request that accepts a UUID, updates the updated_at timestamp if its already present in the db, else inserts it. + +- a PUT request that writes the employee details to a mysql variant of Aurora DB +- a PUT request that writes the office details to a mysql variant of Aurora DB +- a GET request that queries the DB by employeeId and returns the employee info +- a GET request that queries the DB by officeId and returns the office info +- a PUT request that associates office with employee +- a GET request that queries the DB and returns the all the offices or all the offices of a particular employee with pagination support +- a GET request that queries the DB and returns the all the employees or all the employees of a particular office with pagination support ## Migrations -Migrations are handled using sequelize. After a successful deployment a migration script is triggered by executing the following command: +Migrations are handled using sequelize. After a successful deployment a migration script is triggered by executing the following command: `sls migrations up --host= --stage=` -The host URL is fetched by querying the cloudformation Output for the RDSHost. +The host URL is fetched by querying the cloudformation Output for the RDSHost. diff --git a/aws/apigateway-aurora-lambda/__mocks__/constants.js b/aws/apigateway-aurora-lambda/__mocks__/constants.js index 580af64..0fa657a 100644 --- a/aws/apigateway-aurora-lambda/__mocks__/constants.js +++ b/aws/apigateway-aurora-lambda/__mocks__/constants.js @@ -5,5 +5,22 @@ MockDate.set('2000-11-22'); export const CONSTANTS = { uuid: 'uuid', - updatedAt: moment().format('YYYY-MM-DD HH:mm:s:ss') + updatedAt: moment().format('YYYY-MM-DD HH:mm:s:ss'), + employee: { + id: 1, + employee_name: 'Tapan', + office_id: null, + updated_at: '2020-10-25T16:02:21.000Z' + }, + office: { + id: 1, + office_name: 'Wednesday Solutions', + office_address: 'Pune, MH, India', + employee_id: null, + updated_at: '2020-10-25T16:01:55.000Z' + } }; +export const GET_EMP_BY_OFF_ID = + '{"res":{"employeeList":[{"employee_name":"Tapan","office_id":null,"updated_at":"2000-11-22 05:30:0:00","id":1,"createdAt":"2000-11-22T00:00:00.000Z","updatedAt":"2000-11-22T00:00:00.000Z"}],"office":{"office_name":"Wednesday Solutions","office_address":"Pune, MH, India","employee_id":null,"updated_at":"2000-11-22 05:30:0:00","id":1,"createdAt":"2000-11-22T00:00:00.000Z","updatedAt":"2000-11-22T00:00:00.000Z"}}}'; +export const GET_OFF_BY_EMP_ID = + '{"res":{"officeList":[{"office_name":"Wednesday Solutions","office_address":"Pune, MH, India","employee_id":null,"updated_at":"2000-11-22 05:30:0:00","id":1,"createdAt":"2000-11-22T00:00:00.000Z","updatedAt":"2000-11-22T00:00:00.000Z"}],"employee":{"employee_name":"Tapan","office_id":null,"updated_at":"2000-11-22 05:30:0:00","id":1,"createdAt":"2000-11-22T00:00:00.000Z","updatedAt":"2000-11-22T00:00:00.000Z"}}}'; diff --git a/aws/apigateway-aurora-lambda/functions/employee-office/join/data.json b/aws/apigateway-aurora-lambda/functions/employee-office/join/data.json new file mode 100644 index 0000000..4b3a077 --- /dev/null +++ b/aws/apigateway-aurora-lambda/functions/employee-office/join/data.json @@ -0,0 +1,7 @@ +{ + "body": "{\"employeeId\":1, \"officeId\":2}", + "headers": { + "content-type": "application/json", + "x-ws-system-id": "WS" + } +} diff --git a/aws/apigateway-aurora-lambda/functions/employee-office/join/index.js b/aws/apigateway-aurora-lambda/functions/employee-office/join/index.js new file mode 100644 index 0000000..2026053 --- /dev/null +++ b/aws/apigateway-aurora-lambda/functions/employee-office/join/index.js @@ -0,0 +1,24 @@ +import { getDB } from '@models'; +import { success, failure, logHandler, getSystemId } from '@utils'; +export const handler = async (event, context, callback) => + logHandler(event, async () => { + try { + const { employeeId, officeId } = JSON.parse(event.body); + if (!getSystemId(event)) { + throw new Error('Request Id Missing!'); + } + const { employeeOffice } = getDB(); + + const res = await employeeOffice.create({ + office_id: officeId, + employee_id: employeeId + }); + return success(callback, { + status: 200, + body: JSON.stringify({ res }) + }); + } catch (err) { + console.log(err); + return failure(callback, err); + } + }); diff --git a/aws/apigateway-aurora-lambda/functions/employee-office/join/package.json b/aws/apigateway-aurora-lambda/functions/employee-office/join/package.json new file mode 100644 index 0000000..ab8c871 --- /dev/null +++ b/aws/apigateway-aurora-lambda/functions/employee-office/join/package.json @@ -0,0 +1,12 @@ +{ + "name": "join", + "version": "1.0.0", + "description": "", + "main": "index.js", + "scripts": { + "test": "echo \"Error: no test specified\" && exit 1" + }, + "keywords": [], + "author": "", + "license": "ISC" +} diff --git a/aws/apigateway-aurora-lambda/functions/employee-office/join/tests/index.test.js b/aws/apigateway-aurora-lambda/functions/employee-office/join/tests/index.test.js new file mode 100644 index 0000000..7e7bcce --- /dev/null +++ b/aws/apigateway-aurora-lambda/functions/employee-office/join/tests/index.test.js @@ -0,0 +1,37 @@ +import { resetAndMockDB } from '@utils/testUtils'; + +describe('join-office-employee', () => { + let event; + let mocks; + + beforeEach(() => { + event = require('../data.json'); + mocks = { + callback: jest.fn() + }; + jest.spyOn(mocks, 'callback'); + }); + it('should update/put an employee', async () => { + await resetAndMockDB(mockDbs => { + mockDbs.employeeOffice.create = () => true; + }); + const handler = require('../index').handler; + await handler(event, null, mocks.callback); + expect(mocks.callback.mock.calls.length).toBe(1); + expect(mocks.callback.mock.calls[0][0]).toBe(null); + expect(mocks.callback.mock.calls[0][1]).toBeTruthy(); + expect(mocks.callback.mock.calls[0][1]).toEqual({ + status: 200, + body: JSON.stringify({ res: true }) + }); + }); + + it('should throw an error when req Id is missing', async () => { + event.headers['x-ws-system-id'] = null; + const handler = require('../index').handler; + await handler(event, null, mocks.callback); + expect(mocks.callback.mock.calls.length).toBe(1); + expect(mocks.callback.mock.calls[0][0]).toStrictEqual(`Request Id Missing!`); + expect(mocks.callback.mock.calls[0][1]).toBe(undefined); + }); +}); diff --git a/aws/apigateway-aurora-lambda/functions/employee/get-employee/data.json b/aws/apigateway-aurora-lambda/functions/employee/get-employee/data.json new file mode 100644 index 0000000..7453f56 --- /dev/null +++ b/aws/apigateway-aurora-lambda/functions/employee/get-employee/data.json @@ -0,0 +1,7 @@ +{ + "body": "{\"employeeId\":\"1\"}", + "headers": { + "content-type": "application/json", + "x-ws-system-id": "WS" + } +} diff --git a/aws/apigateway-aurora-lambda/functions/employee/get-employee/index.js b/aws/apigateway-aurora-lambda/functions/employee/get-employee/index.js new file mode 100644 index 0000000..74a1e9c --- /dev/null +++ b/aws/apigateway-aurora-lambda/functions/employee/get-employee/index.js @@ -0,0 +1,19 @@ +import { getDB } from '@models'; +import { success, failure, logHandler, getSystemId } from '@utils'; +export const handler = async (event, context, callback) => + logHandler(event, async () => { + try { + const { employeeId } = JSON.parse(event.body); + if (!getSystemId(event)) { + throw new Error('Request Id Missing!'); + } + const res = await getDB().employees.findOne({ where: { id: employeeId }, raw: true }); + return success(callback, { + status: 200, + body: JSON.stringify({ res }) + }); + } catch (err) { + console.log(err); + return failure(callback, err); + } + }); diff --git a/aws/apigateway-aurora-lambda/functions/employee/get-employee/package.json b/aws/apigateway-aurora-lambda/functions/employee/get-employee/package.json new file mode 100644 index 0000000..10ccf62 --- /dev/null +++ b/aws/apigateway-aurora-lambda/functions/employee/get-employee/package.json @@ -0,0 +1,12 @@ +{ + "name": "getEmployee", + "version": "1.0.0", + "description": "", + "main": "index.js", + "scripts": { + "test": "echo \"Error: no test specified\" && exit 1" + }, + "keywords": [], + "author": "", + "license": "ISC" +} diff --git a/aws/apigateway-aurora-lambda/functions/employee/get-employee/tests/index.test.js b/aws/apigateway-aurora-lambda/functions/employee/get-employee/tests/index.test.js new file mode 100644 index 0000000..f87b585 --- /dev/null +++ b/aws/apigateway-aurora-lambda/functions/employee/get-employee/tests/index.test.js @@ -0,0 +1,38 @@ +import { CONSTANTS } from '@mocks/constants'; +import { resetAndMockDB } from '@utils/testUtils'; + +describe('get-employee', () => { + let event; + let mocks; + + beforeEach(() => { + event = require('../data.json'); + mocks = { + callback: jest.fn() + }; + jest.spyOn(mocks, 'callback'); + }); + it('should fetch employee with employeeId', async () => { + await resetAndMockDB(mockDbs => { + mockDbs.employees.findOne = () => CONSTANTS.employee; + }); + const handler = require('../index').handler; + await handler(event, null, mocks.callback); + expect(mocks.callback.mock.calls.length).toBe(1); + expect(mocks.callback.mock.calls[0][0]).toBe(null); + expect(mocks.callback.mock.calls[0][1]).toBeTruthy(); + expect(mocks.callback.mock.calls[0][1]).toEqual({ + status: 200, + body: JSON.stringify({ res: CONSTANTS.employee }) + }); + }); + + it('should throw an error when req Id is missing', async () => { + event.headers['x-ws-system-id'] = null; + const handler = require('../index').handler; + await handler(event, null, mocks.callback); + expect(mocks.callback.mock.calls.length).toBe(1); + expect(mocks.callback.mock.calls[0][0]).toStrictEqual(`Request Id Missing!`); + expect(mocks.callback.mock.calls[0][1]).toBe(undefined); + }); +}); diff --git a/aws/apigateway-aurora-lambda/functions/employee/get-employees/data.json b/aws/apigateway-aurora-lambda/functions/employee/get-employees/data.json new file mode 100644 index 0000000..c7c0ac4 --- /dev/null +++ b/aws/apigateway-aurora-lambda/functions/employee/get-employees/data.json @@ -0,0 +1,7 @@ +{ + "body": "{\"officeId\":1}", + "headers": { + "content-type": "application/json", + "x-ws-system-id": "WS" + } +} diff --git a/aws/apigateway-aurora-lambda/functions/employee/get-employees/index.js b/aws/apigateway-aurora-lambda/functions/employee/get-employees/index.js new file mode 100644 index 0000000..4669df0 --- /dev/null +++ b/aws/apigateway-aurora-lambda/functions/employee/get-employees/index.js @@ -0,0 +1,36 @@ +import { getDB } from '@models'; +import { success, failure, logHandler, getSystemId } from '@utils'; +export const handler = async (event, context, callback) => + logHandler(event, async () => { + try { + const { limit, offset, officeId } = JSON.parse(event.body); + if (!getSystemId(event)) { + throw new Error('Request Id Missing!'); + } + let res; + const employeeList = []; + + const { offices, employeeOffice, employees } = getDB(); + if (!officeId) { + res = await employees.findAll(); + } else { + const employeeOfficeRes = await employeeOffice.findAll({ limit, offset, where: { office_id: officeId } }); + await Promise.all( + employeeOfficeRes.map(async e => { + employeeList.push(await employees.findOne({ where: { id: e.dataValues.employee_id }, raw: true })); + }) + ); + res = { + employeeList, + office: await offices.findOne({ where: { id: officeId }, raw: true }) + }; + } + return success(callback, { + status: 200, + body: JSON.stringify({ res }) + }); + } catch (err) { + console.log(err); + return failure(callback, err); + } + }); diff --git a/aws/apigateway-aurora-lambda/functions/employee/get-employees/package.json b/aws/apigateway-aurora-lambda/functions/employee/get-employees/package.json new file mode 100644 index 0000000..ed014a2 --- /dev/null +++ b/aws/apigateway-aurora-lambda/functions/employee/get-employees/package.json @@ -0,0 +1,12 @@ +{ + "name": "get-employees", + "version": "1.0.0", + "description": "", + "main": "index.js", + "scripts": { + "test": "echo \"Error: no test specified\" && exit 1" + }, + "keywords": [], + "author": "", + "license": "ISC" +} diff --git a/aws/apigateway-aurora-lambda/functions/employee/get-employees/tests/index.test.js b/aws/apigateway-aurora-lambda/functions/employee/get-employees/tests/index.test.js new file mode 100644 index 0000000..fe33f24 --- /dev/null +++ b/aws/apigateway-aurora-lambda/functions/employee/get-employees/tests/index.test.js @@ -0,0 +1,54 @@ +import { CONSTANTS, GET_EMP_BY_OFF_ID } from '@mocks/constants'; +import { resetAndMockDB } from '@utils/testUtils'; + +describe('get-employees', () => { + let event; + let mocks; + + beforeEach(() => { + event = require('../data.json'); + mocks = { + callback: jest.fn() + }; + jest.spyOn(mocks, 'callback'); + }); + it('should fetch all employees', async () => { + await resetAndMockDB(mockDbs => { + mockDbs.employees.findAll = () => [CONSTANTS.employee]; + }); + const handler = require('../index').handler; + event.body = '{}'; + await handler(event, null, mocks.callback); + expect(mocks.callback.mock.calls.length).toBe(1); + expect(mocks.callback.mock.calls[0][0]).toBe(null); + expect(mocks.callback.mock.calls[0][1]).toBeTruthy(); + expect(mocks.callback.mock.calls[0][1]).toEqual({ + status: 200, + body: JSON.stringify({ res: [CONSTANTS.employee] }) + }); + }); + + it('should fetch all employees of the given officeId', async () => { + await resetAndMockDB(mockDbs => { + mockDbs.employeeOffice.findAll = () => [{ dataValues: CONSTANTS.employee }]; + }); + const handler = require('../index').handler; + await handler(event, null, mocks.callback); + expect(mocks.callback.mock.calls.length).toBe(1); + expect(mocks.callback.mock.calls[0][0]).toBe(null); + expect(mocks.callback.mock.calls[0][1]).toBeTruthy(); + expect(mocks.callback.mock.calls[0][1]).toEqual({ + status: 200, + body: GET_EMP_BY_OFF_ID + }); + }); + + it('should throw an error when req Id is missing', async () => { + event.headers['x-ws-system-id'] = null; + const handler = require('../index').handler; + await handler(event, null, mocks.callback); + expect(mocks.callback.mock.calls.length).toBe(1); + expect(mocks.callback.mock.calls[0][0]).toStrictEqual(`Request Id Missing!`); + expect(mocks.callback.mock.calls[0][1]).toBe(undefined); + }); +}); diff --git a/aws/apigateway-aurora-lambda/functions/employee/put-employee/data.json b/aws/apigateway-aurora-lambda/functions/employee/put-employee/data.json new file mode 100644 index 0000000..677b2b4 --- /dev/null +++ b/aws/apigateway-aurora-lambda/functions/employee/put-employee/data.json @@ -0,0 +1,7 @@ +{ + "body": "{\"employeeName\":\"Mac\"}", + "headers": { + "content-type": "application/json", + "x-ws-system-id": "WS" + } +} diff --git a/aws/apigateway-aurora-lambda/functions/employee/put-employee/index.js b/aws/apigateway-aurora-lambda/functions/employee/put-employee/index.js new file mode 100644 index 0000000..bd1d185 --- /dev/null +++ b/aws/apigateway-aurora-lambda/functions/employee/put-employee/index.js @@ -0,0 +1,20 @@ +import { getDB } from '@models'; +import { success, failure, logHandler, getSystemId } from '@utils'; +export const handler = async (event, context, callback) => + logHandler(event, async () => { + try { + const { employeeName, officeId } = JSON.parse(event.body); + if (!getSystemId(event)) { + throw new Error('Request Id Missing!'); + } + const res = await getDB().employees.upsert({ employee_name: employeeName, office_id: officeId }); + + return success(callback, { + status: 200, + body: JSON.stringify({ res }) + }); + } catch (err) { + console.log(err); + return failure(callback, err); + } + }); diff --git a/aws/apigateway-aurora-lambda/functions/employee/put-employee/package.json b/aws/apigateway-aurora-lambda/functions/employee/put-employee/package.json new file mode 100644 index 0000000..728ba78 --- /dev/null +++ b/aws/apigateway-aurora-lambda/functions/employee/put-employee/package.json @@ -0,0 +1,12 @@ +{ + "name": "putEmployee", + "version": "1.0.0", + "description": "", + "main": "index.js", + "scripts": { + "test": "echo \"Error: no test specified\" && exit 1" + }, + "keywords": [], + "author": "", + "license": "ISC" +} diff --git a/aws/apigateway-aurora-lambda/functions/employee/put-employee/tests/index.test.js b/aws/apigateway-aurora-lambda/functions/employee/put-employee/tests/index.test.js new file mode 100644 index 0000000..0c5d0f4 --- /dev/null +++ b/aws/apigateway-aurora-lambda/functions/employee/put-employee/tests/index.test.js @@ -0,0 +1,37 @@ +import { resetAndMockDB } from '@utils/testUtils'; + +describe('put-employee', () => { + let event; + let mocks; + + beforeEach(() => { + event = require('../data.json'); + mocks = { + callback: jest.fn() + }; + jest.spyOn(mocks, 'callback'); + }); + it('should update/put an employee', async () => { + await resetAndMockDB(mockDbs => { + mockDbs.employees.upsert = () => true; + }); + const handler = require('../index').handler; + await handler(event, null, mocks.callback); + expect(mocks.callback.mock.calls.length).toBe(1); + expect(mocks.callback.mock.calls[0][0]).toBe(null); + expect(mocks.callback.mock.calls[0][1]).toBeTruthy(); + expect(mocks.callback.mock.calls[0][1]).toEqual({ + status: 200, + body: JSON.stringify({ res: true }) + }); + }); + + it('should throw an error when req Id is missing', async () => { + event.headers['x-ws-system-id'] = null; + const handler = require('../index').handler; + await handler(event, null, mocks.callback); + expect(mocks.callback.mock.calls.length).toBe(1); + expect(mocks.callback.mock.calls[0][0]).toStrictEqual(`Request Id Missing!`); + expect(mocks.callback.mock.calls[0][1]).toBe(undefined); + }); +}); diff --git a/aws/apigateway-aurora-lambda/functions/office/get-office/data.json b/aws/apigateway-aurora-lambda/functions/office/get-office/data.json new file mode 100644 index 0000000..6ae18df --- /dev/null +++ b/aws/apigateway-aurora-lambda/functions/office/get-office/data.json @@ -0,0 +1,7 @@ +{ + "body": "{\"officeId\":\"1\"}", + "headers": { + "content-type": "application/json", + "x-ws-system-id": "WS" + } +} diff --git a/aws/apigateway-aurora-lambda/functions/office/get-office/index.js b/aws/apigateway-aurora-lambda/functions/office/get-office/index.js new file mode 100644 index 0000000..1402434 --- /dev/null +++ b/aws/apigateway-aurora-lambda/functions/office/get-office/index.js @@ -0,0 +1,19 @@ +import { getDB } from '@models'; +import { success, failure, logHandler, getSystemId } from '@utils'; +export const handler = async (event, context, callback) => + logHandler(event, async () => { + try { + const { officeId } = JSON.parse(event.body); + if (!getSystemId(event)) { + throw new Error('Request Id Missing!'); + } + const res = await getDB().offices.findOne({ where: { id: officeId }, raw: true }); + return success(callback, { + status: 200, + body: JSON.stringify({ res }) + }); + } catch (err) { + console.log(err); + return failure(callback, err); + } + }); diff --git a/aws/apigateway-aurora-lambda/functions/office/get-office/package.json b/aws/apigateway-aurora-lambda/functions/office/get-office/package.json new file mode 100644 index 0000000..5f81265 --- /dev/null +++ b/aws/apigateway-aurora-lambda/functions/office/get-office/package.json @@ -0,0 +1,12 @@ +{ + "name": "get-office", + "version": "1.0.0", + "description": "", + "main": "index.js", + "scripts": { + "test": "echo \"Error: no test specified\" && exit 1" + }, + "keywords": [], + "author": "", + "license": "ISC" +} diff --git a/aws/apigateway-aurora-lambda/functions/office/get-office/tests/index.test.js b/aws/apigateway-aurora-lambda/functions/office/get-office/tests/index.test.js new file mode 100644 index 0000000..c0f60f8 --- /dev/null +++ b/aws/apigateway-aurora-lambda/functions/office/get-office/tests/index.test.js @@ -0,0 +1,38 @@ +import { CONSTANTS } from '@mocks/constants'; +import { resetAndMockDB } from '@utils/testUtils'; + +describe('get-office', () => { + let event; + let mocks; + + beforeEach(() => { + event = require('../data.json'); + mocks = { + callback: jest.fn() + }; + jest.spyOn(mocks, 'callback'); + }); + it('should fetch office with officeId', async () => { + await resetAndMockDB(mockDbs => { + mockDbs.offices.findOne = () => CONSTANTS.office; + }); + const handler = require('../index').handler; + await handler(event, null, mocks.callback); + expect(mocks.callback.mock.calls.length).toBe(1); + expect(mocks.callback.mock.calls[0][0]).toBe(null); + expect(mocks.callback.mock.calls[0][1]).toBeTruthy(); + expect(mocks.callback.mock.calls[0][1]).toEqual({ + status: 200, + body: JSON.stringify({ res: CONSTANTS.office }) + }); + }); + + it('should throw an error when req Id is missing', async () => { + event.headers['x-ws-system-id'] = null; + const handler = require('../index').handler; + await handler(event, null, mocks.callback); + expect(mocks.callback.mock.calls.length).toBe(1); + expect(mocks.callback.mock.calls[0][0]).toStrictEqual(`Request Id Missing!`); + expect(mocks.callback.mock.calls[0][1]).toBe(undefined); + }); +}); diff --git a/aws/apigateway-aurora-lambda/functions/office/get-offices/data.json b/aws/apigateway-aurora-lambda/functions/office/get-offices/data.json new file mode 100644 index 0000000..e1ac41b --- /dev/null +++ b/aws/apigateway-aurora-lambda/functions/office/get-offices/data.json @@ -0,0 +1,7 @@ +{ + "body": "{\"employeeId\":1}", + "headers": { + "content-type": "application/json", + "x-ws-system-id": "WS" + } +} diff --git a/aws/apigateway-aurora-lambda/functions/office/get-offices/index.js b/aws/apigateway-aurora-lambda/functions/office/get-offices/index.js new file mode 100644 index 0000000..1c42ee5 --- /dev/null +++ b/aws/apigateway-aurora-lambda/functions/office/get-offices/index.js @@ -0,0 +1,36 @@ +import { getDB } from '@models'; +import { success, failure, logHandler, getSystemId } from '@utils'; +export const handler = async (event, context, callback) => + logHandler(event, async () => { + try { + const { limit, offset, employeeId } = JSON.parse(event.body); + if (!getSystemId(event)) { + throw new Error('Request Id Missing!'); + } + let res; + const officeList = []; + + const { offices, employeeOffice, employees } = getDB(); + if (!employeeId) { + res = await offices.findAll(); + } else { + const employeeOfficeRes = await employeeOffice.findAll({ limit, offset, where: { employee_id: employeeId } }); + await Promise.all( + employeeOfficeRes.map(async e => { + officeList.push(await offices.findOne({ where: { id: e.dataValues.office_id }, raw: true })); + }) + ); + res = { + officeList, + employee: await employees.findOne({ where: { id: employeeId }, raw: true }) + }; + } + return success(callback, { + status: 200, + body: JSON.stringify({ res }) + }); + } catch (err) { + console.log(err); + return failure(callback, err); + } + }); diff --git a/aws/apigateway-aurora-lambda/functions/office/get-offices/package.json b/aws/apigateway-aurora-lambda/functions/office/get-offices/package.json new file mode 100644 index 0000000..ab69a02 --- /dev/null +++ b/aws/apigateway-aurora-lambda/functions/office/get-offices/package.json @@ -0,0 +1,12 @@ +{ + "name": "get-offices", + "version": "1.0.0", + "description": "", + "main": "index.js", + "scripts": { + "test": "echo \"Error: no test specified\" && exit 1" + }, + "keywords": [], + "author": "", + "license": "ISC" +} diff --git a/aws/apigateway-aurora-lambda/functions/office/get-offices/tests/index.test.js b/aws/apigateway-aurora-lambda/functions/office/get-offices/tests/index.test.js new file mode 100644 index 0000000..ebef0eb --- /dev/null +++ b/aws/apigateway-aurora-lambda/functions/office/get-offices/tests/index.test.js @@ -0,0 +1,54 @@ +import { CONSTANTS, GET_OFF_BY_EMP_ID } from '@mocks/constants'; +import { resetAndMockDB } from '@utils/testUtils'; + +describe('get-offices', () => { + let event; + let mocks; + + beforeEach(() => { + event = require('../data.json'); + mocks = { + callback: jest.fn() + }; + jest.spyOn(mocks, 'callback'); + }); + it('should fetch all offices', async () => { + await resetAndMockDB(mockDbs => { + mockDbs.offices.findAll = () => [CONSTANTS.office]; + }); + const handler = require('../index').handler; + event.body = '{}'; + await handler(event, null, mocks.callback); + expect(mocks.callback.mock.calls.length).toBe(1); + expect(mocks.callback.mock.calls[0][0]).toBe(null); + expect(mocks.callback.mock.calls[0][1]).toBeTruthy(); + expect(mocks.callback.mock.calls[0][1]).toEqual({ + status: 200, + body: JSON.stringify({ res: [CONSTANTS.office] }) + }); + }); + + it('should fetch all offices of the given employeeId', async () => { + await resetAndMockDB(mockDbs => { + mockDbs.employeeOffice.findAll = () => [{ dataValues: CONSTANTS.office }]; + }); + const handler = require('../index').handler; + await handler(event, null, mocks.callback); + expect(mocks.callback.mock.calls.length).toBe(1); + expect(mocks.callback.mock.calls[0][0]).toBe(null); + expect(mocks.callback.mock.calls[0][1]).toBeTruthy(); + expect(mocks.callback.mock.calls[0][1]).toEqual({ + status: 200, + body: GET_OFF_BY_EMP_ID + }); + }); + + it('should throw an error when req Id is missing', async () => { + event.headers['x-ws-system-id'] = null; + const handler = require('../index').handler; + await handler(event, null, mocks.callback); + expect(mocks.callback.mock.calls.length).toBe(1); + expect(mocks.callback.mock.calls[0][0]).toStrictEqual(`Request Id Missing!`); + expect(mocks.callback.mock.calls[0][1]).toBe(undefined); + }); +}); diff --git a/aws/apigateway-aurora-lambda/functions/office/put-office/data.json b/aws/apigateway-aurora-lambda/functions/office/put-office/data.json new file mode 100644 index 0000000..3bd68ab --- /dev/null +++ b/aws/apigateway-aurora-lambda/functions/office/put-office/data.json @@ -0,0 +1,7 @@ +{ + "body": "{\"officeName\":\"WS\", \"officeAddress\":\"Pune, MH, India\"}", + "headers": { + "content-type": "application/json", + "x-ws-system-id": "WS" + } +} diff --git a/aws/apigateway-aurora-lambda/functions/office/put-office/index.js b/aws/apigateway-aurora-lambda/functions/office/put-office/index.js new file mode 100644 index 0000000..70f3092 --- /dev/null +++ b/aws/apigateway-aurora-lambda/functions/office/put-office/index.js @@ -0,0 +1,24 @@ +import { getDB } from '@models'; +import { success, failure, logHandler, getSystemId } from '@utils'; +export const handler = async (event, context, callback) => + logHandler(event, async () => { + try { + const { officeName, officeAddress, employeeId } = JSON.parse(event.body); + if (!getSystemId(event)) { + throw new Error('Request Id Missing!'); + } + const res = await getDB().offices.upsert({ + office_name: officeName, + office_address: officeAddress, + employee_id: employeeId + }); + + return success(callback, { + status: 200, + body: JSON.stringify({ res }) + }); + } catch (err) { + console.log(err); + return failure(callback, err); + } + }); diff --git a/aws/apigateway-aurora-lambda/functions/office/put-office/package.json b/aws/apigateway-aurora-lambda/functions/office/put-office/package.json new file mode 100644 index 0000000..728ba78 --- /dev/null +++ b/aws/apigateway-aurora-lambda/functions/office/put-office/package.json @@ -0,0 +1,12 @@ +{ + "name": "putEmployee", + "version": "1.0.0", + "description": "", + "main": "index.js", + "scripts": { + "test": "echo \"Error: no test specified\" && exit 1" + }, + "keywords": [], + "author": "", + "license": "ISC" +} diff --git a/aws/apigateway-aurora-lambda/functions/office/put-office/tests/index.test.js b/aws/apigateway-aurora-lambda/functions/office/put-office/tests/index.test.js new file mode 100644 index 0000000..4f42413 --- /dev/null +++ b/aws/apigateway-aurora-lambda/functions/office/put-office/tests/index.test.js @@ -0,0 +1,37 @@ +import { resetAndMockDB } from '@utils/testUtils'; + +describe('put-office', () => { + let event; + let mocks; + + beforeEach(() => { + event = require('../data.json'); + mocks = { + callback: jest.fn() + }; + jest.spyOn(mocks, 'callback'); + }); + it('should update/put an office', async () => { + await resetAndMockDB(mockDbs => { + mockDbs.offices.upsert = () => true; + }); + const handler = require('../index').handler; + await handler(event, null, mocks.callback); + expect(mocks.callback.mock.calls.length).toBe(1); + expect(mocks.callback.mock.calls[0][0]).toBe(null); + expect(mocks.callback.mock.calls[0][1]).toBeTruthy(); + expect(mocks.callback.mock.calls[0][1]).toEqual({ + status: 200, + body: JSON.stringify({ res: true }) + }); + }); + + it('should throw an error when req Id is missing', async () => { + event.headers['x-ws-system-id'] = null; + const handler = require('../index').handler; + await handler(event, null, mocks.callback); + expect(mocks.callback.mock.calls.length).toBe(1); + expect(mocks.callback.mock.calls[0][0]).toStrictEqual(`Request Id Missing!`); + expect(mocks.callback.mock.calls[0][1]).toBe(undefined); + }); +}); diff --git a/aws/apigateway-aurora-lambda/functions/uuid/get/tests/index.test.js b/aws/apigateway-aurora-lambda/functions/uuid/get/tests/index.test.js index f2fbbcf..70b8001 100644 --- a/aws/apigateway-aurora-lambda/functions/uuid/get/tests/index.test.js +++ b/aws/apigateway-aurora-lambda/functions/uuid/get/tests/index.test.js @@ -4,7 +4,6 @@ describe('get-uuid', () => { it('should get a new UUID', async () => { const response = await handler(); const body = JSON.parse(response.body); - console.log({ body }); expect(body.uuid).toEqual(CONSTANTS.uuid); }); }); diff --git a/aws/apigateway-aurora-lambda/functions/uuid/put/data.json b/aws/apigateway-aurora-lambda/functions/uuid/put/data.json new file mode 100644 index 0000000..ebcec2c --- /dev/null +++ b/aws/apigateway-aurora-lambda/functions/uuid/put/data.json @@ -0,0 +1,7 @@ +{ + "body": "{\"uuid\":\"bf1cb7b6-6199-488a-a485-8b72e74c8c95\"}", + "headers": { + "content-type": "application/json", + "x-ws-system-id": "WS" + } +} diff --git a/aws/apigateway-aurora-lambda/functions/uuid/put/index.js b/aws/apigateway-aurora-lambda/functions/uuid/put/index.js index a553b52..fd2685e 100644 --- a/aws/apigateway-aurora-lambda/functions/uuid/put/index.js +++ b/aws/apigateway-aurora-lambda/functions/uuid/put/index.js @@ -1,15 +1,29 @@ import moment from 'moment'; import { getDB } from '@models'; +import { success, failure, logHandler, getSystemId } from '@utils'; -export const handler = async event => { - const { uuid } = JSON.parse(event.body); - const dbResponse = await getDB().uuids.findOne({ where: { uuid }, raw: true }); - let newUUID; - if (dbResponse) { - await getDB().uuids.update({ updatedAt: moment() }, { where: { id: dbResponse.id } }); - newUUID = await getDB().uuids.findOne({ where: { uuid }, raw: true }); - } else { - newUUID = await getDB().uuids.create({ uuid }); - } - return { status: 200, body: JSON.stringify({ uuid: newUUID.uuid, updated_at: newUUID.updatedAt }) }; -}; +export const handler = async (event, context, callback) => + logHandler(event, async () => { + try { + if (!getSystemId(event)) { + throw new Error('Request Id Missing!'); + } + const { uuid } = JSON.parse(event.body); + const dbResponse = await getDB().uuids.findOne({ where: { uuid }, raw: true }); + let newUUID; + const { uuids } = getDB(); + if (dbResponse) { + await uuids.update({ updatedAt: moment() }, { where: { id: dbResponse.id } }); + newUUID = await uuids.findOne({ where: { uuid }, raw: true }); + } else { + newUUID = await uuids.create({ uuid }); + } + return success(callback, { + status: 200, + body: JSON.stringify({ uuid: newUUID.uuid, updated_at: newUUID.updatedAt }) + }); + } catch (err) { + console.log(err); + return failure(callback, err); + } + }); diff --git a/aws/apigateway-aurora-lambda/functions/uuid/put/tests/index.test.js b/aws/apigateway-aurora-lambda/functions/uuid/put/tests/index.test.js index 4a70e94..60bc634 100644 --- a/aws/apigateway-aurora-lambda/functions/uuid/put/tests/index.test.js +++ b/aws/apigateway-aurora-lambda/functions/uuid/put/tests/index.test.js @@ -2,13 +2,24 @@ import { CONSTANTS } from '@mocks/constants'; import { resetAndMockDB } from '@utils/testUtils'; describe('put-uuid', () => { + let mocks; + let event; + + beforeEach(() => { + event = require('../data.json'); + + mocks = { + callback: jest.fn() + }; + jest.spyOn(mocks, 'callback'); + }); it('should update the updatedAt if the UUID is already present', async () => { resetAndMockDB(); const handler = require('../index').handler; - const response = await handler({ body: `{"uuid": "${CONSTANTS.uuid}"}` }); - const body = JSON.parse(response.body); - expect(body.uuid).toEqual(CONSTANTS.uuid); - expect(body.updated_at).toEqual('2000-11-22T00:00:00.000Z'); + await handler({ body: `{"uuid": "${CONSTANTS.uuid}"}`, headers: event.headers }, null, mocks.callback); + expect(mocks.callback.mock.calls.length).toBe(1); + expect(mocks.callback.mock.calls[0][0]).toBe(null); + expect(mocks.callback.mock.calls[0][1]).toBeTruthy(); }); it('should add a new UUID', async () => { @@ -18,9 +29,17 @@ describe('put-uuid', () => { mockDbs.uuids.create = () => ({ uuid: newUUID, updatedAt: CONSTANTS.updatedAt }); }); const handler = require('../index').handler; - const response = await handler({ body: `{"uuid": "${CONSTANTS.uuid}"}` }); - const body = JSON.parse(response.body); - expect(body.uuid).toEqual(newUUID); - expect(body.updated_at).toEqual(CONSTANTS.updatedAt); + await handler({ body: `{"uuid": "${CONSTANTS.uuid}"}`, headers: event.headers }, null, mocks.callback); + expect(mocks.callback.mock.calls.length).toBe(1); + expect(mocks.callback.mock.calls[0][0]).toBe(null); + expect(mocks.callback.mock.calls[0][1]).toBeTruthy(); + }); + it('should throw an error when req Id is missing', async () => { + event.headers['x-ws-system-id'] = null; + const handler = require('../index').handler; + await handler(event, null, mocks.callback); + expect(mocks.callback.mock.calls.length).toBe(1); + expect(mocks.callback.mock.calls[0][0]).toStrictEqual(`Request Id Missing!`); + expect(mocks.callback.mock.calls[0][1]).toBe(undefined); }); }); diff --git a/aws/apigateway-aurora-lambda/migrations/resources/v1/02_employees.sql b/aws/apigateway-aurora-lambda/migrations/resources/v1/02_employees.sql new file mode 100644 index 0000000..8bd1ce1 --- /dev/null +++ b/aws/apigateway-aurora-lambda/migrations/resources/v1/02_employees.sql @@ -0,0 +1,6 @@ +create table employees ( + id INT PRIMARY KEY AUTO_INCREMENT, + employee_name VARCHAR(36) UNIQUE NOT NULL, + office_id INT, + updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP +); \ No newline at end of file diff --git a/aws/apigateway-aurora-lambda/migrations/resources/v1/03_office.sql b/aws/apigateway-aurora-lambda/migrations/resources/v1/03_office.sql new file mode 100644 index 0000000..c7b0c65 --- /dev/null +++ b/aws/apigateway-aurora-lambda/migrations/resources/v1/03_office.sql @@ -0,0 +1,8 @@ +create table offices ( + id INT PRIMARY KEY AUTO_INCREMENT, + office_name VARCHAR(36) UNIQUE NOT NULL, + office_address VARCHAR(36) NOT NULL, + employee_id INT, + updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + CONSTRAINT offices_employee_id_fk FOREIGN KEY (employee_id) REFERENCES employees (id) +); \ No newline at end of file diff --git a/aws/apigateway-aurora-lambda/migrations/resources/v1/04_employee_office.sql b/aws/apigateway-aurora-lambda/migrations/resources/v1/04_employee_office.sql new file mode 100644 index 0000000..ee4ef0c --- /dev/null +++ b/aws/apigateway-aurora-lambda/migrations/resources/v1/04_employee_office.sql @@ -0,0 +1,9 @@ +create table employee_office ( + id INT PRIMARY KEY AUTO_INCREMENT, + employee_id INT NOT NULL, + office_id INT NOT NULL, + updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + CONSTRAINT employee_office_id UNIQUE (id), + CONSTRAINT employee_id_fk FOREIGN KEY (employee_id) REFERENCES employees (id), + CONSTRAINT office_id_fk FOREIGN KEY (office_id) REFERENCES offices (id) +); \ No newline at end of file diff --git a/aws/apigateway-aurora-lambda/migrations/resources/v1/alter_employees.sql b/aws/apigateway-aurora-lambda/migrations/resources/v1/alter_employees.sql new file mode 100644 index 0000000..7b854df --- /dev/null +++ b/aws/apigateway-aurora-lambda/migrations/resources/v1/alter_employees.sql @@ -0,0 +1 @@ +alter table employees add foreign key (office_id) references offices(id); diff --git a/aws/apigateway-aurora-lambda/models/employee_office.js b/aws/apigateway-aurora-lambda/models/employee_office.js new file mode 100644 index 0000000..0ef8025 --- /dev/null +++ b/aws/apigateway-aurora-lambda/models/employee_office.js @@ -0,0 +1,44 @@ +/* jshint indent: 2 */ + +const Sequelize = require('sequelize'); +module.exports = function(sequelize, DataTypes) { + return sequelize.define( + 'employee_office', + { + id: { + autoIncrement: true, + type: DataTypes.INTEGER, + allowNull: false, + primaryKey: true + }, + employee_id: { + type: DataTypes.INTEGER, + allowNull: false, + references: { + model: 'employees', + key: 'id' + }, + unique: 'employee_id_fk' + }, + office_id: { + type: DataTypes.INTEGER, + allowNull: false, + references: { + model: 'offices', + key: 'id' + }, + unique: 'office_id_fk' + }, + updated_at: { + type: DataTypes.DATE, + allowNull: true, + defaultValue: Sequelize.literal('CURRENT_TIMESTAMP') + } + }, + { + sequelize, + tableName: 'employee_office', + timestamps: false + } + ); +}; diff --git a/aws/apigateway-aurora-lambda/models/employees.js b/aws/apigateway-aurora-lambda/models/employees.js new file mode 100644 index 0000000..39c469a --- /dev/null +++ b/aws/apigateway-aurora-lambda/models/employees.js @@ -0,0 +1,40 @@ +/* jshint indent: 2 */ + +const Sequelize = require('sequelize'); +module.exports = function(sequelize, DataTypes) { + return sequelize.define( + 'employees', + { + id: { + autoIncrement: true, + type: DataTypes.INTEGER, + allowNull: false, + primaryKey: true + }, + employee_name: { + type: DataTypes.STRING(36), + allowNull: false, + unique: 'employee_name' + }, + office_id: { + type: DataTypes.INTEGER, + allowNull: true, + references: { + model: 'offices', + key: 'id' + }, + unique: 'employees_ibfk_1' + }, + updated_at: { + type: DataTypes.DATE, + allowNull: true, + defaultValue: Sequelize.literal('CURRENT_TIMESTAMP') + } + }, + { + sequelize, + tableName: 'employees', + timestamps: false + } + ); +}; diff --git a/aws/apigateway-aurora-lambda/models/index.js b/aws/apigateway-aurora-lambda/models/index.js index 04ca42d..f024515 100644 --- a/aws/apigateway-aurora-lambda/models/index.js +++ b/aws/apigateway-aurora-lambda/models/index.js @@ -28,8 +28,14 @@ function getDB() { ); } const uuids = require('@models/uuids'); + const employees = require('@models/employees'); + const offices = require('@models/offices'); + const employeeOffice = require('@models/employee_office'); db.uuids = uuids(sequelize, Sequelize.DataTypes); + db.employees = employees(sequelize, Sequelize.DataTypes); + db.offices = offices(sequelize, Sequelize.DataTypes); + db.employeeOffice = employeeOffice(sequelize, Sequelize.DataTypes); Object.keys(db).forEach(modelName => { if (db[modelName].associate) { diff --git a/aws/apigateway-aurora-lambda/models/offices.js b/aws/apigateway-aurora-lambda/models/offices.js new file mode 100644 index 0000000..f8de38e --- /dev/null +++ b/aws/apigateway-aurora-lambda/models/offices.js @@ -0,0 +1,44 @@ +/* jshint indent: 2 */ + +const Sequelize = require('sequelize'); +module.exports = function(sequelize, DataTypes) { + return sequelize.define( + 'offices', + { + id: { + autoIncrement: true, + type: DataTypes.INTEGER, + allowNull: false, + primaryKey: true + }, + office_name: { + type: DataTypes.STRING(36), + allowNull: false, + unique: 'office_name' + }, + office_address: { + type: DataTypes.STRING(36), + allowNull: false + }, + employee_id: { + type: DataTypes.INTEGER, + allowNull: true, + references: { + model: 'employees', + key: 'id' + }, + unique: 'offices_employee_id_fk' + }, + updated_at: { + type: DataTypes.DATE, + allowNull: true, + defaultValue: Sequelize.literal('CURRENT_TIMESTAMP') + } + }, + { + sequelize, + tableName: 'offices', + timestamps: false + } + ); +}; diff --git a/aws/apigateway-aurora-lambda/models/uuids.js b/aws/apigateway-aurora-lambda/models/uuids.js index 4da659f..265eb72 100644 --- a/aws/apigateway-aurora-lambda/models/uuids.js +++ b/aws/apigateway-aurora-lambda/models/uuids.js @@ -1,26 +1,31 @@ -const moment = require('moment'); +/* jshint indent: 2 */ + +const Sequelize = require('sequelize'); module.exports = function(sequelize, DataTypes) { return sequelize.define( 'uuids', { id: { - type: DataTypes.INTEGER(11), - allowNull: true, + autoIncrement: true, + type: DataTypes.INTEGER, + allowNull: false, primaryKey: true }, uuid: { type: DataTypes.STRING(36), - allowNull: false + allowNull: false, + unique: 'uuid' }, - updatedAt: { - field: 'updated_at', + updated_at: { type: DataTypes.DATE, allowNull: true, - defaultValue: moment().format('YYYY-MM-DD HH:mm:s:ss') + defaultValue: Sequelize.literal('CURRENT_TIMESTAMP') } }, { - tableName: 'uuids' + sequelize, + tableName: 'uuids', + timestamps: false } ); }; diff --git a/aws/apigateway-aurora-lambda/serverless.yml b/aws/apigateway-aurora-lambda/serverless.yml index 0422bbd..1e57877 100644 --- a/aws/apigateway-aurora-lambda/serverless.yml +++ b/aws/apigateway-aurora-lambda/serverless.yml @@ -31,6 +31,45 @@ functions: role: LambdaRDSRole events: - http: PUT uuids + getEmployees: + handler: functions/employee/get-employees/index.handler + role: LambdaDDBRole + events: + - http: GET employees + + getEmployee: + handler: functions/employee/get-employee/index.handler + role: LambdaDDBRole + events: + - http: GET employee + + getOffices: + handler: functions/office/get-offices/index.handler + role: LambdaDDBRole + + getOffice: + handler: functions/office/get-office/index.handler + role: LambdaDDBRole + events: + - http: GET office + + putEmployee: + handler: functions/employee/put-employee/index.handler + role: LambdaDDBRole + events: + - http: PUT employee + + putOffice: + handler: functions/office/put-office/index.handler + role: LambdaDDBRole + events: + - http: PUT office + joinEmployeeAndOffice: + handler: functions/employee-office/join/index.handler + role: LambdaDDBRole + events: + - http: PUT join-employee-office + custom: scripts: hooks: @@ -41,14 +80,14 @@ custom: DB_NAME: uuids_${env:RDS_PREFIX}_${env:STAGE} resources: Resources: -# internet gateway + # internet gateway ServerlessInternetGateway: Type: AWS::EC2::InternetGateway Properties: Tags: - Key: 'Name' Value: 'ServerlessInternetGateway' -# route table + # route table RouteTablePublic: Type: AWS::EC2::RouteTable Properties: @@ -65,7 +104,7 @@ resources: Ref: ServerlessInternetGateway RouteTableId: Ref: RouteTablePublic -#route table associations + #route table associations RouteTableAssociationSubnetA: Type: AWS::EC2::SubnetRouteTableAssociation Properties: @@ -87,7 +126,7 @@ resources: Ref: RouteTablePublic SubnetId: Ref: ServerlessSubnetC -# security group + # security group ServerlessSecurityGroup: DependsOn: - ServerlessVPC @@ -104,7 +143,7 @@ resources: Tags: - Key: 'Name' Value: 'ServerlessSecurityGroup' -# subnets + # subnets ServerlessSubnetGroup: DependsOn: - ServerlessSubnetA @@ -157,7 +196,7 @@ resources: Tags: - Key: 'Name' Value: 'ServerlessSubnetC' -# vpc + # vpc ServerlessVPC: Type: AWS::EC2::VPC Properties: @@ -175,7 +214,7 @@ resources: Ref: ServerlessVPC InternetGatewayId: Ref: ServerlessInternetGateway -# rds + # rds WednesdayRDSCluster: Type: AWS::RDS::DBCluster DependsOn: @@ -230,19 +269,19 @@ resources: Properties: Description: Managed policy to provide access to the RDS PolicyDocument: - Version: "2012-10-17" + Version: '2012-10-17' Statement: - - Effect: "Allow" + - Effect: 'Allow' Action: - rds:* - Resource: "arn:aws:rds:${env:REGION}:${env:ACCOUNT_ID}:table/*_${env:RDS_PREFIX}_${env:STAGE}" + Resource: 'arn:aws:rds:${env:REGION}:${env:ACCOUNT_ID}:table/*_${env:RDS_PREFIX}_${env:STAGE}' LambdaRDSRole: Type: AWS::IAM::Role - Description: "An IAM Role to allow Lambdas to access DDB" + Description: 'An IAM Role to allow Lambdas to access DDB' Properties: RoleName: ${self:service.name}-${self:provider.stage}-lambda-ddb-role AssumeRolePolicyDocument: - Version: "2012-10-17" + Version: '2012-10-17' Statement: - Effect: Allow Action: @@ -258,4 +297,4 @@ resources: WednesdayRDSPort: Description: Port of the wednesday RDS instance Value: - Fn::GetAtt: [WednesdayRDSCluster, Endpoint.Port] \ No newline at end of file + Fn::GetAtt: [WednesdayRDSCluster, Endpoint.Port] diff --git a/aws/apigateway-aurora-lambda/setup-lambda.sh b/aws/apigateway-aurora-lambda/setup-lambda.sh new file mode 100755 index 0000000..266149c --- /dev/null +++ b/aws/apigateway-aurora-lambda/setup-lambda.sh @@ -0,0 +1,9 @@ +#!/bin/bash +set -x +# Create a lambda +npm init -y +touch index.js +touch data.json +mkdir tests +cd tests +touch index.test.js \ No newline at end of file diff --git a/aws/apigateway-aurora-lambda/utils/index.js b/aws/apigateway-aurora-lambda/utils/index.js new file mode 100644 index 0000000..6f1dfad --- /dev/null +++ b/aws/apigateway-aurora-lambda/utils/index.js @@ -0,0 +1,23 @@ +import get from 'lodash/get'; + +export const failure = (callback, error) => { + console.log('failure', error); + return callback(get(error, 'message', 'Something went wrong. Please contact support@bankshift.com')); +}; + +export const success = (callback, data) => { + console.log('success', JSON.stringify(data)); + return callback(null, data); +}; + +export const getSystemId = event => { + if (!event.headers['x-ws-system-id']) { + return null; + } + return { systemId: event.headers['x-ws-system-id'] }; +}; + +export const logHandler = (event, callback) => { + console.log(JSON.stringify({ event })); + return callback(event); +}; diff --git a/aws/apigateway-aurora-lambda/utils/testUtils.js b/aws/apigateway-aurora-lambda/utils/testUtils.js index 639e642..affee00 100644 --- a/aws/apigateway-aurora-lambda/utils/testUtils.js +++ b/aws/apigateway-aurora-lambda/utils/testUtils.js @@ -5,10 +5,44 @@ export function configDB() { const DBConnectionMock = new SequelizeMock(); const uuidsMock = DBConnectionMock.define('uuids', { uuid: CONSTANTS.uuid, updated_at: CONSTANTS.updatedAt }); + const employeesMock = DBConnectionMock.define('employees', { + employee_name: CONSTANTS.employee.employee_name, + office_id: null, + updated_at: CONSTANTS.updatedAt + }); + const officesMock = DBConnectionMock.define('offices', { + office_name: CONSTANTS.office.office_name, + office_address: CONSTANTS.office.office_address, + employee_id: null, + updated_at: CONSTANTS.updatedAt + }); + const employeeOfficeMock = DBConnectionMock.define('employeeOffice', { + office_name: CONSTANTS.office.office_address, + office_address: CONSTANTS.office.office_address, + employee_name: CONSTANTS.employee.employee_name, + employee_id: 1, + office_id: 1 + }); + uuidsMock.findByPk = query => uuidsMock.findById(query); uuidsMock.count = () => 1; + employeesMock.findByPk = query => employeesMock.findById(query); + employeesMock.findAll = query => employeesMock.findAll(query); + employeesMock.upsert = query => employeesMock.upsert(query); + + officesMock.findByPk = query => officesMock.findById(query); + officesMock.findAll = query => officesMock.findAll(query); + officesMock.upsert = query => officesMock.upsert(query); + + employeeOfficeMock.findAll = query => employeeOfficeMock.findAll(query); + employeeOfficeMock.upsert = query => employeeOfficeMock.upsert(query); + employeeOfficeMock.create = query => employeeOfficeMock.create(query); + return { - uuids: uuidsMock + uuids: uuidsMock, + employees: employeesMock, + offices: officesMock, + employeeOffice: employeeOfficeMock }; } diff --git a/aws/apigateway-aurora-lambda/utils/tests/indes.test.js b/aws/apigateway-aurora-lambda/utils/tests/indes.test.js new file mode 100644 index 0000000..8dbd393 --- /dev/null +++ b/aws/apigateway-aurora-lambda/utils/tests/indes.test.js @@ -0,0 +1,81 @@ +import { failure, success, logHandler, getSystemId } from '@utils'; +describe('utils/index', () => { + let mocks; + beforeEach(() => { + mocks = { + callback: jest.fn() + }; + jest.spyOn(mocks, 'callback'); + jest.spyOn(console, 'log'); + }); + + describe('failure', () => { + it('should print the error and invoke the callback', () => { + const error = new Error('some error'); + failure(mocks.callback, error); + + // ensure that the error is being printed + expect(console.log.mock.calls[0][0]).toBe('failure'); + expect(console.log.mock.calls[0][1]).toBe(error); + + // check if callback has been invoked + expect(mocks.callback).toBeCalled(); + + // check the parameters that have been supplied to the callback + expect(mocks.callback.mock.calls[0][0]).toBe(error.message); + expect(mocks.callback.mock.calls[0][1]).toBe(undefined); + }); + }); + + describe('success', () => { + it('should print the data and invoke the callback', () => { + const data = { otpSentAt: '123123' }; + success(mocks.callback, data); + + // ensure that the data is being printed + expect(console.log.mock.calls[0][0]).toBe('success'); + expect(console.log.mock.calls[0][1]).toBe(JSON.stringify(data)); + + // check if callback has been invoked + expect(mocks.callback).toBeCalled(); + + // check the parameters that have been supplied to callback + expect(mocks.callback.mock.calls[0][0]).toBe(null); + expect(mocks.callback.mock.calls[0][1]).toBe(data); + }); + }); + + describe('logHandler', () => { + let mocks; + beforeEach(() => { + mocks = { + callback: jest.fn() + }; + jest.spyOn(mocks, 'callback'); + }); + it('should log the event and return callback', async () => { + const mockEvent = {}; + logHandler(mockEvent, mocks.callback); + expect(mocks.callback.mock.calls.length).toBe(1); + }); + }); + + describe('getSystemId', () => { + it('should return getSystemId', async () => { + const event = { + headers: { 'x-ws-system-id': 'WS' } + }; + const response = getSystemId(event); + expect(response).toEqual({ + systemId: event.headers['x-ws-system-id'] + }); + }); + it('should return null when no auth header is present', async () => { + const event = { + headers: {} + }; + const response = getSystemId(event); + expect(response).toEqual(null); + }); + }); +});