diff --git a/package-lock.json b/package-lock.json index e00b896..f66ce16 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9,11 +9,13 @@ "version": "0.1.0", "dependencies": { "@types/aws-lambda": "^8.10.83", + "@types/node-fetch": "^2.6.11", "jsonschema": "^1.4.0", + "node-fetch": "^3.3.2", "source-map-support": "^0.5.16" }, "devDependencies": { - "@types/node": "10.17.27", + "@types/node": "^10.17.27", "@typescript-eslint/eslint-plugin": "4.14.0", "@typescript-eslint/parser": "4.14.0", "@vitest/coverage-istanbul": "0.34.3", @@ -1128,7 +1130,31 @@ "version": "10.17.27", "resolved": "https://registry.npmjs.org/@types/node/-/node-10.17.27.tgz", "integrity": "sha512-J0oqm9ZfAXaPdwNXMMgAhylw5fhmXkToJd06vuDUSAgEDZ/n/69/69UmyBZbc+zT34UnShuDSBqvim3SPnozJg==", - "dev": true + "license": "MIT" + }, + "node_modules/@types/node-fetch": { + "version": "2.6.11", + "resolved": "https://registry.npmjs.org/@types/node-fetch/-/node-fetch-2.6.11.tgz", + "integrity": "sha512-24xFj9R5+rfQJLRyM56qh+wnVSYhyXC2tkoBndtY0U+vubqNsYXGjufB2nn8Q6gt0LrARwL6UBtMCSVCwl4B1g==", + "license": "MIT", + "dependencies": { + "@types/node": "*", + "form-data": "^4.0.0" + } + }, + "node_modules/@types/node-fetch/node_modules/form-data": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz", + "integrity": "sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==", + "license": "MIT", + "dependencies": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "mime-types": "^2.1.12" + }, + "engines": { + "node": ">= 6" + } }, "node_modules/@typescript-eslint/eslint-plugin": { "version": "4.14.0", @@ -1864,10 +1890,7 @@ "node_modules/asynckit": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", - "integrity": "sha1-x57Zf380y48robyXkLzDZkdLS3k=", - "dev": true, - "optional": true, - "peer": true + "integrity": "sha1-x57Zf380y48robyXkLzDZkdLS3k=" }, "node_modules/aws-sign2": { "version": "0.7.0", @@ -2165,9 +2188,6 @@ "version": "1.0.8", "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", - "dev": true, - "optional": true, - "peer": true, "dependencies": { "delayed-stream": "~1.0.0" }, @@ -2287,7 +2307,6 @@ "version": "4.0.1", "resolved": "https://registry.npmjs.org/data-uri-to-buffer/-/data-uri-to-buffer-4.0.1.tgz", "integrity": "sha512-0R9ikRb668HB7QDxT1vkpuUBtqc53YyAwMwGeUFKRojY/NWKvdZ+9UYtRfGmhqNbRkTSVpMbmyhXipFFv2cb/A==", - "dev": true, "license": "MIT", "engines": { "node": ">= 12" @@ -2356,9 +2375,6 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", "integrity": "sha1-3zrhmayt+31ECqrgsp4icrJOxhk=", - "dev": true, - "optional": true, - "peer": true, "engines": { "node": ">=0.4.0" } @@ -3067,7 +3083,6 @@ "version": "3.2.0", "resolved": "https://registry.npmjs.org/fetch-blob/-/fetch-blob-3.2.0.tgz", "integrity": "sha512-7yAQpD2UMJzLi1Dqv7qFYnPbaPx7ZfFK6PiIxQ4PfkGPyNyl2Ugx+a/umUonmKqjhM4DnfbMvdX6otXq83soQQ==", - "dev": true, "funding": [ { "type": "github", @@ -3191,7 +3206,6 @@ "version": "4.0.10", "resolved": "https://registry.npmjs.org/formdata-polyfill/-/formdata-polyfill-4.0.10.tgz", "integrity": "sha512-buewHzMvYL29jdeQTVILecSaZKnt/RJWjoZCF5OW60Z67/GmSLBkOFM7qh1PI3zFNtJbaZL5eQu1vLfazOwj4g==", - "dev": true, "license": "MIT", "dependencies": { "fetch-blob": "^3.1.2" @@ -4272,8 +4286,6 @@ "version": "1.47.0", "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.47.0.tgz", "integrity": "sha512-QBmA/G2y+IfeS4oktet3qRZ+P5kPhCKRXxXnQEudYqUaEioAU1/Lq2us3D/t1Jfo4hE9REQPrbB7K5sOczJVIw==", - "dev": true, - "peer": true, "engines": { "node": ">= 0.6" } @@ -4282,8 +4294,6 @@ "version": "2.1.30", "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.30.tgz", "integrity": "sha512-crmjA4bLtR8m9qLpHvgxSChT+XoSlZi8J4n/aIdn3z92e/U47Z0V/yl+Wh9W046GgFVAmoNR/fmdbZYcSSIUeg==", - "dev": true, - "peer": true, "dependencies": { "mime-db": "1.47.0" }, @@ -4431,7 +4441,6 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/node-domexception/-/node-domexception-1.0.0.tgz", "integrity": "sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ==", - "dev": true, "funding": [ { "type": "github", @@ -4451,7 +4460,6 @@ "version": "3.3.2", "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-3.3.2.tgz", "integrity": "sha512-dRB78srN/l6gqWulah9SrxeYnxeddIG30+GOqK/9OlLVyLg3HPnr6SqOWTWOXKRwC2eGYCkZ59NNuSgvSrpgOA==", - "dev": true, "license": "MIT", "dependencies": { "data-uri-to-buffer": "^4.0.0", @@ -6826,7 +6834,6 @@ "version": "3.3.3", "resolved": "https://registry.npmjs.org/web-streams-polyfill/-/web-streams-polyfill-3.3.3.tgz", "integrity": "sha512-d2JWLCivmZYTSIoge9MsgFCZrt571BikcWGYkjC1khllbTeDlGqZ2D8vD8E/lJa8WGWbb7Plm8/XJYV7IJHZZw==", - "dev": true, "license": "MIT", "engines": { "node": ">= 8" @@ -7985,8 +7992,28 @@ "@types/node": { "version": "10.17.27", "resolved": "https://registry.npmjs.org/@types/node/-/node-10.17.27.tgz", - "integrity": "sha512-J0oqm9ZfAXaPdwNXMMgAhylw5fhmXkToJd06vuDUSAgEDZ/n/69/69UmyBZbc+zT34UnShuDSBqvim3SPnozJg==", - "dev": true + "integrity": "sha512-J0oqm9ZfAXaPdwNXMMgAhylw5fhmXkToJd06vuDUSAgEDZ/n/69/69UmyBZbc+zT34UnShuDSBqvim3SPnozJg==" + }, + "@types/node-fetch": { + "version": "2.6.11", + "resolved": "https://registry.npmjs.org/@types/node-fetch/-/node-fetch-2.6.11.tgz", + "integrity": "sha512-24xFj9R5+rfQJLRyM56qh+wnVSYhyXC2tkoBndtY0U+vubqNsYXGjufB2nn8Q6gt0LrARwL6UBtMCSVCwl4B1g==", + "requires": { + "@types/node": "*", + "form-data": "^4.0.0" + }, + "dependencies": { + "form-data": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz", + "integrity": "sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==", + "requires": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "mime-types": "^2.1.12" + } + } + } }, "@typescript-eslint/eslint-plugin": { "version": "4.14.0", @@ -8555,10 +8582,7 @@ "asynckit": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", - "integrity": "sha1-x57Zf380y48robyXkLzDZkdLS3k=", - "dev": true, - "optional": true, - "peer": true + "integrity": "sha1-x57Zf380y48robyXkLzDZkdLS3k=" }, "aws-sign2": { "version": "0.7.0", @@ -8785,9 +8809,6 @@ "version": "1.0.8", "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", - "dev": true, - "optional": true, - "peer": true, "requires": { "delayed-stream": "~1.0.0" } @@ -8892,8 +8913,7 @@ "data-uri-to-buffer": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/data-uri-to-buffer/-/data-uri-to-buffer-4.0.1.tgz", - "integrity": "sha512-0R9ikRb668HB7QDxT1vkpuUBtqc53YyAwMwGeUFKRojY/NWKvdZ+9UYtRfGmhqNbRkTSVpMbmyhXipFFv2cb/A==", - "dev": true + "integrity": "sha512-0R9ikRb668HB7QDxT1vkpuUBtqc53YyAwMwGeUFKRojY/NWKvdZ+9UYtRfGmhqNbRkTSVpMbmyhXipFFv2cb/A==" }, "data-urls": { "version": "2.0.0", @@ -8943,10 +8963,7 @@ "delayed-stream": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", - "integrity": "sha1-3zrhmayt+31ECqrgsp4icrJOxhk=", - "dev": true, - "optional": true, - "peer": true + "integrity": "sha1-3zrhmayt+31ECqrgsp4icrJOxhk=" }, "dir-glob": { "version": "3.0.1", @@ -9500,7 +9517,6 @@ "version": "3.2.0", "resolved": "https://registry.npmjs.org/fetch-blob/-/fetch-blob-3.2.0.tgz", "integrity": "sha512-7yAQpD2UMJzLi1Dqv7qFYnPbaPx7ZfFK6PiIxQ4PfkGPyNyl2Ugx+a/umUonmKqjhM4DnfbMvdX6otXq83soQQ==", - "dev": true, "requires": { "node-domexception": "^1.0.0", "web-streams-polyfill": "^3.0.3" @@ -9583,7 +9599,6 @@ "version": "4.0.10", "resolved": "https://registry.npmjs.org/formdata-polyfill/-/formdata-polyfill-4.0.10.tgz", "integrity": "sha512-buewHzMvYL29jdeQTVILecSaZKnt/RJWjoZCF5OW60Z67/GmSLBkOFM7qh1PI3zFNtJbaZL5eQu1vLfazOwj4g==", - "dev": true, "requires": { "fetch-blob": "^3.1.2" } @@ -10394,16 +10409,12 @@ "mime-db": { "version": "1.47.0", "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.47.0.tgz", - "integrity": "sha512-QBmA/G2y+IfeS4oktet3qRZ+P5kPhCKRXxXnQEudYqUaEioAU1/Lq2us3D/t1Jfo4hE9REQPrbB7K5sOczJVIw==", - "dev": true, - "peer": true + "integrity": "sha512-QBmA/G2y+IfeS4oktet3qRZ+P5kPhCKRXxXnQEudYqUaEioAU1/Lq2us3D/t1Jfo4hE9REQPrbB7K5sOczJVIw==" }, "mime-types": { "version": "2.1.30", "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.30.tgz", "integrity": "sha512-crmjA4bLtR8m9qLpHvgxSChT+XoSlZi8J4n/aIdn3z92e/U47Z0V/yl+Wh9W046GgFVAmoNR/fmdbZYcSSIUeg==", - "dev": true, - "peer": true, "requires": { "mime-db": "1.47.0" } @@ -10506,14 +10517,12 @@ "node-domexception": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/node-domexception/-/node-domexception-1.0.0.tgz", - "integrity": "sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ==", - "dev": true + "integrity": "sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ==" }, "node-fetch": { "version": "3.3.2", "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-3.3.2.tgz", "integrity": "sha512-dRB78srN/l6gqWulah9SrxeYnxeddIG30+GOqK/9OlLVyLg3HPnr6SqOWTWOXKRwC2eGYCkZ59NNuSgvSrpgOA==", - "dev": true, "requires": { "data-uri-to-buffer": "^4.0.0", "fetch-blob": "^3.1.4", @@ -12059,8 +12068,7 @@ "web-streams-polyfill": { "version": "3.3.3", "resolved": "https://registry.npmjs.org/web-streams-polyfill/-/web-streams-polyfill-3.3.3.tgz", - "integrity": "sha512-d2JWLCivmZYTSIoge9MsgFCZrt571BikcWGYkjC1khllbTeDlGqZ2D8vD8E/lJa8WGWbb7Plm8/XJYV7IJHZZw==", - "dev": true + "integrity": "sha512-d2JWLCivmZYTSIoge9MsgFCZrt571BikcWGYkjC1khllbTeDlGqZ2D8vD8E/lJa8WGWbb7Plm8/XJYV7IJHZZw==" }, "webidl-conversions": { "version": "6.1.0", diff --git a/package.json b/package.json index cf35aa4..e50ccc7 100644 --- a/package.json +++ b/package.json @@ -9,7 +9,7 @@ "compile-schemas": "json2ts -i ./external-service/schema.json -o src/product.ts" }, "devDependencies": { - "@types/node": "10.17.27", + "@types/node": "^10.17.27", "@typescript-eslint/eslint-plugin": "4.14.0", "@typescript-eslint/parser": "4.14.0", "@vitest/coverage-istanbul": "0.34.3", @@ -29,7 +29,9 @@ }, "dependencies": { "@types/aws-lambda": "^8.10.83", + "@types/node-fetch": "^2.6.11", "jsonschema": "^1.4.0", + "node-fetch": "^3.3.2", "source-map-support": "^0.5.16" } } diff --git a/src/index.ts b/src/index.ts index 64dc326..d515af8 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,13 +1,19 @@ import { KinesisStreamEvent } from 'aws-lambda'; import { Processor } from './processor'; +import { KinesisStreamRecord } from 'aws-lambda/trigger/kinesis-stream'; export const handler = (event: KinesisStreamEvent) => { - console.log('!!'); - // console.log(event); + const url = process.env.PUBLISH_URL || ''; - const processor = new Processor(); + const processor = new Processor(url); - for (const elm of event.Records) { - processor.processEvent(elm); - } + event.Records.map(async (elm: KinesisStreamRecord) => { + const processorResponse = processor.processEvent(elm); + + if (processorResponse) { + const postRes = await processor.sendPayload(processorResponse); + + console.log('postRes: ', postRes); + } + }); }; diff --git a/src/processor.ts b/src/processor.ts index 420e650..b61b083 100644 --- a/src/processor.ts +++ b/src/processor.ts @@ -1,32 +1,45 @@ import { KinesisStreamRecord } from 'aws-lambda/trigger/kinesis-stream'; -import { EventBlock } from './structs'; +import { EventBlock, PostResult } from './structs'; import { Product } from './product'; export class Processor { + url = ''; + + constructor(url: string) { + if (!url) throw new Error('url must be a string'); + + this.url = url; + } + + /** + * Process the event + * @param event + */ public processEvent(event: KinesisStreamRecord) { - // console.log(JSON.stringify(event)); - let data: EventBlock | null = this.decodePayload(event.kinesis.data); - // console.log('DecodePayload:: ', JSON.stringify(data)); - if (!data) { return null; } - // console.log('!!data.type:', data.type); switch (data.type) { case 'booking_requested': - // console.log('booking_requested'); - const product: Product | null = this.transformPayload(data); - console.log('Product:', JSON.stringify(product)); - this.sendPayload(product); - break; + const product: Product | null = this.transformBookingRequestPayload( + data + ); + + return product; + default: } } - public decodePayload(data: any): EventBlock | null { + /** + * Decodes the Data payload which should be in base64 format + * @param data + * @private + */ + private decodePayload(data: any): EventBlock | null { if (data === null || data === '') { return null; } @@ -39,10 +52,12 @@ export class Processor { } } - public transformPayload(data: EventBlock): Product | null { - console.log('Transform and roll out'); - console.log('Data:', JSON.stringify(data)); - + /** + * Transformer specifically for Booking Requests + * @param data + * @private + */ + private transformBookingRequestPayload(data: EventBlock): Product | null { const { booking_requested } = data; if (booking_requested) { @@ -60,9 +75,35 @@ export class Processor { return null; } - public sendPayload(data: Product | null): boolean { - console.log('Sendpayload', data); + /** + * Send the payload. + * @param data + */ + public async sendPayload(data: Product | null | undefined) { + if (!data) return { status: 0, message: 'Empty Payload' }; - return false; + // Using fetch to simplify the http post + const response = await fetch(this.url, { + method: 'POST', + body: JSON.stringify(data), + headers: { + 'Content-Type': 'application/json', + Accept: 'application/json', + }, + }); + + const result: PostResult = { + status: response.status, + message: response.statusText, + }; + + if (!response.ok) { + return { + status: response.status, + message: response.statusText, + }; + } + + return result; } } diff --git a/src/structs.ts b/src/structs.ts index 5131ab3..50940fe 100644 --- a/src/structs.ts +++ b/src/structs.ts @@ -17,3 +17,8 @@ export interface BookingRequested { orderId: number; product_provider: string; } + +export interface PostResult { + status: number; + message: string; +} \ No newline at end of file diff --git a/test/index.test.ts b/test/index.test.ts index be6a627..7a2a417 100644 --- a/test/index.test.ts +++ b/test/index.test.ts @@ -1,7 +1,7 @@ import { Processor } from '../src/processor'; describe('myTSTest', () => { - const processor = new Processor(); + const processor = new Processor('http://localhost:3000'); describe('decodePayload', () => { const properDataBase64 = @@ -134,12 +134,12 @@ describe('myTSTest', () => { }, }); - expect(processor.processEvent(tempKinesisData)).toBeUndefined(); + expect(processor.processEvent(tempKinesisData)).toBeFalsy(); } }); }); - describe('transformPayload', () => { + describe('transformBookingRequestPayload', () => { const validData = { id: 'a4388132-1492-11ec-a0b2-c78ffbd69347', partitionKey: '2fce53f1-1daf-4247-9245-0ad78d0b0d2e', @@ -171,11 +171,55 @@ describe('myTSTest', () => { }; it('should pass', () => { - expect(processor.transformPayload(validData)).toEqual(validTransformed); + expect(processor.transformBookingRequestPayload(validData)).toEqual(validTransformed); }); it('should return a null for an item which does not contain a booking_request', () => { - expect(processor.transformPayload(invalidData)).toBeNull(); + expect(processor.transformBookingRequestPayload(invalidData)).toBeNull(); + }); + }); + + describe('transformBookingRequestPayload', () => { + const validTransformed = { + product_order_id_buyer: 10017, + timestamp: '2021-09-13T13:00:59.459Z', + product_provider_buyer: 'Stena Line', + }; + + const invalidTransformed = { + not_product_order_id_buyer: 10017, + timestamp: '2021-09-13T13:00:59.459Z', + not_product_provider_buyer: 'Stena Line', + }; + + it('should pass', async () => { + expect(await processor.sendPayload(validTransformed)).toEqual({ + status: 200, + message: 'OK', + }); + }); + + it('should reject null data', async () => { + expect(await processor.sendPayload(null)).toEqual({ + status: 0, + message: 'Empty Payload', + }); + }); + + it('should handle an error response from the server', async () => { + // @ts-ignore + expect(await processor.sendPayload(invalidTransformed)).toEqual({ + status: 400, + message: 'Bad Request', + }); + }); + }); + + describe('Processor Construction', () => { + it('should return an error if no url is passed on construction', () => { + // @ts-ignore + + expect(() => new Processor()).toThrow(); }); }); });