-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
task3.1: create lambda function for products list
✔️ Create a lambda function called `getProductsList` of Product Service which will be triggered by the HTTP GET method. ✔️ The requested URL should be `/products`. ✔️ The response from the lambda should be a full array of products. ✔️ This endpoint should be integrated with Frontend app for Product List Page representation Additional tasks: ✔️ Async/await is used in lambda functions ✔️ ES6 modules are used for Product Service implementation ✔️ ESBuild is configured for Product Service. Extra effort: * api gateway is defined as separate service to reuse for all future lambdas * app is build on deploy stage with forked scriptable plugin * api gateway url is passed to frontend app build
- Loading branch information
Showing
28 changed files
with
1,318 additions
and
325 deletions.
There are no files selected for viewing
Large diffs are not rendered by default.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,138 @@ | ||
"use strict"; | ||
|
||
const vm = require("vm"); | ||
const fs = require("fs"); | ||
const Module = require("module"); | ||
const path = require("path"); | ||
const Bluebird = require("bluebird"); | ||
const { execSync } = require("child_process"); | ||
|
||
// same as https://github.com/weixu365/serverless-scriptable-plugin | ||
// but without legacy config namespace support and with proper interpolation of parameters in scripts | ||
class Scriptable { | ||
constructor(serverless, options) { | ||
this.serverless = serverless; | ||
this.options = options; | ||
this.hooks = {}; | ||
this.commands = {}; | ||
|
||
this.stdin = process.stdin; | ||
this.stdout = process.stdout; | ||
this.stderr = process.stderr; | ||
this.showCommands = true; | ||
|
||
const scriptable = this.getScripts("scriptable") || {}; | ||
|
||
if (this.isFalse(scriptable.showCommands)) { | ||
this.showCommands = false; | ||
} | ||
|
||
if (this.isFalse(scriptable.showStdoutOutput)) { | ||
console.log( | ||
"Not showing command output because showStdoutOutput is false" | ||
); | ||
this.stdout = "ignore"; | ||
} | ||
|
||
if (this.isFalse(scriptable.showStderrOutput)) { | ||
console.log( | ||
"Not showing command error output because showStderrOutput is false" | ||
); | ||
this.stderr = "ignore"; | ||
} | ||
|
||
this.setupHooks(scriptable.hooks); | ||
this.setupCustomCommands(scriptable.commands); | ||
} | ||
|
||
setupHooks(hooks = {}) { | ||
// Hooks are run at serverless lifecycle events. | ||
Object.keys(hooks).forEach((event) => { | ||
this.hooks[event] = this.runScript("hooks", event); | ||
}, this); | ||
} | ||
|
||
setupCustomCommands(commands = {}) { | ||
// Custom Serverless commands would run by `npx serverless <command-name>` | ||
Object.keys(commands).forEach((name) => { | ||
this.hooks[`${name}:command`] = this.runScript("commands", name); | ||
|
||
this.commands[name] = { | ||
usage: `Run ${commands[name]}`, | ||
lifecycleEvents: ["command"], | ||
}; | ||
}, this); | ||
} | ||
|
||
isFalse(val) { | ||
return val != null && !val; | ||
} | ||
|
||
getScripts(namespace) { | ||
const { custom } = this.serverless.service; | ||
return custom && custom[namespace]; | ||
} | ||
|
||
runScript(type, event) { | ||
return () => { | ||
const eventScript = this.getScripts("scriptable")[type][event]; | ||
const scripts = Array.isArray(eventScript) ? eventScript : [eventScript]; | ||
|
||
return Bluebird.each(scripts, (script) => { | ||
if (fs.existsSync(script) && path.extname(script) === ".js") { | ||
return this.runJavascriptFile(script); | ||
} | ||
|
||
return this.runCommand(script); | ||
}); | ||
}; | ||
} | ||
|
||
runCommand(script) { | ||
if (this.showCommands) { | ||
console.log(`Running command: ${script}`); | ||
} | ||
|
||
return execSync(script, { stdio: [this.stdin, this.stdout, this.stderr] }); | ||
} | ||
|
||
runJavascriptFile(scriptFile) { | ||
if (this.showCommands) { | ||
console.log(`Running javascript file: ${scriptFile}`); | ||
} | ||
|
||
const buildModule = () => { | ||
const m = new Module(scriptFile, module.parent); | ||
m.exports = exports; | ||
m.filename = scriptFile; | ||
m.paths = Module._nodeModulePaths(path.dirname(scriptFile)).concat( | ||
module.paths | ||
); | ||
|
||
return m; | ||
}; | ||
|
||
const sandbox = { | ||
module: buildModule(), | ||
require: (id) => sandbox.module.require(id), | ||
console, | ||
process, | ||
serverless: this.serverless, | ||
options: this.options, | ||
__filename: scriptFile, | ||
__dirname: path.dirname(fs.realpathSync(scriptFile)), | ||
}; | ||
|
||
// See: https://github.com/nodejs/node/blob/7c452845b8d44287f5db96a7f19e7d395e1899ab/lib/internal/modules/cjs/helpers.js#L14 | ||
sandbox.require.resolve = (req) => | ||
Module._resolveFilename(req, sandbox.module); | ||
|
||
const scriptCode = fs.readFileSync(scriptFile); | ||
const script = vm.createScript(scriptCode, scriptFile); | ||
const context = vm.createContext(sandbox); | ||
|
||
return script.runInContext(context); | ||
} | ||
} | ||
|
||
module.exports = Scriptable; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,12 @@ | ||
{ | ||
"name": "@guria.dev/serverless-scriptable", | ||
"version": "1.0.0", | ||
"private": true, | ||
"main": "index.js", | ||
"dependencies": { | ||
"bluebird": "^3.7.2" | ||
}, | ||
"engines": { | ||
"node": ">=16.0.0" | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,3 +1,14 @@ | ||
services: | ||
api-gw: | ||
path: ./services/api-gateway | ||
|
||
products-api: | ||
path: ./services/products-api | ||
params: | ||
apiGatewayRestApiId: ${api-gw.apiGatewayRestApiId} | ||
apiGatewayRestApiRootResourceId: ${api-gw.apiGatewayRestApiRootResourceId} | ||
|
||
shop-frontend-app: | ||
path: ./services/shop-frontend-app | ||
params: | ||
productsApiServiceEndpoint: ${products-api.ServiceEndpoint} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,25 @@ | ||
service: aws-js-practitioner-api | ||
|
||
provider: | ||
name: aws | ||
runtime: nodejs16.x | ||
|
||
custom: | ||
vars: | ||
prefix: ${self:service}-${sls:stage} | ||
|
||
resources: | ||
Resources: | ||
ApiGW: | ||
Type: AWS::ApiGateway::RestApi | ||
Properties: | ||
Name: ${self:custom.vars.prefix} | ||
Description: API Gateway for the AWS JS Practitioner course | ||
|
||
Outputs: | ||
apiGatewayRestApiId: | ||
Value: !Ref ApiGW | ||
|
||
apiGatewayRestApiRootResourceId: | ||
Value: | ||
Fn::GetAtt: [ApiGW, RootResourceId] |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,42 @@ | ||
const path = require("path"); | ||
const projectFilesPaths = [ | ||
path.resolve(__dirname, "tsconfig.json"), | ||
path.resolve(__dirname, "tsconfig.eslint.json"), | ||
]; | ||
|
||
module.exports = { | ||
root: true, | ||
parser: "@typescript-eslint/parser", | ||
env: { | ||
node: true, | ||
browser: false, | ||
}, | ||
overrides: [ | ||
{ | ||
files: ["*.ts", "*.tsx"], | ||
parser: "@typescript-eslint/parser", | ||
parserOptions: { | ||
project: projectFilesPaths, | ||
}, | ||
extends: [ | ||
"plugin:@typescript-eslint/recommended", | ||
"plugin:prettier/recommended", | ||
"prettier", | ||
], | ||
plugins: ["@typescript-eslint", "react", "prettier"], | ||
settings: { | ||
react: { | ||
version: "detect", | ||
}, | ||
}, | ||
}, | ||
{ | ||
files: [".eslintrc.js"], | ||
plugins: ["prettier"], | ||
parserOptions: { | ||
ecmaVersion: 2018, | ||
sourceType: "commonjs", | ||
}, | ||
}, | ||
], | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,11 @@ | ||
# package directories | ||
node_modules | ||
jspm_packages | ||
|
||
# Serverless directories | ||
.serverless | ||
|
||
# esbuild directories | ||
.esbuild | ||
|
||
lib |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,21 @@ | ||
{ | ||
"name": "@guria.dev/aws-js-practitioner-products-api", | ||
"version": "1.0.0", | ||
"scripts": { | ||
"lint": "eslint ." | ||
}, | ||
"engines": { | ||
"node": ">=16.0.0" | ||
}, | ||
"dependencies": { | ||
"@middy/core": "^3.6.1", | ||
"@middy/http-cors": "^3.6.1", | ||
"@middy/http-json-body-parser": "^3.6.1" | ||
}, | ||
"devDependencies": { | ||
"@types/aws-lambda": "^8.10.107", | ||
"@types/node": "^16.11.65", | ||
"esbuild": "^0.15.11", | ||
"serverless-esbuild": "^1.33.0" | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,39 @@ | ||
import type { AWS } from "@serverless/typescript"; | ||
|
||
import getProductsList from "@functions/getProductsList"; | ||
|
||
const serverlessConfiguration: AWS = { | ||
service: "products-api", | ||
frameworkVersion: "3", | ||
plugins: ["serverless-esbuild"], | ||
provider: { | ||
name: "aws", | ||
runtime: "nodejs16.x", | ||
apiGateway: { | ||
minimumCompressionSize: 1024, | ||
shouldStartNameWithService: true, | ||
restApiId: "${param:apiGatewayRestApiId}", | ||
restApiRootResourceId: "${param:apiGatewayRestApiRootResourceId}", | ||
}, | ||
environment: { | ||
AWS_NODEJS_CONNECTION_REUSE_ENABLED: "1", | ||
NODE_OPTIONS: "--enable-source-maps --stack-trace-limit=1000", | ||
}, | ||
}, | ||
functions: { getProductsList }, | ||
package: { individually: true }, | ||
custom: { | ||
esbuild: { | ||
bundle: true, | ||
minify: false, | ||
sourcemap: true, | ||
exclude: ["aws-sdk"], | ||
target: "node16", | ||
define: { "require.resolve": undefined }, | ||
platform: "node", | ||
concurrency: 10, | ||
}, | ||
}, | ||
}; | ||
|
||
module.exports = serverlessConfiguration; |
9 changes: 9 additions & 0 deletions
9
services/products-api/src/functions/getProductsList/handler.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,9 @@ | ||
import { formatJSONResponse, middyfy } from "@libs/api-gateway"; | ||
import { products } from "@guria.dev/aws-js-practitioner-commons/mocks"; | ||
import type { APIGatewayProxyHandler } from "aws-lambda"; | ||
|
||
const handler: APIGatewayProxyHandler = async () => { | ||
return formatJSONResponse(await products); | ||
}; | ||
|
||
export const main = middyfy(handler); |
17 changes: 17 additions & 0 deletions
17
services/products-api/src/functions/getProductsList/index.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,17 @@ | ||
import type { AWS } from "@serverless/typescript"; | ||
import { handlerPath } from "@libs/handler-resolver"; | ||
import { PRODUCTS_API_PATH } from "@guria.dev/aws-js-practitioner-commons/constants/api-paths"; | ||
|
||
const handler: AWS["functions"][string] = { | ||
handler: handlerPath(__dirname, "handler.main"), | ||
events: [ | ||
{ | ||
http: { | ||
method: "get", | ||
path: PRODUCTS_API_PATH, | ||
}, | ||
}, | ||
], | ||
}; | ||
|
||
export default handler; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,16 @@ | ||
import middy from "@middy/core"; | ||
import middyJsonBodyParser from "@middy/http-json-body-parser"; | ||
import middyCors from "@middy/http-cors"; | ||
|
||
export const middyfy = (handler) => { | ||
return middy(handler) | ||
.use(middyJsonBodyParser()) | ||
.use(middyCors({ origin: "*" })); | ||
}; | ||
|
||
export const formatJSONResponse = (response: unknown) => { | ||
return { | ||
statusCode: 200, | ||
body: JSON.stringify(response), | ||
}; | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,6 @@ | ||
export const handlerPath = (context: string, path: string) => { | ||
return `${context | ||
.split(process.cwd())[1] | ||
.substring(1) | ||
.replace(/\\/g, "/")}/${path}`; | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,12 @@ | ||
import { Product } from "@guria.dev/aws-js-practitioner-commons/models/Product"; | ||
import { products } from "@guria.dev/aws-js-practitioner-commons/mocks"; | ||
|
||
class ProductsService { | ||
constructor(private products: Product[]) {} | ||
|
||
public getProducts(): Product[] { | ||
return this.products; | ||
} | ||
} | ||
|
||
export default new ProductsService(products); |
Oops, something went wrong.