Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Apigateway aurora lambda #20

Open
wants to merge 6 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 17 additions & 10 deletions aws/apigateway-aurora-lambda/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,13 @@
<div>
<a href="https://www.wednesday.is?utm_source=gthb&utm_medium=repo&utm_campaign=serverless" align="left"><img src="https://uploads-ssl.webflow.com/5ee36ce1473112550f1e1739/5f5879492fafecdb3e5b0e75_wednesday_logo.svg"></a>
<p>
<h1 align="left">UUID</h1>
<h1 align="left">Offices and Employees</h1>
</p>
<p>
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.
</p>

___

---

<p>
<h4>
Expand All @@ -27,19 +26,27 @@
</a>
</div>

___
---

<span>We’re always looking for people who value their work, so come and join us. <a href="https://www.wednesday.is/hiring">We are hiring!</a></span>

<span>We’re always looking for people who value their work, so come and join us. <a href="https://www.wednesday.is/hiring">We are hiring!</a></span>
</div>

## 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=<host> --stage=<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.
19 changes: 18 additions & 1 deletion aws/apigateway-aurora-lambda/__mocks__/constants.js
Original file line number Diff line number Diff line change
Expand Up @@ -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"}}}';
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{
"body": "{\"employeeId\":1, \"officeId\":2}",
"headers": {
"content-type": "application/json",
"x-ws-system-id": "WS"
}
}
Original file line number Diff line number Diff line change
@@ -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);
}
});
Original file line number Diff line number Diff line change
@@ -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"
}
Original file line number Diff line number Diff line change
@@ -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);
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{
"body": "{\"employeeId\":\"1\"}",
"headers": {
"content-type": "application/json",
"x-ws-system-id": "WS"
}
}
Original file line number Diff line number Diff line change
@@ -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);
}
});
Original file line number Diff line number Diff line change
@@ -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"
}
Original file line number Diff line number Diff line change
@@ -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);
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{
"body": "{\"officeId\":1}",
"headers": {
"content-type": "application/json",
"x-ws-system-id": "WS"
}
}
Original file line number Diff line number Diff line change
@@ -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);
}
});
Original file line number Diff line number Diff line change
@@ -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"
}
Original file line number Diff line number Diff line change
@@ -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);
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{
"body": "{\"employeeName\":\"Mac\"}",
"headers": {
"content-type": "application/json",
"x-ws-system-id": "WS"
}
}
Original file line number Diff line number Diff line change
@@ -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);
}
});
Original file line number Diff line number Diff line change
@@ -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"
}
Loading